diff --git a/.github/workflows/block-remove-before-merge.yml b/.github/workflows/block-remove-before-merge.yml index 443eea9f7c..aedf2dfe4a 100644 --- a/.github/workflows/block-remove-before-merge.yml +++ b/.github/workflows/block-remove-before-merge.yml @@ -14,10 +14,9 @@ jobs: check-paths: name: "No remove-before-merge directories" if: github.event_name == 'pull_request' && github.base_ref == 'main' - runs-on: ubuntu-latest + runs-on: ubuntu-26.04-2core-agent-runtime steps: - uses: actions/checkout@v7 - timeout-minutes: 4 with: fetch-depth: 2 - name: Check for remove-before-merge paths in PR diff --git a/.github/workflows/java-publish-snapshot.yml b/.github/workflows/java-publish-snapshot.yml index 77d5287964..219dd43bf2 100644 --- a/.github/workflows/java-publish-snapshot.yml +++ b/.github/workflows/java-publish-snapshot.yml @@ -370,7 +370,7 @@ jobs: build-darwin-classifier: name: Build Darwin snapshot classifier needs: resolve-source - runs-on: macos-26 + runs-on: macos-26-agent-runtime permissions: contents: read outputs: diff --git a/.github/workflows/sdk-dotnet.yml b/.github/workflows/sdk-dotnet.yml index 63b689f94e..996570df9b 100644 --- a/.github/workflows/sdk-dotnet.yml +++ b/.github/workflows/sdk-dotnet.yml @@ -7,7 +7,6 @@ env: HUSKY: 0 POWERSHELL_UPDATECHECK: Off SDK_HOME: ${{ inputs.sdk-home }} - SETUP_NODE_TIMEOUT_MINUTES: 10 on: workflow_call: @@ -25,10 +24,9 @@ permissions: jobs: dotnet-linux-x64: - name: ".NET (ubuntu-latest, CAPI)" + name: ".NET (ubuntu-26.04-2core-agent-runtime, CAPI)" if: inputs.platform == 'all' || inputs.platform == 'linux-x64' - timeout-minutes: 30 - runs-on: ubuntu-latest + runs-on: ubuntu-26.04-2core-agent-runtime defaults: run: shell: bash @@ -38,7 +36,6 @@ jobs: DOTNET_TEST_RESULTS_DIRECTORY: TestResults/subprocess steps: - uses: actions/checkout@v7 - timeout-minutes: 4 with: persist-credentials: false - uses: ./.github/actions/download-sdk-runtime @@ -93,16 +90,15 @@ jobs: - if: failure() uses: actions/upload-artifact@v7 with: - name: dotnet-test-diagnostics-ubuntu-latest-capi-${{ github.run_attempt }} + name: dotnet-test-diagnostics-ubuntu-26.04-2core-agent-runtime-capi-${{ github.run_attempt }} path: ${{ inputs.sdk-home }}/dotnet/TestResults/ if-no-files-found: warn retention-days: 7 dotnet-darwin-arm64: - name: ".NET (macos-26-xlarge, default, CAPI)" + name: ".NET (macos-26-xlarge-agent-runtime, default, CAPI)" if: inputs.platform == 'all' || inputs.platform == 'darwin-arm64' - runs-on: macos-26-xlarge - timeout-minutes: 30 + runs-on: macos-26-xlarge-agent-runtime defaults: run: shell: bash @@ -112,7 +108,6 @@ jobs: DOTNET_TEST_RESULTS_DIRECTORY: TestResults/subprocess steps: - uses: actions/checkout@v7 - timeout-minutes: 4 with: persist-credentials: false - uses: ./.github/actions/download-sdk-runtime @@ -153,10 +148,19 @@ jobs: retention-days: 7 dotnet-win32-x64: - name: ".NET (windows-latest, default, CAPI)" + name: ".NET (windows-2025-4core-agent-runtime, ${{ matrix.framework }}, ${{ matrix.suite.name }}, CAPI)" if: inputs.platform == 'all' || inputs.platform == 'win32-x64' - runs-on: windows-latest - timeout-minutes: 30 + runs-on: windows-2025-4core-agent-runtime + strategy: + fail-fast: false + matrix: + framework: [net8.0, net472] + # Complementary filters include every test, including newly added classes. + suite: + - name: e2e-s + filter: FullyQualifiedName~GitHub.Copilot.Test.E2E.S + - name: remaining + filter: FullyQualifiedName!~GitHub.Copilot.Test.E2E.S defaults: run: shell: bash @@ -164,10 +168,11 @@ jobs: env: COPILOT_SDK_E2E_BACKEND: capi DOTNET_TEST_RUNTIME: win-x64 + DOTNET_TEST_FRAMEWORK: ${{ matrix.framework }} + DOTNET_TEST_FILTER: ${{ matrix.suite.filter }} DOTNET_TEST_RESULTS_DIRECTORY: TestResults/subprocess steps: - uses: actions/checkout@v7 - timeout-minutes: 4 with: persist-credentials: false - uses: ./.github/actions/download-sdk-runtime @@ -183,7 +188,7 @@ jobs: with: node-version: 22 - run: dotnet restore test/GitHub.Copilot.SDK.Test.csproj --runtime "$DOTNET_TEST_RUNTIME" -p:CopilotCliBinaryPath="$COPILOT_RUNTIME_BINARY_PATH" -p:CopilotSkipCliDownload=true - - run: dotnet build test/GitHub.Copilot.SDK.Test.csproj --no-restore --runtime "$DOTNET_TEST_RUNTIME" -p:CopilotCliBinaryPath="$COPILOT_RUNTIME_BINARY_PATH" -p:CopilotSkipCliDownload=true + - run: dotnet build test/GitHub.Copilot.SDK.Test.csproj --no-restore --framework "$DOTNET_TEST_FRAMEWORK" --runtime "$DOTNET_TEST_RUNTIME" -p:CopilotCliBinaryPath="$COPILOT_RUNTIME_BINARY_PATH" -p:CopilotSkipCliDownload=true - run: npm ci --ignore-scripts working-directory: ${{ inputs.sdk-home }}/test/harness - run: pwsh.exe -Command "Write-Host 'PowerShell ready'" @@ -203,7 +208,7 @@ jobs: - if: failure() uses: actions/upload-artifact@v7 with: - name: dotnet-test-diagnostics-windows-latest-default-capi-${{ github.run_attempt }} + name: dotnet-test-diagnostics-windows-2025-4core-agent-runtime-${{ matrix.framework }}-${{ matrix.suite.name }}-capi-${{ github.run_attempt }} path: ${{ inputs.sdk-home }}/dotnet/TestResults/ if-no-files-found: warn retention-days: 7 @@ -211,13 +216,11 @@ jobs: dotnet-musl-x64: name: ".NET (Alpine x64, CAPI)" if: inputs.platform == 'all' || inputs.platform == 'linuxmusl-x64' - runs-on: ubuntu-latest - timeout-minutes: 20 + runs-on: ubuntu-26.04-2core-agent-runtime env: COPILOT_SDK_E2E_BACKEND: capi steps: - uses: actions/checkout@v7 - timeout-minutes: 4 with: persist-credentials: false - uses: ./.github/actions/download-sdk-runtime diff --git a/.github/workflows/sdk-go.yml b/.github/workflows/sdk-go.yml index e7235998f8..b738a30972 100644 --- a/.github/workflows/sdk-go.yml +++ b/.github/workflows/sdk-go.yml @@ -7,7 +7,6 @@ env: HUSKY: 0 POWERSHELL_UPDATECHECK: Off SDK_HOME: ${{ inputs.sdk-home }} - SETUP_NODE_TIMEOUT_MINUTES: 10 on: workflow_call: @@ -30,7 +29,7 @@ jobs: strategy: fail-fast: false matrix: - os: ${{ fromJSON(inputs.platform == 'linux-x64' && '["ubuntu-latest"]' || inputs.platform == 'darwin-arm64' && '["macos-26"]' || inputs.platform == 'win32-x64' && '["windows-latest"]' || '["ubuntu-latest","macos-26","windows-latest"]') }} + os: ${{ fromJSON(inputs.platform == 'linux-x64' && '["ubuntu-26.04-2core-agent-runtime"]' || inputs.platform == 'darwin-arm64' && '["macos-26-agent-runtime"]' || inputs.platform == 'win32-x64' && '["windows-2025-4core-agent-runtime"]' || '["ubuntu-26.04-2core-agent-runtime","macos-26-agent-runtime","windows-2025-4core-agent-runtime"]') }} runs-on: ${{ matrix.os }} defaults: run: @@ -38,7 +37,6 @@ jobs: working-directory: ${{ inputs.sdk-home }}/go steps: - uses: actions/checkout@v7 - timeout-minutes: 4 with: persist-credentials: false - uses: ./.github/actions/download-sdk-runtime @@ -68,7 +66,9 @@ jobs: - if: runner.os == 'Linux' run: go test ./... working-directory: ${{ inputs.sdk-home }}/go/samples - - run: npm ci --ignore-scripts + - run: | + npm ci --ignore-scripts + npm rebuild esbuild working-directory: ${{ inputs.sdk-home }}/test/harness - if: runner.os == 'Windows' run: pwsh.exe -Command "Write-Host 'PowerShell ready'" @@ -101,11 +101,9 @@ jobs: go-musl-x64: name: "Go (Alpine x64)" if: inputs.platform == 'all' || inputs.platform == 'linuxmusl-x64' - runs-on: ubuntu-latest - timeout-minutes: 20 + runs-on: ubuntu-26.04-2core-agent-runtime steps: - uses: actions/checkout@v7 - timeout-minutes: 4 with: persist-credentials: false - uses: ./.github/actions/download-sdk-runtime @@ -125,6 +123,7 @@ jobs: apk add --no-cache build-base nodejs npm npm --prefix "$COPILOT_SDK_ROOT/nodejs" ci --ignore-scripts npm --prefix "$COPILOT_SDK_ROOT/test/harness" ci --ignore-scripts + npm --prefix "$COPILOT_SDK_ROOT/test/harness" rebuild esbuild export GITHUB_ENV=/tmp/copilot-sdk-runtime.env export COPILOT_RUNTIME_TARGET=linuxmusl-x64 diff --git a/.github/workflows/sdk-java.yml b/.github/workflows/sdk-java.yml index fbadf275ce..9c43a3a2ed 100644 --- a/.github/workflows/sdk-java.yml +++ b/.github/workflows/sdk-java.yml @@ -6,7 +6,6 @@ env: HUSKY: 0 POWERSHELL_UPDATECHECK: Off SDK_HOME: ${{ inputs.sdk-home }} - SETUP_NODE_TIMEOUT_MINUTES: 10 on: workflow_call: @@ -29,12 +28,12 @@ jobs: strategy: fail-fast: false matrix: - os: ${{ fromJSON(inputs.platform == 'linux-x64' && '["ubuntu-latest"]' || inputs.platform == 'darwin-arm64' && '["macos-26"]' || inputs.platform == 'win32-x64' && '["windows-latest"]' || '["ubuntu-latest","macos-26","windows-latest"]') }} + os: ${{ fromJSON(inputs.platform == 'linux-x64' && '["ubuntu-26.04-2core-agent-runtime"]' || inputs.platform == 'darwin-arm64' && '["macos-26-agent-runtime"]' || inputs.platform == 'win32-x64' && '["windows-2025-4core-agent-runtime"]' || '["ubuntu-26.04-2core-agent-runtime","macos-26-agent-runtime","windows-2025-4core-agent-runtime"]') }} test-jdk: ["25", "17"] exclude: - - os: macos-26 + - os: macos-26-agent-runtime test-jdk: "17" - - os: windows-latest + - os: windows-2025-4core-agent-runtime test-jdk: "17" runs-on: ${{ matrix.os }} defaults: @@ -43,7 +42,6 @@ jobs: working-directory: ${{ inputs.sdk-home }}/java steps: - uses: actions/checkout@v7 - timeout-minutes: 4 with: persist-credentials: false - uses: ./.github/actions/download-sdk-runtime @@ -125,11 +123,9 @@ jobs: java-musl-x64: name: "Java (JDK 25, linuxmusl-x64)" if: inputs.platform == 'all' || inputs.platform == 'linuxmusl-x64' - runs-on: ubuntu-latest - timeout-minutes: 30 + runs-on: ubuntu-26.04-2core-agent-runtime steps: - uses: actions/checkout@v7 - timeout-minutes: 4 with: persist-credentials: false - uses: ./.github/actions/download-sdk-runtime @@ -187,10 +183,8 @@ jobs: name: "Java (JDK 25, linuxmusl-arm64)" if: inputs.platform == 'all' || inputs.platform == 'linuxmusl-arm64' runs-on: ubuntu-24.04-arm - timeout-minutes: 30 steps: - uses: actions/checkout@v7 - timeout-minutes: 4 with: persist-credentials: false - uses: ./.github/actions/download-sdk-runtime diff --git a/.github/workflows/sdk-nodejs.yml b/.github/workflows/sdk-nodejs.yml index 9fbbf97e76..6dcd12c354 100644 --- a/.github/workflows/sdk-nodejs.yml +++ b/.github/workflows/sdk-nodejs.yml @@ -1,13 +1,12 @@ # Invoked by sdk.yml to run Node.js SDK builds, tests, packaging checks, # documentation validation, full subprocess coverage, and focused in-process -# smoke coverage on standard platforms and x64 musl. +# smoke coverage on standard platforms and x64 musl, plus full in-process E2Es on Linux. name: "SDK Node.js" env: HUSKY: 0 POWERSHELL_UPDATECHECK: Off SDK_HOME: ${{ inputs.sdk-home }} - SETUP_NODE_TIMEOUT_MINUTES: 10 on: workflow_call: @@ -35,7 +34,7 @@ jobs: strategy: fail-fast: false matrix: - os: ${{ fromJSON(inputs.platform == 'linux-x64' && '["ubuntu-latest"]' || inputs.platform == 'darwin-arm64' && '["macos-26"]' || inputs.platform == 'win32-x64' && '["windows-latest"]' || '["ubuntu-latest","macos-26","windows-latest"]') }} + os: ${{ fromJSON(inputs.platform == 'linux-x64' && '["ubuntu-26.04-4core-agent-runtime"]' || inputs.platform == 'darwin-arm64' && '["macos-26-agent-runtime"]' || inputs.platform == 'win32-x64' && '["windows-2025-4core-agent-runtime"]' || '["ubuntu-26.04-4core-agent-runtime","macos-26-agent-runtime","windows-2025-4core-agent-runtime"]') }} runs-on: ${{ matrix.os }} defaults: run: @@ -43,7 +42,6 @@ jobs: working-directory: ${{ inputs.sdk-home }}/nodejs steps: - uses: actions/checkout@v7 - timeout-minutes: 4 with: persist-credentials: false - uses: ./.github/actions/download-sdk-runtime @@ -93,18 +91,19 @@ jobs: id: subprocess env: COPILOT_HMAC_KEY: ${{ secrets.COPILOT_DEVELOPER_CLI_INTEGRATION_HMAC_KEY }} + VITEST_MAX_WORKERS: ${{ runner.os == 'Linux' && '4' || '' }} run: | - if [ "$GITHUB_EVENT_NAME" = "merge_group" ] || [ "$GITHUB_EVENT_NAME" = "pull_request" ]; then + if [ "$GITHUB_EVENT_NAME" = "merge_group" ] || [ "$GITHUB_EVENT_NAME" = "pull_request" ] || [ "$GITHUB_EVENT_NAME" = "push" ]; then npm test -- --reporter=default --reporter=json --outputFile="$RUNNER_TEMP/sdk-nodejs-results.json" else npm test fi - name: Upload Flake Finder Node.js test results - if: always() && (github.event_name == 'merge_group' || github.event_name == 'pull_request') && runner.os == 'Linux' + if: always() && (github.event_name == 'merge_group' || github.event_name == 'pull_request' || github.event_name == 'push') continue-on-error: true uses: actions/upload-artifact@v7 with: - name: suspected-flakes-sdk-nodejs-${{ github.run_attempt }}-linux + name: suspected-flakes-sdk-nodejs-${{ github.run_attempt }}-${{ runner.os == 'macOS' && 'macos' || runner.os == 'Windows' && 'windows' || 'linux' }} path: ${{ runner.temp }}/sdk-nodejs-results.json if-no-files-found: warn retention-days: 7 @@ -122,13 +121,48 @@ jobs: npm run extract npm run validate:ts + # Keep full in-process coverage separate from the required CAPI result. + nodejs-inprocess: + name: "Node.js (ubuntu-26.04-4core-agent-runtime, in-process, CAPI)" + if: github.event_name != 'merge_group' && (inputs.platform == 'all' || inputs.platform == 'linux-x64') + runs-on: ubuntu-26.04-4core-agent-runtime + defaults: + run: + shell: bash + working-directory: ${{ inputs.sdk-home }}/nodejs + env: + COPILOT_SDK_DEFAULT_CONNECTION: inprocess + VITEST_MAX_WORKERS: 4 + steps: + - uses: actions/checkout@v7 + with: + persist-credentials: false + - uses: ./.github/actions/download-sdk-runtime + with: + artifact-name: executable-sdk-Linux-X64-gnu + path: ${{ github.workspace }} + - run: node "$SDK_HOME/scripts/ci/runtime-artifact.mjs" restore && node "$SDK_HOME/scripts/ci/runtime-artifact.mjs" prepare + working-directory: . + - uses: actions/setup-node@v6 + with: + node-version: 22 + cache: npm + cache-dependency-path: ${{ inputs.sdk-home }}/nodejs/package-lock.json + - run: npm ci --ignore-scripts + - run: npm run build + - run: npm ci --ignore-scripts + working-directory: ${{ inputs.sdk-home }}/test/harness + - name: Test in-process E2Es + env: + COPILOT_HMAC_KEY: ${{ secrets.COPILOT_DEVELOPER_CLI_INTEGRATION_HMAC_KEY }} + run: npm test -- test/e2e + # Keep each BYOK backend on its own runner; shared runtime E2E coverage belongs # in TypeScript rather than repeating these sweeps across SDK languages. nodejs-backends: - name: "Node.js (ubuntu-latest, ${{ matrix.backend }})" + name: "Node.js (ubuntu-26.04-2core-agent-runtime, ${{ matrix.backend }})" if: github.event_name != 'merge_group' && (inputs.platform == 'all' || inputs.platform == 'linux-x64') - runs-on: ubuntu-latest - timeout-minutes: 30 + runs-on: ubuntu-26.04-2core-agent-runtime strategy: fail-fast: false matrix: @@ -141,7 +175,6 @@ jobs: COPILOT_SDK_E2E_BACKEND: ${{ matrix.backend }} steps: - uses: actions/checkout@v7 - timeout-minutes: 4 with: persist-credentials: false - uses: ./.github/actions/download-sdk-runtime @@ -167,10 +200,9 @@ jobs: nodejs-musl-x64: name: "Node.js (Alpine x64, CAPI)" if: inputs.platform == 'all' || inputs.platform == 'linuxmusl-x64' - runs-on: ubuntu-latest + runs-on: ubuntu-26.04-2core-agent-runtime steps: - uses: actions/checkout@v7 - timeout-minutes: 4 with: persist-credentials: false - uses: ./.github/actions/download-sdk-runtime @@ -187,6 +219,7 @@ jobs: sdk-root: /workspace/${{ inputs.sdk-home }} workdir: /workspace/${{ inputs.sdk-home }}/nodejs command: | + apk add --no-cache git-daemon npm ci --ignore-scripts (cd "$COPILOT_SDK_ROOT/scripts/codegen" && npm ci --ignore-scripts) npm run build diff --git a/.github/workflows/sdk-python.yml b/.github/workflows/sdk-python.yml index 2abeca3d8a..c04c8a6805 100644 --- a/.github/workflows/sdk-python.yml +++ b/.github/workflows/sdk-python.yml @@ -7,7 +7,6 @@ env: HUSKY: 0 POWERSHELL_UPDATECHECK: Off SDK_HOME: ${{ inputs.sdk-home }} - SETUP_NODE_TIMEOUT_MINUTES: 10 on: workflow_call: @@ -30,9 +29,8 @@ jobs: strategy: fail-fast: false matrix: - os: ${{ fromJSON(inputs.platform == 'linux-x64' && '["ubuntu-latest"]' || inputs.platform == 'darwin-arm64' && '["macos-26"]' || inputs.platform == 'win32-x64' && '["windows-latest"]' || '["ubuntu-latest","macos-26","windows-latest"]') }} + os: ${{ fromJSON(inputs.platform == 'linux-x64' && '["ubuntu-26.04-2core-agent-runtime"]' || inputs.platform == 'darwin-arm64' && '["macos-26-agent-runtime"]' || inputs.platform == 'win32-x64' && '["windows-2025-4core-agent-runtime"]' || '["ubuntu-26.04-2core-agent-runtime","macos-26-agent-runtime","windows-2025-4core-agent-runtime"]') }} runs-on: ${{ matrix.os }} - timeout-minutes: 20 defaults: run: shell: bash @@ -41,7 +39,6 @@ jobs: PYTHONUTF8: 1 steps: - uses: actions/checkout@v7 - timeout-minutes: 4 with: persist-credentials: false - uses: ./.github/actions/download-sdk-runtime @@ -104,11 +101,9 @@ jobs: python-musl-x64: name: "Python (Alpine x64)" if: inputs.platform == 'all' || inputs.platform == 'linuxmusl-x64' - runs-on: ubuntu-latest - timeout-minutes: 20 + runs-on: ubuntu-26.04-2core-agent-runtime steps: - uses: actions/checkout@v7 - timeout-minutes: 4 with: persist-credentials: false - uses: ./.github/actions/download-sdk-runtime diff --git a/.github/workflows/sdk-runtime-artifact.yml b/.github/workflows/sdk-runtime-artifact.yml index 0481e945ae..10baa49617 100644 --- a/.github/workflows/sdk-runtime-artifact.yml +++ b/.github/workflows/sdk-runtime-artifact.yml @@ -11,7 +11,6 @@ env: GIT_HTTP_LOW_SPEED_LIMIT: 1000 GIT_HTTP_LOW_SPEED_TIME: 60 HUSKY: 0 - SETUP_NODE_TIMEOUT_MINUTES: 10 on: workflow_call: @@ -47,10 +46,6 @@ on: required: false default: "" type: string - timeout-minutes: - required: false - default: 90 - type: number permissions: contents: read @@ -63,7 +58,6 @@ jobs: cache-relay: if: inputs.runtime-source == 'checkout' runs-on: ubuntu-slim - timeout-minutes: 15 permissions: actions: read contents: read @@ -71,7 +65,7 @@ jobs: cache-hit: ${{ steps.cache.outputs.cache-hit }} steps: - uses: actions/checkout@v7 - timeout-minutes: 4 + timeout-minutes: 60 with: persist-credentials: false @@ -143,8 +137,7 @@ jobs: !cancelled() && (inputs.runtime-source == 'published' || (needs.cache-relay.result == 'success' && needs.cache-relay.outputs.cache-hit != 'true')) - runs-on: ${{ inputs.runtime-source == 'checkout' && inputs.os || 'ubuntu-latest' }} - timeout-minutes: ${{ inputs.timeout-minutes }} + runs-on: ${{ inputs.runtime-source == 'checkout' && inputs.os || 'ubuntu-26.04-2core-agent-runtime' }} permissions: actions: read contents: read @@ -152,7 +145,7 @@ jobs: packages: read steps: - uses: actions/checkout@v7 - timeout-minutes: 4 + timeout-minutes: 60 with: persist-credentials: false @@ -197,7 +190,7 @@ jobs: - if: inputs.runtime-source == 'checkout' && steps.cache.outputs.cache-hit != 'true' uses: ./.github/actions/setup-node - timeout-minutes: ${{ fromJSON(env.SETUP_NODE_TIMEOUT_MINUTES) }} + timeout-minutes: 60 - name: Capture verified addon source identity if: inputs.runtime-source == 'checkout' && steps.cache.outputs.cache-hit != 'true' diff --git a/.github/workflows/sdk-rust.yml b/.github/workflows/sdk-rust.yml index abe13e28bb..86c16d671b 100644 --- a/.github/workflows/sdk-rust.yml +++ b/.github/workflows/sdk-rust.yml @@ -7,7 +7,6 @@ env: HUSKY: 0 POWERSHELL_UPDATECHECK: Off SDK_HOME: ${{ inputs.sdk-home }} - SETUP_NODE_TIMEOUT_MINUTES: 10 on: workflow_call: @@ -27,11 +26,10 @@ jobs: rust: name: "Rust (${{ matrix.os }})" if: contains(fromJSON('["all","linux-x64","darwin-arm64","win32-x64"]'), inputs.platform) - timeout-minutes: 60 strategy: fail-fast: false matrix: - os: ${{ fromJSON(inputs.platform == 'linux-x64' && '["ubuntu-latest"]' || inputs.platform == 'darwin-arm64' && '["macos-26-xlarge"]' || inputs.platform == 'win32-x64' && '["windows-latest"]' || '["ubuntu-latest","macos-26-xlarge","windows-latest"]') }} + os: ${{ fromJSON(inputs.platform == 'linux-x64' && '["ubuntu-26.04-2core-agent-runtime"]' || inputs.platform == 'darwin-arm64' && '["macos-26-xlarge-agent-runtime"]' || inputs.platform == 'win32-x64' && '["windows-2025-4core-agent-runtime"]' || '["ubuntu-26.04-2core-agent-runtime","macos-26-xlarge-agent-runtime","windows-2025-4core-agent-runtime"]') }} runs-on: ${{ matrix.os }} defaults: run: @@ -40,9 +38,11 @@ jobs: env: CARGO_TERM_COLOR: always RUST_BACKTRACE: 1 + # Preserve source-line backtraces without full variable/type debug information. + CARGO_PROFILE_DEV_DEBUG: line-tables-only + CARGO_PROFILE_TEST_DEBUG: line-tables-only steps: - uses: actions/checkout@v7 - timeout-minutes: 4 with: persist-credentials: false - uses: ./.github/actions/download-sdk-runtime @@ -127,11 +127,9 @@ jobs: rust-musl-x64: name: "Rust (Alpine x64)" if: inputs.platform == 'all' || inputs.platform == 'linuxmusl-x64' - runs-on: ubuntu-latest - timeout-minutes: 60 + runs-on: ubuntu-26.04-2core-agent-runtime steps: - uses: actions/checkout@v7 - timeout-minutes: 4 with: persist-credentials: false - uses: ./.github/actions/download-sdk-runtime diff --git a/.github/workflows/sdk.yml b/.github/workflows/sdk.yml index 56c165db89..8a77f8729d 100644 --- a/.github/workflows/sdk.yml +++ b/.github/workflows/sdk.yml @@ -9,7 +9,6 @@ env: COPILOT_BAZEL_ANG_PR_SAMPLED_MODE: ${{ vars.COPILOT_BAZEL_ANG_PR_SAMPLED_MODE }} HUSKY: 0 POWERSHELL_UPDATECHECK: Off - SETUP_NODE_TIMEOUT_MINUTES: 10 on: pull_request: @@ -42,7 +41,6 @@ jobs: id: checkout if: github.event_name == 'pull_request' continue-on-error: true - timeout-minutes: 4 with: # Include the synthetic merge commit's base parent without fetching # repository history. @@ -171,15 +169,20 @@ jobs: name: "Detect SDK layout" needs: detect-sdk-changes if: ${{ !cancelled() && (needs.detect-sdk-changes.result != 'success' || needs.detect-sdk-changes.outputs.has_sdk_changes == 'true') }} - runs-on: ubuntu-latest + runs-on: ubuntu-26.04-2core-agent-runtime outputs: runtime-source: ${{ steps.detect.outputs.runtime-source }} sdk-home: ${{ steps.detect.outputs.sdk-home }} steps: - uses: actions/checkout@v7 - timeout-minutes: 4 with: persist-credentials: false + sparse-checkout: | + /src/sdk/package.json + /script/sea-build.ts + /package.json + /nodejs/package.json + sparse-checkout-cone-mode: false - id: detect run: | if [[ -f src/sdk/package.json && -f script/sea-build.ts ]]; then @@ -202,7 +205,6 @@ jobs: needs: detect-layout if: github.event_name != 'merge_group' && needs.detect-layout.outputs.runtime-source == 'checkout' runs-on: ubuntu-latest-xl - timeout-minutes: 60 concurrency: group: windows-x64-addons-release-windows-e2e-no-icf-${{ github.sha }} queue: max @@ -212,7 +214,6 @@ jobs: packages: read steps: - uses: actions/checkout@v7 - timeout-minutes: 4 with: persist-credentials: false @@ -242,7 +243,7 @@ jobs: - uses: ./.github/actions/setup-node if: steps.cache.outputs.cache-hit != 'true' - timeout-minutes: ${{ fromJSON(env.SETUP_NODE_TIMEOUT_MINUTES) }} + timeout-minutes: 60 - uses: ./.github/actions/setup-bazel if: steps.cache.outputs.cache-hit != 'true' with: @@ -347,12 +348,11 @@ jobs: with: runtime-source: ${{ needs.detect-layout.outputs.runtime-source }} sdk-home: ${{ needs.detect-layout.outputs.sdk-home }} - os: ubuntu-latest-xl + os: ubuntu-26.04-16core-agent-runtime target: linux-x64 artifact: Linux-X64-gnu bazel-config: e2e-no-lto shared-cli-build-compatible: true - timeout-minutes: 60 secrets: inherit build-runtime-linuxmusl-x64: @@ -368,11 +368,10 @@ jobs: with: runtime-source: ${{ needs.detect-layout.outputs.runtime-source }} sdk-home: ${{ needs.detect-layout.outputs.sdk-home }} - os: ubuntu-latest-xl + os: ubuntu-26.04-16core-agent-runtime target: linuxmusl-x64 artifact: Linux-X64-musl bazel-config: e2e-no-lto - timeout-minutes: 60 secrets: inherit build-runtime-linuxmusl-arm64: @@ -388,11 +387,10 @@ jobs: with: runtime-source: ${{ needs.detect-layout.outputs.runtime-source }} sdk-home: ${{ needs.detect-layout.outputs.sdk-home }} - os: ubuntu-latest-xl + os: ubuntu-26.04-16core-agent-runtime target: linuxmusl-arm64 artifact: Linux-ARM64-musl bazel-config: e2e-no-lto - timeout-minutes: 60 secrets: inherit build-runtime-darwin-arm64: @@ -408,13 +406,12 @@ jobs: with: runtime-source: ${{ needs.detect-layout.outputs.runtime-source }} sdk-home: ${{ needs.detect-layout.outputs.sdk-home }} - os: macos-26-xlarge + os: macos-26-xlarge-agent-runtime target: darwin-arm64 artifact: macOS-ARM64 bazel-config: e2e-no-lto use-bazel-addons-on-darwin: true shared-cli-build-compatible: true - timeout-minutes: 60 secrets: inherit build-runtime-win32-x64: @@ -430,13 +427,12 @@ jobs: with: runtime-source: ${{ needs.detect-layout.outputs.runtime-source }} sdk-home: ${{ needs.detect-layout.outputs.sdk-home }} - os: windows-2025-16core + os: windows-2025-16core-agent-runtime target: win32-x64 artifact: Windows-X64 bazel-config: windows-e2e-no-icf shared-cli-build-compatible: true prebuilt-addons-artifact: ${{ needs.detect-layout.outputs.runtime-source == 'checkout' && 'sdk-bazel-cross-addons-win32-x64-release' || '' }} - timeout-minutes: 60 secrets: inherit sdk-linux-x64: @@ -522,19 +518,40 @@ jobs: check-freshness: name: "Check schema and SDK freshness" needs: detect-layout - runs-on: ubuntu-latest + runs-on: ubuntu-26.04-2core-agent-runtime permissions: contents: read id-token: write steps: - uses: actions/checkout@v7 - timeout-minutes: 10 with: persist-credentials: false - fetch-depth: 0 + fetch-depth: ${{ github.event_name == 'pull_request' && 2 || 1 }} + - name: Fetch generation comparison baseline + if: needs.detect-layout.outputs.runtime-source == 'checkout' && (github.event_name == 'merge_group' || github.event_name == 'push') + env: + BASELINE_SHA: ${{ github.event.merge_group.base_sha || github.event.before }} + CHECKOUT_TOKEN: ${{ github.token }} + run: | + set -euo pipefail + if [[ "$GITHUB_EVENT_NAME" == "push" && "$BASELINE_SHA" =~ ^0+$ ]]; then + echo "New branch push has no generation baseline; checking all projections." + exit 0 + fi + if [[ ! "$BASELINE_SHA" =~ ^[0-9a-fA-F]{40}$ ]]; then + echo "::error::Missing or invalid SDK generation baseline SHA." + exit 1 + fi + if git cat-file -e "$BASELINE_SHA^{commit}" 2>/dev/null; then + exit 0 + fi + auth_header="$(printf 'x-access-token:%s' "$CHECKOUT_TOKEN" | base64 | tr -d '\n')" + GIT_CONFIG_COUNT=1 \ + GIT_CONFIG_KEY_0=http.https://github.com/.extraheader \ + GIT_CONFIG_VALUE_0="AUTHORIZATION: basic $auth_header" \ + git fetch --no-tags --depth=1 origin "$BASELINE_SHA" - if: needs.detect-layout.outputs.runtime-source == 'checkout' uses: ./.github/actions/setup-node - timeout-minutes: ${{ fromJSON(env.SETUP_NODE_TIMEOUT_MINUTES) }} with: install-deps: "false" - if: needs.detect-layout.outputs.runtime-source == 'checkout' diff --git a/BUILD.bazel b/BUILD.bazel index 4fa953fbf5..b9465c7090 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -1,3 +1,8 @@ +exports_files( + ["test/harness/sdk-diagnostics.mjs"], + visibility = ["//src/runtime-bin:__pkg__"], +) + SHARED_CODEGEN_INPUTS = [ "nodejs/scripts/releaseArtifacts.ts", "nodejs/src/cliVersion.ts", diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 74519107b4..527e72ee70 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -356,18 +356,17 @@ For TypeScript SDK E2Es, use the existing `nodejs/test/e2e/harness/sdkTestContext.ts` fixture. In the runtime repository, also follow the `e2e-test-author` skill's SDK section. -All six SDKs run full subprocess coverage followed by a short in-process -smoke step on the same runner on Linux/glibc x64, macOS ARM64, Windows x64, -and Linux/musl x64. Smoke still runs if the subprocess tests fail, and either -failure fails the job. Java uses JDK 25 on all four platforms, plus a -Linux/glibc JDK 17 compatibility job using precompiled classes. +All SDKs run full subprocess coverage and in-process smoke on Linux/glibc +x64, macOS ARM64, Windows x64, and Linux/musl x64. Windows .NET uses complementary +jobs per framework. Smoke also runs after subprocess failures; either failure +fails the job. Java uses JDK 25 on these platforms, +plus a Linux/glibc JDK 17 compatibility job using precompiled classes. Merge groups retain the reduced Linux TypeScript CAPI subprocess coverage. -The `sdk-typescript` required rollup checks only the Linux CAPI job, including -its build, packaging, and applicable static checks. Other platforms, BYOK -backends, and languages keep their existing scheduling and failure reporting; -they do not gate this rollup. The full `SDK` aggregate still requires all -scheduled coverage to succeed. +`sdk-typescript` gates only the Linux CAPI subprocess job's build, packaging, +tests, and static checks. Full TypeScript in-process E2Es run on Linux/glibc x64 +outside merge groups. They, other platforms, BYOK backends, and languages do not +gate this rollup; the full `SDK` aggregate requires all scheduled coverage to succeed. The three BYOK backend sweeps run in separate Linux TypeScript jobs, alongside the normal CAPI job; they do not repeat unit tests, packaging, or static diff --git a/README.md b/README.md index 1ae92a18a9..676cf4ea80 100644 --- a/README.md +++ b/README.md @@ -57,6 +57,21 @@ Your Application The SDK manages the CLI process lifecycle automatically. You can also connect to an external CLI server—see the [Getting Started Guide](./docs/getting-started.md#connecting-to-an-external-cli-server) for details on running the CLI in server mode. +### Portable elicitation schemas + +For form elicitation callbacks, the portable schema contract is an object form +with modeled `properties` and optional `required` fields (`type: "object"` on +the wire; an SDK may represent the object type implicitly). Applications that +support multiple SDK languages must use the modeled property definitions and +must not depend on additional schema metadata (such as a top-level `title`) or +on the insertion order of `properties`. + +Node.js and Rust pass through the wire schema, including additional metadata +and property order. Python, Go, .NET, and Java reconstruct the callback schema +from the modeled fields and do not offer that same preservation guarantee. +Treat wire-schema preservation as a language-specific capability, not a +cross-language requirement. + ### Agent Finder Skill installation APIs Runtimes that grant `skill-confirmed-installation` expose diff --git a/docs/features/custom-agents.md b/docs/features/custom-agents.md index 9e2f597688..965582a01b 100644 --- a/docs/features/custom-agents.md +++ b/docs/features/custom-agents.md @@ -25,6 +25,8 @@ flowchart TD ## Defining custom agents +To discover Markdown definitions from versioned packages or vendored folders, use `agentDirectories` or [repository customization locations](./customization-locations.md). + Pass `customAgents` when creating a session. Each agent needs at minimum a `name` and `prompt`.
diff --git a/docs/features/customization-locations.md b/docs/features/customization-locations.md new file mode 100644 index 0000000000..703c5d5066 --- /dev/null +++ b/docs/features/customization-locations.md @@ -0,0 +1,55 @@ + + +# Customization discovery locations + +GitHub Copilot can discover versioned skills, agents, instructions, and file hooks directly from package installations, submodules, and vendored directories. You don't need to copy them into conventional directories. + +## Repository settings + +Declare checkout-relative directories in `.github/copilot/settings.json`: + +```json +{ + "skillDirectories": ["node_modules/@example/team/skills"], + "agentDirectories": ["vendor/team/agents"], + "instructionDirectories": ["vendor/team/instructions"], + "hookDirectories": ["vendor/team/hooks"] +} +``` + +Each list adds discovery locations for its own customization type. Conventional locations continue to work. A key in `.github/copilot/settings.local.json` replaces only that repository list; an empty list removes its repository contribution. Omitted keys inherit the checked-in list. Repository lists don't replace SDK inputs or existing user skill directories. + +Repository paths must be nonempty, literal directories within the declaring checkout. Absolute paths, home or environment-variable expansion, globs, and escapes outside the checkout are rejected. Links and junctions are allowed only when their resolved roots and discovered descendants remain inside the checkout. A missing directory is reported and can be discovered after installation and an explicit customization reload. Copilot doesn't install packages or watch these directories. + +File content from repository-defined discovery locations is read through a validated, opened file handle. Retargeting a link outside the checkout between discovery and invocation doesn't authorize reading its new target. + +Repository locations require trust covering the declaring checkout root and remain subject to each customization type's existing discovery switches, disabled entries, and managed policy. Persisted trust for only a subdirectory doesn't authorize its parent checkout's settings. Listing a location doesn't enable a disabled customization type or grant filesystem access. + +In the CLI, remembered folder trust refreshes repository customizations in the current session, even if model catalog initialization failed. `/skills reload` also rechecks persisted trust and repository discovery settings. + +## SDK inputs + +Session creation, resume, and option updates accept the same four lists. TypeScript and Java use `skillDirectories`, `agentDirectories`, `instructionDirectories`, and `hookDirectories`. Python and Rust use snake case; Go and .NET use Pascal case. + +SDK directories can be outside the checkout. Leading and trailing whitespace is trimmed, and blank entries are ignored. Relative SDK paths resolve once against the request's effective working directory when supplied. Changing the working directory doesn't move retained SDK roots. Supplying a new relative list uses the new effective working directory. An empty list clears only the explicit SDK contribution; omission or `null` leaves it unchanged during an update. + +If adopting a `hookDirectories` replacement fails or is canceled before the new hook service takes ownership, the previous directory option and live hook service remain in place. Overlapping failed updates restore the retained value rather than another failed update's directories. A failed update doesn't roll back a newer accepted update. + +Explicit agent and file-hook directories don't require ambient configuration discovery. `enableFileHooks` controls repository-origin hooks, including conventional `.github/hooks`, but not explicit SDK hook directories or callback `hooks`. Managed restrictions and hook disables still apply. + +Directory inputs are discovery configuration, not general filesystem grants. They don't widen `additionalDirectories`. Existing file-hook sandbox rules remain in effect. + +Loading active local skills adds read-only grants for their contributing SDK skill directories under the runtime's skill-resource policy. Empty, ignored, and disabled skill sources don't add those grants. Repository-defined skill directories don't add them. + +## File formats and precedence + +| Type | Discovered content | +| --- | --- | +| Skills | A direct `SKILL.md` bundle or recursively discovered topmost bundles | +| Agents | Existing Markdown agent definitions | +| Repository instructions | Recursive `*.instructions.md` files, with existing frontmatter and `applyTo` matching | +| Hooks | Existing hook JSON configurations | + +Repository instruction directories don't import package `AGENTS.md` hierarchies or `.claude/rules`. Existing SDK instruction-directory formats remain supported. Each subsystem preserves its existing source precedence. Overlapping hook file references are deduplicated; distinct authored actions aren't collapsed because their commands happen to match. + +File-backed command hooks receive `COPILOT_HOOK_DIR`, the directory containing their configuration file. Use it to locate adjacent scripts and resources. Hook working-directory semantics don't change: they remain anchored to the consuming workspace. `COPILOT_HOOK_DIR` is an execution environment variable, not JSON template expansion; `COPILOT_PLUGIN_ROOT` retains its separate plugin-root behavior. Inline hooks and SDK callbacks don't gain a fabricated configuration directory. diff --git a/docs/features/hooks.md b/docs/features/hooks.md index a3cfc0f503..b0cd89d6e9 100644 --- a/docs/features/hooks.md +++ b/docs/features/hooks.md @@ -35,6 +35,55 @@ All hooks are **optional**—register only the ones you need. Returning `null` ( ## Registering hooks +Callback `hooks` remain separate from file-backed `hookDirectories`. For packaged hook configurations and adjacent resources, see [customization discovery locations](./customization-locations.md). + +### Agent-scoped declarative hooks + +Custom agents can declare a `hooks` event map in `.agent.md` frontmatter or in +the SDK's custom-agent configuration. These are declarative command or HTTP +actions, not SDK callback functions. They run only while that agent is selected, +in addition to applicable session, user, repository, plugin, and managed-policy +hooks. Changing or clearing the selected agent changes the scoped hooks for the +next invocation without changing other sessions or concurrent subagents. + +```yaml +--- +name: formatter +description: Formats changed files +hooks: + PostToolUse: + - type: command + command: ./scripts/format-changed-files.sh + windows: ./scripts/format-changed-files.ps1 +--- +Format the files you change. +``` + +Both runtime event names such as `postToolUse` and supported VS Code aliases +such as `PostToolUse` are accepted. `command` supplies the fallback shell command; +`windows`, `linux`, and `osx` override it on the corresponding platform. Hook +commands are relative to the executing session's repository root, unless they +set an explicit working directory. Hook payloads and decisions follow the +runtime's existing hook contracts. + +When a local agent runs as a subagent, its scoped `Stop` (`agentStop`) actions +run at `SubagentStop` instead of ordinary turn stop. An explicit `SubagentStop` +action on that agent still observes its own children. Stop callbacks receive +`stop_hook_active: true` after a prior block. After eight consecutive scoped +Stop continuations, the runtime ignores further scoped blocks and accepts the +child's final response unless a parent lifecycle hook still blocks completion. + +Repository and inherited-agent hooks require workspace admission. Agent +discovery or selection does not itself grant trust. Existing hook-disable +settings, enterprise restrictions, sandbox permissions, and timeouts also apply +to scoped actions. SDK-provided agents cannot claim managed-plugin privileges. +Remote or organization-hosted agent definitions do not execute scoped hooks, +and scoped Stop dispatch is supported for local runtime subagents, not external +AHP participants. + +Unsupported event names are reported before event selection. Action preparation +errors affect only their declared event; shared and managed-policy hooks still run. + Pass a `hooks` object when you create (or resume) a session. Every example below follows this pattern.
diff --git a/docs/features/mcp.md b/docs/features/mcp.md index 4be11f2452..1c93c82710 100644 --- a/docs/features/mcp.md +++ b/docs/features/mcp.md @@ -57,7 +57,7 @@ Inventory retries concurrent graph replacement a bounded number of times and returns an error if it cannot obtain a consistent snapshot. It never omits `live` to hide a graph race. -An explicit `startServer` or `restartServer` resolves and initializes only the +A name-only `startServer` or `restartServer` resolves and initializes only the requested server after a cold session resume without first calling `list`. This includes user and plugin configurations and the built-in GitHub server configured through `githubMcpToolConfig`. It does not start other configured diff --git a/docs/features/plugin-directories.md b/docs/features/plugin-directories.md index 456ef88e7f..393fc4c048 100644 --- a/docs/features/plugin-directories.md +++ b/docs/features/plugin-directories.md @@ -422,6 +422,63 @@ for (const plugin of plugins.plugins) { Plugins loaded via `--plugin-dir` appear in this list with their cache path set to the directory you provided. Marketplace installs are tagged with their registry source. +## Retrying plugins required by managed settings + +When managed settings require plugins through `enabledPlugins`, the runtime installs or updates them before chat messages. If that fails, the session emits a `session.warning` with `warningType: "managed_plugins"` and waits an hour, or until the managed settings change, before trying again. The experimental `session.rpc.plugins.retryManaged()` tries again right away, for example from a Retry action next to that warning. + +Call it with no arguments to retry every plugin the managed settings require, or pass `plugin@marketplace` specs in `plugins` to retry only those. The result has one entry per plugin with a `status`: `installed`, `updated`, `already_present`, `failed`, `deferred`, or `not_required`. `failed` and `deferred` entries include an `error`. The session also emits the usual `session.info` progress and `session.warning` failure events. + +
+Node.js / TypeScript + + +```typescript +import { CopilotClient } from "@github/copilot-sdk"; + +async function main() { + const client = new CopilotClient(); + await client.start(); + const session = await client.createSession({ + onPermissionRequest: async () => ({ kind: "approve-once" }), + }); + + // Retry every plugin the managed settings require, for example from a Retry action. + const result = await session.rpc.plugins.retryManaged(); + for (const entry of result.plugins) { + console.log(`${entry.spec}: ${entry.status}${entry.error ? ` (${entry.error})` : ""}`); + } + + // Or retry only the required plugins that are still missing. + const { plugins } = await session.rpc.plugins.list(); + const missing = plugins + .filter((plugin) => plugin.managedDesiredEnabled === true && plugin.installed === false) + .map((plugin) => `${plugin.name}@${plugin.marketplace}`); + await session.rpc.plugins.retryManaged({ plugins: missing }); +} + +main(); +``` + + +```typescript +// Retry every plugin the managed settings require, for example from a Retry action. +const result = await session.rpc.plugins.retryManaged(); +for (const entry of result.plugins) { + console.log(`${entry.spec}: ${entry.status}${entry.error ? ` (${entry.error})` : ""}`); +} + +// Or retry only the required plugins that are still missing. +const { plugins } = await session.rpc.plugins.list(); +const missing = plugins + .filter((plugin) => plugin.managedDesiredEnabled === true && plugin.installed === false) + .map((plugin) => `${plugin.name}@${plugin.marketplace}`); +await session.rpc.plugins.retryManaged({ plugins: missing }); +``` + +
+ +Runtimes that do not prepare managed plugins for the session reject the call. + ## Troubleshooting * **"no plugin.json or SKILL.md found in <dir>"** — the directory exists but doesn't qualify as a plugin. Add a `plugin.json` manifest at the root (or under `.github/`), or include a top-level `SKILL.md`. diff --git a/dotnet/README.md b/dotnet/README.md index 84f5b650f1..60634a2c27 100644 --- a/dotnet/README.md +++ b/dotnet/README.md @@ -229,11 +229,13 @@ 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. +- `EnableGitHubCacheStore` - Requests the read-only GitHub cache store (`github-cache-store` capability) on create or resume. The runtime registers the `github_sql` tool only when its `GITHUB_CACHE_STORE` feature flag is also on. Defaults to disabled in every mode. - `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`. - `SkillProvider` - Experimental session-scoped skill provider. See [Skill providers (experimental)](#skill-providers-experimental). - `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 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`. +- `InteractiveUser` - Declares that a person types this session's messages, as in an editor chat panel. Enables the runtime's user-message analysis telemetry for user-authored messages; automated messages stay excluded. Defaults to off. Ignored for cloud sessions. - `Hooks` - Hook handlers for session lifecycle events. See [Session Hooks](#session-hooks) section. - `CanvasHandler` - Handles canvas open, close, and action callbacks. The SDK awaits asynchronous callbacks before replying, including callbacks without a result, unless @@ -251,6 +253,7 @@ Resume an existing session. Returns the session with `WorkspacePath` populated i - `GitHubTokenProvider` - Replaces the session-scoped token provider when resuming. Cannot be combined with `GitHubToken`. - `SkillProvider` - Re-supplies the session-scoped skill provider when resuming. - `AskUserVariant` - Re-supplies the model-facing `ask_user` tool shape on cold resume. +- `InteractiveUser` - Re-supplies the interactive-user declaration on cold resume. It is ignored when resuming a session that is already active. - `AllowTranscriptRecovery` - Repairs a damaged transcript when true. The default is true in all modes; set false to reject recovery. `session.TranscriptRecovery` contains `PlannedBackupPath`, `InvalidLineNumbers` (including torn-tail loss), and diff --git a/dotnet/src/Client.cs b/dotnet/src/Client.cs index 7fcfbfef99..2b09e80f7e 100644 --- a/dotnet/src/Client.cs +++ b/dotnet/src/Client.cs @@ -403,29 +403,7 @@ async Task StartCoreAsync(CancellationToken ct) ffiEnvironment["COPILOT_DISABLE_KEYTAR"] = "1"; } - var ffiArgs = new List(); - if (_options.LogLevel is { } logLevel && !string.IsNullOrEmpty(logLevel.Value)) - { - ffiArgs.AddRange(["--log-level", logLevel.Value]); - } - if (!string.IsNullOrEmpty(_options.GitHubToken)) - { - ffiArgs.AddRange(["--auth-token-env", "COPILOT_SDK_AUTH_TOKEN"]); - } - var useLoggedInUser = _options.UseLoggedInUser ?? string.IsNullOrEmpty(_options.GitHubToken); - if (!useLoggedInUser) - { - ffiArgs.Add("--no-auto-login"); - } - if (_options.SessionIdleTimeoutSeconds is > 0) - { - ffiArgs.AddRange(["--session-idle-timeout", _options.SessionIdleTimeoutSeconds.Value.ToString(CultureInfo.InvariantCulture)]); - } - if (_options.EnableRemoteSessions) - { - ffiArgs.Add("--remote"); - } - + var ffiArgs = BuildInProcessRuntimeArguments(_options); var explicitCliPath = System.Environment.GetEnvironmentVariable("COPILOT_CLI_PATH"); if (string.IsNullOrEmpty(explicitCliPath)) { @@ -1284,6 +1262,7 @@ public async Task CreateSessionAsync(SessionConfig config, Cance var request = new CreateSessionRequest( config.Model, localSessionId, + config.Name, config.ClientName, config.ReasoningEffort, config.ReasoningSummary, @@ -1326,6 +1305,7 @@ public async Task CreateSessionAsync(SessionConfig config, Cance config.EnableFileHooks, config.EnableHostGitOperations, config.EnableSessionStore, + config.EnableGitHubCacheStore, config.EnableSkills, config.SkillDirectories, config.DisabledSkills, @@ -1343,10 +1323,13 @@ public async Task CreateSessionAsync(SessionConfig config, Cance Cloud: config.Cloud, RefreshCustomInstructions: config.RefreshCustomInstructions, InstructionDirectories: config.InstructionDirectories, + AgentDirectories: config.AgentDirectories, + HookDirectories: config.HookDirectories, PluginDirectories: config.PluginDirectories, DisabledMcpServers: config.DisabledMcpServers, LargeOutput: config.LargeOutput, ToolSearch: config.ToolSearch, + ImageGeneration: config.ImageGeneration, Memory: config.Memory, Canvases: config.Canvases, RequestCanvasRenderer: config.RequestCanvasRenderer, @@ -1361,6 +1344,7 @@ public async Task CreateSessionAsync(SessionConfig config, Cance ExpAssignments: config.ExpAssignments, EnableManagedSettings: config.EnableManagedSettings, EnforceManagedModelDefaults: config.EnforceManagedModelDefaults, + InteractiveUser: config.InteractiveUser, GitHubMcpToolConfig: config.GitHubMcpToolConfig, ManagedSettings: config.ManagedSettings, EnableGitHubTelemetryForwarding: _options.OnGitHubTelemetry != null ? true : null, @@ -1588,6 +1572,7 @@ public async Task ResumeSessionAsync(string sessionId, ResumeSes config.EnableFileHooks, config.EnableHostGitOperations, config.EnableSessionStore, + config.EnableGitHubCacheStore, config.EnableSkills, config.SuppressResumeEvent is true ? true : null, config.Streaming is true ? true : null, @@ -1615,10 +1600,13 @@ public async Task ResumeSessionAsync(string sessionId, ResumeSes RemoteSession: config.RemoteSession, ContinuePendingWork: config.ContinuePendingWork, InstructionDirectories: config.InstructionDirectories, + AgentDirectories: config.AgentDirectories, + HookDirectories: config.HookDirectories, PluginDirectories: config.PluginDirectories, DisabledMcpServers: config.DisabledMcpServers, LargeOutput: config.LargeOutput, ToolSearch: config.ToolSearch, + ImageGeneration: config.ImageGeneration, Memory: config.Memory, Canvases: config.Canvases, RequestCanvasRenderer: config.RequestCanvasRenderer, @@ -1634,6 +1622,7 @@ public async Task ResumeSessionAsync(string sessionId, ResumeSes ExpAssignments: config.ExpAssignments, EnableManagedSettings: config.EnableManagedSettings, EnforceManagedModelDefaults: config.EnforceManagedModelDefaults, + InteractiveUser: config.InteractiveUser, GitHubMcpToolConfig: config.GitHubMcpToolConfig, ManagedSettings: config.ManagedSettings, EnableGitHubTelemetryForwarding: _options.OnGitHubTelemetry != null ? true : null, @@ -2579,6 +2568,40 @@ private static void ApplyTelemetryEnvironment(IDictionary envir return GetBundledNativePath(OperatingSystem.IsWindows() ? "copilot.exe" : "copilot", out searchedPath); } + private static List BuildInProcessRuntimeArguments(CopilotClientOptions options) + { + var args = new List(); + if (options.LogLevel is { } logLevel && !string.IsNullOrEmpty(logLevel.Value)) + { + args.AddRange(["--log-level", logLevel.Value]); + } + if (!string.IsNullOrEmpty(options.GitHubToken)) + { + args.AddRange(["--auth-token-env", "COPILOT_SDK_AUTH_TOKEN"]); + } + var useLoggedInUser = options.UseLoggedInUser ?? string.IsNullOrEmpty(options.GitHubToken); + if (!useLoggedInUser) + { + args.Add("--no-auto-login"); + } + if (options.SessionIdleTimeoutSeconds is > 0) + { + args.AddRange(["--session-idle-timeout", options.SessionIdleTimeoutSeconds.Value.ToString(CultureInfo.InvariantCulture)]); + } + if (options.EnableRemoteSessions) + { + args.Add("--remote"); + } + + var cliLoginSyncIntegrationId = + System.Environment.GetEnvironmentVariable("GITHUB_COPILOT_INTEGRATION_ID")?.Trim(); + if (!string.IsNullOrEmpty(cliLoginSyncIntegrationId)) + { + args.Add($"--cli-login-sync-integration-id={cliLoginSyncIntegrationId}"); + } + return args; + } + private static string? GetBundledNativePath(string binaryName, out string searchedPath) { // Always use portable RID (e.g., linux-x64) to match the build-time placement, @@ -3210,6 +3233,7 @@ public static string Escape(string arg) internal record CreateSessionRequest( string? Model, string? SessionId, + string? Name, string? ClientName, string? ReasoningEffort, ReasoningSummary? ReasoningSummary, @@ -3252,6 +3276,7 @@ internal record CreateSessionRequest( bool? EnableFileHooks, bool? EnableHostGitOperations, bool? EnableSessionStore, + [property: JsonPropertyName("enableGitHubCacheStore")] bool? EnableGitHubCacheStore, bool? EnableSkills, IList? SkillDirectories, IList? DisabledSkills, @@ -3269,10 +3294,13 @@ internal record CreateSessionRequest( CloudSessionOptions? Cloud = null, bool? RefreshCustomInstructions = null, IList? InstructionDirectories = null, + IList? AgentDirectories = null, + IList? HookDirectories = null, IList? PluginDirectories = null, [property: JsonPropertyName("disabledMcpServers")] IList? DisabledMcpServers = null, LargeToolOutputConfig? LargeOutput = null, ToolSearchConfig? ToolSearch = null, + ImageGenerationConfig? ImageGeneration = null, MemoryConfiguration? Memory = null, #pragma warning disable GHCP001 IList? Canvases = null, @@ -3288,6 +3316,7 @@ internal record CreateSessionRequest( [property: JsonPropertyName("expAssignments")] CopilotExpAssignmentResponse? ExpAssignments = null, [property: JsonPropertyName("enableManagedSettings")] bool? EnableManagedSettings = null, [property: JsonPropertyName("enforceManagedModelDefaults")] bool? EnforceManagedModelDefaults = null, + [property: JsonPropertyName("interactiveUser")] bool? InteractiveUser = null, [property: JsonPropertyName("managedSettings")] ManagedSettings? ManagedSettings = null, bool? EnableGitHubTelemetryForwarding = null, [property: JsonPropertyName("githubMcpToolConfig")] GitHubMcpToolConfig? GitHubMcpToolConfig = null, @@ -3364,6 +3393,7 @@ internal record ResumeSessionRequest( bool? EnableFileHooks, bool? EnableHostGitOperations, bool? EnableSessionStore, + [property: JsonPropertyName("enableGitHubCacheStore")] bool? EnableGitHubCacheStore, bool? EnableSkills, [property: JsonPropertyName("disableResume")] bool? SuppressResumeEvent, bool? Streaming, @@ -3391,10 +3421,13 @@ internal record ResumeSessionRequest( RemoteSessionMode? RemoteSession = null, bool? ContinuePendingWork = null, IList? InstructionDirectories = null, + IList? AgentDirectories = null, + IList? HookDirectories = null, IList? PluginDirectories = null, [property: JsonPropertyName("disabledMcpServers")] IList? DisabledMcpServers = null, LargeToolOutputConfig? LargeOutput = null, ToolSearchConfig? ToolSearch = null, + ImageGenerationConfig? ImageGeneration = null, MemoryConfiguration? Memory = null, #pragma warning disable GHCP001 IList? Canvases = null, @@ -3411,6 +3444,7 @@ internal record ResumeSessionRequest( [property: JsonPropertyName("expAssignments")] CopilotExpAssignmentResponse? ExpAssignments = null, [property: JsonPropertyName("enableManagedSettings")] bool? EnableManagedSettings = null, [property: JsonPropertyName("enforceManagedModelDefaults")] bool? EnforceManagedModelDefaults = null, + [property: JsonPropertyName("interactiveUser")] bool? InteractiveUser = null, [property: JsonPropertyName("managedSettings")] ManagedSettings? ManagedSettings = null, bool? EnableGitHubTelemetryForwarding = null, [property: JsonPropertyName("githubMcpToolConfig")] GitHubMcpToolConfig? GitHubMcpToolConfig = null, diff --git a/dotnet/src/Generated/Rpc.cs b/dotnet/src/Generated/Rpc.cs index b21d884f1d..c7f7973d96 100644 --- a/dotnet/src/Generated/Rpc.cs +++ b/dotnet/src/Generated/Rpc.cs @@ -107,2239 +107,2383 @@ internal sealed class ConnectRequest public string? Token { get; set; } } -/// Hosting capabilities and session capacity advertised by an environment. +/// The user's sandbox settings raised to the managed floor. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class EnvironmentCapabilities +internal sealed class SandboxResolveFlooredConfigResult { - /// Advertised Agent Host Protocol version. - [JsonPropertyName("ahpVersion")] - public string? AhpVersion { get; set; } - - /// Current session count, when advertised. - [JsonPropertyName("currentSessions")] - public long? CurrentSessions { get; set; } + /// The floored sandbox config, normalized through the declared sandbox shape so only declared fields in a fixed order are present. + [JsonPropertyName("sandboxJson")] + public JsonElement SandboxJson { get; set; } +} - /// Feature identifiers advertised by the environment. - [JsonPropertyName("features")] - public IList Features { get => field ??= []; set; } +/// The user's sandbox settings and the managed sandbox floor to raise them to. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class SandboxResolveFlooredConfigParams +{ + /// The effective managed `sandbox` slice acting as a floor. Omitted when no administrator policy applies, in which case the user's own settings stand unchanged. + [JsonPropertyName("managedSandbox")] + public JsonElement? ManagedSandbox { get; set; } - /// Maximum session capacity, when advertised. - [JsonPropertyName("maxSessions")] - public long? MaxSessions { get; set; } + /// The user's own `sandbox` settings slice. Omitted when the user has configured none, which still yields the managed floor rather than a disabled sandbox. + [JsonPropertyName("userSandbox")] + public JsonElement? UserSandbox { get; set; } } -/// Safe discovery information. Host-side relay bootstrap credentials are never included. +/// The prepared file-picker policy, or none when the sandbox is disabled. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class GitHubEnvironment +internal sealed class SandboxPreparePickerPolicyResult { - /// Hosting capabilities advertised by the environment. - [JsonPropertyName("capabilities")] - public EnvironmentCapabilities? Capabilities { get; set; } - - /// Identifier assigned by Mission Control. - [JsonPropertyName("id")] - public string Id { get; set; } = string.Empty; + /// The prepared policy to pass back to `sandbox.filterPickerCandidates`. Absent when the sandbox is disabled or absent, in which case filtering keeps every candidate. + [JsonPropertyName("sandboxJson")] + public JsonElement? SandboxJson { get; set; } +} - /// Compute kind reported by Mission Control. - [JsonPropertyName("kind")] - public EnvironmentKind Kind { get; set; } +/// The working directory and sandbox settings to build a file-picker policy from. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class SandboxPreparePickerPolicyParams +{ + /// The session's working directory. Git discovery and grant canonicalization are resolved against it. + [JsonPropertyName("cwd")] + public string Cwd { get; set; } = string.Empty; - /// Discovery labels attached to the environment. - [JsonPropertyName("labels")] - public IDictionary? Labels { get; set; } + /// The session's resolved `sandbox` slice. Omitted, or disabled, yields no policy and every candidate is kept. + [JsonPropertyName("sandbox")] + public JsonElement? Sandbox { get; set; } +} - /// Timestamp of the last heartbeat received by Mission Control. - [JsonPropertyName("lastHeartbeatAt")] - public string? LastHeartbeatAt { get; set; } +/// Per-candidate allow decisions, positionally aligned with the request. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class SandboxFilterPickerCandidatesResult +{ + /// One decision per candidate, in the same order. Always the same length as the request's `candidates`. + [JsonPropertyName("allowed")] + public IList Allowed { get => field ??= []; set; } +} - /// Human-readable environment name. - [JsonPropertyName("name")] - public string Name { get; set; } = string.Empty; +/// One file-picker suggestion offered up for the sandbox filesystem check. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class SandboxPickerCandidate +{ + /// Whether the suggestion is a directory. A directory the policy does not itself allow may still be needed as a waypoint to an allowed subtree. + [JsonPropertyName("isDirectory")] + public bool IsDirectory { get; set; } - /// Organization identifier, when the environment belongs to an organization. - [JsonPropertyName("orgId")] - public string? OrgId { get; set; } + /// Absolute path the suggestion would attach. + [JsonPropertyName("path")] + public string Path { get; set; } = string.Empty; +} - /// Identifier of the environment owner. - [JsonPropertyName("ownerId")] - public string? OwnerId { get; set; } +/// A batch of file-picker candidates to check against a prepared policy. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class SandboxFilterPickerCandidatesParams +{ + /// The candidates to check, in the order the answers are returned. + [JsonPropertyName("candidates")] + public IList Candidates { get => field ??= []; set; } - /// Owner category reported by Mission Control. - [JsonPropertyName("ownerType")] - public string? OwnerType { get; set; } + /// The session's working directory, matching the one the policy was prepared from. + [JsonPropertyName("cwd")] + public string Cwd { get; set; } = string.Empty; - /// Open-ended operational status vocabulary. - [JsonPropertyName("status")] - public string Status { get; set; } = string.Empty; + /// The policy returned by `sandbox.preparePickerPolicy`. Omitted keeps every candidate. + [JsonPropertyName("sandbox")] + public JsonElement? Sandbox { get; set; } } -/// Environments visible to the authenticated caller and matching the supplied filters. +/// The sandbox a named feature actually launches under. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class EnvironmentsListResult +internal sealed class SandboxEffectiveForOptionalResult { - /// Safe discovery records without relay bootstrap credentials. - [JsonPropertyName("environments")] - public IList Environments { get => field ??= []; set; } + /// The effective sandbox config for the requested feature, or a disabled sandbox when the feature is routed out of it. + [JsonPropertyName("sandboxJson")] + public JsonElement SandboxJson { get; set; } } -/// Optional discovery filters supported by GitHub Mission Control. +/// A sandbox config and the per-feature routing flag to resolve it for. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class EnvironmentsListRequest +internal sealed class SandboxEffectiveForOptionalParams { - /// Restrict discovery to this compute kind. - [JsonPropertyName("kind")] - public EnvironmentKind? Kind { get; set; } + /// The routing flag naming the feature being launched, such as `sandboxMcpServers` or `sandboxLspServers`. + [JsonPropertyName("routingFlag")] + public string RoutingFlag { get; set; } = string.Empty; - /// Operational status, such as online, offline, degraded, waking, or draining. - [JsonPropertyName("status")] - public string? Status { get; set; } + /// The session's resolved `sandbox` slice. Omitted when the host has none, which yields a disabled sandbox rather than an error. + [JsonPropertyName("sandbox")] + public JsonElement? Sandbox { get; set; } } -/// Safe discovery information for the requested environment. +/// The startup warning, absent when the machine can run a sandbox. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class EnvironmentsGetResult +internal sealed class SandboxUnsupportedStartupNoticeResult { - /// The requested environment without relay bootstrap credentials. - [JsonPropertyName("environment")] - public GitHubEnvironment Environment { get => field ??= new(); set; } + /// The user-facing warning, carrying the platform remedy and the recovery the policy in force actually allows. Absent when this machine supports a sandbox, so a host can call unconditionally and print only what it gets. + [JsonPropertyName("notice")] + public string? Notice { get; set; } } -/// Identify a Mission Control environment to retrieve. +/// The managed sandbox policy in force, which selects which remedy the notice offers. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class EnvironmentsGetRequest +internal sealed class SandboxUnsupportedStartupNoticeParams { - /// Identifier assigned by Mission Control. - [JsonPropertyName("environmentId")] - public string EnvironmentId { get; set; } = string.Empty; + /// True when the sandbox stayed on only because the managed policy could not be determined. Distinguishes a policy that positively enforces the sandbox from one that merely failed to resolve, which is the difference between telling the user to contact an administrator and telling them to wait. Defaults to false. + [JsonPropertyName("enabledByUndeterminedPolicy")] + public bool? EnabledByUndeterminedPolicy { get; set; } + + /// The managed `sandbox` slice, when a policy supplies one. The notice treats the sandbox as enforced only when this both enables it and sets `failIfUnavailable`, because a managed sandbox that tolerates being unavailable still leaves the user able to turn it off. + [JsonPropertyName("managedSandbox")] + public JsonElement? ManagedSandbox { get; set; } } -/// Acknowledgement that the requested environment was deleted. +/// One slash command, as typed, to test against the enforcement-block allowlist. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class EnvironmentsDeleteResult +internal sealed class SandboxEnforcementCommandAllowedParams { + /// The command's arguments, in order. Pass an empty array for a bare command: several entries in the allowlist admit the bare form and refuse every argument. + [JsonPropertyName("args")] + public IList Args { get => field ??= []; set; } + + /// The command name as the user typed it, with or without its leading slash. + [JsonPropertyName("commandName")] + public string CommandName { get; set; } = string.Empty; } -/// Identify a user-managed Mission Control environment to delete. +/// The documentation URL a host cites when it declines or downgrades a sandbox setting. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class EnvironmentsDeleteRequest +internal sealed class SandboxDocsUrlResult { - /// User-managed environment to delete. GitHub-managed environments cannot be deleted. - [JsonPropertyName("environmentId")] - public string EnvironmentId { get; set; } = string.Empty; + /// Documentation URL describing the sandbox and the settings that control it. + [JsonPropertyName("url")] + public string Url { get; set; } = string.Empty; } -/// The existing runtime identity and its resource on the listener. +/// Whether this host can run one sandbox policy feature. A session whose effective policy uses an unsupported feature fails each sandboxed command with `reason`, except `filesystem_enumeration`, whose absence degrades sandboxed PowerShell instead of failing it. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class HostPublishSessionResult +public sealed class SandboxHostCapability { - /// Canonical runtime ID of the published session. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// The policy feature, as an extensible string: ignore names you do not recognize. Known values: `network` (sandboxed commands can reach the network; on Linux this needs the tooling for Bubblewrap's private network namespace, such as slirp4netns), `network_filtering` (host rules and the sandbox proxy; on Linux this needs the same tooling as `network`; on Windows it needs Process Security Environment 1.1 host-loopback support or MXC's PSEC 1.0-only proxy-loopback compatibility capability, and a policy that uses it must also set `network.allowLocalNetwork`; compatibility applies only to an explicit identity-less runtime proxy, not general host-loopback access, and other policy restrictions still apply), `denied_paths` (native enforcement of `filesystem.deniedPaths`), `shell` (shell commands inside the sandbox), and `filesystem_enumeration` (enumerate-only filesystem grants; on Windows this needs Process Security Environment 1.1 filesystem enumeration support, and without it sandboxed PowerShell still runs but cannot resolve its current location; other platforms always report it). + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; - /// AHP resource URI for the session on this listener. - [JsonPropertyName("sessionUri")] - public string SessionUri { get; set; } = string.Empty; + /// Human-readable reason and remedy when the feature is unsupported, such as a package to install or an OS update. Present only when `supported` is false. + [JsonPropertyName("reason")] + public string? Reason { get; set; } + + /// Whether this host can run the feature. + [JsonPropertyName("supported")] + public bool Supported { get; set; } } -/// Publishes a resident session attached to the listener's owning connection. +/// Whether the host running this runtime can run the command sandbox. The runtime checks `supported` once per process. A capability answer can change while the process runs, for example after the user installs a missing package. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class HostPublishSessionRequest +public sealed class SandboxHostSupport { - /// Listener UUID returned by host.start. - [JsonPropertyName("hostId")] - public string HostId { get; set; } = string.Empty; + /// Sandbox policy features whose availability varies between hosts that can run the backend. Empty when `supported` is false, because no feature can run without a backend. Later runtimes can add entries; ignore an entry whose `name` you do not recognize. + [JsonPropertyName("capabilities")] + public IList Capabilities { get => field ??= []; set; } - /// Canonical runtime session ID attached to the listener's owning connection. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Human-readable reason the sandbox cannot run on this host. Present only when `supported` is false. + [JsonPropertyName("reason")] + public string? Reason { get; set; } + + /// Whether a process-containment backend is usable on this host: Seatbelt on macOS, Bubblewrap on Linux, or ProcessContainer on Windows. + [JsonPropertyName("supported")] + public bool Supported { get; set; } } -/// Metadata advertised by the AHP host, not a guarantee that history can be resumed. +/// 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(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class HostSessionSummary +public sealed class SandboxConfigAuth { - /// Optional activity description advertised by the host. - [JsonPropertyName("activity")] - public string? Activity { get; set; } - - /// Session creation time as an ISO 8601 timestamp. - [JsonPropertyName("createdAt")] - public string CreatedAt { get; set; } = string.Empty; - - /// Last modification time as an ISO 8601 timestamp. - [JsonPropertyName("modifiedAt")] - public string ModifiedAt { get; set; } = string.Empty; - - /// Stable AHP resource URI identifying the session on this host. - [JsonPropertyName("resource")] - public string Resource { get; set; } = string.Empty; + /// Whether to authenticate sandboxed gh through the local masking proxy. The child receives a fake GH_TOKEN; its real value is substituted only at github.com, api.github.com and uploads.github.com (github.com because gh repo clone authenticates git through gh auth git-credential). The repository's GitHub account takes precedence over the Copilot login. Default: false (opt-in). + [JsonPropertyName("gh")] + public bool? Gh { get; set; } - /// Unsigned 32-bit AHP session status bitset (0..=4294967295); unknown bits must be preserved. - [JsonPropertyName("status")] - public long Status { get; set; } + /// Whether to authenticate sandboxed HTTPS git through the local masking proxy. The child receives a fake `http.<url>.extraheader`; the real Authorization header is substituted only at its original HTTPS host, port, and repository path scope. github.com uses the Copilot token; other forges use credentials resolved from the user's own helper on the host. Default: false (opt-in). + [JsonPropertyName("git")] + public bool? Git { get; set; } +} - /// Authoritative title advertised by the host. - [JsonPropertyName("title")] - public string Title { get; set; } = string.Empty; +/// Destinations authorized to receive one masked environment credential. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public sealed class SandboxMaskedEnvVar +{ + /// Nonempty list of HTTPS injection hostnames or *.example.com patterns. Bare * is not accepted. These grants never override the sandbox network policy. Values in plaintext HTTP requests, URLs, bodies, encoded credentials, and signed requests are not substituted. + [JsonPropertyName("injectHosts")] + public IList InjectHosts { get => field ??= []; set; } } -/// The complete live and dormant advertised catalog, without subscribing to sessions. +/// Whole-value environment credential masking for sandboxed children. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class HostListSessionsResult +public sealed class SandboxCredentialsConfig { - /// Complete advertised session summaries, in the host's catalog order. - [JsonPropertyName("sessions")] - public IList Sessions { get => field ??= []; set; } + /// Environment variable names and their HTTPS injection destinations. Absent variables stay absent. No real values or sentinels are stored in this map. + [JsonPropertyName("envVars")] + public IDictionary EnvVars { get => field ??= new Dictionary(); set; } } -/// Reads a running host's catalog or the runtime's saved default catalog. +/// macOS seatbelt experimental options. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class HostListSessionsRequest +public sealed class SandboxConfigUserPolicyExperimentalSeatbelt { - /// - /// Listener UUID returned by host.start; the caller must own it. Omit to read - /// the saved default compute-identity catalog without starting a listener. - /// - [JsonPropertyName("hostId")] - public string? HostId { get; set; } + /// Whether the macOS seatbelt profile may access the keychain. + [JsonPropertyName("keychainAccess")] + public bool? KeychainAccess { get; set; } } -/// The resident session the application has materialized on its own connection. +/// Platform-specific experimental policy fields. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class HostCreateSessionResult +public sealed class SandboxConfigUserPolicyExperimental { - /// Runtime session UUID materialized on the application's SDK connection. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// macOS seatbelt experimental options. + [JsonPropertyName("seatbelt")] + public SandboxConfigUserPolicyExperimentalSeatbelt? Seatbelt { get; set; } } -/// One application-owned session handoff, requested by the supervised hosting participant. +/// Filesystem rules to merge into the base policy. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class HostCreateSessionRequest +public sealed class SandboxConfigUserPolicyFilesystem { - /// Host-selected SDK creation or resume settings, without executable callbacks or tools. - [JsonPropertyName("config")] - public IDictionary Config { get => field ??= new Dictionary(); set; } + /// Whether to clear the policy when the session exits. + [JsonPropertyName("clearPolicyOnExit")] + public bool? ClearPolicyOnExit { get; set; } - /// Unique identity for this participation, independent of the session lifetime. - [JsonPropertyName("handoffId")] - public string HandoffId { get; set; } = string.Empty; + /// Paths explicitly denied. + [JsonPropertyName("deniedPaths")] + public IList? DeniedPaths { get; set; } - /// - /// For an explicit publication, borrow an existing owning-connection resident - /// instead of invoking the resume factory. Cold sessions still use the factory. - /// - [JsonPropertyName("preferResident")] - public bool? PreferResident { get; set; } + /// Paths granted read-only access. + [JsonPropertyName("readonlyPaths")] + public IList? ReadonlyPaths { get; set; } - /// Resume an app-owned durable session instead of creating a new session. - [JsonPropertyName("resume")] - public bool? Resume { get; set; } + /// Paths granted read/write access. + [JsonPropertyName("readwritePaths")] + public IList? ReadwritePaths { get; set; } } -/// Empty acknowledgement for a completed host lifecycle operation. +/// HTTP proxy configuration for sandboxed traffic. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class HostReleaseSessionResult +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; } -/// Ends one participation, not the application's session lifetime. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class HostReleaseSessionRequest -{ - /// Identity of the participation to release without destroying the session. - [JsonPropertyName("handoffId")] - public string HandoffId { get; set; } = string.Empty; + /// Proxy URL (e.g. http://proxy.example.com:8080). The port is optional and defaults to the scheme's standard port when omitted; 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; } } -/// Listener readiness, returned only after binding and the supervised participant's SDK handshake. +/// Network rules to merge into the base policy. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class HostStartResult +public sealed class SandboxConfigUserPolicyNetwork { - /// GitHub Mission Control environment ID, present when its relay transport is ready. - [JsonPropertyName("environmentId")] - public string? EnvironmentId { get; set; } + /// Hosts allowed through the built-in sandbox proxy. A non-empty list denies unmatched hosts; an absent or empty list allows all hosts not blocked. Supports exact hostnames, IP addresses, and *.example.com for strict subdomains. Host rules do not override the outbound or local-network toggles. + [JsonPropertyName("allowedHosts")] + public IList? AllowedHosts { get; set; } - /// Caller-generated listener UUID. - [JsonPropertyName("hostId")] - public string HostId { get; set; } = string.Empty; + /// Whether traffic to local/loopback addresses is allowed. + [JsonPropertyName("allowLocalNetwork")] + public bool? AllowLocalNetwork { get; set; } - /// Separate host process ID, when provided by a legacy runtime. Absent for in-process listeners. - [JsonPropertyName("pid")] - public long? Pid { get; set; } + /// Whether outbound network traffic is allowed at all. + [JsonPropertyName("allowOutbound")] + public bool? AllowOutbound { get; set; } - /// Secret connection token, absent when authentication is disabled. - [JsonPropertyName("token")] - public string? Token { get; set; } + /// Hosts denied by the built-in sandbox proxy. Deny rules take precedence over allowedHosts. A domain also denies all its subdomains. IP addresses match exactly; *.example.com matches strict subdomains, and * denies every host. + [JsonPropertyName("blockedHosts")] + public IList? BlockedHosts { get; set; } - /// Actual bound WebSocket URL, including the allocated port. - [JsonPropertyName("url")] - public string? Url { get; set; } + /// HTTP(S) proxy for sandboxed traffic. This is the built-in local proxy's upstream: every sandboxed command reaches it through a loopback listener, so credentials stay in the runtime and never reach the child. On Windows the sandbox also needs local network access, because it reaches that listener over host loopback. Configure credentials in the separate username/password fields. The transient local listener URL is never persisted. + [JsonPropertyName("proxy")] + public SandboxConfigUserPolicyNetworkProxy? Proxy { get; set; } } -/// GitHub Mission Control registration options. The compute ID is application-owned and stable. +/// macOS seatbelt-specific options. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class HostGitHubEnvironmentOptions +public sealed class SandboxConfigUserPolicySeatbelt { - /// Stable application installation identity, reused across host restarts. - [JsonPropertyName("computeId")] - public string ComputeId { get; set; } = string.Empty; - - /// Human-readable environment display name. - [JsonPropertyName("name")] - public string Name { get; set; } = string.Empty; - - /// Require sealed, connection-bound authentication (default true), independent of listener address. False permits unsealed direct credentials and unbound sealed relay credentials; use only when the caller fully controls and trusts the direct transport. Relay token encryption remains mandatory. - [JsonPropertyName("requireConnectionBinding")] - public bool? RequireConnectionBinding { get; set; } + /// Whether the macOS seatbelt profile may access the keychain. + [JsonPropertyName("keychainAccess")] + public bool? KeychainAccess { get; set; } } -/// Local WebSocket transport options. +/// User-managed sandbox policy fragment merged into the auto-discovered base policy. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class HostLocalServerOptions +public sealed class SandboxConfigUserPolicy { - /// Listener hostname. Defaults to 127.0.0.1; explicit non-loopback binds are allowed. - [JsonPropertyName("hostname")] - public string? Hostname { get; set; } + /// Deprecated legacy location for `seatbelt`; read only when the top-level `seatbelt` is absent. + [JsonPropertyName("experimental")] + public SandboxConfigUserPolicyExperimental? Experimental { get; set; } - /// Listener port. Omitted or zero requests an OS-allocated port. - [JsonPropertyName("port")] - public int? Port { get; set; } + /// Filesystem rules to merge into the base policy. + [JsonPropertyName("filesystem")] + public SandboxConfigUserPolicyFilesystem? Filesystem { get; set; } - /// Require token authentication (default true). Cannot be false with a token. - [JsonPropertyName("requireConnectionToken")] - public bool? RequireConnectionToken { get; set; } + /// Network rules to merge into the base policy. + [JsonPropertyName("network")] + public SandboxConfigUserPolicyNetwork? Network { get; set; } - /// Nonempty connection token. Generated randomly when required and omitted. - [JsonPropertyName("token")] - public string? Token { get; set; } + /// macOS seatbelt options to merge into the base policy. + [JsonPropertyName("seatbelt")] + public SandboxConfigUserPolicySeatbelt? Seatbelt { get; set; } } -/// Starts a supervised AHP host with at least one explicitly selected transport. +/// Resolved sandbox configuration. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class HostStartRequest +public sealed class SandboxConfig { - /// Stable application identity selecting its durable host catalog. Defaults to the persisted runtime compute GUID; must agree with githubEnvironment.computeId when both are supplied. - [JsonPropertyName("computeId")] - public string? ComputeId { get; set; } + /// Whether to auto-add the current working directory to readwritePaths. Default: true. + [JsonPropertyName("addCurrentWorkingDirectory")] + public bool? AddCurrentWorkingDirectory { get; set; } - /// Registers a GitHub Mission Control environment and enables its relay transport. - [JsonPropertyName("githubEnvironment")] - public HostGitHubEnvironmentOptions? GitHubEnvironment { get; set; } + /// Whether the agent may request that an individual command run outside the sandbox, which the host then approves or denies through the usual permission flow. A host capability flag rather than part of the policy: it is stripped from the effective spawn policy and only has an effect while `enabled` is true. Fail-closed, unlike the opt-out flags on this object: omitting it offers no bypass. Default: false (opt-in). + [JsonPropertyName("allowBypass")] + public bool? AllowBypass { get; set; } - /// Caller-generated UUID identifying this connection-owned listener. - [JsonPropertyName("hostId")] - public required string HostId { 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; } - /// Enables a local WebSocket listener. - [JsonPropertyName("localServer")] - public HostLocalServerOptions? LocalServer { get; set; } + /// Credential-injection capability flags. + [JsonPropertyName("auth")] + public SandboxConfigAuth? Auth { get; set; } - /// Ask the owning application to resume its durable AHP sessions. - [JsonPropertyName("resumeFactory")] - public bool? ResumeFactory { get; set; } + /// Opt-in whole-value environment masking for sandboxed shell, MCP, and LSP children. Configured names get random sentinels; the local proxy substitutes them only in HTTPS request headers at their injection hosts. Approved bypasses skip masking and the sandbox proxy, so bypassed shells may receive the real environment values. Disabled or explicitly opted-out routes are not protected. No credential values are stored in this configuration. + [JsonPropertyName("credentials")] + public SandboxCredentialsConfig? Credentials { get; set; } - /// Ask the owning SDK application to materialize AHP sessions. - [JsonPropertyName("sessionFactory")] - public bool? SessionFactory { get; set; } -} + /// Whether sandboxing is enabled for the session. + [JsonPropertyName("enabled")] + public bool Enabled { get; set; } -/// Empty acknowledgement for a completed host lifecycle operation. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class HostDisposeResult -{ + /// The `sandboxLspServers` counterpart of `managedMcpRoutingLocked`. + [JsonInclude] + [JsonPropertyName("managedLspRoutingLocked")] + internal bool? ManagedLspRoutingLocked { get; set; } + + /// Set by the runtime when a managed policy forced `sandboxMcpServers` on and took the local opt-out away. Provenance rather than policy: it lets a sandbox startup failure point at the administrator instead of a setting the next managed merge would override, and it is ignored when comparing two configs for change. Only the managed merge may set it; a caller-supplied value is stripped. + [JsonInclude] + [JsonPropertyName("managedMcpRoutingLocked")] + internal bool? ManagedMcpRoutingLocked { get; set; } + + /// Whether language servers the session launches are confined by the sandbox. Only an explicit `false` opts out. Ignored while `enabled` is false. Default: true (enabled by default; set to false to opt out). + [JsonPropertyName("sandboxLspServers")] + public bool? SandboxLspServers { get; set; } + + /// Whether MCP servers the session launches are confined by the sandbox. Only an explicit `false` opts out; doing so also lets remote-MCP egress leave the sandbox, so the flag and `enabled` are always read together. Ignored while `enabled` is false. Default: true (enabled by default; set to false to opt out). + [JsonPropertyName("sandboxMcpServers")] + public bool? SandboxMcpServers { get; set; } + + /// User-managed sandbox policy fragment merged into the auto-discovered base policy. + [JsonPropertyName("userPolicy")] + public SandboxConfigUserPolicy? UserPolicy { get; set; } } -/// Stops a connection-owned listener and joins its teardown. +/// Identifies the credential hosts that the persistent certificate authority of the sandbox credential proxy must cover. The runtime always adds the hosts from the saved user settings. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class HostDisposeRequest +internal sealed class SandboxProxyCaRequest { - /// Listener UUID. Unknown or successfully stopped IDs are harmless. - [JsonPropertyName("hostId")] - public string HostId { get; set; } = string.Empty; + /// The sandbox configuration that the host gives its sessions. The runtime reads the credential hosts from `auth` and `credentials`; it ignores `enabled` and the other fields. + [JsonPropertyName("sandboxConfig")] + public SandboxConfig? SandboxConfig { get; set; } } -/// Private credentials delivered only to a runtime-owned Mission Control hosting participant. +/// Status of the persistent certificate authority of the sandbox credential proxy. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class HostGetEnvironmentCredentialsResult +public sealed class SandboxProxyCaStatus { - /// GitHub API base URL for the authenticated service. - [JsonPropertyName("githubApiUrl")] - public string GitHubApiUrl { get; set; } = string.Empty; - - /// Hostname of the authenticated GitHub service. - [JsonPropertyName("githubHost")] - public string GitHubHost { get; set; } = string.Empty; + /// Whether this process can add the certificate authority to OS trust without credentials from a different user. False where OS trust is unsupported, and on Windows when the process cannot elevate itself to write the machine trust store. When false, do not offer to set up the certificate authority. + [JsonPropertyName("canInstall")] + public bool CanInstall { get; set; } - /// Mission Control API origin for environment registration and management. - [JsonPropertyName("missionControlUrl")] - public string MissionControlUrl { get; set; } = string.Empty; + /// Human-readable reason for the state. On `installed` or `notInstalled`, present only when the certificate authority must be rotated, and then says why. + [JsonPropertyName("detail")] + public string? Detail { get; set; } - /// Current bearer token for the authenticated GitHub identity. - [JsonPropertyName("token")] - public string Token { get; set; } = string.Empty; + /// The state of the certificate authority. + [JsonPropertyName("state")] + public SandboxProxyCaState State { get; set; } } -/// Normalized local WebSocket listener settings. +/// Result of creating the persistent certificate authority of the sandbox credential proxy. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class HostLocalServerConfiguration +public sealed class SandboxProxyCaCreateResult { - /// Hostname or IP address to bind. - [JsonPropertyName("hostname")] - public string Hostname { get; set; } = string.Empty; - - /// Port to bind, with zero requesting OS allocation. - [JsonPropertyName("port")] - public int Port { get; set; } + /// Absolute path of the public certificate of the certificate authority, in PEM format. + [JsonPropertyName("certificatePath")] + public string CertificatePath { get; set; } = string.Empty; +} - /// Whether the listener requires token authentication. - [JsonPropertyName("requireConnectionToken")] - public bool RequireConnectionToken { get; set; } +/// Where this runtime keeps the sandbox proxy password. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class SandboxProxySecretLocation +{ + /// Absolute path of the credential store holding the sandbox proxy password. The proxy password belongs to no MCP server, so it is kept in its own directory rather than in the MCP secret store. + [JsonPropertyName("configDir")] + public string ConfigDir { get; set; } = string.Empty; - /// Secret connection token, absent when authentication is disabled. - [JsonPropertyName("token")] - public string? Token { get; set; } + /// Identifier of the proxy password entry within that store. Derived from the configuration directory, so two Copilot homes on one machine never share an entry. Use it to recognize a `${secret:…}` reference in stored settings as this runtime's own proxy password. + [JsonPropertyName("secretId")] + public string SecretId { get; set; } = string.Empty; } -/// Normalized listener settings delivered only to the supervised hosting participant. +/// The stored sandbox proxy password, if one is present. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class HostGetConfigurationResult +internal sealed class SandboxProxySecretGetResult { - /// Requested GitHub Mission Control registration. - [JsonPropertyName("githubEnvironment")] - public HostGitHubEnvironmentOptions? GitHubEnvironment { get; set; } - - /// Normalized local listener settings, absent for relay-only hosts. - [JsonPropertyName("localServer")] - public HostLocalServerConfiguration? LocalServer { get; set; } + /// The stored password. Absent when no entry exists in either the keychain or the on-disk fallback store. + [JsonPropertyName("value")] + public string? Value { get; set; } +} - /// Whether app-owned durable sessions are resumed by the owning application. - [JsonPropertyName("resumeFactory")] - public bool ResumeFactory { get; set; } +/// Where the sandbox proxy password was stored. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class SandboxProxySecretSaveResult +{ + /// Whether the OS keychain accepted the value. False means it was written to the on-disk fallback store instead, which is still readable by `sandbox.proxySecret.get`. + [JsonPropertyName("storedInKeychain")] + public bool StoredInKeychain { get; set; } +} - /// Whether session materialization is delegated to the owning application. - [JsonPropertyName("sessionFactory")] - public bool SessionFactory { get; set; } +/// The sandbox proxy password to store. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class SandboxProxySecretSaveRequest +{ + /// The password to store for the sandbox proxy. + [JsonPropertyName("value")] + public string Value { get; set; } = string.Empty; } -/// Empty acknowledgement for a completed host lifecycle operation. +/// The stored sandbox settings to resolve against the defaults. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class HostReadyResult +internal sealed class SandboxApplyDefaultsRequest { + /// The sandbox settings as stored in the settings file. An empty object resolves to the defaults alone. + [JsonPropertyName("stored")] + public JsonElement Stored { get; set; } } -/// Readiness reported by the supervised hosting participant on its own SDK connection. +/// The outcome of a locked snapshot write. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class HostReadyRequest +internal sealed class PersistenceWriteRawJsonLockedResult { - /// Actual bound WebSocket URL. - [JsonPropertyName("address")] - public string? Address { get; set; } - - /// Registered environment ID, reported only once the relay transport is connected. - [JsonPropertyName("environmentId")] - public string? EnvironmentId { get; set; } + /// Why the write failed. Absent when `success` is true. A lock the runtime could not acquire in time reports here rather than raising. + [JsonPropertyName("error")] + public string? Error { get; set; } - /// Configured secret token, absent when authentication is disabled. - [JsonPropertyName("token")] - public string? Token { get; set; } + /// Whether the file now holds the requested contents. + [JsonPropertyName("success")] + public bool Success { get; set; } } -/// One server-discovered hook action from user, repository, plugin, or managed-policy configuration. +/// A complete JSON snapshot to write under the runtime's per-path file lock. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class DiscoveredHook +internal sealed class PersistenceWriteRawJsonLockedRequest { - /// 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; } + /// The serialized JSON document to write. It replaces the file's contents entirely; nothing is merged. + [JsonPropertyName("dataJson")] + public string DataJson { get; set; } = string.Empty; - /// 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; + /// Comment lines to emit above the document, for a format that tolerates them. Omit for strict JSON. + [JsonPropertyName("fileHeader")] + public string? FileHeader { get; set; } - /// Configuration tier that contributed this hook action. - [JsonPropertyName("origin")] - public HookOrigin Origin { get; set; } + /// Absolute path of the file to replace. Parent directories are created when missing. + [JsonPropertyName("path")] + public string Path { get; set; } = string.Empty; +} - /// 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; } +/// Outcome of rewriting the `disabledHooks` setting. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class UserSettingsSetHookDisabledResult +{ + /// Why the write failed. Absent when the status is `ok`. + [JsonPropertyName("errorMessage")] + public string? ErrorMessage { get; set; } - /// Human-readable source label, such as a hook file path, settings source, or plugin name. - [JsonPropertyName("source")] - public string? Source { get; set; } + /// `ok` when the setting was written, `invalid` when settings.json could not be parsed and was left alone, `error` when the write itself failed. + [JsonPropertyName("status")] + public string Status { get; set; } = string.Empty; } -/// 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. +/// One hook to disable or re-enable. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class HooksDiscoverResult +internal sealed class UserSettingsSetHookDisabledRequest { - /// 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; } + /// True adds the key to `disabledHooks`, false removes it. + [JsonPropertyName("disabled")] + public bool Disabled { get; 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; } + /// Durable content-hash key of the hook, as `hooks.loadResources` reports it. + [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("disableKey")] + public string DisableKey { get; set; } = string.Empty; - /// 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; } + /// Path of the settings.json to rewrite. + [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("settingsPath")] + public string SettingsPath { get; set; } = string.Empty; } -/// Optional project paths and host-exclusion behavior for server-scoped hook discovery. +/// Selects the configuration directory whose user settings to read. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class HooksDiscoverRequest +internal sealed class UserSettingsLoadForConfigDirRequest { - /// 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; } + /// Copilot configuration directory to read the settings document from, taking precedence over the server's own `COPILOT_HOME` and default home. Omit it, or pass an empty string, to read the directory the server resolved for itself. + [JsonPropertyName("configDir")] + public string? ConfigDir { get; set; } } -/// Active server-driven promotion for a model, including its discount and optional expiry. +/// A repository's settings as stored, together with any diagnostic raised while reading them. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class ModelBillingPromo +internal sealed class UserSettingsLoadRepoResult { - /// Percentage discount (0-100) applied while the promotion is active. May be fractional. - [JsonPropertyName("discountPercent")] - public double? DiscountPercent { get; set; } - - /// UTC ISO 8601 timestamp marking when the promotion ends. Optional: an open-ended promotion omits this field. When present, the API only surfaces a promo whose expiry parses and is in the future, so consumers should treat a past value as expired. - [JsonPropertyName("endsAt")] - public string? EndsAt { get; set; } - - /// Stable identifier for the promotion campaign. - [JsonPropertyName("id")] - public string? Id { get; set; } + /// The repository's settings document, keyed by setting name, with each value carried verbatim. Null when the repository declares no settings. + [JsonPropertyName("settings")] + public IDictionary? Settings { get; set; } - /// 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; } + /// Why part of the document was ignored, as a single human-readable string joining every diagnostic with `; `. Null when the document loaded cleanly. The settings that did load are still reported, so a consumer should surface this and continue rather than treat it as a failure. + [JsonPropertyName("warning")] + public string? Warning { 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; } +/// Selects the repository whose checked-in settings to read. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class UserSettingsLoadRepoRequest +{ + /// Absolute path of the repository root whose settings to read. When this is the user's home directory, the Claude-compatible configuration is skipped, so a home-directory session does not inherit a personal config as if it were repository policy. + [JsonPropertyName("repoRoot")] + public string RepoRoot { get; set; } = string.Empty; } -/// Long context tier pricing (available for models with extended context windows). +/// The settings a consumer should act on inside a repository, together with any diagnostic raised while reading the repository layer. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class ModelBillingTokenPricesLongContext +internal sealed class UserSettingsLoadAndMergeRepoResult { - /// Use cacheReadPrice instead. AI Credits cost per billing batch of cached tokens. - [EditorBrowsable(EditorBrowsableState.Never)] -#if NET5_0_OR_GREATER - [Obsolete("This member is deprecated and will be removed in a future version.", DiagnosticId = "GHCP001")] -#else - [Obsolete("This member is deprecated and will be removed in a future version.")] -#endif - [JsonPropertyName("cachePrice")] - public double? CachePrice { get; set; } + /// The merged settings document, keyed by setting name. Equals the supplied user layer when the repository declares no settings. + [JsonPropertyName("settings")] + public IDictionary Settings { get => field ??= new Dictionary(); set; } - /// AI Credits cost per billing batch of cached (read) tokens. - [JsonPropertyName("cacheReadPrice")] - public double? CacheReadPrice { get; set; } + /// Why part of the repository document was ignored, as a single human-readable string joining every diagnostic with `; `. Null when it loaded cleanly. The settings that did load are still merged, so a consumer should surface this and continue rather than treat it as a failure. + [JsonPropertyName("warning")] + public string? Warning { get; set; } +} - /// AI Credits cost per billing batch of 1-hour cache-write (cache creation) tokens. - [JsonPropertyName("cacheWrite1hPrice")] - public double? CacheWrite1hPrice { get; set; } +/// A repository to read settings from, and the user layer they are merged over. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class UserSettingsLoadAndMergeRepoRequest +{ + /// Absolute path of the repository root whose settings to read. When this is the user's home directory, the Claude-compatible configuration is skipped, so a home-directory session does not inherit a personal config as if it were repository policy. + [JsonPropertyName("repoRoot")] + public string RepoRoot { get; set; } = string.Empty; - /// AI Credits cost per billing batch of cache-write (cache creation) tokens. - [JsonPropertyName("cacheWritePrice")] - public double? CacheWritePrice { get; set; } + /// The user's own settings, keyed by setting name. The repository layer is merged over this; a repository may only add to the collection settings it is allowed to influence, so a user setting it does not name survives unchanged. + [JsonPropertyName("userSettings")] + public IDictionary UserSettings { get => field ??= new Dictionary(); set; } +} - /// 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. - [EditorBrowsable(EditorBrowsableState.Never)] -#if NET5_0_OR_GREATER - [Obsolete("This member is deprecated and will be removed in a future version.", DiagnosticId = "GHCP001")] -#else - [Obsolete("This member is deprecated and will be removed in a future version.")] -#endif - [JsonPropertyName("contextMax")] - public long? ContextMax { get; set; } +/// The on-disk location of the user's settings document. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class UserSettingsPathResult +{ + /// Absolute path to the settings file this runtime reads and writes. Normally `settings.json` under the Copilot configuration directory; an extensionless legacy `settings` file is reported instead when it exists and `settings.json` does not. + [JsonPropertyName("path")] + public string Path { get; set; } = string.Empty; +} - /// AI Credits cost per billing batch of input tokens. - [JsonPropertyName("inputPrice")] - public double? InputPrice { get; set; } +/// The user's stored settings, together with any diagnostic raised while reading them. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class UserSettingsLoadWithWarningResult +{ + /// The stored user settings document, keyed by setting name, with each value carried verbatim. Empty when the file could not be read at all. + [JsonPropertyName("settings")] + public IDictionary Settings { get => field ??= new Dictionary(); set; } - /// Prompt token budget for the long context tier. The total context window is this value plus the model's max_output_tokens. - [JsonPropertyName("maxPromptTokens")] - public long? MaxPromptTokens { get; set; } + /// Why part of the document was ignored, phrased for display to the user. Null when the document loaded cleanly. The settings that did load are still reported, so a consumer may surface this and continue rather than treat it as a failure. + [JsonPropertyName("warning")] + public string? Warning { get; set; } +} - /// AI Credits cost per billing batch of output tokens. - [JsonPropertyName("outputPrice")] - public double? OutputPrice { get; set; } +/// The user layer folded over the managed layer. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class UserSettingsMergeManagedAndUserResult +{ + /// The merged settings. An empty object when neither layer was supplied, so an unmanaged host with no user settings governs nothing. + [JsonPropertyName("settingsJson")] + public JsonElement SettingsJson { get; set; } } -/// Token-level pricing information for this model. +/// The managed (enterprise policy) layer and the user layer to fold together. The field names carry the layer identity: nothing about a settings object identifies which layer produced it, so a generic base/override pair would let a caller transpose them and silently resolve the governance keys in the user's favour. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class ModelBillingTokenPrices +internal sealed class UserSettingsMergeManagedAndUserParams { - /// Number of tokens per standard billing batch. - [JsonPropertyName("batchSize")] - public long? BatchSize { get; set; } + /// The managed layer. Its governance keys win, and the per-entry `enabledPlugins` and `extraKnownMarketplaces` control maps are stamped back from it after the union. Omitted when the host is unmanaged. + [JsonPropertyName("managed")] + public JsonElement? Managed { get; set; } - /// Use cacheReadPrice instead. AI Credits cost per billing batch of cached tokens. - [EditorBrowsable(EditorBrowsableState.Never)] -#if NET5_0_OR_GREATER - [Obsolete("This member is deprecated and will be removed in a future version.", DiagnosticId = "GHCP001")] -#else - [Obsolete("This member is deprecated and will be removed in a future version.")] -#endif - [JsonPropertyName("cachePrice")] - public double? CachePrice { get; set; } + /// The user's own settings layer. Wins on every key the managed layer does not govern. Omitted when the user has no settings yet. + [JsonPropertyName("user")] + public JsonElement? User { get; set; } +} - /// AI Credits cost per billing batch of cached (read) tokens. - [JsonPropertyName("cacheReadPrice")] - public double? CacheReadPrice { get; set; } +/// The repository layer folded into the user layer. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class UserSettingsMergeRepoValueResult +{ + /// The merged settings, with the per-entry managed control maps re-stamped from the runtime's ambient managed policy. + [JsonPropertyName("settingsJson")] + public JsonElement SettingsJson { get; set; } +} - /// AI Credits cost per billing batch of 1-hour cache-write (cache creation) tokens. - [JsonPropertyName("cacheWrite1hPrice")] - public double? CacheWrite1hPrice { get; set; } +/// The user layer and the repository layer to fold together. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class UserSettingsMergeRepoValueParams +{ + /// The repository's checked-in settings layer. + [JsonPropertyName("repoSettings")] + public JsonElement RepoSettings { get; set; } - /// AI Credits cost per billing batch of cache-write (cache creation) tokens. - [JsonPropertyName("cacheWritePrice")] - public double? CacheWritePrice { get; set; } + /// The user's settings, already resolved against any managed layer. + [JsonPropertyName("userSettings")] + public JsonElement UserSettings { get; set; } +} - /// Use maxPromptTokens instead. Prompt token budget for the default tier. The total context window is this value plus the model's max_output_tokens. - [EditorBrowsable(EditorBrowsableState.Never)] -#if NET5_0_OR_GREATER - [Obsolete("This member is deprecated and will be removed in a future version.", DiagnosticId = "GHCP001")] -#else - [Obsolete("This member is deprecated and will be removed in a future version.")] -#endif - [JsonPropertyName("contextMax")] - public long? ContextMax { get; set; } +/// The outcome of the one-time staff update-channel migration. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class UserSettingsMigrateStaffUpdateChannelOnceResult +{ + /// When the migration was recorded as done, as an ISO-8601 timestamp. Absent only for `ineligible`, which records nothing. + [JsonPropertyName("completedAt")] + public string? CompletedAt { get; set; } - /// AI Credits cost per billing batch of input tokens. - [JsonPropertyName("inputPrice")] - public double? InputPrice { get; set; } + /// What the migration did. Only `migrated` means the persisted settings changed. + [JsonPropertyName("status")] + public StaffUpdateChannelMigrationStatus Status { get; set; } +} - /// Long context tier pricing (available for models with extended context windows). - [JsonPropertyName("longContext")] - public ModelBillingTokenPricesLongContext? LongContext { get; set; } +/// The filesystem and environment facts a user-settings file is resolved from. Passed explicitly rather than read from the runtime's own process, so a host whose configuration directory differs from its process environment resolves the file it means. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class UserSettingsHostContext +{ + /// The Copilot configuration directory, when the host set one. Takes precedence over `COPILOT_HOME` and the home directory. + [JsonPropertyName("configDir")] + public string? ConfigDir { get; set; } - /// Prompt token budget for the default tier. The total context window is this value plus the model's max_output_tokens. - [JsonPropertyName("maxPromptTokens")] - public long? MaxPromptTokens { get; set; } + /// The host's environment variables. `COPILOT_HOME` is read from here, as are the variables a settings hook may expand. + [JsonPropertyName("environment")] + public IDictionary Environment { get => field ??= new Dictionary(); set; } - /// AI Credits cost per billing batch of output tokens. - [JsonPropertyName("outputPrice")] - public double? OutputPrice { get; set; } + /// The user's home directory, used when neither a configuration directory nor `COPILOT_HOME` is set. + [JsonPropertyName("homeDirectory")] + public string HomeDirectory { get; set; } = string.Empty; } -/// Billing information. +/// The outcome of the one-time staff model-selection reset. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class ModelBilling +internal sealed class UserSettingsResetStaffModelSelectionOnceResult { - /// 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. - [JsonPropertyName("discountPercent")] - public int? DiscountPercent { get; set; } + /// The cutoff this reset is gated on, as an ISO-8601 timestamp. A recorded timestamp at or after it means the reset is already done. + [JsonPropertyName("cutoff")] + public string Cutoff { get; set; } = string.Empty; - /// Billing cost multiplier relative to the base rate. - [JsonPropertyName("multiplier")] - public double? Multiplier { get; set; } + /// Whether the persisted model selection was cleared. Only `true` means the settings document changed and the host should adopt the returned settings. + [JsonPropertyName("didReset")] + public bool DidReset { get; set; } - /// Active server-driven promotion for this model, if any. Present when the model is being promoted with a discount, which may be time-boxed or open-ended. - [JsonPropertyName("promo")] - public ModelBillingPromo? Promo { get; set; } + /// The timestamp the host should now record, as an ISO-8601 timestamp. Carries the caller's own value back unchanged when nothing was reset. + [JsonPropertyName("resetAt")] + public string? ResetAt { get; set; } - /// Token-level pricing information for this model. - [JsonPropertyName("tokenPrices")] - public ModelBillingTokenPrices? TokenPrices { get; set; } + /// The settings document as it stands after the reset, keyed by setting name. Present only when `didReset` is true. + [JsonPropertyName("settings")] + public IDictionary? Settings { get; set; } } -/// Vision-specific limits. +/// Selects the configuration directory to run the one-time staff model-selection reset against, and supplies the caller's record of whether it has already run. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class ModelCapabilitiesLimitsVision +internal sealed class UserSettingsResetStaffModelSelectionOnceRequest { - /// Maximum image size in bytes. - [JsonPropertyName("max_prompt_image_size")] - public long MaxPromptImageSize { get; set; } + /// The configuration directory, home directory and environment to resolve the settings and global-state documents from. A host that scopes a session to its own Copilot home must pass that directory here, or the reset is evaluated against the server's own home instead. + [JsonPropertyName("context")] + public UserSettingsHostContext Context { get => field ??= new(); set; } - /// Maximum number of images per prompt. - [JsonPropertyName("max_prompt_images")] - public long MaxPromptImages { get; set; } + /// Whether the signed-in account is a staff account. The reset applies only to staff, so anything other than `true` returns `didReset: false` without touching the settings document. + [JsonPropertyName("staff")] + public bool? Staff { get; set; } - /// MIME types the model accepts. - [JsonPropertyName("supported_media_types")] - public IList SupportedMediaTypes { get => field ??= []; set; } + /// When the caller last recorded this reset, as an ISO-8601 timestamp. A timestamp at or after the cutoff means the reset already ran and is returned unchanged. + [JsonPropertyName("staffModelResetAt")] + public string? StaffModelResetAt { get; set; } } -/// Token limits for prompts, outputs, and context window. +/// The unrecognized top-level keys found in the user settings document, already phrased for display. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class ModelCapabilitiesLimits +internal sealed class UserSettingsUnknownKeysWarningResult { - /// Maximum total context window size in tokens. - [JsonPropertyName("max_context_window_tokens")] - public long? MaxContextWindowTokens { get; 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 ModelCapabilitiesLimitsVision? Vision { get; set; } + /// The warning to show the user, naming the unrecognized keys and the file they were found in. Absent when every top-level key is recognized, and also when the document is missing or could not be read, because an unknown-key list taken from a document the runtime could not load would name keys that may not be there. + [JsonPropertyName("warning")] + public string? Warning { get; set; } } -/// Feature flags indicating what the model supports. +/// Selects the configuration directory whose user settings document should be inspected for unrecognized top-level keys. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class ModelCapabilitiesSupports +internal sealed class UserSettingsUnknownKeysWarningRequest { - /// Resolved Anthropic adaptive-thinking capability — unsupported / optional / required / adaptive_only. 'required' models reject thinking.type='enabled' with HTTP 400 but still accept 'disabled' (e.g. opus-4.7/4.8/5, sonnet-5); 'adaptive_only' models accept nothing but 'adaptive' (e.g. fable, mythos). - [JsonPropertyName("adaptive_thinking")] - public AdaptiveThinkingSupport? AdaptiveThinking { get; set; } + /// The configuration directory, home directory and environment to resolve the settings document from. A host that scopes a session to its own Copilot home must pass that directory here, or the server's own home is inspected instead. + [JsonPropertyName("context")] + public UserSettingsHostContext Context { get => field ??= new(); set; } +} - /// Whether this model supports reasoning effort configuration. - [JsonPropertyName("reasoningEffort")] - public bool? ReasoningEffort { get; set; } +/// The overrides and host context to resolve effective runtime settings from. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class UserSettingsBuildRuntimeRequest +{ + /// The environment to read settings-bearing variables from. Passed explicitly rather than read from the server process so a host can resolve settings for an environment it is about to create rather than the one it runs in. + [JsonPropertyName("environment")] + public IDictionary Environment { get => field ??= new Dictionary(); set; } - /// Whether the model supports provider-native thinking. Independent of configurable reasoning effort; omission means unknown. - [JsonPropertyName("thinking")] - public bool? Thinking { get; set; } + /// Settings that take precedence over every document and environment layer, such as the values a host derived from its own command line. Keyed by setting name, with each value carried verbatim, so a host may pass keys this runtime does not recognize. Absent when the host contributes no overrides of its own. + [JsonPropertyName("overrides")] + public IDictionary? Overrides { get; set; } - /// Whether this model supports canonical tool calling. - [JsonPropertyName("toolCalls")] - public bool? ToolCalls { get; set; } + /// The platform to apply platform-specific defaults for, in the same spelling the host's own runtime reports. + [JsonPropertyName("platform")] + public string Platform { get; set; } = string.Empty; - /// Whether this model supports vision/image input. - [JsonPropertyName("vision")] - public bool? Vision { get; set; } + /// The directory the settings are being resolved for. This selects the repository-scoped document, so a host resolving settings for a directory other than its own process working directory must pass that directory here. + [JsonPropertyName("workingDirectory")] + public string WorkingDirectory { get; set; } = string.Empty; } -/// Model capabilities and limits. +/// The outcome of the one-time staff log-level migration. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class ModelCapabilities +internal sealed class UserSettingsMigrateStaffLogLevelOnceResult { - /// Token limits for prompts, outputs, and context window. - [JsonPropertyName("limits")] - public ModelCapabilitiesLimits? Limits { get; set; } + /// When the migration was recorded as done, as an ISO-8601 timestamp. Absent only for `ineligible`, which records nothing. + [JsonPropertyName("completedAt")] + public string? CompletedAt { get; set; } - /// Feature flags indicating what the model supports. - [JsonPropertyName("supports")] - public ModelCapabilitiesSupports? Supports { get; set; } + /// The settings document as it stands after the migration, keyed by setting name. Present only for `refreshed`, and only when the document could be re-read cleanly. + [JsonPropertyName("settings")] + public IDictionary? Settings { get; set; } + + /// What the migration did. Only `refreshed` can have changed the persisted settings. + [JsonPropertyName("status")] + public StaffLogLevelMigrationStatus Status { get; set; } } -/// A service-published message about a model, carrying a stable machine-readable code alongside human-readable text. +/// Selects the configuration directory to run the one-time staff log-level migration against, and supplies the caller's record of whether it has already run. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class ModelMessage +internal sealed class UserSettingsMigrateStaffLogLevelOnceRequest { - /// 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; + /// When the caller last recorded this migration, as an ISO-8601 timestamp. Supplying it lets the runtime answer `current` without re-running the evaluation. + [JsonPropertyName("cachedCompletedAt")] + public string? CachedCompletedAt { get; set; } - /// Human-readable message text intended for display to the user. - [JsonPropertyName("message")] - public string Message { get; set; } = string.Empty; + /// The configuration directory, home directory and environment to resolve the settings and global-state documents from. A host that scopes a session to its own Copilot home must pass that directory here, or the migration is evaluated against the server's own home instead. + [JsonPropertyName("context")] + public UserSettingsHostContext Context { get => field ??= new(); set; } } -/// Policy state (if applicable). +/// The outcome of the one-time obsolete-settings cleanup. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class ModelPolicy +internal sealed class UserSettingsMigrateObsoleteOnDiskResult { - /// Current policy state for this model. - [JsonPropertyName("state")] - public ModelPolicyState State { get; set; } - - /// Usage terms or conditions for this model. - [JsonPropertyName("terms")] - public string? Terms { get; set; } + /// What the cleanup did. Only `changed` rewrote the document. The cleaned settings are not returned here: the document is written before this replies, so `user.settings.load` reads it authoritatively. + [JsonPropertyName("status")] + public UserSettingsMigrateObsoleteOnDiskStatus Status { get; set; } } -/// Service-published warning text that hosts should display when presenting a model. +/// Selects the configuration directory whose settings document to clean. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class ModelWarningText +internal sealed class UserSettingsMigrateObsoleteOnDiskRequest { - /// 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; } + /// The configuration directory holding the settings document to clean. A host that scopes a session to its own Copilot home passes that directory here; omit it to clean the document under the home the server resolved for itself. The home directory and environment are deliberately not caller-supplied: they decide only the settings path root and the two hook-parsing flags, and those describe the process doing the reading rather than the caller. + [JsonPropertyName("configDir")] + public string? ConfigDir { get; set; } } -/// Copilot model metadata, including identifier, display name, capabilities, policy, billing, reasoning efforts, and picker categories. +/// A single user setting's effective value alongside its default, so consumers can render settings left at their default. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class Model +public sealed class UserSettingMetadata { - /// Billing information. - [JsonPropertyName("billing")] - public ModelBilling? Billing { get; set; } - - /// Model capabilities and limits. - [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; } - - /// Provider-supplied model metadata. Keys and JSON-compatible values are preserved unchanged. This is factual metadata published by the model provider; it carries no picker or UX semantics. - [JsonPropertyName("metadata")] - public IDictionary? Metadata { get; set; } - - /// Model capability category for grouping in the model picker. - [JsonPropertyName("modelPickerCategory")] - public ModelPickerCategory? ModelPickerCategory { get; set; } - - /// Relative cost tier for token-based billing users. - [JsonPropertyName("modelPickerPriceCategory")] - public ModelPickerPriceCategory? ModelPickerPriceCategory { get; set; } - - /// Display name. - [JsonPropertyName("name")] - public string Name { get; set; } = string.Empty; - - /// Policy state (if applicable). - [JsonPropertyName("policy")] - public ModelPolicy? Policy { get; set; } - - /// The model provider that produced this model, as a neutral reference (opaque id, human-readable label, and provider kind). Present on models returned by `session.model.list`, which resolves provider attribution from the session's account roster; absent on the flat `server.models.list`, which does not resolve a session roster. The model picker groups by this reference. - [JsonPropertyName("provider")] - public ModelProviderRef? Provider { 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; } - - /// Model vendor as the Copilot API reports it, for example "Anthropic" or "Azure OpenAI". Open vocabulary, passed through unchanged. It can name the vendor that serves the model instead of the one that built it, or a label that is not a vendor, such as "Experimental". Absent when the Copilot API reports no vendor. - [JsonPropertyName("vendor")] - public string? Vendor { get; set; } + /// The centrally-known default for this setting (null when no default is registered). + [JsonPropertyName("default")] + public JsonElement Default { 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; } + /// 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; } - /// 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; } + /// The effective value: the user's value if set, otherwise the default. + [JsonPropertyName("value")] + public JsonElement Value { get; set; } } -/// List of Copilot models available to the resolved user, including capabilities and billing metadata. +/// Per-key metadata for every known user setting in settings.json, including settings left at their default. Excludes repository- and enterprise-managed overrides. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class ModelList +public sealed class UserSettingsGetResult { - /// List of available models with full metadata. - [JsonPropertyName("models")] - public IList Models { get => field ??= []; set; } + /// 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; } } -/// RPC data type for ModelsList operations. +/// On-disk locations of the settings files this runtime reads. A path is reported whether or not the file exists. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class ModelsListRequest +internal sealed class UserSettingsGetPathsResult { - /// 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; } + /// The repository's local, untracked settings file, or null when no repository root was supplied. + [JsonPropertyName("repoLocalSettings")] + public string? RepoLocalSettings { get; set; } - /// Opaque account identifier returned by `account.getAllUsers`. When omitted, the current account is used. - [JsonPropertyName("selectionId")] - public string? SelectionId { get; set; } + /// The repository's checked-in settings file, or null when no repository root was supplied. + [JsonPropertyName("repoSettings")] + public string? RepoSettings { get; set; } + + /// The user settings file: `settings.json` under the resolved Copilot home. This is the file `user.settings.get`, `user.settings.load`, and `user.settings.set` read and write. + [JsonPropertyName("userSettings")] + public string UserSettings { get; set; } = string.Empty; } -/// A well-known model in the runtime's built-in catalog. +/// Selects which settings file paths to resolve. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class BuiltInModelCatalogEntry +internal sealed class UserSettingsGetPathsRequest { - /// 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; + /// Absolute path of a repository root. Supply it to also resolve that repository's two settings file paths; omit it to resolve only the user path. + [JsonPropertyName("repoRoot")] + public string? RepoRoot { get; set; } } -/// The running runtime's complete catalog of well-known built-in model IDs, including supported models and additional IDs with built-in metadata. +/// 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(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class BuiltInModelCatalog +internal sealed class UserSettingsSetRequest { - /// Built-in model entries. - [JsonPropertyName("models")] - public IList Models { get => field ??= []; set; } + /// Partial user settings to write, as a free-form object keyed by setting name. + [JsonPropertyName("settings")] + public JsonElement Settings { get; set; } } -/// Whether this host can run one sandbox policy feature. A session whose effective policy uses an unsupported feature fails each sandboxed command with `reason`, except `filesystem_enumeration`, whose absence degrades sandboxed PowerShell instead of failing it. +/// Top-level user-setting keys grouped by who may govern them. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class SandboxHostCapability +internal sealed class UserSettingsGovernanceKeysResult { - /// The policy feature, as an extensible string: ignore names you do not recognize. Known values: `network` (sandboxed commands can reach the network; on Linux this needs the tooling for Bubblewrap's private network namespace, such as slirp4netns), `network_filtering` (host rules and the sandbox proxy; on Linux this needs the same tooling as `network`; on Windows it needs Process Security Environment 1.1 host-loopback support or MXC's PSEC 1.0-only proxy-loopback compatibility capability, and a policy that uses it must also set `network.allowLocalNetwork`; compatibility applies only to an explicit identity-less runtime proxy, not general host-loopback access, and other policy restrictions still apply), `denied_paths` (native enforcement of `filesystem.deniedPaths`), `shell` (shell commands inside the sandbox), and `filesystem_enumeration` (enumerate-only filesystem grants; on Windows this needs Process Security Environment 1.1 filesystem enumeration support, and without it sandboxed PowerShell still runs but cannot resolve its current location; other platforms always report it). - [JsonPropertyName("name")] - public string Name { get; set; } = string.Empty; + /// Top-level keys a repository may override through a Claude-compatible repository config. + [JsonPropertyName("claudeRepo")] + public IList ClaudeRepo { get => field ??= []; set; } - /// Human-readable reason and remedy when the feature is unsupported, such as a package to install or an OS update. Present only when `supported` is false. - [JsonPropertyName("reason")] - public string? Reason { get; set; } + /// Top-level keys an enterprise policy may fix through managed settings. + [JsonPropertyName("managed")] + public IList Managed { get => field ??= []; set; } - /// Whether this host can run the feature. - [JsonPropertyName("supported")] - public bool Supported { get; set; } + /// Top-level keys a repository may override through `.github/copilot/settings.json` or `.github/copilot/settings.local.json`. + [JsonPropertyName("repo")] + public IList Repo { get => field ??= []; set; } } -/// Whether the host running this runtime can run the command sandbox. The runtime checks `supported` once per process. A capability answer can change while the process runs, for example after the user installs a missing package. +/// Outcome of an atomic user or repository settings edit. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class SandboxHostSupport +internal sealed class SettingsEditResult { - /// Sandbox policy features whose availability varies between hosts that can run the backend. Empty when `supported` is false, because no feature can run without a backend. Later runtimes can add entries; ignore an entry whose `name` you do not recognize. - [JsonPropertyName("capabilities")] - public IList Capabilities { get => field ??= []; set; } - - /// Human-readable reason the sandbox cannot run on this host. Present only when `supported` is false. - [JsonPropertyName("reason")] - public string? Reason { get; set; } - - /// Whether a process-containment backend is usable on this host: Seatbelt on macOS, Bubblewrap on Linux, or ProcessContainer on Windows. - [JsonPropertyName("supported")] - public bool Supported { get; set; } + /// Changed, unchanged or invalid existing file. Rejected input, validation failures and I/O errors remain RPC errors. + [JsonPropertyName("status")] + public SettingsEditStatus Status { get; set; } } -/// Status of the persistent certificate authority of the sandbox credential proxy. +/// One atomic edit of a canonical setting in settings.json. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class SandboxProxyCaStatus +internal sealed class UserSettingsEditRequest { - /// Whether this process can add the certificate authority to OS trust without credentials from a different user. False where OS trust is unsupported, and on Windows when the process cannot elevate itself to write the machine trust store. When false, do not offer to set up the certificate authority. - [JsonPropertyName("canInstall")] - public bool CanInstall { get; set; } + /// Nonempty canonical path segments of the setting to edit, starting with its top-level key. + [JsonPropertyName("canonicalPath")] + public IList CanonicalPath { get => field ??= []; set; } - /// Human-readable reason for the state. On `installed` or `notInstalled`, present only when the certificate authority must be rotated, and then says why. - [JsonPropertyName("detail")] - public string? Detail { get; set; } + /// Configuration directory to edit. Omitted or empty resolves the server's own home, as in `user.settings.loadForConfigDir`. + [JsonPropertyName("configDir")] + public string? ConfigDir { get; set; } - /// The state of the certificate authority. - [JsonPropertyName("state")] - public SandboxProxyCaState State { get; set; } + /// Set a scalar, add or remove a string list item, or remove the setting. The handler rejects a missing or incompatible value. + [JsonPropertyName("operation")] + public SettingsEditOperation Operation { get; set; } + + /// Required boolean, string or number for set, required string for add-item and remove-item, and forbidden for remove. The runtime rejects all other JSON values. + [JsonPropertyName("value")] + public JsonElement? Value { 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. +/// Settings object with canonical key names. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class SandboxConfigAuth +internal sealed class UserSettingsNormalizeResult { - /// Whether to authenticate sandboxed gh through the local masking proxy. The child receives a fake GH_TOKEN; its real value is substituted only at github.com, api.github.com and uploads.github.com (github.com because gh repo clone authenticates git through gh auth git-credential). The repository's GitHub account takes precedence over the Copilot login. Default: false (opt-in). - [JsonPropertyName("gh")] - public bool? Gh { get; set; } - - /// Whether to authenticate sandboxed HTTPS git through the local masking proxy. The child receives a fake `http.<url>.extraheader`; the real Authorization header is substituted only at its original HTTPS host, port, and repository path scope. github.com uses the Copilot token; other forges use credentials resolved from the user's own helper on the host. Default: false (opt-in). - [JsonPropertyName("git")] - public bool? Git { get; set; } + /// The same settings with every legacy or snake_case key renamed to its canonical spelling. + [JsonPropertyName("settings")] + public JsonElement Settings { get; set; } } -/// Destinations authorized to receive one masked environment credential. +/// Settings object whose key names should be canonicalized. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class SandboxMaskedEnvVar +internal sealed class UserSettingsNormalizeRequest { - /// Nonempty list of HTTPS injection hostnames or *.example.com patterns. Bare * is not accepted. These grants never override the sandbox network policy. Values in plaintext HTTP requests, URLs, bodies, encoded credentials, and signed requests are not substituted. - [JsonPropertyName("injectHosts")] - public IList InjectHosts { get => field ??= []; set; } + /// Free-form settings object, possibly using legacy or snake_case key names. + [JsonPropertyName("settings")] + public JsonElement Settings { get; set; } } -/// Whole-value environment credential masking for sandboxed children. +/// The settings layer a problem was found in. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class SandboxCredentialsConfig +public sealed class UserSettingsProblemLocation { - /// Environment variable names and their HTTPS injection destinations. Absent variables stay absent. No real values or sentinels are stored in this map. - [JsonPropertyName("envVars")] - public IDictionary EnvVars { get => field ??= new Dictionary(); set; } -} + /// Absolute path of the file backing this layer. + [JsonPropertyName("filePath")] + public string FilePath { get; set; } = string.Empty; -/// macOS seatbelt experimental options. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class SandboxConfigUserPolicyExperimentalSeatbelt -{ - /// Whether the macOS seatbelt profile may access the keychain. - [JsonPropertyName("keychainAccess")] - public bool? KeychainAccess { get; set; } -} + /// Which layer this is: `user`, `repo`, or `local`. + [JsonPropertyName("kind")] + public string Kind { get; set; } = string.Empty; -/// Platform-specific experimental policy fields. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class SandboxConfigUserPolicyExperimental -{ - /// macOS seatbelt experimental options. - [JsonPropertyName("seatbelt")] - public SandboxConfigUserPolicyExperimentalSeatbelt? Seatbelt { get; set; } + /// The caller-supplied display label for the layer. + [JsonPropertyName("label")] + public string Label { get; set; } = string.Empty; } -/// Filesystem rules to merge into the base policy. +/// One setting that will not take effect, and why. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class SandboxConfigUserPolicyFilesystem +public sealed class UserSettingsProblem { - /// Whether to clear the policy when the session exits. - [JsonPropertyName("clearPolicyOnExit")] - public bool? ClearPolicyOnExit { get; set; } + /// Coarse grouping for the problem, for a caller that presents problems in sections. + [JsonPropertyName("category")] + public string Category { get; set; } = string.Empty; - /// Paths explicitly denied. - [JsonPropertyName("deniedPaths")] - public IList? DeniedPaths { get; set; } + /// Why the setting will not take effect. Never contains the setting's value, which may be a token or another secret. + [JsonPropertyName("explanation")] + public string Explanation { get; set; } = string.Empty; - /// Paths granted read-only access. - [JsonPropertyName("readonlyPaths")] - public IList? ReadonlyPaths { get; set; } + /// Stable identifier for this problem within the reported set, so a caller can track a selection across refreshes. + [JsonPropertyName("id")] + public string Id { get; set; } = string.Empty; - /// Paths granted read/write access. - [JsonPropertyName("readwritePaths")] - public IList? ReadwritePaths { get; set; } + /// What is wrong in machine-readable form, for a caller that wants to branch rather than display. + [JsonPropertyName("kind")] + public string Kind { get; set; } = string.Empty; + + /// The offending key as written in the file, when the problem is attributable to a single key. Absent when the whole file is at fault. + [JsonPropertyName("rawKey")] + public string? RawKey { get; set; } + + /// The settings layer the problem was found in. + [JsonPropertyName("source")] + public UserSettingsProblemLocation Source { get => field ??= new(); set; } } -/// HTTP proxy configuration for sandboxed traffic. +/// The settings that will not take effect across the given layers. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class SandboxConfigUserPolicyNetworkProxy +internal sealed class UserSettingsProblemsResult { - /// 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; } + /// Each setting that will not take effect, and why. Empty when every layer is healthy. + [JsonPropertyName("problems")] + public IList? Problems { get; set; } } -/// Network rules to merge into the base policy. +/// One already-loaded settings layer to diagnose. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class SandboxConfigUserPolicyNetwork +public sealed class UserSettingsProblemSource { - /// Hosts allowed through the built-in sandbox proxy. A non-empty list denies unmatched hosts; an absent or empty list allows all hosts not blocked. Supports exact hostnames, IP addresses, and *.example.com for strict subdomains. Host rules do not override the outbound or local-network toggles. - [JsonPropertyName("allowedHosts")] - public IList? AllowedHosts { get; set; } + /// The layer's settings exactly as loaded, unnormalized. Empty when the file is missing or failed to parse. + [JsonPropertyName("config")] + public IDictionary? Config { get; set; } - /// Whether traffic to local/loopback addresses is allowed. - [JsonPropertyName("allowLocalNetwork")] - public bool? AllowLocalNetwork { get; set; } + /// Absolute path of the file backing this layer. + [JsonPropertyName("filePath")] + public string FilePath { get; set; } = string.Empty; - /// Whether outbound network traffic is allowed at all. - [JsonPropertyName("allowOutbound")] - public bool? AllowOutbound { get; set; } + /// True when the file itself failed to parse, so its keys could not be read at all. + [JsonPropertyName("invalid")] + public bool? Invalid { get; set; } - /// Hosts denied by the built-in sandbox proxy. Deny rules take precedence over allowedHosts. A domain also denies all its subdomains. IP addresses match exactly; *.example.com matches strict subdomains, and * denies every host. - [JsonPropertyName("blockedHosts")] - public IList? BlockedHosts { get; set; } + /// Which layer this is: `user`, `repo`, or `local`. + [JsonPropertyName("kind")] + public string Kind { get; set; } = string.Empty; - /// HTTP(S) proxy for sandboxed traffic. This is the built-in local proxy's upstream: every sandboxed command reaches it through a loopback listener, so credentials stay in the runtime and never reach the child. On Windows the sandbox also needs local network access, because it reaches that listener over host loopback. Configure credentials in the separate username/password fields. The transient local listener URL is never persisted. - [JsonPropertyName("proxy")] - public SandboxConfigUserPolicyNetworkProxy? Proxy { get; set; } + /// Caller-chosen display label for the layer, echoed back on each problem so a caller can group by layer without re-deriving its own naming. + [JsonPropertyName("label")] + public string Label { get; set; } = string.Empty; } -/// macOS seatbelt-specific options. +/// The settings layers to diagnose. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class SandboxConfigUserPolicySeatbelt +internal sealed class UserSettingsProblemsRequest { - /// Whether the macOS seatbelt profile may access the keychain. - [JsonPropertyName("keychainAccess")] - public bool? KeychainAccess { get; set; } + /// Layers to diagnose, in the precedence order the caller applies them. + [JsonPropertyName("sources")] + public IList? Sources { get; set; } } -/// User-managed sandbox policy fragment merged into the auto-discovered base policy. +/// One problem found while validating a setting value. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class SandboxConfigUserPolicy +public sealed class UserSettingsValidationIssue { - /// 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; } + /// Human-readable description of the problem. + [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; } + /// Where in the settings tree the problem sits, as object keys and array indices. The first element is the top-level key; a whole-value problem is reported as that single key. + [JsonPropertyName("path")] + public IList Path { get => field ??= []; set; } } -/// Resolved sandbox configuration. +/// Outcome of validating and writing one top-level setting subtree. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class SandboxConfig +internal sealed class UserSettingsApplyTopKeyResult { - /// Whether to auto-add the current working directory to readwritePaths. Default: true. - [JsonPropertyName("addCurrentWorkingDirectory")] - public bool? AddCurrentWorkingDirectory { get; set; } - - /// Whether the agent may request that an individual command run outside the sandbox, which the host then approves or denies through the usual permission flow. A host capability flag rather than part of the policy: it is stripped from the effective spawn policy and only has an effect while `enabled` is true. Fail-closed, unlike the opt-out flags on this object: omitting it offers no bypass. Default: false (opt-in). - [JsonPropertyName("allowBypass")] - public bool? AllowBypass { 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; } + /// Why the write failed, present only when `status` is `write-error`. + [JsonPropertyName("error")] + public string? Error { get; set; } - /// Opt-in whole-value environment masking for sandboxed shell, MCP, and LSP children. Configured names get random sentinels; the local proxy substitutes them only in HTTPS request headers at their injection hosts. Approved bypasses skip masking and the sandbox proxy, so bypassed shells may receive the real environment values. Disabled or explicitly opted-out routes are not protected. No credential values are stored in this configuration. - [JsonPropertyName("credentials")] - public SandboxCredentialsConfig? Credentials { get; set; } + /// Problems found, present only when `status` is `validation-error`. + [JsonPropertyName("issues")] + public IList? Issues { get; set; } - /// Whether sandboxing is enabled for the session. - [JsonPropertyName("enabled")] - public bool Enabled { get; set; } + /// `ok` when the value was written, `validation-error` when it was rejected before any write, `write-error` when validation passed but the write failed. + [JsonPropertyName("status")] + public string Status { get; set; } = string.Empty; +} - /// The `sandboxLspServers` counterpart of `managedMcpRoutingLocked`. - [JsonInclude] - [JsonPropertyName("managedLspRoutingLocked")] - internal bool? ManagedLspRoutingLocked { get; set; } +/// A single top-level setting subtree to validate and write. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class UserSettingsApplyTopKeyRequest +{ + /// Canonical path of the setting the caller is editing. Currently informational only. + [JsonPropertyName("canonicalPath")] + public IList CanonicalPath { get => field ??= []; set; } - /// Set by the runtime when a managed policy forced `sandboxMcpServers` on and took the local opt-out away. Provenance rather than policy: it lets a sandbox startup failure point at the administrator instead of a setting the next managed merge would override, and it is ignored when comparing two configs for change. Only the managed merge may set it; a caller-supplied value is stripped. - [JsonInclude] - [JsonPropertyName("managedMcpRoutingLocked")] - internal bool? ManagedMcpRoutingLocked { get; set; } + /// Records that the user chose an update channel explicitly, so the one-time staff channel migration stops reapplying. Honored only for the `autoUpdatesChannel` key. + [JsonPropertyName("completeStaffUpdateChannelMigration")] + public bool? CompleteStaffUpdateChannelMigration { get; set; } - /// Whether language servers the session launches are confined by the sandbox. Only an explicit `false` opts out. Ignored while `enabled` is false. Default: true (enabled by default; set to false to opt out). - [JsonPropertyName("sandboxLspServers")] - public bool? SandboxLspServers { get; set; } + /// True to remove the top-level key instead of writing it. + [JsonPropertyName("delete")] + public bool? Delete { get; set; } - /// Whether MCP servers the session launches are confined by the sandbox. Only an explicit `false` opts out; doing so also lets remote-MCP egress leave the sandbox, so the flag and `enabled` are always read together. Ignored while `enabled` is false. Default: true (enabled by default; set to false to opt out). - [JsonPropertyName("sandboxMcpServers")] - public bool? SandboxMcpServers { get; set; } + /// Top-level setting name to write. + [JsonPropertyName("topKey")] + public string TopKey { get; set; } = string.Empty; - /// User-managed sandbox policy fragment merged into the auto-discovered base policy. - [JsonPropertyName("userPolicy")] - public SandboxConfigUserPolicy? UserPolicy { get; set; } + /// Value to write for the whole top-level key. Omitted when `delete` is true. Explicit null is preserved and validated as null. + [JsonPropertyName("value")] + public JsonElement? Value { get; set; } } -/// Identifies the credential hosts that the persistent certificate authority of the sandbox credential proxy must cover. The runtime always adds the hosts from the saved user settings. +/// The whole content to store in the user settings file. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SandboxProxyCaRequest +internal sealed class UserSettingsWriteAllRequest { - /// The sandbox configuration that the host gives its sessions. The runtime reads the credential hosts from `auth` and `credentials`; it ignores `enabled` and the other fields. - [JsonPropertyName("sandboxConfig")] - public SandboxConfig? SandboxConfig { get; set; } + /// The settings object to store. It replaces the file's entire content, so a key absent here is removed. + [JsonPropertyName("settings")] + public JsonElement Settings { get; set; } } -/// Result of creating the persistent certificate authority of the sandbox credential proxy. +/// The stored value of the user setting after the merge. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class SandboxProxyCaCreateResult +internal sealed class UserSettingsUpdateResult { - /// Absolute path of the public certificate of the certificate authority, in PEM format. - [JsonPropertyName("certificatePath")] - public string CertificatePath { get; set; } = string.Empty; + /// The key's stored value after the write: the merged object when both the stored value and the supplied value are objects, otherwise the supplied value. + [JsonPropertyName("value")] + public JsonElement Value { get; set; } } -/// Built-in tool metadata with identifier, optional namespaced name, description, input-parameter schema, and usage instructions. +/// Names the user setting to merge into, and the partial object to merge. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class Tool +internal sealed class UserSettingsUpdateRequest { - /// Description of what the tool does. - [JsonPropertyName("description")] - public string Description { get; set; } = string.Empty; - - /// Optional instructions for how to use this tool effectively. - [JsonPropertyName("instructions")] - public string? Instructions { get; set; } - - /// Tool identifier (e.g., "bash", "grep", "str_replace_editor"). - [JsonPropertyName("name")] - public string Name { get; set; } = string.Empty; + /// Copilot configuration directory to write the settings document in, taking precedence over the server's own `COPILOT_HOME` and default home. Omit it, or pass an empty string, to use the directory the server resolved for itself. + [JsonPropertyName("configDir")] + public string? ConfigDir { get; set; } - /// Optional namespaced name for declarative filtering (e.g., "playwright/navigate" for MCP tools). - [JsonPropertyName("namespacedName")] - public string? NamespacedName { get; set; } + /// Top-level user-setting key to merge into, such as `extensions`. + [JsonPropertyName("key")] + public string Key { get; set; } = string.Empty; - /// JSON Schema for the tool's input parameters. - [JsonPropertyName("parameters")] - public IDictionary? Parameters { get; set; } + /// Single-component name for the settings document at `<configDir>/settings/<subDir>.json`; path separators, `.` and `..` are rejected. Omit it, or pass an empty string, to use the main settings document. + [JsonPropertyName("subDir")] + public string? SubDir { get; set; } - /// Telemetry-safety policy for the tool name and input names, not input values. Treat omitted metadata as unsafe. - [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] - [JsonPropertyName("safeForTelemetry")] - public JsonElement? SafeForTelemetry { get; set; } + /// Value to store for `key`. When both the stored value and this value are objects, their fields are shallow-merged and fields this object does not name are preserved. Otherwise this value replaces the stored value. + [JsonPropertyName("value")] + public JsonElement Value { get; set; } } -/// Built-in tools available for the requested model, with their parameters and instructions. +/// Contents of settings.json as stored, for a caller about to rewrite it. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class ToolList +internal sealed class UserSettingsLoadForEditResult { - /// List of available built-in tools with metadata. - [JsonPropertyName("tools")] - public IList Tools { get => field ??= []; set; } + /// Parsed contents, present only when `status` is `ok`. + [JsonPropertyName("settings")] + public JsonElement? Settings { get; set; } + + /// `ok` when the file parsed, `missing` when there is no file yet, `invalid` when the file exists but will not parse. + [JsonPropertyName("status")] + public string Status { get; set; } = string.Empty; } -/// Optional model identifier whose tool overrides should be applied to the listing. +/// Outcome of validating one top-level setting subtree. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class ToolsListRequest +internal sealed class UserSettingsValidateResult { - /// Optional model ID — when provided, the returned tool list reflects model-specific overrides. - [JsonPropertyName("model")] - public string? Model { get; set; } + /// Problems found, empty when `ok` is true. + [JsonPropertyName("issues")] + public IList? Issues { get; set; } + + /// True when the subtree is valid. + [JsonPropertyName("ok")] + public bool Ok { get; set; } } -/// Quota usage snapshot for a Copilot quota type, including entitlement, used requests, overage, reset date, and remaining percentage. +/// A single top-level setting subtree to validate. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class AccountQuotaSnapshot +internal sealed class UserSettingsValidateRequest { - /// Number of requests included in the entitlement, or -1 for unlimited entitlements. - [JsonPropertyName("entitlementRequests")] - public long EntitlementRequests { get; set; } + /// True to validate a removal of this key. The current settings file is not read. + [JsonPropertyName("delete")] + public bool? Delete { get; set; } - /// Whether the user has an unlimited usage entitlement. - [JsonPropertyName("isUnlimitedEntitlement")] - public bool IsUnlimitedEntitlement { get; set; } - - /// Number of additional usage requests made this period. - [JsonPropertyName("overage")] - public double Overage { get; set; } + /// Top-level setting name to validate. + [JsonPropertyName("topKey")] + public string TopKey { get; set; } = string.Empty; - /// Whether additional usage is allowed when quota is exhausted. - [JsonPropertyName("overageAllowedWithExhaustedQuota")] - public bool OverageAllowedWithExhaustedQuota { get; set; } + /// Value to validate. Omitted when `delete` is true. + [JsonPropertyName("value")] + public JsonElement? Value { get; set; } +} - /// Percentage of entitlement remaining. - [JsonPropertyName("remainingPercentage")] - public double RemainingPercentage { get; set; } +/// One atomic edit of an allowed top-level repository setting. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class RepoSettingsEditRequest +{ + /// One canonical top-level repository setting that the allowlist permits. + [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; - /// Date when the quota resets (ISO 8601 string). - [JsonPropertyName("resetDate")] - public DateTimeOffset? ResetDate { get; set; } + /// Set a scalar, add or remove a string list item, or remove the setting. The handler rejects a missing or incompatible value. + [JsonPropertyName("operation")] + public SettingsEditOperation Operation { get; set; } - /// Whether usage is still permitted after quota exhaustion. - [JsonPropertyName("usageAllowedWithExhaustedQuota")] - public bool UsageAllowedWithExhaustedQuota { get; set; } + /// Absolute path of the repository root. + [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("repoRoot")] + public string RepoRoot { get; set; } = string.Empty; - /// Number of requests used so far this period. - [JsonPropertyName("usedRequests")] - public long UsedRequests { get; set; } -} + /// Shared or local repository settings file. + [JsonPropertyName("scope")] + public RepoSettingsEditScope Scope { get; set; } -/// Quota usage snapshots for the resolved user, keyed by quota type. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class AccountGetQuotaResult -{ - /// Quota snapshots keyed by type (e.g., chat, completions, premium_interactions). - [JsonPropertyName("quotaSnapshots")] - public IDictionary QuotaSnapshots { get => field ??= new Dictionary(); set; } + /// Required boolean, string or number for set, required string for add-item and remove-item, and forbidden for remove. The runtime rejects all other JSON values. + [JsonPropertyName("value")] + public JsonElement? Value { get; set; } } -/// RPC data type for AccountGetQuota operations. +/// Parsed repository settings without normalization or layering. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class AccountGetQuotaRequest +internal sealed class RepoSettingsReadRawResult { - /// 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; } + /// Stored JSON values after the existing JSONC parser and prototype-safety checks. Empty when the file is missing, blank, or invalid. + [JsonPropertyName("config")] + public JsonElement Config { get; set; } - /// Opaque account identifier returned by `account.getAllUsers`. When omitted, the current account is used. - [JsonPropertyName("selectionId")] - public string? SelectionId { get; set; } + /// True when the file exists but did not parse, so `config` is empty for that reason rather than because nothing is set. + [JsonPropertyName("invalid")] + public bool Invalid { get; set; } } -/// Authentication credentials accepted only at native protocol ingress. Runtime outputs use credential-free `AuthIdentity` metadata. -/// Polymorphic base type discriminated by type. +/// Which repository settings file to read. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -[JsonPolymorphic( - TypeDiscriminatorPropertyName = "type", - UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] -[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(AuthInfoAccount), "account")] -[JsonDerivedType(typeof(AuthInfoGhCli), "gh-cli")] -[JsonDerivedType(typeof(AuthInfoApiKey), "api-key")] -public partial class AuthInfo +internal sealed class RepoSettingsReadRawRequest { - /// The type discriminator. - [JsonPropertyName("type")] - public virtual string Type { get; set; } = string.Empty; -} + /// Absolute path of the repository root. + [JsonPropertyName("repoRoot")] + public string RepoRoot { get; set; } = string.Empty; + /// Use repo for shared settings or local for settings.local.json. Other scopes are rejected. + [JsonPropertyName("scope")] + public string Scope { get; set; } = string.Empty; +} -/// Endpoint URLs from the raw Copilot `/copilot_internal/v2/token` user-response passthrough. +/// One key to write or remove in a repository settings file. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class CopilotUserResponseEndpoints +internal sealed class RepoSettingsWriteKeyRequest { - /// Copilot API endpoint URL. - [JsonPropertyName("api")] - public string? Api { get; set; } - - /// Experimental-service endpoint URL. - [JsonPropertyName("exp")] - public string? Exp { get; set; } + /// True to remove the key instead of writing it. + [JsonPropertyName("delete")] + public bool? Delete { get; set; } - /// Origin-tracker endpoint URL. - [JsonPropertyName("origin-tracker")] - public string? OriginTracker { get; set; } + /// Canonical top-level key permitted by the repository settings allowlist. + [JsonPropertyName("key")] + public string Key { get; set; } = string.Empty; - /// Copilot proxy endpoint URL. - [JsonPropertyName("proxy")] - public string? Proxy { get; set; } + /// Absolute path of the repository root. + [JsonPropertyName("repoRoot")] + public string RepoRoot { get; set; } = string.Empty; - /// Copilot telemetry endpoint URL. - [JsonPropertyName("telemetry")] - public string? Telemetry { get; set; } -} + /// Use repo for shared settings or local for settings.local.json. Other scopes are rejected. + [JsonPropertyName("scope")] + public string Scope { get; set; } = string.Empty; -/// RPC data type for CopilotUserResponseEnterpriseListItem operations. -public sealed class CopilotUserResponseEnterpriseListItem -{ - /// JavaScript-safe numeric database ID of the enterprise. - [JsonPropertyName("id")] - public long? Id { get; set; } + /// Value to validate and write. Required unless delete is true; a null value is validated, not treated as deletion. + [JsonPropertyName("value")] + public JsonElement? Value { get; set; } } -/// RPC data type for CopilotUserResponseOrganizationListItem operations. -public sealed class CopilotUserResponseOrganizationListItem +/// The complete set of repository-overridable settings to store. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class RepoSettingsWriteAllRequest { - /// Numeric database ID of the organization. - [JsonPropertyName("id")] - public double? Id { get; set; } + /// Absolute path of the repository root. + [JsonPropertyName("repoRoot")] + public string RepoRoot { get; set; } = string.Empty; - /// GitHub login of the organization. - [JsonPropertyName("login")] - public string? Login { get; set; } + /// Use repo for shared settings or local for settings.local.json. Other scopes are rejected. + [JsonPropertyName("scope")] + public string Scope { get; set; } = string.Empty; - /// Display name of the organization. - [JsonPropertyName("name")] - public string? Name { get; set; } + /// Object of allowed settings to store. Missing allowlisted keys are removed. Other input keys are ignored, while unknown stored keys are preserved. + [JsonPropertyName("settings")] + public JsonElement Settings { get; set; } } -/// Chat quota snapshot from the raw Copilot user-response passthrough, with entitlement, overage, remaining quota, reset, and billing fields. +/// Caller population and startup inputs to resolve named online-experiment gates for. Carries no account: the runtime resolves the assignment for the authentication it already holds. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class CopilotUserResponseQuotaSnapshotsChat +internal sealed class FeatureFlagsResolveStartupGatesRequest { - /// Number of requests/units included in the entitlement for this period; `-1` denotes an unlimited entitlement. - [JsonPropertyName("entitlement")] - public double? Entitlement { get; set; } - - /// Whether the user currently has quota available; when `false` and not unlimited, further requests are blocked until the quota resets. - [JsonPropertyName("has_quota")] - public bool? HasQuota { get; set; } + /// User settings document whose `featureFlags` block is layered on top of the resolved set. + [JsonPropertyName("config")] + public JsonElement? Config { get; set; } - /// Count of additional pay-per-request usage consumed this period beyond the entitlement. - [JsonPropertyName("overage_count")] - public double? OverageCount { get; set; } + /// Environment to read flag overrides from. Defaults to this runtime process's own environment. + [JsonPropertyName("environment")] + public IDictionary? Environment { get; set; } - /// Whether usage may continue at pay-per-request rates once the entitlement is exhausted. - [JsonPropertyName("overage_permitted")] - public bool? OveragePermitted { get; set; } + /// When this installation first launched, in milliseconds since the Unix epoch. Experiments that segment on installation age read it. Omit when the first launch was never recorded. + [JsonPropertyName("firstLaunchAtMillis")] + public double? FirstLaunchAtMillis { get; set; } - /// Percentage of the entitlement remaining at the snapshot timestamp. - [JsonPropertyName("percent_remaining")] - public double? PercentRemaining { get; set; } + /// The feature flags to resolve. A name this runtime does not declare is reported as an error rather than as a disabled flag. + [JsonPropertyName("flags")] + public IList Flags { get => field ??= []; set; } - /// Identifier of the quota bucket this snapshot describes. - [JsonPropertyName("quota_id")] - public string? QuotaId { get; set; } + /// Whether the caller is in an experimental population. + [JsonPropertyName("isExperimental")] + public bool IsExperimental { get; set; } - /// Amount of quota remaining at the snapshot timestamp. - [JsonPropertyName("quota_remaining")] - public double? QuotaRemaining { get; set; } + /// Whether the caller is GitHub or Microsoft staff. + [JsonPropertyName("isStaff")] + public bool IsStaff { get; set; } - /// Unix epoch time, in seconds, when this quota next resets. - [JsonPropertyName("quota_reset_at")] - public double? QuotaResetAt { get; set; } + /// GitHub-staff membership. Omit when it was never recorded: a legacy staff account then counts as both staff populations and keeps every staff feature. + [JsonPropertyName("isStaffGithub")] + public bool? IsStaffGitHub { get; set; } - /// Remaining entitlement/quota amount at the snapshot timestamp. - [JsonPropertyName("remaining")] - public double? Remaining { get; set; } + /// Microsoft-staff membership. Omitted the same way as `isStaffGitHub`. + [JsonPropertyName("isStaffMicrosoft")] + public bool? IsStaffMicrosoft { get; set; } - /// UTC timestamp when this snapshot was captured. - [JsonPropertyName("timestamp_utc")] - public string? TimestampUtc { get; set; } + /// Whether the caller belongs to a team plan. + [JsonPropertyName("isTeam")] + public bool IsTeam { get; set; } - /// Whether this category uses usage-based (token/AI-credit) billing rather than a fixed premium-request count. - [JsonPropertyName("token_based_billing")] - public bool? TokenBasedBilling { get; set; } + /// Whether the runtime may refresh the cached assignment over the network without an active session. The refresh never blocks this call: the answer comes from the cache either way, so a refresh only affects a later launch. + [JsonPropertyName("refreshWithoutSession")] + public bool RefreshWithoutSession { get; set; } - /// Whether the entitlement for this category is unlimited. - [JsonPropertyName("unlimited")] - public bool? Unlimited { get; set; } + /// Whether streamer mode is on, which withholds the populations an experiment would otherwise segment on. + [JsonPropertyName("streamerMode")] + public bool StreamerMode { get; set; } } -/// Completions quota snapshot from the raw Copilot user-response passthrough, with entitlement, overage, remaining quota, reset, and billing fields. +/// The declared shape of one feature flag, independent of any caller population. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class CopilotUserResponseQuotaSnapshotsCompletions +internal sealed class FeatureFlagMetadata { - /// Number of requests/units included in the entitlement for this period; `-1` denotes an unlimited entitlement. - [JsonPropertyName("entitlement")] - public double? Entitlement { get; set; } - - /// Whether the user currently has quota available; when `false` and not unlimited, further requests are blocked until the quota resets. - [JsonPropertyName("has_quota")] - public bool? HasQuota { get; set; } - - /// Count of additional pay-per-request usage consumed this period beyond the entitlement. - [JsonPropertyName("overage_count")] - public double? OverageCount { get; set; } - - /// Whether usage may continue at pay-per-request rates once the entitlement is exhausted. - [JsonPropertyName("overage_permitted")] - public bool? OveragePermitted { get; set; } - - /// Percentage of the entitlement remaining at the snapshot timestamp. - [JsonPropertyName("percent_remaining")] - public double? PercentRemaining { get; set; } - - /// Identifier of the quota bucket this snapshot describes. - [JsonPropertyName("quota_id")] - public string? QuotaId { get; set; } + /// The population the flag is declared for before any environment or user-settings override is applied. + [JsonPropertyName("availability")] + public FeatureFlagAvailability Availability { get; set; } - /// Amount of quota remaining at the snapshot timestamp. - [JsonPropertyName("quota_remaining")] - public double? QuotaRemaining { get; set; } + /// Whether the flag participates in the CAPI sanity suite. + [JsonPropertyName("capiSanity")] + public bool CapiSanity { get; set; } - /// Unix epoch time, in seconds, when this quota next resets. - [JsonPropertyName("quota_reset_at")] - public double? QuotaResetAt { get; set; } + /// Text shown to a user choosing whether to turn the flag on in experimental mode. Absent when the flag is not user-facing. + [JsonPropertyName("experimentalDescription")] + public string? ExperimentalDescription { get; set; } - /// Remaining entitlement/quota amount at the snapshot timestamp. - [JsonPropertyName("remaining")] - public double? Remaining { get; set; } + /// The online-experiment (ExP) flag this maps to, when the flag is assigned by an experiment rather than declared statically. + [JsonPropertyName("expFlag")] + public string? ExpFlag { get; set; } - /// UTC timestamp when this snapshot was captured. - [JsonPropertyName("timestamp_utc")] - public string? TimestampUtc { get; set; } + /// Environment variable that overrides this flag's experiment assignment locally in the experiment service, not in `featureFlags.resolve`. Present exactly when `expFlag` is, and derived from it, so a caller reads a local override without reproducing the naming rule. + [JsonPropertyName("expFlagEnvVar")] + public string? ExpFlagEnvVar { get; set; } - /// Whether this category uses usage-based (token/AI-credit) billing rather than a fixed premium-request count. - [JsonPropertyName("token_based_billing")] - public bool? TokenBasedBilling { get; set; } + /// The flag's name, matching the key it is listed under. + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; +} - /// Whether the entitlement for this category is unlimited. - [JsonPropertyName("unlimited")] - public bool? Unlimited { get; set; } +/// Every feature flag this runtime declares, with the shape each one was declared with. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class FeatureFlagsMetadataResult +{ + /// Declared feature flags keyed by flag name. + [JsonPropertyName("flags")] + public IDictionary Flags { get => field ??= new Dictionary(); set; } } -/// Premium-interactions quota snapshot from the raw Copilot user-response passthrough, with entitlement, overage, remaining quota, reset, and billing fields. +/// Caller population and overrides for static feature-flag resolution, independent of experiment assignments and active runtime state. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class CopilotUserResponseQuotaSnapshotsPremiumInteractions +internal sealed class FeatureFlagsResolveRequest { - /// Number of requests/units included in the entitlement for this period; `-1` denotes an unlimited entitlement. - [JsonPropertyName("entitlement")] - public double? Entitlement { get; set; } + /// Resolve against this already-computed flag set rather than each flag's declared availability. Keyed by flag name. + [JsonPropertyName("baseFlags")] + public IDictionary? BaseFlags { get; set; } - /// Whether the user currently has quota available; when `false` and not unlimited, further requests are blocked until the quota resets. - [JsonPropertyName("has_quota")] - public bool? HasQuota { get; set; } + /// User settings to apply, including `enabledFeatureFlags`, legacy `feature_flags.enabled`, and supported convenience settings. + [JsonPropertyName("config")] + public JsonElement? Config { get; set; } - /// Count of additional pay-per-request usage consumed this period beyond the entitlement. - [JsonPropertyName("overage_count")] - public double? OverageCount { get; set; } + /// Environment for direct feature-flag overrides and `COPILOT_CLI_ENABLED_FEATURE_FLAGS`. Does not apply `COPILOT_EXP_*` experiment assignments. Defaults to this runtime process's own environment. + [JsonPropertyName("environment")] + public IDictionary? Environment { get; set; } - /// Whether usage may continue at pay-per-request rates once the entitlement is exhausted. - [JsonPropertyName("overage_permitted")] - public bool? OveragePermitted { get; set; } + /// Flags forced to a value after every other layer, keyed by flag name. + [JsonPropertyName("flagOverrides")] + public IDictionary? FlagOverrides { get; set; } - /// Percentage of the entitlement remaining at the snapshot timestamp. - [JsonPropertyName("percent_remaining")] - public double? PercentRemaining { get; set; } + /// Whether the caller is in an experimental population. + [JsonPropertyName("isExperimental")] + public bool IsExperimental { get; set; } - /// Identifier of the quota bucket this snapshot describes. - [JsonPropertyName("quota_id")] - public string? QuotaId { get; set; } + /// Whether the caller is GitHub or Microsoft staff. + [JsonPropertyName("isStaff")] + public bool IsStaff { get; set; } - /// Amount of quota remaining at the snapshot timestamp. - [JsonPropertyName("quota_remaining")] - public double? QuotaRemaining { get; set; } + /// GitHub-staff membership. Omit when it was never recorded: a legacy staff account then counts as both staff populations and keeps every staff feature. + [JsonPropertyName("isStaffGithub")] + public bool? IsStaffGitHub { get; set; } - /// Unix epoch time, in seconds, when this quota next resets. - [JsonPropertyName("quota_reset_at")] - public double? QuotaResetAt { get; set; } + /// Microsoft-staff membership. Omitted the same way as `isStaffGitHub`. + [JsonPropertyName("isStaffMicrosoft")] + public bool? IsStaffMicrosoft { get; set; } - /// Remaining entitlement/quota amount at the snapshot timestamp. - [JsonPropertyName("remaining")] - public double? Remaining { get; set; } + /// Whether the caller belongs to the team population used by feature availability. + [JsonPropertyName("isTeam")] + public bool IsTeam { get; set; } +} - /// UTC timestamp when this snapshot was captured. - [JsonPropertyName("timestamp_utc")] - public string? TimestampUtc { get; set; } +/// The policy questions a host asks before granting elevated permissions. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class PermissionsEvaluateManagedPolicyResult +{ + /// True when managed policy forbids bypass-permissions mode, so allow-all style flags must not take effect. + [JsonPropertyName("bypassPermissionsDisabled")] + public bool BypassPermissionsDisabled { get; set; } - /// Whether this category uses usage-based (token/AI-credit) billing rather than a fixed premium-request count. - [JsonPropertyName("token_based_billing")] - public bool? TokenBasedBilling { get; set; } + /// True when the managed layer supplies permission rules that the session must enforce. + [JsonPropertyName("permissionRulesActive")] + public bool PermissionRulesActive { get; set; } - /// Whether the entitlement for this category is unlimited. - [JsonPropertyName("unlimited")] - public bool? Unlimited { get; set; } + /// True when a managed policy exists but this build could not determine its meaning. Hosts fail closed on it rather than assuming permission was granted. + [JsonPropertyName("policyUndetermined")] + public bool PolicyUndetermined { get; set; } } -/// Quota snapshot map from the raw Copilot user-response passthrough, with chat, completions, premium-interactions, and other entries. +/// The verbatim managed `permissions` slice to interpret. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class CopilotUserResponseQuotaSnapshots +internal sealed class PermissionsEvaluateManagedPolicyParams { - /// Chat quota snapshot from the raw Copilot user-response passthrough, with entitlement, overage, remaining quota, reset, and billing fields. - [JsonPropertyName("chat")] - public CopilotUserResponseQuotaSnapshotsChat? Chat { get; set; } + /// The `permissions` object from a managed settings layer, for example `{"disableBypassPermissionsMode":"disable"}`. Omitted or null means no managed policy applies. + [JsonPropertyName("permissions")] + public JsonElement? Permissions { get; set; } +} - /// Completions quota snapshot from the raw Copilot user-response passthrough, with entitlement, overage, remaining quota, reset, and billing fields. - [JsonPropertyName("completions")] - public CopilotUserResponseQuotaSnapshotsCompletions? Completions { get; set; } +/// Which stored location permissions were dropped because their directory is gone. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class LocationPermissionsCleanupResult +{ + /// Location keys whose grants were removed. Empty when every stored location still exists. + [JsonPropertyName("removedLocationKeys")] + public IList RemovedLocationKeys { get => field ??= []; set; } +} - /// Premium-interactions quota snapshot from the raw Copilot user-response passthrough, with entitlement, overage, remaining quota, reset, and billing fields. - [JsonPropertyName("premium_interactions")] - public CopilotUserResponseQuotaSnapshotsPremiumInteractions? PremiumInteractions { get; set; } +/// Folder trust check result. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public sealed class FolderTrustCheckResult +{ + /// Whether the folder is trusted. + [JsonPropertyName("trusted")] + public bool Trusted { 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. +/// A folder to check against a persisted trusted-folders list, and the configuration directory holding that list. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class CopilotUserResponse +internal sealed class FolderTrustIsTrustedParams { - /// Copilot access SKU identifier (e.g. `free_limited_copilot`, `copilot_for_business_seat_quota`) used to gate model and feature access. - [JsonPropertyName("access_type_sku")] - public string? AccessTypeSku { get; set; } + /// Configuration directory to read the trusted-folders list from. Omit it to read the default location; supply it when the host has redirected its configuration, or the check answers from a list the host is not using. + [JsonPropertyName("configDir")] + public string? ConfigDir { get; set; } - /// Opaque analytics tracking identifier for the user, forwarded from the Copilot API. - [JsonPropertyName("analytics_tracking_id")] - public string? AnalyticsTrackingId { get; set; } - - /// Date the Copilot seat was assigned to the user, if applicable. - [JsonPropertyName("assigned_date")] - public string? AssignedDate { get; set; } - - /// Whether the user is eligible to sign up for the free/limited Copilot tier. - [JsonPropertyName("can_signup_for_limited")] - public bool? CanSignupForLimited { get; set; } - - /// Whether the user is able to upgrade their Copilot plan. - [JsonPropertyName("can_upgrade_plan")] - public bool? CanUpgradePlan { get; set; } - - /// Whether Copilot chat is enabled for the user. - [JsonPropertyName("chat_enabled")] - public bool? ChatEnabled { get; set; } - - /// Whether CLI remote control is enabled for the user. - [JsonPropertyName("cli_remote_control_enabled")] - public bool? CliRemoteControlEnabled { get; set; } - - /// Whether cloud session storage is enabled for the user. - [JsonPropertyName("cloud_session_storage_enabled")] - public bool? CloudSessionStorageEnabled { get; set; } + /// Folder to check. A folder counts as trusted when it is itself listed, when an ancestor is listed, or when its git root is listed. + [JsonPropertyName("path")] + public string Path { get; set; } = string.Empty; +} - /// Whether the Codex agent is enabled for the user. - [JsonPropertyName("codex_agent_enabled")] - public bool? CodexAgentEnabled { get; set; } +/// The device-managed settings layer, with the sandbox floor and the load outcome a host needs to compose it into an effective policy. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class ManagedSettingsReadDeviceResult +{ + /// Whether discovery or validation failed. A host must treat this as "policy unknown" rather than "no policy": absent settings with this set false mean the device genuinely has none. + [JsonPropertyName("loadFailed")] + public bool LoadFailed { get; set; } - /// Copilot plan name for the user (e.g. `individual`, `business`, `enterprise`). - [JsonPropertyName("copilot_plan")] - public string? CopilotPlan { get; set; } + /// Sandbox floor read from the discovered document before validation, so a document that is otherwise rejected can still raise the floor it declared. + [JsonPropertyName("sandboxFloor")] + public JsonElement? SandboxFloor { get; set; } - /// Whether `.copilotignore` content-exclusion support is enabled for the user. - [JsonPropertyName("copilotignore_enabled")] - public bool? CopilotignoreEnabled { get; set; } + /// Validated, canonical device-managed settings. Omitted when no device policy was discovered, when the discovered document failed validation, or when it validated to an empty object — an empty document carries no policy, and reporting it as present would outrank layers that do. + [JsonPropertyName("settings")] + public JsonElement? Settings { get; set; } +} - /// Endpoint URLs from the raw Copilot `/copilot_internal/v2/token` user-response passthrough. - [JsonPropertyName("endpoints")] - public CopilotUserResponseEndpoints? Endpoints { get; set; } +/// The managed layers to retain as this process's startup policy, with the outcome of fetching each one. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class ManagedSettingsRetainPolicyParams +{ + /// Whether the device load failed. Report it honestly, for the same reason as `serverFetchFailed`. + [JsonPropertyName("deviceLoadFailed")] + public bool DeviceLoadFailed { get; set; } - /// Enterprises that provide the user's Copilot license; malformed entries are normalized to null or ID-less shapes. - [JsonPropertyName("enterprise_list")] - public IList? EnterpriseList { get; set; } + /// The device/MDM policy layer. + [JsonPropertyName("deviceResponse")] + public JsonElement? DeviceResponse { get; set; } - /// Whether MCP (Model Context Protocol) support is enabled for the user. - [JsonPropertyName("is_mcp_enabled")] - public bool? IsMcpEnabled { get; set; } + /// Sandbox floor declared by the device layer, carried separately so a document that failed validation can still raise the floor. + [JsonPropertyName("deviceSandboxFloor")] + public JsonElement? DeviceSandboxFloor { get; set; } - /// Whether the user is a GitHub/Microsoft staff member. - [JsonPropertyName("is_staff")] - public bool? IsStaff { get; set; } + /// Whether the device sandbox intent is unreadable independently of its other settings. + [JsonPropertyName("deviceSandboxUndetermined")] + public bool DeviceSandboxUndetermined { get; set; } - /// Per-category quota allotments for free/limited-tier users, keyed by quota category. - [JsonPropertyName("limited_user_quotas")] - public IDictionary? LimitedUserQuotas { get; set; } + /// Whether a device policy source demonstrably exists. This, not a read error, is the evidence a device is managed, so a fail-closed policy gates on it. + [JsonPropertyName("deviceSourceFound")] + public bool DeviceSourceFound { get; set; } - /// Date the free/limited-tier user's quotas next reset, as a raw string from the Copilot API. - [JsonPropertyName("limited_user_reset_date")] - public string? LimitedUserResetDate { get; set; } + /// Whether any managed policy source exists, independently of fetch failures. + [JsonPropertyName("managedPolicyPresent")] + public bool ManagedPolicyPresent { get; set; } - /// GitHub login of the authenticated user. - [JsonPropertyName("login")] - public string? Login { get; set; } + /// Whether permission policy is undetermined. Sandbox-only uncertainty does not imply this flag. + [JsonPropertyName("permissionPolicyUndetermined")] + public bool PermissionPolicyUndetermined { get; set; } - /// Per-category monthly quota allotments, keyed by quota category. - [JsonPropertyName("monthly_quotas")] - public IDictionary? MonthlyQuotas { get; set; } + /// The policy helper's response: the third managed layer that a process owning its startup policy resolves. It is retained with the other two so that every ambient read in this process, including the mutation guards and the governed marketplace allowlist that the `plugins.*` handlers derive, sees the whole policy the process fetched. A session-owning process omits it, because it resolves a helper per session against that session's working directory. + [JsonPropertyName("policyHelperResponse")] + public JsonElement? PolicyHelperResponse { get; set; } - /// Organizations the user belongs to, each with an optional ID, login, and display name. - [JsonPropertyName("organization_list")] - public IList? OrganizationList { get; set; } + /// Preserve the retained server layer during a device-only resolution. + [JsonPropertyName("preserveServerState")] + public bool PreserveServerState { get; set; } - /// Logins of the organizations the user belongs to. - [JsonPropertyName("organization_login_list")] - public IList? OrganizationLoginList { get; set; } + /// Whether uncertainty alone enabled the shared sandbox floor. + [JsonPropertyName("sandboxEnabledByUndeterminedPolicy")] + public bool SandboxEnabledByUndeterminedPolicy { get; set; } - /// 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. - [JsonPropertyName("quota_reset_date")] - public string? QuotaResetDate { get; set; } + /// Whether the shared sandbox policy is undetermined. Diagnostic errors alone do not imply this flag. + [JsonPropertyName("sandboxFloorUndetermined")] + public bool SandboxFloorUndetermined { get; set; } - /// UTC-normalized form of `quota_reset_date` (the date the user's usage quota next resets). - [JsonPropertyName("quota_reset_date_utc")] - public string? QuotaResetDateUtc { get; set; } + /// Whether the server fetch failed. Report it honestly: true keeps the already-retained server layer rather than replacing it with an absent one. + [JsonPropertyName("serverFetchFailed")] + public bool ServerFetchFailed { get; set; } - /// Quota snapshot map from the raw Copilot user-response passthrough, with chat, completions, premium-interactions, and other entries. - [JsonPropertyName("quota_snapshots")] - public CopilotUserResponseQuotaSnapshots? QuotaSnapshots { get; set; } + /// Whether a required fresh server-policy confirmation failed. + [JsonPropertyName("serverForcedRefreshFailure")] + public bool ServerForcedRefreshFailure { get; set; } - /// Whether the user's telemetry is subject to restricted-data handling. - [JsonPropertyName("restricted_telemetry")] - public bool? RestrictedTelemetry { get; set; } + /// The resolved server policy. A known source can lack a response after a forced-refresh failure. + [JsonPropertyName("serverResponse")] + public JsonElement? ServerResponse { get; set; } - /// 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. - [JsonPropertyName("te")] - public bool? Te { get; set; } + /// Whether the server policy source exists, even when its response is unavailable. + [JsonPropertyName("serverSourceFound")] + public bool ServerSourceFound { get; set; } - /// Whether the account is on usage-based (token/AI-credit) billing rather than a fixed premium-request quota. - [JsonPropertyName("token_based_billing")] - public bool? TokenBasedBilling { get; set; } + /// Shared server/device sandbox floor, excluding session-local helper policy. + [JsonPropertyName("sharedSandboxFloor")] + public JsonElement? SharedSandboxFloor { get; set; } } -/// Authentication-info input variant for GitHub-internal HMAC auth, carrying the public GitHub host and HMAC secret. -/// The hmac variant of . +/// The user settings with the managed layer's controlled entries stamped on. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class AuthInfoHmac : AuthInfo +internal sealed class ManagedSettingsApplyControlsResult { - /// - [JsonIgnore] - public override string Type => "hmac"; + /// The resulting settings document. + [JsonPropertyName("settingsJson")] + public JsonElement SettingsJson { 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; } +/// A user settings document and the effective managed policy whose controlled entries should be stamped onto it. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class ManagedSettingsApplyControlsParams +{ + /// The effective managed policy, as composed by `managedSettings.resolveEffectiveValue`. Omit it on an unmanaged host: the settings then come back unchanged. + [JsonPropertyName("managed")] + public JsonElement? Managed { get; set; } - /// HMAC secret used to sign requests. - [JsonPropertyName("hmac")] - public required string Hmac { get; set; } + /// The user settings document to stamp. Entries the managed layer does not name are returned exactly as supplied. + [JsonPropertyName("settings")] + public JsonElement Settings { get; set; } +} - /// Authentication host. HMAC auth always targets the public GitHub host. - [JsonPropertyName("host")] - public required string Host { get; set; } +/// The accepted managed settings, reduced to the keys a client is allowed to set. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class ManagedSettingsParseClientValueResult +{ + /// The normalized settings. Omitted when the client supplied none. An accepted document that sets nothing comes back as an empty object, which is distinct from omission. + [JsonPropertyName("settingsJson")] + public JsonElement? SettingsJson { get; set; } } -/// 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 . +/// The client-supplied managed settings to validate and normalize. Omitting them is valid and yields no policy. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class AuthInfoEnv : AuthInfo +internal sealed class ManagedSettingsParseClientValueParams { - /// - [JsonIgnore] - public override string Type => "env"; + /// The managed settings a client is supplying for a session it is about to open. Omit them when the client is imposing no policy of its own. + [JsonPropertyName("settings")] + public JsonElement? Settings { 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; } +/// The effective managed policy composed from the supplied layers. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class ManagedSettingsResolveEffectiveValueResult +{ + /// The composed effective managed settings. Omitted when no layer sets anything, so an unmanaged host locks nothing. Carries `sandboxAdvisoryFloorDeclined: true` when an advisory server force-on was dropped because this host cannot run the sandbox. + [JsonPropertyName("settingsJson")] + public JsonElement? SettingsJson { get; set; } +} - /// Name of the environment variable the token was sourced from. - [JsonPropertyName("envVar")] - public required string EnvVar { get; set; } +/// The managed-settings layers to compose into an effective policy. Every layer is optional; omitting all of them yields no effective policy. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class ManagedSettingsResolveEffectiveValueParams +{ + /// The device sandbox floor, folded into the device layer under `sandbox` before composition. + [JsonPropertyName("deviceSandboxFloor")] + public JsonElement? DeviceSandboxFloor { get; set; } - /// Authentication host (e.g. https://github.com or a GHES host). - [JsonPropertyName("host")] - public required string Host { get; set; } + /// The device layer: native MDM and managed-file settings describing the machine rather than the account. + [JsonPropertyName("mdm")] + public JsonElement? Mdm { 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 policy-helper layer, an enforced channel sourced from a platform policy helper process. + [JsonPropertyName("policyHelper")] + public JsonElement? PolicyHelper { get; set; } - /// The token value itself. Treat as a secret. - [JsonPropertyName("token")] - public required string Token { get; set; } + /// The account's server-delivered org policy, as returned by a managed-settings fetch. Omitted before sign-in or when the fetch has not landed. + [JsonPropertyName("server")] + public JsonElement? Server { get; set; } } -/// Authentication-info input variant for SDK-configured token authentication, carrying host and the secret token value. -/// The token variant of . +/// The bypass-permissions state a host should hold after the resolution, and what it must do to get there. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class AuthInfoToken : AuthInfo +internal sealed class ManagedSettingsResolveBypassDisableTransitionResult { - /// - [JsonIgnore] - public override string Type => "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. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("copilotUser")] - public CopilotUserResponse? CopilotUser { get; set; } + /// The account the returned `managed` source is pinned to. Store it alongside `source`; the next call needs it to decide whether an account switch lifts the disable. + [JsonPropertyName("managedIdentity")] + public string? ManagedIdentity { get; set; } - /// Authentication host. - [JsonPropertyName("host")] - public required string Host { get; set; } + /// The value to store in the host's own bypass-disabled flag. Omitted means leave it exactly as it is, which is not the same as being told to store `false`. + [JsonPropertyName("setDisabledState")] + public bool? SetDisabledState { get; set; } - /// Opaque native GitHub credential registration backing this token identity, when applicable. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("registrationId")] - public string? RegistrationId { get; set; } + /// Which layer disables bypass mode after this resolution. Omitted when none does, which is what a lifted disable looks like. + [JsonPropertyName("source")] + public ManagedSettingsBypassDisableSource? Source { get; set; } - /// The token value itself. Treat as a secret. - [JsonPropertyName("token")] - public required string Token { get; set; } + /// Whether a bypass-permissions session already running must be turned off. True only when this resolution newly disables bypass mode, so a host that is already compliant is not interrupted. + [JsonPropertyName("suppressRuntimeAllowAll")] + public bool SuppressRuntimeAllowAll { 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 . +/// The bypass-permissions state a host holds today, the account now signed in, and what the managed-policy resolution said. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class AuthInfoTokenProvider : AuthInfo +internal sealed class ManagedSettingsResolveBypassDisableTransitionParams { - /// - [JsonIgnore] - public override string Type => "token-provider"; + /// The account signed in now. A `managed` disable pinned to a different account is lifted. + [JsonPropertyName("activeIdentity")] + public string ActiveIdentity { get; set; } = string.Empty; - /// Snapshot of the authenticated user's Copilot subscription info, if known. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("copilotUser")] - public CopilotUserResponse? CopilotUser { get; set; } + /// The account a `managed` disable is pinned to. Omit it when the current source is not `managed`; it is ignored for the other sources. + [JsonPropertyName("currentManagedIdentity")] + public string? CurrentManagedIdentity { get; set; } - /// Authentication host. - [JsonPropertyName("host")] - public required string Host { get; set; } + /// Which layer currently disables bypass mode. Omit it when nothing does. + [JsonPropertyName("currentSource")] + public ManagedSettingsBypassDisableSource? CurrentSource { get; set; } - /// Opaque SDK callback registration identifier. - [JsonPropertyName("registrationId")] - public required string RegistrationId { get; set; } + /// What the resolution said about bypass mode. Send `unknown` rather than omitting the field when there is no verdict. + [JsonPropertyName("policy")] + public ManagedSettingsBypassPolicyResolution Policy { 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 . +/// One diagnostic emitted while ingesting managed settings. Returned rather than logged by the runtime so the host decides where ingestion diagnostics go, which matters because an ingestion runs before a session exists to carry a log sink. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class AuthInfoCopilotApiToken : AuthInfo +internal sealed class ManagedSettingsLogMessage { - /// - [JsonIgnore] - public override string Type => "copilot-api-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. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("copilotUser")] - public CopilotUserResponse? CopilotUser { get; set; } + /// Severity of this diagnostic. + [JsonPropertyName("level")] + public ManagedSettingsLogLevel Level { get; set; } - /// Authentication host (always the public GitHub host). - [JsonPropertyName("host")] - public required string Host { get; set; } + /// Human-readable diagnostic text. + [JsonPropertyName("message")] + public string Message { get; set; } = string.Empty; } -/// Authentication-info variant for OAuth user auth, with host and login; the token remains in the runtime secret store. -/// The user variant of . +/// The managed policy layers an ingestion resolved, together with the per-layer outcome flags a host needs to decide how much to trust the result. The flags are reported rather than folded into the settings because a host must be able to distinguish an unmanaged device from a managed device whose policy could not be confirmed, and apply its fail-closed gates in the second case. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class AuthInfoUser : AuthInfo +internal sealed class ManagedSettingsIngestResult { - /// - [JsonIgnore] - public override string Type => "user"; + /// The device layer could not be read, so it is unconfirmed rather than known-absent. + [JsonPropertyName("deviceLoadFailed")] + public bool DeviceLoadFailed { 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; } + /// The device layer discovered from the machine, when one exists. + [JsonPropertyName("deviceResponse")] + public JsonElement? DeviceResponse { get; set; } - /// Authentication host. - [JsonPropertyName("host")] - public required string Host { get; set; } + /// The sandbox floor contributed by the device layer. + [JsonPropertyName("deviceSandboxFloor")] + public JsonElement? DeviceSandboxFloor { get; set; } - /// OAuth user login. - [JsonPropertyName("login")] - public required string Login { get; set; } -} + /// Whether the device layer's sandbox intent could not be determined, so a consumer must not read its absence as permission to run unsandboxed. + [JsonPropertyName("deviceSandboxUndetermined")] + public bool DeviceSandboxUndetermined { get; set; } -/// An interactive account whose model provider owns its credentials. It carries no GitHub credential. -/// The account variant of . -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class AuthInfoAccount : AuthInfo -{ - /// - [JsonIgnore] - public override string Type => "account"; + /// A device-managed policy is known to exist. + [JsonPropertyName("deviceSourceFound")] + public bool DeviceSourceFound { get; set; } - /// Host coordinate owned by the account's model provider. - [JsonPropertyName("host")] - public required string Host { get; set; } + /// Diagnostics emitted while resolving the layers, in the order they occurred. + [JsonPropertyName("logMessages")] + public IList LogMessages { get => field ??= []; set; } - /// Login identifying the provider-owned account. - [JsonPropertyName("login")] - public required string Login { get; set; } + /// The effective managed permissions layer, when the resolved policy carries one. + [JsonPropertyName("permissions")] + public JsonElement? Permissions { get; set; } + + /// Which channel registered the policy helper that produced the helper layer. + [JsonPropertyName("policyHelperAuthority")] + public ManagedSettingsPolicyHelperAuthority? PolicyHelperAuthority { get; set; } + + /// A policy helper is configured for this host. + [JsonPropertyName("policyHelperConfigured")] + public bool PolicyHelperConfigured { get; set; } + + /// The policy helper's failure must be treated as fail-closed rather than ignored. + [JsonPropertyName("policyHelperFailClosed")] + public bool PolicyHelperFailClosed { get; set; } + + /// The policy helper was configured but could not be run or its output could not be read. + [JsonPropertyName("policyHelperLoadFailed")] + public bool PolicyHelperLoadFailed { get; set; } + + /// The layer produced by the platform policy helper, when one ran. + [JsonPropertyName("policyHelperResponse")] + public JsonElement? PolicyHelperResponse { get; set; } + + /// A live fetch failed but a recent cached entry was served in its place, so `serverFetchFailed` stays false. Lets a consumer distinguish a clean fetch from a cache-backed fallback. + [JsonPropertyName("serverCacheFallback")] + public bool ServerCacheFallback { get; set; } + + /// Minimum client version requested by a successful response. Advisory only; the delivered policy remains active. + [JsonPropertyName("serverClientUpdateRequested")] + public string? ServerClientUpdateRequested { get; set; } + + /// The server refused to deliver a policy because this client is too old to enforce it, carrying the user-facing sentence describing the refusal. No retry will help; the remedy is a client update. Gates that exist only because of managed policy should deny on this rather than falling back to their unmanaged default. + [JsonPropertyName("serverClientUpdateRequired")] + public string? ServerClientUpdateRequired { get; set; } + + /// The error that made the server fetch fail, when one did. + [JsonPropertyName("serverFetchError")] + public string? ServerFetchError { get; set; } + + /// The server fetch failed and no cached entry stood in for it, so the server layer is unconfirmed and fail-closed gates apply. + [JsonPropertyName("serverFetchFailed")] + public bool ServerFetchFailed { get; set; } + + /// The server fetch failed while a policy demanded a fresh confirmation, which is the strict posture an enterprise opted into rather than an ordinary transient failure. + [JsonPropertyName("serverForcedRefreshFailure")] + public bool ServerForcedRefreshFailure { get; set; } + + /// The synthesized fail-closed permissions layer applied in place of a policy a refused client may not enforce. + [JsonPropertyName("serverPermissionsFloor")] + public JsonElement? ServerPermissionsFloor { get; set; } + + /// How the server layer was resolved. + [JsonPropertyName("serverResolution")] + public ManagedSettingsServerPolicyResolution ServerResolution { get; set; } + + /// The account's server-delivered policy, when one was resolved. + [JsonPropertyName("serverResponse")] + public JsonElement? ServerResponse { get; set; } + + /// The server fetch failed in a way a later retry could succeed at, as opposed to a refusal that no retry will change. + [JsonPropertyName("serverRetryableFailure")] + public bool ServerRetryableFailure { get; set; } + + /// The sandbox object from a cached server policy that a forced refresh declined to serve in full. Folded back into the effective sandbox layer so an outage during a forced refresh still synthesizes the fail-closed sandbox floor from the administrator's known intent. + [JsonPropertyName("serverSandboxFloor")] + public JsonElement? ServerSandboxFloor { get; set; } + + /// A server-managed policy is known to exist even though `serverResponse` is intentionally unset. Consumers should OR this with the presence of `serverResponse` when deciding whether the device is managed, so a fresh start during an outage still applies the fail-closed gates instead of treating the device as unmanaged. + [JsonPropertyName("serverSourceFound")] + public bool ServerSourceFound { get; set; } + + /// No usable token was available, so no server fetch was attempted. + [JsonPropertyName("serverTokenUnavailable")] + public bool ServerTokenUnavailable { get; set; } + + /// The effective managed settings composed from every resolved layer. Omitted when no layer set anything, so an unmanaged host locks nothing. + [JsonPropertyName("settings")] + public JsonElement? Settings { get; set; } } -/// Authentication-info input variant for GitHub CLI credentials, carrying host, login, and the `gh auth token` value. -/// The gh-cli variant of . +/// Inputs describing the host and account an ingestion should resolve managed policy for. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class AuthInfoGhCli : AuthInfo +internal sealed class ManagedSettingsIngestParams { - /// - [JsonIgnore] - public override string Type => "gh-cli"; + /// The authenticated identity to resolve policy for. Omitted before sign-in, in which case no server layer is fetched. + [JsonPropertyName("authInfo")] + public JsonElement? AuthInfo { 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 to ignore a cached server policy when reading, forcing the resolution to come from a live fetch. + [JsonPropertyName("bypassCacheRead")] + public bool? BypassCacheRead { get; set; } - /// Authentication host. - [JsonPropertyName("host")] - public required string Host { get; set; } + /// Name of the embedding client, reported to the managed-settings service as `client_id` so it can refuse to deliver a policy this client is too old to enforce. Omit it and the runtime reports a generic identity; the runtime version sent alongside is the gate that actually matters. + [JsonPropertyName("clientName")] + public string? ClientName { get; set; } - /// User login as reported by `gh auth status`. - [JsonPropertyName("login")] - public required string Login { get; set; } + /// A device layer supplied by the host instead of discovered from the machine. Pair it with `hasDeviceResponse` so an explicit absence is distinguishable from an omitted override. + [JsonPropertyName("deviceResponse")] + public JsonElement? DeviceResponse { get; set; } - /// The token returned by `gh auth token`. Treat as a secret. - [JsonPropertyName("token")] - public required string Token { get; set; } + /// The host's environment variables, read for cache location and policy overrides. Supplied by the host rather than read from the runtime process so a command that selected its own configuration home is honored. + [JsonPropertyName("environment")] + public JsonElement Environment { get; set; } + + /// Whether `deviceResponse` is an override at all. True with a null `deviceResponse` asserts the device layer is empty; false discovers it from the machine. + [JsonPropertyName("hasDeviceResponse")] + public bool? HasDeviceResponse { get; set; } + + /// The host's home directory, used to resolve cache and managed-file locations. + [JsonPropertyName("homeDirectory")] + public string HomeDirectory { get; set; } = string.Empty; + + /// The host's platform, in Node's `process.platform` spelling, used to locate platform-specific cache and policy paths. + [JsonPropertyName("platform")] + public string Platform { get; set; } = string.Empty; + + /// Whether a configured platform policy helper may be run to produce an enforced helper layer. + [JsonPropertyName("policyHelperEnabled")] + public bool? PolicyHelperEnabled { get; set; } + + /// The policy-helper invocation reason. Defaults to sessionStart. + [JsonPropertyName("policyHelperReason")] + public ManagedSettingsPolicyHelperReason? PolicyHelperReason { get; set; } + + /// Directory the policy helper runs in when this ingestion is not owned by a registered session. A host resolving policy before its session exists must pass this, or a directory-sensitive helper judges the wrong repository. + [JsonPropertyName("policyHelperWorkingDirectory")] + public string? PolicyHelperWorkingDirectory { get; set; } + + /// Whether to run ingestion. Omit or set false to return an empty result without discovering or fetching policy. + [JsonPropertyName("selfFetch")] + public bool? SelfFetch { get; set; } + + /// How this ingestion may use the on-disk server-policy cache. Defaults to the runtime's own policy when omitted. + [JsonPropertyName("serverPolicyCacheMode")] + public ManagedSettingsServerPolicyCacheMode? ServerPolicyCacheMode { get; set; } + + /// The session whose credential store and working directory the policy helper should use. Omit it when no session exists yet and the process-wide store applies. + [JsonPropertyName("sessionId")] + public string? SessionId { get; set; } + + /// User agent to send with the policy fetch. + [JsonPropertyName("userAgent")] + public string UserAgent { get; set; } = string.Empty; } -/// 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 . +/// The verdict of the `forceLoginOrgs` enterprise sign-in pin for one credential. A managed device may pin Copilot to an approved set of GitHub organizations so a developer cannot sign in with a personal — or another enterprise's — account and escape every control that keys off the signed-in identity. A host must refuse to proceed when `allowed` is false; the remaining fields explain the refusal rather than qualifying it. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class AuthInfoApiKey : AuthInfo +internal sealed class ForceLoginOrgsDecision { - /// - [JsonIgnore] - public override string Type => "api-key"; + /// Whether the client may proceed. True whenever no pin is in force, so an unmanaged host is never blocked. + [JsonPropertyName("allowed")] + public bool Allowed { get; set; } - /// The API key. Treat as a secret. - [JsonPropertyName("apiKey")] - public required string ApiKey { get; set; } + /// A stable kebab-case reason code when refused: `org-not-approved`, `unverifiable-credential`, `policy-undetermined`, `policy-invalid`, `not-signed-in`, or `membership-unknown`. Omitted when allowed. Treat an unrecognized code as a refusal rather than mapping it to an allow: a code this host has not been taught about still denied the credential. + [JsonPropertyName("denial")] + public string? Denial { 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 a pin applies at all. Distinguishes "allowed because unmanaged" from "allowed because the account is approved", which a diagnostics surface needs and which also decides whether a bring-your-own-key provider may serve inference. + [JsonPropertyName("enforced")] + public bool Enforced { get; set; } - /// Authentication host. - [JsonPropertyName("host")] - public required string Host { get; set; } + /// The approved organization the account matched, when allowed under a pin. + [JsonPropertyName("matchedOrg")] + public string? MatchedOrg { get; set; } + + /// A developer-facing explanation of the refusal. Omitted when allowed. + [JsonPropertyName("message")] + public string? Message { get; set; } + + /// Error-level detail from managed-policy discovery — which file failed to load, which field failed validation. Carried on the decision rather than left to the log because a refusal can exit the process before a log sink exists, which would discard the only actionable diagnostic a misconfigured managed device produces. + [JsonPropertyName("policyDiagnostics")] + public IList PolicyDiagnostics { get => field ??= []; set; } + + /// The approved organization logins, as configured. Enterprise configuration rather than user data, so it is safe to name in a message or a log line. + [JsonPropertyName("requiredOrgs")] + public IList RequiredOrgs { get => field ??= []; set; } } -/// Current authentication state. +/// The startup policy a resolution composed, the per-layer outcome flags a host needs to decide how much to trust it, and the enterprise sign-in verdict for the credential it ran against. The flags are reported rather than folded into the settings because a host must distinguish an unmanaged device from a managed device whose policy could not be confirmed, and apply its fail-closed gates in the second case. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class AccountGetCurrentAuthResult +internal sealed class ManagedSettingsResolveStartupPolicyResult { - /// Authentication errors from the last auth attempt, if any. - [JsonPropertyName("authErrors")] - public IList? AuthErrors { get; set; } + /// Advisory minimum client version from a successful server response. + [JsonPropertyName("clientUpdateRequested")] + public string? ClientUpdateRequested { get; set; } - /// Current authentication information, if authenticated. - [JsonPropertyName("authInfo")] - public AuthInfo? AuthInfo { get; set; } + /// The server refused to deliver a policy because this client is too old to enforce it, carrying the user-facing sentence describing the refusal. No retry will help; the remedy is a client update. + [JsonPropertyName("clientUpdateRequired")] + public string? ClientUpdateRequired { get; set; } + + /// The device layer could not be read, so it is unconfirmed rather than known-absent. + [JsonPropertyName("deviceLoadFailed")] + public bool DeviceLoadFailed { get; set; } + + /// The device layer discovered from the machine, when one exists. + [JsonPropertyName("deviceResponse")] + public JsonElement? DeviceResponse { get; set; } + + /// The sandbox floor contributed by the device layer. + [JsonPropertyName("deviceSandboxFloor")] + public JsonElement? DeviceSandboxFloor { get; set; } + + /// The enterprise sign-in verdict for the credential this resolution ran against, evaluated over the effective policy so a server-delivered pin is honored alongside a device one. Computed on every resolution, including one that was not retained, so a host that declines to apply policy still cannot proceed past a pin. + [JsonPropertyName("forceLoginOrgs")] + public ForceLoginOrgsDecision ForceLoginOrgs { get => field ??= new(); set; } + + /// A managed policy is known to apply to this host, from a discovered source or a resolved document. + [JsonPropertyName("managedPolicyPresent")] + public bool ManagedPolicyPresent { get; set; } + + /// The permissions layer specifically could not be confirmed, so permission gates apply their fail-closed posture. + [JsonPropertyName("permissionPolicyUndetermined")] + public bool PermissionPolicyUndetermined { get; set; } + + /// The policy helper was configured but could not be run, or its output could not be read. + [JsonPropertyName("policyHelperLoadFailed")] + public bool PolicyHelperLoadFailed { get; set; } + + /// The layer produced by the platform policy helper, when one ran. + [JsonPropertyName("policyHelperResponse")] + public JsonElement? PolicyHelperResponse { get; set; } + + /// Some layer of the policy could not be confirmed, so its absence must not be read as permission. Folds in a failed server fetch as well as an unreadable device layer. + [JsonPropertyName("policyUndetermined")] + public bool PolicyUndetermined { get; set; } + + /// The sandbox was enabled because policy could not be confirmed rather than because an administrator asked for it. Lets a host explain a sandbox the user did not configure. + [JsonPropertyName("sandboxEnabledByUndeterminedPolicy")] + public bool SandboxEnabledByUndeterminedPolicy { get; set; } + + /// The effective sandbox floor composed across every layer, which a host must apply rather than its own default. + [JsonPropertyName("sandboxFloor")] + public JsonElement? SandboxFloor { get; set; } + + /// The server fetch failed and no cached entry stood in for it, so the server layer is unconfirmed and fail-closed gates apply. + [JsonPropertyName("serverFetchFailed")] + public bool ServerFetchFailed { get; set; } + + /// How the current runtime resolved the server layer. + [JsonPropertyName("serverResolution")] + public ManagedSettingsServerPolicyResolution ServerResolution { get; set; } + + /// The account's server-delivered policy, when one was resolved. + [JsonPropertyName("serverResponse")] + public JsonElement? ServerResponse { get; set; } + + /// Telemetry describing which layers answered and how, for the host to report alongside its own startup events. + [JsonPropertyName("telemetry")] + public JsonElement Telemetry { get; set; } + + /// Warnings raised while resolving, in the order they occurred. + [JsonPropertyName("warnings")] + public IList Warnings { get => field ??= []; set; } } -/// Authenticated account entry returned by `account.getAllUsers`. +/// Inputs describing the host and account a startup policy resolution should run for. Mirrors `managedSettings.ingest`, and adds the decision of whether the resolved policy is retained for this runtime process. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class AccountAllUsers +internal sealed class ManagedSettingsResolveStartupPolicyParams { - /// Authentication information for this user. + /// The authenticated identity to resolve policy for. Omitted before sign-in, in which case no server layer is fetched and the sign-in pin is evaluated against no credential. [JsonPropertyName("authInfo")] - public AuthInfo AuthInfo { get => field ??= new(); set; } + public JsonElement? AuthInfo { get; set; } - /// Opaque identifier accepted by account and model selection APIs. - [JsonPropertyName("selectionId")] - public string? SelectionId { get; set; } + /// Whether to ignore a cached server policy when reading, forcing the resolution to come from a live fetch. + [JsonPropertyName("bypassCacheRead")] + public bool? BypassCacheRead { get; set; } - /// Associated token, if available. - [JsonPropertyName("token")] - public string? Token { get; set; } + /// Name of the embedding client, reported to the managed-settings service as `client_id` so it can refuse to deliver a policy this client is too old to enforce. + [JsonPropertyName("clientName")] + public string? ClientName { get; set; } + + /// A device layer supplied by the host instead of discovered from the machine. Pair it with `hasDeviceResponse` so an explicit absence is distinguishable from an omitted override. + [JsonPropertyName("deviceResponse")] + public JsonElement? DeviceResponse { get; set; } + + /// The host's environment variables, read for cache location, policy overrides, and the variables that would make a membership snapshot forgeable. + [JsonPropertyName("environment")] + public JsonElement Environment { get; set; } + + /// Whether `deviceResponse` is an override at all. True with a null `deviceResponse` asserts the device layer is empty; false discovers it from the machine. + [JsonPropertyName("hasDeviceResponse")] + public bool? HasDeviceResponse { get; set; } + + /// The host's home directory, used to resolve cache and managed-file locations. + [JsonPropertyName("homeDirectory")] + public string HomeDirectory { get; set; } = string.Empty; + + /// The host's platform, in Node's `process.platform` spelling, used to locate platform-specific cache and policy paths. + [JsonPropertyName("platform")] + public string Platform { get; set; } = string.Empty; + + /// Whether a configured platform policy helper may be run to produce an enforced helper layer. + [JsonPropertyName("policyHelperEnabled")] + public bool? PolicyHelperEnabled { get; set; } + + /// The policy-helper invocation reason. Defaults to sessionStart. + [JsonPropertyName("policyHelperReason")] + public ManagedSettingsPolicyHelperReason? PolicyHelperReason { get; set; } + + /// Directory the policy helper runs in when this resolution is not owned by a registered session. A host resolving policy before its session exists must pass this, or a directory-sensitive helper judges the wrong repository. + [JsonPropertyName("policyHelperWorkingDirectory")] + public string? PolicyHelperWorkingDirectory { get; set; } + + /// Whether to retain the resolved policy as this runtime process's startup policy, so later work in the process enforces it. Set false to resolve without side effects, which is what a protocol host wants before session creation: its sessions apply the complete policy through the normal lifecycle, and retaining here would let a readiness credential affect unrelated sessions. The returned verdict is identical either way — this decides only whether the process keeps it. + [JsonPropertyName("retainAndApply")] + public bool RetainAndApply { get; set; } + + /// Whether to run ingestion. Omit or set false to return an empty result without discovering or fetching policy. + [JsonPropertyName("selfFetch")] + public bool? SelfFetch { get; set; } + + /// How this resolution may use the on-disk server-policy cache. Defaults to the runtime's own policy when omitted. Rejected when combined with `bypassCacheRead`, because the two express the same decision and accepting both would leave the caller unsure which one the runtime honored. + [JsonPropertyName("serverPolicyCacheMode")] + public ManagedSettingsServerPolicyCacheMode? ServerPolicyCacheMode { get; set; } + + /// The session whose credential store, working directory and client kind the resolution should use. Omit it when no session exists yet and the process-wide store applies. A resolution whose session names a credential other than `authInfo` is returned but not retained, so a stale sign-in cannot apply policy for an account that has already been switched away from. + [JsonPropertyName("sessionId")] + public string? SessionId { get; set; } + + /// User agent to send with the policy fetch. + [JsonPropertyName("userAgent")] + public string UserAgent { get; set; } = string.Empty; } -/// Result of a successful login; throws on failure. +/// The managed remote-control policy in force for an account, if any. An absent policy means the account is unmanaged for this capability, which a host reads as allowed — so absence here is a positive statement that no policy applies, never a stand-in for a resolution that failed. A resolution that cannot be completed is rejected instead. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class AccountLoginResult +internal sealed class ManagedSettingsLoadRemoteControlResult { - /// 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. - [JsonPropertyName("storedInVault")] - public bool StoredInVault { get; set; } + /// The account's `remoteControl` policy document. Absent when no managed policy governs remote control for this account. Reported as an explicit disabled policy, rather than as absence, when the service refused to deliver a policy this client is too old to enforce. + [JsonPropertyName("remoteControl")] + public JsonElement? RemoteControl { get; set; } } -/// Credentials to validate and store. Omit login to resolve the authenticated user from the token. +/// Inputs describing the host and account whose managed remote-control policy should be resolved. Mirrors `managedSettings.ingest`, and deliberately carries no retention decision: reading this gate never becomes this runtime process's startup policy. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class AccountLoginRequest +internal sealed class ManagedSettingsLoadRemoteControlParams { - /// GitHub host URL. - [JsonPropertyName("host")] - public string Host { get; set; } = string.Empty; + /// The authenticated identity to resolve policy for. Omitted before sign-in, in which case no server layer is fetched. + [JsonPropertyName("authInfo")] + public JsonElement? AuthInfo { get; set; } - /// User login/username. When omitted, the runtime validates the token and resolves the login from GitHub. - [JsonPropertyName("login")] - public string? Login { get; set; } + /// Name of the embedding client, reported to the managed-settings service as `client_id` so it can refuse to deliver a policy this client is too old to enforce. That refusal is exactly what turns into an explicit `disabled` below, so a host that omits this field forfeits the protection. + [JsonPropertyName("clientName")] + public string? ClientName { get; set; } - /// GitHub authentication token. - [JsonPropertyName("token")] - public string Token { get; set; } = string.Empty; -} + /// A device layer supplied by the host instead of discovered from the machine. Pair it with `hasDeviceResponse` so an explicit absence is distinguishable from an omitted override. + [JsonPropertyName("deviceResponse")] + public JsonElement? DeviceResponse { get; set; } -/// Logout result indicating if more users remain. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class AccountLogoutResult -{ - /// Whether other authenticated users remain after logout. - [JsonPropertyName("hasMoreUsers")] - public bool HasMoreUsers { get; set; } + /// The host's environment variables, read for cache location and policy overrides. + [JsonPropertyName("environment")] + public JsonElement Environment { get; set; } + + /// Whether `deviceResponse` is an override at all. True with a null `deviceResponse` asserts the device layer is empty; false discovers it from the machine. + [JsonPropertyName("hasDeviceResponse")] + public bool? HasDeviceResponse { get; set; } + + /// The host's home directory, used to resolve cache and managed-file locations. + [JsonPropertyName("homeDirectory")] + public string HomeDirectory { get; set; } = string.Empty; + + /// The host's platform, in Node's `process.platform` spelling, used to locate platform-specific cache and policy paths. + [JsonPropertyName("platform")] + public string Platform { get; set; } = string.Empty; + + /// Whether a configured platform policy helper may be run to produce an enforced helper layer. + [JsonPropertyName("policyHelperEnabled")] + public bool? PolicyHelperEnabled { get; set; } + + /// The policy-helper invocation reason. Defaults to sessionStart. + [JsonPropertyName("policyHelperReason")] + public ManagedSettingsPolicyHelperReason? PolicyHelperReason { get; set; } + + /// Whether to run ingestion. Omit or set false to return an empty result without discovering or fetching policy. + [JsonPropertyName("selfFetch")] + public bool? SelfFetch { get; set; } + + /// User agent to send with the policy fetch. + [JsonPropertyName("userAgent")] + public string UserAgent { get; set; } = string.Empty; } -/// User to log out. +/// The credential and environment to evaluate the device-channel sign-in pin against. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class AccountLogoutRequest +internal sealed class ManagedSettingsResolveDeviceForceLoginOrgsGateParams { - /// Authentication information for the user to log out. + /// The authenticated identity to judge. Omitted when signed out, which is the case the device channel exists to serve: it can refuse a first sign-in before any credential exists. [JsonPropertyName("authInfo")] - public AuthInfo? AuthInfo { get; set; } + public JsonElement? AuthInfo { get; set; } - /// Opaque account identifier returned by `account.getAllUsers`. - [JsonPropertyName("selectionId")] - public string? SelectionId { get; set; } + /// The host's environment variables. Read for the variables that would make an identity's membership snapshot forgeable, so a credential whose provenance cannot be trusted is refused rather than accepted. + [JsonPropertyName("environment")] + public JsonElement Environment { get; set; } } -/// Confirmation that the secret values were registered. +/// Validated device-managed settings discovered before a session exists. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class SecretsAddFilterValuesResult +public sealed class ManagedSettingsReadResult { - /// Whether the values were successfully registered. - [JsonPropertyName("ok")] - public bool Ok { get; 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; } } -/// Secret values to add to the redaction filter. +/// One validation finding for a managed-settings document. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SecretsAddFilterValuesRequest +public sealed class ManagedSettingsDiagnostic { - /// Raw secret values to register for redaction. - [JsonPropertyName("values")] - public IList Values { get => field ??= []; set; } + /// Human-readable description of the finding. + [JsonPropertyName("message")] + public string Message { get; set; } = string.Empty; + + /// Dot-separated path of the offending setting, such as `autoTier.overridable`. Empty for the document as a whole. + [JsonPropertyName("path")] + public string Path { get; set; } = string.Empty; + + /// Whether the finding rejects the document. + [JsonPropertyName("severity")] + public ManagedSettingsDiagnosticSeverity Severity { get; set; } } -/// Concrete configuration file containing an MCP server declaration. +/// One managed-settings channel and the document it delivered. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class McpSourceFile +public sealed class ManagedSettingsLayer { - /// RFC 6901 JSON Pointer to the server declaration, when known. - [JsonPropertyName("jsonPointer")] - public string? JsonPointer { get; set; } + /// Validated managed-settings document this channel delivered. Absent when the channel delivered none. + [JsonPropertyName("settings")] + public JsonElement? Settings { get; set; } - /// Canonical file URI for the configuration document. - [Url] - [StringSyntax(StringSyntaxAttribute.Uri)] - [JsonPropertyName("uri")] - public string Uri { get; set; } = string.Empty; + /// Channel identifier: `device` (MDM, plist, registry, or managed file), `server` (account or organization policy), or `policyHelper` (session-local helper output, supported by compose). Treat unknown output values as additional channels; more may be added. + [JsonPropertyName("source")] + public string Source { get; set; } = string.Empty; } -/// Plugin identity associated with an MCP server declaration. +/// Lock state and provenance of one managed setting. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class McpSourcePlugin +public sealed class ManagedSettingMeta { - /// Canonical plugin identity. - [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; + /// Whether users and repositories may choose a different value. `false` means policy locks the value. + [JsonPropertyName("overridable")] + public bool Overridable { get; set; } - /// Human-readable plugin name, when available. - [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; } + /// Original managed value when the runtime adjusted it to a supported effective value. Omitted when no adjustment was needed. + [JsonPropertyName("requested")] + public string? Requested { get; set; } - /// Plugin version, when available. - [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("version")] - public string? Version { get; set; } + /// Channel that supplied this scalar value, matching a `layers[].source`: `device`, `server`, or `policyHelper`. These scalar defaults select one winning channel, not a mixed source. Treat unknown values as additional channels; more may be added. + [JsonPropertyName("source")] + public string Source { get; set; } = string.Empty; } -/// Canonical identity and location of the effective MCP server declaration. The declaration is uniquely addressed by this source id together with the discovered server name. +/// Per-key lock state and provenance for `ManagedSettingsValues`, with the same field names. Producers emit each typed key in values and meta together; both outer objects are omitted when no typed key is set. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class McpSourceRef +public sealed class ManagedSettingsMeta { - /// Open semantic editability identifier. Known values are editable and read-only. - [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("editability")] - public string Editability { get; set; } = string.Empty; - - /// Configuration file location, when the declaration is file-backed. - [JsonPropertyName("file")] - public McpSourceFile? File { get; set; } + /// Lock state and provenance of `values.autoTier`. + [JsonPropertyName("autoTier")] + public ManagedSettingMeta? AutoTier { get; set; } - /// Opaque stable identity for the configuration source. Clients must not parse this 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; + /// Lock state and provenance of `values.contextTier`. + [JsonPropertyName("contextTier")] + public ManagedSettingMeta? ContextTier { get; set; } - /// Open source-kind identifier. Known values include user, workspace, invocation, plugin, builtin, and device-registry. - [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("kind")] - public string Kind { get; set; } = string.Empty; + /// Lock state and provenance of `values.effortLevel`. + [JsonPropertyName("effortLevel")] + public ManagedSettingMeta? EffortLevel { get; set; } - /// Plugin identity, when the declaration is plugin-provided. - [JsonPropertyName("plugin")] - public McpSourcePlugin? Plugin { get; set; } + /// Lock state and provenance of `values.model`. + [JsonPropertyName("model")] + public ManagedSettingMeta? Model { get; set; } } -/// MCP server discovered by `mcp.discover`, with config source, optional plugin source, transport type, and enabled state. +/// Effective enterprise managed settings and contributing channels. Session events report applied policy; sessionless resolve reports an account/device snapshot, and compose reports a non-applying preview of candidate documents. Device values take precedence over server values, then the policy helper, per ordinary key, while permissions compose restrictively. Session-local SDK-client policy is included only in session results. Marked experimental while the managed-settings surface stabilizes. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class DiscoveredMcpServer +public sealed class ManagedSettingsResolvedData { - /// Canonical identity and location of the effective server declaration. - [JsonPropertyName("effectiveSource")] - public McpSourceRef? EffectiveSource { get; set; } + /// Whether an explicit enterprise policy restriction disables bypass-permissions ("yolo") mode for this session. Deny-wins across layers; an unresolved policy does not force this on by itself. + [JsonPropertyName("bypassPermissionsDisabled")] + public bool BypassPermissionsDisabled { get; set; } - /// Whether the server is enabled (not in the disabled list). - [JsonPropertyName("enabled")] - public bool Enabled { get; set; } + /// Whether a session-local permissions layer injected by the SDK host was present. + [JsonPropertyName("clientManaged")] + public bool? ClientManaged { 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; + /// Whether an actual device MDM/plist/registry/file managed-settings layer was present. + [JsonPropertyName("deviceManaged")] + public bool DeviceManaged { get; set; } - /// Configuration source: user, workspace, plugin, or builtin. - [JsonPropertyName("source")] - public McpServerSource Source { get; set; } + /// Whether managed policy could not be determined (e.g. a failed server fetch) and unresolved-policy safeguards remain active. This does not by itself disable bypass-permissions; `bypassPermissionsDisabled` reports only an explicit policy restriction. + [JsonPropertyName("failClosed")] + public bool FailClosed { get; set; } - /// Plugin name that provided this server, when source is plugin. - [JsonPropertyName("sourcePlugin")] - public string? SourcePlugin { 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 IList ManagedKeys { get => field ??= []; set; } - /// Plugin version that provided this server, when source is plugin. - [JsonPropertyName("sourcePluginVersion")] - public string? SourcePluginVersion { get; set; } + /// Whether at least two managed sources supplied permission allowlists, so enforcement intersects them and the flattened settings payload omits `permissions.allow`. + [JsonPropertyName("permissionsAllowIntersected")] + public bool? PermissionsAllowIntersected { get; set; } - /// Server transport type: stdio, http, sse (deprecated), or memory. - [JsonPropertyName("type")] - public DiscoveredMcpServerType? Type { get; set; } -} + /// Reusable retained managed permission policy for managedSettings.permissions.evaluate, including source composition and session-local injection. Provided by live session snapshots; absent in older events. Refresh the context when managed policy changes. + [JsonPropertyName("permissionsContext")] + public ManagedPermissionsContext? PermissionsContext { get; set; } -/// MCP servers discovered from user, workspace, plugin, and built-in sources. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class McpDiscoverResult -{ - /// MCP servers discovered from all sources. - [JsonPropertyName("servers")] - public IList Servers { get => field ??= []; set; } -} + /// 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. + [JsonPropertyName("policyHelperManaged")] + public bool? PolicyHelperManaged { get; set; } -/// Optional working directory used as context for MCP server discovery. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class McpDiscoverRequest -{ - /// Whether to include canonical effectiveSource metadata for each discovered server. Callers must opt in so protocol-3 clients retain the legacy closed response shape. - [JsonPropertyName("includeEffectiveSource")] - public bool? IncludeEffectiveSource { get; set; } + /// 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. + [JsonPropertyName("sandboxEnabledByUndeterminedPolicy")] + public bool? SandboxEnabledByUndeterminedPolicy { get; set; } - /// Working directory used as context for discovery (e.g., plugin resolution). - [JsonPropertyName("workingDirectory")] - public string? WorkingDirectory { get; set; } + /// Whether the server (account/org) managed-settings layer was present. + [JsonPropertyName("serverManaged")] + public bool ServerManaged { get; set; } + + /// The effective (resolved) managed settings values, so clients can render exactly what is enforced. Absent when no managed policy is in force. + [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 ManagedSettingsResolvedSource Source { get; set; } } -/// 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. +/// Typed effective values of managed settings. Each field mirrors the managed-settings schema key of the same name; more keys are added as they are typed. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -[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 +public sealed class ManagedSettingsValues { - /// The type discriminator. - [JsonPropertyName("kind")] - public virtual string Kind { get; set; } = string.Empty; -} + /// Managed Auto routing preference, used when the selected model is `auto`. + [JsonPropertyName("autoTier")] + public AutoTier? AutoTier { get; set; } + /// Managed context-tier default for the managed concrete model. + [JsonPropertyName("contextTier")] + public ContextTier? ContextTier { get; set; } -/// The protocol version and capability set the runtime actually honoured for a successful catalog operation. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class CatalogNegotiatedContract -{ - /// Wire features the runtime understood for this operation. Includes the five original catalog capabilities and only explicitly requested supported additions, in supported order without duplicates. Capabilities that introduce new success-union variants or operations are therefore included only when explicitly required, preserving older protocol-v3 clients. 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; } + /// Managed reasoning-effort default for the managed concrete model. The runtime clamps it to an entitled effort when model availability is known. + [JsonPropertyName("effortLevel")] + public string? EffortLevel { get; set; } - /// Protocol version of the runtime that served the request. - [JsonPropertyName("runtimeProtocolVersion")] - public long RuntimeProtocolVersion { get; set; } + /// Managed default model identifier. When model availability was resolved, aliases and family names are projected to a concrete available model ID; otherwise the configured value is returned. + [JsonPropertyName("model")] + public string? Model { get; set; } } -/// The configuration-change alternative for the transportChoices entry at the same index. Only the selected alternative is applied; entries are not cumulative. The payload stays behind the runtime boundary. +/// Effective enterprise managed settings for an account, resolved without a session. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class McpPlanConfigurationChange +public sealed class ManagedSettingsResolveResult { - /// Names of the configuration fields the change would set, without their values. - [JsonPropertyName("changedFields")] - public IList ChangedFields { get => field ??= []; set; } + /// Printable opaque identity of the account the settings were resolved for, suitable for comparison and storage, not an account selectionId. Absent when no account was available, in which case only device policy is reported. + [JsonPropertyName("account")] + public string? Account { get; set; } - /// 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("configKey")] - public string ConfigKey { get; set; } = string.Empty; + /// Warnings about unavailable policy sources or a failed refresh served from cache. A cached response is not proof of a successful live fetch; `resolved.failClosed` separately describes enforcement. + [JsonPropertyName("diagnostics")] + public IList Diagnostics { get => field ??= []; set; } - /// Whether the change would create a new entry or modify an existing one. - [JsonPropertyName("operation")] - public McpPlanConfigurationOperation Operation { get; set; } + /// Each managed-settings channel consulted, strongest first, with the validated document it delivered before merging. `resolved.settings` is the merged result. More channels may be added over time. + [JsonPropertyName("layers")] + public IList Layers { get => field ??= []; set; } - /// Scope the change would be written to. - [JsonPropertyName("scope")] - public McpPlanScope Scope { get; set; } + /// Per-key lock state and provenance for the entries in `values`, using the same key names. + [JsonPropertyName("meta")] + public ManagedSettingsMeta? Meta { 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; } + /// Reusable source-composed permission policy for managedSettings.permissions.evaluate. Unlike resolved.settings.permissions, this retains every source's allowlist and default-prompt semantics. Refresh through resolve when account/device policy changes. Absent on runtimes that do not support permission evaluation. + [JsonPropertyName("permissionsContext")] + public ManagedPermissionsContext? PermissionsContext { get; set; } + + /// Effective managed settings from the device and account (server) channels, in the same shape as `session.managedSettings.get`, excluding session-local injection. + [JsonPropertyName("resolved")] + public ManagedSettingsResolvedData Resolved { get => field ??= new(); set; } + + /// Typed effective values of managed settings, keyed like the managed-settings schema and already resolved across channels, with the `{ "overridable": ... }` wrapper removed. Present when policy sets at least one typed key. Keys not typed here are available in `resolved.settings`. + [JsonPropertyName("values")] + public ManagedSettingsValues? Values { get; set; } } -/// Normalised identity of the MCP server a plan targets, independent of how the card spelled it. +/// RPC data type for ManagedSettingsResolve operations. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class McpPlanResourceIdentity +internal sealed class ManagedSettingsResolveRequest { - /// 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("canonicalName")] - public string CanonicalName { get; set; } = string.Empty; + /// Embedding client identity for server policy requests, as in session creation. Omit for the CLI identity. + [JsonPropertyName("clientName")] + public string? ClientName { get; set; } - /// Registry identifier of the server, when it came from a registry. - [JsonPropertyName("registryId")] - public string? RegistryId { get; set; } + /// GitHub token to resolve instead of the current account. The call fails when the token cannot be resolved. + [JsonPropertyName("gitHubToken")] + public string? GitHubToken { 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; + /// Opaque account identifier returned by `account.getAllUsers`. When omitted, the current account is used, or device policy only when no account is signed in. + [JsonPropertyName("selectionId")] + public string? SelectionId { get; set; } - /// Version advertised by the card, when it declares one. - [JsonPropertyName("version")] - public string? Version { get; set; } + /// Working directory used to run an organization policy helper. When omitted, sessionless resolution does not run the helper. + [JsonPropertyName("workingDirectory")] + public string? WorkingDirectory { get; set; } } -/// Outcome of evaluating the planned server against registry and enterprise policy. Evaluation is read-only. +/// The authoring JSON schema for managed settings recognized by this runtime. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class McpPlanPolicyResult +public sealed class ManagedSettingsSchemaResult { - /// What policy decided for this server. - [JsonPropertyName("decision")] - public McpPlanPolicyDecision Decision { get; set; } + /// Version of the runtime that owns this schema. + [JsonPropertyName("runtimeVersion")] + public string RuntimeVersion { 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("reason")] - public string? Reason { get; set; } + /// JSON schema (draft 2020-12) with descriptive shared `x-composition` annotations, not a complete runtime composition contract. Model, effortLevel, and contextTier remain coupled; use `managedSettings.compose` for the runtime's effective result. + [JsonPropertyName("schema")] + public JsonElement Schema { get; set; } +} - /// Which authority produced the decision. - [JsonPropertyName("source")] - public McpPlanPolicySource Source { get; set; } +/// Result of validating a managed-settings document. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public sealed class ManagedSettingsValidateResult +{ + /// Errors that reject the document and warnings about content the runtime ignores. + [JsonPropertyName("diagnostics")] + public IList Diagnostics { get => field ??= []; set; } + + /// Canonical form of the document the runtime would apply, with unrecognized keys removed. Absent when the document is invalid. + [JsonPropertyName("settings")] + public JsonElement? Settings { get; set; } + + /// Whether the runtime would accept the document within the preview resource limits. Always equals whether `settings` is present. An invalid document is rejected as a whole. + [JsonPropertyName("valid")] + public bool Valid { get; set; } } -/// 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. +/// A candidate managed-settings document to validate without applying it. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class CardDigest +internal sealed class ManagedSettingsValidateRequest { - /// Digest algorithm and canonical representation. - [JsonPropertyName("algorithm")] - public CardDigestAlgorithm Algorithm { get; set; } + /// The document to validate: a JSON object, or a string containing the document's JSON text. Preview documents are limited to 1 MiB and 64 levels of nesting, a stricter resource limit than delivered-policy parsing; violations are returned as diagnostics. + [JsonPropertyName("content")] + public JsonElement Content { 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; + /// Channel the document is meant for (`device`, `server`, or `policyHelper`). Some keys are only honored in some channels; for example, a `policyHelper` registration is ignored in policy-helper output. When omitted, no channel-specific checks run. + [JsonPropertyName("layer")] + public string? Layer { get; set; } } -/// Provenance of the exact validated JSON MCP card content bound privately to a completed plan and its opaque handle. +/// The effective managed settings the runtime would enforce for the given documents, in the same shape `managedSettings.resolve` returns. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class McpPlanProvenance +public sealed class ManagedSettingsComposeResult { - /// 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; + /// Warnings about ignored content, with paths prefixed by the channel name. + [JsonPropertyName("diagnostics")] + public IList Diagnostics { get => field ??= []; set; } - /// Semantic digest of the exact validated JSON content bound to the plan handle. - [JsonPropertyName("cardDigest")] - public CardDigest CardDigest { get => field ??= new(); set; } + /// Only the supplied channels, strongest first, with canonical documents. Empty canonical documents are represented as absent settings, as in live resolution. + [JsonPropertyName("layers")] + public IList Layers { get => field ??= []; set; } - /// JSON MCP media type the validated card was interpreted as. - [JsonPropertyName("mediaType")] - public McpServerCardMediaType MediaType { get; set; } + /// Per-key lock state and provenance for `values`. + [JsonPropertyName("meta")] + public ManagedSettingsMeta? Meta { 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; + /// Effective managed settings, in the same shape as `session.managedSettings.get`. + [JsonPropertyName("resolved")] + public ManagedSettingsResolvedData Resolved { get => field ??= new(); set; } + + /// Typed effective values, as in `managedSettings.resolve`. + [JsonPropertyName("values")] + public ManagedSettingsValues? Values { get; set; } } -/// Where a plan would be written. +/// One candidate channel; absent settings represents a channel that delivered no document. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class McpPlanTarget +public sealed class ManagedSettingsComposeLayer { - /// 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; + /// Candidate managed-settings document. Omit when the channel delivered none, as in resolve output. + [JsonPropertyName("settings")] + public JsonElement? Settings { get; set; } - /// Configuration scope the plan targets. - [JsonPropertyName("scope")] - public McpPlanScope Scope { get; set; } + /// The channel whose candidate document is being supplied. + [JsonPropertyName("source")] + public ManagedSettingsChannel Source { get; set; } } -/// 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. +/// Candidate managed-settings documents to merge without applying them. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -[JsonPolymorphic( - TypeDiscriminatorPropertyName = "installMethod", - UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] -[JsonDerivedType(typeof(McpPlanTransportChoicePackage), "package")] -[JsonDerivedType(typeof(McpPlanTransportChoiceRemote), "remote")] -public partial class McpPlanTransportChoice +internal sealed class ManagedSettingsComposeRequest { - /// The type discriminator. - [JsonPropertyName("installMethod")] - public virtual string InstallMethod { get; set; } = string.Empty; + /// One entry per channel. `source` must be `device`, `server`, or `policyHelper`, each at most once (checked at runtime); order does not matter, because channel precedence is fixed. To preview documents from resolve output, map recognized source strings to ManagedSettingsChannel and copy their settings; generated resolve and compose layer types are distinct. Omitted settings means this channel delivered no document. Supplied documents must be valid within the preview limits; warnings are returned in diagnostics. Compose does not reproduce source-failure state or retained enforcement floors from resolve. + [JsonPropertyName("layers")] + public IList Layers { get => field ??= []; set; } } - -/// 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. +/// Polymorphic base type discriminated by kind. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] [JsonPolymorphic( TypeDiscriminatorPropertyName = "kind", UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] -[JsonDerivedType(typeof(McpPlanRequiredValueScalar), "scalar")] -[JsonDerivedType(typeof(McpPlanRequiredValueEnum), "enum")] -public partial class McpPlanRequiredValue +[JsonDerivedType(typeof(ManagedPermissionOperationUrl), "url")] +public partial class ManagedPermissionOperation { /// The type discriminator. [JsonPropertyName("kind")] @@ -2347,1905 +2491,2196 @@ public partial class McpPlanRequiredValue } -/// One non-secret scalar value a transport choice needs before it can be applied. -/// The scalar variant of . +/// The url variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class McpPlanRequiredValueScalar : McpPlanRequiredValue +public partial class ManagedPermissionOperationUrl : ManagedPermissionOperation { /// [JsonIgnore] - public override string Kind => "scalar"; - - /// Where the value is applied when the server is launched. - [JsonPropertyName("category")] - public required McpPlanValueCategory Category { get; set; } + public override string Kind => "url"; - /// 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; } + /// Absolute HTTP(S) URL with a host. Invalid URLs reject the entire evaluation batch. + [JsonPropertyName("url")] + public required string Url { 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; } +/// RPC data type for ManagedPermissionEvaluation operations. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public sealed class ManagedPermissionEvaluation +{ + /// Original input operation associated with this verdict. + [JsonPropertyName("operation")] + public ManagedPermissionOperation Operation { get => field ??= new(); set; } - /// Whether the value may be supplied more than once. - [JsonPropertyName("isRepeated")] - public required bool IsRepeated { get; set; } + /// Managed-policy decision only; never bypasses user permissions or other security controls. + [JsonPropertyName("verdict")] + public ManagedPermissionVerdict Verdict { 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; } +/// Ordered managed permission verdicts and the supplied policy's fail-closed posture. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public sealed class ManagedSettingsPermissionsEvaluateResult +{ + /// Managed permission policy could not be determined; every operation is denied. + [JsonPropertyName("failClosed")] + public bool FailClosed { get; set; } - /// Whether the value must be present for the plan to be applicable. - [JsonPropertyName("required")] - public required bool Required { get; set; } + /// One verdict per input operation, in the same order, including duplicates. + [JsonPropertyName("results")] + public IList Results { get => field ??= []; 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; } +/// Managed policy context and ordered operations for pure, sessionless evaluation. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class ManagedSettingsPermissionsEvaluateRequest +{ + /// Trusted, reusable managed permission policy snapshot; no policy is discovered during evaluation. + [JsonPropertyName("context")] + public ManagedPermissionsContext Context { get; set; } = null!; - /// Scalar type the value must conform to. - [JsonPropertyName("valueType")] - public required McpPlanScalarValueType ValueType { get; set; } + /// Operations to evaluate, preserving input order and duplicates. An empty batch is valid. + [JsonPropertyName("operations")] + public IList Operations { get => field ??= []; set; } } -/// One enumerated non-secret value a transport choice needs before it can be applied. The permitted values are structurally required. -/// The enum variant of . +/// Hosting capabilities and session capacity advertised by an environment. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class McpPlanRequiredValueEnum : McpPlanRequiredValue +public sealed class EnvironmentCapabilities { - /// - [JsonIgnore] - public override string Kind => "enum"; + /// Advertised Agent Host Protocol version. + [JsonPropertyName("ahpVersion")] + public string? AhpVersion { get; set; } - /// Where the value is applied when the server is launched. - [JsonPropertyName("category")] - public required McpPlanValueCategory Category { get; set; } + /// Current session count, when advertised. + [JsonPropertyName("currentSessions")] + public long? CurrentSessions { 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; } + /// Feature identifiers advertised by the environment. + [JsonPropertyName("features")] + public IList Features { get => field ??= []; 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; } + /// Maximum session capacity, when advertised. + [JsonPropertyName("maxSessions")] + public long? MaxSessions { get; set; } +} - /// Non-empty permitted value set. Inert untrusted data. - [JsonPropertyName("enumValues")] - public required IList EnumValues { get; set; } +/// Safe discovery information. Host-side relay bootstrap credentials are never included. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public sealed class GitHubEnvironment +{ + /// Hosting capabilities advertised by the environment. + [JsonPropertyName("capabilities")] + public EnvironmentCapabilities? Capabilities { get; set; } - /// Whether the value may be supplied more than once. - [JsonPropertyName("isRepeated")] - public required bool IsRepeated { get; set; } + /// Identifier assigned by Mission Control. + [JsonPropertyName("id")] + public string Id { get; set; } = string.Empty; - /// 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; } + /// Compute kind reported by Mission Control. + [JsonPropertyName("kind")] + public EnvironmentKind Kind { get; set; } - /// Whether the value must be present for the plan to be applicable. - [JsonPropertyName("required")] - public required bool Required { get; set; } + /// Discovery labels attached to the environment. + [JsonPropertyName("labels")] + public IDictionary? Labels { 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; } + /// Timestamp of the last heartbeat received by Mission Control. + [JsonPropertyName("lastHeartbeatAt")] + public string? LastHeartbeatAt { get; set; } - /// Discriminator: the value must be one of `enumValues`. - [JsonPropertyName("valueType")] - public required McpPlanEnumValueType ValueType { get; set; } -} + /// Human-readable environment name. + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; -/// 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(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class McpPlanSecretPlaceholder -{ - /// 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; + /// Organization identifier, when the environment belongs to an organization. + [JsonPropertyName("orgId")] + public string? OrgId { get; set; } - /// The runtime-assigned `${secret:<id>}` placeholder written into configuration in place of the value. - [JsonPropertyName("placeholder")] - public string Placeholder { get; set; } = string.Empty; + /// Identifier of the environment owner. + [JsonPropertyName("ownerId")] + public string? OwnerId { 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)] - [JsonPropertyName("title")] - public string? Title { get; set; } + /// Owner category reported by Mission Control. + [JsonPropertyName("ownerType")] + public string? OwnerType { get; set; } + + /// Open-ended operational status vocabulary. + [JsonPropertyName("status")] + public string Status { get; set; } = string.Empty; } -/// An eligible local-package transport choice. Package identity is required and a remote endpoint cannot be represented. -/// The package variant of . +/// Environments visible to the authenticated caller and matching the supplied filters. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class McpPlanTransportChoicePackage : McpPlanTransportChoice +public sealed class EnvironmentsListResult { - /// - [JsonIgnore] - public override string InstallMethod => "package"; - - /// 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; } - - /// 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; } + /// Safe discovery records without relay bootstrap credentials. + [JsonPropertyName("environments")] + public IList Environments { get => field ??= []; set; } } -/// An eligible remote-endpoint transport choice. The endpoint is required and package identity cannot be represented. -/// The remote variant of . +/// Optional discovery filters supported by GitHub Mission Control. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class McpPlanTransportChoiceRemote : McpPlanTransportChoice +internal sealed class EnvironmentsListRequest { - /// - [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; } + /// Restrict discovery to this compute kind. + [JsonPropertyName("kind")] + public EnvironmentKind? Kind { 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; } + /// Operational status, such as online, offline, degraded, waking, or draining. + [JsonPropertyName("status")] + public string? Status { get; set; } +} - /// Typed values this choice requires, excluding secrets. - [JsonPropertyName("requiredValues")] - public required IList RequiredValues { get; set; } +/// Safe discovery information for the requested environment. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public sealed class EnvironmentsGetResult +{ + /// The requested environment without relay bootstrap credentials. + [JsonPropertyName("environment")] + public GitHubEnvironment Environment { get => field ??= new(); set; } +} - /// Secrets this choice requires, referenced by placeholder only. - [JsonPropertyName("secretPlaceholders")] - public required IList SecretPlaceholders { get; set; } +/// Identify a Mission Control environment to retrieve. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class EnvironmentsGetRequest +{ + /// Identifier assigned by Mission Control. + [JsonPropertyName("environmentId")] + public string EnvironmentId { get; set; } = string.Empty; +} - /// Endpoint transport this remote choice would use. - [JsonPropertyName("transport")] - public required McpPlanRemoteTransport Transport { get; set; } +/// Acknowledgement that the requested environment was deleted. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public sealed class EnvironmentsDeleteResult +{ } -/// A normalised, inert description of what installing an MCP server would involve. Carries no raw card, no install specification, and no secret value. +/// Identify a user-managed Mission Control environment to delete. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class McpInstallPlan +internal sealed class EnvironmentsDeleteRequest { - /// Alternative configuration changes, with exactly one entry for each transportChoices entry in the same order. Only the entry for the subsequently selected transport applies; these are not cumulative writes. Payloads remain behind the runtime boundary. - [JsonPropertyName("configurationChanges")] - public IList ConfigurationChanges { get => field ??= []; set; } + /// User-managed environment to delete. GitHub-managed environments cannot be deleted. + [JsonPropertyName("environmentId")] + public string EnvironmentId { get; set; } = string.Empty; +} - /// Normalised identity of the server the plan would install. - [JsonPropertyName("identity")] - public McpPlanResourceIdentity Identity { get => field ??= new(); set; } +/// The existing runtime identity and its resource on the listener. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public sealed class HostPublishSessionResult +{ + /// Canonical runtime ID of the published session. + [JsonPropertyName("sessionId")] + public string SessionId { 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; + /// AHP resource URI for the session on this listener. + [JsonPropertyName("sessionUri")] + public string SessionUri { get; set; } = string.Empty; +} - /// 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; +/// Publishes a resident session attached to the listener's owning connection. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class HostPublishSessionRequest +{ + /// Listener UUID returned by host.start. + [JsonPropertyName("hostId")] + public string HostId { get; set; } = string.Empty; - /// Outcome of evaluating the server against registry and enterprise policy. - [JsonPropertyName("policy")] - public McpPlanPolicyResult Policy { get => field ??= new(); set; } + /// Canonical runtime session ID attached to the listener's owning connection. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} - /// 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; } +/// Metadata advertised by the AHP host, not a guarantee that history can be resumed. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public sealed class HostSessionSummary +{ + /// Optional activity description advertised by the host. + [JsonPropertyName("activity")] + public string? Activity { 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; } + /// Session creation time as an ISO 8601 timestamp. + [JsonPropertyName("createdAt")] + public string CreatedAt { get; set; } = string.Empty; - /// Whether applying this plan would require an MCP reload to take effect. Planning itself never reloads. - [JsonPropertyName("reloadRequired")] - public bool ReloadRequired { get; set; } + /// Last modification time as an ISO 8601 timestamp. + [JsonPropertyName("modifiedAt")] + public string ModifiedAt { get; set; } = string.Empty; - /// True only when every eligible transport choice needs additional values or secrets. False means at least one choice needs no additional configuration, not that every choice is ready. A later apply operation must validate the selected choice's own inputs, secrets and policy after explicit confirmation. - [JsonPropertyName("requiresInteractiveConfiguration")] - public bool RequiresInteractiveConfiguration { get; set; } + /// Stable AHP resource URI identifying the session on this host. + [JsonPropertyName("resource")] + public string Resource { get; set; } = string.Empty; - /// Configuration scope and key the plan would write to. - [JsonPropertyName("target")] - public McpPlanTarget Target { get => field ??= new(); set; } + /// Unsigned 32-bit AHP session status bitset (0..=4294967295); unknown bits must be preserved. + [JsonPropertyName("status")] + public long Status { get; 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; } + /// Authoritative title advertised by the host. + [JsonPropertyName("title")] + public string Title { get; set; } = string.Empty; } -/// 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 . +/// The complete live and dormant advertised catalog, without subscribing to sessions. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class McpPlanInstallResultPlanned : McpPlanInstallResult +public sealed class HostListSessionsResult { - /// - [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; } + /// Complete advertised session summaries, in the host's catalog order. + [JsonPropertyName("sessions")] + public IList Sessions { get => field ??= []; set; } } -/// The caller's protocol version or required capabilities cannot be honoured. Returned instead of a partial or ambiguous success. -/// The negotiation-refused variant of . +/// Reads a running host's catalog or the runtime's saved default catalog. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class McpPlanInstallResultNegotiationRefused : McpPlanInstallResult +internal sealed class HostListSessionsRequest { - /// - [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; } + /// + /// Listener UUID returned by host.start; the caller must own it. Omit to read + /// the saved default compute-identity catalog without starting a listener. + /// + [JsonPropertyName("hostId")] + public string? HostId { get; set; } +} - /// Lowest caller protocol version this runtime will serve. - [JsonPropertyName("minimumSupportedProtocolVersion")] - public required long MinimumSupportedProtocolVersion { get; set; } +/// The resident session the application has materialized on its own connection. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class HostCreateSessionResult +{ + /// Runtime session UUID materialized on the application's SDK connection. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} - /// Whether the version or the capability set was the problem. - [JsonPropertyName("reason")] - public required CatalogNegotiationRefusedReason Reason { get; set; } +/// One application-owned session handoff, requested by the supervised hosting participant. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class HostCreateSessionRequest +{ + /// Host-selected SDK creation or resume settings, without executable callbacks or tools. + [JsonPropertyName("config")] + public IDictionary Config { get => field ??= new Dictionary(); set; } - /// Protocol version of the runtime that refused the request. - [JsonPropertyName("runtimeProtocolVersion")] - public required long RuntimeProtocolVersion { get; set; } + /// Unique identity for this participation, independent of the session lifetime. + [JsonPropertyName("handoffId")] + public string HandoffId { get; set; } = string.Empty; - /// Capabilities this runtime can safely advertise to this caller. The complete five-capability protocol-3 legacy set is always present; every capability added after that baseline appears only when the caller required it, so an older closed-enum decoder can still consume a refusal. This list does not imply that every deployment has enabled every operation. - [JsonPropertyName("supportedCapabilities")] - public required IList SupportedCapabilities { get; set; } + /// + /// For an explicit publication, borrow an existing owning-connection resident + /// instead of invoking the resume factory. Cold sessions still use the factory. + /// + [JsonPropertyName("preferResident")] + public bool? PreferResident { get; set; } - /// The subset of the caller's bounded extensible capability identifiers this runtime cannot honour. - [JsonPropertyName("unsupportedCapabilities")] - public required IList UnsupportedCapabilities { get; set; } + /// Resume an app-owned durable session instead of creating a new session. + [JsonPropertyName("resume")] + public bool? Resume { get; set; } } -/// 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 . +/// Empty acknowledgement for a completed host lifecycle operation. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class McpPlanInstallResultHandleRejected : McpPlanInstallResult +internal sealed class HostReleaseSessionResult { - /// - [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; } +/// Ends one participation, not the application's session lifetime. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class HostReleaseSessionRequest +{ + /// Identity of the participation to release without destroying the session. + [JsonPropertyName("handoffId")] + public string HandoffId { get; set; } = string.Empty; } -/// The request was rejected because a bounded field fell outside its permitted range or a required field was unusable. Pagination may also be rejected by the authority after a continuation request; repeat the search without page. -/// The invalid-request variant of . +/// Listener readiness, returned only after binding and the supervised participant's SDK handshake. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class McpPlanInstallResultInvalidRequest : McpPlanInstallResult +public sealed class HostStartResult { - /// - [JsonIgnore] - public override string Kind => "invalid-request"; + /// GitHub Mission Control environment ID, present when its relay transport is ready. + [JsonPropertyName("environmentId")] + public string? EnvironmentId { get; set; } - /// Which request field was rejected. - [JsonPropertyName("field")] - public required CatalogInvalidRequestField Field { get; set; } + /// Caller-generated listener UUID. + [JsonPropertyName("hostId")] + public string HostId { 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; } + /// Separate host process ID, when provided by a legacy runtime. Absent for in-process listeners. + [JsonPropertyName("pid")] + public long? Pid { get; set; } + + /// Secret connection token, absent when authentication is disabled. + [JsonPropertyName("token")] + public string? Token { get; set; } + + /// Actual bound WebSocket URL, including the allocated port. + [JsonPropertyName("url")] + public string? Url { get; set; } } -/// 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 . +/// GitHub Mission Control registration options. The compute ID is application-owned and stable. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class McpPlanInstallResultAuthenticationRequired : McpPlanInstallResult +public sealed class HostGitHubEnvironmentOptions { - /// - [JsonIgnore] - public override string Kind => "authentication-required"; + /// Stable application installation identity, reused across host restarts. + [JsonPropertyName("computeId")] + public string ComputeId { get; set; } = string.Empty; - /// 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; } + /// Human-readable environment display name. + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; - /// 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; } + /// Require sealed, connection-bound authentication (default true), independent of listener address. False permits unsealed direct credentials and unbound sealed relay credentials; use only when the caller fully controls and trusts the direct transport. Relay token encryption remains mandatory. + [JsonPropertyName("requireConnectionBinding")] + public bool? RequireConnectionBinding { get; set; } } -/// Registry or enterprise policy refused the operation. -/// The policy-rejected variant of . +/// Local WebSocket transport options. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class McpPlanInstallResultPolicyRejected : McpPlanInstallResult +public sealed class HostLocalServerOptions { - /// - [JsonIgnore] - public override string Kind => "policy-rejected"; + /// Listener hostname. Defaults to 127.0.0.1; explicit non-loopback binds are allowed. + [JsonPropertyName("hostname")] + public string? Hostname { 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; } + /// Listener port. Omitted or zero requests an OS-allocated port. + [JsonPropertyName("port")] + public int? Port { get; set; } - /// Which authority produced the decision. - [JsonPropertyName("source")] - public required McpPlanPolicySource Source { get; set; } + /// Require token authentication (default true). Cannot be false with a token. + [JsonPropertyName("requireConnectionToken")] + public bool? RequireConnectionToken { get; set; } + + /// Nonempty connection token. Generated randomly when required and omitted. + [JsonPropertyName("token")] + public string? Token { get; set; } } -/// 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 . +/// Starts a supervised AHP host with at least one explicitly selected transport. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class McpPlanInstallResultNetworkFailure : McpPlanInstallResult +public sealed class HostStartRequest { - /// - [JsonIgnore] - public override string Kind => "network-failure"; + /// Stable application identity selecting its durable host catalog. Defaults to the persisted runtime compute GUID; must agree with githubEnvironment.computeId when both are supplied. + [JsonPropertyName("computeId")] + public string? ComputeId { 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; } + /// Registers a GitHub Mission Control environment and enables its relay transport. + [JsonPropertyName("githubEnvironment")] + public HostGitHubEnvironmentOptions? GitHubEnvironment { get; set; } - /// Categorised failure, low cardinality so it can be aggregated without carrying a URL. - [JsonPropertyName("reason")] - public required CatalogNetworkFailureReason Reason { get; set; } + /// Caller-generated UUID identifying this connection-owned listener. + [JsonPropertyName("hostId")] + public required string HostId { 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; } + /// Enables a local WebSocket listener. + [JsonPropertyName("localServer")] + public HostLocalServerOptions? LocalServer { get; set; } - /// HTTP status code, when the failure was a rejected response. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("statusCode")] - public int? StatusCode { get; set; } + /// Ask the owning application to resume its durable AHP sessions. + [JsonPropertyName("resumeFactory")] + public bool? ResumeFactory { get; set; } + + /// Ask the owning SDK application to materialize AHP sessions. + [JsonPropertyName("sessionFactory")] + public bool? SessionFactory { get; set; } } -/// 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 . +/// Empty acknowledgement for a completed host lifecycle operation. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class McpPlanInstallResultUnsafeRetrieval : McpPlanInstallResult +public sealed class HostDisposeResult { - /// - [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; } +/// Stops a connection-owned listener and joins its teardown. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class HostDisposeRequest +{ + /// Listener UUID. Unknown or successfully stopped IDs are harmless. + [JsonPropertyName("hostId")] + public string HostId { get; set; } = string.Empty; } -/// A card could not be parsed or did not satisfy its declared media type's schema. -/// The malformed-card variant of . +/// Private credentials delivered only to a runtime-owned Mission Control hosting participant. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class McpPlanInstallResultMalformedCard : McpPlanInstallResult +internal sealed class HostGetEnvironmentCredentialsResult { - /// - [JsonIgnore] - public override string Kind => "malformed-card"; + /// GitHub API base URL for the authenticated service. + [JsonPropertyName("githubApiUrl")] + public string GitHubApiUrl { 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; } + /// Hostname of the authenticated GitHub service. + [JsonPropertyName("githubHost")] + public string GitHubHost { 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; } + /// Mission Control API origin for environment registration and management. + [JsonPropertyName("missionControlUrl")] + public string MissionControlUrl { get; set; } = string.Empty; - /// How the card failed validation. - [JsonPropertyName("reason")] - public required CatalogMalformedCardReason Reason { get; set; } + /// Current bearer token for the authenticated GitHub identity. + [JsonPropertyName("token")] + public string Token { get; set; } = string.Empty; } -/// 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 . +/// Normalized local WebSocket listener settings. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class McpPlanInstallResultContractViolation : McpPlanInstallResult +public sealed class HostLocalServerConfiguration { - /// - [JsonIgnore] - public override string Kind => "contract-violation"; + /// Hostname or IP address to bind. + [JsonPropertyName("hostname")] + public string Hostname { 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; } + /// Port to bind, with zero requesting OS allocation. + [JsonPropertyName("port")] + public int Port { get; set; } - /// Which rule the response broke. - [JsonPropertyName("reason")] - public required CatalogContractViolationReason Reason { get; set; } + /// Whether the listener requires token authentication. + [JsonPropertyName("requireConnectionToken")] + public bool RequireConnectionToken { get; set; } + + /// Secret connection token, absent when authentication is disabled. + [JsonPropertyName("token")] + public string? Token { get; set; } } -/// No transport this runtime can use is available for the requested server. -/// The unavailable-transport variant of . +/// Normalized listener settings delivered only to the supervised hosting participant. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class McpPlanInstallResultUnavailableTransport : McpPlanInstallResult +internal sealed class HostGetConfigurationResult { - /// - [JsonIgnore] - public override string Kind => "unavailable-transport"; + /// Requested GitHub Mission Control registration. + [JsonPropertyName("githubEnvironment")] + public HostGitHubEnvironmentOptions? GitHubEnvironment { 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; } + /// Normalized local listener settings, absent for relay-only hosts. + [JsonPropertyName("localServer")] + public HostLocalServerConfiguration? LocalServer { get; set; } - /// Why no transport could be offered. - [JsonPropertyName("reason")] - public required CatalogUnavailableTransportReason Reason { get; set; } + /// Whether app-owned durable sessions are resumed by the owning application. + [JsonPropertyName("resumeFactory")] + public bool ResumeFactory { get; set; } + + /// Whether session materialization is delegated to the owning application. + [JsonPropertyName("sessionFactory")] + public bool SessionFactory { get; set; } } -/// 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 . +/// Empty acknowledgement for a completed host lifecycle operation. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class McpPlanInstallResultNotInstallable : McpPlanInstallResult +internal sealed class HostReadyResult { - /// - [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; } +/// Readiness reported by the supervised hosting participant on its own SDK connection. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class HostReadyRequest +{ + /// Actual bound WebSocket URL. + [JsonPropertyName("address")] + public string? Address { get; set; } - /// Why the candidate cannot be installed. - [JsonPropertyName("reason")] - public required CatalogNotInstallableReason Reason { get; set; } + /// Registered environment ID, reported only once the relay transport is connected. + [JsonPropertyName("environmentId")] + public string? EnvironmentId { get; set; } + + /// Configured secret token, absent when authentication is disabled. + [JsonPropertyName("token")] + public string? Token { get; set; } } -/// The operation is not available on this runtime. Distinct from a network failure: nothing was attempted. -/// The unavailable variant of . +/// One server-discovered hook action from user, repository, plugin, or managed-policy configuration. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class McpPlanInstallResultUnavailable : McpPlanInstallResult +public sealed class DiscoveredHook { - /// - [JsonIgnore] - public override string Kind => "unavailable"; + /// 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; } - /// 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 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; } - /// Why the operation is unavailable. - [JsonPropertyName("reason")] - public required CatalogUnavailableReason Reason { 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; } } -/// The protocol version and capability set a caller requires, supplied on every catalog request so negotiation cannot be skipped by omission. +/// 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(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class CatalogClientContract +public sealed class HooksDiscoverResult { - /// 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; } + /// 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; } - /// 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; } + /// 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; } } -/// 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. +/// Optional project paths and host-exclusion behavior for server-scoped hook discovery. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -[JsonPolymorphic( - TypeDiscriminatorPropertyName = "kind", - UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] -[JsonDerivedType(typeof(McpPlanInstallSourceCandidate), "candidate")] -[JsonDerivedType(typeof(McpPlanInstallSourceCard), "card")] -public partial class McpPlanInstallSource +public sealed class HooksDiscoverRequest { - /// The type discriminator. - [JsonPropertyName("kind")] - public virtual string Kind { get; set; } = string.Empty; -} + /// 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; } + /// Explicit discovery-only file-hook roots, separate from callback hooks and filesystem grants. Available without ambient discovery or repository file-hook enablement, subject to managed/hook disables. Relative paths resolve against the request's effective working directory when supplied. Null and omission are equivalent; an empty array contributes no explicit roots. + [JsonPropertyName("hookDirectories")] + public IList? HookDirectories { get; set; } -/// Plan from a candidate returned by a previous catalog search. -/// The candidate variant of . + /// 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(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class McpPlanInstallSourceCandidate : McpPlanInstallSource +public sealed class ModelBillingPromo { - /// - [JsonIgnore] - public override string Kind => "candidate"; + /// Percentage discount (0-100) applied while the promotion is active. May be fractional. + [JsonPropertyName("discountPercent")] + public double? DiscountPercent { get; set; } - /// 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; } + /// UTC ISO 8601 timestamp marking when the promotion ends. Optional: an open-ended promotion omits this field. When present, the API only surfaces a promo whose expiry parses and is in the future, so consumers should treat a past value as expired. + [JsonPropertyName("endsAt")] + public string? EndsAt { get; set; } - /// The runtime- or authority-minted `searchId` returned with the search that produced this candidate. Planning verifies the private correlation and atomically consumes a matching candidate before downstream work, including attempts that subsequently fail or report unavailable. A mismatched search does not consume the candidate. 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; } + /// Stable identifier for the promotion campaign. + [JsonPropertyName("id")] + public string? Id { get; set; } + + /// Human-readable promotion message. Does not include the expiry timestamp; consumers may format endsAt and append it 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; } } -/// 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. +/// Long context tier pricing (available for models with extended context windows). [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -[JsonPolymorphic( - TypeDiscriminatorPropertyName = "kind", - UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] -[JsonDerivedType(typeof(McpServerCardReferenceUrl), "url")] -[JsonDerivedType(typeof(McpServerCardReferenceEmbedded), "embedded")] -public partial class McpServerCardReference +public sealed class ModelBillingTokenPricesLongContext { - /// The type discriminator. - [JsonPropertyName("kind")] - public virtual string Kind { get; set; } = string.Empty; -} + /// Use cacheReadPrice instead. AI Credits cost per billing batch of cached tokens. + [EditorBrowsable(EditorBrowsableState.Never)] +#if NET5_0_OR_GREATER + [Obsolete("This member is deprecated and will be removed in a future version.", DiagnosticId = "GHCP001")] +#else + [Obsolete("This member is deprecated and will be removed in a future version.")] +#endif + [JsonPropertyName("cachePrice")] + public double? CachePrice { get; set; } + /// AI Credits cost per billing batch of cached (read) tokens. + [JsonPropertyName("cacheReadPrice")] + public double? CacheReadPrice { get; set; } -/// An MCP server card to be retrieved from a URL through the runtime's hardened fetch boundary. -/// The url variant of . + /// 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; } + + /// 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. + [EditorBrowsable(EditorBrowsableState.Never)] +#if NET5_0_OR_GREATER + [Obsolete("This member is deprecated and will be removed in a future version.", DiagnosticId = "GHCP001")] +#else + [Obsolete("This member is deprecated and will be removed in a future version.")] +#endif + [JsonPropertyName("contextMax")] + public long? ContextMax { get; set; } + + /// AI Credits cost per billing batch of input tokens. + [JsonPropertyName("inputPrice")] + public double? InputPrice { get; set; } + + /// Prompt token budget for the long context tier. The total context window is this value plus the model's max_output_tokens. + [JsonPropertyName("maxPromptTokens")] + public long? MaxPromptTokens { get; set; } + + /// AI Credits cost per billing batch of output tokens. + [JsonPropertyName("outputPrice")] + public double? OutputPrice { get; set; } +} + +/// Token-level pricing information for this model. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class McpServerCardReferenceUrl : McpServerCardReference +public sealed class ModelBillingTokenPrices { - /// - [JsonIgnore] - public override string Kind => "url"; + /// Number of tokens per standard billing batch. + [JsonPropertyName("batchSize")] + public long? BatchSize { get; set; } - /// Media type the card is expected to conform to. - [JsonPropertyName("mediaType")] - public required McpServerCardMediaType MediaType { get; set; } + /// Use cacheReadPrice instead. AI Credits cost per billing batch of cached tokens. + [EditorBrowsable(EditorBrowsableState.Never)] +#if NET5_0_OR_GREATER + [Obsolete("This member is deprecated and will be removed in a future version.", DiagnosticId = "GHCP001")] +#else + [Obsolete("This member is deprecated and will be removed in a future version.")] +#endif + [JsonPropertyName("cachePrice")] + public double? CachePrice { 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; } + /// AI Credits cost per billing batch of cached (read) tokens. + [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; } + + /// Use maxPromptTokens instead. Prompt token budget for the default tier. The total context window is this value plus the model's max_output_tokens. + [EditorBrowsable(EditorBrowsableState.Never)] +#if NET5_0_OR_GREATER + [Obsolete("This member is deprecated and will be removed in a future version.", DiagnosticId = "GHCP001")] +#else + [Obsolete("This member is deprecated and will be removed in a future version.")] +#endif + [JsonPropertyName("contextMax")] + public long? ContextMax { get; set; } + + /// AI Credits cost per billing batch of input tokens. + [JsonPropertyName("inputPrice")] + public double? InputPrice { get; set; } + + /// Long context tier pricing (available for models with extended context windows). + [JsonPropertyName("longContext")] + public ModelBillingTokenPricesLongContext? LongContext { get; set; } + + /// Prompt token budget for the default tier. The total context window is this value plus the model's max_output_tokens. + [JsonPropertyName("maxPromptTokens")] + public long? MaxPromptTokens { get; set; } + + /// AI Credits cost per billing batch of output tokens. + [JsonPropertyName("outputPrice")] + public double? OutputPrice { get; set; } } -/// An MCP server card supplied inline as an inert document. -/// The embedded variant of . +/// Billing information. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class McpServerCardReferenceEmbedded : McpServerCardReference +public sealed class ModelBilling { - /// - [JsonIgnore] - public override string Kind => "embedded"; + /// 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. + [JsonPropertyName("discountPercent")] + public int? DiscountPercent { get; 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; } + /// Billing cost multiplier relative to the base rate. + [JsonPropertyName("multiplier")] + public double? Multiplier { get; set; } - /// Media type the card is expected to conform to. - [JsonPropertyName("mediaType")] - public required McpServerCardMediaType MediaType { get; set; } + /// Active server-driven promotion for this model, if any. Present when the model is being promoted with a discount, which may be time-boxed or open-ended. + [JsonPropertyName("promo")] + public ModelBillingPromo? Promo { get; set; } + + /// Token-level pricing information for this model. + [JsonPropertyName("tokenPrices")] + public ModelBillingTokenPrices? TokenPrices { get; set; } } -/// Plan from a card supplied directly by the caller, without a preceding search. -/// The card variant of . +/// Vision-specific limits. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class McpPlanInstallSourceCard : McpPlanInstallSource +public sealed class ModelCapabilitiesLimitsVision { - /// - [JsonIgnore] - public override string Kind => "card"; + /// Maximum image size in bytes. + [JsonPropertyName("max_prompt_image_size")] + public long MaxPromptImageSize { get; set; } - /// The card to plan from: exactly one of a URL or embedded data. - [JsonPropertyName("card")] - public required McpServerCardReference Card { get; set; } + /// Maximum number of images per prompt. + [JsonPropertyName("max_prompt_images")] + public long MaxPromptImages { get; set; } + + /// MIME types the model accepts. + [JsonPropertyName("supported_media_types")] + public IList SupportedMediaTypes { get => field ??= []; set; } } -/// A side-effect-free request for an MCP install plan. Computing a plan never writes configuration, stores a secret, or reloads MCP servers. +/// Token limits for prompts, outputs, and context window. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class McpPlanInstallRequest +public sealed class ModelCapabilitiesLimits { - /// Protocol version and capabilities the caller requires. - [JsonPropertyName("contract")] - public required CatalogClientContract Contract { get; set; } + /// Maximum total context window size in tokens. + [JsonPropertyName("max_context_window_tokens")] + public long? MaxContextWindowTokens { get; set; } - /// The same existing attached session that owns the original catalogue candidate. - [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("policySessionId")] - public string? PolicySessionId { get; set; } + /// Maximum number of output/completion tokens. + [JsonPropertyName("max_output_tokens")] + public long? MaxOutputTokens { get; set; } - /// Configuration scope the plan targets. Defaults to user scope when omitted. - [JsonPropertyName("scope")] - public McpPlanScope? Scope { get; set; } + /// Maximum number of prompt/input tokens. + [JsonPropertyName("max_prompt_tokens")] + public long? MaxPromptTokens { get; set; } - /// What to plan: either a candidate handle from a previous search, or a card supplied directly. - [JsonPropertyName("source")] - public required McpPlanInstallSource Source { get; set; } + /// Vision-specific limits. + [JsonPropertyName("vision")] + public ModelCapabilitiesLimitsVision? Vision { get; set; } } -/// Management result with contract receipt, or a typed request/negotiation refusal. -/// Polymorphic base type discriminated by kind. +/// Feature flags indicating what the model supports. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -[JsonPolymorphic( - TypeDiscriminatorPropertyName = "kind", - UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] -[JsonDerivedType(typeof(McpInstallationManagementResultOutcome), "outcome")] -[JsonDerivedType(typeof(McpInstallationManagementResultNegotiationRefused), "negotiation-refused")] -[JsonDerivedType(typeof(McpInstallationManagementResultInvalidRequest), "invalid-request")] -public partial class McpInstallationManagementResult +public sealed class ModelCapabilitiesSupports { - /// The type discriminator. - [JsonPropertyName("kind")] - public virtual string Kind { get; set; } = string.Empty; -} + /// Resolved Anthropic adaptive-thinking capability — unsupported / optional / required / adaptive_only. 'required' models reject thinking.type='enabled' with HTTP 400 but still accept 'disabled' (e.g. opus-4.7/4.8/5, sonnet-5); 'adaptive_only' models accept nothing but 'adaptive' (e.g. fable, mythos). + [JsonPropertyName("adaptive_thinking")] + public AdaptiveThinkingSupport? AdaptiveThinking { get; set; } + /// Whether this model supports reasoning effort configuration. + [JsonPropertyName("reasoningEffort")] + public bool? ReasoningEffort { get; set; } -/// Read-only or recovery management result, never permission to activate or replay. -/// Polymorphic base type discriminated by kind. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -[JsonPolymorphic( - TypeDiscriminatorPropertyName = "kind", - UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] -[JsonDerivedType(typeof(McpInstallationManagementOutcomeRecoveryRequired), "recovery-required")] -[JsonDerivedType(typeof(McpInstallationManagementOutcomeInstallPrepared), "install-prepared")] -[JsonDerivedType(typeof(McpInstallationManagementOutcomeListed), "listed")] -[JsonDerivedType(typeof(McpInstallationManagementOutcomeRecovered), "recovered")] -[JsonDerivedType(typeof(McpInstallationManagementOutcomeUninstallPlanned), "uninstall-planned")] -[JsonDerivedType(typeof(McpInstallationManagementOutcomeOperation), "operation")] -[JsonDerivedType(typeof(McpInstallationManagementOutcomeRefused), "refused")] -public partial class McpInstallationManagementOutcome -{ - /// The type discriminator. - [JsonPropertyName("kind")] - public virtual string Kind { get; set; } = string.Empty; -} + /// Whether the model supports provider-native thinking. Independent of configurable reasoning effort; omission means unknown. + [JsonPropertyName("thinking")] + public bool? Thinking { get; set; } + /// Whether this model supports canonical tool calling. + [JsonPropertyName("toolCalls")] + public bool? ToolCalls { get; set; } -/// Already-confirmed durable work must be reconciled before new mutations or inventory. -/// The recovery-required variant of . + /// Whether this model supports vision/image input. + [JsonPropertyName("vision")] + public bool? Vision { get; set; } +} + +/// Model capabilities and limits. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class McpInstallationManagementOutcomeRecoveryRequired : McpInstallationManagementOutcome +public sealed class ModelCapabilities { - /// - [JsonIgnore] - public override string Kind => "recovery-required"; + /// Token limits for prompts, outputs, and context window. + [JsonPropertyName("limits")] + public ModelCapabilitiesLimits? Limits { get; set; } + + /// Feature flags indicating what the model supports. + [JsonPropertyName("supports")] + public ModelCapabilitiesSupports? Supports { get; set; } } -/// Inert, runtime-owned admission. The operation ID is known before confirmation or effects. +/// A service-published message about a model, carrying a stable machine-readable code alongside human-readable text. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class McpPreparedInstall +public sealed class ModelMessage { - /// Original plan expiry in Unix epoch milliseconds; preparation does not extend it. - [JsonPropertyName("expiresAtEpochMs")] - public long ExpiresAtEpochMs { get; set; } + /// 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; - /// Original connection-owned operation, known before the first confirmation callback. - [JsonPropertyName("operationId")] - public string OperationId { get; set; } = string.Empty; + /// Human-readable message text intended for display to the user. + [JsonPropertyName("message")] + public string Message { get; set; } = string.Empty; } -/// The install-prepared variant of . +/// Policy state (if applicable). [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class McpInstallationManagementOutcomeInstallPrepared : McpInstallationManagementOutcome +public sealed class ModelPolicy { - /// - [JsonIgnore] - public override string Kind => "install-prepared"; + /// Current policy state for this model. + [JsonPropertyName("state")] + public ModelPolicyState State { get; set; } - /// Known original operation identity, returned before callback or effects. - [JsonPropertyName("operation")] - public required McpPreparedInstall Operation { get; set; } + /// Usage terms or conditions for this model. + [JsonPropertyName("terms")] + public string? Terms { get; set; } } -/// A versioned, bounded trust observation carried unchanged with a catalog candidate and its private handle context. Current observations require a recognised T1/T2 tier; every non-current state structurally forbids a tier. Eligibility remains `unknown` while Agent Finder supplies no exposure decision, and states absent from its current wire are never inferred from age, relevance, popularity, or a tier transition. -/// Polymorphic base type discriminated by status. +/// Service-published warning text that hosts should display when presenting a model. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -[JsonPolymorphic( - TypeDiscriminatorPropertyName = "status", - UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] -[JsonDerivedType(typeof(CatalogTrustSnapshotCurrent), "current")] -[JsonDerivedType(typeof(CatalogTrustSnapshotAbsent), "absent")] -[JsonDerivedType(typeof(CatalogTrustSnapshotStale), "stale")] -[JsonDerivedType(typeof(CatalogTrustSnapshotDowngraded), "downgraded")] -[JsonDerivedType(typeof(CatalogTrustSnapshotRevoked), "revoked")] -[JsonDerivedType(typeof(CatalogTrustSnapshotUnsupported), "unsupported")] -[JsonDerivedType(typeof(CatalogTrustSnapshotMalformed), "malformed")] -public partial class CatalogTrustSnapshot +public sealed class ModelWarningText { - /// The type discriminator. - [JsonPropertyName("status")] - public virtual string Status { get; set; } = string.Empty; + /// 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; } } - -/// Where and when the runtime observed the trust metadata. Observation time is not the authority's evaluation time and must not be used to infer staleness. +/// Copilot model metadata, including identifier, display name, capabilities, policy, billing, reasoning efforts, and picker categories. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class CatalogTrustProvenance +public sealed class Model { - /// ISO 8601 timestamp with a timezone offset at which the runtime observed the search result carrying this trust field. - [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(20)] - [MaxLength(64)] - [JsonPropertyName("observedAt")] - public DateTimeOffset ObservedAt { get; set; } + /// Billing information. + [JsonPropertyName("billing")] + public ModelBilling? Billing { get; set; } - /// Bounded authority that supplied the trust field. - [JsonPropertyName("source")] - public CatalogTrustSource Source { get; set; } -} + /// Model capabilities and limits. + [JsonPropertyName("capabilities")] + public ModelCapabilities Capabilities { get => field ??= new(); set; } -/// A recognised current Agent Finder T1 or T2 trust tier. -/// The current variant of . -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class CatalogTrustSnapshotCurrent : CatalogTrustSnapshot -{ - /// - [JsonIgnore] - public override string Status => "current"; + /// Default reasoning effort level (only present if model supports reasoning effort). + [JsonPropertyName("defaultReasoningEffort")] + public string? DefaultReasoningEffort { get; set; } - /// Service-computed exposure eligibility. `unknown` is required while Agent Finder returns no explicit eligibility field. - [JsonPropertyName("eligibility")] - public required CatalogTrustEligibility Eligibility { get; set; } + /// Model identifier (e.g., "claude-sonnet-4.5"). + [JsonPropertyName("id")] + public string Id { get; set; } = string.Empty; - /// Bounded source and observation time for this snapshot. This is distinct from evidence used by the authority to calculate trust. - [JsonPropertyName("provenance")] - public required CatalogTrustProvenance Provenance { get; set; } + /// 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; } - /// Schema version of this runtime-owned snapshot envelope. - [JsonPropertyName("schemaVersion")] - public required CatalogTrustSnapshotSchemaVersion SchemaVersion { get; set; } + /// Provider-supplied model metadata. Keys and JSON-compatible values are preserved unchanged. This is factual metadata published by the model provider; it carries no picker or UX semantics. + [JsonPropertyName("metadata")] + public IDictionary? Metadata { get; set; } - /// Service-computed T1 or T2 trust tier. - [JsonPropertyName("tier")] - public required CatalogTrustTier Tier { get; set; } -} + /// Model capability category for grouping in the model picker. + [JsonPropertyName("modelPickerCategory")] + public ModelPickerCategory? ModelPickerCategory { get; set; } -/// Discriminator: the authority omitted trust metadata. -/// The absent variant of . -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class CatalogTrustSnapshotAbsent : CatalogTrustSnapshot -{ - /// - [JsonIgnore] - public override string Status => "absent"; + /// Relative cost tier for token-based billing users. + [JsonPropertyName("modelPickerPriceCategory")] + public ModelPickerPriceCategory? ModelPickerPriceCategory { get; set; } - /// Service-computed exposure eligibility. `unknown` is required while Agent Finder returns no explicit eligibility field. - [JsonPropertyName("eligibility")] - public required CatalogTrustEligibility Eligibility { get; set; } + /// Display name. + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; - /// Bounded source and observation time for this snapshot. This is distinct from evidence used by the authority to calculate trust. - [JsonPropertyName("provenance")] - public required CatalogTrustProvenance Provenance { get; set; } + /// Policy state (if applicable). + [JsonPropertyName("policy")] + public ModelPolicy? Policy { get; set; } - /// Schema version of this runtime-owned snapshot envelope. - [JsonPropertyName("schemaVersion")] - public required CatalogTrustSnapshotSchemaVersion SchemaVersion { get; set; } -} + /// The model provider that produced this model, as a neutral reference (opaque id, human-readable label, and provider kind). Present on models returned by `session.model.list`, which resolves provider attribution from the session's account roster; absent on the flat `server.models.list`, which does not resolve a session roster. The model picker groups by this reference. + [JsonPropertyName("provider")] + public ModelProviderRef? Provider { get; set; } -/// Discriminator: the authority explicitly marked the assessment stale. -/// The stale variant of . -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class CatalogTrustSnapshotStale : CatalogTrustSnapshot -{ - /// - [JsonIgnore] - public override string Status => "stale"; + /// 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; } - /// Service-computed exposure eligibility. `unknown` is required while Agent Finder returns no explicit eligibility field. - [JsonPropertyName("eligibility")] - public required CatalogTrustEligibility Eligibility { get; set; } + /// Supported reasoning effort levels (only present if model supports reasoning effort). + [JsonPropertyName("supportedReasoningEfforts")] + public IList? SupportedReasoningEfforts { get; set; } - /// Bounded source and observation time for this snapshot. This is distinct from evidence used by the authority to calculate trust. - [JsonPropertyName("provenance")] - public required CatalogTrustProvenance Provenance { get; set; } + /// Model vendor as the Copilot API reports it, for example "Anthropic" or "Azure OpenAI". Open vocabulary, passed through unchanged. It can name the vendor that serves the model instead of the one that built it, or a label that is not a vendor, such as "Experimental". Absent when the Copilot API reports no vendor. + [JsonPropertyName("vendor")] + public string? Vendor { get; set; } - /// Schema version of this runtime-owned snapshot envelope. - [JsonPropertyName("schemaVersion")] - public required CatalogTrustSnapshotSchemaVersion SchemaVersion { 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; } } -/// Discriminator: the authority explicitly reported a downgraded assessment. -/// The downgraded variant of . +/// List of Copilot models available to the resolved user, including capabilities and billing metadata. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class CatalogTrustSnapshotDowngraded : CatalogTrustSnapshot +public sealed class ModelList { - /// - [JsonIgnore] - public override string Status => "downgraded"; - - /// Service-computed exposure eligibility. `unknown` is required while Agent Finder returns no explicit eligibility field. - [JsonPropertyName("eligibility")] - public required CatalogTrustEligibility Eligibility { get; set; } - - /// Bounded source and observation time for this snapshot. This is distinct from evidence used by the authority to calculate trust. - [JsonPropertyName("provenance")] - public required CatalogTrustProvenance Provenance { get; set; } - - /// Schema version of this runtime-owned snapshot envelope. - [JsonPropertyName("schemaVersion")] - public required CatalogTrustSnapshotSchemaVersion SchemaVersion { get; set; } + /// List of available models with full metadata. + [JsonPropertyName("models")] + public IList Models { get => field ??= []; set; } } -/// Discriminator: the authority explicitly revoked the assessment. -/// The revoked variant of . +/// RPC data type for ModelsList operations. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class CatalogTrustSnapshotRevoked : CatalogTrustSnapshot +internal sealed class ModelsListRequest { - /// - [JsonIgnore] - public override string Status => "revoked"; - - /// Service-computed exposure eligibility. `unknown` is required while Agent Finder returns no explicit eligibility field. - [JsonPropertyName("eligibility")] - public required CatalogTrustEligibility Eligibility { get; set; } - - /// Bounded source and observation time for this snapshot. This is distinct from evidence used by the authority to calculate trust. - [JsonPropertyName("provenance")] - public required CatalogTrustProvenance Provenance { get; set; } + /// 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; } - /// Schema version of this runtime-owned snapshot envelope. - [JsonPropertyName("schemaVersion")] - public required CatalogTrustSnapshotSchemaVersion SchemaVersion { get; set; } + /// Opaque account identifier returned by `account.getAllUsers`. When omitted, the current account is used. + [JsonPropertyName("selectionId")] + public string? SelectionId { get; set; } } -/// Discriminator: the authority supplied a bounded trust value this runtime does not understand. -/// The unsupported variant of . +/// A well-known model in the runtime's built-in catalog. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class CatalogTrustSnapshotUnsupported : CatalogTrustSnapshot +public sealed class BuiltInModelCatalogEntry { - /// - [JsonIgnore] - public override string Status => "unsupported"; - - /// Service-computed exposure eligibility. `unknown` is required while Agent Finder returns no explicit eligibility field. - [JsonPropertyName("eligibility")] - public required CatalogTrustEligibility Eligibility { get; set; } - - /// Bounded source and observation time for this snapshot. This is distinct from evidence used by the authority to calculate trust. - [JsonPropertyName("provenance")] - public required CatalogTrustProvenance Provenance { get; set; } - - /// Schema version of this runtime-owned snapshot envelope. - [JsonPropertyName("schemaVersion")] - public required CatalogTrustSnapshotSchemaVersion SchemaVersion { get; set; } + /// 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; } -/// Discriminator: the trust field was empty, unbounded, or had the wrong JSON type. -/// The malformed variant of . +/// The running runtime's complete catalog of well-known built-in model IDs, including supported models and additional IDs with built-in metadata. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class CatalogTrustSnapshotMalformed : CatalogTrustSnapshot +public sealed class BuiltInModelCatalog { - /// - [JsonIgnore] - public override string Status => "malformed"; - - /// Service-computed exposure eligibility. `unknown` is required while Agent Finder returns no explicit eligibility field. - [JsonPropertyName("eligibility")] - public required CatalogTrustEligibility Eligibility { get; set; } - - /// Bounded source and observation time for this snapshot. This is distinct from evidence used by the authority to calculate trust. - [JsonPropertyName("provenance")] - public required CatalogTrustProvenance Provenance { get; set; } - - /// Schema version of this runtime-owned snapshot envelope. - [JsonPropertyName("schemaVersion")] - public required CatalogTrustSnapshotSchemaVersion SchemaVersion { get; set; } + /// Built-in model entries. + [JsonPropertyName("models")] + public IList Models { get => field ??= []; set; } } -/// Catalogue identity retained from a bound candidate or plan at installation time. +/// The accepted policies, parsed and normalized. A rejected input is reported as an error rather than in this result. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class InstallationCatalogueIdentity +public sealed class ContentExclusionValidatePoliciesResult { - /// Catalogue description retained at install planning time. - [JsonPropertyName("description")] - public string? Description { get; set; } - - /// Human display name retained from the catalogue candidate. - [JsonPropertyName("displayName")] - public string DisplayName { get; set; } = string.Empty; - - /// Catalogue item URL when supplied by the authority. - [JsonPropertyName("itemUrl")] - public string? ItemUrl { get; set; } - - /// Catalogue publisher retained at install planning time. - [JsonPropertyName("publisher")] - public string? Publisher { get; set; } - - /// Authority resource identifier when supplied by the catalogue. - [JsonPropertyName("resourceId")] - public string? ResourceId { get; set; } - - /// Catalogue authority/source string that supplied the candidate. - [JsonPropertyName("source")] - public string Source { get; set; } = string.Empty; - - /// Catalogue trust observation retained at install planning time. - [JsonPropertyName("trustAtInstall")] - public CatalogTrustSnapshot? TrustAtInstall { get; set; } - - /// Catalogue version retained at install planning time. - [JsonPropertyName("version")] - public string? Version { get; set; } + /// Validated policy objects in input order. Raw JSON preserves every field returned by the runtime's policy parser. + [JsonPropertyName("policies")] + public IList Policies { get => field ??= []; set; } } -/// Durable configuration ownership is distinct from session-specific usability. +/// Candidate content-exclusion policies to validate, as the raw JSON text a host received. The text is supplied unparsed so that a syntax error and a schema violation are both reported by the runtime's own validator rather than by the caller's JSON parser. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class McpInstallationSummary +internal sealed class ContentExclusionValidatePoliciesRequest { - /// Catalogue identity retained from the installed plan when available. - [JsonPropertyName("catalogue")] - public InstallationCatalogueIdentity? Catalogue { get; set; } + /// Unparsed JSON text expected to hold an array of content-exclusion policies. + [JsonPropertyName("policiesJson")] + public string PoliciesJson { get; set; } = string.Empty; +} - /// Exact alternative retained in the installing receipt. - [JsonPropertyName("choiceId")] - public string ChoiceId { get; set; } = string.Empty; +/// Built-in tool metadata with identifier, optional namespaced name, description, input-parameter schema, and usage instructions. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public sealed class Tool +{ + /// Description of what the tool does. + [JsonPropertyName("description")] + public string Description { get; set; } = string.Empty; - /// Identity retained from the validated original plan. - [JsonPropertyName("identity")] - public McpPlanResourceIdentity Identity { get => field ??= new(); set; } + /// Optional instructions for how to use this tool effectively. + [JsonPropertyName("instructions")] + public string? Instructions { get; set; } - /// Exact durable installation receipt identity. - [JsonPropertyName("installationId")] - public string InstallationId { get; set; } = string.Empty; + /// Tool identifier (e.g., "bash", "grep", "str_replace_editor"). + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; - /// ISO 8601 wall-clock installation time when available. - [JsonPropertyName("installedAt")] - public string? InstalledAt { get; set; } + /// Optional namespaced name for declarative filtering (e.g., "playwright/navigate" for MCP tools). + [JsonPropertyName("namespacedName")] + public string? NamespacedName { get; set; } - /// Original installing operation, not a fresh management operation. - [JsonPropertyName("operationId")] - public string OperationId { get; set; } = string.Empty; + /// JSON Schema for the tool's input parameters. + [JsonPropertyName("parameters")] + public IDictionary? Parameters { get; set; } - /// Ownership or setup state, never inferred proof of tool usability. - [JsonPropertyName("state")] - public McpInstallationState State { get; set; } + /// Telemetry-safety policy for the tool name and input names, not input values. Treat omitted metadata as unsafe. + [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] + [JsonPropertyName("safeForTelemetry")] + public JsonElement? SafeForTelemetry { get; set; } } -/// The listed variant of . +/// Built-in tools available for the requested model, with their parameters and instructions. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class McpInstallationManagementOutcomeListed : McpInstallationManagementOutcome +public sealed class ToolList { - /// - [JsonIgnore] - public override string Kind => "listed"; - - /// Owned receipts visible to the selected account and host. - [JsonPropertyName("installations")] - public required IList Installations { get; set; } + /// List of available built-in tools with metadata. + [JsonPropertyName("tools")] + public IList Tools { get => field ??= []; set; } } -/// The recovered variant of . +/// Optional model identifier whose tool overrides should be applied to the listing. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class McpInstallationManagementOutcomeRecovered : McpInstallationManagementOutcome +internal sealed class ToolsListRequest { - /// - [JsonIgnore] - public override string Kind => "recovered"; - - /// Freshly inspected receipts after successful durable reconciliation. - [JsonPropertyName("installations")] - public required IList Installations { get; set; } + /// Optional model ID — when provided, the returned tool list reflects model-specific overrides. + [JsonPropertyName("model")] + public string? Model { get; set; } } -/// Exact inert removal plan. No configuration or credentials have changed. +/// Quota usage snapshot for a Copilot quota type, including entitlement, used requests, overage, reset date, and remaining percentage. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class McpUninstallPlan +public sealed class AccountQuotaSnapshot { - /// Original wall-clock expiry in milliseconds. Applying never renews it. - [JsonPropertyName("expiresAtEpochMs")] - public long ExpiresAtEpochMs { get; set; } + /// Number of requests included in the entitlement, or -1 for unlimited entitlements. + [JsonPropertyName("entitlementRequests")] + public long EntitlementRequests { get; set; } - /// Original owned receipt being removed. - [JsonPropertyName("installation")] - public McpInstallationSummary Installation { get => field ??= new(); set; } + /// Whether the user has an unlimited usage entitlement. + [JsonPropertyName("isUnlimitedEntitlement")] + public bool IsUnlimitedEntitlement { get; set; } - /// The original operation, inspectable and cancellable on this same connection. - [JsonPropertyName("operationId")] - public string OperationId { get; set; } = string.Empty; + /// Number of additional usage requests made this period. + [JsonPropertyName("overage")] + public double Overage { get; set; } - /// Exact configured input slots owned by this installation, never shared OAuth tokens. - [JsonPropertyName("ownedSecretCount")] - public long OwnedSecretCount { get; set; } + /// Whether additional usage is allowed when quota is exhausted. + [JsonPropertyName("overageAllowedWithExhaustedQuota")] + public bool OverageAllowedWithExhaustedQuota { get; set; } - /// One-use original connection and authority-bound plan handle. - [JsonPropertyName("planHandle")] - public string PlanHandle { get; set; } = string.Empty; + /// Percentage of entitlement remaining. + [JsonPropertyName("remainingPercentage")] + public double RemainingPercentage { get; set; } - /// Shared authentication is deliberately retained; revocation is a separate action. - [JsonPropertyName("preservesSharedAuthentication")] - public bool PreservesSharedAuthentication { get; set; } + /// Date when the quota resets (ISO 8601 string). + [JsonPropertyName("resetDate")] + public DateTimeOffset? ResetDate { get; set; } - /// Whether removal restores a protected earlier configuration. - [JsonPropertyName("restoresPreviousConfiguration")] - public bool RestoresPreviousConfiguration { get; set; } + /// Whether usage is still permitted after quota exhaustion. + [JsonPropertyName("usageAllowedWithExhaustedQuota")] + public bool UsageAllowedWithExhaustedQuota { get; set; } + + /// Number of requests used so far this period. + [JsonPropertyName("usedRequests")] + public long UsedRequests { get; set; } } -/// The uninstall-planned variant of . +/// Quota usage snapshots for the resolved user, keyed by quota type. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class McpInstallationManagementOutcomeUninstallPlanned : McpInstallationManagementOutcome +public sealed class AccountGetQuotaResult { - /// - [JsonIgnore] - public override string Kind => "uninstall-planned"; + /// Quota snapshots keyed by type (e.g., chat, completions, premium_interactions). + [JsonPropertyName("quotaSnapshots")] + public IDictionary QuotaSnapshots { get => field ??= new Dictionary(); set; } +} - /// Original owned removal plan and operation. - [JsonPropertyName("plan")] - public required McpUninstallPlan Plan { get; set; } +/// RPC data type for AccountGetQuota operations. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class AccountGetQuotaRequest +{ + /// 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; } } -/// Status snapshot from the original connection, independent of new-work account availability. -/// Polymorphic base type discriminated by phase. +/// Authentication credentials accepted only at native protocol ingress. Runtime outputs use credential-free `AuthIdentity` metadata. +/// Polymorphic base type discriminated by type. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] [JsonPolymorphic( - TypeDiscriminatorPropertyName = "phase", + TypeDiscriminatorPropertyName = "type", UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] -[JsonDerivedType(typeof(McpInstallationOperationStatusPreparing), "preparing")] -[JsonDerivedType(typeof(McpInstallationOperationStatusPrepared), "prepared")] -[JsonDerivedType(typeof(McpInstallationOperationStatusAwaitingConfirmation), "awaiting-confirmation")] -[JsonDerivedType(typeof(McpInstallationOperationStatusRevalidating), "revalidating")] -[JsonDerivedType(typeof(McpInstallationOperationStatusApplying), "applying")] -[JsonDerivedType(typeof(McpInstallationOperationStatusCompleted), "completed")] -public partial class McpInstallationOperationStatus +[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(AuthInfoAccount), "account")] +[JsonDerivedType(typeof(AuthInfoGhCli), "gh-cli")] +[JsonDerivedType(typeof(AuthInfoApiKey), "api-key")] +public partial class AuthInfo { /// The type discriminator. - [JsonPropertyName("phase")] - public virtual string Phase { get; set; } = string.Empty; + [JsonPropertyName("type")] + public virtual string Type { get; set; } = string.Empty; } -/// The preparing variant of . +/// Endpoint URLs from the raw Copilot `/copilot_internal/v2/token` user-response passthrough. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class McpInstallationOperationStatusPreparing : McpInstallationOperationStatus +public sealed class CopilotUserResponseEndpoints { - /// - [JsonIgnore] - public override string Phase => "preparing"; + /// Copilot API endpoint URL. + [JsonPropertyName("api")] + public string? Api { get; set; } - /// Whether cancellation has been requested, not proof that a write was undone. - [JsonPropertyName("cancellationRequested")] - public required bool CancellationRequested { get; set; } + /// Experimental-service endpoint URL. + [JsonPropertyName("exp")] + public string? Exp { get; set; } - /// Original runtime-issued operation identity. - [JsonPropertyName("operationId")] - public required string OperationId { get; set; } -} + /// Origin-tracker endpoint URL. + [JsonPropertyName("origin-tracker")] + public string? OriginTracker { get; set; } -/// The prepared variant of . -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class McpInstallationOperationStatusPrepared : McpInstallationOperationStatus -{ - /// - [JsonIgnore] - public override string Phase => "prepared"; + /// Copilot proxy endpoint URL. + [JsonPropertyName("proxy")] + public string? Proxy { get; set; } - /// Whether the inert prepared operation was asked to cancel. - [JsonPropertyName("cancellationRequested")] - public required bool CancellationRequested { get; set; } + /// Copilot telemetry endpoint URL. + [JsonPropertyName("telemetry")] + public string? Telemetry { get; set; } +} - /// Original runtime-issued operation identity. - [JsonPropertyName("operationId")] - public required string OperationId { get; set; } +/// RPC data type for CopilotUserResponseEnterpriseListItem operations. +public sealed class CopilotUserResponseEnterpriseListItem +{ + /// JavaScript-safe numeric database ID of the enterprise. + [JsonPropertyName("id")] + public long? Id { get; set; } } -/// The awaiting-confirmation variant of . -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class McpInstallationOperationStatusAwaitingConfirmation : McpInstallationOperationStatus +/// RPC data type for CopilotUserResponseOrganizationListItem operations. +public sealed class CopilotUserResponseOrganizationListItem { - /// - [JsonIgnore] - public override string Phase => "awaiting-confirmation"; + /// Numeric database ID of the organization. + [JsonPropertyName("id")] + public double? Id { get; set; } - /// Whether the pending human callback was asked to cancel. - [JsonPropertyName("cancellationRequested")] - public required bool CancellationRequested { get; set; } + /// GitHub login of the organization. + [JsonPropertyName("login")] + public string? Login { get; set; } - /// Original runtime-issued operation identity. - [JsonPropertyName("operationId")] - public required string OperationId { get; set; } + /// Display name of the organization. + [JsonPropertyName("name")] + public string? Name { get; set; } } -/// The revalidating variant of . +/// Chat quota snapshot from the raw Copilot user-response passthrough, with entitlement, overage, remaining quota, reset, and billing fields. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class McpInstallationOperationStatusRevalidating : McpInstallationOperationStatus +public sealed class CopilotUserResponseQuotaSnapshotsChat { - /// - [JsonIgnore] - public override string Phase => "revalidating"; + /// Number of requests/units included in the entitlement for this period; `-1` denotes an unlimited entitlement. + [JsonPropertyName("entitlement")] + public double? Entitlement { get; set; } - /// Whether source or authority revalidation was asked to cancel. - [JsonPropertyName("cancellationRequested")] - public required bool CancellationRequested { get; set; } + /// Whether the user currently has quota available; when `false` and not unlimited, further requests are blocked until the quota resets. + [JsonPropertyName("has_quota")] + public bool? HasQuota { get; set; } - /// Original runtime-issued operation identity. - [JsonPropertyName("operationId")] - public required string OperationId { get; set; } + /// Count of additional pay-per-request usage consumed this period beyond the entitlement. + [JsonPropertyName("overage_count")] + public double? OverageCount { get; set; } + + /// Whether usage may continue at pay-per-request rates once the entitlement is exhausted. + [JsonPropertyName("overage_permitted")] + public bool? OveragePermitted { get; set; } + + /// Percentage of the entitlement remaining at the snapshot timestamp. + [JsonPropertyName("percent_remaining")] + public double? PercentRemaining { get; set; } + + /// Identifier of the quota bucket this snapshot describes. + [JsonPropertyName("quota_id")] + public string? QuotaId { get; set; } + + /// Amount of quota remaining at the snapshot timestamp. + [JsonPropertyName("quota_remaining")] + public double? QuotaRemaining { get; set; } + + /// Unix epoch time, in seconds, when this quota next resets. + [JsonPropertyName("quota_reset_at")] + public double? QuotaResetAt { get; set; } + + /// Remaining entitlement/quota amount at the snapshot timestamp. + [JsonPropertyName("remaining")] + public double? Remaining { get; set; } + + /// UTC timestamp when this snapshot was captured. + [JsonPropertyName("timestamp_utc")] + public string? TimestampUtc { get; set; } + + /// Whether this category uses usage-based (token/AI-credit) billing rather than a fixed premium-request count. + [JsonPropertyName("token_based_billing")] + public bool? TokenBasedBilling { get; set; } + + /// Whether the entitlement for this category is unlimited. + [JsonPropertyName("unlimited")] + public bool? Unlimited { get; set; } } -/// The applying variant of . +/// Completions quota snapshot from the raw Copilot user-response passthrough, with entitlement, overage, remaining quota, reset, and billing fields. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class McpInstallationOperationStatusApplying : McpInstallationOperationStatus +public sealed class CopilotUserResponseQuotaSnapshotsCompletions { - /// - [JsonIgnore] - public override string Phase => "applying"; + /// Number of requests/units included in the entitlement for this period; `-1` denotes an unlimited entitlement. + [JsonPropertyName("entitlement")] + public double? Entitlement { get; set; } - /// Whether applying was asked to cancel; already-started effects retain their lease. - [JsonPropertyName("cancellationRequested")] - public required bool CancellationRequested { get; set; } + /// Whether the user currently has quota available; when `false` and not unlimited, further requests are blocked until the quota resets. + [JsonPropertyName("has_quota")] + public bool? HasQuota { get; set; } - /// Original runtime-issued operation identity. - [JsonPropertyName("operationId")] - public required string OperationId { get; set; } + /// Count of additional pay-per-request usage consumed this period beyond the entitlement. + [JsonPropertyName("overage_count")] + public double? OverageCount { get; set; } + + /// Whether usage may continue at pay-per-request rates once the entitlement is exhausted. + [JsonPropertyName("overage_permitted")] + public bool? OveragePermitted { get; set; } + + /// Percentage of the entitlement remaining at the snapshot timestamp. + [JsonPropertyName("percent_remaining")] + public double? PercentRemaining { get; set; } + + /// Identifier of the quota bucket this snapshot describes. + [JsonPropertyName("quota_id")] + public string? QuotaId { get; set; } + + /// Amount of quota remaining at the snapshot timestamp. + [JsonPropertyName("quota_remaining")] + public double? QuotaRemaining { get; set; } + + /// Unix epoch time, in seconds, when this quota next resets. + [JsonPropertyName("quota_reset_at")] + public double? QuotaResetAt { get; set; } + + /// Remaining entitlement/quota amount at the snapshot timestamp. + [JsonPropertyName("remaining")] + public double? Remaining { get; set; } + + /// UTC timestamp when this snapshot was captured. + [JsonPropertyName("timestamp_utc")] + public string? TimestampUtc { get; set; } + + /// Whether this category uses usage-based (token/AI-credit) billing rather than a fixed premium-request count. + [JsonPropertyName("token_based_billing")] + public bool? TokenBasedBilling { get; set; } + + /// Whether the entitlement for this category is unlimited. + [JsonPropertyName("unlimited")] + public bool? Unlimited { get; set; } } -/// Terminal mutation result. Uncertainty is not approval, rollback or permission to replay. -/// Polymorphic base type discriminated by kind. +/// Premium-interactions quota snapshot from the raw Copilot user-response passthrough, with entitlement, overage, remaining quota, reset, and billing fields. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -[JsonPolymorphic( - TypeDiscriminatorPropertyName = "kind", - UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] -[JsonDerivedType(typeof(McpInstallationOutcomeInstalled), "installed")] -[JsonDerivedType(typeof(McpInstallationOutcomeUninstalled), "uninstalled")] -[JsonDerivedType(typeof(McpInstallationOutcomeRolledBack), "rolled-back")] -[JsonDerivedType(typeof(McpInstallationOutcomeRecoveryRequired), "recovery-required")] -[JsonDerivedType(typeof(McpInstallationOutcomeDeclined), "declined")] -[JsonDerivedType(typeof(McpInstallationOutcomeCancelled), "cancelled")] -[JsonDerivedType(typeof(McpInstallationOutcomeRefused), "refused")] -public partial class McpInstallationOutcome +public sealed class CopilotUserResponseQuotaSnapshotsPremiumInteractions { - /// The type discriminator. - [JsonPropertyName("kind")] - public virtual string Kind { get; set; } = string.Empty; -} + /// Number of requests/units included in the entitlement for this period; `-1` denotes an unlimited entitlement. + [JsonPropertyName("entitlement")] + public double? Entitlement { get; set; } + /// Whether the user currently has quota available; when `false` and not unlimited, further requests are blocked until the quota resets. + [JsonPropertyName("has_quota")] + public bool? HasQuota { get; set; } -/// The installed variant of . + /// Count of additional pay-per-request usage consumed this period beyond the entitlement. + [JsonPropertyName("overage_count")] + public double? OverageCount { get; set; } + + /// Whether usage may continue at pay-per-request rates once the entitlement is exhausted. + [JsonPropertyName("overage_permitted")] + public bool? OveragePermitted { get; set; } + + /// Percentage of the entitlement remaining at the snapshot timestamp. + [JsonPropertyName("percent_remaining")] + public double? PercentRemaining { get; set; } + + /// Identifier of the quota bucket this snapshot describes. + [JsonPropertyName("quota_id")] + public string? QuotaId { get; set; } + + /// Amount of quota remaining at the snapshot timestamp. + [JsonPropertyName("quota_remaining")] + public double? QuotaRemaining { get; set; } + + /// Unix epoch time, in seconds, when this quota next resets. + [JsonPropertyName("quota_reset_at")] + public double? QuotaResetAt { get; set; } + + /// Remaining entitlement/quota amount at the snapshot timestamp. + [JsonPropertyName("remaining")] + public double? Remaining { get; set; } + + /// UTC timestamp when this snapshot was captured. + [JsonPropertyName("timestamp_utc")] + public string? TimestampUtc { get; set; } + + /// Whether this category uses usage-based (token/AI-credit) billing rather than a fixed premium-request count. + [JsonPropertyName("token_based_billing")] + public bool? TokenBasedBilling { get; set; } + + /// Whether the entitlement for this category is unlimited. + [JsonPropertyName("unlimited")] + public bool? Unlimited { get; set; } +} + +/// Quota snapshot map from the raw Copilot user-response passthrough, with chat, completions, premium-interactions, and other entries. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class McpInstallationOutcomeInstalled : McpInstallationOutcome +public sealed class CopilotUserResponseQuotaSnapshots { - /// - [JsonIgnore] - public override string Kind => "installed"; + /// Chat quota snapshot from the raw Copilot user-response passthrough, with entitlement, overage, remaining quota, reset, and billing fields. + [JsonPropertyName("chat")] + public CopilotUserResponseQuotaSnapshotsChat? Chat { get; set; } - /// Durable installation succeeded but final transaction cleanup remains. - [JsonPropertyName("cleanupPending")] - public required bool CleanupPending { get; set; } + /// Completions quota snapshot from the raw Copilot user-response passthrough, with entitlement, overage, remaining quota, reset, and billing fields. + [JsonPropertyName("completions")] + public CopilotUserResponseQuotaSnapshotsCompletions? Completions { get; set; } - /// Receipt identity produced by the confirmed transaction. - [JsonPropertyName("installation")] - public required McpInstallationSummary Installation { get; set; } + /// Premium-interactions quota snapshot from the raw Copilot user-response passthrough, with entitlement, overage, remaining quota, reset, and billing fields. + [JsonPropertyName("premium_interactions")] + public CopilotUserResponseQuotaSnapshotsPremiumInteractions? PremiumInteractions { get; set; } } -/// The uninstalled variant of . +/// 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. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class McpInstallationOutcomeUninstalled : McpInstallationOutcome +public sealed class CopilotUserResponse { - /// - [JsonIgnore] - public override string Kind => "uninstalled"; + /// Copilot access SKU identifier (e.g. `free_limited_copilot`, `copilot_for_business_seat_quota`) used to gate model and feature access. + [JsonPropertyName("access_type_sku")] + public string? AccessTypeSku { get; set; } - /// Durable removal succeeded but final cleanup remains. - [JsonPropertyName("cleanupPending")] - public required bool CleanupPending { get; set; } + /// Opaque analytics tracking identifier for the user, forwarded from the Copilot API. + [JsonPropertyName("analytics_tracking_id")] + public string? AnalyticsTrackingId { get; set; } - /// Exact removed receipt identity. - [JsonPropertyName("installationId")] - public required string InstallationId { get; set; } + /// Date the Copilot seat was assigned to the user, if applicable. + [JsonPropertyName("assigned_date")] + public string? AssignedDate { get; set; } - /// Original removal operation. - [JsonPropertyName("operationId")] - public required string OperationId { get; set; } + /// Whether the user is eligible to sign up for the free/limited Copilot tier. + [JsonPropertyName("can_signup_for_limited")] + public bool? CanSignupForLimited { get; set; } - /// Any grants in the incumbent shared OAuth store remain unowned and retained. - [JsonPropertyName("preservedSharedAuthentication")] - public required bool PreservedSharedAuthentication { get; set; } + /// Whether the user is able to upgrade their Copilot plan. + [JsonPropertyName("can_upgrade_plan")] + public bool? CanUpgradePlan { get; set; } - /// Exact owned input slots removed, excluding shared OAuth credentials. - [JsonPropertyName("removedOwnedSecrets")] - public required long RemovedOwnedSecrets { get; set; } + /// Whether Copilot chat is enabled for the user. + [JsonPropertyName("chat_enabled")] + public bool? ChatEnabled { get; set; } - /// Whether protected pre-install configuration was restored. - [JsonPropertyName("restoredPreviousConfiguration")] - public required bool RestoredPreviousConfiguration { get; set; } + /// Whether CLI remote control is enabled for the user. + [JsonPropertyName("cli_remote_control_enabled")] + public bool? CliRemoteControlEnabled { get; set; } + + /// Whether cloud session storage is enabled for the user. + [JsonPropertyName("cloud_session_storage_enabled")] + public bool? CloudSessionStorageEnabled { get; set; } + + /// Whether the Codex agent is enabled for the user. + [JsonPropertyName("codex_agent_enabled")] + public bool? CodexAgentEnabled { get; set; } + + /// Copilot plan name for the user (e.g. `individual`, `business`, `enterprise`). + [JsonPropertyName("copilot_plan")] + public string? CopilotPlan { get; set; } + + /// Whether `.copilotignore` content-exclusion support is enabled for the user. + [JsonPropertyName("copilotignore_enabled")] + public bool? CopilotignoreEnabled { get; set; } + + /// Endpoint URLs from the raw Copilot `/copilot_internal/v2/token` user-response passthrough. + [JsonPropertyName("endpoints")] + public CopilotUserResponseEndpoints? Endpoints { get; set; } + + /// Enterprises that provide the user's Copilot license; malformed entries are normalized to null or ID-less shapes. + [JsonPropertyName("enterprise_list")] + public IList? EnterpriseList { get; set; } + + /// Whether MCP (Model Context Protocol) support is enabled for the user. + [JsonPropertyName("is_mcp_enabled")] + public bool? IsMcpEnabled { get; set; } + + /// Whether the user is a GitHub/Microsoft staff member. + [JsonPropertyName("is_staff")] + public bool? IsStaff { get; set; } + + /// Per-category quota allotments for free/limited-tier users, keyed by quota category. + [JsonPropertyName("limited_user_quotas")] + public IDictionary? LimitedUserQuotas { get; set; } + + /// Date the free/limited-tier user's quotas next reset, as a raw string from the Copilot API. + [JsonPropertyName("limited_user_reset_date")] + public string? LimitedUserResetDate { get; set; } + + /// GitHub login of the authenticated user. + [JsonPropertyName("login")] + public string? Login { get; set; } + + /// Per-category monthly quota allotments, keyed by quota category. + [JsonPropertyName("monthly_quotas")] + public IDictionary? MonthlyQuotas { get; set; } + + /// Organizations the user belongs to, each with an optional ID, login, and display name. + [JsonPropertyName("organization_list")] + public IList? OrganizationList { get; set; } + + /// Logins of the organizations the user belongs to. + [JsonPropertyName("organization_login_list")] + public IList? OrganizationLoginList { get; set; } + + /// 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. + [JsonPropertyName("quota_reset_date")] + public string? QuotaResetDate { get; set; } + + /// UTC-normalized form of `quota_reset_date` (the date the user's usage quota next resets). + [JsonPropertyName("quota_reset_date_utc")] + public string? QuotaResetDateUtc { get; set; } + + /// Quota snapshot map from the raw Copilot user-response passthrough, with chat, completions, premium-interactions, and other entries. + [JsonPropertyName("quota_snapshots")] + public CopilotUserResponseQuotaSnapshots? QuotaSnapshots { get; set; } + + /// Whether the user's telemetry is subject to restricted-data handling. + [JsonPropertyName("restricted_telemetry")] + public bool? RestrictedTelemetry { get; set; } + + /// 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. + [JsonPropertyName("te")] + public bool? Te { get; set; } + + /// Whether the account is on usage-based (token/AI-credit) billing rather than a fixed premium-request quota. + [JsonPropertyName("token_based_billing")] + public bool? TokenBasedBilling { get; set; } } -/// The durable transaction was aborted or fully compensated. -/// The rolled-back variant of . +/// Authentication-info input variant for GitHub-internal HMAC auth, carrying the public GitHub host and HMAC secret. +/// The hmac variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class McpInstallationOutcomeRolledBack : McpInstallationOutcome +public partial class AuthInfoHmac : AuthInfo { /// [JsonIgnore] - public override string Kind => "rolled-back"; + public override string Type => "hmac"; - /// Original connection-owned operation. - [JsonPropertyName("operationId")] - public required string OperationId { 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; } - /// Cause of the fully aborted or compensated operation. - [JsonPropertyName("reason")] - public required McpInstallationFailureReason Reason { 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; } } -/// A write may have completed. Recover and inspect durable state before retrying. -/// The recovery-required variant of . +/// 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(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class McpInstallationOutcomeRecoveryRequired : McpInstallationOutcome +public partial class AuthInfoEnv : AuthInfo { /// [JsonIgnore] - public override string Kind => "recovery-required"; + public override string Type => "env"; - /// Operation whose durable result must be recovered and inspected. - [JsonPropertyName("operationId")] - public required string OperationId { 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; } + + /// 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; } } -/// The declined variant of . +/// Authentication-info input variant for SDK-configured token authentication, carrying host and the secret token value. +/// The token variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class McpInstallationOutcomeDeclined : McpInstallationOutcome +public partial class AuthInfoToken : AuthInfo { /// [JsonIgnore] - public override string Kind => "declined"; + public override string Type => "token"; - /// Original operation explicitly declined by the user. - [JsonPropertyName("operationId")] - public required string OperationId { 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; } + + /// 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; } } -/// The cancelled variant of . +/// 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(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class McpInstallationOutcomeCancelled : McpInstallationOutcome +public partial class AuthInfoTokenProvider : AuthInfo { /// [JsonIgnore] - public override string Kind => "cancelled"; + public override string Type => "token-provider"; - /// Original operation cancelled before a terminal application result. - [JsonPropertyName("operationId")] - public required string OperationId { get; set; } + /// 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; } } -/// The refused variant of . +/// Authentication-info variant for direct Copilot API token auth sourced from environment variables, with public GitHub host. +/// The copilot-api-token variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class McpInstallationOutcomeRefused : McpInstallationOutcome +public partial class AuthInfoCopilotApiToken : AuthInfo { /// [JsonIgnore] - public override string Kind => "refused"; + public override string Type => "copilot-api-token"; - /// Present once an operation has been allocated; never a plan handle. + /// 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("operationId")] - public string? OperationId { get; set; } + [JsonPropertyName("copilotUser")] + public CopilotUserResponse? CopilotUser { get; set; } - /// Specific bounded refusal, never a success-shaped fallback. - [JsonPropertyName("reason")] - public required McpInstallationFailureReason Reason { get; set; } + /// Authentication host (always the public GitHub host). + [JsonPropertyName("host")] + public required string Host { get; set; } } -/// The completed variant of . +/// Authentication-info variant for OAuth user auth, with host and login; the token remains in the runtime secret store. +/// The user variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class McpInstallationOperationStatusCompleted : McpInstallationOperationStatus +public partial class AuthInfoUser : AuthInfo { /// [JsonIgnore] - public override string Phase => "completed"; + public override string Type => "user"; - /// Whether cancellation was requested before the terminal result. - [JsonPropertyName("cancellationRequested")] - public required bool CancellationRequested { 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; } - /// Original runtime-issued operation identity. - [JsonPropertyName("operationId")] - public required string OperationId { get; set; } + /// Authentication host. + [JsonPropertyName("host")] + public required string Host { get; set; } - /// Immutable terminal receipt. - [JsonPropertyName("outcome")] - public required McpInstallationOutcome Outcome { get; set; } + /// OAuth user login. + [JsonPropertyName("login")] + public required string Login { get; set; } } -/// The operation variant of . +/// An interactive account whose model provider owns its credentials. It carries no GitHub credential. +/// The account variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class McpInstallationManagementOutcomeOperation : McpInstallationManagementOutcome +public partial class AuthInfoAccount : AuthInfo { /// [JsonIgnore] - public override string Kind => "operation"; + public override string Type => "account"; - /// Original-connection operation snapshot. - [JsonPropertyName("operation")] - public required McpInstallationOperationStatus Operation { get; set; } + /// Host coordinate owned by the account's model provider. + [JsonPropertyName("host")] + public required string Host { get; set; } + + /// Login identifying the provider-owned account. + [JsonPropertyName("login")] + public required string Login { get; set; } } -/// The refused variant of . +/// Authentication-info input variant for GitHub CLI credentials, carrying host, login, and the `gh auth token` value. +/// The gh-cli variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class McpInstallationManagementOutcomeRefused : McpInstallationManagementOutcome +public partial class AuthInfoGhCli : AuthInfo { /// [JsonIgnore] - public override string Kind => "refused"; - - /// Specific bounded refusal. - [JsonPropertyName("reason")] - public required McpInstallationFailureReason Reason { get; set; } -} + public override string Type => "gh-cli"; -/// The outcome variant of . -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class McpInstallationManagementResultOutcome : McpInstallationManagementResult -{ - /// - [JsonIgnore] - public override string Kind => "outcome"; + /// 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; } - /// Capabilities actually honoured for this request. - [JsonPropertyName("negotiated")] - public required CatalogNegotiatedContract Negotiated { get; set; } + /// Authentication host. + [JsonPropertyName("host")] + public required string Host { get; set; } - /// Observed management outcome. - [JsonPropertyName("outcome")] - public required McpInstallationManagementOutcome Outcome { get; set; } + /// User login as reported by `gh auth status`. + [JsonPropertyName("login")] + public required string Login { get; set; } + + /// The token returned by `gh auth token`. Treat as a secret. + [JsonPropertyName("token")] + public required string Token { get; set; } } -/// The caller's protocol version or required capabilities cannot be honoured. Returned instead of a partial or ambiguous success. -/// The negotiation-refused variant of . +/// 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(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class McpInstallationManagementResultNegotiationRefused : McpInstallationManagementResult +public partial class AuthInfoApiKey : AuthInfo { /// [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; } + public override string Type => "api-key"; - /// Protocol version of the runtime that refused the request. - [JsonPropertyName("runtimeProtocolVersion")] - public required long RuntimeProtocolVersion { get; set; } + /// The API key. Treat as a secret. + [JsonPropertyName("apiKey")] + public required string ApiKey { get; set; } - /// Capabilities this runtime can safely advertise to this caller. The complete five-capability protocol-3 legacy set is always present; every capability added after that baseline appears only when the caller required it, so an older closed-enum decoder can still consume a refusal. This list does not imply that every deployment has enabled every operation. - [JsonPropertyName("supportedCapabilities")] - public required IList SupportedCapabilities { 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; } - /// The subset of the caller's bounded extensible capability identifiers this runtime cannot honour. - [JsonPropertyName("unsupportedCapabilities")] - public required IList UnsupportedCapabilities { get; set; } + /// Authentication host. + [JsonPropertyName("host")] + public required string Host { get; set; } } -/// The request was rejected because a bounded field fell outside its permitted range or a required field was unusable. Pagination may also be rejected by the authority after a continuation request; repeat the search without page. -/// The invalid-request variant of . +/// Current authentication state. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class McpInstallationManagementResultInvalidRequest : McpInstallationManagementResult +public sealed class AccountGetCurrentAuthResult { - /// - [JsonIgnore] - public override string Kind => "invalid-request"; - - /// Which request field was rejected. - [JsonPropertyName("field")] - public required CatalogInvalidRequestField Field { get; set; } + /// Authentication errors from the last auth attempt, if any. + [JsonPropertyName("authErrors")] + public IList? AuthErrors { 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; } + /// Current authentication information, if authenticated. + [JsonPropertyName("authInfo")] + public AuthInfo? AuthInfo { get; set; } } -/// One Registry string-valued configuration entry for the selected transport. +/// Authenticated account entry returned by `account.getAllUsers`. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class McpInstallationInput +public sealed class AccountAllUsers { - /// Exact category declared by the selected choice. - [JsonPropertyName("category")] - public McpPlanValueCategory Category { get; set; } + /// Authentication information for this user. + [JsonPropertyName("authInfo")] + public AuthInfo AuthInfo { get => field ??= new(); set; } - /// Exact key declared by the selected choice. - [JsonPropertyName("key")] - public string Key { get; set; } = string.Empty; + /// Opaque identifier accepted by account and model selection APIs. + [JsonPropertyName("selectionId")] + public string? SelectionId { get; set; } - /// Explicit non-secret value. Secret placeholders use a separate input channel. - [JsonPropertyName("value")] - public string Value { get; set; } = string.Empty; + /// Associated token, if available. + [JsonPropertyName("token")] + public string? Token { get; set; } } -/// A request-local value for one exact reviewed placeholder. Never logged or persisted in a plan. +/// Result of a successful login; throws on failure. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class McpInstallationSecret +public sealed class AccountLoginResult { - /// Exact placeholder from the selected choice, not a caller-chosen backend identifier. - [JsonPropertyName("placeholder")] - public string Placeholder { get; set; } = string.Empty; - - /// Fresh explicit secret value. It is omitted from confirmation reviews and telemetry. - [JsonPropertyName("value")] - public string Value { get; set; } = string.Empty; + /// 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. + [JsonPropertyName("storedInVault")] + public bool StoredInVault { get; set; } } -/// Side-effect-free preparation of one original bound remote MCP choice. +/// Credentials to validate and store. Omit login to resolve the authenticated user from the token. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class McpPrepareInstallRequest +internal sealed class AccountLoginRequest { - /// Exact selected alternative from that plan. - [JsonPropertyName("choiceId")] - public string ChoiceId { get; set; } = string.Empty; - - /// Required bound catalogue and confirmed remote installation capabilities. - [JsonPropertyName("contract")] - public CatalogClientContract Contract { get => field ??= new(); set; } - - /// - /// Declared non-secret values. Non-empty only when the caller requires - /// `mcp-configured-remote-installation`; omitted values use the card default. - /// - [JsonPropertyName("inputs")] - public IList Inputs { get => field ??= []; set; } - - /// Original single-use bound plan, never a client-authored configuration. - [JsonPropertyName("planHandle")] - public string PlanHandle { get; set; } = string.Empty; - - /// An existing local session attached to this connection, not permission to attach one. - [JsonPropertyName("policySessionId")] - public string PolicySessionId { get; set; } = string.Empty; - - /// - /// One entry per declared secret placeholder of the selected choice. Non-empty - /// only when the caller requires `mcp-configured-remote-installation`. - /// - [JsonPropertyName("secrets")] - public IList Secrets { get => field ??= []; set; } + /// GitHub host URL. + [JsonPropertyName("host")] + public string Host { get; set; } = string.Empty; - /// The trusted host presents this choice alongside the exact secret placeholders. - [JsonPropertyName("secretStorage")] - public McpInstallationSecretStorage SecretStorage { get; set; } + /// User login/username. When omitted, the runtime validates the token and resolves the login from GitHub. + [JsonPropertyName("login")] + public string? Login { get; set; } - /// The exact original source, used transiently only after confirmation. - [JsonPropertyName("source")] - public McpServerCardReference Source { get => field ??= new(); set; } + /// GitHub authentication token. + [JsonPropertyName("token")] + public string Token { get; set; } = string.Empty; } -/// An installation result together with the exact honoured contract, or a negotiation refusal. -/// Polymorphic base type discriminated by kind. +/// Logout result indicating if more users remain. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -[JsonPolymorphic( - TypeDiscriminatorPropertyName = "kind", - UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] -[JsonDerivedType(typeof(McpInstallationResultOutcome), "outcome")] -[JsonDerivedType(typeof(McpInstallationResultNegotiationRefused), "negotiation-refused")] -[JsonDerivedType(typeof(McpInstallationResultInvalidRequest), "invalid-request")] -public partial class McpInstallationResult +public sealed class AccountLogoutResult { - /// The type discriminator. - [JsonPropertyName("kind")] - public virtual string Kind { get; set; } = string.Empty; + /// Whether other authenticated users remain after logout. + [JsonPropertyName("hasMoreUsers")] + public bool HasMoreUsers { get; set; } } - -/// The outcome variant of . +/// User to log out. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class McpInstallationResultOutcome : McpInstallationResult +internal sealed class AccountLogoutRequest { - /// - [JsonIgnore] - public override string Kind => "outcome"; - - /// Capabilities actually honoured for this request. - [JsonPropertyName("negotiated")] - public required CatalogNegotiatedContract Negotiated { get; set; } + /// Authentication information for the user to log out. + [JsonPropertyName("authInfo")] + public AuthInfo? AuthInfo { get; set; } - /// Terminal result of the original operation. - [JsonPropertyName("outcome")] - public required McpInstallationOutcome Outcome { get; set; } + /// Opaque account identifier returned by `account.getAllUsers`. + [JsonPropertyName("selectionId")] + public string? SelectionId { get; set; } } -/// The caller's protocol version or required capabilities cannot be honoured. Returned instead of a partial or ambiguous success. -/// The negotiation-refused variant of . +/// The wording the runtime uses for an authentication identity. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class McpInstallationResultNegotiationRefused : McpInstallationResult +internal sealed class AccountDescribeAuthInfoResult { - /// - [JsonIgnore] - public override string Kind => "negotiation-refused"; + /// One-line description of the identity, suitable after a verb such as "Switched to". + [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)] + /// Sign-in banner announcing the authenticated user. [JsonPropertyName("message")] - public required string Message { get; set; } + public string Message { get; set; } = string.Empty; +} - /// Lowest caller protocol version this runtime will serve. - [JsonPropertyName("minimumSupportedProtocolVersion")] - public required long MinimumSupportedProtocolVersion { get; set; } +/// Credential-free authentication identity safe to expose to hosts and user interfaces. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public sealed class AuthIdentity +{ + /// Snapshot of the authenticated user's Copilot subscription info, if known. + [JsonPropertyName("copilotUser")] + public CopilotUserResponse? CopilotUser { get; set; } - /// Whether the version or the capability set was the problem. - [JsonPropertyName("reason")] - public required CatalogNegotiationRefusedReason Reason { get; set; } + /// Name of the environment variable that supplied the credential, when applicable. + [JsonPropertyName("envVar")] + public string? EnvVar { get; set; } - /// Protocol version of the runtime that refused the request. - [JsonPropertyName("runtimeProtocolVersion")] - public required long RuntimeProtocolVersion { get; set; } + /// Authentication host. + [JsonPropertyName("host")] + public string Host { get; set; } = string.Empty; - /// Capabilities this runtime can safely advertise to this caller. The complete five-capability protocol-3 legacy set is always present; every capability added after that baseline appears only when the caller required it, so an older closed-enum decoder can still consume a refusal. This list does not imply that every deployment has enabled every operation. - [JsonPropertyName("supportedCapabilities")] - public required IList SupportedCapabilities { get; set; } + /// Authenticated login, when available. + [JsonPropertyName("login")] + public string? Login { get; set; } - /// The subset of the caller's bounded extensible capability identifiers this runtime cannot honour. - [JsonPropertyName("unsupportedCapabilities")] - public required IList UnsupportedCapabilities { get; set; } + /// Opaque SDK GitHub credential registration backing this identity. Routing metadata only; never a credential. + [JsonPropertyName("registrationId")] + public string? RegistrationId { get; set; } + + /// Authentication type. + [JsonPropertyName("type")] + public AuthInfoType Type { get; set; } } -/// The request was rejected because a bounded field fell outside its permitted range or a required field was unusable. Pagination may also be rejected by the authority after a continuation request; repeat the search without page. -/// The invalid-request variant of . +/// The authentication identity whose shared wording should be rendered. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class McpInstallationResultInvalidRequest : McpInstallationResult +internal sealed class AccountDescribeAuthInfoRequest { - /// - [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; } + /// Identity to describe. + [JsonPropertyName("identity")] + public AuthIdentity Identity { get => field ??= new(); set; } } -/// Applies exactly one previously prepared operation on its original connection. +/// Whether the identity can create gists, and why not when it cannot. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class McpApplyInstallRequest +internal sealed class AccountGetGistSupportResult { - /// Capabilities required by the original prepared operation. - [JsonPropertyName("contract")] - public CatalogClientContract Contract { get => field ??= new(); set; } + /// Machine-readable reason category when unsupported. + [JsonPropertyName("kind")] + public string? Kind { get; set; } - /// Runtime-issued ID already returned by prepareInstall, never reused or rebound. - [JsonPropertyName("operationId")] - public string OperationId { get; set; } = string.Empty; + /// Human-readable explanation to show where the gist action would have been. + [JsonPropertyName("reason")] + public string? Reason { get; set; } - /// Same existing attached or privately borrowed session as preparation. - [JsonPropertyName("policySessionId")] - public string PolicySessionId { get; set; } = string.Empty; + /// True when the identity can create gists. + [JsonPropertyName("supported")] + public bool Supported { get; set; } } -/// Read-only preparation of one owned removal under fresh selected-session authority. +/// The authentication identity whose gist support should be reported. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class McpPlanUninstallRequest +internal sealed class AccountGetGistSupportRequest { - /// Required authenticated bound installation capabilities. - [JsonPropertyName("contract")] - public CatalogClientContract Contract { get => field ??= new(); set; } + /// Identity to evaluate. + [JsonPropertyName("identity")] + public AuthIdentity Identity { get => field ??= new(); set; } +} - /// Exact receipt to inspect, not a server-name guess. - [JsonPropertyName("installationId")] - public string InstallationId { get; set; } = string.Empty; +/// An absolute URL on the identity's GitHub host. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class AccountGetWebUrlResult +{ + /// Absolute URL for the requested path. + [JsonPropertyName("url")] + public string Url { get; set; } = string.Empty; +} - /// Existing selected session on the original connection. - [JsonPropertyName("policySessionId")] - public string PolicySessionId { get; set; } = string.Empty; +/// A path to resolve against the GitHub host an identity is signed in to. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class AccountGetWebUrlRequest +{ + /// Identity whose host the path resolves against. Omit to resolve against the public github.com host. + [JsonPropertyName("identity")] + public AuthIdentity? Identity { get; set; } + + /// Host-relative path, without a leading slash, for example `settings/copilot/features`. + [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("path")] + public string Path { get; set; } = string.Empty; } -/// One-use application of the exact retained removal plan. +/// The environment variable supplying a classic personal access token, when one is set. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class McpApplyUninstallRequest +internal sealed class AccountGetTokenEnvVarResult { - /// Required authenticated bound installation capabilities. - [JsonPropertyName("contract")] - public CatalogClientContract Contract { get => field ??= new(); set; } + /// Variable name, for example `GH_TOKEN`. Absent when no classic token is present in the environment. + [JsonPropertyName("name")] + public string? Name { get; set; } +} - /// Opaque original removal plan, consumed once. - [JsonPropertyName("planHandle")] - public string PlanHandle { get; set; } = string.Empty; +/// Confirmation that the secret values were registered. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public sealed class SecretsAddFilterValuesResult +{ + /// Whether the values were successfully registered. + [JsonPropertyName("ok")] + public bool Ok { get; set; } +} - /// Same existing selected session as removal preparation. - [JsonPropertyName("policySessionId")] - public string PolicySessionId { get; set; } = string.Empty; +/// Secret values to add to the redaction filter. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class SecretsAddFilterValuesRequest +{ + /// Raw secret values to register for redaction. + [JsonPropertyName("values")] + public IList Values { get => field ??= []; set; } } -/// User-configured MCP servers, keyed by server name. +/// Concrete configuration file containing an MCP server declaration. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class McpConfigList +public sealed class McpSourceFile { - /// All MCP servers from user config, keyed by name. - [JsonPropertyName("servers")] - public IDictionary Servers { get => field ??= new Dictionary(); set; } + /// RFC 6901 JSON Pointer to the server declaration, when known. + [JsonPropertyName("jsonPointer")] + public string? JsonPointer { get; set; } + + /// Canonical file URI for the configuration document. + [Url] + [StringSyntax(StringSyntaxAttribute.Uri)] + [JsonPropertyName("uri")] + public string Uri { get; set; } = string.Empty; } -/// MCP server name and configuration to add to user configuration. +/// Plugin identity associated with an MCP server declaration. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class McpConfigAddRequest +public sealed class McpSourcePlugin { - /// MCP server configuration (stdio process or remote HTTP/SSE). - [JsonPropertyName("config")] - public JsonElement Config { get; set; } + /// Canonical plugin identity. + [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; - /// Unique name for the MCP server. - [RegularExpression("^[^\\x00-\\x1f/\\x7f-\\x9f}]+(?:\\/[^\\x00-\\x1f/\\x7f-\\x9f}]+)*$")] + /// Human-readable plugin name, when available. [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; + public string? Name { get; set; } + + /// Plugin version, when available. + [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("version")] + public string? Version { get; set; } } -/// MCP server name and replacement configuration to write to user configuration. +/// Canonical identity and location of the effective MCP server declaration. The declaration is uniquely addressed by this source id together with the discovered server name. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class McpConfigUpdateRequest +public sealed class McpSourceRef { - /// MCP server configuration (stdio process or remote HTTP/SSE). - [JsonPropertyName("config")] - public JsonElement Config { get; set; } + /// Open semantic editability identifier. Known values are editable and read-only. + [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("editability")] + public string Editability { get; set; } = string.Empty; - /// Name of the MCP server to update. - [RegularExpression("^[^\\x00-\\x1f/\\x7f-\\x9f}]+(?:\\/[^\\x00-\\x1f/\\x7f-\\x9f}]+)*$")] + /// Configuration file location, when the declaration is file-backed. + [JsonPropertyName("file")] + public McpSourceFile? File { get; set; } + + /// Opaque stable identity for the configuration source. Clients must not parse this 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("name")] - public string Name { get; set; } = string.Empty; + [JsonPropertyName("id")] + public string Id { get; set; } = string.Empty; + + /// Open source-kind identifier. Known values include user, workspace, invocation, plugin, builtin, and device-registry. + [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("kind")] + public string Kind { get; set; } = string.Empty; + + /// Plugin identity, when the declaration is plugin-provided. + [JsonPropertyName("plugin")] + public McpSourcePlugin? Plugin { get; set; } } -/// MCP server name to remove from user configuration. +/// MCP server discovered by `mcp.discover`, with config source, optional plugin source, transport type, and enabled state. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class McpConfigRemoveRequest +public sealed class DiscoveredMcpServer { - /// OAuth Client ID Metadata Document URL whose persisted credentials should also be removed. - [JsonPropertyName("authClientIdMetadataUrl")] - public string? AuthClientIdMetadataUrl { get; set; } + /// Canonical identity and location of the effective server declaration. + [JsonPropertyName("effectiveSource")] + public McpSourceRef? EffectiveSource { get; set; } - /// Name of the MCP server to remove. + /// Whether the server is enabled (not in the disabled list). + [JsonPropertyName("enabled")] + public bool Enabled { 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; -} -/// MCP server names to enable for new sessions. -[Experimental(global::GitHub.Copilot.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; } + /// 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; } + + /// Server transport type: stdio, http, sse (deprecated), or memory. + [JsonPropertyName("type")] + public DiscoveredMcpServerType? Type { get; set; } } -/// MCP server names to disable for new sessions. +/// MCP servers discovered from user, workspace, plugin, and built-in sources. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class McpConfigDisableRequest +public sealed class McpDiscoverResult { - /// 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; } + /// MCP servers discovered from all sources. + [JsonPropertyName("servers")] + public IList Servers { get => field ??= []; set; } } -/// New-work inventory or recovery request under an explicitly selected existing session. +/// Optional working directory used as context for MCP server discovery. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class McpInstallationsRequest +internal sealed class McpDiscoverRequest { - /// Required authenticated bound installation contract. - [JsonPropertyName("contract")] - public CatalogClientContract Contract { get => field ??= new(); set; } + /// Whether to include canonical effectiveSource metadata for each discovered server. Callers must opt in so protocol-3 clients retain the legacy closed response shape. + [JsonPropertyName("includeEffectiveSource")] + public bool? IncludeEffectiveSource { get; set; } - /// Existing selected local session on this connection. - [JsonPropertyName("policySessionId")] - public string PolicySessionId { get; set; } = string.Empty; + /// Working directory used as context for discovery (e.g., plugin resolution). + [JsonPropertyName("workingDirectory")] + public string? WorkingDirectory { get; set; } } -/// Existing-operation control. A new session selector is deliberately not accepted. +/// Names of the MCP servers the runtime supplies itself. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class McpInstallationOperationRequest +internal sealed class McpListBuiltinServerNamesResult { - /// Original installation wire capability; new-work authentication is not reacquired. - [JsonPropertyName("contract")] - public CatalogClientContract Contract { get => field ??= new(); set; } - - /// Exact runtime-issued operation ID on the original connection. - [JsonPropertyName("operationId")] - public string OperationId { get; set; } = string.Empty; + /// Every built-in server name. A host disabling the built-ins wholesale suppresses exactly this set. + [JsonPropertyName("names")] + public IList Names { get => field ??= []; set; } } -/// Installed plugin that contributes a discovered extension. +/// Whether the gh-replaceable GitHub MCP tools may be clipped for the session described by the request. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class DiscoveredExtensionPlugin +internal sealed class McpShouldExcludeGitHubToolsResult { - /// Installed plugin name. - [JsonPropertyName("name")] - public string Name { get; set; } = string.Empty; + /// True only when both halves hold: the session's filters still reach the platform shell tool, and the host actually has the `gh` those tools would be replaced by. Feed it straight back as the `excludeGhReplaceableTools` build option. + [JsonPropertyName("excludeGhReplaceableTools")] + public bool ExcludeGhReplaceableTools { get; set; } } -/// Discovered extension metadata and persistent enablement state. +/// The session tool filters that decide whether the session still has a shell to run `gh` with. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class DiscoveredExtension +internal sealed class McpShouldExcludeGitHubToolsRequest { - /// Whether this extension's persistent per-ID preference is enabled. - [JsonPropertyName("enabled")] - public bool Enabled { get; set; } + /// The session's tool allowlist, when it set one. Omitted means the session constrains nothing this way. + [JsonPropertyName("availableTools")] + public IList? AvailableTools { get; set; } - /// Source-qualified ID accepted by both server and session extension enablement methods. - [JsonPropertyName("id")] - public string Id { get; set; } = string.Empty; + /// The session's tool denylist, when it set one. Omitted means the session constrains nothing this way. + [JsonPropertyName("excludedTools")] + public IList? ExcludedTools { get; set; } - /// Human-readable extension name. - [JsonPropertyName("name")] - public string Name { get; set; } = string.Empty; + /// How the allowlist and denylist combine when both are set. Omitted means the default every session gets, so a caller that never chose a precedence is answered as its sessions behave. + [JsonPropertyName("toolFilterPrecedence")] + public OptionsUpdateToolFilterPrecedence? ToolFilterPrecedence { get; set; } +} - /// Absolute path to the extension entry module, suitable for revealing it in a file manager. - [JsonPropertyName("path")] - public string Path { get; set; } = string.Empty; +/// 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(global::GitHub.Copilot.Diagnostics.Experimental)] +[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 +{ + /// The type discriminator. + [JsonPropertyName("kind")] + public virtual string Kind { get; set; } = string.Empty; +} - /// Containing plugin metadata for plugin-contributed extensions. - [JsonPropertyName("plugin")] - public DiscoveredExtensionPlugin? Plugin { get; set; } - /// Discovery source. +/// The protocol version and capability set the runtime actually honoured for a successful catalog operation. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public sealed class CatalogNegotiatedContract +{ + /// Wire features the runtime understood for this operation. Includes the five original catalog capabilities and only explicitly requested supported additions, in supported order without duplicates. Capabilities that introduce new success-union variants or operations are therefore included only when explicitly required, preserving older protocol-v3 clients. 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; } + + /// Protocol version of the runtime that served the request. + [JsonPropertyName("runtimeProtocolVersion")] + public long RuntimeProtocolVersion { get; set; } +} + +/// The configuration-change alternative for the transportChoices entry at the same index. Only the selected alternative is applied; entries are not cumulative. The payload stays behind the runtime boundary. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public sealed class McpPlanConfigurationChange +{ + /// Names of the configuration fields the change would set, without their values. + [JsonPropertyName("changedFields")] + public IList ChangedFields { get => field ??= []; set; } + + /// 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("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; } +} + +/// Normalised identity of the MCP server a plan targets, independent of how the card spelled it. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public sealed class McpPlanResourceIdentity +{ + /// 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("canonicalName")] + public string CanonicalName { get; set; } = string.Empty; + + /// 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; } +} + +/// Outcome of evaluating the planned server against registry and enterprise policy. Evaluation is read-only. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public sealed class McpPlanPolicyResult +{ + /// 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 DiscoveredExtensionSource Source { get; set; } + public McpPlanPolicySource Source { get; set; } } -/// Extensions discovered from persisted Copilot home state and their effective loading mode. Launch-scoped additional plugins are not included. +/// 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(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class DiscoveredExtensions +public sealed class CardDigest { - /// Discovered user and enabled installed-plugin extensions from persisted Copilot home state. - [JsonPropertyName("extensions")] - public IList Extensions { get => field ??= []; set; } + /// Digest algorithm and canonical representation. + [JsonPropertyName("algorithm")] + public CardDigestAlgorithm Algorithm { get; set; } - /// Effective extension loading mode. Defaults to load_and_augment when unset. - [JsonPropertyName("mode")] - public DiscoveredExtensionMode Mode { 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; } -/// Source-qualified extension identifiers to persistently enable for future sessions. +/// Provenance of the exact validated JSON MCP card content bound privately to a completed plan and its opaque handle. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class DiscoveredExtensionsEnableRequest +public sealed class McpPlanProvenance { - /// Source-qualified user or plugin extension IDs to enable. - [JsonPropertyName("ids")] - public IList Ids { get => field ??= []; set; } + /// 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; + + /// Semantic digest of the exact validated JSON content bound to the plan handle. + [JsonPropertyName("cardDigest")] + public CardDigest CardDigest { get => field ??= new(); set; } + + /// JSON MCP media type the validated card was interpreted as. + [JsonPropertyName("mediaType")] + public McpServerCardMediaType MediaType { 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; } -/// Source-qualified extension identifiers to persistently disable for future sessions. +/// Where a plan would be written. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class DiscoveredExtensionsDisableRequest +public sealed class McpPlanTarget { - /// Source-qualified user or plugin extension IDs to disable. - [JsonPropertyName("ids")] - public IList Ids { 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; + + /// Configuration scope the plan targets. + [JsonPropertyName("scope")] + public McpPlanScope Scope { get; set; } } -/// Skill installation management result with the honoured contract, or a typed refusal. -/// Polymorphic base type discriminated by kind. +/// 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(global::GitHub.Copilot.Diagnostics.Experimental)] [JsonPolymorphic( - TypeDiscriminatorPropertyName = "kind", + TypeDiscriminatorPropertyName = "installMethod", UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] -[JsonDerivedType(typeof(SkillInstallationManagementResultOutcome), "outcome")] -[JsonDerivedType(typeof(SkillInstallationManagementResultNegotiationRefused), "negotiation-refused")] -[JsonDerivedType(typeof(SkillInstallationManagementResultInvalidRequest), "invalid-request")] -public partial class SkillInstallationManagementResult +[JsonDerivedType(typeof(McpPlanTransportChoicePackage), "package")] +[JsonDerivedType(typeof(McpPlanTransportChoiceRemote), "remote")] +public partial class McpPlanTransportChoice { /// The type discriminator. - [JsonPropertyName("kind")] - public virtual string Kind { get; set; } = string.Empty; + [JsonPropertyName("installMethod")] + public virtual string InstallMethod { get; set; } = string.Empty; } -/// Management outcome for verified Skill inventory, planning and removal. +/// 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(global::GitHub.Copilot.Diagnostics.Experimental)] [JsonPolymorphic( TypeDiscriminatorPropertyName = "kind", UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] -[JsonDerivedType(typeof(SkillInstallationManagementOutcomeRecoveryRequired), "recovery-required")] -[JsonDerivedType(typeof(SkillInstallationManagementOutcomeInstallPlanned), "install-planned")] -[JsonDerivedType(typeof(SkillInstallationManagementOutcomeListed), "listed")] -[JsonDerivedType(typeof(SkillInstallationManagementOutcomeRecovered), "recovered")] -[JsonDerivedType(typeof(SkillInstallationManagementOutcomeRolledBack), "rolled-back")] -[JsonDerivedType(typeof(SkillInstallationManagementOutcomeUninstallPlanned), "uninstall-planned")] -[JsonDerivedType(typeof(SkillInstallationManagementOutcomeOperation), "operation")] -[JsonDerivedType(typeof(SkillInstallationManagementOutcomeEnabledChanged), "enabled-changed")] -[JsonDerivedType(typeof(SkillInstallationManagementOutcomeRefused), "refused")] -public partial class SkillInstallationManagementOutcome +[JsonDerivedType(typeof(McpPlanRequiredValueScalar), "scalar")] +[JsonDerivedType(typeof(McpPlanRequiredValueEnum), "enum")] +public partial class McpPlanRequiredValue { /// The type discriminator. [JsonPropertyName("kind")] @@ -4253,488 +4688,578 @@ public partial class SkillInstallationManagementOutcome } -/// Already-confirmed durable work must be reconciled before new mutations or inventory. -/// The recovery-required variant of . +/// One non-secret scalar value a transport choice needs before it can be applied. +/// The scalar variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class SkillInstallationManagementOutcomeRecoveryRequired : SkillInstallationManagementOutcome +public partial class McpPlanRequiredValueScalar : McpPlanRequiredValue { /// [JsonIgnore] - public override string Kind => "recovery-required"; -} + public override string Kind => "scalar"; -/// Safe verified Skill review fields. No raw credential, candidate handle or plan handle. -/// Polymorphic base type discriminated by action. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -[JsonPolymorphic( - TypeDiscriminatorPropertyName = "action", - UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] -[JsonDerivedType(typeof(SkillInstallationReviewInstall), "install")] -[JsonDerivedType(typeof(SkillInstallationReviewUninstall), "uninstall")] -public partial class SkillInstallationReview -{ - /// The type discriminator. - [JsonPropertyName("action")] - public virtual string Action { get; set; } = string.Empty; -} + /// Where the value is applied when the server is launched. + [JsonPropertyName("category")] + public required McpPlanValueCategory Category { 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; } -/// One reviewed Skill file. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class SkillInstallationFileReview -{ - /// SHA-256 digest of the exact file bytes. - [JsonPropertyName("digest")] - public string Digest { 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; } - /// Whether the file is installed with executable permissions. - [JsonPropertyName("executable")] - public bool Executable { get; set; } + /// Whether the value may be supplied more than once. + [JsonPropertyName("isRepeated")] + public required bool IsRepeated { get; set; } - /// Declared media type for the file. - [JsonPropertyName("mediaType")] - public string MediaType { get; set; } = string.Empty; + /// 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; } - /// Relative file path within the Skill root. - [JsonPropertyName("path")] - public string Path { get; set; } = string.Empty; + /// Whether the value must be present for the plan to be applicable. + [JsonPropertyName("required")] + public required bool Required { get; set; } - /// Exact reviewed file size in bytes. - [JsonPropertyName("sizeBytes")] - public long SizeBytes { 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; } } -/// Source identity retained from Agent Finder and the pinned GitHub descriptor. +/// 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(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class SkillInstallationSource +public partial class McpPlanRequiredValueEnum : McpPlanRequiredValue { - /// Digest of the descriptor's bundle manifest. - [JsonPropertyName("bundleDigest")] - public string BundleDigest { get; set; } = string.Empty; + /// + [JsonIgnore] + public override string Kind => "enum"; - /// Agent Finder materialisation revision identifier. - [JsonPropertyName("catalogRevisionId")] - public string CatalogRevisionId { get; set; } = string.Empty; + /// Where the value is applied when the server is launched. + [JsonPropertyName("category")] + public required McpPlanValueCategory Category { get; set; } - /// Digest of the canonical materialisation descriptor. - [JsonPropertyName("descriptorDigest")] - public string DescriptorDigest { 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; } - /// Repository full name, for example owner/name. - [JsonPropertyName("repository")] - public string Repository { 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; } - /// GitHub repository database identifier. - [JsonPropertyName("repositoryId")] - public string RepositoryId { get; set; } = string.Empty; + /// Non-empty permitted value set. Inert untrusted data. + [JsonPropertyName("enumValues")] + public required IList EnumValues { get; set; } - /// Agent Finder resource identifier. - [JsonPropertyName("resourceId")] - public string ResourceId { get; set; } = string.Empty; + /// Whether the value may be supplied more than once. + [JsonPropertyName("isRepeated")] + public required bool IsRepeated { get; set; } - /// Pinned Git commit revision. - [JsonPropertyName("revision")] - public string Revision { get; set; } = string.Empty; + /// 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; } - /// Root path within the pinned repository. - [JsonPropertyName("root")] - public string Root { get; set; } = string.Empty; + /// 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; } } -/// A user-facing personal Skill installation location without absolute host paths. +/// 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(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class SkillInstallationLocation +public sealed class McpPlanSecretPlaceholder { - /// Diagnostics-only absolute host path. Hosts must not display it by default. - [JsonPropertyName("diagnosticsAbsolutePath")] - public string? DiagnosticsAbsolutePath { get; set; } - - /// Safe display label, for example ~/.copilot/skills/run-checks. - [JsonPropertyName("displayLabel")] - public string DisplayLabel { get; set; } = string.Empty; + /// 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; - /// Path relative to the Copilot home. - [JsonPropertyName("relativePath")] - public string RelativePath { 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; - /// Installation scope. Agent Finder Skills are installed in the user's personal Copilot home. - [JsonPropertyName("scope")] - public SkillInstallationScope Scope { 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)] + [JsonPropertyName("title")] + public string? Title { get; set; } } -/// Review for installing a verified Skill. -/// The install variant of . +/// An eligible local-package transport choice. Package identity is required and a remote endpoint cannot be represented. +/// The package variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class SkillInstallationReviewInstall : SkillInstallationReview +public partial class McpPlanTransportChoicePackage : McpPlanTransportChoice { /// [JsonIgnore] - public override string Action => "install"; + public override string InstallMethod => "package"; - /// Catalogue identity retained from the bound candidate before consent. - [JsonPropertyName("catalogue")] - public required InstallationCatalogueIdentity Catalogue { 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; } - /// Skill description from SKILL.md when present. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("description")] - public string? Description { 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; } - /// - /// Complete verified SKILL.md content. Planning refuses with review-too-large - /// when this exceeds 262144 UTF-8 bytes; it is never truncated. - /// - [JsonPropertyName("entrypointContent")] - public required string EntrypointContent { 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; } - /// Relative path of the verified Skill entrypoint. - [JsonPropertyName("entrypointPath")] - public required string EntrypointPath { get; set; } + /// Typed values this choice requires, excluding secrets. + [JsonPropertyName("requiredValues")] + public required IList RequiredValues { get; set; } - /// Reviewed files and digests. - [JsonPropertyName("files")] - public required IList Files { get; set; } + /// Secrets this choice requires, referenced by placeholder only. + [JsonPropertyName("secretPlaceholders")] + public required IList SecretPlaceholders { get; set; } - /// Installing never grants immediate use; the Skill is written disabled. - [JsonPropertyName("installsDisabled")] - public required bool InstallsDisabled { get; set; } + /// Local process transport this package choice would use. + [JsonPropertyName("transport")] + public required McpPlanPackageTransport Transport { get; set; } +} - /// Skill invocation name from SKILL.md. - [JsonPropertyName("name")] - public required string Name { get; set; } +/// An eligible remote-endpoint transport choice. The endpoint is required and package identity cannot be represented. +/// The remote variant of . +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public partial class McpPlanTransportChoiceRemote : McpPlanTransportChoice +{ + /// + [JsonIgnore] + public override string InstallMethod => "remote"; - /// Exact verified source identity. - [JsonPropertyName("source")] - public required SkillInstallationSource Source { 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; } - /// Exact user-scope target location without an absolute host path. - [JsonPropertyName("target")] - public required SkillInstallationLocation Target { 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; } - /// Total reviewed payload size in bytes. - [JsonPropertyName("totalBytes")] - public required long TotalBytes { 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; } } -/// Durable verified Skill ownership summary. +/// A normalised, inert description of what installing an MCP server would involve. Carries no raw card, no install specification, and no secret value. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class SkillInstallationSummary +public sealed class McpInstallPlan { - /// Catalogue identity retained at install time. - [JsonPropertyName("catalogue")] - public InstallationCatalogueIdentity Catalogue { get => field ??= new(); set; } + /// Alternative configuration changes, with exactly one entry for each transportChoices entry in the same order. Only the entry for the subsequently selected transport applies; these are not cumulative writes. Payloads remain behind the runtime boundary. + [JsonPropertyName("configurationChanges")] + public IList ConfigurationChanges { get => field ??= []; set; } - /// Persisted enablement requested for this installation. - [JsonPropertyName("configuredEnabled")] - public bool ConfiguredEnabled { get; set; } + /// Normalised identity of the server the plan would install. + [JsonPropertyName("identity")] + public McpPlanResourceIdentity Identity { get => field ??= new(); set; } - /// Exact durable installation receipt identity. - [JsonPropertyName("installationId")] - public string InstallationId { 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; - /// ISO 8601 wall-clock installation time. - [JsonPropertyName("installedAt")] - public string InstalledAt { get; set; } = string.Empty; + /// 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; - /// Skill invocation name. - [JsonPropertyName("name")] - public string Name { get; set; } = string.Empty; + /// Outcome of evaluating the server against registry and enterprise policy. + [JsonPropertyName("policy")] + public McpPlanPolicyResult Policy { get => field ??= new(); set; } - /// Operation that installed this Skill. - [JsonPropertyName("operationId")] - public string OperationId { get; set; } = string.Empty; + /// 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; } - /// Ownership state observed from files and receipts. - [JsonPropertyName("ownershipState")] - public SkillInstallationOwnershipState OwnershipState { 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; } - /// Bound-session load observation. - [JsonPropertyName("sessionState")] - public SkillInstallationSessionState SessionState { 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; } - /// Exact retained verified source identity. - [JsonPropertyName("source")] - public SkillInstallationSource Source { get => field ??= new(); set; } + /// True only when every eligible transport choice needs additional values or secrets. False means at least one choice needs no additional configuration, not that every choice is ready. A later apply operation must validate the selected choice's own inputs, secrets and policy after explicit confirmation. + [JsonPropertyName("requiresInteractiveConfiguration")] + public bool RequiresInteractiveConfiguration { get; set; } - /// User-facing installation location without an absolute host path. + /// Configuration scope and key the plan would write to. [JsonPropertyName("target")] - public SkillInstallationLocation Target { get => field ??= new(); set; } + 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; } } -/// Review for uninstalling an owned verified Skill. -/// The uninstall variant of . +/// 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(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class SkillInstallationReviewUninstall : SkillInstallationReview +public partial class McpPlanInstallResultPlanned : McpPlanInstallResult { /// [JsonIgnore] - public override string Action => "uninstall"; - - /// Catalogue identity retained at install time. - [JsonPropertyName("catalogue")] - public required InstallationCatalogueIdentity Catalogue { get; set; } - - /// Files recorded by the installation receipt. - [JsonPropertyName("files")] - public required IList Files { get; set; } - - /// Whether current files differ from the receipt. Apply refuses drift. - [JsonPropertyName("filesModified")] - public required bool FilesModified { get; set; } + public override string Kind => "planned"; - /// Owned installation being removed. - [JsonPropertyName("installation")] - public required SkillInstallationSummary Installation { get; set; } + /// Protocol version and capabilities the runtime honoured. + [JsonPropertyName("negotiated")] + public required CatalogNegotiatedContract Negotiated { get; set; } - /// Total receipt-owned payload size in bytes. - [JsonPropertyName("totalBytes")] - public required long TotalBytes { get; set; } + /// The normalised plan. + [JsonPropertyName("plan")] + public required McpInstallPlan Plan { get; set; } } -/// A computed Skill install plan. Nothing has been applied. +/// 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(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class SkillInstallPlan +public partial class McpPlanInstallResultNegotiationRefused : McpPlanInstallResult { - /// Original wall-clock expiry as an ISO 8601 timestamp. - [JsonPropertyName("expiresAt")] - public string ExpiresAt { get; set; } = string.Empty; + /// + [JsonIgnore] + public override string Kind => "negotiation-refused"; - /// Original operation identifier returned before confirmation. - [JsonPropertyName("operationId")] - public string OperationId { 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; } - /// One-use plan handle, bound to the original candidate authority. - [JsonPropertyName("planHandle")] - public string PlanHandle { get; set; } = string.Empty; + /// Lowest caller protocol version this runtime will serve. + [JsonPropertyName("minimumSupportedProtocolVersion")] + public required long MinimumSupportedProtocolVersion { get; set; } - /// Safe review to present before applying the plan. - [JsonPropertyName("review")] - public SkillInstallationReview Review { get => field ??= new(); 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; } + + /// Capabilities this runtime can safely advertise to this caller. The complete five-capability protocol-3 legacy set is always present; every capability added after that baseline appears only when the caller required it, so an older closed-enum decoder can still consume a refusal. 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; } } -/// A verified Skill installation plan was prepared. -/// The install-planned variant of . +/// 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(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class SkillInstallationManagementOutcomeInstallPlanned : SkillInstallationManagementOutcome +public partial class McpPlanInstallResultHandleRejected : McpPlanInstallResult { /// [JsonIgnore] - public override string Kind => "install-planned"; + public override string Kind => "handle-rejected"; - /// Prepared install plan. - [JsonPropertyName("plan")] - public required SkillInstallPlan Plan { get; set; } + /// 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; } } -/// Owned Skill installations were listed. -/// The listed variant of . +/// The request was rejected because a bounded field fell outside its permitted range or a required field was unusable. Pagination may also be rejected by the authority after a continuation request; repeat the search without page. +/// The invalid-request variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class SkillInstallationManagementOutcomeListed : SkillInstallationManagementOutcome +public partial class McpPlanInstallResultInvalidRequest : McpPlanInstallResult { /// [JsonIgnore] - public override string Kind => "listed"; + public override string Kind => "invalid-request"; - /// Owned Skill installation summaries. - [JsonPropertyName("installations")] - public required IList Installations { get; set; } + /// 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; } } -/// Recovery completed and inventory was inspected. -/// The recovered variant of . +/// 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(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class SkillInstallationManagementOutcomeRecovered : SkillInstallationManagementOutcome +public partial class McpPlanInstallResultAuthenticationRequired : McpPlanInstallResult { /// [JsonIgnore] - public override string Kind => "recovered"; + public override string Kind => "authentication-required"; - /// Owned Skill installation summaries after recovery. - [JsonPropertyName("installations")] - public required IList Installations { get; set; } + /// 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; } } -/// Interrupted work was safely compensated and the pending marker was cleared. -/// The rolled-back variant of . +/// Registry or enterprise policy refused the operation. +/// The policy-rejected variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class SkillInstallationManagementOutcomeRolledBack : SkillInstallationManagementOutcome +public partial class McpPlanInstallResultPolicyRejected : McpPlanInstallResult { /// [JsonIgnore] - public override string Kind => "rolled-back"; + public override string Kind => "policy-rejected"; - /// Original operation identity. - [JsonPropertyName("operation_id")] - public required string OperationId { 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; } - /// Cause of the compensation. - [JsonPropertyName("reason")] - public required SkillInstallationFailureReason Reason { get; set; } + /// Which authority produced the decision. + [JsonPropertyName("source")] + public required McpPlanPolicySource Source { get; set; } } -/// A computed Skill uninstall plan. Nothing has been removed. +/// 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(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class SkillUninstallPlan +public partial class McpPlanInstallResultNetworkFailure : McpPlanInstallResult { - /// Original wall-clock expiry as an ISO 8601 timestamp. - [JsonPropertyName("expiresAt")] - public string ExpiresAt { get; set; } = string.Empty; + /// + [JsonIgnore] + public override string Kind => "network-failure"; - /// Owned Skill installation being removed. - [JsonPropertyName("installation")] - public SkillInstallationSummary Installation { get => field ??= new(); 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; } - /// Original removal operation identifier returned before confirmation. - [JsonPropertyName("operationId")] - public string OperationId { get; set; } = string.Empty; + /// Categorised failure, low cardinality so it can be aggregated without carrying a URL. + [JsonPropertyName("reason")] + public required CatalogNetworkFailureReason Reason { get; set; } - /// One-use uninstall plan handle. - [JsonPropertyName("planHandle")] - public string PlanHandle { get; set; } = string.Empty; + /// 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; } - /// Safe review to present before applying removal. - [JsonPropertyName("review")] - public SkillInstallationReview Review { get => field ??= new(); set; } + /// HTTP status code, when the failure was a rejected response. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("statusCode")] + public int? StatusCode { get; set; } } -/// An owned Skill uninstall plan was prepared. -/// The uninstall-planned variant of . +/// 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(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class SkillInstallationManagementOutcomeUninstallPlanned : SkillInstallationManagementOutcome +public partial class McpPlanInstallResultUnsafeRetrieval : McpPlanInstallResult { /// [JsonIgnore] - public override string Kind => "uninstall-planned"; + public override string Kind => "unsafe-retrieval"; - /// Prepared uninstall plan. - [JsonPropertyName("plan")] - public required SkillUninstallPlan Plan { 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; } -/// Status snapshot from the original connection, independent of new-work account availability. -/// Polymorphic base type discriminated by phase. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -[JsonPolymorphic( - TypeDiscriminatorPropertyName = "phase", - UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] -[JsonDerivedType(typeof(SkillInstallationOperationStatusPreparing), "preparing")] -[JsonDerivedType(typeof(SkillInstallationOperationStatusPrepared), "prepared")] -[JsonDerivedType(typeof(SkillInstallationOperationStatusAwaitingConfirmation), "awaiting-confirmation")] -[JsonDerivedType(typeof(SkillInstallationOperationStatusRevalidating), "revalidating")] -[JsonDerivedType(typeof(SkillInstallationOperationStatusApplying), "applying")] -[JsonDerivedType(typeof(SkillInstallationOperationStatusCompleted), "completed")] -public partial class SkillInstallationOperationStatus -{ - /// The type discriminator. - [JsonPropertyName("phase")] - public virtual string Phase { get; set; } = string.Empty; + /// Which control refused the retrieval, low cardinality so it can be aggregated without carrying a URL. + [JsonPropertyName("reason")] + public required CatalogUnsafeRetrievalReason Reason { get; set; } } - -/// Original operation progress discriminator. -/// The preparing variant of . +/// A card could not be parsed or did not satisfy its declared media type's schema. +/// The malformed-card variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class SkillInstallationOperationStatusPreparing : SkillInstallationOperationStatus +public partial class McpPlanInstallResultMalformedCard : McpPlanInstallResult { /// [JsonIgnore] - public override string Phase => "preparing"; + public override string Kind => "malformed-card"; - /// Whether cancellation has been requested. - [JsonPropertyName("cancellationRequested")] - public required bool CancellationRequested { 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; } - /// Original runtime-issued operation identity. - [JsonPropertyName("operationId")] - public required string OperationId { 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; } } -/// Original operation progress discriminator. -/// The prepared variant of . +/// 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(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class SkillInstallationOperationStatusPrepared : SkillInstallationOperationStatus +public partial class McpPlanInstallResultContractViolation : McpPlanInstallResult { /// [JsonIgnore] - public override string Phase => "prepared"; + public override string Kind => "contract-violation"; - /// Whether cancellation has been requested. - [JsonPropertyName("cancellationRequested")] - public required bool CancellationRequested { get; set; } + /// 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; } - /// Original runtime-issued operation identity. - [JsonPropertyName("operationId")] - public required string OperationId { get; set; } + /// Which rule the response broke. + [JsonPropertyName("reason")] + public required CatalogContractViolationReason Reason { get; set; } } -/// Original operation progress discriminator. -/// The awaiting-confirmation variant of . +/// No transport this runtime can use is available for the requested server. +/// The unavailable-transport variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class SkillInstallationOperationStatusAwaitingConfirmation : SkillInstallationOperationStatus +public partial class McpPlanInstallResultUnavailableTransport : McpPlanInstallResult { /// [JsonIgnore] - public override string Phase => "awaiting-confirmation"; + public override string Kind => "unavailable-transport"; - /// Whether cancellation has been requested. - [JsonPropertyName("cancellationRequested")] - public required bool CancellationRequested { 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; } - /// Original runtime-issued operation identity. - [JsonPropertyName("operationId")] - public required string OperationId { get; set; } + /// Why no transport could be offered. + [JsonPropertyName("reason")] + public required CatalogUnavailableTransportReason Reason { get; set; } } -/// Original operation progress discriminator. -/// The revalidating variant of . +/// 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(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class SkillInstallationOperationStatusRevalidating : SkillInstallationOperationStatus +public partial class McpPlanInstallResultNotInstallable : McpPlanInstallResult { /// [JsonIgnore] - public override string Phase => "revalidating"; + public override string Kind => "not-installable"; - /// Whether cancellation has been requested. - [JsonPropertyName("cancellationRequested")] - public required bool CancellationRequested { 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; } - /// Original runtime-issued operation identity. - [JsonPropertyName("operationId")] - public required string OperationId { get; set; } + /// Why the candidate cannot be installed. + [JsonPropertyName("reason")] + public required CatalogNotInstallableReason Reason { get; set; } } -/// Original operation progress discriminator. -/// The applying variant of . +/// The operation is not available on this runtime. Distinct from a network failure: nothing was attempted. +/// The unavailable variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class SkillInstallationOperationStatusApplying : SkillInstallationOperationStatus +public partial class McpPlanInstallResultUnavailable : McpPlanInstallResult { /// [JsonIgnore] - public override string Phase => "applying"; + public override string Kind => "unavailable"; - /// Whether cancellation has been requested; already-started effects require recovery. - [JsonPropertyName("cancellationRequested")] - public required bool CancellationRequested { 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; } - /// Original runtime-issued operation identity. - [JsonPropertyName("operationId")] - public required string OperationId { get; set; } + /// Why the operation is unavailable. + [JsonPropertyName("reason")] + public required CatalogUnavailableReason Reason { get; set; } } -/// Terminal verified Skill mutation result. +/// The protocol version and capability set a caller requires, supplied on every catalog request so negotiation cannot be skipped by omission. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public sealed class 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. + [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; } +} + +/// 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(global::GitHub.Copilot.Diagnostics.Experimental)] [JsonPolymorphic( TypeDiscriminatorPropertyName = "kind", UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] -[JsonDerivedType(typeof(SkillInstallationOutcomeInstalled), "installed")] -[JsonDerivedType(typeof(SkillInstallationOutcomeUninstalled), "uninstalled")] -[JsonDerivedType(typeof(SkillInstallationOutcomeRolledBack), "rolled-back")] -[JsonDerivedType(typeof(SkillInstallationOutcomeRecoveryRequired), "recovery-required")] -[JsonDerivedType(typeof(SkillInstallationOutcomeDeclined), "declined")] -[JsonDerivedType(typeof(SkillInstallationOutcomeCancelled), "cancelled")] -[JsonDerivedType(typeof(SkillInstallationOutcomeRefused), "refused")] -public partial class SkillInstallationOutcome +[JsonDerivedType(typeof(McpPlanInstallSourceCandidate), "candidate")] +[JsonDerivedType(typeof(McpPlanInstallSourceCard), "card")] +public partial class McpPlanInstallSource { /// The type discriminator. [JsonPropertyName("kind")] @@ -4742,625 +5267,656 @@ public partial class SkillInstallationOutcome } -/// The verified Skill was installed disabled. -/// The installed variant of . +/// Plan from a candidate returned by a previous catalog search. +/// The candidate variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class SkillInstallationOutcomeInstalled : SkillInstallationOutcome +public partial class McpPlanInstallSourceCandidate : McpPlanInstallSource { /// [JsonIgnore] - public override string Kind => "installed"; + public override string Kind => "candidate"; - /// Durable installed Skill summary. - [JsonPropertyName("installation")] - public required SkillInstallationSummary Installation { get; set; } + /// 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; } + + /// The runtime- or authority-minted `searchId` returned with the search that produced this candidate. Planning verifies the private correlation and atomically consumes a matching candidate before downstream work, including attempts that subsequently fail or report unavailable. A mismatched search does not consume the candidate. 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; } } -/// The owned Skill was removed. -/// The uninstalled variant of . +/// 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(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class SkillInstallationOutcomeUninstalled : SkillInstallationOutcome +[JsonPolymorphic( + TypeDiscriminatorPropertyName = "kind", + UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] +[JsonDerivedType(typeof(McpServerCardReferenceUrl), "url")] +[JsonDerivedType(typeof(McpServerCardReferenceEmbedded), "embedded")] +public partial class McpServerCardReference { - /// - [JsonIgnore] - public override string Kind => "uninstalled"; - - /// Removed installation identity. - [JsonPropertyName("installationId")] - public required string InstallationId { get; set; } - - /// Original removal operation identity. - [JsonPropertyName("operationId")] - public required string OperationId { get; set; } + /// The type discriminator. + [JsonPropertyName("kind")] + public virtual string Kind { get; set; } = string.Empty; } -/// The durable transaction was aborted or fully compensated. -/// The rolled-back variant of . + +/// An MCP server card to be retrieved from a URL through the runtime's hardened fetch boundary. +/// The url variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class SkillInstallationOutcomeRolledBack : SkillInstallationOutcome +public partial class McpServerCardReferenceUrl : McpServerCardReference { /// [JsonIgnore] - public override string Kind => "rolled-back"; + public override string Kind => "url"; - /// Original operation identity. - [JsonPropertyName("operationId")] - public required string OperationId { get; set; } + /// Media type the card is expected to conform to. + [JsonPropertyName("mediaType")] + public required McpServerCardMediaType MediaType { get; set; } - /// Cause of the fully aborted or compensated operation. - [JsonPropertyName("reason")] - public required SkillInstallationFailureReason Reason { 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; } } -/// A write may have completed. Recover and inspect durable state before retrying. -/// The recovery-required variant of . +/// An MCP server card supplied inline as an inert document. +/// The embedded variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class SkillInstallationOutcomeRecoveryRequired : SkillInstallationOutcome +public partial class McpServerCardReferenceEmbedded : McpServerCardReference { /// [JsonIgnore] - public override string Kind => "recovery-required"; + public override string Kind => "embedded"; - /// Operation whose durable result must be recovered and inspected. - [JsonPropertyName("operationId")] - public required string OperationId { get; 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; } } -/// The user declined the confirmation request. -/// The declined variant of . +/// Plan from a card supplied directly by the caller, without a preceding search. +/// The card variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class SkillInstallationOutcomeDeclined : SkillInstallationOutcome +public partial class McpPlanInstallSourceCard : McpPlanInstallSource { /// [JsonIgnore] - public override string Kind => "declined"; + public override string Kind => "card"; - /// Original operation identity. - [JsonPropertyName("operationId")] - public required string OperationId { get; set; } + /// The card to plan from: exactly one of a URL or embedded data. + [JsonPropertyName("card")] + public required McpServerCardReference Card { get; set; } } -/// The operation was cancelled before a terminal mutation. -/// The cancelled variant of . +/// A side-effect-free request for an MCP install plan. Computing a plan never writes configuration, stores a secret, or reloads MCP servers. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class SkillInstallationOutcomeCancelled : SkillInstallationOutcome +public sealed class McpPlanInstallRequest { - /// - [JsonIgnore] - public override string Kind => "cancelled"; + /// Protocol version and capabilities the caller requires. + [JsonPropertyName("contract")] + public required CatalogClientContract Contract { get; set; } - /// Original operation identity. - [JsonPropertyName("operationId")] - public required string OperationId { get; set; } + /// The same existing attached session that owns the original catalogue candidate. + [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("policySessionId")] + public string? PolicySessionId { get; set; } + + /// Configuration scope the plan targets. Defaults to user scope when omitted. + [JsonPropertyName("scope")] + public McpPlanScope? Scope { get; set; } + + /// What to plan: either a candidate handle from a previous search, or a card supplied directly. + [JsonPropertyName("source")] + public required McpPlanInstallSource Source { get; set; } } -/// The operation was refused without applying changes. -/// The refused variant of . +/// Management result with contract receipt, or a typed request/negotiation refusal. +/// Polymorphic base type discriminated by kind. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class SkillInstallationOutcomeRefused : SkillInstallationOutcome +[JsonPolymorphic( + TypeDiscriminatorPropertyName = "kind", + UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] +[JsonDerivedType(typeof(McpInstallationManagementResultOutcome), "outcome")] +[JsonDerivedType(typeof(McpInstallationManagementResultNegotiationRefused), "negotiation-refused")] +[JsonDerivedType(typeof(McpInstallationManagementResultInvalidRequest), "invalid-request")] +public partial class McpInstallationManagementResult { - /// - [JsonIgnore] - public override string Kind => "refused"; + /// The type discriminator. + [JsonPropertyName("kind")] + public virtual string Kind { get; set; } = string.Empty; +} - /// Present once an operation has been allocated. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("operationId")] - public string? OperationId { get; set; } - /// Bounded refusal reason. - [JsonPropertyName("reason")] - public required SkillInstallationFailureReason Reason { get; set; } +/// Read-only or recovery management result, never permission to activate or replay. +/// Polymorphic base type discriminated by kind. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +[JsonPolymorphic( + TypeDiscriminatorPropertyName = "kind", + UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] +[JsonDerivedType(typeof(McpInstallationManagementOutcomeRecoveryRequired), "recovery-required")] +[JsonDerivedType(typeof(McpInstallationManagementOutcomeInstallPrepared), "install-prepared")] +[JsonDerivedType(typeof(McpInstallationManagementOutcomeListed), "listed")] +[JsonDerivedType(typeof(McpInstallationManagementOutcomeRecovered), "recovered")] +[JsonDerivedType(typeof(McpInstallationManagementOutcomeUninstallPlanned), "uninstall-planned")] +[JsonDerivedType(typeof(McpInstallationManagementOutcomeOperation), "operation")] +[JsonDerivedType(typeof(McpInstallationManagementOutcomeRefused), "refused")] +public partial class McpInstallationManagementOutcome +{ + /// The type discriminator. + [JsonPropertyName("kind")] + public virtual string Kind { get; set; } = string.Empty; } -/// Original operation progress discriminator. -/// The completed variant of . + +/// Already-confirmed durable work must be reconciled before new mutations or inventory. +/// The recovery-required variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class SkillInstallationOperationStatusCompleted : SkillInstallationOperationStatus +public partial class McpInstallationManagementOutcomeRecoveryRequired : McpInstallationManagementOutcome { /// [JsonIgnore] - public override string Phase => "completed"; + public override string Kind => "recovery-required"; +} - /// Whether cancellation was requested before the terminal result. - [JsonPropertyName("cancellationRequested")] - public required bool CancellationRequested { get; set; } +/// Inert, runtime-owned admission. The operation ID is known before confirmation or effects. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public sealed class McpPreparedInstall +{ + /// Original plan expiry in Unix epoch milliseconds; preparation does not extend it. + [JsonPropertyName("expiresAtEpochMs")] + public long ExpiresAtEpochMs { get; set; } - /// Original runtime-issued operation identity. + /// Original connection-owned operation, known before the first confirmation callback. [JsonPropertyName("operationId")] - public required string OperationId { get; set; } - - /// Immutable terminal receipt. - [JsonPropertyName("outcome")] - public required SkillInstallationOutcome Outcome { get; set; } + public string OperationId { get; set; } = string.Empty; } -/// Original-connection operation snapshot. -/// The operation variant of . +/// The install-prepared variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class SkillInstallationManagementOutcomeOperation : SkillInstallationManagementOutcome +public partial class McpInstallationManagementOutcomeInstallPrepared : McpInstallationManagementOutcome { /// [JsonIgnore] - public override string Kind => "operation"; + public override string Kind => "install-prepared"; - /// Operation status. + /// Known original operation identity, returned before callback or effects. [JsonPropertyName("operation")] - public required SkillInstallationOperationStatus Operation { get; set; } + public required McpPreparedInstall Operation { get; set; } } -/// Enablement changed and the selected session was reconciled. -/// The enabled-changed variant of . +/// A versioned, bounded trust observation carried unchanged with a catalog candidate and its private handle context. Current observations require a recognised T1/T2 tier; every non-current state structurally forbids a tier. Eligibility remains `unknown` while Agent Finder supplies no exposure decision, and states absent from its current wire are never inferred from age, relevance, popularity, or a tier transition. +/// Polymorphic base type discriminated by status. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class SkillInstallationManagementOutcomeEnabledChanged : SkillInstallationManagementOutcome +[JsonPolymorphic( + TypeDiscriminatorPropertyName = "status", + UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] +[JsonDerivedType(typeof(CatalogTrustSnapshotCurrent), "current")] +[JsonDerivedType(typeof(CatalogTrustSnapshotAbsent), "absent")] +[JsonDerivedType(typeof(CatalogTrustSnapshotStale), "stale")] +[JsonDerivedType(typeof(CatalogTrustSnapshotDowngraded), "downgraded")] +[JsonDerivedType(typeof(CatalogTrustSnapshotRevoked), "revoked")] +[JsonDerivedType(typeof(CatalogTrustSnapshotUnsupported), "unsupported")] +[JsonDerivedType(typeof(CatalogTrustSnapshotMalformed), "malformed")] +public partial class CatalogTrustSnapshot { - /// - [JsonIgnore] - public override string Kind => "enabled-changed"; - - /// Safe reload or reconciliation diagnostics. - [JsonPropertyName("diagnostics")] - public required IList Diagnostics { get; set; } - - /// Updated installation summary. - [JsonPropertyName("installation")] - public required SkillInstallationSummary Installation { get; set; } + /// The type discriminator. + [JsonPropertyName("status")] + public virtual string Status { get; set; } = string.Empty; } -/// The management request was refused. -/// The refused variant of . + +/// Where and when the runtime observed the trust metadata. Observation time is not the authority's evaluation time and must not be used to infer staleness. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class SkillInstallationManagementOutcomeRefused : SkillInstallationManagementOutcome +public sealed class CatalogTrustProvenance { - /// - [JsonIgnore] - public override string Kind => "refused"; + /// ISO 8601 timestamp with a timezone offset at which the runtime observed the search result carrying this trust field. + [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(20)] + [MaxLength(64)] + [JsonPropertyName("observedAt")] + public DateTimeOffset ObservedAt { get; set; } - /// Bounded refusal reason. - [JsonPropertyName("reason")] - public required SkillInstallationFailureReason Reason { get; set; } + /// Bounded authority that supplied the trust field. + [JsonPropertyName("source")] + public CatalogTrustSource Source { get; set; } } -/// The outcome variant of . +/// A recognised current Agent Finder T1 or T2 trust tier. +/// The current variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class SkillInstallationManagementResultOutcome : SkillInstallationManagementResult +public partial class CatalogTrustSnapshotCurrent : CatalogTrustSnapshot { /// [JsonIgnore] - public override string Kind => "outcome"; + public override string Status => "current"; - /// Capabilities honoured for this request. - [JsonPropertyName("negotiated")] - public required CatalogNegotiatedContract Negotiated { get; set; } + /// Service-computed exposure eligibility. `unknown` is required while Agent Finder returns no explicit eligibility field. + [JsonPropertyName("eligibility")] + public required CatalogTrustEligibility Eligibility { get; set; } - /// Observed management outcome. - [JsonPropertyName("outcome")] - public required SkillInstallationManagementOutcome Outcome { get; set; } + /// Bounded source and observation time for this snapshot. This is distinct from evidence used by the authority to calculate trust. + [JsonPropertyName("provenance")] + public required CatalogTrustProvenance Provenance { get; set; } + + /// Schema version of this runtime-owned snapshot envelope. + [JsonPropertyName("schemaVersion")] + public required CatalogTrustSnapshotSchemaVersion SchemaVersion { get; set; } + + /// Service-computed T1 or T2 trust tier. + [JsonPropertyName("tier")] + public required CatalogTrustTier Tier { get; set; } } -/// The caller's protocol version or required capabilities cannot be honoured. Returned instead of a partial or ambiguous success. -/// The negotiation-refused variant of . +/// Discriminator: the authority omitted trust metadata. +/// The absent variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class SkillInstallationManagementResultNegotiationRefused : SkillInstallationManagementResult +public partial class CatalogTrustSnapshotAbsent : CatalogTrustSnapshot { /// [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; } + public override string Status => "absent"; - /// Protocol version of the runtime that refused the request. - [JsonPropertyName("runtimeProtocolVersion")] - public required long RuntimeProtocolVersion { get; set; } + /// Service-computed exposure eligibility. `unknown` is required while Agent Finder returns no explicit eligibility field. + [JsonPropertyName("eligibility")] + public required CatalogTrustEligibility Eligibility { get; set; } - /// Capabilities this runtime can safely advertise to this caller. The complete five-capability protocol-3 legacy set is always present; every capability added after that baseline appears only when the caller required it, so an older closed-enum decoder can still consume a refusal. This list does not imply that every deployment has enabled every operation. - [JsonPropertyName("supportedCapabilities")] - public required IList SupportedCapabilities { get; set; } + /// Bounded source and observation time for this snapshot. This is distinct from evidence used by the authority to calculate trust. + [JsonPropertyName("provenance")] + public required CatalogTrustProvenance Provenance { get; set; } - /// The subset of the caller's bounded extensible capability identifiers this runtime cannot honour. - [JsonPropertyName("unsupportedCapabilities")] - public required IList UnsupportedCapabilities { get; set; } + /// Schema version of this runtime-owned snapshot envelope. + [JsonPropertyName("schemaVersion")] + public required CatalogTrustSnapshotSchemaVersion SchemaVersion { get; set; } } -/// The request was rejected because a bounded field fell outside its permitted range or a required field was unusable. Pagination may also be rejected by the authority after a continuation request; repeat the search without page. -/// The invalid-request variant of . +/// Discriminator: the authority explicitly marked the assessment stale. +/// The stale variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class SkillInstallationManagementResultInvalidRequest : SkillInstallationManagementResult +public partial class CatalogTrustSnapshotStale : CatalogTrustSnapshot { /// [JsonIgnore] - public override string Kind => "invalid-request"; + public override string Status => "stale"; - /// Which request field was rejected. - [JsonPropertyName("field")] - public required CatalogInvalidRequestField Field { get; set; } + /// Service-computed exposure eligibility. `unknown` is required while Agent Finder returns no explicit eligibility field. + [JsonPropertyName("eligibility")] + public required CatalogTrustEligibility Eligibility { 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; } + /// Bounded source and observation time for this snapshot. This is distinct from evidence used by the authority to calculate trust. + [JsonPropertyName("provenance")] + public required CatalogTrustProvenance Provenance { get; set; } + + /// Schema version of this runtime-owned snapshot envelope. + [JsonPropertyName("schemaVersion")] + public required CatalogTrustSnapshotSchemaVersion SchemaVersion { get; set; } } -/// Side-effect-free planning of one verified Agent Finder Skill candidate. +/// Discriminator: the authority explicitly reported a downgraded assessment. +/// The downgraded variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SkillPlanInstallRequest +public partial class CatalogTrustSnapshotDowngraded : CatalogTrustSnapshot { - /// Fresh single-use AI skill candidate handle returned by a bound catalogue search. - [JsonPropertyName("candidateHandle")] - public string CandidateHandle { get; set; } = string.Empty; + /// + [JsonIgnore] + public override string Status => "downgraded"; - /// Required authenticated bound catalogue and Skill installation capabilities. - [JsonPropertyName("contract")] - public CatalogClientContract Contract { get => field ??= new(); set; } + /// Service-computed exposure eligibility. `unknown` is required while Agent Finder returns no explicit eligibility field. + [JsonPropertyName("eligibility")] + public required CatalogTrustEligibility Eligibility { get; set; } - /// Existing local session attached to this connection. - [JsonPropertyName("policySessionId")] - public string PolicySessionId { get; set; } = string.Empty; -} + /// Bounded source and observation time for this snapshot. This is distinct from evidence used by the authority to calculate trust. + [JsonPropertyName("provenance")] + public required CatalogTrustProvenance Provenance { get; set; } -/// Skill installation result with the honoured contract, or a typed request/negotiation refusal. -/// Polymorphic base type discriminated by kind. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -[JsonPolymorphic( - TypeDiscriminatorPropertyName = "kind", - UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] -[JsonDerivedType(typeof(SkillInstallationResultOutcome), "outcome")] -[JsonDerivedType(typeof(SkillInstallationResultNegotiationRefused), "negotiation-refused")] -[JsonDerivedType(typeof(SkillInstallationResultInvalidRequest), "invalid-request")] -public partial class SkillInstallationResult -{ - /// The type discriminator. - [JsonPropertyName("kind")] - public virtual string Kind { get; set; } = string.Empty; + /// Schema version of this runtime-owned snapshot envelope. + [JsonPropertyName("schemaVersion")] + public required CatalogTrustSnapshotSchemaVersion SchemaVersion { get; set; } } - -/// The outcome variant of . +/// Discriminator: the authority explicitly revoked the assessment. +/// The revoked variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class SkillInstallationResultOutcome : SkillInstallationResult +public partial class CatalogTrustSnapshotRevoked : CatalogTrustSnapshot { /// [JsonIgnore] - public override string Kind => "outcome"; + public override string Status => "revoked"; - /// Capabilities honoured for this request. - [JsonPropertyName("negotiated")] - public required CatalogNegotiatedContract Negotiated { get; set; } + /// Service-computed exposure eligibility. `unknown` is required while Agent Finder returns no explicit eligibility field. + [JsonPropertyName("eligibility")] + public required CatalogTrustEligibility Eligibility { get; set; } - /// Terminal operation outcome. - [JsonPropertyName("outcome")] - public required SkillInstallationOutcome Outcome { get; set; } + /// Bounded source and observation time for this snapshot. This is distinct from evidence used by the authority to calculate trust. + [JsonPropertyName("provenance")] + public required CatalogTrustProvenance Provenance { get; set; } + + /// Schema version of this runtime-owned snapshot envelope. + [JsonPropertyName("schemaVersion")] + public required CatalogTrustSnapshotSchemaVersion SchemaVersion { get; set; } } -/// The caller's protocol version or required capabilities cannot be honoured. Returned instead of a partial or ambiguous success. -/// The negotiation-refused variant of . +/// Discriminator: the authority supplied a bounded trust value this runtime does not understand. +/// The unsupported variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class SkillInstallationResultNegotiationRefused : SkillInstallationResult +public partial class CatalogTrustSnapshotUnsupported : CatalogTrustSnapshot { /// [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; } + public override string Status => "unsupported"; - /// Protocol version of the runtime that refused the request. - [JsonPropertyName("runtimeProtocolVersion")] - public required long RuntimeProtocolVersion { get; set; } + /// Service-computed exposure eligibility. `unknown` is required while Agent Finder returns no explicit eligibility field. + [JsonPropertyName("eligibility")] + public required CatalogTrustEligibility Eligibility { get; set; } - /// Capabilities this runtime can safely advertise to this caller. The complete five-capability protocol-3 legacy set is always present; every capability added after that baseline appears only when the caller required it, so an older closed-enum decoder can still consume a refusal. This list does not imply that every deployment has enabled every operation. - [JsonPropertyName("supportedCapabilities")] - public required IList SupportedCapabilities { get; set; } + /// Bounded source and observation time for this snapshot. This is distinct from evidence used by the authority to calculate trust. + [JsonPropertyName("provenance")] + public required CatalogTrustProvenance Provenance { get; set; } - /// The subset of the caller's bounded extensible capability identifiers this runtime cannot honour. - [JsonPropertyName("unsupportedCapabilities")] - public required IList UnsupportedCapabilities { get; set; } + /// Schema version of this runtime-owned snapshot envelope. + [JsonPropertyName("schemaVersion")] + public required CatalogTrustSnapshotSchemaVersion SchemaVersion { get; set; } } -/// The request was rejected because a bounded field fell outside its permitted range or a required field was unusable. Pagination may also be rejected by the authority after a continuation request; repeat the search without page. -/// The invalid-request variant of . +/// Discriminator: the trust field was empty, unbounded, or had the wrong JSON type. +/// The malformed variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class SkillInstallationResultInvalidRequest : SkillInstallationResult +public partial class CatalogTrustSnapshotMalformed : CatalogTrustSnapshot { /// [JsonIgnore] - public override string Kind => "invalid-request"; + public override string Status => "malformed"; - /// Which request field was rejected. - [JsonPropertyName("field")] - public required CatalogInvalidRequestField Field { get; set; } + /// Service-computed exposure eligibility. `unknown` is required while Agent Finder returns no explicit eligibility field. + [JsonPropertyName("eligibility")] + public required CatalogTrustEligibility Eligibility { 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; } + /// Bounded source and observation time for this snapshot. This is distinct from evidence used by the authority to calculate trust. + [JsonPropertyName("provenance")] + public required CatalogTrustProvenance Provenance { get; set; } + + /// Schema version of this runtime-owned snapshot envelope. + [JsonPropertyName("schemaVersion")] + public required CatalogTrustSnapshotSchemaVersion SchemaVersion { get; set; } } -/// Applies exactly one retained verified Skill installation plan. +/// Catalogue identity retained from a bound candidate or plan at installation time. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SkillApplyInstallRequest +public sealed class InstallationCatalogueIdentity { - /// Required authenticated bound catalogue and Skill installation capabilities. - [JsonPropertyName("contract")] - public CatalogClientContract Contract { get => field ??= new(); set; } + /// Catalogue description retained at install planning time. + [JsonPropertyName("description")] + public string? Description { get; set; } - /// Opaque original plan, consumed once. - [JsonPropertyName("planHandle")] - public string PlanHandle { get; set; } = string.Empty; + /// Human display name retained from the catalogue candidate. + [JsonPropertyName("displayName")] + public string DisplayName { get; set; } = string.Empty; - /// Same existing selected session as planning. - [JsonPropertyName("policySessionId")] - public string PolicySessionId { get; set; } = string.Empty; + /// Catalogue item URL when supplied by the authority. + [JsonPropertyName("itemUrl")] + public string? ItemUrl { get; set; } + + /// Catalogue publisher retained at install planning time. + [JsonPropertyName("publisher")] + public string? Publisher { get; set; } + + /// Authority resource identifier when supplied by the catalogue. + [JsonPropertyName("resourceId")] + public string? ResourceId { get; set; } + + /// Catalogue authority/source string that supplied the candidate. + [JsonPropertyName("source")] + public string Source { get; set; } = string.Empty; + + /// Catalogue trust observation retained at install planning time. + [JsonPropertyName("trustAtInstall")] + public CatalogTrustSnapshot? TrustAtInstall { get; set; } + + /// Catalogue version retained at install planning time. + [JsonPropertyName("version")] + public string? Version { get; set; } } -/// Read-only preparation of one owned Skill removal under fresh selected-session authority. +/// Durable configuration ownership is distinct from session-specific usability. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SkillPlanUninstallRequest +public sealed class McpInstallationSummary { - /// Required authenticated bound installation contract. - [JsonPropertyName("contract")] - public CatalogClientContract Contract { get => field ??= new(); set; } + /// Catalogue identity retained from the installed plan when available. + [JsonPropertyName("catalogue")] + public InstallationCatalogueIdentity? Catalogue { get; set; } - /// Exact receipt to inspect. + /// Exact alternative retained in the installing receipt. + [JsonPropertyName("choiceId")] + public string ChoiceId { get; set; } = string.Empty; + + /// Identity retained from the validated original plan. + [JsonPropertyName("identity")] + public McpPlanResourceIdentity Identity { get => field ??= new(); set; } + + /// Exact durable installation receipt identity. [JsonPropertyName("installationId")] public string InstallationId { get; set; } = string.Empty; - /// Existing selected local session on this connection. - [JsonPropertyName("policySessionId")] - public string PolicySessionId { get; set; } = string.Empty; + /// ISO 8601 wall-clock installation time when available. + [JsonPropertyName("installedAt")] + public string? InstalledAt { get; set; } + + /// Original installing operation, not a fresh management operation. + [JsonPropertyName("operationId")] + public string OperationId { get; set; } = string.Empty; + + /// Ownership or setup state, never inferred proof of tool usability. + [JsonPropertyName("state")] + public McpInstallationState State { get; set; } } -/// One-use application of the exact retained Skill removal plan. +/// The listed variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SkillApplyUninstallRequest +public partial class McpInstallationManagementOutcomeListed : McpInstallationManagementOutcome { - /// Required authenticated bound installation contract. - [JsonPropertyName("contract")] - public CatalogClientContract Contract { get => field ??= new(); set; } - - /// Opaque original removal plan, consumed once. - [JsonPropertyName("planHandle")] - public string PlanHandle { get; set; } = string.Empty; + /// + [JsonIgnore] + public override string Kind => "listed"; - /// Same existing selected session as removal preparation. - [JsonPropertyName("policySessionId")] - public string PolicySessionId { get; set; } = string.Empty; + /// Owned receipts visible to the selected account and host. + [JsonPropertyName("installations")] + public required IList Installations { get; set; } } -/// Server-side skill metadata, including name, description, source, enabled/invocable state, path, project path, and argument hint. +/// The recovered variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class ServerSkill +public partial class McpInstallationManagementOutcomeRecovered : McpInstallationManagementOutcome { - /// 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; } - - /// Description of what the skill does. - [JsonPropertyName("description")] - public string Description { get; set; } = string.Empty; - - /// Whether the skill is currently enabled (based on global config). - [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; } - - /// The project path this skill belongs to (only for project/inherited skills). - [JsonPropertyName("projectPath")] - public string? ProjectPath { get; set; } - - /// Source location type (e.g., project, personal-copilot, plugin, builtin). - [JsonPropertyName("source")] - public SkillSource Source { get; set; } + /// + [JsonIgnore] + public override string Kind => "recovered"; - /// Whether the skill can be invoked by the user as a slash command. - [JsonPropertyName("userInvocable")] - public bool UserInvocable { get; set; } + /// Freshly inspected receipts after successful durable reconciliation. + [JsonPropertyName("installations")] + public required IList Installations { get; set; } } -/// Skills discovered across global and project sources. +/// Exact inert removal plan. No configuration or credentials have changed. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class ServerSkillList +public sealed class McpUninstallPlan { - /// 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; } + /// Original wall-clock expiry in milliseconds. Applying never renews it. + [JsonPropertyName("expiresAtEpochMs")] + public long ExpiresAtEpochMs { get; set; } - /// All discovered skills across all sources. - [JsonPropertyName("skills")] - public IList Skills { get => field ??= []; set; } -} + /// Original owned receipt being removed. + [JsonPropertyName("installation")] + public McpInstallationSummary Installation { get => field ??= new(); set; } -/// Optional project paths and additional skill directories to include in discovery. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class 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. - [JsonPropertyName("excludeHostSkills")] - public bool? ExcludeHostSkills { get; set; } + /// The original operation, inspectable and cancellable on this same connection. + [JsonPropertyName("operationId")] + public string OperationId { get; set; } = string.Empty; - /// Optional skill scan paths to exclude from discovery. - [JsonPropertyName("ignoredSkillsLocations")] - public IList? IgnoredSkillsLocations { get; set; } + /// Exact configured input slots owned by this installation, never shared OAuth tokens. + [JsonPropertyName("ownedSecretCount")] + public long OwnedSecretCount { get; set; } - /// Optional list of project directory paths to scan for project-scoped skills. - [JsonPropertyName("projectPaths")] - public IList? ProjectPaths { get; set; } + /// One-use original connection and authority-bound plan handle. + [JsonPropertyName("planHandle")] + public string PlanHandle { get; set; } = string.Empty; - /// Optional list of additional skill directory paths to include. - [JsonPropertyName("skillDirectories")] - public IList? SkillDirectories { get; set; } + /// Shared authentication is deliberately retained; revocation is a separate action. + [JsonPropertyName("preservesSharedAuthentication")] + public bool PreservesSharedAuthentication { get; set; } + + /// Whether removal restores a protected earlier configuration. + [JsonPropertyName("restoresPreviousConfiguration")] + public bool RestoresPreviousConfiguration { get; set; } } -/// Canonical directory where skills can be discovered or created, with scope, preference, and optional project path. +/// The uninstall-planned variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class SkillDiscoveryPath +public partial class McpInstallationManagementOutcomeUninstallPlanned : McpInstallationManagementOutcome { - /// 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; } + /// + [JsonIgnore] + public override string Kind => "uninstall-planned"; - /// Which tier this directory belongs to. - [JsonPropertyName("scope")] - public SkillDiscoveryScope Scope { get; set; } + /// Original owned removal plan and operation. + [JsonPropertyName("plan")] + public required McpUninstallPlan Plan { get; set; } } -/// Canonical locations where skills can be created so the runtime will recognize them. +/// Status snapshot from the original connection, independent of new-work account availability. +/// Polymorphic base type discriminated by phase. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class SkillDiscoveryPathList +[JsonPolymorphic( + TypeDiscriminatorPropertyName = "phase", + UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] +[JsonDerivedType(typeof(McpInstallationOperationStatusPreparing), "preparing")] +[JsonDerivedType(typeof(McpInstallationOperationStatusPrepared), "prepared")] +[JsonDerivedType(typeof(McpInstallationOperationStatusAwaitingConfirmation), "awaiting-confirmation")] +[JsonDerivedType(typeof(McpInstallationOperationStatusRevalidating), "revalidating")] +[JsonDerivedType(typeof(McpInstallationOperationStatusApplying), "applying")] +[JsonDerivedType(typeof(McpInstallationOperationStatusCompleted), "completed")] +public partial class McpInstallationOperationStatus { - /// Canonical skill create/discovery directories, in priority order. - [JsonPropertyName("paths")] - public IList Paths { get => field ??= []; set; } + /// The type discriminator. + [JsonPropertyName("phase")] + public virtual string Phase { get; set; } = string.Empty; } -/// Optional project paths to enumerate. + +/// The preparing variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SkillsGetDiscoveryPathsRequest +public partial class McpInstallationOperationStatusPreparing : McpInstallationOperationStatus { - /// 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; } + /// + [JsonIgnore] + public override string Phase => "preparing"; - /// Optional skill scan paths to exclude from discovery. - [JsonPropertyName("ignoredSkillsLocations")] - public IList? IgnoredSkillsLocations { get; set; } + /// Whether cancellation has been requested, not proof that a write was undone. + [JsonPropertyName("cancellationRequested")] + public required bool CancellationRequested { 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; } + /// Original runtime-issued operation identity. + [JsonPropertyName("operationId")] + public required string OperationId { get; set; } } -/// Inventory request under an explicitly selected existing session. +/// The prepared variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SkillInstallationsRequest +public partial class McpInstallationOperationStatusPrepared : McpInstallationOperationStatus { - /// Required authenticated bound installation contract. - [JsonPropertyName("contract")] - public CatalogClientContract Contract { get => field ??= new(); set; } + /// + [JsonIgnore] + public override string Phase => "prepared"; - /// Existing selected local session on this connection. - [JsonPropertyName("policySessionId")] - public string PolicySessionId { get; set; } = string.Empty; + /// Whether the inert prepared operation was asked to cancel. + [JsonPropertyName("cancellationRequested")] + public required bool CancellationRequested { get; set; } + + /// Original runtime-issued operation identity. + [JsonPropertyName("operationId")] + public required string OperationId { get; set; } } -/// Existing-operation control. A new session selector is deliberately not accepted. +/// The awaiting-confirmation variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SkillInstallationOperationRequest +public partial class McpInstallationOperationStatusAwaitingConfirmation : McpInstallationOperationStatus { - /// Required authenticated bound Skill installation capability. - [JsonPropertyName("contract")] - public CatalogClientContract Contract { get => field ??= new(); set; } + /// + [JsonIgnore] + public override string Phase => "awaiting-confirmation"; - /// Exact runtime-issued operation ID on the original connection. + /// Whether the pending human callback was asked to cancel. + [JsonPropertyName("cancellationRequested")] + public required bool CancellationRequested { get; set; } + + /// Original runtime-issued operation identity. [JsonPropertyName("operationId")] - public string OperationId { get; set; } = string.Empty; + public required string OperationId { get; set; } } -/// Persisted enablement update for one owned Skill installation. +/// The revalidating variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SkillSetEnabledRequest +public partial class McpInstallationOperationStatusRevalidating : McpInstallationOperationStatus { - /// Required authenticated bound Skill installation capability. - [JsonPropertyName("contract")] - public CatalogClientContract Contract { get => field ??= new(); set; } - - /// Persisted enablement value. - [JsonPropertyName("enabled")] - public bool Enabled { get; set; } + /// + [JsonIgnore] + public override string Phase => "revalidating"; - /// Exact receipt identity to update. - [JsonPropertyName("installationId")] - public string InstallationId { get; set; } = string.Empty; + /// Whether source or authority revalidation was asked to cancel. + [JsonPropertyName("cancellationRequested")] + public required bool CancellationRequested { get; set; } - /// Existing selected local session to reconcile after persistence. - [JsonPropertyName("policySessionId")] - public string PolicySessionId { get; set; } = string.Empty; + /// Original runtime-issued operation identity. + [JsonPropertyName("operationId")] + public required string OperationId { get; set; } } -/// Skill names to mark as disabled in global configuration, replacing any previous list. +/// The applying variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SkillsConfigSetDisabledSkillsRequest +public partial class McpInstallationOperationStatusApplying : McpInstallationOperationStatus { - /// List of skill names to disable. - [JsonPropertyName("disabledSkills")] - public IList DisabledSkills { get => field ??= []; set; } -} + /// + [JsonIgnore] + public override string Phase => "applying"; -/// Adds or removes a single skill from the global disabled list, leaving every other entry untouched. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SkillsConfigSetSkillDisabledRequest -{ - /// True to disable the skill, false to enable it. - [JsonPropertyName("disabled")] - public bool Disabled { get; set; } + /// Whether applying was asked to cancel; already-started effects retain their lease. + [JsonPropertyName("cancellationRequested")] + public required bool CancellationRequested { get; set; } - /// Name of the skill to add to or remove from the disabled list. - [JsonPropertyName("name")] - public string Name { get; set; } = string.Empty; + /// Original runtime-issued operation identity. + [JsonPropertyName("operationId")] + public required string OperationId { get; set; } } -/// Outcome of a catalog.search call: either bounded inert candidates, or one typed refusal. Never a partial success. +/// Terminal mutation result. Uncertainty is not approval, rollback or permission to replay. /// Polymorphic base type discriminated by kind. [Experimental(global::GitHub.Copilot.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 +[JsonDerivedType(typeof(McpInstallationOutcomeInstalled), "installed")] +[JsonDerivedType(typeof(McpInstallationOutcomeUninstalled), "uninstalled")] +[JsonDerivedType(typeof(McpInstallationOutcomeRolledBack), "rolled-back")] +[JsonDerivedType(typeof(McpInstallationOutcomeRecoveryRequired), "recovery-required")] +[JsonDerivedType(typeof(McpInstallationOutcomeDeclined), "declined")] +[JsonDerivedType(typeof(McpInstallationOutcomeCancelled), "cancelled")] +[JsonDerivedType(typeof(McpInstallationOutcomeRefused), "refused")] +public partial class McpInstallationOutcome { /// The type discriminator. [JsonPropertyName("kind")] @@ -5368,402 +5924,363 @@ public partial class CatalogSearchResult } -/// One inert catalog result, represented as an MCP server, discovery-only AI skill, or opt-in Agent Plugin variant so kind, media type, provenance, and available operations cannot contradict each other. -/// Polymorphic base type discriminated by kind. +/// The installed variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -[JsonPolymorphic( - TypeDiscriminatorPropertyName = "kind", - UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] -[JsonDerivedType(typeof(CatalogCandidateMcpServer), "mcp-server")] -[JsonDerivedType(typeof(CatalogCandidateAiSkill), "ai-skill")] -[JsonDerivedType(typeof(CatalogCandidatePlugin), "plugin")] -public partial class CatalogCandidate +public partial class McpInstallationOutcomeInstalled : McpInstallationOutcome { - /// The type discriminator. - [JsonPropertyName("kind")] - public virtual string Kind { get; set; } = string.Empty; -} + /// + [JsonIgnore] + public override string Kind => "installed"; + /// Durable installation succeeded but final transaction cleanup remains. + [JsonPropertyName("cleanupPending")] + public required bool CleanupPending { 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. + /// Receipt identity produced by the confirmed transaction. + [JsonPropertyName("installation")] + public required McpInstallationSummary Installation { get; set; } +} + +/// The uninstalled variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class CatalogMcpServerCandidateProvenance +public partial class McpInstallationOutcomeUninstalled : McpInstallationOutcome { - /// 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; + /// + [JsonIgnore] + public override string Kind => "uninstalled"; - /// JSON MCP media type advertised for the referenced card. - [JsonPropertyName("mediaType")] - public McpServerCardMediaType MediaType { get; set; } + /// Durable removal succeeded but final cleanup remains. + [JsonPropertyName("cleanupPending")] + public required bool CleanupPending { 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; + /// Exact removed receipt identity. + [JsonPropertyName("installationId")] + public required string InstallationId { get; set; } + + /// Original removal operation. + [JsonPropertyName("operationId")] + public required string OperationId { get; set; } + + /// Any grants in the incumbent shared OAuth store remain unowned and retained. + [JsonPropertyName("preservedSharedAuthentication")] + public required bool PreservedSharedAuthentication { get; set; } + + /// Exact owned input slots removed, excluding shared OAuth credentials. + [JsonPropertyName("removedOwnedSecrets")] + public required long RemovedOwnedSecrets { get; set; } + + /// Whether protected pre-install configuration was restored. + [JsonPropertyName("restoredPreviousConfiguration")] + public required bool RestoredPreviousConfiguration { get; set; } } -/// 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. +/// The durable transaction was aborted or fully compensated. +/// The rolled-back variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -[JsonPolymorphic( - TypeDiscriminatorPropertyName = "kind", - UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] -[JsonDerivedType(typeof(CatalogCandidateSourceUrl), "url")] -[JsonDerivedType(typeof(CatalogCandidateSourceEmbedded), "embedded")] -public partial class CatalogCandidateSource +public partial class McpInstallationOutcomeRolledBack : McpInstallationOutcome { - /// The type discriminator. - [JsonPropertyName("kind")] - public virtual string Kind { get; set; } = string.Empty; -} + /// + [JsonIgnore] + public override string Kind => "rolled-back"; + + /// Original connection-owned operation. + [JsonPropertyName("operationId")] + public required string OperationId { get; set; } + /// Cause of the fully aborted or compensated operation. + [JsonPropertyName("reason")] + public required McpInstallationFailureReason Reason { get; set; } +} -/// Candidate whose card is retrieved from a URL through the runtime's hardened fetch boundary. -/// The url variant of . +/// A write may have completed. Recover and inspect durable state before retrying. +/// The recovery-required variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class CatalogCandidateSourceUrl : CatalogCandidateSource +public partial class McpInstallationOutcomeRecoveryRequired : McpInstallationOutcome { /// [JsonIgnore] - public override string Kind => "url"; + public override string Kind => "recovery-required"; - /// 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; } + /// Operation whose durable result must be recovered and inspected. + [JsonPropertyName("operationId")] + public required string OperationId { get; set; } } -/// Candidate whose card reference arrived inline. The document and its content-derived properties stay behind the runtime boundary. -/// The embedded variant of . +/// The declined variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class CatalogCandidateSourceEmbedded : CatalogCandidateSource +public partial class McpInstallationOutcomeDeclined : McpInstallationOutcome { /// [JsonIgnore] - public override string Kind => "embedded"; + public override string Kind => "declined"; + + /// Original operation explicitly declined by the user. + [JsonPropertyName("operationId")] + public required string OperationId { 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 . +/// The cancelled variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class CatalogCandidateMcpServer : CatalogCandidate +public partial class McpInstallationOutcomeCancelled : McpInstallationOutcome { /// [JsonIgnore] - public override string Kind => "mcp-server"; + public override string Kind => "cancelled"; - /// 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; } + /// Original operation cancelled before a terminal application result. + [JsonPropertyName("operationId")] + public required string OperationId { 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 refused variant of . +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public partial class McpInstallationOutcomeRefused : McpInstallationOutcome +{ + /// + [JsonIgnore] + public override string Kind => "refused"; - /// 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; } + /// Present once an operation has been allocated; never a plan handle. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("operationId")] + public string? OperationId { get; set; } - /// ISO 8601 timestamp after which the handle is stale and will be rejected. - [JsonPropertyName("handleExpiresAt")] - public required string HandleExpiresAt { get; set; } + /// Specific bounded refusal, never a success-shaped fallback. + [JsonPropertyName("reason")] + public required McpInstallationFailureReason Reason { get; set; } +} - /// Whether this MCP server can be planned for installation, and if policy prevents it. - [JsonPropertyName("installability")] - public required CatalogMcpServerInstallability Installability { get; set; } +/// The completed variant of . +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public partial class McpInstallationOperationStatusCompleted : McpInstallationOperationStatus +{ + /// + [JsonIgnore] + public override string Phase => "completed"; - /// JSON MCP media type of the underlying card. - [JsonPropertyName("mediaType")] - public required McpServerCardMediaType MediaType { get; set; } + /// Whether cancellation was requested before the terminal result. + [JsonPropertyName("cancellationRequested")] + public required bool CancellationRequested { get; set; } - /// Where the catalog reference was observed, without the card itself or any content digest. - [JsonPropertyName("provenance")] - public required CatalogMcpServerCandidateProvenance Provenance { get; set; } + /// Original runtime-issued operation identity. + [JsonPropertyName("operationId")] + public required string OperationId { get; 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; } + /// Immutable terminal receipt. + [JsonPropertyName("outcome")] + public required McpInstallationOutcome Outcome { 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; } +/// The operation variant of . +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public partial class McpInstallationManagementOutcomeOperation : McpInstallationManagementOutcome +{ + /// + [JsonIgnore] + public override string Kind => "operation"; - /// Versioned trust metadata observed from the catalog authority. Optional for protocol-3 compatibility with runtimes that predate trust snapshots. A trust-capable runtime emits an explicit snapshot even when the authority omitted or malformed its trust field. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("trust")] - public CatalogTrustSnapshot? Trust { get; set; } + /// Original-connection operation snapshot. + [JsonPropertyName("operation")] + public required McpInstallationOperationStatus Operation { get; set; } } -/// 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. +/// The refused variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class CatalogAiSkillCandidateProvenance +public partial class McpInstallationManagementOutcomeRefused : McpInstallationManagementOutcome { - /// 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; - - /// Media type advertised for the referenced AI skill card. - [JsonPropertyName("mediaType")] - public CatalogAiSkillMediaType MediaType { get; set; } + /// + [JsonIgnore] + public override string Kind => "refused"; - /// 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; + /// Specific bounded refusal. + [JsonPropertyName("reason")] + public required McpInstallationFailureReason Reason { get; set; } } -/// An inert AI skill catalog result. Verified Skill candidates may be installable only when the runtime reports installability and the selected session is permitted to plan installation. -/// The ai-skill variant of . +/// The outcome variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class CatalogCandidateAiSkill : CatalogCandidate +public partial class McpInstallationManagementResultOutcome : McpInstallationManagementResult { /// [JsonIgnore] - public override string Kind => "ai-skill"; + public override string Kind => "outcome"; - /// 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; } + /// Capabilities actually honoured for this request. + [JsonPropertyName("negotiated")] + public required CatalogNegotiatedContract Negotiated { 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; } + /// Observed management outcome. + [JsonPropertyName("outcome")] + public required McpInstallationManagementOutcome Outcome { 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. +/// 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(global::GitHub.Copilot.Diagnostics.Experimental)] +public partial class McpInstallationManagementResultNegotiationRefused : McpInstallationManagementResult +{ + /// + [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")] - [MinLength(1)] - [JsonPropertyName("handle")] - public required string Handle { get; set; } + [MaxLength(1000)] + [JsonPropertyName("message")] + public required string Message { get; set; } - /// ISO 8601 timestamp after which the handle is stale and will be rejected. - [JsonPropertyName("handleExpiresAt")] - public required string HandleExpiresAt { get; set; } + /// Lowest caller protocol version this runtime will serve. + [JsonPropertyName("minimumSupportedProtocolVersion")] + public required long MinimumSupportedProtocolVersion { get; set; } - /// Whether this AI skill candidate can be planned for verified installation in the selected session. - [JsonPropertyName("installability")] - public required CatalogAiSkillInstallability Installability { get; set; } + /// Whether the version or the capability set was the problem. + [JsonPropertyName("reason")] + public required CatalogNegotiationRefusedReason Reason { get; set; } - /// Media type of the underlying AI skill card. - [JsonPropertyName("mediaType")] - public required CatalogAiSkillMediaType MediaType { get; set; } + /// Protocol version of the runtime that refused the request. + [JsonPropertyName("runtimeProtocolVersion")] + public required long RuntimeProtocolVersion { get; set; } - /// Where the catalog reference was observed, without the card itself or any content digest. - [JsonPropertyName("provenance")] - public required CatalogAiSkillCandidateProvenance Provenance { get; set; } + /// Capabilities this runtime can safely advertise to this caller. The complete five-capability protocol-3 legacy set is always present; every capability added after that baseline appears only when the caller required it, so an older closed-enum decoder can still consume a refusal. This list does not imply that every deployment has enabled every operation. + [JsonPropertyName("supportedCapabilities")] + public required IList SupportedCapabilities { get; 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; } + /// The subset of the caller's bounded extensible capability identifiers this runtime cannot honour. + [JsonPropertyName("unsupportedCapabilities")] + public required IList UnsupportedCapabilities { 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; } +/// The request was rejected because a bounded field fell outside its permitted range or a required field was unusable. Pagination may also be rejected by the authority after a continuation request; repeat the search without page. +/// The invalid-request variant of . +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public partial class McpInstallationManagementResultInvalidRequest : McpInstallationManagementResult +{ + /// + [JsonIgnore] + public override string Kind => "invalid-request"; - /// Versioned trust metadata observed from the catalog authority. Optional for protocol-3 compatibility with runtimes that predate trust snapshots. A trust-capable runtime emits an explicit snapshot even when the authority omitted or malformed its trust field. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("trust")] - public CatalogTrustSnapshot? Trust { get; set; } + /// 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; } } -/// Where and when an Agent Plugin catalog reference was observed. Discovery provenance deliberately carries no descriptor URL, raw data, candidate handle, or content digest. +/// One Registry string-valued configuration entry for the selected transport. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class CatalogAgentPluginCandidateProvenance +public sealed class McpInstallationInput { - /// 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; + /// Exact category declared by the selected choice. + [JsonPropertyName("category")] + public McpPlanValueCategory Category { get; set; } - /// Canonical Agent Plugin media type. - [JsonPropertyName("mediaType")] - public CatalogAgentPluginMediaType MediaType { get; set; } + /// Exact key declared by the selected choice. + [JsonPropertyName("key")] + public string Key { 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; + /// Explicit non-secret value. Secret placeholders use a separate input channel. + [JsonPropertyName("value")] + public string Value { get; set; } = string.Empty; } -/// Syntactically validated GitHub repository provenance declared by catalog metadata. This is a source claim rather than proof that the descriptor URL resolves to the repository. +/// A request-local value for one exact reviewed placeholder. Never logged or persisted in a plan. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class CatalogPluginRepositorySource +public sealed class McpInstallationSecret { - /// Case-preserving safe relative POSIX path derived from metadata.repoPath. - [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("path")] - public string Path { get; set; } = string.Empty; + /// Exact placeholder from the selected choice, not a caller-chosen backend identifier. + [JsonPropertyName("placeholder")] + public string Placeholder { get; set; } = string.Empty; - /// Canonical lowercase owner/repository name derived from metadata.sourceSet. - [RegularExpression("^[a-z0-9_.-]+/[a-z0-9_.-]+$")] - [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(3)] - [MaxLength(256)] - [JsonPropertyName("repository")] - public string Repository { get; set; } = string.Empty; + /// Fresh explicit secret value. It is omitted from confirmation reviews and telemetry. + [JsonPropertyName("value")] + public string Value { get; set; } = string.Empty; } -/// An inert Agent Plugin catalog result. Its canonical catalog identity, declared version, repository source claim, and explicit compatibility tags are safe to correlate, while its descriptor, URL, raw data, and installed-plugin state remain runtime-private. This contract-only variant does not mint or expose a candidate handle. -/// The plugin variant of . +/// Side-effect-free preparation of one original bound remote MCP choice. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class CatalogCandidatePlugin : CatalogCandidate +internal sealed class McpPrepareInstallRequest { - /// - [JsonIgnore] - public override string Kind => "plugin"; - - /// Explicit validated compatibility tags, in canonical order. An empty list means the source declared no recognised compatibility; clients must not infer compatibility from other fields. `canvas-only` requires both `canvas` and `github-copilot`. - [JsonPropertyName("compatibilityTags")] - public required IList CompatibilityTags { get; set; } + /// Exact selected alternative from that plan. + [JsonPropertyName("choiceId")] + public string ChoiceId { 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; } + /// Required bound catalogue and confirmed remote installation capabilities. + [JsonPropertyName("contract")] + public CatalogClientContract Contract { get => field ??= new(); 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; } + /// + /// Declared non-secret values. Non-empty only when the caller requires + /// `mcp-configured-remote-installation`; omitted values use the card default. + /// + [JsonPropertyName("inputs")] + public IList Inputs { get => field ??= []; set; } - /// Validated, normalised catalogue resource URN. This identity comes only from the catalog identifier and is never inferred from display text or installed-plugin state. - [JsonPropertyName("identity")] - public required string Identity { get; set; } + /// Original single-use bound plan, never a client-authored configuration. + [JsonPropertyName("planHandle")] + public string PlanHandle { get; set; } = string.Empty; - /// Canonical Agent Plugin media type. - [JsonPropertyName("mediaType")] - public required CatalogAgentPluginMediaType MediaType { get; set; } + /// An existing local session attached to this connection, not permission to attach one. + [JsonPropertyName("policySessionId")] + public string PolicySessionId { get; set; } = string.Empty; - /// Where the Agent Plugin catalog reference was observed, without its descriptor, URL, raw data, or content digest. - [JsonPropertyName("provenance")] - public required CatalogAgentPluginCandidateProvenance Provenance { get; set; } + /// + /// One entry per declared secret placeholder of the selected choice. Non-empty + /// only when the caller requires `mcp-configured-remote-installation`. + /// + [JsonPropertyName("secrets")] + public IList Secrets { 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; } + /// The trusted host presents this choice alongside the exact secret placeholders. + [JsonPropertyName("secretStorage")] + public McpInstallationSecretStorage SecretStorage { get; set; } - /// Bounded repository provenance declared through the catalog's sourceSet and repoPath metadata. It contains no descriptor URL. + /// The exact original source, used transiently only after confirmation. [JsonPropertyName("source")] - public required CatalogPluginRepositorySource Source { get; set; } - - /// Versioned trust metadata observed from the catalog authority. Optional for protocol-3 compatibility and emitted only when the caller also requires the trust-snapshot capability. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("trust")] - public CatalogTrustSnapshot? Trust { get; set; } - - /// Optional version declared by the catalog source. Omitted rather than guessed when the source supplies no version. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("version")] - public string? Version { get; set; } + public McpServerCardReference Source { get => field ??= new(); set; } } -/// Authority-reported navigation metadata, returned only to callers requiring catalog-search-pagination and only when a supported token is present. Tokenless first-page and continuation responses omit this object; no counts are inferred from candidates. The opaque token may be retained for previous or numbered navigation even when hasNextPage is false. +/// An installation result together with the exact honoured contract, or a negotiation refusal. +/// Polymorphic base type discriminated by kind. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class CatalogSearchPagination +[JsonPolymorphic( + TypeDiscriminatorPropertyName = "kind", + UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] +[JsonDerivedType(typeof(McpInstallationResultOutcome), "outcome")] +[JsonDerivedType(typeof(McpInstallationResultNegotiationRefused), "negotiation-refused")] +[JsonDerivedType(typeof(McpInstallationResultInvalidRequest), "invalid-request")] +public partial class McpInstallationResult { - /// One-based page returned by the authority. - [JsonPropertyName("currentPage")] - public int CurrentPage { get; set; } - - /// Whether the authority token advertises a valid next target within the navigation window. Not inferred from token presence, truncated, or currentPage being less than pageCount. - [JsonPropertyName("hasNextPage")] - public bool HasNextPage { get; set; } - - /// Navigation window ceiling ceil(1000 / pageSize), not the number of existing pages. Legal targets must not exceed this ceiling or the token's signed pageCount. - [JsonPropertyName("maxPage")] - public int MaxPage { get; set; } - - /// Backend-reported page count, which may exceed maxPage. Present pagination metadata always describes a multi-page result; zero- and single-page responses omit pagination. Navigation targets must also be within the signed pageCount carried by the supplied token. - [JsonPropertyName("pageCount")] - public long PageCount { get; set; } - - /// Page size bound to the search, equal to the effective request limit. - [JsonPropertyName("pageSize")] - public int PageSize { get; set; } - - /// Opaque pagination token. Session-bound search returns an expiring runtime-owned reference retaining the exact private authority token, original search and authority. Legacy unbound search returns the authority token unchanged, without a runtime-created expiry. Only the runtime unwraps tokens or changes targetPage; SDK consumers must not decode, modify or log them. - [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(4096)] - [JsonPropertyName("token")] - public string Token { get; set; } = string.Empty; - - /// Backend-reported count for this response, not the number of returned candidates. Its relationship to the full query result set is unknown. - [JsonPropertyName("totalCount")] - public long TotalCount { get; set; } - - /// The relationship of totalCount to the complete query result set is unknown; neither exactness nor a lower-bound guarantee is implied. - [JsonPropertyName("totalCountRelation")] - public CatalogSearchTotalCountRelation TotalCountRelation { get; set; } + /// The type discriminator. + [JsonPropertyName("kind")] + public virtual string Kind { get; set; } = string.Empty; } -/// A completed catalog search containing inert candidate summaries. MCP server and AI skill variants carry a single-use handle; the Agent Plugin variant is handleless. -/// The succeeded variant of . + +/// The outcome variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class CatalogSearchResultSucceeded : CatalogSearchResult +public partial class McpInstallationResultOutcome : McpInstallationResult { /// [JsonIgnore] - public override string Kind => "succeeded"; - - /// Matching candidates, never more than the requested limit. All text is inert untrusted data. - [JsonPropertyName("candidates")] - public required IList Candidates { get; set; } + public override string Kind => "outcome"; - /// Protocol version and capabilities the runtime honoured. + /// Capabilities actually honoured for this request. [JsonPropertyName("negotiated")] public required CatalogNegotiatedContract Negotiated { get; set; } - /// Navigation metadata for callers explicitly requiring catalog-search-pagination. Omitted when the authority returns no token, including tokenless first-page and continuation responses. Counts are never substituted from candidates.length. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("pagination")] - public CatalogSearchPagination? Pagination { 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; } - - /// Legacy indication that the authority returned a page token. Preserved for compatibility; this is not a has-next-page indicator. Use pagination.hasNextPage when pagination metadata is present. - [JsonPropertyName("truncated")] - public required bool Truncated { get; set; } + /// Terminal result of the original operation. + [JsonPropertyName("outcome")] + public required McpInstallationOutcome Outcome { get; set; } } /// The caller's protocol version or required capabilities cannot be honoured. Returned instead of a partial or ambiguous success. -/// The negotiation-refused variant of . +/// The negotiation-refused variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class CatalogSearchResultNegotiationRefused : CatalogSearchResult +public partial class McpInstallationResultNegotiationRefused : McpInstallationResult { /// [JsonIgnore] @@ -5796,34 +6313,10 @@ public partial class CatalogSearchResultNegotiationRefused : CatalogSearchResult public required IList UnsupportedCapabilities { get; set; } } -/// The request asked for a candidate kind this runtime does not serve. -/// The unsupported-kind variant of . -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class CatalogSearchResultUnsupportedKind : CatalogSearchResult -{ - /// - [JsonIgnore] - public override string Kind => "unsupported-kind"; - - /// 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; } - - /// The kinds from the request that are not supported. - [JsonPropertyName("requestedKinds")] - public required IList RequestedKinds { get; set; } - - /// Every candidate kind this runtime can serve. - [JsonPropertyName("supportedKinds")] - public required IList SupportedKinds { get; set; } -} - /// The request was rejected because a bounded field fell outside its permitted range or a required field was unusable. Pagination may also be rejected by the authority after a continuation request; repeat the search without page. -/// The invalid-request variant of . +/// The invalid-request variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class CatalogSearchResultInvalidRequest : CatalogSearchResult +public partial class McpInstallationResultInvalidRequest : McpInstallationResult { /// [JsonIgnore] @@ -5840,12662 +6333,12858 @@ public partial class CatalogSearchResultInvalidRequest : CatalogSearchResult public required string Message { get; set; } } -/// 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 . +/// Applies exactly one previously prepared operation on its original connection. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class CatalogSearchResultAuthenticationRequired : CatalogSearchResult +internal sealed class McpApplyInstallRequest { - /// - [JsonIgnore] - public override string Kind => "authentication-required"; + /// Capabilities required by the original prepared operation. + [JsonPropertyName("contract")] + public CatalogClientContract Contract { get => field ??= new(); set; } - /// 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; } + /// Runtime-issued ID already returned by prepareInstall, never reused or rebound. + [JsonPropertyName("operationId")] + public string OperationId { get; set; } = string.Empty; - /// 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; } + /// Same existing attached or privately borrowed session as preparation. + [JsonPropertyName("policySessionId")] + public string PolicySessionId { get; set; } = string.Empty; } -/// Registry or enterprise policy refused the operation. -/// The policy-rejected variant of . +/// Read-only preparation of one owned removal under fresh selected-session authority. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class CatalogSearchResultPolicyRejected : CatalogSearchResult +internal sealed class McpPlanUninstallRequest { - /// - [JsonIgnore] - public override string Kind => "policy-rejected"; + /// Required authenticated bound installation capabilities. + [JsonPropertyName("contract")] + public CatalogClientContract Contract { get => field ??= new(); 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; } + /// Exact receipt to inspect, not a server-name guess. + [JsonPropertyName("installationId")] + public string InstallationId { get; set; } = string.Empty; - /// Which authority produced the decision. - [JsonPropertyName("source")] - public required McpPlanPolicySource Source { get; set; } + /// Existing selected session on the original connection. + [JsonPropertyName("policySessionId")] + public string PolicySessionId { get; set; } = string.Empty; } -/// 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 . +/// One-use application of the exact retained removal plan. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class CatalogSearchResultNetworkFailure : CatalogSearchResult +internal sealed class McpApplyUninstallRequest { - /// - [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; } + /// Required authenticated bound installation capabilities. + [JsonPropertyName("contract")] + public CatalogClientContract Contract { get => field ??= new(); 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; } + /// Opaque original removal plan, consumed once. + [JsonPropertyName("planHandle")] + public string PlanHandle { get; set; } = string.Empty; - /// HTTP status code, when the failure was a rejected response. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("statusCode")] - public int? StatusCode { get; set; } + /// Same existing selected session as removal preparation. + [JsonPropertyName("policySessionId")] + public string PolicySessionId { get; set; } = string.Empty; } -/// 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 . +/// User-configured MCP servers, keyed by server name. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class CatalogSearchResultUnsafeRetrieval : CatalogSearchResult +public sealed class McpConfigList { - /// - [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; } + /// All MCP servers from user config, keyed by name. + [JsonPropertyName("servers")] + public IDictionary Servers { get => field ??= new Dictionary(); set; } } -/// A card could not be parsed or did not satisfy its declared media type's schema. -/// The malformed-card variant of . +/// MCP server name and configuration to add to user configuration. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class CatalogSearchResultMalformedCard : CatalogSearchResult +internal sealed class McpConfigAddRequest { - /// - [JsonIgnore] - public override string Kind => "malformed-card"; - - /// 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; } + /// MCP server configuration (stdio process or remote HTTP/SSE). + [JsonPropertyName("config")] + public JsonElement Config { get; set; } - /// Human-readable explanation, safe to surface. Never echoes card content, nor a query, URL, handle, or secret. + /// 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")] - [MaxLength(1000)] - [JsonPropertyName("message")] - public required string Message { get; set; } - - /// How the card failed validation. - [JsonPropertyName("reason")] - public required CatalogMalformedCardReason Reason { get; set; } + [MinLength(1)] + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; } -/// 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 . +/// MCP server name and replacement configuration to write to user configuration. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class CatalogSearchResultContractViolation : CatalogSearchResult +internal sealed class McpConfigUpdateRequest { - /// - [JsonIgnore] - public override string Kind => "contract-violation"; + /// MCP server configuration (stdio process or remote HTTP/SSE). + [JsonPropertyName("config")] + public JsonElement Config { get; set; } - /// Human-readable explanation, safe to surface. Never echoes response content, nor a query, URL, handle, or secret. + /// 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")] - [MaxLength(1000)] - [JsonPropertyName("message")] - public required string Message { get; set; } - - /// Which rule the response broke. - [JsonPropertyName("reason")] - public required CatalogContractViolationReason Reason { get; set; } + [MinLength(1)] + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; } -/// The operation is not available on this runtime. Distinct from a network failure: nothing was attempted. -/// The unavailable variant of . +/// MCP server name to remove from user configuration. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class CatalogSearchResultUnavailable : CatalogSearchResult +internal sealed class McpConfigRemoveRequest { - /// - [JsonIgnore] - public override string Kind => "unavailable"; + /// OAuth Client ID Metadata Document URL whose persisted credentials should also be removed. + [JsonPropertyName("authClientIdMetadataUrl")] + public string? AuthClientIdMetadataUrl { get; set; } - /// Human-readable explanation, safe to surface. Never contains a query, URL, handle, or secret. + /// 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")] - [MaxLength(1000)] - [JsonPropertyName("message")] - public required string Message { get; set; } - - /// Why the operation is unavailable. - [JsonPropertyName("reason")] - public required CatalogUnavailableReason Reason { get; set; } + [MinLength(1)] + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; } -/// An explicit numbered-page request. SDK consumers treat the token as opaque. For bound search, the runtime unwraps an expiring owner-bound reference to the private authority token; only the runtime changes the authority token's targetPage. Legacy unbound navigation keeps its authority-issued token semantics. No snapshot stability is promised. +/// MCP server names to enable for new sessions. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class CatalogSearchPage +internal sealed class McpConfigEnableRequest { - /// Requested one-based page. Must not exceed either the token's signed pageCount or the navigation window ceil(1000 / pageSize). Repeat the search without page to discover newly available pages beyond that signed pageCount. - [JsonPropertyName("number")] - public int Number { get; set; } + /// 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; } +} - /// Opaque pagination token from an earlier response, owner-bound when session-bound search was requested. Never decode, modify or log it in an SDK consumer. Expired or foreign bound references require a fresh bound search, not a legacy retry. - [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(4096)] - [JsonPropertyName("token")] - public string Token { get; set; } = string.Empty; +/// MCP server names to disable for new sessions. +[Experimental(global::GitHub.Copilot.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; } } -/// 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. +/// Request id naming a cancellable registry search. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class CatalogSearchRequest +internal sealed class McpRegistryRequestIdResult { - /// Protocol version and capabilities the caller requires. - [JsonPropertyName("contract")] - public required CatalogClientContract Contract { get; set; } + /// Request ID for `mcp.registry.search` and `mcp.registry.cancel`. It serves one search. Allocation can reclaim unused IDs at the 1,024-ID reservation limit. + [JsonPropertyName("requestId")] + public long RequestId { get; set; } +} - /// Restrict results to these candidate kinds. Agent Plugins are opt-in and require the `agent-plugin-discovery` capability so protocol-v3 clients generated before that variant cannot receive an unknown result; when omitted, the backwards-compatible MCP server and AI skill kinds are searched. - [JsonPropertyName("kinds")] - public IList? Kinds { get; set; } +/// Servers selected from the registry response. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class McpRegistrySearchResult +{ + /// The server objects, carried opaquely. The runtime follows pages, keeps the newest entry per server name, cuts the list to `limit`, and sorts an empty-query result by GitHub stars. Each server object remains unchanged because the registry owns that shape. + [JsonPropertyName("servers")] + public JsonElement Servers { get; set; } +} - /// Maximum number of candidates to return. Defaults to 10 when omitted. +/// Registry search terms, the credential to search under, and the request id that makes the search cancellable. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class McpRegistrySearchRequest +{ + /// The credential the search runs under, carried opaquely. Hosts usually send credential-free `AuthIdentity`; a `token` identity can be resolved only when it embeds a token, while `env` and `gh-cli` identities can use a token embedded in the request first. + [JsonPropertyName("authInfo")] + public JsonElement AuthInfo { get; set; } + + /// Maximum number of servers to return. [JsonPropertyName("limit")] - public int? Limit { get; set; } + public long Limit { get; set; } - /// Numbered navigation using metadata from an earlier response. Requires catalog-search-pagination and the same query, kinds and effective limit. Omit for a fresh first-page search. - [JsonPropertyName("page")] - public CatalogSearchPage? Page { get; set; } + /// Free-text query. Omitted or empty asks the registry for its top servers rather than searching. A value that is not a string is refused. + [JsonPropertyName("query")] + public string? Query { get; set; } - /// - /// Select an existing attached local session. Requires authenticated, session-bound search. - /// The runtime never creates, resumes or reconfigures a session to honour this selector. - /// - [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("policySessionId")] - public string? PolicySessionId { get; set; } + /// Repository used for the policy lookup, as `owner/name`. The policy selects the registry URL and whether the user token goes to the registry. The registry receives this repository only when the policy entry lists it as required context. A value that is not a string is refused. + [JsonPropertyName("repository")] + public string? Repository { 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 required string Query { get; set; } + /// Request ID from `mcp.registry.allocateRequestId`. The search refuses unknown, reclaimed, or canceled IDs and IDs that another search or request already uses. + [JsonPropertyName("requestId")] + public long RequestId { get; set; } } -/// Typed outcome of catalog.select. Only the selected host result carries a fresh candidate handle; the model-facing projection removes both that handle and searchId. -/// Polymorphic base type discriminated by kind. +/// Whether the cancel reached a live search. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -[JsonPolymorphic( - TypeDiscriminatorPropertyName = "kind", - UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] -[JsonDerivedType(typeof(CatalogSelectionResultSelected), "selected")] -[JsonDerivedType(typeof(CatalogSelectionResultDeclined), "declined")] -[JsonDerivedType(typeof(CatalogSelectionResultCancelled), "cancelled")] -[JsonDerivedType(typeof(CatalogSelectionResultTimedOut), "timed-out")] -[JsonDerivedType(typeof(CatalogSelectionResultInvalid), "invalid")] -[JsonDerivedType(typeof(CatalogSelectionResultStale), "stale")] -[JsonDerivedType(typeof(CatalogSelectionResultReplayed), "replayed")] -[JsonDerivedType(typeof(CatalogSelectionResultForeign), "foreign")] -[JsonDerivedType(typeof(CatalogSelectionResultWrongKind), "wrong-kind")] -[JsonDerivedType(typeof(CatalogSelectionResultNegotiationRefused), "negotiation-refused")] -[JsonDerivedType(typeof(CatalogSelectionResultInvalidRequest), "invalid-request")] -[JsonDerivedType(typeof(CatalogSelectionResultUnavailable), "unavailable")] -public partial class CatalogSelectionResult +internal sealed class McpRegistryCancelResult { - /// The type discriminator. - [JsonPropertyName("kind")] - public virtual string Kind { get; set; } = string.Empty; + /// True when the cancel stopped a running search. False for unknown or reclaimed IDs, completed or canceled searches, and unused reservations. The cancel releases an unused reservation. + [JsonPropertyName("canceled")] + public bool Canceled { get; set; } } - -/// The chosen candidate was transferred into a fresh bounded single-use handle for a later explicit planning request. -/// The selected variant of . +/// Registry search to abandon. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class CatalogSelectionResultSelected : CatalogSelectionResult +internal sealed class McpRegistryCancelRequest { - /// - [JsonIgnore] - public override string Kind => "selected"; - - /// Fresh single-use candidate handle accepted by mcp.planInstall. Returned only to the native host and never included in model-tool output. - [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; } - - /// The exact search identifier privately bound to the selected candidate. - [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; } + /// Request ID from `mcp.registry.allocateRequestId` that the search uses. + [JsonPropertyName("requestId")] + public long RequestId { get; set; } } -/// The caller explicitly declined every candidate and the retained search state was released. -/// The declined variant of . +/// New-work inventory or recovery request under an explicitly selected existing session. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class CatalogSelectionResultDeclined : CatalogSelectionResult +internal sealed class McpInstallationsRequest { - /// - [JsonIgnore] - public override string Kind => "declined"; + /// Required authenticated bound installation contract. + [JsonPropertyName("contract")] + public CatalogClientContract Contract { get => field ??= new(); set; } - /// The search identifier privately bound to the released selection group. - [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; } + /// Existing selected local session on this connection. + [JsonPropertyName("policySessionId")] + public string PolicySessionId { get; set; } = string.Empty; } -/// The caller cancelled the selection interaction and the retained search state was released. -/// The cancelled variant of . +/// Existing-operation control. A new session selector is deliberately not accepted. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class CatalogSelectionResultCancelled : CatalogSelectionResult +internal sealed class McpInstallationOperationRequest { - /// - [JsonIgnore] - public override string Kind => "cancelled"; + /// Original installation wire capability; new-work authentication is not reacquired. + [JsonPropertyName("contract")] + public CatalogClientContract Contract { get => field ??= new(); set; } - /// The search identifier privately bound to the released selection group. - [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; } + /// Exact runtime-issued operation ID on the original connection. + [JsonPropertyName("operationId")] + public string OperationId { get; set; } = string.Empty; } -/// The host declared that its live selection interaction timed out, and the retained search state was released. -/// The timed-out variant of . +/// Installed plugin that contributes a discovered extension. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class CatalogSelectionResultTimedOut : CatalogSelectionResult +public sealed class DiscoveredExtensionPlugin { - /// - [JsonIgnore] - public override string Kind => "timed-out"; - - /// The search identifier privately bound to the released selection group. - [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; } + /// Installed plugin name. + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; } -/// The selection reference was malformed or unknown. -/// The invalid variant of . +/// Discovered extension metadata and persistent enablement state. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class CatalogSelectionResultInvalid : CatalogSelectionResult +public sealed class DiscoveredExtension { - /// - [JsonIgnore] - public override string Kind => "invalid"; + /// Whether this extension's persistent per-ID preference is enabled. + [JsonPropertyName("enabled")] + public bool Enabled { get; set; } - /// Human-readable explanation safe to surface. Never contains the presented reference or private candidate state. - [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; } + /// Source-qualified ID accepted by both server and session extension enablement methods. + [JsonPropertyName("id")] + public string Id { get; set; } = string.Empty; + + /// Human-readable extension name. + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; + + /// Absolute path to the extension entry module, suitable for revealing it in a file manager. + [JsonPropertyName("path")] + public string Path { get; set; } = string.Empty; + + /// Containing plugin metadata for plugin-contributed extensions. + [JsonPropertyName("plugin")] + public DiscoveredExtensionPlugin? Plugin { get; set; } + + /// Discovery source. + [JsonPropertyName("source")] + public DiscoveredExtensionSource Source { get; set; } } -/// The runtime-enforced selection reference lifetime elapsed before the request arrived. -/// The stale variant of . +/// Extensions discovered from persisted Copilot home state and their effective loading mode. Launch-scoped additional plugins are not included. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class CatalogSelectionResultStale : CatalogSelectionResult +public sealed class DiscoveredExtensions { - /// - [JsonIgnore] - public override string Kind => "stale"; + /// Discovered user and enabled installed-plugin extensions from persisted Copilot home state. + [JsonPropertyName("extensions")] + public IList Extensions { get => field ??= []; set; } - /// Human-readable explanation safe to surface. Never contains the presented reference or private candidate state. - [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; } + /// Effective extension loading mode. Defaults to load_and_augment when unset. + [JsonPropertyName("mode")] + public DiscoveredExtensionMode Mode { get; set; } } -/// The selection group was already terminated or its pending host hand-off was already claimed. -/// The replayed variant of . +/// Source-qualified extension identifiers to persistently enable for future sessions. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class CatalogSelectionResultReplayed : CatalogSelectionResult +internal sealed class DiscoveredExtensionsEnableRequest { - /// - [JsonIgnore] - public override string Kind => "replayed"; - - /// Human-readable explanation safe to surface. Never contains the presented reference or private candidate state. - [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; } + /// Source-qualified user or plugin extension IDs to enable. + [JsonPropertyName("ids")] + public IList Ids { get => field ??= []; set; } } -/// The selection reference belongs to another runtime instance or session. -/// The foreign variant of . +/// Source-qualified extension identifiers to persistently disable for future sessions. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class CatalogSelectionResultForeign : CatalogSelectionResult +internal sealed class DiscoveredExtensionsDisableRequest { - /// - [JsonIgnore] - public override string Kind => "foreign"; - - /// Human-readable explanation safe to surface. Never contains the presented reference or private candidate state. - [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; } + /// Source-qualified user or plugin extension IDs to disable. + [JsonPropertyName("ids")] + public IList Ids { get => field ??= []; set; } } -/// The presented opaque handle was issued for another catalog operation. -/// The wrong-kind variant of . +/// The prompt the init command runs. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class CatalogSelectionResultWrongKind : CatalogSelectionResult +internal sealed class InstructionsInitPromptResult { - /// - [JsonIgnore] - public override string Kind => "wrong-kind"; - - /// Human-readable explanation safe to surface. Never contains the presented reference or private candidate state. - [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; } + /// Prompt text authoring repository instructions. + [JsonPropertyName("prompt")] + public string Prompt { get; set; } = string.Empty; } -/// The caller's protocol version or required capabilities cannot be honoured. Returned instead of a partial or ambiguous success. -/// The negotiation-refused variant of . +/// Loaded instruction source for a session, including path, content, category, location, applicability, and optional description. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class CatalogSelectionResultNegotiationRefused : CatalogSelectionResult +public sealed class InstructionSource { - /// - [JsonIgnore] - public override string Kind => "negotiation-refused"; + /// Glob pattern(s) from frontmatter — when set, this instruction applies only to matching files. + [JsonPropertyName("applyTo")] + public IList? ApplyTo { 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; } + /// Raw content of the instruction file. + [JsonPropertyName("content")] + public string Content { get; set; } = string.Empty; - /// Lowest caller protocol version this runtime will serve. - [JsonPropertyName("minimumSupportedProtocolVersion")] - public required long MinimumSupportedProtocolVersion { get; set; } + /// When true, this source starts disabled and must be toggled on by the user. + [JsonPropertyName("defaultDisabled")] + public bool? DefaultDisabled { get; set; } - /// Whether the version or the capability set was the problem. - [JsonPropertyName("reason")] - public required CatalogNegotiationRefusedReason Reason { get; set; } + /// Short description (body after frontmatter) for use in instruction tables. + [JsonPropertyName("description")] + public string? Description { get; set; } - /// Protocol version of the runtime that refused the request. - [JsonPropertyName("runtimeProtocolVersion")] - public required long RuntimeProtocolVersion { get; set; } + /// Unique identifier for this source (used for toggling). + [JsonPropertyName("id")] + public string Id { get; set; } = string.Empty; - /// Capabilities this runtime can safely advertise to this caller. The complete five-capability protocol-3 legacy set is always present; every capability added after that baseline appears only when the caller required it, so an older closed-enum decoder can still consume a refusal. This list does not imply that every deployment has enabled every operation. - [JsonPropertyName("supportedCapabilities")] - public required IList SupportedCapabilities { get; set; } + /// Human-readable label. + [JsonPropertyName("label")] + public string Label { get; set; } = string.Empty; - /// The subset of the caller's bounded extensible capability identifiers this runtime cannot honour. - [JsonPropertyName("unsupportedCapabilities")] - public required IList UnsupportedCapabilities { get; set; } -} + /// Where this source lives — used for UI grouping. + [JsonPropertyName("location")] + public InstructionSourceLocation Location { get; set; } -/// The request was rejected because a bounded field fell outside its permitted range or a required field was unusable. Pagination may also be rejected by the authority after a continuation request; repeat the search without page. -/// The invalid-request variant of . -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class CatalogSelectionResultInvalidRequest : CatalogSelectionResult -{ - /// - [JsonIgnore] - public override string Kind => "invalid-request"; + /// 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; } - /// Which request field was rejected. - [JsonPropertyName("field")] - public required CatalogInvalidRequestField Field { get; set; } + /// File path relative to repo or absolute for home. + [JsonPropertyName("sourcePath")] + public string SourcePath { 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; } + /// Category of instruction source — used for merge logic. + [JsonPropertyName("type")] + public InstructionSourceType Type { get; set; } } -/// The operation is not available on this runtime. Distinct from a network failure: nothing was attempted. -/// The unavailable variant of . +/// Instruction sources discovered across user, repository, and plugin sources. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class CatalogSelectionResultUnavailable : CatalogSelectionResult +public sealed class ServerInstructionSourceList { - /// - [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; } + /// All discovered instruction sources. + [JsonPropertyName("sources")] + public IList Sources { get => field ??= []; set; } } -/// Terminates one retained catalog selection group through an opaque reference previously returned by the model-safe search projection. +/// Optional project paths to include in instruction discovery. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class CatalogSelectionRequest +public sealed class InstructionsDiscoverRequest { - /// Protocol version and capabilities the caller requires. - [JsonPropertyName("contract")] - public CatalogClientContract Contract { get => field ??= new(); set; } - - /// The terminal outcome declared by the caller. Timed-out means the host's live interaction deadline elapsed; a reference whose runtime TTL elapsed is rejected separately as stale. - [JsonPropertyName("outcome")] - public CatalogSelectionDecision Outcome { get; set; } + /// 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; } - /// Opaque runtime-instance scoped reference to one visible candidate. For a non-selected outcome, any candidate reference from the same search closes that search's retained group. - [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("selectionRef")] - public string SelectionRef { get; set; } = string.Empty; + /// Explicit discovery-only instruction roots using existing SDK instruction formats, not filesystem grants. Available without ambient discovery, subject to instruction/managed disables. Relative paths resolve against the request's effective working directory when supplied. Null and omission are equivalent; an empty array contributes no explicit roots. + [JsonPropertyName("instructionDirectories")] + public IList? InstructionDirectories { get; set; } - /// Locally owned root session whose retained search state is being resolved. - [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("sessionId")] - public string SessionId { 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; } } -/// Information about an installed plugin tracked in global state. +/// Explicit discovery arguments for one location, supplied by the caller rather than derived from a session. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class InstalledPluginInfo +internal sealed class InstructionsDiscoverRepoSourcesRequest { - /// 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; } - - /// Whether the managed desired plugin currently has an installed or live record. Set to false for a managed desired entry retained in the listing after installation or reconciliation failed. - [JsonPropertyName("installed")] - public bool? Installed { 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; } - - /// Whether enterprise managed settings control this plugin's enabled state. - [JsonPropertyName("managed")] - public bool? Managed { get; set; } - - /// The enabled state required by enterprise managed settings, when this plugin spec is managed. - [JsonPropertyName("managedDesiredEnabled")] - public bool? ManagedDesiredEnabled { get; set; } + /// Extra directories to scan alongside instruction directories configured in the runtime environment. + [JsonPropertyName("additionalDirs")] + public IList? AdditionalDirs { get; set; } - /// Marketplace the plugin came from. Empty string ("") for direct repo / URL / local installs. - [JsonPropertyName("marketplace")] - public string Marketplace { get; set; } = string.Empty; + /// Copilot configuration directory for user-level sources. An omitted or empty value uses the server's configuration home. + [JsonPropertyName("configDir")] + public string? ConfigDir { get; set; } - /// Plugin name. - [JsonPropertyName("name")] - public string Name { get; set; } = string.Empty; + /// Working directory whose instruction sources should also be included. Omission adds no separate working-directory scope. + [JsonPropertyName("cwdPath")] + public string? CwdPath { get; set; } - /// Runtime-reported plugin provenance. Currently set to "builtin" only for plugins registered through the trusted host built-in boundary; absent for installed, marketplace, direct, and live plugins. - [JsonPropertyName("source")] - public string? Source { get; set; } + /// When true, also discover instruction files nested below the location. + [JsonPropertyName("enableChildInstructions")] + public bool? EnableChildInstructions { get; set; } - /// Installed version (when reported by the plugin manifest). - [JsonPropertyName("version")] - public string? Version { get; set; } -} + /// When true, include the sources a command-line host reads. Hosts that are not the CLI omit it. + [JsonPropertyName("isCli")] + public bool? IsCli { get; set; } -/// Plugins installed in user/global state. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class PluginListResult -{ - /// Installed plugins. - [JsonPropertyName("plugins")] - public IList Plugins { get => field ??= []; set; } + /// Repository location to discover. An empty string selects no repository scope; cwdPath can still supply working-directory sources. + [JsonPropertyName("location")] + public string Location { get; set; } = string.Empty; } -/// Result of installing a plugin. +/// Canonical file or directory where custom instructions can be discovered or created, with location, kind, preference, and project path. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class PluginInstallResult +public sealed class InstructionDiscoveryPath { - /// 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; } + /// Whether the target is a single file or a directory of instruction files. + [JsonPropertyName("kind")] + public InstructionDiscoveryPathKind Kind { get; set; } - /// The newly installed plugin's metadata. - [JsonPropertyName("plugin")] - public InstalledPluginInfo Plugin { get => field ??= new(); set; } + /// Which tier this target belongs to. + [JsonPropertyName("location")] + public InstructionDiscoveryPathLocation Location { get; set; } - /// Optional post-install message provided by the plugin (e.g. setup instructions). - [JsonPropertyName("postInstallMessage")] - public string? PostInstallMessage { get; set; } + /// Absolute path of the file or directory (may not exist on disk yet). + [JsonPropertyName("path")] + public string Path { get; set; } = string.Empty; - /// Number of skills discovered and installed from the plugin. - [JsonPropertyName("skillsInstalled")] - public long SkillsInstalled { 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; } - /// Where the completed plugin tree was staged before atomic promotion. - [JsonPropertyName("stagingMode")] - public PluginInstallStagingMode? StagingMode { get; set; } + /// The input project path this target was derived from (only for repository targets). + [JsonPropertyName("projectPath")] + public string? ProjectPath { get; set; } } -/// Plugin source and optional working directory for relative-path resolution. +/// Canonical files and directories where custom instructions can be created so the runtime will recognize them. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class PluginsInstallRequest +public sealed class InstructionDiscoveryPathList { - /// 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; - - /// 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; } + /// Canonical instruction create/discovery files and directories, in priority order. + [JsonPropertyName("paths")] + public IList Paths { get => field ??= []; set; } } -/// Name (or spec) of the plugin to uninstall. +/// Optional project paths to include when enumerating instruction discovery targets. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class PluginsUninstallRequest +public sealed class InstructionsGetDiscoveryPathsRequest { - /// 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; } + /// 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; } - /// Plugin name or "plugin@marketplace" spec to uninstall. When ambiguous, prefer the fully-qualified spec. - [JsonPropertyName("name")] - public string Name { get; set; } = string.Empty; + /// Explicit discovery-only instruction roots to enumerate, including configured missing directories. Relative paths resolve against the request's effective working directory when supplied. These are not filesystem grants or preferred creation targets. Null and omission are equivalent. + [JsonPropertyName("instructionDirectories")] + public IList? InstructionDirectories { 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; } } -/// Result of updating a single plugin. +/// Skill installation management result with the honoured contract, or a typed refusal. +/// Polymorphic base type discriminated by kind. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class PluginUpdateResult +[JsonPolymorphic( + TypeDiscriminatorPropertyName = "kind", + UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] +[JsonDerivedType(typeof(SkillInstallationManagementResultOutcome), "outcome")] +[JsonDerivedType(typeof(SkillInstallationManagementResultNegotiationRefused), "negotiation-refused")] +[JsonDerivedType(typeof(SkillInstallationManagementResultInvalidRequest), "invalid-request")] +public partial class SkillInstallationManagementResult { - /// Version after the update, when reported by the plugin manifest. - [JsonPropertyName("newVersion")] - public string? NewVersion { get; set; } + /// The type discriminator. + [JsonPropertyName("kind")] + public virtual string Kind { get; set; } = string.Empty; +} - /// Version that was previously installed, when available. - [JsonPropertyName("previousVersion")] - public string? PreviousVersion { get; set; } - /// Number of skills discovered and installed after the update. - [JsonPropertyName("skillsInstalled")] - public long SkillsInstalled { get; set; } +/// Management outcome for verified Skill inventory, planning and removal. +/// Polymorphic base type discriminated by kind. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +[JsonPolymorphic( + TypeDiscriminatorPropertyName = "kind", + UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] +[JsonDerivedType(typeof(SkillInstallationManagementOutcomeRecoveryRequired), "recovery-required")] +[JsonDerivedType(typeof(SkillInstallationManagementOutcomeInstallPlanned), "install-planned")] +[JsonDerivedType(typeof(SkillInstallationManagementOutcomeListed), "listed")] +[JsonDerivedType(typeof(SkillInstallationManagementOutcomeRecovered), "recovered")] +[JsonDerivedType(typeof(SkillInstallationManagementOutcomeRolledBack), "rolled-back")] +[JsonDerivedType(typeof(SkillInstallationManagementOutcomeUninstallPlanned), "uninstall-planned")] +[JsonDerivedType(typeof(SkillInstallationManagementOutcomeOperation), "operation")] +[JsonDerivedType(typeof(SkillInstallationManagementOutcomeEnabledChanged), "enabled-changed")] +[JsonDerivedType(typeof(SkillInstallationManagementOutcomeRefused), "refused")] +public partial class SkillInstallationManagementOutcome +{ + /// The type discriminator. + [JsonPropertyName("kind")] + public virtual string Kind { get; set; } = string.Empty; } -/// Name (or spec) of the plugin to update. + +/// Already-confirmed durable work must be reconciled before new mutations or inventory. +/// The recovery-required variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class PluginsUpdateRequest +public partial class SkillInstallationManagementOutcomeRecoveryRequired : SkillInstallationManagementOutcome { - /// Plugin name or "plugin@marketplace" spec to update. - [JsonPropertyName("name")] - public string Name { get; set; } = string.Empty; + /// + [JsonIgnore] + public override string Kind => "recovery-required"; } -/// Per-plugin result from updating all plugins, with versions, skills installed, success flag, and optional error. +/// Safe verified Skill review fields. No raw credential, candidate handle or plan handle. +/// Polymorphic base type discriminated by action. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class PluginUpdateAllEntry +[JsonPolymorphic( + TypeDiscriminatorPropertyName = "action", + UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] +[JsonDerivedType(typeof(SkillInstallationReviewInstall), "install")] +[JsonDerivedType(typeof(SkillInstallationReviewUninstall), "uninstall")] +public partial class SkillInstallationReview { - /// Error message (failure only). - [JsonPropertyName("error")] - public string? Error { get; set; } + /// The type discriminator. + [JsonPropertyName("action")] + public virtual string Action { get; set; } = string.Empty; +} - /// Marketplace the plugin came from. Empty string ("") for direct installs. - [JsonPropertyName("marketplace")] - public string Marketplace { get; set; } = string.Empty; - /// Plugin name that was updated. - [JsonPropertyName("name")] - public string Name { get; set; } = string.Empty; +/// One reviewed Skill file. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public sealed class SkillInstallationFileReview +{ + /// SHA-256 digest of the exact file bytes. + [JsonPropertyName("digest")] + public string Digest { get; set; } = string.Empty; - /// Version after the update, when available. - [JsonPropertyName("newVersion")] - public string? NewVersion { get; set; } + /// Whether the file is installed with executable permissions. + [JsonPropertyName("executable")] + public bool Executable { get; set; } - /// Previously installed version, when available. - [JsonPropertyName("previousVersion")] - public string? PreviousVersion { get; set; } + /// Declared media type for the file. + [JsonPropertyName("mediaType")] + public string MediaType { get; set; } = string.Empty; - /// Number of skills installed after the update (success only). - [JsonPropertyName("skillsInstalled")] - public long? SkillsInstalled { get; set; } + /// Relative file path within the Skill root. + [JsonPropertyName("path")] + public string Path { get; set; } = string.Empty; - /// Whether the update succeeded for this plugin. - [JsonPropertyName("success")] - public bool Success { get; set; } + /// Exact reviewed file size in bytes. + [JsonPropertyName("sizeBytes")] + public long SizeBytes { get; set; } } -/// Result of updating all installed plugins. +/// Source identity retained from Agent Finder and the pinned GitHub descriptor. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class PluginUpdateAllResult +public sealed class SkillInstallationSource { - /// Per-plugin update results in deterministic order. - [JsonPropertyName("results")] - public IList Results { get => field ??= []; set; } -} + /// Digest of the descriptor's bundle manifest. + [JsonPropertyName("bundleDigest")] + public string BundleDigest { get; set; } = string.Empty; -/// Plugin names (or specs) to enable, plus the optional working directory the repository-controlled guard is evaluated against. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class 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. - [JsonPropertyName("names")] - public IList Names { get => field ??= []; set; } + /// Agent Finder materialisation revision identifier. + [JsonPropertyName("catalogRevisionId")] + public string CatalogRevisionId { get; set; } = string.Empty; - /// Working directory whose repository `enabledPlugins` overlay decides whether this mutation is repository-controlled. Hosts that serve sessions across several repositories (the SDK server) should pass the session's directory; otherwise the guard is evaluated against the server process's own working directory, which may belong to a different repository. Defaults to the server's current working directory. - [JsonPropertyName("workingDirectory")] - public string? WorkingDirectory { get; set; } -} + /// Digest of the canonical materialisation descriptor. + [JsonPropertyName("descriptorDigest")] + public string DescriptorDigest { get; set; } = string.Empty; -/// Plugin names (or specs) to disable, plus the optional working directory the repository-controlled guard is evaluated against. -[Experimental(global::GitHub.Copilot.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; } + /// Repository full name, for example owner/name. + [JsonPropertyName("repository")] + public string Repository { get; set; } = string.Empty; - /// Working directory whose repository `enabledPlugins` overlay decides whether this mutation is repository-controlled. Hosts that serve sessions across several repositories (the SDK server) should pass the session's directory; otherwise the guard is evaluated against the server process's own working directory, which may belong to a different repository. Defaults to the server's current working directory. - [JsonPropertyName("workingDirectory")] - public string? WorkingDirectory { get; set; } + /// GitHub repository database identifier. + [JsonPropertyName("repositoryId")] + public string RepositoryId { get; set; } = string.Empty; + + /// Agent Finder resource identifier. + [JsonPropertyName("resourceId")] + public string ResourceId { get; set; } = string.Empty; + + /// Pinned Git commit revision. + [JsonPropertyName("revision")] + public string Revision { get; set; } = string.Empty; + + /// Root path within the pinned repository. + [JsonPropertyName("root")] + public string Root { get; set; } = string.Empty; } -/// Trusted built-in plugin directories to use for this runtime process. +/// A user-facing personal Skill installation location without absolute host paths. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class PluginsBuiltinSetRequest +public sealed class SkillInstallationLocation { - /// 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; } + /// Diagnostics-only absolute host path. Hosts must not display it by default. + [JsonPropertyName("diagnosticsAbsolutePath")] + public string? DiagnosticsAbsolutePath { get; set; } + + /// Safe display label, for example ~/.copilot/skills/run-checks. + [JsonPropertyName("displayLabel")] + public string DisplayLabel { get; set; } = string.Empty; + + /// Path relative to the Copilot home. + [JsonPropertyName("relativePath")] + public string RelativePath { get; set; } = string.Empty; + + /// Installation scope. Agent Finder Skills are installed in the user's personal Copilot home. + [JsonPropertyName("scope")] + public SkillInstallationScope Scope { get; set; } } -/// Registered marketplace summary. +/// Review for installing a verified Skill. +/// The install variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class MarketplaceInfo +public partial class SkillInstallationReviewInstall : SkillInstallationReview { - /// Whether the managed marketplace currently resolved into the runtime marketplace registry. Set to false when the desired managed entry is retained for governance visibility after loading or reconciliation failed. - [JsonPropertyName("available")] - public bool? Available { get; set; } + /// + [JsonIgnore] + public override string Action => "install"; - /// True when this is a default marketplace shipped with the runtime. Defaults are not removable. - [JsonPropertyName("isDefault")] - public bool? IsDefault { get; set; } + /// Catalogue identity retained from the bound candidate before consent. + [JsonPropertyName("catalogue")] + public required InstallationCatalogueIdentity Catalogue { get; set; } - /// Whether enterprise managed settings provide and control this marketplace entry. - [JsonPropertyName("managed")] - public bool? Managed { get; set; } + /// Skill description from SKILL.md when present. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("description")] + public string? Description { get; set; } - /// Marketplace name (matches the @marketplace suffix in plugin specs). + /// + /// Complete verified SKILL.md content. Planning refuses with review-too-large + /// when this exceeds 262144 UTF-8 bytes; it is never truncated. + /// + [JsonPropertyName("entrypointContent")] + public required string EntrypointContent { get; set; } + + /// Relative path of the verified Skill entrypoint. + [JsonPropertyName("entrypointPath")] + public required string EntrypointPath { get; set; } + + /// Reviewed files and digests. + [JsonPropertyName("files")] + public required IList Files { get; set; } + + /// Installing never grants immediate use; the Skill is written disabled. + [JsonPropertyName("installsDisabled")] + public required bool InstallsDisabled { get; set; } + + /// Skill invocation name from SKILL.md. [JsonPropertyName("name")] - public string Name { get; set; } = string.Empty; + public required string Name { get; set; } - /// Human-readable description of where the marketplace data is fetched from (e.g. "GitHub: owner/repo"). + /// Exact verified source identity. [JsonPropertyName("source")] - public string Source { get; set; } = string.Empty; -} + public required SkillInstallationSource Source { get; set; } -/// All registered marketplaces, including built-in defaults. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class MarketplaceListResult -{ - /// Registered marketplaces. - [JsonPropertyName("marketplaces")] - public IList Marketplaces { get => field ??= []; set; } + /// Exact user-scope target location without an absolute host path. + [JsonPropertyName("target")] + public required SkillInstallationLocation Target { get; set; } + + /// Total reviewed payload size in bytes. + [JsonPropertyName("totalBytes")] + public required long TotalBytes { get; set; } } -/// Result of registering a new marketplace. +/// Durable verified Skill ownership summary. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class MarketplaceAddResult +public sealed class SkillInstallationSummary { - /// Final name of the marketplace as resolved from its manifest. + /// Catalogue identity retained at install time. + [JsonPropertyName("catalogue")] + public InstallationCatalogueIdentity Catalogue { get => field ??= new(); set; } + + /// Persisted enablement requested for this installation. + [JsonPropertyName("configuredEnabled")] + public bool ConfiguredEnabled { get; set; } + + /// Exact durable installation receipt identity. + [JsonPropertyName("installationId")] + public string InstallationId { get; set; } = string.Empty; + + /// ISO 8601 wall-clock installation time. + [JsonPropertyName("installedAt")] + public string InstalledAt { get; set; } = string.Empty; + + /// Skill invocation name. [JsonPropertyName("name")] public string Name { get; set; } = string.Empty; + + /// Operation that installed this Skill. + [JsonPropertyName("operationId")] + public string OperationId { get; set; } = string.Empty; + + /// Ownership state observed from files and receipts. + [JsonPropertyName("ownershipState")] + public SkillInstallationOwnershipState OwnershipState { get; set; } + + /// Bound-session load observation. + [JsonPropertyName("sessionState")] + public SkillInstallationSessionState SessionState { get; set; } + + /// Exact retained verified source identity. + [JsonPropertyName("source")] + public SkillInstallationSource Source { get => field ??= new(); set; } + + /// User-facing installation location without an absolute host path. + [JsonPropertyName("target")] + public SkillInstallationLocation Target { get => field ??= new(); set; } } -/// Marketplace source and optional working directory for relative-path resolution. +/// Review for uninstalling an owned verified Skill. +/// The uninstall variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class PluginsMarketplacesAddRequest +public partial class SkillInstallationReviewUninstall : SkillInstallationReview { - /// 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 Action => "uninstall"; - /// 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; } + /// Catalogue identity retained at install time. + [JsonPropertyName("catalogue")] + public required InstallationCatalogueIdentity Catalogue { get; set; } + + /// Files recorded by the installation receipt. + [JsonPropertyName("files")] + public required IList Files { get; set; } + + /// Whether current files differ from the receipt. Apply refuses drift. + [JsonPropertyName("filesModified")] + public required bool FilesModified { get; set; } + + /// Owned installation being removed. + [JsonPropertyName("installation")] + public required SkillInstallationSummary Installation { get; set; } + + /// Total receipt-owned payload size in bytes. + [JsonPropertyName("totalBytes")] + public required long TotalBytes { get; set; } } -/// Outcome of the remove attempt, including dependent-plugin info when applicable. +/// A computed Skill install plan. Nothing has been applied. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class MarketplaceRemoveResult +public sealed class SkillInstallPlan { - /// Names of installed plugins that prevented removal. Populated only when `removed=false`. - [JsonPropertyName("dependentPlugins")] - public IList? DependentPlugins { get; set; } + /// Original wall-clock expiry as an ISO 8601 timestamp. + [JsonPropertyName("expiresAt")] + public string ExpiresAt { get; set; } = string.Empty; - /// 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; } + /// Original operation identifier returned before confirmation. + [JsonPropertyName("operationId")] + public string OperationId { get; set; } = string.Empty; + + /// One-use plan handle, bound to the original candidate authority. + [JsonPropertyName("planHandle")] + public string PlanHandle { get; set; } = string.Empty; + + /// Safe review to present before applying the plan. + [JsonPropertyName("review")] + public SkillInstallationReview Review { get => field ??= new(); set; } } -/// Name of the marketplace to remove and an optional force flag. +/// A verified Skill installation plan was prepared. +/// The install-planned variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class PluginsMarketplacesRemoveRequest +public partial class SkillInstallationManagementOutcomeInstallPlanned : SkillInstallationManagementOutcome { - /// 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 => "install-planned"; - /// Marketplace name to remove. - [JsonPropertyName("name")] - public string Name { get; set; } = string.Empty; + /// Prepared install plan. + [JsonPropertyName("plan")] + public required SkillInstallPlan Plan { get; set; } } -/// Plugin entry advertised by a marketplace. +/// Owned Skill installations were listed. +/// The listed variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class MarketplacePluginInfo +public partial class SkillInstallationManagementOutcomeListed : SkillInstallationManagementOutcome { - /// Short description from the marketplace catalog, when present. - [JsonPropertyName("description")] - public string? Description { get; set; } + /// + [JsonIgnore] + public override string Kind => "listed"; - /// Plugin name as listed in the marketplace catalog. - [JsonPropertyName("name")] - public string Name { get; set; } = string.Empty; + /// Owned Skill installation summaries. + [JsonPropertyName("installations")] + public required IList Installations { get; set; } } -/// Plugins advertised by the marketplace. +/// Recovery completed and inventory was inspected. +/// The recovered variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class MarketplaceBrowseResult +public partial class SkillInstallationManagementOutcomeRecovered : SkillInstallationManagementOutcome { - /// Plugins advertised by the marketplace. - [JsonPropertyName("plugins")] - public IList Plugins { get => field ??= []; set; } + /// + [JsonIgnore] + public override string Kind => "recovered"; + + /// Owned Skill installation summaries after recovery. + [JsonPropertyName("installations")] + public required IList Installations { get; set; } } -/// Name of the marketplace whose plugin catalog to fetch. +/// Interrupted work was safely compensated and the pending marker was cleared. +/// The rolled-back variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class PluginsMarketplacesBrowseRequest +public partial class SkillInstallationManagementOutcomeRolledBack : SkillInstallationManagementOutcome { - /// Marketplace name to browse. - [JsonPropertyName("name")] - public string Name { get; set; } = string.Empty; + /// + [JsonIgnore] + public override string Kind => "rolled-back"; + + /// Original operation identity. + [JsonPropertyName("operation_id")] + public required string OperationId { get; set; } + + /// Cause of the compensation. + [JsonPropertyName("reason")] + public required SkillInstallationFailureReason Reason { get; set; } } -/// Per-marketplace refresh result, including marketplace name, success flag, and optional failure error. +/// A computed Skill uninstall plan. Nothing has been removed. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class MarketplaceRefreshEntry +public sealed class SkillUninstallPlan { - /// Error message (failure only). - [JsonPropertyName("error")] - public string? Error { get; set; } + /// Original wall-clock expiry as an ISO 8601 timestamp. + [JsonPropertyName("expiresAt")] + public string ExpiresAt { get; set; } = string.Empty; - /// Marketplace name that was refreshed. - [JsonPropertyName("name")] - public string Name { get; set; } = string.Empty; + /// Owned Skill installation being removed. + [JsonPropertyName("installation")] + public SkillInstallationSummary Installation { get => field ??= new(); set; } - /// Whether the refresh succeeded. - [JsonPropertyName("success")] - public bool Success { get; set; } + /// Original removal operation identifier returned before confirmation. + [JsonPropertyName("operationId")] + public string OperationId { get; set; } = string.Empty; + + /// One-use uninstall plan handle. + [JsonPropertyName("planHandle")] + public string PlanHandle { get; set; } = string.Empty; + + /// Safe review to present before applying removal. + [JsonPropertyName("review")] + public SkillInstallationReview Review { get => field ??= new(); set; } } -/// Result of refreshing one or more marketplace catalogs. +/// An owned Skill uninstall plan was prepared. +/// The uninstall-planned variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class MarketplaceRefreshResult +public partial class SkillInstallationManagementOutcomeUninstallPlanned : SkillInstallationManagementOutcome { - /// Per-marketplace refresh results in deterministic order. - [JsonPropertyName("results")] - public IList Results { get => field ??= []; set; } + /// + [JsonIgnore] + public override string Kind => "uninstall-planned"; + + /// Prepared uninstall plan. + [JsonPropertyName("plan")] + public required SkillUninstallPlan Plan { get; set; } } -/// RPC data type for PluginsMarketplacesRefresh operations. +/// Status snapshot from the original connection, independent of new-work account availability. +/// Polymorphic base type discriminated by phase. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class PluginsMarketplacesRefreshRequest +[JsonPolymorphic( + TypeDiscriminatorPropertyName = "phase", + UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] +[JsonDerivedType(typeof(SkillInstallationOperationStatusPreparing), "preparing")] +[JsonDerivedType(typeof(SkillInstallationOperationStatusPrepared), "prepared")] +[JsonDerivedType(typeof(SkillInstallationOperationStatusAwaitingConfirmation), "awaiting-confirmation")] +[JsonDerivedType(typeof(SkillInstallationOperationStatusRevalidating), "revalidating")] +[JsonDerivedType(typeof(SkillInstallationOperationStatusApplying), "applying")] +[JsonDerivedType(typeof(SkillInstallationOperationStatusCompleted), "completed")] +public partial class SkillInstallationOperationStatus { - /// Marketplace name to refresh. When omitted, every registered marketplace is refreshed. - [JsonPropertyName("name")] - public string? Name { get; set; } + /// The type discriminator. + [JsonPropertyName("phase")] + public virtual string Phase { get; set; } = string.Empty; } -/// Agent metadata, including identifiers, display details, source, tools, model, models, reasoning effort, MCP servers, skills, and file path. + +/// Original operation progress discriminator. +/// The preparing variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class AgentInfo +public partial class SkillInstallationOperationStatusPreparing : SkillInstallationOperationStatus { - /// Description of the agent's purpose. - [JsonPropertyName("description")] - public string Description { get; set; } = string.Empty; - - /// Whether model-driven invocation is disabled for this agent. - [JsonPropertyName("disableModelInvocation")] - public bool? DisableModelInvocation { get; set; } + /// + [JsonIgnore] + public override string Phase => "preparing"; - /// Human-readable display name. - [JsonPropertyName("displayName")] - public string DisplayName { get; set; } = string.Empty; + /// Whether cancellation has been requested. + [JsonPropertyName("cancellationRequested")] + public required bool CancellationRequested { 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; + /// Original runtime-issued operation identity. + [JsonPropertyName("operationId")] + public required string OperationId { get; set; } +} - /// MCP server configurations attached to this agent, keyed by server name. Server config shape mirrors the MCP `mcpServers` schema. - [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] - [JsonPropertyName("mcpServers")] - public IDictionary? McpServers { 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; } - - /// Whether authored models are preferences or required constraints. - [JsonPropertyName("modelPolicy")] - public AgentModelPolicy? ModelPolicy { get; set; } - - /// 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; } = 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; } - - /// 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; } - - /// Authored reasoning effort for this agent. Applied on selection to models that support it; omitted means no authored preference. - [JsonPropertyName("reasoningEffort")] - public string? ReasoningEffort { 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; } +/// Original operation progress discriminator. +/// The prepared variant of . +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public partial class SkillInstallationOperationStatusPrepared : SkillInstallationOperationStatus +{ + /// + [JsonIgnore] + public override string Phase => "prepared"; - /// Allowed tool names for this agent. Empty array means none; omitted means inherit defaults. - [JsonPropertyName("tools")] - public IList? Tools { get; set; } + /// Whether cancellation has been requested. + [JsonPropertyName("cancellationRequested")] + public required bool CancellationRequested { 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; } + /// Original runtime-issued operation identity. + [JsonPropertyName("operationId")] + public required string OperationId { get; set; } } -/// Agents discovered across user, project, plugin, and remote sources. +/// Original operation progress discriminator. +/// The awaiting-confirmation variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class ServerAgentList +public partial class SkillInstallationOperationStatusAwaitingConfirmation : SkillInstallationOperationStatus { - /// All discovered agents across all sources. - [JsonPropertyName("agents")] - public IList Agents { get => field ??= []; set; } + /// + [JsonIgnore] + public override string Phase => "awaiting-confirmation"; + + /// Whether cancellation has been requested. + [JsonPropertyName("cancellationRequested")] + public required bool CancellationRequested { get; set; } + + /// Original runtime-issued operation identity. + [JsonPropertyName("operationId")] + public required string OperationId { get; set; } } -/// Optional project paths to include in agent discovery. +/// Original operation progress discriminator. +/// The revalidating variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class AgentsDiscoverRequest +public partial class SkillInstallationOperationStatusRevalidating : SkillInstallationOperationStatus { - /// 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; } + /// + [JsonIgnore] + public override string Phase => "revalidating"; - /// 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; } + /// Whether cancellation has been requested. + [JsonPropertyName("cancellationRequested")] + public required bool CancellationRequested { get; set; } + + /// Original runtime-issued operation identity. + [JsonPropertyName("operationId")] + public required string OperationId { get; set; } } -/// Canonical directory where custom agents can be discovered or created, with scope, preference, and optional project path. +/// Original operation progress discriminator. +/// The applying variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class AgentDiscoveryPath +public partial class SkillInstallationOperationStatusApplying : SkillInstallationOperationStatus { - /// Absolute path of the search/create directory (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 agent in its tier. At most one entry per tier is preferred. - [JsonPropertyName("preferredForCreation")] - public bool PreferredForCreation { get; set; } + /// + [JsonIgnore] + public override string Phase => "applying"; - /// The input project path this directory was derived from (only for project scope). - [JsonPropertyName("projectPath")] - public string? ProjectPath { get; set; } + /// Whether cancellation has been requested; already-started effects require recovery. + [JsonPropertyName("cancellationRequested")] + public required bool CancellationRequested { get; set; } - /// Which tier this directory belongs to. - [JsonPropertyName("scope")] - public AgentDiscoveryPathScope Scope { get; set; } + /// Original runtime-issued operation identity. + [JsonPropertyName("operationId")] + public required string OperationId { get; set; } } -/// Canonical locations where custom agents can be created so the runtime will recognize them. +/// Terminal verified Skill mutation result. +/// Polymorphic base type discriminated by kind. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class AgentDiscoveryPathList +[JsonPolymorphic( + TypeDiscriminatorPropertyName = "kind", + UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] +[JsonDerivedType(typeof(SkillInstallationOutcomeInstalled), "installed")] +[JsonDerivedType(typeof(SkillInstallationOutcomeUninstalled), "uninstalled")] +[JsonDerivedType(typeof(SkillInstallationOutcomeRolledBack), "rolled-back")] +[JsonDerivedType(typeof(SkillInstallationOutcomeRecoveryRequired), "recovery-required")] +[JsonDerivedType(typeof(SkillInstallationOutcomeDeclined), "declined")] +[JsonDerivedType(typeof(SkillInstallationOutcomeCancelled), "cancelled")] +[JsonDerivedType(typeof(SkillInstallationOutcomeRefused), "refused")] +public partial class SkillInstallationOutcome { - /// 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. + +/// The verified Skill was installed disabled. +/// The installed variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class AgentsGetDiscoveryPathsRequest +public partial class SkillInstallationOutcomeInstalled : SkillInstallationOutcome { - /// 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 => "installed"; - /// Optional list of project directory paths. When omitted or empty, only the user-level directory is returned. - [JsonPropertyName("projectPaths")] - public IList? ProjectPaths { get; set; } + /// Durable installed Skill summary. + [JsonPropertyName("installation")] + public required SkillInstallationSummary Installation { get; set; } } -/// The agents this runtime ships, named so a consumer can tell them apart from authored ones. +/// The owned Skill was removed. +/// The uninstalled variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class AgentsGetBuiltinsResult +public partial class SkillInstallationOutcomeUninstalled : SkillInstallationOutcome { - /// The subset of `names` a user is allowed to turn off. A shipped agent outside this list is always active and a client should not offer a toggle for it. - [JsonPropertyName("disableableNames")] - public IList DisableableNames { get => field ??= []; set; } + /// + [JsonIgnore] + public override string Kind => "uninstalled"; - /// Every agent name this runtime ships. - [JsonPropertyName("names")] - public IList Names { get => field ??= []; set; } + /// Removed installation identity. + [JsonPropertyName("installationId")] + public required string InstallationId { get; set; } - /// The subset of `names` defined by a shipped YAML definition. The remainder are special-cased in code and have no definition to load. - [JsonPropertyName("yamlBasedNames")] - public IList YamlBasedNames { get => field ??= []; set; } + /// Original removal operation identity. + [JsonPropertyName("operationId")] + public required string OperationId { get; set; } } -/// A shipped agent, named and described. +/// The durable transaction was aborted or fully compensated. +/// The rolled-back variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class BuiltinAgentSummary +public partial class SkillInstallationOutcomeRolledBack : SkillInstallationOutcome { - /// One-line description of what the agent does. - [JsonPropertyName("description")] - public string Description { get; set; } = string.Empty; + /// + [JsonIgnore] + public override string Kind => "rolled-back"; - /// The agent name, as it appears in `getBuiltins`. - [JsonPropertyName("name")] - public string Name { get; set; } = string.Empty; + /// Original operation identity. + [JsonPropertyName("operationId")] + public required string OperationId { get; set; } + + /// Cause of the fully aborted or compensated operation. + [JsonPropertyName("reason")] + public required SkillInstallationFailureReason Reason { get; set; } } -/// The shipped agents available under the requested flags. +/// A write may have completed. Recover and inspect durable state before retrying. +/// The recovery-required variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class AgentsGetAvailableBuiltinsResult +public partial class SkillInstallationOutcomeRecoveryRequired : SkillInstallationOutcome { - /// Available shipped agents, in the runtime's own order. - [JsonPropertyName("agents")] - public IList Agents { get => field ??= []; set; } + /// + [JsonIgnore] + public override string Kind => "recovery-required"; + + /// Operation whose durable result must be recovered and inspected. + [JsonPropertyName("operationId")] + public required string OperationId { get; set; } } -/// The feature flags to evaluate shipped agents against. +/// The user declined the confirmation request. +/// The declined variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class AgentsGetAvailableBuiltinsRequest +public partial class SkillInstallationOutcomeDeclined : SkillInstallationOutcome { - /// The surface asking, which gates agents that only apply to one client. Omit or pass null to apply no client filter. - [JsonPropertyName("context")] - public string? Context { get; set; } - - /// Feature flag values keyed by name, evaluated with the runtime's truthiness rules. Omit or pass null for no flags. - [JsonPropertyName("featureFlags")] - public IDictionary? FeatureFlags { get; set; } + /// + [JsonIgnore] + public override string Kind => "declined"; - /// Flag overrides keyed by name. A null entry uses the corresponding base flag; false explicitly disables it. Omit or pass null for no overrides. - [JsonPropertyName("overrides")] - public IDictionary? Overrides { get; set; } + /// Original operation identity. + [JsonPropertyName("operationId")] + public required string OperationId { get; set; } } -/// One shipped agent's definition. +/// The operation was cancelled before a terminal mutation. +/// The cancelled variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class AgentsGetBuiltinDefinitionResult +public partial class SkillInstallationOutcomeCancelled : SkillInstallationOutcome { - /// The agent's definition, serialized as JSON. It carries the authored keys plus the runtime's projected `__nativeCustomAgent` view of the same agent. It is a string rather than an object because the runtime parses it with the agent schema's tolerant shape, which accepts keys this contract does not name. - [JsonPropertyName("definitionJson")] - public string DefinitionJson { get; set; } = string.Empty; + /// + [JsonIgnore] + public override string Kind => "cancelled"; + + /// Original operation identity. + [JsonPropertyName("operationId")] + public required string OperationId { get; set; } } -/// The shipped agent whose definition to load. +/// The operation was refused without applying changes. +/// The refused variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class AgentsGetBuiltinDefinitionRequest +public partial class SkillInstallationOutcomeRefused : SkillInstallationOutcome { - /// The agent name, which must be one of `getBuiltins`'s `yamlBasedNames`. A name outside that list is special-cased in code and has no definition, and is reported as an error rather than as an empty definition. - [JsonPropertyName("name")] - public string Name { get; set; } = string.Empty; + /// + [JsonIgnore] + public override string Kind => "refused"; + + /// Present once an operation has been allocated. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("operationId")] + public string? OperationId { get; set; } + + /// Bounded refusal reason. + [JsonPropertyName("reason")] + public required SkillInstallationFailureReason Reason { get; set; } } -/// One shipped agent, projected for a listing. +/// Original operation progress discriminator. +/// The completed variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class AgentsGetBuiltinListingDefinitionResult +public partial class SkillInstallationOperationStatusCompleted : SkillInstallationOperationStatus { - /// The agent projected as a custom agent, serialized as JSON. It is a string rather than an object for the same reason as `getBuiltinDefinition`: the runtime parses the underlying definition with the agent schema's tolerant shape, which accepts keys this contract does not name. - [JsonPropertyName("definitionJson")] - public string DefinitionJson { get; set; } = string.Empty; + /// + [JsonIgnore] + public override string Phase => "completed"; + + /// Whether cancellation was requested before the terminal result. + [JsonPropertyName("cancellationRequested")] + public required bool CancellationRequested { get; set; } + + /// Original runtime-issued operation identity. + [JsonPropertyName("operationId")] + public required string OperationId { get; set; } + + /// Immutable terminal receipt. + [JsonPropertyName("outcome")] + public required SkillInstallationOutcome Outcome { get; set; } } -/// The shipped agent whose listing entry to load. +/// Original-connection operation snapshot. +/// The operation variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class AgentsGetBuiltinListingDefinitionRequest +public partial class SkillInstallationManagementOutcomeOperation : SkillInstallationManagementOutcome { - /// The agent name, taken from `getAvailableBuiltins`. Unlike `getBuiltinDefinition`, the agent that `getBuiltins` reports as special-cased rather than YAML-based is answered here too, from its in-code definition. - [JsonPropertyName("name")] - public string Name { get; set; } = string.Empty; + /// + [JsonIgnore] + public override string Kind => "operation"; + + /// Operation status. + [JsonPropertyName("operation")] + public required SkillInstallationOperationStatus Operation { get; set; } } -/// The model to switch to, and the warning to show when the agent's preference could not be met. +/// Enablement changed and the selected session was reconciled. +/// The enabled-changed variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class AgentsCustomAgentInitialModelDecisionResult +public partial class SkillInstallationManagementOutcomeEnabledChanged : SkillInstallationManagementOutcome { - /// The reasoning effort attached to the selected model preference. Absent when that preference does not specify an effort. - [JsonPropertyName("reasoningEffort")] - public string? ReasoningEffort { get; set; } + /// + [JsonIgnore] + public override string Kind => "enabled-changed"; - /// The first available model that matches the agent's preferences. Absent when none of the requested models is available. - [JsonPropertyName("targetModel")] - public string? TargetModel { get; set; } + /// Safe reload or reconciliation diagnostics. + [JsonPropertyName("diagnostics")] + public required IList Diagnostics { get; set; } - /// What to tell the user about an unmet preference. Absent when the preference was met. A warning with no `targetModel` means the agent's models are all unavailable. - [JsonPropertyName("warning")] - public string? Warning { get; set; } + /// Updated installation summary. + [JsonPropertyName("installation")] + public required SkillInstallationSummary Installation { get; set; } } -/// The models a custom agent asks for, and the models actually available. +/// The management request was refused. +/// The refused variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class AgentsCustomAgentInitialModelDecisionParams +public partial class SkillInstallationManagementOutcomeRefused : SkillInstallationManagementOutcome { - /// The agent's declared `model:` entry, serialized. A single name or an ordered list of acceptable names. - [JsonPropertyName("agentModelsJson")] - public string AgentModelsJson { get; set; } = string.Empty; + /// + [JsonIgnore] + public override string Kind => "refused"; - /// The models available to this session, serialized in the shape the model list carries. - [JsonPropertyName("availableModelsJson")] - public string AvailableModelsJson { get; set; } = string.Empty; + /// Bounded refusal reason. + [JsonPropertyName("reason")] + public required SkillInstallationFailureReason Reason { get; set; } } -/// Loaded instruction source for a session, including path, content, category, location, applicability, and optional description. +/// The outcome variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class InstructionSource +public partial class SkillInstallationManagementResultOutcome : SkillInstallationManagementResult { - /// Glob pattern(s) from frontmatter — when set, this instruction applies only to matching files. - [JsonPropertyName("applyTo")] - public IList? ApplyTo { get; set; } + /// + [JsonIgnore] + public override string Kind => "outcome"; - /// Raw content of the instruction file. - [JsonPropertyName("content")] - public string Content { get; set; } = string.Empty; + /// Capabilities honoured for this request. + [JsonPropertyName("negotiated")] + public required CatalogNegotiatedContract Negotiated { get; set; } - /// When true, this source starts disabled and must be toggled on by the user. - [JsonPropertyName("defaultDisabled")] - public bool? DefaultDisabled { get; set; } + /// Observed management outcome. + [JsonPropertyName("outcome")] + public required SkillInstallationManagementOutcome Outcome { get; set; } +} - /// Short description (body after frontmatter) for use in instruction tables. - [JsonPropertyName("description")] - public string? Description { get; set; } +/// 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(global::GitHub.Copilot.Diagnostics.Experimental)] +public partial class SkillInstallationManagementResultNegotiationRefused : SkillInstallationManagementResult +{ + /// + [JsonIgnore] + public override string Kind => "negotiation-refused"; - /// Unique identifier for this source (used for toggling). - [JsonPropertyName("id")] - public string Id { 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; } - /// Human-readable label. - [JsonPropertyName("label")] - public string Label { get; set; } = string.Empty; + /// Lowest caller protocol version this runtime will serve. + [JsonPropertyName("minimumSupportedProtocolVersion")] + public required long MinimumSupportedProtocolVersion { get; set; } - /// Where this source lives — used for UI grouping. - [JsonPropertyName("location")] - public InstructionSourceLocation Location { get; set; } + /// Whether the version or the capability set was the problem. + [JsonPropertyName("reason")] + public required CatalogNegotiationRefusedReason Reason { 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; } + /// Protocol version of the runtime that refused the request. + [JsonPropertyName("runtimeProtocolVersion")] + public required long RuntimeProtocolVersion { get; set; } - /// File path relative to repo or absolute for home. - [JsonPropertyName("sourcePath")] - public string SourcePath { get; set; } = string.Empty; + /// Capabilities this runtime can safely advertise to this caller. The complete five-capability protocol-3 legacy set is always present; every capability added after that baseline appears only when the caller required it, so an older closed-enum decoder can still consume a refusal. This list does not imply that every deployment has enabled every operation. + [JsonPropertyName("supportedCapabilities")] + public required IList SupportedCapabilities { get; set; } - /// Category of instruction source — used for merge logic. - [JsonPropertyName("type")] - public InstructionSourceType Type { get; set; } + /// The subset of the caller's bounded extensible capability identifiers this runtime cannot honour. + [JsonPropertyName("unsupportedCapabilities")] + public required IList UnsupportedCapabilities { get; set; } } -/// Instruction sources discovered across user, repository, and plugin sources. +/// The request was rejected because a bounded field fell outside its permitted range or a required field was unusable. Pagination may also be rejected by the authority after a continuation request; repeat the search without page. +/// The invalid-request variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class ServerInstructionSourceList +public partial class SkillInstallationManagementResultInvalidRequest : SkillInstallationManagementResult { - /// All discovered instruction sources. - [JsonPropertyName("sources")] - public IList Sources { get => field ??= []; 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; } } -/// Optional project paths to include in instruction discovery. +/// Side-effect-free planning of one verified Agent Finder Skill candidate. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class InstructionsDiscoverRequest +internal sealed class SkillPlanInstallRequest { - /// 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; } + /// Fresh single-use AI skill candidate handle returned by a bound catalogue search. + [JsonPropertyName("candidateHandle")] + public string CandidateHandle { 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; } + /// Required authenticated bound catalogue and Skill installation capabilities. + [JsonPropertyName("contract")] + public CatalogClientContract Contract { get => field ??= new(); set; } + + /// Existing local session attached to this connection. + [JsonPropertyName("policySessionId")] + public string PolicySessionId { get; set; } = string.Empty; } -/// Canonical file or directory where custom instructions can be discovered or created, with location, kind, preference, and project path. +/// +/// An Agent Finder materialisation reference pinning one exact resource revision. +/// +/// It carries no origin or URL: the runtime always builds the descriptor URL on its fixed Agent +/// Finder authority. It carries no display data either; every reviewed field comes from the +/// verified descriptor and Skill files. +/// [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class InstructionDiscoveryPath +public sealed class CatalogMaterializationReference { - /// Whether the target is a single file or a directory of instruction files. - [JsonPropertyName("kind")] - public InstructionDiscoveryPathKind Kind { get; set; } + /// Expected `sha256:<lowercase hex>` digest of the descriptor's bundle manifest. + [JsonPropertyName("bundleDigest")] + public string BundleDigest { get; set; } = string.Empty; - /// Which tier this target belongs to. - [JsonPropertyName("location")] - public InstructionDiscoveryPathLocation Location { get; set; } + /// Expected `sha256:<lowercase hex>` digest of the canonical descriptor. + [JsonPropertyName("descriptorDigest")] + public string DescriptorDigest { get; set; } = string.Empty; - /// Absolute path of the file or directory (may not exist on disk yet). - [JsonPropertyName("path")] - public string Path { get; set; } = string.Empty; + /// Agent Finder resource identifier, a positive decimal integer. + [JsonPropertyName("resourceId")] + public string ResourceId { 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; } + /// Agent Finder materialisation revision identifier, a positive decimal integer. This is not a Git commit. + [JsonPropertyName("revisionId")] + public string RevisionId { get; set; } = string.Empty; - /// The input project path this target was derived from (only for repository targets). - [JsonPropertyName("projectPath")] - public string? ProjectPath { get; set; } + /// Materialisation descriptor schema version, currently `1.0.0`. + [JsonPropertyName("schemaVersion")] + public string SchemaVersion { get; set; } = string.Empty; } -/// Canonical files and directories where custom instructions can be created so the runtime will recognize them. +/// Side-effect-free planning of one Skill from a materialisation reference rather than a search candidate. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class InstructionDiscoveryPathList +internal sealed class SkillPlanInstallFromReferenceRequest { - /// Canonical instruction create/discovery files and directories, in priority order. - [JsonPropertyName("paths")] - public IList Paths { get => field ??= []; set; } + /// Required authenticated bound catalogue, skill-confirmed-installation and skill-reference-installation capabilities. + [JsonPropertyName("contract")] + public CatalogClientContract Contract { get => field ??= new(); set; } + + /// Existing local session attached to this connection. + [JsonPropertyName("policySessionId")] + public string PolicySessionId { get; set; } = string.Empty; + + /// Materialisation reference to verify and plan. + [JsonPropertyName("reference")] + public CatalogMaterializationReference Reference { get => field ??= new(); set; } } -/// Optional project paths to include when enumerating instruction discovery targets. +/// Skill installation result with the honoured contract, or a typed request/negotiation refusal. +/// Polymorphic base type discriminated by kind. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class InstructionsGetDiscoveryPathsRequest +[JsonPolymorphic( + TypeDiscriminatorPropertyName = "kind", + UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] +[JsonDerivedType(typeof(SkillInstallationResultOutcome), "outcome")] +[JsonDerivedType(typeof(SkillInstallationResultNegotiationRefused), "negotiation-refused")] +[JsonDerivedType(typeof(SkillInstallationResultInvalidRequest), "invalid-request")] +public partial class SkillInstallationResult { - /// 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; } + /// The type discriminator. + [JsonPropertyName("kind")] + public virtual string Kind { get; set; } = string.Empty; } -/// Installed plugin record from global state, with marketplace, version, install time, enabled state, cache path, and source. + +/// The outcome variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class InstalledPlugin +public partial class SkillInstallationResultOutcome : SkillInstallationResult { - /// Path where the plugin is cached locally. - [JsonPropertyName("cache_path")] - public string? CachePath { get; set; } + /// + [JsonIgnore] + public override string Kind => "outcome"; - /// Whether the plugin is currently enabled. - [JsonPropertyName("enabled")] - public bool Enabled { get; set; } + /// Capabilities honoured for this request. + [JsonPropertyName("negotiated")] + public required CatalogNegotiatedContract Negotiated { get; set; } - /// Installation timestamp. - [JsonPropertyName("installed_at")] - public string InstalledAt { get; set; } = string.Empty; + /// Terminal operation outcome. + [JsonPropertyName("outcome")] + public required SkillInstallationOutcome Outcome { 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; } +/// 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(global::GitHub.Copilot.Diagnostics.Experimental)] +public partial class SkillInstallationResultNegotiationRefused : SkillInstallationResult +{ + /// + [JsonIgnore] + public override string Kind => "negotiation-refused"; - /// Marketplace the plugin came from (empty string for direct repo installs). - [JsonPropertyName("marketplace")] - public string Marketplace { 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; } - /// Plugin name. - [JsonPropertyName("name")] - public string Name { get; set; } = string.Empty; + /// Lowest caller protocol version this runtime will serve. + [JsonPropertyName("minimumSupportedProtocolVersion")] + public required long MinimumSupportedProtocolVersion { get; set; } - /// Source for direct repo installs (when marketplace is empty). - [JsonPropertyName("source")] - public JsonElement? Source { get; set; } + /// Whether the version or the capability set was the problem. + [JsonPropertyName("reason")] + public required CatalogNegotiationRefusedReason Reason { 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; } + /// Protocol version of the runtime that refused the request. + [JsonPropertyName("runtimeProtocolVersion")] + public required long RuntimeProtocolVersion { get; set; } - /// Version installed (if available). - [JsonPropertyName("version")] - public string? Version { get; set; } + /// Capabilities this runtime can safely advertise to this caller. The complete five-capability protocol-3 legacy set is always present; every capability added after that baseline appears only when the caller required it, so an older closed-enum decoder can still consume a refusal. 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; } } -/// An account the host has signed in to, identified by the server it lives on and the login it uses there. The same person can appear more than once when they use both github.com and an Enterprise server. +/// The request was rejected because a bounded field fell outside its permitted range or a required field was unusable. Pagination may also be rejected by the authority after a continuation request; repeat the search without page. +/// The invalid-request variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class LoggedInUser +public partial class SkillInstallationResultInvalidRequest : SkillInstallationResult { - /// Source account this account was derived from, when one was recorded. - [JsonPropertyName("derivedFrom")] - public string? DerivedFrom { get; set; } - - /// Host the account belongs to, such as `github.com` or an Enterprise server. - [JsonPropertyName("host")] - public string Host { get; set; } = string.Empty; + /// + [JsonIgnore] + public override string Kind => "invalid-request"; - /// Account kind, when the host recorded one. Consumers must tolerate new strings. - [JsonPropertyName("kind")] - public string? Kind { get; set; } + /// Which request field was rejected. + [JsonPropertyName("field")] + public required CatalogInvalidRequestField Field { get; set; } - /// Account login on that host. - [JsonPropertyName("login")] - public string Login { 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; } } -/// The host's machine-wide state. Every field is optional because a fresh install has recorded nothing yet, so a reader must treat an absent field as `not yet`, never as a negative answer. Stored credentials are deliberately absent from this shape. +/// Applies exactly one retained verified Skill installation plan. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class GlobalStateLoadResult +internal sealed class SkillApplyInstallRequest { - /// Whether the user has answered the prompt suggesting they install the desktop app. - [JsonPropertyName("appInstallNudgeResponded")] - public bool? AppInstallNudgeResponded { get; set; } - - /// Whether the app tip has been shown. - [JsonPropertyName("appTipShown")] - public bool? AppTipShown { get; set; } - - /// Terminals the user has already been asked to set up, so the host does not ask twice. - [JsonPropertyName("askedSetupTerminals")] - public IList? AskedSetupTerminals { get; set; } + /// Required authenticated bound catalogue and Skill installation capabilities. + [JsonPropertyName("contract")] + public CatalogClientContract Contract { get => field ??= new(); set; } - /// When the Auto-feedback hint was last shown, as an ISO 8601 timestamp. It enforces the once-per-day cap for non-staff users across restarts. - [JsonPropertyName("autoFeedbackLastPromptedAt")] - public string? AutoFeedbackLastPromptedAt { get; set; } + /// Opaque original plan, consumed once. + [JsonPropertyName("planHandle")] + public string PlanHandle { get; set; } = string.Empty; - /// When the host first ran on this machine. - [JsonPropertyName("firstLaunchAt")] - public string? FirstLaunchAt { get; set; } + /// Same existing selected session as planning. + [JsonPropertyName("policySessionId")] + public string PolicySessionId { get; set; } = string.Empty; +} - /// Plugins installed on this machine. - [JsonPropertyName("installedPlugins")] - public IList? InstalledPlugins { get; set; } +/// Read-only preparation of one owned Skill removal under fresh selected-session authority. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class SkillPlanUninstallRequest +{ + /// Required authenticated bound installation contract. + [JsonPropertyName("contract")] + public CatalogClientContract Contract { get => field ??= new(); set; } - /// Account used for the most recent sign-in. - [JsonPropertyName("lastLoggedInUser")] - public LoggedInUser? LastLoggedInUser { get; set; } + /// Exact receipt to inspect. + [JsonPropertyName("installationId")] + public string InstallationId { get; set; } = string.Empty; - /// Every account the host has signed in to on this machine. - [JsonPropertyName("loggedInUsers")] - public IList? LoggedInUsers { get; set; } + /// Existing selected local session on this connection. + [JsonPropertyName("policySessionId")] + public string PolicySessionId { get; set; } = string.Empty; +} - /// Whether the one-off cleanup of stored reasoning summaries has run. - [JsonPropertyName("reasoningSummariesCleanupDone")] - public bool? ReasoningSummariesCleanupDone { get; set; } +/// One-use application of the exact retained Skill removal plan. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class SkillApplyUninstallRequest +{ + /// Required authenticated bound installation contract. + [JsonPropertyName("contract")] + public CatalogClientContract Contract { get => field ??= new(); set; } - /// Models the user selected recently, most recent first. - [JsonPropertyName("recentModelIds")] - public IList? RecentModelIds { get; set; } + /// Opaque original removal plan, consumed once. + [JsonPropertyName("planHandle")] + public string PlanHandle { get; set; } = string.Empty; - /// Whether the user declined to trust the sandbox credential proxy CA. - [JsonPropertyName("sandboxCredentialProxyCaDeclined")] - public bool? SandboxCredentialProxyCaDeclined { get; set; } + /// Same existing selected session as removal preparation. + [JsonPropertyName("policySessionId")] + public string PolicySessionId { get; set; } = string.Empty; +} - /// Whether the sandbox onboarding has been shown. - [JsonPropertyName("sandboxOnboardingShown")] - public bool? SandboxOnboardingShown { get; set; } +/// Server-side skill metadata, including name, description, source, enabled/invocable state, path, project path, and argument hint. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public sealed class ServerSkill +{ + /// Optional freeform hint describing the skill's expected arguments, from the `argument-hint` frontmatter field. + [JsonPropertyName("argumentHint")] + public string? ArgumentHint { get; set; } - /// Whether the user is a GitHub or Microsoft staff member, which unlocks internal-only behavior. - [JsonPropertyName("staff")] - public bool? Staff { get; set; } + /// Canonical slash command name used to invoke the skill, without the leading '/'. + [JsonPropertyName("commandName")] + public string? CommandName { get; set; } - /// Whether the user was recognized as GitHub staff. - [JsonPropertyName("staffGithub")] - public bool? StaffGitHub { get; set; } + /// Description of what the skill does. + [JsonPropertyName("description")] + public string Description { get; set; } = string.Empty; - /// When the staff-only log level migration last ran. - [JsonPropertyName("staffLogLevelMigrationAt")] - public string? StaffLogLevelMigrationAt { get; set; } + /// Whether the skill is currently enabled (based on global config). + [JsonPropertyName("enabled")] + public bool Enabled { get; set; } - /// Whether the user was recognized as Microsoft staff. - [JsonPropertyName("staffMicrosoft")] - public bool? StaffMicrosoft { get; set; } + /// Unique identifier for the skill. + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; - /// When the staff-only model reset last ran. - [JsonPropertyName("staffModelResetAt")] - public string? StaffModelResetAt { get; set; } + /// Absolute path to the skill file. + [JsonPropertyName("path")] + public string? Path { get; set; } - /// When the staff-only update channel migration last ran. - [JsonPropertyName("staffUpdateChannelMigrationAt")] - public string? StaffUpdateChannelMigrationAt { get; set; } + /// The project path this skill belongs to (only for project/inherited skills). + [JsonPropertyName("projectPath")] + public string? ProjectPath { get; set; } - /// Folders where the user declined the init prompt, so it stays hidden there. - [JsonPropertyName("suppressInitFolders")] - public IList? SuppressInitFolders { get; set; } + /// Source location type (e.g., project, personal-copilot, plugin, builtin). + [JsonPropertyName("source")] + public SkillSource Source { get; set; } - /// Folders the user has marked as trusted. - [JsonPropertyName("trustedFolders")] - public IList? TrustedFolders { get; set; } + /// Whether the skill can be invoked by the user as a slash command. + [JsonPropertyName("userInvocable")] + public bool UserInvocable { get; set; } } -/// Selects the configuration directory whose machine-wide state to read. +/// Skills discovered across global and project sources. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class GlobalStateLoadForConfigDirRequest +public sealed class ServerSkillList { - /// Copilot configuration directory to read the state document from, taking precedence over the server's own `COPILOT_HOME` and default home. Omit it, or pass an empty string, to read the directory the server resolved for itself. - [JsonPropertyName("configDir")] - public string? ConfigDir { get; set; } + /// 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; } + + /// All discovered skills across all sources. + [JsonPropertyName("skills")] + public IList Skills { get => field ??= []; set; } } -/// A single top-level key to record in the host's machine-wide state. The write replaces only that key and leaves the rest of the document untouched, so two writers recording different one-off flags do not overwrite each other. The stored credential keys cannot be written through this method. +/// Optional project paths and additional skill directories to include in discovery. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class GlobalStateWriteKeyRequest +internal sealed class SkillsDiscoverRequest { - /// Copilot configuration directory to write the state document in, taking precedence over the server's own `COPILOT_HOME` and default home. Omit it, or pass an empty string, to write the directory the server resolved for itself. Mirrors `globalState.loadForConfigDir`, so a caller can read and write the same directory. - [JsonPropertyName("configDir")] - public string? ConfigDir { get; set; } - - /// Top-level key to write, named as it appears in the result of `globalState.load`. It must be one of the writable keys that `globalState.writeKey` lists. - [JsonPropertyName("key")] - public string Key { get; set; } = string.Empty; + /// 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; } - /// Value to store for the key. Omit it, or pass null, to remove the key instead. - [JsonPropertyName("value")] - public JsonElement? Value { get; set; } -} + /// Optional skill scan paths to exclude from discovery. + [JsonPropertyName("ignoredSkillsLocations")] + public IList? IgnoredSkillsLocations { get; set; } -/// A literal choice the command input accepts, with a human-facing description. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class SlashCommandInputChoice -{ - /// Human-readable description shown alongside the choice. - [JsonPropertyName("description")] - public string Description { get; set; } = string.Empty; + /// Optional list of project directory paths to scan for project-scoped skills. + [JsonPropertyName("projectPaths")] + public IList? ProjectPaths { get; set; } - /// The literal choice value (e.g. 'on', 'off', 'show'). - [JsonPropertyName("name")] - public string Name { get; set; } = string.Empty; + /// Explicit discovery-only skill roots, not filesystem grants. Available without ambient discovery, subject to skill/managed disables. Relative paths resolve against the request's effective working directory when supplied. Null and omission are equivalent; an empty array contributes no explicit roots. + [JsonPropertyName("skillDirectories")] + public IList? SkillDirectories { get; set; } } -/// Optional unstructured input hint. +/// Canonical directory where skills can be discovered or created, with scope, preference, and optional project path. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class SlashCommandInput +public sealed class SkillDiscoveryPath { - /// 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; } - - /// Optional completion hint for the input (e.g. 'directory' for filesystem path completion). - [JsonPropertyName("completion")] - public SlashCommandInputCompletion? Completion { get; set; } + /// Absolute path of the create/discovery target (may not exist on disk yet). + [JsonPropertyName("path")] + public string Path { get; set; } = string.Empty; - /// Hint to display when command input has not been provided. - [JsonPropertyName("hint")] - public string Hint { 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; } - /// 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; } + /// The input project path this directory was derived from (only for project scope). + [JsonPropertyName("projectPath")] + public string? ProjectPath { 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; } + /// Which tier this directory belongs to. + [JsonPropertyName("scope")] + public SkillDiscoveryScope Scope { get; set; } } -/// Slash-command metadata with name, aliases, description, kind, input hint, execution allowance, and schedulability. +/// Canonical locations where skills can be created so the runtime will recognize them. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class SlashCommandInfo +public sealed class SkillDiscoveryPathList { - /// 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; } - - /// Human-readable command description. - [JsonPropertyName("description")] - public string Description { get; set; } = string.Empty; - - /// Whether the command is experimental. - [JsonPropertyName("experimental")] - public bool? Experimental { get; set; } + /// Canonical skill create/discovery directories, in priority order. + [JsonPropertyName("paths")] + public IList Paths { get => field ??= []; set; } +} - /// Optional unstructured input hint. - [JsonPropertyName("input")] - public SlashCommandInput? Input { get; set; } +/// Optional project paths to enumerate. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public 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; } - /// 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; } + /// Optional skill scan paths to exclude from discovery. + [JsonPropertyName("ignoredSkillsLocations")] + public IList? IgnoredSkillsLocations { get; set; } - /// Canonical command name without a leading slash. - [JsonPropertyName("name")] - public string Name { get; set; } = string.Empty; + /// Optional list of project directory paths. When omitted or empty, only personal and custom directories are returned. + [JsonPropertyName("projectPaths")] + public IList? ProjectPaths { 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; } + /// Explicit discovery-only skill roots to enumerate, including configured missing directories. Relative paths resolve against the request's effective working directory when supplied. These are not filesystem grants or preferred creation targets. Null and omission are equivalent. + [JsonPropertyName("skillDirectories")] + public IList? SkillDirectories { get; set; } } -/// Slash commands available in the session, after applying any include/exclude filters. +/// Inventory request under an explicitly selected existing session. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class CommandList +internal sealed class SkillInstallationsRequest { - /// Commands available in this session. - [JsonPropertyName("commands")] - public IList Commands { get => field ??= []; set; } + /// Required authenticated bound installation contract. + [JsonPropertyName("contract")] + public CatalogClientContract Contract { get => field ??= new(); set; } + + /// Existing selected local session on this connection. + [JsonPropertyName("policySessionId")] + public string PolicySessionId { get; set; } = string.Empty; } -/// A single user setting's effective value alongside its default, so consumers can render settings left at their default. +/// Existing-operation control. A new session selector is deliberately not accepted. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class UserSettingMetadata +internal sealed class SkillInstallationOperationRequest { - /// 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; } + /// Required authenticated bound Skill installation capability. + [JsonPropertyName("contract")] + public CatalogClientContract Contract { get => field ??= new(); set; } - /// The effective value: the user's value if set, otherwise the default. - [JsonPropertyName("value")] - public JsonElement Value { get; set; } + /// Exact runtime-issued operation ID on the original connection. + [JsonPropertyName("operationId")] + public string OperationId { get; set; } = string.Empty; } -/// Per-key metadata for every known user setting in settings.json, including settings left at their default. Excludes repository- and enterprise-managed overrides. +/// Persisted enablement update for one owned Skill installation. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class UserSettingsGetResult +internal sealed class SkillSetEnabledRequest { - /// 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; } + /// Required authenticated bound Skill installation capability. + [JsonPropertyName("contract")] + public CatalogClientContract Contract { get => field ??= new(); set; } + + /// Persisted enablement value. + [JsonPropertyName("enabled")] + public bool Enabled { get; set; } + + /// Exact receipt identity to update. + [JsonPropertyName("installationId")] + public string InstallationId { get; set; } = string.Empty; + + /// Existing selected local session to reconcile after persistence. + [JsonPropertyName("policySessionId")] + public string PolicySessionId { get; set; } = string.Empty; } -/// 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. +/// Skill names to mark as disabled in global configuration, replacing any previous list. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class UserSettingsSetRequest +internal sealed class SkillsConfigSetDisabledSkillsRequest { - /// Partial user settings to write, as a free-form object keyed by setting name. - [JsonPropertyName("settings")] - public JsonElement Settings { get; set; } + /// List of skill names to disable. + [JsonPropertyName("disabledSkills")] + public IList DisabledSkills { get => field ??= []; set; } } -/// Owner, name, and host of a GitHub repository, as resolved from a git remote URL. +/// Adds or removes a single skill from the global disabled list, leaving every other entry untouched. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class GitHubRepositoryIdentity +internal sealed class SkillsConfigSetSkillDisabledRequest { - /// Host the remote points at, for example `github.com` or a GitHub Enterprise hostname. - [JsonPropertyName("host")] - public string Host { get; set; } = string.Empty; + /// True to disable the skill, false to enable it. + [JsonPropertyName("disabled")] + public bool Disabled { get; set; } - /// Repository name, without the owner prefix or the `.git` suffix. + /// Name of the skill to add to or remove from the disabled list. [JsonPropertyName("name")] public string Name { get; set; } = string.Empty; - - /// Repository owner login (user or organization). - [JsonPropertyName("owner")] - public string Owner { get; set; } = string.Empty; } -/// The GitHub repository that owns the requested path, when the selected remote (`origin`, else the first) is on a GitHub host. +/// 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(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class GitHubRepositoryAtPathResult +[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 { - /// Resolved repository identity, or null when the selected remote resolves to no GitHub host. - [JsonPropertyName("repository")] - public GitHubRepositoryIdentity? Repository { get; set; } + /// The type discriminator. + [JsonPropertyName("kind")] + public virtual string Kind { get; set; } = string.Empty; } -/// Working-tree path whose owning GitHub repository should be resolved. + +/// One inert catalog result, represented as an MCP server, discovery-only AI skill, or opt-in Agent Plugin variant so kind, media type, provenance, and available operations cannot contradict each other. +/// Polymorphic base type discriminated by kind. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class GitHubRepositoryAtPathRequest +[JsonPolymorphic( + TypeDiscriminatorPropertyName = "kind", + UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] +[JsonDerivedType(typeof(CatalogCandidateMcpServer), "mcp-server")] +[JsonDerivedType(typeof(CatalogCandidateAiSkill), "ai-skill")] +[JsonDerivedType(typeof(CatalogCandidatePlugin), "plugin")] +public partial class CatalogCandidate { - /// Absolute path to a directory inside the git working tree to resolve. - [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("path")] - public string Path { get; set; } = string.Empty; + /// The type discriminator. + [JsonPropertyName("kind")] + public virtual string Kind { get; set; } = string.Empty; } -/// A freshly registered request id. Registering it before the listing starts is what lets a cancel that races the request still find the owner listing slot. The id serves one listing only. Long-abandoned unused ids can be released by later allocations. + +/// 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(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class GitHubOwnersRequestIdResult +public sealed class CatalogMcpServerCandidateProvenance { - /// Request id to pass to `gitHubOwners.list` and, to abandon it, `gitHubOwners.cancel`. - [JsonPropertyName("requestId")] - public long RequestId { 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; -/// A GitHub login the authenticated user may act as: their own account, or an organization they belong to. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class GitHubOwnerOption -{ - /// The owner's GitHub login. - [JsonPropertyName("login")] - public string Login { get; set; } = string.Empty; + /// JSON MCP media type advertised for the referenced card. + [JsonPropertyName("mediaType")] + public McpServerCardMediaType MediaType { get; set; } - /// Which kind of owner this is. The authenticated user's own account is always reported as `user`. - [JsonPropertyName("type")] - public string Type { 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; } -/// Outcome of an owner listing. Exactly one of `owners` and `message` is present, except that `throwError` reports a failure the caller is expected to raise rather than render. +/// 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(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class GitHubOwnersListResult +[JsonPolymorphic( + TypeDiscriminatorPropertyName = "kind", + UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] +[JsonDerivedType(typeof(CatalogCandidateSourceUrl), "url")] +[JsonDerivedType(typeof(CatalogCandidateSourceEmbedded), "embedded")] +public partial class CatalogCandidateSource { - /// Why no owners could be listed, phrased for a user. Present when the listing failed in a way the caller should render rather than raise. - [JsonPropertyName("message")] - public string? Message { get; set; } - - /// The owners, on success: the authenticated user first, then the organizations they belong to. - [JsonPropertyName("owners")] - public IList? Owners { get; set; } - - /// A malformed request or an unreadable credential, which the caller raises instead of rendering. Kept a field rather than a dispatch error so it stays distinct from `message`, which the caller renders. - [JsonPropertyName("throwError")] - public string? ThrowError { get; set; } - - /// A line the caller should log. Present only alongside `message`, and only for failures worth recording. - [JsonPropertyName("warning")] - public string? Warning { get; set; } + /// The type discriminator. + [JsonPropertyName("kind")] + public virtual string Kind { get; set; } = string.Empty; } -/// Credential to list owners under, and the request id that makes the listing cancellable. + +/// Candidate whose card is retrieved from a URL through the runtime's hardened fetch boundary. +/// The url variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class GitHubOwnersListRequest +public partial class CatalogCandidateSourceUrl : CatalogCandidateSource { - /// The credential the listing runs under, carried opaquely because its shape is the host's own and the runtime only resolves a token and a GitHub host from it. No credential travels: this selects one the runtime already holds. - [JsonPropertyName("authInfo")] - public JsonElement AuthInfo { get; set; } + /// + [JsonIgnore] + public override string Kind => "url"; - /// Request id from `gitHubOwners.nextRequestId`. An id that was never registered, canceled before use, released after being abandoned, or already used is refused rather than silently running uncancellable. - [JsonPropertyName("requestId")] - public long RequestId { 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; } } -/// Whether the id named a running owner listing. +/// Candidate whose card reference arrived inline. The document and its content-derived properties stay behind the runtime boundary. +/// The embedded variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class GitHubOwnersCancelResult +public partial class CatalogCandidateSourceEmbedded : CatalogCandidateSource { - /// True when a listing with the id was running and the cancel stopped it. False when the id was never registered, was registered but unused, was released after being abandoned, or its listing had ended. An unused id is released and cannot start a later listing. - [JsonPropertyName("canceled")] - public bool Canceled { get; set; } + /// + [JsonIgnore] + public override string Kind => "embedded"; } -/// The owner listing to abandon. +/// 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(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class GitHubOwnersCancelRequest +public partial class CatalogCandidateMcpServer : CatalogCandidate { - /// Request id the listing was started with. - [JsonPropertyName("requestId")] - public long RequestId { get; set; } -} + /// + [JsonIgnore] + public override string Kind => "mcp-server"; -/// The remote the checked-out branch tracks. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class GitCurrentBranchRemoteResult -{ - /// Name of the tracked remote. Reports `origin` whenever the working tree has no tracking configuration to read, including on a detached HEAD, so this is never null and never 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; } + + /// 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; } + + /// 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("remote")] - public string Remote { get; set; } = string.Empty; + [JsonPropertyName("handle")] + public required string Handle { get; set; } + + /// ISO 8601 timestamp after which the handle is stale and will be rejected. + [JsonPropertyName("handleExpiresAt")] + public required string HandleExpiresAt { get; set; } + + /// Whether this MCP server can be planned for installation, and if policy prevents it. + [JsonPropertyName("installability")] + public required CatalogMcpServerInstallability Installability { get; set; } + + /// JSON MCP media type of the underlying card. + [JsonPropertyName("mediaType")] + public required McpServerCardMediaType MediaType { get; set; } + + /// Where the catalog reference was observed, without the card itself or any content digest. + [JsonPropertyName("provenance")] + public required CatalogMcpServerCandidateProvenance Provenance { get; 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; } + + /// Versioned trust metadata observed from the catalog authority. Optional for protocol-3 compatibility with runtimes that predate trust snapshots. A trust-capable runtime emits an explicit snapshot even when the authority omitted or malformed its trust field. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("trust")] + public CatalogTrustSnapshot? Trust { get; set; } } -/// Working-tree path a git query applies to. +/// 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(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class GitCwdRequest +public sealed class CatalogAiSkillCandidateProvenance { - /// Absolute path to a directory inside the git working tree to query. + /// 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("cwd")] - public string Cwd { get; set; } = string.Empty; + [JsonPropertyName("authority")] + public string Authority { get; set; } = string.Empty; + + /// Media type advertised for the referenced AI skill card. + [JsonPropertyName("mediaType")] + public CatalogAiSkillMediaType 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; } -/// Updated working directory and git context. Emitted as the new payload of `session.context_changed`. +/// An inert AI skill catalog result. Verified Skill candidates may be installable only when the runtime reports installability and the selected session is permitted to plan installation. +/// The ai-skill variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class SessionWorkingDirectoryContext +public partial class CatalogCandidateAiSkill : CatalogCandidate { - /// 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; } + /// + [JsonIgnore] + public override string Kind => "ai-skill"; - /// Current working directory path. - [JsonPropertyName("cwd")] - public string Cwd { 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; } - /// Root directory of the git repository, resolved via git rev-parse. - [JsonPropertyName("gitRoot")] - public string? GitRoot { 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; } - /// Head commit of the current git branch. - [JsonPropertyName("headCommit")] - public string? HeadCommit { 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; } - /// Hosting platform type of the repository. - [JsonPropertyName("hostType")] - public SessionWorkingDirectoryContextHostType? HostType { get; set; } + /// ISO 8601 timestamp after which the handle is stale and will be rejected. + [JsonPropertyName("handleExpiresAt")] + public required string HandleExpiresAt { 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; } + /// Whether this AI skill candidate can be planned for verified installation in the selected session. + [JsonPropertyName("installability")] + public required CatalogAiSkillInstallability Installability { get; set; } - /// Raw host string from the git remote URL (e.g. "github.com", "dev.azure.com"). - [JsonPropertyName("repositoryHost")] - public string? RepositoryHost { get; set; } -} + /// Media type of the underlying AI skill card. + [JsonPropertyName("mediaType")] + public required CatalogAiSkillMediaType MediaType { get; set; } -/// A GitHub repository one of a working tree's remotes points at. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class GitRemoteRepository -{ - /// GitHub host serving the repository, which is not `github.com` for a GitHub Enterprise remote. - [JsonPropertyName("host")] - public string Host { 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; } - /// Repository name, without the owner. - [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; } - /// Account or organization owning the repository. - [JsonPropertyName("owner")] - public string Owner { get; set; } = string.Empty; + /// 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; } - /// Name of the first remote that produced this distinct repository entry, such as `origin` or `upstream`. - [JsonPropertyName("remoteName")] - public string RemoteName { get; set; } = string.Empty; + /// Versioned trust metadata observed from the catalog authority. Optional for protocol-3 compatibility with runtimes that predate trust snapshots. A trust-capable runtime emits an explicit snapshot even when the authority omitted or malformed its trust field. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("trust")] + public CatalogTrustSnapshot? Trust { get; set; } } -/// The GitHub repositories a working tree's remotes point at. +/// One place an Agent Plugin can install from: Agent Finder's materialisation, a marketplace, or a GitHub repository. Each variant carries only its own fields. +/// Polymorphic base type discriminated by kind. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class GitReposFromRemotesResult +[JsonPolymorphic( + TypeDiscriminatorPropertyName = "kind", + UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] +[JsonDerivedType(typeof(CatalogAgentPluginInstallSourceMaterialization), "materialization")] +[JsonDerivedType(typeof(CatalogAgentPluginInstallSourceMarketplace), "marketplace")] +[JsonDerivedType(typeof(CatalogAgentPluginInstallSourceRepository), "repository")] +public partial class CatalogAgentPluginInstallSource { - /// One entry per distinct GitHub repository, in the order git reports the first remote for each repository. Empty when no remote points at a GitHub host, which a caller should read as `not connected to GitHub`. Failing to read the remotes is an error, not an empty list. - [JsonPropertyName("repositories")] - public IList Repositories { get => field ??= []; set; } + /// The type discriminator. + [JsonPropertyName("kind")] + public virtual string Kind { get; set; } = string.Empty; } -/// Git working tree whose GitHub remotes should be listed. + +/// Agent Finder's verified materialisation of the plugin. Present only when the candidate carries a complete materialisation reference; the reference itself stays runtime-private. +/// The materialization variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class GitReposFromRemotesRequest +public partial class CatalogAgentPluginInstallSourceMaterialization : CatalogAgentPluginInstallSource { - /// Absolute path to the root of the git working tree. - [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("gitRoot")] - public string GitRoot { get; set; } = string.Empty; + /// + [JsonIgnore] + public override string Kind => "materialization"; } -/// Validated device-managed settings discovered before a session exists. +/// A plugin marketplace named by verified catalogue install data, installed as `pluginName@name`. +/// The marketplace variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class ManagedSettingsReadResult +public partial class CatalogAgentPluginInstallSourceMarketplace : CatalogAgentPluginInstallSource { - /// Discovery or validation error text when managed settings could not be read safely. - [JsonPropertyName("errorMessage")] - public string? ErrorMessage { get; set; } + /// + [JsonIgnore] + public override string Kind => "marketplace"; - /// 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; } + /// Marketplace name. + [RegularExpression("^[A-Za-z0-9._-]+$")] + [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("name")] + public required string Name { get; set; } + + /// Canonical lowercase GitHub owner/repository hosting the marketplace. + [RegularExpression("^[a-z0-9_.-]+/[a-z0-9_.-]+$")] + [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(3)] + [MaxLength(256)] + [JsonPropertyName("repository")] + public required string Repository { get; set; } } -/// One validation finding for a managed-settings document. +/// A GitHub repository holding the plugin, installed directly as `owner/repo[:path]` at the plan's commit, without a marketplace. +/// The repository variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class ManagedSettingsDiagnostic +public partial class CatalogAgentPluginInstallSourceRepository : CatalogAgentPluginInstallSource { - /// Human-readable description of the finding. - [JsonPropertyName("message")] - public string Message { get; set; } = string.Empty; + /// + [JsonIgnore] + public override string Kind => "repository"; - /// Dot-separated path of the offending setting, such as `autoTier.overridable`. Empty for the document as a whole. + /// Relative POSIX path of the plugin directory in the repository. Omitted when the plugin is at the repository root. + [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)] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("path")] - public string Path { get; set; } = string.Empty; + public string? Path { get; set; } - /// Whether the finding rejects the document. - [JsonPropertyName("severity")] - public ManagedSettingsDiagnosticSeverity Severity { get; set; } + /// Canonical lowercase GitHub owner/repository holding the plugin. + [RegularExpression("^[a-z0-9_.-]+/[a-z0-9_.-]+$")] + [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(3)] + [MaxLength(256)] + [JsonPropertyName("repository")] + public required string Repository { get; set; } } -/// One managed-settings channel and the document it delivered. +/// What installing an Agent Plugin would do, for the user to review and confirm. Provisional: the catalogue install data it is built from is not final. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class ManagedSettingsLayer +public sealed class CatalogAgentPluginInstallPlan { - /// Validated managed-settings document this channel delivered. Absent when the channel delivered none. - [JsonPropertyName("settings")] - public JsonElement? Settings { get; set; } + /// Canvas IDs the plugin contributes. Non-empty exactly when components include `canvas`. + [JsonPropertyName("canvasIds")] + public IList CanvasIds { get => field ??= []; set; } - /// Channel identifier: `device` (MDM, plist, registry, or managed file), `server` (account or organization policy), or `policyHelper` (session-local helper output, supported by compose). Treat unknown output values as additional channels; more may be added. - [JsonPropertyName("source")] - public string Source { get; set; } = string.Empty; + /// Exact full commit ID to install, in lowercase hex. Always present when the plan has a repository source; a plan with a marketplace source carries at least one of version and commit. + [RegularExpression("^(?:[0-9a-f]{40}|[0-9a-f]{64})$")] + [JsonPropertyName("commit")] + public string? Commit { get; set; } + + /// Components the plugin contributes, in canonical order. + [JsonPropertyName("components")] + public IList Components { get => field ??= []; set; } + + /// Connectors the plugin depends on. + [JsonPropertyName("connectorDependencies")] + public IList ConnectorDependencies { get => field ??= []; set; } + + /// Extension IDs the plugin contributes. Non-empty exactly when components include `extension`. + [JsonPropertyName("extensionIds")] + public IList ExtensionIds { get => field ??= []; set; } + + /// MCP servers the plugin depends on but does not bundle. + [JsonPropertyName("mcpDependencies")] + public IList McpDependencies { get => field ??= []; set; } + + /// The plugin's name, as named in its marketplace or manifest. + [RegularExpression("^[A-Za-z0-9._-]+$")] + [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("pluginName")] + public string PluginName { get; set; } = string.Empty; + + /// Every valid install source, in preference order: materialization, then marketplace, then repository. Each kind appears at most once. + [JsonPropertyName("sources")] + public IList Sources { get => field ??= []; set; } + + /// Exact plugin version to install. A plan with a marketplace source carries at least one of version and commit. + [JsonPropertyName("version")] + public string? Version { get; set; } } -/// Lock state and provenance of one managed setting. +/// Where and when an Agent Plugin catalog reference was observed. Discovery provenance deliberately carries no descriptor URL, raw data, candidate handle, or content digest. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class ManagedSettingMeta +public sealed class CatalogAgentPluginCandidateProvenance { - /// Whether users and repositories may choose a different value. `false` means policy locks the value. - [JsonPropertyName("overridable")] - public bool Overridable { 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; - /// Original managed value when the runtime adjusted it to a supported effective value. Omitted when no adjustment was needed. - [JsonPropertyName("requested")] - public string? Requested { get; set; } + /// Media type naming the plugin's format. + [JsonPropertyName("mediaType")] + public CatalogAgentPluginMediaType MediaType { get; set; } - /// Channel that supplied this scalar value, matching a `layers[].source`: `device`, `server`, or `policyHelper`. These scalar defaults select one winning channel, not a mixed source. Treat unknown values as additional channels; more may be added. - [JsonPropertyName("source")] - public string Source { 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; } -/// Per-key lock state and provenance for `ManagedSettingsValues`, with the same field names. Producers emit each typed key in values and meta together; both outer objects are omitted when no typed key is set. +/// Syntactically validated GitHub repository provenance declared by catalog metadata. This is a source claim rather than proof that the descriptor URL resolves to the repository. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class ManagedSettingsMeta +public sealed class CatalogPluginRepositorySource { - /// Lock state and provenance of `values.autoTier`. - [JsonPropertyName("autoTier")] - public ManagedSettingMeta? AutoTier { get; set; } - - /// Lock state and provenance of `values.contextTier`. - [JsonPropertyName("contextTier")] - public ManagedSettingMeta? ContextTier { get; set; } - - /// Lock state and provenance of `values.effortLevel`. - [JsonPropertyName("effortLevel")] - public ManagedSettingMeta? EffortLevel { get; set; } + /// Case-preserving safe relative POSIX path derived from metadata.repoPath. + [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("path")] + public string Path { get; set; } = string.Empty; - /// Lock state and provenance of `values.model`. - [JsonPropertyName("model")] - public ManagedSettingMeta? Model { get; set; } + /// Canonical lowercase owner/repository name derived from metadata.sourceSet. + [RegularExpression("^[a-z0-9_.-]+/[a-z0-9_.-]+$")] + [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(3)] + [MaxLength(256)] + [JsonPropertyName("repository")] + public string Repository { get; set; } = string.Empty; } -/// Effective enterprise managed settings and contributing channels. Session events report applied policy; sessionless resolve reports an account/device snapshot, and compose reports a non-applying preview of candidate documents. Device values take precedence over server values, then the policy helper, per ordinary key, while permissions compose restrictively. Session-local SDK-client policy is included only in session results. Marked experimental while the managed-settings surface stabilizes. +/// An inert Agent Plugin catalog result. Its canonical catalog identity, declared version, repository source claim, and explicit compatibility tags are safe to correlate, while its descriptor, URL, raw data, and installed-plugin state remain runtime-private. This contract-only variant does not mint or expose a candidate handle. +/// The plugin variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class ManagedSettingsResolvedData +public partial class CatalogCandidatePlugin : CatalogCandidate { - /// 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 bool BypassPermissionsDisabled { get; set; } + /// + [JsonIgnore] + public override string Kind => "plugin"; - /// Whether a session-local permissions layer injected by the SDK host was present. - [JsonPropertyName("clientManaged")] - public bool? ClientManaged { get; set; } + /// Explicit validated compatibility tags, in canonical order. An empty list means the source declared no recognised compatibility; clients must not infer compatibility from other fields. `canvas-only` requires both `canvas` and `github-copilot`. + [JsonPropertyName("compatibilityTags")] + public required IList CompatibilityTags { get; set; } - /// Whether an actual device MDM/plist/registry/file managed-settings layer was present. - [JsonPropertyName("deviceManaged")] - public bool DeviceManaged { get; set; } + /// 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 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 bool FailClosed { 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 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 IList ManagedKeys { get => field ??= []; set; } + /// Validated, normalised catalogue resource URN. This identity comes only from the catalog identifier and is never inferred from display text or installed-plugin state. + [JsonPropertyName("identity")] + public required string Identity { get; set; } - /// Whether at least two managed sources supplied permission allowlists, so enforcement intersects them and the flattened settings payload omits `permissions.allow`. - [JsonPropertyName("permissionsAllowIntersected")] - public bool? PermissionsAllowIntersected { get; set; } + /// Whether this plugin can be installed in the selected session. Present only when the caller requires the agent-plugin-confirmed-installation capability. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("installability")] + public CatalogAgentPluginInstallability? Installability { get; set; } - /// 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. - [JsonPropertyName("policyHelperManaged")] - public bool? PolicyHelperManaged { get; set; } + /// Reviewable install plan built only from verified catalogue install data. Present exactly when installability is `installable`; the runtime never derives it from the repository source, display text, or identity. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("installPlan")] + public CatalogAgentPluginInstallPlan? InstallPlan { get; set; } - /// 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. - [JsonPropertyName("sandboxEnabledByUndeterminedPolicy")] - public bool? SandboxEnabledByUndeterminedPolicy { get; set; } + /// Media type naming the plugin's format. + [JsonPropertyName("mediaType")] + public required CatalogAgentPluginMediaType MediaType { get; set; } - /// Whether the server (account/org) managed-settings layer was present. - [JsonPropertyName("serverManaged")] - public bool ServerManaged { get; set; } + /// Where the Agent Plugin catalog reference was observed, without its descriptor, URL, raw data, or content digest. + [JsonPropertyName("provenance")] + public required CatalogAgentPluginCandidateProvenance Provenance { get; set; } - /// The effective (resolved) managed settings values, so clients can render exactly what is enforced. Absent when no managed policy is in force. - [JsonPropertyName("settings")] - public JsonElement? Settings { get; 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; } - /// 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. + /// Bounded repository provenance declared through the catalog's sourceSet and repoPath metadata. It contains no descriptor URL. [JsonPropertyName("source")] - public ManagedSettingsResolvedSource Source { get; set; } -} - -/// Typed effective values of managed settings. Each field mirrors the managed-settings schema key of the same name; more keys are added as they are typed. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class ManagedSettingsValues -{ - /// Managed Auto routing preference, used when the selected model is `auto`. - [JsonPropertyName("autoTier")] - public AutoTier? AutoTier { get; set; } - - /// Managed context-tier default for the managed concrete model. - [JsonPropertyName("contextTier")] - public ContextTier? ContextTier { get; set; } + public required CatalogPluginRepositorySource Source { get; set; } - /// Managed reasoning-effort default for the managed concrete model. The runtime clamps it to an entitled effort when model availability is known. - [JsonPropertyName("effortLevel")] - public string? EffortLevel { get; set; } + /// Versioned trust metadata observed from the catalog authority. Optional for protocol-3 compatibility and emitted only when the caller also requires the trust-snapshot capability. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("trust")] + public CatalogTrustSnapshot? Trust { get; set; } - /// Managed default model identifier. When model availability was resolved, aliases and family names are projected to a concrete available model ID; otherwise the configured value is returned. - [JsonPropertyName("model")] - public string? Model { get; set; } + /// Optional version declared by the catalog source. Omitted rather than guessed when the source supplies no version. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("version")] + public string? Version { get; set; } } -/// Effective enterprise managed settings for an account, resolved without a session. +/// Authority-reported navigation metadata, returned only to callers requiring catalog-search-pagination and only when a supported token is present. Tokenless first-page and continuation responses omit this object; no counts are inferred from candidates. The opaque token may be retained for previous or numbered navigation even when hasNextPage is false. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class ManagedSettingsResolveResult +public sealed class CatalogSearchPagination { - /// Printable opaque identity of the account the settings were resolved for, suitable for comparison and storage, not an account selectionId. Absent when no account was available, in which case only device policy is reported. - [JsonPropertyName("account")] - public string? Account { get; set; } + /// One-based page returned by the authority. + [JsonPropertyName("currentPage")] + public int CurrentPage { get; set; } - /// Warnings about unavailable policy sources or a failed refresh served from cache. A cached response is not proof of a successful live fetch; `resolved.failClosed` separately describes enforcement. - [JsonPropertyName("diagnostics")] - public IList Diagnostics { get => field ??= []; set; } + /// Whether the authority token advertises a valid next target within the navigation window. Not inferred from token presence, truncated, or currentPage being less than pageCount. + [JsonPropertyName("hasNextPage")] + public bool HasNextPage { get; set; } - /// Each managed-settings channel consulted, strongest first, with the validated document it delivered before merging. `resolved.settings` is the merged result. More channels may be added over time. - [JsonPropertyName("layers")] - public IList Layers { get => field ??= []; set; } + /// Navigation window ceiling ceil(1000 / pageSize), not the number of existing pages. Legal targets must not exceed this ceiling or the token's signed pageCount. + [JsonPropertyName("maxPage")] + public int MaxPage { get; set; } - /// Per-key lock state and provenance for the entries in `values`, using the same key names. - [JsonPropertyName("meta")] - public ManagedSettingsMeta? Meta { get; set; } + /// Backend-reported page count, which may exceed maxPage. Present pagination metadata always describes a multi-page result; zero- and single-page responses omit pagination. Navigation targets must also be within the signed pageCount carried by the supplied token. + [JsonPropertyName("pageCount")] + public long PageCount { get; set; } - /// Effective managed settings from the device and account (server) channels, in the same shape as `session.managedSettings.get`, excluding session-local injection. - [JsonPropertyName("resolved")] - public ManagedSettingsResolvedData Resolved { get => field ??= new(); set; } + /// Page size bound to the search, equal to the effective request limit. + [JsonPropertyName("pageSize")] + public int PageSize { get; set; } - /// Typed effective values of managed settings, keyed like the managed-settings schema and already resolved across channels, with the `{ "overridable": ... }` wrapper removed. Present when policy sets at least one typed key. Keys not typed here are available in `resolved.settings`. - [JsonPropertyName("values")] - public ManagedSettingsValues? Values { get; set; } + /// Opaque pagination token. Session-bound search returns an expiring runtime-owned reference retaining the exact private authority token, original search and authority. Legacy unbound search returns the authority token unchanged, without a runtime-created expiry. Only the runtime unwraps tokens or changes targetPage; SDK consumers must not decode, modify or log them. + [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(4096)] + [JsonPropertyName("token")] + public string Token { get; set; } = string.Empty; + + /// Backend-reported count for this response, not the number of returned candidates. Its relationship to the full query result set is unknown. + [JsonPropertyName("totalCount")] + public long TotalCount { get; set; } + + /// The relationship of totalCount to the complete query result set is unknown; neither exactness nor a lower-bound guarantee is implied. + [JsonPropertyName("totalCountRelation")] + public CatalogSearchTotalCountRelation TotalCountRelation { get; set; } } -/// RPC data type for ManagedSettingsResolve operations. +/// A completed catalog search containing inert candidate summaries. MCP server and AI skill variants carry a single-use handle; the Agent Plugin variant is handleless. +/// The succeeded variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class ManagedSettingsResolveRequest +public partial class CatalogSearchResultSucceeded : CatalogSearchResult { - /// Embedding client identity for server policy requests, as in session creation. Omit for the CLI identity. - [JsonPropertyName("clientName")] - public string? ClientName { get; set; } + /// + [JsonIgnore] + public override string Kind => "succeeded"; - /// GitHub token to resolve instead of the current account. The call fails when the token cannot be resolved. - [JsonPropertyName("gitHubToken")] - public string? GitHubToken { get; set; } + /// Matching candidates, never more than the requested limit. All text is inert untrusted data. + [JsonPropertyName("candidates")] + public required IList Candidates { get; set; } - /// Opaque account identifier returned by `account.getAllUsers`. When omitted, the current account is used, or device policy only when no account is signed in. - [JsonPropertyName("selectionId")] - public string? SelectionId { get; set; } + /// Protocol version and capabilities the runtime honoured. + [JsonPropertyName("negotiated")] + public required CatalogNegotiatedContract Negotiated { get; set; } - /// Working directory used to run an organization policy helper. When omitted, sessionless resolution does not run the helper. - [JsonPropertyName("workingDirectory")] - public string? WorkingDirectory { get; set; } -} + /// Navigation metadata for callers explicitly requiring catalog-search-pagination. Omitted when the authority returns no token, including tokenless first-page and continuation responses. Counts are never substituted from candidates.length. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("pagination")] + public CatalogSearchPagination? Pagination { get; set; } -/// The authoring JSON schema for managed settings recognized by this runtime. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class ManagedSettingsSchemaResult -{ - /// Version of the runtime that owns this schema. - [JsonPropertyName("runtimeVersion")] - public string RuntimeVersion { get; set; } = string.Empty; + /// 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; } - /// JSON schema (draft 2020-12) with descriptive shared `x-composition` annotations, not a complete runtime composition contract. Model, effortLevel, and contextTier remain coupled; use `managedSettings.compose` for the runtime's effective result. - [JsonPropertyName("schema")] - public JsonElement Schema { get; set; } + /// Legacy indication that the authority returned a page token. Preserved for compatibility; this is not a has-next-page indicator. Use pagination.hasNextPage when pagination metadata is present. + [JsonPropertyName("truncated")] + public required bool Truncated { get; set; } } -/// Result of validating a managed-settings document. +/// 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(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class ManagedSettingsValidateResult +public partial class CatalogSearchResultNegotiationRefused : CatalogSearchResult { - /// Errors that reject the document and warnings about content the runtime ignores. - [JsonPropertyName("diagnostics")] - public IList Diagnostics { get => field ??= []; set; } + /// + [JsonIgnore] + public override string Kind => "negotiation-refused"; - /// Canonical form of the document the runtime would apply, with unrecognized keys removed. Absent when the document is invalid. - [JsonPropertyName("settings")] - public JsonElement? Settings { 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; } - /// Whether the runtime would accept the document within the preview resource limits. Always equals whether `settings` is present. An invalid document is rejected as a whole. - [JsonPropertyName("valid")] - public bool Valid { get; set; } -} + /// Lowest caller protocol version this runtime will serve. + [JsonPropertyName("minimumSupportedProtocolVersion")] + public required long MinimumSupportedProtocolVersion { get; set; } -/// A candidate managed-settings document to validate without applying it. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class ManagedSettingsValidateRequest -{ - /// The document to validate: a JSON object, or a string containing the document's JSON text. Preview documents are limited to 1 MiB and 64 levels of nesting, a stricter resource limit than delivered-policy parsing; violations are returned as diagnostics. - [JsonPropertyName("content")] - public JsonElement Content { get; set; } + /// Whether the version or the capability set was the problem. + [JsonPropertyName("reason")] + public required CatalogNegotiationRefusedReason Reason { get; set; } - /// Channel the document is meant for (`device`, `server`, or `policyHelper`). Some keys are only honored in some channels; for example, a `policyHelper` registration is ignored in policy-helper output. When omitted, no channel-specific checks run. - [JsonPropertyName("layer")] - public string? Layer { get; set; } + /// Protocol version of the runtime that refused the request. + [JsonPropertyName("runtimeProtocolVersion")] + public required long RuntimeProtocolVersion { get; set; } + + /// Capabilities this runtime can safely advertise to this caller. The complete five-capability protocol-3 legacy set is always present; every capability added after that baseline appears only when the caller required it, so an older closed-enum decoder can still consume a refusal. 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; } } -/// The effective managed settings the runtime would enforce for the given documents, in the same shape `managedSettings.resolve` returns. +/// The request asked for a candidate kind this runtime does not serve. +/// The unsupported-kind variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class ManagedSettingsComposeResult +public partial class CatalogSearchResultUnsupportedKind : CatalogSearchResult { - /// Warnings about ignored content, with paths prefixed by the channel name. - [JsonPropertyName("diagnostics")] - public IList Diagnostics { get => field ??= []; set; } - - /// Only the supplied channels, strongest first, with canonical documents. Empty canonical documents are represented as absent settings, as in live resolution. - [JsonPropertyName("layers")] - public IList Layers { get => field ??= []; set; } + /// + [JsonIgnore] + public override string Kind => "unsupported-kind"; - /// Per-key lock state and provenance for `values`. - [JsonPropertyName("meta")] - public ManagedSettingsMeta? Meta { 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; } - /// Effective managed settings, in the same shape as `session.managedSettings.get`. - [JsonPropertyName("resolved")] - public ManagedSettingsResolvedData Resolved { get => field ??= new(); set; } + /// The kinds from the request that are not supported. + [JsonPropertyName("requestedKinds")] + public required IList RequestedKinds { get; set; } - /// Typed effective values, as in `managedSettings.resolve`. - [JsonPropertyName("values")] - public ManagedSettingsValues? Values { get; set; } + /// Every candidate kind this runtime can serve. + [JsonPropertyName("supportedKinds")] + public required IList SupportedKinds { get; set; } } -/// One candidate channel; absent settings represents a channel that delivered no document. +/// The request was rejected because a bounded field fell outside its permitted range or a required field was unusable. Pagination may also be rejected by the authority after a continuation request; repeat the search without page. +/// The invalid-request variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class ManagedSettingsComposeLayer +public partial class CatalogSearchResultInvalidRequest : CatalogSearchResult { - /// Candidate managed-settings document. Omit when the channel delivered none, as in resolve output. - [JsonPropertyName("settings")] - public JsonElement? Settings { get; set; } + /// + [JsonIgnore] + public override string Kind => "invalid-request"; - /// The channel whose candidate document is being supplied. - [JsonPropertyName("source")] - public ManagedSettingsChannel Source { get; set; } -} + /// Which request field was rejected. + [JsonPropertyName("field")] + public required CatalogInvalidRequestField Field { get; set; } -/// Candidate managed-settings documents to merge without applying them. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class ManagedSettingsComposeRequest -{ - /// One entry per channel. `source` must be `device`, `server`, or `policyHelper`, each at most once (checked at runtime); order does not matter, because channel precedence is fixed. To preview documents from resolve output, map recognized source strings to ManagedSettingsChannel and copy their settings; generated resolve and compose layer types are distinct. Omitted settings means this channel delivered no document. Supplied documents must be valid within the preview limits; warnings are returned in diagnostics. Compose does not reproduce source-failure state or retained enforcement floors from resolve. - [JsonPropertyName("layers")] - public IList Layers { get => field ??= []; 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; } } -/// Indicates whether the calling client was registered as the session filesystem provider. +/// 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(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class SessionFsSetProviderResult +public partial class CatalogSearchResultAuthenticationRequired : CatalogSearchResult { - /// Whether the provider was set successfully. - [JsonPropertyName("success")] - public bool Success { get; 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; } } -/// Optional capabilities declared by the provider. +/// Registry or enterprise policy refused the operation. +/// The policy-rejected variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class SessionFsSetProviderCapabilities +public partial class CatalogSearchResultPolicyRejected : CatalogSearchResult { - /// Whether the provider supports binary reads and writes through sessionFs.readFileBytes and sessionFs.writeFileBytes. - [JsonPropertyName("binary")] - public bool? Binary { get; set; } + /// + [JsonIgnore] + public override string Kind => "policy-rejected"; - /// Whether the provider supports SQLite query/exists operations. - [JsonPropertyName("sqlite")] - public bool? Sqlite { 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; } + + /// Which authority produced the decision. + [JsonPropertyName("source")] + public required McpPlanPolicySource Source { 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 registered provider is authoritative for path interpretation and filesystem facts used by workspace permission validation. Paths are interpreted lexically; home-relative paths (`~` and `~/...`) and Windows drive-relative paths such as `C:foo` are unsupported. Until provider-side canonicalization is supported, providers must not expose symlinks inside allowed roots that escape those roots. +/// 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(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionFsSetProviderRequest +public partial class CatalogSearchResultNetworkFailure : CatalogSearchResult { - /// Optional capabilities declared by the provider. - [JsonPropertyName("capabilities")] - public SessionFsSetProviderCapabilities? Capabilities { get; set; } + /// + [JsonIgnore] + public override string Kind => "network-failure"; - /// Path conventions used by this filesystem. - [JsonPropertyName("conventions")] - public SessionFsSetProviderConventions Conventions { 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; } - /// Absolute initial working directory for sessions. Registering the provider establishes this path as the root of its virtual namespace; the runtime does not require the provider to materialize or stat it before creating a session. - [JsonPropertyName("initialCwd")] - public string InitialCwd { get; set; } = string.Empty; + /// Categorised failure, low cardinality so it can be aggregated without carrying a URL. + [JsonPropertyName("reason")] + public required CatalogNetworkFailureReason Reason { 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; -} + /// 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; } -/// Indicates whether the calling client was registered as the LLM inference provider. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class LlmInferenceSetProviderResult -{ - /// Whether the provider was set successfully. - [JsonPropertyName("success")] - public bool Success { get; set; } + /// HTTP status code, when the failure was a rejected response. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("statusCode")] + public int? StatusCode { get; set; } } -/// Whether the start frame was accepted. +/// 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(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class LlmInferenceHttpResponseStartResult +public partial class CatalogSearchResultUnsafeRetrieval : 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 => "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; } } -/// Response head. +/// A card could not be parsed or did not satisfy its declared media type's schema. +/// The malformed-card variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class LlmInferenceHttpResponseStartRequest +public partial class CatalogSearchResultMalformedCard : CatalogSearchResult { - /// HTTP response headers, preserving multiple values per name. - [JsonPropertyName("headers")] - public IDictionary> Headers { get => field ??= new Dictionary>(); set; } + /// + [JsonIgnore] + public override string Kind => "malformed-card"; - /// Matches the requestId from the originating httpRequestStart frame. - [JsonPropertyName("requestId")] - public string RequestId { 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; } - /// HTTP status code. - [JsonPropertyName("status")] - public long Status { 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; } - /// Optional HTTP status reason phrase. - [JsonPropertyName("statusText")] - public string? StatusText { get; set; } + /// How the card failed validation. + [JsonPropertyName("reason")] + public required CatalogMalformedCardReason Reason { get; set; } } -/// Whether the chunk was accepted. +/// 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(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class LlmInferenceHttpResponseChunkResult +public partial class CatalogSearchResultContractViolation : CatalogSearchResult { - /// True when the chunk was matched to a pending request; false when unknown. - [JsonPropertyName("accepted")] - public bool Accepted { 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; } } -/// Set to terminate the response with a transport-level failure. Implies end-of-stream; any further chunks for this requestId are ignored. +/// The operation is not available on this runtime. Distinct from a network failure: nothing was attempted. +/// The unavailable variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class LlmInferenceHttpResponseChunkError +public partial class CatalogSearchResultUnavailable : CatalogSearchResult { - /// Optional machine-readable error code. - [JsonPropertyName("code")] - public string? Code { get; set; } + /// + [JsonIgnore] + public override string Kind => "unavailable"; - /// Human-readable failure description. + /// 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 string Message { get; set; } = string.Empty; + public required string Message { get; set; } + + /// Why the operation is unavailable. + [JsonPropertyName("reason")] + public required CatalogUnavailableReason Reason { get; set; } } -/// A response body chunk or terminal error. +/// An explicit numbered-page request. SDK consumers treat the token as opaque. For bound search, the runtime unwraps an expiring owner-bound reference to the private authority token; only the runtime changes the authority token's targetPage. Legacy unbound navigation keeps its authority-issued token semantics. No snapshot stability is promised. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class LlmInferenceHttpResponseChunkRequest +public sealed class CatalogSearchPage { - /// 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; } + /// Requested one-based page. Must not exceed either the token's signed pageCount or the navigation window ceil(1000 / pageSize). Repeat the search without page to discover newly available pages beyond that signed pageCount. + [JsonPropertyName("number")] + public int Number { get; set; } - /// Matches the requestId from the originating httpRequestStart frame. - [JsonPropertyName("requestId")] - public string RequestId { get; set; } = string.Empty; + /// Opaque pagination token from an earlier response, owner-bound when session-bound search was requested. Never decode, modify or log it in an SDK consumer. Expired or foreign bound references require a fresh bound search, not a legacy retry. + [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(4096)] + [JsonPropertyName("token")] + public string Token { get; set; } = string.Empty; } -/// Pre-resolved working-directory context for session startup. +/// 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(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class SessionContext +public sealed class CatalogSearchRequest { - /// Active git branch. - [JsonPropertyName("branch")] - public string? Branch { get; set; } + /// Protocol version and capabilities the caller requires. + [JsonPropertyName("contract")] + public required CatalogClientContract Contract { get; set; } - /// Most recent working directory for this session. - [JsonPropertyName("cwd")] - public string Cwd { get; set; } = string.Empty; + /// Restrict results to these candidate kinds. Agent Plugins are opt-in and require the `agent-plugin-discovery` capability so protocol-v3 clients generated before that variant cannot receive an unknown result; when omitted, the backwards-compatible MCP server and AI skill kinds are searched. + [JsonPropertyName("kinds")] + public IList? Kinds { get; set; } - /// Git repository root, if the cwd was inside a git repo. - [JsonPropertyName("gitRoot")] - public string? GitRoot { get; set; } + /// Maximum number of candidates to return. Defaults to 10 when omitted. + [JsonPropertyName("limit")] + public int? Limit { get; set; } - /// Repository host type. - [JsonPropertyName("hostType")] - public SessionContextHostType? HostType { get; set; } + /// Numbered navigation using metadata from an earlier response. Requires catalog-search-pagination and the same query, kinds and effective limit. Omit for a fresh first-page search. + [JsonPropertyName("page")] + public CatalogSearchPage? Page { get; set; } - /// Repository slug in `owner/name` form, when known. - [JsonPropertyName("repository")] - public string? Repository { get; set; } + /// + /// Select an existing attached local session. Requires authenticated, session-bound search. + /// The runtime never creates, resumes or reconfigures a session to honour this selector. + /// + [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("policySessionId")] + public string? PolicySessionId { 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 required string Query { get; set; } } -/// GitHub repository the remote session belongs to. +/// Typed outcome of catalog.select. Only the selected host result carries a fresh candidate handle; the model-facing projection removes both that handle and searchId. +/// Polymorphic base type discriminated by kind. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class RemoteSessionMetadataRepository +[JsonPolymorphic( + TypeDiscriminatorPropertyName = "kind", + UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] +[JsonDerivedType(typeof(CatalogSelectionResultSelected), "selected")] +[JsonDerivedType(typeof(CatalogSelectionResultDeclined), "declined")] +[JsonDerivedType(typeof(CatalogSelectionResultCancelled), "cancelled")] +[JsonDerivedType(typeof(CatalogSelectionResultTimedOut), "timed-out")] +[JsonDerivedType(typeof(CatalogSelectionResultInvalid), "invalid")] +[JsonDerivedType(typeof(CatalogSelectionResultStale), "stale")] +[JsonDerivedType(typeof(CatalogSelectionResultReplayed), "replayed")] +[JsonDerivedType(typeof(CatalogSelectionResultForeign), "foreign")] +[JsonDerivedType(typeof(CatalogSelectionResultWrongKind), "wrong-kind")] +[JsonDerivedType(typeof(CatalogSelectionResultNegotiationRefused), "negotiation-refused")] +[JsonDerivedType(typeof(CatalogSelectionResultInvalidRequest), "invalid-request")] +[JsonDerivedType(typeof(CatalogSelectionResultUnavailable), "unavailable")] +public partial class CatalogSelectionResult { - /// Branch associated with the remote session. - [JsonPropertyName("branch")] - public string Branch { get; set; } = string.Empty; - - /// Repository name. - [JsonPropertyName("name")] - public string Name { get; set; } = string.Empty; - - /// Repository owner. - [JsonPropertyName("owner")] - public string Owner { get; set; } = string.Empty; + /// The type discriminator. + [JsonPropertyName("kind")] + public virtual string Kind { get; set; } = string.Empty; } -/// Remote session metadata for the session to hand off (typically obtained from `sessions.list` with `source: "remote"`). + +/// The chosen candidate was transferred into a fresh bounded single-use handle for a later explicit planning request. +/// The selected variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class RemoteSessionMetadataValue +public partial class CatalogSelectionResultSelected : CatalogSelectionResult { - /// Most recent working directory context. - [JsonPropertyName("context")] - public SessionContext? Context { 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; } + /// + [JsonIgnore] + public override string Kind => "selected"; - /// 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; } + /// Fresh single-use candidate handle accepted by mcp.planInstall. Returned only to the native host and never included in model-tool output. + [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; } - /// Always true for remote sessions. - [JsonPropertyName("isRemote")] - public bool IsRemote { get; set; } + /// The exact search identifier privately bound to the selected candidate. + [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; } +} - /// Last-modified time as an ISO 8601 timestamp. - [JsonPropertyName("modifiedTime")] - public string ModifiedTime { get; set; } = string.Empty; +/// The caller explicitly declined every candidate and the retained search state was released. +/// The declined variant of . +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public partial class CatalogSelectionResultDeclined : CatalogSelectionResult +{ + /// + [JsonIgnore] + public override string Kind => "declined"; - /// Optional human-friendly name set via /rename. - [JsonPropertyName("name")] - public string? Name { get; set; } + /// The search identifier privately bound to the released selection group. + [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; } +} - /// Pull request number associated with the session. - [JsonPropertyName("pullRequestNumber")] - public long? PullRequestNumber { get; set; } +/// The caller cancelled the selection interaction and the retained search state was released. +/// The cancelled variant of . +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public partial class CatalogSelectionResultCancelled : CatalogSelectionResult +{ + /// + [JsonIgnore] + public override string Kind => "cancelled"; - /// Backing remote session IDs (most recent first). - [JsonPropertyName("remoteSessionIds")] - public IList RemoteSessionIds { get => field ??= []; set; } + /// The search identifier privately bound to the released selection group. + [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; } +} - /// GitHub repository the remote session belongs to. - [JsonPropertyName("repository")] - public RemoteSessionMetadataRepository Repository { get => field ??= new(); set; } +/// The host declared that its live selection interaction timed out, and the retained search state was released. +/// The timed-out variant of . +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public partial class CatalogSelectionResultTimedOut : CatalogSelectionResult +{ + /// + [JsonIgnore] + public override string Kind => "timed-out"; - /// Original remote resource identifier (task ID or PR node ID). - [JsonPropertyName("resourceId")] - public string? ResourceId { get; set; } + /// The search identifier privately bound to the released selection group. + [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; } +} - /// Stable session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; +/// The selection reference was malformed or unknown. +/// The invalid variant of . +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public partial class CatalogSelectionResultInvalid : CatalogSelectionResult +{ + /// + [JsonIgnore] + public override string Kind => "invalid"; - /// Deadline (ISO 8601) at which a CLI remote session becomes stale without further heartbeats. - [JsonPropertyName("staleAt")] - public string? StaleAt { get; set; } + /// Human-readable explanation safe to surface. Never contains the presented reference or private candidate state. + [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; } +} - /// Session creation time as an ISO 8601 timestamp. - [JsonPropertyName("startTime")] - public string StartTime { get; set; } = string.Empty; +/// The runtime-enforced selection reference lifetime elapsed before the request arrived. +/// The stale variant of . +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public partial class CatalogSelectionResultStale : CatalogSelectionResult +{ + /// + [JsonIgnore] + public override string Kind => "stale"; - /// Server-side task state returned by GitHub. - [JsonPropertyName("state")] - public string? State { get; set; } + /// Human-readable explanation safe to surface. Never contains the presented reference or private candidate state. + [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; } +} - /// Short summary of the session, when one has been derived. - [JsonPropertyName("summary")] - public string? Summary { get; set; } +/// The selection group was already terminated or its pending host hand-off was already claimed. +/// The replayed variant of . +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public partial class CatalogSelectionResultReplayed : CatalogSelectionResult +{ + /// + [JsonIgnore] + public override string Kind => "replayed"; - /// Whether the remote task originated from CCA or CLI `--remote`. - [JsonPropertyName("taskType")] - public RemoteSessionMetadataTaskType? TaskType { get; set; } + /// Human-readable explanation safe to surface. Never contains the presented reference or private candidate state. + [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; } } -/// `sessions.open` handoff progress update with step, status, and optional message. +/// The selection reference belongs to another runtime instance or session. +/// The foreign variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class SessionsOpenProgress +public partial class CatalogSelectionResultForeign : CatalogSelectionResult { - /// Optional step message. + /// + [JsonIgnore] + public override string Kind => "foreign"; + + /// Human-readable explanation safe to surface. Never contains the presented reference or private candidate state. + [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 string? Message { get; set; } + public required string Message { get; set; } +} - /// Step status. - [JsonPropertyName("status")] - public SessionsOpenProgressStatus Status { get; set; } +/// The presented opaque handle was issued for another catalog operation. +/// The wrong-kind variant of . +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public partial class CatalogSelectionResultWrongKind : CatalogSelectionResult +{ + /// + [JsonIgnore] + public override string Kind => "wrong-kind"; - /// Handoff step. - [JsonPropertyName("step")] - public SessionsOpenProgressStep Step { get; set; } + /// Human-readable explanation safe to surface. Never contains the presented reference or private candidate state. + [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; } } -/// Result of opening a session. +/// 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(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class SessionOpenResult +public partial class CatalogSelectionResultNegotiationRefused : CatalogSelectionResult { - /// Remote session metadata, present when status is `connected`. - [JsonPropertyName("metadata")] - public RemoteSessionMetadataValue? Metadata { get; set; } + /// + [JsonIgnore] + public override string Kind => "negotiation-refused"; - /// Handoff progress steps, present when status is `handed_off`. - [JsonPropertyName("progress")] - public IList? Progress { 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; } - /// Remote session ID, present when status is `connected`. - [JsonPropertyName("remoteSessionId")] - public string? RemoteSessionId { get; set; } + /// Lowest caller protocol version this runtime will serve. + [JsonPropertyName("minimumSupportedProtocolVersion")] + public required long MinimumSupportedProtocolVersion { get; set; } - /// Opened session ID. Omitted when status is `not_found`. - [JsonPropertyName("sessionId")] - public string? SessionId { get; set; } + /// Whether the version or the capability set was the problem. + [JsonPropertyName("reason")] + public required CatalogNegotiationRefusedReason Reason { 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; } + /// Protocol version of the runtime that refused the request. + [JsonPropertyName("runtimeProtocolVersion")] + public required long RuntimeProtocolVersion { get; set; } - /// Outcome of the open request. - [JsonPropertyName("status")] - public SessionsOpenStatus Status { get; set; } + /// Capabilities this runtime can safely advertise to this caller. The complete five-capability protocol-3 legacy set is always present; every capability added after that baseline appears only when the caller required it, so an older closed-enum decoder can still consume a refusal. 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; } } -/// Identifier and optional friendly name assigned to the newly forked session. +/// The request was rejected because a bounded field fell outside its permitted range or a required field was unusable. Pagination may also be rejected by the authority after a continuation request; repeat the search without page. +/// The invalid-request variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class SessionsForkResult +public partial class CatalogSelectionResultInvalidRequest : CatalogSelectionResult { - /// Friendly name assigned to the forked session, if any. - [JsonPropertyName("name")] - public string? Name { get; set; } + /// + [JsonIgnore] + public override string Kind => "invalid-request"; - /// The new forked session's ID. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// 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; } } -/// Source session identifier to fork from, optional event-ID boundary, and optional friendly name for the new session. +/// The operation is not available on this runtime. Distinct from a network failure: nothing was attempted. +/// The unavailable variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionsForkRequest +public partial class CatalogSelectionResultUnavailable : CatalogSelectionResult { - /// Optional friendly name to assign to the forked session. - [JsonPropertyName("name")] - public string? Name { get; set; } + /// + [JsonIgnore] + public override string Kind => "unavailable"; - /// Source session ID to fork from. - [JsonPropertyName("sessionId")] - public string SessionId { 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; } - /// 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; } + /// Why the operation is unavailable. + [JsonPropertyName("reason")] + public required CatalogUnavailableReason Reason { get; set; } } -/// Repository associated with the connected remote session. +/// Terminates one retained catalog selection group through an opaque reference previously returned by the model-safe search projection. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class ConnectedRemoteSessionMetadataRepository +internal sealed class CatalogSelectionRequest { - /// Branch associated with the remote session. - [JsonPropertyName("branch")] - public string Branch { get; set; } = string.Empty; + /// Protocol version and capabilities the caller requires. + [JsonPropertyName("contract")] + public CatalogClientContract Contract { get => field ??= new(); set; } - /// Repository name. - [JsonPropertyName("name")] - public string Name { get; set; } = string.Empty; + /// The terminal outcome declared by the caller. Timed-out means the host's live interaction deadline elapsed; a reference whose runtime TTL elapsed is rejected separately as stale. + [JsonPropertyName("outcome")] + public CatalogSelectionDecision Outcome { get; set; } - /// Repository owner or organization login. - [JsonPropertyName("owner")] - public string Owner { get; set; } = string.Empty; + /// Opaque runtime-instance scoped reference to one visible candidate. For a non-selected outcome, any candidate reference from the same search closes that search's retained group. + [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("selectionRef")] + public string SelectionRef { get; set; } = string.Empty; + + /// Locally owned root session whose retained search state is being resolved. + [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("sessionId")] + public string SessionId { get; set; } = string.Empty; } -/// Metadata for a connected remote session. +/// Information about an installed plugin tracked in global state. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class ConnectedRemoteSessionMetadata +public sealed class InstalledPluginInfo { - /// Neutral SDK discriminator for the connected remote session kind. - [JsonPropertyName("kind")] - public ConnectedRemoteSessionMetadataKind Kind { 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; } - /// Last session update time as an ISO 8601 string. - [JsonPropertyName("modifiedTime")] - public DateTimeOffset ModifiedTime { get; set; } + /// Whether the plugin is currently enabled for new sessions. + [JsonPropertyName("enabled")] + public bool Enabled { get; set; } - /// Optional friendly session name. - [JsonPropertyName("name")] - public string? Name { get; set; } + /// Whether the managed desired plugin currently has an installed or live record. Set to false for a managed desired entry retained in the listing after installation or reconciliation failed. + [JsonPropertyName("installed")] + public bool? Installed { get; set; } - /// Pull request number associated with the session. - [JsonPropertyName("pullRequestNumber")] - public long? PullRequestNumber { 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; } - /// Repository associated with the connected remote session. - [JsonPropertyName("repository")] - public ConnectedRemoteSessionMetadataRepository Repository { get => field ??= new(); set; } + /// Whether enterprise managed settings control this plugin's enabled state. + [JsonPropertyName("managed")] + public bool? Managed { get; set; } - /// Original remote resource identifier. - [JsonPropertyName("resourceId")] - public string? ResourceId { get; set; } + /// The enabled state required by enterprise managed settings, when this plugin spec is managed. + [JsonPropertyName("managedDesiredEnabled")] + public bool? ManagedDesiredEnabled { get; set; } - /// SDK session ID for the connected remote session. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Marketplace the plugin came from. Empty string ("") for direct repo / URL / local installs. + [JsonPropertyName("marketplace")] + public string Marketplace { get; set; } = string.Empty; - /// Remote session staleness deadline as an ISO 8601 string. - [JsonPropertyName("staleAt")] - public DateTimeOffset? StaleAt { get; set; } + /// Plugin name. + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; - /// Session start time as an ISO 8601 string. - [JsonPropertyName("startTime")] - public DateTimeOffset StartTime { get; set; } + /// Runtime-reported plugin provenance. Currently set to "builtin" only for plugins registered through the trusted host built-in boundary; absent for installed, marketplace, direct, and live plugins. + [JsonPropertyName("source")] + public string? Source { get; set; } - /// Remote session state returned by the backing service. - [JsonPropertyName("state")] - public string? State { get; set; } + /// Installed version (when reported by the plugin manifest). + [JsonPropertyName("version")] + public string? Version { get; set; } +} - /// Optional session summary. - [JsonPropertyName("summary")] - public string? Summary { get; set; } +/// Plugins installed in user/global state. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public sealed class PluginListResult +{ + /// Installed plugins. + [JsonPropertyName("plugins")] + public IList Plugins { get => field ??= []; set; } } -/// Remote session connection result. +/// Result of installing a plugin. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class RemoteSessionConnectionResult +public sealed class PluginInstallResult { - /// Metadata for a connected remote session. - [JsonPropertyName("metadata")] - public ConnectedRemoteSessionMetadata Metadata { get => field ??= new(); set; } + /// 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; } - /// SDK session ID for the connected remote session. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// The newly installed plugin's metadata. + [JsonPropertyName("plugin")] + public InstalledPluginInfo Plugin { get => field ??= new(); set; } + + /// Optional post-install message provided by the plugin (e.g. setup instructions). + [JsonPropertyName("postInstallMessage")] + public string? PostInstallMessage { get; set; } + + /// Number of skills discovered and installed from the plugin. + [JsonPropertyName("skillsInstalled")] + public long SkillsInstalled { get; set; } + + /// Where the completed plugin tree was staged before atomic promotion. + [JsonPropertyName("stagingMode")] + public PluginInstallStagingMode? StagingMode { get; set; } } -/// Remote session connection parameters. +/// Plugin source and optional working directory for relative-path resolution. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class ConnectRemoteSessionParams +internal sealed class PluginsInstallRequest { - /// Session ID to connect to. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// 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; + + /// 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; } } -/// Local or remote session metadata entry. Narrow on `isRemote` to access source-specific fields. -/// Data type discriminated by isRemote. +/// Name (or spec) of the plugin to uninstall. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class SessionListEntry +internal sealed class PluginsUninstallRequest { - /// The boolean discriminator. - [JsonPropertyName("isRemote")] - public bool IsRemote { get; set; } + /// 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; } - /// Runtime client name that created/last resumed this session. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("clientName")] - public string? ClientName { get; set; } + /// Plugin name or "plugin@marketplace" spec to uninstall. When ambiguous, prefer the fully-qualified spec. + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; +} - /// Pre-resolved working-directory context for session startup. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("context")] - public SessionContext? Context { get; set; } +/// Result of updating a single plugin. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public sealed class PluginUpdateResult +{ + /// Version after the update, when reported by the plugin manifest. + [JsonPropertyName("newVersion")] + public string? NewVersion { 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; } + /// Version that was previously installed, when available. + [JsonPropertyName("previousVersion")] + public string? PreviousVersion { 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. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("hostStatus")] - public RemoteSessionHostStatus? HostStatus { get; set; } + /// Number of skills discovered and installed after the update. + [JsonPropertyName("skillsInstalled")] + public long SkillsInstalled { 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; } +/// Name (or spec) of the plugin to update. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class PluginsUpdateRequest +{ + /// Plugin name or "plugin@marketplace" spec to update. + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; +} - /// 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; } +/// Per-plugin result from updating all plugins, with versions, skills installed, success flag, and optional error. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public sealed class PluginUpdateAllEntry +{ + /// Error message (failure only). + [JsonPropertyName("error")] + public string? Error { get; set; } - /// Last-modified time of the session's persisted state, as ISO 8601. - [JsonPropertyName("modifiedTime")] - public required string ModifiedTime { get; set; } + /// Marketplace the plugin came from. Empty string ("") for direct installs. + [JsonPropertyName("marketplace")] + public string Marketplace { get; set; } = string.Empty; - /// Optional human-friendly name set via /rename. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + /// Plugin name that was updated. [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; } + /// Version after the update, when available. + [JsonPropertyName("newVersion")] + public string? NewVersion { 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; } - - /// Session creation time as an ISO 8601 timestamp. - [JsonPropertyName("startTime")] - public required string StartTime { get; set; } - - /// Server-side task state returned by GitHub. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [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. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [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`. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("taskType")] - public RemoteSessionMetadataTaskType? TaskType { get; set; } + /// Whether the update succeeded for this plugin. + [JsonPropertyName("success")] + public bool Success { get; set; } } -/// Sessions matching the filter, ordered most-recently-modified first. +/// Result of updating all installed plugins. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class SessionList +public sealed class PluginUpdateAllResult { - /// Sessions ordered most-recently-modified first. Discriminated by `isRemote`. - [JsonPropertyName("sessions")] - public IList Sessions { get => field ??= []; set; } + /// Per-plugin update results in deterministic order. + [JsonPropertyName("results")] + public IList Results { get => field ??= []; set; } } -/// Optional filter applied to the returned sessions. +/// Plugin names (or specs) to enable, plus the optional working directory the repository-controlled guard is evaluated against. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class SessionListFilter +internal sealed class PluginsEnableRequest { - /// Match sessions whose context.branch equals this value. - [JsonPropertyName("branch")] - public string? Branch { get; set; } - - /// Match sessions whose context.cwd equals this value. - [JsonPropertyName("cwd")] - public string? Cwd { get; set; } - - /// Match sessions whose context.gitRoot equals this value. - [JsonPropertyName("gitRoot")] - public string? GitRoot { 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; } - /// Match sessions whose context.repository equals this value. - [JsonPropertyName("repository")] - public string? Repository { get; set; } + /// Working directory whose repository `enabledPlugins` overlay decides whether this mutation is repository-controlled. Hosts that serve sessions across several repositories (the SDK server) should pass the session's directory; otherwise the guard is evaluated against the server process's own working directory, which may belong to a different repository. Defaults to the server's current working directory. + [JsonPropertyName("workingDirectory")] + public string? WorkingDirectory { get; set; } } -/// Optional source filter, metadata-load limit, and context filter applied to the returned sessions. +/// Plugin names (or specs) to disable, plus the optional working directory the repository-controlled guard is evaluated against. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionsListRequest +internal sealed class PluginsDisableRequest { - /// Optional filter applied to the returned sessions. - [JsonPropertyName("filter")] - public SessionListFilter? Filter { get; set; } - - /// When true, include detached maintenance sessions. Defaults to false for user-facing session lists. - [JsonPropertyName("includeDetached")] - public bool? IncludeDetached { 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; } - - /// Which session sources to include. Defaults to `local` for backward compatibility. - [JsonPropertyName("source")] - public SessionSource? Source { get; set; } + /// 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; } - /// 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; } + /// Working directory whose repository `enabledPlugins` overlay decides whether this mutation is repository-controlled. Hosts that serve sessions across several repositories (the SDK server) should pass the session's directory; otherwise the guard is evaluated against the server process's own working directory, which may belong to a different repository. Defaults to the server's current working directory. + [JsonPropertyName("workingDirectory")] + public string? WorkingDirectory { get; set; } } -/// Persisted local session metadata, including identifiers, timestamps, summary/name, client, context, detached state, and task ID. +/// Trusted built-in plugin directories to use for this runtime process. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class LocalSessionMetadataValue +internal sealed class PluginsBuiltinSetRequest { - /// Runtime client name that created/last resumed this session. - [JsonPropertyName("clientName")] - public string? ClientName { get; set; } - - /// Pre-resolved working-directory context for session startup. - [JsonPropertyName("context")] - public SessionContext? Context { get; set; } - - /// True for detached maintenance sessions that should be hidden from normal resume lists. - [JsonPropertyName("isDetached")] - public bool? IsDetached { get; set; } + /// 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; } +} - /// Always false for local sessions. - [JsonPropertyName("isRemote")] - public bool IsRemote { get; set; } +/// Registered marketplace summary. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public sealed class MarketplaceInfo +{ + /// Whether the managed marketplace currently resolved into the runtime marketplace registry. Set to false when the desired managed entry is retained for governance visibility after loading or reconciliation failed. + [JsonPropertyName("available")] + public bool? Available { 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; } + /// True when this is a default marketplace shipped with the runtime. Defaults are not removable. + [JsonPropertyName("isDefault")] + public bool? IsDefault { get; set; } - /// Last-modified time of the session's persisted state, as ISO 8601. - [JsonPropertyName("modifiedTime")] - public string ModifiedTime { get; set; } = string.Empty; + /// Whether enterprise managed settings provide and control this marketplace entry. + [JsonPropertyName("managed")] + public bool? Managed { get; set; } - /// Optional human-friendly name set via /rename. + /// Marketplace name (matches the @marketplace suffix in plugin specs). [JsonPropertyName("name")] - public string? Name { get; set; } - - /// Stable session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; - - /// Session creation time as an ISO 8601 timestamp. - [JsonPropertyName("startTime")] - public string StartTime { get; set; } = string.Empty; + public string Name { get; set; } = string.Empty; - /// Short summary of the session, when one has been derived. - [JsonPropertyName("summary")] - public string? Summary { 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; } -/// Persisted local session metadata when the session exists. +/// All registered marketplaces, including built-in defaults. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionsGetMetadataResult +public sealed class MarketplaceListResult { - /// Local session metadata, omitted when the session does not exist. - [JsonPropertyName("session")] - public LocalSessionMetadataValue? Session { get; set; } + /// Registered marketplaces. + [JsonPropertyName("marketplaces")] + public IList Marketplaces { get => field ??= []; set; } } -/// Session ID whose persisted metadata should be read. +/// Result of registering a new marketplace. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionsGetMetadataRequest +public sealed class MarketplaceAddResult { - /// Session ID to inspect. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Final name of the marketplace as resolved from its manifest. + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; } -/// Client metadata outcome for one requested local session. -/// Polymorphic base type discriminated by status. +/// Marketplace source and optional working directory for relative-path resolution. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -[JsonPolymorphic( - TypeDiscriminatorPropertyName = "status", - UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] -[JsonDerivedType(typeof(SessionsClientMetadataEntryOk), "ok")] -[JsonDerivedType(typeof(SessionsClientMetadataEntryNotFound), "notFound")] -[JsonDerivedType(typeof(SessionsClientMetadataEntryCorrupt), "corrupt")] -[JsonDerivedType(typeof(SessionsClientMetadataEntryUnsupportedVersion), "unsupportedVersion")] -[JsonDerivedType(typeof(SessionsClientMetadataEntryUnavailable), "unavailable")] -public partial class SessionsClientMetadataEntry +internal sealed class PluginsMarketplacesAddRequest { - /// The type discriminator. - [JsonPropertyName("status")] - public virtual string Status { get; set; } = string.Empty; -} + /// 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; + /// 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; } +} -/// The ok variant of . +/// Outcome of the remove attempt, including dependent-plugin info when applicable. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class SessionsClientMetadataEntryOk : SessionsClientMetadataEntry +public sealed class MarketplaceRemoveResult { - /// - [JsonIgnore] - public override string Status => "ok"; - - /// Validated client metadata, possibly empty or projected to requested keys. - [JsonPropertyName("metadata")] - public required IDictionary Metadata { get; set; } + /// Names of installed plugins that prevented removal. Populated only when `removed=false`. + [JsonPropertyName("dependentPlugins")] + public IList? DependentPlugins { get; set; } - /// Requested session ID. - [JsonPropertyName("sessionId")] - public required string SessionId { 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; } } -/// The notFound variant of . +/// Name of the marketplace to remove and an optional force flag. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class SessionsClientMetadataEntryNotFound : SessionsClientMetadataEntry +internal sealed class PluginsMarketplacesRemoveRequest { - /// - [JsonIgnore] - public override string Status => "notFound"; + /// 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; } - /// Requested session ID. - [JsonPropertyName("sessionId")] - public required string SessionId { get; set; } + /// Marketplace name to remove. + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; } -/// The corrupt variant of . +/// Plugin entry advertised by a marketplace. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class SessionsClientMetadataEntryCorrupt : SessionsClientMetadataEntry +public sealed class MarketplacePluginInfo { - /// - [JsonIgnore] - public override string Status => "corrupt"; + /// Short description from the marketplace catalog, when present. + [JsonPropertyName("description")] + public string? Description { get; set; } - /// Requested session ID. - [JsonPropertyName("sessionId")] - public required string SessionId { get; set; } + /// Plugin name as listed in the marketplace catalog. + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; } -/// The unsupportedVersion variant of . +/// Plugins advertised by the marketplace. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class SessionsClientMetadataEntryUnsupportedVersion : SessionsClientMetadataEntry +public sealed class MarketplaceBrowseResult { - /// - [JsonIgnore] - public override string Status => "unsupportedVersion"; - - /// Requested session ID. - [JsonPropertyName("sessionId")] - public required string SessionId { get; set; } + /// Plugins advertised by the marketplace. + [JsonPropertyName("plugins")] + public IList Plugins { get => field ??= []; set; } } -/// The unavailable variant of . +/// Name of the marketplace whose plugin catalog to fetch. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class SessionsClientMetadataEntryUnavailable : SessionsClientMetadataEntry +internal sealed class PluginsMarketplacesBrowseRequest { - /// - [JsonIgnore] - public override string Status => "unavailable"; + /// Marketplace name to browse. + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; +} - /// Filesystem or provider error code. Clients should not assume every provider uses operating-system error codes. - [JsonPropertyName("code")] - public required string Code { get; set; } +/// Per-marketplace refresh result, including marketplace name, success flag, and optional failure error. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public sealed class MarketplaceRefreshEntry +{ + /// Error message (failure only). + [JsonPropertyName("error")] + public string? Error { get; set; } - /// Human-readable diagnostic message. Not stable for programmatic matching. - [JsonPropertyName("message")] - public required string Message { get; set; } + /// Marketplace name that was refreshed. + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; - /// Requested session ID. - [JsonPropertyName("sessionId")] - public required string SessionId { get; set; } + /// Whether the refresh succeeded. + [JsonPropertyName("success")] + public bool Success { get; set; } } -/// Bounded batch request for client-owned metadata from persisted local sessions. +/// Result of refreshing one or more marketplace catalogs. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionsGetClientMetadataRequest +public sealed class MarketplaceRefreshResult { - /// Case-sensitive keys to project from each valid bag. Each key must be non-empty, at most 256 UTF-8 bytes, and outside the reserved `copilot/` and `github/` namespaces. Omit to return every entry. - [JsonPropertyName("keys")] - public IList? Keys { get; set; } + /// Per-marketplace refresh results in deterministic order. + [JsonPropertyName("results")] + public IList Results { get => field ??= []; set; } +} - /// Session IDs to inspect. Results preserve this order. - [JsonPropertyName("sessionIds")] - public IList SessionIds { get => field ??= []; set; } +/// RPC data type for PluginsMarketplacesRefresh operations. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class PluginsMarketplacesRefreshRequest +{ + /// Marketplace name to refresh. When omitted, every registered marketplace is refreshed. + [JsonPropertyName("name")] + public string? Name { get; set; } } -/// Batch of session events returned by a read, with cursor and continuation metadata. +/// Agent metadata, including identifiers, display details, source, tools, model, models, reasoning effort, MCP servers, skills, and file path. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class EventsReadResult +public sealed class AgentInfo { - /// 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; + /// Description of the agent's purpose. + [JsonPropertyName("description")] + public string Description { get; set; } = string.Empty; - /// Cursor status: 'ok' means the cursor was applied successfully. For session.eventLog.read, 'expired' means the cursor referred to an event that no longer exists in active history and the read fell back to a boundary of the remaining history: the beginning for a forward read or the newest window for a backward read. That fallback may overlap already rendered events, so active-session consumers should reset, rebase, or deduplicate before continuing. sessions.readPersistedEvents has stricter snapshot semantics: 'expired' returns an empty terminal page and never switches to a replacement journal generation. Other persisted-read I/O failures are RPC errors with diagnostics, not cursor expiry. - [JsonPropertyName("cursorStatus")] - public EventsCursorStatus CursorStatus { get; set; } + /// Whether model-driven invocation is disabled for this agent. + [JsonPropertyName("disableModelInvocation")] + public bool? DisableModelInvocation { 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; } + /// Human-readable display name. + [JsonPropertyName("displayName")] + public string DisplayName { get; set; } = string.Empty; - /// True when more events are available in the read's direction. For a backward read, true means older persisted events remain before the returned window. A persisted-event page may contain fewer than `max` events because of its byte budget while still reporting hasMore true; continue according to this flag rather than the event count. - [JsonPropertyName("hasMore")] - public bool HasMore { get; set; } -} + /// Declarative hook actions keyed by event name, executed only while this agent is active. + [JsonPropertyName("hooks")] + public IDictionary>? Hooks { get; set; } -/// Pagination options for reading an inactive or active local session's persisted event journal. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionsReadPersistedEventsRequest -{ - /// Opaque, process-local, single-use cursor returned by the previous persisted-event read. Omit on the first call and issue continuations sequentially; reusing the same cursor returns an expired terminal page. - [JsonPropertyName("cursor")] - public string? Cursor { 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; - /// Direction to page through persisted history. Forward starts at the beginning; backward starts with the newest events. Events in each page remain chronological. This selects the initial read only; a continuation always uses the direction bound into its cursor. - [JsonPropertyName("direction")] - public EventsReadDirection? Direction { get; set; } + /// MCP server configurations attached to this agent, keyed by server name. Server config shape mirrors the MCP `mcpServers` schema. + [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] + [JsonPropertyName("mcpServers")] + public IDictionary? McpServers { get; set; } - /// Maximum number of events to return in this batch (1–1000, default 200). Pages may contain fewer events to keep the serialized event array within a soft 1 MiB budget including resolved binary assets; one oversized event is returned alone to guarantee progress. - [JsonPropertyName("max")] - public long? Max { 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; } - /// Session ID whose persisted event journal should be read. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; -} + /// Whether authored models are preferences or required constraints. + [JsonPropertyName("modelPolicy")] + public AgentModelPolicy? ModelPolicy { get; set; } -/// Recent local session IDs that contain user-visible history. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionsListNonEmptySessionIdsResult -{ - /// Session IDs ordered newest-first. - [JsonPropertyName("sessionIds")] - public IList SessionIds { get => field ??= []; set; } -} + /// 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; } -/// Limit for non-empty local session IDs. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionsListNonEmptySessionIdsRequest -{ - /// Maximum number of session IDs to return. - [JsonPropertyName("limit")] - public long? Limit { get; set; } + /// Name of the agent. Use `id` as the stable selection identifier. + [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. + [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; } + + /// Authored reasoning effort for this agent. Applied on selection to models that support it; omitted means no authored preference. + [JsonPropertyName("reasoningEffort")] + public string? ReasoningEffort { 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; } } -/// ID of the local session bound to the given GitHub task, or omitted when none. +/// Agents discovered across user, project, plugin, and remote sources. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class SessionsFindByTaskIDResult +public sealed class ServerAgentList { - /// Omitted when no local session is bound to that GitHub task. - [JsonPropertyName("sessionId")] - public string? SessionId { get; set; } + /// All discovered agents across all sources. + [JsonPropertyName("agents")] + public IList Agents { get => field ??= []; set; } } -/// GitHub task ID to look up. +/// Optional project paths to include in agent discovery. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionsFindByTaskIDRequest +public sealed class AgentsDiscoverRequest { - /// GitHub task ID to look up. - [JsonPropertyName("taskId")] - public string TaskId { get; set; } = string.Empty; + /// Explicit discovery-only agent roots, separate from customAgents and filesystem grants. Available without ambient discovery, subject to managed/source disables. Relative paths resolve against the request's effective working directory when supplied. Null and omission are equivalent; an empty array contributes no explicit roots. + [JsonPropertyName("agentDirectories")] + public IList? AgentDirectories { get; set; } + + /// 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; } } -/// Session ID matching the prefix, omitted when no unique match exists. +/// Canonical directory where custom agents can be discovered or created, with scope, preference, and optional project path. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class SessionsFindByPrefixResult +public sealed class AgentDiscoveryPath { - /// Omitted when no unique session matches the prefix (no match or ambiguous). - [JsonPropertyName("sessionId")] - public string? SessionId { get; set; } + /// Absolute path of the search/create directory (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 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; } + + /// Which tier this directory belongs to. + [JsonPropertyName("scope")] + public AgentDiscoveryPathScope Scope { get; set; } } -/// UUID prefix to resolve to a unique session ID. +/// Canonical locations where custom agents can be created so the runtime will recognize them. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionsFindByPrefixRequest +public sealed class AgentDiscoveryPathList { - /// 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; + /// Canonical agent create/discovery directories, in priority order. + [JsonPropertyName("paths")] + public IList Paths { get => field ??= []; set; } } -/// Most-relevant session ID for the supplied context, or omitted when no sessions exist. +/// Optional project paths to include when enumerating agent discovery directories. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class SessionsGetLastForContextResult +public sealed class AgentsGetDiscoveryPathsRequest { - /// Most-relevant session ID for the supplied context, or omitted when no sessions exist. - [JsonPropertyName("sessionId")] - public string? SessionId { get; set; } + /// Explicit discovery-only agent roots to enumerate, including configured missing directories. Relative paths resolve against the request's effective working directory when supplied. These are not filesystem grants or preferred creation targets. Null and omission are equivalent. + [JsonPropertyName("agentDirectories")] + public IList? AgentDirectories { get; set; } + + /// 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; } + + /// 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. +/// The agents this runtime ships, named so a consumer can tell them apart from authored ones. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionsGetLastForContextRequest +internal sealed class AgentsGetBuiltinsResult { - /// Optional working-directory context used to score session relevance. When omitted the most-recently-modified session wins. - [JsonPropertyName("context")] - public SessionContext? Context { get; set; } -} + /// The subset of `names` a user is allowed to turn off. A shipped agent outside this list is always active and a client should not offer a toggle for it. + [JsonPropertyName("disableableNames")] + public IList DisableableNames { get => field ??= []; set; } -/// Absolute path to the session's events.jsonl file on disk. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionsGetEventFilePathResult -{ - /// Absolute path to the session's events.jsonl file. - [JsonPropertyName("filePath")] - public string FilePath { get; set; } = string.Empty; -} + /// Every agent name this runtime ships. + [JsonPropertyName("names")] + public IList Names { get => field ??= []; set; } -/// Session ID whose event-log file path to compute. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionsGetEventFilePathRequest -{ - /// Session ID whose event-log file path to compute. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// The subset of `names` defined by a shipped YAML definition. The remainder are special-cased in code and have no definition to load. + [JsonPropertyName("yamlBasedNames")] + public IList YamlBasedNames { get => field ??= []; set; } } -/// Map of sessionId -> on-disk size in bytes for each session's workspace directory. +/// A shipped agent, named and described. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class SessionSizes +internal sealed class BuiltinAgentSummary { - /// Map of sessionId -> on-disk size in bytes for the session's workspace directory. - [JsonPropertyName("sizes")] - public IDictionary Sizes { get => field ??= new Dictionary(); set; } -} + /// One-line description of what the agent does. + [JsonPropertyName("description")] + public string Description { get; set; } = string.Empty; -/// Session IDs from the input set that are currently in use by another process. -[Experimental(global::GitHub.Copilot.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; } + /// The agent name, as it appears in `getBuiltins`. + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; } -/// Session IDs to test for live in-use locks. +/// The shipped agents available under the requested flags. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionsCheckInUseRequest +internal sealed class AgentsGetAvailableBuiltinsResult { - /// Session IDs to test for live in-use locks. - [JsonPropertyName("sessionIds")] - public IList SessionIds { get => field ??= []; set; } + /// Available shipped agents, in the runtime's own order. + [JsonPropertyName("agents")] + public IList Agents { get => field ??= []; set; } } -/// The session's persisted remote-steerable flag, or omitted when no value has been persisted. +/// The feature flags to evaluate shipped agents against. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionsGetPersistedRemoteSteerableResult +internal sealed class AgentsGetAvailableBuiltinsRequest { - /// The session's persisted remote-steerable flag if recorded; omitted when no value has been persisted. - [JsonPropertyName("remoteSteerable")] - public bool? RemoteSteerable { get; set; } + /// The surface asking, which gates agents that only apply to one client. Omit or pass null to apply no client filter. + [JsonPropertyName("context")] + public string? Context { get; set; } + + /// Feature flag values keyed by name, evaluated with the runtime's truthiness rules. Omit or pass null for no flags. + [JsonPropertyName("featureFlags")] + public IDictionary? FeatureFlags { get; set; } + + /// Flag overrides keyed by name. A null entry uses the corresponding base flag; false explicitly disables it. Omit or pass null for no overrides. + [JsonPropertyName("overrides")] + public IDictionary? Overrides { get; set; } } -/// Session ID to look up the persisted remote-steerable flag for. +/// One shipped agent's definition. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionsGetPersistedRemoteSteerableRequest +internal sealed class AgentsGetBuiltinDefinitionResult { - /// Session ID to look up the persisted remote-steerable flag for. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// The agent's definition, serialized as JSON. It carries the authored keys plus the runtime's projected `__nativeCustomAgent` view of the same agent. It is a string rather than an object because the runtime parses it with the agent schema's tolerant shape, which accepts keys this contract does not name. + [JsonPropertyName("definitionJson")] + public string DefinitionJson { get; set; } = string.Empty; } -/// 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. +/// The shipped agent whose definition to load. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class SessionsCloseResult +internal sealed class AgentsGetBuiltinDefinitionRequest { + /// The agent name, which must be one of `getBuiltins`'s `yamlBasedNames`. A name outside that list is special-cased in code and has no definition, and is reported as an error rather than as an empty definition. + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; } -/// Session ID to close. +/// One shipped agent, projected for a listing. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionsCloseRequest +internal sealed class AgentsGetBuiltinListingDefinitionResult { - /// Session ID to close. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// The agent projected as a custom agent, serialized as JSON. It is a string rather than an object for the same reason as `getBuiltinDefinition`: the runtime parses the underlying definition with the agent schema's tolerant shape, which accepts keys this contract does not name. + [JsonPropertyName("definitionJson")] + public string DefinitionJson { get; set; } = string.Empty; } -/// Map of sessionId -> bytes freed by removing the session's workspace directory. +/// The shipped agent whose listing entry to load. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class SessionBulkDeleteResult +internal sealed class AgentsGetBuiltinListingDefinitionRequest { - /// 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; } + /// The agent name, taken from `getAvailableBuiltins`. Unlike `getBuiltinDefinition`, the agent that `getBuiltins` reports as special-cased rather than YAML-based is answered here too, from its in-code definition. + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; } -/// Session IDs to close, deactivate, and delete from disk. +/// The model to switch to, and the warning to show when the agent's preference could not be met. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionsBulkDeleteRequest +internal sealed class AgentsCustomAgentInitialModelDecisionResult { - /// Session IDs to close, deactivate, and delete from disk. - [JsonPropertyName("sessionIds")] - public IList SessionIds { get => field ??= []; set; } + /// The reasoning effort attached to the selected model preference. Absent when that preference does not specify an effort. + [JsonPropertyName("reasoningEffort")] + public string? ReasoningEffort { get; set; } + + /// The first available model that matches the agent's preferences. Absent when none of the requested models is available. + [JsonPropertyName("targetModel")] + public string? TargetModel { get; set; } + + /// What to tell the user about an unmet preference. Absent when the preference was met. A warning with no `targetModel` means the agent's models are all unavailable. + [JsonPropertyName("warning")] + public string? Warning { get; set; } } -/// Session ID to delete from disk. +/// The models a custom agent asks for, and the models actually available. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionsDeleteRequest +internal sealed class AgentsCustomAgentInitialModelDecisionParams { - /// Session ID to delete. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// The agent's declared `model:` entry, serialized. A single name or an ordered list of acceptable names. + [JsonPropertyName("agentModelsJson")] + public string AgentModelsJson { get; set; } = string.Empty; - /// Internal resolved session directory path to delete. - [JsonPropertyName("sessionPath")] - public string? SessionPath { get; set; } + /// The models available to this session, serialized in the shape the model list carries. + [JsonPropertyName("availableModelsJson")] + public string AvailableModelsJson { get; set; } = string.Empty; } -/// Outcome of the prune operation: deleted IDs, dry-run candidates, skipped IDs, total bytes freed, and the dry-run flag. +/// Installed plugin record from global state, with marketplace, version, install time, enabled state, cache path, and source. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class SessionPruneResult +public sealed class InstalledPlugin { - /// Session IDs that would be deleted in dry-run mode (always empty otherwise). - [JsonPropertyName("candidates")] - public IList Candidates { get => field ??= []; set; } + /// Path where the plugin is cached locally. + [JsonPropertyName("cache_path")] + public string? CachePath { get; set; } - /// Session IDs that were deleted (always empty in dry-run mode). - [JsonPropertyName("deleted")] - public IList Deleted { get => field ??= []; set; } + /// Whether the plugin is currently enabled. + [JsonPropertyName("enabled")] + public bool Enabled { get; set; } - /// True when no deletions were actually performed. - [JsonPropertyName("dryRun")] - public bool DryRun { get; set; } + /// Installation timestamp. + [JsonPropertyName("installed_at")] + public string InstalledAt { get; set; } = string.Empty; - /// Total bytes freed (actual when not dry-run, projected when dry-run). - [JsonPropertyName("freedBytes")] - public long FreedBytes { 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; } - /// Session IDs that were skipped (e.g., named sessions). - [JsonPropertyName("skipped")] - public IList Skipped { get => field ??= []; set; } -} + /// Marketplace the plugin came from (empty string for direct repo installs). + [JsonPropertyName("marketplace")] + public string Marketplace { get; set; } = string.Empty; -/// Age threshold and optional flags controlling which old sessions are pruned (or simulated when dryRun is true). -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionsPruneOldRequest -{ - /// When true, only report what would be deleted without performing any deletion. - [JsonPropertyName("dryRun")] - public bool? DryRun { get; set; } + /// Plugin name. + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; - /// Session IDs that should never be considered for pruning. - [JsonPropertyName("excludeSessionIds")] - public IList? ExcludeSessionIds { get; set; } + /// Source for direct repo installs (when marketplace is empty). + [JsonPropertyName("source")] + public JsonElement? Source { get; set; } - /// When true, named sessions (set via /rename) are also eligible for pruning. - [JsonPropertyName("includeNamed")] - public bool? IncludeNamed { 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; } - /// Delete sessions whose modifiedTime is at least this many days old. - [JsonPropertyName("olderThanDays")] - public long OlderThanDays { get; set; } + /// Version installed (if available). + [JsonPropertyName("version")] + public string? Version { get; set; } } -/// Flush a session's pending events to disk. No-op when no writer exists for the session (e.g., already closed). +/// An account the host has signed in to, identified by the server it lives on and the login it uses there. The same person can appear more than once when they use both github.com and an Enterprise server. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class SessionsSaveResult +internal sealed class LoggedInUser { -} + /// Source account this account was derived from, when one was recorded. + [JsonPropertyName("derivedFrom")] + public string? DerivedFrom { get; set; } -/// Session ID whose pending events should be flushed to disk. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionsSaveRequest -{ - /// Session ID whose pending events should be flushed to disk. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Host the account belongs to, such as `github.com` or an Enterprise server. + [JsonPropertyName("host")] + public string Host { get; set; } = string.Empty; + + /// Account kind, when the host recorded one. Consumers must tolerate new strings. + [JsonPropertyName("kind")] + public string? Kind { get; set; } + + /// Account login on that host. + [JsonPropertyName("login")] + public string Login { get; set; } = string.Empty; } -/// Release the in-use lock held by this process for the given session. No-op when this process does not currently hold a lock for the session. +/// The host's machine-wide state. Every field is optional because a fresh install has recorded nothing yet, so a reader must treat an absent field as `not yet`, never as a negative answer. Stored credentials are deliberately absent from this shape. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class SessionsReleaseLockResult +internal sealed class GlobalStateLoadResult { + /// Whether the app tip has been shown. + [JsonPropertyName("appTipShown")] + public bool? AppTipShown { get; set; } + + /// Terminals the user has already been asked to set up, so the host does not ask twice. + [JsonPropertyName("askedSetupTerminals")] + public IList? AskedSetupTerminals { get; set; } + + /// When the Auto-feedback hint was last shown, as an ISO 8601 timestamp. It enforces the once-per-day cap for non-staff users across restarts. + [JsonPropertyName("autoFeedbackLastPromptedAt")] + public string? AutoFeedbackLastPromptedAt { get; set; } + + /// When the host first ran on this machine. + [JsonPropertyName("firstLaunchAt")] + public string? FirstLaunchAt { get; set; } + + /// Plugins installed on this machine. + [JsonPropertyName("installedPlugins")] + public IList? InstalledPlugins { get; set; } + + /// Account used for the most recent sign-in. + [JsonPropertyName("lastLoggedInUser")] + public LoggedInUser? LastLoggedInUser { get; set; } + + /// Every account the host has signed in to on this machine. + [JsonPropertyName("loggedInUsers")] + public IList? LoggedInUsers { get; set; } + + /// Whether the one-off cleanup of stored reasoning summaries has run. + [JsonPropertyName("reasoningSummariesCleanupDone")] + public bool? ReasoningSummariesCleanupDone { get; set; } + + /// Models the user selected recently, most recent first. + [JsonPropertyName("recentModelIds")] + public IList? RecentModelIds { get; set; } + + /// Whether the user declined to trust the sandbox credential proxy CA. + [JsonPropertyName("sandboxCredentialProxyCaDeclined")] + public bool? SandboxCredentialProxyCaDeclined { get; set; } + + /// Whether the sandbox onboarding has been shown. + [JsonPropertyName("sandboxOnboardingShown")] + public bool? SandboxOnboardingShown { get; set; } + + /// Whether the user is a GitHub or Microsoft staff member, which unlocks internal-only behavior. + [JsonPropertyName("staff")] + public bool? Staff { get; set; } + + /// Whether the user was recognized as GitHub staff. + [JsonPropertyName("staffGithub")] + public bool? StaffGitHub { get; set; } + + /// When the staff-only log level migration last ran. + [JsonPropertyName("staffLogLevelMigrationAt")] + public string? StaffLogLevelMigrationAt { get; set; } + + /// Whether the user was recognized as Microsoft staff. + [JsonPropertyName("staffMicrosoft")] + public bool? StaffMicrosoft { get; set; } + + /// When the staff-only model reset last ran. + [JsonPropertyName("staffModelResetAt")] + public string? StaffModelResetAt { get; set; } + + /// When the staff-only update channel migration last ran. + [JsonPropertyName("staffUpdateChannelMigrationAt")] + public string? StaffUpdateChannelMigrationAt { get; set; } + + /// Folders where the user declined the init prompt, so it stays hidden there. + [JsonPropertyName("suppressInitFolders")] + public IList? SuppressInitFolders { get; set; } + + /// Folders the user has marked as trusted. + [JsonPropertyName("trustedFolders")] + public IList? TrustedFolders { get; set; } } -/// Session ID whose in-use lock should be released. +/// Selects the configuration directory whose machine-wide state to read. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionsReleaseLockRequest +internal sealed class GlobalStateLoadForConfigDirRequest { - /// Session ID whose in-use lock should be released. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Copilot configuration directory to read the state document from, taking precedence over the server's own `COPILOT_HOME` and default home. Omit it, or pass an empty string, to read the directory the server resolved for itself. + [JsonPropertyName("configDir")] + public string? ConfigDir { get; set; } } -/// The enriched metadata records, with summary and context fields backfilled where available. Sessions confirmed empty and unnamed are omitted. +/// A single top-level key to record in the host's machine-wide state. The write replaces only that key and leaves the rest of the document untouched, so two writers recording different one-off flags do not overwrite each other. The stored credential keys cannot be written through this method. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class SessionEnrichMetadataResult +internal sealed class GlobalStateWriteKeyRequest { - /// Enriched records, with summary and context backfilled. Sessions confirmed empty and unnamed may be omitted. - [JsonPropertyName("sessions")] - public IList Sessions { get => field ??= []; set; } + /// Copilot configuration directory to write the state document in, taking precedence over the server's own `COPILOT_HOME` and default home. Omit it, or pass an empty string, to write the directory the server resolved for itself. Mirrors `globalState.loadForConfigDir`, so a caller can read and write the same directory. + [JsonPropertyName("configDir")] + public string? ConfigDir { get; set; } + + /// Top-level key to write, named as it appears in the result of `globalState.load`. It must be one of the writable keys that `globalState.writeKey` lists. + [JsonPropertyName("key")] + public string Key { get; set; } = string.Empty; + + /// Value to store for the key. Omit it, or pass null, to remove the key instead. + [JsonPropertyName("value")] + public JsonElement? Value { get; set; } } -/// Session metadata records to enrich with summary and context information. +/// A literal choice the command input accepts, with a human-facing description. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionsEnrichMetadataRequest +public sealed class SlashCommandInputChoice { - /// Session metadata records to enrich. Records that already have summary and context are returned unchanged. - [JsonPropertyName("sessions")] - public IList Sessions { get => field ??= []; set; } + /// Human-readable description shown alongside the choice. + [JsonPropertyName("description")] + public string Description { get; set; } = string.Empty; + + /// The literal choice value (e.g. 'on', 'off', 'show'). + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; } -/// The workspace record that was written. +/// Optional unstructured input hint. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionsCreateWorkspaceResult +public sealed class SlashCommandInput { - /// The created workspace record, as JSON. - [JsonPropertyName("workspaceJson")] - public string WorkspaceJson { get; set; } = string.Empty; + /// 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; } + + /// Optional completion hint for the input (e.g. 'directory' for filesystem path completion). + [JsonPropertyName("completion")] + public SlashCommandInputCompletion? Completion { get; set; } + + /// Hint to display when command input has not been provided. + [JsonPropertyName("hint")] + public string Hint { get; set; } = string.Empty; + + /// 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; } } -/// A working-directory context together with the client that produced it. +/// Slash-command metadata with name, aliases, description, kind, input hint, execution allowance, and schedulability. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class SessionWorkingDirectoryContextWithClient +public sealed class SlashCommandInfo { - /// Merge-base commit SHA. - [JsonPropertyName("baseCommit")] - public string? BaseCommit { get; set; } - - /// Current git branch name. - [JsonPropertyName("branch")] - public string? Branch { get; set; } + /// Canonical aliases without leading slashes. + [JsonPropertyName("aliases")] + public IList? Aliases { get; set; } - /// Name of the client that created the session. - [JsonPropertyName("clientName")] - public string? ClientName { get; set; } + /// Whether the command may run while an agent turn is active. + [JsonPropertyName("allowDuringAgentExecution")] + public bool AllowDuringAgentExecution { get; set; } - /// Current working directory path. - [JsonPropertyName("cwd")] - public string Cwd { get; set; } = string.Empty; + /// Human-readable command description. + [JsonPropertyName("description")] + public string Description { get; set; } = string.Empty; - /// Root directory of the git repository. - [JsonPropertyName("gitRoot")] - public string? GitRoot { get; set; } + /// Whether the command is experimental. + [JsonPropertyName("experimental")] + public bool? Experimental { get; set; } - /// Head commit of the current git branch. - [JsonPropertyName("headCommit")] - public string? HeadCommit { get; set; } + /// Optional unstructured input hint. + [JsonPropertyName("input")] + public SlashCommandInput? Input { get; set; } - /// Hosting platform type of the repository. - [JsonPropertyName("hostType")] - public string? HostType { 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; } - /// Repository identifier derived from the git remote URL. - [JsonPropertyName("repository")] - public string? Repository { get; set; } + /// Canonical command name without a leading slash. + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; - /// Raw host string from the git remote URL. - [JsonPropertyName("repositoryHost")] - public string? RepositoryHost { 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; } } -/// Identity, state location and starting context for a workspace record. +/// Slash commands available in the session, after applying any include/exclude filters. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionsCreateWorkspaceRequest +public sealed class CommandList { - /// Starting working-directory context. The record keeps `cwd`, `gitRoot`, `repository`, `hostType`, `branch`, and `clientName`. Other fields, including `repositoryHost`, `headCommit`, and `baseCommit`, are ignored. `hostType` must be `github` or `ado`. - [JsonPropertyName("context")] - public SessionWorkingDirectoryContextWithClient? Context { get; set; } + /// Commands available in this session. + [JsonPropertyName("commands")] + public IList Commands { get => field ??= []; set; } +} - /// `windows` (any letter case) selects Windows path rules. Any other value selects POSIX path rules. - [JsonPropertyName("convention")] - public string Convention { get; set; } = string.Empty; +/// Owner, name, and host of a GitHub repository, as resolved from a git remote URL. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public sealed class GitHubRepositoryIdentity +{ + /// Host the remote points at, for example `github.com` or a GitHub Enterprise hostname. + [JsonPropertyName("host")] + public string Host { get; set; } = string.Empty; - /// User-supplied display name for the workspace. + /// Repository name, without the owner prefix or the `.git` suffix. [JsonPropertyName("name")] - public string? Name { get; set; } - - /// Session ID the workspace record belongs to. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + public string Name { get; set; } = string.Empty; - /// Directory the session's state is written under when no session filesystem provider is configured. Ignored when a provider is configured; the provider's session state path is used instead. - [JsonPropertyName("sessionStatePath")] - public string SessionStatePath { get; set; } = string.Empty; + /// Repository owner login (user or organization). + [JsonPropertyName("owner")] + public string Owner { get; set; } = string.Empty; } -/// The workspace record on disk, omitted when the session has none. +/// The GitHub repository that owns the requested path, when the selected remote (`origin`, else the first) is on a GitHub host. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionsLoadWorkspaceResult +internal sealed class GitHubRepositoryAtPathResult { - /// The workspace record, as JSON. Omitted when the record does not exist. - [JsonPropertyName("workspaceJson")] - public string? WorkspaceJson { get; set; } + /// Resolved repository identity, or null when the selected remote resolves to no GitHub host. + [JsonPropertyName("repository")] + public GitHubRepositoryIdentity? Repository { get; set; } } -/// Where the session's state lives, as a root directory and the session ID under it. +/// Working-tree path whose owning GitHub repository should be resolved. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionsLoadWorkspaceRequest +internal sealed class GitHubRepositoryAtPathRequest { - /// Session ID naming the state directory under the sessions home. Rejected when it is absolute or contains a parent component, so it cannot escape the sessions home. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; - - /// Root directory every session's state directory sits under. - [JsonPropertyName("sessionsHome")] - public string SessionsHome { get; set; } = string.Empty; + /// Absolute path to a directory inside the git working tree to resolve. + [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("path")] + public string Path { get; set; } = string.Empty; } -/// The merge completed. The record carries the supplied workspace-schema fields, but a stored `fork_count` stays. +/// A freshly registered request id. Registering it before the listing starts is what lets a cancel that races the request still find the owner listing slot. The id serves one listing only. Long-abandoned unused ids can be released by later allocations. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionsUpdateWorkspaceFieldsResult +internal sealed class GitHubOwnersRequestIdResult { + /// Request id to pass to `gitHubOwners.list` and, to abandon it, `gitHubOwners.cancel`. + [JsonPropertyName("requestId")] + public long RequestId { get; set; } } -/// Where the session's state lives, plus workspace-schema fields to merge into its workspace record. Stored keys outside the schema are not preserved, and a stored `fork_count` is never replaced. +/// A GitHub login the authenticated user may act as: their own account, or an organization they belong to. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionsUpdateWorkspaceFieldsRequest +internal sealed class GitHubOwnerOption { - /// Workspace-schema fields to merge into the record, as a JSON object. Fields the object omits keep their stored values, except stored keys outside the schema are not preserved and a stored `fork_count` is never replaced. - [JsonPropertyName("fieldsJson")] - public string FieldsJson { get; set; } = string.Empty; - - /// Session ID naming the state directory under the sessions home. Rejected when it is absolute or contains a parent component, so it cannot escape the sessions home. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; - - /// Root directory every session's state directory sits under. - [JsonPropertyName("sessionsHome")] - public string SessionsHome { get; set; } = string.Empty; -} + /// The owner's GitHub login. + [JsonPropertyName("login")] + public string Login { get; set; } = string.Empty; -/// 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(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class SessionsReloadPluginHooksResult -{ + /// Which kind of owner this is. The authenticated user's own account is always reported as `user`. + [JsonPropertyName("type")] + public string Type { get; set; } = string.Empty; } -/// Active session ID and an optional flag for deferring repo-level hooks until folder trust. +/// Outcome of an owner listing. Exactly one of `owners` and `message` is present, except that `throwError` reports a failure the caller is expected to raise rather than render. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionsReloadPluginHooksRequest +internal sealed class GitHubOwnersListResult { - /// When true, skip repo-level hooks. Use before folder trust is confirmed; loadDeferredRepoHooks loads them post-trust. - [JsonPropertyName("deferRepoHooks")] - public bool? DeferRepoHooks { get; set; } + /// Why no owners could be listed, phrased for a user. Present when the listing failed in a way the caller should render rather than raise. + [JsonPropertyName("message")] + public string? Message { get; set; } - /// Active session ID to reload hooks for. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; -} + /// The owners, on success: the authenticated user first, then the organizations they belong to. + [JsonPropertyName("owners")] + public IList? Owners { get; set; } -/// Queued repo-level startup prompts and the total hook command count after loading. -[Experimental(global::GitHub.Copilot.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; } + /// A malformed request or an unreadable credential, which the caller raises instead of rendering. Kept a field rather than a dispatch error so it stays distinct from `message`, which the caller renders. + [JsonPropertyName("throwError")] + public string? ThrowError { 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; } + /// A line the caller should log. Present only alongside `message`, and only for failures worth recording. + [JsonPropertyName("warning")] + public string? Warning { get; set; } } -/// Active session ID whose deferred repo-level hooks should be loaded. +/// Credential to list owners under, and the request id that makes the listing cancellable. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionsLoadDeferredRepoHooksRequest +internal sealed class GitHubOwnersListRequest { - /// Active session ID whose deferred repo-level hooks should be loaded. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; -} + /// The credential the listing runs under, carried opaquely because its shape is the host's own and the runtime only resolves a token and a GitHub host from it. No credential travels: this selects one the runtime already holds. + [JsonPropertyName("authInfo")] + public JsonElement AuthInfo { 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. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class SessionsSetAdditionalPluginsResult -{ + /// Request id from `gitHubOwners.nextRequestId`. An id that was never registered, canceled before use, released after being abandoned, or already used is refused rather than silently running uncancellable. + [JsonPropertyName("requestId")] + public long RequestId { get; set; } } -/// Manager-wide additional plugins to register; replaces any previously-configured set. +/// Whether the id named a running owner listing. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionsSetAdditionalPluginsRequest +internal sealed class GitHubOwnersCancelResult { - /// 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 a listing with the id was running and the cancel stopped it. False when the id was never registered, was registered but unused, was released after being abandoned, or its listing had ended. An unused id is released and cannot start a later listing. + [JsonPropertyName("canceled")] + public bool Canceled { get; set; } } -/// Dynamic-context board entry count, when available. +/// The owner listing to abandon. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionsGetBoardEntryCountResult +internal sealed class GitHubOwnersCancelRequest { - /// Board entry count, when available. - [JsonPropertyName("count")] - public long? Count { get; set; } + /// Request id the listing was started with. + [JsonPropertyName("requestId")] + public long RequestId { get; set; } } -/// Session ID whose board entry count should be returned. +/// The remote the checked-out branch tracks. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionsGetBoardEntryCountRequest +internal sealed class GitCurrentBranchRemoteResult { - /// Session ID whose board entry count should be returned. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Name of the tracked remote. Reports `origin` whenever the working tree has no tracking configuration to read, including on a detached HEAD, so this is never null and never empty. + [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("remote")] + public string Remote { get; set; } = string.Empty; } -/// State of the runtime-managed remote-control singleton. -/// Polymorphic base type discriminated by state. +/// Working-tree path a git query applies to. [Experimental(global::GitHub.Copilot.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 +internal sealed class GitCwdRequest { - /// The type discriminator. - [JsonPropertyName("state")] - public virtual string State { get; set; } = string.Empty; + /// Absolute path to a directory inside the git working tree to query. + [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("cwd")] + public string Cwd { get; set; } = string.Empty; } - -/// Remote control is not connected. -/// The off variant of . +/// Updated working directory and git context. Emitted as the new payload of `session.context_changed`. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class RemoteControlStatusOff : RemoteControlStatus +public sealed class SessionWorkingDirectoryContext { - /// - [JsonIgnore] - public override string State => "off"; -} + /// Merge-base commit SHA (fork point from the remote default branch). + [JsonPropertyName("baseCommit")] + public string? BaseCommit { get; set; } -/// Remote control is in the middle of initial setup. -/// The connecting variant of . -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class RemoteControlStatusConnecting : RemoteControlStatus -{ - /// - [JsonIgnore] - public override string State => "connecting"; + /// Current git branch name. + [JsonPropertyName("branch")] + public string? Branch { get; set; } - /// Session id the connection is attaching to. - [JsonPropertyName("attachedSessionId")] - public required string AttachedSessionId { get; set; } -} + /// Current working directory path. + [JsonPropertyName("cwd")] + public string Cwd { get; set; } = string.Empty; -/// Remote control is connected to a local session. -/// The active variant of . -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class RemoteControlStatusActive : RemoteControlStatus -{ - /// - [JsonIgnore] - public override string State => "active"; + /// Root directory of the git repository, resolved via git rev-parse. + [JsonPropertyName("gitRoot")] + public string? GitRoot { get; set; } - /// Session id remote control is pointed at. - [JsonPropertyName("attachedSessionId")] - public required string AttachedSessionId { get; set; } + /// Head commit of the current git branch. + [JsonPropertyName("headCommit")] + public string? HeadCommit { 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; } + /// Hosting platform type of the repository. + [JsonPropertyName("hostType")] + public SessionWorkingDirectoryContextHostType? HostType { get; set; } - /// MC frontend URL for this session, when known. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("frontendUrl")] - public string? FrontendUrl { 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; } - /// Whether the MC session may steer this session. - [JsonPropertyName("isSteerable")] - public required bool IsSteerable { get; set; } + /// Raw host string from the git remote URL (e.g. "github.com", "dev.azure.com"). + [JsonPropertyName("repositoryHost")] + public string? RepositoryHost { get; set; } } -/// The last setup attempt failed. The singleton is otherwise off. -/// The error variant of . +/// A GitHub repository one of a working tree's remotes points at. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class RemoteControlStatusError : RemoteControlStatus +internal sealed class GitRemoteRepository { - /// - [JsonIgnore] - public override string State => "error"; + /// GitHub host serving the repository, which is not `github.com` for a GitHub Enterprise remote. + [JsonPropertyName("host")] + public string Host { get; set; } = string.Empty; - /// Session id the failing setup attempt targeted, when known. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("attachedSessionId")] - public string? AttachedSessionId { get; set; } + /// Repository name, without the owner. + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; - /// Human-readable error message from the last setup attempt. - [JsonPropertyName("error")] - public required string Error { get; set; } + /// Account or organization owning the repository. + [JsonPropertyName("owner")] + public string Owner { get; set; } = string.Empty; + + /// Name of the first remote that produced this distinct repository entry, such as `origin` or `upstream`. + [JsonPropertyName("remoteName")] + public string RemoteName { get; set; } = string.Empty; } -/// Wrapper for the singleton's current status. +/// The GitHub repositories a working tree's remotes point at. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class RemoteControlStatusResult +internal sealed class GitReposFromRemotesResult { - /// State of the runtime-managed remote-control singleton. - [JsonPropertyName("status")] - public RemoteControlStatus Status { get => field ??= new(); set; } + /// One entry per distinct GitHub repository, in the order git reports the first remote for each repository. Empty when no remote points at a GitHub host, which a caller should read as `not connected to GitHub`. Failing to read the remotes is an error, not an empty list. + [JsonPropertyName("repositories")] + public IList Repositories { get => field ??= []; set; } } -/// Reattach to an existing MC session without creating a new one. +/// Git working tree whose GitHub remotes should be listed. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class RemoteControlConfigExistingMcSession +internal sealed class GitReposFromRemotesRequest { - /// Existing MC session ID to reattach to. - [JsonPropertyName("mcSessionId")] - public string McSessionId { get; set; } = string.Empty; - - /// Existing MC task ID for the reattached session. - [JsonPropertyName("mcTaskId")] - public string McTaskId { get; set; } = string.Empty; + /// Absolute path to the root of the git working tree. + [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("gitRoot")] + public string GitRoot { get; set; } = string.Empty; } -/// Configuration for the runtime-managed remote-control singleton. +/// Indicates whether the calling client was registered as the session filesystem provider. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class RemoteControlConfig +public sealed class SessionFsSetProviderResult { - /// Reattach to an existing MC session without creating a new one. - [JsonPropertyName("existingMcSession")] - public RemoteControlConfigExistingMcSession? ExistingMcSession { 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; } - - /// Whether remote export should be enabled. - [JsonPropertyName("remote")] - public bool Remote { get; set; } - - /// When true, suppresses timeline messages on successful setup. - [JsonPropertyName("silent")] - public bool Silent { get; set; } - - /// Whether the MC session may steer the local session (write mode). - [JsonPropertyName("steerable")] - public bool Steerable { get; set; } - - /// Existing Mission Control task ID to attach the exported session to. - [JsonPropertyName("taskId")] - public string? TaskId { get; set; } + /// Whether the provider was set successfully. + [JsonPropertyName("success")] + public bool Success { get; set; } } -/// Parameters for attaching the remote-control singleton to a session. +/// Optional capabilities declared by the provider. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionsStartRemoteControlRequest +public sealed class SessionFsSetProviderCapabilities { - /// Configuration for the runtime-managed remote-control singleton. - [JsonPropertyName("config")] - public RemoteControlConfig Config { get => field ??= new(); set; } + /// Whether the provider supports binary reads and writes through sessionFs.readFileBytes and sessionFs.writeFileBytes. + [JsonPropertyName("binary")] + public bool? Binary { get; set; } - /// Local session id to attach remote control to. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Whether the provider supports SQLite query/exists operations. + [JsonPropertyName("sqlite")] + public bool? Sqlite { get; set; } } -/// Outcome of a transferRemoteControl call. +/// Initial working directory, session-state path layout, and path conventions used to register the calling SDK client as the session filesystem provider. A registered provider is authoritative for path interpretation and filesystem facts used by workspace permission validation. Paths are interpreted lexically; home-relative paths (`~` and `~/...`) and Windows drive-relative paths such as `C:foo` are unsupported. Until provider-side canonicalization is supported, providers must not expose symlinks inside allowed roots that escape those roots. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class RemoteControlTransferResult +internal sealed class SessionFsSetProviderRequest { - /// State of the runtime-managed remote-control singleton. - [JsonPropertyName("status")] - public RemoteControlStatus Status { get => field ??= new(); set; } + /// Optional capabilities declared by the provider. + [JsonPropertyName("capabilities")] + public SessionFsSetProviderCapabilities? Capabilities { get; set; } - /// Whether the rebinding actually happened. - [JsonPropertyName("transferred")] - public bool Transferred { get; set; } + /// Path conventions used by this filesystem. + [JsonPropertyName("conventions")] + public SessionFsSetProviderConventions Conventions { get; set; } + + /// Absolute initial working directory for sessions. Registering the provider establishes this path as the root of its virtual namespace; the runtime does not require the provider to materialize or stat it before creating a session. + [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; } -/// Parameters for atomically rebinding the remote-control singleton. +/// Indicates whether the calling client was registered as the LLM inference provider. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionsTransferRemoteControlRequest +public sealed class LlmInferenceSetProviderResult { - /// 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; } - - /// Local session id to point remote control at. - [JsonPropertyName("toSessionId")] - public string ToSessionId { get; set; } = string.Empty; + /// Whether the provider was set successfully. + [JsonPropertyName("success")] + public bool Success { get; set; } } -/// Patch for the singleton's steering state. +/// Whether the start frame was accepted. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionsSetRemoteControlSteeringRequest +public sealed class LlmInferenceHttpResponseStartResult { - /// 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; } + /// True when the response start was matched to a pending request; false when unknown. + [JsonPropertyName("accepted")] + public bool Accepted { get; set; } } -/// Outcome of a stopRemoteControl call. +/// Response head. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class RemoteControlStopResult +internal sealed class LlmInferenceHttpResponseStartRequest { - /// State of the runtime-managed remote-control singleton. + /// HTTP response headers, preserving multiple values per name. + [JsonPropertyName("headers")] + public IDictionary> Headers { get => field ??= new Dictionary>(); set; } + + /// Matches the requestId from the originating httpRequestStart frame. + [JsonPropertyName("requestId")] + public string RequestId { get; set; } = string.Empty; + + /// HTTP status code. [JsonPropertyName("status")] - public RemoteControlStatus Status { get => field ??= new(); set; } + public long Status { get; set; } - /// Whether the singleton was actually torn down by this call. - [JsonPropertyName("stopped")] - public bool Stopped { get; set; } + /// Optional HTTP status reason phrase. + [JsonPropertyName("statusText")] + public string? StatusText { get; set; } } -/// RPC data type for SessionsStopRemoteControl operations. +/// Whether the chunk was accepted. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionsStopRemoteControlRequest +public sealed class LlmInferenceHttpResponseChunkResult { - /// 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; } - - /// 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; } + /// True when the chunk was matched to a pending request; false when unknown. + [JsonPropertyName("accepted")] + public bool Accepted { get; set; } } -/// Params to attach or detach an in-process ExtensionController delegate. +/// Set to terminate the response with a transport-level failure. Implies end-of-stream; any further chunks for this requestId are ignored. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class ConfigureSessionExtensionsParams +public sealed class LlmInferenceHttpResponseChunkError { - /// Session to attach the extension controller delegate to. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// 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; } -/// Outcome of an agentRegistry.spawn call. -/// Polymorphic base type discriminated by kind. +/// A response body chunk or terminal error. [Experimental(global::GitHub.Copilot.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 LlmInferenceHttpResponseChunkRequest { - /// The type discriminator. - [JsonPropertyName("kind")] - public virtual string Kind { 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; -/// Full registry entry for the spawned child. Lets the controller call `handleLiveTargetSelected(entry)` directly without re-reading the registry (avoids a TOCTOU window). + /// 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; +} + +/// Pre-resolved working-directory context for session startup. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class AgentRegistryLiveTargetEntry +public sealed class SessionContext { - /// Kind of attention required when status === "attention". Meaningful only when status === "attention". - [JsonPropertyName("attentionKind")] - public AgentRegistryLiveTargetEntryAttentionKind? AttentionKind { get; set; } - - /// Git branch of the session (when known). + /// Active git branch. [JsonPropertyName("branch")] public string? Branch { get; set; } - /// Copilot CLI version that wrote the entry. - [JsonPropertyName("copilotVersion")] - public string CopilotVersion { get; set; } = string.Empty; - - /// Working directory of the session (when known). + /// Most recent working directory for this session. [JsonPropertyName("cwd")] - public string? Cwd { get; set; } + public string Cwd { get; set; } = string.Empty; - /// Bind host for the entry's JSON-RPC server. - [JsonPropertyName("host")] - public string Host { get; set; } = string.Empty; + /// Git repository root, if the cwd was inside a git repo. + [JsonPropertyName("gitRoot")] + public string? GitRoot { get; set; } - /// Process kind tag for the registry entry. - [JsonPropertyName("kind")] - public AgentRegistryLiveTargetEntryKind Kind { get; set; } + /// Repository host type. + [JsonPropertyName("hostType")] + public SessionContextHostType? HostType { get; set; } - /// Wall-clock milliseconds since the watcher last observed this entry (heartbeat freshness). - [JsonPropertyName("lastSeenMs")] - public long LastSeenMs { get; set; } + /// Repository slug in `owner/name` form, when known. + [JsonPropertyName("repository")] + public string? Repository { 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; } +/// GitHub repository the remote session belongs to. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public sealed class RemoteSessionMetadataRepository +{ + /// Branch associated with the remote session. + [JsonPropertyName("branch")] + public string Branch { get; set; } = string.Empty; - /// Model identifier currently selected for the session. - [JsonPropertyName("model")] - public string? Model { get; set; } + /// Repository name. + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; - /// Operating-system pid of the process owning this entry. - [JsonPropertyName("pid")] - public long Pid { get; set; } + /// Repository owner. + [JsonPropertyName("owner")] + public string Owner { get; set; } = string.Empty; +} - /// TCP port the entry's JSON-RPC server is listening on. - [JsonPropertyName("port")] - public long Port { get; set; } +/// Remote session metadata for the session to hand off (typically obtained from `sessions.list` with `source: "remote"`). +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public sealed class RemoteSessionMetadataValue +{ + /// Most recent working directory context. + [JsonPropertyName("context")] + public SessionContext? Context { get; set; } - /// Registry entry schema version (1 = ui-server, 2 = managed-server). - [JsonPropertyName("schemaVersion")] - public long SchemaVersion { 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; } - /// Session ID of the foreground session for this entry. - [JsonPropertyName("sessionId")] - public string? SessionId { 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; } - /// Friendly session name (when set). - [JsonPropertyName("sessionName")] - public string? SessionName { get; set; } + /// Always true for remote sessions. + [JsonPropertyName("isRemote")] + public bool IsRemote { get; set; } - /// ISO 8601 timestamp captured at registration. - [JsonPropertyName("startedAt")] - public string StartedAt { get; set; } = string.Empty; + /// Last-modified time as an ISO 8601 timestamp. + [JsonPropertyName("modifiedTime")] + public string ModifiedTime { get; set; } = string.Empty; - /// Coarse lifecycle status of the foreground session. - [JsonPropertyName("status")] - public AgentRegistryLiveTargetEntryStatus? Status { get; set; } + /// Optional human-friendly name set via /rename. + [JsonPropertyName("name")] + public string? Name { get; set; } - /// Monotonic per-publisher revision counter incremented on every status update. Lets watchers detect transient flips. - [JsonPropertyName("statusRevision")] - public long? StatusRevision { get; set; } + /// Pull request number associated with the session. + [JsonPropertyName("pullRequestNumber")] + public long? PullRequestNumber { get; set; } - /// Connection token (null when the target is unauthenticated). - [JsonInclude] - [JsonPropertyName("token")] - internal string? Token { 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; } + + /// 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; } } -/// Canonical process-log discovery outcome; populated from spawnLiveTarget. +/// `sessions.open` handoff progress update with step, status, and optional message. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class AgentRegistryLogCapture +public sealed class SessionsOpenProgress { - /// Whether a canonical process log was discovered for this managed spawn. - [JsonPropertyName("enabled")] - public bool Enabled { get; set; } - - /// Why no canonical process log could be opened for this managed spawn (set only when enabled is false). - [JsonPropertyName("openError")] - public string? OpenError { get; set; } + /// Optional step message. + [JsonPropertyName("message")] + public string? Message { get; set; } - /// Categorized reason no canonical process log could be opened (set only when enabled is false). - [JsonPropertyName("openErrorReason")] - public AgentRegistryLogCaptureOpenErrorReason? OpenErrorReason { get; set; } + /// Step status. + [JsonPropertyName("status")] + public SessionsOpenProgressStatus Status { get; set; } - /// Absolute path to the managed spawn's process-<timestamp>-<pid>.log file (only set when enabled). - [JsonPropertyName("path")] - public string? Path { get; set; } + /// Handoff step. + [JsonPropertyName("step")] + public SessionsOpenProgressStep Step { get; set; } } -/// Managed-server child was spawned and registered successfully. -/// The spawned variant of . +/// Result of opening a session. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class AgentRegistrySpawnResultSpawned : AgentRegistrySpawnResult +public sealed class SessionOpenResult { - /// - [JsonIgnore] - public override string Kind => "spawned"; + /// Remote session metadata, present when status is `connected`. + [JsonPropertyName("metadata")] + public RemoteSessionMetadataValue? Metadata { 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; } + /// Handoff progress steps, present when status is `handed_off`. + [JsonPropertyName("progress")] + public IList? Progress { 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; } + /// Remote session ID, present when status is `connected`. + [JsonPropertyName("remoteSessionId")] + public string? RemoteSessionId { 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; } + /// Opened session ID. Omitted when status is `not_found`. + [JsonPropertyName("sessionId")] + public string? SessionId { get; set; } - /// Per-spawn log-capture outcome; populated from spawnLiveTarget. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("logCapture")] - public AgentRegistryLogCapture? LogCapture { 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; } + + /// Outcome of the open request. + [JsonPropertyName("status")] + public SessionsOpenStatus Status { get; set; } } -/// `child_process.spawn` itself failed before the child entered the registry. -/// The spawn-error variant of . +/// Identifier and optional friendly name assigned to the newly forked session. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class AgentRegistrySpawnResultSpawnError : AgentRegistrySpawnResult +public sealed class SessionsForkResult { - /// - [JsonIgnore] - public override string Kind => "spawn-error"; - - /// Underlying errno code (e.g. ENOENT, EACCES) when available. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("code")] - public string? Code { get; set; } + /// Friendly name assigned to the forked session, if any. + [JsonPropertyName("name")] + public string? Name { get; set; } - /// Human-readable error message. - [JsonPropertyName("message")] - public required string Message { get; set; } + /// The new forked session's ID. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } -/// Spawn succeeded but the child did not publish a matching managed-server entry within the timeout. -/// The registry-timeout variant of . +/// Source session identifier to fork from, optional event-ID boundary, and optional friendly name for the new session. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class AgentRegistrySpawnResultRegistryTimeout : AgentRegistrySpawnResult +internal sealed class SessionsForkRequest { - /// - [JsonIgnore] - public override string Kind => "registry-timeout"; + /// Optional friendly name to assign to the forked session. + [JsonPropertyName("name")] + public string? Name { 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; } + /// Source session ID to fork from. + [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; } + /// 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; } } -/// Synchronous pre-validation rejected the spawn request. -/// The validation-error variant of . +/// Repository associated with the connected remote session. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class AgentRegistrySpawnResultValidationError : AgentRegistrySpawnResult +public sealed class ConnectedRemoteSessionMetadataRepository { - /// - [JsonIgnore] - public override string Kind => "validation-error"; - - /// Which parameter field was invalid. Omitted when the rejection is not field-specific. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("field")] - public AgentRegistrySpawnValidationErrorField? Field { get; set; } + /// Branch associated with the remote session. + [JsonPropertyName("branch")] + public string Branch { 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; } + /// Repository name. + [JsonPropertyName("name")] + public string Name { 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; } + /// Repository owner or organization login. + [JsonPropertyName("owner")] + public string Owner { get; set; } = string.Empty; } -/// Inputs to spawn a managed-server child via the controller's spawn delegate. +/// Metadata for a connected remote session. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class AgentRegistrySpawnRequest +public sealed class ConnectedRemoteSessionMetadata { - /// Custom or built-in agent name (e.g. 'explore'). When omitted, the child uses its own default. - [JsonPropertyName("agentName")] - public string? AgentName { get; set; } - - /// Working directory for the spawned child (must be an existing directory). - [JsonPropertyName("cwd")] - public string Cwd { 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; } + /// Neutral SDK discriminator for the connected remote session kind. + [JsonPropertyName("kind")] + public ConnectedRemoteSessionMetadataKind Kind { get; set; } - /// Model identifier to apply to the new session. - [JsonPropertyName("model")] - public string? Model { get; set; } + /// Last session update time as an ISO 8601 string. + [JsonPropertyName("modifiedTime")] + public DateTimeOffset ModifiedTime { get; set; } - /// Friendly session name. Must satisfy validateSessionName: non-empty, no leading/trailing whitespace, <=100 chars, no control chars, no double quotes. + /// Optional friendly session name. [JsonPropertyName("name")] public string? Name { 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; } -} + /// Pull request number associated with the session. + [JsonPropertyName("pullRequestNumber")] + public long? PullRequestNumber { get; set; } -/// Feature availability. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class ConnectorDiscoveryCapabilities -{ - /// API version. - [JsonPropertyName("apiVersion")] - public long ApiVersion { get; set; } + /// Repository associated with the connected remote session. + [JsonPropertyName("repository")] + public ConnectedRemoteSessionMetadataRepository Repository { get => field ??= new(); set; } - /// Availability. - [JsonPropertyName("availability")] - public ConnectorDiscoveryAvailability Availability { get; set; } + /// Original remote resource identifier. + [JsonPropertyName("resourceId")] + public string? ResourceId { get; set; } - /// Whether results are cached. - [JsonPropertyName("conditionalCache")] - public bool ConditionalCache { get; set; } + /// SDK session ID for the connected remote session. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; - /// Whether accounts are selected by opaque ID. - [JsonPropertyName("opaqueAccountSelection")] - public bool OpaqueAccountSelection { get; set; } -} + /// Remote session staleness deadline as an ISO 8601 string. + [JsonPropertyName("staleAt")] + public DateTimeOffset? StaleAt { get; set; } -/// Account metadata. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class ConnectorDiscoveryAuthInfo -{ - /// Host. - [JsonPropertyName("host")] - public string Host { get; set; } = string.Empty; + /// Session start time as an ISO 8601 string. + [JsonPropertyName("startTime")] + public DateTimeOffset StartTime { get; set; } - /// Login. - [JsonPropertyName("login")] - public string Login { get; set; } = string.Empty; + /// Remote session state returned by the backing service. + [JsonPropertyName("state")] + public string? State { get; set; } - /// Authentication type. - [JsonPropertyName("type")] - public AuthInfoType Type { get; set; } + /// Optional session summary. + [JsonPropertyName("summary")] + public string? Summary { get; set; } } -/// Eligible account. +/// Remote session connection result. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class ConnectorDiscoveryAccount +public sealed class RemoteSessionConnectionResult { - /// Opaque account ID. - [JsonPropertyName("accountId")] - public string AccountId { get; set; } = string.Empty; + /// Metadata for a connected remote session. + [JsonPropertyName("metadata")] + public ConnectedRemoteSessionMetadata Metadata { get => field ??= new(); set; } - /// Account metadata. - [JsonPropertyName("authInfo")] - public ConnectorDiscoveryAuthInfo AuthInfo { get => field ??= new(); set; } + /// SDK session ID for the connected remote session. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } -/// Eligible accounts. +/// Remote session connection parameters. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class ConnectorDiscoveryAccountList +internal sealed class ConnectRemoteSessionParams { - /// Eligible accounts. - [JsonPropertyName("accounts")] - public IList Accounts { get => field ??= []; set; } - - /// Availability. - [JsonPropertyName("availability")] - public ConnectorDiscoveryAvailability Availability { get; set; } + /// Session ID to connect to. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } -/// Entry. +/// Local or remote session metadata entry. Narrow on `isRemote` to access source-specific fields. +/// Data type discriminated by isRemote. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class ConnectorDiscoveryCatalogEntry +public partial class SessionListEntry { - /// Description. - [JsonPropertyName("description")] - public string? Description { get; set; } + /// The boolean discriminator. + [JsonPropertyName("isRemote")] + public bool IsRemote { get; set; } - /// Display name. - [JsonPropertyName("displayName")] - public string DisplayName { get; set; } = string.Empty; + /// Runtime client name that created/last resumed this session. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("clientName")] + public string? ClientName { get; set; } - /// Logo. - [JsonPropertyName("logo")] - public string? Logo { get; set; } + /// Pre-resolved working-directory context for session startup. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("context")] + public SessionContext? Context { get; set; } - /// Name. - [JsonPropertyName("name")] - public string Name { get; set; } = string.Empty; + /// 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; } - /// Release tag. - [JsonPropertyName("releaseTag")] - public string? ReleaseTag { 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. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("hostStatus")] + public RemoteSessionHostStatus? HostStatus { get; set; } - /// Status. - [JsonPropertyName("status")] - public ConnectorCatalogStatus Status { 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; } - /// Tier. - [JsonPropertyName("tier")] - public string? Tier { 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; } -/// Entries for the selected account. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class ConnectorDiscoveryCatalogResult -{ - /// Opaque account ID. - [JsonPropertyName("accountId")] - public string AccountId { get; set; } = string.Empty; + /// Last-modified time of the session's persisted state, as ISO 8601. + [JsonPropertyName("modifiedTime")] + public required string ModifiedTime { get; set; } - /// Entries. - [JsonPropertyName("connectors")] - public IList Connectors { get => field ??= []; set; } + /// Optional human-friendly name set via /rename. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("name")] + public string? Name { get; set; } - /// Refresh time in Unix epoch milliseconds. - [JsonPropertyName("refreshedAtMs")] - public long RefreshedAtMs { get; set; } + /// Pull request number associated with the session. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("pullRequestNumber")] + public long? PullRequestNumber { get; set; } - /// Revision. - [JsonPropertyName("revision")] - public long Revision { get; set; } -} + /// Backing remote session IDs (most recent first). + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("remoteSessionIds")] + public IList? RemoteSessionIds { get; set; } -/// Selected account. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class ConnectorDiscoveryAccountRequest -{ - /// Opaque account ID. - [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(2048)] - [JsonPropertyName("accountId")] - public string AccountId { get; set; } = string.Empty; -} + /// GitHub repository the remote session belongs to. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("repository")] + public RemoteSessionMetadataRepository? Repository { get; set; } -/// Identifies the target session. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionSuspendRequest -{ - /// Target session identifier. + /// 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; } = string.Empty; -} + public required string SessionId { get; set; } -/// Result of sending a user message. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class SendResult -{ - /// Unique identifier assigned to the message. - [JsonPropertyName("messageId")] - public string MessageId { get; set; } = string.Empty; + /// 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; } + + /// Session creation time as an ISO 8601 timestamp. + [JsonPropertyName("startTime")] + public required string StartTime { get; set; } + + /// Server-side task state returned by GitHub. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("state")] + public string? State { get; set; } + + /// Short summary of the session, when one has been derived. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("summary")] + public string? Summary { get; set; } + + /// Whether the remote task originated from CCA or CLI `--remote`. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("taskType")] + public RemoteSessionMetadataTaskType? TaskType { get; set; } } -/// Provider-native structured output format. JSON Schema is forwarded without rewriting or validating the schema or the generated output. -/// Polymorphic base type discriminated by type. +/// Sessions matching the filter, ordered most-recently-modified first. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -[JsonPolymorphic( - TypeDiscriminatorPropertyName = "type", - UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] -[JsonDerivedType(typeof(ResponseFormatJsonSchema), "json_schema")] -public partial class ResponseFormat +public sealed class SessionList { - /// The type discriminator. - [JsonPropertyName("type")] - public virtual string Type { get; set; } = string.Empty; + /// Sessions ordered most-recently-modified first. Discriminated by `isRemote`. + [JsonPropertyName("sessions")] + public IList Sessions { get => field ??= []; set; } } - -/// A JSON Schema output contract. OpenAI receives the name, description, schema and strict setting; Anthropic receives the schema in output_config.format and always uses its native strict enforcement. +/// Optional filter applied to the returned sessions. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class JsonSchemaResponseFormat +public sealed class SessionListFilter { - /// Optional description passed to OpenAI providers. - [JsonPropertyName("description")] - public string? Description { get; set; } + /// Match sessions whose context.branch equals this value. + [JsonPropertyName("branch")] + public string? Branch { get; set; } - /// Name of the output schema, subject to the provider's naming restrictions. - [JsonPropertyName("name")] - public string Name { get; set; } = string.Empty; + /// Match sessions whose context.cwd equals this value. + [JsonPropertyName("cwd")] + public string? Cwd { get; set; } - /// JSON Schema passed unchanged to the inference provider. Schemas larger than 32 MiB when JSON-encoded are rejected before admission, using the runtime's existing request-size ceiling. This is not a guarantee that the entire model request fits. Supported keywords and schema restrictions are determined by the provider. - [JsonPropertyName("schema")] - public JsonElement Schema { get; set; } + /// Match sessions whose context.gitRoot equals this value. + [JsonPropertyName("gitRoot")] + public string? GitRoot { get; set; } - /// Optional strict enforcement setting for OpenAI providers. Omitted uses the provider default. Anthropic always enforces its supported schema subset. - [JsonPropertyName("strict")] - public bool? Strict { get; set; } + /// Match sessions whose context.repository equals this value. + [JsonPropertyName("repository")] + public string? Repository { get; set; } } -/// The json_schema variant of . +/// Optional source filter, metadata-load limit, and context filter applied to the returned sessions. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class ResponseFormatJsonSchema : ResponseFormat +internal sealed class SessionsListRequest { - /// - [JsonIgnore] - public override string Type => "json_schema"; + /// Optional filter applied to the returned sessions. + [JsonPropertyName("filter")] + public SessionListFilter? Filter { get; set; } - /// JSON Schema and provider options for the turn's output. - [JsonPropertyName("jsonSchema")] - public required JsonSchemaResponseFormat JsonSchema { get; set; } -} + /// When true, include detached maintenance sessions. Defaults to false for user-facing session lists. + [JsonPropertyName("includeDetached")] + public bool? IncludeDetached { get; set; } -/// Parameters for sending a user message to the session. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SendRequest -{ - /// 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; } + /// 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 attachments (files, directories, selections, blobs, GitHub references) to include with the message. - [JsonPropertyName("attachments")] - public IList? Attachments { get; set; } + /// Which session sources to include. Defaults to `local` for backward compatibility. + [JsonPropertyName("source")] + public SessionSource? Source { 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; } + /// 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; } +} - /// If provided, this is shown in the timeline instead of `prompt`. - [JsonPropertyName("displayPrompt")] - public string? DisplayPrompt { get; set; } +/// Persisted local session metadata, including identifiers, timestamps, summary/name, client, context, detached state, and task ID. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public sealed class LocalSessionMetadataValue +{ + /// Runtime client name that created/last resumed this session. + [JsonPropertyName("clientName")] + public string? ClientName { 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; } + /// Pre-resolved working-directory context for session startup. + [JsonPropertyName("context")] + public SessionContext? Context { get; set; } - /// If true, adds the message to the front of the queue instead of the end. - [JsonPropertyName("prepend")] - public bool? Prepend { get; set; } + /// True for detached maintenance sessions that should be hidden from normal resume lists. + [JsonPropertyName("isDetached")] + public bool? IsDetached { get; set; } - /// The user message text. - [JsonPropertyName("prompt")] - public string Prompt { get; set; } = string.Empty; + /// Always false for local sessions. + [JsonPropertyName("isRemote")] + public bool IsRemote { 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; } + /// 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; } - /// 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; } + /// Last-modified time of the session's persisted state, as ISO 8601. + [JsonPropertyName("modifiedTime")] + public string ModifiedTime { get; set; } = string.Empty; - /// Provider-native output format for this turn, including all tool-call iterations. Not inherited by later turns or subagents. Ordinary steering inherits the active format; specifying responseFormat with mode: immediate is an error, even while idle. Returned assistant content remains text; the runtime does not parse or validate it. Unsupported models or schemas produce provider errors. - [JsonPropertyName("responseFormat")] - public ResponseFormat? ResponseFormat { get; set; } + /// Optional human-friendly name set via /rename. + [JsonPropertyName("name")] + public string? Name { get; set; } - /// Target session identifier. + /// Stable session identifier. [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; } + /// Session creation time as an ISO 8601 timestamp. + [JsonPropertyName("startTime")] + public string StartTime { get; set; } = string.Empty; - /// 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; } + /// Short summary of the session, when one has been derived. + [JsonPropertyName("summary")] + public string? Summary { get; set; } } -/// Result of sending zero or more user messages. +/// Persisted local session metadata when the session exists. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class SendMessagesResult +internal sealed class SessionsGetMetadataResult { - /// Unique identifiers assigned to the messages, one per provided message in order. For a batch that starts a run, assistant messages use the final ID as originatingMessageId throughout that run, including tool iterations and stop-hook corrections. Immediate steering does not replace the active run's origin. Empty when no messages were provided; that run has no originatingMessageId. - [JsonPropertyName("messageIds")] - public IList MessageIds { get => field ??= []; set; } + /// Local session metadata, omitted when the session does not exist. + [JsonPropertyName("session")] + public LocalSessionMetadataValue? Session { get; set; } } -/// A single user message to append to the session as part of a `session.sendMessages` turn. +/// Session ID whose persisted metadata should be read. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class SendMessageItem +internal sealed class SessionsGetMetadataRequest { - /// 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; } + /// Session ID to inspect. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } -/// 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. +/// Client metadata outcome for one requested local session. +/// Polymorphic base type discriminated by status. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SendMessagesRequest +[JsonPolymorphic( + TypeDiscriminatorPropertyName = "status", + UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] +[JsonDerivedType(typeof(SessionsClientMetadataEntryOk), "ok")] +[JsonDerivedType(typeof(SessionsClientMetadataEntryNotFound), "notFound")] +[JsonDerivedType(typeof(SessionsClientMetadataEntryCorrupt), "corrupt")] +[JsonDerivedType(typeof(SessionsClientMetadataEntryUnsupportedVersion), "unsupportedVersion")] +[JsonDerivedType(typeof(SessionsClientMetadataEntryUnavailable), "unavailable")] +public partial class SessionsClientMetadataEntry { - /// 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, before running one agent loop. When the batch starts a run, its final message is the primary initiating message; earlier messages provide context, not separate runs or replies. May be empty, in which case a single turn runs over the existing history with no new user message or originatingMessageId. - [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; } + /// The type discriminator. + [JsonPropertyName("status")] + public virtual string Status { get; set; } = string.Empty; +} - /// 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; } +/// The ok variant of . +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public partial class SessionsClientMetadataEntryOk : SessionsClientMetadataEntry +{ + /// + [JsonIgnore] + public override string Status => "ok"; - /// Provider-native output format for the whole turn, including an empty message batch and all tool-call iterations. Not inherited by later turns or subagents. Ordinary steering inherits the active format; specifying responseFormat with mode: immediate is an error, even while idle. Returned assistant content remains text; the runtime does not parse or validate it. Unsupported models or schemas produce provider errors. - [JsonPropertyName("responseFormat")] - public ResponseFormat? ResponseFormat { get; set; } + /// Validated client metadata, possibly empty or projected to requested keys. + [JsonPropertyName("metadata")] + public required IDictionary Metadata { get; set; } - /// Target session identifier. + /// Requested session ID. [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 required string SessionId { get; set; } } -/// Internal request for sending a system notification. +/// The notFound variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SendSystemNotificationRequest +public partial class SessionsClientMetadataEntryNotFound : SessionsClientMetadataEntry { - /// Optional structured notification kind. - [JsonPropertyName("kind")] - public JsonElement? Kind { get; set; } - - /// Notification text to deliver to the model. - [JsonPropertyName("message")] - public string Message { get; set; } = string.Empty; - - /// Internal delivery options, including passive policy. - [JsonPropertyName("options")] - public JsonElement? Options { get; set; } + /// + [JsonIgnore] + public override string Status => "notFound"; - /// Target session identifier. + /// Requested session ID. [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + public required string SessionId { get; set; } } -/// Result of aborting the current turn. +/// The corrupt variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class AbortResult +public partial class SessionsClientMetadataEntryCorrupt : SessionsClientMetadataEntry { - /// Error message if the abort failed. - [JsonPropertyName("error")] - public string? Error { get; set; } + /// + [JsonIgnore] + public override string Status => "corrupt"; - /// Whether the abort completed successfully. - [JsonPropertyName("success")] - public bool Success { get; set; } + /// Requested session ID. + [JsonPropertyName("sessionId")] + public required string SessionId { get; set; } } -/// Parameters for aborting the current turn. +/// The unsupportedVersion variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class AbortRequest +public partial class SessionsClientMetadataEntryUnsupportedVersion : SessionsClientMetadataEntry { - /// Finite reason code describing why the current turn was aborted. - [JsonPropertyName("reason")] - public AbortReason? Reason { get; set; } + /// + [JsonIgnore] + public override string Status => "unsupportedVersion"; - /// Target session identifier. + /// Requested session ID. [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + public required string SessionId { get; set; } } -/// Result of interrupting the main agent turn. +/// The unavailable variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class InterruptMainTurnResult +public partial class SessionsClientMetadataEntryUnavailable : SessionsClientMetadataEntry { - /// Whether an in-flight main agent turn was interrupted. False when the main loop was not processing. - [JsonPropertyName("interrupted")] - public bool Interrupted { get; set; } + /// + [JsonIgnore] + public override string Status => "unavailable"; + + /// Filesystem or provider error code. Clients should not assume every provider uses operating-system error codes. + [JsonPropertyName("code")] + public required string Code { get; set; } + + /// Human-readable diagnostic message. Not stable for programmatic matching. + [JsonPropertyName("message")] + public required string Message { get; set; } + + /// Requested session ID. + [JsonPropertyName("sessionId")] + public required string SessionId { get; set; } } -/// Parameters for interrupting the main agent turn. +/// Bounded batch request for client-owned metadata from persisted local sessions. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class InterruptMainTurnRequest +internal sealed class SessionsGetClientMetadataRequest { - /// 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; } + /// Case-sensitive keys to project from each valid bag. Each key must be non-empty, at most 256 UTF-8 bytes, and outside the reserved `copilot/` and `github/` namespaces. Omit to return every entry. + [JsonPropertyName("keys")] + public IList? Keys { get; set; } - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Session IDs to inspect. Results preserve this order. + [JsonPropertyName("sessionIds")] + public IList SessionIds { get => field ??= []; set; } } -/// Identifies the target session. +/// Batch of session events returned by a read, with cursor and continuation metadata. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionCancelAllBackgroundAgentsRequest +public sealed class EventsReadResult { - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Opaque cursor for the next read. Pass back unchanged in the next read.cursor to continue from where this read left off. Always present, even when no events were returned. For a backward read this cursor pages toward OLDER events; keep passing `direction: backward` with it (the cursor is also self-describing, so backward paging continues correctly). + [JsonPropertyName("cursor")] + public string Cursor { get; set; } = string.Empty; + + /// Cursor status: 'ok' means the cursor was applied successfully. For session.eventLog.read, 'expired' means the cursor referred to an event that no longer exists in active history and the read fell back to a boundary of the remaining history: the beginning for a forward read or the newest window for a backward read. That fallback may overlap already rendered events, so active-session consumers should reset, rebase, or deduplicate before continuing. sessions.readPersistedEvents has stricter snapshot semantics: 'expired' returns an empty terminal page and never switches to a replacement journal generation. Other persisted-read I/O failures are RPC errors with diagnostics, not cursor expiry. + [JsonPropertyName("cursorStatus")] + public EventsCursorStatus CursorStatus { get; set; } + + /// Session events for this batch, merged into a single stream in creation order: durable (persisted) events and ephemeral events interleave exactly as they were emitted. Set `includeEphemeral: false` to receive only durable events. Ephemeral events are never replayable once pruned from the in-memory ring, so a consumer that needs them should keep reading with a non-zero `waitMs`. For a backward (tail-first) read, the returned window contains persisted events only, still in chronological (oldest-to-newest) append order. + [JsonPropertyName("events")] + public IList Events { get => field ??= []; set; } + + /// True when more events are available in the read's direction. For a backward read, true means older persisted events remain before the returned window. A persisted-event page may contain fewer than `max` events because of its byte budget while still reporting hasMore true; continue according to this flag rather than the event count. + [JsonPropertyName("hasMore")] + public bool HasMore { get; set; } } -/// Parameters for shutting down the session. +/// Pagination options for reading an inactive or active local session's persisted event journal. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class ShutdownRequest +internal sealed class SessionsReadPersistedEventsRequest { - /// Dispatch deferred sessionEnd hooks in the background with their full per-hook timeoutSec instead of awaiting them under the short shared shutdown budget. Set this when the host process keeps running after the session closes (for example the CLI's /clear), so a slow hook neither blocks the close nor is aborted. Hooks still detached when the process later exits are terminated with it. Defaults to false. - [JsonPropertyName("detachSessionEndHooks")] - public bool? DetachSessionEndHooks { get; set; } + /// Opaque, process-local, single-use cursor returned by the previous persisted-event read. Omit on the first call and issue continuations sequentially; reusing the same cursor returns an expired terminal page. + [JsonPropertyName("cursor")] + public string? Cursor { get; set; } - /// Optional human-readable reason. Typically the message of the error that triggered shutdown when type is 'error'. - [JsonPropertyName("reason")] - public string? Reason { 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. This selects the initial read only; a continuation always uses the direction bound into its cursor. + [JsonPropertyName("direction")] + public EventsReadDirection? Direction { get; set; } - /// Target session identifier. + /// Maximum number of events to return in this batch (1–1000, default 200). Pages may contain fewer events to keep the serialized event array within a soft 1 MiB budget including resolved binary assets; one oversized event is returned alone to guarantee progress. + [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; - - /// Why the session is being shut down. Defaults to "routine" when omitted. - [JsonPropertyName("type")] - public ShutdownType? Type { get; set; } } -/// Identifier of the session event that was emitted for the log message. +/// Recent local session IDs that contain user-visible history. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class LogResult +internal sealed class SessionsListNonEmptySessionIdsResult { - /// The unique identifier of the emitted session event. - [JsonPropertyName("eventId")] - public Guid EventId { get; set; } + /// Session IDs ordered newest-first. + [JsonPropertyName("sessionIds")] + public IList SessionIds { get => field ??= []; set; } } -/// Message text, optional severity level, persistence flag, optional follow-up URL, and optional tip. +/// Limit for non-empty local session IDs. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class LogRequest +internal sealed class SessionsListNonEmptySessionIdsRequest { - /// When true, the message is transient and not persisted to the session event log on disk. - [JsonPropertyName("ephemeral")] - public bool? Ephemeral { get; set; } - - /// Log severity level. Determines how the message is displayed in the timeline. Defaults to "info". - [JsonPropertyName("level")] - public SessionLogLevel? Level { get; set; } - - /// Human-readable message. - [JsonPropertyName("message")] - public string Message { get; set; } = string.Empty; + /// 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(global::GitHub.Copilot.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 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; } + public string? SessionId { get; set; } } -/// Adapter-declared policy that tells clients whether discovery may run automatically. +/// GitHub task ID to look up. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class ModelProviderAutomaticDiscoveryPolicy +internal sealed class SessionsFindByTaskIDRequest { - /// Whether automatic discovery is allowed, limited to configured providers, or explicit-only. - [JsonPropertyName("mode")] - public ModelProviderAutomaticDiscoveryMode Mode { get; set; } - - /// Maximum network scope used by this adapter during discovery. - [JsonPropertyName("networkScope")] - public ModelProviderDiscoveryNetworkScope NetworkScope { get; set; } - - /// True when discovery requires non-null caller input. Omission or null is rejected before adapter execution. When false, omitted or null input selects adapter defaults without schema validation. - [JsonPropertyName("requiresInput")] - public bool RequiresInput { get; set; } - - /// True when the adapter must be enabled by a trusted owner, such as a trusted extension, before automatic discovery may run. - [JsonPropertyName("requiresTrust")] - public bool RequiresTrust { get; set; } + /// GitHub task ID to look up. + [JsonPropertyName("taskId")] + public string TaskId { get; set; } = string.Empty; } -/// An operation supported by a model-provider adapter. +/// Session ID matching the prefix, omitted when no unique match exists. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class ModelProviderAdapterOperationDescriptor +public sealed class SessionsFindByPrefixResult { - /// Optional self-contained JSON Schema Draft 7 for non-null discovery input. Only supported on discover. No external references are resolved. Omitted or null input selects defaults when requiresInput is false. Without a schema, the adapter validates supplied input. - [JsonPropertyName("inputSchema")] - public JsonElement? InputSchema { get; set; } - - /// Supported operation name: `discover`, `getStatus`, or `models.list`. Unknown names and duplicate declarations are rejected. - [JsonPropertyName("name")] - public string Name { get; set; } = string.Empty; + /// Omitted when no unique session matches the prefix (no match or ambiguous). + [JsonPropertyName("sessionId")] + public string? SessionId { get; set; } } -/// Contributor attribution, independent of routing identity and authorization. +/// UUID prefix to resolve to a unique session ID. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class ModelProviderAttribution +internal sealed class SessionsFindByPrefixRequest { - /// Human-readable contributor name, not the adapter display name. - [JsonPropertyName("ownerDisplayName")] - public string? OwnerDisplayName { get; set; } - - /// Stable contributor identifier. Required and nonblank for extension and custom sources; optional for built-in and configured sources. Does not grant authority. - [JsonPropertyName("ownerId")] - public string? OwnerId { get; set; } - - /// Kind of component that supplied the adapter. Attribution does not confer authority. - [JsonPropertyName("source")] - public ModelProviderProvenanceSource Source { 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; } -/// A normalized model-provider adapter in the session's effective catalog. +/// Most-relevant session ID for the supplied context, or omitted when no sessions exist. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class ModelProviderAdapterDescriptor +public sealed class SessionsGetLastForContextResult { - /// Stable opaque identity for routing to this adapter. Unique in the effective catalog, independent of live registration generations. - [JsonPropertyName("adapterId")] - public string AdapterId { get; set; } = string.Empty; - - /// Adapter-declared policy for passive and automatic discovery. - [JsonPropertyName("automaticDiscovery")] - public ModelProviderAutomaticDiscoveryPolicy AutomaticDiscovery { get => field ??= new(); set; } - - /// Human-readable provider name. - [JsonPropertyName("displayName")] - public string DisplayName { get; set; } = string.Empty; - - /// Operations supported by this provider adapter. - [JsonPropertyName("operations")] - public IList Operations { get => field ??= []; set; } - - /// Attribution for the adapter itself. - [JsonPropertyName("provenance")] - public ModelProviderAttribution Provenance { get => field ??= new(); set; } + /// Most-relevant session ID for the supplied context, or omitted when no sessions exist. + [JsonPropertyName("sessionId")] + public string? SessionId { get; set; } +} - /// Descriptive provider family, such as `ollama`. Different adapters may have the same family; use adapterId for routing. - [JsonPropertyName("providerKind")] - public string ProviderKind { get; set; } = string.Empty; +/// Optional working-directory context used to score session relevance. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class SessionsGetLastForContextRequest +{ + /// Optional working-directory context used to score session relevance. When omitted the most-recently-modified session wins. + [JsonPropertyName("context")] + public SessionContext? Context { get; set; } } -/// Normalized model-provider adapter definitions available to the session, not discovered instances. +/// Absolute path to the session's events.jsonl file on disk. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class ModelProviderAdapterCatalog +internal sealed class SessionsGetEventFilePathResult { - /// Available provider adapters ordered by adapterId. - [JsonPropertyName("providers")] - public IList Providers { get => field ??= []; set; } + /// Absolute path to the session's events.jsonl file. + [JsonPropertyName("filePath")] + public string FilePath { get; set; } = string.Empty; } -/// Identifies the target session. +/// Session ID whose event-log file path to compute. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionProvidersGetCatalogRequest +internal sealed class SessionsGetEventFilePathRequest { - /// Target session identifier. + /// Session ID whose event-log file path to compute. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Attribution for the adapter that produced a provider row. +/// Map of sessionId -> on-disk size in bytes for each session's workspace directory. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class ModelProviderProvenance +public sealed class SessionSizes { - /// Stable opaque adapter identity from the effective catalog. Treat this as a whole identifier, not a parseable owner or kind. - [JsonPropertyName("adapterId")] - public string AdapterId { get; set; } = string.Empty; + /// Map of sessionId -> on-disk size in bytes for the session's workspace directory. + [JsonPropertyName("sizes")] + public IDictionary Sizes { get => field ??= new Dictionary(); set; } +} - /// Human-readable contributor name, not the adapter display name. - [JsonPropertyName("ownerDisplayName")] - public string? OwnerDisplayName { get; set; } +/// Session IDs from the input set that are currently in use by another process. +[Experimental(global::GitHub.Copilot.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; } +} - /// Stable contributor identifier when the adapter has an owner outside the runtime. Independent of the contribution mechanism and not a routing key. - [JsonPropertyName("ownerId")] - public string? OwnerId { get; set; } +/// Session IDs to test for live in-use locks. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class SessionsCheckInUseRequest +{ + /// Session IDs to test for live in-use locks. + [JsonPropertyName("sessionIds")] + public IList SessionIds { get => field ??= []; set; } +} - /// Descriptive provider family that produced this row; not a routing key. - [JsonPropertyName("providerKind")] - public string ProviderKind { get; set; } = string.Empty; +/// The session's persisted remote-steerable flag, or omitted when no value has been persisted. +[Experimental(global::GitHub.Copilot.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; } +} - /// Kind of component that supplied the adapter. - [JsonPropertyName("source")] - public ModelProviderProvenanceSource Source { get; set; } +/// Session ID to look up the persisted remote-steerable flag for. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class SessionsGetPersistedRemoteSteerableRequest +{ + /// Session ID to look up the persisted remote-steerable flag for. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } -/// Serializable reference to a discovered provider instance. +/// 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(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class ModelProviderInstanceReference +public sealed class SessionsCloseResult { - /// Stable opaque identity of the adapter that owns this reference. Must be present in the target session's effective catalog. - [JsonPropertyName("adapterId")] - public string AdapterId { get; set; } = string.Empty; +} - /// Stable instance identifier derived by the provider adapter, such as `ollama:{normalizedEndpoint}`. - [JsonPropertyName("id")] - public string Id { get; set; } = string.Empty; +/// Session ID to close. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class SessionsCloseRequest +{ + /// Session ID to close. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} - /// Absolute provider management URI. The adapter validates normalization, supported schemes, and permission to access it against its bound configuration; a reference does not grant authority. - [Url] - [StringSyntax(StringSyntaxAttribute.Uri)] - [JsonPropertyName("managementEndpoint")] - public string ManagementEndpoint { get; set; } = string.Empty; +/// Map of sessionId -> bytes freed by removing the session's workspace directory. +[Experimental(global::GitHub.Copilot.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; } +} - /// Descriptive provider family. Must match the selected adapter; not a routing key. - [JsonPropertyName("providerKind")] - public string ProviderKind { get; set; } = string.Empty; +/// Session IDs to close, deactivate, and delete from disk. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class SessionsBulkDeleteRequest +{ + /// Session IDs to close, deactivate, and delete from disk. + [JsonPropertyName("sessionIds")] + public IList SessionIds { get => field ??= []; set; } } -/// A normalized model-provider instance discovered by the runtime. +/// Session ID to delete from disk. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class ModelProviderInstance +internal sealed class SessionsDeleteRequest { - /// Human-readable instance name. - [JsonPropertyName("displayName")] - public string DisplayName { get; set; } = string.Empty; + /// Session ID to delete. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; - /// Inference API endpoint when the provider exposes one separately from its management endpoint. - [Url] - [StringSyntax(StringSyntaxAttribute.Uri)] - [JsonPropertyName("inferenceEndpoint")] - public string? InferenceEndpoint { get; set; } + /// Internal resolved session directory path to delete. + [JsonPropertyName("sessionPath")] + public string? SessionPath { get; set; } +} - /// Transport to use for inference against this instance. - [JsonPropertyName("inferenceTransport")] - public ProviderEndpointTransport? InferenceTransport { get; set; } +/// Outcome of the prune operation: deleted IDs, dry-run candidates, skipped IDs, total bytes freed, and the dry-run flag. +[Experimental(global::GitHub.Copilot.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; } - /// Provider family to use for inference against this instance. - [JsonPropertyName("inferenceType")] - public ProviderEndpointType? InferenceType { get; set; } + /// Session IDs that were deleted (always empty in dry-run mode). + [JsonPropertyName("deleted")] + public IList Deleted { get => field ??= []; set; } - /// Wire API to use for inference against this instance, when required by the provider family. - [JsonPropertyName("inferenceWireApi")] - public ProviderEndpointWireApi? InferenceWireApi { get; set; } + /// True when no deletions were actually performed. + [JsonPropertyName("dryRun")] + public bool DryRun { get; set; } - /// Attribution for the adapter that produced this instance. - [JsonPropertyName("provenance")] - public ModelProviderProvenance Provenance { get => field ??= new(); set; } + /// Total bytes freed (actual when not dry-run, projected when dry-run). + [JsonPropertyName("freedBytes")] + public long FreedBytes { get; set; } - /// Self-contained reference for subsequent provider operations. - [JsonPropertyName("reference")] - public ModelProviderInstanceReference Reference { get => field ??= new(); set; } + /// Session IDs that were skipped (e.g., named sessions). + [JsonPropertyName("skipped")] + public IList Skipped { get => field ??= []; set; } } -/// Typed provider-operation outcome. Use the code for control flow and the optional message for display. +/// Age threshold and optional flags controlling which old sessions are pruned (or simulated when dryRun is true). [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class ModelProviderOperationOutcome +internal sealed class SessionsPruneOldRequest { - /// Machine-readable operation outcome. - [JsonPropertyName("code")] - public ModelProviderOperationOutcomeCode Code { get; set; } + /// When true, only report what would be deleted without performing any deletion. + [JsonPropertyName("dryRun")] + public bool? DryRun { get; set; } - /// Human-readable detail for non-success outcomes. - [JsonPropertyName("message")] - public string? Message { get; set; } + /// Session IDs that should never be considered for pruning. + [JsonPropertyName("excludeSessionIds")] + public IList? ExcludeSessionIds { get; set; } + + /// When true, named sessions (set via /rename) are also eligible for pruning. + [JsonPropertyName("includeNamed")] + public bool? IncludeNamed { get; set; } + + /// Delete sessions whose modifiedTime is at least this many days old. + [JsonPropertyName("olderThanDays")] + public long OlderThanDays { get; set; } } -/// Provider instances found by a discovery operation. +/// Flush a session's pending events to disk. No-op when no writer exists for the session (e.g., already closed). [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class ModelProviderDiscoverResult +public sealed class SessionsSaveResult { - /// Discovered provider instances. Empty when passive default discovery finds no reachable provider. - [JsonPropertyName("instances")] - public IList Instances { get => field ??= []; set; } - - /// Typed operation outcome. Passive discovery can return `absent` with an empty instance list. - [JsonPropertyName("outcome")] - public ModelProviderOperationOutcome Outcome { get => field ??= new(); set; } } -/// Provider discovery parameters. +/// Session ID whose pending events should be flushed to disk. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class ModelProviderDiscoverRequest +internal sealed class SessionsSaveRequest { - /// Opaque adapter identity returned by `session.providers.getCatalog`. - [JsonPropertyName("adapterId")] - public string AdapterId { get; set; } = string.Empty; - - /// Provider-specific JSON input. Omission or null selects adapter defaults unless requiresInput is true. Non-null input is validated against the advertised Draft 7 schema when present; otherwise validation belongs to the adapter. - [JsonPropertyName("input")] - public JsonElement? Input { get; set; } - - /// Target session identifier. + /// Session ID whose pending events should be flushed to disk. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Current health information for a provider instance. +/// 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(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class ModelProviderStatus +public sealed class SessionsReleaseLockResult { - /// Normalized provider instance. - [JsonPropertyName("instance")] - public ModelProviderInstance Instance { get => field ??= new(); set; } - - /// Typed operation outcome. - [JsonPropertyName("outcome")] - public ModelProviderOperationOutcome Outcome { get => field ??= new(); set; } - - /// Open provider status value, such as `healthy`, `unreachable`, or `notInstalled`. - [JsonPropertyName("status")] - public string Status { get; set; } = string.Empty; - - /// Provider-reported version. - [JsonPropertyName("version")] - public string? Version { get; set; } } -/// Provider status request parameters. +/// Session ID whose in-use lock should be released. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class ModelProviderGetStatusRequest +internal sealed class SessionsReleaseLockRequest { - /// Provider instance reference returned by discovery. - [JsonPropertyName("instance")] - public ModelProviderInstanceReference Instance { get => field ??= new(); set; } - - /// Target session identifier. + /// Session ID whose in-use lock should be released. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Provider-reported model artifact metadata. +/// The enriched metadata records, with summary and context fields backfilled where available. Sessions confirmed empty and unnamed are omitted. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class ModelArtifactDetails +public sealed class SessionEnrichMetadataResult { - /// Provider-reported model architecture. - [JsonPropertyName("architecture")] - public string? Architecture { get; set; } - - /// Provider-reported model families. - [JsonPropertyName("families")] - public IList? Families { get; set; } - - /// Primary model family. - [JsonPropertyName("family")] - public string? Family { get; set; } - - /// Artifact format, such as `gguf`. - [JsonPropertyName("format")] - public string? Format { get; set; } - - /// Provider-reported parameter count label. - [JsonPropertyName("parameterSize")] - public string? ParameterSize { get; set; } - - /// Provider-reported quantization label. - [JsonPropertyName("quantization")] - public string? Quantization { get; set; } - - /// Provider-reported tokenizer. - [JsonPropertyName("tokenizer")] - public string? Tokenizer { 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; } } -/// A non-fatal provider observation warning. +/// Session metadata records to enrich with summary and context information. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class ModelProviderWarning +internal sealed class SessionsEnrichMetadataRequest { - /// Machine-readable warning code. - [JsonPropertyName("code")] - public string Code { 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; } +} - /// Human-readable warning message. - [JsonPropertyName("message")] - public string Message { get; set; } = string.Empty; +/// The workspace record that was written. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class SessionsCreateWorkspaceResult +{ + /// The created workspace record, as JSON. + [JsonPropertyName("workspaceJson")] + public string WorkspaceJson { get; set; } = string.Empty; } -/// A model offered for agent conversations. Missing capability metadata does not disqualify a candidate. Models known to be incompatible, such as embedding-only models, are excluded by the adapter. +/// A working-directory context together with the client that produced it. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class DiscoveredModel +public sealed class SessionWorkingDirectoryContextWithClient { - /// Provider-reported model capabilities. Omitted capability fields are unknown; explicit false values are preserved. - [JsonPropertyName("capabilities")] - public ModelCapabilities Capabilities { get => field ??= new(); set; } + /// Merge-base commit SHA. + [JsonPropertyName("baseCommit")] + public string? BaseCommit { get; set; } - /// Provider-reported model artifact details. - [JsonPropertyName("details")] - public ModelArtifactDetails Details { get => field ??= new(); set; } + /// Current git branch name. + [JsonPropertyName("branch")] + public string? Branch { get; set; } - /// Provider-reported artifact digest. - [JsonPropertyName("digest")] - public string? Digest { get; set; } + /// Name of the client that created the session. + [JsonPropertyName("clientName")] + public string? ClientName { get; set; } - /// Provider-native model identifier. - [JsonPropertyName("id")] - public string Id { get; set; } = string.Empty; + /// Current working directory path. + [JsonPropertyName("cwd")] + public string Cwd { get; set; } = string.Empty; - /// Provider-reported last-modified timestamp. - [JsonPropertyName("modifiedAt")] - public DateTimeOffset? ModifiedAt { get; set; } + /// Root directory of the git repository. + [JsonPropertyName("gitRoot")] + public string? GitRoot { get; set; } - /// Provider-reported display name. - [JsonPropertyName("name")] - public string? Name { get; set; } + /// Head commit of the current git branch. + [JsonPropertyName("headCommit")] + public string? HeadCommit { get; set; } - /// Attribution for the adapter that produced this model row. - [JsonPropertyName("provenance")] - public ModelProviderProvenance Provenance { get => field ??= new(); set; } + /// Hosting platform type of the repository. + [JsonPropertyName("hostType")] + public string? HostType { get; set; } - /// Provider-reported artifact size in bytes. - [JsonPropertyName("sizeBytes")] - public long? SizeBytes { get; set; } + /// Repository identifier derived from the git remote URL. + [JsonPropertyName("repository")] + public string? Repository { get; set; } - /// Non-fatal warnings encountered while enriching this model. - [JsonPropertyName("warnings")] - public IList Warnings { get => field ??= []; set; } + /// Raw host string from the git remote URL. + [JsonPropertyName("repositoryHost")] + public string? RepositoryHost { get; set; } } -/// Models offered for agent conversations by one provider instance. Adapters exclude known-incompatible models, but retain candidates with unknown capabilities. Listing does not guarantee compatibility. +/// Identity, state location and starting context for a workspace record. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class DiscoveredModelList +internal sealed class SessionsCreateWorkspaceRequest { - /// Provider-native models in provider order. - [JsonPropertyName("models")] - public IList Models { get => field ??= []; set; } + /// Starting working-directory context. The record keeps `cwd`, `gitRoot`, `repository`, `hostType`, `branch`, and `clientName`. Other fields, including `repositoryHost`, `headCommit`, and `baseCommit`, are ignored. `hostType` must be `github` or `ado`. + [JsonPropertyName("context")] + public SessionWorkingDirectoryContextWithClient? Context { get; set; } - /// Typed operation outcome. - [JsonPropertyName("outcome")] - public ModelProviderOperationOutcome Outcome { get => field ??= new(); set; } -} + /// `windows` (any letter case) selects Windows path rules. Any other value selects POSIX path rules. + [JsonPropertyName("convention")] + public string Convention { get; set; } = string.Empty; -/// Provider model inventory request parameters. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class ModelProviderModelsListRequest -{ - /// Provider instance reference returned by discovery. - [JsonPropertyName("instance")] - public ModelProviderInstanceReference Instance { get => field ??= new(); set; } + /// User-supplied display name for the workspace. + [JsonPropertyName("name")] + public string? Name { get; set; } - /// Target session identifier. + /// Session ID the workspace record belongs to. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; + + /// Directory the session's state is written under when no session filesystem provider is configured. Ignored when a provider is configured; the provider's session state path is used instead. + [JsonPropertyName("sessionStatePath")] + public string SessionStatePath { get; set; } = string.Empty; } -/// Vision-specific limits. +/// The workspace record on disk, omitted when the session has none. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class ModelCapabilitiesOverrideLimitsVision +internal sealed class SessionsLoadWorkspaceResult { - /// Maximum image size in bytes. - [JsonPropertyName("max_prompt_image_size")] - public long? MaxPromptImageSize { get; set; } - - /// Maximum number of images per prompt. - [JsonPropertyName("max_prompt_images")] - public long? MaxPromptImages { get; set; } - - /// MIME types the model accepts. - [JsonPropertyName("supported_media_types")] - public IList? SupportedMediaTypes { get; set; } + /// The workspace record, as JSON. Omitted when the record does not exist. + [JsonPropertyName("workspaceJson")] + public string? WorkspaceJson { get; set; } } -/// Token limits for prompts, outputs, and context window. +/// Where the session's state lives, as a root directory and the session ID under it. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class ModelCapabilitiesOverrideLimits +internal sealed class SessionsLoadWorkspaceRequest { - /// Maximum total context window size in tokens. - [JsonPropertyName("max_context_window_tokens")] - public long? MaxContextWindowTokens { get; 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; } + /// Session ID naming the state directory under the sessions home. Rejected when it is absolute or contains a parent component, so it cannot escape the sessions home. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; - /// Vision-specific limits. - [JsonPropertyName("vision")] - public ModelCapabilitiesOverrideLimitsVision? Vision { get; set; } + /// Root directory every session's state directory sits under. + [JsonPropertyName("sessionsHome")] + public string SessionsHome { get; set; } = string.Empty; } -/// Feature flags indicating what the model supports. +/// The merge completed. The record carries the supplied workspace-schema fields, but a stored `fork_count` stays. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class ModelCapabilitiesOverrideSupports +internal sealed class SessionsUpdateWorkspaceFieldsResult { - /// Resolved Anthropic adaptive-thinking capability — unsupported / optional / required / adaptive_only. 'required' models reject thinking.type='enabled' with HTTP 400 but still accept 'disabled' (e.g. opus-4.7/4.8/5, sonnet-5); 'adaptive_only' models accept nothing but 'adaptive' (e.g. fable, mythos). - [JsonPropertyName("adaptive_thinking")] - public AdaptiveThinkingSupport? AdaptiveThinking { get; set; } +} - /// Whether this model supports reasoning effort configuration. - [JsonPropertyName("reasoningEffort")] - public bool? ReasoningEffort { get; set; } +/// Where the session's state lives, plus workspace-schema fields to merge into its workspace record. Stored keys outside the schema are not preserved, and a stored `fork_count` is never replaced. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class SessionsUpdateWorkspaceFieldsRequest +{ + /// Workspace-schema fields to merge into the record, as a JSON object. Fields the object omits keep their stored values, except stored keys outside the schema are not preserved and a stored `fork_count` is never replaced. + [JsonPropertyName("fieldsJson")] + public string FieldsJson { get; set; } = string.Empty; - /// Whether this model supports canonical tool calling. - [JsonPropertyName("toolCalls")] - public bool? ToolCalls { get; set; } + /// Session ID naming the state directory under the sessions home. Rejected when it is absolute or contains a parent component, so it cannot escape the sessions home. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; - /// Whether this model supports vision/image input. - [JsonPropertyName("vision")] - public bool? Vision { get; set; } + /// Root directory every session's state directory sits under. + [JsonPropertyName("sessionsHome")] + public string SessionsHome { get; set; } = string.Empty; } -/// Optional capability overrides (vision, tool_calls, reasoning, etc.). +/// 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(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class ModelCapabilitiesOverride +public sealed class SessionsReloadPluginHooksResult { - /// Token limits for prompts, outputs, and context window. - [JsonPropertyName("limits")] - public ModelCapabilitiesOverrideLimits? Limits { get; set; } - - /// Feature flags indicating what the model supports. - [JsonPropertyName("supports")] - public ModelCapabilitiesOverrideSupports? Supports { get; set; } } -/// RPC data type for ProtocolSystemMessageAppendConfig operations. +/// Active session ID and an optional flag for deferring repo-level hooks until folder trust. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class ProtocolSystemMessageAppendConfig +internal sealed class SessionsReloadPluginHooksRequest { - /// Text appended to the standard system prompt. - [JsonPropertyName("content")] - public string? Content { 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; } - /// Append-mode discriminator. Omission also selects append mode. - [JsonPropertyName("mode")] - public ProtocolAppendMode? Mode { get; set; } + /// Active session ID to reload hooks for. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } -/// RPC data type for SystemMessageBlock operations. +/// Queued repo-level startup prompts and the total hook command count after loading. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class SystemMessageBlock +public sealed class SessionLoadDeferredRepoHooksResult { - /// Whether providers with explicit prompt caching should place a cache breakpoint after this block. - [JsonPropertyName("cacheBreakpoint")] - public bool? CacheBreakpoint { get; set; } - - /// Text content for this system-message block. - [JsonPropertyName("content")] - public string Content { get; set; } = string.Empty; + /// 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; } - /// Whether the block is static and may be cached independently of dynamic prompt content. - [JsonPropertyName("isStatic")] - public bool? IsStatic { 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; } } -/// RPC data type for ProtocolSystemMessageReplaceConfig operations. +/// Active session ID whose deferred repo-level hooks should be loaded. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class ProtocolSystemMessageReplaceConfig +internal sealed class SessionsLoadDeferredRepoHooksRequest { - /// Complete replacement system-message text. - [JsonPropertyName("content")] - public string Content { get; set; } = string.Empty; - - /// Optional structured blocks corresponding to the replacement content. - [JsonPropertyName("contentBlocks")] - public IList? ContentBlocks { get; set; } - - /// Replace-mode discriminator. - [JsonPropertyName("mode")] - public ProtocolReplaceMode Mode { get; set; } + /// Active session ID whose deferred repo-level hooks should be loaded. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } -/// RPC data type for ProtocolStaticSectionOverride operations. +/// 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(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class ProtocolStaticSectionOverride +public sealed class SessionsSetAdditionalPluginsResult { - /// Declarative operation applied to the section. - [JsonPropertyName("action")] - public ProtocolStaticSectionAction Action { get; set; } - - /// Optional content used by replace, append, and prepend operations. - [JsonPropertyName("content")] - public string? Content { get; set; } } -/// Polymorphic base type discriminated by action. +/// Manager-wide additional plugins to register; replaces any previously-configured set. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -[JsonPolymorphic( - TypeDiscriminatorPropertyName = "action", - UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] -[JsonDerivedType(typeof(ProtocolMarkerSectionOverrideTransform), "transform")] -[JsonDerivedType(typeof(ProtocolMarkerSectionOverridePreserve), "preserve")] -public partial class ProtocolMarkerSectionOverride +internal sealed class SessionsSetAdditionalPluginsRequest { - /// The type discriminator. - [JsonPropertyName("action")] - public virtual string Action { get; set; } = string.Empty; + /// 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; } } - -/// The transform variant of . +/// Dynamic-context board entry count, when available. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class ProtocolMarkerSectionOverrideTransform : ProtocolMarkerSectionOverride +internal sealed class SessionsGetBoardEntryCountResult { - /// - [JsonIgnore] - public override string Action => "transform"; + /// Board entry count, when available. + [JsonPropertyName("count")] + public long? Count { get; set; } } -/// The preserve variant of . +/// Session ID whose board entry count should be returned. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class ProtocolMarkerSectionOverridePreserve : ProtocolMarkerSectionOverride +internal sealed class SessionsGetBoardEntryCountRequest { - /// - [JsonIgnore] - public override string Action => "preserve"; + /// Session ID whose board entry count should be returned. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } -/// JSON union data type for ProtocolSectionOverride. +/// State of the runtime-managed remote-control singleton. +/// Polymorphic base type discriminated by state. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -[JsonConverter(typeof(Converter))] -public sealed partial class ProtocolSectionOverride +[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 { - /// Gets the value when this instance contains . - public ProtocolStaticSectionOverride? ProtocolStaticSectionOverride { get; } - - /// Gets the value when this instance contains . - public ProtocolMarkerSectionOverride? ProtocolMarkerSectionOverride { get; } - - /// Initializes a new instance of the class from . - public ProtocolSectionOverride(ProtocolStaticSectionOverride value) - { - ArgumentNullException.ThrowIfNull(value); - ProtocolStaticSectionOverride = value; - } - - /// Converts to . - public static implicit operator ProtocolSectionOverride(ProtocolStaticSectionOverride value) => new(value); - - /// Initializes a new instance of the class from . - public ProtocolSectionOverride(ProtocolMarkerSectionOverride value) - { - ArgumentNullException.ThrowIfNull(value); - ProtocolMarkerSectionOverride = value; - } - - /// Converts to . - public static implicit operator ProtocolSectionOverride(ProtocolMarkerSectionOverride value) => new(value); - - /// Provides a for serializing instances. - [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class Converter : JsonConverter - { - /// - public override ProtocolSectionOverride Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - if (reader.TokenType == JsonTokenType.Null) - { - throw new JsonException("Expected JSON object for ProtocolSectionOverride."); - } - - using var document = JsonDocument.ParseValue(ref reader); - var element = document.RootElement; - if (element.ValueKind == JsonValueKind.Object && (element.TryGetProperty("action", out _) && element.GetProperty("action").ValueKind == JsonValueKind.String && (element.GetProperty("action").GetString() == "replace" || element.GetProperty("action").GetString() == "remove" || element.GetProperty("action").GetString() == "append" || element.GetProperty("action").GetString() == "prepend"))) - { - var protocolStaticSectionOverride = JsonSerializer.Deserialize(element, RpcJsonContext.Default.ProtocolStaticSectionOverride); - return protocolStaticSectionOverride is null ? throw new JsonException("Expected ProtocolStaticSectionOverride value.") : new ProtocolSectionOverride(protocolStaticSectionOverride); - } - if ((element.ValueKind == JsonValueKind.Object && (element.TryGetProperty("action", out _) && element.GetProperty("action").ValueKind == JsonValueKind.String && (element.GetProperty("action").GetString() == "transform")) || element.ValueKind == JsonValueKind.Object && (element.TryGetProperty("action", out _) && element.GetProperty("action").ValueKind == JsonValueKind.String && (element.GetProperty("action").GetString() == "preserve")))) - { - var protocolMarkerSectionOverride = JsonSerializer.Deserialize(element, RpcJsonContext.Default.ProtocolMarkerSectionOverride); - return protocolMarkerSectionOverride is null ? throw new JsonException("Expected ProtocolMarkerSectionOverride value.") : new ProtocolSectionOverride(protocolMarkerSectionOverride); - } - - throw new JsonException("JSON value did not match any ProtocolSectionOverride variant."); - } + /// The type discriminator. + [JsonPropertyName("state")] + public virtual string State { get; set; } = string.Empty; +} - /// - public override void Write(Utf8JsonWriter writer, ProtocolSectionOverride value, JsonSerializerOptions options) - { - if (value.ProtocolStaticSectionOverride is { } protocolStaticSectionOverride) - { - JsonSerializer.Serialize(writer, protocolStaticSectionOverride, RpcJsonContext.Default.ProtocolStaticSectionOverride); - return; - } - if (value.ProtocolMarkerSectionOverride is { } protocolMarkerSectionOverride) - { - JsonSerializer.Serialize(writer, protocolMarkerSectionOverride, RpcJsonContext.Default.ProtocolMarkerSectionOverride); - return; - } - throw new JsonException("No ProtocolSectionOverride variant value is set."); - } - } +/// Remote control is not connected. +/// The off variant of . +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public partial class RemoteControlStatusOff : RemoteControlStatus +{ + /// + [JsonIgnore] + public override string State => "off"; } -/// RPC data type for ProtocolSystemMessageCustomizeConfig operations. +/// Remote control is in the middle of initial setup. +/// The connecting variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class ProtocolSystemMessageCustomizeConfig +public partial class RemoteControlStatusConnecting : RemoteControlStatus { - /// Text appended after the customized sections. - [JsonPropertyName("content")] - public string? Content { get; set; } - - /// Customize-mode discriminator. - [JsonPropertyName("mode")] - public ProtocolCustomizeMode Mode { get; set; } + /// + [JsonIgnore] + public override string State => "connecting"; - /// Named standard-prompt section overrides. - [JsonPropertyName("sections")] - public IDictionary? Sections { get; set; } + /// Session id the connection is attaching to. + [JsonPropertyName("attachedSessionId")] + public required string AttachedSessionId { get; set; } } -/// JSON union data type for ProtocolSystemMessageConfig. +/// Remote control is connected to a local session. +/// The active variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -[JsonConverter(typeof(Converter))] -public sealed partial class ProtocolSystemMessageConfig +public partial class RemoteControlStatusActive : RemoteControlStatus { - /// Gets the value when this instance contains . - public ProtocolSystemMessageAppendConfig? ProtocolSystemMessageAppendConfig { get; } - - /// Gets the value when this instance contains . - public ProtocolSystemMessageReplaceConfig? ProtocolSystemMessageReplaceConfig { get; } - - /// Gets the value when this instance contains . - public ProtocolSystemMessageCustomizeConfig? ProtocolSystemMessageCustomizeConfig { get; } - - /// Initializes a new instance of the class from . - public ProtocolSystemMessageConfig(ProtocolSystemMessageAppendConfig value) - { - ArgumentNullException.ThrowIfNull(value); - ProtocolSystemMessageAppendConfig = value; - } - - /// Converts to . - public static implicit operator ProtocolSystemMessageConfig(ProtocolSystemMessageAppendConfig value) => new(value); - - /// Initializes a new instance of the class from . - public ProtocolSystemMessageConfig(ProtocolSystemMessageReplaceConfig value) - { - ArgumentNullException.ThrowIfNull(value); - ProtocolSystemMessageReplaceConfig = value; - } - - /// Converts to . - public static implicit operator ProtocolSystemMessageConfig(ProtocolSystemMessageReplaceConfig value) => new(value); - - /// Initializes a new instance of the class from . - public ProtocolSystemMessageConfig(ProtocolSystemMessageCustomizeConfig value) - { - ArgumentNullException.ThrowIfNull(value); - ProtocolSystemMessageCustomizeConfig = value; - } - - /// Converts to . - public static implicit operator ProtocolSystemMessageConfig(ProtocolSystemMessageCustomizeConfig value) => new(value); - - /// Provides a for serializing instances. - [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class Converter : JsonConverter - { - /// - public override ProtocolSystemMessageConfig Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - if (reader.TokenType == JsonTokenType.Null) - { - throw new JsonException("Expected JSON object for ProtocolSystemMessageConfig."); - } + /// + [JsonIgnore] + public override string State => "active"; - using var document = JsonDocument.ParseValue(ref reader); - var element = document.RootElement; - if (element.ValueKind == JsonValueKind.Object && (!element.TryGetProperty("mode", out _) || (element.TryGetProperty("mode", out _) && element.GetProperty("mode").ValueKind == JsonValueKind.String && (element.GetProperty("mode").GetString() == "append")))) - { - var protocolSystemMessageAppendConfig = JsonSerializer.Deserialize(element, RpcJsonContext.Default.ProtocolSystemMessageAppendConfig); - return protocolSystemMessageAppendConfig is null ? throw new JsonException("Expected ProtocolSystemMessageAppendConfig value.") : new ProtocolSystemMessageConfig(protocolSystemMessageAppendConfig); - } - if (element.ValueKind == JsonValueKind.Object && (element.TryGetProperty("mode", out _) && element.GetProperty("mode").ValueKind == JsonValueKind.String && (element.GetProperty("mode").GetString() == "replace"))) - { - var protocolSystemMessageReplaceConfig = JsonSerializer.Deserialize(element, RpcJsonContext.Default.ProtocolSystemMessageReplaceConfig); - return protocolSystemMessageReplaceConfig is null ? throw new JsonException("Expected ProtocolSystemMessageReplaceConfig value.") : new ProtocolSystemMessageConfig(protocolSystemMessageReplaceConfig); - } - if (element.ValueKind == JsonValueKind.Object && (element.TryGetProperty("mode", out _) && element.GetProperty("mode").ValueKind == JsonValueKind.String && (element.GetProperty("mode").GetString() == "customize"))) - { - var protocolSystemMessageCustomizeConfig = JsonSerializer.Deserialize(element, RpcJsonContext.Default.ProtocolSystemMessageCustomizeConfig); - return protocolSystemMessageCustomizeConfig is null ? throw new JsonException("Expected ProtocolSystemMessageCustomizeConfig value.") : new ProtocolSystemMessageConfig(protocolSystemMessageCustomizeConfig); - } + /// Session id remote control is pointed at. + [JsonPropertyName("attachedSessionId")] + public required string AttachedSessionId { get; set; } - throw new JsonException("JSON value did not match any ProtocolSystemMessageConfig variant."); - } + /// 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; } - /// - public override void Write(Utf8JsonWriter writer, ProtocolSystemMessageConfig value, JsonSerializerOptions options) - { - if (value.ProtocolSystemMessageAppendConfig is { } protocolSystemMessageAppendConfig) - { - JsonSerializer.Serialize(writer, protocolSystemMessageAppendConfig, RpcJsonContext.Default.ProtocolSystemMessageAppendConfig); - return; - } - if (value.ProtocolSystemMessageReplaceConfig is { } protocolSystemMessageReplaceConfig) - { - JsonSerializer.Serialize(writer, protocolSystemMessageReplaceConfig, RpcJsonContext.Default.ProtocolSystemMessageReplaceConfig); - return; - } - if (value.ProtocolSystemMessageCustomizeConfig is { } protocolSystemMessageCustomizeConfig) - { - JsonSerializer.Serialize(writer, protocolSystemMessageCustomizeConfig, RpcJsonContext.Default.ProtocolSystemMessageCustomizeConfig); - return; - } + /// MC frontend URL for this session, when known. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("frontendUrl")] + public string? FrontendUrl { get; set; } - throw new JsonException("No ProtocolSystemMessageConfig variant value is set."); - } - } + /// Whether the MC session may steer this session. + [JsonPropertyName("isSteerable")] + public required bool IsSteerable { get; set; } } -/// A BYOK model definition referencing a named provider. +/// The last setup attempt failed. The singleton is otherwise off. +/// The error variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class ProviderModelConfig +public partial class RemoteControlStatusError : RemoteControlStatus { - /// 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; } - - /// Provider-published model metadata, preserved verbatim as the public Model.metadata object. - [JsonPropertyName("metadata")] - public IDictionary? Metadata { 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; + /// + [JsonIgnore] + public override string State => "error"; - /// System-message configuration used when the runtime builds the standard prompt for this provider-qualified model, including general-purpose subagents. It uses the same object hierarchy as session-level systemMessage configuration, except transform actions are rejected because the current callback protocol is not model-scoped. When present, it overrides the session-wide configuration on those prompt paths. Selected custom-agent and specialized-subagent prompts remain authoritative. - [JsonPropertyName("systemMessage")] - public ProtocolSystemMessageConfig? SystemMessage { get; set; } + /// Session id the failing setup attempt targeted, when known. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("attachedSessionId")] + public string? AttachedSessionId { get; set; } - /// The model name sent to the provider API for inference. Defaults to `id`. - [JsonPropertyName("wireModel")] - public string? WireModel { get; set; } + /// Human-readable error message from the last setup attempt. + [JsonPropertyName("error")] + public required string Error { get; set; } } -/// Azure-specific provider options. +/// Wrapper for the singleton's current status. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class ProviderConfigAzure +public sealed class RemoteControlStatusResult { - /// API version. When set, uses the versioned deployment route. When omitted, uses the GA versionless v1 route. - [JsonPropertyName("apiVersion")] - public string? ApiVersion { get; set; } + /// State of the runtime-managed remote-control singleton. + [JsonPropertyName("status")] + public RemoteControlStatus Status { get => field ??= new(); set; } } -/// External SDK input for a named custom model provider. Ingested by the native protocol boundary before host dispatch. +/// Reattach to an existing MC session without creating a new one. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class NamedProviderConfig +public sealed class RemoteControlConfigExistingMcSession { - /// 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; } - - /// The product serving the provider's models, reported in telemetry as `model_provider`. Only affects telemetry. - [JsonPropertyName("modelProvider")] - public ProviderConfigModelProvider? ModelProvider { 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; } + /// Existing MC session ID to reattach to. + [JsonPropertyName("mcSessionId")] + public string McSessionId { get; set; } = string.Empty; - /// Wire API used to communicate with the provider. - [JsonPropertyName("wireApi")] - public ProviderConfigWireApi? WireApi { get; set; } + /// Existing MC task ID for the reattached session. + [JsonPropertyName("mcTaskId")] + public string McTaskId { get; set; } = string.Empty; } -/// Provider configuration prepared from a discovered model. Preparing a plan changes nothing: it neither registers the model with the session nor writes durable configuration. To apply it, pass `provider` and `model` to `session.provider.add`, omitting whichever the dispositions report as already configured. +/// Configuration for the runtime-managed remote-control singleton. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class ModelProviderConfigurationPlan +public sealed class RemoteControlConfig { - /// Model definition prepared from the discovered model. Capability fields the provider did not report stay omitted rather than being asserted false. - [JsonPropertyName("model")] - public ProviderModelConfig Model { get => field ??= new(); set; } + /// Reattach to an existing MC session without creating a new one. + [JsonPropertyName("existingMcSession")] + public RemoteControlConfigExistingMcSession? ExistingMcSession { get; set; } - /// Whether `model` still needs to be registered. When `alreadyConfigured`, `selectionId` is already registered and the caller can select it without adding anything. - [JsonPropertyName("modelDisposition")] - public ModelProviderConfigurationDisposition ModelDisposition { 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; } - /// Provider connection prepared from the instance's inference metadata. Carries no credential; supply one if the endpoint requires it. - [JsonPropertyName("provider")] - public NamedProviderConfig Provider { get => field ??= new(); set; } + /// Whether remote export should be enabled. + [JsonPropertyName("remote")] + public bool Remote { get; set; } - /// Whether `provider` still needs to be registered. When `alreadyConfigured`, a provider with the same endpoint is already registered and `provider` restates it under its existing name; adding it again is rejected as a duplicate. - [JsonPropertyName("providerDisposition")] - public ModelProviderConfigurationDisposition ProviderDisposition { get; set; } + /// When true, suppresses timeline messages on successful setup. + [JsonPropertyName("silent")] + public bool Silent { get; set; } - /// Provider-qualified selection id (`provider/id`) to pass to `switchTo` once the plan is applied. - [JsonPropertyName("selectionId")] - public string SelectionId { get; set; } = string.Empty; + /// Whether the MC session may steer the local session (write mode). + [JsonPropertyName("steerable")] + public bool Steerable { get; set; } - /// Non-fatal warnings carried over from the discovered model, such as capabilities the provider did not report. - [JsonPropertyName("warnings")] - public IList Warnings { get => field ??= []; set; } + /// Existing Mission Control task ID to attach the exported session to. + [JsonPropertyName("taskId")] + public string? TaskId { get; set; } } -/// A discovered instance and one of its models to translate into provider configuration. Pass back the instance and model as returned by `session.providers.discover` and `session.providers.models.list`. +/// Parameters for attaching the remote-control singleton to a session. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class ModelProviderPrepareConfigurationRequest +internal sealed class SessionsStartRemoteControlRequest { - /// The discovered instance that serves the model. - [JsonPropertyName("instance")] - public ModelProviderInstance Instance { get => field ??= new(); set; } - - /// The discovered model to configure. - [JsonPropertyName("model")] - public DiscoveredModel Model { get => field ??= new(); set; } + /// Configuration for the runtime-managed remote-control singleton. + [JsonPropertyName("config")] + public RemoteControlConfig Config { get => field ??= new(); set; } - /// Target session identifier. + /// Local session id to attach remote control to. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Managed sandbox enforcement state for a session. +/// Outcome of a transferRemoteControl call. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class SandboxEnforcementStatus +public sealed class RemoteControlTransferResult { - /// Whether an enforcement failure has permanently blocked the session. - [JsonPropertyName("blocked")] - public bool Blocked { get; set; } - - /// The first sandbox enforcement failure that blocked the session. - [JsonPropertyName("reason")] - public string? Reason { get; set; } + /// State of the runtime-managed remote-control singleton. + [JsonPropertyName("status")] + public RemoteControlStatus Status { get => field ??= new(); set; } - /// Whether the effective managed policy requires an available sandbox backend. - [JsonPropertyName("required")] - public bool Required { get; set; } + /// Whether the rebinding actually happened. + [JsonPropertyName("transferred")] + public bool Transferred { get; set; } } -/// Identifies the target session. +/// Parameters for atomically rebinding the remote-control singleton. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionSandboxGetEnforcementStatusRequest +internal sealed class SessionsTransferRemoteControlRequest { - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { 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; } + + /// Local session id to point remote control at. + [JsonPropertyName("toSessionId")] + public string ToSessionId { get; set; } = string.Empty; } -/// Result of attempting to disable sandboxing for the current session. +/// Patch for the singleton's steering state. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class SandboxDisableForSessionResult +internal sealed class SessionsSetRemoteControlSteeringRequest { - /// The authoritative sandbox enabled state after the operation. + /// 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; } - - /// Whether this call resolved the pending request and applied the session opt-out. - [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. +/// Outcome of a stopRemoteControl call. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class PermissionDecisionContext +public sealed class RemoteControlStopResult { - /// 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; } + /// State of the runtime-managed remote-control singleton. + [JsonPropertyName("status")] + public RemoteControlStatus Status { get => field ??= new(); set; } - /// Client surface that submitted the response. - [JsonPropertyName("surface")] - public PermissionDecisionSurface Surface { get; set; } + /// Whether the singleton was actually torn down by this call. + [JsonPropertyName("stopped")] + public bool Stopped { get; set; } } -/// Request to disable sandboxing for the current session while resolving an active sandbox-bypass permission prompt. +/// RPC data type for SessionsStopRemoteControl operations. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SandboxDisableForSessionRequest +internal sealed class SessionsStopRemoteControlRequest { - /// Optional attribution for the permission decision. - [JsonPropertyName("decisionContext")] - public PermissionDecisionContext? DecisionContext { get; set; } - - /// Identifier of the exact pending sandbox-bypass permission request that authorized the session opt-out. - [JsonPropertyName("requestId")] - public string RequestId { get; set; } = string.Empty; - - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { 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; } -/// Result of accepting a sandbox path grant. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class SandboxGrantPathForRequestResult -{ - /// Whether this call resolved the pending request and added the path to the session's sandbox policy. - [JsonPropertyName("success")] - public bool Success { 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; } } -/// Request to accept the sandbox path grant offered on an active sandbox escalation permission prompt. +/// Params to attach or detach an in-process ExtensionController delegate. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SandboxGrantPathForRequestRequest +internal sealed class ConfigureSessionExtensionsParams { - /// Optional attribution for the permission decision. - [JsonPropertyName("decisionContext")] - public PermissionDecisionContext? DecisionContext { get; set; } - - /// Identifier of the exact pending sandbox escalation permission request whose sandboxPathGrant to accept. - [JsonPropertyName("requestId")] - public string RequestId { get; set; } = string.Empty; - - /// Target session identifier. + /// Session to attach the extension controller delegate to. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Authentication status and account metadata for the session. +/// Outcome of an agentRegistry.spawn call. +/// Polymorphic base type discriminated by kind. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class SessionAuthStatus +[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 { - /// Authentication type. - [JsonPropertyName("authType")] - public AuthInfoType? AuthType { get; set; } - - /// Copilot plan tier (e.g., individual_pro, business). - [JsonPropertyName("copilotPlan")] - public string? CopilotPlan { get; set; } + /// The type discriminator. + [JsonPropertyName("kind")] + public virtual string Kind { get; set; } = string.Empty; +} - /// Authentication host URL. - [Url] - [StringSyntax(StringSyntaxAttribute.Uri)] - [JsonPropertyName("host")] - public string? Host { get; set; } - /// Whether the session has resolved authentication. - [JsonPropertyName("isAuthenticated")] - public bool IsAuthenticated { 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). +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public sealed class AgentRegistryLiveTargetEntry +{ + /// Kind of attention required when status === "attention". Meaningful only when status === "attention". + [JsonPropertyName("attentionKind")] + public AgentRegistryLiveTargetEntryAttentionKind? AttentionKind { get; set; } - /// Authenticated login/username, if available. - [JsonPropertyName("login")] - public string? Login { get; set; } + /// Git branch of the session (when known). + [JsonPropertyName("branch")] + public string? Branch { get; set; } - /// Human-readable authentication status description. - [JsonPropertyName("statusMessage")] - public string? StatusMessage { get; set; } -} + /// Copilot CLI version that wrote the entry. + [JsonPropertyName("copilotVersion")] + public string CopilotVersion { get; set; } = string.Empty; -/// Identifies the target session. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionGitHubAuthGetStatusRequest -{ - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; -} + /// Working directory of the session (when known). + [JsonPropertyName("cwd")] + public string? Cwd { get; set; } -/// Indicates whether the credential update succeeded. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class 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). - [JsonPropertyName("copilotUserResolved")] - public bool? CopilotUserResolved { get; set; } + /// Bind host for the entry's JSON-RPC server. + [JsonPropertyName("host")] + public string Host { get; set; } = string.Empty; - /// Whether the operation succeeded. - [JsonPropertyName("success")] - public bool Success { get; set; } -} + /// Process kind tag for the registry entry. + [JsonPropertyName("kind")] + public AgentRegistryLiveTargetEntryKind Kind { get; set; } -/// 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(global::GitHub.Copilot.Diagnostics.Experimental)] -[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(SettableAuthInfoAccount), "account")] -[JsonDerivedType(typeof(SettableAuthInfoGhCli), "gh-cli")] -[JsonDerivedType(typeof(SettableAuthInfoApiKey), "api-key")] -public partial class SettableAuthInfo -{ - /// The type discriminator. - [JsonPropertyName("type")] - public virtual string Type { get; set; } = string.Empty; -} + /// Wall-clock milliseconds since the watcher last observed this entry (heartbeat freshness). + [JsonPropertyName("lastSeenMs")] + public long LastSeenMs { 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; } -/// Authentication-info input variant for GitHub-internal HMAC auth, carrying the public GitHub host and HMAC secret. -/// The hmac variant of . -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class SettableAuthInfoHmac : SettableAuthInfo -{ - /// - [JsonIgnore] - public override string Type => "hmac"; + /// Model identifier currently selected for the session. + [JsonPropertyName("model")] + public string? Model { 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; } + /// Operating-system pid of the process owning this entry. + [JsonPropertyName("pid")] + public long Pid { get; set; } - /// HMAC secret used to sign requests. - [JsonPropertyName("hmac")] - public required string Hmac { get; set; } + /// TCP port the entry's JSON-RPC server is listening on. + [JsonPropertyName("port")] + public long Port { get; set; } - /// Authentication host. HMAC auth always targets the public GitHub host. - [JsonPropertyName("host")] - public required string Host { get; set; } -} + /// Registry entry schema version (1 = ui-server, 2 = managed-server). + [JsonPropertyName("schemaVersion")] + public long SchemaVersion { get; set; } -/// 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(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class SettableAuthInfoEnv : SettableAuthInfo -{ - /// - [JsonIgnore] - public override string Type => "env"; + /// Session ID of the foreground session for this entry. + [JsonPropertyName("sessionId")] + public string? SessionId { 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; } + /// Friendly session name (when set). + [JsonPropertyName("sessionName")] + public string? SessionName { get; set; } - /// Name of the environment variable the token was sourced from. - [JsonPropertyName("envVar")] - public required string EnvVar { get; set; } + /// ISO 8601 timestamp captured at registration. + [JsonPropertyName("startedAt")] + public string StartedAt { get; set; } = string.Empty; - /// Authentication host (e.g. https://github.com or a GHES host). - [JsonPropertyName("host")] - public required string Host { get; set; } + /// Coarse lifecycle status of the foreground session. + [JsonPropertyName("status")] + public AgentRegistryLiveTargetEntryStatus? Status { 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; } + /// Monotonic per-publisher revision counter incremented on every status update. Lets watchers detect transient flips. + [JsonPropertyName("statusRevision")] + public long? StatusRevision { get; set; } - /// The token value itself. Treat as a secret. + /// Connection token (null when the target is unauthenticated). + [JsonInclude] [JsonPropertyName("token")] - public required string Token { get; set; } + internal string? Token { get; set; } } -/// Token authentication accepted by session.gitHubAuth.setCredentials. -/// The token variant of . +/// Canonical process-log discovery outcome; populated from spawnLiveTarget. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class SettableAuthInfoToken : SettableAuthInfo +public sealed class AgentRegistryLogCapture { - /// - [JsonIgnore] - public override string Type => "token"; + /// Whether a canonical process log was discovered for this managed spawn. + [JsonPropertyName("enabled")] + public bool Enabled { 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; } + /// Why no canonical process log could be opened for this managed spawn (set only when enabled is false). + [JsonPropertyName("openError")] + public string? OpenError { get; set; } - /// Authentication host. - [JsonPropertyName("host")] - public required string Host { get; set; } + /// Categorized reason no canonical process log could be opened (set only when enabled is false). + [JsonPropertyName("openErrorReason")] + public AgentRegistryLogCaptureOpenErrorReason? OpenErrorReason { get; set; } - /// The token value itself. Treat as a secret. - [JsonPropertyName("token")] - public required string Token { get; set; } + /// Absolute path to the managed spawn's process-<timestamp>-<pid>.log file (only set when enabled). + [JsonPropertyName("path")] + public string? Path { 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 . +/// Managed-server child was spawned and registered successfully. +/// The spawned variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class SettableAuthInfoCopilotApiToken : SettableAuthInfo +public partial class AgentRegistrySpawnResultSpawned : AgentRegistrySpawnResult { /// [JsonIgnore] - public override string Type => "copilot-api-token"; + public override string Kind => "spawned"; - /// 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. + /// 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; } + + /// If the delegate attempted to send the initial prompt and failed, the categorized error message. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("copilotUser")] - public CopilotUserResponse? CopilotUser { get; set; } + [JsonPropertyName("initialPromptError")] + public string? InitialPromptError { get; set; } - /// Authentication host (always the public GitHub host). - [JsonPropertyName("host")] - public required string Host { 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; } + + /// Per-spawn log-capture outcome; populated from spawnLiveTarget. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("logCapture")] + public AgentRegistryLogCapture? LogCapture { get; set; } } -/// Authentication-info variant for OAuth user auth, with host and login; the token remains in the runtime secret store. -/// The user variant of . +/// `child_process.spawn` itself failed before the child entered the registry. +/// The spawn-error variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class SettableAuthInfoUser : SettableAuthInfo +public partial class AgentRegistrySpawnResultSpawnError : AgentRegistrySpawnResult { /// [JsonIgnore] - public override string Type => "user"; + public override string Kind => "spawn-error"; - /// 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. + /// Underlying errno code (e.g. ENOENT, EACCES) when available. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("copilotUser")] - public CopilotUserResponse? CopilotUser { get; set; } - - /// Authentication host. - [JsonPropertyName("host")] - public required string Host { get; set; } + [JsonPropertyName("code")] + public string? Code { get; set; } - /// OAuth user login. - [JsonPropertyName("login")] - public required string Login { get; set; } + /// Human-readable error message. + [JsonPropertyName("message")] + public required string Message { get; set; } } -/// An interactive account whose model provider owns its credentials. It carries no GitHub credential. -/// The account variant of . +/// Spawn succeeded but the child did not publish a matching managed-server entry within the timeout. +/// The registry-timeout variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class SettableAuthInfoAccount : SettableAuthInfo +public partial class AgentRegistrySpawnResultRegistryTimeout : AgentRegistrySpawnResult { /// [JsonIgnore] - public override string Type => "account"; + public override string Kind => "registry-timeout"; - /// Host coordinate owned by the account's model provider. - [JsonPropertyName("host")] - public required string Host { 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; } - /// Login identifying the provider-owned account. - [JsonPropertyName("login")] - public required string Login { get; set; } + /// Per-spawn log-capture outcome; populated from spawnLiveTarget. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("logCapture")] + public AgentRegistryLogCapture? LogCapture { get; set; } } -/// Authentication-info input variant for GitHub CLI credentials, carrying host, login, and the `gh auth token` value. -/// The gh-cli variant of . +/// Synchronous pre-validation rejected the spawn request. +/// The validation-error variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class SettableAuthInfoGhCli : SettableAuthInfo +public partial class AgentRegistrySpawnResultValidationError : AgentRegistrySpawnResult { /// [JsonIgnore] - public override string Type => "gh-cli"; + public override string Kind => "validation-error"; - /// 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. + /// Which parameter field was invalid. Omitted when the rejection is not field-specific. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("copilotUser")] - public CopilotUserResponse? CopilotUser { get; set; } - - /// Authentication host. - [JsonPropertyName("host")] - public required string Host { get; set; } + [JsonPropertyName("field")] + public AgentRegistrySpawnValidationErrorField? Field { get; set; } - /// User login as reported by `gh auth status`. - [JsonPropertyName("login")] - public required string Login { 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; } - /// The token returned by `gh auth token`. Treat as a secret. - [JsonPropertyName("token")] - public required string Token { 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; } } -/// 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 . +/// Inputs to spawn a managed-server child via the controller's spawn delegate. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class SettableAuthInfoApiKey : SettableAuthInfo +internal sealed class AgentRegistrySpawnRequest { - /// - [JsonIgnore] - public override string Type => "api-key"; + /// Custom or built-in agent name (e.g. 'explore'). When omitted, the child uses its own default. + [JsonPropertyName("agentName")] + public string? AgentName { get; set; } - /// The API key. Treat as a secret. - [JsonPropertyName("apiKey")] - public required string ApiKey { get; set; } + /// Working directory for the spawned child (must be an existing directory). + [JsonPropertyName("cwd")] + public string Cwd { get; set; } = string.Empty; - /// 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; } + /// 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; } - /// Authentication host. - [JsonPropertyName("host")] - public required string Host { get; set; } + /// Model identifier to apply to the new session. + [JsonPropertyName("model")] + public string? Model { 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; } + + /// 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; } } -/// New auth credentials to install on the session. Omit to leave credentials unchanged. +/// Feature availability. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionSetCredentialsParams +public sealed class ConnectorDiscoveryCapabilities { - /// 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; } + /// API version. + [JsonPropertyName("apiVersion")] + public long ApiVersion { get; set; } - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Availability. + [JsonPropertyName("availability")] + public ConnectorDiscoveryAvailability Availability { get; set; } + + /// Whether results are cached. + [JsonPropertyName("conditionalCache")] + public bool ConditionalCache { get; set; } + + /// Whether accounts are selected by opaque ID. + [JsonPropertyName("opaqueAccountSelection")] + public bool OpaqueAccountSelection { get; set; } } -/// Credential-free authentication identity safe to expose to hosts and user interfaces. +/// Account metadata. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class AuthIdentity +public sealed class ConnectorDiscoveryAuthInfo { - /// Snapshot of the authenticated user's Copilot subscription info, if known. - [JsonPropertyName("copilotUser")] - public CopilotUserResponse? CopilotUser { get; set; } - - /// Name of the environment variable that supplied the credential, when applicable. - [JsonPropertyName("envVar")] - public string? EnvVar { get; set; } - - /// Authentication host. + /// Host. [JsonPropertyName("host")] public string Host { get; set; } = string.Empty; - /// Authenticated login, when available. + /// Login. [JsonPropertyName("login")] - public string? Login { get; set; } - - /// Opaque SDK GitHub credential registration backing this identity. Routing metadata only; never a credential. - [JsonPropertyName("registrationId")] - public string? RegistrationId { get; set; } + public string Login { get; set; } = string.Empty; /// Authentication type. [JsonPropertyName("type")] public AuthInfoType Type { get; set; } } -/// Identifies the target session. +/// Eligible account. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionGitHubAuthGetCurrentAuthInfoRequest +public sealed class ConnectorDiscoveryAccount { - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; -} + /// Opaque account ID. + [JsonPropertyName("accountId")] + public string AccountId { get; set; } = string.Empty; -/// Identifies the target session. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionGitHubAuthGetAllAuthAvailableRequest -{ - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Account metadata. + [JsonPropertyName("authInfo")] + public ConnectorDiscoveryAuthInfo AuthInfo { get => field ??= new(); set; } } -/// Identifies the target session. +/// Eligible accounts. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionGitHubAuthRefreshCopilotUserRequest +public sealed class ConnectorDiscoveryAccountList { - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Eligible accounts. + [JsonPropertyName("accounts")] + public IList Accounts { get => field ??= []; set; } + + /// Availability. + [JsonPropertyName("availability")] + public ConnectorDiscoveryAvailability Availability { get; set; } } -/// Internal GitHub login parameters. +/// Entry. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionAuthLoginRequest +public sealed class ConnectorDiscoveryCatalogEntry { - /// GitHub host URL. - [JsonPropertyName("host")] - public string Host { get; set; } = string.Empty; + /// Description. + [JsonPropertyName("description")] + public string? Description { get; set; } - /// GitHub login. - [JsonPropertyName("login")] - public string Login { get; set; } = string.Empty; + /// Display name. + [JsonPropertyName("displayName")] + public string DisplayName { get; set; } = string.Empty; - /// Whether to persist the token after login. - [JsonPropertyName("persist")] - public bool? Persist { get; set; } + /// Logo. + [JsonPropertyName("logo")] + public string? Logo { get; set; } - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Name. + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; - /// GitHub authentication token. - [JsonPropertyName("token")] - public string Token { get; set; } = string.Empty; -} + /// Release tag. + [JsonPropertyName("releaseTag")] + public string? ReleaseTag { get; set; } -/// Parameters for switching the session's active authentication. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionAuthSwitchRequest -{ - /// 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; + /// Status. + [JsonPropertyName("status")] + public ConnectorCatalogStatus Status { get; set; } - /// Optional token paired with the authentication information. - [JsonPropertyName("token")] - public string? Token { get; set; } + /// Tier. + [JsonPropertyName("tier")] + public string? Tier { get; set; } } -/// Identifies the target session. +/// Entries for the selected account. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionGitHubAuthLogoutRequest +public sealed class ConnectorDiscoveryCatalogResult { - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; -} + /// Opaque account ID. + [JsonPropertyName("accountId")] + public string AccountId { get; set; } = string.Empty; -/// Parameters identifying a GitHub authentication to log out. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionAuthLogoutUserRequest -{ - /// Authentication information to log out. - [JsonPropertyName("authInfo")] - public AuthInfo AuthInfo { get => field ??= new(); set; } + /// Entries. + [JsonPropertyName("connectors")] + public IList Connectors { get => field ??= []; set; } - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Refresh time in Unix epoch milliseconds. + [JsonPropertyName("refreshedAtMs")] + public long RefreshedAtMs { get; set; } + + /// Revision. + [JsonPropertyName("revision")] + public long Revision { get; set; } } -/// Validation error from an authentication attempt. +/// Selected account. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class AuthValidationError +internal sealed class ConnectorDiscoveryAccountRequest { - /// Optional message returned by GitHub. - [JsonPropertyName("githubMessage")] - public string? GitHubMessage { get; set; } - - /// Authentication validation error message. - [JsonPropertyName("message")] - public string Message { get; set; } = string.Empty; + /// Opaque account ID. + [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(2048)] + [JsonPropertyName("accountId")] + public string AccountId { get; set; } = string.Empty; } /// Identifies the target session. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionGitHubAuthLastAuthErrorsRequest +internal sealed class SessionSuspendRequest { /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// The enumerated collection, keyed by the same selector as the query. -/// Polymorphic base type discriminated by kind. +/// Result of sending a user message. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public sealed class SendResult +{ + /// Unique identifier assigned to the message. + [JsonPropertyName("messageId")] + public string MessageId { get; set; } = string.Empty; +} + +/// Provider-native structured output format. JSON Schema is forwarded without rewriting or validating the schema or the generated output. +/// Polymorphic base type discriminated by type. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] [JsonPolymorphic( - TypeDiscriminatorPropertyName = "kind", + TypeDiscriminatorPropertyName = "type", UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] -[JsonDerivedType(typeof(AuthEnumerateValueAccounts), "accounts")] -[JsonDerivedType(typeof(AuthEnumerateValueProviders), "providers")] -public partial class AuthEnumerateValue +[JsonDerivedType(typeof(ResponseFormatJsonSchema), "json_schema")] +public partial class ResponseFormat { /// The type discriminator. - [JsonPropertyName("kind")] - public virtual string Kind { get; set; } = string.Empty; + [JsonPropertyName("type")] + public virtual string Type { get; set; } = string.Empty; } -/// One signed-in account in the roster forest. +/// A JSON Schema output contract. OpenAI receives the name, description, schema and strict setting; Anthropic receives the schema in output_config.format and always uses its native strict enforcement. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class AccountStatus +public sealed class JsonSchemaResponseFormat { - /// Whether this is the active account. - [JsonPropertyName("active")] - public bool Active { get; set; } - - /// Opaque id of the account this one was derived from (e.g. an EMU account's base Entra identity); absent for a root account. Matches the base identity account's selectionId, forming the derivation edge. - [JsonPropertyName("derivedFrom")] - public string? DerivedFrom { get; set; } - - /// Authentication host URL. - [Url] - [StringSyntax(StringSyntaxAttribute.Uri)] - [JsonPropertyName("host")] - public string Host { get; set; } = string.Empty; + /// Optional description passed to OpenAI providers. + [JsonPropertyName("description")] + public string? Description { get; set; } - /// The provider kind of this account. - [JsonPropertyName("kind")] - public AccountKind Kind { get; set; } + /// Name of the output schema, subject to the provider's naming restrictions. + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; - /// Authenticated login/username. - [JsonPropertyName("login")] - public string Login { get; set; } = string.Empty; + /// JSON Schema passed unchanged to the inference provider. Schemas larger than 32 MiB when JSON-encoded are rejected before admission, using the runtime's existing request-size ceiling. This is not a guarantee that the entire model request fits. Supported keywords and schema restrictions are determined by the provider. + [JsonPropertyName("schema")] + public JsonElement Schema { get; set; } - /// Opaque selection id used to switch to, or log out, this account. - [JsonPropertyName("selectionId")] - public string SelectionId { get; set; } = string.Empty; + /// Optional strict enforcement setting for OpenAI providers. Omitted uses the provider default. Anthropic always enforces its supported schema subset. + [JsonPropertyName("strict")] + public bool? Strict { get; set; } } -/// The accounts variant of . +/// The json_schema variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class AuthEnumerateValueAccounts : AuthEnumerateValue +public partial class ResponseFormatJsonSchema : ResponseFormat { /// [JsonIgnore] - public override string Kind => "accounts"; + public override string Type => "json_schema"; - /// The signed-in account forest; empty when not logged in. - [JsonPropertyName("items")] - public required IList Items { get; set; } + /// JSON Schema and provider options for the turn's output. + [JsonPropertyName("jsonSchema")] + public required JsonSchemaResponseFormat JsonSchema { get; set; } } -/// A provider offered for interactive login. +/// Parameters for sending a user message to the session. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class ProviderDescriptor +internal sealed class SendRequest { - /// Whether this provider is currently available to sign in with. - [JsonPropertyName("available")] - public bool Available { get; set; } - - /// The neutral provider kind. - [JsonPropertyName("kind")] - public LoginProviderKind Kind { get; 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; } - /// Human-readable menu label, owned by the runtime so every consumer renders identical text. - [JsonPropertyName("label")] - public string Label { get; set; } = string.Empty; -} + /// Optional attachments (files, directories, selections, blobs, GitHub references) to include with the message. + [JsonPropertyName("attachments")] + public IList? Attachments { get; set; } -/// The providers variant of . -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class AuthEnumerateValueProviders : AuthEnumerateValue -{ - /// - [JsonIgnore] - public override string Kind => "providers"; + /// If false, this message will not trigger a Premium Request Unit charge. User messages default to billable. + [JsonPropertyName("billable")] + public bool? Billable { get; set; } - /// The providers offered for interactive login. - [JsonPropertyName("items")] - public required IList Items { get; set; } -} + /// If provided, this is shown in the timeline instead of `prompt`. + [JsonPropertyName("displayPrompt")] + public string? DisplayPrompt { get; set; } -/// Selects which accounts collection to enumerate. A no-arg selector is the empty-payload variant. -/// Polymorphic base type discriminated by kind. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -[JsonPolymorphic( - TypeDiscriminatorPropertyName = "kind", - UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] -[JsonDerivedType(typeof(AuthEnumerateQueryAccounts), "accounts")] -[JsonDerivedType(typeof(AuthEnumerateQueryProviders), "providers")] -public partial class AuthEnumerateQuery -{ - /// The type discriminator. - [JsonPropertyName("kind")] - public virtual string Kind { get; set; } = string.Empty; -} + /// 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 accounts variant of . -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class AuthEnumerateQueryAccounts : AuthEnumerateQuery -{ - /// - [JsonIgnore] - public override string Kind => "accounts"; -} + /// The user message text. + [JsonPropertyName("prompt")] + public string Prompt { get; set; } = string.Empty; -/// The providers variant of . -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class AuthEnumerateQueryProviders : AuthEnumerateQuery -{ - /// - [JsonIgnore] - public override string Kind => "providers"; + /// 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; } - /// Whether an interactive Entra broker is available on the host; gates Entra availability in the returned list. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("brokerAvailable")] - public bool? BrokerAvailable { 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; } -/// Enumerate request carrying the typed collection query. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class AccountsEnumerateRequest -{ - /// Which typed accounts collection to enumerate. - [JsonPropertyName("query")] - public AuthEnumerateQuery Query { get => field ??= new(); set; } + /// Provider-native output format for this turn, including all tool-call iterations. Not inherited by later turns or subagents. Ordinary steering inherits the active format; specifying responseFormat with mode: immediate is an error, even while idle. Returned assistant content remains text; the runtime does not parse or validate it. Unsupported models or schemas produce provider errors. + [JsonPropertyName("responseFormat")] + public ResponseFormat? ResponseFormat { get; set; } /// Target session identifier. [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; } } -/// The read result, keyed by the same selector as the query. -/// Polymorphic base type discriminated by kind. +/// Result of sending zero or more user messages. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -[JsonPolymorphic( - TypeDiscriminatorPropertyName = "kind", - UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] -[JsonDerivedType(typeof(AuthReadValueActiveAccount), "activeAccount")] -[JsonDerivedType(typeof(AuthReadValueStatus), "status")] -[JsonDerivedType(typeof(AuthReadValueLastErrors), "lastErrors")] -public partial class AuthReadValue +public sealed class SendMessagesResult { - /// The type discriminator. - [JsonPropertyName("kind")] - public virtual string Kind { get; set; } = string.Empty; + /// Unique identifiers assigned to the messages, one per provided message in order. For a batch that starts a run, assistant messages use the final ID as originatingMessageId throughout that run, including tool iterations and stop-hook corrections. Immediate steering does not replace the active run's origin. Empty when no messages were provided; that run has no originatingMessageId. + [JsonPropertyName("messageIds")] + public IList MessageIds { get => field ??= []; set; } } - -/// The activeAccount variant of . +/// A single user message to append to the session as part of a `session.sendMessages` turn. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class AuthReadValueActiveAccount : AuthReadValue +public sealed class SendMessageItem { - /// - [JsonIgnore] - public override string Kind => "activeAccount"; + /// Optional attachments (files, directories, selections, blobs, GitHub references) to include with this message. + [JsonPropertyName("attachments")] + public IList? Attachments { get; set; } - /// The active account, or absent when not logged in. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("account")] - public AccountStatus? Account { 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; } - /// Credential-free identity metadata for the active account, including resolved Copilot user information when available. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("authInfo")] - public AuthIdentity? AuthInfo { 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; } } -/// Neutral authentication status summary. +/// 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(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class AuthStatusDto +internal sealed class SendMessagesRequest { - /// Number of signed-in accounts in the roster. - [JsonPropertyName("accountCount")] - public long AccountCount { 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; } - /// Active account host, if authenticated. - [Url] - [StringSyntax(StringSyntaxAttribute.Uri)] - [JsonPropertyName("activeHost")] - public string? ActiveHost { get; set; } + /// The user messages to append to the conversation, in order, before running one agent loop. When the batch starts a run, its final message is the primary initiating message; earlier messages provide context, not separate runs or replies. May be empty, in which case a single turn runs over the existing history with no new user message or originatingMessageId. + [JsonPropertyName("messages")] + public IList Messages { get => field ??= []; set; } - /// Active account login, if authenticated. - [JsonPropertyName("activeLogin")] - public string? ActiveLogin { 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; } - /// Copilot plan tier of the active account, if known. - [JsonPropertyName("copilotPlan")] - public string? CopilotPlan { get; set; } + /// If true, adds the messages to the front of the queue instead of the end. + [JsonPropertyName("prepend")] + public bool? Prepend { get; set; } - /// Whether the session has resolved authentication. - [JsonPropertyName("isAuthenticated")] - public bool IsAuthenticated { 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; } + + /// Provider-native output format for the whole turn, including an empty message batch and all tool-call iterations. Not inherited by later turns or subagents. Ordinary steering inherits the active format; specifying responseFormat with mode: immediate is an error, even while idle. Returned assistant content remains text; the runtime does not parse or validate it. Unsupported models or schemas produce provider errors. + [JsonPropertyName("responseFormat")] + public ResponseFormat? ResponseFormat { get; set; } + + /// Target session identifier. + [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; } } -/// The status variant of . +/// Internal request for sending a system notification. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class AuthReadValueStatus : AuthReadValue +internal sealed class SendSystemNotificationRequest { - /// - [JsonIgnore] - public override string Kind => "status"; + /// Optional structured notification kind. + [JsonPropertyName("kind")] + public JsonElement? Kind { get; set; } - /// The neutral authentication status summary. - [JsonPropertyName("status")] - public required AuthStatusDto Status { get; set; } + /// Notification text to deliver to the model. + [JsonPropertyName("message")] + public string Message { get; set; } = string.Empty; + + /// Internal delivery options, including passive policy. + [JsonPropertyName("options")] + public JsonElement? Options { get; set; } + + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } -/// The lastErrors variant of . +/// Result of aborting the current turn. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class AuthReadValueLastErrors : AuthReadValue +public sealed class AbortResult { - /// - [JsonIgnore] - public override string Kind => "lastErrors"; + /// Error message if the abort failed. + [JsonPropertyName("error")] + public string? Error { get; set; } - /// Validation errors from the most recent authentication attempt. - [JsonPropertyName("errors")] - public required IList Errors { get; set; } + /// Whether the abort completed successfully. + [JsonPropertyName("success")] + public bool Success { get; set; } } -/// Selects which typed accounts datum to read. -/// Polymorphic base type discriminated by kind. +/// Parameters for aborting the current turn. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -[JsonPolymorphic( - TypeDiscriminatorPropertyName = "kind", - UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] -[JsonDerivedType(typeof(AuthReadQueryActiveAccount), "activeAccount")] -[JsonDerivedType(typeof(AuthReadQueryStatus), "status")] -[JsonDerivedType(typeof(AuthReadQueryLastErrors), "lastErrors")] -public partial class AuthReadQuery +internal sealed class AbortRequest { - /// The type discriminator. - [JsonPropertyName("kind")] - public virtual string Kind { get; set; } = string.Empty; -} + /// 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; +} -/// The activeAccount variant of . +/// Result of interrupting the main agent turn. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class AuthReadQueryActiveAccount : AuthReadQuery +public sealed class InterruptMainTurnResult { - /// - [JsonIgnore] - public override string Kind => "activeAccount"; + /// Whether an in-flight main agent turn was interrupted. False when the main loop was not processing. + [JsonPropertyName("interrupted")] + public bool Interrupted { get; set; } } -/// The status variant of . +/// Parameters for interrupting the main agent turn. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class AuthReadQueryStatus : AuthReadQuery +internal sealed class InterruptMainTurnRequest { - /// - [JsonIgnore] - public override string Kind => "status"; + /// 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 lastErrors variant of . +/// Identifies the target session. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class AuthReadQueryLastErrors : AuthReadQuery +internal sealed class SessionCancelAllBackgroundAgentsRequest { - /// - [JsonIgnore] - public override string Kind => "lastErrors"; + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } -/// Read request carrying the typed datum query. +/// Parameters for shutting down the session. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class AccountsGetRequest +internal sealed class ShutdownRequest { - /// Which typed accounts datum to read. - [JsonPropertyName("query")] - public AuthReadQuery Query { get => field ??= new(); set; } + /// Dispatch deferred sessionEnd hooks in the background with their full per-hook timeoutSec instead of awaiting them under the short shared shutdown budget. Set this when the host process keeps running after the session closes (for example the CLI's /clear), so a slow hook neither blocks the close nor is aborted. Hooks still detached when the process later exits are terminated with it. Defaults to false. + [JsonPropertyName("detachSessionEndHooks")] + public bool? DetachSessionEndHooks { get; set; } + + /// 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; } } -/// Result of a non-interactive accounts mutation. +/// Identifier of the session event that was emitted for the log message. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class AuthWriteResult +public sealed class LogResult { - /// For a logout, whether other signed-in accounts remain. - [JsonPropertyName("moreUsers")] - public bool? MoreUsers { get; set; } - - /// Whether the mutation was applied. - [JsonPropertyName("ok")] - public bool Ok { get; set; } + /// The unique identifier of the emitted session event. + [JsonPropertyName("eventId")] + public Guid EventId { get; set; } } -/// One non-interactive accounts mutation command (the selector is fused with its typed args). -/// Polymorphic base type discriminated by kind. +/// Message text, optional severity level, persistence flag, optional follow-up URL, and optional tip. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -[JsonPolymorphic( - TypeDiscriminatorPropertyName = "kind", - UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] -[JsonDerivedType(typeof(AuthWriteSwitchActive), "switchActive")] -[JsonDerivedType(typeof(AuthWriteLogout), "logout")] -[JsonDerivedType(typeof(AuthWriteSetCredentials), "setCredentials")] -public partial class AuthWrite +internal sealed class LogRequest { - /// The type discriminator. - [JsonPropertyName("kind")] - public virtual string Kind { 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; } + /// Log severity level. Determines how the message is displayed in the timeline. Defaults to "info". + [JsonPropertyName("level")] + public SessionLogLevel? Level { get; set; } -/// The switchActive variant of . -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class AuthWriteSwitchActive : AuthWrite -{ - /// - [JsonIgnore] - public override string Kind => "switchActive"; + /// Human-readable message. + [JsonPropertyName("message")] + public string Message { get; set; } = string.Empty; - /// Opaque selection id of the account to make active. - [JsonPropertyName("selectionId")] - public required string SelectionId { get; set; } -} + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; -/// The logout variant of . -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class AuthWriteLogout : AuthWrite -{ - /// - [JsonIgnore] - public override string Kind => "logout"; + /// Optional actionable tip displayed alongside the message. Only honored on `level: "info"`. + [JsonPropertyName("tip")] + public string? Tip { get; set; } - /// Opaque selection id of the account to log out; absent logs out the active account. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("selectionId")] - public string? SelectionId { 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; } } -/// The setCredentials variant of . +/// Adapter-declared policy that tells clients whether discovery may run automatically. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class AuthWriteSetCredentials : AuthWrite +public sealed class ModelProviderAutomaticDiscoveryPolicy { - /// - [JsonIgnore] - public override string Kind => "setCredentials"; + /// Whether automatic discovery is allowed, limited to configured providers, or explicit-only. + [JsonPropertyName("mode")] + public ModelProviderAutomaticDiscoveryMode Mode { get; set; } - /// Authentication host URL. - [Url] - [StringSyntax(StringSyntaxAttribute.Uri)] - [JsonPropertyName("host")] - public required string Host { get; set; } + /// Maximum network scope used by this adapter during discovery. + [JsonPropertyName("networkScope")] + public ModelProviderDiscoveryNetworkScope NetworkScope { get; set; } - /// Login/username for the credential. - [JsonPropertyName("login")] - public required string Login { get; set; } + /// True when discovery requires non-null caller input. Omission or null is rejected before adapter execution. When false, omitted or null input selects adapter defaults without schema validation. + [JsonPropertyName("requiresInput")] + public bool RequiresInput { get; set; } - /// GitHub authentication token to install. - [JsonPropertyName("token")] - public required string Token { get; set; } + /// True when the adapter must be enabled by a trusted owner, such as a trusted extension, before automatic discovery may run. + [JsonPropertyName("requiresTrust")] + public bool RequiresTrust { get; set; } } -/// Mutation request carrying the typed write command. +/// An operation supported by a model-provider adapter. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class AccountsSetRequest +public sealed class ModelProviderAdapterOperationDescriptor { - /// The non-interactive mutation command to apply. - [JsonPropertyName("command")] - public AuthWrite Command { get => field ??= new(); set; } + /// Optional self-contained JSON Schema Draft 7 for non-null discovery input. Only supported on discover. No external references are resolved. Omitted or null input selects defaults when requiresInput is false. Without a schema, the adapter validates supplied input. + [JsonPropertyName("inputSchema")] + public JsonElement? InputSchema { get; set; } - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Supported operation name: `discover`, `getStatus`, or `models.list`. Unknown names and duplicate declarations are rejected. + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; } -/// One step in an interactive login flow. The consumer acts on the step and calls advance to proceed. Browser-open is encoded as two distinct steps by design: `open-url` is CONSUMER-driven (the provider surfaces the authorize URL and the consumer opens it — github.com/GHEC web), while `needs-interaction` is PROVIDER-driven (the provider opens the browser or broker UI itself and does not surface a URL — Entra). -/// Polymorphic base type discriminated by kind. +/// Contributor attribution, independent of routing identity and authorization. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -[JsonPolymorphic( - TypeDiscriminatorPropertyName = "kind", - UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] -[JsonDerivedType(typeof(AuthLoginStepOpenUrl), "open-url")] -[JsonDerivedType(typeof(AuthLoginStepInputRequired), "input-required")] -[JsonDerivedType(typeof(AuthLoginStepAwaiting), "awaiting")] -[JsonDerivedType(typeof(AuthLoginStepNeedsInteraction), "needs-interaction")] -[JsonDerivedType(typeof(AuthLoginStepCompleted), "completed")] -[JsonDerivedType(typeof(AuthLoginStepError), "error")] -public partial class AuthLoginStep +public sealed class ModelProviderAttribution { - /// The type discriminator. - [JsonPropertyName("kind")] - public virtual string Kind { get; set; } = string.Empty; -} - + /// Human-readable contributor name, not the adapter display name. + [JsonPropertyName("ownerDisplayName")] + public string? OwnerDisplayName { get; set; } -/// The open-url variant of . -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class AuthLoginStepOpenUrl : AuthLoginStep -{ - /// - [JsonIgnore] - public override string Kind => "open-url"; + /// Stable contributor identifier. Required and nonblank for extension and custom sources; optional for built-in and configured sources. Does not grant authority. + [JsonPropertyName("ownerId")] + public string? OwnerId { get; set; } - /// Authorize URL the consumer should open in a browser (consumer-driven browser-open). - [JsonPropertyName("url")] - public required string Url { get; set; } + /// Kind of component that supplied the adapter. Attribution does not confer authority. + [JsonPropertyName("source")] + public ModelProviderProvenanceSource Source { get; set; } } -/// The input-required variant of . +/// A normalized model-provider adapter in the session's effective catalog. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class AuthLoginStepInputRequired : AuthLoginStep +public sealed class ModelProviderAdapterDescriptor { - /// - [JsonIgnore] - public override string Kind => "input-required"; + /// Stable opaque identity for routing to this adapter. Unique in the effective catalog, independent of live registration generations. + [JsonPropertyName("adapterId")] + public string AdapterId { get; set; } = string.Empty; - /// Prompt for the value the provider needs; the consumer supplies it as advance input (e.g. a GitHub Enterprise Cloud host, *.ghe.com). - [JsonPropertyName("prompt")] - public required string Prompt { get; set; } + /// Adapter-declared policy for passive and automatic discovery. + [JsonPropertyName("automaticDiscovery")] + public ModelProviderAutomaticDiscoveryPolicy AutomaticDiscovery { get => field ??= new(); set; } + + /// Human-readable provider name. + [JsonPropertyName("displayName")] + public string DisplayName { get; set; } = string.Empty; + + /// Operations supported by this provider adapter. + [JsonPropertyName("operations")] + public IList Operations { get => field ??= []; set; } + + /// Attribution for the adapter itself. + [JsonPropertyName("provenance")] + public ModelProviderAttribution Provenance { get => field ??= new(); set; } + + /// Descriptive provider family, such as `ollama`. Different adapters may have the same family; use adapterId for routing. + [JsonPropertyName("providerKind")] + public string ProviderKind { get; set; } = string.Empty; } -/// The awaiting variant of . +/// Normalized model-provider adapter definitions available to the session, not discovered instances. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class AuthLoginStepAwaiting : AuthLoginStep +public sealed class ModelProviderAdapterCatalog { - /// - [JsonIgnore] - public override string Kind => "awaiting"; + /// Available provider adapters ordered by adapterId. + [JsonPropertyName("providers")] + public IList Providers { get => field ??= []; set; } } -/// The needs-interaction variant of . +/// Identifies the target session. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class AuthLoginStepNeedsInteraction : AuthLoginStep +internal sealed class SessionProvidersGetCatalogRequest { - /// - [JsonIgnore] - public override string Kind => "needs-interaction"; + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } -/// A credential-free account choice after sign-in. +/// Attribution for the adapter that produced a provider row. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class AuthLoginAccount +public sealed class ModelProviderProvenance { - /// Host coordinate owned by the selected account's provider. - [JsonPropertyName("host")] - public string Host { get; set; } = string.Empty; + /// Stable opaque adapter identity from the effective catalog. Treat this as a whole identifier, not a parseable owner or kind. + [JsonPropertyName("adapterId")] + public string AdapterId { get; set; } = string.Empty; - /// Provider kind that owns this account choice. - [JsonPropertyName("kind")] - public AccountKind Kind { get; set; } + /// Human-readable contributor name, not the adapter display name. + [JsonPropertyName("ownerDisplayName")] + public string? OwnerDisplayName { get; set; } - /// Human-readable login for the account choice. - [JsonPropertyName("login")] - public string Login { get; set; } = string.Empty; + /// Stable contributor identifier when the adapter has an owner outside the runtime. Independent of the contribution mechanism and not a routing key. + [JsonPropertyName("ownerId")] + public string? OwnerId { get; set; } - /// Opaque identifier supplied to the next login step to select this account. - [JsonPropertyName("selectionId")] - public string SelectionId { get; set; } = string.Empty; + /// Descriptive provider family that produced this row; not a routing key. + [JsonPropertyName("providerKind")] + public string ProviderKind { get; set; } = string.Empty; + + /// Kind of component that supplied the adapter. + [JsonPropertyName("source")] + public ModelProviderProvenanceSource Source { get; set; } } -/// Result of an interactive login flow. Pending consent or account selection is not terminal. +/// Serializable reference to a discovered provider instance. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class AuthLoginResultDto +public sealed class ModelProviderInstanceReference { - /// Available accounts when sign-in is awaiting account selection, ordered with Microsoft 365 first. - [JsonPropertyName("accounts")] - public IList? Accounts { get; set; } + /// Stable opaque identity of the adapter that owns this reference. Must be present in the target session's effective catalog. + [JsonPropertyName("adapterId")] + public string AdapterId { get; set; } = string.Empty; - /// Host that was signed in, when completed. + /// Stable instance identifier derived by the provider adapter, such as `ollama:{normalizedEndpoint}`. + [JsonPropertyName("id")] + public string Id { get; set; } = string.Empty; + + /// Absolute provider management URI. The adapter validates normalization, supported schemes, and permission to access it against its bound configuration; a reference does not grant authority. [Url] [StringSyntax(StringSyntaxAttribute.Uri)] - [JsonPropertyName("host")] - public string? Host { get; set; } - - /// Login that was signed in, when completed. - [JsonPropertyName("login")] - public string? Login { get; set; } + [JsonPropertyName("managementEndpoint")] + public string ManagementEndpoint { get; set; } = string.Empty; - /// Current disposition of the login, including pending user decisions. - [JsonPropertyName("status")] - public AuthLoginResultStatus Status { get; set; } + /// Descriptive provider family. Must match the selected adapter; not a routing key. + [JsonPropertyName("providerKind")] + public string ProviderKind { get; set; } = string.Empty; } -/// The completed variant of . +/// A normalized model-provider instance discovered by the runtime. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class AuthLoginStepCompleted : AuthLoginStep +public sealed class ModelProviderInstance { - /// - [JsonIgnore] - public override string Kind => "completed"; + /// Human-readable instance name. + [JsonPropertyName("displayName")] + public string DisplayName { get; set; } = string.Empty; - /// Login result. When status is needs-plaintext-consent or needs-account-selection, advance with the user's decision to continue. - [JsonPropertyName("result")] - public required AuthLoginResultDto Result { get; set; } + /// Inference API endpoint when the provider exposes one separately from its management endpoint. + [Url] + [StringSyntax(StringSyntaxAttribute.Uri)] + [JsonPropertyName("inferenceEndpoint")] + public string? InferenceEndpoint { get; set; } + + /// Transport to use for inference against this instance. + [JsonPropertyName("inferenceTransport")] + public ProviderEndpointTransport? InferenceTransport { get; set; } + + /// Provider family to use for inference against this instance. + [JsonPropertyName("inferenceType")] + public ProviderEndpointType? InferenceType { get; set; } + + /// Wire API to use for inference against this instance, when required by the provider family. + [JsonPropertyName("inferenceWireApi")] + public ProviderEndpointWireApi? InferenceWireApi { get; set; } + + /// Attribution for the adapter that produced this instance. + [JsonPropertyName("provenance")] + public ModelProviderProvenance Provenance { get => field ??= new(); set; } + + /// Self-contained reference for subsequent provider operations. + [JsonPropertyName("reference")] + public ModelProviderInstanceReference Reference { get => field ??= new(); set; } } -/// The error variant of . +/// Typed provider-operation outcome. Use the code for control flow and the optional message for display. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class AuthLoginStepError : AuthLoginStep +public sealed class ModelProviderOperationOutcome { - /// - [JsonIgnore] - public override string Kind => "error"; + /// Machine-readable operation outcome. + [JsonPropertyName("code")] + public ModelProviderOperationOutcomeCode Code { get; set; } - /// Human-readable failure message. + /// Human-readable detail for non-success outcomes. [JsonPropertyName("message")] - public required string Message { get; set; } + public string? Message { get; set; } } -/// A started login flow: its opaque id and first step. +/// Provider instances found by a discovery operation. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class AuthLoginBegun +public sealed class ModelProviderDiscoverResult { - /// Opaque flow id used to advance or cancel this login. - [JsonPropertyName("flowId")] - public string FlowId { get; set; } = string.Empty; + /// Discovered provider instances. Empty when passive default discovery finds no reachable provider. + [JsonPropertyName("instances")] + public IList Instances { get => field ??= []; set; } - /// The first step of the flow. - [JsonPropertyName("step")] - public AuthLoginStep Step { get => field ??= new(); set; } + /// Typed operation outcome. Passive discovery can return `absent` with an empty instance list. + [JsonPropertyName("outcome")] + public ModelProviderOperationOutcome Outcome { get => field ??= new(); set; } } -/// Begin an interactive login flow for a provider kind. Dispatch is kind-only. +/// Provider discovery parameters. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class AuthLoginBeginRequest +internal sealed class ModelProviderDiscoverRequest { - /// The provider kind to sign in with. - [JsonPropertyName("kind")] - public LoginProviderKind Kind { get; set; } + /// Opaque adapter identity returned by `session.providers.getCatalog`. + [JsonPropertyName("adapterId")] + public string AdapterId { get; set; } = string.Empty; + + /// Provider-specific JSON input. Omission or null selects adapter defaults unless requiresInput is true. Non-null input is validated against the advertised Draft 7 schema when present; otherwise validation belongs to the adapter. + [JsonPropertyName("input")] + public JsonElement? Input { get; set; } /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Advance an in-flight login flow, optionally fulfilling an input-required step. +/// Current health information for a provider instance. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class AuthLoginAdvanceRequest +public sealed class ModelProviderStatus { - /// Opaque flow id from begin. - [JsonPropertyName("flowId")] - public string FlowId { get; set; } = string.Empty; + /// Normalized provider instance. + [JsonPropertyName("instance")] + public ModelProviderInstance Instance { get => field ??= new(); set; } - /// Neutral input fulfilling a preceding input-required step (e.g. a GHEC host); ignored otherwise. - [JsonPropertyName("input")] - public string? Input { get; set; } + /// Typed operation outcome. + [JsonPropertyName("outcome")] + public ModelProviderOperationOutcome Outcome { get => field ??= new(); set; } - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Open provider status value, such as `healthy`, `unreachable`, or `notInstalled`. + [JsonPropertyName("status")] + public string Status { get; set; } = string.Empty; + + /// Provider-reported version. + [JsonPropertyName("version")] + public string? Version { get; set; } } -/// Cancel an in-flight login flow. +/// Provider status request parameters. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class AuthLoginCancelRequest +internal sealed class ModelProviderGetStatusRequest { - /// Opaque flow id from begin. - [JsonPropertyName("flowId")] - public string FlowId { get; set; } = string.Empty; + /// Provider instance reference returned by discovery. + [JsonPropertyName("instance")] + public ModelProviderInstanceReference Instance { get => field ??= new(); set; } /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// A file included in the session debug bundle. +/// Provider-reported model artifact metadata. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class DebugCollectLogsCollectedEntry +public sealed class ModelArtifactDetails { - /// Relative path of the file in the staged bundle/archive. - [JsonPropertyName("bundlePath")] - public string BundlePath { get; set; } = string.Empty; + /// Provider-reported model architecture. + [JsonPropertyName("architecture")] + public string? Architecture { get; set; } - /// Redacted output size in bytes. - [JsonPropertyName("sizeBytes")] - public long SizeBytes { get; set; } + /// Provider-reported model families. + [JsonPropertyName("families")] + public IList? Families { get; set; } - /// Source category for this entry. - [JsonPropertyName("source")] - public DebugCollectLogsSource Source { get; set; } + /// Primary model family. + [JsonPropertyName("family")] + public string? Family { get; set; } + + /// Artifact format, such as `gguf`. + [JsonPropertyName("format")] + public string? Format { get; set; } + + /// Provider-reported parameter count label. + [JsonPropertyName("parameterSize")] + public string? ParameterSize { get; set; } + + /// Provider-reported quantization label. + [JsonPropertyName("quantization")] + public string? Quantization { get; set; } + + /// Provider-reported tokenizer. + [JsonPropertyName("tokenizer")] + public string? Tokenizer { get; set; } } -/// An optional debug bundle entry that could not be included. +/// A non-fatal provider observation warning. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class DebugCollectLogsSkippedEntry +public sealed class ModelProviderWarning { - /// 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; } + /// Machine-readable warning code. + [JsonPropertyName("code")] + public string Code { get; set; } = string.Empty; - /// Reason the entry was skipped. - [JsonPropertyName("reason")] - public string Reason { get; set; } = string.Empty; + /// Human-readable warning message. + [JsonPropertyName("message")] + public string Message { get; set; } = string.Empty; } -/// Result of collecting a session debug bundle. +/// A model offered for agent conversations. Missing capability metadata does not disqualify a candidate. Models known to be incompatible, such as embedding-only models, are excluded by the adapter. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class DebugCollectLogsResult +public sealed class DiscoveredModel { - /// Files included in the bundle. - [JsonPropertyName("entries")] - public IList Entries { get => field ??= []; set; } + /// Provider-reported model capabilities. Omitted capability fields are unknown; explicit false values are preserved. + [JsonPropertyName("capabilities")] + public ModelCapabilities Capabilities { get => field ??= new(); set; } - /// Destination kind that was written. - [JsonPropertyName("kind")] - public DebugCollectLogsResultKind Kind { get; set; } + /// Provider-reported model artifact details. + [JsonPropertyName("details")] + public ModelArtifactDetails Details { get => field ??= new(); 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; + /// Provider-reported artifact digest. + [JsonPropertyName("digest")] + public string? Digest { get; set; } - /// Optional files or directories that could not be included. - [JsonPropertyName("skippedEntries")] - public IList? SkippedEntries { get; set; } -} + /// Provider-native model identifier. + [JsonPropertyName("id")] + public string Id { get; set; } = string.Empty; -/// A caller-provided server-local file or directory to include in the debug bundle. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class DebugCollectLogsEntry -{ - /// Relative path to use inside the staged bundle/archive. - [JsonPropertyName("bundlePath")] - public string BundlePath { get; set; } = string.Empty; + /// Provider-reported last-modified timestamp. + [JsonPropertyName("modifiedAt")] + public DateTimeOffset? ModifiedAt { get; set; } - /// Kind of source path to include. - [JsonPropertyName("kind")] - public DebugCollectLogsEntryKind Kind { get; set; } + /// Provider-reported display name. + [JsonPropertyName("name")] + public string? Name { get; set; } - /// Server-local source path to read. - [JsonPropertyName("path")] - public string Path { get; set; } = string.Empty; + /// Attribution for the adapter that produced this model row. + [JsonPropertyName("provenance")] + public ModelProviderProvenance Provenance { get => field ??= new(); set; } - /// How text content from this entry should be redacted. Defaults to plain-text. With none, no redaction is applied; the caller must ensure any necessary redaction is performed before this call. - [JsonPropertyName("redaction")] - public DebugCollectLogsRedaction? Redaction { get; set; } + /// Provider-reported artifact size in bytes. + [JsonPropertyName("sizeBytes")] + public long? SizeBytes { 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; } + /// Non-fatal warnings encountered while enriching this model. + [JsonPropertyName("warnings")] + public IList Warnings { get => field ??= []; set; } } -/// Destination for the session debug bundle. -/// Polymorphic base type discriminated by kind. +/// Models offered for agent conversations by one provider instance. Adapters exclude known-incompatible models, but retain candidates with unknown capabilities. Listing does not guarantee compatibility. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -[JsonPolymorphic( - TypeDiscriminatorPropertyName = "kind", - UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] -[JsonDerivedType(typeof(DebugCollectLogsDestinationArchive), "archive")] -[JsonDerivedType(typeof(DebugCollectLogsDestinationDirectory), "directory")] -public partial class DebugCollectLogsDestination +public sealed class DiscoveredModelList { - /// The type discriminator. - [JsonPropertyName("kind")] - public virtual string Kind { get; set; } = string.Empty; -} + /// Provider-native models in provider order. + [JsonPropertyName("models")] + public IList Models { get => field ??= []; set; } + /// Typed operation outcome. + [JsonPropertyName("outcome")] + public ModelProviderOperationOutcome Outcome { get => field ??= new(); set; } +} -/// The archive variant of . +/// Provider model inventory request parameters. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class DebugCollectLogsDestinationArchive : DebugCollectLogsDestination +internal sealed class ModelProviderModelsListRequest { - /// - [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; } + /// Provider instance reference returned by discovery. + [JsonPropertyName("instance")] + public ModelProviderInstanceReference Instance { get => field ??= new(); set; } - /// Absolute or server-relative path for the .tgz archive to create. - [JsonPropertyName("outputPath")] - public required string OutputPath { get; set; } + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } -/// The directory variant of . +/// Vision-specific limits. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class DebugCollectLogsDestinationDirectory : DebugCollectLogsDestination +public sealed class ModelCapabilitiesOverrideLimitsVision { - /// - [JsonIgnore] - public override string Kind => "directory"; + /// Maximum image size in bytes. + [JsonPropertyName("max_prompt_image_size")] + public long? MaxPromptImageSize { get; set; } - /// Directory where files should be staged. The directory is created if needed. - [JsonPropertyName("outputDirectory")] - public required string OutputDirectory { get; set; } + /// Maximum number of images per prompt. + [JsonPropertyName("max_prompt_images")] + public long? MaxPromptImages { get; set; } + + /// MIME types the model accepts. + [JsonPropertyName("supported_media_types")] + public IList? SupportedMediaTypes { get; set; } } -/// Built-in session diagnostics to include in the bundle. Omitted fields default to true. +/// Token limits for prompts, outputs, and context window. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class DebugCollectLogsInclude +public sealed class ModelCapabilitiesOverrideLimits { - /// 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; } - - /// Include the session event log (`events.jsonl`). Defaults to true. - [JsonPropertyName("events")] - public bool? Events { 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; } - - /// Maximum number of previous process logs to include. Defaults to 5. - [JsonPropertyName("previousProcessLogLimit")] - public long? PreviousProcessLogLimit { get; set; } + /// Maximum total context window size in tokens. + [JsonPropertyName("max_context_window_tokens")] + public long? MaxContextWindowTokens { 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; } + /// Maximum number of output/completion tokens. + [JsonPropertyName("max_output_tokens")] + public long? MaxOutputTokens { get; set; } - /// Include process logs for the session. Defaults to true. - [JsonPropertyName("processLogs")] - public bool? ProcessLogs { get; set; } + /// Maximum number of prompt/input tokens. + [JsonPropertyName("max_prompt_tokens")] + public long? MaxPromptTokens { get; set; } - /// Include interactive shell logs written under the session's `shell-logs` directory. Defaults to true. - [JsonPropertyName("shellLogs")] - public bool? ShellLogs { get; set; } + /// Vision-specific limits. + [JsonPropertyName("vision")] + public ModelCapabilitiesOverrideLimitsVision? Vision { get; set; } } -/// Options for collecting a session debug bundle with configurable redaction. +/// Feature flags indicating what the model supports. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class DebugCollectLogsRequest +public sealed class ModelCapabilitiesOverrideSupports { - /// 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; } + /// Resolved Anthropic adaptive-thinking capability — unsupported / optional / required / adaptive_only. 'required' models reject thinking.type='enabled' with HTTP 400 but still accept 'disabled' (e.g. opus-4.7/4.8/5, sonnet-5); 'adaptive_only' models accept nothing but 'adaptive' (e.g. fable, mythos). + [JsonPropertyName("adaptive_thinking")] + public AdaptiveThinkingSupport? AdaptiveThinking { get; set; } - /// Where the bundle should be written. Use `archive` to produce a .tgz, or `directory` to stage files for caller-managed upload/post-processing. - [JsonPropertyName("destination")] - public DebugCollectLogsDestination Destination { get => field ??= new(); set; } + /// Whether this model supports reasoning effort configuration. + [JsonPropertyName("reasoningEffort")] + public bool? ReasoningEffort { get; set; } - /// Which built-in session diagnostics to include. Omitted fields default to true. - [JsonPropertyName("include")] - public DebugCollectLogsInclude? Include { get; set; } + /// Whether this model supports canonical tool calling. + [JsonPropertyName("toolCalls")] + public bool? ToolCalls { get; set; } - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Whether this model supports vision/image input. + [JsonPropertyName("vision")] + public bool? Vision { get; set; } } -/// Canvas action that the agent or host can invoke. To discover the input schema for a particular action, call the list_canvas_capabilities tool. +/// Optional capability overrides (vision, tool_calls, reasoning, etc.). [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class CanvasAction +public sealed class ModelCapabilitiesOverride { - /// Description of the action. - [JsonPropertyName("description")] - public string? Description { get; set; } - - /// JSON Schema for the action input. - [JsonPropertyName("inputSchema")] - public JsonElement? InputSchema { get; set; } + /// Token limits for prompts, outputs, and context window. + [JsonPropertyName("limits")] + public ModelCapabilitiesOverrideLimits? Limits { get; set; } - /// Action name exposed by the canvas provider. - [JsonPropertyName("name")] - public string Name { get; set; } = string.Empty; + /// Feature flags indicating what the model supports. + [JsonPropertyName("supports")] + public ModelCapabilitiesOverrideSupports? Supports { get; set; } } -/// Canvas available in the current session. +/// RPC data type for ProtocolSystemMessageAppendConfig operations. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class DiscoveredCanvas +public sealed class ProtocolSystemMessageAppendConfig { - /// 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; - - /// 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; } + /// Text appended to the standard system prompt. + [JsonPropertyName("content")] + public string? Content { get; set; } - /// JSON Schema for canvas open input. - [JsonPropertyName("inputSchema")] - public JsonElement? InputSchema { get; set; } + /// Append-mode discriminator. Omission also selects append mode. + [JsonPropertyName("mode")] + public ProtocolAppendMode? Mode { get; set; } } -/// Declared canvases available in this session. +/// RPC data type for SystemMessageBlock operations. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class CanvasList +public sealed class SystemMessageBlock { - /// Declared canvases available in this session. - [JsonPropertyName("canvases")] - public IList Canvases { get => field ??= []; set; } -} + /// Whether providers with explicit prompt caching should place a cache breakpoint after this block. + [JsonPropertyName("cacheBreakpoint")] + public bool? CacheBreakpoint { get; set; } -/// Identifies the target session. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionCanvasListRequest -{ - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Text content for this system-message block. + [JsonPropertyName("content")] + public string Content { get; set; } = string.Empty; + + /// Whether the block is static and may be cached independently of dynamic prompt content. + [JsonPropertyName("isStatic")] + public bool? IsStatic { get; set; } } -/// Open canvas instance snapshot. +/// RPC data type for ProtocolSystemMessageReplaceConfig operations. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class OpenCanvasInstance +public sealed class ProtocolSystemMessageReplaceConfig { - /// 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; - - /// 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; } - - /// Input supplied when the instance was opened. - [JsonPropertyName("input")] - public JsonElement? Input { get; set; } - - /// Stable caller-supplied canvas instance identifier. - [JsonPropertyName("instanceId")] - public string InstanceId { get; set; } = string.Empty; - - /// Provider-supplied status text. - [JsonPropertyName("status")] - public string? Status { get; set; } + /// Complete replacement system-message text. + [JsonPropertyName("content")] + public string Content { get; set; } = string.Empty; - /// Rendered title. - [JsonPropertyName("title")] - public string? Title { get; set; } + /// Optional structured blocks corresponding to the replacement content. + [JsonPropertyName("contentBlocks")] + public IList? ContentBlocks { get; set; } - /// URL for web-rendered canvases. - [JsonPropertyName("url")] - public string? Url { get; set; } + /// Replace-mode discriminator. + [JsonPropertyName("mode")] + public ProtocolReplaceMode Mode { get; set; } } -/// Live open-canvas snapshot. +/// RPC data type for ProtocolStaticSectionOverride operations. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class CanvasListOpenResult +public sealed class ProtocolStaticSectionOverride { - /// Currently open canvas instances. - [JsonPropertyName("openCanvases")] - public IList OpenCanvases { get => field ??= []; set; } -} + /// Declarative operation applied to the section. + [JsonPropertyName("action")] + public ProtocolStaticSectionAction Action { get; set; } -/// Identifies the target session. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionCanvasListOpenRequest -{ - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Optional content used by replace, append, and prepend operations. + [JsonPropertyName("content")] + public string? Content { get; set; } } -/// Canvas open parameters. +/// Polymorphic base type discriminated by action. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class CanvasOpenRequest +[JsonPolymorphic( + TypeDiscriminatorPropertyName = "action", + UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] +[JsonDerivedType(typeof(ProtocolMarkerSectionOverrideTransform), "transform")] +[JsonDerivedType(typeof(ProtocolMarkerSectionOverridePreserve), "preserve")] +public partial class ProtocolMarkerSectionOverride { - /// Provider-local canvas identifier. - [JsonPropertyName("canvasId")] - public string CanvasId { get; set; } = string.Empty; - - /// 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; } - - /// Canvas open input. - [JsonPropertyName("input")] - public JsonElement? Input { get; set; } - - /// 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; + /// The type discriminator. + [JsonPropertyName("action")] + public virtual string Action { get; set; } = string.Empty; } -/// Canvas close parameters. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class CanvasCloseRequest -{ - /// 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; -} -/// Canvas action invocation result. +/// The transform variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class CanvasActionInvokeResult +public partial class ProtocolMarkerSectionOverrideTransform : ProtocolMarkerSectionOverride { - /// Provider-supplied action result. - [JsonPropertyName("result")] - public JsonElement? Result { get; set; } + /// + [JsonIgnore] + public override string Action => "transform"; } -/// Canvas action invocation parameters. +/// The preserve variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class CanvasActionInvokeRequest +public partial class ProtocolMarkerSectionOverridePreserve : ProtocolMarkerSectionOverride { - /// 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; + /// + [JsonIgnore] + public override string Action => "preserve"; } -/// Internal canvas provider registration parameters. +/// JSON union data type for ProtocolSectionOverride. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class CanvasProviderRegisterRequest +[JsonConverter(typeof(Converter))] +public sealed partial class ProtocolSectionOverride { - /// Canvas contributions supplied by the provider. - [JsonPropertyName("canvases")] - public IList Canvases { get => field ??= []; set; } - - /// 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; } + /// Gets the value when this instance contains . + public ProtocolStaticSectionOverride? ProtocolStaticSectionOverride { get; } - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; -} + /// Gets the value when this instance contains . + public ProtocolMarkerSectionOverride? ProtocolMarkerSectionOverride { get; } -/// Internal canvas provider unregistration parameters. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class CanvasProviderUnregisterRequest -{ - /// Connection identifier to unregister. - [JsonPropertyName("connectionId")] - public string ConnectionId { get; set; } = string.Empty; + /// Initializes a new instance of the class from . + public ProtocolSectionOverride(ProtocolStaticSectionOverride value) + { + ArgumentNullException.ThrowIfNull(value); + ProtocolStaticSectionOverride = value; + } - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; -} + /// Converts to . + public static implicit operator ProtocolSectionOverride(ProtocolStaticSectionOverride value) => new(value); -/// Machine-readable workflow run failure. -/// Polymorphic base type discriminated by type. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -[JsonPolymorphic( - TypeDiscriminatorPropertyName = "type", - UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] -[JsonDerivedType(typeof(WorkflowRunFailureWorkflowLimitReached), "workflow_limit_reached")] -[JsonDerivedType(typeof(WorkflowRunFailureWorkflowResumeDeclined), "workflow_resume_declined")] -[JsonDerivedType(typeof(WorkflowRunFailureWorkflowDurableFailure), "workflow_durable_failure")] -[JsonDerivedType(typeof(WorkflowRunFailureWorkflowAccountingIncomplete), "workflow_accounting_incomplete")] -[JsonDerivedType(typeof(WorkflowRunFailureWorkflowProviderDisconnected), "workflow_provider_disconnected")] -public partial class WorkflowRunFailure -{ - /// The type discriminator. - [JsonPropertyName("type")] - public virtual string Type { get; set; } = string.Empty; -} + /// Initializes a new instance of the class from . + public ProtocolSectionOverride(ProtocolMarkerSectionOverride value) + { + ArgumentNullException.ThrowIfNull(value); + ProtocolMarkerSectionOverride = value; + } + /// Converts to . + public static implicit operator ProtocolSectionOverride(ProtocolMarkerSectionOverride value) => new(value); -/// The workflow_limit_reached variant of . -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class WorkflowRunFailureWorkflowLimitReached : WorkflowRunFailure -{ - /// - [JsonIgnore] - public override string Type => "workflow_limit_reached"; + /// Provides a for serializing instances. + [EditorBrowsable(EditorBrowsableState.Never)] + public sealed class Converter : JsonConverter + { + /// + public override ProtocolSectionOverride Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (reader.TokenType == JsonTokenType.Null) + { + throw new JsonException("Expected JSON object for ProtocolSectionOverride."); + } - /// Resource ceiling that stopped the run. - [JsonPropertyName("kind")] - public required WorkflowRunFailureKind Kind { get; set; } + using var document = JsonDocument.ParseValue(ref reader); + var element = document.RootElement; + if (element.ValueKind == JsonValueKind.Object && (element.TryGetProperty("action", out _) && element.GetProperty("action").ValueKind == JsonValueKind.String && (element.GetProperty("action").GetString() == "replace" || element.GetProperty("action").GetString() == "remove" || element.GetProperty("action").GetString() == "append" || element.GetProperty("action").GetString() == "prepend"))) + { + var protocolStaticSectionOverride = JsonSerializer.Deserialize(element, RpcJsonContext.Default.ProtocolStaticSectionOverride); + return protocolStaticSectionOverride is null ? throw new JsonException("Expected ProtocolStaticSectionOverride value.") : new ProtocolSectionOverride(protocolStaticSectionOverride); + } + if ((element.ValueKind == JsonValueKind.Object && (element.TryGetProperty("action", out _) && element.GetProperty("action").ValueKind == JsonValueKind.String && (element.GetProperty("action").GetString() == "transform")) || element.ValueKind == JsonValueKind.Object && (element.TryGetProperty("action", out _) && element.GetProperty("action").ValueKind == JsonValueKind.String && (element.GetProperty("action").GetString() == "preserve")))) + { + var protocolMarkerSectionOverride = JsonSerializer.Deserialize(element, RpcJsonContext.Default.ProtocolMarkerSectionOverride); + return protocolMarkerSectionOverride is null ? throw new JsonException("Expected ProtocolMarkerSectionOverride value.") : new ProtocolSectionOverride(protocolMarkerSectionOverride); + } - /// Workflow run identifier. - [JsonPropertyName("runId")] - public required string RunId { get; set; } + throw new JsonException("JSON value did not match any ProtocolSectionOverride variant."); + } - /// Suggested larger ceiling when the runtime can derive one safely. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("suggestedValue")] - public double? SuggestedValue { get; set; } + /// + public override void Write(Utf8JsonWriter writer, ProtocolSectionOverride value, JsonSerializerOptions options) + { + if (value.ProtocolStaticSectionOverride is { } protocolStaticSectionOverride) + { + JsonSerializer.Serialize(writer, protocolStaticSectionOverride, RpcJsonContext.Default.ProtocolStaticSectionOverride); + return; + } + if (value.ProtocolMarkerSectionOverride is { } protocolMarkerSectionOverride) + { + JsonSerializer.Serialize(writer, protocolMarkerSectionOverride, RpcJsonContext.Default.ProtocolMarkerSectionOverride); + return; + } - /// Approved effective ceiling that was reached. - [JsonPropertyName("value")] - public required double Value { get; set; } + throw new JsonException("No ProtocolSectionOverride variant value is set."); + } + } } -/// The workflow_resume_declined variant of . +/// RPC data type for ProtocolSystemMessageCustomizeConfig operations. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class WorkflowRunFailureWorkflowResumeDeclined : WorkflowRunFailure +public sealed class ProtocolSystemMessageCustomizeConfig { - /// - [JsonIgnore] - public override string Type => "workflow_resume_declined"; + /// Text appended after the customized sections. + [JsonPropertyName("content")] + public string? Content { get; set; } - /// Human-readable reason the resume did not proceed. - [JsonPropertyName("reason")] - public required string Reason { get; set; } + /// Customize-mode discriminator. + [JsonPropertyName("mode")] + public ProtocolCustomizeMode Mode { get; set; } - /// Workflow run identifier whose changed limits were declined. - [JsonPropertyName("runId")] - public required string RunId { get; set; } + /// Named standard-prompt section overrides. + [JsonPropertyName("sections")] + public IDictionary? Sections { get; set; } } -/// The workflow_durable_failure variant of . +/// JSON union data type for ProtocolSystemMessageConfig. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class WorkflowRunFailureWorkflowDurableFailure : WorkflowRunFailure +[JsonConverter(typeof(Converter))] +public sealed partial class ProtocolSystemMessageConfig { - /// - [JsonIgnore] - public override string Type => "workflow_durable_failure"; + /// Gets the value when this instance contains . + public ProtocolSystemMessageAppendConfig? ProtocolSystemMessageAppendConfig { get; } - /// Stable failure code. - [JsonPropertyName("code")] - public required string Code { get; set; } + /// Gets the value when this instance contains . + public ProtocolSystemMessageReplaceConfig? ProtocolSystemMessageReplaceConfig { get; } - /// Execution-critical durable operation that failed. - [JsonPropertyName("operation")] - public required WorkflowDurableOperation Operation { get; set; } + /// Gets the value when this instance contains . + public ProtocolSystemMessageCustomizeConfig? ProtocolSystemMessageCustomizeConfig { get; } - /// Workflow run identifier. - [JsonPropertyName("runId")] - public required string RunId { get; set; } -} + /// Initializes a new instance of the class from . + public ProtocolSystemMessageConfig(ProtocolSystemMessageAppendConfig value) + { + ArgumentNullException.ThrowIfNull(value); + ProtocolSystemMessageAppendConfig = value; + } -/// The run stopped because its usage accounting could not be completed. -/// The workflow_accounting_incomplete variant of . -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class WorkflowRunFailureWorkflowAccountingIncomplete : WorkflowRunFailure -{ - /// - [JsonIgnore] - public override string Type => "workflow_accounting_incomplete"; + /// Converts to . + public static implicit operator ProtocolSystemMessageConfig(ProtocolSystemMessageAppendConfig value) => new(value); - /// Confirmed usage in nano-AIU, representing the floor of what the run spent. - [JsonPropertyName("drainedNanoAiu")] - public required long DrainedNanoAiu { get; set; } + /// Initializes a new instance of the class from . + public ProtocolSystemMessageConfig(ProtocolSystemMessageReplaceConfig value) + { + ArgumentNullException.ThrowIfNull(value); + ProtocolSystemMessageReplaceConfig = value; + } - /// Workflow run identifier. - [JsonPropertyName("runId")] - public required string RunId { get; set; } -} + /// Converts to . + public static implicit operator ProtocolSystemMessageConfig(ProtocolSystemMessageReplaceConfig value) => new(value); -/// The extension that owns the workflow 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 workflow_provider_disconnected variant of . -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class WorkflowRunFailureWorkflowProviderDisconnected : WorkflowRunFailure -{ - /// - [JsonIgnore] - public override string Type => "workflow_provider_disconnected"; + /// Initializes a new instance of the class from . + public ProtocolSystemMessageConfig(ProtocolSystemMessageCustomizeConfig value) + { + ArgumentNullException.ThrowIfNull(value); + ProtocolSystemMessageCustomizeConfig = value; + } - /// Workflow run identifier. - [JsonPropertyName("runId")] - public required string RunId { get; set; } + /// Converts to . + public static implicit operator ProtocolSystemMessageConfig(ProtocolSystemMessageCustomizeConfig value) => new(value); + + /// Provides a for serializing instances. + [EditorBrowsable(EditorBrowsableState.Never)] + public sealed class Converter : JsonConverter + { + /// + public override ProtocolSystemMessageConfig Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (reader.TokenType == JsonTokenType.Null) + { + throw new JsonException("Expected JSON object for ProtocolSystemMessageConfig."); + } + + using var document = JsonDocument.ParseValue(ref reader); + var element = document.RootElement; + if (element.ValueKind == JsonValueKind.Object && (!element.TryGetProperty("mode", out _) || (element.TryGetProperty("mode", out _) && element.GetProperty("mode").ValueKind == JsonValueKind.String && (element.GetProperty("mode").GetString() == "append")))) + { + var protocolSystemMessageAppendConfig = JsonSerializer.Deserialize(element, RpcJsonContext.Default.ProtocolSystemMessageAppendConfig); + return protocolSystemMessageAppendConfig is null ? throw new JsonException("Expected ProtocolSystemMessageAppendConfig value.") : new ProtocolSystemMessageConfig(protocolSystemMessageAppendConfig); + } + if (element.ValueKind == JsonValueKind.Object && (element.TryGetProperty("mode", out _) && element.GetProperty("mode").ValueKind == JsonValueKind.String && (element.GetProperty("mode").GetString() == "replace"))) + { + var protocolSystemMessageReplaceConfig = JsonSerializer.Deserialize(element, RpcJsonContext.Default.ProtocolSystemMessageReplaceConfig); + return protocolSystemMessageReplaceConfig is null ? throw new JsonException("Expected ProtocolSystemMessageReplaceConfig value.") : new ProtocolSystemMessageConfig(protocolSystemMessageReplaceConfig); + } + if (element.ValueKind == JsonValueKind.Object && (element.TryGetProperty("mode", out _) && element.GetProperty("mode").ValueKind == JsonValueKind.String && (element.GetProperty("mode").GetString() == "customize"))) + { + var protocolSystemMessageCustomizeConfig = JsonSerializer.Deserialize(element, RpcJsonContext.Default.ProtocolSystemMessageCustomizeConfig); + return protocolSystemMessageCustomizeConfig is null ? throw new JsonException("Expected ProtocolSystemMessageCustomizeConfig value.") : new ProtocolSystemMessageConfig(protocolSystemMessageCustomizeConfig); + } + + throw new JsonException("JSON value did not match any ProtocolSystemMessageConfig variant."); + } + + /// + public override void Write(Utf8JsonWriter writer, ProtocolSystemMessageConfig value, JsonSerializerOptions options) + { + if (value.ProtocolSystemMessageAppendConfig is { } protocolSystemMessageAppendConfig) + { + JsonSerializer.Serialize(writer, protocolSystemMessageAppendConfig, RpcJsonContext.Default.ProtocolSystemMessageAppendConfig); + return; + } + if (value.ProtocolSystemMessageReplaceConfig is { } protocolSystemMessageReplaceConfig) + { + JsonSerializer.Serialize(writer, protocolSystemMessageReplaceConfig, RpcJsonContext.Default.ProtocolSystemMessageReplaceConfig); + return; + } + if (value.ProtocolSystemMessageCustomizeConfig is { } protocolSystemMessageCustomizeConfig) + { + JsonSerializer.Serialize(writer, protocolSystemMessageCustomizeConfig, RpcJsonContext.Default.ProtocolSystemMessageCustomizeConfig); + return; + } + + throw new JsonException("No ProtocolSystemMessageConfig variant value is set."); + } + } } -/// Durable metadata describing who initiated a workflow pause. -/// Polymorphic base type discriminated by type. +/// A BYOK model definition referencing a named provider. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -[JsonPolymorphic( - TypeDiscriminatorPropertyName = "type", - UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] -[JsonDerivedType(typeof(WorkflowPauseInfoUser), "user")] -[JsonDerivedType(typeof(WorkflowPauseInfoCheckpoint), "checkpoint")] -public partial class WorkflowPauseInfo +public sealed class ProviderModelConfig { - /// The type discriminator. - [JsonPropertyName("type")] - public virtual string Type { get; set; } = string.Empty; -} + /// 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; -/// The user variant of . -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class WorkflowPauseInfoUser : WorkflowPauseInfo -{ - /// - [JsonIgnore] - public override string Type => "user"; + /// 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; } + + /// Provider-published model metadata, preserved verbatim as the public Model.metadata object. + [JsonPropertyName("metadata")] + public IDictionary? Metadata { 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; + + /// System-message configuration used when the runtime builds the standard prompt for this provider-qualified model, including general-purpose subagents. It uses the same object hierarchy as session-level systemMessage configuration, except transform actions are rejected because the current callback protocol is not model-scoped. When present, it overrides the session-wide configuration on those prompt paths. Selected custom-agent and specialized-subagent prompts remain authoritative. + [JsonPropertyName("systemMessage")] + public ProtocolSystemMessageConfig? SystemMessage { get; set; } + + /// The model name sent to the provider API for inference. Defaults to `id`. + [JsonPropertyName("wireModel")] + public string? WireModel { get; set; } } -/// The checkpoint variant of . +/// Azure-specific provider options. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class WorkflowPauseInfoCheckpoint : WorkflowPauseInfo +public sealed class ProviderConfigAzure { - /// - [JsonIgnore] - public override string Type => "checkpoint"; - - /// Stable author-defined checkpoint key that initiated the pause. - [JsonPropertyName("key")] - public required string Key { get; set; } + /// API version. When set, uses the versioned deployment route. When omitted, uses the GA versionless v1 route. + [JsonPropertyName("apiVersion")] + public string? ApiVersion { get; set; } } -/// Complete current or terminal workflow run envelope. +/// External SDK input for a named custom model provider. Ingested by the native protocol boundary before host dispatch. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class WorkflowRunResult +public sealed class NamedProviderConfig { - /// 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; } + /// Static API key used to authenticate provider requests. + [JsonPropertyName("apiKey")] + public string? ApiKey { get; set; } - /// Error message for an errored run. - [JsonPropertyName("error")] - public string? Error { get; set; } + /// Azure authentication configuration for the provider. + [JsonPropertyName("azure")] + public ProviderConfigAzure? Azure { get; set; } - /// Machine-readable failure details for a halted or errored run. - [JsonPropertyName("failure")] - public WorkflowRunFailure? Failure { get; set; } + /// Base URL for provider API requests. + [JsonPropertyName("baseUrl")] + public string BaseUrl { get; set; } = string.Empty; - /// Structured pause initiator metadata for a paused attempt. - [JsonPropertyName("pauseInfo")] - public WorkflowPauseInfo? PauseInfo { get; set; } + /// Static bearer token used to authenticate provider requests. + [JsonPropertyName("bearerToken")] + public string? BearerToken { get; set; } - /// Reason for a halted or cancelled run. - [JsonPropertyName("reason")] - public string? Reason { get; set; } + /// Whether the host supplies bearer tokens dynamically. + [JsonPropertyName("hasBearerTokenProvider")] + public bool? HasBearerTokenProvider { get; set; } - /// Completed workflow result. - [JsonPropertyName("result")] - public JsonElement? Result { get; set; } + /// Additional HTTP headers included with provider requests. + [JsonPropertyName("headers")] + public IDictionary? Headers { get; set; } - /// Workflow run identifier. - [JsonPropertyName("runId")] - public string RunId { get; set; } = string.Empty; + /// The product serving the provider's models, reported in telemetry as `model_provider`. Only affects telemetry. + [JsonPropertyName("modelProvider")] + public ProviderConfigModelProvider? ModelProvider { get; set; } - /// Partial journal and progress snapshot for a halted, cancelled, or errored run. - [JsonPropertyName("snapshot")] - public JsonElement? Snapshot { get; set; } + /// Unique provider name used to qualify model selection IDs. + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; - /// Current or terminal workflow run status. - [JsonPropertyName("status")] - public WorkflowRunStatus Status { get; set; } + /// 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; } } -/// Wire-only per-invocation workflow resource ceiling overrides. +/// Provider configuration prepared from a discovered model. Preparing a plan changes nothing: it neither registers the model with the session nor writes durable configuration. To apply it, pass `provider` and `model` to `session.provider.add`, omitting whichever the dispositions report as already configured. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class WorkflowRunLimits +public sealed class ModelProviderConfigurationPlan { - /// Maximum AI credits consumed by workflow 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; } + /// Model definition prepared from the discovered model. Capability fields the provider did not report stay omitted rather than being asserted false. + [JsonPropertyName("model")] + public ProviderModelConfig Model { get => field ??= new(); set; } - /// Maximum number of workflow subagents that may run concurrently. - [JsonPropertyName("maxConcurrentSubagents")] - public long? MaxConcurrentSubagents { get; set; } + /// Whether `model` still needs to be registered. When `alreadyConfigured`, `selectionId` is already registered and the caller can select it without adding anything. + [JsonPropertyName("modelDisposition")] + public ModelProviderConfigurationDisposition ModelDisposition { get; set; } - /// Maximum total number of workflow subagents that may be admitted. - [JsonPropertyName("maxTotalSubagents")] - public long? MaxTotalSubagents { get; set; } + /// Provider connection prepared from the instance's inference metadata. Carries no credential; supply one if the endpoint requires it. + [JsonPropertyName("provider")] + public NamedProviderConfig Provider { get => field ??= new(); 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; } + /// Whether `provider` still needs to be registered. When `alreadyConfigured`, a provider with the same endpoint is already registered and `provider` restates it under its existing name; adding it again is rejected as a duplicate. + [JsonPropertyName("providerDisposition")] + public ModelProviderConfigurationDisposition ProviderDisposition { get; set; } + + /// Provider-qualified selection id (`provider/id`) to pass to `switchTo` once the plan is applied. + [JsonPropertyName("selectionId")] + public string SelectionId { get; set; } = string.Empty; + + /// Non-fatal warnings carried over from the discovered model, such as capabilities the provider did not report. + [JsonPropertyName("warnings")] + public IList Warnings { get => field ??= []; set; } } -/// Options controlling workflow invocation. +/// A discovered instance and one of its models to translate into provider configuration. Pass back the instance and model as returned by `session.providers.discover` and `session.providers.models.list`. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class WorkflowRunOptions +internal sealed class ModelProviderPrepareConfigurationRequest { - /// Per-invocation resource ceiling overrides. - [JsonPropertyName("limits")] - public WorkflowRunLimits? Limits { get; set; } - - /// Whether to emit workflow phase names to the session transcript. - [JsonPropertyName("logPhaseNames")] - public bool? LogPhaseNames { get; set; } + /// The discovered instance that serves the model. + [JsonPropertyName("instance")] + public ModelProviderInstance Instance { get => field ??= new(); set; } - /// Whether to notify the originating session when the workflow completes. - [JsonPropertyName("notifyOnComplete")] - public bool? NotifyOnComplete { get; set; } + /// The discovered model to configure. + [JsonPropertyName("model")] + public DiscoveredModel Model { get => field ??= new(); set; } - /// Run identifier whose journal and progress should seed this resumed run. - [JsonPropertyName("resumeFromRunId")] - public string? ResumeFromRunId { get; set; } + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } -/// Parameters for invoking a registered workflow. +/// Managed sandbox enforcement state for a session. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class WorkflowRunRequest +public sealed class SandboxEnforcementStatus { - /// Workflow input value. - [JsonPropertyName("args")] - public JsonElement Args { get; set; } + /// Whether an enforcement failure has permanently blocked the session. + [JsonPropertyName("blocked")] + public bool Blocked { get; set; } - /// Registered workflow name. - [JsonPropertyName("name")] - public string Name { get; set; } = string.Empty; + /// The first sandbox enforcement failure that blocked the session. + [JsonPropertyName("reason")] + public string? Reason { get; set; } - /// Workflow invocation options. - [JsonPropertyName("options")] - public WorkflowRunOptions? Options { get; set; } + /// Whether the effective managed policy requires an available sandbox backend. + [JsonPropertyName("required")] + public bool Required { get; set; } +} +/// Identifies the target session. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class SessionSandboxGetEnforcementStatusRequest +{ /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Resolved persisted workflow identity and resumed run envelope. +/// A possible secret-bearing environment variable and optional locally suggested HTTPS injection hosts. This is a draft for user review, not an active grant. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class WorkflowResumeResult +public sealed class SandboxCredentialSuggestion { - /// Terminal resumed run envelope. - [JsonPropertyName("run")] - public WorkflowRunResult Run { get => field ??= new(); set; } + /// Environment variable name. The secret value is never returned. + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; - /// Persisted workflow name resolved for the resumed run. - [JsonPropertyName("workflowName")] - public string WorkflowName { get; set; } = string.Empty; + /// HTTPS hostnames suggested by a local known-provider mapping. An empty list requires the user to supply injection hosts before adding a masking entry. These suggestions do not allow network access. + [JsonPropertyName("suggestedInjectHosts")] + public IList SuggestedInjectHosts { get => field ??= []; set; } } -/// Parameters for resuming a workflow run from its persisted identity. +/// Possible unconfigured secrets in the sandbox shell environment, sorted by variable name, with no secret values. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class WorkflowResumeRequest +public sealed class SandboxCredentialSuggestionsResult { - /// Optional per-invocation resource ceiling overrides. - [JsonPropertyName("limits")] - public WorkflowRunLimits? Limits { get; set; } - - /// Whether to emit workflow phase names to the session transcript. - [JsonPropertyName("logPhaseNames")] - public bool? LogPhaseNames { get; set; } - - /// Whether to notify the originating session when the workflow completes. - [JsonPropertyName("notifyOnComplete")] - public bool? NotifyOnComplete { get; set; } - - /// Workflow run identifier. - [JsonPropertyName("runId")] - public string RunId { get; set; } = string.Empty; + /// Candidates for user review. Empty when sandboxing is disabled or no unconfigured candidates are present. + [JsonPropertyName("suggestions")] + public IList Suggestions { get => field ??= []; set; } +} +/// Identifies the target session. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class SessionSandboxGetCredentialSuggestionsRequest +{ /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Options for an internal tool-originated workflow invocation. +/// Result of attempting to disable sandboxing for the current session. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class WorkflowToolRunOptions +public sealed class SandboxDisableForSessionResult { - /// Per-invocation resource ceiling overrides. - [JsonPropertyName("limits")] - public WorkflowRunLimits? Limits { get; set; } + /// The authoritative sandbox enabled state after the operation. + [JsonPropertyName("enabled")] + public bool Enabled { get; set; } - /// Run identifier whose journal and progress should seed this resumed run. - [JsonPropertyName("resumeFromRunId")] - public string? ResumeFromRunId { get; set; } + /// Whether this call resolved the pending request and applied the session opt-out. + [JsonPropertyName("success")] + public bool Success { get; set; } } -/// Internal parameters for invoking a registered workflow from a tool. +/// Optional informational context describing how and where the permission decision was made. This does not affect permission behavior. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class WorkflowToolRunRequest +public sealed class PermissionDecisionContext { - /// Workflow input value. - [JsonPropertyName("args")] - public JsonElement Args { get; set; } - - /// Registered workflow name. - [JsonPropertyName("name")] - public string Name { get; set; } = string.Empty; + /// Disposition of the permission request as observed by the responding client. + [JsonPropertyName("outcome")] + public PermissionDecisionOutcome Outcome { get; set; } - /// Tool-originated workflow invocation options. - [JsonPropertyName("options")] - public WorkflowToolRunOptions? Options { 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; } - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Controlled reason or actor responsible for the response. + [JsonPropertyName("source")] + public PermissionDecisionSource Source { get; set; } - /// Opaque identifier of the originating tool call. - [JsonPropertyName("toolCallId")] - public string? ToolCallId { get; set; } + /// Client surface that submitted the response. + [JsonPropertyName("surface")] + public PermissionDecisionSurface Surface { get; set; } } -/// Internal parameters for resuming a workflow run from a tool. +/// Request to disable sandboxing for the current session while resolving an active sandbox-bypass permission prompt. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class WorkflowToolResumeRequest +internal sealed class SandboxDisableForSessionRequest { - /// Optional per-invocation resource ceiling overrides. - [JsonPropertyName("limits")] - public WorkflowRunLimits? Limits { get; set; } + /// Optional attribution for the permission decision. + [JsonPropertyName("decisionContext")] + public PermissionDecisionContext? DecisionContext { get; set; } - /// Workflow run identifier. - [JsonPropertyName("runId")] - public string RunId { get; set; } = string.Empty; + /// Identifier of the exact pending sandbox-bypass permission request that authorized the session opt-out. + [JsonPropertyName("requestId")] + public string RequestId { 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; } +/// Result of accepting a sandbox path grant. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public sealed class SandboxGrantPathForRequestResult +{ + /// Whether this call resolved the pending request and added the path to the session's sandbox policy. + [JsonPropertyName("success")] + public bool Success { get; set; } } -/// Parameters for retrieving a workflow run. +/// Request to accept the sandbox path grant offered on an active sandbox escalation permission prompt. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class WorkflowGetRunRequest +internal sealed class SandboxGrantPathForRequestRequest { - /// Workflow run identifier. - [JsonPropertyName("runId")] - public string RunId { get; set; } = string.Empty; + /// Optional attribution for the permission decision. + [JsonPropertyName("decisionContext")] + public PermissionDecisionContext? DecisionContext { get; set; } + + /// Identifier of the exact pending sandbox escalation permission request whose sandboxPathGrant to accept. + [JsonPropertyName("requestId")] + public string RequestId { get; set; } = string.Empty; /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Declared or approved workflow resource ceilings. +/// Authentication status and account metadata for the session. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class WorkflowDeclaredLimits +public sealed class SessionAuthStatus { - /// Maximum AI credits consumed by subagents and descendants. - [JsonPropertyName("maxAiCredits")] - public double? MaxAiCredits { get; set; } + /// Authentication type. + [JsonPropertyName("authType")] + public AuthInfoType? AuthType { get; set; } - /// Maximum concurrently active subagents. - [JsonPropertyName("maxConcurrentSubagents")] - public long? MaxConcurrentSubagents { get; set; } + /// Copilot plan tier (e.g., individual_pro, business). + [JsonPropertyName("copilotPlan")] + public string? CopilotPlan { get; set; } - /// Maximum total subagents spawned by the run. - [JsonPropertyName("maxTotalSubagents")] - public long? MaxTotalSubagents { get; set; } + /// Authentication host URL. + [Url] + [StringSyntax(StringSyntaxAttribute.Uri)] + [JsonPropertyName("host")] + public string? Host { get; set; } - /// Maximum accumulated active execution time in seconds. - [JsonPropertyName("timeoutSeconds")] - public double? TimeoutSeconds { get; set; } + /// Whether the session has resolved authentication. + [JsonPropertyName("isAuthenticated")] + public bool IsAuthenticated { get; set; } + + /// Authenticated login/username, if available. + [JsonPropertyName("login")] + public string? Login { get; set; } + + /// Human-readable authentication status description. + [JsonPropertyName("statusMessage")] + public string? StatusMessage { get; set; } } -/// Durable workflow resource consumption. +/// Identifies the target session. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class WorkflowRunConsumed +internal sealed class SessionGitHubAuthGetStatusRequest { - /// Accumulated active execution time in milliseconds. - [JsonPropertyName("activeMs")] - public long ActiveMs { get; set; } - - /// AI usage consumed by the run in nano-AIU. - [JsonPropertyName("nanoAiu")] - public long NanoAiu { get; set; } - - /// Total subagents spawned by the run. - [JsonPropertyName("subagents")] - public long Subagents { get; set; } + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } -/// Current workflow phase identity. +/// Indicates whether the credential update succeeded. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class WorkflowCurrentPhase +public sealed class SessionSetCredentialsResult { - /// Current phase identifier. - [JsonPropertyName("id")] - public string Id { get; set; } = string.Empty; + /// 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; } - /// Zero-based declared phase ordinal, or null for an undeclared phase. - [JsonPropertyName("ordinal")] - public long? Ordinal { get; set; } + /// Whether the operation succeeded. + [JsonPropertyName("success")] + public bool Success { get; set; } } -/// Prompt-safe terminal workflow outcome. +/// 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(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class WorkflowRunTerminal +[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(SettableAuthInfoAccount), "account")] +[JsonDerivedType(typeof(SettableAuthInfoGhCli), "gh-cli")] +[JsonDerivedType(typeof(SettableAuthInfoApiKey), "api-key")] +public partial class SettableAuthInfo { - /// Human-readable terminal error. - [JsonPropertyName("error")] - public string? Error { get; set; } + /// The type discriminator. + [JsonPropertyName("type")] + public virtual string Type { get; set; } = string.Empty; +} - /// Machine-readable terminal failure. - [JsonPropertyName("failure")] - public WorkflowRunFailure? Failure { get; set; } - /// Pause initiator metadata, or null when the run did not pause. - [JsonPropertyName("pauseInfo")] - public WorkflowPauseInfo? PauseInfo { get; set; } +/// Authentication-info input variant for GitHub-internal HMAC auth, carrying the public GitHub host and HMAC secret. +/// The hmac variant of . +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public partial class SettableAuthInfoHmac : SettableAuthInfo +{ + /// + [JsonIgnore] + public override string Type => "hmac"; - /// Human-readable terminal reason. - [JsonPropertyName("reason")] - public string? Reason { 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; } - /// Prompt-safe preview of the completed result. - [JsonPropertyName("resultPreview")] - public string? ResultPreview { 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; } } -/// Durable workflow run summary with read-time live overlays. +/// 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(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class WorkflowRunSummary +public partial class SettableAuthInfoEnv : SettableAuthInfo { - /// Epoch milliseconds when the current active segment started, or null while inactive. - [JsonPropertyName("activeSegmentStartedAt")] - public long? ActiveSegmentStartedAt { get; set; } + /// + [JsonIgnore] + public override string Type => "env"; - /// Approved effective resource ceilings, or null until approved. - [JsonPropertyName("approved")] - public WorkflowDeclaredLimits? Approved { 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 the durable run state currently passes runtime resume eligibility checks. - [JsonPropertyName("canResume")] - public bool CanResume { get; set; } + /// Name of the environment variable the token was sourced from. + [JsonPropertyName("envVar")] + public required string EnvVar { get; set; } - /// Epoch milliseconds when the run completed, or null while nonterminal. - [JsonPropertyName("completedAt")] - public long? CompletedAt { get; set; } + /// Authentication host (e.g. https://github.com or a GHES host). + [JsonPropertyName("host")] + public required string Host { get; set; } - /// Durable resource consumption. - [JsonPropertyName("consumed")] - public WorkflowRunConsumed Consumed { get => field ??= new(); 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; } - /// Epoch milliseconds when the run was created. - [JsonPropertyName("createdAt")] - public long CreatedAt { get; set; } + /// The token value itself. Treat as a secret. + [JsonPropertyName("token")] + public required string Token { get; set; } +} - /// Current phase identity, or null before any phase is entered. - [JsonPropertyName("currentPhase")] - public WorkflowCurrentPhase? CurrentPhase { get; set; } +/// Token authentication accepted by session.gitHubAuth.setCredentials. +/// The token variant of . +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public partial class SettableAuthInfoToken : SettableAuthInfo +{ + /// + [JsonIgnore] + public override string Type => "token"; - /// Resource ceilings declared by the workflow. - [JsonPropertyName("declaredLimits")] - public WorkflowDeclaredLimits DeclaredLimits { get => field ??= new(); 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; } - /// Number of phases declared by the workflow. - [JsonPropertyName("declaredPhaseCount")] - public long DeclaredPhaseCount { get; set; } + /// Authentication host. + [JsonPropertyName("host")] + public required string Host { get; set; } - /// Human-readable workflow description. - [JsonPropertyName("description")] - public string Description { get; set; } = string.Empty; + /// The token value itself. Treat as a secret. + [JsonPropertyName("token")] + public required string Token { get; set; } +} - /// Number of direct workflow agents currently live. - [JsonPropertyName("liveAgentCount")] - public long LiveAgentCount { 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(global::GitHub.Copilot.Diagnostics.Experimental)] +public partial class SettableAuthInfoCopilotApiToken : SettableAuthInfo +{ + /// + [JsonIgnore] + public override string Type => "copilot-api-token"; - /// Epoch milliseconds when this live-overlay snapshot was observed. - [JsonPropertyName("observedAt")] - public long ObservedAt { 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; } - /// Monotonic durable run revision. - [JsonPropertyName("revision")] - public long Revision { get; set; } + /// Authentication host (always the public GitHub host). + [JsonPropertyName("host")] + public required string Host { get; set; } +} - /// Workflow run identifier. - [JsonPropertyName("runId")] - public string RunId { get; set; } = string.Empty; +/// Authentication-info variant for OAuth user auth, with host and login; the token remains in the runtime secret store. +/// The user variant of . +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public partial class SettableAuthInfoUser : SettableAuthInfo +{ + /// + [JsonIgnore] + public override string Type => "user"; - /// Epoch milliseconds when execution first started, or null before start. - [JsonPropertyName("startedAt")] - public long? StartedAt { 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; } - /// Current workflow run status. - [JsonPropertyName("status")] - public WorkflowRunStatus Status { get; set; } + /// Authentication host. + [JsonPropertyName("host")] + public required string Host { get; set; } - /// Terminal run outcome, or null while nonterminal. - [JsonPropertyName("terminal")] - public WorkflowRunTerminal? Terminal { get; set; } + /// OAuth user login. + [JsonPropertyName("login")] + public required string Login { get; set; } +} - /// Total direct workflow agents spawned across all attempts. - [JsonPropertyName("totalSpawnedAgentCount")] - public long TotalSpawnedAgentCount { get; set; } +/// An interactive account whose model provider owns its credentials. It carries no GitHub credential. +/// The account variant of . +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public partial class SettableAuthInfoAccount : SettableAuthInfo +{ + /// + [JsonIgnore] + public override string Type => "account"; - /// Epoch milliseconds when the durable run was last updated. - [JsonPropertyName("updatedAt")] - public long UpdatedAt { get; set; } + /// Host coordinate owned by the account's model provider. + [JsonPropertyName("host")] + public required string Host { get; set; } - /// Registered workflow name. - [JsonPropertyName("workflowName")] - public string WorkflowName { get; set; } = string.Empty; + /// Login identifying the provider-owned account. + [JsonPropertyName("login")] + public required string Login { get; set; } } -/// A page of workflow runs in durable creation order. +/// Authentication-info input variant for GitHub CLI credentials, carrying host, login, and the `gh auth token` value. +/// The gh-cli variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class WorkflowListRunsResult +public partial class SettableAuthInfoGhCli : SettableAuthInfo { - /// Whether terminal runs newer than this page exist. - [JsonPropertyName("hasMoreNewer")] - public bool? HasMoreNewer { get; set; } + /// + [JsonIgnore] + public override string Type => "gh-cli"; - /// Newest terminal-run cursor in this page, or null when the terminal window is empty. - [JsonPropertyName("newestSeq")] - public long? NewestSeq { 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; } - /// Oldest terminal-run cursor in this page, or null when the terminal window is empty. - [JsonPropertyName("oldestSeq")] - public long? OldestSeq { get; set; } + /// Authentication host. + [JsonPropertyName("host")] + public required string Host { get; set; } - /// Number of terminal runs older than this page. - [JsonPropertyName("omittedOlder")] - public long? OmittedOlder { get; set; } + /// User login as reported by `gh auth status`. + [JsonPropertyName("login")] + public required string Login { get; set; } - /// Workflow run summaries in durable creation order. - [JsonPropertyName("runs")] - public IList Runs { get => field ??= []; set; } + /// The token returned by `gh auth token`. Treat as a secret. + [JsonPropertyName("token")] + public required string Token { get; set; } } -/// Parameters for paging workflow runs. +/// 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(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class WorkflowListRunsRequest +public partial class SettableAuthInfoApiKey : SettableAuthInfo { - /// Exclusive forward cursor. - [JsonPropertyName("afterSeq")] - public long? AfterSeq { get; set; } + /// + [JsonIgnore] + public override string Type => "api-key"; - /// Exclusive backward cursor. - [JsonPropertyName("beforeSeq")] - public long? BeforeSeq { get; set; } + /// The API key. Treat as a secret. + [JsonPropertyName("apiKey")] + public required string ApiKey { get; set; } - /// Maximum terminal runs to return. Defaults to 200 and is capped at 500. - [JsonPropertyName("limit")] - public int? Limit { 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; } +} + +/// New auth credentials to install on the session. Omit to leave credentials unchanged. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class 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. + [JsonPropertyName("credentials")] + public SettableAuthInfo? Credentials { get; set; } /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Prompt-safe durable identity and live status for a direct workflow agent. +/// Identifies the target session. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class WorkflowAgentSummary +internal sealed class SessionGitHubAuthGetCurrentAuthInfoRequest { - /// Accumulated active agent time in milliseconds. - [JsonPropertyName("activeMs")] - public long ActiveMs { get; set; } + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} - /// Prompt-safe live activity text. - [JsonPropertyName("activity")] - public string? Activity { get; set; } +/// Identifies the target session. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class SessionGitHubAuthGetAllAuthAvailableRequest +{ + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} - /// Stable direct-agent identifier. - [JsonPropertyName("agentId")] - public string AgentId { get; set; } = string.Empty; +/// Identifies the target session. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class SessionGitHubAuthRefreshCopilotUserRequest +{ + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} - /// Registered agent type. - [JsonPropertyName("agentType")] - public string AgentType { get; set; } = string.Empty; +/// Internal GitHub login parameters. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class SessionAuthLoginRequest +{ + /// GitHub host URL. + [JsonPropertyName("host")] + public string Host { get; set; } = string.Empty; - /// Epoch milliseconds when the agent completed. - [JsonPropertyName("completedAt")] - public long? CompletedAt { get; set; } + /// GitHub login. + [JsonPropertyName("login")] + public string Login { get; set; } = string.Empty; - /// Friendly, non-unique name intended for display. - [JsonPropertyName("displayName")] - public string? DisplayName { get; set; } + /// Whether to persist the token after login. + [JsonPropertyName("persist")] + public bool? Persist { get; set; } - /// Friendly, non-unique name intended for display. - [JsonPropertyName("label")] - public string Label { 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; } + /// GitHub authentication token. + [JsonPropertyName("token")] + public string Token { get; set; } = string.Empty; +} - /// Model requested when the agent was launched. - [JsonPropertyName("requestedModel")] - public string? RequestedModel { get; set; } +/// Parameters for switching the session's active authentication. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class SessionAuthSwitchRequest +{ + /// Authentication information to activate. + [JsonPropertyName("authInfo")] + public AuthInfo AuthInfo { get => field ??= new(); set; } - /// Concrete model resolved for the agent. - [JsonPropertyName("resolvedModel")] - public string? ResolvedModel { get; set; } + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; - /// Owning workflow run identifier. - [JsonPropertyName("runId")] - public string RunId { get; set; } = string.Empty; + /// Optional token paired with the authentication information. + [JsonPropertyName("token")] + public string? Token { get; set; } +} - /// Epoch milliseconds when the agent started. - [JsonPropertyName("startedAt")] - public long? StartedAt { get; set; } +/// Identifies the target session. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class SessionGitHubAuthLogoutRequest +{ + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} - /// Current durable or live agent status. - [JsonPropertyName("status")] - public string Status { get; set; } = string.Empty; +/// Parameters identifying a GitHub authentication to log out. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class SessionAuthLogoutUserRequest +{ + /// Authentication information to log out. + [JsonPropertyName("authInfo")] + public AuthInfo AuthInfo { get => field ??= new(); set; } - /// Tool-call identifier that launched the agent. - [JsonPropertyName("toolCallId")] - public string ToolCallId { get; set; } = string.Empty; + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } -/// Durable lifecycle and timing for one workflow phase. +/// Validation error from an authentication attempt. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class WorkflowPhaseObservation +public sealed class AuthValidationError { - /// Completed active time accumulated by this phase in milliseconds. - [JsonPropertyName("accumulatedActiveMs")] - public long AccumulatedActiveMs { get; set; } - - /// Epoch milliseconds when this phase completed; for a skipped phase, the synthetic skip timestamp (equal to `startedAt`). - [JsonPropertyName("completedAt")] - public long? CompletedAt { get; set; } + /// Optional message returned by GitHub. + [JsonPropertyName("githubMessage")] + public string? GitHubMessage { get; set; } - /// Current live active time for this phase in milliseconds. - [JsonPropertyName("currentActiveMs")] - public long CurrentActiveMs { get; set; } + /// Authentication validation error message. + [JsonPropertyName("message")] + public string Message { get; set; } = string.Empty; +} - /// Optional human-readable phase detail. - [JsonPropertyName("detail")] - public string? Detail { get; set; } +/// Identifies the target session. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class SessionGitHubAuthLastAuthErrorsRequest +{ + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} - /// Number of times execution entered this phase. - [JsonPropertyName("entryCount")] - public long EntryCount { get; set; } +/// The enumerated collection, keyed by the same selector as the query. +/// Polymorphic base type discriminated by kind. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +[JsonPolymorphic( + TypeDiscriminatorPropertyName = "kind", + UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] +[JsonDerivedType(typeof(AuthEnumerateValueAccounts), "accounts")] +[JsonDerivedType(typeof(AuthEnumerateValueProviders), "providers")] +public partial class AuthEnumerateValue +{ + /// The type discriminator. + [JsonPropertyName("kind")] + public virtual string Kind { get; set; } = string.Empty; +} - /// Phase identifier. - [JsonPropertyName("id")] - public string Id { get; set; } = string.Empty; - /// Most recent run attempt that entered this phase, or `0` if the phase has never been entered. - [JsonPropertyName("lastEnteredRunAttempt")] - public long LastEnteredRunAttempt { get; set; } +/// One signed-in account in the roster forest. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public sealed class AccountStatus +{ + /// Whether this is the active account. + [JsonPropertyName("active")] + public bool Active { get; set; } - /// Direct agents in this phase that are currently live. - [JsonPropertyName("liveAgentCount")] - public long LiveAgentCount { get; set; } + /// Human-readable credential source used to authenticate this account, such as `gh` or `GITHUB_TOKEN`. + [JsonPropertyName("authSource")] + public string? AuthSource { get; set; } - /// Zero-based declared phase ordinal, or null for an undeclared phase. - [JsonPropertyName("ordinal")] - public long? Ordinal { get; set; } + /// Opaque id of the account this one was derived from (e.g. an EMU account's base Entra identity); absent for a root account. Matches the base identity account's selectionId, forming the derivation edge. + [JsonPropertyName("derivedFrom")] + public string? DerivedFrom { get; set; } - /// Epoch milliseconds when this phase first started; for a skipped phase, the synthetic skip timestamp (equal to `completedAt`). - [JsonPropertyName("startedAt")] - public long? StartedAt { get; set; } + /// Authentication host URL. + [Url] + [StringSyntax(StringSyntaxAttribute.Uri)] + [JsonPropertyName("host")] + public string Host { get; set; } = string.Empty; - /// Derived lifecycle state of the phase. - [JsonPropertyName("status")] - public WorkflowPhaseStatus Status { get; set; } + /// The provider kind of this account. + [JsonPropertyName("kind")] + public AccountKind Kind { get; set; } - /// Human-readable phase title. - [JsonPropertyName("title")] - public string Title { get; set; } = string.Empty; + /// Authenticated login/username. + [JsonPropertyName("login")] + public string Login { get; set; } = string.Empty; - /// Total direct agents associated with this phase. - [JsonPropertyName("totalAgentCount")] - public long TotalAgentCount { get; set; } + /// Opaque selection id used to switch to, or log out, this account. + [JsonPropertyName("selectionId")] + public string SelectionId { get; set; } = string.Empty; } -/// One durable workflow progress record. +/// The accounts variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class WorkflowProgressLine +public partial class AuthEnumerateValueAccounts : AuthEnumerateValue { - /// Resume attempt that emitted this record. - [JsonPropertyName("attempt")] - public long Attempt { get; set; } - - /// Progress record kind. - [JsonPropertyName("kind")] - public WorkflowLogLineKind 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; } + /// + [JsonIgnore] + public override string Kind => "accounts"; - /// Prompt-safe progress text. - [JsonPropertyName("text")] - public string Text { get; set; } = string.Empty; + /// The signed-in account forest; empty when not logged in. + [JsonPropertyName("items")] + public required IList Items { get; set; } } -/// A bidirectional page of workflow progress. +/// A provider offered for interactive login. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class WorkflowProgressPage +public sealed class ProviderDescriptor { - /// Whether progress records newer than this page exist. - [JsonPropertyName("hasMoreNewer")] - public bool HasMoreNewer { get; set; } - - /// Whether progress records older than this page exist. - [JsonPropertyName("hasMoreOlder")] - public bool HasMoreOlder { get; set; } - - /// Newest sequence number in this page, or null when empty. - [JsonPropertyName("newestSeq")] - public long? NewestSeq { get; set; } - - /// Oldest sequence number in this page, or null when empty. - [JsonPropertyName("oldestSeq")] - public long? OldestSeq { get; set; } + /// Whether this provider is currently available to sign in with. + [JsonPropertyName("available")] + public bool Available { get; set; } - /// Progress records in sequence order. - [JsonPropertyName("records")] - public IList Records { get => field ??= []; set; } + /// The neutral provider kind. + [JsonPropertyName("kind")] + public LoginProviderKind Kind { get; set; } - /// Run revision reflected by this page. - [JsonPropertyName("revision")] - public long Revision { get; set; } + /// Human-readable menu label, owned by the runtime so every consumer renders identical text. + [JsonPropertyName("label")] + public string Label { get; set; } = string.Empty; } -/// Full workflow run observability detail. +/// The providers variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class WorkflowRunDetail +public partial class AuthEnumerateValueProviders : AuthEnumerateValue { - /// Epoch milliseconds when the current active segment started, or null while inactive. - [JsonPropertyName("activeSegmentStartedAt")] - public long? ActiveSegmentStartedAt { get; set; } - - /// Durable identities and live statuses for direct workflow agents. - [JsonPropertyName("agents")] - public IList Agents { get => field ??= []; set; } - - /// Approved effective resource ceilings, or null until approved. - [JsonPropertyName("approved")] - public WorkflowDeclaredLimits? Approved { get; set; } - - /// Whether the durable run state currently passes runtime resume eligibility checks. - [JsonPropertyName("canResume")] - public bool CanResume { get; set; } - - /// Epoch milliseconds when the run completed, or null while nonterminal. - [JsonPropertyName("completedAt")] - public long? CompletedAt { get; set; } - - /// Durable resource consumption. - [JsonPropertyName("consumed")] - public WorkflowRunConsumed 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 WorkflowCurrentPhase? CurrentPhase { get; set; } - - /// Resource ceilings declared by the workflow. - [JsonPropertyName("declaredLimits")] - public WorkflowDeclaredLimits DeclaredLimits { get => field ??= new(); set; } - - /// Number of phases declared by the workflow. - [JsonPropertyName("declaredPhaseCount")] - public long DeclaredPhaseCount { get; set; } + /// + [JsonIgnore] + public override string Kind => "providers"; - /// Human-readable workflow description. - [JsonPropertyName("description")] - public string Description { get; set; } = string.Empty; + /// The providers offered for interactive login. + [JsonPropertyName("items")] + public required IList Items { get; set; } +} - /// Number of direct workflow agents currently live. - [JsonPropertyName("liveAgentCount")] - public long LiveAgentCount { get; set; } +/// Selects which accounts collection to enumerate. A no-arg selector is the empty-payload variant. +/// Polymorphic base type discriminated by kind. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +[JsonPolymorphic( + TypeDiscriminatorPropertyName = "kind", + UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] +[JsonDerivedType(typeof(AuthEnumerateQueryAccounts), "accounts")] +[JsonDerivedType(typeof(AuthEnumerateQueryProviders), "providers")] +public partial class AuthEnumerateQuery +{ + /// The type discriminator. + [JsonPropertyName("kind")] + public virtual string Kind { get; set; } = string.Empty; +} - /// Epoch milliseconds when this live-overlay snapshot was observed. - [JsonPropertyName("observedAt")] - public long ObservedAt { get; set; } - /// Lifecycle and timing observations for each workflow phase. - [JsonPropertyName("phases")] - public IList Phases { get => field ??= []; set; } +/// The accounts variant of . +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public partial class AuthEnumerateQueryAccounts : AuthEnumerateQuery +{ + /// + [JsonIgnore] + public override string Kind => "accounts"; +} - /// Bidirectional page of durable workflow progress. - [JsonPropertyName("progress")] - public WorkflowProgressPage Progress { get => field ??= new(); set; } +/// The providers variant of . +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public partial class AuthEnumerateQueryProviders : AuthEnumerateQuery +{ + /// + [JsonIgnore] + public override string Kind => "providers"; - /// Monotonic durable run revision. - [JsonPropertyName("revision")] - public long Revision { get; set; } + /// Whether an interactive Entra broker is available on the host; gates Entra availability in the returned list. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("brokerAvailable")] + public bool? BrokerAvailable { get; set; } +} - /// Workflow run identifier. - [JsonPropertyName("runId")] - public string RunId { get; set; } = string.Empty; +/// Enumerate request carrying the typed collection query. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class AccountsEnumerateRequest +{ + /// Which typed accounts collection to enumerate. + [JsonPropertyName("query")] + public AuthEnumerateQuery Query { get => field ??= new(); set; } - /// 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 workflow run status. - [JsonPropertyName("status")] - public WorkflowRunStatus Status { get; set; } +/// The read result, keyed by the same selector as the query. +/// Polymorphic base type discriminated by kind. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +[JsonPolymorphic( + TypeDiscriminatorPropertyName = "kind", + UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] +[JsonDerivedType(typeof(AuthReadValueActiveAccount), "activeAccount")] +[JsonDerivedType(typeof(AuthReadValueStatus), "status")] +[JsonDerivedType(typeof(AuthReadValueLastErrors), "lastErrors")] +public partial class AuthReadValue +{ + /// The type discriminator. + [JsonPropertyName("kind")] + public virtual string Kind { get; set; } = string.Empty; +} - /// Terminal run outcome, or null while nonterminal. - [JsonPropertyName("terminal")] - public WorkflowRunTerminal? Terminal { get; set; } - /// Total direct workflow agents spawned across all attempts. - [JsonPropertyName("totalSpawnedAgentCount")] - public long TotalSpawnedAgentCount { get; set; } +/// The activeAccount variant of . +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public partial class AuthReadValueActiveAccount : AuthReadValue +{ + /// + [JsonIgnore] + public override string Kind => "activeAccount"; - /// Epoch milliseconds when the durable run was last updated. - [JsonPropertyName("updatedAt")] - public long UpdatedAt { get; set; } + /// The active account, or absent when not logged in. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("account")] + public AccountStatus? Account { get; set; } - /// Registered workflow name. - [JsonPropertyName("workflowName")] - public string WorkflowName { get; set; } = string.Empty; + /// Credential-free identity metadata for the active account, including resolved Copilot user information when available. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("authInfo")] + public AuthIdentity? AuthInfo { get; set; } } -/// Parameters for paging workflow progress. +/// Neutral authentication status summary. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class WorkflowGetRunProgressRequest +public sealed class AuthStatusDto { - /// Exclusive forward cursor. - [JsonPropertyName("afterSeq")] - public long? AfterSeq { get; set; } - - /// Exclusive backward cursor. - [JsonPropertyName("beforeSeq")] - public long? BeforeSeq { get; set; } + /// Number of signed-in accounts in the roster. + [JsonPropertyName("accountCount")] + public long AccountCount { get; set; } - /// Maximum records to return. Defaults to 200 and is capped at 500. - [JsonPropertyName("limit")] - public int? Limit { get; set; } + /// Active account host, if authenticated. + [Url] + [StringSyntax(StringSyntaxAttribute.Uri)] + [JsonPropertyName("activeHost")] + public string? ActiveHost { get; set; } - /// Optional phase identifier used to scope records and cursors. - [JsonPropertyName("phaseId")] - public string? PhaseId { get; set; } + /// Active account login, if authenticated. + [JsonPropertyName("activeLogin")] + public string? ActiveLogin { get; set; } - /// Workflow run identifier. - [JsonPropertyName("runId")] - public string RunId { get; set; } = string.Empty; + /// Copilot plan tier of the active account, if known. + [JsonPropertyName("copilotPlan")] + public string? CopilotPlan { get; set; } - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Whether the session has resolved authentication. + [JsonPropertyName("isAuthenticated")] + public bool IsAuthenticated { get; set; } } -/// Parameters for cancelling a workflow run. +/// The status variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class WorkflowCancelRequest +public partial class AuthReadValueStatus : AuthReadValue { - /// Workflow run identifier. - [JsonPropertyName("runId")] - public string RunId { get; set; } = string.Empty; + /// + [JsonIgnore] + public override string Kind => "status"; - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// The neutral authentication status summary. + [JsonPropertyName("status")] + public required AuthStatusDto Status { get; set; } } -/// Parameters for pausing a running workflow. +/// The lastErrors variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class WorkflowPauseRequest +public partial class AuthReadValueLastErrors : AuthReadValue { - /// Workflow run identifier. - [JsonPropertyName("runId")] - public string RunId { get; set; } = string.Empty; + /// + [JsonIgnore] + public override string Kind => "lastErrors"; - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Validation errors from the most recent authentication attempt. + [JsonPropertyName("errors")] + public required IList Errors { get; set; } } -/// RPC data type for SessionWorkflowPauseAtCheckpoint operations. +/// Selects which typed accounts datum to read. +/// Polymorphic base type discriminated by kind. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionWorkflowPauseAtCheckpointResult +[JsonPolymorphic( + TypeDiscriminatorPropertyName = "kind", + UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] +[JsonDerivedType(typeof(AuthReadQueryActiveAccount), "activeAccount")] +[JsonDerivedType(typeof(AuthReadQueryStatus), "status")] +[JsonDerivedType(typeof(AuthReadQueryLastErrors), "lastErrors")] +public partial class AuthReadQuery { - /// Whether this execution attempt must pause or may continue. - [JsonPropertyName("action")] - public WorkflowPauseCheckpointAction Action { get; set; } + /// The type discriminator. + [JsonPropertyName("kind")] + public virtual string Kind { get; set; } = string.Empty; } -/// Parameters for an owned durable pause checkpoint. + +/// The activeAccount variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class WorkflowPauseCheckpointRequest +public partial class AuthReadQueryActiveAccount : AuthReadQuery { - /// Opaque token identifying the execution attempt that reached the checkpoint. - [JsonPropertyName("executionToken")] - public string ExecutionToken { get; set; } = string.Empty; - - /// Stable author-defined checkpoint key. - [JsonPropertyName("key")] - public string Key { get; set; } = string.Empty; - - /// Workflow run identifier. - [JsonPropertyName("runId")] - public string RunId { get; set; } = string.Empty; - - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// + [JsonIgnore] + public override string Kind => "activeAccount"; } -/// Acknowledgement that a workflow request was accepted. +/// The status variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class WorkflowAckResult +public partial class AuthReadQueryStatus : AuthReadQuery { + /// + [JsonIgnore] + public override string Kind => "status"; } -/// One ordered workflow progress line. +/// The lastErrors variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class WorkflowLogLine +public partial class AuthReadQueryLastErrors : AuthReadQuery { - /// Progress line kind. - [JsonPropertyName("kind")] - public WorkflowLogLineKind Kind { get; set; } - - /// Monotonic sequence number within the workflow run. - [JsonPropertyName("seq")] - public long Seq { get; set; } - - /// Progress text. - [JsonPropertyName("text")] - public string Text { get; set; } = string.Empty; + /// + [JsonIgnore] + public override string Kind => "lastErrors"; } -/// Parameters for recording workflow progress. +/// Read request carrying the typed datum query. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class WorkflowLogRequest +internal sealed class AccountsGetRequest { - /// Opaque token identifying the current workflow execution attempt. - [JsonPropertyName("executionToken")] - public string ExecutionToken { get; set; } = string.Empty; - - /// Ordered progress lines to append. - [JsonPropertyName("lines")] - public IList Lines { get => field ??= []; set; } - - /// Workflow run identifier. - [JsonPropertyName("runId")] - public string RunId { get; set; } = string.Empty; + /// Which typed accounts datum to read. + [JsonPropertyName("query")] + public AuthReadQuery Query { get => field ??= new(); set; } /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Result of one workflow-scoped subagent call. +/// Result of a non-interactive accounts mutation. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class WorkflowAgentResult +public sealed class AuthWriteResult { - /// Agent result, omitted when the agent produced no result. - [JsonPropertyName("result")] - public JsonElement? Result { get; set; } + /// For a logout, whether other signed-in accounts remain. + [JsonPropertyName("moreUsers")] + public bool? MoreUsers { get; set; } + + /// Whether the mutation was applied. + [JsonPropertyName("ok")] + public bool Ok { get; set; } } -/// Options for one workflow-scoped subagent call. +/// One non-interactive accounts mutation command (the selector is fused with its typed args). +/// Polymorphic base type discriminated by kind. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class WorkflowAgentOptions +[JsonPolymorphic( + TypeDiscriminatorPropertyName = "kind", + UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] +[JsonDerivedType(typeof(AuthWriteSwitchActive), "switchActive")] +[JsonDerivedType(typeof(AuthWriteLogout), "logout")] +[JsonDerivedType(typeof(AuthWriteSetCredentials), "setCredentials")] +public partial class AuthWrite { - /// Optional built-in or custom agent name whose definition configures the subagent. - [JsonPropertyName("agent")] - public string? Agent { get; set; } - - /// Optional context tier override for the subagent. - [JsonPropertyName("contextTier")] - public ContextTier? ContextTier { get; set; } - - /// Optional label distinguishing otherwise identical memoized agent calls. - [JsonPropertyName("label")] - public string? Label { get; set; } + /// The type discriminator. + [JsonPropertyName("kind")] + public virtual string Kind { get; set; } = string.Empty; +} - /// Optional model identifier for the subagent. - [JsonPropertyName("model")] - public string? Model { get; set; } - /// Optional reasoning effort override for the subagent. - [JsonPropertyName("reasoningEffort")] - public string? ReasoningEffort { get; set; } +/// The switchActive variant of . +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public partial class AuthWriteSwitchActive : AuthWrite +{ + /// + [JsonIgnore] + public override string Kind => "switchActive"; - /// Optional JSON Schema for structured agent output. - [JsonPropertyName("schema")] - public JsonElement? Schema { get; set; } + /// Opaque selection id of the account to make active. + [JsonPropertyName("selectionId")] + public required string SelectionId { get; set; } } -/// Parameters for one workflow-scoped subagent call. +/// The logout variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class WorkflowAgentRequest +public partial class AuthWriteLogout : AuthWrite { - /// Opaque token identifying the current workflow execution attempt. - [JsonPropertyName("executionToken")] - public string ExecutionToken { get; set; } = string.Empty; - - /// Subagent execution options. - [JsonPropertyName("opts")] - public WorkflowAgentOptions 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; + /// + [JsonIgnore] + public override string Kind => "logout"; - /// Workflow run identifier that owns the subagent. - [JsonPropertyName("workflowRunId")] - public string WorkflowRunId { get; set; } = string.Empty; + /// Opaque selection id of the account to log out; absent logs out the active account. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("selectionId")] + public string? SelectionId { get; set; } } -/// Result of reading a workflow journal entry. +/// The setCredentials variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class WorkflowJournalGetResult +public partial class AuthWriteSetCredentials : AuthWrite { - /// Whether the journal contained the requested key. - [JsonPropertyName("hit")] - public bool Hit { get; set; } + /// + [JsonIgnore] + public override string Kind => "setCredentials"; - /// Cached JSON result. The hit field distinguishes a cached JSON null from a miss. - [JsonPropertyName("resultJson")] - public JsonElement? ResultJson { get; set; } + /// Authentication host URL. + [Url] + [StringSyntax(StringSyntaxAttribute.Uri)] + [JsonPropertyName("host")] + public required string Host { get; set; } + + /// Login/username for the credential. + [JsonPropertyName("login")] + public required string Login { get; set; } + + /// GitHub authentication token to install. + [JsonPropertyName("token")] + public required string Token { get; set; } } -/// Parameters for reading a workflow journal entry. +/// Mutation request carrying the typed write command. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class WorkflowJournalGetRequest +internal sealed class AccountsSetRequest { - /// Opaque token identifying the current workflow execution attempt. - [JsonPropertyName("executionToken")] - public string ExecutionToken { get; set; } = string.Empty; - - /// Namespaced journal key. - [JsonPropertyName("key")] - public string Key { get; set; } = string.Empty; - - /// Workflow run identifier. - [JsonPropertyName("runId")] - public string RunId { get; set; } = string.Empty; + /// The non-interactive mutation command to apply. + [JsonPropertyName("command")] + public AuthWrite Command { get => field ??= new(); set; } /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Parameters for storing a workflow journal entry. +/// One step in an interactive login flow. The consumer acts on the step and calls advance to proceed. Browser-open is encoded as two distinct steps by design: `open-url` is CONSUMER-driven (the provider surfaces the authorize URL and the consumer opens it — github.com/GHEC web), while `needs-interaction` is PROVIDER-driven (the provider opens the browser or broker UI itself and does not surface a URL — Entra). +/// Polymorphic base type discriminated by kind. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class WorkflowJournalPutRequest +[JsonPolymorphic( + TypeDiscriminatorPropertyName = "kind", + UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] +[JsonDerivedType(typeof(AuthLoginStepOpenUrl), "open-url")] +[JsonDerivedType(typeof(AuthLoginStepInputRequired), "input-required")] +[JsonDerivedType(typeof(AuthLoginStepAwaiting), "awaiting")] +[JsonDerivedType(typeof(AuthLoginStepNeedsInteraction), "needs-interaction")] +[JsonDerivedType(typeof(AuthLoginStepCompleted), "completed")] +[JsonDerivedType(typeof(AuthLoginStepError), "error")] +public partial class AuthLoginStep { - /// Opaque token identifying the current workflow execution attempt. - [JsonPropertyName("executionToken")] - public string ExecutionToken { get; set; } = string.Empty; - - /// Namespaced journal key. - [JsonPropertyName("key")] - public string Key { get; set; } = string.Empty; + /// The type discriminator. + [JsonPropertyName("kind")] + public virtual string Kind { get; set; } = string.Empty; +} - /// JSON result to memoize. - [JsonPropertyName("resultJson")] - public JsonElement ResultJson { get; set; } - /// Workflow run identifier. - [JsonPropertyName("runId")] - public string RunId { get; set; } = string.Empty; +/// The open-url variant of . +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public partial class AuthLoginStepOpenUrl : AuthLoginStep +{ + /// + [JsonIgnore] + public override string Kind => "open-url"; - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Authorize URL the consumer should open in a browser (consumer-driven browser-open). + [JsonPropertyName("url")] + public required string Url { get; set; } } -/// The session's authoritative model snapshot. Auto preference fields are configuration for the virtual `auto` model and do not change the selected model identifier. The context tier reflects `Session.getContextTier()`, restored from the session journal on resume. +/// The input-required variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class CurrentModel +public partial class AuthLoginStepInputRequired : AuthLoginStep { - /// Auto preference currently claimed by an in-progress activation. Null means the activation is returning to provider-default routing. - [JsonPropertyName("activatingAutoTier")] - public AutoTier? ActivatingAutoTier { get; set; } - - /// Auto preference currently committed for the session. This can remain available while another model is selected so a later switch to `auto` can reuse it. - [JsonPropertyName("autoTier")] - public AutoTier? AutoTier { get; set; } - - /// Context tier for models that support multiple context-window sizes. - [JsonPropertyName("contextTier")] - public ContextTier? ContextTier { get; set; } - - /// Currently active model identifier. - [JsonPropertyName("modelId")] - public string? ModelId { get; set; } - - /// Latest unclaimed Auto preference waiting for a future user turn. Null means the pending request is returning to provider-default routing. - [JsonPropertyName("pendingAutoTier")] - public AutoTier? PendingAutoTier { get; set; } - - /// Captured base model to restore when leaving plan mode. Omitted outside plan mode or when no plan override has captured a base model. Persistent agent model requirements apply to this model rather than the temporary plan model. - [JsonPropertyName("planBaseModelId")] - public string? PlanBaseModelId { get; set; } - - /// Provider of planBaseModelId, when that saved selection is provider-qualified. - [JsonPropertyName("planBaseProviderId")] - public string? PlanBaseProviderId { get; set; } + /// + [JsonIgnore] + public override string Kind => "input-required"; - /// Provider selected for this model. Together with modelId, identifies a catalog entry even when providers serve the same model. Omitted for unattributed legacy selections. - [JsonPropertyName("providerId")] - public string? ProviderId { get; set; } + /// Prompt for the value the provider needs; the consumer supplies it as advance input (e.g. a GitHub Enterprise Cloud host, *.ghe.com). + [JsonPropertyName("prompt")] + public required string Prompt { 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; } +/// The awaiting variant of . +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public partial class AuthLoginStepAwaiting : AuthLoginStep +{ + /// + [JsonIgnore] + public override string Kind => "awaiting"; } -/// Identifies the target session. +/// The needs-interaction variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionModelGetCurrentRequest +public partial class AuthLoginStepNeedsInteraction : AuthLoginStep { - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// + [JsonIgnore] + public override string Kind => "needs-interaction"; } -/// RPC data type for ModelSwitchConfirmation operations. +/// A credential-free account choice after sign-in. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class ModelSwitchConfirmation +public sealed class AuthLoginAccount { - /// Current conversation token count before switching models. - [JsonPropertyName("currentTokens")] - public double CurrentTokens { get; set; } + /// Host coordinate owned by the selected account's provider. + [JsonPropertyName("host")] + public string Host { get; set; } = string.Empty; - /// Target model token limit used by the compaction preflight. - [JsonPropertyName("targetLimit")] - public double TargetLimit { get; set; } + /// Provider kind that owns this account choice. + [JsonPropertyName("kind")] + public AccountKind Kind { get; set; } - /// Display name of the model that requires compaction confirmation. - [JsonPropertyName("targetModelDisplayName")] - public string TargetModelDisplayName { get; set; } = string.Empty; + /// Human-readable login for the account choice. + [JsonPropertyName("login")] + public string Login { get; set; } = string.Empty; + + /// Opaque identifier supplied to the next login step to select this account. + [JsonPropertyName("selectionId")] + public string SelectionId { get; set; } = string.Empty; } -/// The model identifier active on the session after the switch. +/// Result of an interactive login flow. Pending consent or account selection is not terminal. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class ModelSwitchToResult +public sealed class AuthLoginResultDto { - /// Compaction confirmation projection when status is confirmation_required. - [JsonPropertyName("confirmation")] - public ModelSwitchConfirmation? Confirmation { get; set; } + /// Available accounts when sign-in is awaiting account selection, ordered with Microsoft 365 first. + [JsonPropertyName("accounts")] + public IList? Accounts { get; 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; } + /// Host that was signed in, when completed. + [Url] + [StringSyntax(StringSyntaxAttribute.Uri)] + [JsonPropertyName("host")] + public string? Host { get; set; } - /// Deprecation warnings associated with the selected model or options. - [JsonPropertyName("deprecationWarnings")] - public IList? DeprecationWarnings { get; set; } + /// Login that was signed in, when completed. + [JsonPropertyName("login")] + public string? Login { get; set; } - /// User-facing outcome message for the model switch. - [JsonPropertyName("message")] - public string? Message { get; set; } + /// Current disposition of the login, including pending user decisions. + [JsonPropertyName("status")] + public AuthLoginResultStatus Status { get; set; } +} - /// Currently active model identifier after the switch. - [JsonPropertyName("modelId")] - public string? ModelId { get; set; } +/// The completed variant of . +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public partial class AuthLoginStepCompleted : AuthLoginStep +{ + /// + [JsonIgnore] + public override string Kind => "completed"; - /// Authoritative model and Auto preference state after an immediate switch. For deferred switches this remains the current state until the queued change drains. - [JsonPropertyName("modelState")] - public CurrentModel? ModelState { get; set; } + /// Login result. When status is needs-plaintext-consent or needs-account-selection, advance with the user's decision to continue. + [JsonPropertyName("result")] + public required AuthLoginResultDto Result { get; set; } +} - /// Persistence failure encountered after applying the model switch. - [JsonPropertyName("persistenceError")] - public string? PersistenceError { get; set; } +/// The error variant of . +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public partial class AuthLoginStepError : AuthLoginStep +{ + /// + [JsonIgnore] + public override string Kind => "error"; - /// Stable queue item identifier when this request was enqueued. Remains present if the item drains before the response is returned. - [JsonPropertyName("queueId")] - public string? QueueId { get; set; } + /// Human-readable failure message. + [JsonPropertyName("message")] + public required string Message { get; set; } +} - /// Lifecycle result for the requested switch. - [JsonPropertyName("status")] - public string? Status { get; set; } +/// A started login flow: its opaque id and first step. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public sealed class AuthLoginBegun +{ + /// Opaque flow id used to advance or cancel this login. + [JsonPropertyName("flowId")] + public string FlowId { get; set; } = string.Empty; - /// User-facing warning produced while applying the model switch. - [JsonPropertyName("warning")] - public string? Warning { get; set; } + /// The first step of the flow. + [JsonPropertyName("step")] + public AuthLoginStep Step { get => field ??= new(); set; } } -/// Environment variables consulted while resolving model-picker settings. -public sealed class ModelPickerSettingsContextEnvironment +/// Begin an interactive login flow for a provider kind. Dispatch is kind-only. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class AuthLoginBeginRequest { + /// The provider kind to sign in with. + [JsonPropertyName("kind")] + public LoginProviderKind Kind { get; set; } + + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } -/// Filesystem and environment context used to resolve model-picker settings. +/// Advance an in-flight login flow, optionally fulfilling an input-required step. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class ModelPickerSettingsContext +internal sealed class AuthLoginAdvanceRequest { - /// Optional Copilot configuration directory containing persisted settings. - [JsonPropertyName("configDir")] - public string? ConfigDir { get; set; } + /// Opaque flow id from begin. + [JsonPropertyName("flowId")] + public string FlowId { get; set; } = string.Empty; - /// Environment variables consulted while resolving model-picker settings. - [JsonPropertyName("environment")] - public ModelPickerSettingsContextEnvironment Environment { get => field ??= new(); set; } + /// Neutral input fulfilling a preceding input-required step (e.g. a GHEC host); ignored otherwise. + [JsonPropertyName("input")] + public string? Input { get; set; } - /// User home directory used when resolving persisted settings. - [JsonPropertyName("homeDirectory")] - public string HomeDirectory { get; set; } = string.Empty; + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } -/// RPC data type for ModelPickerPersistence operations. +/// Cancel an in-flight login flow. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class ModelPickerPersistenceRequest +internal sealed class AuthLoginCancelRequest { - /// 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; } + /// Opaque flow id from begin. + [JsonPropertyName("flowId")] + public string FlowId { get; set; } = string.Empty; - /// Filesystem and environment context used to resolve settings persistence. - [JsonPropertyName("settingsContext")] - public ModelPickerSettingsContext SettingsContext { get => field ??= new(); set; } + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } -/// Target model identifier and optional reasoning effort, summary, capability overrides, and context tier. +/// A file included in the session debug bundle. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class ModelSwitchToRequest +public sealed class DebugCollectLogsCollectedEntry { - /// Optional Auto routing preference to stage atomically with selecting `auto`. Pass null to return to provider-default Auto routing. This field is rejected when `modelId` is not `auto`. - [JsonPropertyName("autoTier")] - public AutoTier? AutoTier { get; set; } + /// Relative path of the file in the staged bundle/archive. + [JsonPropertyName("bundlePath")] + public string BundlePath { 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; } + /// Redacted output size in bytes. + [JsonPropertyName("sizeBytes")] + public long SizeBytes { 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; } + /// Source category for this entry. + [JsonPropertyName("source")] + public DebugCollectLogsSource Source { 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; } +/// An optional debug bundle entry that could not be included. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public sealed class DebugCollectLogsSkippedEntry +{ + /// Relative path requested for this bundle entry. + [JsonPropertyName("bundlePath")] + public string BundlePath { get; set; } = string.Empty; - /// Override individual model capabilities resolved by the runtime. - [JsonPropertyName("modelCapabilities")] - public ModelCapabilitiesOverride? ModelCapabilities { get; set; } + /// Server-local source path that could not be read. + [JsonPropertyName("path")] + public string? Path { get; set; } - /// Settings scope used when persisting the selected model. - [JsonPropertyName("modelChangeScope")] - public string? ModelChangeScope { get; set; } + /// Reason the entry was skipped. + [JsonPropertyName("reason")] + public string Reason { get; set; } = string.Empty; +} - /// Model id to switch to, as returned by `list`. Include providerId to select an exact catalog entry when providers share the id. Without providerId, a bare id retains incumbent selection behavior; a registry-qualified id (`provider/id`, e.g. `acme/claude-sonnet`) targets a BYOK model. - [JsonPropertyName("modelId")] - public required string ModelId { get; set; } +/// Result of collecting a session debug bundle. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public sealed class DebugCollectLogsResult +{ + /// Files included in the bundle. + [JsonPropertyName("entries")] + public IList Entries { get => field ??= []; set; } - /// Optional settings context and explicit-override flags used to persist a picker selection. - [JsonPropertyName("pickerPersistence")] - public ModelPickerPersistenceRequest? PickerPersistence { get; set; } + /// Destination kind that was written. + [JsonPropertyName("kind")] + public DebugCollectLogsResultKind Kind { get; set; } - /// Provider id from the selected list entry's provider reference. Selects this exact provider/model pair; an unavailable pair fails rather than using another provider. Omit for deterministic legacy bare-model selection. - [JsonPropertyName("providerId")] - public string? ProviderId { 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; - /// 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. Pass null to clear any session effort override and fall back to the model's default. When omitted, the session's current effort is kept. - [JsonPropertyName("reasoningEffort")] - public string? ReasoningEffort { get; set; } + /// Optional files or directories that could not be included. + [JsonPropertyName("skippedEntries")] + public IList? SkippedEntries { get; set; } +} - /// Reasoning summary mode to request for supported model clients. - [JsonPropertyName("reasoningSummary")] - public ReasoningSummary? ReasoningSummary { get; set; } +/// A caller-provided server-local file or directory to include in the debug bundle. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public sealed class DebugCollectLogsEntry +{ + /// Relative path to use inside the staged bundle/archive. + [JsonPropertyName("bundlePath")] + public string BundlePath { get; set; } = string.Empty; - /// Optional repository settings scope to persist after the switch commits. - [JsonPropertyName("repoScope")] - public string? RepoScope { get; set; } + /// Kind of source path to include. + [JsonPropertyName("kind")] + public DebugCollectLogsEntryKind Kind { get; set; } - /// Require the target to be currently available and enabled before applying the switch. - [JsonPropertyName("requireAvailable")] - public bool? RequireAvailable { get; set; } + /// Server-local source path to read. + [JsonPropertyName("path")] + public string Path { get; set; } = string.Empty; - /// When true, evaluate context-window compaction policy before applying the switch. - [JsonPropertyName("runCompactionPreflight")] - public bool? RunCompactionPreflight { get; set; } + /// How text content from this entry should be redacted. Defaults to plain-text. With none, no redaction is applied; the caller must ensure any necessary redaction is performed before this call. + [JsonPropertyName("redaction")] + public DebugCollectLogsRedaction? Redaction { get; set; } - /// 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; } + /// 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; } +} - /// Output verbosity level to request for supported models. - [JsonPropertyName("verbosity")] - public Verbosity? Verbosity { get; set; } +/// Destination for the session debug bundle. +/// Polymorphic base type discriminated by kind. +[Experimental(global::GitHub.Copilot.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; } -/// Target model identifier and optional reasoning effort, summary, capability overrides, and context tier. + +/// The archive variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class ModelSwitchToRequestWithSession +public partial class DebugCollectLogsDestinationArchive : DebugCollectLogsDestination { - /// Optional Auto routing preference to stage atomically with selecting `auto`. Pass null to return to provider-default Auto routing. This field is rejected when `modelId` is not `auto`. - [JsonPropertyName("autoTier")] - public AutoTier? AutoTier { get; set; } + /// + [JsonIgnore] + public override string Kind => "archive"; - /// 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; } + /// 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; } - /// 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; } + /// Absolute or server-relative path for the .tgz archive to create. + [JsonPropertyName("outputPath")] + public required string OutputPath { 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; } +/// The directory variant of . +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public partial class DebugCollectLogsDestinationDirectory : DebugCollectLogsDestination +{ + /// + [JsonIgnore] + public override string Kind => "directory"; - /// Override individual model capabilities resolved by the runtime. - [JsonPropertyName("modelCapabilities")] - public ModelCapabilitiesOverride? ModelCapabilities { get; set; } + /// Directory where files should be staged. The directory is created if needed. + [JsonPropertyName("outputDirectory")] + public required string OutputDirectory { get; set; } +} - /// Settings scope used when persisting the selected model. - [JsonPropertyName("modelChangeScope")] - public string? ModelChangeScope { get; set; } +/// Built-in session diagnostics to include in the bundle. Omitted fields default to true. +[Experimental(global::GitHub.Copilot.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; } - /// Model id to switch to, as returned by `list`. Include providerId to select an exact catalog entry when providers share the id. Without providerId, a bare id retains incumbent selection behavior; a registry-qualified id (`provider/id`, e.g. `acme/claude-sonnet`) targets a BYOK model. - [JsonPropertyName("modelId")] - public string ModelId { get; set; } = string.Empty; + /// Include the session event log (`events.jsonl`). Defaults to true. + [JsonPropertyName("events")] + public bool? Events { get; set; } - /// Optional settings context and explicit-override flags used to persist a picker selection. - [JsonPropertyName("pickerPersistence")] - public ModelPickerPersistenceRequest? PickerPersistence { 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; } - /// Provider id from the selected list entry's provider reference. Selects this exact provider/model pair; an unavailable pair fails rather than using another provider. Omit for deterministic legacy bare-model selection. - [JsonPropertyName("providerId")] - public string? ProviderId { get; set; } + /// Maximum number of previous process logs to include. Defaults to 5. + [JsonPropertyName("previousProcessLogLimit")] + public long? PreviousProcessLogLimit { 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. Pass null to clear any session effort override and fall back to the model's default. When omitted, the session's current effort is kept. - [JsonPropertyName("reasoningEffort")] - public string? ReasoningEffort { 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; } - /// Reasoning summary mode to request for supported model clients. - [JsonPropertyName("reasoningSummary")] - public ReasoningSummary? ReasoningSummary { get; set; } + /// Include process logs for the session. Defaults to true. + [JsonPropertyName("processLogs")] + public bool? ProcessLogs { get; set; } - /// Optional repository settings scope to persist after the switch commits. - [JsonPropertyName("repoScope")] - public string? RepoScope { get; set; } + /// Include interactive shell logs written under the session's `shell-logs` directory. Defaults to true. + [JsonPropertyName("shellLogs")] + public bool? ShellLogs { get; set; } +} - /// Require the target to be currently available and enabled before applying the switch. - [JsonPropertyName("requireAvailable")] - public bool? RequireAvailable { get; set; } +/// Options for collecting a session debug bundle with configurable redaction. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class 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. + [JsonPropertyName("additionalEntries")] + public IList? AdditionalEntries { get; set; } - /// When true, evaluate context-window compaction policy before applying the switch. - [JsonPropertyName("runCompactionPreflight")] - public bool? RunCompactionPreflight { get; set; } + /// Where the bundle should be written. Use `archive` to produce a .tgz, or `directory` to stage 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; } /// 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; } } -/// Immediate acknowledgement and Auto preference snapshot after a switch request. This result never implies that a pending preference committed. +/// 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(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class ModelSwitchAutoTierResult +public sealed class CanvasAction { - /// Auto preference currently claimed by an in-progress activation. Null means the activation is returning to provider-default routing. - [JsonPropertyName("activatingAutoTier")] - public AutoTier? ActivatingAutoTier { get; set; } + /// Description of the action. + [JsonPropertyName("description")] + public string? Description { get; set; } - /// Auto preference currently committed for the session. - [JsonPropertyName("effectiveAutoTier")] - public AutoTier? EffectiveAutoTier { get; set; } + /// JSON Schema for the action input. + [JsonPropertyName("inputSchema")] + public JsonElement? InputSchema { get; set; } - /// Latest unclaimed Auto preference waiting for a future user turn. - [JsonPropertyName("pendingAutoTier")] - public AutoTier? PendingAutoTier { get; set; } + /// Action name exposed by the canvas provider. + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; +} - /// Immediate request status. `pending` means accepted but not committed. - [JsonPropertyName("status")] - public ModelSwitchAutoTierStatus Status { get; set; } +/// Canvas available in the current session. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public sealed class DiscoveredCanvas +{ + /// Actions the agent or host may invoke on an open instance. + [JsonPropertyName("actions")] + public IList? Actions { get; set; } - /// Earlier unclaimed preference replaced by this request. This can be present with either status, including when selecting the effective preference cancels pending work. - [JsonPropertyName("supersededAutoTier")] - public AutoTier? SupersededAutoTier { get; set; } + /// 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; } } -/// An Auto preference request for the session. This updates Auto configuration only; it does not change the selected model to `auto`. +/// Declared canvases available in this session. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class ModelSwitchAutoTierRequest +public sealed class CanvasList { - /// Auto preference to activate when a future user turn using the `auto` model safely mints a replacement model and token pair. Pass null to return to provider-default Auto routing. - [JsonPropertyName("autoTier")] - [JsonIgnore(Condition = JsonIgnoreCondition.Never)] - public AutoTier? AutoTier { get; set; } + /// Declared canvases available in this session. + [JsonPropertyName("canvases")] + public IList Canvases { get => field ??= []; set; } +} +/// Identifies the target session. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class SessionCanvasListRequest +{ /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; - - /// Origin to record on the effective `session.model_change` event. Defaults to `sdk` when omitted. - [JsonPropertyName("source")] - public ModelChangeSource? Source { get; set; } } -/// Managed, repository, and CLI model overrides to overlay onto the session at startup. +/// Open canvas instance snapshot. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class ModelApplyStartupOverlayRequest +public sealed class OpenCanvasInstance { - /// Effective default Auto routing preference from user and managed settings. Applies only to fresh sessions and never replaces a per-session selection. - [JsonPropertyName("autoTier")] - public AutoTier? AutoTier { get; set; } - - /// 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; } - - /// Context tier paired with the effective organization-managed model. Applies only when that concrete managed model is selected; it is ignored for Auto and for CLI, resume, or user overrides. - [JsonPropertyName("managedContextTier")] - public string? ManagedContextTier { get; set; } - - /// Reasoning effort paired with the effective organization-managed model. Applies only when that concrete managed model is selected; it is ignored for Auto and for CLI, resume, or user overrides. - [JsonPropertyName("managedReasoningEffort")] - public string? ManagedReasoningEffort { get; set; } + /// Provider-local canvas identifier. + [JsonPropertyName("canvasId")] + public string CanvasId { get; set; } = string.Empty; - /// 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; } + /// Owning provider identifier. + [JsonPropertyName("extensionId")] + public string ExtensionId { get; set; } = string.Empty; - /// Auto routing preference selected by repository settings, when configured. Applied only when the overlay selects the Auto model; beside a concrete model it stays dormant. - [JsonPropertyName("repoAutoTier")] - public string? RepoAutoTier { get; set; } + /// Owning extension display name, when available. + [JsonPropertyName("extensionName")] + public string? ExtensionName { get; set; } - /// Context tier selected by repository settings, when configured. - [JsonPropertyName("repoContextTier")] - public string? RepoContextTier { get; set; } + /// Host-local PNG path for the canvas icon, when supplied. + [JsonPropertyName("icon")] + public string? Icon { get; set; } - /// Model selected by repository settings, when configured. - [JsonPropertyName("repoModel")] - public string? RepoModel { get; set; } + /// Input supplied when the instance was opened. + [JsonPropertyName("input")] + public JsonElement? Input { get; set; } - /// Provider owning the repository-selected model. Omitted repository provider identity retains legacy bare-model behavior. - [JsonPropertyName("repoModelProviderId")] - public string? RepoModelProviderId { get; set; } + /// Stable caller-supplied canvas instance identifier. + [JsonPropertyName("instanceId")] + public string InstanceId { get; set; } = string.Empty; - /// Reasoning effort selected by repository settings, when configured. - [JsonPropertyName("repoReasoningEffort")] - public string? RepoReasoningEffort { get; set; } + /// Provider-supplied status text. + [JsonPropertyName("status")] + public string? Status { get; set; } - /// Model required by server-managed policy, when configured. - [JsonPropertyName("serverManagedModel")] - public string? ServerManagedModel { get; set; } + /// Rendered 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; } } -/// RPC data type for SessionModelClearStartupSeed operations. +/// Live open-canvas snapshot. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionModelClearStartupSeedResult +public sealed class CanvasListOpenResult { - /// Gets or sets the cleared value. - [JsonPropertyName("cleared")] - public bool Cleared { get; set; } + /// Currently open canvas instances. + [JsonPropertyName("openCanvases")] + public IList OpenCanvases { get => field ??= []; set; } } -/// RPC data type for SessionModelClearStartupSeed operations. +/// Identifies the target session. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionModelClearStartupSeedRequest +internal sealed class SessionCanvasListOpenRequest { - /// Gets or sets the expectedModel value. - [JsonPropertyName("expectedModel")] - public string ExpectedModel { get; set; } = string.Empty; - - /// Provider captured with the startup seed; omission matches only an unqualified seed. - [JsonPropertyName("expectedProviderId")] - public string? ExpectedProviderId { get; set; } - /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// The applied host allowlist and effective session model policy after intersection. +/// Canvas open parameters. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class ModelSetAllowedModelsResult +internal sealed class CanvasOpenRequest { - /// Normalized host allowlist. Omitted when the host restriction was cleared, or when a relay client does not return the host policy. - [JsonPropertyName("allowedModels")] - public IList? AllowedModels { get; set; } + /// Provider-local canvas identifier. + [JsonPropertyName("canvasId")] + public string CanvasId { get; set; } = string.Empty; - /// Effective exact IDs or repository policy patterns after applying the host restriction. Omitted by relay clients that do not return the host policy. - [JsonPropertyName("effectiveAllowedModels")] - public IList? EffectiveAllowedModels { 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; } - /// Effective deterministic fallback model, when the policy defines one. - [JsonPropertyName("fallbackModel")] - public string? FallbackModel { get; set; } + /// Canvas open input. + [JsonPropertyName("input")] + public JsonElement? Input { get; set; } - /// Selected session model after reconciling a now-disallowed concrete selection. - [JsonPropertyName("modelId")] - public string? ModelId { get; set; } + /// 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; } -/// Host-supplied exact model selection IDs to allow for this running session. CAPI IDs are intersected with repository `.github/allowed_models.txt` policy; provider-qualified IDs remain exempt from repository-only policy but are restricted by this host list. Omit or pass null to clear the host restriction; an explicit empty or disjoint list is rejected. Validation and pre-selection fallback failures preserve the previous restriction. Failures after a fallback selection commits retain the new restriction and selected model; callers should inspect current session state after such an error. +/// Canvas close parameters. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class ModelSetAllowedModelsRequest +internal sealed class CanvasCloseRequest { - /// Exact model IDs to permit, or null to clear the host restriction. - [JsonPropertyName("allowedModels")] - public IList? AllowedModels { 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; } -/// 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. +/// Canvas action invocation result. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class ModelSetReasoningEffortResult +public sealed class CanvasActionInvokeResult { - /// Reasoning effort level recorded on the session after the update. - [JsonPropertyName("reasoningEffort")] - public string ReasoningEffort { get; set; } = string.Empty; + /// Provider-supplied action result. + [JsonPropertyName("result")] + public JsonElement? Result { get; set; } } -/// Reasoning effort level to apply to the currently selected model. +/// Canvas action invocation parameters. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class ModelSetReasoningEffortRequest +internal sealed class CanvasActionInvokeRequest { - /// 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; + /// 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; } -/// Availability of a server-advertised routing preference. +/// Internal canvas provider registration parameters. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class AutoTierStatus +internal sealed class CanvasProviderRegisterRequest { - /// Whether the provider permits selecting this preference. - [JsonPropertyName("enabled")] - public bool Enabled { get; set; } + /// Canvas contributions supplied by the provider. + [JsonPropertyName("canvases")] + public IList Canvases { get => field ??= []; set; } - /// Human-readable explanation of availability. - [JsonPropertyName("message")] - public string? Message { get; set; } + /// Connection identifier for callback routing. + [JsonPropertyName("connectionId")] + public string ConnectionId { get; set; } = string.Empty; - /// Extensible machine-readable unavailability reason. - [JsonPropertyName("reason")] - public string? Reason { get; set; } + /// 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; } -/// A server-advertised routing preference. Identifiers and execution types are extensible. +/// Internal canvas provider unregistration parameters. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class AutoTierDescriptor +internal sealed class CanvasProviderUnregisterRequest { - /// Description displayed beside the preference. - [JsonPropertyName("description")] - public string Description { get; set; } = string.Empty; - - /// Human-readable label, not a routing identifier. - [JsonPropertyName("displayName")] - public string DisplayName { get; set; } = string.Empty; - - /// Opaque routing identifier transmitted unchanged to the provider. - [JsonPropertyName("id")] - public string Id { get; set; } = string.Empty; + /// Connection identifier to unregister. + [JsonPropertyName("connectionId")] + public string ConnectionId { get; set; } = string.Empty; - /// Current account-specific availability. - [JsonPropertyName("status")] - public AutoTierStatus Status { get => field ??= new(); set; } + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} - /// Execution kind; this client supports `auto` preferences on the Auto model. +/// Machine-readable workflow run failure. +/// Polymorphic base type discriminated by type. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +[JsonPolymorphic( + TypeDiscriminatorPropertyName = "type", + UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] +[JsonDerivedType(typeof(WorkflowRunFailureWorkflowLimitReached), "workflow_limit_reached")] +[JsonDerivedType(typeof(WorkflowRunFailureWorkflowResumeDeclined), "workflow_resume_declined")] +[JsonDerivedType(typeof(WorkflowRunFailureWorkflowDurableFailure), "workflow_durable_failure")] +[JsonDerivedType(typeof(WorkflowRunFailureWorkflowAccountingIncomplete), "workflow_accounting_incomplete")] +[JsonDerivedType(typeof(WorkflowRunFailureWorkflowProviderDisconnected), "workflow_provider_disconnected")] +public partial class WorkflowRunFailure +{ + /// The type discriminator. [JsonPropertyName("type")] - public string Type { get; set; } = string.Empty; + public virtual string Type { get; set; } = string.Empty; } -/// Account-bound discovery metadata for the virtual `auto` model. + +/// The workflow_limit_reached variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class AutoTierMetadata +public partial class WorkflowRunFailureWorkflowLimitReached : WorkflowRunFailure { - /// Provider-default preference, used only when no explicit preference exists. - [JsonPropertyName("defaultTier")] - public string DefaultTier { get; set; } = string.Empty; + /// + [JsonIgnore] + public override string Type => "workflow_limit_reached"; - /// Provider that supplied this metadata, when the catalog is provider-attributed. - [JsonPropertyName("providerId")] - public string? ProviderId { get; set; } + /// Resource ceiling that stopped the run. + [JsonPropertyName("kind")] + public required WorkflowRunFailureKind Kind { get; set; } - /// Routing preferences in the server's presentation order. - [JsonPropertyName("tiers")] - public IList Tiers { get => field ??= []; set; } + /// Workflow run identifier. + [JsonPropertyName("runId")] + public required string RunId { get; set; } + + /// Suggested larger ceiling when the runtime can derive one safely. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("suggestedValue")] + public double? SuggestedValue { get; set; } + + /// Approved effective ceiling that was reached. + [JsonPropertyName("value")] + public required double Value { get; set; } } -/// Cost-category metadata for a CAPI model. +/// The workflow_resume_declined variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class SessionModelPriceCategory +public partial class WorkflowRunFailureWorkflowResumeDeclined : WorkflowRunFailure { - /// CAPI model identifier. - [JsonPropertyName("id")] - public string Id { get; set; } = string.Empty; + /// + [JsonIgnore] + public override string Type => "workflow_resume_declined"; - /// Cost category assigned to the model. - [JsonPropertyName("priceCategory")] - public ModelPickerPriceCategory PriceCategory { get; set; } + /// Human-readable reason the resume did not proceed. + [JsonPropertyName("reason")] + public required string Reason { get; set; } + + /// Workflow run identifier whose changed limits were declined. + [JsonPropertyName("runId")] + public required string RunId { get; set; } } -/// One model provider available to the session — the model analog of the account `ProviderDescriptor`. Opaque id/label/kind plus a stable ordering; central code never branches on kind. +/// The workflow_durable_failure variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class ModelProviderDescriptor +public partial class WorkflowRunFailureWorkflowDurableFailure : WorkflowRunFailure { - /// Opaque, stable provider id, stamped onto every model this provider returns. - [JsonPropertyName("id")] - public string Id { get; set; } = string.Empty; + /// + [JsonIgnore] + public override string Type => "workflow_durable_failure"; - /// The neutral provider kind. - [JsonPropertyName("kind")] - public ModelProviderKind Kind { get; set; } + /// Stable failure code. + [JsonPropertyName("code")] + public required string Code { get; set; } - /// Human-readable menu label, owned by the runtime so every consumer renders identical text. - [JsonPropertyName("label")] - public string Label { get; set; } = string.Empty; + /// Execution-critical durable operation that failed. + [JsonPropertyName("operation")] + public required WorkflowDurableOperation Operation { get; set; } - /// Stable ordering key for presenting providers in a deterministic sequence. - [JsonPropertyName("ordering")] - public long Ordering { get; set; } + /// Workflow run identifier. + [JsonPropertyName("runId")] + public required string RunId { get; set; } } -/// The list of models available to this session. +/// The run stopped because its usage accounting could not be completed. +/// The workflow_accounting_incomplete variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class SessionModelList +public partial class WorkflowRunFailureWorkflowAccountingIncomplete : WorkflowRunFailure { - /// Ordered Auto routing preferences discovered for this session's account. - [JsonPropertyName("auto")] - public AutoTierMetadata? Auto { get; set; } + /// + [JsonIgnore] + public override string Type => "workflow_accounting_incomplete"; - /// 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; } + /// Confirmed usage in nano-AIU, representing the floor of what the run spent. + [JsonPropertyName("drainedNanoAiu")] + public required long DrainedNanoAiu { get; set; } - /// Cost categories for the full CAPI catalog, including picker-disabled models that Auto may select. Metadata only; entries absent from `list` are not manually selectable. - [JsonPropertyName("modelPriceCategories")] - public IList? ModelPriceCategories { get; set; } + /// Workflow run identifier. + [JsonPropertyName("runId")] + public required string RunId { get; set; } +} - /// The model providers available to this session, in ordering order, resolved from the account roster; empty when no provider is entitled (logged out / seatless). Each model in `list` carries its own provider reference; this roster gives the deterministic provider sequence and lets a consumer group by provider without deriving ordering from the model list. Central code never branches on a provider kind. - [JsonPropertyName("providers")] - public IList? Providers { get; set; } +/// The extension that owns the workflow 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 workflow_provider_disconnected variant of . +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public partial class WorkflowRunFailureWorkflowProviderDisconnected : WorkflowRunFailure +{ + /// + [JsonIgnore] + public override string Type => "workflow_provider_disconnected"; - /// Per-quota snapshots returned alongside the model list, keyed by quota type. - [JsonPropertyName("quotaSnapshots")] - public IDictionary? QuotaSnapshots { get; set; } + /// Workflow run identifier. + [JsonPropertyName("runId")] + public required string RunId { get; set; } } -/// RPC data type for SessionModelList operations. +/// Durable metadata describing who initiated a workflow pause. +/// Polymorphic base type discriminated by type. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class SessionModelListRequest +[JsonPolymorphic( + TypeDiscriminatorPropertyName = "type", + UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] +[JsonDerivedType(typeof(WorkflowPauseInfoUser), "user")] +[JsonDerivedType(typeof(WorkflowPauseInfoCheckpoint), "checkpoint")] +public partial class WorkflowPauseInfo { - /// If true, bypasses the per-session model list cache and re-fetches from CAPI. - [JsonPropertyName("skipCache")] - public bool? SkipCache { get; set; } + /// The type discriminator. + [JsonPropertyName("type")] + public virtual string Type { get; set; } = string.Empty; } -/// RPC data type for SessionModelListRequestWithSession operations. + +/// The user variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionModelListRequestWithSession +public partial class WorkflowPauseInfoUser : WorkflowPauseInfo { - /// 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; } + /// + [JsonIgnore] + public override string Type => "user"; } -/// Identifies the target session. +/// The checkpoint variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionModeGetRequest +public partial class WorkflowPauseInfoCheckpoint : WorkflowPauseInfo { - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// + [JsonIgnore] + public override string Type => "checkpoint"; + + /// Stable author-defined checkpoint key that initiated the pause. + [JsonPropertyName("key")] + public required string Key { get; set; } } -/// Outcome of a session mode change, including any model switch it triggered and follow-up the host must perform. +/// Complete current or terminal workflow run envelope. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class ModeSetResult +public sealed class WorkflowRunResult { - /// Whether the host should arm an interactive continuation after the mode change. - [JsonPropertyName("armInteractiveContinuation")] - public bool? ArmInteractiveContinuation { 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; } - /// Compaction confirmation required before the mode change can complete. - [JsonPropertyName("confirmation")] - public ModelSwitchConfirmation? Confirmation { get; set; } + /// Error message for an errored run. + [JsonPropertyName("error")] + public string? Error { get; set; } - /// Whether the host must defer implementing the requested mode change. - [JsonPropertyName("deferImplementation")] - public bool? DeferImplementation { get; set; } + /// Machine-readable failure details for a halted or errored run. + [JsonPropertyName("failure")] + public WorkflowRunFailure? Failure { get; set; } - /// Deprecation warnings associated with the model selected by the mode change. - [JsonPropertyName("deprecationWarnings")] - public IList? DeprecationWarnings { get; set; } + /// Structured pause initiator metadata for a paused attempt. + [JsonPropertyName("pauseInfo")] + public WorkflowPauseInfo? PauseInfo { get; set; } - /// User-facing outcome message for the model switch triggered by the mode change. - [JsonPropertyName("message")] - public string? Message { get; set; } + /// Reason for a halted or cancelled run. + [JsonPropertyName("reason")] + public string? Reason { get; set; } - /// Whether the requested mode was applied to the session. False only when an 'expectedMode' precondition did not hold, in which case any model change reported alongside it was still applied. - [JsonPropertyName("modeApplied")] - public bool? ModeApplied { get; set; } + /// Completed workflow result. + [JsonPropertyName("result")] + public JsonElement? Result { get; set; } - /// Whether applying the mode changed the active model. - [JsonPropertyName("modelChanged")] - public bool ModelChanged { get; set; } + /// Workflow run identifier. + [JsonPropertyName("runId")] + public string RunId { get; set; } = string.Empty; - /// Lifecycle status of the requested mode change. - [JsonPropertyName("status")] - public string Status { get; set; } = string.Empty; + /// Partial journal and progress snapshot for a halted, cancelled, or errored run. + [JsonPropertyName("snapshot")] + public JsonElement? Snapshot { get; set; } - /// User-facing warning produced while applying the mode change. - [JsonPropertyName("warning")] - public string? Warning { get; set; } + /// Current or terminal workflow run status. + [JsonPropertyName("status")] + public WorkflowRunStatus Status { get; set; } } -/// Agent interaction mode to apply to the session. +/// Wire-only per-invocation workflow resource ceiling overrides. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class ModeSetRequest +public sealed class WorkflowRunLimits { - /// Explicit response to a model-switch compaction preflight. - [JsonPropertyName("compactionDecision")] - public string? CompactionDecision { get; set; } - - /// Mode the session must currently be in for the change to apply. When set and the session is in a different mode the request is a no-op and reports status 'unchanged'. - [JsonPropertyName("expectedMode")] - public SessionMode? ExpectedMode { get; set; } - - /// Session whose plan-mode base state should be inherited. - [JsonPropertyName("inheritPlanBaseFromSessionId")] - public string? InheritPlanBaseFromSessionId { get; set; } + /// Maximum AI credits consumed by workflow 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; } - /// The session mode the agent is operating in. - [JsonPropertyName("mode")] - public SessionMode Mode { get; set; } + /// Maximum number of workflow subagents that may run concurrently. + [JsonPropertyName("maxConcurrentSubagents")] + public long? MaxConcurrentSubagents { get; set; } - /// Whether the selected plan model should be persisted. - [JsonPropertyName("persistPlanSelection")] - public bool? PersistPlanSelection { get; set; } + /// Maximum total number of workflow subagents that may be admitted. + [JsonPropertyName("maxTotalSubagents")] + public long? MaxTotalSubagents { get; set; } - /// Settings context used when persisting the selected plan model. - [JsonPropertyName("pickerSettingsContext")] - public ModelPickerSettingsContext? PickerSettingsContext { 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; } +} - /// Context tier to use with the dedicated plan model. - [JsonPropertyName("planContextTier")] - public string? PlanContextTier { get; set; } +/// Options controlling workflow invocation. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public sealed class WorkflowRunOptions +{ + /// Per-invocation resource ceiling overrides. + [JsonPropertyName("limits")] + public WorkflowRunLimits? Limits { get; set; } - /// Action to perform when leaving plan mode. - [JsonPropertyName("planExitAction")] - public string? PlanExitAction { get; set; } + /// Whether to emit workflow phase names to the session transcript. + [JsonPropertyName("logPhaseNames")] + public bool? LogPhaseNames { get; set; } - /// Dedicated model to use in plan mode, when configured. - [JsonPropertyName("planModel")] - public string? PlanModel { get; set; } + /// Whether to notify the originating session when the workflow completes. + [JsonPropertyName("notifyOnComplete")] + public bool? NotifyOnComplete { get; set; } - /// Whether a dedicated plan model is configured. - [JsonPropertyName("planModelConfigured")] - public bool? PlanModelConfigured { get; set; } + /// Run identifier whose journal and progress should seed this resumed run. + [JsonPropertyName("resumeFromRunId")] + public string? ResumeFromRunId { get; set; } +} - /// Provider owning planModel. Omit for legacy bare-model selection. - [JsonPropertyName("planModelProviderId")] - public string? PlanModelProviderId { get; set; } +/// Parameters for invoking a registered workflow. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class WorkflowRunRequest +{ + /// Workflow input value. + [JsonPropertyName("args")] + public JsonElement Args { get; set; } - /// Reasoning effort to use with the dedicated plan model. - [JsonPropertyName("planReasoningEffort")] - public string? PlanReasoningEffort { get; set; } + /// Registered workflow name. + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; - /// Whether leaving plan mode should restore the session's previous model. - [JsonPropertyName("restorePlanModel")] - public bool? RestorePlanModel { get; set; } + /// Workflow invocation options. + [JsonPropertyName("options")] + public WorkflowRunOptions? Options { 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. +/// Resolved persisted workflow identity and resumed run envelope. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class NameGetResult +public sealed class WorkflowResumeResult { - /// The session name (user-set or auto-generated), or null if not yet set. - [JsonPropertyName("name")] - public string? Name { get; set; } -} + /// Terminal resumed run envelope. + [JsonPropertyName("run")] + public WorkflowRunResult Run { get => field ??= new(); set; } -/// Identifies the target session. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionNameGetRequest -{ - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Persisted workflow name resolved for the resumed run. + [JsonPropertyName("workflowName")] + public string WorkflowName { get; set; } = string.Empty; } -/// New friendly name to apply to the session. +/// Parameters for resuming a workflow run from its persisted identity. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class NameSetRequest +internal sealed class WorkflowResumeRequest { - /// 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; + /// Optional per-invocation resource ceiling overrides. + [JsonPropertyName("limits")] + public WorkflowRunLimits? Limits { get; set; } + + /// Whether to emit workflow phase names to the session transcript. + [JsonPropertyName("logPhaseNames")] + public bool? LogPhaseNames { get; set; } + + /// Whether to notify the originating session when the workflow completes. + [JsonPropertyName("notifyOnComplete")] + public bool? NotifyOnComplete { get; set; } + + /// Workflow run identifier. + [JsonPropertyName("runId")] + public string RunId { get; set; } = string.Empty; /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Indicates whether the auto-generated summary was applied as the session's name. -[Experimental(global::GitHub.Copilot.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; } -} - -/// Auto-generated session summary to apply as the session's name when no user-set name exists. +/// Options for an internal tool-originated workflow invocation. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class NameSetAutoRequest +internal sealed class WorkflowToolRunOptions { - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Per-invocation resource ceiling overrides. + [JsonPropertyName("limits")] + public WorkflowRunLimits? Limits { 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; + /// Run identifier whose journal and progress should seed this resumed run. + [JsonPropertyName("resumeFromRunId")] + public string? ResumeFromRunId { get; set; } } -/// Existence, contents, and resolved path of the session plan file. +/// Internal parameters for invoking a registered workflow from a tool. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class PlanReadResult +internal sealed class WorkflowToolRunRequest { - /// The content of the plan file, or null if it does not exist. - [JsonPropertyName("content")] - public string? Content { get; set; } + /// Workflow input value. + [JsonPropertyName("args")] + public JsonElement Args { get; set; } - /// Whether the plan file exists in the workspace. - [JsonPropertyName("exists")] - public bool Exists { get; set; } + /// Registered workflow name. + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; - /// Absolute file path of the plan file, or null if workspace is not enabled. - [JsonPropertyName("path")] - public string? Path { get; set; } -} + /// Tool-originated workflow invocation options. + [JsonPropertyName("options")] + public WorkflowToolRunOptions? Options { get; set; } -/// Identifies the target session. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionPlanReadRequest -{ /// 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; } } -/// Replacement contents to write to the session plan file. +/// Internal parameters for resuming a workflow run from a tool. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class PlanUpdateRequest +internal sealed class WorkflowToolResumeRequest { - /// The new content for the plan file. - [JsonPropertyName("content")] - public string Content { get; set; } = string.Empty; + /// Optional per-invocation resource ceiling overrides. + [JsonPropertyName("limits")] + public WorkflowRunLimits? Limits { get; set; } + + /// Workflow 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; } } -/// Identifies the target session. +/// Parameters for retrieving a workflow run. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionPlanDeleteRequest +internal sealed class WorkflowGetRunRequest { + /// Workflow run identifier. + [JsonPropertyName("runId")] + public string RunId { get; set; } = string.Empty; + /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// A single todo row read from the session SQL `todos` table. All fields are optional because the SQL schema is best-effort and the agent may not have populated every column. +/// Declared or approved workflow resource ceilings. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class PlanSqlTodosRow +public sealed class WorkflowDeclaredLimits { - /// 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; } - - /// Todo description. - [JsonPropertyName("description")] - public string? Description { get; set; } + /// Maximum AI credits consumed by subagents and descendants. + [JsonPropertyName("maxAiCredits")] + public double? MaxAiCredits { get; set; } - /// Todo identifier. - [JsonPropertyName("id")] - public string? Id { get; set; } + /// Maximum concurrently active subagents. + [JsonPropertyName("maxConcurrentSubagents")] + public long? MaxConcurrentSubagents { get; set; } - /// Todo status. - [JsonPropertyName("status")] - public string? Status { get; set; } + /// Maximum total subagents spawned by the run. + [JsonPropertyName("maxTotalSubagents")] + public long? MaxTotalSubagents { get; set; } - /// Todo title. - [JsonPropertyName("title")] - public string? Title { get; set; } + /// Maximum accumulated active execution time in seconds. + [JsonPropertyName("timeoutSeconds")] + public double? TimeoutSeconds { get; set; } } -/// Todo rows read from the session SQL database. Empty when no session database is available. +/// Durable workflow resource consumption. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class PlanReadSqlTodosResult +public sealed class WorkflowRunConsumed { - /// 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; } -} + /// Accumulated active execution time in milliseconds. + [JsonPropertyName("activeMs")] + public long ActiveMs { get; set; } -/// Identifies the target session. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionPlanReadSqlTodosRequest -{ - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// AI usage consumed by the run in nano-AIU. + [JsonPropertyName("nanoAiu")] + public long NanoAiu { get; set; } + + /// Total subagents spawned by the run. + [JsonPropertyName("subagents")] + public long Subagents { get; set; } } -/// A single dependency edge read from the session SQL `todo_deps` table, indicating that one todo must complete before another. +/// Current workflow phase identity. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class PlanSqlTodoDependency +public sealed class WorkflowCurrentPhase { - /// ID of the todo it depends on. - [JsonPropertyName("dependsOn")] - public string DependsOn { get; set; } = string.Empty; + /// Current phase identifier. + [JsonPropertyName("id")] + public string Id { get; set; } = string.Empty; - /// ID of the todo that has the dependency. - [JsonPropertyName("todoId")] - public string TodoId { get; set; } = string.Empty; + /// Zero-based declared phase ordinal, or null for an undeclared phase. + [JsonPropertyName("ordinal")] + public long? Ordinal { get; set; } } -/// Todo rows + dependency edges read from the session SQL database. +/// Prompt-safe terminal workflow outcome. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class PlanReadSqlTodosWithDependenciesResult +public sealed class WorkflowRunTerminal { - /// 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; } + /// Human-readable terminal error. + [JsonPropertyName("error")] + public string? Error { get; set; } - /// 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; } + /// Machine-readable terminal failure. + [JsonPropertyName("failure")] + public WorkflowRunFailure? Failure { get; set; } + + /// Pause initiator metadata, or null when the run did not pause. + [JsonPropertyName("pauseInfo")] + public WorkflowPauseInfo? PauseInfo { get; 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; } } -/// Identifies the target session. +/// Durable workflow run summary with read-time live overlays. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionPlanReadSqlTodosWithDependenciesRequest +public sealed class WorkflowRunSummary { - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; -} + /// Epoch milliseconds when the current active segment started, or null while inactive. + [JsonPropertyName("activeSegmentStartedAt")] + public long? ActiveSegmentStartedAt { get; set; } -/// RPC data type for WorkspacesGetWorkspaceResultWorkspace operations. -public sealed class WorkspacesGetWorkspaceResultWorkspace -{ - /// Current Git branch. - [JsonPropertyName("branch")] - public string? Branch { get; set; } + /// Approved effective resource ceilings, or null until approved. + [JsonPropertyName("approved")] + public WorkflowDeclaredLimits? Approved { get; set; } - /// Whether the per-session Chronicle upgrade prompt was dismissed for the workspace. - [JsonPropertyName("chronicle_sync_dismissed")] - public bool? ChronicleSyncDismissed { get; set; } + /// Whether the durable run state currently passes runtime resume eligibility checks. + [JsonPropertyName("canResume")] + public bool CanResume { get; set; } - /// Name of the client that created the workspace. - [JsonPropertyName("client_name")] - public string? ClientName { get; set; } + /// Epoch milliseconds when the run completed, or null while nonterminal. + [JsonPropertyName("completedAt")] + public long? CompletedAt { get; set; } - /// Timestamp when the workspace was created. - [JsonPropertyName("created_at")] - public DateTimeOffset? CreatedAt { get; set; } + /// Durable resource consumption. + [JsonPropertyName("consumed")] + public WorkflowRunConsumed Consumed { get => field ??= new(); set; } - /// Current working directory associated with the workspace. - [JsonPropertyName("cwd")] - public string? Cwd { get; set; } + /// Epoch milliseconds when the run was created. + [JsonPropertyName("createdAt")] + public long CreatedAt { get; set; } - /// Git repository root associated with the workspace. - [JsonPropertyName("git_root")] - public string? GitRoot { get; set; } + /// Current phase identity, or null before any phase is entered. + [JsonPropertyName("currentPhase")] + public WorkflowCurrentPhase? CurrentPhase { get; set; } - /// Allowed values for the `WorkspacesWorkspaceDetailsHostType` enumeration. - [JsonPropertyName("host_type")] - public WorkspacesWorkspaceDetailsHostType? HostType { get; set; } + /// Resource ceilings declared by the workflow. + [JsonPropertyName("declaredLimits")] + public WorkflowDeclaredLimits DeclaredLimits { get => field ??= new(); 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; + /// Number of phases declared by the workflow. + [JsonPropertyName("declaredPhaseCount")] + public long DeclaredPhaseCount { get; set; } - /// Most recent Mission Control event identifier observed for the workspace. - [JsonPropertyName("mc_last_event_id")] - public string? McLastEventId { get; set; } + /// Human-readable workflow description. + [JsonPropertyName("description")] + public string Description { get; set; } = string.Empty; - /// Mission Control session identifier associated with the workspace. - [JsonPropertyName("mc_session_id")] - public string? McSessionId { get; set; } + /// Number of direct workflow agents currently live. + [JsonPropertyName("liveAgentCount")] + public long LiveAgentCount { get; set; } - /// Mission Control task identifier associated with the workspace. - [JsonPropertyName("mc_task_id")] - public string? McTaskId { get; set; } + /// Epoch milliseconds when this live-overlay snapshot was observed. + [JsonPropertyName("observedAt")] + public long ObservedAt { get; set; } - /// Workspace display name. - [JsonPropertyName("name")] - public string? Name { get; set; } + /// Monotonic durable run revision. + [JsonPropertyName("revision")] + public long Revision { get; set; } - /// Whether the workspace session can be steered remotely. - [JsonPropertyName("remote_steerable")] - public bool? RemoteSteerable { get; set; } + /// Workflow run identifier. + [JsonPropertyName("runId")] + public string RunId { get; set; } = string.Empty; - /// Repository identifier associated with the workspace. - [JsonPropertyName("repository")] - public string? Repository { get; set; } + /// Epoch milliseconds when execution first started, or null before start. + [JsonPropertyName("startedAt")] + public long? StartedAt { get; set; } - /// Number of persisted summaries in the workspace. - [JsonPropertyName("summary_count")] - public long? SummaryCount { get; set; } + /// Current workflow run status. + [JsonPropertyName("status")] + public WorkflowRunStatus Status { get; set; } - /// Timestamp when the workspace was last updated. - [JsonPropertyName("updated_at")] - public DateTimeOffset? UpdatedAt { get; set; } + /// Terminal run outcome, or null while nonterminal. + [JsonPropertyName("terminal")] + public WorkflowRunTerminal? Terminal { get; set; } - /// Whether the workspace name was explicitly chosen by the user. - [JsonPropertyName("user_named")] - public bool? UserNamed { get; set; } -} + /// Total direct workflow agents spawned across all attempts. + [JsonPropertyName("totalSpawnedAgentCount")] + public long TotalSpawnedAgentCount { get; set; } -/// Current workspace metadata for the session, including its absolute filesystem path when available. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class WorkspacesGetWorkspaceResult -{ - /// 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; } + /// Epoch milliseconds when the durable run was last updated. + [JsonPropertyName("updatedAt")] + public long UpdatedAt { get; set; } - /// Current workspace metadata, or null if not available. - [JsonPropertyName("workspace")] - public WorkspacesGetWorkspaceResultWorkspace? Workspace { get; set; } + /// Registered workflow name. + [JsonPropertyName("workflowName")] + public string WorkflowName { get; set; } = string.Empty; } -/// Identifies the target session. +/// A page of workflow runs in durable creation order. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionWorkspacesGetWorkspaceRequest +public sealed class WorkflowListRunsResult { - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// 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; } + + /// Workflow run summaries in durable creation order. + [JsonPropertyName("runs")] + public IList Runs { get => field ??= []; set; } } -/// Workspace metadata fields to update. +/// Parameters for paging workflow runs. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class WorkspacesUpdateMetadataRequest +internal sealed class WorkflowListRunsRequest { - /// Opaque workspace context supplied by the session host. - [JsonPropertyName("context")] - public JsonElement? Context { get; set; } + /// Exclusive forward cursor. + [JsonPropertyName("afterSeq")] + public long? AfterSeq { get; set; } - /// Optional workspace display name override. - [JsonPropertyName("name")] - public string? Name { 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; } -/// Optional session context used when creating a local workspace. +/// Prompt-safe durable identity and live status for a direct workflow agent. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class WorkspacesEnsureRequest +public sealed class WorkflowAgentSummary { - /// Opaque workspace context supplied by the session host. - [JsonPropertyName("context")] - public JsonElement? Context { get; set; } + /// Accumulated active agent time in milliseconds. + [JsonPropertyName("activeMs")] + public long ActiveMs { get; set; } - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; -} + /// Prompt-safe live activity text. + [JsonPropertyName("activity")] + public string? Activity { get; set; } -/// Relative paths of files stored in the session workspace files directory. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class WorkspacesListFilesResult -{ - /// Slash-separated relative file paths in the workspace files directory. - [JsonPropertyName("files")] - public IList Files { get => field ??= []; set; } -} + /// Stable direct-agent identifier. + [JsonPropertyName("agentId")] + public string AgentId { get; set; } = string.Empty; -/// Identifies the target session. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionWorkspacesListFilesRequest -{ - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; -} + /// Registered agent type. + [JsonPropertyName("agentType")] + public string AgentType { get; set; } = string.Empty; -/// Contents of the requested workspace file as a UTF-8 string. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class WorkspacesReadFileResult -{ - /// File content as a UTF-8 string. - [JsonPropertyName("content")] - public string Content { get; set; } = string.Empty; -} + /// Epoch milliseconds when the agent completed. + [JsonPropertyName("completedAt")] + public long? CompletedAt { get; set; } -/// Relative path of the workspace file to read. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class WorkspacesReadFileRequest -{ - /// Slash-separated relative path within the workspace files directory. - [JsonPropertyName("path")] - public string Path { get; set; } = string.Empty; + /// Friendly, non-unique name intended for display. + [JsonPropertyName("displayName")] + public string? DisplayName { get; set; } - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; -} + /// Friendly, non-unique name intended for display. + [JsonPropertyName("label")] + public string Label { get; set; } = string.Empty; -/// Relative path and UTF-8 content for the workspace file to create or overwrite. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class WorkspacesCreateFileRequest -{ - /// File content to write as a UTF-8 string. - [JsonPropertyName("content")] - public string Content { get; set; } = string.Empty; + /// Phase identifier active when the agent was launched, or null. + [JsonPropertyName("phaseId")] + public string? PhaseId { get; set; } - /// Slash-separated relative path within the workspace files directory. - [JsonPropertyName("path")] - public string Path { get; set; } = string.Empty; + /// Model requested when the agent was launched. + [JsonPropertyName("requestedModel")] + public string? RequestedModel { get; set; } - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Concrete model resolved for the agent. + [JsonPropertyName("resolvedModel")] + public string? ResolvedModel { get; set; } + + /// Owning workflow run identifier. + [JsonPropertyName("runId")] + public string RunId { get; set; } = string.Empty; + + /// Epoch milliseconds when the agent started. + [JsonPropertyName("startedAt")] + public long? StartedAt { get; set; } + + /// Current durable or live agent status. + [JsonPropertyName("status")] + public string Status { get; set; } = string.Empty; + + /// Tool-call identifier that launched the agent. + [JsonPropertyName("toolCallId")] + public string ToolCallId { get; set; } = string.Empty; } -/// Filesystem metadata for a path in the session workspace files directory. +/// Durable lifecycle and timing for one workflow phase. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class WorkspacesStatFileResult +public sealed class WorkflowPhaseObservation { - /// Creation time in Unix epoch milliseconds. - [JsonPropertyName("birthtimeMs")] - public double BirthtimeMs { get; set; } + /// Completed active time accumulated by this phase in milliseconds. + [JsonPropertyName("accumulatedActiveMs")] + public long AccumulatedActiveMs { get; set; } - /// Whether the path identifies a directory. - [JsonPropertyName("isDirectory")] - public bool IsDirectory { get; set; } + /// Epoch milliseconds when this phase completed; for a skipped phase, the synthetic skip timestamp (equal to `startedAt`). + [JsonPropertyName("completedAt")] + public long? CompletedAt { get; set; } - /// Whether the path identifies a regular file. - [JsonPropertyName("isFile")] - public bool IsFile { get; set; } + /// Current live active time for this phase in milliseconds. + [JsonPropertyName("currentActiveMs")] + public long CurrentActiveMs { get; set; } - /// Last modification time in Unix epoch milliseconds. - [JsonPropertyName("mtimeMs")] - public double MtimeMs { get; set; } + /// Optional human-readable phase detail. + [JsonPropertyName("detail")] + public string? Detail { get; set; } - /// Size in bytes. - [JsonPropertyName("size")] - public double Size { get; set; } -} + /// Number of times execution entered this phase. + [JsonPropertyName("entryCount")] + public long EntryCount { get; set; } -/// Relative path of the workspace file or directory to inspect. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class WorkspacesStatFileRequest -{ - /// Slash-separated relative path within the workspace files directory. - [JsonPropertyName("path")] - public string Path { get; set; } = string.Empty; + /// Phase identifier. + [JsonPropertyName("id")] + public string Id { get; set; } = string.Empty; - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; -} + /// Most recent run attempt that entered this phase, or `0` if the phase has never been entered. + [JsonPropertyName("lastEnteredRunAttempt")] + public long LastEnteredRunAttempt { get; set; } -/// Directory to create within the session workspace files directory. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class WorkspacesCreateDirectoryRequest -{ - /// Slash-separated relative path within the workspace files directory. - [JsonPropertyName("path")] - public string Path { get; set; } = string.Empty; + /// Direct agents in this phase that are currently live. + [JsonPropertyName("liveAgentCount")] + public long LiveAgentCount { get; set; } - /// Whether to create missing parent directories. Defaults to false. - [JsonPropertyName("recursive")] - public bool? Recursive { get; set; } + /// Zero-based declared phase ordinal, or null for an undeclared phase. + [JsonPropertyName("ordinal")] + public long? Ordinal { get; set; } - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Epoch milliseconds when this phase first started; for a skipped phase, the synthetic skip timestamp (equal to `completedAt`). + [JsonPropertyName("startedAt")] + public long? StartedAt { get; set; } + + /// Derived lifecycle state of the phase. + [JsonPropertyName("status")] + public WorkflowPhaseStatus Status { 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; } } -/// File or directory to remove from the session workspace files directory. +/// One durable workflow progress record. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class WorkspacesRemovePathRequest +public sealed class WorkflowProgressLine { - /// Whether a missing path should be treated as success. Defaults to false. - [JsonPropertyName("force")] - public bool? Force { get; set; } + /// Resume attempt that emitted this record. + [JsonPropertyName("attempt")] + public long Attempt { get; set; } - /// Slash-separated relative path within the workspace files directory. - [JsonPropertyName("path")] - public string Path { get; set; } = string.Empty; + /// Progress record kind. + [JsonPropertyName("kind")] + public WorkflowLogLineKind Kind { get; set; } - /// Whether to remove directory contents recursively. Defaults to false. - [JsonPropertyName("recursive")] - public bool? Recursive { get; set; } + /// Phase active when the record was emitted, or null before any phase. + [JsonPropertyName("phaseId")] + public string? PhaseId { get; set; } - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Epoch milliseconds when the record was persisted. + [JsonPropertyName("recordedAt")] + public long RecordedAt { get; set; } + + /// Global monotonic sequence number within the run. + [JsonPropertyName("seq")] + public long Seq { get; set; } + + /// Prompt-safe progress text. + [JsonPropertyName("text")] + public string Text { get; set; } = string.Empty; } -/// Source and destination paths for a rename within the session workspace files directory. +/// A bidirectional page of workflow progress. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class WorkspacesRenamePathRequest +public sealed class WorkflowProgressPage { - /// Slash-separated destination path relative to the workspace files directory. - [JsonPropertyName("destination")] - public string Destination { get; set; } = string.Empty; + /// Whether progress records newer than this page exist. + [JsonPropertyName("hasMoreNewer")] + public bool HasMoreNewer { get; set; } - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Whether progress records older than this page exist. + [JsonPropertyName("hasMoreOlder")] + public bool HasMoreOlder { get; set; } - /// Slash-separated source path relative to the workspace files directory. - [JsonPropertyName("source")] - public string Source { get; set; } = string.Empty; -} + /// Newest sequence number in this page, or null when empty. + [JsonPropertyName("newestSeq")] + public long? NewestSeq { get; set; } -/// Workspace checkpoint metadata with assigned number, human-readable title, and checkpoint filename. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class WorkspacesCheckpoints -{ - /// Filename of the checkpoint within the workspace checkpoints directory. - [JsonPropertyName("filename")] - public string Filename { get; set; } = string.Empty; + /// Oldest sequence number in this page, or null when empty. + [JsonPropertyName("oldestSeq")] + public long? OldestSeq { get; set; } - /// Checkpoint number assigned by the workspace manager. - [JsonPropertyName("number")] - public long Number { get; set; } + /// Progress records in sequence order. + [JsonPropertyName("records")] + public IList Records { get => field ??= []; set; } - /// Human-readable checkpoint title. - [JsonPropertyName("title")] - public string Title { get; set; } = string.Empty; + /// Run revision reflected by this page. + [JsonPropertyName("revision")] + public long Revision { get; set; } } -/// Workspace checkpoints in chronological order; empty when the workspace is not enabled. +/// Full workflow run observability detail. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class WorkspacesListCheckpointsResult +public sealed class WorkflowRunDetail { - /// Workspace checkpoints in chronological order. Empty when workspace is not enabled. - [JsonPropertyName("checkpoints")] - public IList Checkpoints { 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(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionWorkspacesListCheckpointsRequest -{ - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; -} + /// Durable identities and live statuses for direct workflow agents. + [JsonPropertyName("agents")] + public IList Agents { get => field ??= []; set; } -/// Checkpoint content as a UTF-8 string, or null when the checkpoint or workspace is missing. -[Experimental(global::GitHub.Copilot.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; } -} + /// Approved effective resource ceilings, or null until approved. + [JsonPropertyName("approved")] + public WorkflowDeclaredLimits? Approved { get; set; } -/// Checkpoint number to read. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class WorkspacesReadCheckpointRequest -{ - /// Checkpoint number to read. - [JsonPropertyName("number")] - public long Number { get; set; } + /// Whether the durable run state currently passes runtime resume eligibility checks. + [JsonPropertyName("canResume")] + public bool CanResume { get; set; } - /// 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; } -/// Metadata for the persisted summary. -public sealed class WorkspacesAddSummaryResultSummary -{ -} + /// Durable resource consumption. + [JsonPropertyName("consumed")] + public WorkflowRunConsumed Consumed { get => field ??= new(); set; } -/// Refreshed metadata for the containing workspace. -public sealed class WorkspacesAddSummaryResultWorkspace -{ -} + /// Epoch milliseconds when the run was created. + [JsonPropertyName("createdAt")] + public long CreatedAt { get; set; } -/// Persisted summary metadata and refreshed workspace metadata. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class WorkspacesAddSummaryResult -{ - /// Metadata for the persisted summary. - [JsonPropertyName("summary")] - public WorkspacesAddSummaryResultSummary? Summary { get; set; } + /// Current phase identity, or null before any phase is entered. + [JsonPropertyName("currentPhase")] + public WorkflowCurrentPhase? CurrentPhase { get; set; } - /// Refreshed metadata for the containing workspace. - [JsonPropertyName("workspace")] - public WorkspacesAddSummaryResultWorkspace? Workspace { get; set; } + /// Resource ceilings declared by the workflow. + [JsonPropertyName("declaredLimits")] + public WorkflowDeclaredLimits DeclaredLimits { get => field ??= new(); set; } + + /// Number of phases declared by the workflow. + [JsonPropertyName("declaredPhaseCount")] + public long DeclaredPhaseCount { get; set; } + + /// Human-readable workflow description. + [JsonPropertyName("description")] + public string Description { get; set; } = string.Empty; + + /// Number of direct workflow 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; } + + /// Lifecycle and timing observations for each workflow phase. + [JsonPropertyName("phases")] + public IList Phases { get => field ??= []; set; } + + /// Bidirectional page of durable workflow progress. + [JsonPropertyName("progress")] + public WorkflowProgressPage Progress { get => field ??= new(); set; } + + /// Monotonic durable run revision. + [JsonPropertyName("revision")] + public long Revision { get; set; } + + /// Workflow 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 workflow run status. + [JsonPropertyName("status")] + public WorkflowRunStatus Status { get; set; } + + /// Terminal run outcome, or null while nonterminal. + [JsonPropertyName("terminal")] + public WorkflowRunTerminal? Terminal { get; set; } + + /// Total direct workflow 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; } + + /// Registered workflow name. + [JsonPropertyName("workflowName")] + public string WorkflowName { get; set; } = string.Empty; } -/// Compaction summary checkpoint to persist. +/// Parameters for paging workflow progress. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class WorkspacesAddSummaryRequest +internal sealed class WorkflowGetRunProgressRequest { - /// Markdown summary content to persist. - [JsonPropertyName("content")] - public string Content { 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; } + + /// Workflow run identifier. + [JsonPropertyName("runId")] + public string RunId { 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. +/// Parameters for cancelling a workflow run. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class WorkspacesTruncateSummariesRequest +internal sealed class WorkflowCancelRequest { - /// Number of newest summaries to keep. - [JsonPropertyName("keepCount")] - public long KeepCount { get; set; } + /// Workflow run identifier. + [JsonPropertyName("runId")] + public string RunId { get; set; } = string.Empty; /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Autopilot objective file content, or null when missing. +/// Parameters for pausing a running workflow. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class WorkspacesReadAutopilotObjectiveResult +internal sealed class WorkflowPauseRequest { - /// Autopilot objective file content, or null when missing. - [JsonPropertyName("content")] - public string? Content { get; set; } -} + /// Workflow run identifier. + [JsonPropertyName("runId")] + public string RunId { get; set; } = string.Empty; -/// Identifies the target session. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionWorkspacesReadAutopilotObjectiveRequest -{ /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Result of writing the autopilot objective file. +/// RPC data type for SessionWorkflowPauseAtCheckpoint operations. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class WorkspacesWriteAutopilotObjectiveResult +internal sealed class SessionWorkflowPauseAtCheckpointResult { - /// Filesystem operation performed. - [JsonPropertyName("operation")] - public string Operation { get; set; } = string.Empty; + /// Whether this execution attempt must pause or may continue. + [JsonPropertyName("action")] + public WorkflowPauseCheckpointAction Action { get; set; } } -/// Autopilot objective file content to persist. +/// Parameters for an owned durable pause checkpoint. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class WorkspacesWriteAutopilotObjectiveRequest +internal sealed class WorkflowPauseCheckpointRequest { - /// Autopilot objective file content. - [JsonPropertyName("content")] - public string Content { get; set; } = string.Empty; + /// Opaque token identifying the execution attempt that reached the checkpoint. + [JsonPropertyName("executionToken")] + public string ExecutionToken { get; set; } = string.Empty; + + /// Stable author-defined checkpoint key. + [JsonPropertyName("key")] + public string Key { get; set; } = string.Empty; + + /// Workflow run identifier. + [JsonPropertyName("runId")] + public string RunId { get; set; } = string.Empty; /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Result of deleting the autopilot objective file. +/// Acknowledgement that a workflow request was accepted. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class WorkspacesDeleteAutopilotObjectiveResult +public sealed class WorkflowAckResult { - /// True when a file was deleted. - [JsonPropertyName("deleted")] - public bool Deleted { get; set; } } -/// Identifies the target session. +/// One ordered workflow progress line. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionWorkspacesDeleteAutopilotObjectiveRequest +public sealed class WorkflowLogLine { - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; -} - -/// Whether the autopilot objective file exists. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class WorkspacesAutopilotObjectiveExistsResult -{ - /// True when the objective file exists. - [JsonPropertyName("exists")] - public bool Exists { get; set; } -} - -/// Identifies the target session. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionWorkspacesAutopilotObjectiveExistsRequest -{ - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; -} - -/// 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; + /// Progress line kind. + [JsonPropertyName("kind")] + public WorkflowLogLineKind Kind { get; set; } - /// Absolute filesystem path to the saved paste file. - [JsonPropertyName("filePath")] - public string FilePath { get; set; } = string.Empty; + /// Monotonic sequence number within the workflow run. + [JsonPropertyName("seq")] + public long Seq { get; set; } - /// Size of the saved file in bytes. - [JsonPropertyName("sizeBytes")] - public long SizeBytes { get; set; } + /// Progress text. + [JsonPropertyName("text")] + public string Text { get; set; } = string.Empty; } -/// Descriptor for the saved paste file, or null when the workspace is unavailable. +/// Parameters for recording workflow progress. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class WorkspacesSaveLargePasteResult +internal sealed class WorkflowLogRequest { - /// 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; } -} + /// Opaque token identifying the current workflow execution attempt. + [JsonPropertyName("executionToken")] + public string ExecutionToken { get; set; } = string.Empty; -/// Pasted content to save as a UTF-8 file in the session workspace. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class WorkspacesSaveLargePasteRequest -{ - /// Pasted content to save as a UTF-8 file. - [JsonPropertyName("content")] - public string Content { get; set; } = string.Empty; + /// Ordered progress lines to append. + [JsonPropertyName("lines")] + public IList Lines { get => field ??= []; set; } + + /// Workflow run identifier. + [JsonPropertyName("runId")] + public string RunId { get; set; } = string.Empty; /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Complete text used to generate one session diff. These are display contents, with the same text decoding as the patch, not a file-restore contract. +/// Result of one workflow-scoped subagent call. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class WorkspaceDiffContents +public sealed class WorkflowAgentResult { - /// Complete current text read when computing the diff. Omitted for a deleted file; an empty string represents an existing empty file. - [JsonPropertyName("after")] - public string? After { get; set; } - - /// Complete text before the session first changed the file. Omitted when the file did not exist; an empty string represents an existing empty file. - [JsonPropertyName("before")] - public string? Before { get; set; } + /// Agent result, omitted when the agent produced no result. + [JsonPropertyName("result")] + public JsonElement? Result { get; set; } } -/// A single changed file and its unified diff. +/// Options for one workflow-scoped subagent call. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class WorkspaceDiffFileChange +public sealed class WorkflowAgentOptions { - /// Type of change represented by this file diff. - [JsonPropertyName("changeType")] - public WorkspaceDiffFileChangeType ChangeType { get; set; } + /// Optional built-in or custom agent name whose definition configures the subagent. + [JsonPropertyName("agent")] + public string? Agent { get; set; } - /// Full text used for this patch, only when includeContents was requested for session mode. Omitted for binary, oversized or unavailable contents, and for fallback results. Read isFallback and isTruncated before treating an absent value as a missing file. - [JsonPropertyName("contents")] - public WorkspaceDiffContents? Contents { get; set; } + /// Optional context tier override for the subagent. + [JsonPropertyName("contextTier")] + public ContextTier? ContextTier { get; set; } - /// Unified diff content for the file. Empty when the diff was truncated. - [JsonPropertyName("diff")] - public string Diff { get; set; } = string.Empty; + /// Optional label distinguishing otherwise identical memoized agent calls. + [JsonPropertyName("label")] + public string? Label { get; set; } - /// Whether the diff content was omitted because it exceeded the per-file size limit. - [JsonPropertyName("isTruncated")] - public bool? IsTruncated { get; set; } + /// Optional model identifier for the subagent. + [JsonPropertyName("model")] + public string? Model { get; set; } - /// Original file path for renamed files. - [JsonPropertyName("oldPath")] - public string? OldPath { get; set; } + /// Optional reasoning effort override for the subagent. + [JsonPropertyName("reasoningEffort")] + public string? ReasoningEffort { get; set; } - /// Path to the changed file, relative to the workspace root when the file lives under it. A file changed outside the workspace root keeps a `../`-relative path, or an absolute path when no relative path exists (for example a different Windows drive). - [JsonPropertyName("path")] - public string Path { get; set; } = string.Empty; + /// Optional JSON Schema for structured agent output. + [JsonPropertyName("schema")] + public JsonElement? Schema { get; set; } } -/// Workspace diff result for the requested mode. +/// Parameters for one workflow-scoped subagent call. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class WorkspaceDiffResult +internal sealed class WorkflowAgentRequest { - /// 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; } + /// Opaque token identifying the current workflow execution attempt. + [JsonPropertyName("executionToken")] + public string ExecutionToken { get; set; } = string.Empty; - /// 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; } + /// Subagent execution options. + [JsonPropertyName("opts")] + public WorkflowAgentOptions Opts { get => field ??= new(); set; } - /// Effective mode used for the returned changes. - [JsonPropertyName("mode")] - public WorkspaceDiffMode Mode { get; set; } + /// Prompt to send to the subagent. + [JsonPropertyName("prompt")] + public string Prompt { get; set; } = string.Empty; - /// Diff mode requested by the client. - [JsonPropertyName("requestedMode")] - public WorkspaceDiffMode RequestedMode { get; set; } + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; - /// 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; } + /// Workflow run identifier that owns the subagent. + [JsonPropertyName("workflowRunId")] + public string WorkflowRunId { get; set; } = string.Empty; } -/// Parameters for computing a workspace diff. +/// Result of reading a workflow journal entry. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class WorkspacesDiffRequest +public sealed class WorkflowJournalGetResult { - /// When true, ignore whitespace-only changes (git `--ignore-all-space`). Defaults to false. - [JsonPropertyName("ignoreWhitespace")] - public bool? IgnoreWhitespace { get; set; } - - /// Include the full before/after text used to compute each session diff. Defaults to false; true is accepted only for session mode. Existing capture/read limits still apply, and binary or unavailable contents are not returned. This can substantially increase response size. - [JsonPropertyName("includeContents")] - public bool? IncludeContents { get; set; } + /// Whether the journal contained the requested key. + [JsonPropertyName("hit")] + public bool Hit { get; set; } - /// Diff mode requested by the client. - [JsonPropertyName("mode")] - public required WorkspaceDiffMode Mode { get; set; } + /// Cached JSON result. The hit field distinguishes a cached JSON null from a miss. + [JsonPropertyName("resultJson")] + public JsonElement? ResultJson { get; set; } } -/// Parameters for computing a workspace diff. +/// Parameters for reading a workflow journal entry. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class WorkspacesDiffRequestWithSession +internal sealed class WorkflowJournalGetRequest { - /// When true, ignore whitespace-only changes (git `--ignore-all-space`). Defaults to false. - [JsonPropertyName("ignoreWhitespace")] - public bool? IgnoreWhitespace { get; set; } + /// Opaque token identifying the current workflow execution attempt. + [JsonPropertyName("executionToken")] + public string ExecutionToken { get; set; } = string.Empty; - /// Include the full before/after text used to compute each session diff. Defaults to false; true is accepted only for session mode. Existing capture/read limits still apply, and binary or unavailable contents are not returned. This can substantially increase response size. - [JsonPropertyName("includeContents")] - public bool? IncludeContents { get; set; } + /// Namespaced journal key. + [JsonPropertyName("key")] + public string Key { get; set; } = string.Empty; - /// Diff mode requested by the client. - [JsonPropertyName("mode")] - public WorkspaceDiffMode Mode { get; set; } + /// Workflow run identifier. + [JsonPropertyName("runId")] + public string RunId { get; set; } = string.Empty; /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Current per-window credit limit and consumption for an autopilot objective. +/// Parameters for storing a workflow journal entry. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class AutopilotObjectiveCreditLimit +internal sealed class WorkflowJournalPutRequest { - /// Configured AI-credit cap, when one is set. - [JsonPropertyName("credits")] - public double? Credits { get; set; } + /// Opaque token identifying the current workflow execution attempt. + [JsonPropertyName("executionToken")] + public string ExecutionToken { get; set; } = string.Empty; - /// Window consumption in fractional AI credits, for display. - [JsonPropertyName("creditsUsed")] - public double CreditsUsed { get; set; } + /// Namespaced journal key. + [JsonPropertyName("key")] + public string Key { get; set; } = string.Empty; - /// Exact window consumption in non-negative integer nano-AIU, encoded as a decimal string. - [RegularExpression("^[0-9]+$")] - [JsonPropertyName("creditsUsedNanoAiu")] - public string CreditsUsedNanoAiu { get; set; } = string.Empty; + /// JSON result to memoize. + [JsonPropertyName("resultJson")] + public JsonElement ResultJson { get; set; } + + /// Workflow run identifier. + [JsonPropertyName("runId")] + public string RunId { get; set; } = string.Empty; + + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } -/// Public, persistence-independent projection of an autopilot objective. +/// The session's authoritative model snapshot. Auto preference fields are configuration for the virtual `auto` model and do not change the selected model identifier. The context tier reflects `Session.getContextTier()`, restored from the session journal on resume. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class AutopilotObjectiveState +public sealed class CurrentModel { - /// Optional summary recorded when the objective completed. - [JsonPropertyName("completionSummary")] - public string? CompletionSummary { get; set; } + /// Auto preference currently claimed by an in-progress activation. Null means the activation is returning to provider-default routing. + [JsonPropertyName("activatingAutoTier")] + public AutoTier? ActivatingAutoTier { get; set; } - /// Exact lifetime AI-credit consumption in non-negative integer nano-AIU, encoded as a decimal string. - [RegularExpression("^[0-9]+$")] - [JsonPropertyName("creditCountNanoAiu")] - public string CreditCountNanoAiu { get; set; } = string.Empty; + /// Auto preference currently committed for the session. This can remain available while another model is selected so a later switch to `auto` can reuse it. + [JsonPropertyName("autoTier")] + public AutoTier? AutoTier { get; set; } - /// Current per-window consumption and optional cap, when a credit-tracking window is present. - [JsonPropertyName("creditLimit")] - public AutopilotObjectiveCreditLimit? CreditLimit { get; set; } + /// Context tier for models that support multiple context-window sizes. + [JsonPropertyName("contextTier")] + public ContextTier? ContextTier { get; set; } - /// Session-local objective identifier. - [JsonPropertyName("id")] - public long Id { get; set; } + /// Currently active model identifier. + [JsonPropertyName("modelId")] + public string? ModelId { get; set; } - /// User-provided objective text. - [JsonPropertyName("objective")] - public string Objective { get; set; } = string.Empty; + /// Latest unclaimed Auto preference waiting for a future user turn. Null means the pending request is returning to provider-default routing. + [JsonPropertyName("pendingAutoTier")] + public AutoTier? PendingAutoTier { get; set; } - /// Optional reason the objective is paused. - [JsonPropertyName("pauseReason")] - public string? PauseReason { get; set; } + /// Captured base model to restore when leaving plan mode. Omitted outside plan mode or when no plan override has captured a base model. Persistent agent model requirements apply to this model rather than the temporary plan model. + [JsonPropertyName("planBaseModelId")] + public string? PlanBaseModelId { get; set; } - /// Current normalized lifecycle status. - [JsonPropertyName("status")] - public AutopilotObjectiveStatus Status { get; set; } + /// Provider of planBaseModelId, when that saved selection is provider-qualified. + [JsonPropertyName("planBaseProviderId")] + public string? PlanBaseProviderId { get; set; } - /// Number of objective turns started. - [JsonPropertyName("turnCount")] - public long TurnCount { get; set; } -} + /// Provider selected for this model. Together with modelId, identifies a catalog entry even when providers serve the same model. Omitted for unattributed legacy selections. + [JsonPropertyName("providerId")] + public string? ProviderId { get; set; } -/// Canonical runtime state for the session's current autopilot objective. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class AutopilotObjectiveGetStateResult -{ - /// Current objective state, or `null` when the session has no objective. - [JsonPropertyName("state")] - public AutopilotObjectiveState? State { get; set; } + /// Reasoning effort level currently applied to the active model, when one is set. Reads `Session.getReasoningEffort()` synchronously after `getSelectedModel()` resolves so the two values are reported as a snapshot. + [JsonPropertyName("reasoningEffort")] + public string? ReasoningEffort { get; set; } } /// Identifies the target session. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionAutopilotObjectiveGetStateRequest +internal sealed class SessionModelGetCurrentRequest { /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Characters that, when typed in the composer, should trigger a `completions.request`. Empty when the session has no host-driven completions (e.g. local sessions, or a relay host that does not advertise `completionTriggerCharacters`). +/// RPC data type for ModelSwitchConfirmation operations. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class CompletionsGetTriggerCharactersResult +public sealed class ModelSwitchConfirmation { - /// Trigger characters advertised by the host (e.g. `["@", "#"]`). Empty disables host-driven completions for the session. - [JsonPropertyName("triggerCharacters")] - public IList TriggerCharacters { get => field ??= []; set; } -} + /// Current conversation token count before switching models. + [JsonPropertyName("currentTokens")] + public double CurrentTokens { get; set; } -/// Identifies the target session. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionCompletionsGetTriggerCharactersRequest -{ - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Target model token limit used by the compaction preflight. + [JsonPropertyName("targetLimit")] + public double TargetLimit { get; set; } + + /// Display name of the model that requires compaction confirmation. + [JsonPropertyName("targetModelDisplayName")] + public string TargetModelDisplayName { 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. +/// The model identifier active on the session after the switch. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class SessionCompletionItem +public sealed class ModelSwitchToResult { - /// Text spliced into the composer when the item is accepted. - [JsonPropertyName("insertText")] - public string InsertText { get; set; } = string.Empty; + /// Compaction confirmation projection when status is confirmation_required. + [JsonPropertyName("confirmation")] + public ModelSwitchConfirmation? Confirmation { 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; } + /// 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; } - /// Primary display label for the picker row. Falls back to `insertText` when absent. - [JsonPropertyName("label")] - public string? Label { get; set; } + /// Deprecation warnings associated with the selected model or options. + [JsonPropertyName("deprecationWarnings")] + public IList? DeprecationWarnings { get; set; } - /// End (exclusive) of the replacement range in `text`, in UTF-16 code units. - [JsonPropertyName("rangeEnd")] - public long? RangeEnd { get; set; } + /// User-facing outcome message for the model switch. + [JsonPropertyName("message")] + public string? Message { get; set; } - /// Start of the replacement range in `text`, in UTF-16 code units. - [JsonPropertyName("rangeStart")] - public long? RangeStart { get; set; } -} + /// Currently active model identifier after the switch. + [JsonPropertyName("modelId")] + public string? ModelId { get; set; } -/// Host-driven completion items for the current composer input. Empty when the host returns no items or does not support completions. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class CompletionsRequestResult -{ - /// Completion items in host-ranked order. - [JsonPropertyName("items")] - public IList Items { get => field ??= []; set; } -} + /// Authoritative model and Auto preference state after an immediate switch. For deferred switches this remains the current state until the queued change drains. + [JsonPropertyName("modelState")] + public CurrentModel? ModelState { get; set; } -/// Request host-driven completions for the current composer input. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class CompletionsRequestRequest -{ - /// Cursor offset within `text`, in UTF-16 code units. - [JsonPropertyName("offset")] - public long Offset { get; set; } + /// Persistence failure encountered after applying the model switch. + [JsonPropertyName("persistenceError")] + public string? PersistenceError { get; set; } - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Stable queue item identifier when this request was enqueued. Remains present if the item drains before the response is returned. + [JsonPropertyName("queueId")] + public string? QueueId { get; set; } - /// The full composed composer input. - [JsonPropertyName("text")] - public string Text { get; set; } = string.Empty; -} + /// Lifecycle result for the requested switch. + [JsonPropertyName("status")] + public string? Status { get; set; } -/// Instruction sources loaded for the session, in merge order. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class InstructionsGetSourcesResult -{ - /// Instruction sources for the session. - [JsonPropertyName("sources")] - public IList Sources { get => field ??= []; set; } + /// User-facing warning produced while applying the model switch. + [JsonPropertyName("warning")] + public string? Warning { get; set; } } -/// Identifies the target session. +/// Filesystem and environment context used to resolve model-picker settings. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionInstructionsGetSourcesRequest +public sealed class ModelPickerSettingsContext { - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; -} + /// Optional Copilot configuration directory containing persisted settings. + [JsonPropertyName("configDir")] + public string? ConfigDir { get; set; } -/// Identifies the target session. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionInstructionsReloadRequest -{ - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Environment variables consulted while resolving model-picker settings. + [JsonPropertyName("environment")] + public IDictionary Environment { get => field ??= new Dictionary(); set; } + + /// User home directory used when resolving persisted settings. + [JsonPropertyName("homeDirectory")] + public string HomeDirectory { get; set; } = string.Empty; } -/// Result of one customization reload component. +/// RPC data type for ModelPickerPersistence operations. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class CustomizationReloadOutcome +public sealed class ModelPickerPersistenceRequest { - /// Reason for a skipped component or description of a failure, when available. - [JsonPropertyName("detail")] - public string? Detail { get; set; } + /// Whether context tier was explicitly selected and should be persisted. + [JsonPropertyName("contextTierExplicit")] + public bool? ContextTierExplicit { get; set; } - /// Whether the component reloaded, was skipped, or failed. - [JsonPropertyName("status")] - public CustomizationReloadStatus Status { get; set; } + /// Whether reasoning effort was explicitly selected and should be persisted. + [JsonPropertyName("reasoningEffortExplicit")] + public bool? ReasoningEffortExplicit { get; set; } - /// Component whose reload was attempted or skipped. - [JsonPropertyName("subsystem")] - public CustomizationReloadSubsystem Subsystem { get; set; } + /// Filesystem and environment context used to resolve settings persistence. + [JsonPropertyName("settingsContext")] + public ModelPickerSettingsContext SettingsContext { get => field ??= new(); set; } } -/// Results of reloading discovered session customizations. Inspect outcomes for reloaded, skipped, or failed subsystems; a rejection may follow partial mutation. Changes to the model-facing prompt and tools apply on the next turn. +/// Target model identifier and optional reasoning effort, summary, capability overrides, and context tier. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class CustomizationsReloadResult +public sealed class ModelSwitchToRequest { - /// Errors from any component that could not be refreshed. - [JsonPropertyName("errors")] - public IList Errors { get => field ??= []; set; } + /// Optional Auto routing preference to stage atomically with selecting `auto`. Pass null to return to provider-default Auto routing. This field is rejected when `modelId` is not `auto`. + [JsonPropertyName("autoTier")] + public AutoTier? AutoTier { get; set; } - /// Outcome of each component in reload order; a skipped component was not configured or loaded. - [JsonPropertyName("outcomes")] - public IList Outcomes { get => field ??= []; set; } + /// 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; } - /// Warnings from skill discovery. - [JsonPropertyName("warnings")] - public IList Warnings { get => field ??= []; 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; } -/// Identifies the target session. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionCustomizationsReloadRequest -{ - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; -} + /// 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; } -/// Indicates whether fleet mode was successfully activated. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class FleetStartResult -{ - /// Whether fleet mode was successfully activated. - [JsonPropertyName("started")] - public bool Started { get; set; } -} + /// Override individual model capabilities resolved by the runtime. + [JsonPropertyName("modelCapabilities")] + public ModelCapabilitiesOverride? ModelCapabilities { get; set; } -/// Parameters for starting fleet orchestration: an optional user prompt combined with the fleet instructions, plus the send options forwarded to the resulting turn. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class FleetStartRequest -{ - /// Optional attachments (files, directories, selections, blobs, GitHub references) to include with the fleet request. - [JsonPropertyName("attachments")] - public IList? Attachments { get; set; } + /// Settings scope used when persisting the selected model. + [JsonPropertyName("modelChangeScope")] + public string? ModelChangeScope { get; set; } - /// If false, this request will not trigger a Premium Request Unit charge. User requests default to billable. - [JsonInclude] - [JsonPropertyName("billable")] - internal bool? Billable { get; set; } + /// Model id to switch to, as returned by `list`. Include providerId to select an exact catalog entry when providers share the id. Without providerId, a bare id retains incumbent selection behavior; a registry-qualified id (`provider/id`, e.g. `acme/claude-sonnet`) targets a BYOK model. + [JsonPropertyName("modelId")] + public required string ModelId { get; set; } - /// Optional user prompt to combine with fleet instructions. - [JsonPropertyName("prompt")] - public string? Prompt { get; set; } + /// Optional settings context and explicit-override flags used to persist a picker selection. + [JsonPropertyName("pickerPersistence")] + public ModelPickerPersistenceRequest? PickerPersistence { get; set; } - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Provider id from the selected list entry's provider reference. Selects this exact provider/model pair; an unavailable pair fails rather than using another provider. Omit for deterministic legacy bare-model selection. + [JsonPropertyName("providerId")] + public string? ProviderId { get; set; } - /// If true, await completion of the agentic loop for this fleet request before returning. Defaults to false. - [JsonPropertyName("wait")] - public bool? Wait { 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. Pass null to clear any session effort override and fall back to the model's default. When omitted, the session's current effort is kept. + [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; } + + /// 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; } } -/// Agents available to the session. +/// Target model identifier and optional reasoning effort, summary, capability overrides, and context tier. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class AgentList +internal sealed class ModelSwitchToRequestWithSession { - /// Available agents. - [JsonPropertyName("agents")] - public IList Agents { get => field ??= []; set; } + /// Optional Auto routing preference to stage atomically with selecting `auto`. Pass null to return to provider-default Auto routing. This field is rejected when `modelId` is not `auto`. + [JsonPropertyName("autoTier")] + public AutoTier? AutoTier { get; set; } + + /// 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 id to switch to, as returned by `list`. Include providerId to select an exact catalog entry when providers share the id. Without providerId, a bare id retains incumbent selection behavior; a registry-qualified id (`provider/id`, e.g. `acme/claude-sonnet`) targets a 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; } + + /// Provider id from the selected list entry's provider reference. Selects this exact provider/model pair; an unavailable pair fails rather than using another provider. Omit for deterministic legacy bare-model selection. + [JsonPropertyName("providerId")] + public string? ProviderId { 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. Pass null to clear any session effort override and fall back to the model's default. When omitted, the session's current effort is kept. + [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; } } -/// RPC data type for SessionAgentList operations. +/// Immediate acknowledgement and Auto preference snapshot after a switch request. This result never implies that a pending preference committed. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class SessionAgentListRequest +public sealed class ModelSwitchAutoTierResult { - /// 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; } + /// Auto preference currently claimed by an in-progress activation. Null means the activation is returning to provider-default routing. + [JsonPropertyName("activatingAutoTier")] + public AutoTier? ActivatingAutoTier { 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; } + /// Auto preference currently committed for the session. + [JsonPropertyName("effectiveAutoTier")] + public AutoTier? EffectiveAutoTier { get; set; } + + /// Latest unclaimed Auto preference waiting for a future user turn. + [JsonPropertyName("pendingAutoTier")] + public AutoTier? PendingAutoTier { get; set; } + + /// Immediate request status. `pending` means accepted but not committed. + [JsonPropertyName("status")] + public ModelSwitchAutoTierStatus Status { get; set; } + + /// Earlier unclaimed preference replaced by this request. This can be present with either status, including when selecting the effective preference cancels pending work. + [JsonPropertyName("supersededAutoTier")] + public AutoTier? SupersededAutoTier { get; set; } } -/// RPC data type for SessionAgentListRequestWithSession operations. +/// An Auto preference request for the session. This updates Auto configuration only; it does not change the selected model to `auto`. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionAgentListRequestWithSession +internal sealed class ModelSwitchAutoTierRequest { - /// 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; } - - /// 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; } + /// Auto preference to activate when a future user turn using the `auto` model safely mints a replacement model and token pair. Pass null to return to provider-default Auto routing. + [JsonPropertyName("autoTier")] + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public AutoTier? AutoTier { get; set; } /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; + + /// Origin to record on the effective `session.model_change` event. Defaults to `sdk` when omitted. + [JsonPropertyName("source")] + public ModelChangeSource? Source { get; set; } } -/// An in-memory authored prompt override for an available agent. +/// Managed, repository, and CLI model overrides to overlay onto the session at startup. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class AgentSetPromptRequest +internal sealed class ModelApplyStartupOverlayRequest { - /// Stable effective agent id. Plugin namespace separators are normalized. - [JsonPropertyName("id")] - public string Id { get; set; } = string.Empty; + /// Effective default Auto routing preference from user and managed settings. Applies only to fresh sessions and never replaces a per-session selection. + [JsonPropertyName("autoTier")] + public AutoTier? AutoTier { get; set; } - /// Replacement authored prompt. Empty text is valid. - [JsonPropertyName("prompt")] - public string Prompt { 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; } + + /// Context tier paired with the effective organization-managed model. Applies only when that concrete managed model is selected; it is ignored for Auto and for CLI, resume, or user overrides. + [JsonPropertyName("managedContextTier")] + public string? ManagedContextTier { get; set; } + + /// Reasoning effort paired with the effective organization-managed model. Applies only when that concrete managed model is selected; it is ignored for Auto and for CLI, resume, or user overrides. + [JsonPropertyName("managedReasoningEffort")] + public string? ManagedReasoningEffort { 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; } + + /// Auto routing preference selected by repository settings, when configured. Applied only when the overlay selects the Auto model; beside a concrete model it stays dormant. + [JsonPropertyName("repoAutoTier")] + public string? RepoAutoTier { 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; } + + /// Provider owning the repository-selected model. Omitted repository provider identity retains legacy bare-model behavior. + [JsonPropertyName("repoModelProviderId")] + public string? RepoModelProviderId { 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; } -/// The currently selected custom agent, or null when using the default agent. +/// RPC data type for SessionModelClearStartupSeed operations. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class AgentGetCurrentResult +internal sealed class SessionModelClearStartupSeedResult { - /// Currently selected custom agent, or null if using the default agent. - [JsonPropertyName("agent")] - public AgentInfo? Agent { get; set; } + /// Gets or sets the cleared value. + [JsonPropertyName("cleared")] + public bool Cleared { get; set; } } -/// Identifies the target session. +/// RPC data type for SessionModelClearStartupSeed operations. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionAgentGetCurrentRequest +internal sealed class SessionModelClearStartupSeedRequest { + /// Gets or sets the expectedModel value. + [JsonPropertyName("expectedModel")] + public string ExpectedModel { get; set; } = string.Empty; + + /// Provider captured with the startup seed; omission matches only an unqualified seed. + [JsonPropertyName("expectedProviderId")] + public string? ExpectedProviderId { get; set; } + /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// The newly selected custom agent. +/// The applied host allowlist and effective session model policy after intersection. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class AgentSelectResult +public sealed class ModelSetAllowedModelsResult { - /// The newly selected custom agent. - [JsonPropertyName("agent")] - public AgentInfo Agent { get => field ??= new(); set; } -} + /// Normalized host allowlist. Omitted when the host restriction was cleared, or when a relay client does not return the host policy. + [JsonPropertyName("allowedModels")] + public IList? AllowedModels { get; set; } -/// Name of the custom agent to select for subsequent turns. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class AgentSelectRequest -{ - /// Name of the custom agent to select. - [JsonPropertyName("name")] - public string Name { get; set; } = string.Empty; + /// Effective exact IDs or repository policy patterns after applying the host restriction. Omitted by relay clients that do not return the host policy. + [JsonPropertyName("effectiveAllowedModels")] + public IList? EffectiveAllowedModels { get; set; } - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Effective deterministic fallback model, when the policy defines one. + [JsonPropertyName("fallbackModel")] + public string? FallbackModel { get; set; } + + /// Selected session model after reconciling a now-disallowed concrete selection. + [JsonPropertyName("modelId")] + public string? ModelId { get; set; } } -/// Identifies the target session. +/// Host-supplied exact model selection IDs to allow for this running session. CAPI IDs are intersected with repository `.github/allowed_models.txt` policy; provider-qualified IDs remain exempt from repository-only policy but are restricted by this host list. Omit or pass null to clear the host restriction; an explicit empty or disjoint list is rejected. Validation and pre-selection fallback failures preserve the previous restriction. Failures after a fallback selection commits retain the new restriction and selected model; callers should inspect current session state after such an error. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionAgentDeselectRequest +internal sealed class ModelSetAllowedModelsRequest { + /// Exact model IDs to permit, or null to clear the host restriction. + [JsonPropertyName("allowedModels")] + public IList? AllowedModels { get; set; } + /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Custom agents available to the session after reloading definitions from disk. +/// 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(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class AgentReloadResult +public sealed class ModelSetReasoningEffortResult { - /// Reloaded custom agents. - [JsonPropertyName("agents")] - public IList Agents { get => field ??= []; set; } + /// Reasoning effort level recorded on the session after the update. + [JsonPropertyName("reasoningEffort")] + public string ReasoningEffort { get; set; } = string.Empty; } -/// Identifies the target session. +/// Reasoning effort level to apply to the currently selected model. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionAgentReloadRequest +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; } -/// Identifier assigned to the newly started background agent task. +/// Reasoning-summaries presentation policy for the session's current model. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class TasksStartAgentResult +public sealed class ModelGetReasoningSummariesResult { - /// Generated agent ID for the background task. - [JsonPropertyName("agentId")] - public string AgentId { get; set; } = string.Empty; -} + /// Whether the supplied `persistedShowReasoning` setting only restates the model default. This reports a redundancy, not a standing instruction to delete the setting: it stays true for as long as the setting and the model default agree, including for a user who later chose that value deliberately. It exists for a one-time migration of a legacy setting written before the model default made it redundant, so a host that acts on it owns the cleanup and must gate it behind its own persisted `done` marker, delete only a setting whose value still matches the one it read, and never repeat the deletion on a later launch. Under an off-by-default reasoning-summaries experiment an explicit `showReasoning: true` opt-in also reads as redundant on a summary-producing model, so an ungated host would keep deleting that opt-in and hide summaries again on every launch. + [JsonPropertyName("clearRedundantSetting")] + public bool ClearRedundantSetting { get; set; } -/// Agent type, prompt, name, and optional description and model override for the new task. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class TasksStartAgentRequest -{ - /// Type of agent to start (e.g., 'explore', 'task', 'general-purpose'). - [JsonPropertyName("agentType")] - public string AgentType { get; set; } = string.Empty; + /// Whether the session's reasoning-summaries experiment assignment has settled. False means this read does not wait for a pending assignment and reports the experiment-derived fields conservatively: `showHiddenHint` is false and `visibility` falls back to `persistedShowReasoning`, or is omitted when none was supplied. A host that wants the assigned answer re-reads until this is true. + [JsonPropertyName("experimentSettled")] + public bool ExperimentSettled { get; set; } - /// Short description of the task. - [JsonPropertyName("description")] - public string? Description { get; set; } + /// Whether the selected model shows reasoning summaries by default. + [JsonPropertyName("modelDefaultShowsSummaries")] + public bool ModelDefaultShowsSummaries { get; set; } - /// Optional model override. - [JsonPropertyName("model")] - public string? Model { get; set; } + /// False until the session has a model catalog and a selected model. This read never resolves the catalog itself, and the runtime emits no readiness notification, so repeating this call alone leaves `ready` false indefinitely on a session that has neither listed models nor run a turn. `session.model.list` is the prerequisite and the signal: it resolves and caches the catalog for the session, so a successful return means the next read is ready. The exception is a session that resolves models without a Copilot catalog, that is a BYOK registry or an alternate provider: `session.model.list` returns an empty list without caching a catalog, so `ready` stays false and this policy is unavailable there. While false, every model-policy field is false and `visibility` is omitted; `experimentSettled` is independent of readiness and still reports the assignment state. + [JsonPropertyName("ready")] + public bool Ready { get; set; } - /// Friendly, non-unique name used when displaying the agent. - [JsonPropertyName("name")] - public string Name { get; set; } = string.Empty; + /// Whether turns on this session produce reasoning summaries, honouring an explicit session reasoning-summary override. + [JsonPropertyName("sessionShowsSummaries")] + public bool SessionShowsSummaries { get; set; } - /// Task prompt for the agent. - [JsonPropertyName("prompt")] - public string Prompt { get; set; } = string.Empty; + /// Whether to show the hint that reasoning summaries are hidden. Always false while `experimentSettled` is false. + [JsonPropertyName("showHiddenHint")] + public bool ShowHiddenHint { get; set; } - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; -} + /// Whether the selected model emits reasoning headers. + [JsonPropertyName("showReasoningHeaders")] + public bool ShowReasoningHeaders { get; set; } -/// Tracked task union returned by task APIs, containing an agent, client, or shell task. -/// Polymorphic base type discriminated by type. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -[JsonPolymorphic( - TypeDiscriminatorPropertyName = "type", - UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] -[JsonDerivedType(typeof(TaskInfoAgent), "agent")] -[JsonDerivedType(typeof(TaskInfoClient), "client")] -[JsonDerivedType(typeof(TaskInfoShell), "shell")] -public partial class TaskInfo -{ - /// The type discriminator. - [JsonPropertyName("type")] - public virtual string Type { get; set; } = string.Empty; + /// Default reasoning visibility, and only meaningful once `ready` is true: false when the session produces no summaries, else the persisted setting, else the experiment's default. While `ready` is true it is omitted only when all three are unavailable, that is when the session produces summaries, no `persistedShowReasoning` was supplied, and `experimentSettled` is false. While `ready` is false there is no model policy to report and this field is always omitted, so an absent value alone does not imply a pending experiment. + [JsonPropertyName("visibility")] + public bool? Visibility { get; set; } } - -/// Tracked background agent task metadata, including IDs, status, timing, agent type, prompt, model, result, and latest response. -/// The agent variant of . +/// Host presentation state the reasoning-summaries policy depends on. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class TaskInfoAgent : TaskInfo +internal sealed class ModelGetReasoningSummariesRequest { - /// - [JsonIgnore] - public override string Type => "agent"; + /// The host's persisted `showReasoning` user setting, omitted when unset. + [JsonPropertyName("persistedShowReasoning")] + public bool? PersistedShowReasoning { get; set; } - /// ISO 8601 timestamp when the current active period began. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("activeStartedAt")] - public DateTimeOffset? ActiveStartedAt { get; set; } + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; - /// Accumulated active execution time in milliseconds. - [JsonConverter(typeof(MillisecondsTimeSpanConverter))] - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("activeTimeMs")] - public TimeSpan? ActiveTime { get; set; } + /// Whether the host is currently showing reasoning summaries. Defaults to false. + [JsonPropertyName("summariesVisible")] + public bool? SummariesVisible { get; set; } +} - /// Type of agent running this task. - [JsonPropertyName("agentType")] - public required string AgentType { get; set; } +/// Availability of a server-advertised routing preference. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public sealed class AutoTierStatus +{ + /// Whether the provider permits selecting this preference. + [JsonPropertyName("enabled")] + public bool Enabled { 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; } + /// Human-readable explanation of availability. + [JsonPropertyName("message")] + public string? Message { get; set; } - /// ISO 8601 timestamp when the task finished. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("completedAt")] - public DateTimeOffset? CompletedAt { get; set; } + /// Extensible machine-readable unavailability reason. + [JsonPropertyName("reason")] + public string? Reason { get; set; } +} - /// Short description of the task. +/// A server-advertised routing preference. Identifiers and execution types are extensible. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public sealed class AutoTierDescriptor +{ + /// Description displayed beside the preference. [JsonPropertyName("description")] - public required string Description { get; set; } + public string Description { get; set; } = string.Empty; - /// Friendly, non-unique name intended for display. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + /// Human-readable label, not a routing identifier. [JsonPropertyName("displayName")] - public string? DisplayName { get; set; } - - /// Error message when the task failed. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("error")] - public string? Error { get; set; } - - /// Whether task execution is synchronously awaited or managed in the background. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("executionMode")] - public TaskExecutionMode? ExecutionMode { get; set; } + public string DisplayName { get; set; } = string.Empty; - /// Unique task identifier. + /// Opaque routing identifier transmitted unchanged to the provider. [JsonPropertyName("id")] - public required string Id { get; set; } - - /// ISO 8601 timestamp when the agent entered idle state. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("idleSince")] - public DateTimeOffset? IdleSince { get; set; } - - /// Most recent response text from the agent. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("latestResponse")] - public string? LatestResponse { get; set; } - - /// Requested model override for the task when specified. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("model")] - public string? Model { get; set; } - - /// Most recent prompt delivered to the agent. Updated whenever the agent receives a follow-up message. - [JsonPropertyName("prompt")] - public required string Prompt { get; set; } - - /// Runtime model resolved for the task when available. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("resolvedModel")] - public string? ResolvedModel { get; set; } - - /// Result text from the task when available. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("result")] - public string? Result { get; set; } - - /// ISO 8601 timestamp when the task was started. - [JsonPropertyName("startedAt")] - public required DateTimeOffset StartedAt { get; set; } + public string Id { get; set; } = string.Empty; - /// Current lifecycle status of the task. + /// Current account-specific availability. [JsonPropertyName("status")] - public required TaskStatus Status { get; set; } + public AutoTierStatus Status { get => field ??= new(); set; } - /// Tool call ID associated with this agent task. - [JsonPropertyName("toolCallId")] - public required string ToolCallId { get; set; } + /// Execution kind; this client supports `auto` preferences on the Auto model. + [JsonPropertyName("type")] + public string Type { get; set; } = string.Empty; } -/// Public owner attribution for a client-owned task. Identifiers are opaque and never authorize requests. +/// Account-bound discovery metadata for the virtual `auto` model. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class TaskClientOwner +public sealed class AutoTierMetadata { - /// ISO 8601 timestamp when the bound join disconnected. - [JsonPropertyName("disconnectedAt")] - public DateTimeOffset? DisconnectedAt { get; set; } - - /// Display-only owner name. - [JsonPropertyName("displayName")] - public string? DisplayName { get; set; } - - /// Opaque identity of the currently or most recently bound session join. - [JsonPropertyName("joinId")] - public string JoinId { get; set; } = string.Empty; + /// Provider-default preference, used only when no explicit preference exists. + [JsonPropertyName("defaultTier")] + public string DefaultTier { get; set; } = string.Empty; - /// Class of the task owner. - [JsonPropertyName("kind")] - public TaskClientOwnerKind Kind { get; set; } + /// Provider that supplied this metadata, when the catalog is provider-attributed. + [JsonPropertyName("providerId")] + public string? ProviderId { get; set; } - /// Opaque session-scoped participant identity. - [JsonPropertyName("participantId")] - public string ParticipantId { get; set; } = string.Empty; + /// Routing preferences in the server's presentation order. + [JsonPropertyName("tiers")] + public IList Tiers { get => field ??= []; set; } +} - /// Whether this task's bound join is currently connected. - [JsonPropertyName("presence")] - public TaskClientOwnerPresence Presence { get; set; } +/// Cost-category metadata for a CAPI model. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public sealed class SessionModelPriceCategory +{ + /// CAPI model identifier. + [JsonPropertyName("id")] + public string Id { get; set; } = string.Empty; - /// Display-only owner source. - [JsonPropertyName("source")] - public string? Source { get; set; } + /// Cost category assigned to the model. + [JsonPropertyName("priceCategory")] + public ModelPickerPriceCategory PriceCategory { get; set; } } -/// Tracked client-owned task metadata. -/// The client variant of . +/// One model provider available to the session — the model analog of the account `ProviderDescriptor`. Opaque id/label/kind plus a stable ordering; central code never branches on kind. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class TaskInfoClient : TaskInfo +public sealed class ModelProviderDescriptor { - /// - [JsonIgnore] - public override string Type => "client"; - - /// ISO 8601 timestamp when the current active segment started. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("activeStartedAt")] - public DateTimeOffset? ActiveStartedAt { get; set; } - - /// Accumulated active execution time in milliseconds. - [JsonPropertyName("activeTimeMs")] - public required long ActiveTimeMs { get; set; } - - /// Whether the currently bound owner can receive a cancellation request. - [JsonPropertyName("canCancel")] - public required bool CanCancel { get; set; } - - /// Human-readable reason for terminal cancellation. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("cancellationReason")] - public string? CancellationReason { get; set; } - - /// Owner-scoped registration and reclaim key. - [JsonPropertyName("clientTaskId")] - public required string ClientTaskId { get; set; } - - /// ISO 8601 timestamp when the task reached a terminal status. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("completedAt")] - public DateTimeOffset? CompletedAt { get; set; } - - /// Task description. - [JsonPropertyName("description")] - public required string Description { get; set; } - - /// Optional task display name. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("displayName")] - public string? DisplayName { get; set; } - - /// Human-readable terminal failure message. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("error")] - public string? Error { get; set; } - - /// Optional owner-supplied terminal failure code. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("errorCode")] - public string? ErrorCode { get; set; } - - /// Execution mode, which is always background for client-owned tasks. - [JsonPropertyName("executionMode")] - public required TaskClientExecutionMode ExecutionMode { get; set; } - - /// Canonical runtime-generated task identifier. + /// Opaque, stable provider id, stamped onto every model this provider returns. [JsonPropertyName("id")] - public required string Id { get; set; } - - /// ISO 8601 timestamp when the connected owner entered idle status. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("idleSince")] - public DateTimeOffset? IdleSince { get; set; } - - /// ISO 8601 timestamp of the most recent orphan transition. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("orphanedAt")] - public DateTimeOffset? OrphanedAt { get; set; } - - /// Public attribution and presence for the task owner. - [JsonPropertyName("owner")] - public required TaskClientOwner Owner { get; set; } - - /// ISO 8601 timestamp of the most recent successful reclaim. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("reclaimedAt")] - public DateTimeOffset? ReclaimedAt { get; set; } - - /// Opaque successful terminal result supplied by the task owner. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("result")] - public JsonElement? Result { get; set; } - - /// Sequence number of the latest accepted owner update. - [JsonPropertyName("sequence")] - public required long Sequence { get; set; } + public string Id { get; set; } = string.Empty; - /// ISO 8601 timestamp when the task started. - [JsonPropertyName("startedAt")] - public required DateTimeOffset StartedAt { get; set; } + /// The neutral provider kind. + [JsonPropertyName("kind")] + public ModelProviderKind Kind { get; set; } - /// Client task lifecycle status. - [JsonPropertyName("status")] - public required TaskClientStatus Status { get; set; } + /// Human-readable menu label, owned by the runtime so every consumer renders identical text. + [JsonPropertyName("label")] + public string Label { get; set; } = string.Empty; - /// ISO 8601 timestamp of the latest accepted lifecycle change. - [JsonPropertyName("updatedAt")] - public required DateTimeOffset UpdatedAt { get; set; } + /// Stable ordering key for presenting providers in a deterministic sequence. + [JsonPropertyName("ordering")] + public long Ordering { get; set; } } -/// Tracked shell task metadata, including ID, command, status, timing, attachment/execution mode, log path, and PID. -/// The shell variant of . +/// The list of models available to this session. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class TaskInfoShell : TaskInfo +public sealed class SessionModelList { - /// - [JsonIgnore] - public override string Type => "shell"; - - /// Whether the shell runs inside a managed PTY session or as an independent background process. - [JsonPropertyName("attachmentMode")] - public required TaskShellInfoAttachmentMode AttachmentMode { get; set; } - - /// Whether this shell task can be promoted to background mode. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("canPromoteToBackground")] - public bool? CanPromoteToBackground { get; set; } - - /// Command being executed. - [JsonPropertyName("command")] - public required string Command { get; set; } - - /// ISO 8601 timestamp when the task finished. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("completedAt")] - public DateTimeOffset? CompletedAt { get; set; } - - /// Short description of the task. - [JsonPropertyName("description")] - public required string Description { get; set; } - - /// Whether task execution is synchronously awaited or managed in the background. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("executionMode")] - public TaskExecutionMode? ExecutionMode { get; set; } + /// Ordered Auto routing preferences discovered for this session's account. + [JsonPropertyName("auto")] + public AutoTierMetadata? Auto { get; set; } - /// Unique task identifier. - [JsonPropertyName("id")] - public required string Id { get; set; } + /// 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; } - /// Path to the detached shell log, when available. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("logPath")] - public string? LogPath { get; set; } + /// Cost categories for the full CAPI catalog, including picker-disabled models that Auto may select. Metadata only; entries absent from `list` are not manually selectable. + [JsonPropertyName("modelPriceCategories")] + public IList? ModelPriceCategories { get; set; } - /// Process ID when available. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("pid")] - public long? Pid { get; set; } + /// The model providers available to this session, in ordering order, resolved from the account roster; empty when no provider is entitled (logged out / seatless). Each model in `list` carries its own provider reference; this roster gives the deterministic provider sequence and lets a consumer group by provider without deriving ordering from the model list. Central code never branches on a provider kind. + [JsonPropertyName("providers")] + public IList? Providers { get; set; } - /// ISO 8601 timestamp when the task was started. - [JsonPropertyName("startedAt")] - public required DateTimeOffset StartedAt { get; set; } + /// Per-quota snapshots returned alongside the model list, keyed by quota type. + [JsonPropertyName("quotaSnapshots")] + public IDictionary? QuotaSnapshots { get; set; } +} - /// Current lifecycle status of the task. - [JsonPropertyName("status")] - public required TaskStatus Status { get; set; } +/// RPC data type for SessionModelList operations. +[Experimental(global::GitHub.Copilot.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; } } -/// Background tasks currently tracked by the session. +/// RPC data type for SessionModelListRequestWithSession operations. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class TaskList +internal sealed class SessionModelListRequestWithSession { - /// Currently tracked tasks. - [JsonPropertyName("tasks")] - public IList Tasks { get => field ??= []; 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; } } /// Identifies the target session. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionTasksListRequest +internal sealed class SessionModeGetRequest { /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Tracked client-owned task metadata. +/// Outcome of a session mode change, including any model switch it triggered and follow-up the host must perform. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class TaskClientInfo +public sealed class ModeSetResult { - /// ISO 8601 timestamp when the current active segment started. - [JsonPropertyName("activeStartedAt")] - public DateTimeOffset? ActiveStartedAt { get; set; } + /// Whether the host should arm an interactive continuation after the mode change. + [JsonPropertyName("armInteractiveContinuation")] + public bool? ArmInteractiveContinuation { get; set; } - /// Accumulated active execution time in milliseconds. - [JsonPropertyName("activeTimeMs")] - public long ActiveTimeMs { get; set; } + /// Compaction confirmation required before the mode change can complete. + [JsonPropertyName("confirmation")] + public ModelSwitchConfirmation? Confirmation { get; set; } - /// Whether the currently bound owner can receive a cancellation request. - [JsonPropertyName("canCancel")] - public bool CanCancel { get; set; } + /// Whether the host must defer implementing the requested mode change. + [JsonPropertyName("deferImplementation")] + public bool? DeferImplementation { get; set; } - /// Human-readable reason for terminal cancellation. - [JsonPropertyName("cancellationReason")] - public string? CancellationReason { get; set; } + /// Deprecation warnings associated with the model selected by the mode change. + [JsonPropertyName("deprecationWarnings")] + public IList? DeprecationWarnings { get; set; } - /// Owner-scoped registration and reclaim key. - [JsonPropertyName("clientTaskId")] - public string ClientTaskId { get; set; } = string.Empty; + /// User-facing outcome message for the model switch triggered by the mode change. + [JsonPropertyName("message")] + public string? Message { get; set; } - /// ISO 8601 timestamp when the task reached a terminal status. - [JsonPropertyName("completedAt")] - public DateTimeOffset? CompletedAt { get; set; } + /// Whether the requested mode was applied to the session. False only when an 'expectedMode' precondition did not hold, in which case any model change reported alongside it was still applied. + [JsonPropertyName("modeApplied")] + public bool? ModeApplied { get; set; } - /// Task description. - [JsonPropertyName("description")] - public string Description { get; set; } = string.Empty; + /// Whether applying the mode changed the active model. + [JsonPropertyName("modelChanged")] + public bool ModelChanged { get; set; } - /// Optional task display name. - [JsonPropertyName("displayName")] - public string? DisplayName { get; set; } + /// Lifecycle status of the requested mode change. + [JsonPropertyName("status")] + public string Status { get; set; } = string.Empty; - /// Human-readable terminal failure message. - [JsonPropertyName("error")] - public string? Error { get; set; } + /// User-facing warning produced while applying the mode change. + [JsonPropertyName("warning")] + public string? Warning { get; set; } +} - /// Optional owner-supplied terminal failure code. - [JsonPropertyName("errorCode")] - public string? ErrorCode { get; set; } +/// Agent interaction mode to apply to the session. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class ModeSetRequest +{ + /// Explicit response to a model-switch compaction preflight. + [JsonPropertyName("compactionDecision")] + public string? CompactionDecision { get; set; } - /// Execution mode, which is always background for client-owned tasks. - [JsonPropertyName("executionMode")] - public TaskClientExecutionMode ExecutionMode { get; set; } + /// Mode the session must currently be in for the change to apply. When set and the session is in a different mode the request is a no-op and reports status 'unchanged'. + [JsonPropertyName("expectedMode")] + public SessionMode? ExpectedMode { get; set; } - /// Canonical runtime-generated task identifier. - [JsonPropertyName("id")] - public string Id { get; set; } = string.Empty; + /// Session whose plan-mode base state should be inherited. + [JsonPropertyName("inheritPlanBaseFromSessionId")] + public string? InheritPlanBaseFromSessionId { get; set; } - /// ISO 8601 timestamp when the connected owner entered idle status. - [JsonPropertyName("idleSince")] - public DateTimeOffset? IdleSince { get; set; } + /// The session mode the agent is operating in. + [JsonPropertyName("mode")] + public SessionMode Mode { get; set; } - /// ISO 8601 timestamp of the most recent orphan transition. - [JsonPropertyName("orphanedAt")] - public DateTimeOffset? OrphanedAt { get; set; } + /// Whether the selected plan model should be persisted. + [JsonPropertyName("persistPlanSelection")] + public bool? PersistPlanSelection { get; set; } - /// Public attribution and presence for the task owner. - [JsonPropertyName("owner")] - public TaskClientOwner Owner { get => field ??= new(); set; } + /// Settings context used when persisting the selected plan model. + [JsonPropertyName("pickerSettingsContext")] + public ModelPickerSettingsContext? PickerSettingsContext { get; set; } - /// ISO 8601 timestamp of the most recent successful reclaim. - [JsonPropertyName("reclaimedAt")] - public DateTimeOffset? ReclaimedAt { get; set; } + /// Context tier to use with the dedicated plan model. + [JsonPropertyName("planContextTier")] + public string? PlanContextTier { get; set; } - /// Opaque successful terminal result supplied by the task owner. - [JsonPropertyName("result")] - public JsonElement? Result { get; set; } + /// Action to perform when leaving plan mode. + [JsonPropertyName("planExitAction")] + public string? PlanExitAction { get; set; } - /// Sequence number of the latest accepted owner update. - [JsonPropertyName("sequence")] - public long Sequence { get; set; } + /// Dedicated model to use in plan mode, when configured. + [JsonPropertyName("planModel")] + public string? PlanModel { get; set; } - /// ISO 8601 timestamp when the task started. - [JsonPropertyName("startedAt")] - public DateTimeOffset StartedAt { get; set; } + /// Whether a dedicated plan model is configured. + [JsonPropertyName("planModelConfigured")] + public bool? PlanModelConfigured { get; set; } - /// Client task lifecycle status. - [JsonPropertyName("status")] - public TaskClientStatus Status { get; set; } + /// Provider owning planModel. Omit for legacy bare-model selection. + [JsonPropertyName("planModelProviderId")] + public string? PlanModelProviderId { get; set; } - /// Task kind. - [JsonPropertyName("type")] - public TaskClientType Type { get; set; } + /// Reasoning effort to use with the dedicated plan model. + [JsonPropertyName("planReasoningEffort")] + public string? PlanReasoningEffort { get; set; } - /// ISO 8601 timestamp of the latest accepted lifecycle change. - [JsonPropertyName("updatedAt")] - public DateTimeOffset UpdatedAt { 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; } -/// Result of registering or reclaiming a client-owned task. +/// The session's friendly name, or null when not yet set. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class TasksRegisterResult +public sealed class NameGetResult { - /// True only when this invocation created a new task. - [JsonPropertyName("created")] - public bool Created { get; set; } - - /// True only when this invocation reclaimed an orphaned task. - [JsonPropertyName("reclaimed")] - public bool Reclaimed { get; set; } - - /// Authoritative registered or reclaimed task. - [JsonPropertyName("task")] - public TaskClientInfo Task { get => field ??= new(); set; } + /// The session name (user-set or auto-generated), or null if not yet set. + [JsonPropertyName("name")] + public string? Name { get; set; } } -/// Registers or reclaims a client-owned task. +/// Identifies the target session. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class TasksRegisterRequest +internal sealed class SessionNameGetRequest { - /// Whether the owner supports runtime cancellation requests. - [JsonPropertyName("cancellable")] - public bool Cancellable { get; set; } - - /// Owner-scoped idempotency key used for registration and reclaim. - [JsonPropertyName("clientTaskId")] - public string ClientTaskId { get; set; } = string.Empty; - - /// Human-readable description of the external work. - [JsonPropertyName("description")] - public string Description { get; set; } = string.Empty; - - /// Optional short display name for the external work. - [JsonPropertyName("displayName")] - public string? DisplayName { get; set; } + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} - /// Expected current sequence for idempotent registration or orphan reclaim. - [JsonPropertyName("expectedSequence")] - public long? ExpectedSequence { get; set; } +/// New friendly name to apply to the session. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class NameSetRequest +{ + /// 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; - - /// Task kind. - [JsonPropertyName("type")] - public TaskClientType Type { get; set; } } -/// Result of publishing a client-owned task update. +/// Indicates whether the auto-generated summary was applied as the session's name. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class TasksUpdateResult +public sealed class NameSetAutoResult { - /// Whether this invocation changed task state. + /// 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; } - - /// Whether this invocation repeated the latest accepted update. - [JsonPropertyName("duplicate")] - public bool Duplicate { get; set; } - - /// Authoritative task after processing the update. - [JsonPropertyName("task")] - public TaskClientInfo Task { get => field ??= new(); set; } } -/// Progress or terminal update for a client-owned task. -/// Polymorphic base type discriminated by kind. +/// Auto-generated session summary to apply as the session's name when no user-set name exists. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -[JsonPolymorphic( - TypeDiscriminatorPropertyName = "kind", - UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] -[JsonDerivedType(typeof(TaskClientUpdateProgress), "progress")] -[JsonDerivedType(typeof(TaskClientUpdateCompleted), "completed")] -[JsonDerivedType(typeof(TaskClientUpdateFailed), "failed")] -[JsonDerivedType(typeof(TaskClientUpdateCancelled), "cancelled")] -public partial class TaskClientUpdate +internal sealed class NameSetAutoRequest { - /// 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; + /// Auto-generated session summary. Empty/whitespace-only values are ignored; values are trimmed before persisting. + [JsonPropertyName("summary")] + public string Summary { get; set; } = string.Empty; +} -/// Publishes nonterminal progress for a running or idle client task. -/// The progress variant of . +/// Existence, contents, and resolved path of the session plan file. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class TaskClientUpdateProgress : TaskClientUpdate +public sealed class PlanReadResult { - /// - [JsonIgnore] - public override string Kind => "progress"; - - /// Optional progress message appended to recent activity when nonempty. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("message")] - public string? Message { get; set; } - - /// Optional completion percentage; null clears the current percentage. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("percentage")] - public double? Percentage { get; set; } + /// The content of the plan file, or null if it does not exist. + [JsonPropertyName("content")] + public string? Content { get; set; } - /// Optional progress phase; null clears the current phase. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("phase")] - public string? Phase { get; set; } + /// Whether the plan file exists in the workspace. + [JsonPropertyName("exists")] + public bool Exists { get; set; } - /// Optional active status transition. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("status")] - public TaskClientActiveStatus? Status { get; set; } + /// Absolute file path of the plan file, or null if workspace is not enabled. + [JsonPropertyName("path")] + public string? Path { get; set; } } -/// Reports successful terminal completion. -/// The completed variant of . +/// Identifies the target session. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class TaskClientUpdateCompleted : TaskClientUpdate +internal sealed class SessionPlanReadRequest { - /// - [JsonIgnore] - public override string Kind => "completed"; + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} - /// Optional final progress message. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("message")] - public string? Message { get; set; } +/// Replacement contents to write to the session plan file. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class PlanUpdateRequest +{ + /// The new content for the plan file. + [JsonPropertyName("content")] + public string Content { get; set; } = string.Empty; - /// Optional opaque successful terminal result. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("result")] - public JsonElement? Result { get; set; } + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } -/// Reports terminal failure. -/// The failed variant of . +/// Identifies the target session. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class TaskClientUpdateFailed : TaskClientUpdate +internal sealed class SessionPlanDeleteRequest { - /// - [JsonIgnore] - public override string Kind => "failed"; + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} - /// Optional owner-supplied terminal failure code. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("code")] - public string? Code { get; set; } - - /// Human-readable terminal failure message. - [JsonPropertyName("error")] - public required string Error { get; set; } - - /// Optional final progress message. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("message")] - public string? Message { get; set; } -} - -/// Reports terminal cancellation after external work stopped. -/// The cancelled variant of . +/// 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(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class TaskClientUpdateCancelled : TaskClientUpdate +public sealed class PlanSqlTodosRow { - /// - [JsonIgnore] - public override string Kind => "cancelled"; - - /// Optional final progress message. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("message")] - public string? Message { 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; } - /// Optional human-readable cancellation reason. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("reason")] - public string? Reason { get; set; } -} + /// Todo description. + [JsonPropertyName("description")] + public string? Description { get; set; } -/// Updates a client-owned task. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class TasksUpdateRequest -{ - /// Canonical runtime-generated task identifier. + /// Todo identifier. [JsonPropertyName("id")] - public string Id { get; set; } = string.Empty; - - /// Owner update sequence to apply. - [JsonPropertyName("sequence")] - public long Sequence { get; set; } + 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; } - /// Progress or terminal update payload. - [JsonPropertyName("update")] - public TaskClientUpdate Update { get => field ??= new(); set; } + /// Todo title. + [JsonPropertyName("title")] + public string? Title { get; 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. +/// Todo rows read from the session SQL database. Empty when no session database is available. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class TasksRefreshResult +public sealed class 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. + [JsonPropertyName("rows")] + public IList Rows { get => field ??= []; set; } } /// Identifies the target session. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionTasksRefreshRequest +internal sealed class SessionPlanReadSqlTodosRequest { /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Wait until all in-flight background tasks (agents + shells) and any follow-up turns scheduled by their completions have settled. Returns when the runtime is fully drained or after an internal timeout (default 10 minutes; configurable via COPILOT_TASK_WAIT_TIMEOUT_SECONDS). +/// A single dependency edge read from the session SQL `todo_deps` table, indicating that one todo must complete before another. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class TasksWaitForPendingResult +public sealed class PlanSqlTodoDependency +{ + /// ID of the todo it depends on. + [JsonPropertyName("dependsOn")] + public string DependsOn { get; set; } = string.Empty; + + /// ID of the todo that has the dependency. + [JsonPropertyName("todoId")] + public string TodoId { get; set; } = string.Empty; +} + +/// Todo rows + dependency edges read from the session SQL database. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public sealed class PlanReadSqlTodosWithDependenciesResult { + /// Edges from the session SQL todo_deps table. Empty when no database, no todo_deps table, or the SELECT failed. Read independently from `rows`, so a broken todo_deps table does not affect the rows result and vice versa. + [JsonPropertyName("dependencies")] + public IList Dependencies { get => field ??= []; set; } + + /// Rows from the session SQL todos table, ordered by creation time 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; } } /// Identifies the target session. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionTasksWaitForPendingRequest +internal sealed class SessionPlanReadSqlTodosWithDependenciesRequest { /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Progress information for the task, discriminated by type. Returns null when no task with this ID is currently tracked. -/// Polymorphic base type discriminated by type. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -[JsonPolymorphic( - TypeDiscriminatorPropertyName = "type", - UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] -[JsonDerivedType(typeof(TaskProgressAgent), "agent")] -[JsonDerivedType(typeof(TaskProgressClient), "client")] -[JsonDerivedType(typeof(TaskProgressShell), "shell")] -public partial class TaskProgress +/// RPC data type for WorkspacesGetWorkspaceResultWorkspace operations. +public sealed class WorkspacesGetWorkspaceResultWorkspace { - /// The type discriminator. - [JsonPropertyName("type")] - public virtual string Type { 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; } -/// Timestamped display line for task progress output or recent agent activity. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class TaskProgressLine -{ - /// Display message, e.g., "▸ bash", "✓ edit src/foo.ts". - [JsonPropertyName("message")] - public string Message { get; set; } = string.Empty; + /// Name of the client that created the workspace. + [JsonPropertyName("client_name")] + public string? ClientName { get; set; } - /// ISO 8601 timestamp when this event occurred. - [JsonPropertyName("timestamp")] - public DateTimeOffset Timestamp { get; set; } -} + /// Timestamp when the workspace was created. + [JsonPropertyName("created_at")] + public DateTimeOffset? CreatedAt { get; set; } -/// Progress snapshot for an agent task, with recent activity lines and optional latest intent. -/// The agent variant of . -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class TaskProgressAgent : TaskProgress -{ - /// - [JsonIgnore] - public override string Type => "agent"; + /// Current working directory associated with the workspace. + [JsonPropertyName("cwd")] + public string? Cwd { get; set; } - /// The most recent intent reported by the agent. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("latestIntent")] - public string? LatestIntent { get; set; } + /// Git repository root associated with the workspace. + [JsonPropertyName("git_root")] + public string? GitRoot { get; set; } - /// Recent tool execution events converted to display lines. - [JsonPropertyName("recentActivity")] - public required IList RecentActivity { get; set; } -} + /// Allowed values for the `WorkspacesWorkspaceDetailsHostType` enumeration. + [JsonPropertyName("host_type")] + public WorkspacesWorkspaceDetailsHostType? HostType { get; set; } -/// Generic progress for a client-owned task. -/// The client variant of . -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class TaskProgressClient : TaskProgress -{ - /// - [JsonIgnore] - public override string Type => "client"; + /// 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 nonempty progress message. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("lastMessage")] - public string? LastMessage { get; set; } + /// Most recent Mission Control event identifier observed for the workspace. + [JsonPropertyName("mc_last_event_id")] + public string? McLastEventId { get; set; } - /// Current completion percentage from zero through one hundred. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("percentage")] - public double? Percentage { get; set; } + /// Mission Control session identifier associated with the workspace. + [JsonPropertyName("mc_session_id")] + public string? McSessionId { get; set; } - /// Current owner-defined progress phase. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("phase")] - public string? Phase { get; set; } + /// Mission Control task identifier associated with the workspace. + [JsonPropertyName("mc_task_id")] + public string? McTaskId { get; set; } - /// Recent server-timestamped progress messages. - [JsonPropertyName("recentActivity")] - public required IList RecentActivity { get; set; } + /// Workspace display name. + [JsonPropertyName("name")] + public string? Name { get; set; } - /// Sequence number of the latest accepted owner update. - [JsonPropertyName("sequence")] - public required long Sequence { get; set; } + /// Whether the workspace session can be steered remotely. + [JsonPropertyName("remote_steerable")] + public bool? RemoteSteerable { get; set; } - /// Current client task lifecycle status. - [JsonPropertyName("status")] - public required TaskClientStatus Status { get; set; } + /// Repository identifier associated with the workspace. + [JsonPropertyName("repository")] + public string? Repository { get; set; } - /// ISO 8601 timestamp of the latest accepted lifecycle change. - [JsonPropertyName("updatedAt")] - public required DateTimeOffset UpdatedAt { 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; } } -/// Progress snapshot for a shell task, with recent stdout/stderr output and optional process ID. -/// The shell variant of . +/// Current workspace metadata for the session, including its absolute filesystem path when available. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class TaskProgressShell : TaskProgress +public sealed class WorkspacesGetWorkspaceResult { - /// - [JsonIgnore] - public override string Type => "shell"; - - /// Process ID when available. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("pid")] - public long? Pid { get; set; } + /// Absolute filesystem path to the workspace directory. Omitted when the session has no workspace (e.g. remote sessions). + [JsonPropertyName("path")] + public string? Path { get; set; } - /// Recent stdout/stderr lines from the running shell command. - [JsonPropertyName("recentOutput")] - public required string RecentOutput { get; set; } + /// Current workspace metadata, or null if not available. + [JsonPropertyName("workspace")] + public WorkspacesGetWorkspaceResultWorkspace? Workspace { get; set; } } -/// Progress information for the task, or null when no task with that ID is tracked. +/// Identifies the target session. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class TasksGetProgressResult +internal sealed class SessionWorkspacesGetWorkspaceRequest { - /// Progress information for the task, discriminated by type. Returns null when no task with this ID is currently tracked. - [JsonPropertyName("progress")] - public TaskProgress? Progress { get; set; } + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } -/// Identifier of the background task to fetch progress for. +/// Workspace metadata fields to update. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class TasksGetProgressRequest +internal sealed class WorkspacesUpdateMetadataRequest { - /// Task identifier (agent ID or shell ID). - [JsonPropertyName("id")] - public string Id { get; set; } = string.Empty; + /// Opaque workspace context supplied by the session host. + [JsonPropertyName("context")] + public JsonElement? Context { 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; } -/// The first sync-waiting task that can currently be promoted to background mode. +/// Optional session context used when creating a local workspace. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class TasksGetCurrentPromotableResult +internal sealed class WorkspacesEnsureRequest { - /// 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; } -} + /// Opaque workspace context supplied by the session host. + [JsonPropertyName("context")] + public JsonElement? Context { get; set; } -/// Identifies the target session. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionTasksGetCurrentPromotableRequest -{ /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Indicates whether the task was successfully promoted to background mode. +/// Relative paths of files stored in the session workspace files directory. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class TasksPromoteToBackgroundResult +public sealed class WorkspacesListFilesResult { - /// Whether the task was successfully promoted to background mode. - [JsonPropertyName("promoted")] - public bool Promoted { get; set; } + /// Slash-separated relative file paths in the workspace files directory. + [JsonPropertyName("files")] + public IList Files { get => field ??= []; set; } } -/// Identifier of the task to promote to background mode. +/// Identifies the target session. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class TasksPromoteToBackgroundRequest +internal sealed class SessionWorkspacesListFilesRequest { - /// Task identifier. - [JsonPropertyName("id")] - public string Id { 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. +/// Contents of the requested workspace file as a UTF-8 string. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class TasksPromoteCurrentToBackgroundResult +public sealed class WorkspacesReadFileResult { - /// 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; } + /// File content as a UTF-8 string. + [JsonPropertyName("content")] + public string Content { get; set; } = string.Empty; } -/// Identifies the target session. +/// Relative path of the workspace file to read. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionTasksPromoteCurrentToBackgroundRequest +internal sealed class WorkspacesReadFileRequest { + /// Slash-separated 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; } -/// Indicates whether the background task was successfully cancelled. +/// Relative path and UTF-8 content for the workspace file to create or overwrite. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class TasksCancelResult +internal sealed class WorkspacesCreateFileRequest { - /// Whether the task was successfully cancelled. - [JsonPropertyName("cancelled")] - public bool Cancelled { get; set; } -} + /// File content to write as a UTF-8 string. + [JsonPropertyName("content")] + public string Content { get; set; } = string.Empty; -/// Identifier of the background task to cancel. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class TasksCancelRequest -{ - /// Task identifier. - [JsonPropertyName("id")] - public string Id { get; set; } = string.Empty; + /// Slash-separated 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; } -/// Indicates whether the task was removed. False when the task does not exist or is still running/idle. +/// Filesystem metadata for a path in the session workspace files directory. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class TasksRemoveResult +public sealed class WorkspacesStatFileResult { - /// 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; } + /// Creation time in Unix epoch milliseconds. + [JsonPropertyName("birthtimeMs")] + public double BirthtimeMs { get; set; } + + /// Whether the path identifies a directory. + [JsonPropertyName("isDirectory")] + public bool IsDirectory { get; set; } + + /// Whether the path identifies a regular file. + [JsonPropertyName("isFile")] + public bool IsFile { get; set; } + + /// Last modification time in Unix epoch milliseconds. + [JsonPropertyName("mtimeMs")] + public double MtimeMs { get; set; } + + /// Size in bytes. + [JsonPropertyName("size")] + public double Size { get; set; } } -/// Identifier of the completed or cancelled task to remove from tracking. +/// Relative path of the workspace file or directory to inspect. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class TasksRemoveRequest +internal sealed class WorkspacesStatFileRequest { - /// Task identifier. - [JsonPropertyName("id")] - public string Id { get; set; } = string.Empty; + /// Slash-separated 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; } -/// Indicates whether the message was delivered, with an error message when delivery failed. +/// Directory to create within the session workspace files directory. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class TasksSendMessageResult +internal sealed class WorkspacesCreateDirectoryRequest { - /// Error message if delivery failed. - [JsonPropertyName("error")] - public string? Error { get; set; } + /// Slash-separated relative path within the workspace files directory. + [JsonPropertyName("path")] + public string Path { get; set; } = string.Empty; - /// Whether the message was successfully delivered or steered. - [JsonPropertyName("sent")] - public bool Sent { get; set; } + /// Whether to create missing parent directories. Defaults to false. + [JsonPropertyName("recursive")] + public bool? Recursive { get; set; } + + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } -/// Identifier of the target agent task, message content, and optional sender agent ID. +/// File or directory to remove from the session workspace files directory. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class TasksSendMessageRequest +internal sealed class WorkspacesRemovePathRequest { - /// Agent ID of the sender, if sent on behalf of another agent. - [JsonPropertyName("fromAgentId")] - public string? FromAgentId { get; set; } + /// Whether a missing path should be treated as success. Defaults to false. + [JsonPropertyName("force")] + public bool? Force { get; set; } - /// Agent task identifier. - [JsonPropertyName("id")] - public string Id { get; set; } = string.Empty; + /// Slash-separated relative path within the workspace files directory. + [JsonPropertyName("path")] + public string Path { get; set; } = string.Empty; - /// Message content to send to the agent. - [JsonPropertyName("message")] - public string Message { get; set; } = string.Empty; + /// Whether to remove directory contents recursively. Defaults to false. + [JsonPropertyName("recursive")] + public bool? Recursive { get; set; } /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Skill metadata available to a session, with name, description, source, enabled/invocable state, path, plugin, and argument hint. +/// Source and destination paths for a rename within the session workspace files directory. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class Skill +internal sealed class WorkspacesRenamePathRequest { - /// 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; } - - /// 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; } + /// Slash-separated destination path relative to the workspace files directory. + [JsonPropertyName("destination")] + public string Destination { get; set; } = string.Empty; - /// Unique identifier for the skill. - [JsonPropertyName("name")] - public string Name { get; set; } = string.Empty; + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; - /// Absolute path to the skill file. - [JsonPropertyName("path")] - public string? Path { get; set; } + /// Slash-separated source path relative to the workspace files directory. + [JsonPropertyName("source")] + public string Source { get; set; } = string.Empty; +} - /// Name of the plugin that provides the skill, when source is 'plugin'. - [JsonPropertyName("pluginName")] - public string? PluginName { get; set; } +/// Workspace checkpoint metadata with assigned number, human-readable title, and checkpoint filename. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public sealed class WorkspacesCheckpoints +{ + /// Filename of the checkpoint within the workspace checkpoints directory. + [JsonPropertyName("filename")] + public string Filename { get; set; } = string.Empty; - /// Source location type (e.g., project, personal-copilot, plugin, builtin). - [JsonPropertyName("source")] - public SkillSource Source { get; set; } + /// Checkpoint number assigned by the workspace manager. + [JsonPropertyName("number")] + public long Number { get; set; } - /// Whether the skill can be invoked by the user as a slash command. - [JsonPropertyName("userInvocable")] - public bool UserInvocable { get; set; } + /// Human-readable checkpoint title. + [JsonPropertyName("title")] + public string Title { get; set; } = string.Empty; } -/// Skills available to the session, with their enabled state. +/// Workspace checkpoints in chronological order; empty when the workspace is not enabled. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class SkillList +public sealed class WorkspacesListCheckpointsResult { - /// Available skills. - [JsonPropertyName("skills")] - public IList Skills { get => field ??= []; set; } + /// Workspace checkpoints in chronological order. Empty when workspace is not enabled. + [JsonPropertyName("checkpoints")] + public IList Checkpoints { get => field ??= []; set; } } /// Identifies the target session. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionSkillsListRequest +internal sealed class SessionWorkspacesListCheckpointsRequest { /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Skill invocation record with name, path, content, allowed tools, and turn number. +/// Checkpoint content as a UTF-8 string, or null when the checkpoint or workspace is missing. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class SkillsInvokedSkill +public sealed class WorkspacesReadCheckpointResult { - /// 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. + /// Checkpoint content as a UTF-8 string, or null when the checkpoint or workspace is missing. [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; } + public string? Content { 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; +/// Checkpoint number to read. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class WorkspacesReadCheckpointRequest +{ + /// Checkpoint number to read. + [JsonPropertyName("number")] + public long Number { get; set; } - /// 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; + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } -/// Skills invoked during this session, ordered by invocation time (most recent last). +/// Persisted summary metadata and refreshed workspace metadata. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class SkillsGetInvokedResult +public sealed class WorkspacesAddSummaryResult { - /// Skills invoked during this session, ordered by invocation time (most recent last). - [JsonPropertyName("skills")] - public IList Skills { get => field ??= []; set; } + /// Metadata for the persisted summary. + [JsonPropertyName("summary")] + public IDictionary? Summary { get; set; } + + /// Refreshed metadata for the containing workspace. + [JsonPropertyName("workspace")] + public IDictionary? Workspace { get; set; } } -/// Identifies the target session. +/// Compaction summary checkpoint to persist. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionSkillsGetInvokedRequest +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; } -/// Name of the skill to enable for the session. +/// Rollback point for local workspace summaries. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SkillsEnableRequest +internal sealed class WorkspacesTruncateSummariesRequest { - /// Name of the skill to enable. - [JsonPropertyName("name")] - public string Name { 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; } -/// Name of the skill to disable for the session. +/// Autopilot objective file content, or null when missing. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SkillsDisableRequest +public sealed class WorkspacesReadAutopilotObjectiveResult { - /// Name of the skill to disable. - [JsonPropertyName("name")] - public string Name { get; set; } = string.Empty; + /// Autopilot objective file content, or null when missing. + [JsonPropertyName("content")] + public string? Content { get; set; } +} +/// Identifies the target session. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class SessionWorkspacesReadAutopilotObjectiveRequest +{ /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Diagnostics from reloading skill definitions, with warnings and errors as separate lists. +/// Result of writing the autopilot objective file. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class SkillsLoadDiagnostics +public sealed class WorkspacesWriteAutopilotObjectiveResult { - /// 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; } + /// Filesystem operation performed. + [JsonPropertyName("operation")] + public string Operation { get; set; } = string.Empty; } -/// Identifies the target session. +/// Autopilot objective file content to persist. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionSkillsReloadRequest +internal sealed class WorkspacesWriteAutopilotObjectiveRequest { + /// Autopilot objective file content. + [JsonPropertyName("content")] + public string Content { get; set; } = string.Empty; + /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } +/// Result of deleting the autopilot objective file. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public sealed class WorkspacesDeleteAutopilotObjectiveResult +{ + /// True when a file was deleted. + [JsonPropertyName("deleted")] + public bool Deleted { get; set; } +} + /// Identifies the target session. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionSkillsEnsureLoadedRequest +internal sealed class SessionWorkspacesDeleteAutopilotObjectiveRequest { /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// The IDE a host is connected to, as reported to the session. +/// Whether the autopilot objective file exists. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class SessionConnectedIdeInfo +public sealed class WorkspacesAutopilotObjectiveExistsResult { - /// Display name of the connected IDE, for example `VS Code`. - [JsonPropertyName("ideName")] - public string IdeName { get; set; } = string.Empty; - - /// Absolute path of the workspace folder the IDE has open. - [JsonPropertyName("workspaceFolder")] - public string WorkspaceFolder { get; set; } = string.Empty; + /// True when the objective file exists. + [JsonPropertyName("exists")] + public bool Exists { get; set; } } -/// Records which IDE the host is connected to, or clears it. +/// Identifies the target session. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionMcpSetConnectedIdeInfoParams +internal sealed class SessionWorkspacesAutopilotObjectiveExistsRequest { - /// The connected IDE. Null or omitted clears the recorded IDE, which is how a host reports that it is disconnected. - [JsonPropertyName("ide")] - public SessionConnectedIdeInfo? Ide { get; set; } - /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Recorded MCP server connection failure. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class McpServerFailureInfo +/// RPC data type for WorkspacesSaveLargePasteResultSaved operations. +public sealed class WorkspacesSaveLargePasteResultSaved { - /// Failure message produced when the MCP server connection failed. - [JsonPropertyName("message")] - public string Message { get; set; } = string.Empty; + /// Filename within the workspace files directory. + [JsonPropertyName("filename")] + public string Filename { get; set; } = string.Empty; - /// epoch-ms timestamp at which the failure was recorded. - [JsonPropertyName("timestamp")] - public long Timestamp { get; set; } + /// Absolute filesystem path to the saved paste file. + [JsonPropertyName("filePath")] + public string FilePath { get; set; } = string.Empty; + + /// Size of the saved file in bytes. + [JsonPropertyName("sizeBytes")] + public long SizeBytes { get; set; } } -/// Recorded MCP server pending-auth state. +/// Descriptor for the saved paste file, or null when the workspace is unavailable. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class McpServerNeedsAuthInfo +public sealed class WorkspacesSaveLargePasteResult { - /// epoch-ms timestamp at which the server signalled it needs authentication. - [JsonPropertyName("timestamp")] - public long Timestamp { get; 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; } } -/// Host-level state, omitted when no MCP host is initialized. +/// Pasted content to save as a UTF-8 file in the session workspace. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class McpHostState +internal sealed class WorkspacesSaveLargePasteRequest { - /// 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; } + /// Pasted content to save as a UTF-8 file. + [JsonPropertyName("content")] + public string Content { get; set; } = string.Empty; - /// Names of servers with in-flight connection attempts. - [JsonPropertyName("pendingConnections")] - public IList PendingConnections { get => field ??= []; set; } + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } -/// Owned installation that a listed MCP server's live configuration came from. +/// Complete text used to generate one session diff. These are display contents, with the same text decoding as the patch, not a file-restore contract. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class McpServerOwnership +public sealed class WorkspaceDiffContents { - /// Stable installation identifier from the owned installation receipt. - [JsonPropertyName("installationId")] - public string InstallationId { get; set; } = string.Empty; + /// Complete current text read when computing the diff. Omitted for a deleted file; an empty string represents an existing empty file. + [JsonPropertyName("after")] + public string? After { get; set; } + + /// Complete text before the session first changed the file. Omitted when the file did not exist; an empty string represents an existing empty file. + [JsonPropertyName("before")] + public string? Before { get; set; } } -/// MCP server status entry, including config source/plugin source and any connection error. +/// A single changed file and its unified diff. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class McpServer +public sealed class WorkspaceDiffFileChange { - /// Human-readable display name supplied by a managed server catalog. - [JsonPropertyName("displayName")] - public string? DisplayName { get; set; } + /// Type of change represented by this file diff. + [JsonPropertyName("changeType")] + public WorkspaceDiffFileChangeType ChangeType { get; set; } - /// Error message if the server failed to connect. - [JsonPropertyName("error")] - public string? Error { get; set; } + /// Full text used for this patch, only when includeContents was requested for session mode. Omitted for binary, oversized or unavailable contents, and for fallback results. Read isFallback and isTruncated before treating an absent value as a missing file. + [JsonPropertyName("contents")] + public WorkspaceDiffContents? Contents { 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; + /// Unified diff content for the file. Empty when the diff was truncated. + [JsonPropertyName("diff")] + public string Diff { get; set; } = string.Empty; - /// Owned installation this entry's live configuration came from. Absent for manual, workspace, plugin, builtin and same-name servers, and on runtimes without owned installations. - [JsonPropertyName("owned")] - public McpServerOwnership? Owned { get; set; } + /// Whether the diff content was omitted because it exceeded the per-file size limit. + [JsonPropertyName("isTruncated")] + public bool? IsTruncated { get; set; } - /// Server-advertised metadata for a connected server. Omitted when no live connection metadata is available, including while pending or when failed, disabled, stopped, or not configured. - [JsonPropertyName("serverMetadata")] - public McpServerMetadata? ServerMetadata { get; set; } + /// Original file path for renamed files. + [JsonPropertyName("oldPath")] + public string? OldPath { get; set; } - /// Configuration source: user, workspace, plugin, builtin, or managed. - [JsonPropertyName("source")] - public McpServerSource? Source { get; set; } + /// Path to the changed file, relative to the workspace root when the file lives under it. A file changed outside the workspace root keeps a `../`-relative path, or an absolute path when no relative path exists (for example a different Windows drive). + [JsonPropertyName("path")] + public string Path { get; set; } = string.Empty; +} - /// Plugin name that provided this server, when source is plugin. - [JsonPropertyName("sourcePlugin")] - public string? SourcePlugin { get; set; } +/// Workspace diff result for the requested mode. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public sealed class WorkspaceDiffResult +{ + /// Default branch used for a branch diff, when branch mode was requested. + [JsonPropertyName("baseBranch")] + public string? BaseBranch { get; set; } - /// Plugin version that provided this server, when source is plugin. - [JsonPropertyName("sourcePluginVersion")] - public string? SourcePluginVersion { get; set; } + /// Changed files and their unified diffs. + [JsonPropertyName("changes")] + public IList Changes { get => field ??= []; set; } - /// Connection status: connected, failed, needs-auth, pending, disabled, stopped, or not_configured. - [JsonPropertyName("status")] - public McpServerStatus Status { 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; } - /// Configured URL for an HTTP/SSE server, regardless of configuration source. Omitted for local and in-memory servers. - [JsonPropertyName("url")] - public string? Url { 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 servers configured for the session, with their connection status and host-level state. +/// Parameters for computing a workspace diff. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class McpServerList +public sealed class WorkspacesDiffRequest { - /// Host-level state, omitted when no MCP host is initialized. - [JsonPropertyName("host")] - public McpHostState? Host { get; set; } + /// When true, ignore whitespace-only changes (git `--ignore-all-space`). Defaults to false. + [JsonPropertyName("ignoreWhitespace")] + public bool? IgnoreWhitespace { get; set; } - /// Configured MCP servers. - [JsonPropertyName("servers")] - public IList Servers { get => field ??= []; set; } + /// Include the full before/after text used to compute each session diff. Defaults to false; true is accepted only for session mode. Existing capture/read limits still apply, and binary or unavailable contents are not returned. This can substantially increase response size. + [JsonPropertyName("includeContents")] + public bool? IncludeContents { get; set; } + + /// Diff mode requested by the client. + [JsonPropertyName("mode")] + public required WorkspaceDiffMode Mode { get; set; } } -/// Identifies the target session. +/// Parameters for computing a workspace diff. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionMcpListRequest +internal sealed class WorkspacesDiffRequestWithSession { + /// When true, ignore whitespace-only changes (git `--ignore-all-space`). Defaults to false. + [JsonPropertyName("ignoreWhitespace")] + public bool? IgnoreWhitespace { get; set; } + + /// Include the full before/after text used to compute each session diff. Defaults to false; true is accepted only for session mode. Existing capture/read limits still apply, and binary or unavailable contents are not returned. This can substantially increase response size. + [JsonPropertyName("includeContents")] + public bool? IncludeContents { 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; } -/// Observational state for a matching already materialized MCP server. +/// Current per-window credit limit and consumption for an autopilot objective. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class McpConfiguredServerState +public sealed class AutopilotObjectiveCreditLimit { - /// Observed connection error, when the materialized server failed. - [JsonPropertyName("error")] - public string? Error { get; set; } + /// Configured AI-credit cap, when one is set. + [JsonPropertyName("credits")] + public double? Credits { get; set; } - /// Observed connection status. This is not a configuration or readiness guarantee. - [JsonPropertyName("status")] - public McpServerStatus Status { get; set; } + /// Window consumption in fractional AI credits, for display. + [JsonPropertyName("creditsUsed")] + public double CreditsUsed { get; set; } + + /// Exact window consumption in non-negative integer nano-AIU, encoded as a decimal string. + [RegularExpression("^[0-9]+$")] + [JsonPropertyName("creditsUsedNanoAiu")] + public string CreditsUsedNanoAiu { get; set; } = string.Empty; } -/// Effective MCP configuration entry. Configuration enablement is distinct from the optional live observation. +/// Public, persistence-independent projection of an autopilot objective. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class McpConfiguredServer +public sealed class AutopilotObjectiveState { - /// Human-readable display name supplied by configuration. - [JsonPropertyName("displayName")] - public string? DisplayName { get; set; } + /// Optional summary recorded when the objective completed. + [JsonPropertyName("completionSummary")] + public string? CompletionSummary { get; set; } - /// Whether this configured server is enabled after session configuration and policy filtering. - [JsonPropertyName("enabled")] - public bool Enabled { get; set; } + /// Exact lifetime AI-credit consumption in non-negative integer nano-AIU, encoded as a decimal string. + [RegularExpression("^[0-9]+$")] + [JsonPropertyName("creditCountNanoAiu")] + public string CreditCountNanoAiu { get; set; } = string.Empty; - /// Observed state from an already materialized matching server. Omitted when no live graph has this configured server; it never determines configuration enablement. - [JsonPropertyName("live")] - public McpConfiguredServerState Live { get => field ??= new(); set; } + /// Current per-window consumption and optional cap, when a credit-tracking window is present. + [JsonPropertyName("creditLimit")] + public AutopilotObjectiveCreditLimit? CreditLimit { 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; + /// Session-local objective identifier. + [JsonPropertyName("id")] + public long Id { get; set; } - /// Configuration provenance: user, workspace, plugin, builtin, or managed. - [JsonPropertyName("source")] - public McpServerSource? Source { get; set; } + /// User-provided objective text. + [JsonPropertyName("objective")] + public string Objective { get; set; } = string.Empty; - /// Plugin name that provided this server, when source is plugin. - [JsonPropertyName("sourcePlugin")] - public string? SourcePlugin { get; set; } + /// Optional reason the objective is paused. + [JsonPropertyName("pauseReason")] + public string? PauseReason { get; set; } - /// Plugin version that provided this server, when source is plugin. - [JsonPropertyName("sourcePluginVersion")] - public string? SourcePluginVersion { get; set; } + /// Current normalized lifecycle status. + [JsonPropertyName("status")] + public AutopilotObjectiveStatus Status { get; set; } + + /// Number of objective turns started. + [JsonPropertyName("turnCount")] + public long TurnCount { get; set; } } -/// Effective MCP configuration with optional live observations from matching already materialized servers. +/// Canonical runtime state for the session's current autopilot objective. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class McpConfiguredServerList +public sealed class AutopilotObjectiveGetStateResult { - /// Effective configured MCP servers. - [JsonPropertyName("servers")] - public IList Servers { get => field ??= []; set; } + /// Current objective state, or `null` when the session has no objective. + [JsonPropertyName("state")] + public AutopilotObjectiveState? State { get; set; } } /// Identifies the target session. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionMcpListConfiguredRequest +internal sealed class SessionAutopilotObjectiveGetStateRequest { /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Normalized MCP Apps discovery metadata from a tool's `_meta.ui` block. +/// 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(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class McpToolUi +public sealed class CompletionsGetTriggerCharactersResult { - /// 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; } + /// Trigger characters advertised by the host (e.g. `["@", "#"]`). Empty disables host-driven completions for the session. + [JsonPropertyName("triggerCharacters")] + public IList TriggerCharacters { get => field ??= []; set; } +} - /// Tool visibility advertised by the server. When absent, MCP Apps defaults apply. - [JsonPropertyName("visibility")] - public IList? Visibility { get; set; } +/// Identifies the target session. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class SessionCompletionsGetTriggerCharactersRequest +{ + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } -/// MCP tool metadata with tool name, optional description, and normalized MCP Apps discovery metadata. +/// 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(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class McpTools +public sealed class SessionCompletionItem { - /// Tool description, when provided. - [JsonPropertyName("description")] - public string? Description { get; set; } + /// Text spliced into the composer when the item is accepted. + [JsonPropertyName("insertText")] + public string InsertText { get; set; } = string.Empty; - /// Tool name. - [JsonPropertyName("name")] - public string Name { 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; } - /// 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; } + /// Primary display label for the picker row. Falls back to `insertText` when absent. + [JsonPropertyName("label")] + public string? Label { get; set; } + + /// End (exclusive) of the replacement range in `text`, in UTF-16 code units. + [JsonPropertyName("rangeEnd")] + public long? RangeEnd { get; set; } + + /// Start of the replacement range in `text`, in UTF-16 code units. + [JsonPropertyName("rangeStart")] + public long? RangeStart { get; set; } } -/// Tools exposed by the connected MCP server. Throws when the server is not connected. +/// Host-driven completion items for the current composer input. Empty when the host returns no items or does not support completions. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class McpListToolsResult +public sealed class CompletionsRequestResult { - /// Tools exposed by the server. - [JsonPropertyName("tools")] - public IList Tools { get => field ??= []; set; } + /// Completion items in host-ranked order. + [JsonPropertyName("items")] + public IList Items { get => field ??= []; set; } } -/// Server name whose tool list should be returned. +/// Request host-driven completions for the current composer input. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class McpListToolsRequest +internal sealed class CompletionsRequestRequest { - /// 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; + /// Cursor offset within `text`, in UTF-16 code units. + [JsonPropertyName("offset")] + public long Offset { get; set; } /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; + + /// The full composed composer input. + [JsonPropertyName("text")] + public string Text { get; set; } = string.Empty; } -/// Name of the MCP server to enable for the session. +/// Instruction sources loaded for the session, in merge order. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class McpEnableRequest +public sealed class InstructionsGetSourcesResult { - /// Exact receipt identity for explicit owned activation in this session. - [JsonPropertyName("expectedInstallationId")] - public string? ExpectedInstallationId { get; set; } - - /// 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 required string ServerName { get; set; } + /// Instruction sources for the session. + [JsonPropertyName("sources")] + public IList Sources { get => field ??= []; set; } } -/// Name of the MCP server to enable for the session. +/// Identifies the target session. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class McpEnableRequestWithSession +internal sealed class SessionInstructionsGetSourcesRequest { - /// Exact receipt identity for explicit owned activation in this session. - [JsonPropertyName("expectedInstallationId")] - public string? ExpectedInstallationId { get; set; } - - /// 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; +} +/// Identifies the target session. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class SessionInstructionsReloadRequest +{ /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Name of the MCP server to disable for the session. +/// Result of one customization reload component. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class McpDisableRequest +public sealed class CustomizationReloadOutcome { - /// Required for an owned installation; omission preserves only manual-server behaviour. - [JsonPropertyName("expectedInstallationId")] - public string? ExpectedInstallationId { get; set; } + /// Reason for a skipped component or description of a failure, when available. + [JsonPropertyName("detail")] + public string? Detail { get; set; } - /// 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 required string ServerName { get; set; } + /// Whether the component reloaded, was skipped, or failed. + [JsonPropertyName("status")] + public CustomizationReloadStatus Status { get; set; } + + /// Component whose reload was attempted or skipped. + [JsonPropertyName("subsystem")] + public CustomizationReloadSubsystem Subsystem { get; set; } } -/// Name of the MCP server to disable for the session. +/// Results of reloading discovered session customizations. Inspect outcomes for reloaded, skipped, or failed subsystems; a rejection may follow partial mutation. Changes to the model-facing prompt and tools apply on the next turn. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class McpDisableRequestWithSession +public sealed class CustomizationsReloadResult { - /// Required for an owned installation; omission preserves only manual-server behaviour. - [JsonPropertyName("expectedInstallationId")] - public string? ExpectedInstallationId { get; set; } + /// Errors from any component that could not be refreshed. + [JsonPropertyName("errors")] + public IList Errors { get => field ??= []; set; } - /// 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; + /// Outcome of each component in reload order; a skipped component was not configured or loaded. + [JsonPropertyName("outcomes")] + public IList Outcomes { get => field ??= []; set; } - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Warnings from skill discovery. + [JsonPropertyName("warnings")] + public IList Warnings { get => field ??= []; set; } } /// Identifies the target session. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionMcpReloadRequest +internal sealed class SessionCustomizationsReloadRequest { /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Result of moving in-flight MCP loading to the background. +/// Indicates whether fleet mode was successfully activated. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class MoveMcpLoadingToBackgroundResult +public sealed class FleetStartResult { - /// 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; } + /// Whether fleet mode was successfully activated. + [JsonPropertyName("started")] + public bool Started { get; set; } } -/// Identifies the target session. +/// Parameters for starting fleet orchestration: an optional user prompt combined with the fleet instructions, plus the send options forwarded to the resulting turn. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionMcpMoveLoadingToBackgroundRequest +internal sealed class FleetStartRequest { + /// Optional attachments (files, directories, selections, blobs, GitHub references) to include with the fleet request. + [JsonPropertyName("attachments")] + public IList? Attachments { get; set; } + + /// If false, this request will not trigger a Premium Request Unit charge. User requests default to billable. + [JsonInclude] + [JsonPropertyName("billable")] + internal bool? Billable { get; set; } + + /// 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; -} - -/// MCP server allowed by policy, with server name and optional PII-free explanatory note. -[Experimental(global::GitHub.Copilot.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; } + /// If true, await completion of the agentic loop for this fleet request before returning. Defaults to false. + [JsonPropertyName("wait")] + public bool? Wait { get; set; } } -/// MCP server whose connection attempt failed. +/// Agents available to the session. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class McpFailedServer +public sealed class AgentList { - /// 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; + /// Available agents. + [JsonPropertyName("agents")] + public IList Agents { get => field ??= []; set; } } -/// MCP server filtered by policy, with name, reason, and optional redacted reason. +/// RPC data type for SessionAgentList operations. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class McpFilteredServer +public sealed class SessionAgentListRequest { - /// 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")] -#else - [Obsolete("This member is deprecated and will be removed in a future version.")] -#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; + /// 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; } - /// PII-free filter reason. - [JsonPropertyName("redactedReason")] - public string? RedactedReason { 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; } } -/// MCP server startup filtering result. +/// RPC data type for SessionAgentListRequestWithSession operations. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class McpStartServersResult +internal sealed class SessionAgentListRequestWithSession { - /// 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; } + /// 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; } - /// Servers filtered out before startup. - [JsonPropertyName("filteredServers")] - public IList FilteredServers { 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; } -/// Opaque MCP reload configuration. -[Experimental(global::GitHub.Copilot.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(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class McpExecuteSamplingResult -{ -} - -/// Outcome of an MCP sampling execution: success result, failure error, or cancellation. +/// An in-memory authored prompt override for an available agent. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class McpSamplingExecutionResult +internal sealed class AgentSetPromptRequest { - /// 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; } + /// Stable effective agent id. Plugin namespace separators are normalized. + [JsonPropertyName("id")] + public string Id { get; set; } = string.Empty; - /// Error description, present when action='failure'. - [JsonPropertyName("error")] - public string? Error { get; set; } + /// Replacement authored prompt. Empty text is valid. + [JsonPropertyName("prompt")] + public string Prompt { 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. - [JsonPropertyName("result")] - public McpExecuteSamplingResult? Result { get; set; } + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } -/// 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. +/// The currently selected custom agent, or null when using the default agent. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class McpExecuteSamplingRequest +public sealed class AgentGetCurrentResult { + /// Currently selected custom agent, or null if using the default agent. + [JsonPropertyName("agent")] + public AgentInfo? Agent { get; set; } } -/// Identifiers and raw MCP CreateMessageRequest params used to run a sampling inference. +/// Identifies the target session. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class McpExecuteSamplingParams +internal sealed class SessionAgentGetCurrentRequest { - /// 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. +/// The newly selected custom agent. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class McpCancelSamplingExecutionResult +public sealed class AgentSelectResult { - /// 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 newly selected custom agent. + [JsonPropertyName("agent")] + public AgentInfo Agent { get => field ??= new(); set; } } -/// The requestId previously passed to executeSampling that should be cancelled. +/// Name of the custom agent to select for subsequent turns. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class McpCancelSamplingExecutionParams +internal sealed class AgentSelectRequest { - /// The requestId previously passed to executeSampling that should be cancelled. - [JsonPropertyName("requestId")] - public string RequestId { get; set; } = string.Empty; + /// 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; } -/// Env-value mode recorded on the session after the update. -[Experimental(global::GitHub.Copilot.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`). +/// Identifies the target session. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class McpSetEnvValueModeParams +internal sealed class SessionAgentDeselectRequest { - /// 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). +/// Custom agents available to the session after reloading definitions from disk. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class McpRemoveGitHubResult +public sealed class AgentReloadResult { - /// 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; } + /// Reloaded custom agents. + [JsonPropertyName("agents")] + public IList Agents { get => field ??= []; set; } } /// Identifies the target session. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionMcpRemoveGitHubRequest +internal sealed class SessionAgentReloadRequest { /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Result of configuring GitHub MCP. +/// Identifier assigned to the newly started background agent task. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class McpConfigureGitHubResult +public sealed class TasksStartAgentResult { - /// Whether GitHub MCP configuration changed. - [JsonPropertyName("changed")] - public bool Changed { get; set; } + /// Generated agent ID for the background task. + [JsonPropertyName("agentId")] + public string AgentId { get; set; } = string.Empty; } -/// Credential-free authentication identity used to configure GitHub MCP. +/// Agent type, prompt, name, and optional description and model override for the new task. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class McpConfigureGitHubRequest +internal sealed class TasksStartAgentRequest { + /// 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; } -/// 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. +/// Tracked task union returned by task APIs, containing an agent, client, or shell task. +/// Polymorphic base type discriminated by type. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class McpStartServerRequest +[JsonPolymorphic( + TypeDiscriminatorPropertyName = "type", + UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] +[JsonDerivedType(typeof(TaskInfoAgent), "agent")] +[JsonDerivedType(typeof(TaskInfoClient), "client")] +[JsonDerivedType(typeof(TaskInfoShell), "shell")] +public partial class TaskInfo { - /// 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; } - - /// Exact receipt identity for explicit owned activation in this session. - [JsonPropertyName("expectedInstallationId")] - public string? ExpectedInstallationId { get; set; } - - /// Name of the MCP server to start. - [JsonPropertyName("serverName")] - public required string ServerName { get; set; } + /// The type discriminator. + [JsonPropertyName("type")] + public virtual string Type { 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. + +/// Tracked background agent task metadata, including IDs, status, timing, agent type, prompt, model, result, and latest response. +/// The agent variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class McpStartServerRequestWithSession +public partial class TaskInfoAgent : TaskInfo { - /// 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; } + /// + [JsonIgnore] + public override string Type => "agent"; - /// Exact receipt identity for explicit owned activation in this session. - [JsonPropertyName("expectedInstallationId")] - public string? ExpectedInstallationId { get; set; } + /// ISO 8601 timestamp when the current active period began. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("activeStartedAt")] + public DateTimeOffset? ActiveStartedAt { get; set; } - /// Name of the MCP server to start. - [JsonPropertyName("serverName")] - public string ServerName { get; set; } = string.Empty; + /// Accumulated active execution time in milliseconds. + [JsonConverter(typeof(MillisecondsTimeSpanConverter))] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("activeTimeMs")] + public TimeSpan? ActiveTime { get; set; } - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; -} + /// Type of agent running this task. + [JsonPropertyName("agentType")] + public required string AgentType { get; set; } -/// 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(global::GitHub.Copilot.Diagnostics.Experimental)] -public 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; } + /// 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; } - /// Exact receipt identity for an explicit owned restart; configuration overrides are refused. - [JsonPropertyName("expectedInstallationId")] - public string? ExpectedInstallationId { get; set; } + /// ISO 8601 timestamp when the task finished. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("completedAt")] + public DateTimeOffset? CompletedAt { get; set; } - /// Name of the MCP server to restart. - [JsonPropertyName("serverName")] - public required string ServerName { get; set; } -} + /// Short description of the task. + [JsonPropertyName("description")] + public required string Description { get; set; } -/// 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(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class McpRestartServerRequestWithSession -{ - /// 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; } + /// Friendly, non-unique name intended for display. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("displayName")] + public string? DisplayName { get; set; } - /// Exact receipt identity for an explicit owned restart; configuration overrides are refused. - [JsonPropertyName("expectedInstallationId")] - public string? ExpectedInstallationId { get; set; } + /// Error message when the task failed. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("error")] + public string? Error { get; set; } - /// Name of the MCP server to restart. - [JsonPropertyName("serverName")] - public string ServerName { 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; } - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; -} + /// Unique task identifier. + [JsonPropertyName("id")] + public required string Id { get; set; } -/// Server name for an individual MCP server stop. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class McpStopServerRequest -{ - /// Exact owned receipt identity. Stop also forgets this session's durable activation. - [JsonPropertyName("expectedInstallationId")] - public string? ExpectedInstallationId { get; set; } + /// ISO 8601 timestamp when the agent entered idle state. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("idleSince")] + public DateTimeOffset? IdleSince { get; set; } - /// Name of the MCP server to stop. - [JsonPropertyName("serverName")] - public required string ServerName { get; set; } -} + /// Most recent response text from the agent. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("latestResponse")] + public string? LatestResponse { get; set; } -/// Server name for an individual MCP server stop. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class McpStopServerRequestWithSession -{ - /// Exact owned receipt identity. Stop also forgets this session's durable activation. - [JsonPropertyName("expectedInstallationId")] - public string? ExpectedInstallationId { get; set; } + /// Requested model override for the task when specified. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("model")] + public string? Model { get; set; } - /// Name of the MCP server to stop. - [JsonPropertyName("serverName")] - public string ServerName { 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; } - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; -} + /// Runtime model resolved for the task when available. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("resolvedModel")] + public string? ResolvedModel { get; set; } -/// Registration parameters for an external MCP client. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class McpRegisterExternalClientRequest -{ - /// Logical server name for the external client. - [JsonPropertyName("serverName")] - public string ServerName { get; set; } = string.Empty; + /// Result text from the task when available. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("result")] + public string? Result { get; set; } - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; -} + /// ISO 8601 timestamp when the task was started. + [JsonPropertyName("startedAt")] + public required DateTimeOffset StartedAt { get; set; } -/// Server name identifying the external client to remove. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class McpUnregisterExternalClientRequest -{ - /// Server name of the external client to unregister. - [JsonPropertyName("serverName")] - public string ServerName { get; set; } = string.Empty; + /// Current lifecycle status of the task. + [JsonPropertyName("status")] + public required TaskStatus Status { get; set; } - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Tool call ID associated with this agent task. + [JsonPropertyName("toolCallId")] + public required string ToolCallId { get; set; } } -/// Whether the named MCP server is running. +/// Public owner attribution for a client-owned task. Identifiers are opaque and never authorize requests. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class McpIsServerRunningResult +public sealed class TaskClientOwner { - /// True if the server has an active client and transport. - [JsonPropertyName("running")] - public bool Running { get; set; } -} + /// ISO 8601 timestamp when the bound join disconnected. + [JsonPropertyName("disconnectedAt")] + public DateTimeOffset? DisconnectedAt { get; set; } -/// Server name to check running status for. -[Experimental(global::GitHub.Copilot.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; -} + /// Display-only owner name. + [JsonPropertyName("displayName")] + public string? DisplayName { get; set; } -/// Indicates whether the pending MCP OAuth response was accepted. -[Experimental(global::GitHub.Copilot.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; } -} + /// Opaque identity of the currently or most recently bound session join. + [JsonPropertyName("joinId")] + public string JoinId { get; set; } = string.Empty; -/// Host response to the pending OAuth request. -/// Polymorphic base type discriminated by kind. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -[JsonPolymorphic( - TypeDiscriminatorPropertyName = "kind", - UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] -[JsonDerivedType(typeof(McpOauthPendingRequestResponseToken), "token")] -[JsonDerivedType(typeof(McpOauthPendingRequestResponseCancelled), "cancelled")] -public partial class McpOauthPendingRequestResponse -{ - /// The type discriminator. + /// Class of the task owner. [JsonPropertyName("kind")] - public virtual string Kind { get; set; } = string.Empty; -} + public TaskClientOwnerKind Kind { get; set; } + + /// Opaque session-scoped participant identity. + [JsonPropertyName("participantId")] + public string ParticipantId { get; set; } = string.Empty; + /// Whether this task's bound join is currently connected. + [JsonPropertyName("presence")] + public TaskClientOwnerPresence Presence { get; set; } -/// The token variant of . + /// Display-only owner source. + [JsonPropertyName("source")] + public string? Source { get; set; } +} + +/// Tracked client-owned task metadata. +/// The client variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class McpOauthPendingRequestResponseToken : McpOauthPendingRequestResponse +public partial class TaskInfoClient : TaskInfo { /// [JsonIgnore] - public override string Kind => "token"; + public override string Type => "client"; - /// Access token acquired by the SDK host. - [JsonPropertyName("accessToken")] - public required string AccessToken { get; set; } + /// ISO 8601 timestamp when the current active segment started. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("activeStartedAt")] + public DateTimeOffset? ActiveStartedAt { get; set; } - /// Token lifetime in seconds, if known. + /// Accumulated active execution time in milliseconds. + [JsonPropertyName("activeTimeMs")] + public required long ActiveTimeMs { get; set; } + + /// Whether the currently bound owner can receive a cancellation request. + [JsonPropertyName("canCancel")] + public required bool CanCancel { get; set; } + + /// Human-readable reason for terminal cancellation. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("expiresIn")] - public long? ExpiresIn { get; set; } + [JsonPropertyName("cancellationReason")] + public string? CancellationReason { get; set; } - /// OAuth token type. Defaults to bearer when omitted. + /// Owner-scoped registration and reclaim key. + [JsonPropertyName("clientTaskId")] + public required string ClientTaskId { get; set; } + + /// ISO 8601 timestamp when the task reached a terminal status. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("tokenType")] - public string? TokenType { get; set; } + [JsonPropertyName("completedAt")] + public DateTimeOffset? CompletedAt { get; set; } + + /// Task description. + [JsonPropertyName("description")] + public required string Description { get; set; } + + /// Optional task display name. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("displayName")] + public string? DisplayName { get; set; } + + /// Human-readable terminal failure message. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("error")] + public string? Error { get; set; } + + /// Optional owner-supplied terminal failure code. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("errorCode")] + public string? ErrorCode { get; set; } + + /// Execution mode, which is always background for client-owned tasks. + [JsonPropertyName("executionMode")] + public required TaskClientExecutionMode ExecutionMode { get; set; } + + /// Canonical runtime-generated task identifier. + [JsonPropertyName("id")] + public required string Id { get; set; } + + /// ISO 8601 timestamp when the connected owner entered idle status. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("idleSince")] + public DateTimeOffset? IdleSince { get; set; } + + /// ISO 8601 timestamp of the most recent orphan transition. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("orphanedAt")] + public DateTimeOffset? OrphanedAt { get; set; } + + /// Public attribution and presence for the task owner. + [JsonPropertyName("owner")] + public required TaskClientOwner Owner { get; set; } + + /// ISO 8601 timestamp of the most recent successful reclaim. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("reclaimedAt")] + public DateTimeOffset? ReclaimedAt { get; set; } + + /// Opaque successful terminal result supplied by the task owner. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("result")] + public JsonElement? Result { get; set; } + + /// Sequence number of the latest accepted owner update. + [JsonPropertyName("sequence")] + public required long Sequence { get; set; } + + /// ISO 8601 timestamp when the task started. + [JsonPropertyName("startedAt")] + public required DateTimeOffset StartedAt { get; set; } + + /// Client task lifecycle status. + [JsonPropertyName("status")] + public required TaskClientStatus Status { get; set; } + + /// ISO 8601 timestamp of the latest accepted lifecycle change. + [JsonPropertyName("updatedAt")] + public required DateTimeOffset UpdatedAt { get; set; } } -/// The cancelled variant of . +/// Tracked shell task metadata, including ID, command, status, timing, attachment/execution mode, log path, and PID. +/// The shell variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class McpOauthPendingRequestResponseCancelled : McpOauthPendingRequestResponse +public partial class TaskInfoShell : TaskInfo { /// [JsonIgnore] - public override string Kind => "cancelled"; -} + public override string Type => "shell"; -/// Pending MCP OAuth request ID and host-provided token or cancellation response. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class McpOauthHandlePendingRequest -{ - /// OAuth request identifier from the mcp.oauth_required event. - [JsonPropertyName("requestId")] - public string RequestId { get; set; } = string.Empty; + /// Whether the shell runs inside a managed PTY session or as an independent background process. + [JsonPropertyName("attachmentMode")] + public required TaskShellInfoAttachmentMode AttachmentMode { get; set; } - /// Host response to the pending OAuth request. - [JsonPropertyName("result")] - public McpOauthPendingRequestResponse Result { get => field ??= new(); set; } + /// Whether this shell task can be promoted to background mode. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("canPromoteToBackground")] + public bool? CanPromoteToBackground { get; set; } - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; -} + /// Command being executed. + [JsonPropertyName("command")] + public required string Command { get; set; } -/// Identifies the MCP server whose persisted OAuth credentials were updated. -[Experimental(global::GitHub.Copilot.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; } + /// ISO 8601 timestamp when the task finished. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("completedAt")] + public DateTimeOffset? CompletedAt { 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; } + /// Short description of the task. + [JsonPropertyName("description")] + public required string Description { get; set; } - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { 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; } + + /// 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; } } -/// An inert runtime-issued login handle. Preparation alone performs no activation or OAuth work. +/// Background tasks currently tracked by the session. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class SessionMcpOauthPrepareLoginResult +public sealed class TaskList { - /// Original expiry, not extended by consumption, retries or cancellation. - [JsonPropertyName("expiresAt")] - public DateTimeOffset ExpiresAt { get; set; } - - /// Retain with the original requester and use for one login or cancellation. - [JsonPropertyName("loginId")] - public string LoginId { get; set; } = string.Empty; + /// Currently tracked tasks. + [JsonPropertyName("tasks")] + public IList Tasks { get => field ??= []; set; } } -/// Effect-free preparation bound to the existing local session, requester and installation, with frozen options. +/// Identifies the target session. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionMcpOauthPrepareLoginRequest +internal sealed class SessionTasksListRequest { - /// Text shown on the loopback callback page after successful authorisation. - [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(2048)] - [JsonPropertyName("callbackSuccessMessage")] - public string? CallbackSuccessMessage { get; set; } - - /// Display name used by the incumbent OAuth client-registration flow. - [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(2048)] - [JsonPropertyName("clientName")] - public string? ClientName { get; set; } - - /// Exact installation identity from owned inventory, never a server-name alias. - [JsonPropertyName("expectedInstallationId")] - public string ExpectedInstallationId { get; set; } = string.Empty; - - /// Request a new authorisation rather than accepting a usable cached grant. - [JsonPropertyName("forceReauth")] - public bool? ForceReauth { get; set; } - - /// Name recorded by the authoritative owned installation receipt. - [JsonPropertyName("serverName")] - public string ServerName { get; set; } = string.Empty; - /// 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. +/// Tracked client-owned task metadata. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class McpOauthLoginResult +public sealed class TaskClientInfo { - /// Opaque authorization identifier returned only for a host-managed redirect URI. The runtime also sends it as the OAuth state value, so the callback endpoint can read state and pass it with the full callback URL to session.mcp.oauth.complete. - [JsonPropertyName("authorizationId")] - public string? AuthorizationId { get; set; } - - /// URL the caller should open in a browser to complete OAuth. Omitted when cached tokens were still valid and no browser interaction was needed — the server is already reconnected in that case. For the default loopback flow, the runtime starts its listener before returning. With redirectUri, the host receives the callback and completes it through session.mcp.oauth.complete. The runtime continues the flow in the background and signals completion via session.mcp_server_status_changed. - [Url] - [StringSyntax(StringSyntaxAttribute.Uri)] - [JsonPropertyName("authorizationUrl")] - public string? AuthorizationUrl { get; set; } - - /// Runtime-issued owned flow identity; never a server name or installation operation ID. - [JsonPropertyName("loginId")] - public string? LoginId { get; set; } + /// ISO 8601 timestamp when the current active segment started. + [JsonPropertyName("activeStartedAt")] + public DateTimeOffset? ActiveStartedAt { get; set; } - /// Explicit outcome for owned sign-in. Manual callers retain their legacy response shape. - [JsonPropertyName("status")] - public McpOwnedOauthLoginStatus? Status { get; set; } -} + /// Accumulated active execution time in milliseconds. + [JsonPropertyName("activeTimeMs")] + public long ActiveTimeMs { get; set; } -/// Remote MCP server name and optional overrides controlling reauthentication, OAuth client display name, callback handling, and static OAuth client selection. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public 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; } + /// Whether the currently bound owner can receive a cancellation request. + [JsonPropertyName("canCancel")] + public bool CanCancel { 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; } + /// Human-readable reason for terminal cancellation. + [JsonPropertyName("cancellationReason")] + public string? CancellationReason { 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; } + /// Owner-scoped registration and reclaim key. + [JsonPropertyName("clientTaskId")] + public string ClientTaskId { get; set; } = string.Empty; - /// 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; } + /// ISO 8601 timestamp when the task reached a terminal status. + [JsonPropertyName("completedAt")] + public DateTimeOffset? CompletedAt { get; set; } - /// Exact owned receipt identity. Owned login never uses an implicit helper session. - [JsonPropertyName("expectedInstallationId")] - public string? ExpectedInstallationId { get; set; } + /// Task description. + [JsonPropertyName("description")] + public string Description { get; set; } = string.Empty; - /// 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 task display name. + [JsonPropertyName("displayName")] + public string? DisplayName { 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; } + /// Human-readable terminal failure message. + [JsonPropertyName("error")] + public string? Error { get; set; } - /// - /// Required for owned login. Consumes the exact prepareLogin handle once. - /// Set forceReauth and display options during preparation, not consumption. - /// - [JsonPropertyName("loginId")] - public string? LoginId { get; set; } + /// Optional owner-supplied terminal failure code. + [JsonPropertyName("errorCode")] + public string? ErrorCode { 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; } + /// Execution mode, which is always background for client-owned tasks. + [JsonPropertyName("executionMode")] + public TaskClientExecutionMode ExecutionMode { get; set; } - /// Optional externally visible HTTPS redirect URI for a host-managed callback endpoint. When supplied, the runtime still owns discovery, PKCE, token exchange, persistence, and reconnect, but does not bind a loopback listener or terminate HTTPS. The URI must not contain query parameters or a fragment and must be registered for the selected CIMD, DCR, or static OAuth client. - [Url] - [StringSyntax(StringSyntaxAttribute.Uri)] - [JsonPropertyName("redirectUri")] - public string? RedirectUri { get; set; } + /// Canonical runtime-generated task identifier. + [JsonPropertyName("id")] + public string Id { get; set; } = string.Empty; - /// 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 required string ServerName { get; set; } -} + /// ISO 8601 timestamp when the connected owner entered idle status. + [JsonPropertyName("idleSince")] + public DateTimeOffset? IdleSince { get; set; } -/// Remote MCP server name and optional overrides controlling reauthentication, OAuth client display name, callback handling, and static OAuth client selection. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class McpOauthLoginRequestWithSession -{ - /// 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; } + /// ISO 8601 timestamp of the most recent orphan transition. + [JsonPropertyName("orphanedAt")] + public DateTimeOffset? OrphanedAt { 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; } + /// Public attribution and presence for the task owner. + [JsonPropertyName("owner")] + public TaskClientOwner Owner { get => field ??= new(); 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; } + /// ISO 8601 timestamp of the most recent successful reclaim. + [JsonPropertyName("reclaimedAt")] + public DateTimeOffset? ReclaimedAt { 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; } + /// Opaque successful terminal result supplied by the task owner. + [JsonPropertyName("result")] + public JsonElement? Result { get; set; } - /// Exact owned receipt identity. Owned login never uses an implicit helper session. - [JsonPropertyName("expectedInstallationId")] - public string? ExpectedInstallationId { get; set; } + /// Sequence number of the latest accepted owner update. + [JsonPropertyName("sequence")] + public long Sequence { 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; } + /// ISO 8601 timestamp when the task started. + [JsonPropertyName("startedAt")] + public DateTimeOffset StartedAt { 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; } + /// Client task lifecycle status. + [JsonPropertyName("status")] + public TaskClientStatus Status { get; set; } - /// - /// Required for owned login. Consumes the exact prepareLogin handle once. - /// Set forceReauth and display options during preparation, not consumption. - /// - [JsonPropertyName("loginId")] - public string? LoginId { get; set; } + /// Task kind. + [JsonPropertyName("type")] + public TaskClientType Type { 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; } + /// ISO 8601 timestamp of the latest accepted lifecycle change. + [JsonPropertyName("updatedAt")] + public DateTimeOffset UpdatedAt { get; set; } +} - /// Optional externally visible HTTPS redirect URI for a host-managed callback endpoint. When supplied, the runtime still owns discovery, PKCE, token exchange, persistence, and reconnect, but does not bind a loopback listener or terminate HTTPS. The URI must not contain query parameters or a fragment and must be registered for the selected CIMD, DCR, or static OAuth client. - [Url] - [StringSyntax(StringSyntaxAttribute.Uri)] - [JsonPropertyName("redirectUri")] - public string? RedirectUri { get; set; } +/// Result of registering or reclaiming a client-owned task. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public sealed class TasksRegisterResult +{ + /// True only when this invocation created a new task. + [JsonPropertyName("created")] + public bool Created { 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; + /// True only when this invocation reclaimed an orphaned task. + [JsonPropertyName("reclaimed")] + public bool Reclaimed { get; set; } - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Authoritative registered or reclaimed task. + [JsonPropertyName("task")] + public TaskClientInfo Task { get => field ??= new(); set; } } -/// Host-delivered callback for a runtime-managed MCP OAuth login. +/// Registers or reclaims a client-owned task. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class McpOauthCompleteRequest +internal sealed class TasksRegisterRequest { - /// Opaque identifier returned by session.mcp.oauth.login for the pending external callback. - [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("authorizationId")] - public string AuthorizationId { get; set; } = string.Empty; + /// Whether the owner supports runtime cancellation requests. + [JsonPropertyName("cancellable")] + public bool Cancellable { get; set; } - /// Full externally visible HTTPS callback URL received by the host, including the authorization response query parameters. Applications behind a reverse proxy must reconstruct the public URL rather than passing an internal proxy URL. - [Url] - [StringSyntax(StringSyntaxAttribute.Uri)] - [JsonPropertyName("callbackUrl")] - public string CallbackUrl { get; set; } = string.Empty; + /// Owner-scoped idempotency key used for registration and reclaim. + [JsonPropertyName("clientTaskId")] + public string ClientTaskId { get; set; } = string.Empty; + + /// Human-readable description of the external work. + [JsonPropertyName("description")] + public string Description { get; set; } = string.Empty; + + /// Optional short display name for the external work. + [JsonPropertyName("displayName")] + public string? DisplayName { get; set; } + + /// Expected current sequence for idempotent registration or orphan reclaim. + [JsonPropertyName("expectedSequence")] + public long? ExpectedSequence { get; set; } /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; + + /// Task kind. + [JsonPropertyName("type")] + public TaskClientType Type { get; set; } } -/// 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. +/// Result of publishing a client-owned task update. [Experimental(global::GitHub.Copilot.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 +public sealed class TasksUpdateResult { - /// The type discriminator. - [JsonPropertyName("status")] - public virtual string Status { get; set; } = string.Empty; -} + /// Whether this invocation changed task state. + [JsonPropertyName("applied")] + public bool Applied { get; set; } + + /// Whether this invocation repeated the latest accepted update. + [JsonPropertyName("duplicate")] + public bool Duplicate { get; set; } + /// Authoritative task after processing the update. + [JsonPropertyName("task")] + public TaskClientInfo Task { get => field ??= new(); set; } +} -/// The no-auth-required variant of . +/// Progress or terminal update for a client-owned task. +/// Polymorphic base type discriminated by kind. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class McpOauthProbeResultNoAuthRequired : McpOauthProbeResult +[JsonPolymorphic( + TypeDiscriminatorPropertyName = "kind", + UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] +[JsonDerivedType(typeof(TaskClientUpdateProgress), "progress")] +[JsonDerivedType(typeof(TaskClientUpdateCompleted), "completed")] +[JsonDerivedType(typeof(TaskClientUpdateFailed), "failed")] +[JsonDerivedType(typeof(TaskClientUpdateCancelled), "cancelled")] +public partial class TaskClientUpdate { - /// - [JsonIgnore] - public override string Status => "no-auth-required"; - - /// HTTP response returned by the server. - [JsonPropertyName("httpResponse")] - public required McpOauthHttpResponse HttpResponse { get; set; } + /// The type discriminator. + [JsonPropertyName("kind")] + public virtual string Kind { get; set; } = string.Empty; } -/// The authenticated variant of . + +/// Publishes nonterminal progress for a running or idle client task. +/// The progress variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class McpOauthProbeResultAuthenticated : McpOauthProbeResult +public partial class TaskClientUpdateProgress : TaskClientUpdate { /// [JsonIgnore] - public override string Status => "authenticated"; + public override string Kind => "progress"; - /// HTTP response returned by the server. - [JsonPropertyName("httpResponse")] - public required McpOauthHttpResponse HttpResponse { get; set; } + /// Optional progress message appended to recent activity when nonempty. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("message")] + public string? Message { get; set; } + + /// Optional completion percentage; null clears the current percentage. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("percentage")] + public double? Percentage { get; set; } + + /// Optional progress phase; null clears the current phase. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("phase")] + public string? Phase { get; set; } + + /// Optional active status transition. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("status")] + public TaskClientActiveStatus? Status { get; set; } } -/// The needs-auth variant of . +/// Reports successful terminal completion. +/// The completed variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class McpOauthProbeResultNeedsAuth : McpOauthProbeResult +public partial class TaskClientUpdateCompleted : TaskClientUpdate { /// [JsonIgnore] - public override string Status => "needs-auth"; - - /// HTTP 401 or 403 response returned by the server. - [JsonPropertyName("httpResponse")] - public required McpOauthHttpResponse HttpResponse { get; set; } + public override string Kind => "completed"; - /// Why authentication is needed. - [JsonPropertyName("reason")] - public required McpOauthProbeNeedsAuthReason Reason { get; set; } + /// Optional final progress message. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("message")] + public string? Message { get; set; } - /// Parsed WWW-Authenticate challenge parameters, when present and parseable. + /// Optional opaque successful terminal result. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("wwwAuthenticateParams")] - public McpOauthWWWAuthenticateParams? WwwAuthenticateParams { get; set; } + [JsonPropertyName("result")] + public JsonElement? Result { get; set; } } -/// The failed variant of . +/// Reports terminal failure. +/// The failed variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class McpOauthProbeResultFailed : McpOauthProbeResult +public partial class TaskClientUpdateFailed : TaskClientUpdate { /// [JsonIgnore] - public override string Status => "failed"; + public override string Kind => "failed"; - /// Human-readable probe failure detail. + /// Optional owner-supplied terminal failure code. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("code")] + public string? Code { get; set; } + + /// Human-readable terminal failure message. [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. + /// Optional final progress message. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("httpResponse")] - public McpOauthHttpResponse? HttpResponse { get; set; } + [JsonPropertyName("message")] + public string? Message { get; set; } } -/// Remote MCP server name for a passive OAuth status probe. +/// Reports terminal cancellation after external work stopped. +/// The cancelled variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class McpOauthProbeRequest +public partial class TaskClientUpdateCancelled : TaskClientUpdate { - /// Exact owned receipt identity; probing never activates a dormant installation. - [JsonPropertyName("expectedInstallationId")] - public string? ExpectedInstallationId { get; set; } + /// + [JsonIgnore] + public override string Kind => "cancelled"; - /// 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 required string ServerName { get; set; } + /// Optional final progress message. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("message")] + public string? Message { get; set; } + + /// Optional human-readable cancellation reason. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("reason")] + public string? Reason { get; set; } } -/// Remote MCP server name for a passive OAuth status probe. +/// Updates a client-owned task. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class McpOauthProbeRequestWithSession +internal sealed class TasksUpdateRequest { - /// Exact owned receipt identity; probing never activates a dormant installation. - [JsonPropertyName("expectedInstallationId")] - public string? ExpectedInstallationId { get; set; } + /// Canonical runtime-generated task identifier. + [JsonPropertyName("id")] + public string Id { get; set; } = string.Empty; - /// 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; + /// Owner update sequence to apply. + [JsonPropertyName("sequence")] + public long Sequence { get; set; } /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; + + /// Progress or terminal update payload. + [JsonPropertyName("update")] + public TaskClientUpdate Update { get => field ??= new(); set; } } -/// Honest terminal cancellation result; persistence or recovery failures remain RPC errors. +/// 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(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class SessionMcpOauthCancelLoginResult +public sealed class TasksRefreshResult { - /// True after cancellation settles, false when the original login already connected successfully. - [JsonPropertyName("cancelled")] - public bool Cancelled { get; set; } } -/// Targets only the original prepared/applying owned login on this exact session requester. +/// Identifies the target session. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionMcpOauthCancelLoginRequest +internal sealed class SessionTasksRefreshRequest { - /// The same authoritative installation identity supplied during preparation. - [JsonPropertyName("expectedInstallationId")] - public string ExpectedInstallationId { get; set; } = string.Empty; - - /// Runtime-issued login handle known before the effectful login request begins. - [JsonPropertyName("loginId")] - public string LoginId { 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. +/// 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(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class McpOauthRespondResult +public sealed class TasksWaitForPendingResult { - /// 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. +/// Identifies the target session. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class McpOauthRespondRequest +internal sealed class SessionTasksWaitForPendingRequest { - /// 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(global::GitHub.Copilot.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. +/// Progress information for the task, discriminated by type. Returns null when no task with this ID is currently tracked. +/// Polymorphic base type discriminated by type. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] [JsonPolymorphic( - TypeDiscriminatorPropertyName = "kind", + TypeDiscriminatorPropertyName = "type", UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] -[JsonDerivedType(typeof(McpHeadersHandlePendingHeadersRefreshRequestHeaders), "headers")] -[JsonDerivedType(typeof(McpHeadersHandlePendingHeadersRefreshRequestNone), "none")] -[JsonDerivedType(typeof(McpHeadersHandlePendingHeadersRefreshRequestError), "error")] -public partial class McpHeadersHandlePendingHeadersRefreshRequest +[JsonDerivedType(typeof(TaskProgressAgent), "agent")] +[JsonDerivedType(typeof(TaskProgressClient), "client")] +[JsonDerivedType(typeof(TaskProgressShell), "shell")] +public partial class TaskProgress { /// The type discriminator. - [JsonPropertyName("kind")] - public virtual string Kind { get; set; } = string.Empty; + [JsonPropertyName("type")] + public virtual string Type { get; set; } = string.Empty; } -/// The headers variant of . +/// Timestamped display line for task progress output or recent agent activity. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class McpHeadersHandlePendingHeadersRefreshRequestHeaders : McpHeadersHandlePendingHeadersRefreshRequest +public sealed class TaskProgressLine +{ + /// Display message, e.g., "▸ bash", "✓ edit src/foo.ts". + [JsonPropertyName("message")] + public string Message { get; set; } = string.Empty; + + /// ISO 8601 timestamp when this event occurred. + [JsonPropertyName("timestamp")] + public DateTimeOffset Timestamp { get; set; } +} + +/// Progress snapshot for an agent task, with recent activity lines and optional latest intent. +/// The agent variant of . +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public partial class TaskProgressAgent : TaskProgress { /// [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; } + public override string Type => "agent"; - /// Optional lifetime in milliseconds for these returned headers. The runtime clamps its configured cache lifetime to this value. + /// The most recent intent reported by the agent. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("ttlMs")] - public long? TtlMs { get; set; } + [JsonPropertyName("latestIntent")] + public string? LatestIntent { get; set; } + + /// Recent tool execution events converted to display lines. + [JsonPropertyName("recentActivity")] + public required IList RecentActivity { get; set; } } -/// The none variant of . +/// Generic progress for a client-owned task. +/// The client variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class McpHeadersHandlePendingHeadersRefreshRequestNone : McpHeadersHandlePendingHeadersRefreshRequest +public partial class TaskProgressClient : TaskProgress { /// [JsonIgnore] - public override string Kind => "none"; + public override string Type => "client"; + + /// Most recent nonempty progress message. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("lastMessage")] + public string? LastMessage { get; set; } + + /// Current completion percentage from zero through one hundred. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("percentage")] + public double? Percentage { get; set; } + + /// Current owner-defined progress phase. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("phase")] + public string? Phase { get; set; } + + /// Recent server-timestamped progress messages. + [JsonPropertyName("recentActivity")] + public required IList RecentActivity { get; set; } + + /// Sequence number of the latest accepted owner update. + [JsonPropertyName("sequence")] + public required long Sequence { get; set; } + + /// Current client task lifecycle status. + [JsonPropertyName("status")] + public required TaskClientStatus Status { get; set; } + + /// ISO 8601 timestamp of the latest accepted lifecycle change. + [JsonPropertyName("updatedAt")] + public required DateTimeOffset UpdatedAt { get; set; } } -/// The error variant of . +/// Progress snapshot for a shell task, with recent stdout/stderr output and optional process ID. +/// The shell variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class McpHeadersHandlePendingHeadersRefreshRequestError : McpHeadersHandlePendingHeadersRefreshRequest +public partial class TaskProgressShell : TaskProgress { /// [JsonIgnore] - public override string Kind => "error"; + public override string Type => "shell"; - /// Host credential broker failure, denial, or revocation reason. - [JsonPropertyName("message")] - public required string Message { get; set; } + /// Process ID when available. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("pid")] + public long? Pid { get; set; } + + /// Recent stdout/stderr lines from the running shell command. + [JsonPropertyName("recentOutput")] + public required string RecentOutput { get; set; } } -/// MCP headers refresh request id and the host response. +/// Progress information for the task, or null when no task with that ID is tracked. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class McpHeadersHandlePendingHeadersRefreshRequestRequest +public sealed class TasksGetProgressResult { - /// Headers refresh request identifier from mcp.headers_refresh_required. - [JsonPropertyName("requestId")] - public string RequestId { get; set; } = string.Empty; + /// Progress information for the task, discriminated by type. Returns null when no task with this ID is currently tracked. + [JsonPropertyName("progress")] + public TaskProgress? Progress { get; set; } +} - /// Host response: supply dynamic headers or decline this refresh. - [JsonPropertyName("result")] - public McpHeadersHandlePendingHeadersRefreshRequest Result { get => field ??= new(); set; } +/// Identifier of the background task to fetch progress for. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class TasksGetProgressRequest +{ + /// Task identifier (agent ID or shell ID). + [JsonPropertyName("id")] + public string Id { get; set; } = string.Empty; /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// MCP Apps resource content with URI, optional MIME type, text or base64 blob, and resource metadata. +/// The first sync-waiting task that can currently be promoted to background mode. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class McpAppsResourceContent +public sealed class TasksGetCurrentPromotableResult { - /// 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 first sync-waiting task (agent first, then shell) that can currently be promoted to background mode. Omitted if no such task exists. The returned task is guaranteed to have executionMode='sync' and canPromoteToBackground=true at the time of the call. + [JsonPropertyName("task")] + public TaskInfo? Task { get; set; } +} - /// The resource URI (typically ui://...). - [JsonPropertyName("uri")] - public string Uri { get; set; } = string.Empty; +/// Identifies the target session. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class SessionTasksGetCurrentPromotableRequest +{ + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } -/// Resource contents returned by the MCP server. +/// Indicates whether the task was successfully promoted to background mode. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class McpAppsReadResourceResult +public sealed class TasksPromoteToBackgroundResult { - /// Resource contents returned by the server. - [JsonPropertyName("contents")] - public IList Contents { get => field ??= []; set; } + /// Whether the task was successfully promoted to background mode. + [JsonPropertyName("promoted")] + public bool Promoted { get; set; } } -/// MCP server and resource URI to fetch. +/// Identifier of the task to promote to background mode. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class McpAppsReadResourceRequest +internal sealed class TasksPromoteToBackgroundRequest { - /// 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; + /// Task identifier. + [JsonPropertyName("id")] + public string Id { 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. +/// The promoted task as it now exists in background mode, omitted if no promotable task was waiting. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class McpAppsListToolsResult +public sealed class TasksPromoteCurrentToBackgroundResult { - /// App-callable tools from the server. - [JsonPropertyName("tools")] - public IList> Tools { get => field ??= []; 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; } } -/// MCP server to list app-callable tools for. +/// Identifies the target session. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class McpAppsListToolsRequest +internal sealed class SessionTasksPromoteCurrentToBackgroundRequest { - /// **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. +/// Indicates whether the background task was successfully cancelled. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class McpAppsCallToolRequest +public sealed class TasksCancelResult { - /// 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; + /// Whether the task was successfully cancelled. + [JsonPropertyName("cancelled")] + public bool Cancelled { get; set; } +} - /// MCP server hosting the tool. - [RegularExpression("^[^\\x00-\\x1f/\\x7f-\\x9f}]+(?:\\/[^\\x00-\\x1f/\\x7f-\\x9f}]+)*$")] - [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Safe for generated string properties: JSON Schema minLength/maxLength map to string length validation, not reflection over trimmed Count members")] - [MinLength(1)] - [JsonPropertyName("serverName")] - public string ServerName { get; set; } = string.Empty; +/// Identifier of the background task to cancel. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class TasksCancelRequest +{ + /// Task identifier. + [JsonPropertyName("id")] + public string Id { 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. +/// Indicates whether the task was removed. False when the task does not exist or is still running/idle. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class McpAppsSetHostContextDetails +public sealed class TasksRemoveResult { - /// 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; } + /// 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; } } -/// Host context to advertise to MCP App guests. +/// Identifier of the completed or cancelled task to remove from tracking. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class McpAppsSetHostContextRequest +internal sealed class TasksRemoveRequest { - /// Host context advertised to MCP App guests. - [JsonPropertyName("context")] - public McpAppsSetHostContextDetails Context { get => field ??= new(); set; } + /// Task identifier. + [JsonPropertyName("id")] + public string Id { get; set; } = string.Empty; /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Current host context. +/// Indicates whether the message was delivered, with an error message when delivery failed. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class McpAppsHostContextDetails +public sealed class TasksSendMessageResult { - /// Display modes the host supports. - [JsonPropertyName("availableDisplayModes")] - public IList? AvailableDisplayModes { get; set; } + /// Error message if delivery failed. + [JsonPropertyName("error")] + public string? Error { get; set; } - /// Current display mode (SEP-1865). - [JsonPropertyName("displayMode")] - public McpAppsHostContextDetailsDisplayMode? DisplayMode { get; set; } + /// Whether the message was successfully delivered or steered. + [JsonPropertyName("sent")] + public bool Sent { get; set; } +} - /// BCP-47 locale, e.g. 'en-US'. - [JsonPropertyName("locale")] - public string? Locale { get; set; } +/// Identifier of the target agent task, message content, and optional sender agent ID. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class TasksSendMessageRequest +{ + /// Agent ID of the sender, if sent on behalf of another agent. + [JsonPropertyName("fromAgentId")] + public string? FromAgentId { get; set; } - /// Platform type for responsive design. - [JsonPropertyName("platform")] - public McpAppsHostContextDetailsPlatform? Platform { get; set; } + /// Agent task identifier. + [JsonPropertyName("id")] + public string Id { get; set; } = string.Empty; - /// UI theme preference per SEP-1865. - [JsonPropertyName("theme")] - public McpAppsHostContextDetailsTheme? Theme { get; set; } + /// Message content to send to the agent. + [JsonPropertyName("message")] + public string Message { get; set; } = string.Empty; - /// IANA timezone, e.g. 'America/New_York'. - [JsonPropertyName("timeZone")] - public string? TimeZone { get; set; } + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} - /// Host application identifier. - [JsonPropertyName("userAgent")] - public string? UserAgent { get; set; } +/// Skill metadata available to a session, with name, description, source, enabled/invocable state, path, plugin, and argument hint. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public sealed class Skill +{ + /// Optional freeform hint describing the skill's expected arguments, from the `argument-hint` frontmatter field. + [JsonPropertyName("argumentHint")] + public string? ArgumentHint { get; set; } + + /// Canonical slash command name used to invoke the skill, without the leading '/'. + [JsonPropertyName("commandName")] + public string? CommandName { get; set; } + + /// Description of what the skill does. + [JsonPropertyName("description")] + public string Description { get; set; } = string.Empty; + + /// Whether the skill is currently enabled. + [JsonPropertyName("enabled")] + public bool Enabled { get; set; } + + /// Unique identifier for the skill. + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; + + /// Absolute path to the skill file. + [JsonPropertyName("path")] + public string? Path { get; set; } + + /// Name of the plugin that provides the skill, when source is 'plugin'. + [JsonPropertyName("pluginName")] + public string? PluginName { get; set; } + + /// Source location type (e.g., project, personal-copilot, plugin, builtin). + [JsonPropertyName("source")] + public SkillSource Source { get; set; } + + /// Whether the skill can be invoked by the user as a slash command. + [JsonPropertyName("userInvocable")] + public bool UserInvocable { get; set; } } -/// Current host context advertised to MCP App guests. +/// Skills available to the session, with their enabled state. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class McpAppsHostContext +public sealed class SkillList { - /// Current host context. - [JsonPropertyName("context")] - public McpAppsHostContextDetails Context { get => field ??= new(); set; } + /// Available skills. + [JsonPropertyName("skills")] + public IList Skills { get => field ??= []; set; } } /// Identifies the target session. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionMcpAppsGetHostContextRequest +internal sealed class SessionSkillsListRequest { /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Capability negotiation snapshot. +/// Skill invocation record with name, path, content, allowed tools, and turn number. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class McpAppsDiagnoseCapability +public sealed class SkillsInvokedSkill { - /// 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; } + /// Tools that should be auto-approved when this skill is active, captured at invocation time. + [JsonPropertyName("allowedTools")] + public IList? AllowedTools { get; set; } - /// Whether the session has the `mcp-apps` capability. - [JsonPropertyName("sessionHasMcpApps")] - public bool SessionHasMcpApps { get; set; } -} + /// Full content of the skill file. + [JsonPropertyName("content")] + public string Content { get; set; } = string.Empty; -/// What the server returned for this session. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class McpAppsDiagnoseServer -{ - /// Whether the named server is currently connected. - [JsonPropertyName("connected")] - public bool Connected { get; set; } + /// Whether model invocation was disabled when this skill was invoked. + [JsonPropertyName("disableModelInvocation")] + public bool? DisableModelInvocation { get; set; } - /// Up to 5 tool names with `_meta.ui` for quick inspection. - [JsonPropertyName("sampleToolNames")] - public IList SampleToolNames { get => field ??= []; set; } + /// Turn number when the skill was invoked. + [JsonPropertyName("invokedAtTurn")] + public long InvokedAtTurn { get; set; } - /// Total tools returned by the server's tools/list. - [JsonPropertyName("toolCount")] - public double ToolCount { get; set; } + /// Unique identifier for the skill. + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; - /// Tools whose `_meta.ui` is populated (resourceUri and/or visibility set). - [JsonPropertyName("toolsWithUiMeta")] - public double ToolsWithUiMeta { get; set; } + /// 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; } -/// Diagnostic snapshot of MCP Apps wiring for the named server. +/// Skills invoked during this session, ordered by invocation time (most recent last). [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class McpAppsDiagnoseResult +public sealed class SkillsGetInvokedResult { - /// Capability negotiation snapshot. - [JsonPropertyName("capability")] - public McpAppsDiagnoseCapability Capability { get => field ??= new(); set; } + /// Skills invoked during this session, ordered by invocation time (most recent last). + [JsonPropertyName("skills")] + public IList Skills { get => field ??= []; set; } +} - /// What the server returned for this session. - [JsonPropertyName("server")] - public McpAppsDiagnoseServer Server { get => field ??= new(); set; } +/// Identifies the target session. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class SessionSkillsGetInvokedRequest +{ + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } -/// MCP server to diagnose MCP Apps wiring for. +/// Name of the skill to enable for the session. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class McpAppsDiagnoseRequest +internal sealed class SkillsEnableRequest { - /// 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; + /// 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; } -/// MCP resource content with URI, optional MIME type, text or base64 blob, and resource metadata. +/// Name of the skill to disable for the session. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class McpResourceContent +internal sealed class SkillsDisableRequest { - /// Resource-level metadata (CSP, permissions, etc.). - [JsonPropertyName("_meta")] - public IDictionary? Meta { get; set; } - - /// Base64-encoded binary content. - [JsonPropertyName("blob")] - public string? Blob { get; set; } + /// Name of the skill to disable. + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; - /// MIME type of the content. - [JsonPropertyName("mimeType")] - public string? MimeType { get; set; } + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} - /// Text content (e.g. HTML). - [JsonPropertyName("text")] - public string? Text { get; set; } +/// Diagnostics from reloading skill definitions, with warnings and errors as separate lists. +[Experimental(global::GitHub.Copilot.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; } - /// The resource URI. - [JsonPropertyName("uri")] - public string Uri { get; set; } = string.Empty; + /// Warnings emitted while loading skills (e.g. skills that loaded but had issues). + [JsonPropertyName("warnings")] + public IList Warnings { get => field ??= []; set; } } -/// Resource contents returned by the MCP server. +/// Identifies the target session. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class McpResourcesReadResult +internal sealed class SessionSkillsReloadRequest { - /// Resource contents returned by the server. - [JsonPropertyName("contents")] - public IList Contents { get => field ??= []; set; } + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } -/// MCP server and resource URI to fetch. +/// Identifies the target session. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class McpResourcesReadRequest +internal sealed class SessionSkillsEnsureLoadedRequest { - /// 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. +/// The IDE a host is connected to, as reported to the session. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class McpResourceAnnotations +public sealed class SessionConnectedIdeInfo { - /// 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; } + /// Display name of the connected IDE, for example `VS Code`. + [JsonPropertyName("ideName")] + public string IdeName { get; set; } = string.Empty; - /// Priority hint for model/client use. - [JsonPropertyName("priority")] - public double? Priority { get; set; } + /// Absolute path of the workspace folder the IDE has open. + [JsonPropertyName("workspaceFolder")] + public string WorkspaceFolder { get; set; } = string.Empty; } -/// A resource icon descriptor plus preserved non-standard icon fields. +/// Records which IDE the host is connected to, or clears it. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class McpResourceIcon +internal sealed class SessionMcpSetConnectedIdeInfoParams { - /// 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; + /// The connected IDE. Null or omitted clears the recorded IDE, which is how a host reports that it is disconnected. + [JsonPropertyName("ide")] + public SessionConnectedIdeInfo? Ide { get; set; } - /// Theme hint for this icon. - [JsonPropertyName("theme")] - public string? Theme { get; set; } + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } -/// An MCP resource descriptor (spec `Resource`): URI, name, and optional title, description, MIME type, size, icons, annotations, and metadata. Server-provided fields outside the standard descriptor shape are exposed under `additionalProperties`. +/// Recorded MCP server connection failure. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class McpResource +public sealed class McpServerFailureInfo { - /// Resource-level metadata. - [JsonPropertyName("_meta")] - public IDictionary? Meta { get; set; } + /// Failure message produced when the MCP server connection failed. + [JsonPropertyName("message")] + public string Message { get; set; } = string.Empty; - /// Server-provided non-standard descriptor fields preserved from the MCP response. - [JsonPropertyName("additionalProperties")] - public IDictionary? AdditionalProperties { get; set; } + /// epoch-ms timestamp at which the failure was recorded. + [JsonPropertyName("timestamp")] + public long Timestamp { get; set; } +} - /// Model/client annotations associated with this resource. - [JsonPropertyName("annotations")] - public McpResourceAnnotations? Annotations { get; set; } +/// Recorded MCP server pending-auth state. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public sealed class McpServerNeedsAuthInfo +{ + /// epoch-ms timestamp at which the server signalled it needs authentication. + [JsonPropertyName("timestamp")] + public long Timestamp { get; set; } +} - /// Optional description of what this resource represents. - [JsonPropertyName("description")] - public string? Description { get; set; } +/// Host-level state, omitted when no MCP host is initialized. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public sealed class McpHostState +{ + /// Names of currently-connected MCP clients. + [JsonPropertyName("clients")] + public IList Clients { get => field ??= []; set; } - /// Icons associated with this resource. - [JsonPropertyName("icons")] - public IList? Icons { get; set; } + /// Configured servers that are explicitly disabled. + [JsonPropertyName("disabledServers")] + public IList DisabledServers { get => field ??= []; set; } - /// MIME type of the resource, if known. - [JsonPropertyName("mimeType")] - public string? MimeType { get; set; } + /// Map of server name to recorded connection failure. + [JsonPropertyName("failedServers")] + public IDictionary FailedServers { get => field ??= new Dictionary(); set; } - /// The programmatic name of the resource. - [JsonPropertyName("name")] - public string Name { get; set; } = string.Empty; + /// Configured servers filtered out by MCP server policy. + [JsonPropertyName("filteredServers")] + public IList FilteredServers { get => field ??= []; set; } - /// Resource size in bytes, when known. - [JsonPropertyName("size")] - public long? Size { get; set; } + /// Whether third-party MCP servers are policy-enabled for this session. + [JsonPropertyName("mcp3pEnabled")] + public bool Mcp3pEnabled { get; set; } - /// Optional human-readable display title. - [JsonPropertyName("title")] - public string? Title { get; set; } + /// Map of server name to recorded pending-auth state. + [JsonPropertyName("needsAuthServers")] + public IDictionary NeedsAuthServers { get => field ??= new Dictionary(); set; } - /// The resource URI (e.g. ui://... or file:///...). - [JsonPropertyName("uri")] - public string Uri { get; set; } = string.Empty; + /// Names of servers with in-flight connection attempts. + [JsonPropertyName("pendingConnections")] + public IList PendingConnections { get => field ??= []; set; } } -/// One page of resources advertised by the named MCP server. +/// Owned installation that a listed MCP server's live configuration came from. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class McpResourcesListResult +public sealed class McpServerOwnership { - /// 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; } + /// Stable installation identifier from the owned installation receipt. + [JsonPropertyName("installationId")] + public string InstallationId { get; set; } = string.Empty; } -/// MCP server whose resources to enumerate. +/// MCP server status entry, including config source/plugin source and any connection error. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class McpResourcesListRequest +public sealed class McpServer { - /// Opaque MCP pagination cursor from a prior `nextCursor` value. - [JsonPropertyName("cursor")] - public string? Cursor { get; set; } + /// Human-readable display name supplied by a managed server catalog. + [JsonPropertyName("displayName")] + public string? DisplayName { get; set; } - /// Name of the MCP server whose resources to enumerate. + /// Error message if the server failed to connect. + [JsonPropertyName("error")] + public string? Error { get; set; } + + /// Server name (config key). [RegularExpression("^[^\\x00-\\x1f/\\x7f-\\x9f}]+(?:\\/[^\\x00-\\x1f/\\x7f-\\x9f}]+)*$")] [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Safe for generated string properties: JSON Schema minLength/maxLength map to string length validation, not reflection over trimmed Count members")] [MinLength(1)] - [JsonPropertyName("serverName")] - public string ServerName { get; set; } = string.Empty; - - /// 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(global::GitHub.Copilot.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; } + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; - /// Model/client annotations associated with this template. - [JsonPropertyName("annotations")] - public McpResourceAnnotations? Annotations { get; set; } + /// Owned installation this entry's live configuration came from. Absent for manual, workspace, plugin, builtin and same-name servers, and on runtimes without owned installations. + [JsonPropertyName("owned")] + public McpServerOwnership? Owned { get; set; } - /// Optional description of what this template is for. - [JsonPropertyName("description")] - public string? Description { get; set; } + /// Server-advertised metadata for a connected server. Omitted when no live connection metadata is available, including while pending or when failed, disabled, stopped, or not configured. + [JsonPropertyName("serverMetadata")] + public McpServerMetadata? ServerMetadata { get; set; } - /// Icons associated with resources matching this template. - [JsonPropertyName("icons")] - public IList? Icons { get; set; } + /// Configuration source: user, workspace, plugin, builtin, or managed. + [JsonPropertyName("source")] + public McpServerSource? Source { get; set; } - /// MIME type for resources matching this template, if uniform. - [JsonPropertyName("mimeType")] - public string? MimeType { get; set; } + /// Plugin name that provided this server, when source is plugin. + [JsonPropertyName("sourcePlugin")] + public string? SourcePlugin { get; set; } - /// The programmatic name of the resource template. - [JsonPropertyName("name")] - public string Name { get; set; } = string.Empty; + /// Plugin version that provided this server, when source is plugin. + [JsonPropertyName("sourcePluginVersion")] + public string? SourcePluginVersion { get; set; } - /// Optional human-readable display title. - [JsonPropertyName("title")] - public string? Title { get; set; } + /// Connection status: connected, failed, needs-auth, pending, disabled, stopped, or not_configured. + [JsonPropertyName("status")] + public McpServerStatus Status { get; set; } - /// An RFC 6570 URI template for constructing resource URIs. - [JsonPropertyName("uriTemplate")] - public string UriTemplate { get; set; } = string.Empty; + /// Configured URL for an HTTP/SSE server, regardless of configuration source. Omitted for local and in-memory servers. + [JsonPropertyName("url")] + public string? Url { get; set; } } -/// One page of resource templates advertised by the named MCP server. +/// MCP servers configured for the session, with their connection status and host-level state. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class McpResourcesListTemplatesResult +public sealed class McpServerList { - /// Opaque cursor for the next page, if the server has more resource templates. - [JsonPropertyName("nextCursor")] - public string? NextCursor { get; set; } + /// Host-level state, omitted when no MCP host is initialized. + [JsonPropertyName("host")] + public McpHostState? Host { get; set; } - /// Resource templates advertised by the server (proxied MCP `resources/templates/list`). - [JsonPropertyName("resourceTemplates")] - public IList ResourceTemplates { get => field ??= []; set; } + /// Configured MCP servers. + [JsonPropertyName("servers")] + public IList Servers { get => field ??= []; set; } } -/// MCP server whose resource templates to enumerate. +/// Identifies the target session. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class McpResourcesListTemplatesRequest +internal sealed class SessionMcpListRequest { - /// 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; } -/// An argument accepted by an MCP prompt. +/// Observational state for a matching already materialized MCP server. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class McpPromptArgument +public sealed class McpConfiguredServerState { - /// Argument-level metadata. - [JsonPropertyName("_meta")] - public IDictionary? Meta { get; set; } - - /// Server-provided non-standard argument fields. - [JsonPropertyName("additionalProperties")] - public IDictionary? AdditionalProperties { get; set; } + /// Observed connection error, when the materialized server failed. + [JsonPropertyName("error")] + public string? Error { get; set; } - /// Description of the argument. - [JsonPropertyName("description")] - public string? Description { get; set; } + /// Observed connection status. This is not a configuration or readiness guarantee. + [JsonPropertyName("status")] + public McpServerStatus Status { get; set; } +} - /// Name of the argument. - [JsonPropertyName("name")] - public string Name { get; set; } = string.Empty; - - /// Whether the argument is required; omission is distinct from false. - [JsonPropertyName("required")] - public bool? Required { get; set; } -} - -/// An MCP prompt icon with standard size hints and preserved non-standard fields. +/// Effective MCP configuration entry. Configuration enablement is distinct from the optional live observation. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class McpPromptIcon +public sealed class McpConfiguredServer { - /// Server-provided non-standard icon fields. - [JsonPropertyName("additionalProperties")] - public IDictionary? AdditionalProperties { get; set; } + /// Human-readable display name supplied by configuration. + [JsonPropertyName("displayName")] + public string? DisplayName { get; set; } - /// Icon MIME type, when known. - [JsonPropertyName("mimeType")] - public string? MimeType { get; set; } + /// Whether this configured server is enabled after session configuration and policy filtering. + [JsonPropertyName("enabled")] + public bool Enabled { get; set; } - /// Icon sizes, such as `48x48` or `any`. - [JsonPropertyName("sizes")] - public IList? Sizes { get; set; } + /// Observed state from an already materialized matching server. Omitted when no live graph has this configured server; it never determines configuration enablement. + [JsonPropertyName("live")] + public McpConfiguredServerState Live { get => field ??= new(); set; } - /// Icon URI. - [JsonPropertyName("src")] - public string Src { get; set; } = string.Empty; + /// 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; - /// Theme hint for this icon. - [JsonPropertyName("theme")] - public string? Theme { get; set; } + /// Configuration provenance: user, workspace, plugin, builtin, or managed. + [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; } } -/// An MCP prompt descriptor. Server-provided non-standard fields are exposed under `additionalProperties`. +/// Effective MCP configuration with optional live observations from matching already materialized servers. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class McpPrompt +public sealed class McpConfiguredServerList { - /// Prompt-level metadata. - [JsonPropertyName("_meta")] - public IDictionary? Meta { get; set; } + /// Effective configured MCP servers. + [JsonPropertyName("servers")] + public IList Servers { get => field ??= []; set; } +} - /// Server-provided non-standard descriptor fields. - [JsonPropertyName("additionalProperties")] - public IDictionary? AdditionalProperties { get; set; } +/// Identifies the target session. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class SessionMcpListConfiguredRequest +{ + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} - /// Arguments accepted by the prompt. - [JsonPropertyName("arguments")] - public IList? Arguments { get; set; } +/// Normalized MCP Apps discovery metadata from a tool's `_meta.ui` block. +[Experimental(global::GitHub.Copilot.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; } - /// Description of what this prompt provides. + /// 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(global::GitHub.Copilot.Diagnostics.Experimental)] +public sealed class McpTools +{ + /// Tool description, when provided. [JsonPropertyName("description")] public string? Description { get; set; } - /// Icons associated with this prompt. - [JsonPropertyName("icons")] - public IList? Icons { get; set; } - - /// The programmatic name of the prompt. + /// Tool name. [JsonPropertyName("name")] public string Name { get; set; } = string.Empty; - /// Human-readable display title. - [JsonPropertyName("title")] - public string? Title { get; set; } + /// Normalized MCP Apps discovery metadata. An empty object indicates that a valid `_meta.ui` block was present without recognized fields. + [JsonPropertyName("ui")] + public McpToolUi? Ui { get; set; } } -/// One page of prompts advertised by the named MCP server. +/// Tools exposed by the connected MCP server. Throws when the server is not connected. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class McpPromptsListResult +public sealed class McpListToolsResult { - /// MCP result metadata. - [JsonPropertyName("_meta")] - public IDictionary? Meta { get; set; } - - /// Server-provided non-standard result fields. - [JsonPropertyName("additionalProperties")] - public IDictionary? AdditionalProperties { get; set; } - - /// Opaque cursor for the next page, if the server has more prompts. - [JsonPropertyName("nextCursor")] - public string? NextCursor { get; set; } - - /// Prompts advertised by the server. - [JsonPropertyName("prompts")] - public IList Prompts { get => field ??= []; set; } + /// Tools exposed by the server. + [JsonPropertyName("tools")] + public IList Tools { get => field ??= []; set; } } -/// MCP server whose prompts to enumerate. +/// Server name whose tool list should be returned. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class McpPromptsListRequest +internal sealed class McpListToolsRequest { - /// Opaque MCP pagination cursor from a prior `nextCursor` value. - [JsonPropertyName("cursor")] - public string? Cursor { get; set; } - - /// Name of the MCP server whose prompts to enumerate. + /// 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)] @@ -18507,63 +19196,67 @@ internal sealed class McpPromptsListRequest public string SessionId { get; set; } = string.Empty; } -/// An MCP prompt message with opaque JSON content preserved without flattening or content-type filtering. +/// Name of the MCP server to enable for the session. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class McpPromptMessage +public sealed class McpEnableRequest { - /// Message-level metadata. - [JsonPropertyName("_meta")] - public IDictionary? Meta { get; set; } - - /// Server-provided non-standard message fields. - [JsonPropertyName("additionalProperties")] - public IDictionary? AdditionalProperties { get; set; } - - /// The original MCP content block, including nested metadata and unfamiliar content types. - [JsonPropertyName("content")] - public JsonElement Content { get; set; } + /// Exact receipt identity for explicit owned activation in this session. + [JsonPropertyName("expectedInstallationId")] + public string? ExpectedInstallationId { get; set; } - /// The role of the message sender. - [JsonPropertyName("role")] - public McpPromptRole Role { get; set; } + /// 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 required string ServerName { get; set; } } -/// Prompt messages returned by the MCP server without sending them to the model. +/// Name of the MCP server to enable for the session. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class McpPromptsGetResult +internal sealed class McpEnableRequestWithSession { - /// MCP result metadata. - [JsonPropertyName("_meta")] - public IDictionary? Meta { get; set; } - - /// Server-provided non-standard result fields. - [JsonPropertyName("additionalProperties")] - public IDictionary? AdditionalProperties { get; set; } + /// Exact receipt identity for explicit owned activation in this session. + [JsonPropertyName("expectedInstallationId")] + public string? ExpectedInstallationId { get; set; } - /// Description of the prompt. - [JsonPropertyName("description")] - public string? Description { get; set; } + /// 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; - /// Ordered prompt messages. - [JsonPropertyName("messages")] - public IList Messages { get => field ??= []; set; } + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } -/// MCP server, prompt name, and optional string-valued arguments. +/// Name of the MCP server to disable for the session. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class McpPromptsGetRequest +public sealed class McpDisableRequest { - /// String-valued arguments to pass to the prompt. - [JsonPropertyName("arguments")] - public IDictionary? Arguments { get; set; } + /// Required for an owned installation; omission preserves only manual-server behaviour. + [JsonPropertyName("expectedInstallationId")] + public string? ExpectedInstallationId { get; set; } - /// The programmatic name of the prompt. + /// 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("promptName")] - public string PromptName { get; set; } = string.Empty; + [JsonPropertyName("serverName")] + public required string ServerName { get; set; } +} - /// Name of the MCP server hosting the prompt. +/// Name of the MCP server to disable for the session. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class McpDisableRequestWithSession +{ + /// Required for an owned installation; omission preserves only manual-server behaviour. + [JsonPropertyName("expectedInstallationId")] + public string? ExpectedInstallationId { get; set; } + + /// 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)] @@ -18575,1683 +19268,4135 @@ internal sealed class McpPromptsGetRequest public string SessionId { get; set; } = string.Empty; } -/// MCP diagnostic source configuration. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class McpDiagnosticSourceConfiguration -{ - /// Threshold to apply to MCP diagnostic producers in this session. - [JsonPropertyName("level")] - public DiagnosticLogLevel Level { get; set; } -} - -/// Typed diagnostic source configuration. At least one source is required by diagnostics configuration methods. +/// Identifies the target session. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class DiagnosticSourcesConfiguration +internal sealed class SessionMcpReloadRequest { - /// MCP diagnostic capture threshold. Omit to leave the current threshold unchanged. - [JsonPropertyName("mcp")] - public McpDiagnosticSourceConfiguration? Mcp { get; set; } + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } -/// Per-source session diagnostics configuration. +/// Result of moving in-flight MCP loading to the background. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class DiagnosticsConfiguration +public sealed class MoveMcpLoadingToBackgroundResult { - /// Diagnostic thresholds keyed by supported source. - [JsonPropertyName("sources")] - public DiagnosticSourcesConfiguration Sources { get => field ??= new(); set; } + /// 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; } } -/// Patch session diagnostic thresholds for explicitly supplied sources. +/// Identifies the target session. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class DiagnosticsConfigureRequest +internal sealed class SessionMcpMoveLoadingToBackgroundRequest { /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; - - /// Sources to configure. At least one supported source must be supplied. - [JsonPropertyName("sources")] - public DiagnosticSourcesConfiguration Sources { get => field ??= new(); set; } } -/// One retained session-scoped diagnostic record. Potentially content-bearing diagnostic data is opt-in and must not be exported automatically as telemetry. -/// Polymorphic base type discriminated by source. +/// MCP server allowed by policy, with server name and optional PII-free explanatory note. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -[JsonPolymorphic( - TypeDiscriminatorPropertyName = "source", - UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] -[JsonDerivedType(typeof(DiagnosticEntryMcp), "mcp")] -public partial class DiagnosticEntry +public sealed class McpAllowedServer { - /// The type discriminator. - [JsonPropertyName("source")] - public virtual string Source { get; set; } = string.Empty; -} + /// 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-specific detail for a source-discriminated diagnostic entry. +/// MCP server whose connection attempt failed. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class McpDiagnosticDetails +public sealed class McpFailedServer { - /// Fresh identifier for the MCP connection attempt, including failed starts. - [JsonPropertyName("connectionId")] - public string ConnectionId { get; set; } = string.Empty; - - /// Serialized diagnostic detail. Protocol and HTTP records use JSON when detail is present. - [JsonPropertyName("data")] - public string? Data { get; set; } - - /// Protocol-frame direction when kind is protocol. - [JsonPropertyName("direction")] - public McpDiagnosticDirection? Direction { get; set; } - - /// Diagnostic record category. - [JsonPropertyName("kind")] - public McpDiagnosticKind Kind { get; set; } + /// The captured connection failure detail. + [JsonPropertyName("error")] + public string? Error { get; set; } - /// Configured MCP server name. - [JsonPropertyName("serverName")] - public string ServerName { get; set; } = string.Empty; + /// The config key of the server that failed to connect. + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; } -/// The mcp variant of . +/// MCP server filtered by policy, with name, reason, and optional redacted reason. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class DiagnosticEntryMcp : DiagnosticEntry +public sealed class McpFilteredServer { - /// - [JsonIgnore] - public override string Source => "mcp"; - - /// Agent identifier for a subagent host. Omitted for the root agent. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("agentId")] - public string? AgentId { get; set; } - - /// Typed MCP diagnostic detail. - [JsonPropertyName("details")] - public required McpDiagnosticDetails Details { get; set; } - - /// Severity of this emitted diagnostic record. - [JsonPropertyName("level")] - public required DiagnosticSeverity Level { get; set; } - - /// Human-readable diagnostic summary. - [JsonPropertyName("message")] - public required string Message { get; set; } + /// 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")] +#else + [Obsolete("This member is deprecated and will be removed in a future version.")] +#endif + [JsonPropertyName("enterpriseName")] + public string? EnterpriseName { get; set; } - /// Original byte count when a known-size message or data value was truncated. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("originalBytes")] - public long? OriginalBytes { get; set; } + /// Filtered server name. + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; - /// UTC RFC 3339 timestamp captured at the diagnostic source. - [JsonPropertyName("timestamp")] - public required string Timestamp { get; set; } + /// Human-readable filter reason. + [JsonPropertyName("reason")] + public string Reason { get; set; } = string.Empty; - /// Whether message or data was truncated to the record-size bound. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("truncated")] - public bool? Truncated { get; set; } + /// PII-free filter reason. + [JsonPropertyName("redactedReason")] + public string? RedactedReason { get; set; } } -/// One cursor-addressed page of retained session diagnostics. +/// MCP server startup filtering result. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class DiagnosticsReadResult +internal sealed class McpStartServersResult { - /// Opaque cursor for the next independent read. - [JsonPropertyName("cursor")] - public string Cursor { get; set; } = string.Empty; - - /// Whether the requested cursor remained within the retained buffer window. - [JsonPropertyName("cursorStatus")] - public DiagnosticCursorStatus CursorStatus { get; set; } - - /// Number of records lost before this page when known. Omitted when a buffer generation change makes the count unknowable. - [JsonPropertyName("droppedCount")] - public long? DroppedCount { get; set; } + /// Non-default servers allowed by policy. + [JsonPropertyName("allowedServers")] + public IList? AllowedServers { get; set; } - /// Retained records beginning at the requested cursor. - [JsonPropertyName("entries")] - public IList Entries { get => field ??= []; set; } + /// Servers whose connection attempt failed. + [JsonPropertyName("failedServers")] + public IList? FailedServers { get; set; } - /// Whether additional retained records follow this page. - [JsonPropertyName("hasMore")] - public bool HasMore { get; set; } + /// Servers filtered out before startup. + [JsonPropertyName("filteredServers")] + public IList FilteredServers { get => field ??= []; set; } } -/// Cursor-based request for session diagnostics. The default limit is 100 (maximum 500); the default waitMs is zero (maximum 30000). +/// Opaque MCP reload configuration. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class DiagnosticsReadRequest +internal sealed class McpReloadWithConfigRequest { - /// Opaque cursor returned by an earlier read. Omit to start at the oldest retained record. - [JsonPropertyName("cursor")] - public string? Cursor { get; set; } - - /// Maximum number of records to return, from 1 through 500. Omit for 100. - [JsonPropertyName("max")] - public long? Max { get; set; } - /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; - - /// Nonempty selection of sources to read. Each source may be listed once. - [JsonPropertyName("sources")] - public IList Sources { get => field ??= []; set; } - - /// Maximum time in milliseconds to wait for a new record, from 0 through 30000. - [JsonPropertyName("waitMs")] - public int? WaitMs { get; set; } } -/// Feature detection and hard polling limits for the EXPERIMENTAL session connector API. +/// Outcome of an MCP sampling execution: success result, failure error, or cancellation. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class ConnectorCapabilities +public sealed class McpSamplingExecutionResult { - /// Connector API contract version. - [JsonPropertyName("apiVersion")] - public long ApiVersion { get; set; } - - /// Current session availability. Disabled availability is reported without making a Connector request. - [JsonPropertyName("availability")] - public ConnectorAvailability Availability { get; set; } - - /// Whether connect and reconnect can return an opaque continuation for bounded consent polling. - [JsonPropertyName("consentContinuation")] - public bool ConsentContinuation { get; set; } + /// Outcome of the sampling inference. 'success' produced a response; 'failure' encountered an error (including agent-side rejection by content filter or criteria); 'cancelled' the caller cancelled this execution via cancelSamplingExecution. + [JsonPropertyName("action")] + public McpSamplingExecutionAction Action { get; set; } - /// Maximum accepted wall-clock deadline in milliseconds for one continuation call. - [JsonPropertyName("maxDeadlineMs")] - public long MaxDeadlineMs { get; set; } + /// Error description, present when action='failure'. + [JsonPropertyName("error")] + public string? Error { get; set; } - /// Maximum accepted polling attempts for one continuation call. - [JsonPropertyName("maxPollAttempts")] - public long MaxPollAttempts { 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 JsonElement? Result { get; set; } +} - /// Maximum accepted delay in milliseconds between polling attempts. - [JsonPropertyName("maxPollIntervalMs")] - public long MaxPollIntervalMs { get; set; } +/// Identifiers and raw MCP CreateMessageRequest params used to run a sampling inference. +[Experimental(global::GitHub.Copilot.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; } - /// Whether callers select a host-owned GitHub account through an opaque selection ID rather than supplying a provider token. - [JsonPropertyName("opaqueAccountSelection")] - public bool OpaqueAccountSelection { 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 JsonElement Request { get; set; } - /// Whether getAccount is supported. Absence means false. - [JsonPropertyName("sessionAccountSelection")] - public bool? SessionAccountSelection { 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; - /// Whether reconcile accepts forceConnectorName. Absence means false. - [JsonPropertyName("targetedReconcile")] - public bool? TargetedReconcile { get; set; } -} + /// Name of the MCP server that initiated the sampling request. + [JsonPropertyName("serverName")] + public string ServerName { get; set; } = string.Empty; -/// Identifies the target session. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionConnectorsGetCapabilitiesRequest -{ /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Credential-free identity metadata. +/// Indicates whether an in-flight sampling execution with the given requestId was found and cancelled. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class AuthIdentityMetadata +public sealed class McpCancelSamplingExecutionResult { - /// Identity host. - [JsonPropertyName("host")] - public string Host { get; set; } = string.Empty; - - /// User login. - [JsonPropertyName("login")] - public string Login { get; set; } = string.Empty; - - /// Authentication type. - [JsonPropertyName("type")] - public AuthInfoType Type { get; set; } + /// True if an in-flight execution with the given requestId was found and signalled to cancel. False when no such execution is in flight (already completed, never started, or cancelled by another caller). + [JsonPropertyName("cancelled")] + public bool Cancelled { get; set; } } -/// Session account selection. +/// The requestId previously passed to executeSampling that should be cancelled. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class ConnectorSessionAccount +internal sealed class McpCancelSamplingExecutionParams { - /// Opaque session-scoped account selection ID. - [JsonPropertyName("accountId")] - public string AccountId { get; set; } = string.Empty; - - /// Credential-free identity metadata. - [JsonPropertyName("authInfo")] - public AuthIdentityMetadata AuthInfo { get => field ??= new(); set; } -} + /// The requestId previously passed to executeSampling that should be cancelled. + [JsonPropertyName("requestId")] + public string RequestId { get; set; } = string.Empty; -/// Identifies the target session. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionConnectorsGetAccountRequest -{ /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Account-targeted authorization update required by the Connector service. The account ID is an opaque host routing identifier; no credential is included. +/// Env-value mode recorded on the session after the update. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class ConnectorAuthorizationRequirement +public sealed class McpSetEnvValueModeResult { - /// Exact opaque account selection that made the Connector request. - [JsonPropertyName("accountId")] - public string AccountId { get; set; } = string.Empty; - - /// Stable OAuth scope the selected account must grant. - [JsonPropertyName("scope")] - public ConnectorAuthorizationScope Scope { get; set; } + /// Mode recorded on the session after the update. + [JsonPropertyName("mode")] + public McpSetEnvValueModeDetails Mode { get; set; } } -/// Credential-free Connector catalog entry. +/// Mode controlling how MCP server env values are resolved (`direct` or `indirect`). [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class ConnectorCatalogEntry +internal sealed class McpSetEnvValueModeParams { - /// Untrusted service description, when present. - [JsonPropertyName("description")] - public string? Description { get; set; } - - /// Untrusted display label from the service. - [JsonPropertyName("displayName")] - public string DisplayName { get; set; } = string.Empty; - - /// Optional catalog logo. - [JsonPropertyName("logo")] - public string? Logo { get; set; } - - /// Canonical Connector name used by lifecycle methods. - [JsonPropertyName("name")] - public string Name { get; set; } = string.Empty; - - /// Optional catalog release tag. - [JsonPropertyName("releaseTag")] - public string? ReleaseTag { get; set; } - - /// Opaque stable runtime IDs currently projected into the session for this Connector. - [JsonPropertyName("runtimeServerIds")] - public IList RuntimeServerIds { get => field ??= []; set; } - - /// Current authoritative service connection state. - [JsonPropertyName("status")] - public ConnectorCatalogStatus Status { get; set; } + /// 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; } - /// Optional catalog tier. - [JsonPropertyName("tier")] - public string? Tier { get; set; } + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } -/// Validated Connector catalog snapshot cached by the session. +/// Indicates whether the auto-managed `github` MCP server was removed (false when nothing to remove). [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class ConnectorCatalogResult +public sealed class McpRemoveGitHubResult { - /// Validated catalog entries in service order. - [JsonPropertyName("connectors")] - public IList Connectors { get => field ??= []; set; } - - /// Unix epoch milliseconds when this snapshot was accepted. - [JsonPropertyName("refreshedAtMs")] - public long RefreshedAtMs { get; set; } - - /// Monotonically increasing session-local catalog revision. - [JsonPropertyName("revision")] - public long Revision { get; set; } + /// True when the auto-managed `github` MCP server was removed; false when no removal happened (e.g. user has explicitly configured a `github` server, or the server was not registered). + [JsonPropertyName("removed")] + public bool Removed { get; set; } } -/// Live status of one session-owned MCP projection. +/// Identifies the target session. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class ConnectorRuntimeStatus +internal sealed class SessionMcpRemoveGitHubRequest { - /// Canonical Connector name that owns this server. - [JsonPropertyName("connectorName")] - public string ConnectorName { get; set; } = string.Empty; - - /// Opaque runtime server ID. - [JsonPropertyName("runtimeServerId")] - public string RuntimeServerId { get; set; } = string.Empty; - - /// Current live MCP host status. - [JsonPropertyName("status")] - public ConnectorMcpStatus Status { get; set; } + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } -/// Authoritative session connector state. Account IDs are opaque routing identifiers and credentials are never included. +/// Result of configuring GitHub MCP. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class ConnectorStatus +internal sealed class McpConfigureGitHubResult { - /// Opaque account selection pinned to this session, when one has been selected. - [JsonPropertyName("accountId")] - public string? AccountId { get; set; } - - /// Connector API contract version. - [JsonPropertyName("apiVersion")] - public long ApiVersion { get; set; } - - /// Exact selected account and stable scope requiring an authorization update, when proven by the Connector service. - [JsonPropertyName("authorizationRequirement")] - public ConnectorAuthorizationRequirement? AuthorizationRequirement { get; set; } - - /// Current feature and session availability. - [JsonPropertyName("availability")] - public ConnectorAvailability Availability { get; set; } - - /// Latest validated catalog snapshot, when available. - [JsonPropertyName("catalog")] - public ConnectorCatalogResult? Catalog { get; set; } - - /// Number of active opaque connection continuations. - [JsonPropertyName("pendingConnections")] - public long PendingConnections { get; set; } - - /// Live MCP status for every Connector-owned runtime server. - [JsonPropertyName("runtimeServers")] - public IList RuntimeServers { get => field ??= []; set; } + /// Whether GitHub MCP configuration changed. + [JsonPropertyName("changed")] + public bool Changed { get; set; } } -/// Identifies the target session. +/// Credential-free authentication identity used to configure GitHub MCP. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionConnectorsGetStatusRequest +internal sealed class McpConfigureGitHubRequest { /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Pins a Connector operation to one host-owned GitHub account through its opaque selection ID. Provider tokens are never accepted. +/// 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(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class ConnectorAccountRequest +public sealed class McpStartServerRequest { - /// Opaque account selection ID previously returned by an account discovery API. - [JsonPropertyName("accountId")] - public string AccountId { get; set; } = string.Empty; + /// 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; } - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Exact receipt identity for explicit owned activation in this session. + [JsonPropertyName("expectedInstallationId")] + public string? ExpectedInstallationId { get; set; } + + /// Name of the MCP server to start. + [JsonPropertyName("serverName")] + public required string ServerName { get; set; } } -/// Typed result of initiating or continuing a Connector connection. -/// Polymorphic base type discriminated by kind. +/// 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(global::GitHub.Copilot.Diagnostics.Experimental)] -[JsonPolymorphic( - TypeDiscriminatorPropertyName = "kind", - UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] -[JsonDerivedType(typeof(ConnectorConnectResultConnected), "connected")] -[JsonDerivedType(typeof(ConnectorConnectResultConsentRequired), "consent_required")] -[JsonDerivedType(typeof(ConnectorConnectResultPending), "pending")] -public partial class ConnectorConnectResult +internal sealed class McpStartServerRequestWithSession { - /// The type discriminator. - [JsonPropertyName("kind")] - public virtual string Kind { get; set; } = string.Empty; -} + /// 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; } + /// Exact receipt identity for explicit owned activation in this session. + [JsonPropertyName("expectedInstallationId")] + public string? ExpectedInstallationId { get; set; } -/// The service is connected and the session MCP graph was reconciled. -/// The connected variant of . + /// 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(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class ConnectorConnectResultConnected : ConnectorConnectResult +public sealed class McpRestartServerRequest { - /// - [JsonIgnore] - public override string Kind => "connected"; + /// 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; } - /// Fresh authoritative Connector state after MCP reconciliation. - [JsonPropertyName("status")] - public required ConnectorStatus Status { get; set; } + /// Exact receipt identity for an explicit owned restart; configuration overrides are refused. + [JsonPropertyName("expectedInstallationId")] + public string? ExpectedInstallationId { get; set; } + + /// Name of the MCP server to restart. + [JsonPropertyName("serverName")] + public required string ServerName { get; set; } } -/// Host-owned consent is required before bounded continuation can complete. -/// The consent_required variant of . +/// 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(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class ConnectorConnectResultConsentRequired : ConnectorConnectResult +internal sealed class McpRestartServerRequestWithSession { - /// - [JsonIgnore] - public override string Kind => "consent_required"; + /// 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; } - /// Validated HTTPS consent URL. The runtime does not open it. - [JsonPropertyName("consentUrl")] - public required string ConsentUrl { get; set; } + /// Exact receipt identity for an explicit owned restart; configuration overrides are refused. + [JsonPropertyName("expectedInstallationId")] + public string? ExpectedInstallationId { get; set; } - /// Opaque ID accepted by continueConnection. - [JsonPropertyName("continuationId")] - public required string ContinuationId { 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; } -/// The service is still completing the connection without a consent URL. -/// The pending variant of . +/// Server name for an individual MCP server stop. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class ConnectorConnectResultPending : ConnectorConnectResult +public sealed class McpStopServerRequest { - /// - [JsonIgnore] - public override string Kind => "pending"; + /// Exact owned receipt identity. Stop also forgets this session's durable activation. + [JsonPropertyName("expectedInstallationId")] + public string? ExpectedInstallationId { get; set; } - /// Opaque ID accepted by continueConnection. - [JsonPropertyName("continuationId")] - public required string ContinuationId { get; set; } + /// Name of the MCP server to stop. + [JsonPropertyName("serverName")] + public required string ServerName { get; set; } } -/// Selects one Connector and the pinned host-owned account used for its service and MCP authorization. +/// Server name for an individual MCP server stop. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class ConnectorConnectRequest +internal sealed class McpStopServerRequestWithSession { - /// Opaque account selection ID. It must match the account already pinned to the session, if any. - [JsonPropertyName("accountId")] - public string AccountId { get; set; } = string.Empty; + /// Exact owned receipt identity. Stop also forgets this session's durable activation. + [JsonPropertyName("expectedInstallationId")] + public string? ExpectedInstallationId { get; set; } - /// Canonical Connector name from the current catalog. - [JsonPropertyName("connectorName")] - public string ConnectorName { get; set; } = string.Empty; + /// 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; } -/// Explicitly bounded continuation of a pending Connector connection. +/// Registration parameters for an external MCP client. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class ConnectorContinueRequest +internal sealed class McpRegisterExternalClientRequest { - /// Opaque continuation ID returned by connect, reconnect, or an earlier continuation. - [JsonPropertyName("continuationId")] - public string ContinuationId { get; set; } = string.Empty; - - /// Maximum wall-clock duration in milliseconds for this call. Must be between one and the capability limit. - [JsonPropertyName("deadlineMs")] - public int DeadlineMs { get; set; } - - /// Maximum catalog requests made by this call. Must be between one and the capability limit. - [JsonPropertyName("maxAttempts")] - public int MaxAttempts { get; set; } - - /// Delay in milliseconds between attempts. Must not exceed the capability limit. - [JsonPropertyName("pollIntervalMs")] - public int PollIntervalMs { get; set; } + /// 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; } -/// Authoritative result after disconnect and MCP reconciliation. +/// Server name identifying the external client to remove. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class ConnectorDisconnectResult +internal sealed class McpUnregisterExternalClientRequest { - /// Whether the service accepted the idempotent disconnect. - [JsonPropertyName("disconnected")] - public bool Disconnected { get; set; } + /// Server name of the external client to unregister. + [JsonPropertyName("serverName")] + public string ServerName { get; set; } = string.Empty; - /// Fresh authoritative session state after removing Connector-owned MCP servers. - [JsonPropertyName("status")] - public ConnectorStatus Status { get => field ??= new(); set; } + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } -/// Requests authoritative Connector-to-MCP reconciliation for the pinned account. +/// Whether the named MCP server is running. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class ConnectorReconcileRequest +public sealed class McpIsServerRunningResult { - /// Opaque account selection ID. It must match the account already pinned to the session, if any. - [JsonPropertyName("accountId")] - public required string AccountId { get; set; } - - /// Optional Connector name to reinitialize. Requires the targetedReconcile capability. - [JsonPropertyName("forceConnectorName")] - public string? ForceConnectorName { get; set; } - - /// When true, refresh the catalog before reconciling. A disabled Connector API performs no service request. - [JsonPropertyName("refreshCatalog")] - public bool? RefreshCatalog { get; set; } + /// True if the server has an active client and transport. + [JsonPropertyName("running")] + public bool Running { get; set; } } -/// Requests authoritative Connector-to-MCP reconciliation for the pinned account. +/// Server name to check running status for. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class ConnectorReconcileRequestWithSession +internal sealed class McpIsServerRunningRequest { - /// Opaque account selection ID. It must match the account already pinned to the session, if any. - [JsonPropertyName("accountId")] - public string AccountId { get; set; } = string.Empty; - - /// Optional Connector name to reinitialize. Requires the targetedReconcile capability. - [JsonPropertyName("forceConnectorName")] - public string? ForceConnectorName { get; set; } - - /// When true, refresh the catalog before reconciling. A disabled Connector API performs no service request. - [JsonPropertyName("refreshCatalog")] - public bool? RefreshCatalog { get; set; } + /// 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; } -/// Identifies the target session. +/// Indicates whether the pending MCP OAuth response was accepted. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionConnectorsWithdrawProjectionRequest +public sealed class McpOauthHandlePendingResult { - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Whether the response was accepted. False if the request was unknown, timed out, or already resolved. + [JsonPropertyName("success")] + public bool Success { get; set; } } -/// Identifies the target session. +/// Host response to the pending OAuth request. +/// Polymorphic base type discriminated by kind. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionManagedSettingsGetRequest +[JsonPolymorphic( + TypeDiscriminatorPropertyName = "kind", + UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] +[JsonDerivedType(typeof(McpOauthPendingRequestResponseToken), "token")] +[JsonDerivedType(typeof(McpOauthPendingRequestResponseCancelled), "cancelled")] +public partial class McpOauthPendingRequestResponse { - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// The type discriminator. + [JsonPropertyName("kind")] + public virtual string Kind { get; set; } = string.Empty; } -/// Session plugin metadata, with name, marketplace, optional version, and enabled state. + +/// The token variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class Plugin +public partial class McpOauthPendingRequestResponseToken : McpOauthPendingRequestResponse { - /// Opaque stable identity for a direct plugin source. - [JsonPropertyName("directSourceId")] - public string? DirectSourceId { get; set; } - - /// Whether the plugin is currently enabled. - [JsonPropertyName("enabled")] - public bool Enabled { get; set; } - - /// Whether this managed desired plugin has an installed or live record. - [JsonPropertyName("installed")] - public bool? Installed { get; set; } - - /// Absolute marketplace directory for a live plugin. - [JsonPropertyName("installedFrom")] - public string? InstalledFrom { get; set; } - - /// Whether enterprise managed settings control this plugin. - [JsonPropertyName("managed")] - public bool? Managed { get; set; } - - /// Enabled state required by enterprise managed settings. - [JsonPropertyName("managedDesiredEnabled")] - public bool? ManagedDesiredEnabled { get; set; } - - /// Marketplace the plugin came from. - [JsonPropertyName("marketplace")] - public string Marketplace { get; set; } = string.Empty; + /// + [JsonIgnore] + public override string Kind => "token"; - /// Plugin name. - [JsonPropertyName("name")] - public string Name { get; set; } = string.Empty; + /// Access token acquired by the SDK host. + [JsonPropertyName("accessToken")] + public required string AccessToken { get; set; } - /// Runtime plugin provenance, such as "builtin". - [JsonPropertyName("source")] - public string? Source { get; set; } + /// Token lifetime in seconds, if known. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("expiresIn")] + public long? ExpiresIn { get; set; } - /// Installed version. - [JsonPropertyName("version")] - public string? Version { get; set; } + /// OAuth token type. Defaults to bearer when omitted. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("tokenType")] + public string? TokenType { get; set; } } -/// Plugins installed for the session, with their enabled state and version metadata. +/// The cancelled variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class PluginList +public partial class McpOauthPendingRequestResponseCancelled : McpOauthPendingRequestResponse { - /// Installed plugins. - [JsonPropertyName("plugins")] - public IList Plugins { get => field ??= []; set; } + /// + [JsonIgnore] + public override string Kind => "cancelled"; } -/// Identifies the target session. +/// Pending MCP OAuth request ID and host-provided token or cancellation response. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionPluginsListRequest +internal sealed class McpOauthHandlePendingRequest { - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; -} + /// 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; } -/// Plugin source resolved relative to the session's authoritative working directory. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionPluginsInstallRequest -{ /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; - - /// 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; } -/// Name (or spec) of the plugin to uninstall. +/// Identifies the MCP server whose persisted OAuth credentials were updated. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class PluginsUninstallRequestWithSession +internal sealed class McpOauthAuthenticationStateChangedRequest { - /// 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; } + /// 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; } - /// Plugin name or "plugin@marketplace" spec to uninstall. When ambiguous, prefer the fully-qualified spec. - [JsonPropertyName("name")] - public string Name { get; set; } = string.Empty; + /// 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; } -/// Name (or spec) of the plugin to update. +/// An inert runtime-issued login handle. Preparation alone performs no activation or OAuth work. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class PluginsUpdateRequestWithSession +public sealed class SessionMcpOauthPrepareLoginResult { - /// Plugin name or "plugin@marketplace" spec to update. - [JsonPropertyName("name")] - public string Name { get; set; } = string.Empty; + /// Original expiry, not extended by consumption, retries or cancellation. + [JsonPropertyName("expiresAt")] + public DateTimeOffset ExpiresAt { get; set; } - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Retain with the original requester and use for one login or cancellation. + [JsonPropertyName("loginId")] + public string LoginId { get; set; } = string.Empty; } -/// Plugin names (or specs) to enable in the session's authoritative working directory. +/// Effect-free preparation bound to the existing local session, requester and installation, with frozen options. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionPluginsEnableRequest +internal sealed class SessionMcpOauthPrepareLoginRequest { - /// 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; } + /// Text shown on the loopback callback page after successful authorisation. + [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(2048)] + [JsonPropertyName("callbackSuccessMessage")] + public string? CallbackSuccessMessage { get; set; } + + /// Display name used by the incumbent OAuth client-registration flow. + [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(2048)] + [JsonPropertyName("clientName")] + public string? ClientName { get; set; } + + /// Exact installation identity from owned inventory, never a server-name alias. + [JsonPropertyName("expectedInstallationId")] + public string ExpectedInstallationId { get; set; } = string.Empty; + + /// Request a new authorisation rather than accepting a usable cached grant. + [JsonPropertyName("forceReauth")] + public bool? ForceReauth { get; set; } + + /// Name recorded by the authoritative owned installation receipt. + [JsonPropertyName("serverName")] + public string ServerName { get; set; } = string.Empty; /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Plugin names (or specs) to disable in the session's authoritative working directory. +/// OAuth authorization URL the caller should open, or empty when cached tokens already authenticated the server. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionPluginsDisableRequest +public sealed class McpOauthLoginResult { - /// 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; } + /// Opaque authorization identifier returned only for a host-managed redirect URI. The runtime also sends it as the OAuth state value, so the callback endpoint can read state and pass it with the full callback URL to session.mcp.oauth.complete. + [JsonPropertyName("authorizationId")] + public string? AuthorizationId { get; set; } - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// 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. For the default loopback flow, the runtime starts its listener before returning. With redirectUri, the host receives the callback and completes it through session.mcp.oauth.complete. The runtime continues the flow in the background and signals completion via session.mcp_server_status_changed. + [Url] + [StringSyntax(StringSyntaxAttribute.Uri)] + [JsonPropertyName("authorizationUrl")] + public string? AuthorizationUrl { get; set; } + + /// Runtime-issued owned flow identity; never a server name or installation operation ID. + [JsonPropertyName("loginId")] + public string? LoginId { get; set; } + + /// Explicit outcome for owned sign-in. Manual callers retain their legacy response shape. + [JsonPropertyName("status")] + public McpOwnedOauthLoginStatus? Status { get; set; } } -/// RPC data type for SessionPluginsReload operations. +/// Remote MCP server name and optional overrides controlling reauthentication, OAuth client display name, callback handling, and static OAuth client selection. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class SessionPluginsReloadRequest +public sealed class McpOauthLoginRequest { - /// 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; } + /// 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; } - /// Re-run custom-agent discovery after refreshing plugins. Defaults to true. - [JsonPropertyName("reloadCustomAgents")] - public bool? ReloadCustomAgents { 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; } - /// 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; } + /// 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; } - /// 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; } + /// 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; } - /// Reload MCP server connections after refreshing plugins. Defaults to true. - [JsonPropertyName("reloadMcp")] - public bool? ReloadMcp { get; set; } -} + /// Exact owned receipt identity. Owned login never uses an implicit helper session. + [JsonPropertyName("expectedInstallationId")] + public string? ExpectedInstallationId { get; set; } -/// RPC data type for SessionPluginsReloadRequestWithSession operations. -[Experimental(global::GitHub.Copilot.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; } + /// 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; } - /// Re-run custom-agent discovery after refreshing plugins. Defaults to true. - [JsonPropertyName("reloadCustomAgents")] - public bool? ReloadCustomAgents { 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; } - /// 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; } + /// + /// Required for owned login. Consumes the exact prepareLogin handle once. + /// Set forceReauth and display options during preparation, not consumption. + /// + [JsonPropertyName("loginId")] + public string? LoginId { 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; } + /// 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; } - /// Reload MCP server connections after refreshing plugins. Defaults to true. - [JsonPropertyName("reloadMcp")] - public bool? ReloadMcp { get; set; } + /// Optional externally visible HTTPS redirect URI for a host-managed callback endpoint. When supplied, the runtime still owns discovery, PKCE, token exchange, persistence, and reconnect, but does not bind a loopback listener or terminate HTTPS. The URI must not contain query parameters or a fragment and must be registered for the selected CIMD, DCR, or static OAuth client. + [Url] + [StringSyntax(StringSyntaxAttribute.Uri)] + [JsonPropertyName("redirectUri")] + public string? RedirectUri { get; set; } - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// 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 required string ServerName { get; set; } } -/// Identifies the target session. +/// Remote MCP server name and optional overrides controlling reauthentication, OAuth client display name, callback handling, and static OAuth client selection. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionPluginsMarketplacesListRequest +internal sealed class McpOauthLoginRequestWithSession { - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; -} + /// Optional override for the body text shown on the OAuth loopback callback success page. When omitted, the runtime applies a neutral fallback; callers driving interactive auth should pass surface-specific copy telling the user where to return. + [JsonPropertyName("callbackSuccessMessage")] + public string? CallbackSuccessMessage { get; set; } -/// Marketplace source and optional working directory for relative-path resolution. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class PluginsMarketplacesAddRequestWithSession -{ - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// 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; } - /// 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; + /// 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; } - /// 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 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; } -/// Name of the marketplace to remove and an optional force flag. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class PluginsMarketplacesRemoveRequestWithSession -{ - /// 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; } + /// Exact owned receipt identity. Owned login never uses an implicit helper session. + [JsonPropertyName("expectedInstallationId")] + public string? ExpectedInstallationId { get; set; } - /// Marketplace name to remove. - [JsonPropertyName("name")] - public string Name { get; set; } = string.Empty; + /// 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; } + + /// + /// Required for owned login. Consumes the exact prepareLogin handle once. + /// Set forceReauth and display options during preparation, not consumption. + /// + [JsonPropertyName("loginId")] + public string? LoginId { 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; } + + /// Optional externally visible HTTPS redirect URI for a host-managed callback endpoint. When supplied, the runtime still owns discovery, PKCE, token exchange, persistence, and reconnect, but does not bind a loopback listener or terminate HTTPS. The URI must not contain query parameters or a fragment and must be registered for the selected CIMD, DCR, or static OAuth client. + [Url] + [StringSyntax(StringSyntaxAttribute.Uri)] + [JsonPropertyName("redirectUri")] + public string? RedirectUri { 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; } -/// Name of the marketplace whose plugin catalog to fetch. +/// Host-delivered callback for a runtime-managed MCP OAuth login. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class PluginsMarketplacesBrowseRequestWithSession +internal sealed class McpOauthCompleteRequest { - /// Marketplace name to browse. - [JsonPropertyName("name")] - public string Name { get; set; } = string.Empty; + /// Opaque identifier returned by session.mcp.oauth.login for the pending external callback. + [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("authorizationId")] + public string AuthorizationId { get; set; } = string.Empty; + + /// Full externally visible HTTPS callback URL received by the host, including the authorization response query parameters. Applications behind a reverse proxy must reconstruct the public URL rather than passing an internal proxy URL. + [Url] + [StringSyntax(StringSyntaxAttribute.Uri)] + [JsonPropertyName("callbackUrl")] + public string CallbackUrl { get; set; } = string.Empty; /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// RPC data type for SessionPluginsMarketplacesRefresh operations. +/// 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(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class SessionPluginsMarketplacesRefreshRequest +[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 { - /// Marketplace name to refresh. When omitted, every registered marketplace is refreshed. - [JsonPropertyName("name")] - public string? Name { get; set; } + /// The type discriminator. + [JsonPropertyName("status")] + public virtual string Status { get; set; } = string.Empty; } -/// RPC data type for SessionPluginsMarketplacesRefreshRequestWithSession operations. + +/// The no-auth-required variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionPluginsMarketplacesRefreshRequestWithSession +public partial class McpOauthProbeResultNoAuthRequired : McpOauthProbeResult { - /// Marketplace name to refresh. When omitted, every registered marketplace is refreshed. - [JsonPropertyName("name")] - public string? Name { get; set; } + /// + [JsonIgnore] + public override string Status => "no-auth-required"; - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// HTTP response returned by the server. + [JsonPropertyName("httpResponse")] + public required McpOauthHttpResponse HttpResponse { get; set; } } -/// Short-lived, rotating credential the caller must send on every request, in addition to `apiKey` if one is present. Omitted when the endpoint does not require one. +/// The authenticated variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class ProviderSessionToken +public partial class McpOauthProbeResultAuthenticated : McpOauthProbeResult { - /// 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; } + /// + [JsonIgnore] + public override string Status => "authenticated"; - /// The short-lived token value. - [JsonPropertyName("token")] - public string Token { get; set; } = string.Empty; + /// HTTP response returned by the server. + [JsonPropertyName("httpResponse")] + public required McpOauthHttpResponse HttpResponse { get; set; } } -/// A snapshot of the provider endpoint the session is currently configured to talk to. +/// The needs-auth variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class ProviderEndpoint +public partial class McpOauthProbeResultNeedsAuth : McpOauthProbeResult { - /// 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; } + /// + [JsonIgnore] + public override string Status => "needs-auth"; - /// Transport to be used for provider requests. - [JsonPropertyName("transport")] - public ProviderEndpointTransport? Transport { get; set; } + /// HTTP 401 or 403 response returned by the server. + [JsonPropertyName("httpResponse")] + public required McpOauthHttpResponse HttpResponse { get; set; } - /// Provider family. Matches the `type` field of a BYOK provider config. - [JsonPropertyName("type")] - public ProviderEndpointType Type { get; set; } + /// Why authentication is needed. + [JsonPropertyName("reason")] + public required McpOauthProbeNeedsAuthReason Reason { get; set; } - /// Wire API to be used, when required for the provider type. - [JsonPropertyName("wireApi")] - public ProviderEndpointWireApi? WireApi { get; set; } + /// Parsed WWW-Authenticate challenge parameters, when present and parseable. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("wwwAuthenticateParams")] + public McpOauthWWWAuthenticateParams? WwwAuthenticateParams { get; set; } } -/// RPC data type for SessionProviderGetEndpoint operations. +/// The failed variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class SessionProviderGetEndpointRequest +public partial class McpOauthProbeResultFailed : McpOauthProbeResult { - /// 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; } -} + /// + [JsonIgnore] + public override string Status => "failed"; -/// RPC data type for SessionProviderGetEndpointRequestWithSession operations. -[Experimental(global::GitHub.Copilot.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; } + /// Human-readable probe failure detail. + [JsonPropertyName("error")] + public required string Error { get; set; } - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// 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; } } -/// The selectable model entries synthesized for the models added by this call. +/// Remote MCP server name for a passive OAuth status probe. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class ProviderAddResult +public sealed class McpOauthProbeRequest { - /// 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; } + /// Exact owned receipt identity; probing never activates a dormant installation. + [JsonPropertyName("expectedInstallationId")] + public string? ExpectedInstallationId { get; set; } + + /// 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 required string ServerName { 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. +/// Remote MCP server name for a passive OAuth status probe. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class ProviderAddRequest +internal sealed class McpOauthProbeRequestWithSession { - /// 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; } + /// Exact owned receipt identity; probing never activates a dormant installation. + [JsonPropertyName("expectedInstallationId")] + public string? ExpectedInstallationId { 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; } + /// 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; } -/// The selectable model entries and selection ids synthesized for the synchronized BYOK models. +/// Honest terminal cancellation result; persistence or recovery failures remain RPC errors. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class ProviderSyncResult +public sealed class SessionMcpOauthCancelLoginResult { - /// True when synchronization withdrew the selected host-managed model, leaving the session with no explicit selection, so ordinary model resolution picks the session default. Synchronization never promotes a surviving host model in its place: publishing a model offers it, and the choice of which model to use stays with the user. - [JsonPropertyName("modelDeselected")] - public bool? ModelDeselected { get; set; } - - /// Synthesized selectable model entries for the synchronized BYOK models. - [JsonPropertyName("models")] - public IList Models { get => field ??= []; set; } - - /// Provider-qualified model selection ids present after synchronization. - [JsonPropertyName("selectionIds")] - public IList SelectionIds { get => field ??= []; set; } + /// True after cancellation settles, false when the original login already connected successfully. + [JsonPropertyName("cancelled")] + public bool Cancelled { get; set; } } -/// Authoritative BYOK provider and model registry snapshot to apply atomically to the session. +/// Targets only the original prepared/applying owned login on this exact session requester. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class ProviderSyncRequest +internal sealed class SessionMcpOauthCancelLoginRequest { - /// BYOK model definition snapshot. Models absent from this list are removed. - [JsonPropertyName("models")] - public IList? Models { get; set; } + /// The same authoritative installation identity supplied during preparation. + [JsonPropertyName("expectedInstallationId")] + public string ExpectedInstallationId { get; set; } = string.Empty; - /// Named BYOK provider connection snapshot. Providers absent from this list are removed. - [JsonPropertyName("providers")] - public IList? Providers { get; set; } + /// Runtime-issued login handle known before the effectful login request begins. + [JsonPropertyName("loginId")] + public string LoginId { get; set; } = string.Empty; /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// What the withdrawal actually removed from the registry. +/// Indicates whether the pending MCP OAuth response was accepted. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class ProviderWithdrawResult +public sealed class McpOauthRespondResult { - /// True when withdrawal removed the selected host-managed model, leaving the session with no explicit selection, so ordinary model resolution picks the session default. Withdrawal never promotes a surviving model in its place: the choice of which model to use stays with the user. - [JsonPropertyName("modelDeselected")] - public bool? ModelDeselected { get; set; } - - /// Providers removed because one of the withdrawn models was the last entry referencing them. A provider that merely has no models is not removed. - [JsonPropertyName("providersRemoved")] - public IList ProvidersRemoved { get => field ??= []; set; } - - /// Selection ids that were registered and are now withdrawn. Excludes requested ids that were not present. - [JsonPropertyName("withdrawn")] - public IList Withdrawn { get => field ??= []; set; } + /// Whether the response was accepted. False if the request was unknown, timed out, or already resolved. + [JsonPropertyName("success")] + public bool Success { get; set; } } -/// Host-managed model selection ids to withdraw from the session's BYOK registry. +/// Pending MCP OAuth request id to respond to. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class ProviderWithdrawRequest +internal sealed class McpOauthRespondRequest { - /// Provider-qualified selection ids to withdraw. Ids that are not registered are ignored, so withdrawal is idempotent. A provider left with no models referencing it is removed too. - [JsonPropertyName("models")] - public IList Models { get => field ??= []; set; } + /// OAuth request identifier from the mcp.oauth_required event. + [JsonPropertyName("requestId")] + public string RequestId { get; set; } = string.Empty; /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Indicates whether the session options patch was applied successfully. +/// Indicates whether the pending MCP headers refresh response was accepted. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class SessionUpdateOptionsResult +public sealed class McpHeadersHandlePendingHeadersRefreshRequestResult { - /// Number of hooks loaded from installed plugins, returned when installedPlugins is updated. - [JsonPropertyName("pluginHookCount")] - public long? PluginHookCount { get; set; } - - /// Whether the operation succeeded. + /// Whether the response was accepted. False if the request was unknown, timed out, or already resolved. [JsonPropertyName("success")] public bool Success { get; set; } } -/// Source descriptor for a `session.options.update` content-exclusion rule, with source name and type. +/// Host response: supply dynamic headers or decline this refresh. +/// Polymorphic base type discriminated by kind. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class OptionsUpdateAdditionalContentExclusionPolicyRuleSource +[JsonPolymorphic( + TypeDiscriminatorPropertyName = "kind", + UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] +[JsonDerivedType(typeof(McpHeadersHandlePendingHeadersRefreshRequestHeaders), "headers")] +[JsonDerivedType(typeof(McpHeadersHandlePendingHeadersRefreshRequestNone), "none")] +[JsonDerivedType(typeof(McpHeadersHandlePendingHeadersRefreshRequestError), "error")] +public partial class McpHeadersHandlePendingHeadersRefreshRequest { - /// 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; + /// The type discriminator. + [JsonPropertyName("kind")] + public virtual string Kind { get; set; } = string.Empty; } -/// Single content-exclusion rule supplied to `session.options.update`, with paths, match conditions, and source. + +/// The headers variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class OptionsUpdateAdditionalContentExclusionPolicyRule +public partial class McpHeadersHandlePendingHeadersRefreshRequestHeaders : McpHeadersHandlePendingHeadersRefreshRequest { - /// 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; } + /// + [JsonIgnore] + public override string Kind => "headers"; - /// Path patterns covered by this rule. - [JsonPropertyName("paths")] - public IList Paths { get => field ??= []; set; } + /// 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; } - /// Source descriptor for a `session.options.update` content-exclusion rule, with source name and type. - [JsonPropertyName("source")] - public OptionsUpdateAdditionalContentExclusionPolicyRuleSource Source { get => field ??= new(); set; } + /// Optional lifetime in milliseconds for these returned headers. The runtime clamps its configured cache lifetime to this value. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("ttlMs")] + public long? TtlMs { get; set; } } -/// Content-exclusion policy supplied to `session.options.update`, with rules, last-updated data, and scope. +/// The none variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class OptionsUpdateAdditionalContentExclusionPolicy +public partial class McpHeadersHandlePendingHeadersRefreshRequestNone : McpHeadersHandlePendingHeadersRefreshRequest { - /// 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; } + /// + [JsonIgnore] + public override string Kind => "none"; } -/// Options scoped to the built-in CAPI (Copilot API) provider. +/// The error variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class CapiSessionOptions +public partial class McpHeadersHandlePendingHeadersRefreshRequestError : McpHeadersHandlePendingHeadersRefreshRequest { - /// Routing preference for sessions whose model is `auto`. On create or cold resume, this establishes the preference sent as `tier` on CAPI `/auto` requests; when omitted on cold resume, the runtime restores the last committed preference. On resident resume, a different value requests a safe switch after resume succeeds and cannot change an in-flight turn. Successful switches are persisted for later cold resume. When no preference is supplied or restored, CAPI default routing is used. `fast` is an integrator-only latency preset, not a first-party GitHub Copilot product preference. - [JsonPropertyName("autoTier")] - public AutoTier? AutoTier { get; set; } + /// + [JsonIgnore] + public override string Kind => "error"; + + /// Host credential broker failure, denial, or revocation reason. + [JsonPropertyName("message")] + public required string Message { get; set; } +} + +/// MCP headers refresh request id and the host response. +[Experimental(global::GitHub.Copilot.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(global::GitHub.Copilot.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(global::GitHub.Copilot.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(global::GitHub.Copilot.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(global::GitHub.Copilot.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(global::GitHub.Copilot.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(global::GitHub.Copilot.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(global::GitHub.Copilot.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(global::GitHub.Copilot.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(global::GitHub.Copilot.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(global::GitHub.Copilot.Diagnostics.Experimental)] +public sealed class McpAppsHostContext +{ + /// Current host context. + [JsonPropertyName("context")] + public McpAppsHostContextDetails Context { get => field ??= new(); set; } +} + +/// Identifies the target session. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class SessionMcpAppsGetHostContextRequest +{ + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + +/// Capability negotiation snapshot. +[Experimental(global::GitHub.Copilot.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(global::GitHub.Copilot.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(global::GitHub.Copilot.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(global::GitHub.Copilot.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(global::GitHub.Copilot.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(global::GitHub.Copilot.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(global::GitHub.Copilot.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(global::GitHub.Copilot.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(global::GitHub.Copilot.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(global::GitHub.Copilot.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(global::GitHub.Copilot.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(global::GitHub.Copilot.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(global::GitHub.Copilot.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(global::GitHub.Copilot.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(global::GitHub.Copilot.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; +} + +/// An argument accepted by an MCP prompt. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public sealed class McpPromptArgument +{ + /// Argument-level metadata. + [JsonPropertyName("_meta")] + public IDictionary? Meta { get; set; } + + /// Server-provided non-standard argument fields. + [JsonPropertyName("additionalProperties")] + public IDictionary? AdditionalProperties { get; set; } + + /// Description of the argument. + [JsonPropertyName("description")] + public string? Description { get; set; } + + /// Name of the argument. + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; + + /// Whether the argument is required; omission is distinct from false. + [JsonPropertyName("required")] + public bool? Required { get; set; } +} + +/// An MCP prompt icon with standard size hints and preserved non-standard fields. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public sealed class McpPromptIcon +{ + /// Server-provided non-standard icon fields. + [JsonPropertyName("additionalProperties")] + public IDictionary? AdditionalProperties { get; set; } + + /// Icon MIME type, when known. + [JsonPropertyName("mimeType")] + public string? MimeType { get; set; } + + /// Icon sizes, such as `48x48` or `any`. + [JsonPropertyName("sizes")] + public IList? 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 prompt descriptor. Server-provided non-standard fields are exposed under `additionalProperties`. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public sealed class McpPrompt +{ + /// Prompt-level metadata. + [JsonPropertyName("_meta")] + public IDictionary? Meta { get; set; } + + /// Server-provided non-standard descriptor fields. + [JsonPropertyName("additionalProperties")] + public IDictionary? AdditionalProperties { get; set; } + + /// Arguments accepted by the prompt. + [JsonPropertyName("arguments")] + public IList? Arguments { get; set; } + + /// Description of what this prompt provides. + [JsonPropertyName("description")] + public string? Description { get; set; } + + /// Icons associated with this prompt. + [JsonPropertyName("icons")] + public IList? Icons { get; set; } + + /// The programmatic name of the prompt. + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; + + /// Human-readable display title. + [JsonPropertyName("title")] + public string? Title { get; set; } +} + +/// One page of prompts advertised by the named MCP server. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public sealed class McpPromptsListResult +{ + /// MCP result metadata. + [JsonPropertyName("_meta")] + public IDictionary? Meta { get; set; } + + /// Server-provided non-standard result fields. + [JsonPropertyName("additionalProperties")] + public IDictionary? AdditionalProperties { get; set; } + + /// Opaque cursor for the next page, if the server has more prompts. + [JsonPropertyName("nextCursor")] + public string? NextCursor { get; set; } + + /// Prompts advertised by the server. + [JsonPropertyName("prompts")] + public IList Prompts { get => field ??= []; set; } +} + +/// MCP server whose prompts to enumerate. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class McpPromptsListRequest +{ + /// Opaque MCP pagination cursor from a prior `nextCursor` value. + [JsonPropertyName("cursor")] + public string? Cursor { get; set; } + + /// Name of the MCP server whose prompts 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 prompt message with opaque JSON content preserved without flattening or content-type filtering. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public sealed class McpPromptMessage +{ + /// Message-level metadata. + [JsonPropertyName("_meta")] + public IDictionary? Meta { get; set; } + + /// Server-provided non-standard message fields. + [JsonPropertyName("additionalProperties")] + public IDictionary? AdditionalProperties { get; set; } + + /// The original MCP content block, including nested metadata and unfamiliar content types. + [JsonPropertyName("content")] + public JsonElement Content { get; set; } + + /// The role of the message sender. + [JsonPropertyName("role")] + public McpPromptRole Role { get; set; } +} + +/// Prompt messages returned by the MCP server without sending them to the model. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public sealed class McpPromptsGetResult +{ + /// MCP result metadata. + [JsonPropertyName("_meta")] + public IDictionary? Meta { get; set; } + + /// Server-provided non-standard result fields. + [JsonPropertyName("additionalProperties")] + public IDictionary? AdditionalProperties { get; set; } + + /// Description of the prompt. + [JsonPropertyName("description")] + public string? Description { get; set; } + + /// Ordered prompt messages. + [JsonPropertyName("messages")] + public IList Messages { get => field ??= []; set; } +} + +/// MCP server, prompt name, and optional string-valued arguments. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class McpPromptsGetRequest +{ + /// String-valued arguments to pass to the prompt. + [JsonPropertyName("arguments")] + public IDictionary? Arguments { get; set; } + + /// The programmatic name of the prompt. + [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("promptName")] + public string PromptName { get; set; } = string.Empty; + + /// Name of the MCP server hosting the prompt. + [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 diagnostic source configuration. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public sealed class McpDiagnosticSourceConfiguration +{ + /// Threshold to apply to MCP diagnostic producers in this session. + [JsonPropertyName("level")] + public DiagnosticLogLevel Level { get; set; } +} + +/// Typed diagnostic source configuration. At least one source is required by diagnostics configuration methods. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public sealed class DiagnosticSourcesConfiguration +{ + /// MCP diagnostic capture threshold. Omit to leave the current threshold unchanged. + [JsonPropertyName("mcp")] + public McpDiagnosticSourceConfiguration? Mcp { get; set; } +} + +/// Per-source session diagnostics configuration. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public sealed class DiagnosticsConfiguration +{ + /// Diagnostic thresholds keyed by supported source. + [JsonPropertyName("sources")] + public DiagnosticSourcesConfiguration Sources { get => field ??= new(); set; } +} + +/// Patch session diagnostic thresholds for explicitly supplied sources. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class DiagnosticsConfigureRequest +{ + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; + + /// Sources to configure. At least one supported source must be supplied. + [JsonPropertyName("sources")] + public DiagnosticSourcesConfiguration Sources { get => field ??= new(); set; } +} + +/// One retained session-scoped diagnostic record. Potentially content-bearing diagnostic data is opt-in and must not be exported automatically as telemetry. +/// Polymorphic base type discriminated by source. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +[JsonPolymorphic( + TypeDiscriminatorPropertyName = "source", + UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] +[JsonDerivedType(typeof(DiagnosticEntryMcp), "mcp")] +public partial class DiagnosticEntry +{ + /// The type discriminator. + [JsonPropertyName("source")] + public virtual string Source { get; set; } = string.Empty; +} + + +/// MCP-specific detail for a source-discriminated diagnostic entry. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public sealed class McpDiagnosticDetails +{ + /// Fresh identifier for the MCP connection attempt, including failed starts. + [JsonPropertyName("connectionId")] + public string ConnectionId { get; set; } = string.Empty; + + /// Serialized diagnostic detail. Protocol and HTTP records use JSON when detail is present. + [JsonPropertyName("data")] + public string? Data { get; set; } + + /// Protocol-frame direction when kind is protocol. + [JsonPropertyName("direction")] + public McpDiagnosticDirection? Direction { get; set; } + + /// Diagnostic record category. + [JsonPropertyName("kind")] + public McpDiagnosticKind Kind { get; set; } + + /// Configured MCP server name. + [JsonPropertyName("serverName")] + public string ServerName { get; set; } = string.Empty; +} + +/// The mcp variant of . +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public partial class DiagnosticEntryMcp : DiagnosticEntry +{ + /// + [JsonIgnore] + public override string Source => "mcp"; + + /// Agent identifier for a subagent host. Omitted for the root agent. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("agentId")] + public string? AgentId { get; set; } + + /// Typed MCP diagnostic detail. + [JsonPropertyName("details")] + public required McpDiagnosticDetails Details { get; set; } + + /// Severity of this emitted diagnostic record. + [JsonPropertyName("level")] + public required DiagnosticSeverity Level { get; set; } + + /// Human-readable diagnostic summary. + [JsonPropertyName("message")] + public required string Message { get; set; } + + /// Original byte count when a known-size message or data value was truncated. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("originalBytes")] + public long? OriginalBytes { get; set; } + + /// UTC RFC 3339 timestamp captured at the diagnostic source. + [JsonPropertyName("timestamp")] + public required string Timestamp { get; set; } + + /// Whether message or data was truncated to the record-size bound. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("truncated")] + public bool? Truncated { get; set; } +} + +/// One cursor-addressed page of retained session diagnostics. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public sealed class DiagnosticsReadResult +{ + /// Opaque cursor for the next independent read. + [JsonPropertyName("cursor")] + public string Cursor { get; set; } = string.Empty; + + /// Whether the requested cursor remained within the retained buffer window. + [JsonPropertyName("cursorStatus")] + public DiagnosticCursorStatus CursorStatus { get; set; } + + /// Number of records lost before this page when known. Omitted when a buffer generation change makes the count unknowable. + [JsonPropertyName("droppedCount")] + public long? DroppedCount { get; set; } + + /// Retained records beginning at the requested cursor. + [JsonPropertyName("entries")] + public IList Entries { get => field ??= []; set; } + + /// Whether additional retained records follow this page. + [JsonPropertyName("hasMore")] + public bool HasMore { get; set; } +} + +/// Cursor-based request for session diagnostics. The default limit is 100 (maximum 500); the default waitMs is zero (maximum 30000). +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class DiagnosticsReadRequest +{ + /// Opaque cursor returned by an earlier read. Omit to start at the oldest retained record. + [JsonPropertyName("cursor")] + public string? Cursor { get; set; } + + /// Maximum number of records to return, from 1 through 500. Omit for 100. + [JsonPropertyName("max")] + public long? Max { get; set; } + + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; + + /// Nonempty selection of sources to read. Each source may be listed once. + [JsonPropertyName("sources")] + public IList Sources { get => field ??= []; set; } + + /// Maximum time in milliseconds to wait for a new record, from 0 through 30000. + [JsonPropertyName("waitMs")] + public int? WaitMs { get; set; } +} + +/// Feature detection and hard polling limits for the EXPERIMENTAL session connector API. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public sealed class ConnectorCapabilities +{ + /// Connector API contract version. + [JsonPropertyName("apiVersion")] + public long ApiVersion { get; set; } + + /// Current session availability. Disabled availability is reported without making a Connector request. + [JsonPropertyName("availability")] + public ConnectorAvailability Availability { get; set; } + + /// Whether connect and reconnect can return an opaque continuation for bounded consent polling. + [JsonPropertyName("consentContinuation")] + public bool ConsentContinuation { get; set; } + + /// Maximum accepted wall-clock deadline in milliseconds for one continuation call. + [JsonPropertyName("maxDeadlineMs")] + public long MaxDeadlineMs { get; set; } + + /// Maximum accepted polling attempts for one continuation call. + [JsonPropertyName("maxPollAttempts")] + public long MaxPollAttempts { get; set; } + + /// Maximum accepted delay in milliseconds between polling attempts. + [JsonPropertyName("maxPollIntervalMs")] + public long MaxPollIntervalMs { get; set; } + + /// Whether callers select a host-owned GitHub account through an opaque selection ID rather than supplying a provider token. + [JsonPropertyName("opaqueAccountSelection")] + public bool OpaqueAccountSelection { get; set; } + + /// Whether getAccount is supported. Absence means false. + [JsonPropertyName("sessionAccountSelection")] + public bool? SessionAccountSelection { get; set; } + + /// Whether reconcile accepts forceConnectorName. Absence means false. + [JsonPropertyName("targetedReconcile")] + public bool? TargetedReconcile { get; set; } +} + +/// Identifies the target session. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class SessionConnectorsGetCapabilitiesRequest +{ + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + +/// Credential-free identity metadata. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public sealed class AuthIdentityMetadata +{ + /// Identity host. + [JsonPropertyName("host")] + public string Host { get; set; } = string.Empty; + + /// User login. + [JsonPropertyName("login")] + public string Login { get; set; } = string.Empty; + + /// Authentication type. + [JsonPropertyName("type")] + public AuthInfoType Type { get; set; } +} + +/// Session account selection. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public sealed class ConnectorSessionAccount +{ + /// Opaque session-scoped account selection ID. + [JsonPropertyName("accountId")] + public string AccountId { get; set; } = string.Empty; + + /// Credential-free identity metadata. + [JsonPropertyName("authInfo")] + public AuthIdentityMetadata AuthInfo { get => field ??= new(); set; } +} + +/// Identifies the target session. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class SessionConnectorsGetAccountRequest +{ + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + +/// Account-targeted authorization update required by the Connector service. The account ID is an opaque host routing identifier; no credential is included. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public sealed class ConnectorAuthorizationRequirement +{ + /// Exact opaque account selection that made the Connector request. + [JsonPropertyName("accountId")] + public string AccountId { get; set; } = string.Empty; + + /// Stable OAuth scope the selected account must grant. + [JsonPropertyName("scope")] + public ConnectorAuthorizationScope Scope { get; set; } +} + +/// Credential-free Connector catalog entry. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public sealed class ConnectorCatalogEntry +{ + /// Untrusted service description, when present. + [JsonPropertyName("description")] + public string? Description { get; set; } + + /// Untrusted display label from the service. + [JsonPropertyName("displayName")] + public string DisplayName { get; set; } = string.Empty; + + /// Optional catalog logo. + [JsonPropertyName("logo")] + public string? Logo { get; set; } + + /// Canonical Connector name used by lifecycle methods. + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; + + /// Optional catalog release tag. + [JsonPropertyName("releaseTag")] + public string? ReleaseTag { get; set; } + + /// Opaque stable runtime IDs currently projected into the session for this Connector. + [JsonPropertyName("runtimeServerIds")] + public IList RuntimeServerIds { get => field ??= []; set; } + + /// Current authoritative service connection state. + [JsonPropertyName("status")] + public ConnectorCatalogStatus Status { get; set; } + + /// Optional catalog tier. + [JsonPropertyName("tier")] + public string? Tier { get; set; } +} + +/// Validated Connector catalog snapshot cached by the session. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public sealed class ConnectorCatalogResult +{ + /// Validated catalog entries in service order. + [JsonPropertyName("connectors")] + public IList Connectors { get => field ??= []; set; } + + /// Unix epoch milliseconds when this snapshot was accepted. + [JsonPropertyName("refreshedAtMs")] + public long RefreshedAtMs { get; set; } + + /// Monotonically increasing session-local catalog revision. + [JsonPropertyName("revision")] + public long Revision { get; set; } +} + +/// Live status of one session-owned MCP projection. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public sealed class ConnectorRuntimeStatus +{ + /// Canonical Connector name that owns this server. + [JsonPropertyName("connectorName")] + public string ConnectorName { get; set; } = string.Empty; + + /// Opaque runtime server ID. + [JsonPropertyName("runtimeServerId")] + public string RuntimeServerId { get; set; } = string.Empty; + + /// Current live MCP host status. + [JsonPropertyName("status")] + public ConnectorMcpStatus Status { get; set; } +} + +/// Authoritative session connector state. Account IDs are opaque routing identifiers and credentials are never included. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public sealed class ConnectorStatus +{ + /// Opaque account selection pinned to this session, when one has been selected. + [JsonPropertyName("accountId")] + public string? AccountId { get; set; } + + /// Connector API contract version. + [JsonPropertyName("apiVersion")] + public long ApiVersion { get; set; } + + /// Exact selected account and stable scope requiring an authorization update, when proven by the Connector service. + [JsonPropertyName("authorizationRequirement")] + public ConnectorAuthorizationRequirement? AuthorizationRequirement { get; set; } + + /// Current feature and session availability. + [JsonPropertyName("availability")] + public ConnectorAvailability Availability { get; set; } + + /// Latest validated catalog snapshot, when available. + [JsonPropertyName("catalog")] + public ConnectorCatalogResult? Catalog { get; set; } + + /// Number of active opaque connection continuations. + [JsonPropertyName("pendingConnections")] + public long PendingConnections { get; set; } + + /// Live MCP status for every Connector-owned runtime server. + [JsonPropertyName("runtimeServers")] + public IList RuntimeServers { get => field ??= []; set; } +} + +/// Identifies the target session. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class SessionConnectorsGetStatusRequest +{ + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + +/// Pins a Connector operation to one host-owned GitHub account through its opaque selection ID. Provider tokens are never accepted. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class ConnectorAccountRequest +{ + /// Opaque account selection ID previously returned by an account discovery API. + [JsonPropertyName("accountId")] + public string AccountId { get; set; } = string.Empty; + + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + +/// Typed result of initiating or continuing a Connector connection. +/// Polymorphic base type discriminated by kind. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +[JsonPolymorphic( + TypeDiscriminatorPropertyName = "kind", + UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] +[JsonDerivedType(typeof(ConnectorConnectResultConnected), "connected")] +[JsonDerivedType(typeof(ConnectorConnectResultConsentRequired), "consent_required")] +[JsonDerivedType(typeof(ConnectorConnectResultPending), "pending")] +public partial class ConnectorConnectResult +{ + /// The type discriminator. + [JsonPropertyName("kind")] + public virtual string Kind { get; set; } = string.Empty; +} + + +/// The service is connected and the session MCP graph was reconciled. +/// The connected variant of . +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public partial class ConnectorConnectResultConnected : ConnectorConnectResult +{ + /// + [JsonIgnore] + public override string Kind => "connected"; + + /// Fresh authoritative Connector state after MCP reconciliation. + [JsonPropertyName("status")] + public required ConnectorStatus Status { get; set; } +} + +/// Host-owned consent is required before bounded continuation can complete. +/// The consent_required variant of . +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public partial class ConnectorConnectResultConsentRequired : ConnectorConnectResult +{ + /// + [JsonIgnore] + public override string Kind => "consent_required"; + + /// Validated HTTPS consent URL. The runtime does not open it. + [JsonPropertyName("consentUrl")] + public required string ConsentUrl { get; set; } + + /// Opaque ID accepted by continueConnection. + [JsonPropertyName("continuationId")] + public required string ContinuationId { get; set; } +} + +/// The service is still completing the connection without a consent URL. +/// The pending variant of . +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public partial class ConnectorConnectResultPending : ConnectorConnectResult +{ + /// + [JsonIgnore] + public override string Kind => "pending"; + + /// Opaque ID accepted by continueConnection. + [JsonPropertyName("continuationId")] + public required string ContinuationId { get; set; } +} + +/// Selects one Connector and the pinned host-owned account used for its service and MCP authorization. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class ConnectorConnectRequest +{ + /// Opaque account selection ID. It must match the account already pinned to the session, if any. + [JsonPropertyName("accountId")] + public string AccountId { get; set; } = string.Empty; + + /// Canonical Connector name from the current catalog. + [JsonPropertyName("connectorName")] + public string ConnectorName { get; set; } = string.Empty; + + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + +/// Explicitly bounded continuation of a pending Connector connection. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class ConnectorContinueRequest +{ + /// Opaque continuation ID returned by connect, reconnect, or an earlier continuation. + [JsonPropertyName("continuationId")] + public string ContinuationId { get; set; } = string.Empty; + + /// Maximum wall-clock duration in milliseconds for this call. Must be between one and the capability limit. + [JsonPropertyName("deadlineMs")] + public int DeadlineMs { get; set; } + + /// Maximum catalog requests made by this call. Must be between one and the capability limit. + [JsonPropertyName("maxAttempts")] + public int MaxAttempts { get; set; } + + /// Delay in milliseconds between attempts. Must not exceed the capability limit. + [JsonPropertyName("pollIntervalMs")] + public int PollIntervalMs { get; set; } + + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + +/// Authoritative result after disconnect and MCP reconciliation. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public sealed class ConnectorDisconnectResult +{ + /// Whether the service accepted the idempotent disconnect. + [JsonPropertyName("disconnected")] + public bool Disconnected { get; set; } + + /// Fresh authoritative session state after removing Connector-owned MCP servers. + [JsonPropertyName("status")] + public ConnectorStatus Status { get => field ??= new(); set; } +} + +/// Requests authoritative Connector-to-MCP reconciliation for the pinned account. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public sealed class ConnectorReconcileRequest +{ + /// Opaque account selection ID. It must match the account already pinned to the session, if any. + [JsonPropertyName("accountId")] + public required string AccountId { get; set; } + + /// Optional Connector name to reinitialize. Requires the targetedReconcile capability. + [JsonPropertyName("forceConnectorName")] + public string? ForceConnectorName { get; set; } + + /// When true, refresh the catalog before reconciling. A disabled Connector API performs no service request. + [JsonPropertyName("refreshCatalog")] + public bool? RefreshCatalog { get; set; } +} + +/// Requests authoritative Connector-to-MCP reconciliation for the pinned account. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class ConnectorReconcileRequestWithSession +{ + /// Opaque account selection ID. It must match the account already pinned to the session, if any. + [JsonPropertyName("accountId")] + public string AccountId { get; set; } = string.Empty; + + /// Optional Connector name to reinitialize. Requires the targetedReconcile capability. + [JsonPropertyName("forceConnectorName")] + public string? ForceConnectorName { get; set; } + + /// When true, refresh the catalog before reconciling. A disabled Connector API performs no service request. + [JsonPropertyName("refreshCatalog")] + public bool? RefreshCatalog { get; set; } + + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + +/// Identifies the target session. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class SessionConnectorsWithdrawProjectionRequest +{ + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + +/// Identifies the target session. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class SessionManagedSettingsGetRequest +{ + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + +/// Session plugin metadata, with name, marketplace, optional version, and enabled state. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public sealed class Plugin +{ + /// Opaque stable identity for a direct plugin source. + [JsonPropertyName("directSourceId")] + public string? DirectSourceId { get; set; } + + /// Whether the plugin is currently enabled. + [JsonPropertyName("enabled")] + public bool Enabled { get; set; } + + /// Whether this managed desired plugin has an installed or live record. + [JsonPropertyName("installed")] + public bool? Installed { get; set; } + + /// Absolute marketplace directory for a live plugin. + [JsonPropertyName("installedFrom")] + public string? InstalledFrom { get; set; } + + /// Whether enterprise managed settings control this plugin. + [JsonPropertyName("managed")] + public bool? Managed { get; set; } + + /// Enabled state required by enterprise managed settings. + [JsonPropertyName("managedDesiredEnabled")] + public bool? ManagedDesiredEnabled { 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; + + /// Runtime plugin provenance, such as "builtin". + [JsonPropertyName("source")] + public string? Source { get; set; } + + /// Installed version. + [JsonPropertyName("version")] + public string? Version { get; set; } +} + +/// Plugins installed for the session, with their enabled state and version metadata. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public sealed class PluginList +{ + /// Installed plugins. + [JsonPropertyName("plugins")] + public IList Plugins { get => field ??= []; set; } +} + +/// Identifies the target session. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class SessionPluginsListRequest +{ + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + +/// Plugin source resolved relative to the session's authoritative working directory. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class SessionPluginsInstallRequest +{ + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; + + /// 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; +} + +/// Name (or spec) of the plugin to uninstall. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class PluginsUninstallRequestWithSession +{ + /// 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; } + + /// Plugin name or "plugin@marketplace" spec to uninstall. When ambiguous, prefer the fully-qualified spec. + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; + + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + +/// Name (or spec) of the plugin to update. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class PluginsUpdateRequestWithSession +{ + /// Plugin name or "plugin@marketplace" spec to update. + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; + + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + +/// Plugin names (or specs) to enable in the session's authoritative working directory. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class SessionPluginsEnableRequest +{ + /// 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; } + + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + +/// Plugin names (or specs) to disable in the session's authoritative working directory. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class SessionPluginsDisableRequest +{ + /// 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; } + + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + +/// RPC data type for SessionPluginsReload operations. +[Experimental(global::GitHub.Copilot.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(global::GitHub.Copilot.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; +} + +/// Retry outcome for one plugin required by managed settings. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public sealed class ManagedPluginRetryEntry +{ + /// Why the plugin was not prepared. Present when `status` is `failed` or `deferred`. + [JsonPropertyName("error")] + public string? Error { get; set; } + + /// Plugin spec in "plugin@marketplace" form. + [JsonPropertyName("spec")] + public string Spec { get; set; } = string.Empty; + + /// What the retry did for this plugin. + [JsonPropertyName("status")] + public ManagedPluginRetryStatus Status { get; set; } +} + +/// Per-plugin outcomes of retrying the plugins required by the session's managed settings. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public sealed class SessionPluginsRetryManagedResult +{ + /// One entry per requested plugin, in request order. When no plugins were requested, one entry per plugin the managed settings require. + [JsonPropertyName("plugins")] + public IList Plugins { get => field ??= []; set; } +} + +/// RPC data type for SessionPluginsRetryManaged operations. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public sealed class SessionPluginsRetryManagedRequest +{ + /// "plugin@marketplace" specs to retry. Omit to retry every plugin the session's managed settings require. + [JsonPropertyName("plugins")] + public IList? Plugins { get; set; } +} + +/// RPC data type for SessionPluginsRetryManagedRequestWithSession operations. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class SessionPluginsRetryManagedRequestWithSession +{ + /// "plugin@marketplace" specs to retry. Omit to retry every plugin the session's managed settings require. + [JsonPropertyName("plugins")] + public IList? Plugins { get; set; } + + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + +/// Identifies the target session. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class SessionPluginsMarketplacesListRequest +{ + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + +/// Marketplace source and optional working directory for relative-path resolution. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class PluginsMarketplacesAddRequestWithSession +{ + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; + + /// 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; + + /// 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; } +} + +/// Name of the marketplace to remove and an optional force flag. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class PluginsMarketplacesRemoveRequestWithSession +{ + /// 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; } + + /// Marketplace name to remove. + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; + + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + +/// Name of the marketplace whose plugin catalog to fetch. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class PluginsMarketplacesBrowseRequestWithSession +{ + /// Marketplace name to browse. + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; + + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + +/// RPC data type for SessionPluginsMarketplacesRefresh operations. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public sealed class SessionPluginsMarketplacesRefreshRequest +{ + /// Marketplace name to refresh. When omitted, every registered marketplace is refreshed. + [JsonPropertyName("name")] + public string? Name { get; set; } +} + +/// RPC data type for SessionPluginsMarketplacesRefreshRequestWithSession operations. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class SessionPluginsMarketplacesRefreshRequestWithSession +{ + /// Marketplace name to refresh. When omitted, every registered marketplace is refreshed. + [JsonPropertyName("name")] + public string? Name { 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(global::GitHub.Copilot.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(global::GitHub.Copilot.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(global::GitHub.Copilot.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(global::GitHub.Copilot.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(global::GitHub.Copilot.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; } +} + +/// BYOK providers and/or models to add to the session's registry at runtime. Both fields are optional; provide providers, models, or both. +[Experimental(global::GitHub.Copilot.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; +} + +/// The selectable model entries and selection ids synthesized for the synchronized BYOK models. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public sealed class ProviderSyncResult +{ + /// True when synchronization withdrew the selected host-managed model, leaving the session with no explicit selection, so ordinary model resolution picks the session default. Synchronization never promotes a surviving host model in its place: publishing a model offers it, and the choice of which model to use stays with the user. + [JsonPropertyName("modelDeselected")] + public bool? ModelDeselected { get; set; } + + /// Synthesized selectable model entries for the synchronized BYOK models. + [JsonPropertyName("models")] + public IList Models { get => field ??= []; set; } + + /// Provider-qualified model selection ids present after synchronization. + [JsonPropertyName("selectionIds")] + public IList SelectionIds { get => field ??= []; set; } +} + +/// Authoritative BYOK provider and model registry snapshot to apply atomically to the session. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class ProviderSyncRequest +{ + /// BYOK model definition snapshot. Models absent from this list are removed. + [JsonPropertyName("models")] + public IList? Models { get; set; } + + /// Named BYOK provider connection snapshot. Providers absent from this list are removed. + [JsonPropertyName("providers")] + public IList? Providers { get; set; } + + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + +/// What the withdrawal actually removed from the registry. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public sealed class ProviderWithdrawResult +{ + /// True when withdrawal removed the selected host-managed model, leaving the session with no explicit selection, so ordinary model resolution picks the session default. Withdrawal never promotes a surviving model in its place: the choice of which model to use stays with the user. + [JsonPropertyName("modelDeselected")] + public bool? ModelDeselected { get; set; } + + /// Providers removed because one of the withdrawn models was the last entry referencing them. A provider that merely has no models is not removed. + [JsonPropertyName("providersRemoved")] + public IList ProvidersRemoved { get => field ??= []; set; } + + /// Selection ids that were registered and are now withdrawn. Excludes requested ids that were not present. + [JsonPropertyName("withdrawn")] + public IList Withdrawn { get => field ??= []; set; } +} + +/// Host-managed model selection ids to withdraw from the session's BYOK registry. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class ProviderWithdrawRequest +{ + /// Provider-qualified selection ids to withdraw. Ids that are not registered are ignored, so withdrawal is idempotent. A provider left with no models referencing it is removed too. + [JsonPropertyName("models")] + public IList Models { get => field ??= []; set; } + + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + +/// Indicates whether the session options patch was applied successfully. +[Experimental(global::GitHub.Copilot.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(global::GitHub.Copilot.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(global::GitHub.Copilot.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(global::GitHub.Copilot.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(global::GitHub.Copilot.Diagnostics.Experimental)] +public sealed class CapiSessionOptions +{ + /// Routing preference for sessions whose model is `auto`. On create or cold resume, this establishes the preference sent as `tier` on CAPI `/auto` requests; when omitted on cold resume, the runtime restores the last committed preference. On resident resume, a different value requests a safe switch after resume succeeds and cannot change an in-flight turn. Successful switches are persisted for later cold resume. When no preference is supplied or restored, CAPI default routing is used. `fast` is an integrator-only latency preset, not a first-party GitHub Copilot product 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(global::GitHub.Copilot.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(global::GitHub.Copilot.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; } + + /// The product serving the model, reported in telemetry as `model_provider`. Set it when `type` alone cannot identify the product, such as Ollama or LM Studio behind an OpenAI-compatible endpoint. Only affects telemetry. + [JsonPropertyName("modelProvider")] + public ProviderConfigModelProvider? ModelProvider { 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; } +} + +/// +/// 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(global::GitHub.Copilot.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(global::GitHub.Copilot.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(global::GitHub.Copilot.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(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class SessionUpdateOptionsParams +{ + /// Additional content-exclusion policies to merge into the session's policy set. + [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] + [JsonPropertyName("additionalContentExclusionPolicies")] + public IList? AdditionalContentExclusionPolicies { get; set; } + + /// Runtime context discriminator (e.g., `cli`, `actions`). + [JsonPropertyName("agentContext")] + public string? AgentContext { get; set; } + + /// Explicit discovery-only agent roots, separate from customAgents and filesystem grants. Available without enableConfigDiscovery, subject to managed/source disables. Relative SDK paths resolve once when supplied against the request's effective workingDirectory; retained roots do not move on cwd changes. Null and omission are equivalent; [] clears only the explicit SDK contribution. + [JsonPropertyName("agentDirectories")] + public IList? AgentDirectories { 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 repository-origin file hooks from `.github/hooks/` and trusted repository-configured roots. Does not control explicit hookDirectories or SDK callback hooks. + [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 explicit SDK roots and a bound SDK provider; changing the value invalidates the loaded skill snapshot. When omitted, creation falls back to enableConfigDiscovery unless explicit SDK skill roots or an SDK skill provider are supplied. + [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; } + + /// Explicit discovery-only file-hook roots, separate from callback hooks and filesystem grants. Available without enableConfigDiscovery or enableFileHooks, which controls repository-origin hooks only; hook/managed disables still apply. Relative SDK paths resolve once when supplied against the request's effective workingDirectory; retained roots do not move on cwd changes. Null and omission are equivalent; [] clears only the explicit SDK contribution. + [JsonPropertyName("hookDirectories")] + public IList? HookDirectories { get; set; } + + /// Skill scan directories and descendants excluded from discovery. Supports `~`-relative paths. + [JsonPropertyName("ignoredSkillsLocations")] + public IList? IgnoredSkillsLocations { 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; } + + /// Explicit discovery-only instruction roots using existing SDK instruction formats, not filesystem grants. Available without enableConfigDiscovery, subject to instruction/managed disables. Relative SDK paths resolve once when supplied against the request's effective workingDirectory; retained roots do not move on cwd changes. Null and omission are equivalent; [] clears only the explicit SDK contribution. + [JsonPropertyName("instructionDirectories")] + public IList? InstructionDirectories { 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. Settings-derived origins (never_configured, user_enabled, user_disabled, repository_policy) let managed policy floor a host preference; explicit below-floor changes remain policy conflicts unless a session opt-out is authorized. Also used for telemetry provenance. + [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] + [JsonPropertyName("sandboxConfigSource")] + public 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")] +#else + [Obsolete("This member is deprecated and will be removed in a future version.")] +#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; } + + /// Explicit discovery-only skill roots, not filesystem grants. Available without enableConfigDiscovery, subject to skill/managed disables. Relative SDK paths resolve once when supplied against the request's effective workingDirectory; retained roots do not move on cwd changes. Null and omission are equivalent; [] clears only the explicit SDK contribution. + [JsonPropertyName("skillDirectories")] + public IList? SkillDirectories { 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; } + /// 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; } } -/// Installed plugin record for a session, with marketplace, version, install time, enabled state, cache path, and source. +/// Parameters for (re)loading the merged LSP configuration set. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class SessionInstalledPlugin +internal sealed class LspInitializeRequest { - /// Path where the plugin is cached locally. - [JsonPropertyName("cache_path")] - public string? CachePath { get; set; } + /// Force re-initialization even when LSP configs were already loaded for the working directory. + [JsonPropertyName("force")] + public bool? Force { get; set; } - /// Whether the plugin is currently enabled. - [JsonPropertyName("enabled")] - public bool Enabled { get; set; } + /// Git root used as the boundary when traversing for project-level LSP configs (supports monorepos). + [JsonPropertyName("gitRoot")] + public string? GitRoot { get; set; } - /// 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; - /// 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; } + /// Working directory used to load project-level LSP configs. Defaults to the session working directory when omitted. + [JsonPropertyName("workingDirectory")] + public string? WorkingDirectory { get; set; } +} - /// Marketplace the plugin came from (empty string for direct repo installs). - [JsonPropertyName("marketplace")] - public string Marketplace { get; set; } = string.Empty; +/// Discovered extension metadata, including source-qualified ID, name, discovery source, status, and optional process ID. +[Experimental(global::GitHub.Copilot.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; - /// Plugin name. + /// Extension name (directory name). [JsonPropertyName("name")] public string Name { get; set; } = string.Empty; - /// Source descriptor for direct repo installs (when marketplace is 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 JsonElement? Source { get; set; } + public ExtensionSource 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; } + /// Current status: running, disabled, failed, or starting. + [JsonPropertyName("status")] + public ExtensionStatus Status { get; set; } +} - /// Installed version, if known. - [JsonPropertyName("version")] - public string? Version { get; set; } +/// Extensions discovered for the session, with their current status. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public sealed class ExtensionList +{ + /// Discovered extensions and their current status. + [JsonPropertyName("extensions")] + public IList Extensions { get => field ??= []; set; } } -/// Custom model-provider configuration (BYOK). +/// Identifies the target session. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class ProviderConfig +internal sealed class SessionExtensionsListRequest { - /// API key. Optional for local providers like Ollama. - [JsonPropertyName("apiKey")] - public string? ApiKey { get; set; } + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} - /// Azure-specific provider options. - [JsonPropertyName("azure")] - public ProviderConfigAzure? Azure { get; set; } +/// Source-qualified extension identifier to enable for the session. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class ExtensionsEnableRequest +{ + /// Source-qualified extension ID to enable. + [JsonPropertyName("id")] + public string Id { get; set; } = string.Empty; - /// API endpoint URL. - [JsonPropertyName("baseUrl")] - public string BaseUrl { get; set; } = string.Empty; + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { 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; } +/// Source-qualified extension identifier to disable for the session. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class ExtensionsDisableRequest +{ + /// Source-qualified extension ID to disable. + [JsonPropertyName("id")] + public string Id { get; set; } = string.Empty; - /// When true, the SDK client supplies bearer tokens on demand: the runtime calls the client-session `providerToken.getToken` callback before each request and applies the returned token as an `Authorization: Bearer <token>` header. This is the bearer/OAuth scheme used by Azure AD / managed-identity tokens and provider OAuth access tokens (including Anthropic's), not a provider-specific API-key header such as Anthropic's `x-api-key`. The token-acquiring function itself stays on the SDK side and is never serialized; only this flag crosses the wire. When set alongside `apiKey`/`bearerToken`, the callback takes precedence: the runtime applies the token returned by `providerToken.getToken` as the `Authorization: Bearer` header for each request and does not send the static credential. - [JsonPropertyName("hasBearerTokenProvider")] - public bool? HasBearerTokenProvider { get; set; } + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} - /// Custom HTTP headers to include in all outbound requests to the provider. - [JsonPropertyName("headers")] - public IDictionary? Headers { get; set; } +/// Identifies the target session. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class SessionExtensionsReloadRequest +{ + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} - /// Maximum context window tokens for the model. - [JsonPropertyName("maxContextWindowTokens")] - public double? MaxContextWindowTokens { get; set; } +/// Identifies the target session. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class SessionExtensionsReconcileRequest +{ + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} - /// Maximum output tokens for the model. - [JsonPropertyName("maxOutputTokens")] - public double? MaxOutputTokens { get; set; } +/// Attachment union accepted by push input, covering files, directories, GitHub objects, blobs, snippets, and extension context. +/// Polymorphic base type discriminated by type. +[Experimental(global::GitHub.Copilot.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; +} - /// 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; } +/// Optional line range to scope the attachment to a specific section of the file. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public sealed class PushAttachmentFileLineRange +{ + /// End line number (1-based, inclusive). + [JsonPropertyName("end")] + public long End { 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; } + /// Start line number (1-based). + [JsonPropertyName("start")] + public long Start { get; set; } +} - /// The product serving the model, reported in telemetry as `model_provider`. Set it when `type` alone cannot identify the product, such as Ollama or LM Studio behind an OpenAI-compatible endpoint. Only affects telemetry. - [JsonPropertyName("modelProvider")] - public ProviderConfigModelProvider? ModelProvider { get; set; } +/// File attachment. +/// The file variant of . +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public partial class PushAttachmentFile : PushAttachment +{ + /// + [JsonIgnore] + public override string Type => "file"; - /// Provider name used for model and telemetry attribution. - [JsonPropertyName("providerName")] - public string? ProviderName { get; set; } + /// User-facing display name for the attachment. + [JsonPropertyName("displayName")] + public required string DisplayName { get; set; } - /// Provider transport. Defaults to "http". - [JsonPropertyName("transport")] - public ProviderConfigTransport? Transport { 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; } - /// Provider type. Defaults to "openai" for generic OpenAI-compatible APIs. - [JsonPropertyName("type")] - public ProviderConfigType? Type { get; set; } + /// Absolute file path. + [JsonPropertyName("path")] + public required string Path { get; set; } +} - /// Wire API format (openai/azure only). Defaults to "completions". - [JsonPropertyName("wireApi")] - public ProviderConfigWireApi? WireApi { get; set; } +/// Directory attachment. +/// The directory variant of . +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public partial class PushAttachmentDirectory : PushAttachment +{ + /// + [JsonIgnore] + public override string Type => "directory"; - /// 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; } + /// 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; } } -/// -/// 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. -/// +/// End position of the selection. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class ShellCredentials +public sealed class PushAttachmentSelectionDetailsEnd { - /// - /// 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; } + /// End character offset within the line (0-based). + [JsonPropertyName("character")] + public long Character { 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; } + /// End line number (0-based). + [JsonPropertyName("line")] + public long Line { get; set; } } -/// A host-provided script sourced before each built-in shell command when its shell target matches the active shell. +/// Start position of the selection. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class ShellInitScript +public sealed class PushAttachmentSelectionDetailsStart { - /// Path to the script to source. - [JsonPropertyName("path")] - public string Path { get; set; } = string.Empty; + /// Start character offset within the line (0-based). + [JsonPropertyName("character")] + public long Character { get; set; } - /// Built-in shell that may source this script. - [JsonPropertyName("shell")] - public ShellInitScriptShell Shell { get; set; } + /// Start line number (0-based). + [JsonPropertyName("line")] + public long Line { get; set; } } -/// Per-session settings for built-in shell tools. +/// Position range of the selection within the file. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class ShellOptions +public sealed class PushAttachmentSelectionDetails { - /// Command-scoped GitHub credential injection for shell commands. - [JsonPropertyName("credentials")] - public ShellCredentials? Credentials { get; set; } + /// End position of the selection. + [JsonPropertyName("end")] + public PushAttachmentSelectionDetailsEnd End { get => field ??= new(); set; } - /// Controls automatic non-interactive profile loading where supported. Explicit initScripts are unaffected. - [JsonPropertyName("initProfile")] - public ShellInitProfile? InitProfile { get; set; } + /// Start position of the selection. + [JsonPropertyName("start")] + public PushAttachmentSelectionDetailsStart Start { get => field ??= new(); 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; } +/// Code selection attachment from an editor. +/// The selection variant of . +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public partial class PushAttachmentSelection : PushAttachment +{ + /// + [JsonIgnore] + public override string Type => "selection"; - /// - /// 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; } + /// 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; } } -/// Patch of mutable session options to apply to the running session. +/// GitHub issue, pull request, or discussion reference. +/// The github_reference variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionUpdateOptionsParams +public partial class PushAttachmentGitHubReference : PushAttachment { - /// Additional content-exclusion policies to merge into the session's policy set. - [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] - [JsonPropertyName("additionalContentExclusionPolicies")] - public IList? AdditionalContentExclusionPolicies { get; set; } + /// + [JsonIgnore] + public override string Type => "github_reference"; - /// Runtime context discriminator (e.g., `cli`, `actions`). - [JsonPropertyName("agentContext")] - public string? AgentContext { get; set; } + /// Issue, pull request, or discussion number. + [JsonPropertyName("number")] + public required long Number { 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; } + /// Type of GitHub reference. + [JsonPropertyName("referenceType")] + public required PushAttachmentGitHubReferenceType ReferenceType { get; set; } - /// Whether to disable the `ask_user` tool (encourages autonomous behavior). - [JsonPropertyName("askUserDisabled")] - public bool? AskUserDisabled { get; set; } + /// Current state of the referenced item (e.g., open, closed, merged). + [JsonPropertyName("state")] + public required string State { get; set; } - /// Allowlist of tool names available to this session. - [JsonPropertyName("availableTools")] - public IList? AvailableTools { get; set; } + /// Title of the referenced item. + [JsonPropertyName("title")] + public required string Title { get; set; } - /// Options scoped to the built-in CAPI (Copilot API) provider. - [JsonPropertyName("capi")] - public CapiSessionOptions? Capi { get; set; } + /// URL to the referenced item on GitHub. + [JsonPropertyName("url")] + public required string Url { get; set; } +} - /// Identifier of the client driving the session. - [JsonPropertyName("clientName")] - public string? ClientName { get; set; } +/// Pointer to a GitHub repository. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public sealed class PushGitHubRepoRef +{ + /// Numeric GitHub repository id. + [JsonPropertyName("id")] + public long? Id { get; set; } - /// Whether to include the `Co-authored-by` trailer in commit messages. - [JsonPropertyName("coauthorEnabled")] - public bool? CoauthorEnabled { get; set; } + /// Repository name (without owner). + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; - /// 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; } + /// Repository owner login (user or organization). + [JsonPropertyName("owner")] + public string Owner { get; set; } = string.Empty; +} - /// Whether to allow auto-mode continuation across turns. - [JsonPropertyName("continueOnAutoMode")] - public bool? ContinueOnAutoMode { get; set; } +/// Pointer to a GitHub commit. +/// The github_commit variant of . +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public partial class PushAttachmentGitHubCommit : PushAttachment +{ + /// + [JsonIgnore] + public override string Type => "github_commit"; - /// Override URL for the Copilot API endpoint. - [JsonPropertyName("copilotUrl")] - public string? CopilotUrl { get; set; } + /// First line of the commit message. + [JsonPropertyName("message")] + public required string Message { get; set; } - /// Whether to default custom agents to local-only execution. - [JsonPropertyName("customAgentsLocalOnly")] - public bool? CustomAgentsLocalOnly { 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(global::GitHub.Copilot.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; } - /// Instruction source IDs to exclude from the system prompt. - [JsonPropertyName("disabledInstructionSources")] - public IList? DisabledInstructionSources { get; set; } + /// URL to the release on GitHub. + [JsonPropertyName("url")] + public required string Url { get; set; } +} - /// Skill IDs that should be excluded from this session. - [JsonPropertyName("disabledSkills")] - public IList? DisabledSkills { get; set; } +/// Pointer to a GitHub Actions job. +/// The github_actions_job variant of . +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public partial class PushAttachmentGitHubActionsJob : PushAttachment +{ + /// + [JsonIgnore] + public override string Type => "github_actions_job"; - /// Whether to enable loading of `.github/hooks/` filesystem hooks. Separate from the SDK callback hook mechanism. - [JsonPropertyName("enableFileHooks")] - public bool? EnableFileHooks { 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; } - /// Whether to enable host git operations (context resolution, child repo scanning, git info in system prompt). - [JsonPropertyName("enableHostGitOperations")] - public bool? EnableHostGitOperations { get; set; } + /// Job id within the workflow run. + [JsonPropertyName("jobId")] + public required long JobId { 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; } + /// Display name of the job. + [JsonPropertyName("jobName")] + public required string JobName { get; set; } - /// Whether to surface reasoning-summary events from the model. - [JsonPropertyName("enableReasoningSummaries")] - public bool? EnableReasoningSummaries { get; set; } + /// Repository the workflow run belongs to. + [JsonPropertyName("repo")] + public required PushGitHubRepoRef Repo { get; set; } - /// Whether shell-script safety heuristics are enabled. - [JsonPropertyName("enableScriptSafety")] - public bool? EnableScriptSafety { get; set; } + /// URL to the job on GitHub. + [JsonPropertyName("url")] + public required string Url { get; set; } - /// Whether to enable cross-session store writes and reads. - [JsonPropertyName("enableSessionStore")] - public bool? EnableSessionStore { get; set; } + /// Display name of the workflow the job ran in. + [JsonPropertyName("workflowName")] + public required string WorkflowName { 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; } +/// Pointer to a GitHub repository. +/// The github_repository variant of . +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public partial class PushAttachmentGitHubRepository : PushAttachment +{ + /// + [JsonIgnore] + public override string Type => "github_repository"; - /// Whether to stream model responses. - [JsonPropertyName("enableStreaming")] - public bool? EnableStreaming { get; set; } + /// Short description of the repository. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("description")] + public string? Description { 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; } + /// 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; } - /// 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; } + /// Repository pointer. + [JsonPropertyName("repo")] + public required PushGitHubRepoRef Repo { get; set; } - /// Whether subagent callback events should be forwarded into the session event log sink. - [JsonPropertyName("eventsLogIncludesSubagents")] - public bool? EventsLogIncludesSubagents { get; set; } + /// URL to the repository on GitHub. + [JsonPropertyName("url")] + public required string Url { 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; } +/// One side of a file diff (head or base). +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public sealed class PushAttachmentGitHubFileDiffSide +{ + /// Repository-relative path to the file. + [JsonPropertyName("path")] + public string Path { get; set; } = string.Empty; - /// Denylist of tool names for this session. - [JsonPropertyName("excludedTools")] - public IList? ExcludedTools { get; set; } + /// Git ref (branch, tag, or commit SHA) the file is read at. + [JsonPropertyName("ref")] + public string Ref { get; set; } = string.Empty; - /// Map of feature-flag IDs to their boolean enabled state. - [JsonPropertyName("featureFlags")] - public IDictionary? FeatureFlags { get; set; } + /// Repository the file lives in. + [JsonPropertyName("repo")] + public PushGitHubRepoRef Repo { get => field ??= new(); set; } +} - /// Skill scan directories and descendants excluded from discovery. Supports `~`-relative paths. - [JsonPropertyName("ignoredSkillsLocations")] - public IList? IgnoredSkillsLocations { get; set; } +/// Pointer to a single-file diff. At least one of `head` and `base` must be present. +/// The github_file_diff variant of . +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public partial class PushAttachmentGitHubFileDiff : PushAttachment +{ + /// + [JsonIgnore] + public override string Type => "github_file_diff"; - /// 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; } + /// File location on the base side of the diff. Absent for additions. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("base")] + public PushAttachmentGitHubFileDiffSide? Base { 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; } + /// File location on the head side of the diff. Absent for deletions. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("head")] + public PushAttachmentGitHubFileDiffSide? Head { 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; } + /// URL to the diff on GitHub (e.g., a commit, compare, or PR-file URL). + [JsonPropertyName("url")] + public required string Url { get; set; } +} - /// Stable integration identifier used for analytics and rate-limit attribution. - [JsonPropertyName("integrationId")] - public string? IntegrationId { get; set; } +/// One side of a tree comparison (head or base). +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public sealed class PushAttachmentGitHubTreeComparisonSide +{ + /// Repository the revision belongs to. + [JsonPropertyName("repo")] + public PushGitHubRepoRef Repo { get => field ??= new(); set; } - /// Whether experimental capabilities are enabled. - [JsonPropertyName("isExperimentalMode")] - public bool? IsExperimentalMode { get; set; } + /// Git revision (branch, tag, or commit SHA). + [JsonPropertyName("revision")] + public string Revision { get; set; } = string.Empty; +} - /// Whether interactive shell sessions are logged. - [JsonPropertyName("logInteractiveShells")] - public bool? LogInteractiveShells { get; set; } +/// Pointer to a comparison between two git revisions. +/// The github_tree_comparison variant of . +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public partial class PushAttachmentGitHubTreeComparison : PushAttachment +{ + /// + [JsonIgnore] + public override string Type => "github_tree_comparison"; - /// Identifier sent to LSP-style integrations. - [JsonPropertyName("lspClientName")] - public string? LspClientName { get; set; } + /// Base side of the comparison. + [JsonPropertyName("base")] + public required PushAttachmentGitHubTreeComparisonSide Base { 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; } + /// Head side of the comparison. + [JsonPropertyName("head")] + public required PushAttachmentGitHubTreeComparisonSide Head { 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; } + /// URL to the comparison on GitHub. + [JsonPropertyName("url")] + public required string Url { get; set; } +} - /// The model ID to use for assistant turns. - [JsonPropertyName("model")] - public string? Model { get; set; } +/// Generic GitHub URL reference. +/// The github_url variant of . +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public partial class PushAttachmentGitHubUrl : PushAttachment +{ + /// + [JsonIgnore] + public override string Type => "github_url"; - /// Per-property model capability overrides for the selected model. - [JsonPropertyName("modelCapabilitiesOverrides")] - public ModelCapabilitiesOverride? ModelCapabilitiesOverrides { get; set; } + /// URL to the GitHub resource. + [JsonPropertyName("url")] + public required string Url { get; set; } +} - /// Organization-level custom instructions to inject into the system prompt. - [JsonPropertyName("organizationCustomInstructions")] - public string? OrganizationCustomInstructions { get; set; } +/// Pointer to a file in a GitHub repository at a specific ref. +/// The github_file variant of . +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public partial class PushAttachmentGitHubFile : PushAttachment +{ + /// + [JsonIgnore] + public override string Type => "github_file"; - /// Custom model-provider configuration (BYOK). - [JsonPropertyName("provider")] - public ProviderConfig? Provider { get; set; } + /// Repository-relative path to the file. + [JsonPropertyName("path")] + public required string Path { 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; } + /// Git ref the file is read at (branch, tag, or commit SHA). + [JsonPropertyName("ref")] + public required string Ref { get; set; } - /// Reasoning summary mode for supported model clients. - [JsonPropertyName("reasoningSummary")] - public OptionsUpdateReasoningSummary? ReasoningSummary { get; set; } + /// Repository the file lives in. + [JsonPropertyName("repo")] + public required PushGitHubRepoRef Repo { get; set; } - /// Whether the session is running in an interactive UI. - [JsonPropertyName("runningInInteractiveMode")] - public bool? RunningInInteractiveMode { get; set; } + /// URL to the file on GitHub. + [JsonPropertyName("url")] + public required string Url { get; set; } +} - /// Resolved sandbox configuration. - [JsonPropertyName("sandboxConfig")] - public SandboxConfig? SandboxConfig { get; set; } +/// Pointer to a line range inside a file in a GitHub repository. +/// The github_snippet variant of . +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public partial class PushAttachmentGitHubSnippet : PushAttachment +{ + /// + [JsonIgnore] + public override string Type => "github_snippet"; - /// Origin of the sandbox choice. Settings-derived origins (never_configured, user_enabled, user_disabled, repository_policy) let managed policy floor a host preference; explicit below-floor changes remain policy conflicts unless a session opt-out is authorized. Also used for telemetry provenance. - [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] - [JsonPropertyName("sandboxConfigSource")] - public SandboxConfigSource? SandboxConfigSource { get; set; } + /// Line range the snippet covers. + [JsonPropertyName("lineRange")] + public required PushAttachmentFileLineRange LineRange { 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; } + /// Repository-relative path to the file. + [JsonPropertyName("path")] + public required string Path { get; set; } - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Git ref the file is read at (branch, tag, or commit SHA). + [JsonPropertyName("ref")] + public required string Ref { get; set; } - /// Optional session limits. Pass null to clear the session limits. - [JsonPropertyName("sessionLimits")] - public SessionLimitsConfig? SessionLimits { get; set; } + /// Repository the file lives in. + [JsonPropertyName("repo")] + public required PushGitHubRepoRef Repo { get; set; } - /// Per-session settings for built-in shell tools. - [JsonPropertyName("shell")] - public ShellOptions? Shell { get; set; } + /// URL to the snippet on GitHub (with line anchor). + [JsonPropertyName("url")] + public required string Url { 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")] -#else - [Obsolete("This member is deprecated and will be removed in a future version.")] -#endif - [JsonPropertyName("shellInitProfile")] - public string? ShellInitProfile { get; set; } +/// Blob attachment with inline base64-encoded data. +/// The blob variant of . +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public partial class PushAttachmentBlob : PushAttachment +{ + /// + [JsonIgnore] + public override string Type => "blob"; - /// PowerShell process flags applied to built-in and user-requested shell commands. - [JsonPropertyName("shellProcessFlags")] - public IList? ShellProcessFlags { get; set; } + /// Base64-encoded content. + [Base64String] + [JsonPropertyName("data")] + public required string Data { get; set; } - /// Additional directories to search for skills. - [JsonPropertyName("skillDirectories")] - public IList? SkillDirectories { get; set; } + /// User-facing display name for the attachment. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("displayName")] + public string? DisplayName { get; set; } - /// Whether to skip loading custom instruction sources. - [JsonPropertyName("skipCustomInstructions")] - public bool? SkipCustomInstructions { get; set; } + /// MIME type of the inline data. + [JsonPropertyName("mimeType")] + public required string MimeType { get; set; } +} - /// Whether to skip embedding retrieval pipeline initialization and execution. - [JsonPropertyName("skipEmbeddingRetrieval")] - public bool? SkipEmbeddingRetrieval { get; set; } +/// Slim input shape for extension_context attachments; identity fields are runtime-derived. +/// The extension_context variant of . +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public partial class PushAttachmentExtensionContext : PushAttachment +{ + /// + [JsonIgnore] + public override string Type => "extension_context"; - /// 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; } + /// Caller-supplied JSON payload (required, may be null but not undefined). + [JsonPropertyName("payload")] + public required JsonElement Payload { get; set; } - /// Controls how availableTools (allowlist) and excludedTools (denylist) combine when both are set. - [JsonPropertyName("toolFilterPrecedence")] - public OptionsUpdateToolFilterPrecedence? ToolFilterPrecedence { 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; } +} - /// Optional path for trajectory output. - [JsonPropertyName("trajectoryFile")] - public string? TrajectoryFile { get; set; } +/// Parameters for session.extensions.sendAttachmentsToMessage. +[Experimental(global::GitHub.Copilot.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; } - /// Output verbosity level for supported models. - [JsonPropertyName("verbosity")] - public Verbosity? Verbosity { get; set; } + /// Optional canvas instance binding the push for provenance. When supplied, the runtime resolves the canvas, verifies it is owned by the calling extension, and stamps canvasId/instanceId onto each extension_context entry. When omitted, no resolution runs and those fields stay unset on the attachment. + [JsonPropertyName("instanceId")] + public string? InstanceId { get; set; } - /// Absolute working-directory path for shell tools. - [JsonPropertyName("workingDirectory")] - public string? WorkingDirectory { get; set; } + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } -/// Parameters for (re)loading the merged LSP configuration set. +/// A tool name and arguments to execute through the session's native invocation pipeline. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class LspInitializeRequest +internal sealed class ToolsExecuteRequest { - /// Force re-initialization even when LSP configs were already loaded for the working directory. - [JsonPropertyName("force")] - public bool? Force { get; set; } + /// Arguments supplied to the tool. + [JsonPropertyName("arguments")] + public JsonElement Arguments { get; set; } - /// Git root used as the boundary when traversing for project-level LSP configs (supports monorepos). - [JsonPropertyName("gitRoot")] - public string? GitRoot { get; set; } + /// Name of the 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; - /// Working directory used to load project-level LSP configs. Defaults to the session working directory when omitted. - [JsonPropertyName("workingDirectory")] - public string? WorkingDirectory { get; set; } + /// Optional identifier used to correlate this invocation with its tool call. + [JsonPropertyName("toolCallId")] + public string? ToolCallId { get; set; } } -/// Discovered extension metadata, including source-qualified ID, name, discovery source, status, and optional process ID. +/// Custom grammar input format accepted by a built-in tool. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class Extension +public sealed class BuiltinToolFormat { - /// 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; + /// Grammar definition accepted by the tool. + [JsonPropertyName("definition")] + public string Definition { get; set; } = string.Empty; - /// Extension name (directory name). + /// 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(global::GitHub.Copilot.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(global::GitHub.Copilot.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; - /// Process ID if the extension is running. - [JsonPropertyName("pid")] - public long? Pid { get; set; } + /// Policy describing which tool metadata may be recorded without obfuscation. + [JsonPropertyName("safeForTelemetry")] + public JsonElement SafeForTelemetry { 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; } + /// Optional human-readable title for the tool. + [JsonPropertyName("title")] + public string? Title { get; set; } - /// Current status: running, disabled, failed, or starting. - [JsonPropertyName("status")] - public ExtensionStatus Status { get; set; } + /// Optional tool category discriminator. + [JsonPropertyName("type")] + public string? Type { get; set; } } -/// Extensions discovered for the session, with their current status. +/// Rust-owned built-in tool descriptors for the session. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class ExtensionList +public sealed class ToolsGetBuiltinDescriptorsResult { - /// Discovered extensions and their current status. - [JsonPropertyName("extensions")] - public IList Extensions { get => field ??= []; set; } + /// Built-in tool descriptors materialized for the session. + [JsonPropertyName("tools")] + public IList Tools { get => field ??= []; set; } } -/// Identifies the target session. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionExtensionsListRequest -{ - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; +/// Shell-specific names and description lines used to materialize built-in shell tool descriptors. +[Experimental(global::GitHub.Copilot.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; } -/// Source-qualified extension identifier to enable for the session. +/// Options controlling how Rust-owned built-in tool descriptors are materialized. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class ExtensionsEnableRequest +internal sealed class ToolsGetBuiltinDescriptorsRequest { - /// Source-qualified extension ID to enable. - [JsonPropertyName("id")] - public string Id { get; set; } = string.Empty; + /// Whether background task completion notifications are enabled. + [JsonPropertyName("backgroundTaskNotificationsEnabled")] + public bool? BackgroundTaskNotificationsEnabled { get; set; } - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; -} + /// Whether tool descriptors should include authoring metadata. + [JsonPropertyName("includeAuthor")] + public bool? IncludeAuthor { get; set; } -/// Source-qualified extension identifier to disable for the session. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class ExtensionsDisableRequest -{ - /// Source-qualified extension ID to disable. - [JsonPropertyName("id")] - public string Id { get; set; } = string.Empty; + /// 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; } } -/// Identifies the target session. +/// Task completion notification with summary from the agent. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionExtensionsReloadRequest +public sealed class TaskCompleteData { - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Structured blocker details when outcome is blocked. + [JsonPropertyName("blocker")] + public TaskBlocker? Blocker { get; set; } + + /// 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; } } -/// Identifies the target session. +/// Binary result returned by a tool for the model. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionExtensionsReconcileRequest +public sealed class ExternalToolTextResultForLlmBinaryResultsForLlm { - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// 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; } } -/// Attachment union accepted by push input, covering files, directories, GitHub objects, blobs, snippets, and extension context. +/// 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(global::GitHub.Copilot.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 +[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")] @@ -20259,1389 +23404,1639 @@ public partial class PushAttachment } -/// Optional line range to scope the attachment to a specific section of the file. +/// Plain text content block. +/// The text variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class PushAttachmentFileLineRange +public partial class ExternalToolTextResultForLlmContentText : ExternalToolTextResultForLlmContent { - /// End line number (1-based, inclusive). - [JsonPropertyName("end")] - public long End { get; set; } + /// + [JsonIgnore] + public override string Type => "text"; - /// Start line number (1-based). - [JsonPropertyName("start")] - public long Start { get; set; } + /// The text content. + [JsonPropertyName("text")] + public required string Text { get; set; } } -/// File attachment. -/// The file variant of . +/// Terminal/shell output content block with optional exit code and working directory. +/// The terminal variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class PushAttachmentFile : PushAttachment +public partial class ExternalToolTextResultForLlmContentTerminal : ExternalToolTextResultForLlmContent { /// [JsonIgnore] - public override string Type => "file"; + public override string Type => "terminal"; - /// User-facing display name for the attachment. - [JsonPropertyName("displayName")] - public required string DisplayName { get; set; } + /// Working directory where the command was executed. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("cwd")] + public string? Cwd { get; set; } - /// Optional line range to scope the attachment to a specific section of the file. + /// Process exit code, if the command has completed. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("lineRange")] - public PushAttachmentFileLineRange? LineRange { get; set; } + [JsonPropertyName("exitCode")] + public long? ExitCode { get; set; } - /// Absolute file path. - [JsonPropertyName("path")] - public required string Path { get; set; } + /// Terminal/shell output text. + [JsonPropertyName("text")] + public required string Text { get; set; } } -/// Directory attachment. -/// The directory variant of . +/// Shell command exit metadata with optional output preview. +/// The shell_exit variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class PushAttachmentDirectory : PushAttachment +public partial class ExternalToolTextResultForLlmContentShellExit : ExternalToolTextResultForLlmContent { /// [JsonIgnore] - public override string Type => "directory"; + public override string Type => "shell_exit"; - /// User-facing display name for the attachment. - [JsonPropertyName("displayName")] - public required string DisplayName { get; set; } + /// Working directory where the shell command was executed. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("cwd")] + public string? Cwd { get; set; } - /// Absolute directory path. - [JsonPropertyName("path")] - public required string Path { get; set; } -} + /// Exit code from the completed shell command. + [JsonPropertyName("exitCode")] + public required long ExitCode { get; set; } -/// End position of the selection. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class PushAttachmentSelectionDetailsEnd -{ - /// End character offset within the line (0-based). - [JsonPropertyName("character")] - public long Character { 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; } - /// End line number (0-based). - [JsonPropertyName("line")] - public long Line { 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; } -/// Start position of the selection. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class PushAttachmentSelectionDetailsStart -{ - /// Start character offset within the line (0-based). - [JsonPropertyName("character")] - public long Character { get; set; } + /// Whether outputPreview is known to be incomplete or truncated. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("outputTruncated")] + public bool? OutputTruncated { get; set; } - /// Start line number (0-based). - [JsonPropertyName("line")] - public long Line { get; set; } + /// Shell id, as assigned by Copilot runtime. + [JsonPropertyName("shellId")] + public required string ShellId { get; set; } } -/// Position range of the selection within the file. +/// Image content block with base64-encoded data. +/// The image variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class PushAttachmentSelectionDetails +public partial class ExternalToolTextResultForLlmContentImage : ExternalToolTextResultForLlmContent { - /// End position of the selection. - [JsonPropertyName("end")] - public PushAttachmentSelectionDetailsEnd End { get => field ??= new(); set; } + /// + [JsonIgnore] + public override string Type => "image"; - /// Start position of the selection. - [JsonPropertyName("start")] - public PushAttachmentSelectionDetailsStart Start { get => field ??= new(); set; } + /// 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; } } -/// Code selection attachment from an editor. -/// The selection variant of . +/// Audio content block with base64-encoded data. +/// The audio variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class PushAttachmentSelection : PushAttachment +public partial class ExternalToolTextResultForLlmContentAudio : ExternalToolTextResultForLlmContent { /// [JsonIgnore] - public override string Type => "selection"; + public override string Type => "audio"; - /// User-facing display name for the selection. - [JsonPropertyName("displayName")] - public required string DisplayName { get; set; } + /// Base64-encoded audio data. + [Base64String] + [JsonPropertyName("data")] + public required string Data { get; set; } - /// Absolute path to the file containing the selection. - [JsonPropertyName("filePath")] - public required string FilePath { get; set; } + /// MIME type of the audio (e.g., audio/wav, audio/mpeg). + [JsonPropertyName("mimeType")] + public required string MimeType { get; set; } +} - /// Position range of the selection within the file. - [JsonPropertyName("selection")] - public required PushAttachmentSelectionDetails Selection { get; set; } +/// Icon image for a resource. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public sealed class ExternalToolTextResultForLlmContentResourceLinkIcon +{ + /// MIME type of the icon image. + [JsonPropertyName("mimeType")] + public string? MimeType { get; set; } - /// The selected text content. - [JsonPropertyName("text")] - public required string Text { 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; } } -/// GitHub issue, pull request, or discussion reference. -/// The github_reference variant of . +/// Resource link content block referencing an external resource. +/// The resource_link variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class PushAttachmentGitHubReference : PushAttachment +public partial class ExternalToolTextResultForLlmContentResourceLink : ExternalToolTextResultForLlmContent { /// [JsonIgnore] - public override string Type => "github_reference"; + public override string Type => "resource_link"; - /// Issue, pull request, or discussion number. - [JsonPropertyName("number")] - public required long Number { get; set; } + /// Human-readable description of the resource. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("description")] + public string? Description { get; set; } - /// Type of GitHub reference. - [JsonPropertyName("referenceType")] - public required PushAttachmentGitHubReferenceType ReferenceType { get; set; } + /// Icons associated with this resource. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("icons")] + public IList? Icons { get; set; } - /// Current state of the referenced item (e.g., open, closed, merged). - [JsonPropertyName("state")] - public required string State { get; set; } + /// MIME type of the resource content. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("mimeType")] + public string? MimeType { get; set; } - /// Title of the referenced item. + /// 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 required string Title { get; set; } + public string? Title { get; set; } - /// URL to the referenced item on GitHub. - [JsonPropertyName("url")] - public required string Url { get; set; } + /// URI identifying the resource. + [JsonPropertyName("uri")] + public required string Uri { get; set; } } -/// Pointer to a GitHub repository. +/// Embedded resource content block with inline text or binary data. +/// The resource variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class PushGitHubRepoRef +public partial class ExternalToolTextResultForLlmContentResource : ExternalToolTextResultForLlmContent { - /// Numeric GitHub repository id. - [JsonPropertyName("id")] - public long? Id { get; set; } + /// + [JsonIgnore] + public override string Type => "resource"; - /// Repository name (without owner). - [JsonPropertyName("name")] - public string Name { get; set; } = string.Empty; + /// The embedded resource contents, either text or base64-encoded binary. + [JsonPropertyName("resource")] + public required JsonElement Resource { get; set; } +} - /// Repository owner login (user or organization). - [JsonPropertyName("owner")] - public string Owner { get; set; } = string.Empty; +/// A message injected by a tool result. +[Experimental(global::GitHub.Copilot.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; } -/// Pointer to a GitHub commit. -/// The github_commit variant of . +/// RPC data type for TaskCompletionDecision operations. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class PushAttachmentGitHubCommit : PushAttachment +public sealed class TaskCompletionDecision { - /// - [JsonIgnore] - public override string Type => "github_commit"; + /// Objective eligibility token captured when the decision was evaluated. + [JsonPropertyName("completionEligibilityToken")] + public long? CompletionEligibilityToken { get; set; } - /// First line of the commit message. - [JsonPropertyName("message")] - public required string Message { get; set; } + /// Whether completion was accepted after the reviewer-rejection budget was exhausted. + [JsonPropertyName("completionRejectionBudgetExhausted")] + public bool? CompletionRejectionBudgetExhausted { get; set; } - /// Full commit SHA. - [JsonPropertyName("oid")] - public required string Oid { get; set; } + /// Active autopilot objective evaluated by the completion reviewer. + [JsonPropertyName("objectiveId")] + public long? ObjectiveId { get; set; } - /// Repository the commit belongs to. - [JsonPropertyName("repo")] - public required PushGitHubRepoRef Repo { get; set; } + /// Semantic result of evaluating the task completion request. + [JsonPropertyName("outcome")] + public TaskCompletionOutcome Outcome { get; set; } - /// URL to the commit on GitHub. - [JsonPropertyName("url")] - public required string Url { 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; } } -/// Pointer to a GitHub release. -/// The github_release variant of . +/// Expanded canonical result returned by a session tool. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class PushAttachmentGitHubRelease : PushAttachment +public sealed class ToolResultExpanded { - /// - [JsonIgnore] - public override string Type => "github_release"; + /// Base64-encoded binary results returned to the model. + [JsonPropertyName("binaryResultsForLlm")] + public IList? BinaryResultsForLlm { get; set; } - /// Human-readable release name. - [JsonPropertyName("name")] - public required string Name { get; set; } + /// Sources returned by the tool that the model may cite. + [JsonPropertyName("citableSources")] + public IList? CitableSources { get; set; } - /// Repository the release belongs to. - [JsonPropertyName("repo")] - public required PushGitHubRepoRef Repo { get; set; } + /// Structured content blocks returned to the model. + [JsonPropertyName("contents")] + public IList? Contents { get; set; } - /// Git tag the release is anchored to. - [JsonPropertyName("tagName")] - public required string TagName { get; set; } + /// Error message for an unsuccessful execution. + [JsonPropertyName("error")] + public string? Error { get; set; } - /// URL to the release on GitHub. - [JsonPropertyName("url")] - public required string Url { get; set; } -} + /// Metadata propagated with the tool result, including information-flow labels. + [JsonPropertyName("mcpMeta")] + public IDictionary? McpMeta { get; set; } -/// Pointer to a GitHub Actions job. -/// The github_actions_job variant of . -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class PushAttachmentGitHubActionsJob : PushAttachment -{ - /// - [JsonIgnore] - public override string Type => "github_actions_job"; + /// Messages to inject after the tool result. + [JsonPropertyName("newMessages")] + public IList? NewMessages { 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; } + /// Whether post-tool-use failure hooks have already processed this result. + [JsonPropertyName("postToolUseFailureHooksProcessed")] + public bool? PostToolUseFailureHooksProcessed { get; set; } - /// Job id within the workflow run. - [JsonPropertyName("jobId")] - public required long JobId { get; set; } + /// Execution outcome classification. + [JsonPropertyName("resultType")] + public ToolResultType ResultType { get; set; } - /// Display name of the job. - [JsonPropertyName("jobName")] - public required string JobName { 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; } - /// Repository the workflow run belongs to. - [JsonPropertyName("repo")] - public required PushGitHubRepoRef Repo { get; set; } + /// Text result returned to the model. + [JsonPropertyName("textResultForLlm")] + public string TextResultForLlm { get; set; } = string.Empty; - /// URL to the job on GitHub. - [JsonPropertyName("url")] - public required string Url { get; set; } + /// Deferred tool names made available by this result. + [JsonPropertyName("toolReferences")] + public IList? ToolReferences { get; set; } - /// Display name of the workflow the job ran in. - [JsonPropertyName("workflowName")] - public required string WorkflowName { 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; } } -/// Pointer to a GitHub repository. -/// The github_repository variant of . +/// Task-completion tool arguments and final result used to build a label-safe session event payload. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class PushAttachmentGitHubRepository : PushAttachment +internal sealed class ToolsTaskCompleteEventDataRequest { - /// - [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; } + /// Final expanded result returned by the task_complete tool. + [JsonPropertyName("finalResult")] + public ToolResultExpanded FinalResult { get => field ??= new(); 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; } + /// Arguments supplied to the completed task_complete tool call. + [JsonPropertyName("toolArgs")] + public JsonElement ToolArgs { get; set; } } -/// One side of a file diff (head or base). +/// Indicates whether the external tool call result was handled successfully. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class PushAttachmentGitHubFileDiffSide +public sealed class HandlePendingToolCallResult { - /// 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 tool call result was handled successfully. + [JsonPropertyName("success")] + public bool Success { get; set; } } -/// Pointer to a single-file diff. At least one of `head` and `base` must be present. -/// The github_file_diff variant of . +/// Pending external tool call request ID, with the tool result or an error describing why it failed. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class PushAttachmentGitHubFileDiff : PushAttachment +internal sealed class HandlePendingToolCallRequest { - /// - [JsonIgnore] - public override string Type => "github_file_diff"; + /// Error message if the tool call failed. + [JsonPropertyName("error")] + public string? Error { get; set; } - /// File location on the base side of the diff. Absent for additions. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("base")] - public PushAttachmentGitHubFileDiffSide? Base { get; set; } + /// Request ID of the pending tool call. + [JsonPropertyName("requestId")] + public string RequestId { 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; } + /// Tool call result (string or expanded result object). + [JsonPropertyName("result")] + public JsonElement? Result { 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; } + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } -/// One side of a tree comparison (head or base). +/// External tool call of the session or one of its sub-agents that is still waiting for session.tools.handlePendingToolCall. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class PushAttachmentGitHubTreeComparisonSide +public sealed class PendingExternalToolRequest { - /// Repository the revision belongs to. - [JsonPropertyName("repo")] - public PushGitHubRepoRef Repo { get => field ??= new(); set; } + /// Sub-agent instance identifier (the envelope agentId of its events) of the agent that issued the call; absent for calls issued by the root agent. + [JsonPropertyName("agentId")] + public string? AgentId { get; set; } - /// Git revision (branch, tag, or commit SHA). - [JsonPropertyName("revision")] - public string Revision { get; set; } = string.Empty; -} + /// Arguments to pass to the external tool. + [JsonPropertyName("arguments")] + public JsonElement? Arguments { get; set; } -/// Pointer to a comparison between two git revisions. -/// The github_tree_comparison variant of . -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class PushAttachmentGitHubTreeComparison : PushAttachment -{ - /// - [JsonIgnore] - public override string Type => "github_tree_comparison"; + /// Stable identity of the provider that offered the tool, for hosts that route extension-owned tools by provider. + [JsonPropertyName("providerId")] + public string? ProviderId { get; set; } - /// Base side of the comparison. - [JsonPropertyName("base")] - public required PushAttachmentGitHubTreeComparisonSide Base { get; set; } + /// Request ID to pass to session.tools.handlePendingToolCall. + [JsonPropertyName("requestId")] + public string RequestId { get; set; } = string.Empty; - /// Head side of the comparison. - [JsonPropertyName("head")] - public required PushAttachmentGitHubTreeComparisonSide Head { get; set; } + /// Tool call ID assigned to this external tool invocation. + [JsonPropertyName("toolCallId")] + public string ToolCallId { get; set; } = string.Empty; - /// URL to the comparison on GitHub. - [JsonPropertyName("url")] - public required string Url { get; set; } + /// Name of the external tool to invoke. + [JsonPropertyName("toolName")] + public string ToolName { get; set; } = string.Empty; } -/// Generic GitHub URL reference. -/// The github_url variant of . +/// External tool calls still waiting for a result. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class PushAttachmentGitHubUrl : PushAttachment +public sealed class PendingExternalToolRequestList { - /// - [JsonIgnore] - public override string Type => "github_url"; - - /// URL to the GitHub resource. - [JsonPropertyName("url")] - public required string Url { get; set; } + /// External tool calls of the session and its sub-agents that session.tools.handlePendingToolCall would still accept, in the order they were requested. Includes calls that were pending when the session was last suspended and resumed with pending work continued, so a host that did not observe the original external_tool.requested event can answer them. + [JsonPropertyName("items")] + public IList Items { get => field ??= []; set; } } -/// Pointer to a file in a GitHub repository at a specific ref. -/// The github_file variant of . +/// No parameters; returns the external tool calls of the session and its sub-agents that are still waiting for a result. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class PushAttachmentGitHubFile : PushAttachment +internal sealed class ToolsListPendingRequestsRequest { - /// - [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; } + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} - /// Repository the file lives in. - [JsonPropertyName("repo")] - public required PushGitHubRepoRef Repo { get; set; } +/// Resolve, build, and validate the runtime tool list for this session. Subagent sessions and consumer flows that need an initialized tool set before `send` invoke this. Default base-class implementation is a no-op for sessions that don't support tool validation. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public sealed class ToolsInitializeAndValidateResult +{ +} - /// URL to the file on GitHub. - [JsonPropertyName("url")] - public required string Url { get; set; } +/// Identifies the target session. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class SessionToolsInitializeAndValidateRequest +{ + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } -/// Pointer to a line range inside a file in a GitHub repository. -/// The github_snippet variant of . +/// Lightweight metadata for a currently initialized session tool. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class PushAttachmentGitHubSnippet : PushAttachment +public sealed class CurrentToolMetadata { - /// - [JsonIgnore] - public override string Type => "github_snippet"; + /// Whether the tool is loaded on demand via tool search. + [JsonPropertyName("deferLoading")] + public bool? DeferLoading { get; set; } - /// Line range the snippet covers. - [JsonPropertyName("lineRange")] - public required PushAttachmentFileLineRange LineRange { get; set; } + /// Tool description. + [JsonPropertyName("description")] + public string Description { get; set; } = string.Empty; - /// Repository-relative path to the file. - [JsonPropertyName("path")] - public required string Path { get; set; } + /// JSON Schema for tool input. + [JsonPropertyName("input_schema")] + public IDictionary? InputSchema { get; set; } - /// Git ref the file is read at (branch, tag, or commit SHA). - [JsonPropertyName("ref")] - public required string Ref { get; set; } + /// MCP server name for MCP-backed tools. + [JsonPropertyName("mcpServerName")] + public string? McpServerName { get; set; } - /// Repository the file lives in. - [JsonPropertyName("repo")] - public required PushGitHubRepoRef Repo { get; set; } + /// Raw MCP tool name for MCP-backed tools. + [JsonPropertyName("mcpToolName")] + public string? McpToolName { get; set; } - /// URL to the snippet on GitHub (with line anchor). - [JsonPropertyName("url")] - public required string Url { 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; } } -/// Blob attachment with inline base64-encoded data. -/// The blob variant of . +/// Current lightweight tool metadata snapshot for the session. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class PushAttachmentBlob : PushAttachment +public sealed class ToolsGetCurrentMetadataResult { - /// - [JsonIgnore] - public override string Type => "blob"; - - /// Base64-encoded content. - [Base64String] - [JsonPropertyName("data")] - public required string Data { get; set; } + /// Current tool metadata, or null when tools have not been initialized yet. + [JsonPropertyName("tools")] + public IList? Tools { get; set; } +} - /// User-facing display name for the attachment. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("displayName")] - public string? DisplayName { get; set; } +/// Identifies the target session. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class SessionToolsGetCurrentMetadataRequest +{ + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} - /// MIME type of the inline data. - [JsonPropertyName("mimeType")] - public required string MimeType { get; set; } +/// Empty result after replacing the calling connection's externally implemented tools. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public sealed class ToolsSetResult +{ } -/// Slim input shape for extension_context attachments; identity fields are runtime-derived. -/// The extension_context variant of . +/// Serializable definition of a caller-implemented tool whose execution is handled over the SDK connection. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class PushAttachmentExtensionContext : PushAttachment +public sealed class ProtocolExternalToolDefinition { - /// - [JsonIgnore] - public override string Type => "extension_context"; + /// Tool-loading deferral policy. + [JsonPropertyName("defer")] + public ProtocolExternalToolDefer? Defer { get; set; } - /// Caller-supplied JSON payload (required, may be null but not undefined). - [JsonPropertyName("payload")] - public required JsonElement Payload { get; set; } + /// Model-visible explanation of what the tool does. + [JsonPropertyName("description")] + public string Description { get; set; } = string.Empty; - /// 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)] + /// 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 required string Title { get; set; } + public string? Title { get; set; } } -/// Parameters for session.extensions.sendAttachmentsToMessage. +/// Complete externally implemented tool list for the calling connection. An empty list removes every tool previously supplied by that connection. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SendAttachmentsToMessageParams +internal sealed class ToolsSetRequest { - /// 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; + + /// Complete replacement list for the calling connection. + [JsonPropertyName("tools")] + public IList Tools { get => field ??= []; set; } } -/// A tool name and arguments to execute through the session's native invocation pipeline. +/// Empty result after applying subagent settings. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class ToolsExecuteRequest +public sealed class ToolsUpdateSubagentSettingsResult { - /// 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; +/// Subagent model, reasoning effort, context tier, and auto-invocation settings. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public sealed class SubagentSettingsEntry +{ + /// Whether this agent's runtime-defined proactive invocation prompting is enabled, if supported. Currently consumed by the built-in rubber-duck agent. + [JsonPropertyName("autoInvoke")] + public bool? AutoInvoke { get; set; } - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Context tier override for matching subagents. + [JsonPropertyName("contextTier")] + public SubagentSettingsEntryContextTier? ContextTier { get; set; } - /// Optional identifier used to correlate this invocation with its tool call. - [JsonPropertyName("toolCallId")] - public string? ToolCallId { 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; } } -/// Custom grammar input format accepted by a built-in tool. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class BuiltinToolFormat +/// Configured per-agent subagent overrides. +public sealed class UpdateSubagentSettingsRequestSubagents { - /// Grammar definition accepted by the tool. - [JsonPropertyName("definition")] - public string Definition { get; set; } = string.Empty; + /// Per-agent settings keyed by subagent agent_type. + [JsonPropertyName("agents")] + public IDictionary? Agents { get; set; } - /// Grammar syntax used by the format definition. - [JsonPropertyName("syntax")] - public string Syntax { get; set; } = string.Empty; + /// Names of subagents the user has turned off; they cannot be dispatched. + [JsonPropertyName("disabledSubagents")] + public IList? DisabledSubagents { get; set; } - /// Custom input-format discriminator. - [JsonPropertyName("type")] - public BuiltinToolFormatType Type { 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; } } -/// JSON Schema object accepted by a built-in tool. +/// Subagent settings to apply to the current session. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class BuiltinToolInputSchema +internal sealed class UpdateSubagentSettingsRequest { - /// Root type of the tool input schema. - [JsonPropertyName("type")] - public BuiltinToolInputSchemaType Type { get; set; } + /// 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; } } -/// Rust-owned metadata and input schema for a built-in tool. +/// RPC data type for SessionCommandsList operations. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class BuiltinToolDescriptor +public sealed class SessionCommandsListRequest { - /// 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; } + /// Include runtime built-in commands. + [JsonPropertyName("includeBuiltins")] + public bool? IncludeBuiltins { get; set; } - /// Optional supplemental usage instructions for the tool. - [JsonPropertyName("instructions")] - public string? Instructions { get; set; } + /// Include commands registered by protocol clients, including SDK clients and extensions. + [JsonPropertyName("includeClientCommands")] + public bool? IncludeClientCommands { get; set; } - /// Whether the tool executes commands in a terminal. - [JsonPropertyName("isTerminal")] - public bool IsTerminal { get; set; } + /// Include enabled user-invocable skills and commands. + [JsonPropertyName("includeSkills")] + public bool? IncludeSkills { get; set; } +} - /// Stable name used to invoke the built-in tool. - [JsonPropertyName("name")] - public string Name { get; set; } = string.Empty; +/// RPC data type for SessionCommandsListRequestWithSession operations. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class SessionCommandsListRequestWithSession +{ + /// Include runtime built-in commands. + [JsonPropertyName("includeBuiltins")] + public bool? IncludeBuiltins { get; set; } - /// Policy describing which tool metadata may be recorded without obfuscation. - [JsonPropertyName("safeForTelemetry")] - public JsonElement SafeForTelemetry { get; set; } + /// Include commands registered by protocol clients, including SDK clients and extensions. + [JsonPropertyName("includeClientCommands")] + public bool? IncludeClientCommands { get; set; } - /// Optional human-readable title for the tool. - [JsonPropertyName("title")] - public string? Title { get; set; } + /// Include enabled user-invocable skills and commands. + [JsonPropertyName("includeSkills")] + public bool? IncludeSkills { get; set; } - /// Optional tool category discriminator. - [JsonPropertyName("type")] - public string? Type { get; set; } + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } -/// Rust-owned built-in tool descriptors for the session. +/// 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(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class ToolsGetBuiltinDescriptorsResult +[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 { - /// Built-in tool descriptors materialized for the session. - [JsonPropertyName("tools")] - public IList Tools { get => field ??= []; set; } + /// The type discriminator. + [JsonPropertyName("kind")] + public virtual string Kind { get; set; } = string.Empty; } -/// Shell-specific names and description lines used to materialize built-in shell tool descriptors. + +/// Slash-command invocation result containing text output plus Markdown/ANSI rendering flags. +/// The text variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class ToolsShellDescriptorConfig +public partial class SlashCommandInvocationResultText : SlashCommandInvocationResult { - /// 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; + /// + [JsonIgnore] + public override string Kind => "text"; - /// Tool name used to list active shells. - [JsonPropertyName("listShellsToolName")] - public string ListShellsToolName { get; set; } = string.Empty; + /// Whether text contains Markdown. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("markdown")] + public bool? Markdown { get; set; } - /// Tool name used to read shell output. - [JsonPropertyName("readShellToolName")] - public string ReadShellToolName { get; set; } = string.Empty; + /// Whether ANSI sequences should be preserved. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("preserveAnsi")] + public bool? PreserveAnsi { get; set; } - /// Tool name used to start shell commands. - [JsonPropertyName("shellToolName")] - public string ShellToolName { 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 shell type identifier. - [JsonPropertyName("shellType")] - public string ShellType { get; set; } = string.Empty; + /// Present when the invocation changed the sandbox for this session only. Nothing was persisted, so consumers must mirror the change onto the live session rather than reloading settings, and must not treat it as a settings change. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("sandboxSessionChange")] + public SandboxSessionChange? SandboxSessionChange { get; set; } - /// Tool name used to stop shell commands. - [JsonPropertyName("stopShellToolName")] - public string StopShellToolName { get; set; } = string.Empty; + /// Text output for the client to render. + [JsonPropertyName("text")] + public required string Text { get; set; } } -/// Options controlling how Rust-owned built-in tool descriptors are materialized. +/// 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(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class ToolsGetBuiltinDescriptorsRequest +public partial class SlashCommandInvocationResultAgentPrompt : SlashCommandInvocationResult { - /// 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; } + /// + [JsonIgnore] + public override string Kind => "agent-prompt"; - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Prompt text to display to the user. + [JsonPropertyName("displayPrompt")] + public required string DisplayPrompt { get; set; } - /// Shell-specific names and description lines for shell tools. - [JsonPropertyName("shellConfig")] - public ToolsShellDescriptorConfig? ShellConfig { get; set; } + /// Optional target session mode for the agent prompt. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("mode")] + public SessionMode? Mode { get; set; } - /// Whether the configured shell supports PowerShell 7 syntax. - [JsonPropertyName("shellSupportsPowerShell7Syntax")] - public bool? ShellSupportsPowerShell7Syntax { get; set; } + /// Optional user-facing notice to show before the prompt is submitted. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("notice")] + public string? Notice { get; set; } - /// Default shell timeout in milliseconds. - [JsonPropertyName("shellTimeoutMs")] - public double? ShellTimeoutMs { get; set; } + /// Prompt to submit to the agent. + [JsonPropertyName("prompt")] + public required string Prompt { get; set; } - /// Whether semantic skill lookup is available. - [JsonPropertyName("skillEmbeddingEnabled")] - public bool? SkillEmbeddingEnabled { 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; } } -/// Task completion notification with summary from the agent. +/// Slash-command invocation result indicating completion, with optional message and settings-change flag. +/// The completed variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class TaskCompleteData +public partial class SlashCommandInvocationResultCompleted : SlashCommandInvocationResult { - /// Structured blocker details when outcome is blocked. - [JsonPropertyName("blocker")] - public TaskBlocker? Blocker { get; set; } + /// + [JsonIgnore] + public override string Kind => "completed"; - /// Active autopilot objective ID evaluated by the completion reviewer. - [JsonPropertyName("objectiveId")] - public long? ObjectiveId { get; set; } + /// Optional user-facing message describing the completed command. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("message")] + public string? Message { get; set; } - /// Semantic completion decision. Absent on legacy events and invalid tool calls. - [JsonPropertyName("outcome")] - public TaskCompletionOutcome? Outcome { get; set; } + /// Optional target session mode applied without submitting an agent prompt. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("mode")] + public SessionMode? Mode { 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; } + /// True when the invocation mutated user runtime settings; consumers caching settings should refresh. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("runtimeSettingsChanged")] + public bool? RuntimeSettingsChanged { get; set; } +} - /// Whether 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; } +/// Selectable slash-command subcommand option with name, description, and optional group label. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public sealed class SlashCommandSelectSubcommandOption +{ + /// Human-readable description of the subcommand. + [JsonPropertyName("description")] + public string Description { get; set; } = string.Empty; - /// Summary of the completed task, provided by the agent. - [JsonPropertyName("summary")] - public string? Summary { get; set; } + /// 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; } -/// Binary result returned by a tool for the model. +/// Slash-command invocation result asking the client to present subcommand options for a parent command. +/// The select-subcommand variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class ExternalToolTextResultForLlmBinaryResultsForLlm +public partial class SlashCommandInvocationResultSelectSubcommand : SlashCommandInvocationResult { - /// Base64-encoded binary data. - [Base64String] - [JsonPropertyName("data")] - public string Data { get; set; } = string.Empty; + /// + [JsonIgnore] + public override string Kind => "select-subcommand"; - /// Human-readable description of the binary data. - [JsonPropertyName("description")] - public string? Description { get; set; } + /// Parent command name that requires subcommand selection. + [JsonPropertyName("command")] + public required string Command { get; set; } - /// Optional metadata from the producing tool. - [JsonPropertyName("metadata")] - public IDictionary? Metadata { get; set; } + /// Available subcommand options for the client to present. + [JsonPropertyName("options")] + public required IList Options { get; set; } - /// MIME type of the binary data. - [JsonPropertyName("mimeType")] - public string MimeType { 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; } - /// Binary result type discriminator. Use "image" for images and "resource" for other binary data. - [JsonPropertyName("type")] - public ExternalToolTextResultForLlmBinaryResultsForLlmType Type { get; set; } + /// Human-readable title for the selection UI. + [JsonPropertyName("title")] + public required string Title { 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. +/// RPC data type for SlashCommandTimelineEntry operations. [Experimental(global::GitHub.Copilot.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 +public sealed class SlashCommandTimelineEntry { - /// The type discriminator. + /// What the user must do to recover, when the entry reports a failure the runtime knows an action for. The `text` never names a client affordance, so a client that offers one renders it from this value. + [JsonPropertyName("remediation")] + public RemediationAction? Remediation { get; set; } + + /// Text displayed for the timeline entry. + [JsonPropertyName("text")] + public string Text { get; set; } = string.Empty; + + /// Timeline entry presentation type. [JsonPropertyName("type")] - public virtual string Type { get; set; } = string.Empty; -} + public string Type { get; set; } = string.Empty; + /// Optional URL associated with the timeline entry. + [JsonPropertyName("url")] + public string? Url { get; set; } +} -/// Plain text content block. -/// The text variant of . +/// The add-timeline-entry variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class ExternalToolTextResultForLlmContentText : ExternalToolTextResultForLlmContent +public partial class SlashCommandInvocationResultAddTimelineEntry : SlashCommandInvocationResult { /// [JsonIgnore] - public override string Type => "text"; + public override string Kind => "add-timeline-entry"; - /// The text content. - [JsonPropertyName("text")] - public required string Text { get; set; } + /// 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; } } -/// Terminal/shell output content block with optional exit code and working directory. -/// The terminal variant of . +/// RPC data type for SlashCommandModelPickerDialog operations. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class ExternalToolTextResultForLlmContentTerminal : ExternalToolTextResultForLlmContent +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(global::GitHub.Copilot.Diagnostics.Experimental)] +public partial class SlashCommandInvocationResultShowDialog : SlashCommandInvocationResult { /// [JsonIgnore] - public override string Type => "terminal"; + public override string Kind => "show-dialog"; - /// Working directory where the command was executed. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("cwd")] - public string? Cwd { get; set; } + /// Dialog the host should display. + [JsonPropertyName("dialog")] + public required SlashCommandModelPickerDialog Dialog { get; set; } - /// Process exit code, if the command has completed. + /// Whether command execution changed persisted runtime settings. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("exitCode")] - public long? ExitCode { get; set; } - - /// Terminal/shell output text. - [JsonPropertyName("text")] - public required string Text { get; set; } + [JsonPropertyName("runtimeSettingsChanged")] + public bool? RuntimeSettingsChanged { get; set; } } -/// Shell command exit metadata with optional output preview. -/// The shell_exit variant of . +/// The set-model variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class ExternalToolTextResultForLlmContentShellExit : ExternalToolTextResultForLlmContent +public partial class SlashCommandInvocationResultSetModel : SlashCommandInvocationResult { /// [JsonIgnore] - public override string Type => "shell_exit"; + public override string Kind => "set-model"; - /// Working directory where the shell command was executed. + /// Auto routing profile selected by the command, when the model is Auto. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("cwd")] - public string? Cwd { get; set; } + [JsonPropertyName("autoTier")] + public AutoTier? AutoTier { get; set; } - /// Exit code from the completed shell command. - [JsonPropertyName("exitCode")] - public required long ExitCode { get; set; } + /// Model selected by the command. + [JsonPropertyName("model")] + public required string Model { get; set; } - /// Path reported in the shell session's filesystem namespace when shell output exceeded the configured large-output threshold. + /// Reasoning effort selected for the model. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("outputFilePath")] - public string? OutputFilePath { get; set; } + [JsonPropertyName("reasoningEffort")] + public string? ReasoningEffort { get; set; } - /// Output associated with this shell command, if available. May be partial, truncated, or a preview; not guaranteed to be full output. + /// Repository settings scope modified by the command. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("outputPreview")] - public string? OutputPreview { get; set; } + [JsonPropertyName("repoScope")] + public string? RepoScope { get; set; } - /// Whether outputPreview is known to be incomplete or truncated. + /// User-settings snapshot to restore if the host cancels the model switch. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("outputTruncated")] - public bool? OutputTruncated { get; set; } + [JsonPropertyName("revertOnCancel")] + public IDictionary? RevertOnCancel { get; set; } - /// Shell id, as assigned by Copilot runtime. - [JsonPropertyName("shellId")] - public required string ShellId { 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; } } -/// Image content block with base64-encoded data. -/// The image variant of . +/// The set-plan-model variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class ExternalToolTextResultForLlmContentImage : ExternalToolTextResultForLlmContent +public partial class SlashCommandInvocationResultSetPlanModel : SlashCommandInvocationResult { /// [JsonIgnore] - public override string Type => "image"; + public override string Kind => "set-plan-model"; - /// Base64-encoded image data. - [Base64String] - [JsonPropertyName("data")] - public required string Data { get; set; } + /// User-facing confirmation message for the plan-model selection. + [JsonPropertyName("message")] + public required string Message { get; set; } - /// MIME type of the image (e.g., image/png, image/jpeg). - [JsonPropertyName("mimeType")] - public required string MimeType { 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; } } -/// Audio content block with base64-encoded data. -/// The audio variant of . +/// Slash command name and optional raw input string to invoke. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class ExternalToolTextResultForLlmContentAudio : ExternalToolTextResultForLlmContent +internal sealed class CommandsInvokeRequest { - /// - [JsonIgnore] - public override string Type => "audio"; + /// Raw input after the command name. + [JsonPropertyName("input")] + public string? Input { get; set; } - /// Base64-encoded audio data. - [Base64String] - [JsonPropertyName("data")] - public required string Data { get; set; } + /// Command name. Leading slashes are stripped and the name is matched case-insensitively. + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; - /// MIME type of the audio (e.g., audio/wav, audio/mpeg). - [JsonPropertyName("mimeType")] - public required string MimeType { get; set; } + /// 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; } -/// Icon image for a resource. +/// Whether finalizing the invocation effect succeeded, and the failure reason when it did not. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class ExternalToolTextResultForLlmContentResourceLinkIcon +internal sealed class CommandsFinalizeInvocationEffectResult { - /// 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; + /// Failure reason when the invocation effect could not be finalized. + [JsonPropertyName("error")] + public string? Error { get; set; } - /// Theme variant this icon is intended for. - [JsonPropertyName("theme")] - public ExternalToolTextResultForLlmContentResourceLinkIconTheme? Theme { get; set; } + /// Whether the pending invocation effect was finalized successfully. + [JsonPropertyName("success")] + public bool Success { get; set; } } -/// Resource link content block referencing an external resource. -/// The resource_link variant of . +/// The pending slash-command invocation effect to finalize, plus whether the host applied or cancelled it. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class ExternalToolTextResultForLlmContentResourceLink : ExternalToolTextResultForLlmContent +internal sealed class CommandsFinalizeInvocationEffectRequest { - /// - [JsonIgnore] - public override string Type => "resource_link"; + /// The slash-command result object that produced the pending effect, echoed back unchanged. + [JsonPropertyName("effect")] + public IDictionary Effect { get => field ??= new Dictionary(); set; } - /// Human-readable description of the resource. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("description")] - public string? Description { get; set; } + /// Whether the host applied or cancelled the pending invocation effect. + [JsonPropertyName("outcome")] + public CommandsInvocationEffectOutcome Outcome { get; set; } - /// Icons associated with this resource. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("icons")] - public IList? Icons { get; set; } + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} - /// MIME type of the resource content. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("mimeType")] - public string? MimeType { get; set; } +/// Indicates whether the pending client-handled command was completed successfully. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public sealed class CommandsHandlePendingCommandResult +{ + /// Whether the command was handled successfully. + [JsonPropertyName("success")] + public bool Success { get; set; } +} - /// Resource name identifier. - [JsonPropertyName("name")] - public required string Name { get; set; } +/// Pending command request ID and an optional error if the client handler failed. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class CommandsHandlePendingCommandRequest +{ + /// Error message if the command handler failed. + [JsonPropertyName("error")] + public string? Error { get; set; } - /// Size of the resource in bytes. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("size")] - public long? Size { get; set; } + /// Request ID from the command invocation event. + [JsonPropertyName("requestId")] + public string RequestId { get; set; } = string.Empty; - /// Human-readable display title for the resource. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("title")] - public string? Title { get; set; } + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} - /// URI identifying the resource. - [JsonPropertyName("uri")] - public required string Uri { get; set; } +/// Error message produced while executing the command, if any. +[Experimental(global::GitHub.Copilot.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; } } -/// Embedded resource content block with inline text or binary data. -/// The resource variant of . +/// Slash command name and argument string to execute synchronously. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class ExternalToolTextResultForLlmContentResource : ExternalToolTextResultForLlmContent +internal sealed class ExecuteCommandParams { - /// - [JsonIgnore] - public override string Type => "resource"; + /// Argument string to pass to the command (empty string if none). + [JsonPropertyName("args")] + public string Args { get; set; } = string.Empty; - /// The embedded resource contents, either text or base64-encoded binary. - [JsonPropertyName("resource")] - public required JsonElement Resource { get; set; } + /// 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; } -/// A message injected by a tool result. +/// Indicates whether the command was accepted into the local execution queue. +/// Data type discriminated by queued. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class ToolResultNewMessage +public partial class EnqueueCommandResult { - /// Message content to inject after the tool result. - [JsonPropertyName("content")] - public string Content { get; set; } = string.Empty; + /// The boolean discriminator. + [JsonPropertyName("queued")] + public bool Queued { get; set; } - /// Source attributed to the injected message. - [JsonPropertyName("source")] - public string Source { get; set; } = string.Empty; + /// Stable opaque ID of the queued command. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("queueId")] + public string? QueueId { get; set; } } -/// RPC data type for TaskCompletionDecision operations. +/// Slash-prefixed command string to enqueue for FIFO processing. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class TaskCompletionDecision +internal sealed class EnqueueCommandParams { - /// 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; } + /// 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; - /// Active autopilot objective evaluated by the completion reviewer. - [JsonPropertyName("objectiveId")] - public long? ObjectiveId { get; set; } + /// Optional user-facing text for the queue row. The command string is shown when omitted. + [JsonPropertyName("displayText")] + public string? DisplayText { get; set; } - /// Semantic result of evaluating the task completion request. - [JsonPropertyName("outcome")] - public TaskCompletionOutcome Outcome { get; set; } + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} - /// Rationale for the completion decision, when one is available. - [JsonPropertyName("reason")] - public string? Reason { get; set; } +/// Indicates whether the queued-command response was matched to a pending request. +[Experimental(global::GitHub.Copilot.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; } +} - /// Whether the rationale was derived from completion-reviewer output. - [JsonPropertyName("reviewerDerived")] - public bool? ReviewerDerived { get; set; } +/// Result of the queued command execution. +/// Data type discriminated by handled. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public partial class QueuedCommandResult +{ + /// The boolean discriminator. + [JsonPropertyName("handled")] + public bool Handled { get; set; } - /// Information-flow metadata captured from the completion reviewer. - [JsonPropertyName("reviewerResultMeta")] - public JsonElement? ReviewerResultMeta { 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; } } -/// Expanded canonical result returned by a session tool. +/// Queued-command request ID and the result indicating whether the host executed it (and whether to stop processing further queued commands). [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class ToolResultExpanded +internal sealed class CommandsRespondToQueuedCommandRequest { - /// Base64-encoded binary results returned to the model. - [JsonPropertyName("binaryResultsForLlm")] - public IList? BinaryResultsForLlm { get; set; } + /// Request ID from the `command.queued` event the host is responding to. + [JsonPropertyName("requestId")] + public string RequestId { get; set; } = string.Empty; - /// Sources returned by the tool that the model may cite. - [JsonPropertyName("citableSources")] - public IList? CitableSources { get; set; } + /// Result of the queued command execution. + [JsonPropertyName("result")] + public QueuedCommandResult Result { get => field ??= new(); set; } - /// Structured content blocks returned to the model. - [JsonPropertyName("contents")] - public IList? Contents { get; set; } + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} - /// Error message for an unsuccessful execution. - [JsonPropertyName("error")] - public string? Error { get; set; } +/// Telemetry engagement ID for the session, when available. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public sealed class SessionTelemetryEngagement +{ + /// Current telemetry engagement ID, when available. + [JsonPropertyName("engagementId")] + public string? EngagementId { get; set; } +} - /// Metadata propagated with the tool result, including information-flow labels. - [JsonPropertyName("mcpMeta")] - public IDictionary? McpMeta { get; set; } +/// Identifies the target session. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class SessionTelemetryGetEngagementIdRequest +{ + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} - /// Messages to inject after the tool result. - [JsonPropertyName("newMessages")] - public IList? NewMessages { get; set; } +/// Feature override key/value pairs to attach to subsequent telemetry events from this session. +[Experimental(global::GitHub.Copilot.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; } - /// Whether post-tool-use failure hooks have already processed this result. - [JsonPropertyName("postToolUseFailureHooksProcessed")] - public bool? PostToolUseFailureHooksProcessed { get; set; } + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} - /// Execution outcome classification. - [JsonPropertyName("resultType")] - public ToolResultType ResultType { get; set; } +/// Completed transient query. Ordered chunks and the terminal outcome are also delivered through `ui.ephemeral_query` session events while it runs. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public sealed class UIEphemeralQueryResult +{ + /// Answer returned by the model. + [JsonPropertyName("answer")] + public string Answer { get; set; } = string.Empty; +} - /// Detailed log content available for session display. - [JsonPropertyName("sessionLog")] - public string? SessionLog { get; set; } +/// Transient question to answer without adding it to conversation history. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class UIEphemeralQueryRequest +{ + /// Question to answer from the current conversation context. + [JsonPropertyName("question")] + public string Question { get; set; } = string.Empty; - /// Skill invocation metadata produced by the tool. - [JsonPropertyName("skillInvocation")] - public JsonElement? SkillInvocation { get; set; } + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} - /// Whether large-output post-processing should be skipped. - [JsonPropertyName("skipLargeOutputProcessing")] - public bool? SkipLargeOutputProcessing { get; set; } +/// The elicitation response (accept with form values, decline, or cancel). +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public sealed class UIElicitationResponse +{ + /// MCP response metadata. + [JsonPropertyName("_meta")] + public IDictionary? Meta { get; set; } - /// Structured result content in addition to the model-facing text. - [JsonPropertyName("structuredContent")] - public JsonElement? StructuredContent { get; set; } + /// The user's response: accept (submitted), decline (rejected), or cancel (dismissed). + [JsonPropertyName("action")] + public UIElicitationResponseAction Action { get; set; } - /// Completion-review decision produced by the task-completion tool. - [JsonPropertyName("taskCompletionDecision")] - public TaskCompletionDecision? TaskCompletionDecision { get; set; } + /// The form values submitted by the user (present when action is 'accept'). + [JsonPropertyName("content")] + public IDictionary? Content { get; set; } +} - /// Text result returned to the model. - [JsonPropertyName("textResultForLlm")] - public string TextResultForLlm { get; set; } = string.Empty; +/// JSON Schema describing the form fields to present to the user. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public sealed class UIElicitationSchema +{ + /// Form field definitions, keyed by field name. + [JsonPropertyName("properties")] + public IDictionary Properties { get => field ??= new Dictionary(); set; } - /// Deferred tool names made available by this result. - [JsonPropertyName("toolReferences")] - public IList? ToolReferences { get; set; } + /// List of required field names. + [JsonPropertyName("required")] + public IList? Required { get; set; } - /// Tool-specific telemetry payload. - [JsonPropertyName("toolTelemetry")] - public JsonElement? ToolTelemetry { get; set; } + /// Schema type indicator (always 'object'). + [JsonPropertyName("type")] + public string Type { get; set; } = string.Empty; +} - /// Optional UI resource produced by the tool. - [JsonPropertyName("uiResource")] - public JsonElement? UiResource { get; set; } +/// Metadata controlling an MCP task's lifetime. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public sealed class McpTaskMetadata +{ + /// Task time-to-live. + [JsonPropertyName("ttl")] + public long? Ttl { get; set; } } -/// Task-completion tool arguments and final result used to build a label-safe session event payload. +/// Prompt message and JSON schema describing the form fields to elicit from the user. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class ToolsTaskCompleteEventDataRequest +internal sealed class UIElicitationRequest { - /// Final expanded result returned by the task_complete tool. - [JsonPropertyName("finalResult")] - public ToolResultExpanded FinalResult { get => field ??= new(); set; } + /// MCP request metadata. + [JsonPropertyName("_meta")] + public IDictionary? 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; - /// Arguments supplied to the completed task_complete tool call. - [JsonPropertyName("toolArgs")] - public JsonElement ToolArgs { get; set; } + /// MCP task metadata. + [JsonPropertyName("task")] + public McpTaskMetadata? Task { get; set; } } -/// Indicates whether the external tool call result was handled successfully. +/// Indicates whether the elicitation response was accepted; false if it was already resolved by another client. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class HandlePendingToolCallResult +public sealed class UIElicitationResult { - /// Whether the tool call result was handled successfully. + /// Whether the response was accepted. False if the request was already resolved by another client. [JsonPropertyName("success")] public bool Success { get; set; } } -/// Pending external tool call request ID, with the tool result or an error describing why it failed. +/// Pending elicitation request ID and the user's response (accept/decline/cancel + form values). [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class HandlePendingToolCallRequest +internal sealed class UIHandlePendingElicitationRequest { - /// Error message if the tool call failed. - [JsonPropertyName("error")] - public string? Error { get; set; } - - /// Request ID of the pending tool call. + /// The unique request ID from the elicitation.requested event. [JsonPropertyName("requestId")] public string RequestId { get; set; } = string.Empty; - /// Tool call result (string or expanded result object). + /// The elicitation response (accept with form values, decline, or cancel). [JsonPropertyName("result")] - public JsonElement? Result { get; set; } + public UIElicitationResponse Result { get => field ??= new(); set; } /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// External tool call of the session or one of its sub-agents that is still waiting for session.tools.handlePendingToolCall. +/// Indicates whether the pending UI request was resolved by this call. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class PendingExternalToolRequest +public sealed class UIHandlePendingResult { - /// Sub-agent instance identifier (the envelope agentId of its events) of the agent that issued the call; absent for calls issued by the root agent. - [JsonPropertyName("agentId")] - public string? AgentId { get; set; } + /// 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; } +} - /// Arguments to pass to the external tool. - [JsonPropertyName("arguments")] - public JsonElement? Arguments { get; set; } +/// User response for a pending user-input request, with answer text and whether it was typed freeform. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public sealed class UIUserInputResponse +{ + /// The user's answer text. + [JsonPropertyName("answer")] + public string Answer { get; set; } = string.Empty; - /// Stable identity of the provider that offered the tool, for hosts that route extension-owned tools by provider. - [JsonPropertyName("providerId")] - public string? ProviderId { get; set; } + /// True if the user typed a freeform response, false if they selected a presented choice. Used by telemetry to differentiate between free text input and choice selection. + [JsonPropertyName("wasFreeform")] + public bool WasFreeform { get; set; } +} - /// Request ID to pass to session.tools.handlePendingToolCall. +/// Request ID of a pending `user_input.requested` event and the user's response. +[Experimental(global::GitHub.Copilot.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; - /// Tool call ID assigned to this external tool invocation. - [JsonPropertyName("toolCallId")] - public string ToolCallId { 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; } - /// Name of the external tool to invoke. - [JsonPropertyName("toolName")] - public string ToolName { get; set; } = string.Empty; + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } -/// External tool calls still waiting for a result. +/// Request ID of a pending `sampling.requested` event and an optional sampling result payload (omit to reject). [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class PendingExternalToolRequestList +internal sealed class UIHandlePendingSamplingRequest { - /// External tool calls of the session and its sub-agents that session.tools.handlePendingToolCall would still accept, in the order they were requested. Includes calls that were pending when the session was last suspended and resumed with pending work continued, so a host that did not observe the original external_tool.requested event can answer them. - [JsonPropertyName("items")] - public IList Items { get => field ??= []; set; } + /// 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 JsonElement? Response { get; set; } + + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } -/// No parameters; returns the external tool calls of the session and its sub-agents that are still waiting for a result. +/// Request ID of a pending `auto_mode_switch.requested` event and the user's response. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class ToolsListPendingRequestsRequest +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; } -/// 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. +/// The user's selected action for an exhausted session limit. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class ToolsInitializeAndValidateResult +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; } } -/// Identifies the target session. +/// Request ID of a pending `session_limits_exhausted.requested` event and the user's selected limit action. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionToolsInitializeAndValidateRequest +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; } -/// Lightweight metadata for a currently initialized session tool. +/// User response for a pending exit-plan-mode request, with approval state, selected action, auto-approve flag, and feedback. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class CurrentToolMetadata +public sealed class UIExitPlanModeResponse { - /// Whether the tool is loaded on demand via tool search. - [JsonPropertyName("deferLoading")] - public bool? DeferLoading { get; set; } + /// Whether the plan was approved. + [JsonPropertyName("approved")] + public bool Approved { get; set; } - /// Tool description. - [JsonPropertyName("description")] - public string Description { get; set; } = string.Empty; + /// Whether subsequent edits should be auto-approved without confirmation. + [JsonPropertyName("autoApproveEdits")] + public bool? AutoApproveEdits { get; set; } - /// JSON Schema for tool input. - [JsonPropertyName("input_schema")] - public IDictionary? InputSchema { 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; } - /// MCP server name for MCP-backed tools. - [JsonPropertyName("mcpServerName")] - public string? McpServerName { get; set; } + /// Feedback from the user when they declined the plan or requested changes. + [JsonPropertyName("feedback")] + public string? Feedback { get; set; } - /// Raw MCP tool name for MCP-backed tools. - [JsonPropertyName("mcpToolName")] - public string? McpToolName { get; set; } + /// The action the user selected. Defaults to 'autopilot' when autoApproveEdits is true, otherwise 'interactive'. + [JsonPropertyName("selectedAction")] + public UIExitPlanModeAction? SelectedAction { get; set; } +} - /// Model-facing tool name. - [JsonPropertyName("name")] - public string Name { get; set; } = string.Empty; +/// Request ID of a pending `exit_plan_mode.requested` event and the user's response. +[Experimental(global::GitHub.Copilot.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; - /// Optional MCP/config namespaced tool name. - [JsonPropertyName("namespacedName")] - public string? NamespacedName { get; set; } + /// 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; } -/// Current lightweight tool metadata snapshot for the 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). [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class ToolsGetCurrentMetadataResult +public sealed class UIRegisterDirectAutoModeSwitchHandlerResult { - /// Current tool metadata, or null when tools have not been initialized yet. - [JsonPropertyName("tools")] - public IList? Tools { get; set; } + /// 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(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionToolsGetCurrentMetadataRequest +internal sealed class SessionUiRegisterDirectAutoModeSwitchHandlerRequest { /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Empty result after replacing the calling connection's externally implemented tools. +/// Indicates whether the handle was active and the registration count was decremented. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class ToolsSetResult +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; } } -/// Serializable definition of a caller-implemented tool whose execution is handled over the SDK connection. +/// Opaque handle previously returned by `registerDirectAutoModeSwitchHandler` to release. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class ProtocolExternalToolDefinition +internal sealed class UIUnregisterDirectAutoModeSwitchHandlerRequest { - /// 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; } + /// Handle previously returned by `registerDirectAutoModeSwitchHandler`. + [JsonPropertyName("handle")] + public string Handle { get; set; } = string.Empty; - /// Optional human-readable display title. - [JsonPropertyName("title")] - public string? Title { get; set; } + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } -/// Complete externally implemented tool list for the calling connection. An empty list removes every tool previously supplied by that connection. +/// Indicates whether the operation succeeded. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class ToolsSetRequest +public sealed class PermissionsConfigureResult { - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Whether the operation succeeded. + [JsonPropertyName("success")] + public bool Success { get; set; } +} - /// Complete replacement list for the calling connection. - [JsonPropertyName("tools")] - public IList Tools { get => field ??= []; set; } +/// Source descriptor for a `session.permissions.configure` content-exclusion rule, with source name and type. +[Experimental(global::GitHub.Copilot.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; } -/// Empty result after applying subagent settings. +/// Single content-exclusion rule supplied to `session.permissions.configure`, with paths, match conditions, and source. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class ToolsUpdateSubagentSettingsResult +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; } } -/// Subagent model, reasoning effort, context tier, and auto-invocation settings. +/// Content-exclusion policy supplied to `session.permissions.configure`, with rules, last-updated data, and scope. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class SubagentSettingsEntry +public sealed class PermissionsConfigureAdditionalContentExclusionPolicy { - /// Whether this agent's runtime-defined proactive invocation prompting is enabled, if supported. Currently consumed by the built-in rubber-duck agent. - [JsonPropertyName("autoInvoke")] - public bool? AutoInvoke { get; set; } - - /// 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; } + /// Opaque policy update timestamp supplied by the host. + [JsonPropertyName("last_updated_at")] + public JsonElement LastUpdatedAt { get; set; } - /// Model override for matching subagents. - [JsonPropertyName("model")] - public string? Model { get; set; } + /// Content-exclusion rules to apply. + [JsonPropertyName("rules")] + public IList Rules { get => field ??= []; set; } - /// Whether the configured model strategy is preferred or required. - [JsonPropertyName("modelPolicy")] - public AgentModelPolicy? ModelPolicy { get; set; } + /// Allowed values for the `PermissionsConfigureAdditionalContentExclusionPolicyScope` enumeration. + [JsonPropertyName("scope")] + public PermissionsConfigureAdditionalContentExclusionPolicyScope Scope { get; set; } } -/// Configured per-agent subagent overrides. -public sealed class UpdateSubagentSettingsRequestSubagents +/// 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(global::GitHub.Copilot.Diagnostics.Experimental)] +public sealed class PermissionPathsConfig { - /// Per-agent settings keyed by subagent agent_type. - [JsonPropertyName("agents")] - public IDictionary? Agents { get; set; } + /// 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; } - /// Names of subagents the user has turned off; they cannot be dispatched. - [JsonPropertyName("disabledSubagents")] - public IList? DisabledSubagents { 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; } - /// Maximum number of subagents that can run concurrently; applies to usage-based billing users only. - [JsonPropertyName("maxConcurrency")] - public int? MaxConcurrency { 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; } - /// Maximum subagent nesting depth; applies to usage-based billing users only. - [JsonPropertyName("maxDepth")] - public int? MaxDepth { 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; } } -/// Subagent settings to apply to the current session. +/// If specified, replaces the session's approved/denied permission rules. Omit to leave the current rules unchanged. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class UpdateSubagentSettingsRequest +public sealed class PermissionRulesSet { - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Rules that auto-approve matching requests. + [JsonPropertyName("approved")] + public IList Approved { get => field ??= []; set; } - /// Subagent settings to apply, or null to clear the live session override. - [JsonPropertyName("subagents")] - public UpdateSubagentSettingsRequestSubagents? Subagents { get; set; } + /// Rules that auto-deny matching requests. + [JsonPropertyName("denied")] + public IList Denied { get => field ??= []; set; } } -/// RPC data type for SessionCommandsList operations. +/// 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(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class SessionCommandsListRequest +public sealed class PermissionUrlsConfig { - /// 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; } + /// Initial list of allowed URL/domain patterns. Patterns may include path components. Ignored when `unrestricted` is true. + [JsonPropertyName("initialAllowed")] + public IList? InitialAllowed { get; set; } - /// Include enabled user-invocable skills and commands. - [JsonPropertyName("includeSkills")] - public bool? IncludeSkills { 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; } } -/// RPC data type for SessionCommandsListRequestWithSession operations. +/// Patch of permission policy fields to apply (omit a field to leave it unchanged). [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionCommandsListRequestWithSession +internal sealed class PermissionsConfigureParams { - /// Include runtime built-in commands. - [JsonPropertyName("includeBuiltins")] - public bool? IncludeBuiltins { get; set; } + /// 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; } - /// Include commands registered by protocol clients, including SDK clients and extensions. - [JsonPropertyName("includeClientCommands")] - public bool? IncludeClientCommands { 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; } - /// Include enabled user-invocable skills and commands. - [JsonPropertyName("includeSkills")] - public bool? IncludeSkills { 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; } } -/// Result of invoking the slash command (text output, prompt to send to the agent, completion, or subcommand selection). +/// Indicates whether the permission decision was applied; false when the request was already resolved. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public sealed class PermissionRequestResult +{ + /// Whether the permission request was handled successfully. + [JsonPropertyName("success")] + public bool Success { get; set; } +} + +/// The client's response to the pending permission prompt. /// Polymorphic base type discriminated by kind. [Experimental(global::GitHub.Copilot.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 +[JsonDerivedType(typeof(PermissionDecisionApproveOnce), "approve-once")] +[JsonDerivedType(typeof(PermissionDecisionApproveForSession), "approve-for-session")] +[JsonDerivedType(typeof(PermissionDecisionApproveReadOnlyForSession), "approve-read-only-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")] @@ -21649,822 +25044,881 @@ public partial class SlashCommandInvocationResult } -/// Slash-command invocation result containing text output plus Markdown/ANSI rendering flags. -/// The text variant of . +/// Permission-decision request variant to approve only the current permission request. +/// The approve-once variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class SlashCommandInvocationResultText : SlashCommandInvocationResult +public partial class PermissionDecisionApproveOnce : PermissionDecision { /// [JsonIgnore] - public override string Kind => "text"; + public override string Kind => "approve-once"; - /// Whether text contains Markdown. + /// True only when a host surfaced this request to a user who approved it. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("markdown")] - public bool? Markdown { get; set; } + [JsonPropertyName("approvedInteractively")] + public bool? ApprovedInteractively { get; set; } +} - /// Whether ANSI sequences should be preserved. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("preserveAnsi")] - public bool? PreserveAnsi { get; set; } +/// Session-scoped approval to remember (tool prompts only; omitted for path/url prompts). +/// Polymorphic base type discriminated by kind. +[Experimental(global::GitHub.Copilot.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(PermissionDecisionApproveForSessionApprovalWorkflow), "workflow")] +[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; +} - /// True when the invocation mutated user runtime settings; consumers caching settings should refresh. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("runtimeSettingsChanged")] - public bool? RuntimeSettingsChanged { get; set; } - /// Present when the invocation changed the sandbox for this session only. Nothing was persisted, so consumers must mirror the change onto the live session rather than reloading settings, and must not treat it as a settings change. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("sandboxSessionChange")] - public SandboxSessionChange? SandboxSessionChange { get; set; } +/// Session-scoped approval details for specific command identifiers. +/// The commands variant of . +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public partial class PermissionDecisionApproveForSessionApprovalCommands : PermissionDecisionApproveForSessionApproval +{ + /// + [JsonIgnore] + public override string Kind => "commands"; - /// Text output for the client to render. - [JsonPropertyName("text")] - public required string Text { get; set; } + /// Command identifiers covered by this approval. + [JsonPropertyName("commandIdentifiers")] + public required IList CommandIdentifiers { 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 . +/// Session-scoped approval details for read-only filesystem operations. +/// The read variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class SlashCommandInvocationResultAgentPrompt : SlashCommandInvocationResult +public partial class PermissionDecisionApproveForSessionApprovalRead : PermissionDecisionApproveForSessionApproval { /// [JsonIgnore] - public override string Kind => "agent-prompt"; - - /// Prompt text to display to the user. - [JsonPropertyName("displayPrompt")] - public required string DisplayPrompt { get; set; } + public override string Kind => "read"; +} - /// Optional target session mode for the agent prompt. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("mode")] - public SessionMode? Mode { get; set; } +/// Session-scoped approval details for filesystem write operations. +/// The write variant of . +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public partial class PermissionDecisionApproveForSessionApprovalWrite : PermissionDecisionApproveForSessionApproval +{ + /// + [JsonIgnore] + public override string Kind => "write"; +} - /// Optional user-facing notice to show before the prompt is submitted. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("notice")] - public string? Notice { 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 . +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public partial class PermissionDecisionApproveForSessionApprovalMcp : PermissionDecisionApproveForSessionApproval +{ + /// + [JsonIgnore] + public override string Kind => "mcp"; - /// Prompt to submit to the agent. - [JsonPropertyName("prompt")] - public required string Prompt { get; set; } + /// MCP server name. + [JsonPropertyName("serverName")] + public required string ServerName { 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; } + /// MCP tool name, or null to cover every tool on the server. + [JsonPropertyName("toolName")] + public string? ToolName { get; set; } } -/// Slash-command invocation result indicating completion, with optional message and settings-change flag. -/// The completed variant of . +/// Session-scoped approval details for MCP sampling requests from a server. +/// The mcp-sampling variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class SlashCommandInvocationResultCompleted : SlashCommandInvocationResult +public partial class PermissionDecisionApproveForSessionApprovalMcpSampling : PermissionDecisionApproveForSessionApproval { /// [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; } + public override string Kind => "mcp-sampling"; - /// True when the invocation mutated user runtime settings; consumers caching settings should refresh. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("runtimeSettingsChanged")] - public bool? RuntimeSettingsChanged { get; set; } + /// MCP server name. + [JsonPropertyName("serverName")] + public required string ServerName { get; set; } } -/// Selectable slash-command subcommand option with name, description, and optional group label. +/// Session-scoped approval details for writes to long-term memory. +/// The memory variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class SlashCommandSelectSubcommandOption +public partial class PermissionDecisionApproveForSessionApprovalMemory : PermissionDecisionApproveForSessionApproval { - /// Human-readable description of the subcommand. - [JsonPropertyName("description")] - public string Description { get; set; } = string.Empty; + /// + [JsonIgnore] + public override string Kind => "memory"; +} - /// Optional group label for organizing options. - [JsonPropertyName("group")] - public string? Group { get; set; } +/// Session-scoped approval details for a custom tool, keyed by tool name. +/// The custom-tool variant of . +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public partial class PermissionDecisionApproveForSessionApprovalCustomTool : PermissionDecisionApproveForSessionApproval +{ + /// + [JsonIgnore] + public override string Kind => "custom-tool"; - /// Subcommand name to invoke. - [JsonPropertyName("name")] - public string Name { get; set; } = string.Empty; + /// Custom tool name. + [JsonPropertyName("toolName")] + public required string ToolName { get; set; } } -/// Slash-command invocation result asking the client to present subcommand options for a parent command. -/// The select-subcommand variant of . +/// Session-scoped approval details for extension-management operations, optionally narrowed by operation. +/// The extension-management variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class SlashCommandInvocationResultSelectSubcommand : SlashCommandInvocationResult +public partial class PermissionDecisionApproveForSessionApprovalExtensionManagement : PermissionDecisionApproveForSessionApproval { /// [JsonIgnore] - public override string Kind => "select-subcommand"; + public override string Kind => "extension-management"; - /// Parent command name that requires subcommand selection. - [JsonPropertyName("command")] - public required string Command { 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; } +} - /// Available subcommand options for the client to present. - [JsonPropertyName("options")] - public required IList Options { get; set; } +/// Session-scoped workflow approval, optionally narrowed by approval key. +/// The workflow variant of . +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public partial class PermissionDecisionApproveForSessionApprovalWorkflow : PermissionDecisionApproveForSessionApproval +{ + /// + [JsonIgnore] + public override string Kind => "workflow"; - /// True when the invocation mutated user runtime settings; consumers caching settings should refresh. + /// Optional workflow operation name or canonical approval key; when omitted, the approval covers all workflow operations. [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; } + [JsonPropertyName("approvalKey")] + public string? ApprovalKey { get; set; } } -/// RPC data type for SlashCommandTimelineEntry operations. +/// Session-scoped approval details for an extension's permission-gated capability access, keyed by extension name. +/// The extension-permission-access variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class SlashCommandTimelineEntry +public partial class PermissionDecisionApproveForSessionApprovalExtensionPermissionAccess : PermissionDecisionApproveForSessionApproval { - /// What the user must do to recover, when the entry reports a failure the runtime knows an action for. The `text` never names a client affordance, so a client that offers one renders it from this value. - [JsonPropertyName("remediation")] - public RemediationAction? Remediation { get; set; } + /// + [JsonIgnore] + public override string Kind => "extension-permission-access"; - /// Text displayed for the timeline entry. - [JsonPropertyName("text")] - public string Text { get; set; } = string.Empty; + /// Extension name. + [JsonPropertyName("extensionName")] + public required string ExtensionName { get; set; } +} - /// Timeline entry presentation type. - [JsonPropertyName("type")] - public string Type { get; set; } = string.Empty; +/// 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(global::GitHub.Copilot.Diagnostics.Experimental)] +public partial class PermissionDecisionApproveForSessionApprovalExtensionEnvAccess : PermissionDecisionApproveForSessionApproval +{ + /// + [JsonIgnore] + public override string Kind => "extension-env-access"; - /// Optional URL associated with the timeline entry. - [JsonPropertyName("url")] - public string? Url { get; set; } + /// 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; } } -/// The add-timeline-entry variant of . +/// 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(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class SlashCommandInvocationResultAddTimelineEntry : SlashCommandInvocationResult +public partial class PermissionDecisionApproveForSession : PermissionDecision { /// [JsonIgnore] - public override string Kind => "add-timeline-entry"; - - /// Timeline entry the host should append. - [JsonPropertyName("entry")] - public required SlashCommandTimelineEntry Entry { get; set; } + public override string Kind => "approve-for-session"; - /// Optional text the host should prefill into the input editor. + /// Session-scoped approval to remember (tool prompts only; omitted for path/url prompts). [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("prefillInput")] - public string? PrefillInput { get; set; } + [JsonPropertyName("approval")] + public PermissionDecisionApproveForSessionApproval? Approval { get; set; } - /// Whether command execution changed persisted runtime settings. + /// URL domain to approve for the rest of the session (URL prompts only). [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("runtimeSettingsChanged")] - public bool? RuntimeSettingsChanged { get; set; } + [JsonPropertyName("domain")] + public string? Domain { get; set; } } -/// RPC data type for SlashCommandModelPickerDialog operations. +/// Approve file-tool read access to specific directories for this logical session, including continuation or resume. +/// The approve-read-only-for-session variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class SlashCommandModelPickerDialog +public partial class PermissionDecisionApproveReadOnlyForSession : PermissionDecision { - /// Discriminator for a model-picker dialog. - [JsonPropertyName("kind")] - public string Kind { get; set; } = string.Empty; + /// + [JsonIgnore] + public override string Kind => "approve-read-only-for-session"; - /// Model that should be enabled before it can be selected. - [JsonPropertyName("modelToEnable")] - public string? ModelToEnable { get; set; } + /// Canonical directories covered by the read-only grant. + [JsonPropertyName("directories")] + public required IList Directories { get; set; } +} - /// Settings scope the picker should modify. - [JsonPropertyName("scope")] - public string? Scope { get; set; } +/// Approval to persist for this location. +/// Polymorphic base type discriminated by kind. +[Experimental(global::GitHub.Copilot.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(PermissionDecisionApproveForLocationApprovalWorkflow), "workflow")] +[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; +} - /// Model-selection target represented by the picker. - [JsonPropertyName("target")] - public string? Target { get; set; } + +/// Location-scoped approval details for specific command identifiers. +/// The commands variant of . +[Experimental(global::GitHub.Copilot.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; } } -/// The show-dialog variant of . +/// Location-scoped approval details for read-only filesystem operations. +/// The read variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class SlashCommandInvocationResultShowDialog : SlashCommandInvocationResult +public partial class PermissionDecisionApproveForLocationApprovalRead : PermissionDecisionApproveForLocationApproval { /// [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; } + public override string Kind => "read"; } -/// User-settings snapshot to restore if the host cancels the model switch. -public sealed class SlashCommandInvocationResultSetModelRevertOnCancel +/// Location-scoped approval details for filesystem write operations. +/// The write variant of . +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public partial class PermissionDecisionApproveForLocationApprovalWrite : PermissionDecisionApproveForLocationApproval { + /// + [JsonIgnore] + public override string Kind => "write"; } -/// The set-model variant of . +/// Location-scoped approval details for an MCP server tool, or all tools on the server when `toolName` is null. +/// The mcp variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class SlashCommandInvocationResultSetModel : SlashCommandInvocationResult +public partial class PermissionDecisionApproveForLocationApprovalMcp : PermissionDecisionApproveForLocationApproval { /// [JsonIgnore] - public override string Kind => "set-model"; - - /// Auto routing profile selected by the command, when the model is Auto. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("autoTier")] - public AutoTier? AutoTier { get; set; } - - /// 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; } + public override string Kind => "mcp"; - /// Settings scope modified by the command. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("scope")] - public string? Scope { get; set; } + /// MCP server name. + [JsonPropertyName("serverName")] + public required string ServerName { get; set; } - /// User-facing warning produced while selecting the model. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("warning")] - public string? Warning { get; set; } + /// MCP tool name, or null to cover every tool on the server. + [JsonPropertyName("toolName")] + public string? ToolName { get; set; } } -/// The set-plan-model variant of . +/// Location-scoped approval details for MCP sampling requests from a server. +/// The mcp-sampling variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class SlashCommandInvocationResultSetPlanModel : SlashCommandInvocationResult +public partial class PermissionDecisionApproveForLocationApprovalMcpSampling : PermissionDecisionApproveForLocationApproval { /// [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; } + public override string Kind => "mcp-sampling"; - /// Whether command execution changed persisted runtime settings. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("runtimeSettingsChanged")] - public bool? RuntimeSettingsChanged { get; set; } + /// MCP server name. + [JsonPropertyName("serverName")] + public required string ServerName { get; set; } } -/// Slash command name and optional raw input string to invoke. +/// Location-scoped approval details for writes to long-term memory. +/// The memory variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class CommandsInvokeRequest +public partial class PermissionDecisionApproveForLocationApprovalMemory : PermissionDecisionApproveForLocationApproval { - /// 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; + /// + [JsonIgnore] + public override string Kind => "memory"; } -/// Whether finalizing the invocation effect succeeded, and the failure reason when it did not. +/// Location-scoped approval details for a custom tool, keyed by tool name. +/// The custom-tool variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class CommandsFinalizeInvocationEffectResult +public partial class PermissionDecisionApproveForLocationApprovalCustomTool : PermissionDecisionApproveForLocationApproval { - /// Failure reason when the invocation effect could not be finalized. - [JsonPropertyName("error")] - public string? Error { get; set; } + /// + [JsonIgnore] + public override string Kind => "custom-tool"; - /// Whether the pending invocation effect was finalized successfully. - [JsonPropertyName("success")] - public bool Success { get; set; } + /// Custom tool name. + [JsonPropertyName("toolName")] + public required string ToolName { get; set; } } -/// The slash-command result object that produced the pending effect, echoed back unchanged. -public sealed class CommandsFinalizeInvocationEffectRequestEffect +/// Location-scoped approval details for extension-management operations, optionally narrowed by operation. +/// The extension-management variant of . +[Experimental(global::GitHub.Copilot.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; } } -/// The pending slash-command invocation effect to finalize, plus whether the host applied or cancelled it. +/// Location-scoped workflow approval, optionally narrowed by approval key. +/// The workflow variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class CommandsFinalizeInvocationEffectRequest +public partial class PermissionDecisionApproveForLocationApprovalWorkflow : PermissionDecisionApproveForLocationApproval { - /// 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; } + /// + [JsonIgnore] + public override string Kind => "workflow"; - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Optional workflow operation name or canonical approval key; when omitted, the approval covers all workflow operations. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("approvalKey")] + public string? ApprovalKey { get; set; } } -/// Indicates whether the pending client-handled command was completed successfully. +/// Location-scoped approval details for an extension's permission-gated capability access, keyed by extension name. +/// The extension-permission-access variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class CommandsHandlePendingCommandResult +public partial class PermissionDecisionApproveForLocationApprovalExtensionPermissionAccess : PermissionDecisionApproveForLocationApproval { - /// Whether the command was handled successfully. - [JsonPropertyName("success")] - public bool Success { get; set; } + /// + [JsonIgnore] + public override string Kind => "extension-permission-access"; + + /// Extension name. + [JsonPropertyName("extensionName")] + public required string ExtensionName { get; set; } } -/// Pending command request ID and an optional error if the client handler failed. +/// 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(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class CommandsHandlePendingCommandRequest +public partial class PermissionDecisionApproveForLocationApprovalExtensionEnvAccess : PermissionDecisionApproveForLocationApproval { - /// Error message if the command handler failed. - [JsonPropertyName("error")] - public string? Error { get; set; } + /// + [JsonIgnore] + public override string Kind => "extension-env-access"; - /// Request ID from the command invocation event. - [JsonPropertyName("requestId")] - public string RequestId { get; set; } = string.Empty; + /// Names of the sensitive environment variables this approval covers. Values are never persisted. + [JsonPropertyName("environmentVariables")] + public required IList EnvironmentVariables { get; set; } - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Extension name. + [JsonPropertyName("extensionName")] + public required string ExtensionName { get; set; } } -/// Error message produced while executing the command, if any. +/// 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(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class ExecuteCommandResult +public partial class PermissionDecisionApproveForLocation : PermissionDecision { - /// Error message produced while executing the command, if any. Omitted when the handler succeeded. - [JsonPropertyName("error")] - public string? Error { get; set; } + /// + [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; } } -/// Slash command name and argument string to execute synchronously. +/// Permission-decision request variant to permanently approve a URL domain across sessions. +/// The approve-permanently variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class ExecuteCommandParams +public partial class PermissionDecisionApprovePermanently : PermissionDecision { - /// 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; + /// + [JsonIgnore] + public override string Kind => "approve-permanently"; - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// URL domain to approve permanently. + [JsonPropertyName("domain")] + public required string Domain { get; set; } } -/// Indicates whether the command was accepted into the local execution queue. -/// Data type discriminated by queued. +/// Permission-decision request variant to reject a pending permission request, with optional feedback. +/// The reject variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class EnqueueCommandResult +public partial class PermissionDecisionReject : PermissionDecision { - /// The boolean discriminator. - [JsonPropertyName("queued")] - public bool Queued { get; set; } + /// + [JsonIgnore] + public override string Kind => "reject"; - /// Stable opaque ID of the queued command. + /// Optional feedback explaining the rejection. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("queueId")] - public string? QueueId { get; set; } + [JsonPropertyName("feedback")] + public string? Feedback { get; set; } } -/// Slash-prefixed command string to enqueue for FIFO processing. +/// Permission-decision variant indicating no user was available to confirm the request. +/// The user-not-available variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class EnqueueCommandParams +public partial class PermissionDecisionUserNotAvailable : PermissionDecision { - /// 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; + /// + [JsonIgnore] + public override string Kind => "user-not-available"; } -/// Indicates whether the queued-command response was matched to a pending request. +/// Permission-decision variant indicating the request was approved. +/// The approved variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class CommandsRespondToQueuedCommandResult +public partial class PermissionDecisionApproved : PermissionDecision { - /// 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; } + /// + [JsonIgnore] + public override string Kind => "approved"; } -/// Result of the queued command execution. -/// Data type discriminated by handled. +/// Permission-decision variant indicating approval was remembered for the session, with approval details. +/// The approved-for-session variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class QueuedCommandResult +public partial class PermissionDecisionApprovedForSession : PermissionDecision { - /// The boolean discriminator. - [JsonPropertyName("handled")] - public bool Handled { get; set; } + /// + [JsonIgnore] + public override string Kind => "approved-for-session"; - /// 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; } + /// The approval to add as a session-scoped rule. + [JsonPropertyName("approval")] + public required UserToolSessionApproval Approval { get; set; } } -/// Queued-command request ID and the result indicating whether the host executed it (and whether to stop processing further queued commands). +/// Permission-decision variant indicating approval was persisted for a project location, with approval details and location key. +/// The approved-for-location variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class CommandsRespondToQueuedCommandRequest +public partial class PermissionDecisionApprovedForLocation : PermissionDecision { - /// Request ID from the `command.queued` event the host is responding to. - [JsonPropertyName("requestId")] - public string RequestId { get; set; } = string.Empty; + /// + [JsonIgnore] + public override string Kind => "approved-for-location"; - /// Result of the queued command execution. - [JsonPropertyName("result")] - public QueuedCommandResult Result { get => field ??= new(); set; } + /// The approval to persist for this location. + [JsonPropertyName("approval")] + public required UserToolSessionApproval Approval { get; set; } - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// The location key (git root or cwd) to persist the approval to. + [JsonPropertyName("locationKey")] + public required string LocationKey { get; set; } } -/// Telemetry engagement ID for the session, when available. +/// Permission-decision variant indicating the request was cancelled before use, with an optional reason. +/// The cancelled variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class SessionTelemetryEngagement +public partial class PermissionDecisionCancelled : PermissionDecision { - /// Current telemetry engagement ID, when available. - [JsonPropertyName("engagementId")] - public string? EngagementId { get; set; } -} + /// + [JsonIgnore] + public override string Kind => "cancelled"; -/// Identifies the target session. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionTelemetryGetEngagementIdRequest -{ - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Optional explanation of why the request was cancelled. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("reason")] + public string? Reason { get; set; } } -/// Feature override key/value pairs to attach to subsequent telemetry events from this session. +/// Permission-decision variant indicating explicit denial by permission rules, with the matching rules. +/// The denied-by-rules variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class TelemetrySetFeatureOverridesRequest +public partial class PermissionDecisionDeniedByRules : PermissionDecision { - /// 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; } + /// + [JsonIgnore] + public override string Kind => "denied-by-rules"; - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Rules that denied the request. + [JsonPropertyName("rules")] + public required IList Rules { get; set; } } -/// Completed transient query. Ordered chunks and the terminal outcome are also delivered through `ui.ephemeral_query` session events while it runs. +/// 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(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class UIEphemeralQueryResult +public partial class PermissionDecisionDeniedNoApprovalRuleAndCouldNotRequestFromUser : PermissionDecision { - /// Answer returned by the model. - [JsonPropertyName("answer")] - public string Answer { get; set; } = string.Empty; + /// + [JsonIgnore] + public override string Kind => "denied-no-approval-rule-and-could-not-request-from-user"; } -/// Transient question to answer without adding it to conversation history. +/// 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(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class UIEphemeralQueryRequest +public partial class PermissionDecisionDeniedInteractivelyByUser : PermissionDecision { - /// Question to answer from the current conversation context. - [JsonPropertyName("question")] - public string Question { get; set; } = string.Empty; + /// + [JsonIgnore] + public override string Kind => "denied-interactively-by-user"; - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// 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; } } -/// The elicitation response (accept with form values, decline, or cancel). +/// Permission-decision variant indicating denial by content-exclusion policy, with path and message. +/// The denied-by-content-exclusion-policy variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class UIElicitationResponse +public partial class PermissionDecisionDeniedByContentExclusionPolicy : PermissionDecision { - /// MCP response metadata. - [JsonPropertyName("_meta")] - public IDictionary? Meta { get; set; } + /// + [JsonIgnore] + public override string Kind => "denied-by-content-exclusion-policy"; - /// The user's response: accept (submitted), decline (rejected), or cancel (dismissed). - [JsonPropertyName("action")] - public UIElicitationResponseAction Action { get; set; } + /// Human-readable explanation of why the path was excluded. + [JsonPropertyName("message")] + public required string Message { get; set; } - /// The form values submitted by the user (present when action is 'accept'). - [JsonPropertyName("content")] - public IDictionary? Content { get; set; } + /// File path that triggered the exclusion. + [JsonPropertyName("path")] + public required string Path { get; set; } } -/// JSON Schema describing the form fields to present to the user. +/// 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(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class UIElicitationSchema +public partial class PermissionDecisionDeniedByPermissionRequestHook : PermissionDecision { - /// Form field definitions, keyed by field name. - [JsonPropertyName("properties")] - public IDictionary Properties { get => field ??= new Dictionary(); set; } + /// + [JsonIgnore] + public override string Kind => "denied-by-permission-request-hook"; - /// List of required field names. - [JsonPropertyName("required")] - public IList? Required { get; set; } + /// Whether to interrupt the current agent turn. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("interrupt")] + public bool? Interrupt { get; set; } - /// Schema type indicator (always 'object'). - [JsonPropertyName("type")] - public string Type { get; set; } = string.Empty; + /// Optional message from the hook explaining the denial. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("message")] + public string? Message { get; set; } } -/// Metadata controlling an MCP task's lifetime. +/// Pending permission request ID and the decision to apply (approve/reject and scope). [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class McpTaskMetadata +internal sealed class PermissionDecisionRequest { - /// Task time-to-live. - [JsonPropertyName("ttl")] - public long? Ttl { get; set; } + /// 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; } -/// Prompt message and JSON schema describing the form fields to elicit from the user. +/// Pending permission prompt reconstructed from event history, with request ID and user-facing prompt details. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class UIElicitationRequest +public sealed class PendingPermissionRequest { - /// MCP request metadata. - [JsonPropertyName("_meta")] - public IDictionary? Meta { get; set; } - - /// Message describing what information is needed from the user. - [JsonPropertyName("message")] - public string Message { get; set; } = string.Empty; + /// Permission-recovery episode that authorized this request to surface for interactive attention. + [JsonPropertyName("recoveryEpisodeId")] + public string? RecoveryEpisodeId { get; set; } - /// Elicitation mode. Omitted and form are equivalent for structured elicitation. - [JsonPropertyName("mode")] - public McpElicitationFormMode? Mode { get; set; } + /// The user-facing permission prompt details (commands, write, read, mcp, url, memory, custom-tool, path, hook). + [JsonPropertyName("request")] + public PermissionPromptRequest Request { get; set; } = null!; - /// JSON Schema describing the form fields to present to the user. - [JsonPropertyName("requestedSchema")] - public UIElicitationSchema RequestedSchema { get => field ??= new(); set; } + /// 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(global::GitHub.Copilot.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(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class PermissionsPendingRequestsRequest +{ /// 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. +/// Indicates whether the operation succeeded. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class UIElicitationResult +public sealed class PermissionsSetApproveAllResult { - /// Whether the response was accepted. False if the request was already resolved by another client. + /// Whether the operation succeeded. [JsonPropertyName("success")] public bool Success { get; set; } } -/// Pending elicitation request ID and the user's response (accept/decline/cancel + form values). +/// Allow-all toggle for tool permission requests, with an optional telemetry source. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class UIHandlePendingElicitationRequest +internal sealed class PermissionsSetApproveAllRequest { - /// 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; } + /// 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 pending UI request was resolved by this call. +/// Indicates whether the requested permission mode was applied and reports the authoritative post-mutation mode. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class UIHandlePendingResult +public sealed class PermissionsSetModeResult { - /// 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. + /// Authoritative permission mode after the mutation. + [JsonPropertyName("mode")] + public PermissionMode Mode { get; set; } + + /// Whether the operation succeeded. [JsonPropertyName("success")] public bool Success { get; set; } } -/// User response for a pending user-input request, with answer text and whether it was typed freeform. +/// Permission mode to apply for the session. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class UIUserInputResponse +internal sealed class PermissionsSetModeRequest { - /// The user's answer text. - [JsonPropertyName("answer")] - public string Answer { get; set; } = string.Empty; + /// 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; } - /// 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; } + /// 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. `organization_targeting` is reserved for startup selection after the authenticated account matches an organization targeting policy; SDK callers default to `rpc` and cannot claim targeting provenance. + [JsonPropertyName("source")] + public PermissionModeSource? Source { get; set; } } -/// Request ID of a pending `user_input.requested` event and the user's response. +/// Current permission mode. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class UIHandlePendingUserInputRequest +public sealed class PermissionsGetModeResult { - /// 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; } + /// Current permission mode. + [JsonPropertyName("mode")] + public PermissionMode Mode { get; set; } +} +/// No parameters. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class PermissionsGetModeRequest +{ /// 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. +/// Indicates whether the operation succeeded. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class UIHandlePendingSamplingResponse +public sealed class PermissionsModifyRulesResult { + /// Whether the operation succeeded. + [JsonPropertyName("success")] + public bool Success { get; set; } } -/// Request ID of a pending `sampling.requested` event and an optional sampling result payload (omit to reject). +/// Scope and add/remove instructions for modifying session- or location-scoped permission rules. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class UIHandlePendingSamplingRequest +internal sealed class PermissionsModifyRulesParams { - /// The unique request ID from the sampling.requested event. - [JsonPropertyName("requestId")] - public string RequestId { get; set; } = string.Empty; + /// Rules to add to the scope. Applied before `remove`/`removeAll`. + [JsonPropertyName("add")] + public IList? Add { get; set; } - /// Optional sampling result payload. Omit to reject/cancel the sampling request without providing a result. - [JsonPropertyName("response")] - public UIHandlePendingSamplingResponse? Response { 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; } -/// Request ID of a pending `auto_mode_switch.requested` event and the user's response. +/// Indicates whether the operation succeeded. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class UIHandlePendingAutoModeSwitchRequest +public sealed class PermissionsSetRequiredResult { - /// The unique request ID from the auto_mode_switch.requested event. - [JsonPropertyName("requestId")] - public string RequestId { get; set; } = string.Empty; + /// Whether the operation succeeded. + [JsonPropertyName("success")] + public bool Success { get; set; } +} - /// User's choice for auto-mode switching: yes (allow this turn), yes_always (allow + persist as setting), or no (decline). - [JsonPropertyName("response")] - public UIAutoModeSwitchResponse Response { get; set; } +/// Toggles whether permission prompts should be bridged into session events for this client. +[Experimental(global::GitHub.Copilot.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; } -/// The user's selected action for an exhausted session limit. +/// Indicates whether the operation succeeded. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class UISessionLimitsExhaustedResponse +public sealed class PermissionsResetSessionApprovalsResult { - /// 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; } + /// Whether the operation succeeded. + [JsonPropertyName("success")] + public bool Success { get; set; } } -/// Request ID of a pending `session_limits_exhausted.requested` event and the user's selected limit action. +/// Clears session-scoped tool approvals and optionally clears location-scoped approvals and exact session-approved paths. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class UIHandlePendingSessionLimitsExhaustedRequest +internal sealed class PermissionsResetSessionApprovalsRequest { - /// 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; } + /// Whether location-scoped approvals and exact session-approved paths 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; } -/// User response for a pending exit-plan-mode request, with approval state, selected action, auto-approve flag, and feedback. +/// Indicates whether the operation succeeded. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class UIExitPlanModeResponse +public sealed class PermissionsNotifyPromptShownResult { - /// 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; } + /// Whether the operation succeeded. + [JsonPropertyName("success")] + public bool Success { get; set; } } -/// Request ID of a pending `exit_plan_mode.requested` event and the user's response. +/// Notification payload describing the permission prompt that the client just rendered. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class UIHandlePendingExitPlanModeRequest +internal sealed class PermissionPromptShownNotification { - /// 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; } + /// 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; } -/// 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). +/// Snapshot of the session's recursive directory grants, exact session-approved paths, and primary working directory. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class UIRegisterDirectAutoModeSwitchHandlerResult +public sealed class PermissionPathsList { - /// 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; + /// 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; + + /// Exact paths approved for this session without recursively allowing their descendants. + [JsonPropertyName("sessionApprovedPaths")] + public IList? SessionApprovedPaths { get; set; } } -/// Identifies the target session. +/// No parameters; returns the session's recursive directory grants and exact session-approved paths. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionUiRegisterDirectAutoModeSwitchHandlerRequest +internal sealed class PermissionsPathsListRequest { /// 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(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class UIUnregisterDirectAutoModeSwitchHandlerResult +public sealed class PermissionsPathsAddResult { - /// 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. +/// Directory path to add to the session's allowed directories. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class UIUnregisterDirectAutoModeSwitchHandlerRequest +internal sealed class PermissionPathsAddParams { - /// Handle previously returned by `registerDirectAutoModeSwitchHandler`. - [JsonPropertyName("handle")] - public string Handle { get; set; } = string.Empty; + /// 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")] @@ -22473,216 +25927,165 @@ internal sealed class UIUnregisterDirectAutoModeSwitchHandlerRequest /// Indicates whether the operation succeeded. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class PermissionsConfigureResult +public sealed class PermissionsPathsUpdatePrimaryResult { /// 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. +/// Directory path to set as the session's new primary working directory. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class PermissionsConfigureAdditionalContentExclusionPolicyRuleSource +internal sealed class PermissionPathsUpdatePrimaryParams { - /// Name of the policy source. - [JsonPropertyName("name")] - public string Name { get; set; } = string.Empty; + /// Directory to set as the new primary working directory for the session's permission policy. + [JsonPropertyName("path")] + public string Path { get; set; } = string.Empty; - /// Type of the policy source. - [JsonPropertyName("type")] - public string Type { get; set; } = string.Empty; + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } -/// Single content-exclusion rule supplied to `session.permissions.configure`, with paths, match conditions, and source. +/// Indicates whether the supplied path is within the session's allowed directories. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class PermissionsConfigureAdditionalContentExclusionPolicyRule +public sealed class PermissionPathsAllowedCheckResult { - /// 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; } + /// Whether the path is within the session's allowed directories. + [JsonPropertyName("allowed")] + public bool Allowed { get; set; } } -/// Content-exclusion policy supplied to `session.permissions.configure`, with rules, last-updated data, and scope. +/// Path to evaluate against the session's allowed directories. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class PermissionsConfigureAdditionalContentExclusionPolicy +internal sealed class PermissionPathsAllowedCheckParams { - /// 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; } + /// Path to check against the session's allowed directories. + [JsonPropertyName("path")] + public string Path { get; set; } = string.Empty; - /// Allowed values for the `PermissionsConfigureAdditionalContentExclusionPolicyScope` enumeration. - [JsonPropertyName("scope")] - public PermissionsConfigureAdditionalContentExclusionPolicyScope Scope { get; set; } + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } -/// 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. +/// Indicates whether the supplied path is within the session's workspace directory. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class PermissionPathsConfig +public sealed class PermissionPathsWorkspaceCheckResult { - /// 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; } + /// Whether the path is within the session workspace directory. + [JsonPropertyName("allowed")] + public bool Allowed { 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; } +/// Path to evaluate against the session's workspace (primary) directory. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class PermissionPathsWorkspaceCheckParams +{ + /// Path to check against the session workspace directory. + [JsonPropertyName("path")] + public string Path { get; set; } = string.Empty; - /// 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; } + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } -/// If specified, replaces the session's approved/denied permission rules. Omit to leave the current rules unchanged. +/// Resolved location-permissions key and type. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class PermissionRulesSet +public sealed class PermissionLocationResolveResult { - /// Rules that auto-approve matching requests. - [JsonPropertyName("approved")] - public IList Approved { get => field ??= []; set; } + /// Location key used in the location-permissions store. + [JsonPropertyName("locationKey")] + public string LocationKey { get; set; } = string.Empty; - /// Rules that auto-deny matching requests. - [JsonPropertyName("denied")] - public IList Denied { get => field ??= []; set; } + /// Whether the location is a git repo or directory. + [JsonPropertyName("locationType")] + public PermissionLocationType LocationType { 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. +/// Working directory to resolve into a location-permissions key. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class PermissionUrlsConfig +internal sealed class PermissionLocationResolveParams { - /// Initial list of allowed URL/domain patterns. Patterns may include path components. Ignored when `unrestricted` is true. - [JsonPropertyName("initialAllowed")] - public IList? InitialAllowed { get; set; } + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; - /// If true, the runtime allows access to all URLs without prompting. Initial allow-list is ignored when this is true. - [JsonPropertyName("unrestricted")] - public bool? Unrestricted { get; set; } + /// Working directory whose permission location should be resolved. + [JsonPropertyName("workingDirectory")] + public string WorkingDirectory { get; set; } = string.Empty; } -/// Patch of permission policy fields to apply (omit a field to leave it unchanged). +/// Summary of persisted location permissions applied to the session. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class PermissionsConfigureParams +public sealed class PermissionLocationApplyResult { - /// 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; } + /// Number of persisted allowed directories added to the live path manager. + [JsonPropertyName("appliedDirectoryCount")] + public long AppliedDirectoryCount { 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; } + /// Number of location-scoped rules added to the live permission service. + [JsonPropertyName("appliedRuleCount")] + public long AppliedRuleCount { 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; } + /// Location-scoped rules applied to the live permission service. + [JsonPropertyName("appliedRules")] + public IList AppliedRules { get => field ??= []; 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; } + /// Whether a different location was applied since the previous apply call. + [JsonPropertyName("changed")] + public bool Changed { 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; } + /// 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(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class PermissionLocationApplyParams +{ /// 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; } + /// Working directory whose persisted location permissions should be applied. + [JsonPropertyName("workingDirectory")] + public string WorkingDirectory { get; set; } = string.Empty; } -/// Indicates whether the permission decision was applied; false when the request was already resolved. +/// Indicates whether the operation succeeded. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class PermissionRequestResult +public sealed class PermissionsLocationsAddToolApprovalResult { - /// Whether the permission request was handled successfully. + /// Whether the operation succeeded. [JsonPropertyName("success")] public bool Success { get; set; } } -/// The client's response to the pending permission prompt. -/// Polymorphic base type discriminated by kind. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -[JsonPolymorphic( - TypeDiscriminatorPropertyName = "kind", - UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] -[JsonDerivedType(typeof(PermissionDecisionApproveOnce), "approve-once")] -[JsonDerivedType(typeof(PermissionDecisionApproveForSession), "approve-for-session")] -[JsonDerivedType(typeof(PermissionDecisionApproveReadOnlyForSession), "approve-read-only-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(global::GitHub.Copilot.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). +/// Tool approval to persist and apply. /// Polymorphic base type discriminated by kind. [Experimental(global::GitHub.Copilot.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(PermissionDecisionApproveForSessionApprovalWorkflow), "workflow")] -[JsonDerivedType(typeof(PermissionDecisionApproveForSessionApprovalExtensionPermissionAccess), "extension-permission-access")] -[JsonDerivedType(typeof(PermissionDecisionApproveForSessionApprovalExtensionEnvAccess), "extension-env-access")] -public partial class PermissionDecisionApproveForSessionApproval +[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(PermissionsLocationsAddToolApprovalDetailsWorkflow), "workflow")] +[JsonDerivedType(typeof(PermissionsLocationsAddToolApprovalDetailsExtensionPermissionAccess), "extension-permission-access")] +[JsonDerivedType(typeof(PermissionsLocationsAddToolApprovalDetailsExtensionEnvAccess), "extension-env-access")] +public partial class PermissionsLocationsAddToolApprovalDetails { /// The type discriminator. [JsonPropertyName("kind")] @@ -22690,10 +26093,10 @@ public partial class PermissionDecisionApproveForSessionApproval } -/// Session-scoped approval details for specific command identifiers. -/// The commands variant of . +/// Location-persisted tool approval details for specific command identifiers. +/// The commands variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class PermissionDecisionApproveForSessionApprovalCommands : PermissionDecisionApproveForSessionApproval +public partial class PermissionsLocationsAddToolApprovalDetailsCommands : PermissionsLocationsAddToolApprovalDetails { /// [JsonIgnore] @@ -22704,30 +26107,30 @@ public partial class PermissionDecisionApproveForSessionApprovalCommands : Permi public required IList CommandIdentifiers { get; set; } } -/// Session-scoped approval details for read-only filesystem operations. -/// The read variant of . +/// Location-persisted tool approval details for read-only filesystem operations. +/// The read variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class PermissionDecisionApproveForSessionApprovalRead : PermissionDecisionApproveForSessionApproval +public partial class PermissionsLocationsAddToolApprovalDetailsRead : PermissionsLocationsAddToolApprovalDetails { /// [JsonIgnore] public override string Kind => "read"; } -/// Session-scoped approval details for filesystem write operations. -/// The write variant of . +/// Location-persisted tool approval details for filesystem write operations. +/// The write variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class PermissionDecisionApproveForSessionApprovalWrite : PermissionDecisionApproveForSessionApproval +public partial class PermissionsLocationsAddToolApprovalDetailsWrite : PermissionsLocationsAddToolApprovalDetails { /// [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 . +/// Location-persisted tool approval details for an MCP server tool, or all tools when `toolName` is null. +/// The mcp variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class PermissionDecisionApproveForSessionApprovalMcp : PermissionDecisionApproveForSessionApproval +public partial class PermissionsLocationsAddToolApprovalDetailsMcp : PermissionsLocationsAddToolApprovalDetails { /// [JsonIgnore] @@ -22742,10 +26145,10 @@ public partial class PermissionDecisionApproveForSessionApprovalMcp : Permission public string? ToolName { get; set; } } -/// Session-scoped approval details for MCP sampling requests from a server. -/// The mcp-sampling variant of . +/// Location-persisted tool approval details for MCP sampling requests from a server. +/// The mcp-sampling variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class PermissionDecisionApproveForSessionApprovalMcpSampling : PermissionDecisionApproveForSessionApproval +public partial class PermissionsLocationsAddToolApprovalDetailsMcpSampling : PermissionsLocationsAddToolApprovalDetails { /// [JsonIgnore] @@ -22756,20 +26159,20 @@ public partial class PermissionDecisionApproveForSessionApprovalMcpSampling : Pe public required string ServerName { get; set; } } -/// Session-scoped approval details for writes to long-term memory. -/// The memory variant of . +/// Location-persisted tool approval details for writes to long-term memory. +/// The memory variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class PermissionDecisionApproveForSessionApprovalMemory : PermissionDecisionApproveForSessionApproval +public partial class PermissionsLocationsAddToolApprovalDetailsMemory : PermissionsLocationsAddToolApprovalDetails { /// [JsonIgnore] public override string Kind => "memory"; } -/// Session-scoped approval details for a custom tool, keyed by tool name. -/// The custom-tool variant of . +/// Location-persisted tool approval details for a custom tool, keyed by tool name. +/// The custom-tool variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class PermissionDecisionApproveForSessionApprovalCustomTool : PermissionDecisionApproveForSessionApproval +public partial class PermissionsLocationsAddToolApprovalDetailsCustomTool : PermissionsLocationsAddToolApprovalDetails { /// [JsonIgnore] @@ -22780,10 +26183,10 @@ public partial class PermissionDecisionApproveForSessionApprovalCustomTool : Per public required string ToolName { get; set; } } -/// Session-scoped approval details for extension-management operations, optionally narrowed by operation. -/// The extension-management variant of . +/// Location-persisted tool approval details for extension-management operations, optionally narrowed by operation. +/// The extension-management variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class PermissionDecisionApproveForSessionApprovalExtensionManagement : PermissionDecisionApproveForSessionApproval +public partial class PermissionsLocationsAddToolApprovalDetailsExtensionManagement : PermissionsLocationsAddToolApprovalDetails { /// [JsonIgnore] @@ -22795,10 +26198,10 @@ public partial class PermissionDecisionApproveForSessionApprovalExtensionManagem public string? Operation { get; set; } } -/// Session-scoped workflow approval, optionally narrowed by approval key. -/// The workflow variant of . +/// Location-persisted workflow approval, optionally narrowed by approval key. +/// The workflow variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class PermissionDecisionApproveForSessionApprovalWorkflow : PermissionDecisionApproveForSessionApproval +public partial class PermissionsLocationsAddToolApprovalDetailsWorkflow : PermissionsLocationsAddToolApprovalDetails { /// [JsonIgnore] @@ -22810,10 +26213,10 @@ public partial class PermissionDecisionApproveForSessionApprovalWorkflow : Permi 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 . +/// Location-persisted tool approval details for an extension's permission-gated capability access, keyed by extension name. +/// The extension-permission-access variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class PermissionDecisionApproveForSessionApprovalExtensionPermissionAccess : PermissionDecisionApproveForSessionApproval +public partial class PermissionsLocationsAddToolApprovalDetailsExtensionPermissionAccess : PermissionsLocationsAddToolApprovalDetails { /// [JsonIgnore] @@ -22824,10 +26227,10 @@ public partial class PermissionDecisionApproveForSessionApprovalExtensionPermiss 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 . +/// 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(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class PermissionDecisionApproveForSessionApprovalExtensionEnvAccess : PermissionDecisionApproveForSessionApproval +public partial class PermissionsLocationsAddToolApprovalDetailsExtensionEnvAccess : PermissionsLocationsAddToolApprovalDetails { /// [JsonIgnore] @@ -22842,5120 +26245,5332 @@ public partial class PermissionDecisionApproveForSessionApprovalExtensionEnvAcce 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 . +/// Location-scoped tool approval to persist. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class PermissionDecisionApproveForSession : PermissionDecision +internal sealed class PermissionLocationAddToolApprovalParams { - /// - [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)] + /// Tool approval to persist and apply. [JsonPropertyName("approval")] - public PermissionDecisionApproveForSessionApproval? Approval { get; set; } + public PermissionsLocationsAddToolApprovalDetails Approval { get => field ??= new(); 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; } + /// 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; } -/// Approve file-tool read access to specific directories for this logical session, including continuation or resume. -/// The approve-read-only-for-session variant of . +/// Folder path to check for trust. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class PermissionDecisionApproveReadOnlyForSession : PermissionDecision +internal sealed class FolderTrustCheckParams { - /// - [JsonIgnore] - public override string Kind => "approve-read-only-for-session"; + /// Folder path to check. + [JsonPropertyName("path")] + public string Path { get; set; } = string.Empty; - /// Canonical directories covered by the read-only grant. - [JsonPropertyName("directories")] - public required IList Directories { get; set; } + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } -/// Approval to persist for this location. -/// Polymorphic base type discriminated by kind. +/// Indicates whether the operation succeeded. [Experimental(global::GitHub.Copilot.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(PermissionDecisionApproveForLocationApprovalWorkflow), "workflow")] -[JsonDerivedType(typeof(PermissionDecisionApproveForLocationApprovalExtensionPermissionAccess), "extension-permission-access")] -[JsonDerivedType(typeof(PermissionDecisionApproveForLocationApprovalExtensionEnvAccess), "extension-env-access")] -public partial class PermissionDecisionApproveForLocationApproval +public sealed class PermissionsFolderTrustAddTrustedResult { - /// The type discriminator. - [JsonPropertyName("kind")] - public virtual string Kind { get; set; } = string.Empty; + /// Whether the operation succeeded. + [JsonPropertyName("success")] + public bool Success { get; set; } +} + +/// Folder path to add to trusted folders. +[Experimental(global::GitHub.Copilot.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(global::GitHub.Copilot.Diagnostics.Experimental)] +public sealed class PermissionsUrlsSetUnrestrictedModeResult +{ + /// Whether the operation succeeded. + [JsonPropertyName("success")] + public bool Success { get; set; } +} -/// Location-scoped approval details for specific command identifiers. -/// The commands variant of . +/// Whether the URL-permission policy should run in unrestricted mode. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class PermissionDecisionApproveForLocationApprovalCommands : PermissionDecisionApproveForLocationApproval +internal sealed class PermissionUrlsSetUnrestrictedModeParams { - /// - [JsonIgnore] - public override string Kind => "commands"; + /// 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; } - /// Command identifiers covered by this approval. - [JsonPropertyName("commandIdentifiers")] - public required IList CommandIdentifiers { get; set; } + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } -/// Location-scoped approval details for read-only filesystem operations. -/// The read variant of . +/// The repository the remote session targets. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class PermissionDecisionApproveForLocationApprovalRead : PermissionDecisionApproveForLocationApproval +public sealed class MetadataSnapshotRemoteMetadataRepository { - /// - [JsonIgnore] - public override string Kind => "read"; + /// 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; } -/// Location-scoped approval details for filesystem write operations. -/// The write variant of . +/// Remote-session-specific metadata. Populated only when `isRemote` is true. Fields are immutable for the lifetime of the session. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class PermissionDecisionApproveForLocationApprovalWrite : PermissionDecisionApproveForLocationApproval +public sealed class MetadataSnapshotRemoteMetadata { - /// - [JsonIgnore] - public override string Kind => "write"; + /// 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; } } -/// Location-scoped approval details for an MCP server tool, or all tools on the server when `toolName` is null. -/// The mcp variant of . +/// 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(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class PermissionDecisionApproveForLocationApprovalMcp : PermissionDecisionApproveForLocationApproval +public sealed class SessionMetadataSnapshot { - /// - [JsonIgnore] - public override string Kind => "mcp"; + /// 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; } - /// MCP server name. - [JsonPropertyName("serverName")] - public required string ServerName { get; set; } + /// Runtime client name associated with the session (telemetry identifier). + [JsonPropertyName("clientName")] + public string? ClientName { get; set; } - /// MCP tool name, or null to cover every tool on the server. - [JsonPropertyName("toolName")] - public string? ToolName { get; set; } + /// The current agent mode for this session (e.g., 'interactive', 'plan', 'autopilot'). + [JsonPropertyName("currentMode")] + public MetadataSnapshotCurrentMode CurrentMode { get; set; } + + /// Live indexed-search state for this session activation. Omitted by runtimes that do not expose indexed-search status; absence does not indicate enablement. + [JsonPropertyName("indexedSearch")] + public IndexedSearchState? IndexedSearch { 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; } + + /// 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; } + + /// Currently selected model identifier, if any. + [JsonPropertyName("selectedModel")] + public string? SelectedModel { get; set; } + + /// The unique identifier of the session. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; + + /// Current session limits, or null when no limits are active. + [JsonPropertyName("sessionLimits")] + public SessionLimitsConfig? SessionLimits { get; set; } + + /// ISO 8601 timestamp of when the session started. + [JsonPropertyName("startTime")] + public DateTimeOffset StartTime { get; set; } + + /// Short human-readable summary of the session, if known. Omitted when no summary has been generated. + [JsonPropertyName("summary")] + public string? Summary { get; set; } + + /// Absolute path to the session's current working directory. + [JsonPropertyName("workingDirectory")] + public string WorkingDirectory { get; set; } = string.Empty; + + /// Public-facing workspace metadata for this session, or null if the session has no associated workspace. Excludes runtime-internal fields (GitHub IDs, summary count, internal flags). + [JsonPropertyName("workspace")] + public SessionMetadataSnapshotWorkspace? Workspace { get; set; } + + /// Absolute path to the session's workspace directory on disk, or null if the session has no associated workspace. + [JsonPropertyName("workspacePath")] + public string? WorkspacePath { get; set; } +} + +/// Identifies the target session. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class SessionMetadataSnapshotRequest +{ + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + +/// Identifies the target session. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class SessionMetadataGetClientMetadataRequest +{ + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + +/// Atomic patch for client-owned session metadata. Operations apply in clear, remove, then set order. The resulting bag must satisfy the ClientMetadata entry and serialized-size limits. Local storage coordinates concurrent runtime processes; custom SessionFs providers must serialize writers that access the same session from multiple processes. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class MetadataUpdateClientMetadataRequest +{ + /// Remove every existing client metadata entry before applying remove and set. Defaults to false. + [JsonPropertyName("clear")] + public bool? Clear { get; set; } + + /// Case-sensitive keys to remove. Missing keys are ignored. Each key must be non-empty, at most 256 UTF-8 bytes, and outside the reserved `copilot/` and `github/` namespaces. + [JsonPropertyName("remove")] + public IList? Remove { get; set; } + + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; + + /// String entries to add or replace. Set wins when a key also appears in remove. Each key must be non-empty, at most 256 UTF-8 bytes, and outside the reserved `copilot/` and `github/` namespaces. Each value may contain at most 16 KiB of UTF-8 data. + [JsonPropertyName("set")] + public IDictionary? Set { get; set; } +} + +/// Indicates whether the local session is currently processing a turn or background continuation. +[Experimental(global::GitHub.Copilot.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; } +} + +/// Identifies the target session. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class SessionMetadataIsProcessingRequest +{ + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + +/// Current activity flags for the session. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public sealed class SessionActivity +{ + /// Whether an in-flight operation can currently be aborted. + [JsonPropertyName("abortable")] + public bool Abortable { get; set; } + + /// Whether the session currently has active work, including running turns or tasks. + [JsonPropertyName("hasActiveWork")] + public bool HasActiveWork { get; set; } +} + +/// Identifies the target session. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class SessionMetadataActivityRequest +{ + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + +/// Token-usage breakdown for the session's current context window. +public sealed class MetadataContextInfoResultContextInfo +{ + /// Output reservation overlapping the displayed prompt allowance plus tokens after the effective input budget's buffer-exhaustion blocking threshold (default 95%). + [JsonPropertyName("bufferTokens")] + public long BufferTokens { get; set; } + + /// Token count at which background compaction starts (configurable percentage of promptTokenLimit). + [JsonPropertyName("compactionThreshold")] + public long CompactionThreshold { get; set; } + + /// Tokens consumed by user/assistant/tool messages. + [JsonPropertyName("conversationTokens")] + public long ConversationTokens { get; set; } + + /// Provider-owned model display label, or the Auto routing label. Presentation only; modelName remains the tokenization identity. + [JsonPropertyName("displayModelName")] + public string? DisplayModelName { get; set; } + + /// Advertised prompt allowance for the selected context tier, without adding output tokens. The denominator for context-usage displays. + [JsonPropertyName("limit")] + public long Limit { get; set; } + + /// Tokens consumed by MCP tool definitions (subset of toolDefinitionsTokens, excludes deferred tools). + [JsonPropertyName("mcpToolsTokens")] + public long McpToolsTokens { get; set; } + + /// The model used for token counting. + [JsonPropertyName("modelName")] + public string ModelName { get; set; } = string.Empty; + + /// Effective input budget: the selected tier's prompt allowance bounded by the combined context ceiling minus the requested output allowance. Uses DEFAULT_TOKEN_LIMIT when limits are unspecified. + [JsonPropertyName("promptTokenLimit")] + public long PromptTokenLimit { get; set; } + + /// Provider whose catalog supplies this context window. Omitted when provider identity is unavailable. + [JsonPropertyName("provider")] + public ModelProviderRef? Provider { get; set; } + + /// Tokens consumed by the system prompt. + [JsonPropertyName("systemTokens")] + public long SystemTokens { get; set; } + + /// Tokens consumed by tool definitions sent to the model (excludes deferred tools). + [JsonPropertyName("toolDefinitionsTokens")] + public long ToolDefinitionsTokens { get; set; } + + /// Sum of system, conversation and tool-definition tokens. + [JsonPropertyName("totalTokens")] + public long TotalTokens { get; set; } +} + +/// Token breakdown for the session's current context window, or null if uninitialized. +[Experimental(global::GitHub.Copilot.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. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class MetadataContextInfoRequest +{ + /// Requested output allowance to reserve against the combined context ceiling. Pass 0 to resolve the session's request cap, falling back to the model's advertised output limit. + [JsonPropertyName("outputTokenLimit")] + public long OutputTokenLimit { get; set; } + + /// Advertised prompt allowance. Pass 0 to resolve the selected model and context tier from the session. + [JsonPropertyName("promptTokenLimit")] + public long PromptTokenLimit { get; set; } + + /// Provider owning selectedModel. Omit to use the current provider for the current model, or deterministic legacy lookup for a different model. + [JsonPropertyName("providerId")] + public string? ProviderId { get; set; } + + /// Model identifier used for tokenization. Omit to use the session default. Used both for token counting and to compute display values. + [JsonPropertyName("selectedModel")] + public string? SelectedModel { get; set; } + + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } -/// Location-scoped approval details for MCP sampling requests from a server. -/// The mcp-sampling variant of . -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class PermissionDecisionApproveForLocationApprovalMcpSampling : PermissionDecisionApproveForLocationApproval -{ - /// - [JsonIgnore] - public override string Kind => "mcp-sampling"; +/// 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 +{ + /// Overlapping output reservation plus post-blocking-threshold buffer. + [JsonPropertyName("buffer")] + public long Buffer { get; set; } + + /// Custom-instructions tokens (0 when none are configured). + [JsonPropertyName("customInstructions")] + public long CustomInstructions { get; set; } + + /// Remaining unused window capacity (clamped at 0). + [JsonPropertyName("freeSpace")] + public long FreeSpace { get; set; } + + /// MCP tool-definition tokens. + [JsonPropertyName("mcpTools")] + public long McpTools { get; set; } + + /// Conversation (user/assistant/tool) message tokens. + [JsonPropertyName("messages")] + public long Messages { get; set; } - /// MCP server name. - [JsonPropertyName("serverName")] - public required string ServerName { get; set; } -} + /// System prompt tokens, excluding custom instructions. + [JsonPropertyName("systemPrompt")] + public long SystemPrompt { get; set; } -/// Location-scoped approval details for writes to long-term memory. -/// The memory variant of . -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class PermissionDecisionApproveForLocationApprovalMemory : PermissionDecisionApproveForLocationApproval -{ - /// - [JsonIgnore] - public override string Kind => "memory"; + /// Non-MCP tool-definition tokens. + [JsonPropertyName("systemTools")] + public long SystemTools { get; set; } } -/// Location-scoped approval details for a custom tool, keyed by tool name. -/// The custom-tool variant of . -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class PermissionDecisionApproveForLocationApprovalCustomTool : PermissionDecisionApproveForLocationApproval +/// Successful compaction history for the session. +public sealed class MetadataContextAttributionResultContextAttributionCompactions { - /// - [JsonIgnore] - public override string Kind => "custom-tool"; - - /// Custom tool name. - [JsonPropertyName("toolName")] - public required string ToolName { get; set; } + /// Number of successful compactions in this session. + [JsonPropertyName("count")] + public long Count { get; set; } } -/// Location-scoped approval details for extension-management operations, optionally narrowed by operation. -/// The extension-management variant of . -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class PermissionDecisionApproveForLocationApprovalExtensionManagement : PermissionDecisionApproveForLocationApproval +/// RPC data type for MetadataContextAttributionResultContextAttributionEntry operations. +public sealed class MetadataContextAttributionResultContextAttributionEntry { - /// - [JsonIgnore] - public override string Kind => "extension-management"; + /// 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; } - /// Optional operation identifier; when omitted, the approval covers all extension management operations. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("operation")] - public string? Operation { 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; -/// Location-scoped workflow approval, optionally narrowed by approval key. -/// The workflow variant of . -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class PermissionDecisionApproveForLocationApprovalWorkflow : PermissionDecisionApproveForLocationApproval -{ - /// - [JsonIgnore] - public override string Kind => "workflow"; + /// Source category for this entry. Not a closed set — tolerate unknown values. Known values today: `skill`, `subagent`, `mcpServer`, `tool`, `system`, `toolDefinition`, `plugin`. + [JsonPropertyName("kind")] + public string Kind { get; set; } = string.Empty; - /// Optional workflow operation name or canonical approval key; when omitted, the approval covers all workflow operations. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("approvalKey")] - public string? ApprovalKey { get; set; } -} + /// Human-readable display label, e.g. `bash` or `skill: tmux`. Presentation-only; may be localized/reformatted without notice — do not key off it. + [JsonPropertyName("label")] + public string Label { get; set; } = string.Empty; -/// Location-scoped approval details for an extension's permission-gated capability access, keyed by extension name. -/// The extension-permission-access variant of . -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class PermissionDecisionApproveForLocationApprovalExtensionPermissionAccess : PermissionDecisionApproveForLocationApproval -{ - /// - [JsonIgnore] - public override string Kind => "extension-permission-access"; + /// 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; } - /// Extension name. - [JsonPropertyName("extensionName")] - public required string ExtensionName { get; set; } + /// Token count currently in context attributable to this entry. + [JsonPropertyName("tokens")] + public long Tokens { 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(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class PermissionDecisionApproveForLocationApprovalExtensionEnvAccess : PermissionDecisionApproveForLocationApproval +/// Per-source token attribution snapshot for the current context window. The heaviest individual messages are available separately via `metadata.getContextHeaviestMessages`. +public sealed class MetadataContextAttributionResultContextAttribution { - /// - [JsonIgnore] - public override string Kind => "extension-env-access"; + /// Output reservation overlapping the displayed prompt allowance plus the tokens past the effective input budget's buffer-exhaustion blocking threshold. Mirrors `SessionContextInfo.bufferTokens`. + [JsonPropertyName("bufferTokens")] + public long BufferTokens { get; set; } - /// Names of the sensitive environment variables this approval covers. Values are never persisted. - [JsonPropertyName("environmentVariables")] - public required IList EnvironmentVariables { 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; } - /// Extension name. - [JsonPropertyName("extensionName")] - public required string ExtensionName { get; set; } -} + /// Successful compaction history for the session. + [JsonPropertyName("compactions")] + public MetadataContextAttributionResultContextAttributionCompactions Compactions { get => field ??= new(); 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(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class PermissionDecisionApproveForLocation : PermissionDecision -{ - /// - [JsonIgnore] - public override string Kind => "approve-for-location"; + /// Token count at which background compaction starts. Mirrors `SessionContextInfo.compactionThreshold`. + [JsonPropertyName("compactionThreshold")] + public long CompactionThreshold { get; set; } - /// Approval to persist for this location. - [JsonPropertyName("approval")] - public required PermissionDecisionApproveForLocationApproval Approval { 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; } - /// Location key (git root or cwd) to persist the approval to. - [JsonPropertyName("locationKey")] - public required string LocationKey { get; set; } -} + /// Advertised prompt allowance for the selected context tier: the denominator for context-usage displays and capacity for `categories.freeSpace` and `categories.buffer`. Mirrors `SessionContextInfo.limit`. + [JsonPropertyName("limit")] + public long Limit { get; set; } -/// Permission-decision request variant to permanently approve a URL domain across sessions. -/// The approve-permanently variant of . -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class PermissionDecisionApprovePermanently : PermissionDecision -{ - /// - [JsonIgnore] - public override string Kind => "approve-permanently"; + /// 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; - /// URL domain to approve permanently. - [JsonPropertyName("domain")] - public required string Domain { 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; -/// Permission-decision request variant to reject a pending permission request, with optional feedback. -/// The reject variant of . -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class PermissionDecisionReject : PermissionDecision -{ - /// - [JsonIgnore] - public override string Kind => "reject"; + /// Effective input budget after reserving requested output against the combined context ceiling. Mirrors `SessionContextInfo.promptTokenLimit`. + [JsonPropertyName("promptTokenLimit")] + public long PromptTokenLimit { get; set; } - /// Optional feedback explaining the rejection. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("feedback")] - public string? Feedback { 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; } } -/// Permission-decision variant indicating no user was available to confirm the request. -/// The user-not-available variant of . +/// Per-source attribution breakdown for the session's current context window, or null if uninitialized. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class PermissionDecisionUserNotAvailable : PermissionDecision +public sealed class MetadataContextAttributionResult { - /// - [JsonIgnore] - public override string Kind => "user-not-available"; + /// 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; } } -/// Permission-decision variant indicating the request was approved. -/// The approved variant of . +/// Identifies the target session. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class PermissionDecisionApproved : PermissionDecision +internal sealed class SessionMetadataGetContextAttributionRequest { - /// - [JsonIgnore] - public override string Kind => "approved"; + /// 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 . +/// A single large message currently in context. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class PermissionDecisionApprovedForSession : PermissionDecision +public sealed class ContextHeaviestMessage { - /// - [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; } -} + /// Stable identifier for this message within the snapshot. + [JsonPropertyName("id")] + public string Id { get; set; } = string.Empty; -/// Permission-decision variant indicating approval was persisted for a project location, with approval details and location key. -/// The approved-for-location variant of . -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class PermissionDecisionApprovedForLocation : PermissionDecision -{ - /// - [JsonIgnore] - public override string Kind => "approved-for-location"; + /// Human-readable source label, e.g. `tool: bash` or `skill: tmux`. Presentation-only. + [JsonPropertyName("label")] + public string Label { get; set; } = string.Empty; - /// The approval to persist for this location. - [JsonPropertyName("approval")] - public required UserToolSessionApproval Approval { get; set; } + /// Role of the chat message (`user`, `assistant`, or `tool`). + [JsonPropertyName("role")] + public string Role { get; set; } = string.Empty; - /// The location key (git root or cwd) to persist the approval to. - [JsonPropertyName("locationKey")] - public required string LocationKey { get; set; } + /// Token count currently in context for this individual message. + [JsonPropertyName("tokens")] + public long Tokens { get; set; } } -/// Permission-decision variant indicating the request was cancelled before use, with an optional reason. -/// The cancelled variant of . +/// The heaviest individual messages in the session's context window, most-expensive first. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class PermissionDecisionCancelled : PermissionDecision +public sealed class MetadataContextHeaviestMessagesResult { - /// - [JsonIgnore] - public override string Kind => "cancelled"; + /// Heaviest messages, most-expensive first. + [JsonPropertyName("messages")] + public IList Messages { get => field ??= []; set; } - /// Optional explanation of why the request was cancelled. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("reason")] - public string? Reason { get; 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; } } -/// Permission-decision variant indicating explicit denial by permission rules, with the matching rules. -/// The denied-by-rules variant of . +/// Parameters for the heaviest-messages query. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class PermissionDecisionDeniedByRules : PermissionDecision +internal sealed class MetadataContextHeaviestMessagesRequest { - /// - [JsonIgnore] - public override string Kind => "denied-by-rules"; + /// Maximum number of messages to return, most-expensive first. Omit for the server default. + [JsonPropertyName("limit")] + public long? Limit { get; set; } - /// Rules that denied the request. - [JsonPropertyName("rules")] - public required IList Rules { get; set; } + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } -/// Permission-decision variant indicating no approval rule matched and user confirmation was unavailable. -/// The denied-no-approval-rule-and-could-not-request-from-user variant of . +/// 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(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class PermissionDecisionDeniedNoApprovalRuleAndCouldNotRequestFromUser : PermissionDecision +public sealed class MetadataRecordContextChangeResult { - /// - [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 . +/// Updated working-directory/git context to record on the session. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class PermissionDecisionDeniedInteractivelyByUser : PermissionDecision +internal sealed class MetadataRecordContextChangeRequest { - /// - [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; } + /// Updated working directory and git context. Emitted as the new payload of `session.context_changed`. + [JsonPropertyName("context")] + public SessionWorkingDirectoryContext Context { get => field ??= new(); set; } - /// Whether to force-reject the current agent turn. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("forceReject")] - public bool? ForceReject { get; set; } + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } -/// Permission-decision variant indicating denial by content-exclusion policy, with path and message. -/// The denied-by-content-exclusion-policy variant of . +/// 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(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class PermissionDecisionDeniedByContentExclusionPolicy : PermissionDecision +public sealed class MetadataSetWorkingDirectoryResult { - /// - [JsonIgnore] - public override string Kind => "denied-by-content-exclusion-policy"; + /// Working directory after the update. + [JsonPropertyName("workingDirectory")] + public string WorkingDirectory { get; set; } = string.Empty; +} - /// Human-readable explanation of why the path was excluded. - [JsonPropertyName("message")] - public required string Message { get; set; } +/// 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(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class MetadataSetWorkingDirectoryRequest +{ + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; - /// File path that triggered the exclusion. - [JsonPropertyName("path")] - public required string Path { 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; } -/// Permission-decision variant indicating denial by a permission request hook, with optional message and interrupt flag. -/// The denied-by-permission-request-hook 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(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class PermissionDecisionDeniedByPermissionRequestHook : PermissionDecision +public sealed class MetadataRecomputeContextTokensResult { - /// - [JsonIgnore] - public override string Kind => "denied-by-permission-request-hook"; + /// Tokens contributed by user/assistant/tool messages (excludes system/developer prompts). + [JsonPropertyName("messagesTokenCount")] + public long MessagesTokenCount { get; set; } - /// Whether to interrupt the current agent turn. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("interrupt")] - public bool? Interrupt { get; set; } + /// Tokens contributed by system/developer prompt snapshots. + [JsonPropertyName("systemTokenCount")] + public long SystemTokenCount { get; set; } - /// Optional message from the hook explaining the denial. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("message")] - public string? Message { get; set; } + /// Sum of tokens across chat-context and system-context messages currently held by the session. + [JsonPropertyName("totalTokens")] + public long TotalTokens { get; set; } } -/// Pending permission request ID and the decision to apply (approve/reject and scope). +/// Model identifier to use when re-tokenizing the session's existing messages. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class PermissionDecisionRequest +internal sealed class MetadataRecomputeContextTokensRequest { - /// 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; } + /// 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; } -/// Pending permission prompt reconstructed from event history, with request ID and user-facing prompt details. +/// Availability of built-in job tools surfaced to boundary consumers. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class PendingPermissionRequest +public sealed class SessionSettingsBuiltInToolAvailabilitySnapshot { - /// Permission-recovery episode that authorized this request to surface for interactive attention. - [JsonPropertyName("recoveryEpisodeId")] - public string? RecoveryEpisodeId { get; set; } - - /// The user-facing permission prompt details (commands, write, read, mcp, url, memory, custom-tool, path, hook). - [JsonPropertyName("request")] - public PermissionPromptRequest Request { get; set; } = null!; + /// Whether the create-pull-request tool is available. + [JsonPropertyName("createPullRequest")] + public bool? CreatePullRequest { get; set; } - /// Unique identifier for the pending permission request. - [JsonPropertyName("requestId")] - public string RequestId { get; set; } = string.Empty; + /// Whether the report-progress tool is available. + [JsonPropertyName("reportProgress")] + public bool? ReportProgress { get; set; } } -/// List of pending permission requests reconstructed from event history. +/// Redacted job settings for a session. The job nonce is excluded. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class PendingPermissionRequestList +public sealed class SessionSettingsJobSnapshot { - /// 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; } + /// Availability of job-specific built-in tools. + [JsonPropertyName("builtInToolAvailability")] + public SessionSettingsBuiltInToolAvailabilitySnapshot? BuiltInToolAvailability { 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; } } -/// No parameters; returns currently-pending permission requests for the session. +/// Redacted model routing settings for a session. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class PermissionsPendingRequestsRequest +public sealed class SessionSettingsModelSnapshot { - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Agent service callback URL for job and progress updates. + [JsonPropertyName("callbackUrl")] + public string? CallbackUrl { 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; } } -/// Indicates whether the operation succeeded. +/// Online-evaluation settings safe to expose across the SDK boundary. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class PermissionsSetApproveAllResult +public sealed class SessionSettingsOnlineEvaluationSnapshot { - /// Whether the operation succeeded. - [JsonPropertyName("success")] - public bool Success { get; set; } + /// Whether online evaluation is disabled. + [JsonPropertyName("disableOnlineEvaluation")] + public bool? DisableOnlineEvaluation { get; set; } + + /// Whether online-evaluation output-file generation is enabled. + [JsonPropertyName("enableOnlineEvaluationOutputFile")] + public bool? EnableOnlineEvaluationOutputFile { get; set; } } -/// Allow-all toggle for tool permission requests, with an optional telemetry source. +/// Redacted repository and GitHub host settings for a session. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class PermissionsSetApproveAllRequest +public sealed class SessionSettingsRepoSnapshot { - /// Whether to auto-approve all tool permission requests. - [JsonPropertyName("enabled")] - public bool Enabled { get; set; } + /// Checked-out repository branch. + [JsonPropertyName("branch")] + public string? Branch { get; set; } - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Checked-out commit SHA. + [JsonPropertyName("commit")] + public string? Commit { get; set; } - /// Optional source for allow-all telemetry. Defaults to `rpc` when omitted for SDK callers. - [JsonPropertyName("source")] - public PermissionsSetApproveAllSource? Source { get; set; } + /// GitHub server host name. + [JsonPropertyName("host")] + public string? Host { get; set; } + + /// Protocol used to access the GitHub host. + [JsonPropertyName("hostProtocol")] + public string? HostProtocol { get; set; } + + /// GitHub repository database ID. + [JsonPropertyName("id")] + public double? Id { get; set; } + + /// Repository name. + [JsonPropertyName("name")] + public string? Name { get; set; } + + /// GitHub repository owner database ID. + [JsonPropertyName("ownerId")] + public double? OwnerId { get; set; } + + /// Repository owner login. + [JsonPropertyName("ownerName")] + public string? OwnerName { get; set; } + + /// Number of commits in the pull request. + [JsonPropertyName("prCommitCount")] + public double? PrCommitCount { get; set; } + + /// Whether the repository is writable. + [JsonPropertyName("readWrite")] + public bool? ReadWrite { 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; } } -/// Indicates whether the requested permission mode was applied and reports the authoritative post-mutation mode. +/// Redacted validation and memory-tool settings for a session. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class PermissionsSetModeResult +public sealed class SessionSettingsValidationSnapshot { - /// Authoritative permission mode after the mutation. - [JsonPropertyName("mode")] - public PermissionMode Mode { get; set; } + /// Whether advisory validation is enabled. + [JsonPropertyName("advisoryEnabled")] + public bool? AdvisoryEnabled { get; set; } - /// Whether the operation succeeded. - [JsonPropertyName("success")] - public bool Success { get; set; } -} + /// Whether CodeQL validation is enabled. + [JsonPropertyName("codeqlEnabled")] + public bool? CodeqlEnabled { get; set; } -/// Permission mode to apply for the session. -[Experimental(global::GitHub.Copilot.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; } + /// Whether code-review validation is enabled. + [JsonPropertyName("codeReviewEnabled")] + public bool? CodeReviewEnabled { get; set; } - /// Permission mode to apply. - [JsonPropertyName("mode")] - public PermissionMode Mode { get; set; } + /// Model used for code-review validation. + [JsonPropertyName("codeReviewModel")] + public string? CodeReviewModel { get; set; } - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Dependabot validation timeout budget in seconds. + [JsonPropertyName("dependabotTimeout")] + public double? DependabotTimeout { get; set; } - /// Optional source for permission-mode telemetry. `organization_targeting` is reserved for startup selection after the authenticated account matches an organization targeting policy; SDK callers default to `rpc` and cannot claim targeting provenance. - [JsonPropertyName("source")] - public PermissionModeSource? Source { get; set; } -} + /// Whether the memory-store tool is enabled. + [JsonPropertyName("memoryStoreEnabled")] + public bool? MemoryStoreEnabled { get; set; } -/// Current permission mode. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class PermissionsGetModeResult -{ - /// Current permission mode. - [JsonPropertyName("mode")] - public PermissionMode Mode { get; set; } -} + /// Whether the memory-vote tool is enabled. + [JsonPropertyName("memoryVoteEnabled")] + public bool? MemoryVoteEnabled { get; set; } -/// No parameters. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class PermissionsGetModeRequest -{ - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; -} + /// Whether secret-scanning validation is enabled. + [JsonPropertyName("secretScanningEnabled")] + public bool? SecretScanningEnabled { get; set; } -/// Indicates whether the operation succeeded. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class PermissionsModifyRulesResult -{ - /// Whether the operation succeeded. - [JsonPropertyName("success")] - public bool Success { get; set; } + /// General validation timeout budget in seconds. + [JsonPropertyName("timeout")] + public double? Timeout { get; set; } } -/// Scope and add/remove instructions for modifying session- or location-scoped permission rules. +/// Redacted, serializable view of session runtime settings for SDK boundary consumers. Secrets and raw feature flags are intentionally excluded. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class PermissionsModifyRulesParams +internal sealed class SessionSettingsSnapshot { - /// Rules to add to the scope. Applied before `remove`/`removeAll`. - [JsonPropertyName("add")] - public IList? Add { get; set; } + /// Name of the SDK client that created the session. + [JsonPropertyName("clientName")] + public string? ClientName { get; set; } - /// Specific rules to remove from the scope. Ignored when `removeAll` is true. - [JsonPropertyName("remove")] - public IList? Remove { get; set; } + /// Redacted job settings. + [JsonPropertyName("job")] + public SessionSettingsJobSnapshot Job { get => field ??= new(); 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; } + /// Redacted model routing settings. + [JsonPropertyName("model")] + public SessionSettingsModelSnapshot Model { get => field ??= new(); 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; } + /// Online-evaluation settings safe for SDK consumers. + [JsonPropertyName("onlineEvaluation")] + public SessionSettingsOnlineEvaluationSnapshot OnlineEvaluation { get => field ??= new(); set; } - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; -} + /// Redacted repository and host settings. + [JsonPropertyName("repo")] + public SessionSettingsRepoSnapshot Repo { get => field ??= new(); set; } -/// Indicates whether the operation succeeded. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class PermissionsSetRequiredResult -{ - /// Whether the operation succeeded. - [JsonPropertyName("success")] - public bool Success { get; set; } -} + /// Session start time as Unix epoch milliseconds. + [JsonPropertyName("startTimeMs")] + public double? StartTimeMs { get; set; } -/// Toggles whether permission prompts should be bridged into session events for this client. -[Experimental(global::GitHub.Copilot.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; } + /// Session timeout in milliseconds. + [JsonPropertyName("timeoutMs")] + public double? TimeoutMs { get; set; } - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; -} + /// Redacted validation and memory-tool settings. + [JsonPropertyName("validation")] + public SessionSettingsValidationSnapshot Validation { get => field ??= new(); set; } -/// Indicates whether the operation succeeded. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class PermissionsResetSessionApprovalsResult -{ - /// Whether the operation succeeded. - [JsonPropertyName("success")] - public bool Success { 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; } } -/// Clears session-scoped tool approvals and optionally clears location-scoped approvals and exact session-approved paths. +/// Identifies the target session. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class PermissionsResetSessionApprovalsRequest +internal sealed class SessionSettingsSnapshotRequest { - /// Whether location-scoped approvals and exact session-approved paths 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. +/// Result of evaluating a Rust-owned settings predicate. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class PermissionsNotifyPromptShownResult +internal sealed class SessionSettingsEvaluatePredicateResult { - /// Whether the operation succeeded. - [JsonPropertyName("success")] - public bool Success { get; set; } + /// Whether the named settings predicate evaluated to enabled. + [JsonPropertyName("enabled")] + public bool Enabled { get; set; } } -/// Notification payload describing the permission prompt that the client just rendered. +/// Named Rust-owned settings predicate to evaluate for this session. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class PermissionPromptShownNotification +internal sealed class SessionSettingsEvaluatePredicateRequest { - /// 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; + /// Predicate name. The runtime owns the raw feature-flag names and composition logic. + [JsonPropertyName("name")] + public SessionSettingsPredicateName Name { get; set; } /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; + + /// Tool name for tool-scoped predicates such as trivial-change handling. + [JsonPropertyName("toolName")] + public string? ToolName { get; set; } } -/// Snapshot of the session's recursive directory grants, exact session-approved paths, and primary working directory. +/// Content-exclusion decision for one requested path. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class PermissionPathsList +public sealed class ContentExclusionPathCheck { - /// 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; + /// Whether the session's complete content-exclusion policy excludes the path. + [JsonPropertyName("excluded")] + public bool Excluded { get; set; } - /// Exact paths approved for this session without recursively allowing their descendants. - [JsonPropertyName("sessionApprovedPaths")] - public IList? SessionApprovedPaths { get; set; } + /// The path supplied by the caller. + [JsonPropertyName("path")] + public string Path { get; set; } = string.Empty; } -/// No parameters; returns the session's recursive directory grants and exact session-approved paths. +/// Batch content-exclusion result. Callers must fail closed when policy evaluation is unavailable. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class PermissionsPathsListRequest +public sealed class ContentExclusionCheckPathsResult { - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; -} + /// 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; } -/// Indicates whether the operation succeeded. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class PermissionsPathsAddResult -{ - /// Whether the operation succeeded. - [JsonPropertyName("success")] - public bool Success { get; set; } + /// Per-path decisions in request order. Empty when available is false. + [JsonPropertyName("checks")] + public IList Checks { get => field ??= []; set; } } -/// Directory path to add to the session's allowed directories. +/// Local file system absolute paths within the session working directory to check against its content-exclusion policy. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class PermissionPathsAddParams +internal sealed class ContentExclusionCheckPathsRequest { - /// 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; + /// Local file system absolute paths within the session working directory to check. Results are returned in the same order, including duplicates. + [JsonPropertyName("paths")] + public IList Paths { get => field ??= []; set; } /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Indicates whether the operation succeeded. +/// Identifier of the spawned shell process, usable with shell.kill while the process is running. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class PermissionsPathsUpdatePrimaryResult +public sealed class ShellExecResult { - /// Whether the operation succeeded. - [JsonPropertyName("success")] - public bool Success { get; set; } + /// Identifier usable with shell.kill while the process is running. + [JsonPropertyName("processId")] + public string ProcessId { get; set; } = string.Empty; } -/// Directory path to set as the session's new primary working directory. +/// Shell command to run, with optional working directory and timeout in milliseconds. Spawn failures return an RPC error. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class PermissionPathsUpdatePrimaryParams +internal sealed class ShellExecRequest { - /// Directory to set as the new primary working directory for the session's permission policy. - [JsonPropertyName("path")] - public string Path { get; set; } = string.Empty; + /// Shell command to execute. + [JsonPropertyName("command")] + public string Command { get; set; } = string.Empty; + + /// Working directory (defaults to session working directory). + [JsonPropertyName("cwd")] + public string? Cwd { get; set; } /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; + + /// Timeout in milliseconds (default: 30000). + [JsonConverter(typeof(MillisecondsTimeSpanConverter))] + [JsonPropertyName("timeout")] + public TimeSpan? Timeout { get; set; } } -/// Indicates whether the supplied path is within the session's allowed directories. +/// Indicates whether the signal was delivered; false if the process was unknown or already exited. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class PermissionPathsAllowedCheckResult +public sealed class ShellKillResult { - /// Whether the path is within the session's allowed directories. - [JsonPropertyName("allowed")] - public bool Allowed { get; set; } + /// Whether the signal was sent successfully. + [JsonPropertyName("killed")] + public bool Killed { get; set; } } -/// Path to evaluate against the session's allowed directories. +/// Identifier of a process previously returned by "shell.exec" and the signal to send. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class PermissionPathsAllowedCheckParams +internal sealed class ShellKillRequest { - /// Path to check against the session's allowed directories. - [JsonPropertyName("path")] - public string Path { get; set; } = string.Empty; + /// Process identifier returned by shell.exec. + [JsonPropertyName("processId")] + public string ProcessId { get; set; } = string.Empty; /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; + + /// Signal to send (default: SIGTERM). + [JsonPropertyName("signal")] + public ShellKillSignal? Signal { get; set; } } -/// Indicates whether the supplied path is within the session's workspace directory. +/// Result of a user-requested shell command. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class PermissionPathsWorkspaceCheckResult +public sealed class UserRequestedShellCommandResult { - /// Whether the path is within the session workspace directory. - [JsonPropertyName("allowed")] - public bool Allowed { get; set; } + /// Error output when the execution failed. + [JsonPropertyName("error")] + public string? Error { get; set; } + + /// Process exit code, when available. + [JsonPropertyName("exitCode")] + public long? ExitCode { get; set; } + + /// Captured command output. + [JsonPropertyName("output")] + public string Output { get; set; } = string.Empty; + + /// Whether the command completed successfully. + [JsonPropertyName("success")] + public bool Success { get; set; } + + /// Tool call id emitted for the shell execution. + [JsonPropertyName("toolCallId")] + public string ToolCallId { get; set; } = string.Empty; } -/// Path to evaluate against the session's workspace (primary) directory. +/// User-requested shell command and cancellation handle. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class PermissionPathsWorkspaceCheckParams +internal sealed class ShellExecuteUserRequestedRequest { - /// Path to check against the session workspace directory. - [JsonPropertyName("path")] - public string Path { get; set; } = string.Empty; + /// Shell command to execute. + [JsonPropertyName("command")] + public string Command { get; set; } = string.Empty; + + /// Caller-provided cancellation handle for this execution. + [JsonPropertyName("requestId")] + public string RequestId { get; set; } = string.Empty; /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Resolved location-permissions key and type. +/// Cancellation result for a user-requested shell command. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class PermissionLocationResolveResult +public sealed class CancelUserRequestedShellCommandResult { - /// 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; } + /// Whether an in-flight execution was found and signalled to cancel. + [JsonPropertyName("cancelled")] + public bool Cancelled { get; set; } } -/// Working directory to resolve into a location-permissions key. +/// User-requested shell execution cancellation handle. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class PermissionLocationResolveParams +internal sealed class ShellCancelUserRequestedRequest { + /// Request ID previously passed to executeUserRequested. + [JsonPropertyName("requestId")] + public string RequestId { get; set; } = string.Empty; + /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; - - /// 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. +/// Post-compaction context window usage breakdown. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class PermissionLocationApplyResult +public sealed class HistoryCompactContextWindow { - /// Number of persisted allowed directories added to the live path manager. - [JsonPropertyName("appliedDirectoryCount")] - public long AppliedDirectoryCount { get; set; } + /// Token count from non-system messages (user, assistant, tool). + [JsonPropertyName("conversationTokens")] + public long? ConversationTokens { get; set; } - /// Number of location-scoped rules added to the live permission service. - [JsonPropertyName("appliedRuleCount")] - public long AppliedRuleCount { get; set; } + /// Current total tokens in the context window (system + conversation + tool definitions). + [JsonPropertyName("currentTokens")] + public long CurrentTokens { get; set; } - /// Location-scoped rules applied to the live permission service. - [JsonPropertyName("appliedRules")] - public IList AppliedRules { get => field ??= []; set; } + /// Current number of messages in the conversation. + [JsonPropertyName("messagesLength")] + public long MessagesLength { get; set; } - /// Whether a different location was applied since the previous apply call. - [JsonPropertyName("changed")] - public bool Changed { get; set; } + /// Token count from system message(s). + [JsonPropertyName("systemTokens")] + public long? SystemTokens { get; set; } - /// Location key used in the location-permissions store. - [JsonPropertyName("locationKey")] - public string LocationKey { get; set; } = string.Empty; + /// Maximum token count for the model's context window. + [JsonPropertyName("tokenLimit")] + public long TokenLimit { get; set; } - /// Whether the location is a git repo or directory. - [JsonPropertyName("locationType")] - public PermissionLocationType LocationType { get; set; } + /// Token count from tool definitions. + [JsonPropertyName("toolDefinitionsTokens")] + public long? ToolDefinitionsTokens { get; set; } } -/// Working directory to load persisted location permissions for. +/// Compaction outcome with the number of tokens and messages removed, summary text, and the resulting context window breakdown. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class PermissionLocationApplyParams +public sealed class HistoryCompactResult { - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Post-compaction context window usage breakdown. + [JsonPropertyName("contextWindow")] + public HistoryCompactContextWindow? ContextWindow { get; set; } - /// Working directory whose persisted location permissions should be applied. - [JsonPropertyName("workingDirectory")] - public string WorkingDirectory { get; set; } = string.Empty; -} + /// Number of messages removed during compaction. + [JsonPropertyName("messagesRemoved")] + public long MessagesRemoved { get; set; } -/// Indicates whether the operation succeeded. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class PermissionsLocationsAddToolApprovalResult -{ - /// Whether the operation succeeded. + /// Whether compaction completed successfully. [JsonPropertyName("success")] public bool Success { get; set; } + + /// Summary text produced by compaction. Omitted when compaction did not produce a summary (e.g. failure path). + [JsonPropertyName("summaryContent")] + public string? SummaryContent { get; set; } + + /// Number of tokens freed by compaction. + [JsonPropertyName("tokensRemoved")] + public long TokensRemoved { get; set; } } -/// Tool approval to persist and apply. -/// Polymorphic base type discriminated by kind. +/// RPC data type for SessionHistoryCompact operations. [Experimental(global::GitHub.Copilot.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(PermissionsLocationsAddToolApprovalDetailsWorkflow), "workflow")] -[JsonDerivedType(typeof(PermissionsLocationsAddToolApprovalDetailsExtensionPermissionAccess), "extension-permission-access")] -[JsonDerivedType(typeof(PermissionsLocationsAddToolApprovalDetailsExtensionEnvAccess), "extension-env-access")] -public partial class PermissionsLocationsAddToolApprovalDetails +public sealed class SessionHistoryCompactRequest { - /// The type discriminator. - [JsonPropertyName("kind")] - public virtual string Kind { get; set; } = string.Empty; -} + /// Optional user-provided instructions to focus the compaction summary. + [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Safe for generated string properties: JSON Schema minLength/maxLength map to string length validation, not reflection over trimmed Count members")] + [MaxLength(4000)] + [JsonPropertyName("customInstructions")] + public string? CustomInstructions { get; set; } + /// 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; } -/// Location-persisted tool approval details for specific command identifiers. -/// The commands variant of . + /// 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; } +} + +/// RPC data type for SessionHistoryCompactRequestWithSession operations. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class PermissionsLocationsAddToolApprovalDetailsCommands : PermissionsLocationsAddToolApprovalDetails +internal sealed class SessionHistoryCompactRequestWithSession { - /// - [JsonIgnore] - public override string Kind => "commands"; + /// 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; } - /// Command identifiers covered by this approval. - [JsonPropertyName("commandIdentifiers")] - public required IList CommandIdentifiers { get; set; } + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; + + /// Context window token limit this compaction is targeting, recorded as the `tokenLimit` on the persisted `session.compaction_start` / `session.compaction_complete` events. Set it when the compaction targets a window other than the compacting model's own, e.g. switching to a model with a smaller context window: the compaction still runs on the current model, so the limit that motivated it would otherwise be lost. When absent, the events record the compacting model's own resolved limit. Attribution metadata only - it does not change how much the compaction removes. + [JsonPropertyName("tokenLimit")] + public long? TokenLimit { get; set; } + + /// What initiated this compaction request, recorded as the `trigger` on the persisted `session.compaction_start` / `session.compaction_complete` events. When absent, the compaction is persisted without trigger attribution (initiator unknown). + [JsonPropertyName("trigger")] + public SessionHistoryCompactRequestTrigger? Trigger { get; set; } } -/// Location-persisted tool approval details for read-only filesystem operations. -/// The read variant of . +/// Number of events that were removed by the truncation. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class PermissionsLocationsAddToolApprovalDetailsRead : PermissionsLocationsAddToolApprovalDetails +public sealed class HistoryTruncateResult { - /// - [JsonIgnore] - public override string Kind => "read"; + /// Failure detail when checkpointCleanupFailed is true. + [JsonPropertyName("checkpointCleanupError")] + public string? CheckpointCleanupError { 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; } + + /// Number of events that were removed. + [JsonPropertyName("eventsRemoved")] + public long EventsRemoved { get; set; } } -/// Location-persisted tool approval details for filesystem write operations. -/// The write variant of . +/// Identifier of the event to truncate to; this event and all later events are removed. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class PermissionsLocationsAddToolApprovalDetailsWrite : PermissionsLocationsAddToolApprovalDetails +internal sealed class HistoryTruncateRequest { - /// - [JsonIgnore] - public override string Kind => "write"; + /// 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; } -/// Location-persisted tool approval details for an MCP server tool, or all tools when `toolName` is null. -/// The mcp variant of . +/// A root user turn that the session can rewind to. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class PermissionsLocationsAddToolApprovalDetailsMcp : PermissionsLocationsAddToolApprovalDetails +public sealed class HistoryRewindPoint { - /// - [JsonIgnore] - public override string Kind => "mcp"; + /// Whether at least one file in this turn or a later turn can be restored. + [JsonPropertyName("canRestoreFiles")] + public bool CanRestoreFiles { get; set; } - /// MCP server name. - [JsonPropertyName("serverName")] - public required string ServerName { get; set; } + /// ID of the user.message event that begins the discarded suffix. + [JsonPropertyName("eventId")] + public string EventId { get; set; } = string.Empty; - /// MCP tool name, or null to cover every tool on the server. - [JsonPropertyName("toolName")] - public string? ToolName { get; set; } + /// Number of unique files in this turn and all later turns that have captured changes. + [JsonPropertyName("fileCount")] + public long FileCount { get; set; } + + /// Whether this turn was an automatically injected autopilot continuation. + [JsonPropertyName("isAutopilotContinuation")] + public bool IsAutopilotContinuation { get; set; } + + /// 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; } -/// Location-persisted tool approval details for MCP sampling requests from a server. -/// The mcp-sampling variant of . +/// Rewind points and file-change-tracking availability for the session. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class PermissionsLocationsAddToolApprovalDetailsMcpSampling : PermissionsLocationsAddToolApprovalDetails +public sealed class HistoryListRewindPointsResult { - /// - [JsonIgnore] - public override string Kind => "mcp-sampling"; + /// Whether this session captured file changes from its first turn. + [JsonPropertyName("fileChangeTrackingEnabled")] + public bool FileChangeTrackingEnabled { get; set; } - /// MCP server name. - [JsonPropertyName("serverName")] - public required string ServerName { 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; } } -/// Location-persisted tool approval details for writes to long-term memory. -/// The memory variant of . +/// Identifies the target session. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class PermissionsLocationsAddToolApprovalDetailsMemory : PermissionsLocationsAddToolApprovalDetails +internal sealed class SessionHistoryListRewindPointsRequest { - /// - [JsonIgnore] - public override string Kind => "memory"; + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } -/// Location-persisted tool approval details for a custom tool, keyed by tool name. -/// The custom-tool variant of . +/// A file that a conversation-and-files rewind would restore. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class PermissionsLocationsAddToolApprovalDetailsCustomTool : PermissionsLocationsAddToolApprovalDetails +public sealed class HistoryRewindFilePreview { - /// - [JsonIgnore] - public override string Kind => "custom-tool"; + /// Aggregate change made across the discarded turns. + [JsonPropertyName("changeType")] + public HistoryRewindChangeType ChangeType { get; set; } - /// Custom tool name. - [JsonPropertyName("toolName")] - public required string ToolName { get; set; } + /// Lines added across the discarded turns. + [JsonPropertyName("linesAdded")] + public long LinesAdded { get; set; } + + /// Lines removed across the discarded turns. + [JsonPropertyName("linesRemoved")] + public long LinesRemoved { get; set; } + + /// Absolute path of the captured file. + [JsonPropertyName("path")] + public string Path { get; set; } = string.Empty; } -/// Location-persisted tool approval details for extension-management operations, optionally narrowed by operation. -/// The extension-management variant of . +/// Files and aggregate changes for a prospective rewind. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class PermissionsLocationsAddToolApprovalDetailsExtensionManagement : PermissionsLocationsAddToolApprovalDetails +public sealed class HistoryPreviewRewindResult { - /// - [JsonIgnore] - public override string Kind => "extension-management"; + /// 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; } - /// Optional operation identifier; when omitted, the approval covers all extension management operations. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("operation")] - public string? Operation { 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; } } -/// Location-persisted workflow approval, optionally narrowed by approval key. -/// The workflow variant of . +/// Event boundary to preview for conversation-and-files rewind. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class PermissionsLocationsAddToolApprovalDetailsWorkflow : PermissionsLocationsAddToolApprovalDetails +internal sealed class HistoryPreviewRewindRequest { - /// - [JsonIgnore] - public override string Kind => "workflow"; + /// ID of the user.message event that begins the discarded suffix. + [JsonPropertyName("eventId")] + public string EventId { get; set; } = string.Empty; - /// Optional workflow operation name or canonical approval key; when omitted, the approval covers all workflow 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-persisted tool approval details for an extension's permission-gated capability access, keyed by extension name. -/// The extension-permission-access variant of . +/// A captured file that rewind intentionally left unchanged. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class PermissionsLocationsAddToolApprovalDetailsExtensionPermissionAccess : PermissionsLocationsAddToolApprovalDetails +public sealed class HistorySkippedFileRestore { - /// - [JsonIgnore] - public override string Kind => "extension-permission-access"; + /// Absolute path of the skipped file. + [JsonPropertyName("path")] + public string Path { get; set; } = string.Empty; - /// Extension name. - [JsonPropertyName("extensionName")] - public required string ExtensionName { get; set; } + /// Reason the file was not restored. + [JsonPropertyName("reason")] + public HistoryFileRestoreSkipReason Reason { 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 . +/// Structured outcome of a rewind request. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class PermissionsLocationsAddToolApprovalDetailsExtensionEnvAccess : PermissionsLocationsAddToolApprovalDetails +public sealed class HistoryRewindResult { - /// - [JsonIgnore] - public override string Kind => "extension-env-access"; + /// 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; } - /// Names of the sensitive environment variables this approval covers. Values are never persisted. - [JsonPropertyName("environmentVariables")] - public required IList EnvironmentVariables { 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; } - /// Extension name. - [JsonPropertyName("extensionName")] - public required string ExtensionName { 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; } } -/// Location-scoped tool approval to persist. +/// Boundary and mode for rewinding session history. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class PermissionLocationAddToolApprovalParams +internal sealed class HistoryRewindRequest { - /// Tool approval to persist and apply. - [JsonPropertyName("approval")] - public PermissionsLocationsAddToolApprovalDetails Approval { get => field ??= new(); set; } + /// ID of the user.message event that begins the discarded suffix. + [JsonPropertyName("eventId")] + public string EventId { get; set; } = string.Empty; - /// Location key (git root or cwd) to persist the approval to. - [JsonPropertyName("locationKey")] - public string LocationKey { get; set; } = string.Empty; + /// Whether to rewind only conversation history or also restore captured files. + [JsonPropertyName("mode")] + public HistoryRewindMode Mode { get; set; } /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Folder trust check result. +/// Indicates whether an in-progress background compaction was cancelled. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class FolderTrustCheckResult +public sealed class HistoryCancelBackgroundCompactionResult { - /// Whether the folder is trusted. - [JsonPropertyName("trusted")] - public bool Trusted { get; set; } + /// Whether an in-progress background compaction was cancelled. False when no compaction was running, when the session is remote, or when the underlying processor was unavailable. + [JsonPropertyName("cancelled")] + public bool Cancelled { get; set; } } -/// Folder path to check for trust. +/// Identifies the target session. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class FolderTrustCheckParams +internal sealed class SessionHistoryCancelBackgroundCompactionRequest { - /// 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. +/// Indicates whether an in-progress manual compaction was aborted. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class PermissionsFolderTrustAddTrustedResult +public sealed class HistoryAbortManualCompactionResult { - /// Whether the operation succeeded. - [JsonPropertyName("success")] - public bool Success { 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; } } -/// Folder path to add to trusted folders. +/// Identifies the target session. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class FolderTrustAddParams +internal sealed class SessionHistoryAbortManualCompactionRequest { - /// 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. +/// Markdown summary of the conversation context (empty when not available). [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class PermissionsUrlsSetUnrestrictedModeResult +public sealed class HistorySummarizeForHandoffResult { - /// Whether the operation succeeded. - [JsonPropertyName("success")] - public bool Success { get; set; } + /// Markdown summary of the conversation context produced by an LLM. Empty string when there are no messages or when the session does not support local summarization. + [JsonPropertyName("summary")] + public string Summary { get; set; } = string.Empty; } -/// Whether the URL-permission policy should run in unrestricted mode. +/// Identifies the target session. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class PermissionUrlsSetUnrestrictedModeParams +internal sealed class SessionHistorySummarizeForHandoffRequest { - /// 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. +/// What a successful clear removed. A clear that could not be applied rejects instead of reporting a count. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class MetadataSnapshotRemoteMetadataRepository +public sealed class HistoryClearContextResult { - /// The branch the remote session is operating on. - [JsonPropertyName("branch")] - public string Branch { get; set; } = string.Empty; + /// 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; } +} - /// The GitHub repository name (without owner). - [JsonPropertyName("name")] - public string Name { get; set; } = string.Empty; +/// Parameters for clearing the conversation and seeding the window that replaces it. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class HistoryClearContextRequest +{ + /// First user message of the fresh context window. Required: a cleared window holding only system and developer messages is not a conversation a model can answer, so every clear seeds the window it creates. Delivered by the enclosing turn driver once the agentic loop exits, which is why the call must be made from inside a tool handler. + [JsonPropertyName("prompt")] + public string Prompt { get; set; } = string.Empty; - /// The GitHub owner (user or organization) of the target repository. - [JsonPropertyName("owner")] - public string Owner { get; set; } = string.Empty; + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } -/// Remote-session-specific metadata. Populated only when `isRemote` is true. Fields are immutable for the lifetime of the session. +/// User-facing pending queue entry, with kind and display text for a queued message, slash command, or model change. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class MetadataSnapshotRemoteMetadata +public sealed class QueuePendingItems { - /// The pull request number the remote session is associated with, if any. - [JsonPropertyName("pullRequestNumber")] - public long? PullRequestNumber { get; set; } + /// Agent mode stored on this queued entry, as stamped when it was enqueued. Items without an explicit mode report interactive. This is not necessarily the mode that will constrain the turn: a plan or autopilot session applies its own write gate, continuation loop and permission posture to every drained item regardless of the mode stored here. + [JsonPropertyName("agentMode")] + public SendAgentMode AgentMode { get; set; } - /// The repository the remote session targets. - [JsonPropertyName("repository")] - public MetadataSnapshotRemoteMetadataRepository Repository { get => field ??= new(); set; } + /// Human-readable text to display for this queue entry in the UI. + [JsonPropertyName("displayText")] + public string DisplayText { get; set; } = string.Empty; - /// 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; } + /// Stable opaque id for the canonical queued item. Batch rows share one id. + [JsonPropertyName("id")] + public string Id { get; set; } = string.Empty; - /// Whether the remote task originated from Copilot Coding Agent (cca) or a CLI `--remote` invocation. - [JsonPropertyName("taskType")] - public MetadataSnapshotRemoteMetadataTaskType? TaskType { get; set; } + /// 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; } + + /// Optional source tag associated with this pending queue entry. This is an open string, not authenticated authorship. In particular, `user` does not prove that a person typed the message. If the source is absent or unrecognized, consumers must not infer human or agent authorship and should handle the entry neutrally. Consumers should tolerate future source values. + [JsonPropertyName("source")] + public string? Source { get; set; } } -/// Public-facing projection of workspace metadata for SDK / TUI consumers. -public sealed class SessionMetadataSnapshotWorkspace +/// Snapshot of the session's pending queued items and immediate-steering messages. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public sealed class QueuePendingItemsResult { - /// Branch checked out at session start, if any. - [JsonPropertyName("branch")] - public string? Branch { get; set; } + /// 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; } - /// ISO 8601 timestamp when the workspace was created. - [JsonPropertyName("created_at")] - public DateTimeOffset? CreatedAt { 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; } - /// Current working directory at session start. - [JsonPropertyName("cwd")] - public string? Cwd { get; set; } + /// Display text for messages currently in the immediate steering queue (interjections sent during a running turn). + [JsonPropertyName("steeringMessages")] + public IList SteeringMessages { get => field ??= []; set; } - /// Resolved git root for cwd, if any. - [JsonPropertyName("git_root")] - public string? GitRoot { get; set; } + /// ID of the running turn's user message while the model has not answered it, so `withdrawMessage` can still take it back once nothing sent after it is pending. A message leaves `items` when its turn starts, before its `user.message` is recorded; this tells that message apart from one that was removed. Absent when no turn prompt can be taken back. + [JsonPropertyName("withdrawableTurnMessageId")] + public string? WithdrawableTurnMessageId { get; set; } +} - /// Repository host type, if known. - [JsonPropertyName("host_type")] - public WorkspaceSummaryHostType? HostType { get; set; } +/// Identifies the target session. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class SessionQueuePendingItemsRequest +{ + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} - /// 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; +/// Internal snapshot of native queue state for local session orchestration. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class QueueSnapshotResult +{ + /// Queue item identifier of a model switch that has been dequeued but not yet applied. + [JsonPropertyName("inFlightModelChangeId")] + public string? InFlightModelChangeId { get; set; } - /// Display name for the session, if set. - [JsonPropertyName("name")] - public string? Name { get; set; } + /// Insertion orders for queued items, aligned with `items`. + [JsonPropertyName("itemOrders")] + public IList? ItemOrders { get; set; } - /// Repository identifier in 'owner/repo' or 'org/project/repo' format, if any. - [JsonPropertyName("repository")] - public string? Repository { get; set; } + /// User-facing pending items in FIFO order. + [JsonPropertyName("items")] + public IList Items { get => field ??= []; set; } - /// ISO 8601 timestamp when the workspace was last updated. - [JsonPropertyName("updated_at")] - public DateTimeOffset? UpdatedAt { get; set; } + /// Insertion orders for immediate steering messages, aligned with `steeringMessages`. + [JsonPropertyName("steeringMessageOrders")] + public IList? SteeringMessageOrders { get; set; } - /// Whether the display name was explicitly set by the user. - [JsonPropertyName("user_named")] - public bool? UserNamed { get; set; } + /// Immediate steering messages waiting for an active turn. + [JsonPropertyName("steeringMessages")] + public IList SteeringMessages { get => field ??= []; set; } } -/// Point-in-time snapshot of slow-changing session identifier and state fields. +/// Identifies the target session. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class SessionMetadataSnapshot +internal sealed class SessionQueueSnapshotRequest { - /// 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; } + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} - /// Runtime client name associated with the session (telemetry identifier). - [JsonPropertyName("clientName")] - public string? ClientName { get; set; } +/// Result of moving a queued item. +[Experimental(global::GitHub.Copilot.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; } +} - /// The current agent mode for this session (e.g., 'interactive', 'plan', 'autopilot'). - [JsonPropertyName("currentMode")] - public MetadataSnapshotCurrentMode CurrentMode { get; set; } +/// Parameters for moving a queued item by stable id. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class QueueMoveItemRequest +{ + /// Stable opaque queued-item id. + [JsonPropertyName("id")] + public string Id { get; set; } = string.Empty; - /// Live indexed-search state for this session activation. Omitted by runtimes that do not expose indexed-search status; absence does not indicate enablement. - [JsonPropertyName("indexedSearch")] - public IndexedSearchState? IndexedSearch { get; set; } + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; - /// User-provided name supplied at session construction (via `--name`), if any. Immutable after construction. - [JsonPropertyName("initialName")] - public string? InitialName { get; set; } + /// Zero-based target position in the public visible queue. Values outside the queue clamp to an end. + [JsonPropertyName("toPosition")] + public long ToPosition { 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; } +/// Result of inserting a queued message. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public sealed class QueueInsertAtResult +{ + /// Fresh stable opaque id assigned to the inserted item. + [JsonPropertyName("id")] + public string Id { get; set; } = string.Empty; +} - /// 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; } +/// Serializable message fields accepted by queue.insertAt. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public sealed class QueueInsertMessage +{ + /// Optional explicit agent mode. When omitted, the session's current mode is assigned. + [JsonPropertyName("agentMode")] + public SendAgentMode? AgentMode { get; set; } - /// 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; } + /// Optional attachments for the message. + [JsonPropertyName("attachments")] + public IList? Attachments { get; set; } - /// Currently selected model identifier, if any. - [JsonPropertyName("selectedModel")] - public string? SelectedModel { get; set; } + /// Whether the message is billable. + [JsonPropertyName("billable")] + public bool? Billable { get; set; } - /// The unique identifier of the session. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Accepted for internal SendOptions compatibility but ignored; delivery is derived from current session activity. + [JsonPropertyName("delivery")] + public string? Delivery { get; set; } - /// Current session limits, or null when no limits are active. - [JsonPropertyName("sessionLimits")] - public SessionLimitsConfig? SessionLimits { 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; } - /// ISO 8601 timestamp of when the session started. - [JsonPropertyName("startTime")] - public DateTimeOffset StartTime { get; set; } + /// Accepted for SendOptions compatibility but ignored; the requested public position controls placement. + [JsonPropertyName("prepend")] + public bool? Prepend { 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; } + /// The user message text. + [JsonPropertyName("prompt")] + public string Prompt { get; set; } = string.Empty; - /// Absolute path to the session's current working directory. - [JsonPropertyName("workingDirectory")] - public string WorkingDirectory { get; set; } = string.Empty; + /// Per-turn request headers. + [JsonPropertyName("requestHeaders")] + public IDictionary? RequestHeaders { 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; } + /// Required tool name for the turn, when any. + [JsonPropertyName("requiredTool")] + public string? RequiredTool { 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; } + /// 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; } } -/// Identifies the target session. +/// Parameters for inserting a queued message at a public visible position. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionMetadataSnapshotRequest +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; } -/// Identifies the target session. +/// Result of removing a queued item. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionMetadataGetClientMetadataRequest +public sealed class QueueRemoveAtResult { - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// True when the addressed item was removed. + [JsonPropertyName("removed")] + public bool Removed { get; set; } } -/// Atomic patch for client-owned session metadata. Operations apply in clear, remove, then set order. The resulting bag must satisfy the ClientMetadata entry and serialized-size limits. Local storage coordinates concurrent runtime processes; custom SessionFs providers must serialize writers that access the same session from multiple processes. +/// Parameters for removing a queued item by stable id. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class MetadataUpdateClientMetadataRequest +internal sealed class QueueRemoveAtRequest { - /// Remove every existing client metadata entry before applying remove and set. Defaults to false. - [JsonPropertyName("clear")] - public bool? Clear { get; set; } - - /// Case-sensitive keys to remove. Missing keys are ignored. Each key must be non-empty, at most 256 UTF-8 bytes, and outside the reserved `copilot/` and `github/` namespaces. - [JsonPropertyName("remove")] - public IList? Remove { get; set; } + /// 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; - - /// String entries to add or replace. Set wins when a key also appears in remove. Each key must be non-empty, at most 256 UTF-8 bytes, and outside the reserved `copilot/` and `github/` namespaces. Each value may contain at most 16 KiB of UTF-8 data. - [JsonPropertyName("set")] - public IDictionary? Set { get; set; } } -/// Indicates whether the local session is currently processing a turn or background continuation. +/// Result of editing a queued message. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class MetadataIsProcessingResult +public sealed class QueueUpdateTextResult { - /// 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; } + /// True when the stored text changed. + [JsonPropertyName("updated")] + public bool Updated { get; set; } } -/// Identifies the target session. +/// Parameters for editing a single queued message. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionMetadataIsProcessingRequest +internal sealed class QueueUpdateTextRequest { + /// Optional replacement prompt displayed to the user. + [JsonPropertyName("displayPrompt")] + public string? DisplayPrompt { get; set; } + + /// Stable opaque ID of the queued item to edit. + [JsonPropertyName("id")] + public string Id { get; set; } = string.Empty; + + /// 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; } -/// Current activity flags for the session. +/// Result of withdrawing a user message. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class SessionActivity +public sealed class QueueWithdrawMessageResult { - /// Whether an in-flight operation can currently be aborted. - [JsonPropertyName("abortable")] - public bool Abortable { get; set; } + /// True when the running turn was interrupted to withdraw the message. With removed false, the turn was interrupted but its events could not be removed, for example because the model answered first, so the message stays in the interrupted turn. + [JsonPropertyName("interrupted")] + public bool Interrupted { get; set; } - /// Whether the session currently has active work, including running turns or tasks. - [JsonPropertyName("hasActiveWork")] - public bool HasActiveWork { get; set; } + /// True when the message left the queue or, for a running turn, history. + [JsonPropertyName("removed")] + public bool Removed { get; set; } } -/// Identifies the target session. +/// Conditional withdrawal of a single user message, from its queue or from the running turn it started. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionMetadataActivityRequest +internal sealed class QueueWithdrawMessageRequest { + /// The prompt originally sent. A message edited since submission is not withdrawn, so an obsolete draft cannot replace the edit. + [JsonPropertyName("expectedPrompt")] + public string ExpectedPrompt { get; set; } = string.Empty; + + /// Message identity returned by send, not the queue item id. Batch messages are not eligible. + [JsonPropertyName("messageId")] + public string MessageId { get; set; } = string.Empty; + /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Token-usage breakdown for the session's current context window. -public sealed class MetadataContextInfoResultContextInfo +/// Append to one pending steering message without changing its identity or delivery position. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class QueueAppendSteeringRequest { - /// Output reservation overlapping the displayed prompt allowance plus tokens after the effective input budget's buffer-exhaustion blocking threshold (default 95%). - [JsonPropertyName("bufferTokens")] - public long BufferTokens { get; set; } - - /// Token count at which background compaction starts (configurable percentage of promptTokenLimit). - [JsonPropertyName("compactionThreshold")] - public long CompactionThreshold { get; set; } - - /// Tokens consumed by user/assistant/tool messages. - [JsonPropertyName("conversationTokens")] - public long ConversationTokens { get; set; } - - /// Provider-owned model display label, or the Auto routing label. Presentation only; modelName remains the tokenization identity. - [JsonPropertyName("displayModelName")] - public string? DisplayModelName { get; set; } - - /// Advertised prompt allowance for the selected context tier, without adding output tokens. The denominator for context-usage displays. - [JsonPropertyName("limit")] - public long Limit { get; set; } - - /// Tokens consumed by MCP tool definitions (subset of toolDefinitionsTokens, excludes deferred tools). - [JsonPropertyName("mcpToolsTokens")] - public long McpToolsTokens { get; set; } + /// Mode captured at submission. Only steering messages in the same mode may be combined. + [JsonPropertyName("agentMode")] + public SendAgentMode AgentMode { get; set; } - /// The model used for token counting. - [JsonPropertyName("modelName")] - public string ModelName { get; set; } = string.Empty; + /// Attachments to add after the message's existing attachments. An empty list preserves the existing attachments. + [JsonPropertyName("attachments")] + public IList Attachments { get => field ??= []; set; } - /// Effective input budget: the selected tier's prompt allowance bounded by the combined context ceiling minus the requested output allowance. Uses DEFAULT_TOKEN_LIMIT when limits are unspecified. - [JsonPropertyName("promptTokenLimit")] - public long PromptTokenLimit { get; set; } + /// Display text to append to the existing preview after a blank line. + [JsonPropertyName("displayPrompt")] + public string DisplayPrompt { get; set; } = string.Empty; - /// Provider whose catalog supplies this context window. Omitted when provider identity is unavailable. - [JsonPropertyName("provider")] - public ModelProviderRef? Provider { get; set; } + /// Expected current prompt, including any previous appends. The runtime applies plan-mode normalization before comparing and refuses a changed message. + [JsonPropertyName("expectedPrompt")] + public string ExpectedPrompt { get; set; } = string.Empty; - /// Tokens consumed by the system prompt. - [JsonPropertyName("systemTokens")] - public long SystemTokens { get; set; } + /// Message identity returned by send, not the queue item id. Only unclaimed user steering messages are eligible. + [JsonPropertyName("messageId")] + public string MessageId { get; set; } = string.Empty; - /// Tokens consumed by tool definitions sent to the model (excludes deferred tools). - [JsonPropertyName("toolDefinitionsTokens")] - public long ToolDefinitionsTokens { get; set; } + /// Text to append after a blank line. + [JsonPropertyName("prompt")] + public string Prompt { get; set; } = string.Empty; - /// Sum of system, conversation and tool-definition tokens. - [JsonPropertyName("totalTokens")] - public long TotalTokens { get; set; } + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } -/// Token breakdown for the session's current context window, or null if uninitialized. +/// Result of duplicating a queued item. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class MetadataContextInfoResult +public sealed class QueueDuplicateAtResult { - /// 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; } + /// Fresh stable opaque id assigned to the duplicate. + [JsonPropertyName("id")] + public string Id { get; set; } = string.Empty; } -/// Model identifier and token limits used to compute the context-info breakdown. +/// Parameters for duplicating a queued item. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class MetadataContextInfoRequest +internal sealed class QueueDuplicateAtRequest { - /// Requested output allowance to reserve against the combined context ceiling. Pass 0 to resolve the session's request cap, falling back to the model's advertised output limit. - [JsonPropertyName("outputTokenLimit")] - public long OutputTokenLimit { get; set; } - - /// Advertised prompt allowance. Pass 0 to resolve the selected model and context tier from the session. - [JsonPropertyName("promptTokenLimit")] - public long PromptTokenLimit { get; set; } - - /// Provider owning selectedModel. Omit to use the current provider for the current model, or deterministic legacy lookup for a different model. - [JsonPropertyName("providerId")] - public string? ProviderId { 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; } + /// 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; } -/// 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 +/// 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(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class QueueSetDrainPausedRequest { - /// Overlapping output reservation plus post-blocking-threshold buffer. - [JsonPropertyName("buffer")] - public long Buffer { get; set; } - - /// Custom-instructions tokens (0 when none are configured). - [JsonPropertyName("customInstructions")] - public long CustomInstructions { get; set; } - - /// Remaining unused window capacity (clamped at 0). - [JsonPropertyName("freeSpace")] - public long FreeSpace { get; set; } - - /// MCP tool-definition tokens. - [JsonPropertyName("mcpTools")] - public long McpTools { get; set; } - - /// Conversation (user/assistant/tool) message tokens. - [JsonPropertyName("messages")] - public long Messages { get; set; } - - /// System prompt tokens, excluding custom instructions. - [JsonPropertyName("systemPrompt")] - public long SystemPrompt { get; set; } + /// Whether queued-lane draining should be paused. + [JsonPropertyName("paused")] + public bool Paused { get; set; } - /// Non-MCP tool-definition tokens. - [JsonPropertyName("systemTools")] - public long SystemTools { get; set; } + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } -/// Successful compaction history for the session. -public sealed class MetadataContextAttributionResultContextAttributionCompactions +/// Result of trying to steer a queued message into a live turn. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public sealed class QueueSendNowResult { - /// Number of successful compactions in this session. - [JsonPropertyName("count")] - public long Count { get; set; } + /// True when the item was accepted into the steering lane; false when no main turn was live. + [JsonPropertyName("steered")] + public bool Steered { get; set; } } -/// RPC data type for MetadataContextAttributionResultContextAttributionEntry operations. -public sealed class MetadataContextAttributionResultContextAttributionEntry +/// Parameters for steering a queued message into a live turn. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class QueueSendNowRequest { - /// 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; } - - /// 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`. + /// Stable opaque ID of the queued item to steer into the live turn. [JsonPropertyName("id")] public string Id { get; set; } = string.Empty; - /// Source category for this entry. Not a closed set — tolerate unknown values. Known values today: `skill`, `subagent`, `mcpServer`, `tool`, `system`, `toolDefinition`, `plugin`. - [JsonPropertyName("kind")] - public string Kind { get; set; } = string.Empty; - - /// 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; + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} - /// Optional `id` of the parent entry: e.g. a `plugin` entry parenting its `skill`/`mcpServer` entries, or the `system` entry parenting `toolDefinition` entries. Omitted for top-level entries. - [JsonPropertyName("parentId")] - public string? ParentId { get; set; } +/// Whether the native queue has pending work. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class QueueHasPendingResult +{ + /// True when queued or immediate native work is pending. + [JsonPropertyName("hasPending")] + public bool HasPending { get; set; } +} - /// Token count currently in context attributable to this entry. - [JsonPropertyName("tokens")] - public long Tokens { get; set; } +/// Identifies the target session. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class SessionQueueHasPendingRequest +{ + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } -/// Per-source token attribution snapshot for the current context window. The heaviest individual messages are available separately via `metadata.getContextHeaviestMessages`. -public sealed class MetadataContextAttributionResultContextAttribution +/// Whether a deferred-idle drain should run. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class QueueBeginDeferredIdleDrainResult { - /// Output reservation overlapping the displayed prompt allowance plus the tokens past the effective input budget's buffer-exhaustion blocking threshold. Mirrors `SessionContextInfo.bufferTokens`. - [JsonPropertyName("bufferTokens")] - public long BufferTokens { get; set; } + /// True when the host should run finishDeferredIdleDrain asynchronously. + [JsonPropertyName("shouldDrain")] + public bool ShouldDrain { 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; } +/// Inputs for starting a deferred-idle drain. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class QueueBeginDeferredIdleDrainRequest +{ + /// Whether the host still has active background work. + [JsonPropertyName("activeBackgroundWork")] + public bool ActiveBackgroundWork { get; set; } - /// Successful compaction history for the session. - [JsonPropertyName("compactions")] - public MetadataContextAttributionResultContextAttributionCompactions Compactions { get => field ??= new(); set; } + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} - /// Token count at which background compaction starts. Mirrors `SessionContextInfo.compactionThreshold`. - [JsonPropertyName("compactionThreshold")] - public long CompactionThreshold { get; set; } +/// Action selected by the native deferred-idle drain. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class QueueFinishDeferredIdleDrainResult +{ + /// Whether the deferred idle was caused by an aborted foreground turn. + [JsonPropertyName("aborted")] + public bool Aborted { 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; } + /// One of none, processQueue, or emitSessionIdle. + [JsonPropertyName("action")] + public string Action { get; set; } = string.Empty; +} - /// Advertised prompt allowance for the selected context tier: the denominator for context-usage displays and capacity for `categories.freeSpace` and `categories.buffer`. Mirrors `SessionContextInfo.limit`. - [JsonPropertyName("limit")] - public long Limit { get; set; } +/// Inputs for completing a deferred-idle drain. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class QueueFinishDeferredIdleDrainRequest +{ + /// Whether the host still has active background work. + [JsonPropertyName("activeBackgroundWork")] + public bool ActiveBackgroundWork { 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; + /// Whether native queued work remains. + [JsonPropertyName("hasPending")] + public bool HasPending { 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; + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} - /// Effective input budget after reserving requested output against the combined context ceiling. Mirrors `SessionContextInfo.promptTokenLimit`. - [JsonPropertyName("promptTokenLimit")] - public long PromptTokenLimit { get; set; } +/// Inputs for marking session.idle deferred in native state. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class QueueDeferSessionIdleRequest +{ + /// Whether the deferred idle was caused by an aborted foreground turn. + [JsonPropertyName("aborted")] + public bool Aborted { 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; } + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } -/// Per-source attribution breakdown for the session's current context window, or null if uninitialized. +/// Indicates whether a user-facing pending item was removed. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class MetadataContextAttributionResult +public sealed class QueueRemoveMostRecentResult { - /// 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; } + /// True if a user-facing pending item was removed (LIFO across both queues); false when no removable items remained. + [JsonPropertyName("removed")] + public bool Removed { get; set; } } /// Identifies the target session. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionMetadataGetContextAttributionRequest +internal sealed class SessionQueueRemoveMostRecentRequest { /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// A single large message currently in context. +/// Identifies the target session. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class ContextHeaviestMessage +internal sealed class SessionQueueClearRequest { - /// Stable identifier for this message within the snapshot. - [JsonPropertyName("id")] - public string Id { get; set; } = string.Empty; + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} - /// Human-readable source label, e.g. `tool: bash` or `skill: tmux`. Presentation-only. - [JsonPropertyName("label")] - public string Label { get; set; } = string.Empty; +/// Internal filter for consuming queued system notifications. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class QueueConsumeSystemNotificationsRequest +{ + /// Opaque runtime-owned filter object. + [JsonPropertyName("filter")] + public JsonElement Filter { get; set; } - /// Role of the chat message (`user`, `assistant`, or `tool`). - [JsonPropertyName("role")] - public string Role { get; set; } = string.Empty; + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} - /// Token count currently in context for this individual message. - [JsonPropertyName("tokens")] - public long Tokens { get; set; } +/// Result of enqueueing the resume-pending wake item. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class QueueEnqueueResumePendingResult +{ + /// True when a wake item was newly queued. + [JsonPropertyName("queued")] + public bool Queued { get; set; } } -/// The heaviest individual messages in the session's context window, most-expensive first. +/// Identifies the target session. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class MetadataContextHeaviestMessagesResult +internal sealed class SessionQueueEnqueueResumePendingRequest { - /// Heaviest messages, most-expensive first. - [JsonPropertyName("messages")] - public IList Messages { get => field ??= []; set; } + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} - /// 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. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +internal sealed class SessionQueueProcessRequest +{ + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } -/// Parameters for the heaviest-messages query. +/// Cursor, batch size, and optional long-poll/filter parameters for reading session events. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class MetadataContextHeaviestMessagesRequest +internal sealed class EventLogReadRequest { - /// Maximum number of messages to return, most-expensive first. Omit for the server default. - [JsonPropertyName("limit")] - public long? Limit { get; set; } + /// Optional non-empty list of subagent identifiers. When provided, only events owned by one of these agents are returned; ownership recognizes the event envelope's agentId plus legacy data.agentId and data.parentToolCallId markers. This filter takes precedence over agentScope. + [JsonPropertyName("agentIds")] + public IList? AgentIds { get; set; } + + /// Agent-scope filter: 'primary' returns only main-agent events plus events whose type starts with 'subagent.' (matching the typed-subscription default behavior); 'all' returns events from all agents (matching wildcard-subscription behavior). Default is 'all' to preserve wildcard semantics for catch-up callers. + [JsonPropertyName("agentScope")] + public EventsAgentScope? AgentScope { get; set; } + + /// Opaque cursor returned by a previous read. Omit on the first call to start from the beginning of the session's persisted history. + [JsonPropertyName("cursor")] + public string? Cursor { get; set; } + + /// Direction to page through the session's persisted event history. 'forward' (default) pages from the cursor toward newer events (or from the start of history when no cursor is given). 'backward' enables tail-first reads: with no cursor it returns the NEWEST `max` events, and the returned cursor pages toward OLDER events on subsequent backward reads. Events within a returned batch are always in chronological (oldest-to-newest) order, even for a backward read. Backward reads cover PERSISTED history only; ephemeral events are never returned by a backward read. `direction` selects the INITIAL read only: the returned cursor is self-describing, so a continuation read pages in the cursor's own direction regardless of the `direction` passed alongside it — a forward cursor always pages forward and a backward cursor always pages backward. Pass the direction that matches the cursor to avoid confusion. + [JsonPropertyName("direction")] + public EventsReadDirection? Direction { get; set; } + + /// When false, skip ephemeral events entirely and return only durable (persisted) events. History-backfill callers that discard ephemerals anyway should set this so the read is bounded by the durable log length instead of racing the ephemeral ring on a busy session. Defaults to true (ephemerals are interleaved with durable events in creation order). Ignored by backward reads, which always cover persisted history only. + [JsonPropertyName("includeEphemeral")] + public bool? IncludeEphemeral { get; set; } + + /// Maximum number of events to return in this batch (1–1000, default 200). + [JsonPropertyName("max")] + public long? Max { get; set; } /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; + + /// Either '*' to receive all event types, or a non-empty list of event types to receive. + [JsonPropertyName("types")] + public JsonElement? Types { get; set; } + + /// Milliseconds to wait for new events when the cursor is at the tail of history. 0 (default) returns immediately even if no events are available. Capped at 30000ms. Ephemeral events that arrive during the wait are delivered in this batch but are NOT replayable on a subsequent read (use a non-zero waitMs in your next call to capture future ephemerals as they happen). This applies to forward reads only: a backward read always returns immediately and ignores `waitMs`, because backward paging covers persisted history only while new events append at the tail (the opposite end from a backward page), so no blocking or ephemeral delivery can occur. + [JsonConverter(typeof(MillisecondsTimeSpanConverter))] + [JsonPropertyName("waitMs")] + public TimeSpan? Wait { get; set; } } -/// 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. +/// 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(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class MetadataRecordContextChangeResult +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; } -/// Updated working-directory/git context to record on the session. +/// Identifies the target session. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class MetadataRecordContextChangeRequest +internal sealed class SessionEventLogTailRequest { - /// 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; } -/// 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. +/// Opaque handle representing an event-type interest registration. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class MetadataSetWorkingDirectoryResult +public sealed class RegisterEventInterestResult { - /// Working directory after the update. - [JsonPropertyName("workingDirectory")] - public string WorkingDirectory { get; set; } = string.Empty; + /// 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; } -/// 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. +/// Event type to register consumer interest for, used by runtime gating logic. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class MetadataSetWorkingDirectoryRequest +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; - - /// 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; } -/// 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. +/// Indicates whether the operation succeeded. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class MetadataRecomputeContextTokensResult +public sealed class EventLogReleaseInterestResult { - /// Tokens contributed by user/assistant/tool messages (excludes system/developer prompts). - [JsonPropertyName("messagesTokenCount")] - public long MessagesTokenCount { get; set; } - - /// Tokens contributed by system/developer prompt snapshots. - [JsonPropertyName("systemTokenCount")] - public long SystemTokenCount { get; set; } - - /// Sum of tokens across chat-context and system-context messages currently held by the session. - [JsonPropertyName("totalTokens")] - public long TotalTokens { get; set; } + /// Whether the operation succeeded. + [JsonPropertyName("success")] + public bool Success { get; set; } } -/// Model identifier to use when re-tokenizing the session's existing messages. +/// Opaque handle previously returned by `registerInterest` to release. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class MetadataRecomputeContextTokensRequest +internal sealed class ReleaseEventInterestParams { - /// 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; + /// Handle returned by a previous `registerInterest` call. Idempotent: releasing an unknown or already-released handle is a no-op (returns success). When the last outstanding handle for an event type is released, the runtime reverts to its 'no consumer' code path for that event type. + [JsonPropertyName("handle")] + public string Handle { get; set; } = string.Empty; /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Availability of built-in job tools surfaced to boundary consumers. -[Experimental(global::GitHub.Copilot.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; } -} - -/// Redacted job settings for a session. The job nonce is excluded. +/// Identifies the target session. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class SessionSettingsJobSnapshot +internal sealed class SessionUsageGetMetricsRequest { - /// Availability of job-specific built-in tools. - [JsonPropertyName("builtInToolAvailability")] - public SessionSettingsBuiltInToolAvailabilitySnapshot? BuiltInToolAvailability { 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; } + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } -/// Redacted model routing settings for a session. +/// Internal absolute code-change totals reported by the owning host. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class SessionSettingsModelSnapshot +internal sealed class SessionUsageSetCodeChangesRequest { - /// Agent service callback URL for job and progress updates. - [JsonPropertyName("callbackUrl")] - public string? CallbackUrl { get; set; } + /// Absolute changed-file count; omission preserves the previous count. + [JsonPropertyName("filesCount")] + public double? FilesCount { get; set; } - /// Default reasoning effort for the selected model. - [JsonPropertyName("defaultReasoningEffort")] - public string? DefaultReasoningEffort { get; set; } + /// Absolute added-line total, replacing the previous reading. + [JsonPropertyName("linesAdded")] + public double LinesAdded { get; set; } - /// Agent job identifier for the session. - [JsonPropertyName("instanceId")] - public string? InstanceId { get; set; } + /// Absolute removed-line total, replacing the previous reading. + [JsonPropertyName("linesRemoved")] + public double LinesRemoved { get; set; } - /// Selected model identifier. - [JsonPropertyName("model")] - public string? Model { get; set; } + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } -/// Online-evaluation settings safe to expose across the SDK boundary. +/// Existing delegation guidance for an account without delegation access. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class SessionSettingsOnlineEvaluationSnapshot +public sealed class SessionQuotaDelegateWarning { - /// Whether online evaluation is disabled. - [JsonPropertyName("disableOnlineEvaluation")] - public bool? DisableOnlineEvaluation { get; set; } + /// User-facing guidance. + [JsonPropertyName("text")] + public string Text { get; set; } = string.Empty; - /// Whether online-evaluation output-file generation is enabled. - [JsonPropertyName("enableOnlineEvaluationOutputFile")] - public bool? EnableOnlineEvaluationOutputFile { get; set; } + /// Link to upgrade the account. + [JsonPropertyName("url")] + public string Url { get; set; } = string.Empty; } -/// Redacted repository and GitHub host settings for a session. +/// Session quota snapshot, preserving the incumbent numeric units and reset metadata. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class SessionSettingsRepoSnapshot +public sealed class SessionQuotaSnapshot { - /// Checked-out repository branch. - [JsonPropertyName("branch")] - public string? Branch { get; set; } + /// Entitled quantity in this quota's units. + [JsonPropertyName("entitlementRequests")] + public double EntitlementRequests { get; set; } - /// Checked-out commit SHA. - [JsonPropertyName("commit")] - public string? Commit { get; set; } + /// Whether the provider reports available quota. + [JsonPropertyName("hasQuota")] + public bool? HasQuota { get; set; } - /// GitHub server host name. - [JsonPropertyName("host")] - public string? Host { get; set; } + /// Whether the entitlement is unlimited. + [JsonPropertyName("isUnlimitedEntitlement")] + public bool IsUnlimitedEntitlement { get; set; } - /// Protocol used to access the GitHub host. - [JsonPropertyName("hostProtocol")] - public string? HostProtocol { get; set; } + /// Additional usage in this quota's units. + [JsonPropertyName("overage")] + public double Overage { get; set; } - /// GitHub repository database ID. - [JsonPropertyName("id")] - public double? Id { get; set; } + /// Whether additional usage is allowed after quota exhaustion. + [JsonPropertyName("overageAllowedWithExhaustedQuota")] + public bool OverageAllowedWithExhaustedQuota { get; set; } - /// Repository name. - [JsonPropertyName("name")] - public string? Name { get; set; } + /// Additional-usage budget cap, when provided. + [JsonPropertyName("overageEntitlement")] + public double? OverageEntitlement { get; set; } - /// GitHub repository owner database ID. - [JsonPropertyName("ownerId")] - public double? OwnerId { get; set; } + /// Percentage of the entitlement remaining. + [JsonPropertyName("remainingPercentage")] + public double RemainingPercentage { get; set; } - /// Repository owner login. - [JsonPropertyName("ownerName")] - public string? OwnerName { get; set; } + /// Quota reset time in milliseconds since the Unix epoch, when known. + [JsonPropertyName("resetDateEpochMs")] + public double? ResetDateEpochMs { get; set; } - /// Number of commits in the pull request. - [JsonPropertyName("prCommitCount")] - public double? PrCommitCount { get; set; } + /// Whether the reset time is estimated. + [JsonPropertyName("resetDateEstimated")] + public bool? ResetDateEstimated { get; set; } - /// Whether the repository is writable. - [JsonPropertyName("readWrite")] - public bool? ReadWrite { get; set; } + /// Whether this quota uses token-based billing. + [JsonPropertyName("tokenBasedBilling")] + public bool? TokenBasedBilling { get; set; } - /// GitHub secret-scanning service URL. - [JsonPropertyName("secretScanningUrl")] - public string? SecretScanningUrl { get; set; } + /// Whether usage is allowed after quota exhaustion. + [JsonPropertyName("usageAllowedWithExhaustedQuota")] + public bool UsageAllowedWithExhaustedQuota { get; set; } - /// GitHub server base URL. - [JsonPropertyName("serverUrl")] - public string? ServerUrl { get; set; } + /// Consumed quantity in this quota's units. + [JsonPropertyName("usedRequests")] + public double UsedRequests { get; set; } } -/// Redacted validation and memory-tool settings for a session. +/// The incumbent session-owned quota and account projection. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class SessionSettingsValidationSnapshot +public sealed class SessionQuotaGetResult { - /// Whether advisory validation is enabled. - [JsonPropertyName("advisoryEnabled")] - public bool? AdvisoryEnabled { get; set; } - - /// Whether CodeQL validation is enabled. - [JsonPropertyName("codeqlEnabled")] - public bool? CodeqlEnabled { get; set; } - - /// 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 account can sign up for Copilot Free. + [JsonPropertyName("canSignupForCopilotFree")] + public bool CanSignupForCopilotFree { get; set; } - /// Whether the memory-vote tool is enabled. - [JsonPropertyName("memoryVoteEnabled")] - public bool? MemoryVoteEnabled { get; set; } + /// Whether the account can delegate tasks to GitHub. + [JsonPropertyName("delegateAvailable")] + public bool DelegateAvailable { get; set; } - /// Whether secret-scanning validation is enabled. - [JsonPropertyName("secretScanningEnabled")] - public bool? SecretScanningEnabled { get; set; } + /// Existing delegation warning for a free account. + [JsonPropertyName("delegateWarning")] + public SessionQuotaDelegateWarning? DelegateWarning { get; set; } - /// General validation timeout budget in seconds. - [JsonPropertyName("timeout")] - public double? Timeout { get; set; } -} + /// Whether dynamic workflows are enabled for the active account. + [JsonPropertyName("dynamicWorkflowsEnabled")] + public bool DynamicWorkflowsEnabled { get; set; } -/// Redacted, serializable view of session runtime settings for SDK boundary consumers. Secrets and raw feature flags are intentionally excluded. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionSettingsSnapshot -{ - /// Name of the SDK client that created the session. - [JsonPropertyName("clientName")] - public string? ClientName { get; set; } + /// Whether dynamic workflows are visible under the session's feature flags. + [JsonPropertyName("dynamicWorkflowsUiVisible")] + public bool DynamicWorkflowsUiVisible { get; set; } - /// Redacted job settings. - [JsonPropertyName("job")] - public SessionSettingsJobSnapshot Job { get => field ??= new(); set; } + /// Whether the account uses the free limited Copilot plan. + [JsonPropertyName("isFreeUser")] + public bool IsFreeUser { get; set; } - /// Redacted model routing settings. - [JsonPropertyName("model")] - public SessionSettingsModelSnapshot Model { get => field ??= new(); set; } + /// Whether the account uses token-based billing. + [JsonPropertyName("isTbbUser")] + public bool IsTbbUser { get; set; } - /// Online-evaluation settings safe for SDK consumers. - [JsonPropertyName("onlineEvaluation")] - public SessionSettingsOnlineEvaluationSnapshot OnlineEvaluation { get => field ??= new(); set; } + /// Whether model cost columns should be shown. + [JsonPropertyName("modelCostColumnVisible")] + public bool ModelCostColumnVisible { get; set; } - /// Redacted repository and host settings. - [JsonPropertyName("repo")] - public SessionSettingsRepoSnapshot Repo { get => field ??= new(); set; } + /// Resolved Copilot plan tier. + [JsonPropertyName("planTier")] + public SessionQuotaPlanTier PlanTier { get; set; } - /// Session start time as Unix epoch milliseconds. - [JsonPropertyName("startTimeMs")] - public double? StartTimeMs { get; set; } + /// Whether premium-request costs are billable. + [JsonPropertyName("premiumRequestsBillable")] + public bool PremiumRequestsBillable { get; set; } - /// Session timeout in milliseconds. - [JsonPropertyName("timeoutMs")] - public double? TimeoutMs { get; set; } + /// Provider-owned account quota state, including snapshots without numeric balances. + [JsonPropertyName("providerQuotas")] + public IList? ProviderQuotas { get; set; } - /// Redacted validation and memory-tool settings. - [JsonPropertyName("validation")] - public SessionSettingsValidationSnapshot Validation { get => field ??= new(); set; } + /// Known quota snapshots, keyed by the provider's quota type. + [JsonPropertyName("snapshots")] + public IDictionary Snapshots { get => field ??= new Dictionary(); 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; } + /// Upgrade link for a free account. + [JsonPropertyName("upgradeUrl")] + public string? UpgradeUrl { get; set; } } /// Identifies the target session. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionSettingsSnapshotRequest -{ - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; -} - -/// Result of evaluating a Rust-owned settings predicate. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionSettingsEvaluatePredicateResult -{ - /// Whether the named settings predicate evaluated to enabled. - [JsonPropertyName("enabled")] - public bool Enabled { get; set; } -} - -/// Named Rust-owned settings predicate to evaluate for this session. -[Experimental(global::GitHub.Copilot.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; } - - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; - - /// Tool name for tool-scoped predicates such as trivial-change handling. - [JsonPropertyName("toolName")] - public string? ToolName { get; set; } -} - -/// Content-exclusion decision for one requested path. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class ContentExclusionPathCheck -{ - /// Whether the session's complete content-exclusion policy excludes the path. - [JsonPropertyName("excluded")] - public bool Excluded { get; set; } - - /// The path supplied by the caller. - [JsonPropertyName("path")] - public string Path { get; set; } = string.Empty; -} - -/// Batch content-exclusion result. Callers must fail closed when policy evaluation is unavailable. -[Experimental(global::GitHub.Copilot.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; } - - /// Per-path decisions in request order. Empty when available is false. - [JsonPropertyName("checks")] - public IList Checks { get => field ??= []; set; } -} - -/// Local file system absolute paths within the session working directory to check against its content-exclusion policy. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class ContentExclusionCheckPathsRequest +internal sealed class SessionQuotaGetRequest { - /// Local file system absolute paths within the session working directory to check. Results are returned in the same order, including duplicates. - [JsonPropertyName("paths")] - public IList Paths { get => field ??= []; set; } - /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Identifier of the spawned shell process, usable with shell.kill while the process is running. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class ShellExecResult -{ - /// Identifier usable with shell.kill while the process is running. - [JsonPropertyName("processId")] - public string ProcessId { get; set; } = string.Empty; -} - -/// Shell command to run, with optional working directory and timeout in milliseconds. Spawn failures return an RPC error. +/// The incumbent session-owned quota and account projection. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class ShellExecRequest +public sealed class SessionQuotaRefreshResult { - /// Shell command to execute. - [JsonPropertyName("command")] - public string Command { get; set; } = string.Empty; + /// Whether the account can sign up for Copilot Free. + [JsonPropertyName("canSignupForCopilotFree")] + public bool CanSignupForCopilotFree { get; set; } - /// Working directory (defaults to session working directory). - [JsonPropertyName("cwd")] - public string? Cwd { get; set; } + /// Whether the account can delegate tasks to GitHub. + [JsonPropertyName("delegateAvailable")] + public bool DelegateAvailable { get; set; } - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Existing delegation warning for a free account. + [JsonPropertyName("delegateWarning")] + public SessionQuotaDelegateWarning? DelegateWarning { get; set; } - /// Timeout in milliseconds (default: 30000). - [JsonConverter(typeof(MillisecondsTimeSpanConverter))] - [JsonPropertyName("timeout")] - public TimeSpan? Timeout { get; set; } -} + /// Whether dynamic workflows are enabled for the active account. + [JsonPropertyName("dynamicWorkflowsEnabled")] + public bool DynamicWorkflowsEnabled { get; set; } -/// Indicates whether the signal was delivered; false if the process was unknown or already exited. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class ShellKillResult -{ - /// Whether the signal was sent successfully. - [JsonPropertyName("killed")] - public bool Killed { get; set; } -} + /// Whether dynamic workflows are visible under the session's feature flags. + [JsonPropertyName("dynamicWorkflowsUiVisible")] + public bool DynamicWorkflowsUiVisible { get; set; } -/// Identifier of a process previously returned by "shell.exec" and the signal to send. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class ShellKillRequest -{ - /// Process identifier returned by shell.exec. - [JsonPropertyName("processId")] - public string ProcessId { get; set; } = string.Empty; + /// Whether the account uses the free limited Copilot plan. + [JsonPropertyName("isFreeUser")] + public bool IsFreeUser { get; set; } - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Whether the account uses token-based billing. + [JsonPropertyName("isTbbUser")] + public bool IsTbbUser { get; set; } - /// Signal to send (default: SIGTERM). - [JsonPropertyName("signal")] - public ShellKillSignal? Signal { get; set; } -} + /// Whether model cost columns should be shown. + [JsonPropertyName("modelCostColumnVisible")] + public bool ModelCostColumnVisible { get; set; } -/// Result of a user-requested shell command. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class UserRequestedShellCommandResult -{ - /// Error output when the execution failed. - [JsonPropertyName("error")] - public string? Error { get; set; } + /// Resolved Copilot plan tier. + [JsonPropertyName("planTier")] + public SessionQuotaPlanTier PlanTier { get; set; } - /// Process exit code, when available. - [JsonPropertyName("exitCode")] - public long? ExitCode { get; set; } + /// Whether premium-request costs are billable. + [JsonPropertyName("premiumRequestsBillable")] + public bool PremiumRequestsBillable { get; set; } - /// Captured command output. - [JsonPropertyName("output")] - public string Output { get; set; } = string.Empty; + /// Provider-owned account quota state, including snapshots without numeric balances. + [JsonPropertyName("providerQuotas")] + public IList? ProviderQuotas { get; set; } - /// Whether the command completed successfully. - [JsonPropertyName("success")] - public bool Success { get; set; } + /// Known quota snapshots, keyed by the provider's quota type. + [JsonPropertyName("snapshots")] + public IDictionary Snapshots { get => field ??= new Dictionary(); set; } - /// Tool call id emitted for the shell execution. - [JsonPropertyName("toolCallId")] - public string ToolCallId { get; set; } = string.Empty; + /// Upgrade link for a free account. + [JsonPropertyName("upgradeUrl")] + public string? UpgradeUrl { get; set; } } -/// User-requested shell command and cancellation handle. +/// Identifies the target session. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class ShellExecuteUserRequestedRequest -{ - /// Shell command to execute. - [JsonPropertyName("command")] - public string Command { get; set; } = string.Empty; - - /// Caller-provided cancellation handle for this execution. - [JsonPropertyName("requestId")] - public string RequestId { get; set; } = string.Empty; - +internal sealed class SessionQuotaRefreshRequest +{ /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Cancellation result for a user-requested shell command. +/// A pending session quota warning, consumed once by takeWarnings. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class CancelUserRequestedShellCommandResult +public sealed class QuotaWarningProjection { - /// Whether an in-flight execution was found and signalled to cancel. - [JsonPropertyName("cancelled")] - public bool Cancelled { get; set; } + /// User-facing warning text. + [JsonPropertyName("message")] + public string Message { get; set; } = string.Empty; + + /// Related account or upgrade link. + [JsonPropertyName("url")] + public string? Url { get; set; } + + /// Existing warning category. + [JsonPropertyName("warningType")] + public string WarningType { get; set; } = string.Empty; } -/// User-requested shell execution cancellation handle. +/// Identifies the target session. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class ShellCancelUserRequestedRequest +internal sealed class SessionQuotaTakeWarningsRequest { - /// Request ID previously passed to executeUserRequested. - [JsonPropertyName("requestId")] - public string RequestId { get; set; } = string.Empty; - /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Post-compaction context window usage breakdown. +/// Prediction result. Available results include prediction details; unavailable results include an explicit reason. +/// Polymorphic base type discriminated by kind. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class HistoryCompactContextWindow +[JsonPolymorphic( + TypeDiscriminatorPropertyName = "kind", + UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] +[JsonDerivedType(typeof(SessionLimitPredictionResultAvailable), "available")] +[JsonDerivedType(typeof(SessionLimitPredictionResultUnavailable), "unavailable")] +public partial class SessionLimitPredictionResult { - /// Token count from non-system messages (user, assistant, tool). - [JsonPropertyName("conversationTokens")] - public long? ConversationTokens { get; set; } + /// The type discriminator. + [JsonPropertyName("kind")] + public virtual string Kind { get; set; } = string.Empty; +} - /// Current total tokens in the context window (system + conversation + tool definitions). - [JsonPropertyName("currentTokens")] - public long CurrentTokens { get; set; } - /// Current number of messages in the conversation. - [JsonPropertyName("messagesLength")] - public long MessagesLength { get; set; } +/// Baseline data provenance for a prediction. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public sealed class SessionLimitPredictionBaselineData +{ + /// End of the baseline data slice. + [JsonPropertyName("windowEnd")] + public string WindowEnd { get; set; } = string.Empty; - /// Token count from system message(s). - [JsonPropertyName("systemTokens")] - public long? SystemTokens { get; set; } + /// Start of the baseline data slice. + [JsonPropertyName("windowStart")] + public string WindowStart { get; set; } = string.Empty; +} - /// Maximum token count for the model's context window. - [JsonPropertyName("tokenLimit")] - public long TokenLimit { get; set; } +/// Semantic usage tier and its AI-credit cap. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public sealed class SessionLimitPredictionTierOption +{ + /// AI-credit cap for this tier. + [JsonPropertyName("cap")] + public double Cap { get; set; } - /// Token count from tool definitions. - [JsonPropertyName("toolDefinitionsTokens")] - public long? ToolDefinitionsTokens { get; set; } + /// Semantic usage tier. + [JsonPropertyName("tier")] + public SessionLimitPredictionTier Tier { get; set; } } -/// Compaction outcome with the number of tokens and messages removed, summary text, and the resulting context window breakdown. +/// Explainable AI-credit session-limit prediction. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class HistoryCompactResult +public sealed class SessionLimitPredictionDetails { - /// Post-compaction context window usage breakdown. - [JsonPropertyName("contextWindow")] - public HistoryCompactContextWindow? ContextWindow { get; set; } + /// Baseline data provenance. + [JsonPropertyName("baselineData")] + public SessionLimitPredictionBaselineData BaselineData { get => field ??= new(); set; } - /// Number of messages removed during compaction. - [JsonPropertyName("messagesRemoved")] - public long MessagesRemoved { get; set; } + /// Client population used for the prediction. + [JsonPropertyName("clientType")] + public SessionLimitPredictionClientType ClientType { get; set; } - /// Whether compaction completed successfully. - [JsonPropertyName("success")] - public bool Success { get; set; } + /// Resolved model family when known. + [JsonPropertyName("family")] + public string? Family { 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; } + /// Model identifier used for lookup. + [JsonPropertyName("modelId")] + public string ModelId { get; set; } = string.Empty; - /// Number of tokens freed by compaction. - [JsonPropertyName("tokensRemoved")] - public long TokensRemoved { get; set; } -} + /// Recommended maximum AI credits for this session. + [JsonPropertyName("recommendedCap")] + public double RecommendedCap { get; set; } -/// RPC data type for SessionHistoryCompact operations. -[Experimental(global::GitHub.Copilot.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; } + /// Tier chosen as the recommended cap. + [JsonPropertyName("recommendedTier")] + public SessionLimitPredictionTier RecommendedTier { 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; } + /// Baseline fallback level used to create the prediction. + [JsonPropertyName("source")] + public SessionLimitPredictionSource Source { 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; } + /// 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; } } -/// RPC data type for SessionHistoryCompactRequestWithSession operations. +/// The available variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionHistoryCompactRequestWithSession +public partial class SessionLimitPredictionResultAvailable : SessionLimitPredictionResult { - /// 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; } + /// + [JsonIgnore] + public override string Kind => "available"; - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Predicted session limit details. + [JsonPropertyName("prediction")] + public required SessionLimitPredictionDetails Prediction { 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; } +/// The unavailable variant of . +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public partial class SessionLimitPredictionResultUnavailable : SessionLimitPredictionResult +{ + /// + [JsonIgnore] + public override string Kind => "unavailable"; - /// 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; } + /// Reason no prediction is available. + [JsonPropertyName("reason")] + public required SessionLimitPredictionUnavailableReason Reason { get; set; } } -/// Number of events that were removed by the truncation. +/// RPC data type for SessionLimitPredictionPredict operations. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class HistoryTruncateResult +public sealed class SessionLimitPredictionPredictRequest { - /// Failure detail when checkpointCleanupFailed is true. - [JsonPropertyName("checkpointCleanupError")] - public string? CheckpointCleanupError { 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; } + /// Client type to size for. Defaults to `cli-interactive`. + [JsonPropertyName("clientType")] + public SessionLimitPredictionClientType? ClientType { get; set; } - /// Number of events that were removed. - [JsonPropertyName("eventsRemoved")] - public long EventsRemoved { get; set; } + /// Optional model identifier override. If omitted, the session's current model is used. + [JsonPropertyName("modelId")] + public string? ModelId { get; set; } } -/// Identifier of the event to truncate to; this event and all later events are removed. +/// RPC data type for SessionLimitPredictionPredictRequestWithSession operations. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class HistoryTruncateRequest +internal sealed class SessionLimitPredictionPredictRequestWithSession { - /// 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; + /// Client type to size for. Defaults to `cli-interactive`. + [JsonPropertyName("clientType")] + public SessionLimitPredictionClientType? ClientType { get; set; } + + /// Optional model identifier override. If omitted, the session's current model is used. + [JsonPropertyName("modelId")] + public string? ModelId { get; set; } /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// A root user turn that the session can rewind to. +/// GitHub URL for the session and a flag indicating whether remote steering is enabled. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class HistoryRewindPoint +public sealed class RemoteEnableResult { - /// Whether at least one file in this turn or a later turn can be restored. - [JsonPropertyName("canRestoreFiles")] - public bool CanRestoreFiles { get; set; } - - /// ID of the user.message event that begins the discarded suffix. - [JsonPropertyName("eventId")] - public string EventId { 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; } - - /// Whether this turn was an automatically injected autopilot continuation. - [JsonPropertyName("isAutopilotContinuation")] - public bool IsAutopilotContinuation { get; set; } - - /// 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; } + /// Whether remote steering is enabled. + [JsonPropertyName("remoteSteerable")] + public bool RemoteSteerable { get; set; } - /// User-visible message text for the turn. - [JsonPropertyName("userMessage")] - public string UserMessage { get; set; } = string.Empty; + /// GitHub frontend URL for this session. + [Url] + [StringSyntax(StringSyntaxAttribute.Uri)] + [JsonPropertyName("url")] + public string? Url { get; set; } } -/// Rewind points and file-change-tracking availability for the session. +/// Optional remote session mode ("off", "export", or "on"); defaults to enabling both export and remote steering. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class HistoryListRewindPointsResult +internal sealed class RemoteEnableRequest { - /// 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; } + /// 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; } - /// 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; } + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } /// Identifies the target session. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionHistoryListRewindPointsRequest +internal sealed class SessionRemoteDisableRequest { /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// A file that a conversation-and-files rewind would restore. +/// 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(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class HistoryRewindFilePreview +public sealed class RemoteNotifySteerableChangedResult { - /// Aggregate change made across the discarded turns. - [JsonPropertyName("changeType")] - public HistoryRewindChangeType ChangeType { get; set; } - - /// Lines added across the discarded turns. - [JsonPropertyName("linesAdded")] - public long LinesAdded { get; set; } +} - /// Lines removed across the discarded turns. - [JsonPropertyName("linesRemoved")] - public long LinesRemoved { get; set; } +/// New remote-steerability state to persist as a `session.remote_steerable_changed` event. +[Experimental(global::GitHub.Copilot.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; } - /// Absolute path of the captured file. - [JsonPropertyName("path")] - public string Path { get; set; } = string.Empty; + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } -/// Files and aggregate changes for a prospective rewind. +/// Current sharing status and shareable GitHub URL for a session. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class HistoryPreviewRewindResult +public sealed class VisibilityGetResult { - /// 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; } + /// 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; } - /// Files ordered by path. - [JsonPropertyName("files")] - public IList Files { get => field ??= []; 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; } - /// 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; } + /// 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; } } -/// Event boundary to preview for conversation-and-files rewind. +/// Identifies the target session. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class HistoryPreviewRewindRequest +internal sealed class SessionVisibilityGetRequest { - /// 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; } -/// A captured file that rewind intentionally left unchanged. +/// Effective sharing status and shareable GitHub URL after updating session visibility. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class HistorySkippedFileRestore +public sealed class VisibilitySetResult { - /// Absolute path of the skipped file. - [JsonPropertyName("path")] - public string Path { get; set; } = string.Empty; + /// 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; } - /// Reason the file was not restored. - [JsonPropertyName("reason")] - public HistoryFileRestoreSkipReason Reason { get; set; } + /// Effective sharing status after the update. May differ from the requested status for task types that are already visible to repository readers by default. Absent when the update could not be applied (e.g. the session is not synced or the user is not authenticated). + [JsonPropertyName("status")] + public SessionVisibilityStatus? Status { get; set; } + + /// Whether the session has been synced to Mission Control (i.e. has a GitHub task). When false, the visibility change could not be applied and `status`/`shareUrl` are absent. + [JsonPropertyName("synced")] + public bool Synced { get; set; } } -/// Structured outcome of a rewind request. +/// Desired sharing status for the session. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class HistoryRewindResult +internal sealed class VisibilitySetRequest { - /// 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; } - - /// Number of persisted events removed by conversation truncation. Present only when truncation succeeded (outcomes `success`, `checkpoint-cleanup-failed`, and `snapshot-prune-failed`); omitted for every unavailable outcome (`session-busy`, `file-change-tracking-disabled`, `unsupported-remote-session`) and for `truncation-failed`, `files-rolled-back`, and `rollback-incomplete`. - [JsonPropertyName("eventsRemoved")] - public long? EventsRemoved { get; set; } - - /// Overall rewind outcome. This discriminates the result: it governs which of the remaining fields are populated, so consumers must switch on it before reading `eventsRemoved`, `restoredFiles`, `skippedFiles`, or `error`. See each field for the outcomes that populate it. - [JsonPropertyName("outcome")] - public HistoryRewindOutcome Outcome { get; set; } - - /// Absolute paths restored to their captured preimages. Always empty for conversation-only rewinds and for the unavailable outcomes (`session-busy`, `file-change-tracking-disabled`, `unsupported-remote-session`); only conversation-and-files outcomes that reached the file-restore stage populate it. - [JsonPropertyName("restoredFiles")] - public IList RestoredFiles { get => field ??= []; set; } + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; - /// 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; } + /// 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; } } -/// Boundary and mode for rewinding session history. +/// Scheduled prompt entry with ID, timing (`intervalMs`, `cron`, or `at`), prompt text, recurrence, and next run time. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class HistoryRewindRequest +public sealed class ScheduleEntry { - /// ID of the user.message event that begins the discarded suffix. - [JsonPropertyName("eventId")] - public string EventId { get; set; } = string.Empty; + /// Absolute fire time (epoch milliseconds) for a one-shot calendar schedule. + [JsonPropertyName("at")] + public long? At { get; set; } - /// Whether to rewind only conversation history or also restore captured files. - [JsonPropertyName("mode")] - public HistoryRewindMode Mode { get; set; } + /// 5-field cron expression for a recurring calendar schedule, evaluated in `tz`. + [JsonPropertyName("cron")] + public string? Cron { get; set; } - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// 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; } } -/// Indicates whether an in-progress background compaction was cancelled. +/// Snapshot of the currently active recurring prompts for this session. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class HistoryCancelBackgroundCompactionResult +public sealed class ScheduleList { - /// 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; } + /// Active scheduled prompts, ordered by id. + [JsonPropertyName("entries")] + public IList Entries { get => field ??= []; set; } } /// Identifies the target session. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionHistoryCancelBackgroundCompactionRequest +internal sealed class SessionScheduleListRequest { /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Indicates whether an in-progress manual compaction was aborted. -[Experimental(global::GitHub.Copilot.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; } -} - /// Identifies the target session. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionHistoryAbortManualCompactionRequest +internal sealed class SessionScheduleHydrateRequest { /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Markdown summary of the conversation context (empty when not available). +/// Whether the session currently has an active self-paced schedule. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class HistorySummarizeForHandoffResult +internal sealed class ScheduleHasSelfPacedResult { - /// 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; + /// True when at least one active schedule is self-paced. + [JsonPropertyName("hasSelfPaced")] + public bool HasSelfPaced { get; set; } } /// Identifies the target session. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionHistorySummarizeForHandoffRequest +internal sealed class SessionScheduleHasSelfPacedRequest { /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// What a successful clear removed. A clear that could not be applied rejects instead of reporting a count. +/// Result of registering or re-arming a scheduled prompt. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class HistoryClearContextResult +internal sealed class ScheduleAddResult { - /// 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; } + /// The registered or updated schedule entry. + [JsonPropertyName("entry")] + public ScheduleEntry? Entry { get; set; } + + /// User-facing validation error, when registration failed. + [JsonPropertyName("error")] + public string? Error { get; set; } } -/// Parameters for clearing the conversation and seeding the window that replaces it. +/// Register a relative-interval scheduled prompt. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class HistoryClearContextRequest +internal sealed class ScheduleAddRequest { - /// 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. + /// Optional display-only prompt label. + [JsonPropertyName("displayPrompt")] + public string? DisplayPrompt { get; set; } + + /// Human-readable interval such as `30s`, `5m`, or `2h`. + [JsonPropertyName("interval")] + public string Interval { get; set; } = string.Empty; + + /// Prompt text to enqueue when the schedule fires. [JsonPropertyName("prompt")] public string Prompt { get; set; } = string.Empty; + /// Whether the schedule should re-arm after each tick. Defaults to true. + [JsonPropertyName("recurring")] + public bool? Recurring { get; set; } + /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// User-facing pending queue entry, with kind and display text for a queued message, slash command, or model change. +/// Register a cron scheduled prompt. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class QueuePendingItems +internal sealed class ScheduleAddCronRequest { - /// 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; } + /// 5-field cron expression. + [JsonPropertyName("cron")] + public string Cron { get; set; } = string.Empty; - /// Human-readable text to display for this queue entry in the UI. - [JsonPropertyName("displayText")] - public string DisplayText { get; set; } = string.Empty; + /// Optional display-only prompt label. + [JsonPropertyName("displayPrompt")] + public string? DisplayPrompt { get; set; } - /// Stable opaque id for the canonical queued item. Batch rows share one id. - [JsonPropertyName("id")] - public string Id { get; set; } = string.Empty; + /// Prompt text to enqueue when the schedule fires. + [JsonPropertyName("prompt")] + public string Prompt { 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; } + /// Whether the schedule should re-arm after each tick. Defaults to true. + [JsonPropertyName("recurring")] + public bool? Recurring { 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; } + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; - /// Optional source tag associated with this pending queue entry. This is an open string, not authenticated authorship. In particular, `user` does not prove that a person typed the message. If the source is absent or unrecognized, consumers must not infer human or agent authorship and should handle the entry neutrally. Consumers should tolerate future source values. - [JsonPropertyName("source")] - public string? Source { get; set; } + /// IANA timezone for evaluating the cron expression. + [JsonPropertyName("tz")] + public string? Tz { get; set; } } -/// Snapshot of the session's pending queued items and immediate-steering messages. +/// Register an absolute-time scheduled prompt. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class QueuePendingItemsResult +internal sealed class ScheduleAddAtRequest { - /// 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; } + /// Epoch milliseconds when the prompt should fire. + [JsonPropertyName("at")] + public long At { 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; } + /// Optional display-only prompt label. + [JsonPropertyName("displayPrompt")] + public string? DisplayPrompt { get; set; } - /// Display text for messages currently in the immediate steering queue (interjections sent during a running turn). - [JsonPropertyName("steeringMessages")] - public IList SteeringMessages { get => field ??= []; set; } + /// Prompt text to enqueue when the schedule fires. + [JsonPropertyName("prompt")] + public string Prompt { get; set; } = string.Empty; - /// ID of the running turn's user message while the model has not answered it, so `withdrawMessage` can still take it back once nothing sent after it is pending. A message leaves `items` when its turn starts, before its `user.message` is recorded; this tells that message apart from one that was removed. Absent when no turn prompt can be taken back. - [JsonPropertyName("withdrawableTurnMessageId")] - public string? WithdrawableTurnMessageId { get; set; } -} + /// Whether the schedule should re-arm after each tick. Defaults to false. + [JsonPropertyName("recurring")] + public bool? Recurring { get; set; } -/// Identifies the target session. -[Experimental(global::GitHub.Copilot.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. +/// Register a self-paced scheduled prompt. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class QueueSnapshotResult +internal sealed class ScheduleAddSelfPacedRequest { - /// Queue item identifier of a model switch that has been dequeued but not yet applied. - [JsonPropertyName("inFlightModelChangeId")] - public string? InFlightModelChangeId { get; set; } - - /// Insertion orders for queued items, aligned with `items`. - [JsonPropertyName("itemOrders")] - public IList? ItemOrders { get; set; } - - /// User-facing pending items in FIFO order. - [JsonPropertyName("items")] - public IList Items { get => field ??= []; set; } + /// Optional display-only prompt label. + [JsonPropertyName("displayPrompt")] + public string? DisplayPrompt { get; set; } - /// Insertion orders for immediate steering messages, aligned with `steeringMessages`. - [JsonPropertyName("steeringMessageOrders")] - public IList? SteeringMessageOrders { get; set; } + /// Prompt text to enqueue when the schedule fires. + [JsonPropertyName("prompt")] + public string Prompt { get; set; } = string.Empty; - /// Immediate steering messages waiting for an active turn. - [JsonPropertyName("steeringMessages")] - public IList SteeringMessages { get => field ??= []; set; } + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } -/// Identifies the target session. +/// Re-arm a self-paced scheduled prompt. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionQueueSnapshotRequest +internal sealed class ScheduleRearmSelfPacedRequest { + /// Epoch milliseconds when the prompt should next fire. + [JsonPropertyName("at")] + public long At { get; set; } + + /// Id of the self-paced scheduled prompt. + [JsonPropertyName("id")] + public long Id { get; set; } + /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Result of moving a queued item. +/// Remove a scheduled prompt by id. The result entry is omitted if the id was unknown. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class QueueMoveItemResult +public sealed class ScheduleStopResult { - /// True when the item changed position; false when it was already at the requested position. - [JsonPropertyName("changed")] - public bool Changed { get; set; } + /// The removed entry, or omitted if no entry matched. + [JsonPropertyName("entry")] + public ScheduleEntry? Entry { get; set; } } -/// Parameters for moving a queued item by stable id. +/// Identifier of the scheduled prompt to remove. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class QueueMoveItemRequest +internal sealed class ScheduleStopRequest { - /// Stable opaque queued-item id. + /// Id of the scheduled prompt to remove. [JsonPropertyName("id")] - public string Id { get; set; } = string.Empty; + public long Id { get; set; } /// 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; } } -/// Result of inserting a queued message. +/// 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(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class QueueInsertAtResult +public sealed class ProviderTokenAcquireResult { - /// Fresh stable opaque id assigned to the inserted item. - [JsonPropertyName("id")] - public string Id { get; set; } = string.Empty; + /// The bearer token value (without the `Bearer ` prefix). + [JsonPropertyName("token")] + public string Token { get; set; } = string.Empty; } -/// Serializable message fields accepted by queue.insertAt. +/// 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(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class QueueInsertMessage +public sealed class ProviderTokenAcquireRequest { - /// Optional explicit agent mode. When omitted, the session's current mode is assigned. - [JsonPropertyName("agentMode")] - public SendAgentMode? AgentMode { get; set; } - - /// Optional attachments for the message. - [JsonPropertyName("attachments")] - public IList? Attachments { get; set; } - - /// Whether the message is billable. - [JsonPropertyName("billable")] - public bool? Billable { get; set; } - - /// Accepted for internal SendOptions compatibility but ignored; delivery is derived from current session activity. - [JsonPropertyName("delivery")] - public string? Delivery { get; set; } - - /// Optional user-facing display text. - [JsonPropertyName("displayPrompt")] - public string? DisplayPrompt { get; set; } - - /// Accepted for SendOptions compatibility but ignored; inserted items always use queued delivery semantics. - [JsonPropertyName("mode")] - public SendMode? Mode { get; set; } - - /// Accepted for SendOptions compatibility but ignored; the requested public position controls placement. - [JsonPropertyName("prepend")] - public bool? Prepend { get; set; } - - /// The user message text. - [JsonPropertyName("prompt")] - public string Prompt { get; set; } = string.Empty; - - /// Per-turn request headers. - [JsonPropertyName("requestHeaders")] - public IDictionary? RequestHeaders { get; set; } - - /// Required tool name for the turn, when any. - [JsonPropertyName("requiredTool")] - public string? RequiredTool { get; set; } + /// 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; - /// 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; } + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} - /// Accepted for SendOptions compatibility but ignored; insertion scheduling is controlled by the queue drain state. - [JsonPropertyName("wait")] - public bool? Wait { get; set; } +/// Result returned by an extension workflow closure. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public sealed class WorkflowExecuteResult +{ + /// Workflow result value. + [JsonPropertyName("result")] + public JsonElement? Result { get; set; } } -/// Parameters for inserting a queued message at a public visible position. +/// Parameters sent to the owning extension to execute a workflow closure. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class QueueInsertAtRequest +public sealed class WorkflowExecuteRequest { - /// Queued message contents and delivery metadata. - [JsonPropertyName("message")] - public QueueInsertMessage Message { get => field ??= new(); set; } + /// Workflow input value. + [JsonPropertyName("args")] + public JsonElement Args { get; set; } - /// Zero-based position in the public visible queue. Values outside the queue clamp to an end. - [JsonPropertyName("position")] - public long Position { get; set; } + /// Opaque token identifying this workflow execution attempt. + [JsonPropertyName("executionToken")] + public string ExecutionToken { get; set; } = string.Empty; + + /// Registered workflow name. + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; + + /// Workflow run identifier. + [JsonPropertyName("runId")] + public string RunId { get; set; } = string.Empty; /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Result of removing a queued item. +/// Parameters for cooperatively aborting a workflow body. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class QueueRemoveAtResult +public sealed class WorkflowAbortRequest { - /// True when the addressed item was removed. - [JsonPropertyName("removed")] - public bool Removed { get; set; } -} + /// Opaque token identifying the execution attempt to abort. + [JsonPropertyName("executionToken")] + public string ExecutionToken { get; set; } = string.Empty; -/// Parameters for removing a queued item by stable id. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class QueueRemoveAtRequest -{ - /// Stable opaque ID of the queued item to remove. - [JsonPropertyName("id")] - public string Id { get; set; } = string.Empty; + /// Workflow run identifier. + [JsonPropertyName("runId")] + public string RunId { get; set; } = string.Empty; /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Result of editing a queued message. +/// Whether the client authoritatively confirmed its external work stopped. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class QueueUpdateTextResult +public sealed class ClientTaskCancelResult { - /// True when the stored text changed. - [JsonPropertyName("updated")] - public bool Updated { get; set; } + /// True only when the owner confirms that external work stopped before responding. + [JsonPropertyName("cancelled")] + public bool Cancelled { get; set; } } -/// Parameters for editing a single queued message. +/// Runtime-to-owner cancellation request for a client-owned task. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class QueueUpdateTextRequest +public sealed class ClientTaskCancelRequest { - /// Optional replacement prompt displayed to the user. - [JsonPropertyName("displayPrompt")] - public string? DisplayPrompt { get; set; } + /// Opaque identifier shared by coalesced cancellation callers. + [JsonPropertyName("cancellationId")] + public string CancellationId { get; set; } = string.Empty; - /// Stable opaque ID of the queued item to edit. + /// Owner-scoped task key included for correlation. + [JsonPropertyName("clientTaskId")] + public string ClientTaskId { get; set; } = string.Empty; + + /// Canonical runtime-generated task identifier. [JsonPropertyName("id")] public string Id { get; set; } = string.Empty; - /// Replacement prompt sent to the model. - [JsonPropertyName("prompt")] - public string Prompt { get; set; } = string.Empty; + /// Reason the runtime requests cancellation. + [JsonPropertyName("reason")] + public ClientTaskCancelReason Reason { get; set; } - /// Target session identifier. + /// Session that owns the client task. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Result of withdrawing a user message. +/// Describes a filesystem error. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class QueueWithdrawMessageResult +public sealed class SessionFsError { - /// True when the running turn was interrupted to withdraw the message. With removed false, the turn was interrupted but its events could not be removed, for example because the model answered first, so the message stays in the interrupted turn. - [JsonPropertyName("interrupted")] - public bool Interrupted { get; set; } + /// Error classification. + [JsonPropertyName("code")] + public SessionFsErrorCode Code { get; set; } - /// True when the message left the queue or, for a running turn, history. - [JsonPropertyName("removed")] - public bool Removed { get; set; } + /// Free-form detail about the error, for logging/diagnostics. + [JsonPropertyName("message")] + public string? Message { get; set; } + + /// For failed writeFile requests only: true if the provider changed the target before failing. Omit when unknown or unchanged. + [JsonPropertyName("writeChanged")] + public bool? WriteChanged { get; set; } } -/// Conditional withdrawal of a single user message, from its queue or from the running turn it started. +/// File content as a UTF-8 string, or a filesystem error if the read failed. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class QueueWithdrawMessageRequest +public sealed class SessionFsReadFileResult { - /// The prompt originally sent. A message edited since submission is not withdrawn, so an obsolete draft cannot replace the edit. - [JsonPropertyName("expectedPrompt")] - public string ExpectedPrompt { get; set; } = string.Empty; - - /// Message identity returned by send, not the queue item id. Batch messages are not eligible. - [JsonPropertyName("messageId")] - public string MessageId { get; set; } = string.Empty; + /// File content as UTF-8 string. + [JsonPropertyName("content")] + public string Content { get; set; } = string.Empty; - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Describes a filesystem error. + [JsonPropertyName("error")] + public SessionFsError? Error { get; set; } } -/// Append to one pending steering message without changing its identity or delivery position. +/// Path of the file to read from the client-provided session filesystem. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class QueueAppendSteeringRequest +public sealed class SessionFsReadFileRequest { - /// Mode captured at submission. Only steering messages in the same mode may be combined. - [JsonPropertyName("agentMode")] - public SendAgentMode AgentMode { get; set; } - - /// Attachments to add after the message's existing attachments. An empty list preserves the existing attachments. - [JsonPropertyName("attachments")] - public IList Attachments { get => field ??= []; set; } - - /// Display text to append to the existing preview after a blank line. - [JsonPropertyName("displayPrompt")] - public string DisplayPrompt { get; set; } = string.Empty; - - /// Expected current prompt, including any previous appends. The runtime applies plan-mode normalization before comparing and refuses a changed message. - [JsonPropertyName("expectedPrompt")] - public string ExpectedPrompt { get; set; } = string.Empty; - - /// Message identity returned by send, not the queue item id. Only unclaimed user steering messages are eligible. - [JsonPropertyName("messageId")] - public string MessageId { get; set; } = string.Empty; - - /// Text to append after a blank line. - [JsonPropertyName("prompt")] - public string Prompt { get; set; } = string.Empty; + /// 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; } -/// Result of duplicating a queued item. +/// File bytes as standard base64, or a filesystem error if the read failed. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class QueueDuplicateAtResult +public sealed class SessionFsReadFileBytesResult { - /// Fresh stable opaque id assigned to the duplicate. - [JsonPropertyName("id")] - public string Id { get; set; } = string.Empty; + /// Exact file bytes encoded as standard base64. + [JsonPropertyName("content")] + public string Content { get; set; } = string.Empty; + + /// Describes a filesystem error. + [JsonPropertyName("error")] + public SessionFsError? Error { get; set; } } -/// Parameters for duplicating a queued item. +/// Path of the binary file to read from the client-provided session filesystem. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class QueueDuplicateAtRequest +public sealed class SessionFsReadFileBytesRequest { - /// Stable opaque ID of the queued item to duplicate. - [JsonPropertyName("id")] - public string Id { get; set; } = string.Empty; + /// 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; } -/// 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. +/// File path, content to write, and optional mode for the client-provided session filesystem. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class QueueSetDrainPausedRequest +public sealed class SessionFsWriteFileRequest { - /// Whether queued-lane draining should be paused. - [JsonPropertyName("paused")] - public bool Paused { get; set; } + /// Content to write. + [JsonPropertyName("content")] + public string Content { get; set; } = string.Empty; + + /// Optional POSIX-style mode for newly created files. + [JsonPropertyName("mode")] + public long? Mode { get; set; } + + /// Path using SessionFs conventions. + [JsonPropertyName("path")] + public string Path { get; set; } = string.Empty; /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Result of trying to steer a queued message into a live turn. +/// File path, standard-base64-encoded bytes to write, and optional mode for the client-provided session filesystem. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class QueueSendNowResult +public sealed class SessionFsWriteFileBytesRequest { - /// True when the item was accepted into the steering lane; false when no main turn was live. - [JsonPropertyName("steered")] - public bool Steered { get; set; } -} + /// Exact file bytes encoded as standard base64. + [JsonPropertyName("content")] + public string Content { get; set; } = string.Empty; -/// Parameters for steering a queued message into a live turn. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class QueueSendNowRequest -{ - /// Stable opaque ID of the queued item to steer into the live turn. - [JsonPropertyName("id")] - public string Id { 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; } -/// Whether the native queue has pending work. +/// File path, content to append, and optional mode for the client-provided session filesystem. Implementations create parent directories as needed. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class QueueHasPendingResult +public sealed class SessionFsAppendFileRequest { - /// True when queued or immediate native work is pending. - [JsonPropertyName("hasPending")] - public bool HasPending { get; set; } -} + /// 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; -/// Identifies the target session. -[Experimental(global::GitHub.Copilot.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. +/// Indicates whether the requested path exists in the client-provided session filesystem. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class QueueBeginDeferredIdleDrainResult +public sealed class SessionFsExistsResult { - /// True when the host should run finishDeferredIdleDrain asynchronously. - [JsonPropertyName("shouldDrain")] - public bool ShouldDrain { get; set; } + /// Whether the path exists. + [JsonPropertyName("exists")] + public bool Exists { get; set; } } -/// Inputs for starting a deferred-idle drain. +/// Path to test for existence in the client-provided session filesystem. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class QueueBeginDeferredIdleDrainRequest +public sealed class SessionFsExistsRequest { - /// Whether the host still has active background work. - [JsonPropertyName("activeBackgroundWork")] - public bool ActiveBackgroundWork { 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; } -/// Action selected by the native deferred-idle drain. +/// Filesystem metadata for the requested path, or a filesystem error if the stat failed. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class QueueFinishDeferredIdleDrainResult +public sealed class SessionFsStatResult { - /// Whether the deferred idle was caused by an aborted foreground turn. - [JsonPropertyName("aborted")] - public bool Aborted { get; set; } + /// ISO 8601 timestamp of creation. + [JsonPropertyName("birthtime")] + public DateTimeOffset Birthtime { get; set; } - /// One of none, processQueue, or emitSessionIdle. - [JsonPropertyName("action")] - public string Action { get; set; } = string.Empty; + /// Describes a filesystem error. + [JsonPropertyName("error")] + public SessionFsError? Error { get; set; } + + /// Whether the path is a directory. + [JsonPropertyName("isDirectory")] + public bool IsDirectory { get; set; } + + /// Whether the path is a file. + [JsonPropertyName("isFile")] + public bool IsFile { get; set; } + + /// ISO 8601 timestamp of last modification. + [JsonPropertyName("mtime")] + public DateTimeOffset Mtime { get; set; } + + /// File size in bytes. + [JsonPropertyName("size")] + public long Size { get; set; } } -/// Inputs for completing a deferred-idle drain. +/// Path whose metadata should be returned from the client-provided session filesystem. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class QueueFinishDeferredIdleDrainRequest +public sealed class SessionFsStatRequest { - /// Whether the host still has active background work. - [JsonPropertyName("activeBackgroundWork")] - public bool ActiveBackgroundWork { get; set; } - - /// Whether native queued work remains. - [JsonPropertyName("hasPending")] - public bool HasPending { get; set; } + /// 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; } -/// Inputs for marking session.idle deferred in native state. +/// Directory path to create in the client-provided session filesystem, with options for recursive creation and POSIX mode. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class QueueDeferSessionIdleRequest +public sealed class SessionFsMkdirRequest { - /// Whether the deferred idle was caused by an aborted foreground turn. - [JsonPropertyName("aborted")] - public bool Aborted { get; set; } + /// Optional POSIX-style mode for newly created directories. + [JsonPropertyName("mode")] + public long? Mode { get; set; } + + /// Path using SessionFs conventions. + [JsonPropertyName("path")] + public string Path { get; set; } = string.Empty; + + /// Create parent directories as needed. + [JsonPropertyName("recursive")] + public bool? Recursive { get; set; } /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Indicates whether a user-facing pending item was removed. +/// Names of entries in the requested directory, or a filesystem error if the read failed. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class QueueRemoveMostRecentResult +public sealed class SessionFsReaddirResult { - /// 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; } -} + /// Entry names in the directory. + [JsonPropertyName("entries")] + public IList Entries { get => field ??= []; set; } -/// Identifies the target session. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionQueueRemoveMostRecentRequest -{ - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Describes a filesystem error. + [JsonPropertyName("error")] + public SessionFsError? Error { get; set; } } -/// Identifies the target session. +/// Directory path whose entries should be listed from the client-provided session filesystem. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionQueueClearRequest +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; } -/// Internal filter for consuming queued system notifications. +/// Directory entry returned by session filesystem `readdirWithTypes`, with name and entry type. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class QueueConsumeSystemNotificationsRequest +public sealed class SessionFsReaddirWithTypesEntry { - /// Opaque runtime-owned filter object. - [JsonPropertyName("filter")] - public JsonElement Filter { get; set; } + /// Entry name. + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Entry type. + [JsonPropertyName("type")] + public SessionFsReaddirWithTypesEntryType Type { get; set; } } -/// Result of enqueueing the resume-pending wake item. +/// Entries in the requested directory paired with file/directory type information, or a filesystem error if the read failed. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class QueueEnqueueResumePendingResult +public sealed class SessionFsReaddirWithTypesResult { - /// True when a wake item was newly queued. - [JsonPropertyName("queued")] - public bool Queued { get; set; } + /// Directory entries with type information. + [JsonPropertyName("entries")] + public IList Entries { get => field ??= []; set; } + + /// Describes a filesystem error. + [JsonPropertyName("error")] + public SessionFsError? Error { get; set; } } -/// Identifies the target session. +/// Directory path whose entries (with type information) should be listed from the client-provided session filesystem. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionQueueEnqueueResumePendingRequest +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; } -/// Identifies the target session. +/// Path to remove from the client-provided session filesystem, with options for recursive removal and force. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionQueueProcessRequest +public sealed class SessionFsRmRequest { + /// Ignore errors if the path does not exist. + [JsonPropertyName("force")] + public bool? Force { get; set; } + + /// Path using SessionFs conventions. + [JsonPropertyName("path")] + public string Path { get; set; } = string.Empty; + + /// Remove directories and their contents recursively. + [JsonPropertyName("recursive")] + public bool? Recursive { get; set; } + /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Cursor, batch size, and optional long-poll/filter parameters for reading session events. +/// Source and destination paths for renaming or moving an entry in the client-provided session filesystem. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class EventLogReadRequest +public sealed class SessionFsRenameRequest { - /// Optional non-empty list of subagent identifiers. When provided, only events owned by one of these agents are returned; ownership recognizes the event envelope's agentId plus legacy data.agentId and data.parentToolCallId markers. This filter takes precedence over agentScope. - [JsonPropertyName("agentIds")] - public IList? AgentIds { get; set; } - - /// Agent-scope filter: 'primary' returns only main-agent events plus events whose type starts with 'subagent.' (matching the typed-subscription default behavior); 'all' returns events from all agents (matching wildcard-subscription behavior). Default is 'all' to preserve wildcard semantics for catch-up callers. - [JsonPropertyName("agentScope")] - public EventsAgentScope? AgentScope { get; set; } - - /// Opaque cursor returned by a previous read. Omit on the first call to start from the beginning of the session's persisted history. - [JsonPropertyName("cursor")] - public string? Cursor { get; set; } - - /// Direction to page through the session's persisted event history. 'forward' (default) pages from the cursor toward newer events (or from the start of history when no cursor is given). 'backward' enables tail-first reads: with no cursor it returns the NEWEST `max` events, and the returned cursor pages toward OLDER events on subsequent backward reads. Events within a returned batch are always in chronological (oldest-to-newest) order, even for a backward read. Backward reads cover PERSISTED history only; ephemeral events are never returned by a backward read. `direction` selects the INITIAL read only: the returned cursor is self-describing, so a continuation read pages in the cursor's own direction regardless of the `direction` passed alongside it — a forward cursor always pages forward and a backward cursor always pages backward. Pass the direction that matches the cursor to avoid confusion. - [JsonPropertyName("direction")] - public EventsReadDirection? Direction { get; set; } - - /// When false, skip ephemeral events entirely and return only durable (persisted) events. History-backfill callers that discard ephemerals anyway should set this so the read is bounded by the durable log length instead of racing the ephemeral ring on a busy session. Defaults to true (ephemerals are interleaved with durable events in creation order). Ignored by backward reads, which always cover persisted history only. - [JsonPropertyName("includeEphemeral")] - public bool? IncludeEphemeral { get; set; } - - /// Maximum number of events to return in this batch (1–1000, default 200). - [JsonPropertyName("max")] - public long? Max { get; set; } + /// 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; - /// 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; } + /// Source path using SessionFs conventions. + [JsonPropertyName("src")] + public string Src { get; set; } = string.Empty; } -/// 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). +/// Query results including rows, columns, and rows affected, or a filesystem error if execution failed. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class EventLogTailResult +public sealed class SessionFsSqliteQueryResult { - /// 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; + /// 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; } } -/// Identifies the target session. +/// 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(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionEventLogTailRequest +public sealed class SessionFsSqliteQueryRequest { + /// Optional named bind parameters. + [JsonPropertyName("params")] + public IDictionary? Params { get; set; } + + /// SQL query to execute. + [JsonPropertyName("query")] + public string Query { get; set; } = string.Empty; + + /// How to execute the query: 'exec' for DDL/multi-statement (no results), 'query' for SELECT (returns rows), 'run' for INSERT/UPDATE/DELETE (returns rowsAffected). + [JsonPropertyName("queryType")] + public SessionFsSqliteQueryType QueryType { get; set; } + /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Opaque handle representing an event-type interest registration. +/// Classified SQLite transaction failure. busyOrLocked guarantees rollback; postCommitAmbiguous must never be retried. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class RegisterEventInterestResult +public sealed class SessionFsSqliteTransactionError { - /// 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; + /// 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; } -/// Event type to register consumer interest for, used by runtime gating logic. +/// Per-statement results, or a classified transaction error. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class RegisterEventInterestParams +public sealed class SessionFsSqliteTransactionResult { - /// 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; + /// 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; } } -/// Indicates whether the operation succeeded. +/// One statement in an atomic SQLite transaction. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class EventLogReleaseInterestResult +public sealed class SessionFsSqliteTransactionStatement { - /// Whether the operation succeeded. - [JsonPropertyName("success")] - public bool Success { get; set; } -} + /// Optional named bind parameters. + [JsonPropertyName("params")] + public IDictionary? Params { get; set; } -/// Opaque handle previously returned by `registerInterest` to release. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class ReleaseEventInterestParams -{ - /// Handle returned by a previous `registerInterest` call. Idempotent: releasing an unknown or already-released handle is a no-op (returns success). When the last outstanding handle for an event type is released, the runtime reverts to its 'no consumer' code path for that event type. - [JsonPropertyName("handle")] - public string Handle { get; set; } = string.Empty; + /// SQL statement to execute. + [JsonPropertyName("query")] + public string Query { get; set; } = string.Empty; - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// How to execute the statement. + [JsonPropertyName("queryType")] + public SessionFsSqliteQueryType QueryType { get; set; } } -/// Identifies the target session. +/// Statements to execute atomically. Providers apply busy handling for every call. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionUsageGetMetricsRequest +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; } } -/// Internal absolute code-change totals reported by the owning host. +/// Indicates whether the per-session SQLite database already exists. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionUsageSetCodeChangesRequest +public sealed class SessionFsSqliteExistsResult { - /// Absolute changed-file count; omission preserves the previous count. - [JsonPropertyName("filesCount")] - public double? FilesCount { get; set; } - - /// Absolute added-line total, replacing the previous reading. - [JsonPropertyName("linesAdded")] - public double LinesAdded { get; set; } - - /// Absolute removed-line total, replacing the previous reading. - [JsonPropertyName("linesRemoved")] - public double LinesRemoved { get; set; } + /// Whether the session database already exists. + [JsonPropertyName("exists")] + public bool Exists { get; set; } +} +/// Identifies the target session. +public sealed class SessionFsSqliteExistsRequest +{ /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Existing delegation guidance for an account without delegation access. +/// Canvas open result returned by the provider. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class SessionQuotaDelegateWarning +public sealed class CanvasProviderOpenResult { - /// User-facing guidance. - [JsonPropertyName("text")] - public string Text { get; set; } = string.Empty; + /// Provider-supplied status text. + [JsonPropertyName("status")] + public string? Status { get; set; } - /// Link to upgrade the account. + /// Provider-supplied title. + [JsonPropertyName("title")] + public string? Title { get; set; } + + /// URL for web-rendered canvases. [JsonPropertyName("url")] - public string Url { get; set; } = string.Empty; + public string? Url { get; set; } } -/// Session quota snapshot, preserving the incumbent numeric units and reset metadata. +/// Host capabilities. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class SessionQuotaSnapshot +public sealed class CanvasHostContextCapabilities { - /// Entitled quantity in this quota's units. - [JsonPropertyName("entitlementRequests")] - public double EntitlementRequests { get; set; } - - /// Whether the provider reports available quota. - [JsonPropertyName("hasQuota")] - public bool? HasQuota { get; set; } - - /// Whether the entitlement is unlimited. - [JsonPropertyName("isUnlimitedEntitlement")] - public bool IsUnlimitedEntitlement { get; set; } - - /// Additional usage in this quota's units. - [JsonPropertyName("overage")] - public double Overage { get; set; } - - /// Whether additional usage is allowed after quota exhaustion. - [JsonPropertyName("overageAllowedWithExhaustedQuota")] - public bool OverageAllowedWithExhaustedQuota { get; set; } - - /// Additional-usage budget cap, when provided. - [JsonPropertyName("overageEntitlement")] - public double? OverageEntitlement { get; set; } - - /// Percentage of the entitlement remaining. - [JsonPropertyName("remainingPercentage")] - public double RemainingPercentage { get; set; } - - /// Quota reset time in milliseconds since the Unix epoch, when known. - [JsonPropertyName("resetDateEpochMs")] - public double? ResetDateEpochMs { get; set; } - - /// Whether the reset time is estimated. - [JsonPropertyName("resetDateEstimated")] - public bool? ResetDateEstimated { get; set; } - - /// Whether this quota uses token-based billing. - [JsonPropertyName("tokenBasedBilling")] - public bool? TokenBasedBilling { get; set; } - - /// Whether usage is allowed after quota exhaustion. - [JsonPropertyName("usageAllowedWithExhaustedQuota")] - public bool UsageAllowedWithExhaustedQuota { get; set; } - - /// Consumed quantity in this quota's units. - [JsonPropertyName("usedRequests")] - public double UsedRequests { get; set; } + /// Whether canvas rendering is supported. + [JsonPropertyName("canvases")] + public bool? Canvases { get; set; } } -/// The incumbent session-owned quota and account projection. +/// Host context supplied by the runtime. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class SessionQuotaGetResult +public sealed class CanvasHostContext { - /// Whether the account can sign up for Copilot Free. - [JsonPropertyName("canSignupForCopilotFree")] - public bool CanSignupForCopilotFree { get; set; } - - /// Whether the account can delegate tasks to GitHub. - [JsonPropertyName("delegateAvailable")] - public bool DelegateAvailable { get; set; } - - /// Existing delegation warning for a free account. - [JsonPropertyName("delegateWarning")] - public SessionQuotaDelegateWarning? DelegateWarning { get; set; } - - /// Whether dynamic workflows are enabled for the active account. - [JsonPropertyName("dynamicWorkflowsEnabled")] - public bool DynamicWorkflowsEnabled { get; set; } - - /// Whether dynamic workflows are visible under the session's feature flags. - [JsonPropertyName("dynamicWorkflowsUiVisible")] - public bool DynamicWorkflowsUiVisible { get; set; } - - /// Whether the account uses the free limited Copilot plan. - [JsonPropertyName("isFreeUser")] - public bool IsFreeUser { get; set; } + /// Host capabilities. + [JsonPropertyName("capabilities")] + public CanvasHostContextCapabilities? Capabilities { get; set; } +} - /// Whether the account uses token-based billing. - [JsonPropertyName("isTbbUser")] - public bool IsTbbUser { get; set; } +/// Session context supplied by the runtime. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public sealed class CanvasSessionContext +{ + /// Active session working directory, when known. + [JsonPropertyName("workingDirectory")] + public string? WorkingDirectory { get; set; } +} - /// Whether model cost columns should be shown. - [JsonPropertyName("modelCostColumnVisible")] - public bool ModelCostColumnVisible { get; set; } +/// Canvas open parameters sent to the provider. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public sealed class CanvasProviderOpenRequest +{ + /// Provider-local canvas identifier. + [JsonPropertyName("canvasId")] + public string CanvasId { get; set; } = string.Empty; - /// Resolved Copilot plan tier. - [JsonPropertyName("planTier")] - public SessionQuotaPlanTier PlanTier { get; set; } + /// Owning provider identifier. + [JsonPropertyName("extensionId")] + public string ExtensionId { get; set; } = string.Empty; - /// Whether premium-request costs are billable. - [JsonPropertyName("premiumRequestsBillable")] - public bool PremiumRequestsBillable { get; set; } + /// Host context supplied by the runtime. + [JsonPropertyName("host")] + public CanvasHostContext? Host { get; set; } - /// Provider-owned account quota state, including snapshots without numeric balances. - [JsonPropertyName("providerQuotas")] - public IList? ProviderQuotas { get; set; } + /// Canvas open input. + [JsonPropertyName("input")] + public JsonElement? Input { get; set; } - /// Known quota snapshots, keyed by the provider's quota type. - [JsonPropertyName("snapshots")] - public IDictionary Snapshots { get => field ??= new Dictionary(); set; } + /// Stable caller-supplied canvas instance identifier. + [JsonPropertyName("instanceId")] + public string InstanceId { get; set; } = string.Empty; - /// Upgrade link for a free account. - [JsonPropertyName("upgradeUrl")] - public string? UpgradeUrl { get; set; } -} + /// Session context supplied by the runtime. + [JsonPropertyName("session")] + public CanvasSessionContext? Session { get; set; } -/// Identifies the target session. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionQuotaGetRequest -{ /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// The incumbent session-owned quota and account projection. +/// Canvas close parameters sent to the provider. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class SessionQuotaRefreshResult +public sealed class CanvasProviderCloseRequest { - /// Whether the account can sign up for Copilot Free. - [JsonPropertyName("canSignupForCopilotFree")] - public bool CanSignupForCopilotFree { get; set; } + /// Provider-local canvas identifier. + [JsonPropertyName("canvasId")] + public string CanvasId { get; set; } = string.Empty; - /// Whether the account can delegate tasks to GitHub. - [JsonPropertyName("delegateAvailable")] - public bool DelegateAvailable { get; set; } + /// Owning provider identifier. + [JsonPropertyName("extensionId")] + public string ExtensionId { get; set; } = string.Empty; - /// Existing delegation warning for a free account. - [JsonPropertyName("delegateWarning")] - public SessionQuotaDelegateWarning? DelegateWarning { get; set; } + /// Host context supplied by the runtime. + [JsonPropertyName("host")] + public CanvasHostContext? Host { get; set; } - /// Whether dynamic workflows are enabled for the active account. - [JsonPropertyName("dynamicWorkflowsEnabled")] - public bool DynamicWorkflowsEnabled { get; set; } + /// Canvas instance identifier. + [JsonPropertyName("instanceId")] + public string InstanceId { get; set; } = string.Empty; - /// Whether dynamic workflows are visible under the session's feature flags. - [JsonPropertyName("dynamicWorkflowsUiVisible")] - public bool DynamicWorkflowsUiVisible { get; set; } + /// Session context supplied by the runtime. + [JsonPropertyName("session")] + public CanvasSessionContext? Session { get; set; } - /// Whether the account uses the free limited Copilot plan. - [JsonPropertyName("isFreeUser")] - public bool IsFreeUser { get; set; } + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} - /// Whether the account uses token-based billing. - [JsonPropertyName("isTbbUser")] - public bool IsTbbUser { get; set; } +/// Canvas action invocation parameters sent to the provider. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public sealed class CanvasProviderInvokeActionRequest +{ + /// Action name to invoke. + [JsonPropertyName("actionName")] + public string ActionName { get; set; } = string.Empty; - /// Whether model cost columns should be shown. - [JsonPropertyName("modelCostColumnVisible")] - public bool ModelCostColumnVisible { get; set; } + /// Provider-local canvas identifier. + [JsonPropertyName("canvasId")] + public string CanvasId { get; set; } = string.Empty; - /// Resolved Copilot plan tier. - [JsonPropertyName("planTier")] - public SessionQuotaPlanTier PlanTier { get; set; } + /// Owning provider identifier. + [JsonPropertyName("extensionId")] + public string ExtensionId { get; set; } = string.Empty; - /// Whether premium-request costs are billable. - [JsonPropertyName("premiumRequestsBillable")] - public bool PremiumRequestsBillable { get; set; } + /// Host context supplied by the runtime. + [JsonPropertyName("host")] + public CanvasHostContext? Host { get; set; } - /// Provider-owned account quota state, including snapshots without numeric balances. - [JsonPropertyName("providerQuotas")] - public IList? ProviderQuotas { get; set; } + /// Action input. + [JsonPropertyName("input")] + public JsonElement? Input { get; set; } - /// Known quota snapshots, keyed by the provider's quota type. - [JsonPropertyName("snapshots")] - public IDictionary Snapshots { get => field ??= new Dictionary(); set; } + /// Canvas instance identifier. + [JsonPropertyName("instanceId")] + public string InstanceId { get; set; } = string.Empty; - /// Upgrade link for a free account. - [JsonPropertyName("upgradeUrl")] - public string? UpgradeUrl { get; set; } -} + /// Session context supplied by the runtime. + [JsonPropertyName("session")] + public CanvasSessionContext? Session { get; set; } -/// Identifies the target session. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionQuotaRefreshRequest -{ /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// A pending session quota warning, consumed once by takeWarnings. +/// Reports a supervised listener's hosting-task termination and cleanup outcome. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class QuotaWarningProjection +public sealed class HostExitedRequest { - /// User-facing warning text. - [JsonPropertyName("message")] - public string Message { get; set; } = string.Empty; + /// Explicit startup or teardown failure, when present. + [JsonPropertyName("error")] + public string? Error { get; set; } - /// Related account or upgrade link. - [JsonPropertyName("url")] - public string? Url { get; set; } + /// Process exit status when available; absent for in-process listener tasks. + [JsonPropertyName("exitCode")] + public long? ExitCode { get; set; } - /// Existing warning category. - [JsonPropertyName("warningType")] - public string WarningType { get; set; } = string.Empty; + /// Listener UUID. + [JsonPropertyName("hostId")] + public string HostId { get; set; } = string.Empty; + + /// Cause of termination. + [JsonPropertyName("reason")] + public HostExitReason Reason { get; set; } } -/// Identifies the target session. +/// Opaque integrator-owned process launch profile for one extension entrypoint. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionQuotaTakeWarningsRequest +public sealed class ExtensionLaunchProfile { - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// 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; } -/// Prediction result. Available results include prediction details; unavailable results include an explicit reason. -/// Polymorphic base type discriminated by kind. +/// The launch profile for a supported entrypoint. Omit launch when the provider does not support the entrypoint. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -[JsonPolymorphic( - TypeDiscriminatorPropertyName = "kind", - UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] -[JsonDerivedType(typeof(SessionLimitPredictionResultAvailable), "available")] -[JsonDerivedType(typeof(SessionLimitPredictionResultUnavailable), "unavailable")] -public partial class SessionLimitPredictionResult +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; } } - -/// Baseline data provenance for a prediction. +/// A discovered extension entrypoint that the registered integrator may classify and resolve to an opaque launch profile. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class SessionLimitPredictionBaselineData +public sealed class ExtensionLaunchProviderResolveRequest { - /// End of the baseline data slice. - [JsonPropertyName("windowEnd")] - public string WindowEnd { get; set; } = string.Empty; + /// Source-qualified extension identifier. + [JsonPropertyName("id")] + public string Id { get; set; } = string.Empty; - /// Start of the baseline data slice. - [JsonPropertyName("windowStart")] - public string WindowStart { get; set; } = string.Empty; + /// Absolute path to the discovered extension entrypoint. + [JsonPropertyName("modulePath")] + public string ModulePath { get; set; } = string.Empty; + + /// Human-readable extension name. + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; + + /// Discovery source for the extension entrypoint. + [JsonPropertyName("source")] + public ExtensionSource Source { get; set; } } -/// Semantic usage tier and its AI-credit cap. +/// Acknowledgement. Returning successfully simply means the SDK accepted the start frame; it does not imply the request will succeed. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class SessionLimitPredictionTierOption +public sealed class LlmInferenceHttpRequestStartResult { - /// AI-credit cap for this tier. - [JsonPropertyName("cap")] - public double Cap { get; set; } - - /// Semantic usage tier. - [JsonPropertyName("tier")] - public SessionLimitPredictionTier Tier { get; set; } } -/// Explainable AI-credit session-limit prediction. +/// The head of an outbound model-layer HTTP request. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class SessionLimitPredictionDetails +public sealed class LlmInferenceHttpRequestStartRequest { - /// Baseline data provenance. - [JsonPropertyName("baselineData")] - public SessionLimitPredictionBaselineData BaselineData { get => field ??= new(); set; } + /// 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; } - /// Client population used for the prediction. - [JsonPropertyName("clientType")] - public SessionLimitPredictionClientType ClientType { 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; } - /// Resolved model family when known. - [JsonPropertyName("family")] - public string? Family { get; set; } + /// HTTP request headers, preserving multiple values per name. + [JsonPropertyName("headers")] + public IDictionary> Headers { get => field ??= new Dictionary>(); set; } - /// Model identifier used for lookup. - [JsonPropertyName("modelId")] - public string ModelId { get; set; } = string.Empty; + /// Coarse classification of the interaction that produced this request. Open string for forward-compatibility; known values include `conversation-agent`, `conversation-subagent`, `conversation-sampling`, `conversation-background`, `conversation-compaction`, and `conversation-user`. Absent when the runtime did not classify the request. Comes from the runtime's per-request agent context independently of transport; on the CAPI transport the runtime derives the upstream `X-Interaction-Type` header from this same context. + [JsonPropertyName("interactionType")] + public string? InteractionType { get; set; } - /// Recommended maximum AI credits for this session. - [JsonPropertyName("recommendedCap")] - public double RecommendedCap { get; set; } + /// HTTP method, e.g. GET, POST. + [JsonPropertyName("method")] + public string Method { get; set; } = string.Empty; - /// Tier chosen as the recommended cap. - [JsonPropertyName("recommendedTier")] - public SessionLimitPredictionTier RecommendedTier { get; set; } + /// 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; } - /// Baseline fallback level used to create the prediction. - [JsonPropertyName("source")] - public SessionLimitPredictionSource Source { 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; - /// Key matched at the source level, such as a model id, family id, or `global`. - [JsonPropertyName("sourceKey")] - public string SourceKey { 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; } - /// Ordered usage tiers and their AI-credit caps. - [JsonPropertyName("tiers")] - public IList Tiers { get => field ??= []; 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; } -/// The available variant of . +/// Acknowledgement. The SDK is free to ignore the ack and treat chunk delivery as fire-and-forget. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class SessionLimitPredictionResultAvailable : SessionLimitPredictionResult +public sealed class LlmInferenceHttpRequestChunkResult { - /// - [JsonIgnore] - public override string Kind => "available"; - - /// Predicted session limit details. - [JsonPropertyName("prediction")] - public required SessionLimitPredictionDetails Prediction { get; set; } } -/// The unavailable variant of . +/// A request body chunk or cancellation signal. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class SessionLimitPredictionResultUnavailable : SessionLimitPredictionResult +public sealed class LlmInferenceHttpRequestChunkRequest { - /// - [JsonIgnore] - public override string Kind => "unavailable"; + /// 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; } - /// Reason no prediction is available. - [JsonPropertyName("reason")] - public required SessionLimitPredictionUnavailableReason Reason { 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; } -/// RPC data type for SessionLimitPredictionPredict operations. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class SessionLimitPredictionPredictRequest -{ - /// Client type to size for. Defaults to `cli-interactive`. - [JsonPropertyName("clientType")] - public SessionLimitPredictionClientType? ClientType { get; set; } + /// When true, the runtime is cancelling the in-flight request (e.g. upstream consumer aborted). `data` is ignored. Implies end-of-request. + [JsonPropertyName("cancel")] + public bool? Cancel { get; set; } - /// Optional model identifier override. If omitted, the session's current model is used. - [JsonPropertyName("modelId")] - public string? ModelId { get; set; } -} + /// Optional human-readable reason for the cancellation, propagated for logging. + [JsonPropertyName("cancelReason")] + public string? CancelReason { get; set; } -/// RPC data type for SessionLimitPredictionPredictRequestWithSession operations. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionLimitPredictionPredictRequestWithSession -{ - /// Client type to size for. Defaults to `cli-interactive`. - [JsonPropertyName("clientType")] - public SessionLimitPredictionClientType? ClientType { get; set; } + /// Body byte range. UTF-8 text when `binary` is absent or false; base64-encoded bytes when `binary` is true. May be empty. + [JsonPropertyName("data")] + public string Data { get; set; } = string.Empty; - /// Optional model identifier override. If omitted, the session's current model is used. - [JsonPropertyName("modelId")] - public string? ModelId { 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; } - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Matches the requestId from the originating httpRequestStart frame. + [JsonPropertyName("requestId")] + public string RequestId { get; set; } = string.Empty; } -/// GitHub URL for the session and a flag indicating whether remote steering is enabled. +/// Client environment metadata describing the process that produced a telemetry event. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class RemoteEnableResult +public sealed class GitHubTelemetryClientInfo { - /// Whether remote steering is enabled. - [JsonPropertyName("remoteSteerable")] - public bool RemoteSteerable { get; set; } + /// Copilot CLI version string. + [JsonPropertyName("cli_version")] + public string CliVersion { get; set; } = string.Empty; - /// GitHub frontend URL for this session. - [Url] - [StringSyntax(StringSyntaxAttribute.Uri)] - [JsonPropertyName("url")] - public string? Url { get; set; } -} + /// Name of the client application. + [JsonPropertyName("client_name")] + public string? ClientName { get; set; } -/// Optional remote session mode ("off", "export", or "on"); defaults to enabling both export and remote steering. -[Experimental(global::GitHub.Copilot.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; } + /// Type of client. + [JsonPropertyName("client_type")] + public string? ClientType { get; set; } - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; -} + /// Copilot subscription plan, when known. + [JsonPropertyName("copilot_plan")] + public string? CopilotPlan { get; set; } -/// Identifies the target session. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionRemoteDisableRequest -{ - /// 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; } -/// 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(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class RemoteNotifySteerableChangedResult -{ -} + /// Distinct CPU model names for the host, comma-separated. + [JsonPropertyName("cpu_model")] + public string? CpuModel { get; set; } -/// New remote-steerability state to persist as a `session.remote_steerable_changed` event. -[Experimental(global::GitHub.Copilot.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; } + /// Stable machine identifier for the device. + [JsonPropertyName("dev_device_id")] + public string? DevDeviceId { get; set; } - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; -} + /// Whether the user is a GitHub/Microsoft staff member. + [JsonPropertyName("is_staff")] + public bool? IsStaff { get; set; } -/// Current sharing status and shareable GitHub URL for a session. -[Experimental(global::GitHub.Copilot.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; } + /// Node.js runtime version string. + [JsonPropertyName("node_version")] + public string NodeVersion { get; set; } = string.Empty; - /// 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; } + /// Operating system architecture (e.g. arm64, x64). + [JsonPropertyName("os_arch")] + public string OsArch { get; set; } = string.Empty; - /// 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; } -} + /// Operating system platform (e.g. darwin, linux, win32). + [JsonPropertyName("os_platform")] + public string OsPlatform { get; set; } = string.Empty; -/// Identifies the target session. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionVisibilityGetRequest -{ - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Operating system version string. + [JsonPropertyName("os_version")] + public string OsVersion { get; set; } = string.Empty; } -/// Effective sharing status and shareable GitHub URL after updating session visibility. +/// 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(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class VisibilitySetResult +public sealed class GitHubTelemetryEvent { - /// 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; } + /// Client environment metadata. + [JsonPropertyName("client")] + public GitHubTelemetryClientInfo? Client { 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; } + /// Copilot tracking ID for user-level attribution. + [JsonPropertyName("copilot_tracking_id")] + public string? CopilotTrackingId { get; set; } - /// Whether the session has been synced to Mission Control (i.e. has a GitHub task). When false, the visibility change could not be applied and `status`/`shareUrl` are absent. - [JsonPropertyName("synced")] - public bool Synced { get; set; } -} + /// Timestamp when the event was created (ISO 8601 format). + [JsonPropertyName("created_at")] + public string? CreatedAt { get; set; } -/// Desired sharing status for the session. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class VisibilitySetRequest -{ - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Experiment assignment context. + [JsonPropertyName("exp_assignment_context")] + public string? ExpAssignmentContext { get; set; } - /// 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; } + /// 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; } } -/// Scheduled prompt entry with ID, timing (`intervalMs`, `cron`, or `at`), prompt text, recurrence, and next run time. +/// 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(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class ScheduleEntry +public sealed class GitHubTelemetryNotification { - /// Absolute fire time (epoch milliseconds) for a one-shot calendar schedule. - [JsonPropertyName("at")] - public long? At { get; set; } - - /// 5-field cron expression for a recurring calendar schedule, evaluated in `tz`. - [JsonPropertyName("cron")] - public string? Cron { get; set; } + /// The telemetry event, in the runtime's native GitHub-shaped telemetry format. + [JsonPropertyName("event")] + public GitHubTelemetryEvent Event { get => field ??= new(); 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; } + /// 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; } - /// Sequential id assigned by the runtime within the session. Stable across resumes (rebuilt from the event log). - [JsonPropertyName("id")] - public long Id { 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; } +} - /// Interval between scheduled ticks, in milliseconds (relative-interval schedules). - [JsonConverter(typeof(MillisecondsTimeSpanConverter))] - [JsonPropertyName("intervalMs")] - public TimeSpan? Interval { get; set; } +/// SDK host response to a GitHub credential request. +/// Polymorphic base type discriminated by kind. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +[JsonPolymorphic( + TypeDiscriminatorPropertyName = "kind", + UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] +[JsonDerivedType(typeof(GitHubTokenAcquireResultToken), "token")] +[JsonDerivedType(typeof(GitHubTokenAcquireResultCancelled), "cancelled")] +public partial class GitHubTokenAcquireResult +{ + /// The type discriminator. + [JsonPropertyName("kind")] + public virtual string Kind { get; set; } = string.Empty; +} - /// 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; +/// The token variant of . +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public partial class GitHubTokenAcquireResultToken : GitHubTokenAcquireResult +{ + /// + [JsonIgnore] + public override string Kind => "token"; - /// Whether the schedule re-arms after each tick (`/every`) or fires once (`/after`). - [JsonPropertyName("recurring")] - public bool Recurring { get; set; } + /// GitHub access token acquired by the SDK host. + [JsonPropertyName("accessToken")] + public required string AccessToken { 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; } + /// 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; } - /// IANA timezone the `cron` expression is evaluated in. - [JsonPropertyName("tz")] - public string? Tz { get; set; } + /// OAuth token type. Defaults to bearer when omitted. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("tokenType")] + public string? TokenType { get; set; } } -/// Snapshot of the currently active recurring prompts for this session. +/// The cancelled variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class ScheduleList +public partial class GitHubTokenAcquireResultCancelled : GitHubTokenAcquireResult { - /// Active scheduled prompts, ordered by id. - [JsonPropertyName("entries")] - public IList Entries { get => field ??= []; set; } + /// + [JsonIgnore] + public override string Kind => "cancelled"; } -/// Identifies the target session. +/// Asks the SDK client to acquire a GitHub access token from an opaque callback registration. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionScheduleListRequest +public sealed class GitHubTokenAcquireRequest { - /// Target session identifier. + /// Effective GitHub host for which the callback must return a token. + [JsonPropertyName("host")] + public string Host { get; set; } = string.Empty; + + /// Why the runtime is requesting a GitHub credential. + [JsonPropertyName("reason")] + public GitHubTokenAcquireReason Reason { get; set; } + + /// 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; } = string.Empty; + public string? SessionId { get; set; } } -/// Identifies the target session. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionScheduleHydrateRequest +/// A response is meaningful only on the connection and request that issued its challenge. +public sealed class InstallationsConfirmResult { - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Exact challenge from the request. + [JsonPropertyName("confirmationId")] + public string ConfirmationId { get; set; } = string.Empty; + + /// Fresh explicit user decision. There is no default. + [JsonPropertyName("decision")] + public InstallationDecision Decision { get; set; } + + /// Exact review commitment from the request. + [JsonPropertyName("reviewFingerprint")] + public string ReviewFingerprint { get; set; } = string.Empty; } -/// Whether the session currently has an active self-paced schedule. +/// Only resource kinds with an implemented installation engine have a review variant. +/// Polymorphic base type discriminated by resource. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class ScheduleHasSelfPacedResult +[JsonPolymorphic( + TypeDiscriminatorPropertyName = "resource", + UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] +[JsonDerivedType(typeof(InstallationReviewMcp), "mcp")] +[JsonDerivedType(typeof(InstallationReviewSkill), "skill")] +public partial class InstallationReview { - /// True when at least one active schedule is self-paced. - [JsonPropertyName("hasSelfPaced")] - public bool HasSelfPaced { get; set; } + /// The type discriminator. + [JsonPropertyName("resource")] + public virtual string Resource { get; set; } = string.Empty; } -/// Identifies the target session. + +/// Safe MCP review fields. No raw card, retrieval URL, plan handle or secret value. +/// Polymorphic base type discriminated by action. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class SessionScheduleHasSelfPacedRequest +[JsonPolymorphic( + TypeDiscriminatorPropertyName = "action", + UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] +[JsonDerivedType(typeof(McpInstallationReviewInstall), "install")] +[JsonDerivedType(typeof(McpInstallationReviewUninstall), "uninstall")] +public partial class McpInstallationReview { - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// The type discriminator. + [JsonPropertyName("action")] + public virtual string Action { get; set; } = string.Empty; } -/// Result of registering or re-arming a scheduled prompt. + +/// +/// Final remote configuration, not a template. The producer refuses external-value +/// expansion before presenting this review. Receipt-owned secrets appear only as +/// `${installation-secret:<id>}` references whose `<id>` matches a reviewed +/// `${secret:<id>}` placeholder; values are never included. +/// [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class ScheduleAddResult +public sealed class McpInstallationRemoteConfiguration { - /// The registered or updated schedule entry. - [JsonPropertyName("entry")] - public ScheduleEntry? Entry { get; set; } + /// + /// Configured headers, excluding separately authorised OAuth tokens. Values may + /// contain owned secret references, never secret values. + /// + [JsonPropertyName("headers")] + public IDictionary Headers { get => field ??= new Dictionary(); set; } - /// User-facing validation error, when registration failed. - [JsonPropertyName("error")] - public string? Error { get; set; } + /// Configured tool selection, not permission to invoke those tools. + [JsonPropertyName("tools")] + public IList Tools { get => field ??= []; set; } + + /// Transport in the effective persisted remote configuration. + [JsonPropertyName("transport")] + public McpPlanRemoteTransport Transport { get; set; } + + /// Exact resolved endpoint, without templates or secret placeholders. + [JsonPropertyName("url")] + public string Url { get; set; } = string.Empty; } -/// Register a relative-interval scheduled prompt. +/// The install variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class ScheduleAddRequest +public partial class McpInstallationReviewInstall : McpInstallationReview { - /// Optional display-only prompt label. - [JsonPropertyName("displayPrompt")] - public string? DisplayPrompt { get; set; } + /// + [JsonIgnore] + public override string Action => "install"; - /// Human-readable interval such as `30s`, `5m`, or `2h`. - [JsonPropertyName("interval")] - public string Interval { get; set; } = string.Empty; + /// Catalogue identity retained from the bound candidate when available. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("catalogue")] + public InstallationCatalogueIdentity? Catalogue { get; set; } - /// Prompt text to enqueue when the schedule fires. - [JsonPropertyName("prompt")] - public string Prompt { get; set; } = string.Empty; + /// Original catalogue trust metadata, not a verification claim. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("catalogueTrust")] + public CatalogTrustSnapshot? CatalogueTrust { get; set; } - /// Whether the schedule should re-arm after each tick. Defaults to true. - [JsonPropertyName("recurring")] - public bool? Recurring { get; set; } + /// The configuration change for the selected alternative only. + [JsonPropertyName("configurationChange")] + public required McpPlanConfigurationChange ConfigurationChange { get; set; } - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; -} + /// + /// Complete effective remote configuration for final installation review. + /// Earlier private selection reviews and package choices omit this field. + /// The owned remote resource requires it before issuing confirmation. + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("effectiveConfiguration")] + public McpInstallationRemoteConfiguration? EffectiveConfiguration { get; set; } -/// Register a cron scheduled prompt. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class ScheduleAddCronRequest -{ - /// 5-field cron expression. - [JsonPropertyName("cron")] - public string Cron { get; set; } = string.Empty; + /// Identity from the retained plan, not caller display text. + [JsonPropertyName("identity")] + public required McpPlanResourceIdentity Identity { get; set; } - /// Optional display-only prompt label. - [JsonPropertyName("displayPrompt")] - public string? DisplayPrompt { get; set; } + /// Non-secret values supplied for this selected alternative. + [JsonPropertyName("inputs")] + public required IList Inputs { get; set; } - /// Prompt text to enqueue when the schedule fires. - [JsonPropertyName("prompt")] - public string Prompt { get; set; } = string.Empty; + /// Policy decision bound to this plan. + [JsonPropertyName("policy")] + public required McpPlanPolicyResult Policy { get; set; } - /// Whether the schedule should re-arm after each tick. Defaults to true. - [JsonPropertyName("recurring")] - public bool? Recurring { get; set; } + /// Original source identity and content commitment. + [JsonPropertyName("provenance")] + public required McpPlanProvenance Provenance { get; set; } - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Explicit reviewed backend selection; no backend is accessed when no secrets are supplied. + [JsonPropertyName("secretStorage")] + public required McpInstallationSecretStorage SecretStorage { get; set; } - /// IANA timezone for evaluating the cron expression. - [JsonPropertyName("tz")] - public string? Tz { get; set; } + /// Only the selected alternative is applied. + [JsonPropertyName("selectedChoice")] + public required McpPlanTransportChoice SelectedChoice { get; set; } + + /// Exact reviewed placeholders supplied separately. Never secret values. + [JsonPropertyName("suppliedSecrets")] + public required IList SuppliedSecrets { get; set; } + + /// Exact reviewed user-scope destination. + [JsonPropertyName("target")] + public required McpPlanTarget Target { get; set; } } -/// Register an absolute-time scheduled prompt. +/// The uninstall variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class ScheduleAddAtRequest +public partial class McpInstallationReviewUninstall : McpInstallationReview { - /// Epoch milliseconds when the prompt should fire. - [JsonPropertyName("at")] - public long At { get; set; } + /// + [JsonIgnore] + public override string Action => "uninstall"; - /// Optional display-only prompt label. - [JsonPropertyName("displayPrompt")] - public string? DisplayPrompt { get; set; } + /// Identity from the installed receipt. + [JsonPropertyName("identity")] + public required McpPlanResourceIdentity Identity { get; set; } - /// Prompt text to enqueue when the schedule fires. - [JsonPropertyName("prompt")] - public string Prompt { get; set; } = string.Empty; + /// Receipt-owned installation being removed. + [JsonPropertyName("installationId")] + public required string InstallationId { get; set; } - /// Whether the schedule should re-arm after each tick. Defaults to false. - [JsonPropertyName("recurring")] - public bool? Recurring { get; set; } + /// Exact planner-owned secret slots to remove, excluding shared OAuth grants. + [JsonPropertyName("ownedSecretCount")] + public required long OwnedSecretCount { get; set; } - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; -} + /// Current removal policy, independent of permission to activate the server. + [JsonPropertyName("policy")] + public required McpPlanPolicyResult Policy { get; set; } -/// Register a self-paced scheduled prompt. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class ScheduleAddSelfPacedRequest -{ - /// Optional display-only prompt label. - [JsonPropertyName("displayPrompt")] - public string? DisplayPrompt { get; set; } + /// Shared profile authentication is deliberately retained, not pending cleanup. + [JsonPropertyName("preservesSharedAuthentication")] + public required bool PreservesSharedAuthentication { get; set; } - /// Prompt text to enqueue when the schedule fires. - [JsonPropertyName("prompt")] - public string Prompt { get; set; } = string.Empty; + /// Source identity and content commitment retained by the installed receipt. + [JsonPropertyName("provenance")] + public required McpPlanProvenance Provenance { get; set; } - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Whether uninstall restores a protected pre-install configuration. + [JsonPropertyName("restoresPreviousConfiguration")] + public required bool RestoresPreviousConfiguration { get; set; } + + /// Exact destination, checked for intervening changes before mutation. + [JsonPropertyName("target")] + public required McpPlanTarget Target { get; set; } } -/// Re-arm a self-paced scheduled prompt. +/// The mcp variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class ScheduleRearmSelfPacedRequest +public partial class InstallationReviewMcp : InstallationReview { - /// Epoch milliseconds when the prompt should next fire. - [JsonPropertyName("at")] - public long At { get; set; } - - /// Id of the self-paced scheduled prompt. - [JsonPropertyName("id")] - public long Id { get; set; } + /// + [JsonIgnore] + public override string Resource => "mcp"; - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// The exact MCP action and its reviewed changes. + [JsonPropertyName("review")] + public required McpInstallationReview Review { get; set; } } -/// Remove a scheduled prompt by id. The result entry is omitted if the id was unknown. +/// The skill variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class ScheduleStopResult +public partial class InstallationReviewSkill : InstallationReview { - /// The removed entry, or omitted if no entry matched. - [JsonPropertyName("entry")] - public ScheduleEntry? Entry { get; set; } + /// + [JsonIgnore] + public override string Resource => "skill"; + + /// The exact verified Skill action and its reviewed files. + [JsonPropertyName("review")] + public required SkillInstallationReview Review { get; set; } } -/// Identifier of the scheduled prompt to remove. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -internal sealed class ScheduleStopRequest +/// One connection-owned, expiring request for a trusted host's explicit user decision. +public sealed class InstallationsConfirmRequest { - /// Id of the scheduled prompt to remove. - [JsonPropertyName("id")] - public long Id { get; set; } + /// Opaque one-use challenge. Return unchanged; never log or persist. + [JsonPropertyName("confirmationId")] + public string ConfirmationId { get; set; } = string.Empty; - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; -} + /// Original plan expiry as an ISO 8601 timestamp. Confirmation never extends it. + [JsonPropertyName("expiresAt")] + public string ExpiresAt { 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(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class ProviderTokenAcquireResult -{ - /// The bearer token value (without the `Bearer ` prefix). - [JsonPropertyName("token")] - public string Token { get; set; } = string.Empty; -} + /// Random identifier of this installation operation, not a plan handle. + [JsonPropertyName("operationId")] + public string OperationId { get; set; } = string.Empty; -/// Asks the SDK client to acquire a bearer token for a BYOK provider whose config set `hasBearerTokenProvider: true`. Issued by the runtime before each outbound model request; the runtime does no caching, so this is sent once per request. -[Experimental(global::GitHub.Copilot.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 the configured provider name. - [JsonPropertyName("providerName")] - public string ProviderName { get; set; } = string.Empty; + /// + /// Original engine-resolved selector for a bound operation, never a dispatch default. + /// Bound MCP confirmation always includes it; correlate it with the original pending action. + /// + [JsonPropertyName("policySessionId")] + public string? PolicySessionId { get; set; } - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; -} + /// Resource-specific review to present before collecting the user's decision. + [JsonPropertyName("review")] + public InstallationReview Review { get => field ??= new(); set; } -/// Result returned by an extension workflow closure. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class WorkflowExecuteResult -{ - /// Workflow result value. - [JsonPropertyName("result")] - public JsonElement? Result { get; set; } + /// Opaque commitment to the exact review and inputs. Return unchanged; never log. + [JsonPropertyName("reviewFingerprint")] + public string ReviewFingerprint { get; set; } = string.Empty; } -/// Parameters sent to the owning extension to execute a workflow closure. +/// Closed set of public task kinds a connection can negotiate. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class WorkflowExecuteRequest +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct TaskKind : IEquatable { - /// Workflow input value. - [JsonPropertyName("args")] - public JsonElement Args { get; set; } + private readonly string? _value; - /// Opaque token identifying this workflow execution attempt. - [JsonPropertyName("executionToken")] - public string ExecutionToken { get; set; } = string.Empty; + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public TaskKind(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } - /// Registered workflow name. - [JsonPropertyName("name")] - public string Name { get; set; } = string.Empty; + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; - /// Workflow run identifier. - [JsonPropertyName("runId")] - public string RunId { get; set; } = string.Empty; + /// Runtime-owned background agent task. + public static TaskKind Agent { get; } = new("agent"); - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; -} + /// Runtime-owned shell task. + public static TaskKind Shell { get; } = new("shell"); -/// Parameters for cooperatively aborting a workflow body. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class WorkflowAbortRequest -{ - /// Opaque token identifying the execution attempt to abort. - [JsonPropertyName("executionToken")] - public string ExecutionToken { get; set; } = string.Empty; + /// Client-owned externally executed task. + public static TaskKind Client { get; } = new("client"); - /// Workflow run identifier. - [JsonPropertyName("runId")] - public string RunId { get; set; } = string.Empty; + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(TaskKind left, TaskKind 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 !=(TaskKind left, TaskKind right) => !(left == right); -/// Whether the client authoritatively confirmed its external work stopped. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class ClientTaskCancelResult -{ - /// True only when the owner confirms that external work stopped before responding. - [JsonPropertyName("cancelled")] - public bool Cancelled { get; set; } -} + /// + public override bool Equals(object? obj) => obj is TaskKind other && Equals(other); -/// Runtime-to-owner cancellation request for a client-owned task. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class ClientTaskCancelRequest -{ - /// Opaque identifier shared by coalesced cancellation callers. - [JsonPropertyName("cancellationId")] - public string CancellationId { get; set; } = string.Empty; + /// + public bool Equals(TaskKind other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); - /// Owner-scoped task key included for correlation. - [JsonPropertyName("clientTaskId")] - public string ClientTaskId { get; set; } = string.Empty; + /// + public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); - /// Canonical runtime-generated task identifier. - [JsonPropertyName("id")] - public string Id { get; set; } = string.Empty; + /// + public override string ToString() => Value; - /// Reason the runtime requests cancellation. - [JsonPropertyName("reason")] - public ClientTaskCancelReason Reason { get; set; } + /// Provides a for serializing instances. + [EditorBrowsable(EditorBrowsableState.Never)] + public sealed class Converter : JsonConverter + { + /// + public override TaskKind Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } - /// Session that owns the client task. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// + public override void Write(Utf8JsonWriter writer, TaskKind value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(TaskKind)); + } + } } -/// Describes a filesystem error. + +/// State of the persistent certificate authority of the sandbox credential proxy. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class SessionFsError +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct SandboxProxyCaState : IEquatable { - /// Error classification. - [JsonPropertyName("code")] - public SessionFsErrorCode Code { get; set; } + private readonly string? _value; - /// Free-form detail about the error, for logging/diagnostics. - [JsonPropertyName("message")] - public string? Message { get; set; } + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public SandboxProxyCaState(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } - /// For failed writeFile requests only: true if the provider changed the target before failing. Omit when unknown or unchanged. - [JsonPropertyName("writeChanged")] - public bool? WriteChanged { get; set; } -} + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; -/// File content as a UTF-8 string, or a filesystem error if the read failed. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class SessionFsReadFileResult -{ - /// File content as UTF-8 string. - [JsonPropertyName("content")] - public string Content { get; set; } = string.Empty; + /// This platform has no supported OS trust store. The proxy uses a per-process certificate bundle. + public static SandboxProxyCaState Unsupported { get; } = new("unsupported"); - /// Describes a filesystem error. - [JsonPropertyName("error")] - public SessionFsError? Error { get; set; } -} + /// OS trust does not include the certificate authority, or none is stored. + public static SandboxProxyCaState NotInstalled { get; } = new("notInstalled"); -/// Path of the file to read from the client-provided session filesystem. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class SessionFsReadFileRequest -{ - /// Path using SessionFs conventions. - [JsonPropertyName("path")] - public string Path { get; set; } = string.Empty; + /// OS trust includes the stored certificate authority. + public static SandboxProxyCaState Installed { get; } = new("installed"); + + /// The runtime could not read the certificate authority or the OS trust store. + public static SandboxProxyCaState Error { get; } = new("error"); + + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(SandboxProxyCaState left, SandboxProxyCaState right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(SandboxProxyCaState left, SandboxProxyCaState right) => !(left == right); + + /// + public override bool Equals(object? obj) => obj is SandboxProxyCaState other && Equals(other); + + /// + public bool Equals(SandboxProxyCaState other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + + /// + public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); + + /// + 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 SandboxProxyCaState Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } + + /// + public override void Write(Utf8JsonWriter writer, SandboxProxyCaState value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(SandboxProxyCaState)); + } + } } -/// File bytes as standard base64, or a filesystem error if the read failed. + +/// What the one-time staff update-channel migration did. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class SessionFsReadFileBytesResult +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct StaffUpdateChannelMigrationStatus : IEquatable { - /// Exact file bytes encoded as standard base64. - [JsonPropertyName("content")] - public string Content { get; set; } = string.Empty; + private readonly string? _value; - /// Describes a filesystem error. - [JsonPropertyName("error")] - public SessionFsError? Error { get; set; } -} + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public StaffUpdateChannelMigrationStatus(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } -/// Path of the binary file to read from the client-provided session filesystem. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class SessionFsReadFileBytesRequest -{ - /// Path using SessionFs conventions. - [JsonPropertyName("path")] - public string Path { 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; -} + /// The account is not a staff account, so the migration does not apply and nothing was recorded. + public static StaffUpdateChannelMigrationStatus Ineligible { get; } = new("ineligible"); -/// File path, content to write, and optional mode for the client-provided session filesystem. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class SessionFsWriteFileRequest -{ - /// Content to write. - [JsonPropertyName("content")] - public string Content { get; set; } = string.Empty; + /// The migration already ran within the cutoff window; the stored completion timestamp is returned unchanged. + public static StaffUpdateChannelMigrationStatus Current { get; } = new("current"); - /// Optional POSIX-style mode for newly created files. - [JsonPropertyName("mode")] - public long? Mode { get; set; } + /// The persisted update channel was changed to stable. Only this outcome requires the host to reload its configuration. + public static StaffUpdateChannelMigrationStatus Migrated { get; } = new("migrated"); - /// Path using SessionFs conventions. - [JsonPropertyName("path")] - public string Path { get; set; } = string.Empty; + /// The persisted channel was already stable, so nothing changed, but the migration is now recorded as done. + public static StaffUpdateChannelMigrationStatus Completed { get; } = new("completed"); - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; -} + /// The account is on the experimental channel, which the migration deliberately leaves alone, and the migration is recorded as done. + public static StaffUpdateChannelMigrationStatus ExcludedExperimental { get; } = new("excluded-experimental"); -/// File path, standard-base64-encoded bytes to write, and optional mode for the client-provided session filesystem. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class SessionFsWriteFileBytesRequest -{ - /// Exact file bytes encoded as standard base64. - [JsonPropertyName("content")] - public string Content { get; set; } = string.Empty; + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(StaffUpdateChannelMigrationStatus left, StaffUpdateChannelMigrationStatus right) => left.Equals(right); - /// Optional POSIX-style mode for newly created files. - [JsonPropertyName("mode")] - public long? Mode { get; set; } + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(StaffUpdateChannelMigrationStatus left, StaffUpdateChannelMigrationStatus right) => !(left == right); - /// Path using SessionFs conventions. - [JsonPropertyName("path")] - public string Path { get; set; } = string.Empty; + /// + public override bool Equals(object? obj) => obj is StaffUpdateChannelMigrationStatus other && Equals(other); - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; -} + /// + public bool Equals(StaffUpdateChannelMigrationStatus other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); -/// File path, content to append, and optional mode for the client-provided session filesystem. Implementations create parent directories as needed. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class SessionFsAppendFileRequest -{ - /// Content to append. - [JsonPropertyName("content")] - public string Content { get; set; } = string.Empty; + /// + public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); - /// Optional POSIX-style mode for newly created files. - [JsonPropertyName("mode")] - public long? Mode { get; set; } + /// + public override string ToString() => Value; - /// 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 StaffUpdateChannelMigrationStatus 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, StaffUpdateChannelMigrationStatus value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(StaffUpdateChannelMigrationStatus)); + } + } } -/// Indicates whether the requested path exists in the client-provided session filesystem. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class SessionFsExistsResult -{ - /// Whether the path exists. - [JsonPropertyName("exists")] - public bool Exists { get; set; } -} -/// Path to test for existence in the client-provided session filesystem. +/// What the one-time staff log-level migration did. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class SessionFsExistsRequest +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct StaffLogLevelMigrationStatus : 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 StaffLogLevelMigrationStatus(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } -/// Filesystem metadata for the requested path, or a filesystem error if the stat failed. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class SessionFsStatResult -{ - /// ISO 8601 timestamp of creation. - [JsonPropertyName("birthtime")] - public DateTimeOffset Birthtime { get; set; } + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; - /// Describes a filesystem error. - [JsonPropertyName("error")] - public SessionFsError? Error { get; set; } + /// The account is not a staff account, so the migration does not apply and nothing was recorded. + public static StaffLogLevelMigrationStatus Ineligible { get; } = new("ineligible"); - /// Whether the path is a directory. - [JsonPropertyName("isDirectory")] - public bool IsDirectory { get; set; } + /// The migration already ran; the stored completion timestamp is returned unchanged. + public static StaffLogLevelMigrationStatus Current { get; } = new("current"); - /// Whether the path is a file. - [JsonPropertyName("isFile")] - public bool IsFile { get; set; } + /// The migration ran now. The persisted settings may have changed, so the host should adopt the returned settings when they are present. + public static StaffLogLevelMigrationStatus Refreshed { get; } = new("refreshed"); - /// ISO 8601 timestamp of last modification. - [JsonPropertyName("mtime")] - public DateTimeOffset Mtime { get; set; } + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(StaffLogLevelMigrationStatus left, StaffLogLevelMigrationStatus right) => left.Equals(right); - /// File size in bytes. - [JsonPropertyName("size")] - public long Size { get; set; } -} + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(StaffLogLevelMigrationStatus left, StaffLogLevelMigrationStatus right) => !(left == right); -/// Path whose metadata should be returned from the client-provided session filesystem. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class SessionFsStatRequest -{ - /// Path using SessionFs conventions. - [JsonPropertyName("path")] - public string Path { get; set; } = string.Empty; + /// + public override bool Equals(object? obj) => obj is StaffLogLevelMigrationStatus other && Equals(other); - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; -} + /// + public bool Equals(StaffLogLevelMigrationStatus other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); -/// Directory path to create in the client-provided session filesystem, with options for recursive creation and POSIX mode. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class SessionFsMkdirRequest -{ - /// Optional POSIX-style mode for newly created directories. - [JsonPropertyName("mode")] - public long? Mode { get; set; } + /// + public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); - /// Path using SessionFs conventions. - [JsonPropertyName("path")] - public string Path { get; set; } = string.Empty; + /// + public override string ToString() => Value; - /// Create parent directories as needed. - [JsonPropertyName("recursive")] - public bool? Recursive { get; set; } + /// Provides a for serializing instances. + [EditorBrowsable(EditorBrowsableState.Never)] + public sealed class Converter : JsonConverter + { + /// + public override StaffLogLevelMigrationStatus 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, StaffLogLevelMigrationStatus value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(StaffLogLevelMigrationStatus)); + } + } } -/// Names of entries in the requested directory, or a filesystem error if the read failed. + +/// What the one-time obsolete-settings cleanup did to the document. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class SessionFsReaddirResult +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct UserSettingsMigrateObsoleteOnDiskStatus : IEquatable { - /// Entry names in the directory. - [JsonPropertyName("entries")] - public IList Entries { get => field ??= []; set; } + private readonly string? _value; - /// Describes a filesystem error. - [JsonPropertyName("error")] - public SessionFsError? Error { get; set; } -} + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public UserSettingsMigrateObsoleteOnDiskStatus(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } -/// Directory path whose entries should be listed from the client-provided session filesystem. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class SessionFsReaddirRequest -{ - /// Path using SessionFs conventions. - [JsonPropertyName("path")] - public string Path { 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; -} + /// No settings document exists, so there was nothing to clean. + public static UserSettingsMigrateObsoleteOnDiskStatus Missing { get; } = new("missing"); -/// Directory entry returned by session filesystem `readdirWithTypes`, with name and entry type. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class SessionFsReaddirWithTypesEntry -{ - /// Entry name. - [JsonPropertyName("name")] - public string Name { get; set; } = string.Empty; + /// The document could not be parsed, so it was left untouched rather than rewritten from a partial read. + public static UserSettingsMigrateObsoleteOnDiskStatus Invalid { get; } = new("invalid"); - /// Entry type. - [JsonPropertyName("type")] - public SessionFsReaddirWithTypesEntryType Type { get; set; } -} + /// The document parsed and held nothing obsolete, so nothing was written. + public static UserSettingsMigrateObsoleteOnDiskStatus Unchanged { get; } = new("unchanged"); -/// Entries in the requested directory paired with file/directory type information, or a filesystem error if the read failed. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class SessionFsReaddirWithTypesResult -{ - /// Directory entries with type information. - [JsonPropertyName("entries")] - public IList Entries { get => field ??= []; set; } + /// Obsolete keys were removed and the document was rewritten. + public static UserSettingsMigrateObsoleteOnDiskStatus Changed { get; } = new("changed"); - /// Describes a filesystem error. - [JsonPropertyName("error")] - public SessionFsError? Error { get; set; } -} + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(UserSettingsMigrateObsoleteOnDiskStatus left, UserSettingsMigrateObsoleteOnDiskStatus right) => left.Equals(right); -/// Directory path whose entries (with type information) should be listed from the client-provided session filesystem. -[Experimental(global::GitHub.Copilot.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 !=(UserSettingsMigrateObsoleteOnDiskStatus left, UserSettingsMigrateObsoleteOnDiskStatus right) => !(left == right); - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; -} + /// + public override bool Equals(object? obj) => obj is UserSettingsMigrateObsoleteOnDiskStatus other && Equals(other); -/// Path to remove from the client-provided session filesystem, with options for recursive removal and force. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class SessionFsRmRequest -{ - /// Ignore errors if the path does not exist. - [JsonPropertyName("force")] - public bool? Force { get; set; } + /// + public bool Equals(UserSettingsMigrateObsoleteOnDiskStatus 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; - /// 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 UserSettingsMigrateObsoleteOnDiskStatus Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } + + /// + public override void Write(Utf8JsonWriter writer, UserSettingsMigrateObsoleteOnDiskStatus value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(UserSettingsMigrateObsoleteOnDiskStatus)); + } + } } -/// Source and destination paths for renaming or moving an entry in the client-provided session filesystem. + +/// Whether one atomic settings edit changed a file. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class SessionFsRenameRequest +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct SettingsEditStatus : 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 SettingsEditStatus(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(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class SessionFsSqliteQueryResult -{ - /// Column names from the result set. - [JsonPropertyName("columns")] - public IList Columns { get => field ??= []; set; } + /// The edit changed the setting and was stored. + public static SettingsEditStatus Changed { get; } = new("changed"); - /// Describes a filesystem error. - [JsonPropertyName("error")] - public SessionFsError? Error { get; set; } + /// The setting already had the requested state. + public static SettingsEditStatus Unchanged { get; } = new("unchanged"); - /// SQLite last_insert_rowid() value for INSERT. - [JsonPropertyName("lastInsertRowid")] - public long? LastInsertRowid { get; set; } + /// The existing settings file could not be edited safely; nothing was written. + public static SettingsEditStatus Invalid { get; } = new("invalid"); - /// For SELECT: array of row objects. For others: empty array. - [JsonPropertyName("rows")] - public IList> Rows { get => field ??= []; set; } + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(SettingsEditStatus left, SettingsEditStatus right) => left.Equals(right); - /// Number of rows affected (for INSERT/UPDATE/DELETE). - [JsonPropertyName("rowsAffected")] - public long RowsAffected { get; set; } -} + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(SettingsEditStatus left, SettingsEditStatus right) => !(left == 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(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class SessionFsSqliteQueryRequest -{ - /// Optional named bind parameters. - [JsonPropertyName("params")] - public IDictionary? Params { get; set; } + /// + public override bool Equals(object? obj) => obj is SettingsEditStatus other && Equals(other); - /// SQL query to execute. - [JsonPropertyName("query")] - public string Query { get; set; } = string.Empty; + /// + public bool Equals(SettingsEditStatus other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); - /// 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 override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; -} + /// + public override string ToString() => Value; -/// Classified SQLite transaction failure. busyOrLocked guarantees rollback; postCommitAmbiguous must never be retried. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class SessionFsSqliteTransactionError -{ - /// Machine-readable classification of the transaction failure. - [JsonPropertyName("errorClass")] - public SessionFsSqliteTransactionErrorClass ErrorClass { get; set; } + /// Provides a for serializing instances. + [EditorBrowsable(EditorBrowsableState.Never)] + public sealed class Converter : JsonConverter + { + /// + public override SettingsEditStatus Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } - /// Human-readable transaction failure message. - [JsonPropertyName("message")] - public string Message { get; set; } = string.Empty; + /// + public override void Write(Utf8JsonWriter writer, SettingsEditStatus value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(SettingsEditStatus)); + } + } } -/// Per-statement results, or a classified transaction error. + +/// One atomic settings mutation. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class SessionFsSqliteTransactionResult +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct SettingsEditOperation : IEquatable { - /// Classified transaction failure, when execution did not succeed. - [JsonPropertyName("error")] - public SessionFsSqliteTransactionError? Error { get; set; } + private readonly string? _value; - /// Per-statement query results in input order. - [JsonPropertyName("results")] - public IList Results { get => field ??= []; set; } -} + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public SettingsEditOperation(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } -/// One statement in an atomic SQLite transaction. -[Experimental(global::GitHub.Copilot.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; + /// Set one scalar value. + public static SettingsEditOperation Set { get; } = new("set"); - /// How to execute the statement. - [JsonPropertyName("queryType")] - public SessionFsSqliteQueryType QueryType { get; set; } -} + /// Add one string to a list, unless present. + public static SettingsEditOperation AddItem { get; } = new("add-item"); -/// Statements to execute atomically. Providers apply busy handling for every call. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class SessionFsSqliteTransactionRequest -{ - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Remove one string from a list, unless absent. + public static SettingsEditOperation RemoveItem { get; } = new("remove-item"); - /// Ordered SQL statements to execute in one transaction. - [JsonPropertyName("statements")] - public IList Statements { get => field ??= []; set; } -} + /// Remove the setting without supplying a value. + public static SettingsEditOperation Remove { get; } = new("remove"); -/// Indicates whether the per-session SQLite database already exists. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class SessionFsSqliteExistsResult -{ - /// Whether the session database already exists. - [JsonPropertyName("exists")] - public bool Exists { get; set; } -} + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(SettingsEditOperation left, SettingsEditOperation right) => left.Equals(right); -/// Identifies the target session. -public sealed class SessionFsSqliteExistsRequest -{ - /// 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 !=(SettingsEditOperation left, SettingsEditOperation right) => !(left == right); -/// Canvas open result returned by the provider. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class CanvasProviderOpenResult -{ - /// Provider-supplied status text. - [JsonPropertyName("status")] - public string? Status { get; set; } + /// + public override bool Equals(object? obj) => obj is SettingsEditOperation other && Equals(other); - /// Provider-supplied title. - [JsonPropertyName("title")] - public string? Title { get; set; } + /// + public bool Equals(SettingsEditOperation other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); - /// URL for web-rendered canvases. - [JsonPropertyName("url")] - public string? Url { get; set; } -} + /// + public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); -/// Host capabilities. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class CanvasHostContextCapabilities -{ - /// Whether canvas rendering is supported. - [JsonPropertyName("canvases")] - public bool? Canvases { get; set; } -} + /// + public override string ToString() => Value; -/// Host context supplied by the runtime. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class CanvasHostContext -{ - /// Host capabilities. - [JsonPropertyName("capabilities")] - public CanvasHostContextCapabilities? Capabilities { get; set; } -} + /// Provides a for serializing instances. + [EditorBrowsable(EditorBrowsableState.Never)] + public sealed class Converter : JsonConverter + { + /// + public override SettingsEditOperation Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } -/// Session context supplied by the runtime. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class CanvasSessionContext -{ - /// Active session working directory, when known. - [JsonPropertyName("workingDirectory")] - public string? WorkingDirectory { get; set; } + /// + public override void Write(Utf8JsonWriter writer, SettingsEditOperation value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(SettingsEditOperation)); + } + } } -/// Canvas open parameters sent to the provider. + +/// Which repository settings file to edit. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class CanvasProviderOpenRequest +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct RepoSettingsEditScope : IEquatable { - /// Provider-local canvas identifier. - [JsonPropertyName("canvasId")] - public string CanvasId { get; set; } = string.Empty; - - /// Owning provider identifier. - [JsonPropertyName("extensionId")] - public string ExtensionId { get; set; } = string.Empty; + private readonly string? _value; - /// Host context supplied by the runtime. - [JsonPropertyName("host")] - public CanvasHostContext? Host { get; set; } + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public RepoSettingsEditScope(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } - /// Canvas open input. - [JsonPropertyName("input")] - public JsonElement? Input { get; set; } + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; - /// Stable caller-supplied canvas instance identifier. - [JsonPropertyName("instanceId")] - public string InstanceId { get; set; } = string.Empty; + /// The shared repository settings file. + public static RepoSettingsEditScope Repo { get; } = new("repo"); - /// Session context supplied by the runtime. - [JsonPropertyName("session")] - public CanvasSessionContext? Session { get; set; } + /// The local repository settings file. + public static RepoSettingsEditScope Local { get; } = new("local"); - /// 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 ==(RepoSettingsEditScope left, RepoSettingsEditScope right) => left.Equals(right); -/// Canvas close parameters sent to the provider. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class CanvasProviderCloseRequest -{ - /// Provider-local canvas identifier. - [JsonPropertyName("canvasId")] - public string CanvasId { get; set; } = string.Empty; + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(RepoSettingsEditScope left, RepoSettingsEditScope right) => !(left == right); - /// Owning provider identifier. - [JsonPropertyName("extensionId")] - public string ExtensionId { get; set; } = string.Empty; + /// + public override bool Equals(object? obj) => obj is RepoSettingsEditScope other && Equals(other); - /// Host context supplied by the runtime. - [JsonPropertyName("host")] - public CanvasHostContext? Host { get; set; } + /// + public bool Equals(RepoSettingsEditScope other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); - /// Canvas instance identifier. - [JsonPropertyName("instanceId")] - public string InstanceId { get; set; } = string.Empty; + /// + public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); - /// Session context supplied by the runtime. - [JsonPropertyName("session")] - public CanvasSessionContext? Session { 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 RepoSettingsEditScope Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } + + /// + public override void Write(Utf8JsonWriter writer, RepoSettingsEditScope value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(RepoSettingsEditScope)); + } + } } -/// Canvas action invocation parameters sent to the provider. + +/// The population a feature flag is declared for, before overrides. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class CanvasProviderInvokeActionRequest +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct FeatureFlagAvailability : IEquatable { - /// Action name to invoke. - [JsonPropertyName("actionName")] - public string ActionName { get; set; } = string.Empty; + private readonly string? _value; - /// Provider-local canvas identifier. - [JsonPropertyName("canvasId")] - public string CanvasId { get; set; } = string.Empty; + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public FeatureFlagAvailability(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } - /// Owning provider identifier. - [JsonPropertyName("extensionId")] - public string ExtensionId { get; set; } = string.Empty; + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; - /// Host context supplied by the runtime. - [JsonPropertyName("host")] - public CanvasHostContext? Host { get; set; } + /// Enabled for everyone, before any override. + public static FeatureFlagAvailability On { get; } = new("on"); - /// Action input. - [JsonPropertyName("input")] - public JsonElement? Input { get; set; } + /// Enabled only for accounts on the owning team. + public static FeatureFlagAvailability Team { get; } = new("team"); - /// Canvas instance identifier. - [JsonPropertyName("instanceId")] - public string InstanceId { get; set; } = string.Empty; + /// Enabled only for staff accounts that have also opted into experimental features. + public static FeatureFlagAvailability StaffAndExperimental { get; } = new("staff-and-experimental"); - /// Session context supplied by the runtime. - [JsonPropertyName("session")] - public CanvasSessionContext? Session { get; set; } + /// Enabled for staff accounts and for accounts that opted into experimental features. + public static FeatureFlagAvailability StaffOrExperimental { get; } = new("staff-or-experimental"); - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; -} + /// Enabled for any staff account, GitHub or Microsoft. + public static FeatureFlagAvailability Staff { get; } = new("staff"); -/// Reports a supervised listener's hosting-task termination and cleanup outcome. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class HostExitedRequest -{ - /// Explicit startup or teardown failure, when present. - [JsonPropertyName("error")] - public string? Error { get; set; } + /// Enabled for GitHub staff accounts only. + public static FeatureFlagAvailability StaffGitHub { get; } = new("staff-github"); - /// Process exit status when available; absent for in-process listener tasks. - [JsonPropertyName("exitCode")] - public long? ExitCode { get; set; } + /// Enabled for Microsoft staff accounts only. + public static FeatureFlagAvailability StaffMicrosoft { get; } = new("staff-microsoft"); - /// Listener UUID. - [JsonPropertyName("hostId")] - public string HostId { get; set; } = string.Empty; + /// Enabled only for accounts that opted into experimental features. + public static FeatureFlagAvailability Experimental { get; } = new("experimental"); - /// Cause of termination. - [JsonPropertyName("reason")] - public HostExitReason Reason { get; set; } -} + /// Disabled for everyone, before any override. + public static FeatureFlagAvailability Off { get; } = new("off"); -/// Opaque integrator-owned process launch profile for one extension entrypoint. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class ExtensionLaunchProfile -{ - /// Opaque integrator-defined arguments passed to the executable. The runtime does not append the extension entrypoint. - [JsonPropertyName("args")] - public IList Args { get => field ??= []; set; } + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(FeatureFlagAvailability left, FeatureFlagAvailability right) => left.Equals(right); - /// 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; } + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(FeatureFlagAvailability left, FeatureFlagAvailability right) => !(left == right); - /// 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; -} + /// + public override bool Equals(object? obj) => obj is FeatureFlagAvailability other && Equals(other); -/// The launch profile for a supported entrypoint. Omit launch when the provider does not support the entrypoint. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class ExtensionLaunchProviderResolveResult -{ - /// Opaque launch profile, omitted when this provider does not support the entrypoint. - [JsonPropertyName("launch")] - public ExtensionLaunchProfile? Launch { get; set; } -} + /// + public bool Equals(FeatureFlagAvailability other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); -/// A discovered extension entrypoint that the registered integrator may classify and resolve to an opaque launch profile. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class ExtensionLaunchProviderResolveRequest -{ - /// Source-qualified extension identifier. - [JsonPropertyName("id")] - public string Id { get; set; } = string.Empty; + /// + public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); - /// Absolute path to the discovered extension entrypoint. - [JsonPropertyName("modulePath")] - public string ModulePath { get; set; } = string.Empty; + /// + public override string ToString() => Value; - /// Human-readable extension name. - [JsonPropertyName("name")] - public string Name { get; set; } = string.Empty; + /// Provides a for serializing instances. + [EditorBrowsable(EditorBrowsableState.Never)] + public sealed class Converter : JsonConverter + { + /// + public override FeatureFlagAvailability Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } - /// Discovery source for the extension entrypoint. - [JsonPropertyName("source")] - public ExtensionSource Source { get; set; } + /// + public override void Write(Utf8JsonWriter writer, FeatureFlagAvailability value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(FeatureFlagAvailability)); + } + } } -/// Acknowledgement. Returning successfully simply means the SDK accepted the start frame; it does not imply the request will succeed. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class LlmInferenceHttpRequestStartResult -{ -} -/// The head of an outbound model-layer HTTP request. +/// Which layer disabled bypass-permissions mode. The layer matters because only a server-delivered disable is pinned to an account and can later be lifted by signing in elsewhere. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class LlmInferenceHttpRequestStartRequest +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct ManagedSettingsBypassDisableSource : IEquatable { - /// Stable identity of the agent trajectory that issued this request. Present when the request originates from an agent turn; absent for requests outside any agent context. This is the same identity used by lifecycle and bridged session events and remains constant across turns and retries. - [JsonPropertyName("agentId")] - public string? AgentId { get; set; } + private readonly string? _value; - /// Identity of the agent invocation (one agentic loop) that issued this request. It remains fixed across physical retries within the invocation and is distinct from the stable trajectory `agentId`. A caller-supplied invocation id always takes precedence (this covers auxiliary calls that have no model call id). Otherwise, first-party CAPI requests fall back to the runtime's agent task id — the same value the runtime emits as the `X-Agent-Task-Id` header — while custom-provider requests fall back to the model call id. - [JsonPropertyName("agentInvocationId")] - public string? AgentInvocationId { get; set; } + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public ManagedSettingsBypassDisableSource(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } - /// HTTP request headers, preserving multiple values per name. - [JsonPropertyName("headers")] - public IDictionary> Headers { get => field ??= new Dictionary>(); set; } + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; - /// Coarse classification of the interaction that produced this request. Open string for forward-compatibility; known values include `conversation-agent`, `conversation-subagent`, `conversation-sampling`, `conversation-background`, `conversation-compaction`, and `conversation-user`. Absent when the runtime did not classify the request. Comes from the runtime's per-request agent context independently of transport; on the CAPI transport the runtime derives the upstream `X-Interaction-Type` header from this same context. - [JsonPropertyName("interactionType")] - public string? InteractionType { get; set; } + /// The user turned bypass mode off themselves. + public static ManagedSettingsBypassDisableSource User { get; } = new("user"); - /// HTTP method, e.g. GET, POST. - [JsonPropertyName("method")] - public string Method { get; set; } = string.Empty; + /// The account's server-delivered org policy disabled it. Pinned to the account named by `currentManagedIdentity`. + public static ManagedSettingsBypassDisableSource Managed { get; } = new("managed"); - /// 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; } + /// The device/MDM layer disabled it. Outranks the account, so no server answer lifts it. + public static ManagedSettingsBypassDisableSource Mdm { get; } = new("mdm"); - /// Opaque runtime-minted id, unique per in-flight request. The SDK uses this to correlate httpRequestChunk frames and to address its httpResponseStart / httpResponseChunk replies back to the runtime. - [JsonPropertyName("requestId")] - public string RequestId { get; set; } = string.Empty; + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(ManagedSettingsBypassDisableSource left, ManagedSettingsBypassDisableSource right) => left.Equals(right); - /// Id of the runtime session that triggered this request, when one is in scope. Absent for requests issued outside any session (e.g. startup model-catalog or capability resolution). This is a payload field — not a dispatch key — because the client-global API is registered process-wide rather than per session. - [JsonPropertyName("sessionId")] - public string? SessionId { get; set; } + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(ManagedSettingsBypassDisableSource left, ManagedSettingsBypassDisableSource right) => !(left == right); - /// Transport the runtime would otherwise use for this request. `http` (the default when absent) covers plain HTTP and SSE responses; `websocket` indicates a full-duplex message channel where each body chunk maps to one WebSocket message and the `binary` flag distinguishes text from binary frames. The SDK consumer uses this to decide whether to service the request with an HTTP client or a WebSocket client. It is the one piece of request metadata the consumer cannot reliably infer from the URL or headers alone. - [JsonPropertyName("transport")] - public LlmInferenceHttpRequestStartTransport? Transport { get; set; } + /// + public override bool Equals(object? obj) => obj is ManagedSettingsBypassDisableSource other && Equals(other); - /// Absolute request URL. - [JsonPropertyName("url")] - public string Url { get; set; } = string.Empty; -} + /// + public bool Equals(ManagedSettingsBypassDisableSource other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); -/// Acknowledgement. The SDK is free to ignore the ack and treat chunk delivery as fire-and-forget. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class LlmInferenceHttpRequestChunkResult -{ + /// + 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 ManagedSettingsBypassDisableSource Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } + + /// + public override void Write(Utf8JsonWriter writer, ManagedSettingsBypassDisableSource value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ManagedSettingsBypassDisableSource)); + } + } } -/// A request body chunk or cancellation signal. + +/// What the managed-policy resolution said about bypass-permissions mode. `unknown` is a third answer rather than a missing one, and the caller must send it explicitly when it has no verdict. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class LlmInferenceHttpRequestChunkRequest +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct ManagedSettingsBypassPolicyResolution : 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 ManagedSettingsBypassPolicyResolution(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; } + /// The policy resolved and does not disable bypass mode. + public static ManagedSettingsBypassPolicyResolution Allowed { get; } = new("allowed"); - /// 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; + /// The policy resolved and disables bypass mode. + public static ManagedSettingsBypassPolicyResolution Disabled { get; } = new("disabled"); - /// 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; } + /// No verdict: the fetch failed, or the service declared a policy this build may not enforce. Whatever is in force is preserved, because reverting to permissive on an unreadable policy would widen access. + public static ManagedSettingsBypassPolicyResolution Unknown { get; } = new("unknown"); - /// Matches the requestId from the originating httpRequestStart frame. - [JsonPropertyName("requestId")] - public string RequestId { get; set; } = string.Empty; + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(ManagedSettingsBypassPolicyResolution left, ManagedSettingsBypassPolicyResolution right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(ManagedSettingsBypassPolicyResolution left, ManagedSettingsBypassPolicyResolution right) => !(left == right); + + /// + public override bool Equals(object? obj) => obj is ManagedSettingsBypassPolicyResolution other && Equals(other); + + /// + public bool Equals(ManagedSettingsBypassPolicyResolution 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 ManagedSettingsBypassPolicyResolution Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } + + /// + public override void Write(Utf8JsonWriter writer, ManagedSettingsBypassPolicyResolution value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ManagedSettingsBypassPolicyResolution)); + } + } } -/// Client environment metadata describing the process that produced a telemetry event. + +/// Severity of a diagnostic emitted while ingesting managed settings. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class GitHubTelemetryClientInfo +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct ManagedSettingsLogLevel : IEquatable { - /// Copilot CLI version string. - [JsonPropertyName("cli_version")] - public string CliVersion { get; set; } = string.Empty; + private readonly string? _value; - /// Name of the client application. - [JsonPropertyName("client_name")] - public string? ClientName { get; set; } + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public ManagedSettingsLogLevel(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } - /// Type of client. - [JsonPropertyName("client_type")] - public string? ClientType { get; set; } + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; - /// Copilot subscription plan, when known. - [JsonPropertyName("copilot_plan")] - public string? CopilotPlan { get; set; } + /// Detail useful only when diagnosing an ingestion. + public static ManagedSettingsLogLevel Debug { get; } = new("debug"); - /// Number of logical CPU cores on the host. - [JsonPropertyName("cpu_count")] - public long? CpuCount { get; set; } + /// Ordinary progress worth surfacing under a verbose flag. + public static ManagedSettingsLogLevel Info { get; } = new("info"); - /// Distinct CPU model names for the host, comma-separated. - [JsonPropertyName("cpu_model")] - public string? CpuModel { get; set; } + /// A condition the operator should see but which did not stop the ingestion. + public static ManagedSettingsLogLevel Warning { get; } = new("warning"); - /// Stable machine identifier for the device. - [JsonPropertyName("dev_device_id")] - public string? DevDeviceId { get; set; } + /// A failure that changed the outcome of the ingestion. + public static ManagedSettingsLogLevel Error { get; } = new("error"); - /// Whether the user is a GitHub/Microsoft staff member. - [JsonPropertyName("is_staff")] - public bool? IsStaff { get; set; } + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(ManagedSettingsLogLevel left, ManagedSettingsLogLevel right) => left.Equals(right); - /// Node.js runtime version string. - [JsonPropertyName("node_version")] - public string NodeVersion { get; set; } = string.Empty; + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(ManagedSettingsLogLevel left, ManagedSettingsLogLevel right) => !(left == right); - /// Operating system architecture (e.g. arm64, x64). - [JsonPropertyName("os_arch")] - public string OsArch { get; set; } = string.Empty; + /// + public override bool Equals(object? obj) => obj is ManagedSettingsLogLevel other && Equals(other); - /// Operating system platform (e.g. darwin, linux, win32). - [JsonPropertyName("os_platform")] - public string OsPlatform { get; set; } = string.Empty; + /// + public bool Equals(ManagedSettingsLogLevel other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); - /// Operating system version string. - [JsonPropertyName("os_version")] - public string OsVersion { get; set; } = string.Empty; + /// + public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); + + /// + public override string ToString() => Value; + + /// Provides a for serializing instances. + [EditorBrowsable(EditorBrowsableState.Never)] + public sealed class Converter : JsonConverter + { + /// + public override ManagedSettingsLogLevel Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } + + /// + public override void Write(Utf8JsonWriter writer, ManagedSettingsLogLevel value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ManagedSettingsLogLevel)); + } + } } -/// 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. + +/// Which channel registered the policy helper that produced a helper layer. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class GitHubTelemetryEvent +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct ManagedSettingsPolicyHelperAuthority : 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 ManagedSettingsPolicyHelperAuthority(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 helper was registered by the device (MDM or managed file) layer. + public static ManagedSettingsPolicyHelperAuthority Device { get; } = new("device"); - /// Feature flags enabled for this session, as a map from flag to value. - [JsonPropertyName("features")] - public IDictionary? Features { get; set; } + /// The helper was registered by the account's server-delivered policy. + public static ManagedSettingsPolicyHelperAuthority Server { get; } = new("server"); - /// 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 ==(ManagedSettingsPolicyHelperAuthority left, ManagedSettingsPolicyHelperAuthority 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 !=(ManagedSettingsPolicyHelperAuthority left, ManagedSettingsPolicyHelperAuthority 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 ManagedSettingsPolicyHelperAuthority 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(ManagedSettingsPolicyHelperAuthority 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(global::GitHub.Copilot.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 ManagedSettingsPolicyHelperAuthority 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, ManagedSettingsPolicyHelperAuthority value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ManagedSettingsPolicyHelperAuthority)); + } + } } -/// SDK host response to a GitHub credential request. -/// Polymorphic base type discriminated by kind. + +/// How the server layer of an ingestion was resolved. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -[JsonPolymorphic( - TypeDiscriminatorPropertyName = "kind", - UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] -[JsonDerivedType(typeof(GitHubTokenAcquireResultToken), "token")] -[JsonDerivedType(typeof(GitHubTokenAcquireResultCancelled), "cancelled")] -public partial class GitHubTokenAcquireResult +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct ManagedSettingsServerPolicyResolution : IEquatable { - /// The type discriminator. - [JsonPropertyName("kind")] - public virtual string Kind { get; set; } = string.Empty; -} + private readonly string? _value; + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public ManagedSettingsServerPolicyResolution(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } -/// The token variant of . -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class GitHubTokenAcquireResultToken : GitHubTokenAcquireResult -{ - /// - [JsonIgnore] - public override string Kind => "token"; + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; - /// GitHub access token acquired by the SDK host. - [JsonPropertyName("accessToken")] - public required string AccessToken { get; set; } + /// A live fetch succeeded. + public static ManagedSettingsServerPolicyResolution Live { get; } = new("live"); - /// 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; } + /// A recent cached entry was served without a live fetch. + public static ManagedSettingsServerPolicyResolution FreshCache { get; } = new("freshCache"); - /// OAuth token type. Defaults to bearer when omitted. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("tokenType")] - public string? TokenType { get; set; } -} + /// No cached policy or confirmed no-policy result exists. + public static ManagedSettingsServerPolicyResolution CacheMiss { get; } = new("cacheMiss"); + + /// A live fetch failed and a cached entry was served in its place. + public static ManagedSettingsServerPolicyResolution FallbackCache { get; } = new("fallbackCache"); + + /// No policy could be resolved and the host proceeds unmanaged. + public static ManagedSettingsServerPolicyResolution FailOpen { get; } = new("failOpen"); + + /// No policy could be resolved and the host applies the fail-closed gates. + public static ManagedSettingsServerPolicyResolution FailClosed { get; } = new("failClosed"); + + /// No fetch was attempted because no usable token was available. + public static ManagedSettingsServerPolicyResolution SkippedNoToken { get; } = new("skippedNoToken"); + + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(ManagedSettingsServerPolicyResolution left, ManagedSettingsServerPolicyResolution right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(ManagedSettingsServerPolicyResolution left, ManagedSettingsServerPolicyResolution right) => !(left == right); -/// The cancelled variant of . -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class GitHubTokenAcquireResultCancelled : GitHubTokenAcquireResult -{ /// - [JsonIgnore] - public override string Kind => "cancelled"; -} + public override bool Equals(object? obj) => obj is ManagedSettingsServerPolicyResolution other && Equals(other); -/// Asks the SDK client to acquire a GitHub access token from an opaque callback registration. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class GitHubTokenAcquireRequest -{ - /// Effective GitHub host for which the callback must return a token. - [JsonPropertyName("host")] - public string Host { get; set; } = string.Empty; + /// + public bool Equals(ManagedSettingsServerPolicyResolution other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); - /// Why the runtime is requesting a GitHub credential. - [JsonPropertyName("reason")] - public GitHubTokenAcquireReason Reason { get; set; } + /// + public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); - /// Opaque identifier generated by the SDK for this callback registration. - [JsonPropertyName("registrationId")] - public string RegistrationId { get; set; } = string.Empty; + /// + public override string ToString() => Value; + + /// Provides a for serializing instances. + [EditorBrowsable(EditorBrowsableState.Never)] + public sealed class Converter : JsonConverter + { + /// + public override ManagedSettingsServerPolicyResolution Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } - /// Session receiving the token. Absent only before a cloud session has been assigned its id. - [JsonPropertyName("sessionId")] - public string? SessionId { get; set; } + /// + public override void Write(Utf8JsonWriter writer, ManagedSettingsServerPolicyResolution value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ManagedSettingsServerPolicyResolution)); + } + } } -/// A response is meaningful only on the connection and request that issued its challenge. -public sealed class InstallationsConfirmResult + +/// The operation that requires a policy-helper response. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct ManagedSettingsPolicyHelperReason : IEquatable { - /// Exact challenge from the request. - [JsonPropertyName("confirmationId")] - public string ConfirmationId { get; set; } = string.Empty; + private readonly string? _value; - /// Fresh explicit user decision. There is no default. - [JsonPropertyName("decision")] - public InstallationDecision Decision { get; set; } + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public ManagedSettingsPolicyHelperReason(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } - /// Exact review commitment from the request. - [JsonPropertyName("reviewFingerprint")] - public string ReviewFingerprint { get; set; } = string.Empty; -} + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; -/// Only resource kinds with an implemented installation engine have a review variant. -/// Polymorphic base type discriminated by resource. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -[JsonPolymorphic( - TypeDiscriminatorPropertyName = "resource", - UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] -[JsonDerivedType(typeof(InstallationReviewMcp), "mcp")] -[JsonDerivedType(typeof(InstallationReviewSkill), "skill")] -public partial class InstallationReview -{ - /// The type discriminator. - [JsonPropertyName("resource")] - public virtual string Resource { get; set; } = string.Empty; -} + /// Resolve policy for session startup. + public static ManagedSettingsPolicyHelperReason SessionStart { get; } = new("sessionStart"); + /// Resolve policy before plugin activation. + public static ManagedSettingsPolicyHelperReason PluginActivation { get; } = new("pluginActivation"); -/// Safe MCP review fields. No raw card, retrieval URL, plan handle or secret value. -/// Polymorphic base type discriminated by action. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -[JsonPolymorphic( - TypeDiscriminatorPropertyName = "action", - UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] -[JsonDerivedType(typeof(McpInstallationReviewInstall), "install")] -[JsonDerivedType(typeof(McpInstallationReviewUninstall), "uninstall")] -public partial class McpInstallationReview -{ - /// The type discriminator. - [JsonPropertyName("action")] - public virtual string Action { get; set; } = string.Empty; -} + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(ManagedSettingsPolicyHelperReason left, ManagedSettingsPolicyHelperReason right) => left.Equals(right); + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(ManagedSettingsPolicyHelperReason left, ManagedSettingsPolicyHelperReason right) => !(left == right); -/// -/// Final remote configuration, not a template. The producer refuses external-value -/// expansion before presenting this review. Receipt-owned secrets appear only as -/// `${installation-secret:<id>}` references whose `<id>` matches a reviewed -/// `${secret:<id>}` placeholder; values are never included. -/// -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class McpInstallationRemoteConfiguration -{ - /// - /// Configured headers, excluding separately authorised OAuth tokens. Values may - /// contain owned secret references, never secret values. - /// - [JsonPropertyName("headers")] - public IDictionary Headers { get => field ??= new Dictionary(); set; } + /// + public override bool Equals(object? obj) => obj is ManagedSettingsPolicyHelperReason other && Equals(other); - /// Configured tool selection, not permission to invoke those tools. - [JsonPropertyName("tools")] - public IList Tools { get => field ??= []; set; } + /// + public bool Equals(ManagedSettingsPolicyHelperReason other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); - /// Transport in the effective persisted remote configuration. - [JsonPropertyName("transport")] - public McpPlanRemoteTransport Transport { get; set; } + /// + public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); - /// Exact resolved endpoint, without templates or secret placeholders. - [JsonPropertyName("url")] - public string Url { get; set; } = string.Empty; + /// + public override string ToString() => Value; + + /// Provides a for serializing instances. + [EditorBrowsable(EditorBrowsableState.Never)] + public sealed class Converter : JsonConverter + { + /// + public override ManagedSettingsPolicyHelperReason Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } + + /// + public override void Write(Utf8JsonWriter writer, ManagedSettingsPolicyHelperReason value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ManagedSettingsPolicyHelperReason)); + } + } } -/// The install variant of . + +/// How an ingestion may use the on-disk server-policy cache. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class McpInstallationReviewInstall : McpInstallationReview +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct ManagedSettingsServerPolicyCacheMode : IEquatable { - /// - [JsonIgnore] - public override string Action => "install"; + private readonly string? _value; - /// Catalogue identity retained from the bound candidate when available. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("catalogue")] - public InstallationCatalogueIdentity? Catalogue { get; set; } + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public ManagedSettingsServerPolicyCacheMode(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } - /// Original catalogue trust metadata, not a verification claim. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("catalogueTrust")] - public CatalogTrustSnapshot? CatalogueTrust { get; set; } + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; - /// The configuration change for the selected alternative only. - [JsonPropertyName("configurationChange")] - public required McpPlanConfigurationChange ConfigurationChange { get; set; } + /// Serve a recent cache entry before a live fetch. + public static ManagedSettingsServerPolicyCacheMode PreferFresh { get; } = new("preferFresh"); - /// - /// Complete effective remote configuration for final installation review. - /// Earlier private selection reviews and package choices omit this field. - /// The owned remote resource requires it before issuing confirmation. - /// - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("effectiveConfiguration")] - public McpInstallationRemoteConfiguration? EffectiveConfiguration { get; set; } + /// Read cached policy without a network request. + public static ManagedSettingsServerPolicyCacheMode CacheOnly { get; } = new("cacheOnly"); - /// Identity from the retained plan, not caller display text. - [JsonPropertyName("identity")] - public required McpPlanResourceIdentity Identity { get; set; } + /// Serve a cached entry only as a fallback after a failed live fetch. + public static ManagedSettingsServerPolicyCacheMode FallbackOnly { get; } = new("fallbackOnly"); - /// Non-secret values supplied for this selected alternative. - [JsonPropertyName("inputs")] - public required IList Inputs { get; set; } + /// Ignore the cache entirely and rely on the live fetch alone. + public static ManagedSettingsServerPolicyCacheMode Disabled { get; } = new("disabled"); - /// Policy decision bound to this plan. - [JsonPropertyName("policy")] - public required McpPlanPolicyResult Policy { get; set; } + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(ManagedSettingsServerPolicyCacheMode left, ManagedSettingsServerPolicyCacheMode right) => left.Equals(right); - /// Original source identity and content commitment. - [JsonPropertyName("provenance")] - public required McpPlanProvenance Provenance { get; set; } + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(ManagedSettingsServerPolicyCacheMode left, ManagedSettingsServerPolicyCacheMode right) => !(left == right); - /// Explicit reviewed backend selection; no backend is accessed when no secrets are supplied. - [JsonPropertyName("secretStorage")] - public required McpInstallationSecretStorage SecretStorage { get; set; } + /// + public override bool Equals(object? obj) => obj is ManagedSettingsServerPolicyCacheMode other && Equals(other); - /// Only the selected alternative is applied. - [JsonPropertyName("selectedChoice")] - public required McpPlanTransportChoice SelectedChoice { get; set; } + /// + public bool Equals(ManagedSettingsServerPolicyCacheMode other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); - /// Exact reviewed placeholders supplied separately. Never secret values. - [JsonPropertyName("suppliedSecrets")] - public required IList SuppliedSecrets { get; set; } + /// + public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); - /// Exact reviewed user-scope destination. - [JsonPropertyName("target")] - public required McpPlanTarget Target { get; set; } + /// + public override string ToString() => Value; + + /// Provides a for serializing instances. + [EditorBrowsable(EditorBrowsableState.Never)] + public sealed class Converter : JsonConverter + { + /// + public override ManagedSettingsServerPolicyCacheMode Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } + + /// + public override void Write(Utf8JsonWriter writer, ManagedSettingsServerPolicyCacheMode value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ManagedSettingsServerPolicyCacheMode)); + } + } } -/// The uninstall variant of . + +/// Severity of a managed-settings validation finding. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class McpInstallationReviewUninstall : McpInstallationReview +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct ManagedSettingsDiagnosticSeverity : IEquatable { - /// - [JsonIgnore] - public override string Action => "uninstall"; + private readonly string? _value; - /// Identity from the installed receipt. - [JsonPropertyName("identity")] - public required McpPlanResourceIdentity Identity { get; set; } + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public ManagedSettingsDiagnosticSeverity(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } - /// Receipt-owned installation being removed. - [JsonPropertyName("installationId")] - public required string InstallationId { get; set; } + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; - /// Exact planner-owned secret slots to remove, excluding shared OAuth grants. - [JsonPropertyName("ownedSecretCount")] - public required long OwnedSecretCount { get; set; } + /// The runtime rejects the document. + public static ManagedSettingsDiagnosticSeverity Error { get; } = new("error"); - /// Current removal policy, independent of permission to activate the server. - [JsonPropertyName("policy")] - public required McpPlanPolicyResult Policy { get; set; } + /// The runtime accepts the document but ignores the flagged content. + public static ManagedSettingsDiagnosticSeverity Warning { get; } = new("warning"); - /// Shared profile authentication is deliberately retained, not pending cleanup. - [JsonPropertyName("preservesSharedAuthentication")] - public required bool PreservesSharedAuthentication { get; set; } + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(ManagedSettingsDiagnosticSeverity left, ManagedSettingsDiagnosticSeverity right) => left.Equals(right); - /// Source identity and content commitment retained by the installed receipt. - [JsonPropertyName("provenance")] - public required McpPlanProvenance Provenance { get; set; } + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(ManagedSettingsDiagnosticSeverity left, ManagedSettingsDiagnosticSeverity right) => !(left == right); - /// Whether uninstall restores a protected pre-install configuration. - [JsonPropertyName("restoresPreviousConfiguration")] - public required bool RestoresPreviousConfiguration { get; set; } + /// + public override bool Equals(object? obj) => obj is ManagedSettingsDiagnosticSeverity other && Equals(other); - /// Exact destination, checked for intervening changes before mutation. - [JsonPropertyName("target")] - public required McpPlanTarget Target { get; set; } -} + /// + public bool Equals(ManagedSettingsDiagnosticSeverity other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); -/// The mcp variant of . -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class InstallationReviewMcp : InstallationReview -{ /// - [JsonIgnore] - public override string Resource => "mcp"; + public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); - /// The exact MCP action and its reviewed changes. - [JsonPropertyName("review")] - public required McpInstallationReview Review { get; set; } + /// + public override string ToString() => Value; + + /// Provides a for serializing instances. + [EditorBrowsable(EditorBrowsableState.Never)] + public sealed class Converter : JsonConverter + { + /// + public override ManagedSettingsDiagnosticSeverity Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } + + /// + public override void Write(Utf8JsonWriter writer, ManagedSettingsDiagnosticSeverity value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ManagedSettingsDiagnosticSeverity)); + } + } } -/// The skill variant of . + +/// A channel accepted by managedSettings.compose. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public partial class InstallationReviewSkill : InstallationReview +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct ManagedSettingsChannel : IEquatable { - /// - [JsonIgnore] - public override string Resource => "skill"; + private readonly string? _value; - /// The exact verified Skill action and its reviewed files. - [JsonPropertyName("review")] - public required SkillInstallationReview Review { get; set; } -} + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public ManagedSettingsChannel(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } -/// One connection-owned, expiring request for a trusted host's explicit user decision. -public sealed class InstallationsConfirmRequest -{ - /// Opaque one-use challenge. Return unchanged; never log or persist. - [JsonPropertyName("confirmationId")] - public string ConfirmationId { get; set; } = string.Empty; + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; - /// Original plan expiry as an ISO 8601 timestamp. Confirmation never extends it. - [JsonPropertyName("expiresAt")] - public string ExpiresAt { get; set; } = string.Empty; + /// Device policy, the strongest channel. + public static ManagedSettingsChannel Device { get; } = new("device"); - /// Random identifier of this installation operation, not a plan handle. - [JsonPropertyName("operationId")] - public string OperationId { get; set; } = string.Empty; + /// Account or organization policy. + public static ManagedSettingsChannel Server { get; } = new("server"); - /// - /// Original engine-resolved selector for a bound operation, never a dispatch default. - /// Bound MCP confirmation always includes it; correlate it with the original pending action. - /// - [JsonPropertyName("policySessionId")] - public string? PolicySessionId { get; set; } + /// Session-local helper output, the weakest channel. + public static ManagedSettingsChannel PolicyHelper { get; } = new("policyHelper"); - /// Resource-specific review to present before collecting the user's decision. - [JsonPropertyName("review")] - public InstallationReview Review { get => field ??= new(); set; } + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(ManagedSettingsChannel left, ManagedSettingsChannel right) => left.Equals(right); - /// Opaque commitment to the exact review and inputs. Return unchanged; never log. - [JsonPropertyName("reviewFingerprint")] - public string ReviewFingerprint { get; set; } = string.Empty; + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(ManagedSettingsChannel left, ManagedSettingsChannel right) => !(left == right); + + /// + public override bool Equals(object? obj) => obj is ManagedSettingsChannel other && Equals(other); + + /// + public bool Equals(ManagedSettingsChannel 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 ManagedSettingsChannel Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } + + /// + public override void Write(Utf8JsonWriter writer, ManagedSettingsChannel value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ManagedSettingsChannel)); + } + } } -/// Closed set of public task kinds a connection can negotiate. + +/// Managed-policy verdict only: deny blocks the URL, ask requires approval, allow approves under managed policy, and unmanaged leaves the normal consumer permission flow in effect. No verdict bypasses other security controls. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct TaskKind : IEquatable +public readonly struct ManagedPermissionVerdict : 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 TaskKind(string value) + public ManagedPermissionVerdict(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; - /// Runtime-owned background agent task. - public static TaskKind Agent { get; } = new("agent"); + /// Managed policy prohibits the operation; do not offer approval. + public static ManagedPermissionVerdict Deny { get; } = new("deny"); - /// Runtime-owned shell task. - public static TaskKind Shell { get; } = new("shell"); + /// Managed policy requires the consumer to obtain approval. + public static ManagedPermissionVerdict Ask { get; } = new("ask"); - /// Client-owned externally executed task. - public static TaskKind Client { get; } = new("client"); + /// Managed policy allows the operation, subject to other security controls. + public static ManagedPermissionVerdict Allow { get; } = new("allow"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(TaskKind left, TaskKind right) => left.Equals(right); + /// Managed policy does not decide the operation; use the consumer's normal permission flow. + public static ManagedPermissionVerdict Unmanaged { get; } = new("unmanaged"); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(TaskKind left, TaskKind right) => !(left == right); + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(ManagedPermissionVerdict left, ManagedPermissionVerdict right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(ManagedPermissionVerdict left, ManagedPermissionVerdict right) => !(left == right); /// - public override bool Equals(object? obj) => obj is TaskKind other && Equals(other); + public override bool Equals(object? obj) => obj is ManagedPermissionVerdict other && Equals(other); /// - public bool Equals(TaskKind other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(ManagedPermissionVerdict other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -27963,20 +31578,20 @@ public TaskKind(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 TaskKind Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override ManagedPermissionVerdict Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, TaskKind value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, ManagedPermissionVerdict value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(TaskKind)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ManagedPermissionVerdict)); } } } @@ -28501,49 +32116,64 @@ public override void Write(Utf8JsonWriter writer, ModelPolicyState value, JsonSe } -/// State of the persistent certificate authority of the sandbox credential proxy. +/// Authentication type. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct SandboxProxyCaState : 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 SandboxProxyCaState(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; - /// This platform has no supported OS trust store. The proxy uses a per-process certificate bundle. - public static SandboxProxyCaState Unsupported { get; } = new("unsupported"); + /// Authentication provided by a GitHub App HMAC credential. + public static AuthInfoType Hmac { get; } = new("hmac"); - /// OS trust does not include the certificate authority, or none is stored. - public static SandboxProxyCaState NotInstalled { get; } = new("notInstalled"); + /// Authentication resolved from environment-provided credentials. + public static AuthInfoType Env { get; } = new("env"); - /// OS trust includes the stored certificate authority. - public static SandboxProxyCaState Installed { get; } = new("installed"); + /// Authentication from an interactive user sign-in. + public static AuthInfoType User { get; } = new("user"); - /// The runtime could not read the certificate authority or the OS trust store. - public static SandboxProxyCaState Error { get; } = new("error"); + /// Authentication from a selected provider-owned account, without a GitHub credential. + public static AuthInfoType Account { get; } = new("account"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(SandboxProxyCaState left, SandboxProxyCaState right) => left.Equals(right); + /// Authentication delegated to the GitHub CLI. + public static AuthInfoType GhCli { get; } = new("gh-cli"); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(SandboxProxyCaState left, SandboxProxyCaState right) => !(left == right); + /// 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 SandboxProxyCaState other && Equals(other); + public override bool Equals(object? obj) => obj is AuthInfoType other && Equals(other); /// - public bool Equals(SandboxProxyCaState 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); @@ -28551,20 +32181,20 @@ public SandboxProxyCaState(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 SandboxProxyCaState 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, SandboxProxyCaState value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, AuthInfoType value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(SandboxProxyCaState)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(AuthInfoType)); } } } @@ -28639,6 +32269,69 @@ public override void Write(Utf8JsonWriter writer, DiscoveredMcpServerType value, } +/// Controls how availableTools (allowlist) and excludedTools (denylist) combine when both are set. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct OptionsUpdateToolFilterPrecedence : IEquatable +{ + private readonly string? _value; + + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public OptionsUpdateToolFilterPrecedence(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } + + /// 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"); + + /// 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 ==(OptionsUpdateToolFilterPrecedence left, OptionsUpdateToolFilterPrecedence right) => left.Equals(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 OptionsUpdateToolFilterPrecedence other && Equals(other); + + /// + public bool Equals(OptionsUpdateToolFilterPrecedence 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 OptionsUpdateToolFilterPrecedence 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) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(OptionsUpdateToolFilterPrecedence)); + } + } +} + + /// 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(global::GitHub.Copilot.Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] @@ -28671,6 +32364,9 @@ public CatalogCapability(string value) /// Understands opt-in `application/vnd.github.copilot-plugin` candidates and their typed identity, version, source, and compatibility fields. public static CatalogCapability AgentPluginDiscovery { get; } = new("agent-plugin-discovery"); + /// Extends agent-plugin-discovery to Claude plugin (`application/vnd.anthropic.claude-plugin+json`) and portable Agent Plugin (`application/agent-plugin+json`) candidates, each reporting its format as its media type. Requires agent-plugin-discovery; without it, plugin searches return only Copilot plugins. + public static CatalogCapability AgentPluginFormats { get; } = new("agent-plugin-formats"); + /// 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"); @@ -28701,6 +32397,12 @@ public CatalogCapability(string value) /// Understands verified Agent Finder Skill install, uninstall, recovery and installation-scoped enablement APIs. Advertised only by runtimes with the Skill installation engine linked; acquisition may still be refused as feature-disabled per selected session. public static CatalogCapability SkillConfirmedInstallation { get; } = new("skill-confirmed-installation"); + /// Understands `installability` and the reviewable `installPlan` on Agent Plugin candidates. Requires agent-plugin-discovery; without it, plugin candidates are unchanged. A plugin is installable only with complete, verified catalogue install data, and may still be reported as feature-disabled per selected session. + public static CatalogCapability AgentPluginConfirmedInstallation { get; } = new("agent-plugin-confirmed-installation"); + + /// Extends skill-confirmed-installation to planning from an Agent Finder materialisation reference through skills.planInstallFromReference, and to its distinct reference failure reasons. Requires skill-confirmed-installation. Advertised only by runtimes with the Skill installation engine linked. + public static CatalogCapability SkillReferenceInstallation { get; } = new("skill-reference-installation"); + /// Returns a value indicating whether two instances are equivalent. public static bool operator ==(CatalogCapability left, CatalogCapability right) => left.Equals(right); @@ -30006,6 +33708,12 @@ public CatalogMediaType(string value) /// An inert Agent Plugin descriptor. public static CatalogMediaType ApplicationVndGitHubCopilotPlugin { get; } = new("application/vnd.github.copilot-plugin"); + /// An inert Claude plugin descriptor, surfaced only under the agent-plugin-formats capability. + public static CatalogMediaType ApplicationVndAnthropicClaudePluginJson { get; } = new("application/vnd.anthropic.claude-plugin+json"); + + /// An inert portable Agent Plugin descriptor, surfaced only under the agent-plugin-formats capability. + public static CatalogMediaType ApplicationAgentPluginJson { get; } = new("application/agent-plugin+json"); + /// Returns a value indicating whether two instances are equivalent. public static bool operator ==(CatalogMediaType left, CatalogMediaType right) => left.Equals(right); @@ -31033,6 +34741,285 @@ public override void Write(Utf8JsonWriter writer, DiscoveredExtensionMode value, } +/// Where this source lives — used for UI grouping. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct InstructionSourceLocation : IEquatable +{ + private readonly string? _value; + + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public InstructionSourceLocation(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } + + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; + + /// Instructions live in user-level configuration. + public static InstructionSourceLocation User { get; } = new("user"); + + /// Instructions live in repository-level configuration. + public static InstructionSourceLocation Repository { get; } = new("repository"); + + /// Instructions live under the current working directory. + public static InstructionSourceLocation WorkingDirectory { get; } = new("working-directory"); + + /// Instructions live in plugin-provided configuration. + public static InstructionSourceLocation Plugin { get; } = new("plugin"); + + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(InstructionSourceLocation left, InstructionSourceLocation right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(InstructionSourceLocation left, InstructionSourceLocation right) => !(left == right); + + /// + public override bool Equals(object? obj) => obj is InstructionSourceLocation other && Equals(other); + + /// + public bool Equals(InstructionSourceLocation other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + + /// + public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); + + /// + public override string ToString() => Value; + + /// Provides a for serializing instances. + [EditorBrowsable(EditorBrowsableState.Never)] + public sealed class Converter : JsonConverter + { + /// + public override InstructionSourceLocation Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } + + /// + public override void Write(Utf8JsonWriter writer, InstructionSourceLocation value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(InstructionSourceLocation)); + } + } +} + + +/// Category of instruction source — used for merge logic. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct InstructionSourceType : IEquatable +{ + private readonly string? _value; + + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public InstructionSourceType(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } + + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; + + /// Instructions loaded from the user's home configuration. + public static InstructionSourceType Home { get; } = new("home"); + + /// Instructions loaded from repository-scoped files. + public static InstructionSourceType Repo { get; } = new("repo"); + + /// Instructions loaded from model-specific files. + public static InstructionSourceType Model { get; } = new("model"); + + /// Instructions loaded from VS Code instruction files. + public static InstructionSourceType Vscode { get; } = new("vscode"); + + /// Instructions discovered from nested agent files. + public static InstructionSourceType NestedAgents { get; } = new("nested-agents"); + + /// Instructions inherited from child instruction files. + public static InstructionSourceType ChildInstructions { get; } = new("child-instructions"); + + /// Instructions supplied by an installed plugin. + public static InstructionSourceType Plugin { get; } = new("plugin"); + + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(InstructionSourceType left, InstructionSourceType right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(InstructionSourceType left, InstructionSourceType right) => !(left == right); + + /// + public override bool Equals(object? obj) => obj is InstructionSourceType other && Equals(other); + + /// + public bool Equals(InstructionSourceType other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + + /// + public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); + + /// + public override string ToString() => Value; + + /// Provides a for serializing instances. + [EditorBrowsable(EditorBrowsableState.Never)] + public sealed class Converter : JsonConverter + { + /// + public override InstructionSourceType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } + + /// + public override void Write(Utf8JsonWriter writer, InstructionSourceType value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(InstructionSourceType)); + } + } +} + + +/// Whether the target is a single file or a directory of instruction files. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct InstructionDiscoveryPathKind : IEquatable +{ + private readonly string? _value; + + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public InstructionDiscoveryPathKind(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } + + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; + + /// The target is a single instruction file. + public static InstructionDiscoveryPathKind File { get; } = new("file"); + + /// The target is a directory that holds instruction files. + public static InstructionDiscoveryPathKind Directory { get; } = new("directory"); + + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(InstructionDiscoveryPathKind left, InstructionDiscoveryPathKind right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(InstructionDiscoveryPathKind left, InstructionDiscoveryPathKind right) => !(left == right); + + /// + public override bool Equals(object? obj) => obj is InstructionDiscoveryPathKind other && Equals(other); + + /// + public bool Equals(InstructionDiscoveryPathKind other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + + /// + public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); + + /// + public override string ToString() => Value; + + /// Provides a for serializing instances. + [EditorBrowsable(EditorBrowsableState.Never)] + public sealed class Converter : JsonConverter + { + /// + public override InstructionDiscoveryPathKind Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } + + /// + public override void Write(Utf8JsonWriter writer, InstructionDiscoveryPathKind value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(InstructionDiscoveryPathKind)); + } + } +} + + +/// Which tier this target belongs to. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct InstructionDiscoveryPathLocation : IEquatable +{ + private readonly string? _value; + + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public InstructionDiscoveryPathLocation(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } + + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; + + /// Instructions live in user-level configuration. + public static InstructionDiscoveryPathLocation User { get; } = new("user"); + + /// Instructions live in repository-level configuration. + public static InstructionDiscoveryPathLocation Repository { get; } = new("repository"); + + /// Instructions live under the current working directory. + public static InstructionDiscoveryPathLocation WorkingDirectory { get; } = new("working-directory"); + + /// Instructions live in plugin-provided configuration. + public static InstructionDiscoveryPathLocation Plugin { get; } = new("plugin"); + + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(InstructionDiscoveryPathLocation left, InstructionDiscoveryPathLocation right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(InstructionDiscoveryPathLocation left, InstructionDiscoveryPathLocation right) => !(left == right); + + /// + public override bool Equals(object? obj) => obj is InstructionDiscoveryPathLocation other && Equals(other); + + /// + public bool Equals(InstructionDiscoveryPathLocation other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + + /// + public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); + + /// + public override string ToString() => Value; + + /// Provides a for serializing instances. + [EditorBrowsable(EditorBrowsableState.Never)] + public sealed class Converter : JsonConverter + { + /// + public override InstructionDiscoveryPathLocation Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } + + /// + public override void Write(Utf8JsonWriter writer, InstructionDiscoveryPathLocation value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(InstructionDiscoveryPathLocation)); + } + } +} + + /// Defines the allowed values. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] @@ -31338,6 +35325,21 @@ public SkillInstallationFailureReason(string value) /// A storage operation failed; inspect durable state before retrying. public static SkillInstallationFailureReason WriteFailed { get; } = new("write-failed"); + /// The materialisation reference is malformed or uses an unsupported schema version. Returned only to callers that require skill-reference-installation. + public static SkillInstallationFailureReason InvalidReference { get; } = new("invalid-reference"); + + /// Agent Finder has no materialisation for the referenced resource revision (HTTP 404). Returned only to callers that require skill-reference-installation. + public static SkillInstallationFailureReason DescriptorNotFound { get; } = new("descriptor-not-found"); + + /// Agent Finder withdrew the referenced resource revision (HTTP 410). Returned only to callers that require skill-reference-installation. + public static SkillInstallationFailureReason DescriptorWithdrawn { get; } = new("descriptor-withdrawn"); + + /// The retrieved descriptor does not match the referenced descriptor or bundle digest. Returned only to callers that require skill-reference-installation. + public static SkillInstallationFailureReason DigestMismatch { get; } = new("digest-mismatch"); + + /// The referenced resource is not a Skill. Returned only to callers that require skill-reference-installation. + public static SkillInstallationFailureReason UnsupportedResource { get; } = new("unsupported-resource"); + /// Returns a value indicating whether two instances are equivalent. public static bool operator ==(SkillInstallationFailureReason left, SkillInstallationFailureReason right) => left.Equals(right); @@ -31705,7 +35707,157 @@ public override void Write(Utf8JsonWriter writer, CatalogAgentPluginCompatibilit } -/// Canonical Agent Plugin media type. +/// Typed installability state for an Agent Plugin candidate. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct CatalogAgentPluginInstallability : IEquatable +{ + private readonly string? _value; + + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public CatalogAgentPluginInstallability(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } + + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; + + /// The candidate carries complete, verified install data and the selected session may install it. Its install plan is present. + public static CatalogAgentPluginInstallability Installable { get; } = new("installable"); + + /// The catalogue supplied no complete, verified install data, so no install target is offered. + public static CatalogAgentPluginInstallability InstallDataUnavailable { get; } = new("install-data-unavailable"); + + /// Plugin installation is understood but disabled for the selected session. + public static CatalogAgentPluginInstallability FeatureDisabled { get; } = new("feature-disabled"); + + /// Reserved for plugin formats this runtime cannot install. Every format surfaced today installs through a marketplace. + public static CatalogAgentPluginInstallability NotInstallableKind { get; } = new("not-installable-kind"); + + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(CatalogAgentPluginInstallability left, CatalogAgentPluginInstallability right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(CatalogAgentPluginInstallability left, CatalogAgentPluginInstallability right) => !(left == right); + + /// + public override bool Equals(object? obj) => obj is CatalogAgentPluginInstallability other && Equals(other); + + /// + public bool Equals(CatalogAgentPluginInstallability 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 CatalogAgentPluginInstallability Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } + + /// + public override void Write(Utf8JsonWriter writer, CatalogAgentPluginInstallability value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(CatalogAgentPluginInstallability)); + } + } +} + + +/// A kind of component an Agent Plugin contributes. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct CatalogAgentPluginComponent : IEquatable +{ + private readonly string? _value; + + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public CatalogAgentPluginComponent(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } + + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; + + /// Custom agents. + public static CatalogAgentPluginComponent Agent { get; } = new("agent"); + + /// Slash commands. + public static CatalogAgentPluginComponent Command { get; } = new("command"); + + /// Skills. + public static CatalogAgentPluginComponent Skill { get; } = new("skill"); + + /// Hooks. + public static CatalogAgentPluginComponent Hook { get; } = new("hook"); + + /// Bundled MCP servers. + public static CatalogAgentPluginComponent McpServer { get; } = new("mcp-server"); + + /// Bundled LSP servers. + public static CatalogAgentPluginComponent LspServer { get; } = new("lsp-server"); + + /// Extensions. + public static CatalogAgentPluginComponent Extension { get; } = new("extension"); + + /// Canvases. + public static CatalogAgentPluginComponent Canvas { get; } = new("canvas"); + + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(CatalogAgentPluginComponent left, CatalogAgentPluginComponent right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(CatalogAgentPluginComponent left, CatalogAgentPluginComponent right) => !(left == right); + + /// + public override bool Equals(object? obj) => obj is CatalogAgentPluginComponent other && Equals(other); + + /// + public bool Equals(CatalogAgentPluginComponent 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 CatalogAgentPluginComponent Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } + + /// + public override void Write(Utf8JsonWriter writer, CatalogAgentPluginComponent value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(CatalogAgentPluginComponent)); + } + } +} + + +/// Media type naming the plugin format of an Agent Plugin candidate. Values other than `application/vnd.github.copilot-plugin` appear only when the caller requires the agent-plugin-formats capability. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] @@ -31728,6 +35880,12 @@ public CatalogAgentPluginMediaType(string value) /// A GitHub Copilot Agent Plugin descriptor. public static CatalogAgentPluginMediaType ApplicationVndGitHubCopilotPlugin { get; } = new("application/vnd.github.copilot-plugin"); + /// A Claude plugin descriptor (`.claude-plugin/plugin.json` layout). + public static CatalogAgentPluginMediaType ApplicationVndAnthropicClaudePluginJson { get; } = new("application/vnd.anthropic.claude-plugin+json"); + + /// A portable Agent Plugin descriptor. + public static CatalogAgentPluginMediaType ApplicationAgentPluginJson { get; } = new("application/agent-plugin+json"); + /// Returns a value indicating whether two instances are equivalent. public static bool operator ==(CatalogAgentPluginMediaType left, CatalogAgentPluginMediaType right) => left.Equals(right); @@ -32161,285 +36319,6 @@ public override void Write(Utf8JsonWriter writer, AgentDiscoveryPathScope value, } -/// Where this source lives — used for UI grouping. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -[JsonConverter(typeof(Converter))] -[DebuggerDisplay("{Value,nq}")] -public readonly struct InstructionSourceLocation : IEquatable -{ - private readonly string? _value; - - /// Initializes a new instance of the struct. - /// The value to associate with this . - [JsonConstructor] - public InstructionSourceLocation(string value) - { - ArgumentException.ThrowIfNullOrWhiteSpace(value); - _value = value; - } - - /// Gets the value associated with this . - public string Value => _value ?? string.Empty; - - /// Instructions live in user-level configuration. - public static InstructionSourceLocation User { get; } = new("user"); - - /// Instructions live in repository-level configuration. - public static InstructionSourceLocation Repository { get; } = new("repository"); - - /// Instructions live under the current working directory. - public static InstructionSourceLocation WorkingDirectory { get; } = new("working-directory"); - - /// Instructions live in plugin-provided configuration. - public static InstructionSourceLocation Plugin { get; } = new("plugin"); - - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(InstructionSourceLocation left, InstructionSourceLocation right) => left.Equals(right); - - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(InstructionSourceLocation left, InstructionSourceLocation right) => !(left == right); - - /// - public override bool Equals(object? obj) => obj is InstructionSourceLocation other && Equals(other); - - /// - public bool Equals(InstructionSourceLocation other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); - - /// - public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); - - /// - public override string ToString() => Value; - - /// Provides a for serializing instances. - [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class Converter : JsonConverter - { - /// - public override InstructionSourceLocation Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); - } - - /// - public override void Write(Utf8JsonWriter writer, InstructionSourceLocation value, JsonSerializerOptions options) - { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(InstructionSourceLocation)); - } - } -} - - -/// Category of instruction source — used for merge logic. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -[JsonConverter(typeof(Converter))] -[DebuggerDisplay("{Value,nq}")] -public readonly struct InstructionSourceType : IEquatable -{ - private readonly string? _value; - - /// Initializes a new instance of the struct. - /// The value to associate with this . - [JsonConstructor] - public InstructionSourceType(string value) - { - ArgumentException.ThrowIfNullOrWhiteSpace(value); - _value = value; - } - - /// Gets the value associated with this . - public string Value => _value ?? string.Empty; - - /// Instructions loaded from the user's home configuration. - public static InstructionSourceType Home { get; } = new("home"); - - /// Instructions loaded from repository-scoped files. - public static InstructionSourceType Repo { get; } = new("repo"); - - /// Instructions loaded from model-specific files. - public static InstructionSourceType Model { get; } = new("model"); - - /// Instructions loaded from VS Code instruction files. - public static InstructionSourceType Vscode { get; } = new("vscode"); - - /// Instructions discovered from nested agent files. - public static InstructionSourceType NestedAgents { get; } = new("nested-agents"); - - /// Instructions inherited from child instruction files. - public static InstructionSourceType ChildInstructions { get; } = new("child-instructions"); - - /// Instructions supplied by an installed plugin. - public static InstructionSourceType Plugin { get; } = new("plugin"); - - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(InstructionSourceType left, InstructionSourceType right) => left.Equals(right); - - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(InstructionSourceType left, InstructionSourceType right) => !(left == right); - - /// - public override bool Equals(object? obj) => obj is InstructionSourceType other && Equals(other); - - /// - public bool Equals(InstructionSourceType other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); - - /// - public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); - - /// - public override string ToString() => Value; - - /// Provides a for serializing instances. - [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class Converter : JsonConverter - { - /// - public override InstructionSourceType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); - } - - /// - public override void Write(Utf8JsonWriter writer, InstructionSourceType value, JsonSerializerOptions options) - { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(InstructionSourceType)); - } - } -} - - -/// Whether the target is a single file or a directory of instruction files. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -[JsonConverter(typeof(Converter))] -[DebuggerDisplay("{Value,nq}")] -public readonly struct InstructionDiscoveryPathKind : IEquatable -{ - private readonly string? _value; - - /// Initializes a new instance of the struct. - /// The value to associate with this . - [JsonConstructor] - public InstructionDiscoveryPathKind(string value) - { - ArgumentException.ThrowIfNullOrWhiteSpace(value); - _value = value; - } - - /// Gets the value associated with this . - public string Value => _value ?? string.Empty; - - /// The target is a single instruction file. - public static InstructionDiscoveryPathKind File { get; } = new("file"); - - /// The target is a directory that holds instruction files. - public static InstructionDiscoveryPathKind Directory { get; } = new("directory"); - - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(InstructionDiscoveryPathKind left, InstructionDiscoveryPathKind right) => left.Equals(right); - - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(InstructionDiscoveryPathKind left, InstructionDiscoveryPathKind right) => !(left == right); - - /// - public override bool Equals(object? obj) => obj is InstructionDiscoveryPathKind other && Equals(other); - - /// - public bool Equals(InstructionDiscoveryPathKind other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); - - /// - public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); - - /// - public override string ToString() => Value; - - /// Provides a for serializing instances. - [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class Converter : JsonConverter - { - /// - public override InstructionDiscoveryPathKind Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); - } - - /// - public override void Write(Utf8JsonWriter writer, InstructionDiscoveryPathKind value, JsonSerializerOptions options) - { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(InstructionDiscoveryPathKind)); - } - } -} - - -/// Which tier this target belongs to. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -[JsonConverter(typeof(Converter))] -[DebuggerDisplay("{Value,nq}")] -public readonly struct InstructionDiscoveryPathLocation : IEquatable -{ - private readonly string? _value; - - /// Initializes a new instance of the struct. - /// The value to associate with this . - [JsonConstructor] - public InstructionDiscoveryPathLocation(string value) - { - ArgumentException.ThrowIfNullOrWhiteSpace(value); - _value = value; - } - - /// Gets the value associated with this . - public string Value => _value ?? string.Empty; - - /// Instructions live in user-level configuration. - public static InstructionDiscoveryPathLocation User { get; } = new("user"); - - /// Instructions live in repository-level configuration. - public static InstructionDiscoveryPathLocation Repository { get; } = new("repository"); - - /// Instructions live under the current working directory. - public static InstructionDiscoveryPathLocation WorkingDirectory { get; } = new("working-directory"); - - /// Instructions live in plugin-provided configuration. - public static InstructionDiscoveryPathLocation Plugin { get; } = new("plugin"); - - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(InstructionDiscoveryPathLocation left, InstructionDiscoveryPathLocation right) => left.Equals(right); - - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(InstructionDiscoveryPathLocation left, InstructionDiscoveryPathLocation right) => !(left == right); - - /// - public override bool Equals(object? obj) => obj is InstructionDiscoveryPathLocation other && Equals(other); - - /// - public bool Equals(InstructionDiscoveryPathLocation other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); - - /// - public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); - - /// - public override string ToString() => Value; - - /// Provides a for serializing instances. - [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class Converter : JsonConverter - { - /// - public override InstructionDiscoveryPathLocation Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); - } - - /// - public override void Write(Utf8JsonWriter writer, InstructionDiscoveryPathLocation value, JsonSerializerOptions options) - { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(InstructionDiscoveryPathLocation)); - } - } -} - - /// Optional completion hint for the input (e.g. 'directory' for filesystem path completion). [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] @@ -32629,135 +36508,6 @@ public override void Write(Utf8JsonWriter writer, SessionWorkingDirectoryContext } -/// Severity of a managed-settings validation finding. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -[JsonConverter(typeof(Converter))] -[DebuggerDisplay("{Value,nq}")] -public readonly struct ManagedSettingsDiagnosticSeverity : IEquatable -{ - private readonly string? _value; - - /// Initializes a new instance of the struct. - /// The value to associate with this . - [JsonConstructor] - public ManagedSettingsDiagnosticSeverity(string value) - { - ArgumentException.ThrowIfNullOrWhiteSpace(value); - _value = value; - } - - /// Gets the value associated with this . - public string Value => _value ?? string.Empty; - - /// The runtime rejects the document. - public static ManagedSettingsDiagnosticSeverity Error { get; } = new("error"); - - /// The runtime accepts the document but ignores the flagged content. - public static ManagedSettingsDiagnosticSeverity Warning { get; } = new("warning"); - - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(ManagedSettingsDiagnosticSeverity left, ManagedSettingsDiagnosticSeverity right) => left.Equals(right); - - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(ManagedSettingsDiagnosticSeverity left, ManagedSettingsDiagnosticSeverity right) => !(left == right); - - /// - public override bool Equals(object? obj) => obj is ManagedSettingsDiagnosticSeverity other && Equals(other); - - /// - public bool Equals(ManagedSettingsDiagnosticSeverity 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 ManagedSettingsDiagnosticSeverity Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); - } - - /// - public override void Write(Utf8JsonWriter writer, ManagedSettingsDiagnosticSeverity value, JsonSerializerOptions options) - { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ManagedSettingsDiagnosticSeverity)); - } - } -} - - -/// A channel accepted by managedSettings.compose. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -[JsonConverter(typeof(Converter))] -[DebuggerDisplay("{Value,nq}")] -public readonly struct ManagedSettingsChannel : IEquatable -{ - private readonly string? _value; - - /// Initializes a new instance of the struct. - /// The value to associate with this . - [JsonConstructor] - public ManagedSettingsChannel(string value) - { - ArgumentException.ThrowIfNullOrWhiteSpace(value); - _value = value; - } - - /// Gets the value associated with this . - public string Value => _value ?? string.Empty; - - /// Device policy, the strongest channel. - public static ManagedSettingsChannel Device { get; } = new("device"); - - /// Account or organization policy. - public static ManagedSettingsChannel Server { get; } = new("server"); - - /// Session-local helper output, the weakest channel. - public static ManagedSettingsChannel PolicyHelper { get; } = new("policyHelper"); - - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(ManagedSettingsChannel left, ManagedSettingsChannel right) => left.Equals(right); - - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(ManagedSettingsChannel left, ManagedSettingsChannel right) => !(left == right); - - /// - public override bool Equals(object? obj) => obj is ManagedSettingsChannel other && Equals(other); - - /// - public bool Equals(ManagedSettingsChannel 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 ManagedSettingsChannel Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); - } - - /// - public override void Write(Utf8JsonWriter writer, ManagedSettingsChannel value, JsonSerializerOptions options) - { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ManagedSettingsChannel)); - } - } -} - - /// Path conventions used by this filesystem. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] @@ -34090,90 +37840,6 @@ public override void Write(Utf8JsonWriter writer, ConnectorDiscoveryAvailability } -/// Authentication type. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -[JsonConverter(typeof(Converter))] -[DebuggerDisplay("{Value,nq}")] -public readonly struct AuthInfoType : IEquatable -{ - private readonly string? _value; - - /// Initializes a new instance of the struct. - /// The value to associate with this . - [JsonConstructor] - public AuthInfoType(string value) - { - ArgumentException.ThrowIfNullOrWhiteSpace(value); - _value = value; - } - - /// Gets the value associated with this . - public string Value => _value ?? string.Empty; - - /// Authentication provided by a GitHub App HMAC credential. - public static AuthInfoType Hmac { get; } = new("hmac"); - - /// Authentication resolved from environment-provided credentials. - public static AuthInfoType Env { get; } = new("env"); - - /// Authentication from an interactive user sign-in. - public static AuthInfoType User { get; } = new("user"); - - /// Authentication from a selected provider-owned account, without a GitHub credential. - public static AuthInfoType Account { get; } = new("account"); - - /// 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 AuthInfoType other && Equals(other); - - /// - public bool Equals(AuthInfoType 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 AuthInfoType 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) - { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(AuthInfoType)); - } - } -} - - /// Authoritative service connection state for one Connector. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] @@ -39292,6 +42958,81 @@ public override void Write(Utf8JsonWriter writer, ConnectorMcpStatus value, Json } +/// What retrying did for one plugin required by managed settings. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct ManagedPluginRetryStatus : IEquatable +{ + private readonly string? _value; + + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public ManagedPluginRetryStatus(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } + + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; + + /// The plugin was missing and is now installed. + public static ManagedPluginRetryStatus Installed { get; } = new("installed"); + + /// An update was available and is now installed. + public static ManagedPluginRetryStatus Updated { get; } = new("updated"); + + /// The plugin was already installed and current, so nothing was done. + public static ManagedPluginRetryStatus AlreadyPresent { get; } = new("already_present"); + + /// Checking, installing, or updating the plugin failed. See `error`. + public static ManagedPluginRetryStatus Failed { get; } = new("failed"); + + /// The plugin was not prepared because another session was changing plugins or the managed settings changed during the retry. It is prepared again before a later message. + public static ManagedPluginRetryStatus Deferred { get; } = new("deferred"); + + /// The session's managed settings do not require this plugin, so nothing was done. + public static ManagedPluginRetryStatus NotRequired { get; } = new("not_required"); + + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(ManagedPluginRetryStatus left, ManagedPluginRetryStatus right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(ManagedPluginRetryStatus left, ManagedPluginRetryStatus right) => !(left == right); + + /// + public override bool Equals(object? obj) => obj is ManagedPluginRetryStatus other && Equals(other); + + /// + public bool Equals(ManagedPluginRetryStatus 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 ManagedPluginRetryStatus Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } + + /// + public override void Write(Utf8JsonWriter writer, ManagedPluginRetryStatus value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ManagedPluginRetryStatus)); + } + } +} + + /// Allowed values for the `OptionsUpdateAdditionalContentExclusionPolicyScope` enumeration. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] @@ -39672,6 +43413,9 @@ public SessionCapability(string value) /// Cross-session history tools and session-store SQL prompt/tool metadata. public static SessionCapability SessionStore { get; } = new("session-store"); + /// Read-only GitHub cache SQL tool (github_sql). + public static SessionCapability GitHubCacheStore { get; } = new("github-cache-store"); + /// MCP Apps UI passthrough. public static SessionCapability McpApps { get; } = new("mcp-apps"); @@ -39841,69 +43585,6 @@ public override void Write(Utf8JsonWriter writer, ShellInitScriptShell value, Js } -/// Controls how availableTools (allowlist) and excludedTools (denylist) combine when both are set. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -[JsonConverter(typeof(Converter))] -[DebuggerDisplay("{Value,nq}")] -public readonly struct OptionsUpdateToolFilterPrecedence : IEquatable -{ - private readonly string? _value; - - /// Initializes a new instance of the struct. - /// The value to associate with this . - [JsonConstructor] - public OptionsUpdateToolFilterPrecedence(string value) - { - ArgumentException.ThrowIfNullOrWhiteSpace(value); - _value = value; - } - - /// 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"); - - /// 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 ==(OptionsUpdateToolFilterPrecedence left, OptionsUpdateToolFilterPrecedence right) => left.Equals(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 OptionsUpdateToolFilterPrecedence other && Equals(other); - - /// - public bool Equals(OptionsUpdateToolFilterPrecedence 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 OptionsUpdateToolFilterPrecedence 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) - { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(OptionsUpdateToolFilterPrecedence)); - } - } -} - - /// Discovery source: project (.github/extensions/), user (~/.copilot/extensions/), plugin (installed plugin), or session (session-state/<id>/extensions/). [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] @@ -42464,38 +46145,485 @@ public override void Write(Utf8JsonWriter writer, SessionLimitPredictionTier val { 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 SessionLimitPredictionSource(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } + + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; + + /// The prediction used the exact resolved model's baseline cell. + public static SessionLimitPredictionSource Model { get; } = new("model"); + + /// The exact model was unavailable, so the prediction used the model family's baseline cell. + public static SessionLimitPredictionSource Family { get; } = new("family"); + + /// No model or family cell was available, so the prediction used the global client-type baseline cell. + public static SessionLimitPredictionSource Global { get; } = new("global"); + + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(SessionLimitPredictionSource left, SessionLimitPredictionSource right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(SessionLimitPredictionSource left, SessionLimitPredictionSource right) => !(left == right); + + /// + public override bool Equals(object? obj) => obj is SessionLimitPredictionSource other && Equals(other); + + /// + public bool Equals(SessionLimitPredictionSource 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 SessionLimitPredictionSource Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } + + /// + public override void Write(Utf8JsonWriter writer, SessionLimitPredictionSource value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(SessionLimitPredictionSource)); + } + } +} + + +/// Reason a prediction could not be computed. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct SessionLimitPredictionUnavailableReason : IEquatable +{ + private readonly string? _value; + + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public SessionLimitPredictionUnavailableReason(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } + + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; + + /// The current model is auto and has not resolved to a concrete model yet. + public static SessionLimitPredictionUnavailableReason AutoUnresolved { get; } = new("auto_unresolved"); + + /// No model was provided and the session does not currently have a selected model. + public static SessionLimitPredictionUnavailableReason NoModel { get; } = new("no_model"); + + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(SessionLimitPredictionUnavailableReason left, SessionLimitPredictionUnavailableReason right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(SessionLimitPredictionUnavailableReason left, SessionLimitPredictionUnavailableReason right) => !(left == right); + + /// + public override bool Equals(object? obj) => obj is SessionLimitPredictionUnavailableReason other && Equals(other); + + /// + public bool Equals(SessionLimitPredictionUnavailableReason 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 SessionLimitPredictionUnavailableReason Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } + + /// + public override void Write(Utf8JsonWriter writer, SessionLimitPredictionUnavailableReason value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(SessionLimitPredictionUnavailableReason)); + } + } +} + + +/// Per-session remote mode. "off" disables remote, "export" exports session events to GitHub without enabling remote steering, "on" enables both export and remote steering. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct RemoteSessionMode : IEquatable +{ + private readonly string? _value; + + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public RemoteSessionMode(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } + + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; + + /// Disable remote session export and steering. + public static RemoteSessionMode Off { get; } = new("off"); + + /// Export session events to GitHub without enabling remote steering. + public static RemoteSessionMode Export { get; } = new("export"); + + /// Enable both remote session export and remote steering. + public static RemoteSessionMode On { get; } = new("on"); + + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(RemoteSessionMode left, RemoteSessionMode right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(RemoteSessionMode left, RemoteSessionMode right) => !(left == right); + + /// + public override bool Equals(object? obj) => obj is RemoteSessionMode other && Equals(other); + + /// + public bool Equals(RemoteSessionMode 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 RemoteSessionMode Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } + + /// + public override void Write(Utf8JsonWriter writer, RemoteSessionMode value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(RemoteSessionMode)); + } + } +} + + +/// Sharing status for a synced session. "repo" makes the session visible to anyone with read access to the repository; "unshared" restricts it to the creator and collaborators. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct SessionVisibilityStatus : IEquatable +{ + private readonly string? _value; + + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public SessionVisibilityStatus(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } + + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; + + /// The session is visible to repository readers. + public static SessionVisibilityStatus Repo { get; } = new("repo"); + + /// The session is restricted to its creator and collaborators. + public static SessionVisibilityStatus Unshared { get; } = new("unshared"); + + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(SessionVisibilityStatus left, SessionVisibilityStatus right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(SessionVisibilityStatus left, SessionVisibilityStatus right) => !(left == right); + + /// + public override bool Equals(object? obj) => obj is SessionVisibilityStatus other && Equals(other); + + /// + public bool Equals(SessionVisibilityStatus 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 SessionVisibilityStatus Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } + + /// + public override void Write(Utf8JsonWriter writer, SessionVisibilityStatus value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(SessionVisibilityStatus)); + } + } +} + + +/// Why the runtime requests client-task cancellation. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct ClientTaskCancelReason : IEquatable +{ + private readonly string? _value; + + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public ClientTaskCancelReason(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } + + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; + + /// A caller requested task cancellation. + public static ClientTaskCancelReason CancelRequested { get; } = new("cancel_requested"); + + /// The session is shutting down. + public static ClientTaskCancelReason SessionShutdown { get; } = new("session_shutdown"); + + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(ClientTaskCancelReason left, ClientTaskCancelReason right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(ClientTaskCancelReason left, ClientTaskCancelReason right) => !(left == right); + + /// + public override bool Equals(object? obj) => obj is ClientTaskCancelReason other && Equals(other); + + /// + public bool Equals(ClientTaskCancelReason 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 ClientTaskCancelReason Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } + + /// + public override void Write(Utf8JsonWriter writer, ClientTaskCancelReason value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ClientTaskCancelReason)); + } + } +} + + +/// Error classification. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct SessionFsErrorCode : IEquatable +{ + private readonly string? _value; + + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public SessionFsErrorCode(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } + + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; + + /// The requested path does not exist. + public static SessionFsErrorCode ENOENT { get; } = new("ENOENT"); + + /// The filesystem operation failed for an unspecified reason. + public static SessionFsErrorCode UNKNOWN { get; } = new("UNKNOWN"); + + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(SessionFsErrorCode left, SessionFsErrorCode right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(SessionFsErrorCode left, SessionFsErrorCode right) => !(left == right); + + /// + public override bool Equals(object? obj) => obj is SessionFsErrorCode other && Equals(other); + + /// + public bool Equals(SessionFsErrorCode 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 SessionFsErrorCode Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } + + /// + public override void Write(Utf8JsonWriter writer, SessionFsErrorCode value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(SessionFsErrorCode)); + } + } +} + + +/// Entry type. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct SessionFsReaddirWithTypesEntryType : IEquatable +{ + private readonly string? _value; + + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public SessionFsReaddirWithTypesEntryType(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } + + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; + + /// The entry is a file. + public static SessionFsReaddirWithTypesEntryType File { get; } = new("file"); + + /// The entry is a directory. + public static SessionFsReaddirWithTypesEntryType Directory { get; } = new("directory"); + + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(SessionFsReaddirWithTypesEntryType left, SessionFsReaddirWithTypesEntryType right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(SessionFsReaddirWithTypesEntryType left, SessionFsReaddirWithTypesEntryType right) => !(left == right); + + /// + public override bool Equals(object? obj) => obj is SessionFsReaddirWithTypesEntryType other && Equals(other); + + /// + public bool Equals(SessionFsReaddirWithTypesEntryType 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 SessionFsReaddirWithTypesEntryType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } + + /// + public override void Write(Utf8JsonWriter writer, SessionFsReaddirWithTypesEntryType value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(SessionFsReaddirWithTypesEntryType)); + } + } +} + + +/// How to execute the query: 'exec' for DDL/multi-statement (no results), 'query' for SELECT (returns rows), 'run' for INSERT/UPDATE/DELETE (returns rowsAffected). +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct SessionFsSqliteQueryType : IEquatable +{ + private readonly string? _value; + + /// Initializes a new instance of the struct. + /// The value to associate with this . [JsonConstructor] - public SessionLimitPredictionSource(string value) + public SessionFsSqliteQueryType(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 prediction used the exact resolved model's baseline cell. - public static SessionLimitPredictionSource Model { get; } = new("model"); + /// Execute DDL or multi-statement SQL without returning rows. + public static SessionFsSqliteQueryType Exec { get; } = new("exec"); - /// The exact model was unavailable, so the prediction used the model family's baseline cell. - public static SessionLimitPredictionSource Family { get; } = new("family"); + /// Execute a SELECT-style query and return rows. + public static SessionFsSqliteQueryType Query { get; } = new("query"); - /// No model or family cell was available, so the prediction used the global client-type baseline cell. - public static SessionLimitPredictionSource Global { get; } = new("global"); + /// Execute INSERT, UPDATE, or DELETE SQL and return affected-row metadata. + public static SessionFsSqliteQueryType Run { get; } = new("run"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(SessionLimitPredictionSource left, SessionLimitPredictionSource right) => left.Equals(right); + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(SessionFsSqliteQueryType left, SessionFsSqliteQueryType right) => left.Equals(right); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(SessionLimitPredictionSource left, SessionLimitPredictionSource right) => !(left == right); + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(SessionFsSqliteQueryType left, SessionFsSqliteQueryType right) => !(left == right); /// - public override bool Equals(object? obj) => obj is SessionLimitPredictionSource other && Equals(other); + public override bool Equals(object? obj) => obj is SessionFsSqliteQueryType other && Equals(other); /// - public bool Equals(SessionLimitPredictionSource other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(SessionFsSqliteQueryType other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -42503,62 +46631,65 @@ public SessionLimitPredictionSource(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 SessionLimitPredictionSource Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override SessionFsSqliteQueryType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, SessionLimitPredictionSource value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, SessionFsSqliteQueryType value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(SessionLimitPredictionSource)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(SessionFsSqliteQueryType)); } } } -/// Reason a prediction could not be computed. +/// SQLite transaction failure classification. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct SessionLimitPredictionUnavailableReason : IEquatable +public readonly struct SessionFsSqliteTransactionErrorClass : 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 SessionLimitPredictionUnavailableReason(string value) + public SessionFsSqliteTransactionErrorClass(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 current model is auto and has not resolved to a concrete model yet. - public static SessionLimitPredictionUnavailableReason AutoUnresolved { get; } = new("auto_unresolved"); + /// SQLite reported BUSY or LOCKED before commit; the transaction was rolled back and may be retried. + public static SessionFsSqliteTransactionErrorClass BusyOrLocked { get; } = new("busyOrLocked"); - /// No model was provided and the session does not currently have a selected model. - public static SessionLimitPredictionUnavailableReason NoModel { get; } = new("no_model"); + /// The statement, database, or provider failed definitively and must not be retried automatically. + public static SessionFsSqliteTransactionErrorClass Fatal { get; } = new("fatal"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(SessionLimitPredictionUnavailableReason left, SessionLimitPredictionUnavailableReason right) => left.Equals(right); + /// The transport failed after the provider may have committed; retrying could duplicate effects. + public static SessionFsSqliteTransactionErrorClass PostCommitAmbiguous { get; } = new("postCommitAmbiguous"); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(SessionLimitPredictionUnavailableReason left, SessionLimitPredictionUnavailableReason right) => !(left == right); + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(SessionFsSqliteTransactionErrorClass left, SessionFsSqliteTransactionErrorClass right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(SessionFsSqliteTransactionErrorClass left, SessionFsSqliteTransactionErrorClass right) => !(left == right); /// - public override bool Equals(object? obj) => obj is SessionLimitPredictionUnavailableReason other && Equals(other); + public override bool Equals(object? obj) => obj is SessionFsSqliteTransactionErrorClass other && Equals(other); /// - public bool Equals(SessionLimitPredictionUnavailableReason other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(SessionFsSqliteTransactionErrorClass other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -42566,65 +46697,68 @@ public SessionLimitPredictionUnavailableReason(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 SessionLimitPredictionUnavailableReason Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override SessionFsSqliteTransactionErrorClass Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, SessionLimitPredictionUnavailableReason value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, SessionFsSqliteTransactionErrorClass value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(SessionLimitPredictionUnavailableReason)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(SessionFsSqliteTransactionErrorClass)); } } } -/// Per-session remote mode. "off" disables remote, "export" exports session events to GitHub without enabling remote steering, "on" enables both export and remote steering. +/// Defines the allowed values. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct RemoteSessionMode : IEquatable +public readonly struct HostExitReason : 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 RemoteSessionMode(string value) + public HostExitReason(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 remote session export and steering. - public static RemoteSessionMode Off { get; } = new("off"); + /// The owner requested disposal. + public static HostExitReason Disposed { get; } = new("disposed"); - /// Export session events to GitHub without enabling remote steering. - public static RemoteSessionMode Export { get; } = new("export"); + /// The hosting task or its SDK transport exited; this does not mean the runtime process exited. + public static HostExitReason Exited { get; } = new("exited"); - /// Enable both remote session export and remote steering. - public static RemoteSessionMode On { get; } = new("on"); + /// The owning SDK connection disconnected. + public static HostExitReason OwnerDisconnected { get; } = new("ownerDisconnected"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(RemoteSessionMode left, RemoteSessionMode right) => left.Equals(right); + /// The runtime is shutting down. + public static HostExitReason RuntimeShutdown { get; } = new("runtimeShutdown"); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(RemoteSessionMode left, RemoteSessionMode right) => !(left == right); + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(HostExitReason left, HostExitReason right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(HostExitReason left, HostExitReason right) => !(left == right); /// - public override bool Equals(object? obj) => obj is RemoteSessionMode other && Equals(other); + public override bool Equals(object? obj) => obj is HostExitReason other && Equals(other); /// - public bool Equals(RemoteSessionMode other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(HostExitReason other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -42632,62 +46766,62 @@ public RemoteSessionMode(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 RemoteSessionMode Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override HostExitReason Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, RemoteSessionMode value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, HostExitReason value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(RemoteSessionMode)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(HostExitReason)); } } } -/// Sharing status for a synced session. "repo" makes the session visible to anyone with read access to the repository; "unshared" restricts it to the creator and collaborators. +/// Transport the runtime would otherwise use for this request. `http` (the default when absent) covers plain HTTP and SSE responses; `websocket` indicates a full-duplex message channel where each body chunk maps to one WebSocket message and the `binary` flag distinguishes text from binary frames. The SDK consumer uses this to decide whether to service the request with an HTTP client or a WebSocket client. It is the one piece of request metadata the consumer cannot reliably infer from the URL or headers alone. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct SessionVisibilityStatus : IEquatable +public readonly struct LlmInferenceHttpRequestStartTransport : 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 SessionVisibilityStatus(string value) + public LlmInferenceHttpRequestStartTransport(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 is visible to repository readers. - public static SessionVisibilityStatus Repo { get; } = new("repo"); + /// 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. + public static LlmInferenceHttpRequestStartTransport Http { get; } = new("http"); - /// The session is restricted to its creator and collaborators. - public static SessionVisibilityStatus Unshared { get; } = new("unshared"); + /// 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. + public static LlmInferenceHttpRequestStartTransport Websocket { get; } = new("websocket"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(SessionVisibilityStatus left, SessionVisibilityStatus right) => left.Equals(right); + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(LlmInferenceHttpRequestStartTransport left, LlmInferenceHttpRequestStartTransport right) => left.Equals(right); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(SessionVisibilityStatus left, SessionVisibilityStatus right) => !(left == right); + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(LlmInferenceHttpRequestStartTransport left, LlmInferenceHttpRequestStartTransport right) => !(left == right); /// - public override bool Equals(object? obj) => obj is SessionVisibilityStatus other && Equals(other); + public override bool Equals(object? obj) => obj is LlmInferenceHttpRequestStartTransport other && Equals(other); /// - public bool Equals(SessionVisibilityStatus other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(LlmInferenceHttpRequestStartTransport other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -42695,62 +46829,62 @@ public SessionVisibilityStatus(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 SessionVisibilityStatus Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override LlmInferenceHttpRequestStartTransport Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, SessionVisibilityStatus value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, LlmInferenceHttpRequestStartTransport value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(SessionVisibilityStatus)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(LlmInferenceHttpRequestStartTransport)); } } } -/// Why the runtime requests client-task cancellation. +/// Why the runtime is requesting a GitHub credential. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct ClientTaskCancelReason : IEquatable +public readonly struct GitHubTokenAcquireReason : 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 ClientTaskCancelReason(string value) + public GitHubTokenAcquireReason(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 caller requested task cancellation. - public static ClientTaskCancelReason CancelRequested { get; } = new("cancel_requested"); + /// The runtime is acquiring the registration's first credential. + public static GitHubTokenAcquireReason Initial { get; } = new("initial"); - /// The session is shutting down. - public static ClientTaskCancelReason SessionShutdown { get; } = new("session_shutdown"); + /// 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 ==(ClientTaskCancelReason left, ClientTaskCancelReason right) => left.Equals(right); + /// 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 !=(ClientTaskCancelReason left, ClientTaskCancelReason right) => !(left == 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 ClientTaskCancelReason other && Equals(other); + public override bool Equals(object? obj) => obj is GitHubTokenAcquireReason other && Equals(other); /// - public bool Equals(ClientTaskCancelReason other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(GitHubTokenAcquireReason other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -42758,62 +46892,65 @@ public ClientTaskCancelReason(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 ClientTaskCancelReason Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override GitHubTokenAcquireReason Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, ClientTaskCancelReason value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, GitHubTokenAcquireReason value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ClientTaskCancelReason)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(GitHubTokenAcquireReason)); } } } -/// Error classification. +/// Explicit user decisions, never inferred from a permission grant or model response. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct SessionFsErrorCode : IEquatable +public readonly struct InstallationDecision : 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 SessionFsErrorCode(string value) + public InstallationDecision(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 requested path does not exist. - public static SessionFsErrorCode ENOENT { get; } = new("ENOENT"); + /// The user explicitly approved the exact review on this request. + public static InstallationDecision Confirm { get; } = new("confirm"); - /// The filesystem operation failed for an unspecified reason. - public static SessionFsErrorCode UNKNOWN { get; } = new("UNKNOWN"); + /// The user declined the reviewed operation. + public static InstallationDecision Decline { get; } = new("decline"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(SessionFsErrorCode left, SessionFsErrorCode right) => left.Equals(right); + /// The user cancelled the pending decision without granting consent. + public static InstallationDecision Cancel { get; } = new("cancel"); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(SessionFsErrorCode left, SessionFsErrorCode right) => !(left == right); + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(InstallationDecision left, InstallationDecision right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(InstallationDecision left, InstallationDecision right) => !(left == right); /// - public override bool Equals(object? obj) => obj is SessionFsErrorCode other && Equals(other); + public override bool Equals(object? obj) => obj is InstallationDecision other && Equals(other); /// - public bool Equals(SessionFsErrorCode other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(InstallationDecision other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -42821,693 +46958,1444 @@ public SessionFsErrorCode(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 SessionFsErrorCode Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override InstallationDecision Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, SessionFsErrorCode value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, InstallationDecision value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(SessionFsErrorCode)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(InstallationDecision)); } } } -/// Entry type. +/// Provides server-scoped RPC methods (no session required). +public sealed class ServerRpc +{ + private readonly JsonRpc _rpc; + + internal ServerRpc(JsonRpc rpc) + { + _rpc = rpc; + } + + /// Checks server responsiveness and returns protocol information. + /// Optional message to echo back. + /// The to monitor for cancellation requests. The default is . + /// Server liveness response, including the echoed message, current server timestamp, and protocol version. + [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] + public async Task PingAsync(string? message = null, CancellationToken cancellationToken = default) + { + var request = new PingRequest { Message = message }; + return await CopilotClient.InvokeRpcAsync(_rpc, "ping", [request], cancellationToken); + } + + /// 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. + /// Opt this connection in to GitHub telemetry forwarding for its lifetime. When set, the runtime forwards this host's telemetry across all its sessions, its sessionless events, and explicitly process-wide events over the `gitHubTelemetry.event` notification. Connections intentionally sharing one server receive that server's events; independently embedded runtime hosts do not receive each other's host-owned events. 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. + /// Task kinds this connection can decode when observing session tasks. Omit to retain agent and shell compatibility. + /// 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(global::GitHub.Copilot.Diagnostics.Experimental)] + internal async Task ConnectAsync(bool? enableGitHubTelemetryForwarding = null, ConnectClientInfo? clientInfo = null, IList? supportedTaskKinds = null, string? token = null, CancellationToken cancellationToken = default) + { + var request = new ConnectRequest { EnableGitHubTelemetryForwarding = enableGitHubTelemetryForwarding, ClientInfo = clientInfo, SupportedTaskKinds = supportedTaskKinds, 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(global::GitHub.Copilot.Diagnostics.Experimental)] + public async Task RegisterExtensionLaunchProviderAsync(CancellationToken cancellationToken = default) + { + await CopilotClient.InvokeRpcAsync(_rpc, "registerExtensionLaunchProvider", [], cancellationToken); + } + + /// Sandbox APIs. + public ServerSandboxApi Sandbox => + field ?? + Interlocked.CompareExchange(ref field, new(_rpc), null) ?? + field; + + /// Persistence APIs. + public ServerPersistenceApi Persistence => + field ?? + Interlocked.CompareExchange(ref field, new(_rpc), null) ?? + field; + + /// User APIs. + public ServerUserApi User => + field ?? + Interlocked.CompareExchange(ref field, new(_rpc), null) ?? + field; + + /// Repo APIs. + public ServerRepoApi Repo => + field ?? + Interlocked.CompareExchange(ref field, new(_rpc), null) ?? + field; + + /// FeatureFlags APIs. + public ServerFeatureFlagsApi FeatureFlags => + field ?? + Interlocked.CompareExchange(ref field, new(_rpc), null) ?? + field; + + /// Permissions APIs. + public ServerPermissionsApi Permissions => + field ?? + Interlocked.CompareExchange(ref field, new(_rpc), null) ?? + field; + + /// ManagedSettings APIs. + public ServerManagedSettingsApi ManagedSettings => + field ?? + Interlocked.CompareExchange(ref field, new(_rpc), null) ?? + field; + + /// Environments APIs. + public ServerEnvironmentsApi Environments => + field ?? + Interlocked.CompareExchange(ref field, new(_rpc), null) ?? + field; + + /// Host APIs. + public ServerHostApi Host => + field ?? + Interlocked.CompareExchange(ref field, new(_rpc), null) ?? + field; + + /// Hooks APIs. + public ServerHooksApi Hooks => + field ?? + Interlocked.CompareExchange(ref field, new(_rpc), null) ?? + field; + + /// Models APIs. + public ServerModelsApi Models => + field ?? + Interlocked.CompareExchange(ref field, new(_rpc), null) ?? + field; + + /// ContentExclusion APIs. + public ServerContentExclusionApi ContentExclusion => + field ?? + Interlocked.CompareExchange(ref field, new(_rpc), null) ?? + field; + + /// Tools APIs. + public ServerToolsApi Tools => + field ?? + Interlocked.CompareExchange(ref field, new(_rpc), null) ?? + field; + + /// Account APIs. + public ServerAccountApi Account => + field ?? + Interlocked.CompareExchange(ref field, new(_rpc), null) ?? + field; + + /// Secrets APIs. + public ServerSecretsApi Secrets => + field ?? + Interlocked.CompareExchange(ref field, new(_rpc), null) ?? + field; + + /// Mcp APIs. + public ServerMcpApi Mcp => + field ?? + Interlocked.CompareExchange(ref field, new(_rpc), null) ?? + field; + + /// Extensions APIs. + public ServerExtensionsApi Extensions => + field ?? + Interlocked.CompareExchange(ref field, new(_rpc), null) ?? + field; + + /// Instructions APIs. + public ServerInstructionsApi Instructions => + field ?? + Interlocked.CompareExchange(ref field, new(_rpc), null) ?? + field; + + /// Skills APIs. + public ServerSkillsApi Skills => + field ?? + 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 ?? + Interlocked.CompareExchange(ref field, new(_rpc), null) ?? + field; + + /// Agents APIs. + public ServerAgentsApi Agents => + field ?? + Interlocked.CompareExchange(ref field, new(_rpc), null) ?? + field; + + /// GlobalState APIs. + public ServerGlobalStateApi GlobalState => + field ?? + Interlocked.CompareExchange(ref field, new(_rpc), null) ?? + field; + + /// Commands APIs. + public ServerCommandsApi Commands => + field ?? + Interlocked.CompareExchange(ref field, new(_rpc), null) ?? + field; + + /// GitHubRepository APIs. + public ServerGitHubRepositoryApi GitHubRepository => + field ?? + Interlocked.CompareExchange(ref field, new(_rpc), null) ?? + field; + + /// GitHubOwners APIs. + public ServerGitHubOwnersApi GitHubOwners => + field ?? + Interlocked.CompareExchange(ref field, new(_rpc), null) ?? + field; + + /// Git APIs. + public ServerGitApi Git => + field ?? + Interlocked.CompareExchange(ref field, new(_rpc), null) ?? + field; + + /// Runtime APIs. + public ServerRuntimeApi Runtime => + field ?? + Interlocked.CompareExchange(ref field, new(_rpc), null) ?? + field; + + /// SessionFs APIs. + public ServerSessionFsApi SessionFs => + field ?? + Interlocked.CompareExchange(ref field, new(_rpc), null) ?? + field; + + /// LlmInference APIs. + public ServerLlmInferenceApi LlmInference => + field ?? + Interlocked.CompareExchange(ref field, new(_rpc), null) ?? + field; + + /// Sessions APIs. + public ServerSessionsApi Sessions => + field ?? + Interlocked.CompareExchange(ref field, new(_rpc), null) ?? + field; + + /// AgentRegistry APIs. + public ServerAgentRegistryApi AgentRegistry => + field ?? + Interlocked.CompareExchange(ref field, new(_rpc), null) ?? + field; + + /// Connectors APIs. + public ServerConnectorsApi Connectors => + field ?? + Interlocked.CompareExchange(ref field, new(_rpc), null) ?? + field; +} + +/// Provides server-scoped Sandbox APIs. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -[JsonConverter(typeof(Converter))] -[DebuggerDisplay("{Value,nq}")] -public readonly struct SessionFsReaddirWithTypesEntryType : IEquatable +public sealed class ServerSandboxApi { - private readonly string? _value; + private readonly JsonRpc _rpc; + + internal ServerSandboxApi(JsonRpc rpc) + { + _rpc = rpc; + } - /// Initializes a new instance of the struct. - /// The value to associate with this . - [JsonConstructor] - public SessionFsReaddirWithTypesEntryType(string value) + /// Raises a user's sandbox settings to an administrator's managed sandbox floor and returns the result a session should actually run under. The floor can only tighten: an enterprise force-on is reflected even when the user's own `enabled` is false, and a user cannot lower a setting the floor raised. The result is normalized through the declared sandbox shape, so a key reorder or an undeclared passthrough key is not mistaken for a policy change by callers that diff two results. A host must call this rather than merge the two layers itself, because reimplementing the direction of the fold is what turns an administrator's floor into a suggestion. + /// The user's own `sandbox` settings slice. Omitted when the user has configured none, which still yields the managed floor rather than a disabled sandbox. + /// The effective managed `sandbox` slice acting as a floor. Omitted when no administrator policy applies, in which case the user's own settings stand unchanged. + /// The to monitor for cancellation requests. The default is . + /// The user's sandbox settings raised to the managed floor. + internal async Task ResolveFlooredConfigAsync(object? userSandbox = null, object? managedSandbox = null, CancellationToken cancellationToken = default) { - ArgumentException.ThrowIfNullOrWhiteSpace(value); - _value = value; + var request = new SandboxResolveFlooredConfigParams { UserSandbox = CopilotClient.ToJsonElementForWire(userSandbox), ManagedSandbox = CopilotClient.ToJsonElementForWire(managedSandbox) }; + return await CopilotClient.InvokeRpcAsync(_rpc, "sandbox.resolveFlooredConfig", [request], cancellationToken); } - /// Gets the value associated with this . - public string Value => _value ?? string.Empty; + /// Builds the effective filesystem policy one `@`-file picker checks its suggestions against, so Git discovery and grant canonicalization run once for the picker's lifetime instead of on every debounced keystroke. Returns no policy when the sandbox is absent or disabled, which the paired filter reads as "keep every candidate". A host must pair this with `sandbox.filterPickerCandidates` rather than resolve a grant list itself, because the picker's grants are narrower than a file tool's — the same path a built-in tool may read can be one the picker must not offer. + /// The session's working directory. Git discovery and grant canonicalization are resolved against it. + /// The session's resolved `sandbox` slice. Omitted, or disabled, yields no policy and every candidate is kept. + /// The to monitor for cancellation requests. The default is . + /// The prepared file-picker policy, or none when the sandbox is disabled. + internal async Task PreparePickerPolicyAsync(string cwd, object? sandbox = null, CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(cwd); - /// The entry is a file. - public static SessionFsReaddirWithTypesEntryType File { get; } = new("file"); + var request = new SandboxPreparePickerPolicyParams { Cwd = cwd, Sandbox = CopilotClient.ToJsonElementForWire(sandbox) }; + return await CopilotClient.InvokeRpcAsync(_rpc, "sandbox.preparePickerPolicy", [request], cancellationToken); + } - /// The entry is a directory. - public static SessionFsReaddirWithTypesEntryType Directory { get; } = new("directory"); + /// Checks a batch of `@`-file picker suggestions against a policy prepared by `sandbox.preparePickerPolicy` and returns one allow decision per candidate, positionally aligned with the request. Batched deliberately: each check canonicalizes a path component-by-component through the real filesystem, so one call amortizes that over a single hop rather than dozens while the user types. A directory the policy does not itself allow can still be kept as a waypoint to an allowed subtree. An absent policy keeps every candidate. + /// The candidates to check, in the order the answers are returned. + /// The session's working directory, matching the one the policy was prepared from. + /// The policy returned by `sandbox.preparePickerPolicy`. Omitted keeps every candidate. + /// The to monitor for cancellation requests. The default is . + /// Per-candidate allow decisions, positionally aligned with the request. + internal async Task FilterPickerCandidatesAsync(IList candidates, string cwd, object? sandbox = null, CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(candidates); + ArgumentNullException.ThrowIfNull(cwd); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(SessionFsReaddirWithTypesEntryType left, SessionFsReaddirWithTypesEntryType right) => left.Equals(right); + var request = new SandboxFilterPickerCandidatesParams { Candidates = candidates, Cwd = cwd, Sandbox = CopilotClient.ToJsonElementForWire(sandbox) }; + return await CopilotClient.InvokeRpcAsync(_rpc, "sandbox.filterPickerCandidates", [request], cancellationToken); + } - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(SessionFsReaddirWithTypesEntryType left, SessionFsReaddirWithTypesEntryType right) => !(left == right); + /// The sandbox a single named feature actually launches under, which is not the same as the session's sandbox. A feature is routed out of the sandbox by its own flag — `sandboxMcpServers`, `sandboxLspServers` — so an enabled session can still start one server confined and another free. Returns a disabled sandbox rather than an error when the host has no sandbox slice at all, so an absent config and an opted-out feature read alike at the launch site. A host must ask per feature rather than reuse the session config, because reading only `enabled` confines a server the user opted out of, or reports as sandboxed one that is not. + /// The routing flag naming the feature being launched, such as `sandboxMcpServers` or `sandboxLspServers`. + /// The session's resolved `sandbox` slice. Omitted when the host has none, which yields a disabled sandbox rather than an error. + /// The to monitor for cancellation requests. The default is . + /// The sandbox a named feature actually launches under. + internal async Task ResolveForOptionalAsync(string routingFlag, object? sandbox = null, CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(routingFlag); - /// - public override bool Equals(object? obj) => obj is SessionFsReaddirWithTypesEntryType other && Equals(other); + var request = new SandboxEffectiveForOptionalParams { RoutingFlag = routingFlag, Sandbox = CopilotClient.ToJsonElementForWire(sandbox) }; + return await CopilotClient.InvokeRpcAsync(_rpc, "sandbox.resolveForOptional", [request], cancellationToken); + } - /// - public bool Equals(SessionFsReaddirWithTypesEntryType other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + /// Reports whether this machine can run an OS-level command sandbox at all. A host must ask before it offers sandboxing, because the answer is a property of the machine rather than of any setting: on Linux it depends on bubblewrap being installed, and on Windows on the OS build supporting BaseContainer. Probing it requires spawning a process the first time, so the answer is cached for the life of the runtime. A host that assumes support and enables the sandbox anyway produces a session in which every shell command fails. + /// The to monitor for cancellation requests. The default is . + /// True when an OS-level sandbox can run on this host. + internal async Task CheckPlatformSupportAsync(CancellationToken cancellationToken = default) + { + return await CopilotClient.InvokeRpcAsync(_rpc, "sandbox.checkPlatformSupport", [], cancellationToken); + } - /// - public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); + /// Reports whether this Windows host's BaseContainer implementation can reach the host's loopback interface directly, which is what a local-network policy needs to avoid routing through the egress proxy. Like `sandbox.checkPlatformSupport`, the answer is a property of the machine's OS build rather than of any setting, and resolving it the first time spawns a platform probe. A host that offers the direct local-network option on a machine that lacks the capability produces a session whose sandboxed commands cannot reach local services. Always false off Windows. + /// The to monitor for cancellation requests. The default is . + /// True when the sandbox on this host can reach host loopback directly. + internal async Task CheckWindowsHostLoopbackSupportAsync(CancellationToken cancellationToken = default) + { + return await CopilotClient.InvokeRpcAsync(_rpc, "sandbox.checkWindowsHostLoopbackSupport", [], cancellationToken); + } - /// - public override string ToString() => Value; + /// The explanation a host shows when it refuses an explicit request to turn the sandbox on for a machine that cannot run one. Carries the platform-specific remedy — install bubblewrap, update Windows — and the documentation link, so a host that got `false` from `sandbox.checkPlatformSupport` does not have to compose its own reason and cannot describe the same machine differently from the runtime's own spawn path. + /// The to monitor for cancellation requests. The default is . + /// The user-facing refusal, including the platform remedy and the documentation link. + internal async Task GetUnsupportedMessageAsync(CancellationToken cancellationToken = default) + { + return await CopilotClient.InvokeRpcAsync(_rpc, "sandbox.getUnsupportedMessage", [], cancellationToken); + } - /// Provides a for serializing instances. - [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class Converter : JsonConverter + /// The notice a host shows when a stored `sandbox` setting is being ignored for this session because the machine cannot enforce it. Distinct from `sandbox.getUnsupportedMessage`, which refuses an explicit request: this one reports that a setting the user already saved is inert, states that commands will run unsandboxed, and confirms the stored setting itself is left alone. A host that stays silent here leaves the user believing a sandbox is active. + /// The to monitor for cancellation requests. The default is . + /// The user-facing notice that a saved sandbox setting is inert for this session. + internal async Task GetUnsupportedSettingDisabledNoticeAsync(CancellationToken cancellationToken = default) { - /// - public override SessionFsReaddirWithTypesEntryType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); - } + return await CopilotClient.InvokeRpcAsync(_rpc, "sandbox.getUnsupportedSettingDisabledNotice", [], cancellationToken); + } - /// - public override void Write(Utf8JsonWriter writer, SessionFsReaddirWithTypesEntryType value, JsonSerializerOptions options) - { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(SessionFsReaddirWithTypesEntryType)); - } + /// The warning a host prints at startup when a session asked for a sandbox on a machine that cannot run one. The third of the unsupported notices, and the only one that depends on the policy in force: it names the remedy the reader can actually act on, which is the stored setting for an unmanaged host, an administrator for a managed one, and waiting for the policy to resolve when the sandbox stayed on only because the policy could not be determined. Returns no notice when the machine does support a sandbox, so a host can call it unconditionally on the startup path. A host that composes its own warning tells a managed user to change a setting their policy will overwrite. + /// The managed `sandbox` slice, when a policy supplies one. The notice treats the sandbox as enforced only when this both enables it and sets `failIfUnavailable`, because a managed sandbox that tolerates being unavailable still leaves the user able to turn it off. + /// True when the sandbox stayed on only because the managed policy could not be determined. Distinguishes a policy that positively enforces the sandbox from one that merely failed to resolve, which is the difference between telling the user to contact an administrator and telling them to wait. Defaults to false. + /// The to monitor for cancellation requests. The default is . + /// The startup warning, absent when the machine can run a sandbox. + internal async Task GetUnsupportedStartupNoticeAsync(object? managedSandbox = null, bool? enabledByUndeterminedPolicy = null, CancellationToken cancellationToken = default) + { + var request = new SandboxUnsupportedStartupNoticeParams { ManagedSandbox = CopilotClient.ToJsonElementForWire(managedSandbox), EnabledByUndeterminedPolicy = enabledByUndeterminedPolicy }; + return await CopilotClient.InvokeRpcAsync(_rpc, "sandbox.getUnsupportedStartupNotice", [request], cancellationToken); } -} + /// Whether one slash command stays reachable while sandbox enforcement is unavailable and the session is otherwise blocked. The permitted set is deliberately narrow — commands that only report state, and the few that let the user repair or leave the situation — and it is keyed on the arguments as well as the name, so `/sandbox status` is admitted where `/sandbox enable` is not. A host must ask rather than keep its own list: a command this returns false for is one the runtime would refuse anyway, and a divergent list either strands the user with no way out or admits a command that acts on an unenforced session. + /// The command name as the user typed it, with or without its leading slash. + /// The command's arguments, in order. Pass an empty array for a bare command: several entries in the allowlist admit the bare form and refuse every argument. + /// The to monitor for cancellation requests. The default is . + /// True when the command stays available while enforcement is blocked. + internal async Task CheckEnforcementCommandAsync(string commandName, IList args, CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(commandName); + ArgumentNullException.ThrowIfNull(args); -/// How to execute the query: 'exec' for DDL/multi-statement (no results), 'query' for SELECT (returns rows), 'run' for INSERT/UPDATE/DELETE (returns rowsAffected). -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -[JsonConverter(typeof(Converter))] -[DebuggerDisplay("{Value,nq}")] -public readonly struct SessionFsSqliteQueryType : IEquatable -{ - private readonly string? _value; + var request = new SandboxEnforcementCommandAllowedParams { CommandName = commandName, Args = args }; + return await CopilotClient.InvokeRpcAsync(_rpc, "sandbox.checkEnforcementCommand", [request], cancellationToken); + } - /// Initializes a new instance of the struct. - /// The value to associate with this . - [JsonConstructor] - public SessionFsSqliteQueryType(string value) + /// Returns the documentation URL a host should point a user at when it declines or downgrades a sandbox setting. The runtime owns the address so every surface cites the same page and a host does not hard-code a link that later moves. + /// The to monitor for cancellation requests. The default is . + /// The documentation URL a host cites when it declines or downgrades a sandbox setting. + internal async Task GetDocsUrlAsync(CancellationToken cancellationToken = default) { - ArgumentException.ThrowIfNullOrWhiteSpace(value); - _value = value; + return await CopilotClient.InvokeRpcAsync(_rpc, "sandbox.getDocsUrl", [], cancellationToken); } - /// Gets the value associated with this . - public string Value => _value ?? string.Empty; + /// Reports whether the host running this runtime can run the command sandbox, without starting a session or spawning a sandboxed command. + /// The to monitor for cancellation requests. The default is . + /// Whether the host running this runtime can run the command sandbox. The runtime checks `supported` once per process. A capability answer can change while the process runs, for example after the user installs a missing package. + public async Task GetHostSupportAsync(CancellationToken cancellationToken = default) + { + return await CopilotClient.InvokeRpcAsync(_rpc, "sandbox.getHostSupport", [], cancellationToken); + } - /// Execute DDL or multi-statement SQL without returning rows. - public static SessionFsSqliteQueryType Exec { get; } = new("exec"); + /// ProxyCa APIs. + public ServerSandboxProxyCaApi ProxyCa => + field ?? + Interlocked.CompareExchange(ref field, new(_rpc), null) ?? + field; - /// Execute a SELECT-style query and return rows. - public static SessionFsSqliteQueryType Query { get; } = new("query"); + /// ProxySecret APIs. + public ServerSandboxProxySecretApi ProxySecret => + field ?? + Interlocked.CompareExchange(ref field, new(_rpc), null) ?? + field; - /// Execute INSERT, UPDATE, or DELETE SQL and return affected-row metadata. - public static SessionFsSqliteQueryType Run { get; } = new("run"); + /// Settings APIs. + public ServerSandboxSettingsApi Settings => + field ?? + Interlocked.CompareExchange(ref field, new(_rpc), null) ?? + field; +} - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(SessionFsSqliteQueryType left, SessionFsSqliteQueryType right) => left.Equals(right); +/// Provides server-scoped SandboxProxyCa APIs. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public sealed class ServerSandboxProxyCaApi +{ + private readonly JsonRpc _rpc; - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(SessionFsSqliteQueryType left, SessionFsSqliteQueryType right) => !(left == right); + internal ServerSandboxProxyCaApi(JsonRpc rpc) + { + _rpc = rpc; + } - /// - public override bool Equals(object? obj) => obj is SessionFsSqliteQueryType other && Equals(other); + /// Reports whether this runtime process can offer installing the sandbox credential-proxy certificate authority into OS trust. + /// The to monitor for cancellation requests. The default is . + /// True when this process can offer installing the sandbox credential-proxy certificate authority into OS trust. + public async Task CheckInstallationSupportAsync(CancellationToken cancellationToken = default) + { + return await CopilotClient.InvokeRpcAsync(_rpc, "sandbox.proxyCa.checkInstallationSupport", [], cancellationToken); + } - /// - public bool Equals(SessionFsSqliteQueryType other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + /// Reports whether this sandbox configuration uses the credential proxy and can need certificate-authority setup. + /// The sandbox configuration that the host gives its sessions. The runtime reads the credential hosts from `auth` and `credentials`; it ignores `enabled` and the other fields. + /// The to monitor for cancellation requests. The default is . + /// True when the sandbox configuration uses the credential proxy and can need certificate-authority setup. + public async Task CheckRelevanceAsync(SandboxConfig? sandboxConfig = null, CancellationToken cancellationToken = default) + { + var request = new SandboxProxyCaRequest { SandboxConfig = sandboxConfig }; + return await CopilotClient.InvokeRpcAsync(_rpc, "sandbox.proxyCa.checkRelevance", [request], cancellationToken); + } - /// - public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); + /// Reports whether the persistent certificate authority of the sandbox credential proxy exists, whether OS trust includes it, and whether it must be rotated. Changes nothing. + /// The sandbox configuration that the host gives its sessions. The runtime reads the credential hosts from `auth` and `credentials`; it ignores `enabled` and the other fields. + /// The to monitor for cancellation requests. The default is . + /// Status of the persistent certificate authority of the sandbox credential proxy. + public async Task GetStatusAsync(SandboxConfig? sandboxConfig = null, CancellationToken cancellationToken = default) + { + var request = new SandboxProxyCaRequest { SandboxConfig = sandboxConfig }; + return await CopilotClient.InvokeRpcAsync(_rpc, "sandbox.proxyCa.getStatus", [request], cancellationToken); + } - /// - public override string ToString() => Value; + /// Creates the persistent certificate authority of the sandbox credential proxy if none is stored, without changing OS trust, and returns the path of its public certificate. Keeps an existing certificate authority, even one that must be rotated. Fails where OS trust is unsupported. Trust it with sandbox.proxyCa.trust: the CLI trusts only the hosts in the saved user settings, so it refuses a certificate authority that also covers hosts from sandboxConfig. + /// The sandbox configuration that the host gives its sessions. The runtime reads the credential hosts from `auth` and `credentials`; it ignores `enabled` and the other fields. + /// The to monitor for cancellation requests. The default is . + /// Result of creating the persistent certificate authority of the sandbox credential proxy. + public async Task CreateAsync(SandboxConfig? sandboxConfig = null, CancellationToken cancellationToken = default) + { + var request = new SandboxProxyCaRequest { SandboxConfig = sandboxConfig }; + return await CopilotClient.InvokeRpcAsync(_rpc, "sandbox.proxyCa.create", [request], cancellationToken); + } - /// Provides a for serializing instances. - [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class Converter : JsonConverter + /// Replaces the persistent certificate authority of the sandbox credential proxy with a new one for the current credential hosts. If OS trust included the old one, removes it and trusts the new one, which can show an OS authentication prompt. Running sandboxed tools keep the old certificate authority until they restart. + /// The sandbox configuration that the host gives its sessions. The runtime reads the credential hosts from `auth` and `credentials`; it ignores `enabled` and the other fields. + /// The to monitor for cancellation requests. The default is . + /// Status of the persistent certificate authority of the sandbox credential proxy. + public async Task RotateAsync(SandboxConfig? sandboxConfig = null, CancellationToken cancellationToken = default) { - /// - public override SessionFsSqliteQueryType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); - } + var request = new SandboxProxyCaRequest { SandboxConfig = sandboxConfig }; + return await CopilotClient.InvokeRpcAsync(_rpc, "sandbox.proxyCa.rotate", [request], cancellationToken); + } - /// - public override void Write(Utf8JsonWriter writer, SessionFsSqliteQueryType value, JsonSerializerOptions options) - { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(SessionFsSqliteQueryType)); - } + /// Adds the persistent certificate authority of the sandbox credential proxy to OS trust, so sandboxed clients that read only OS trust accept the proxy. Call create first. Refuses a certificate authority that is not constrained to the current credential hosts. Can show an OS authentication prompt. + /// The sandbox configuration that the host gives its sessions. The runtime reads the credential hosts from `auth` and `credentials`; it ignores `enabled` and the other fields. + /// The to monitor for cancellation requests. The default is . + /// Status of the persistent certificate authority of the sandbox credential proxy. + public async Task TrustAsync(SandboxConfig? sandboxConfig = null, CancellationToken cancellationToken = default) + { + var request = new SandboxProxyCaRequest { SandboxConfig = sandboxConfig }; + return await CopilotClient.InvokeRpcAsync(_rpc, "sandbox.proxyCa.trust", [request], cancellationToken); } -} + /// Removes the persistent certificate authority of the sandbox credential proxy from OS trust. Keeps the stored certificate authority. Can show an OS authentication prompt. Sandboxed clients that read only OS trust then reject the proxy; clients that read the per-process certificate bundle continue to work. + /// The to monitor for cancellation requests. The default is . + /// Status of the persistent certificate authority of the sandbox credential proxy. + public async Task RemoveAsync(CancellationToken cancellationToken = default) + { + return await CopilotClient.InvokeRpcAsync(_rpc, "sandbox.proxyCa.remove", [], cancellationToken); + } +} -/// SQLite transaction failure classification. +/// Provides server-scoped SandboxProxySecret APIs. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -[JsonConverter(typeof(Converter))] -[DebuggerDisplay("{Value,nq}")] -public readonly struct SessionFsSqliteTransactionErrorClass : IEquatable +public sealed class ServerSandboxProxySecretApi { - private readonly string? _value; + private readonly JsonRpc _rpc; - /// Initializes a new instance of the struct. - /// The value to associate with this . - [JsonConstructor] - public SessionFsSqliteTransactionErrorClass(string value) + internal ServerSandboxProxySecretApi(JsonRpc rpc) { - ArgumentException.ThrowIfNullOrWhiteSpace(value); - _value = value; + _rpc = rpc; } - /// Gets the value associated with this . - public string Value => _value ?? string.Empty; + /// Reports where this runtime keeps the sandbox proxy password: the credential store directory and the entry identifier within it. Both are derived from the configuration directory this runtime was started with, so a host does not resolve them itself. Read this to recognize a `${secret:…}` value in stored sandbox settings as this runtime's own proxy password rather than a reference the user wrote by hand. Marked internal because it exists to carry a CLI call site off the napi boundary onto the SDK contract. + /// The to monitor for cancellation requests. The default is . + /// Where this runtime keeps the sandbox proxy password. + internal async Task GetLocationAsync(CancellationToken cancellationToken = default) + { + return await CopilotClient.InvokeRpcAsync(_rpc, "sandbox.proxySecret.getLocation", [], cancellationToken); + } - /// SQLite reported BUSY or LOCKED before commit; the transaction was rolled back and may be retried. - public static SessionFsSqliteTransactionErrorClass BusyOrLocked { get; } = new("busyOrLocked"); + /// Reads the stored sandbox proxy password from the credential store `sandbox.proxySecret.getLocation` names, preferring the OS keychain and falling back to the on-disk store. Reports an absent value rather than failing when no entry exists. Marked internal because it exists to carry a CLI call site off the napi boundary onto the SDK contract. + /// The to monitor for cancellation requests. The default is . + /// The stored sandbox proxy password, if one is present. + internal async Task GetAsync(CancellationToken cancellationToken = default) + { + return await CopilotClient.InvokeRpcAsync(_rpc, "sandbox.proxySecret.get", [], cancellationToken); + } - /// The statement, database, or provider failed definitively and must not be retried automatically. - public static SessionFsSqliteTransactionErrorClass Fatal { get; } = new("fatal"); + /// Stores the sandbox proxy password in the credential store `sandbox.proxySecret.getLocation` names, replacing any existing entry. Reports whether the OS keychain accepted it or it fell back to the on-disk store. Marked internal because it exists to carry a CLI call site off the napi boundary onto the SDK contract. + /// The password to store for the sandbox proxy. + /// The to monitor for cancellation requests. The default is . + /// Where the sandbox proxy password was stored. + internal async Task SaveAsync(string value, CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(value); - /// The transport failed after the provider may have committed; retrying could duplicate effects. - public static SessionFsSqliteTransactionErrorClass PostCommitAmbiguous { get; } = new("postCommitAmbiguous"); + var request = new SandboxProxySecretSaveRequest { Value = value }; + return await CopilotClient.InvokeRpcAsync(_rpc, "sandbox.proxySecret.save", [request], cancellationToken); + } - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(SessionFsSqliteTransactionErrorClass left, SessionFsSqliteTransactionErrorClass right) => left.Equals(right); + /// Removes the stored sandbox proxy password from the credential store `sandbox.proxySecret.getLocation` names. Succeeds when no entry exists, so it is safe to call while clearing a proxy password whose origin is unknown. Marked internal because it exists to carry a CLI call site off the napi boundary onto the SDK contract. + /// The to monitor for cancellation requests. The default is . + internal async Task DeleteAsync(CancellationToken cancellationToken = default) + { + await CopilotClient.InvokeRpcAsync(_rpc, "sandbox.proxySecret.delete", [], cancellationToken); + } +} - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(SessionFsSqliteTransactionErrorClass left, SessionFsSqliteTransactionErrorClass right) => !(left == right); +/// Provides server-scoped SandboxSettings APIs. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public sealed class ServerSandboxSettingsApi +{ + private readonly JsonRpc _rpc; - /// - public override bool Equals(object? obj) => obj is SessionFsSqliteTransactionErrorClass other && Equals(other); + internal ServerSandboxSettingsApi(JsonRpc rpc) + { + _rpc = rpc; + } - /// - public bool Equals(SessionFsSqliteTransactionErrorClass other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + /// Resolves a stored sandbox settings object into the effective one, filling every value the user left unset with its default. Read this to render or reason about the sandbox configuration a session would actually run under, rather than the sparse document the user wrote. + /// The sandbox settings as stored in the settings file. An empty object resolves to the defaults alone. + /// The to monitor for cancellation requests. The default is . + /// The effective sandbox settings, with every unset value resolved to its default. + internal async Task> ApplyDefaultsAsync(object? stored, CancellationToken cancellationToken = default) + { + var request = new SandboxApplyDefaultsRequest { Stored = CopilotClient.ToJsonElementForWire(stored) ?? JsonElement.Parse("null") }; + return await CopilotClient.InvokeRpcAsync>(_rpc, "sandbox.settings.applyDefaults", [request], cancellationToken); + } +} - /// - public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); +/// Provides server-scoped Persistence APIs. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public sealed class ServerPersistenceApi +{ + private readonly JsonRpc _rpc; - /// - public override string ToString() => Value; + internal ServerPersistenceApi(JsonRpc rpc) + { + _rpc = rpc; + } - /// Provides a for serializing instances. - [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class Converter : JsonConverter + /// Atomically replaces a JSON file's entire contents while holding the runtime's per-path lock. The lock lives in the runtime process, so a host that writes these files itself takes no part in it and can tear a concurrent runtime write; route the write here instead. Unlike a merging write this never reads or parses the existing file, so a previously corrupted snapshot is replaced wholesale rather than failing every later write. Use it only for callers that always persist a complete snapshot. + /// Absolute path of the file to replace. Parent directories are created when missing. + /// The serialized JSON document to write. It replaces the file's contents entirely; nothing is merged. + /// Comment lines to emit above the document, for a format that tolerates them. Omit for strict JSON. + /// The to monitor for cancellation requests. The default is . + /// The outcome of a locked snapshot write. + internal async Task WriteRawJsonLockedAsync(string path, string dataJson, string? fileHeader = null, CancellationToken cancellationToken = default) { - /// - public override SessionFsSqliteTransactionErrorClass Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); - } + ArgumentNullException.ThrowIfNull(path); + ArgumentNullException.ThrowIfNull(dataJson); - /// - public override void Write(Utf8JsonWriter writer, SessionFsSqliteTransactionErrorClass value, JsonSerializerOptions options) - { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(SessionFsSqliteTransactionErrorClass)); - } + var request = new PersistenceWriteRawJsonLockedRequest { Path = path, DataJson = dataJson, FileHeader = fileHeader }; + return await CopilotClient.InvokeRpcAsync(_rpc, "persistence.writeRawJsonLocked", [request], cancellationToken); } } - -/// Defines the allowed values. +/// Provides server-scoped User APIs. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -[JsonConverter(typeof(Converter))] -[DebuggerDisplay("{Value,nq}")] -public readonly struct HostExitReason : IEquatable +public sealed class ServerUserApi { - private readonly string? _value; + private readonly JsonRpc _rpc; - /// Initializes a new instance of the struct. - /// The value to associate with this . - [JsonConstructor] - public HostExitReason(string value) + internal ServerUserApi(JsonRpc rpc) { - ArgumentException.ThrowIfNullOrWhiteSpace(value); - _value = value; + _rpc = rpc; } - /// Gets the value associated with this . - public string Value => _value ?? string.Empty; - - /// The owner requested disposal. - public static HostExitReason Disposed { get; } = new("disposed"); + /// Settings APIs. + public ServerUserSettingsApi Settings => + field ?? + Interlocked.CompareExchange(ref field, new(_rpc), null) ?? + field; +} - /// The hosting task or its SDK transport exited; this does not mean the runtime process exited. - public static HostExitReason Exited { get; } = new("exited"); +/// Provides server-scoped UserSettings APIs. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public sealed class ServerUserSettingsApi +{ + private readonly JsonRpc _rpc; - /// The owning SDK connection disconnected. - public static HostExitReason OwnerDisconnected { get; } = new("ownerDisconnected"); + internal ServerUserSettingsApi(JsonRpc rpc) + { + _rpc = rpc; + } - /// The runtime is shutting down. - public static HostExitReason RuntimeShutdown { get; } = new("runtimeShutdown"); + /// Adds or removes one hook key in the user `disabledHooks` setting, rewriting settings.json in place and leaving every other setting untouched. Reports a typed outcome rather than failing the call, so a host can tell an unparseable settings file apart from a write that could not complete. + /// Path of the settings.json to rewrite. + /// Durable content-hash key of the hook, as `hooks.loadResources` reports it. + /// True adds the key to `disabledHooks`, false removes it. + /// The to monitor for cancellation requests. The default is . + /// Outcome of rewriting the `disabledHooks` setting. + internal async Task SetHookDisabledAsync(string settingsPath, string disableKey, bool disabled, CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(settingsPath); + ArgumentNullException.ThrowIfNull(disableKey); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(HostExitReason left, HostExitReason right) => left.Equals(right); + var request = new UserSettingsSetHookDisabledRequest { SettingsPath = settingsPath, DisableKey = disableKey, Disabled = disabled }; + return await CopilotClient.InvokeRpcAsync(_rpc, "user.settings.setHookDisabled", [request], cancellationToken); + } - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(HostExitReason left, HostExitReason right) => !(left == right); + /// Reads the user's settings document exactly as `user.settings.load` does, but from a caller-supplied configuration directory instead of the one the server resolved for itself. Use this when a consumer scopes a session to its own Copilot home — the SDK's per-session `configDir` override — so the settings read match the directory that session actually uses. An absent or empty `configDir` resolves the server's own home, making this identical to `user.settings.load`. Hook-validation relaxations are deliberately not caller-supplied: they continue to come from the server's environment, so a consumer cannot widen the host's hook policy through this method. + /// Copilot configuration directory to read the settings document from, taking precedence over the server's own `COPILOT_HOME` and default home. Omit it, or pass an empty string, to read the directory the server resolved for itself. + /// The to monitor for cancellation requests. The default is . + /// The stored user settings document, keyed by setting name. + internal async Task> LoadForConfigDirAsync(string? configDir = null, CancellationToken cancellationToken = default) + { + var request = new UserSettingsLoadForConfigDirRequest { ConfigDir = configDir }; + return await CopilotClient.InvokeRpcAsync>(_rpc, "user.settings.loadForConfigDir", [request], cancellationToken); + } - /// - public override bool Equals(object? obj) => obj is HostExitReason other && Equals(other); + /// Reads a repository's checked-in settings document as stored, normalized, together with any diagnostic raised while reading it. This is the repository half of the layering `user.settings.get` reports: `user.settings.load` reads the user's own document, and this reads the one a repository checks in to govern the keys `user.settings.governanceKeys` lists as repo-governable. A document that fails to parse, or that sets a key a repository may not govern, is reported through `warning` while the settings that did load are still returned, so a consumer surfaces the diagnostic and continues rather than failing the session. When `repoRoot` is the user's home directory the Claude-compatible configuration is skipped, so a home-directory session does not inherit a personal config as repository policy. + /// Absolute path of the repository root whose settings to read. When this is the user's home directory, the Claude-compatible configuration is skipped, so a home-directory session does not inherit a personal config as if it were repository policy. + /// The to monitor for cancellation requests. The default is . + /// A repository's settings as stored, together with any diagnostic raised while reading them. + internal async Task LoadRepoAsync(string repoRoot, CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(repoRoot); - /// - public bool Equals(HostExitReason other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + var request = new UserSettingsLoadRepoRequest { RepoRoot = repoRoot }; + return await CopilotClient.InvokeRpcAsync(_rpc, "user.settings.loadRepo", [request], cancellationToken); + } - /// - public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); + /// Reads a repository's settings and merges them over a supplied user layer in one step, returning the settings a consumer should act on inside that repository. Use this rather than reading the two layers separately: the merge is per setting and collection-aware, so a repository adds to the collections it is allowed to influence without replacing the user's own entries, and a whole-document overlay would get that wrong. Any diagnostic raised while reading the repository layer is returned rather than printed, so the consumer decides how to surface it; the settings that did load are merged regardless. + /// Absolute path of the repository root whose settings to read. When this is the user's home directory, the Claude-compatible configuration is skipped, so a home-directory session does not inherit a personal config as if it were repository policy. + /// The user's own settings, keyed by setting name. The repository layer is merged over this; a repository may only add to the collection settings it is allowed to influence, so a user setting it does not name survives unchanged. + /// The to monitor for cancellation requests. The default is . + /// The settings a consumer should act on inside a repository, together with any diagnostic raised while reading the repository layer. + internal async Task LoadAndMergeRepoAsync(string repoRoot, IDictionary userSettings, CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(repoRoot); + ArgumentNullException.ThrowIfNull(userSettings); - /// - public override string ToString() => Value; + var request = new UserSettingsLoadAndMergeRepoRequest { RepoRoot = repoRoot, UserSettings = userSettings }; + return await CopilotClient.InvokeRpcAsync(_rpc, "user.settings.loadAndMergeRepo", [request], cancellationToken); + } - /// Provides a for serializing instances. - [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class Converter : JsonConverter + /// Reports the on-disk path of the settings document `user.settings.load` and `user.settings.set` read and write. Resolved the same way those methods resolve it, so a host that shows or opens the settings file names the file the runtime actually uses, including the extensionless legacy `settings` file when that is what is present. + /// The to monitor for cancellation requests. The default is . + /// The on-disk location of the user's settings document. + internal async Task GetPathAsync(CancellationToken cancellationToken = default) { - /// - public override HostExitReason Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); - } - - /// - public override void Write(Utf8JsonWriter writer, HostExitReason value, JsonSerializerOptions options) - { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(HostExitReason)); - } + return await CopilotClient.InvokeRpcAsync(_rpc, "user.settings.getPath", [], cancellationToken); } -} + /// Reads the user's stored settings together with the diagnostic raised while reading them. This is `user.settings.load` plus the reason part of the document was ignored: that one answers the document alone, so a caller cannot tell a clean read from a salvaged one. Read this when the caller is about to write the file back, where accepting a partial parse would discard every value the loader could not read. The settings that did load are still reported alongside the warning, so a consumer may surface it and continue instead of failing. + /// The to monitor for cancellation requests. The default is . + /// The user's stored settings, together with any diagnostic raised while reading them. + internal async Task LoadWithWarningAsync(CancellationToken cancellationToken = default) + { + return await CopilotClient.InvokeRpcAsync(_rpc, "user.settings.loadWithWarning", [], cancellationToken); + } -/// Transport the runtime would otherwise use for this request. `http` (the default when absent) covers plain HTTP and SSE responses; `websocket` indicates a full-duplex message channel where each body chunk maps to one WebSocket message and the `binary` flag distinguishes text from binary frames. The SDK consumer uses this to decide whether to service the request with an HTTP client or a WebSocket client. It is the one piece of request metadata the consumer cannot reliably infer from the URL or headers alone. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -[JsonConverter(typeof(Converter))] -[DebuggerDisplay("{Value,nq}")] -public readonly struct LlmInferenceHttpRequestStartTransport : IEquatable -{ - private readonly string? _value; + /// Folds a user settings layer over a managed (enterprise policy) layer and returns the merged settings. The managed layer's governance keys win, `autoTier` resolves through its overridable form, and the two per-entry control maps (`enabledPlugins`, `extraKnownMarketplaces`) union across the layers and are then stamped back from the managed layer, so each entry an administrator declares wins while the user keeps control of the rest. The parameters are named for their layers rather than being a generic base/override pair: nothing about a settings object identifies which layer produced it, so the runtime cannot detect a transposed call, and transposing one resolves the governance keys in the user's favour instead of the administrator's. Supplying only one layer returns it unchanged, and supplying neither returns an empty object. Rejects a managed `autoTier` that is neither a user-configurable tier nor a single-key overridable form, rather than silently dropping it. + /// The managed layer. Its governance keys win, and the per-entry `enabledPlugins` and `extraKnownMarketplaces` control maps are stamped back from it after the union. Omitted when the host is unmanaged. + /// The user's own settings layer. Wins on every key the managed layer does not govern. Omitted when the user has no settings yet. + /// The to monitor for cancellation requests. The default is . + /// The user layer folded over the managed layer. + internal async Task MergeManagedAndUserAsync(object? managed = null, object? user = null, CancellationToken cancellationToken = default) + { + var request = new UserSettingsMergeManagedAndUserParams { Managed = CopilotClient.ToJsonElementForWire(managed), User = CopilotClient.ToJsonElementForWire(user) }; + return await CopilotClient.InvokeRpcAsync(_rpc, "user.settings.mergeManagedAndUser", [request], cancellationToken); + } - /// Initializes a new instance of the struct. - /// The value to associate with this . - [JsonConstructor] - public LlmInferenceHttpRequestStartTransport(string value) + /// Folds a repository's settings layer into the user layer. Each repo-overridable key carries a directional policy chosen so a repository can only add restrictions and never relax the user's own. The two per-entry managed control maps are the exception those directional rules cannot express — a repository may legitimately enable plugins and declare marketplaces — so they are re-stamped afterwards from the runtime's ambient managed policy. That makes the result depend on the runtime process's managed state and not on the arguments alone: a host must call this rather than reimplement the fold, or a repository could relax a restriction the administrator imposed. Rejects when either layer is not a JSON object. + /// The user's settings, already resolved against any managed layer. + /// The repository's checked-in settings layer. + /// The to monitor for cancellation requests. The default is . + /// The repository layer folded into the user layer. + internal async Task MergeRepoValueAsync(object? userSettings, object? repoSettings, CancellationToken cancellationToken = default) { - ArgumentException.ThrowIfNullOrWhiteSpace(value); - _value = value; + var request = new UserSettingsMergeRepoValueParams { UserSettings = CopilotClient.ToJsonElementForWire(userSettings) ?? JsonElement.Parse("null"), RepoSettings = CopilotClient.ToJsonElementForWire(repoSettings) ?? JsonElement.Parse("null") }; + return await CopilotClient.InvokeRpcAsync(_rpc, "user.settings.mergeRepoValue", [request], cancellationToken); } - /// Gets the value associated with this . - public string Value => _value ?? string.Empty; + /// Runs the one-time migration that moves a staff account's update channel to stable, and records that it ran. Idempotent and self-gating: it takes a lock over the global-state file, re-reads the completion stamp inside it, and returns `current` rather than migrating twice, so two hosts racing at startup cannot both migrate. Only a `migrated` status means the persisted settings changed and the caller should reload them. An account on the experimental channel is deliberately left alone and reported as `excluded-experimental`. + /// The user's home directory, used when neither a configuration directory nor `COPILOT_HOME` is set. + /// The host's environment variables. `COPILOT_HOME` is read from here, as are the variables a settings hook may expand. + /// The Copilot configuration directory, when the host set one. Takes precedence over `COPILOT_HOME` and the home directory. + /// The to monitor for cancellation requests. The default is . + /// The outcome of the one-time staff update-channel migration. + internal async Task MigrateStaffUpdateChannelOnceAsync(string homeDirectory, IDictionary environment, string? configDir = null, CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(homeDirectory); + ArgumentNullException.ThrowIfNull(environment); - /// 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. - public static LlmInferenceHttpRequestStartTransport Http { get; } = new("http"); + var request = new UserSettingsHostContext { HomeDirectory = homeDirectory, Environment = environment, ConfigDir = configDir }; + return await CopilotClient.InvokeRpcAsync(_rpc, "user.settings.migrateStaffUpdateChannelOnce", [request], cancellationToken); + } - /// 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. - public static LlmInferenceHttpRequestStartTransport Websocket { get; } = new("websocket"); + /// Runs the one-time reset that clears a staff account's pinned model selection, and reports the timestamp the host should record. Idempotent and self-gating: the reset is gated on a fixed cutoff, so a caller that passes back the timestamp it recorded gets `didReset: false` rather than a second reset. It applies to staff accounts only; any other account is reported as not reset without the settings document being opened. Only `didReset: true` means the persisted settings changed, and only then are the updated settings returned for the host to adopt. + /// The configuration directory, home directory and environment to resolve the settings and global-state documents from. A host that scopes a session to its own Copilot home must pass that directory here, or the reset is evaluated against the server's own home instead. + /// Whether the signed-in account is a staff account. The reset applies only to staff, so anything other than `true` returns `didReset: false` without touching the settings document. + /// When the caller last recorded this reset, as an ISO-8601 timestamp. A timestamp at or after the cutoff means the reset already ran and is returned unchanged. + /// The to monitor for cancellation requests. The default is . + /// The outcome of the one-time staff model-selection reset. + internal async Task ResetStaffModelSelectionOnceAsync(UserSettingsHostContext context, bool? staff = null, string? staffModelResetAt = null, CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(context); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(LlmInferenceHttpRequestStartTransport left, LlmInferenceHttpRequestStartTransport right) => left.Equals(right); + var request = new UserSettingsResetStaffModelSelectionOnceRequest { Context = context, Staff = staff, StaffModelResetAt = staffModelResetAt }; + return await CopilotClient.InvokeRpcAsync(_rpc, "user.settings.resetStaffModelSelectionOnce", [request], cancellationToken); + } - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(LlmInferenceHttpRequestStartTransport left, LlmInferenceHttpRequestStartTransport right) => !(left == right); + /// Reports the top-level keys in the user settings document that the runtime does not recognize, phrased for display to the user. This is a separate question from the warning `user.settings.loadWithWarning` returns: that one describes parts of the document the loader had to ignore to load the rest, whereas an unknown key parses cleanly and is simply not a setting, so a host that surfaces only the load warning silently accepts a misspelled key that will never take effect. Nothing is reported when the document is absent, unreadable, or failed to load, because an unknown-key list derived from a document the runtime could not read would be misleading rather than helpful. Inspection never modifies the document. + /// The configuration directory, home directory and environment to resolve the settings document from. A host that scopes a session to its own Copilot home must pass that directory here, or the server's own home is inspected instead. + /// The to monitor for cancellation requests. The default is . + /// The unrecognized top-level keys found in the user settings document, already phrased for display. + internal async Task GetUnknownKeysWarningAsync(UserSettingsHostContext context, CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(context); - /// - public override bool Equals(object? obj) => obj is LlmInferenceHttpRequestStartTransport other && Equals(other); + var request = new UserSettingsUnknownKeysWarningRequest { Context = context }; + return await CopilotClient.InvokeRpcAsync(_rpc, "user.settings.getUnknownKeysWarning", [request], cancellationToken); + } - /// - public bool Equals(LlmInferenceHttpRequestStartTransport other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + /// Resolves the effective runtime settings for a host that is about to start a session, layering the given overrides over the settings documents, the environment and the platform defaults. A host cannot assemble this itself: the precedence between a user document, a repository document, the managed layers and the environment is the runtime's own rule, and a host that re-implements it drifts from the server it is configuring. The working directory, environment and platform are taken as arguments rather than read from the server process, so a host resolving settings for a directory other than its own gets that directory's answer. Nothing is written; this only computes. + /// The directory the settings are being resolved for. This selects the repository-scoped document, so a host resolving settings for a directory other than its own process working directory must pass that directory here. + /// The environment to read settings-bearing variables from. Passed explicitly rather than read from the server process so a host can resolve settings for an environment it is about to create rather than the one it runs in. + /// The platform to apply platform-specific defaults for, in the same spelling the host's own runtime reports. + /// Settings that take precedence over every document and environment layer, such as the values a host derived from its own command line. Keyed by setting name, with each value carried verbatim, so a host may pass keys this runtime does not recognize. Absent when the host contributes no overrides of its own. + /// The to monitor for cancellation requests. The default is . + /// The effective runtime settings, keyed by setting name. Keys this runtime does not recognize are preserved rather than dropped, so a caller reading a document written by a newer runtime keeps every value. + internal async Task> BuildRuntimeAsync(string workingDirectory, IDictionary environment, string platform, IDictionary? overrides = null, CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(workingDirectory); + ArgumentNullException.ThrowIfNull(environment); + ArgumentNullException.ThrowIfNull(platform); - /// - public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); + var request = new UserSettingsBuildRuntimeRequest { WorkingDirectory = workingDirectory, Environment = environment, Platform = platform, Overrides = overrides }; + return await CopilotClient.InvokeRpcAsync>(_rpc, "user.settings.buildRuntime", [request], cancellationToken); + } - /// - public override string ToString() => Value; + /// Runs the one-time migration that moves a staff account's log level to the current default, and records that it ran. Idempotent and self-gating: it takes a lock over the global-state file and re-reads the completion stamp inside it, so two hosts racing at startup cannot both migrate. Only `refreshed` can have changed the persisted settings, and the settings it returns are the document re-read after the write, so a host should adopt them rather than reloading separately and racing the next writer. + /// The configuration directory, home directory and environment to resolve the settings and global-state documents from. A host that scopes a session to its own Copilot home must pass that directory here, or the migration is evaluated against the server's own home instead. + /// When the caller last recorded this migration, as an ISO-8601 timestamp. Supplying it lets the runtime answer `current` without re-running the evaluation. + /// The to monitor for cancellation requests. The default is . + /// The outcome of the one-time staff log-level migration. + internal async Task MigrateStaffLogLevelOnceAsync(UserSettingsHostContext context, string? cachedCompletedAt = null, CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(context); - /// Provides a for serializing instances. - [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class Converter : JsonConverter + var request = new UserSettingsMigrateStaffLogLevelOnceRequest { Context = context, CachedCompletedAt = cachedCompletedAt }; + return await CopilotClient.InvokeRpcAsync(_rpc, "user.settings.migrateStaffLogLevelOnce", [request], cancellationToken); + } + + /// Runs the one-time cleanup that removes obsolete CLI-owned settings and legacy rubber-duck keys from the user's settings document, and reports what it did. Idempotent and safe to call on every startup: with nothing obsolete left it reports `unchanged` and writes nothing. The cleanup preserves every key it does not recognize, so a setting written by a newer host is never dropped by an older one, and a document that fails to parse is reported `invalid` and left untouched rather than rewritten from a partial read. The cleaned document is not returned: it is written before this replies, so `user.settings.load` reads it authoritatively. A host that surfaces unrecognized keys through `user.settings.unknownKeys` wants this first, because the keys this retires would otherwise be shown to the user as if they were misspelled settings. + /// The configuration directory holding the settings document to clean. A host that scopes a session to its own Copilot home passes that directory here; omit it to clean the document under the home the server resolved for itself. The home directory and environment are deliberately not caller-supplied: they decide only the settings path root and the two hook-parsing flags, and those describe the process doing the reading rather than the caller. + /// The to monitor for cancellation requests. The default is . + /// The outcome of the one-time obsolete-settings cleanup. + internal async Task MigrateObsoleteOnDiskAsync(string? configDir = null, CancellationToken cancellationToken = default) { - /// - public override LlmInferenceHttpRequestStartTransport Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); - } + var request = new UserSettingsMigrateObsoleteOnDiskRequest { ConfigDir = configDir }; + return await CopilotClient.InvokeRpcAsync(_rpc, "user.settings.migrateObsoleteOnDisk", [request], cancellationToken); + } - /// - public override void Write(Utf8JsonWriter writer, LlmInferenceHttpRequestStartTransport value, JsonSerializerOptions options) - { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(LlmInferenceHttpRequestStartTransport)); - } + /// Lists every known user setting from settings.json, each with its effective value, its default, and whether it is at the default — so settings the user has never set still appear with their default value. Does not include repository- or enterprise-managed overrides that the runtime layers on top at session time. + /// The to monitor for cancellation requests. The default is . + /// Per-key metadata for every known user setting in settings.json, including settings left at their default. Excludes repository- and enterprise-managed overrides. + public async Task GetAsync(CancellationToken cancellationToken = default) + { + return await CopilotClient.InvokeRpcAsync(_rpc, "user.settings.get", [], cancellationToken); } -} + /// Resolves the on-disk locations of the settings files this runtime reads, so a consumer can show or open them. The user path is the same file `user.settings.get`, `user.settings.load`, and `user.settings.set` operate on. Supply `repoRoot` to also resolve that repository's checked-in and local settings paths. A path is reported whether or not the file exists. + /// Absolute path of a repository root. Supply it to also resolve that repository's two settings file paths; omit it to resolve only the user path. + /// The to monitor for cancellation requests. The default is . + /// On-disk locations of the settings files this runtime reads. A path is reported whether or not the file exists. + internal async Task GetPathsAsync(string? repoRoot = null, CancellationToken cancellationToken = default) + { + var request = new UserSettingsGetPathsRequest { RepoRoot = repoRoot }; + return await CopilotClient.InvokeRpcAsync(_rpc, "user.settings.getPaths", [request], cancellationToken); + } -/// Why the runtime is requesting a GitHub credential. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -[JsonConverter(typeof(Converter))] -[DebuggerDisplay("{Value,nq}")] -public readonly struct GitHubTokenAcquireReason : IEquatable -{ - private readonly string? _value; + /// Writes one or more user settings to settings.json, replacing each provided top-level key. A key whose value is null is removed. + /// Partial user settings to write, as a free-form object keyed by setting name. + /// The to monitor for cancellation requests. The default is . + public async Task SetAsync(object? settings, CancellationToken cancellationToken = default) + { + var request = new UserSettingsSetRequest { Settings = CopilotClient.ToJsonElementForWire(settings) ?? JsonElement.Parse("null") }; + await CopilotClient.InvokeRpcAsync(_rpc, "user.settings.set", [request], cancellationToken); + } - /// Initializes a new instance of the struct. - /// The value to associate with this . - [JsonConstructor] - public GitHubTokenAcquireReason(string value) + /// Lists the top-level user-setting keys by who is allowed to govern them: `repo` keys a repository may override through `.github/copilot/settings.json`, `claudeRepo` keys a repository may override through a Claude-compatible config, and `managed` keys an enterprise policy may fix. The lists are static for a given runtime build and fail closed: a key absent from a list cannot be governed that way. + /// The to monitor for cancellation requests. The default is . + /// Top-level user-setting keys grouped by who may govern them. + internal async Task ListGovernanceKeysAsync(CancellationToken cancellationToken = default) { - ArgumentException.ThrowIfNullOrWhiteSpace(value); - _value = value; + return await CopilotClient.InvokeRpcAsync(_rpc, "user.settings.listGovernanceKeys", [], cancellationToken); } - /// Gets the value associated with this . - public string Value => _value ?? string.Empty; + /// Atomically edits one canonical user setting inside its settings-file lock. Reads, canonicalizes touched aliases, applies the edit, validates the resulting top-level setting and writes before releasing the lock. An explicit autoUpdatesChannel set completes the staff migration in the same transaction. Only a malformed existing file returns `invalid`; refused keys, invalid values and I/O errors reject the call. + /// Nonempty canonical path segments of the setting to edit, starting with its top-level key. + /// Set a scalar, add or remove a string list item, or remove the setting. The handler rejects a missing or incompatible value. + /// Configuration directory to edit. Omitted or empty resolves the server's own home, as in `user.settings.loadForConfigDir`. + /// Required boolean, string or number for set, required string for add-item and remove-item, and forbidden for remove. The runtime rejects all other JSON values. + /// The to monitor for cancellation requests. The default is . + /// Outcome of an atomic user or repository settings edit. + internal async Task EditAsync(IList canonicalPath, SettingsEditOperation operation, string? configDir = null, object? value = null, CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(canonicalPath); - /// The runtime is acquiring the registration's first credential. - public static GitHubTokenAcquireReason Initial { get; } = new("initial"); + var request = new UserSettingsEditRequest { CanonicalPath = canonicalPath, Operation = operation, ConfigDir = configDir, Value = CopilotClient.ToJsonElementForWire(value) }; + return await CopilotClient.InvokeRpcAsync(_rpc, "user.settings.edit", [request], cancellationToken); + } - /// The runtime is replacing a credential that is approaching expiry. - public static GitHubTokenAcquireReason Refresh { get; } = new("refresh"); + /// Rewrites legacy and snake_case setting names in a settings object to their canonical spelling. A caller editing settings.json must normalize before writing: a legacy key left in place survives the write, is re-normalized on the next load, and silently shadows the canonical value the write just stored. + /// Free-form settings object, possibly using legacy or snake_case key names. + /// The to monitor for cancellation requests. The default is . + /// Settings object with canonical key names. + internal async Task NormalizeAsync(object? settings, CancellationToken cancellationToken = default) + { + var request = new UserSettingsNormalizeRequest { Settings = CopilotClient.ToJsonElementForWire(settings) ?? JsonElement.Parse("null") }; + return await CopilotClient.InvokeRpcAsync(_rpc, "user.settings.normalize", [request], cancellationToken); + } - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(GitHubTokenAcquireReason left, GitHubTokenAcquireReason right) => left.Equals(right); + /// Diagnoses a set of already-loaded settings layers, reporting each key that will not take effect and why. Settings load tolerantly so that one bad key does not stop a session from starting; this method surfaces what was dropped so a caller can report it instead of silently ignoring the user's intent. Takes the layers as given rather than reading them, so a caller can diagnose exactly the content it is showing, including unsaved edits. Never echoes a setting's value, because a value may be a token or another secret. Marked internal because it exists to carry a CLI call site off the native addon, not to serve a consumer-facing need; it becomes public once an SDK consumer needs to inspect settings health itself. + /// Layers to diagnose, in the precedence order the caller applies them. + /// The to monitor for cancellation requests. The default is . + /// The settings that will not take effect across the given layers. + internal async Task DiagnoseProblemsAsync(IList? sources = null, CancellationToken cancellationToken = default) + { + var request = new UserSettingsProblemsRequest { Sources = sources }; + return await CopilotClient.InvokeRpcAsync(_rpc, "user.settings.diagnoseProblems", [request], cancellationToken); + } - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(GitHubTokenAcquireReason left, GitHubTokenAcquireReason right) => !(left == right); + /// Validates one top-level setting subtree and writes just that key to settings.json as a single step. Doing both here closes the race a caller cannot close itself: between a separate preflight read and the write, the file may change. Rewriting only the touched key keeps legacy siblings out of settings.json. Answers a status rather than failing, so a validation problem and a write problem stay distinguishable. + /// Canonical path of the setting the caller is editing. Currently informational only. + /// Top-level setting name to write. + /// Value to write for the whole top-level key. Omitted when `delete` is true. Explicit null is preserved and validated as null. + /// True to remove the top-level key instead of writing it. + /// Records that the user chose an update channel explicitly, so the one-time staff channel migration stops reapplying. Honored only for the `autoUpdatesChannel` key. + /// The to monitor for cancellation requests. The default is . + /// Outcome of validating and writing one top-level setting subtree. + internal async Task ApplyTopKeyAsync(IList canonicalPath, string topKey, object? value = null, bool? delete = null, bool? completeStaffUpdateChannelMigration = null, CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(canonicalPath); + ArgumentNullException.ThrowIfNull(topKey); - /// - public override bool Equals(object? obj) => obj is GitHubTokenAcquireReason other && Equals(other); + var request = new UserSettingsApplyTopKeyRequest { CanonicalPath = canonicalPath, TopKey = topKey, Value = CopilotClient.ToJsonElementForWire(value), Delete = delete, CompleteStaffUpdateChannelMigration = completeStaffUpdateChannelMigration }; + var payload = JsonSerializer.SerializeToNode(request, RpcJsonContext.Default.UserSettingsApplyTopKeyRequest)!.AsObject(); + if (request.Delete != true && request.Value is null) payload["value"] = null; + return await CopilotClient.InvokeRpcAsync(_rpc, "user.settings.applyTopKey", [payload], cancellationToken); + } - /// - public bool Equals(GitHubTokenAcquireReason other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + /// Replaces the user settings file's entire content in one write. A key absent from `settings` is removed, which is what distinguishes this from `user.settings.set`: that one merges, so it can never clear a key the caller has dropped. For a caller saving the result of a raw edit, a merge would silently resurrect every key the user deleted. Records the same one-time mutation marks a per-key write records, so a setting the user has now chosen explicitly stops being migrated for them. + /// The settings object to store. It replaces the file's entire content, so a key absent here is removed. + /// The to monitor for cancellation requests. The default is . + internal async Task WriteAllAsync(object? settings, CancellationToken cancellationToken = default) + { + var request = new UserSettingsWriteAllRequest { Settings = CopilotClient.ToJsonElementForWire(settings) ?? JsonElement.Parse("null") }; + await CopilotClient.InvokeRpcAsync(_rpc, "user.settings.writeAll", [request], cancellationToken); + } - /// - public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); + /// Merges a partial object into one top-level user setting, re-reading the stored value inside this runtime process's settings.json write lock so that concurrent writers in this process to different sub-fields of the same key do not clobber each other. Unlike `user.settings.set`, which replaces a key's whole value with what the caller last read, this keeps sub-fields the caller did not name when both the stored value and supplied value are objects; otherwise the supplied value replaces the stored value. Use it whenever two independent surfaces own different sub-fields of one setting. Returns the key's stored value after the write, in `value`. + /// Top-level user-setting key to merge into, such as `extensions`. + /// Value to store for `key`. When both the stored value and this value are objects, their fields are shallow-merged and fields this object does not name are preserved. Otherwise this value replaces the stored value. + /// Copilot configuration directory to write the settings document in, taking precedence over the server's own `COPILOT_HOME` and default home. Omit it, or pass an empty string, to use the directory the server resolved for itself. + /// Single-component name for the settings document at `<configDir>/settings/<subDir>.json`; path separators, `.` and `..` are rejected. Omit it, or pass an empty string, to use the main settings document. + /// The to monitor for cancellation requests. The default is . + /// The stored value of the user setting after the merge. + internal async Task UpdateAsync(string key, object? value, string? configDir = null, string? subDir = null, CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(key); - /// - public override string ToString() => Value; + var request = new UserSettingsUpdateRequest { Key = key, Value = CopilotClient.ToJsonElementForWire(value) ?? JsonElement.Parse("null"), ConfigDir = configDir, SubDir = subDir }; + return await CopilotClient.InvokeRpcAsync(_rpc, "user.settings.update", [request], cancellationToken); + } - /// Provides a for serializing instances. - [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class Converter : JsonConverter + /// Reads settings.json as stored for a caller that is about to edit it. Unlike `user.settings.get`, this returns file values rather than per-key metadata and defaults. Reports `missing` for an absent file and `invalid` when the existing file will not parse. Retired config.json and extensionless files are not read. + /// The to monitor for cancellation requests. The default is . + /// Contents of settings.json as stored, for a caller about to rewrite it. + internal async Task LoadForEditAsync(CancellationToken cancellationToken = default) { - /// - public override GitHubTokenAcquireReason Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); - } + return await CopilotClient.InvokeRpcAsync(_rpc, "user.settings.loadForEdit", [], cancellationToken); + } - /// - public override void Write(Utf8JsonWriter writer, GitHubTokenAcquireReason value, JsonSerializerOptions options) - { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(GitHubTokenAcquireReason)); - } + /// Validates one top-level setting subtree without writing it, reporting each problem with the path it applies to. Scoped to a single top-level key on purpose: validating the whole file would reject an edit whenever an unrelated pre-existing field is malformed, locking the user out of the very tool that could fix it. + /// Top-level setting name to validate. + /// Value to validate. Omitted when `delete` is true. + /// True to validate a removal of this key. The current settings file is not read. + /// The to monitor for cancellation requests. The default is . + /// Outcome of validating one top-level setting subtree. + internal async Task ValidateAsync(string topKey, object? value = null, bool? delete = null, CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(topKey); + + var request = new UserSettingsValidateRequest { TopKey = topKey, Value = CopilotClient.ToJsonElementForWire(value), Delete = delete }; + return await CopilotClient.InvokeRpcAsync(_rpc, "user.settings.validate", [request], cancellationToken); } } - -/// Explicit user decisions, never inferred from a permission grant or model response. +/// Provides server-scoped Repo APIs. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -[JsonConverter(typeof(Converter))] -[DebuggerDisplay("{Value,nq}")] -public readonly struct InstallationDecision : IEquatable +public sealed class ServerRepoApi { - private readonly string? _value; + private readonly JsonRpc _rpc; - /// Initializes a new instance of the struct. - /// The value to associate with this . - [JsonConstructor] - public InstallationDecision(string value) + internal ServerRepoApi(JsonRpc rpc) { - ArgumentException.ThrowIfNullOrWhiteSpace(value); - _value = value; + _rpc = rpc; } - /// Gets the value associated with this . - public string Value => _value ?? string.Empty; - - /// The user explicitly approved the exact review on this request. - public static InstallationDecision Confirm { get; } = new("confirm"); + /// Settings APIs. + public ServerRepoSettingsApi Settings => + field ?? + Interlocked.CompareExchange(ref field, new(_rpc), null) ?? + field; +} - /// The user declined the reviewed operation. - public static InstallationDecision Decline { get; } = new("decline"); +/// Provides server-scoped RepoSettings APIs. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public sealed class ServerRepoSettingsApi +{ + private readonly JsonRpc _rpc; - /// The user cancelled the pending decision without granting consent. - public static InstallationDecision Cancel { get; } = new("cancel"); + internal ServerRepoSettingsApi(JsonRpc rpc) + { + _rpc = rpc; + } - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(InstallationDecision left, InstallationDecision right) => left.Equals(right); + /// Atomically edits one allowed top-level repository setting. Reads the raw file, preserves unrelated keys, applies one scalar or list edit, validates the result and writes under one file lock, so concurrent sibling or list edits are not lost. A malformed or unsafe existing file returns `invalid` without a write; rejected keys, invalid values and I/O errors reject the call. + /// Absolute path of the repository root. + /// Shared or local repository settings file. + /// One canonical top-level repository setting that the allowlist permits. + /// Set a scalar, add or remove a string list item, or remove the setting. The handler rejects a missing or incompatible value. + /// Required boolean, string or number for set, required string for add-item and remove-item, and forbidden for remove. The runtime rejects all other JSON values. + /// The to monitor for cancellation requests. The default is . + /// Outcome of an atomic user or repository settings edit. + internal async Task EditAsync(string repoRoot, RepoSettingsEditScope scope, string key, SettingsEditOperation operation, object? value = null, CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(repoRoot); + ArgumentNullException.ThrowIfNull(key); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(InstallationDecision left, InstallationDecision right) => !(left == right); + var request = new RepoSettingsEditRequest { RepoRoot = repoRoot, Scope = scope, Key = key, Operation = operation, Value = CopilotClient.ToJsonElementForWire(value) }; + return await CopilotClient.InvokeRpcAsync(_rpc, "repo.settings.edit", [request], cancellationToken); + } - /// - public override bool Equals(object? obj) => obj is InstallationDecision other && Equals(other); + /// Reads one repository settings file without settings normalization or layering. The existing JSONC parser accepts comments and trailing commas and applies its prototype-safety rules. Missing or blank files return an empty valid config. Invalid files return an empty config with invalid=true; other read failures reject. + /// Absolute path of the repository root. + /// Use repo for shared settings or local for settings.local.json. Other scopes are rejected. + /// The to monitor for cancellation requests. The default is . + /// Parsed repository settings without normalization or layering. + internal async Task ReadRawAsync(string repoRoot, string scope, CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(repoRoot); + ArgumentNullException.ThrowIfNull(scope); - /// - public bool Equals(InstallationDecision other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + var request = new RepoSettingsReadRawRequest { RepoRoot = repoRoot, Scope = scope }; + return await CopilotClient.InvokeRpcAsync(_rpc, "repo.settings.readRaw", [request], cancellationToken); + } - /// - public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); + /// Writes or removes one allowed top-level repository setting through the existing validator and file lock. Preserves unrelated stored keys. Invalid keys, values, existing documents, and I/O errors reject without replacing the file. Deletion requires delete=true; otherwise value must be present. + /// Absolute path of the repository root. + /// Use repo for shared settings or local for settings.local.json. Other scopes are rejected. + /// Canonical top-level key permitted by the repository settings allowlist. + /// Value to validate and write. Required unless delete is true; a null value is validated, not treated as deletion. + /// True to remove the key instead of writing it. + /// The to monitor for cancellation requests. The default is . + internal async Task WriteKeyAsync(string repoRoot, string scope, string key, object? value = null, bool? delete = null, CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(repoRoot); + ArgumentNullException.ThrowIfNull(scope); + ArgumentNullException.ThrowIfNull(key); - /// - public override string ToString() => Value; + var request = new RepoSettingsWriteKeyRequest { RepoRoot = repoRoot, Scope = scope, Key = key, Value = CopilotClient.ToJsonElementForWire(value), Delete = delete }; + var payload = JsonSerializer.SerializeToNode(request, RpcJsonContext.Default.RepoSettingsWriteKeyRequest)!.AsObject(); + if (request.Delete != true && request.Value is null) payload["value"] = null; + await CopilotClient.InvokeRpcAsync(_rpc, "repo.settings.writeKey", [payload], cancellationToken); + } - /// Provides a for serializing instances. - [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class Converter : JsonConverter + /// Replaces the repository-overridable settings in one locked write. Allowed keys present in settings are validated and written; allowed keys absent from settings are removed. Other input keys are ignored, and unknown keys already stored in the file remain unchanged. Invalid values or an invalid existing file reject without a partial write. + /// Absolute path of the repository root. + /// Use repo for shared settings or local for settings.local.json. Other scopes are rejected. + /// Object of allowed settings to store. Missing allowlisted keys are removed. Other input keys are ignored, while unknown stored keys are preserved. + /// The to monitor for cancellation requests. The default is . + internal async Task WriteAllAsync(string repoRoot, string scope, object? settings, CancellationToken cancellationToken = default) { - /// - public override InstallationDecision Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); - } + ArgumentNullException.ThrowIfNull(repoRoot); + ArgumentNullException.ThrowIfNull(scope); - /// - public override void Write(Utf8JsonWriter writer, InstallationDecision value, JsonSerializerOptions options) - { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(InstallationDecision)); - } + var request = new RepoSettingsWriteAllRequest { RepoRoot = repoRoot, Scope = scope, Settings = CopilotClient.ToJsonElementForWire(settings) ?? JsonElement.Parse("null") }; + await CopilotClient.InvokeRpcAsync(_rpc, "repo.settings.writeAll", [request], cancellationToken); } } - -/// Provides server-scoped RPC methods (no session required). -public sealed class ServerRpc +/// Provides server-scoped FeatureFlags APIs. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public sealed class ServerFeatureFlagsApi { private readonly JsonRpc _rpc; - internal ServerRpc(JsonRpc rpc) + internal ServerFeatureFlagsApi(JsonRpc rpc) { _rpc = rpc; } - /// Checks server responsiveness and returns protocol information. - /// Optional message to echo back. + /// Resolves named gates against this installation's cached online-experiment assignment, for the account the runtime is already authenticated as. Unlike `featureFlags.resolve`, which layers static availability with the populations supplied, this reads the experiment assignment the runtime cached for its own current authentication, so a launch can honor a rollout before any session exists. The account is never taken from the request: naming one would read another account's cached assignments. The cached assignment answers immediately and any network refresh happens behind it, so this call does not wait on the network. Not available to an extension, which cannot carry the user's consent to read their assignment. + /// The feature flags to resolve. A name this runtime does not declare is reported as an error rather than as a disabled flag. + /// Whether the caller is GitHub or Microsoft staff. + /// Whether the caller is in an experimental population. + /// Whether the caller belongs to a team plan. + /// Whether streamer mode is on, which withholds the populations an experiment would otherwise segment on. + /// Whether the runtime may refresh the cached assignment over the network without an active session. The refresh never blocks this call: the answer comes from the cache either way, so a refresh only affects a later launch. + /// GitHub-staff membership. Omit when it was never recorded: a legacy staff account then counts as both staff populations and keeps every staff feature. + /// Microsoft-staff membership. Omitted the same way as `isStaffGitHub`. + /// Environment to read flag overrides from. Defaults to this runtime process's own environment. + /// User settings document whose `featureFlags` block is layered on top of the resolved set. + /// When this installation first launched, in milliseconds since the Unix epoch. Experiments that segment on installation age read it. Omit when the first launch was never recorded. /// The to monitor for cancellation requests. The default is . - /// Server liveness response, including the echoed message, current server timestamp, and protocol version. - [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] - public async Task PingAsync(string? message = null, CancellationToken cancellationToken = default) + /// The resolved gates, keyed by flag name. + internal async Task> ResolveStartupGatesAsync(IList flags, bool isStaff, bool isExperimental, bool isTeam, bool streamerMode, bool refreshWithoutSession, bool? isStaffGithub = null, bool? isStaffMicrosoft = null, IDictionary? environment = null, object? config = null, double? firstLaunchAtMillis = null, CancellationToken cancellationToken = default) { - var request = new PingRequest { Message = message }; - return await CopilotClient.InvokeRpcAsync(_rpc, "ping", [request], cancellationToken); + ArgumentNullException.ThrowIfNull(flags); + + var request = new FeatureFlagsResolveStartupGatesRequest { Flags = flags, IsStaff = isStaff, IsExperimental = isExperimental, IsTeam = isTeam, StreamerMode = streamerMode, RefreshWithoutSession = refreshWithoutSession, IsStaffGitHub = isStaffGithub, IsStaffMicrosoft = isStaffMicrosoft, Environment = environment, Config = CopilotClient.ToJsonElementForWire(config), FirstLaunchAtMillis = firstLaunchAtMillis }; + return await CopilotClient.InvokeRpcAsync>(_rpc, "featureFlags.resolveStartupGates", [request], cancellationToken); } - /// 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. - /// Opt this connection in to GitHub telemetry forwarding for its lifetime. When set, the runtime forwards this host's telemetry across all its sessions, its sessionless events, and explicitly process-wide events over the `gitHubTelemetry.event` notification. Connections intentionally sharing one server receive that server's events; independently embedded runtime hosts do not receive each other's host-owned events. 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. - /// Task kinds this connection can decode when observing session tasks. Omit to retain agent and shell compatibility. - /// Connection token; required when the server was started with COPILOT_CONNECTION_TOKEN. + /// Lists every feature flag this runtime declares, with the population each one is declared for, its experimental description, and the online-experiment flag it maps to. This is the static catalog: it describes how each flag is defined, not whether it is on for anyone. Use `featureFlags.resolve` for static values with caller overrides, not active runtime values or experiment assignments. /// The to monitor for cancellation requests. The default is . - /// Handshake result reporting the server's protocol version and package version on success. - [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] - internal async Task ConnectAsync(bool? enableGitHubTelemetryForwarding = null, ConnectClientInfo? clientInfo = null, IList? supportedTaskKinds = null, string? token = null, CancellationToken cancellationToken = default) + /// Every feature flag this runtime declares, with the shape each one was declared with. + internal async Task ListMetadataAsync(CancellationToken cancellationToken = default) { - var request = new ConnectRequest { EnableGitHubTelemetryForwarding = enableGitHubTelemetryForwarding, ClientInfo = clientInfo, SupportedTaskKinds = supportedTaskKinds, Token = token }; - return await CopilotClient.InvokeRpcAsync(_rpc, "connect", [request], cancellationToken); + return await CopilotClient.InvokeRpcAsync(_rpc, "featureFlags.listMetadata", [], 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. + /// Computes static feature flags from the supplied population and overrides without changing runtime state or verifying audience membership. Starts from declared availability, then applies direct feature-flag environment values, user settings, and explicit `flagOverrides`. It does not apply `COPILOT_EXP_*` experiment assignments, authenticated startup gates, or active runtime state. Settings include `enabledFeatureFlags`, the legacy `feature_flags.enabled` list, and supported convenience settings. Supply `baseFlags` to use an existing flag set instead of declared availability. Omit `isStaffGitHub` or `isStaffMicrosoft` when membership was never recorded: a legacy staff account then counts as both populations. + /// Whether the caller is GitHub or Microsoft staff. + /// Whether the caller is in an experimental population. + /// Whether the caller belongs to the team population used by feature availability. + /// GitHub-staff membership. Omit when it was never recorded: a legacy staff account then counts as both staff populations and keeps every staff feature. + /// Microsoft-staff membership. Omitted the same way as `isStaffGitHub`. + /// Resolve against this already-computed flag set rather than each flag's declared availability. Keyed by flag name. + /// Environment for direct feature-flag overrides and `COPILOT_CLI_ENABLED_FEATURE_FLAGS`. Does not apply `COPILOT_EXP_*` experiment assignments. Defaults to this runtime process's own environment. + /// User settings to apply, including `enabledFeatureFlags`, legacy `feature_flags.enabled`, and supported convenience settings. + /// Flags forced to a value after every other layer, keyed by flag name. /// The to monitor for cancellation requests. The default is . - [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] - public async Task RegisterExtensionLaunchProviderAsync(CancellationToken cancellationToken = default) + /// The resolved feature flags, keyed by flag name. + internal async Task> ResolveAsync(bool isStaff, bool isExperimental, bool isTeam, bool? isStaffGithub = null, bool? isStaffMicrosoft = null, IDictionary? baseFlags = null, IDictionary? environment = null, object? config = null, IDictionary? flagOverrides = null, CancellationToken cancellationToken = default) { - await CopilotClient.InvokeRpcAsync(_rpc, "registerExtensionLaunchProvider", [], cancellationToken); + var request = new FeatureFlagsResolveRequest { IsStaff = isStaff, IsExperimental = isExperimental, IsTeam = isTeam, IsStaffGitHub = isStaffGithub, IsStaffMicrosoft = isStaffMicrosoft, BaseFlags = baseFlags, Environment = environment, Config = CopilotClient.ToJsonElementForWire(config), FlagOverrides = flagOverrides }; + return await CopilotClient.InvokeRpcAsync>(_rpc, "featureFlags.resolve", [request], cancellationToken); } +} - /// Environments APIs. - public ServerEnvironmentsApi Environments => - field ?? - Interlocked.CompareExchange(ref field, new(_rpc), null) ?? - field; +/// Provides server-scoped Permissions APIs. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public sealed class ServerPermissionsApi +{ + private readonly JsonRpc _rpc; - /// Host APIs. - public ServerHostApi Host => - field ?? - Interlocked.CompareExchange(ref field, new(_rpc), null) ?? - field; + internal ServerPermissionsApi(JsonRpc rpc) + { + _rpc = rpc; + } - /// Hooks APIs. - public ServerHooksApi Hooks => - field ?? - Interlocked.CompareExchange(ref field, new(_rpc), null) ?? - field; + /// Interprets a verbatim managed `permissions` slice into the three policy questions a host asks before granting elevated permissions: whether bypass-permissions mode is forbidden, whether a policy exists that this build could not determine, and whether the layer supplies permission rules the session must enforce. An absent or empty slice means no managed policy applies. The runtime and its permission service share one policy type, so a new managed permission setting is interpreted in exactly one place; a host that reimplements the reading would silently diverge the first time that set grows. Hosts must fail closed on an undetermined policy rather than treating it as permission granted. + /// The `permissions` object from a managed settings layer, for example `{"disableBypassPermissionsMode":"disable"}`. Omitted or null means no managed policy applies. + /// The to monitor for cancellation requests. The default is . + /// The policy questions a host asks before granting elevated permissions. + internal async Task EvaluateManagedPolicyAsync(object? permissions = null, CancellationToken cancellationToken = default) + { + var request = new PermissionsEvaluateManagedPolicyParams { Permissions = CopilotClient.ToJsonElementForWire(permissions) }; + return await CopilotClient.InvokeRpcAsync(_rpc, "permissions.evaluateManagedPolicy", [request], cancellationToken); + } - /// Models APIs. - public ServerModelsApi Models => + /// Locations APIs. + public ServerPermissionsLocationsApi Locations => field ?? Interlocked.CompareExchange(ref field, new(_rpc), null) ?? field; - /// Sandbox APIs. - public ServerSandboxApi Sandbox => + /// FolderTrust APIs. + public ServerPermissionsFolderTrustApi FolderTrust => field ?? Interlocked.CompareExchange(ref field, new(_rpc), null) ?? field; +} - /// Tools APIs. - public ServerToolsApi Tools => - field ?? - Interlocked.CompareExchange(ref field, new(_rpc), null) ?? - field; +/// Provides server-scoped PermissionsLocations APIs. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public sealed class ServerPermissionsLocationsApi +{ + private readonly JsonRpc _rpc; - /// Account APIs. - public ServerAccountApi Account => - field ?? - Interlocked.CompareExchange(ref field, new(_rpc), null) ?? - field; + internal ServerPermissionsLocationsApi(JsonRpc rpc) + { + _rpc = rpc; + } - /// Secrets APIs. - public ServerSecretsApi Secrets => - field ?? - Interlocked.CompareExchange(ref field, new(_rpc), null) ?? - field; + /// Drops stored per-location permission grants whose directory no longer exists, and reports which location keys were removed. Runs against the permission store under the runtime's own Copilot home, the same store `session.permissions.locations.resolve` reads. Marked internal because it exists to carry a CLI call site off the napi boundary onto the SDK contract; it is migration plumbing, not a surface consumers are meant to depend on. It can become public once an SDK consumer needs to prune its own permission store. + /// The to monitor for cancellation requests. The default is . + /// Which stored location permissions were dropped because their directory is gone. + internal async Task CleanupOrphanedAsync(CancellationToken cancellationToken = default) + { + return await CopilotClient.InvokeRpcAsync(_rpc, "permissions.locations.cleanupOrphaned", [], cancellationToken); + } +} - /// Mcp APIs. - public ServerMcpApi Mcp => - field ?? - Interlocked.CompareExchange(ref field, new(_rpc), null) ?? - field; +/// Provides server-scoped PermissionsFolderTrust APIs. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public sealed class ServerPermissionsFolderTrustApi +{ + private readonly JsonRpc _rpc; - /// Extensions APIs. - public ServerExtensionsApi Extensions => - field ?? - Interlocked.CompareExchange(ref field, new(_rpc), null) ?? - field; + internal ServerPermissionsFolderTrustApi(JsonRpc rpc) + { + _rpc = rpc; + } - /// Skills APIs. - public ServerSkillsApi Skills => - field ?? - Interlocked.CompareExchange(ref field, new(_rpc), null) ?? - field; + /// Reports whether a folder is trusted, reading the persisted trusted-folders list directly rather than through a session. Use this from a host that has no session yet, such as a one-shot command deciding whether to honor a checkout's own settings. It differs from the session-scoped check in two ways that matter: it takes the configuration directory to read the list from, instead of inheriting the session's, and it answers only from the persisted list, so trust a user granted for the lifetime of one session is not visible here. A host that has a session should prefer the session-scoped check, which also covers session-only trust. + /// Folder to check. A folder counts as trusted when it is itself listed, when an ancestor is listed, or when its git root is listed. + /// Configuration directory to read the trusted-folders list from. Omit it to read the default location; supply it when the host has redirected its configuration, or the check answers from a list the host is not using. + /// The to monitor for cancellation requests. The default is . + /// Folder trust check result. + internal async Task CheckTrustAsync(string path, string? configDir = null, CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(path); - /// Catalog APIs. - public ServerCatalogApi Catalog => - field ?? - Interlocked.CompareExchange(ref field, new(_rpc), null) ?? - field; + var request = new FolderTrustIsTrustedParams { Path = path, ConfigDir = configDir }; + return await CopilotClient.InvokeRpcAsync(_rpc, "permissions.folderTrust.checkTrust", [request], cancellationToken); + } +} - /// Plugins APIs. - public ServerPluginsApi Plugins => - field ?? - Interlocked.CompareExchange(ref field, new(_rpc), null) ?? - field; +/// Provides server-scoped ManagedSettings APIs. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public sealed class ServerManagedSettingsApi +{ + private readonly JsonRpc _rpc; - /// Agents APIs. - public ServerAgentsApi Agents => - field ?? - Interlocked.CompareExchange(ref field, new(_rpc), null) ?? - field; + internal ServerManagedSettingsApi(JsonRpc rpc) + { + _rpc = rpc; + } - /// Instructions APIs. - public ServerInstructionsApi Instructions => - field ?? - Interlocked.CompareExchange(ref field, new(_rpc), null) ?? - field; + /// Discovers device-managed settings the way the runtime's own device layer does, and reports the sandbox floor and the load outcome alongside the validated document. This differs from `managedSettings.read` in three ways a policy-enforcing host depends on: a document that validates to an empty object is reported as absent rather than as an empty policy, a discovery error does not suppress a document that still validates, and a failure is reported as a flag rather than only as error text. Hosts that compose the device layer into an effective policy use this, because `managedSettings.read` cannot distinguish "no device policy" from "device policy could not be read" and would silently hand the user's own layer the win. + /// The to monitor for cancellation requests. The default is . + /// The device-managed settings layer, with the sandbox floor and the load outcome a host needs to compose it into an effective policy. + internal async Task ReadDeviceAsync(CancellationToken cancellationToken = default) + { + return await CopilotClient.InvokeRpcAsync(_rpc, "managedSettings.readDevice", [], cancellationToken); + } - /// GlobalState APIs. - public ServerGlobalStateApi GlobalState => - field ?? - Interlocked.CompareExchange(ref field, new(_rpc), null) ?? - field; + /// Retains the supplied managed layers as this runtime process's startup policy and re-applies the result to every live session. Hosts that own their own short-lived process policy use this, so a guarded mutation is checked against the policy the host actually fetched. Retaining is a whole-process replacement rather than a merge, so a host that already resolved a good policy must not retain a partial one over it. The failure flags are honest inputs rather than assumptions: reporting a fetch or load that failed keeps the layers already retained, and the interim fail-closed state, instead of clearing them. + /// Whether a device policy source demonstrably exists. This, not a read error, is the evidence a device is managed, so a fail-closed policy gates on it. + /// Whether any managed policy source exists, independently of fetch failures. + /// Whether the server policy source exists, even when its response is unavailable. + /// Whether a required fresh server-policy confirmation failed. + /// Preserve the retained server layer during a device-only resolution. + /// Whether the device sandbox intent is unreadable independently of its other settings. + /// Whether the shared sandbox policy is undetermined. Diagnostic errors alone do not imply this flag. + /// Whether permission policy is undetermined. Sandbox-only uncertainty does not imply this flag. + /// Whether uncertainty alone enabled the shared sandbox floor. + /// Whether the server fetch failed. Report it honestly: true keeps the already-retained server layer rather than replacing it with an absent one. + /// Whether the device load failed. Report it honestly, for the same reason as `serverFetchFailed`. + /// The resolved server policy. A known source can lack a response after a forced-refresh failure. + /// The device/MDM policy layer. + /// Sandbox floor declared by the device layer, carried separately so a document that failed validation can still raise the floor. + /// Shared server/device sandbox floor, excluding session-local helper policy. + /// The policy helper's response: the third managed layer that a process owning its startup policy resolves. It is retained with the other two so that every ambient read in this process, including the mutation guards and the governed marketplace allowlist that the `plugins.*` handlers derive, sees the whole policy the process fetched. A session-owning process omits it, because it resolves a helper per session against that session's working directory. + /// The to monitor for cancellation requests. The default is . + internal async Task RetainPolicyAsync(bool deviceSourceFound, bool managedPolicyPresent, bool serverSourceFound, bool serverForcedRefreshFailure, bool preserveServerState, bool deviceSandboxUndetermined, bool sandboxFloorUndetermined, bool permissionPolicyUndetermined, bool sandboxEnabledByUndeterminedPolicy, bool serverFetchFailed, bool deviceLoadFailed, object? serverResponse = null, object? deviceResponse = null, object? deviceSandboxFloor = null, object? sharedSandboxFloor = null, object? policyHelperResponse = null, CancellationToken cancellationToken = default) + { + var request = new ManagedSettingsRetainPolicyParams { DeviceSourceFound = deviceSourceFound, ManagedPolicyPresent = managedPolicyPresent, ServerSourceFound = serverSourceFound, ServerForcedRefreshFailure = serverForcedRefreshFailure, PreserveServerState = preserveServerState, DeviceSandboxUndetermined = deviceSandboxUndetermined, SandboxFloorUndetermined = sandboxFloorUndetermined, PermissionPolicyUndetermined = permissionPolicyUndetermined, SandboxEnabledByUndeterminedPolicy = sandboxEnabledByUndeterminedPolicy, ServerFetchFailed = serverFetchFailed, DeviceLoadFailed = deviceLoadFailed, ServerResponse = CopilotClient.ToJsonElementForWire(serverResponse), DeviceResponse = CopilotClient.ToJsonElementForWire(deviceResponse), DeviceSandboxFloor = CopilotClient.ToJsonElementForWire(deviceSandboxFloor), SharedSandboxFloor = CopilotClient.ToJsonElementForWire(sharedSandboxFloor), PolicyHelperResponse = CopilotClient.ToJsonElementForWire(policyHelperResponse) }; + await CopilotClient.InvokeRpcAsync(_rpc, "managedSettings.retainPolicy", [request], cancellationToken); + } - /// Commands APIs. - public ServerCommandsApi Commands => - field ?? - Interlocked.CompareExchange(ref field, new(_rpc), null) ?? - field; + /// Stamps an effective managed policy's individually-controlled entries onto a user settings document, returning the settings a host should actually act on. The managed layer wins on every plugin spec and marketplace name it names, and leaves everything it does not name as the user wrote it. Hosts use this rather than merging the two documents themselves, because the merge is per entry rather than per document and a whole-document overlay would either discard the user's unmanaged entries or let a user entry outrank a managed one. Omitting the managed layer returns the settings unchanged, which is what an unmanaged host wants. Rejects when either document is present but is not a JSON object rather than treating it as absent, since silently dropping a malformed policy hands the user's layer the win on exactly the entries the policy exists to control. + /// The user settings document to stamp. Entries the managed layer does not name are returned exactly as supplied. + /// The effective managed policy, as composed by `managedSettings.resolveEffectiveValue`. Omit it on an unmanaged host: the settings then come back unchanged. + /// The to monitor for cancellation requests. The default is . + /// The user settings with the managed layer's controlled entries stamped on. + internal async Task ApplyControlsAsync(object? settings, object? managed = null, CancellationToken cancellationToken = default) + { + var request = new ManagedSettingsApplyControlsParams { Settings = CopilotClient.ToJsonElementForWire(settings) ?? JsonElement.Parse("null"), Managed = CopilotClient.ToJsonElementForWire(managed) }; + return await CopilotClient.InvokeRpcAsync(_rpc, "managedSettings.applyControls", [request], cancellationToken); + } - /// User APIs. - public ServerUserApi User => - field ?? - Interlocked.CompareExchange(ref field, new(_rpc), null) ?? - field; + /// Validates the managed settings a client is supplying for a session it is about to open, and returns them reduced to the keys a client is allowed to set. A client-supplied policy is not the same thing as an administrator's: it may only narrow permissions, so this accepts `permissions` and rejects every other key outright rather than ignoring it. Rejecting is the point — a host that silently dropped an unsupported key would report a policy as applied while running without it. Hosts call this before opening the session rather than letting the open fail, so a malformed policy is reported against the request that carried it. The runtime applies the same rule again when the session starts, so this cannot be used to smuggle a wider document past it. An accepted document that sets nothing comes back as an empty object, which is distinct from supplying nothing at all. + /// The managed settings a client is supplying for a session it is about to open. Omit them when the client is imposing no policy of its own. + /// The to monitor for cancellation requests. The default is . + /// The accepted managed settings, reduced to the keys a client is allowed to set. + internal async Task ParseClientValueAsync(object? settings = null, CancellationToken cancellationToken = default) + { + var request = new ManagedSettingsParseClientValueParams { Settings = CopilotClient.ToJsonElementForWire(settings) }; + return await CopilotClient.InvokeRpcAsync(_rpc, "managedSettings.parseClientValue", [request], cancellationToken); + } - /// GitHubRepository APIs. - public ServerGitHubRepositoryApi GitHubRepository => - field ?? - Interlocked.CompareExchange(ref field, new(_rpc), null) ?? - field; + /// Composes the supplied server, device/MDM, device sandbox floor and policy-helper layers into the single effective managed policy a host should enforce, resolving each key across the layers by the runtime's own precedence rules. Hosts that assemble managed layers themselves use this so their precedence matches the runtime's exactly instead of reimplementing it. The composition is not a pure function of its inputs: an advisory sandbox force-on that arrived over the server channel is declined when this host cannot run a sandbox, so the result reflects the runtime host's capabilities, and the returned settings carry `sandboxAdvisoryFloorDeclined: true` when that happened. Enforced channels (device/MDM and policy helper) are never declined, and a server force-on carrying `failIfUnavailable` is preserved so the session fails loudly instead of quietly running unsandboxed. Rejects when a supplied layer is present but is not a JSON object, rather than treating it as unmanaged, because silently dropping a malformed policy would hand the user's own layer the win on exactly the keys the policy exists to control. + /// The account's server-delivered org policy, as returned by a managed-settings fetch. Omitted before sign-in or when the fetch has not landed. + /// The device layer: native MDM and managed-file settings describing the machine rather than the account. + /// The device sandbox floor, folded into the device layer under `sandbox` before composition. + /// The policy-helper layer, an enforced channel sourced from a platform policy helper process. + /// The to monitor for cancellation requests. The default is . + /// The effective managed policy composed from the supplied layers. + internal async Task ResolveEffectiveValueAsync(object? server = null, object? mdm = null, object? deviceSandboxFloor = null, object? policyHelper = null, CancellationToken cancellationToken = default) + { + var request = new ManagedSettingsResolveEffectiveValueParams { Server = CopilotClient.ToJsonElementForWire(server), Mdm = CopilotClient.ToJsonElementForWire(mdm), DeviceSandboxFloor = CopilotClient.ToJsonElementForWire(deviceSandboxFloor), PolicyHelper = CopilotClient.ToJsonElementForWire(policyHelper) }; + return await CopilotClient.InvokeRpcAsync(_rpc, "managedSettings.resolveEffectiveValue", [request], cancellationToken); + } - /// GitHubOwners APIs. - public ServerGitHubOwnersApi GitHubOwners => - field ?? - Interlocked.CompareExchange(ref field, new(_rpc), null) ?? - field; + /// Decides what a host must do to its bypass-permissions state after a managed-policy resolution lands, given the state already in force and the account now signed in. A host holds two pieces of state — which layer disabled bypass mode, and for a server-delivered disable, the account it was pinned to — and cannot decide the next step from the new policy alone. Three rules make that so, and each one is a control rather than a preference. A device/MDM disable outranks the account, so a later server answer never lifts it. An unknown policy, which is what a failed fetch or a refusal this build cannot enforce yields, preserves whatever is in force instead of reverting to permissive. And a server disable is lifted only when the account it was pinned to is no longer the signed-in one, which is what makes signing in to an unrestricted account recover without a restart while a reconnect on the same restricted account does not. Hosts call this rather than reimplementing it because getting the pinning backwards either strands a disable on an account that never carried one or drops one that still applies. `setDisabledState` is omitted when the host must leave its stored flag untouched, which is distinct from being told to set it to false. + /// The account signed in now. A `managed` disable pinned to a different account is lifted. + /// What the resolution said about bypass mode. Send `unknown` rather than omitting the field when there is no verdict. + /// Which layer currently disables bypass mode. Omit it when nothing does. + /// The account a `managed` disable is pinned to. Omit it when the current source is not `managed`; it is ignored for the other sources. + /// The to monitor for cancellation requests. The default is . + /// The bypass-permissions state a host should hold after the resolution, and what it must do to get there. + internal async Task ResolveBypassDisableTransitionAsync(string activeIdentity, ManagedSettingsBypassPolicyResolution policy, ManagedSettingsBypassDisableSource? currentSource = null, string? currentManagedIdentity = null, CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(activeIdentity); - /// Git APIs. - public ServerGitApi Git => - field ?? - Interlocked.CompareExchange(ref field, new(_rpc), null) ?? - field; + var request = new ManagedSettingsResolveBypassDisableTransitionParams { ActiveIdentity = activeIdentity, Policy = policy, CurrentSource = currentSource, CurrentManagedIdentity = currentManagedIdentity }; + return await CopilotClient.InvokeRpcAsync(_rpc, "managedSettings.resolveBypassDisableTransition", [request], cancellationToken); + } - /// ManagedSettings APIs. - public ServerManagedSettingsApi ManagedSettings => - field ?? - Interlocked.CompareExchange(ref field, new(_rpc), null) ?? - field; + /// Resolves managed (enterprise policy) settings for a host from every layer at once: the account's server-delivered policy, the device/MDM layer, and a platform policy helper. Hosts use this instead of fetching and composing the layers themselves, because the per-layer precedence and the fail-closed rules that apply when a layer cannot be confirmed are the runtime's own and must match the policy a session will later enforce. It does not retain or apply anything to process-global policy state, so a host may call it before any session exists — which is the point, since plugin activation and one-shot commands resolve policy before a session is available to carry it. The returned per-layer outcome flags are load-bearing rather than diagnostic: a host must distinguish an unmanaged device from a managed device whose policy could not be confirmed, and apply its fail-closed gates in the second case. Reports its diagnostics in `logMessages` rather than logging them, so a host that runs before a log sink exists still sees them. + /// User agent to send with the policy fetch. + /// The host's environment variables, read for cache location and policy overrides. Supplied by the host rather than read from the runtime process so a command that selected its own configuration home is honored. + /// The host's platform, in Node's `process.platform` spelling, used to locate platform-specific cache and policy paths. + /// The host's home directory, used to resolve cache and managed-file locations. + /// The policy-helper invocation reason. Defaults to sessionStart. + /// Whether to run ingestion. Omit or set false to return an empty result without discovering or fetching policy. + /// The authenticated identity to resolve policy for. Omitted before sign-in, in which case no server layer is fetched. + /// The session whose credential store and working directory the policy helper should use. Omit it when no session exists yet and the process-wide store applies. + /// A device layer supplied by the host instead of discovered from the machine. Pair it with `hasDeviceResponse` so an explicit absence is distinguishable from an omitted override. + /// Whether `deviceResponse` is an override at all. True with a null `deviceResponse` asserts the device layer is empty; false discovers it from the machine. + /// Whether to ignore a cached server policy when reading, forcing the resolution to come from a live fetch. + /// How this ingestion may use the on-disk server-policy cache. Defaults to the runtime's own policy when omitted. + /// Whether a configured platform policy helper may be run to produce an enforced helper layer. + /// Directory the policy helper runs in when this ingestion is not owned by a registered session. A host resolving policy before its session exists must pass this, or a directory-sensitive helper judges the wrong repository. + /// Name of the embedding client, reported to the managed-settings service as `client_id` so it can refuse to deliver a policy this client is too old to enforce. Omit it and the runtime reports a generic identity; the runtime version sent alongside is the gate that actually matters. + /// The to monitor for cancellation requests. The default is . + /// The managed policy layers an ingestion resolved, together with the per-layer outcome flags a host needs to decide how much to trust the result. The flags are reported rather than folded into the settings because a host must be able to distinguish an unmanaged device from a managed device whose policy could not be confirmed, and apply its fail-closed gates in the second case. + internal async Task IngestAsync(string userAgent, object? environment, string platform, string homeDirectory, ManagedSettingsPolicyHelperReason? policyHelperReason = null, bool? selfFetch = null, object? authInfo = null, string? sessionId = null, object? deviceResponse = null, bool? hasDeviceResponse = null, bool? bypassCacheRead = null, ManagedSettingsServerPolicyCacheMode? serverPolicyCacheMode = null, bool? policyHelperEnabled = null, string? policyHelperWorkingDirectory = null, string? clientName = null, CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(userAgent); + ArgumentNullException.ThrowIfNull(platform); + ArgumentNullException.ThrowIfNull(homeDirectory); - /// Runtime APIs. - public ServerRuntimeApi Runtime => - field ?? - Interlocked.CompareExchange(ref field, new(_rpc), null) ?? - field; + var request = new ManagedSettingsIngestParams { UserAgent = userAgent, Environment = CopilotClient.ToJsonElementForWire(environment) ?? JsonElement.Parse("null"), Platform = platform, HomeDirectory = homeDirectory, PolicyHelperReason = policyHelperReason, SelfFetch = selfFetch, AuthInfo = CopilotClient.ToJsonElementForWire(authInfo), SessionId = sessionId, DeviceResponse = CopilotClient.ToJsonElementForWire(deviceResponse), HasDeviceResponse = hasDeviceResponse, BypassCacheRead = bypassCacheRead, ServerPolicyCacheMode = serverPolicyCacheMode, PolicyHelperEnabled = policyHelperEnabled, PolicyHelperWorkingDirectory = policyHelperWorkingDirectory, ClientName = clientName }; + return await CopilotClient.InvokeRpcAsync(_rpc, "managedSettings.ingest", [request], cancellationToken); + } + + /// Resolves the managed (enterprise policy) settings a host must enforce from startup, and judges the account against the enterprise sign-in pin in the same pass. This composes the layers the way `managedSettings.ingest` does and then answers the two questions a host cannot answer for itself: which fail-closed floors apply when a layer could not be confirmed, and whether this credential is allowed to sign in at all. The sign-in verdict is the reason a host must not assemble this itself from `managedSettings.ingest` — a pinned organization is an access control, and a host that composed the layers but skipped the pin would let a developer sign in with a personal or another enterprise's account and escape every control that keys off the signed-in identity. The verdict is computed on every resolution, including one that is not retained, so declining to apply policy never widens access. Set `retainAndApply` to keep the resolved policy as this runtime process's startup policy; leave it false to resolve without side effects. + /// User agent to send with the policy fetch. + /// The host's environment variables, read for cache location, policy overrides, and the variables that would make a membership snapshot forgeable. + /// The host's platform, in Node's `process.platform` spelling, used to locate platform-specific cache and policy paths. + /// The host's home directory, used to resolve cache and managed-file locations. + /// Whether to retain the resolved policy as this runtime process's startup policy, so later work in the process enforces it. Set false to resolve without side effects, which is what a protocol host wants before session creation: its sessions apply the complete policy through the normal lifecycle, and retaining here would let a readiness credential affect unrelated sessions. The returned verdict is identical either way — this decides only whether the process keeps it. + /// The policy-helper invocation reason. Defaults to sessionStart. + /// Whether to run ingestion. Omit or set false to return an empty result without discovering or fetching policy. + /// The authenticated identity to resolve policy for. Omitted before sign-in, in which case no server layer is fetched and the sign-in pin is evaluated against no credential. + /// The session whose credential store, working directory and client kind the resolution should use. Omit it when no session exists yet and the process-wide store applies. A resolution whose session names a credential other than `authInfo` is returned but not retained, so a stale sign-in cannot apply policy for an account that has already been switched away from. + /// A device layer supplied by the host instead of discovered from the machine. Pair it with `hasDeviceResponse` so an explicit absence is distinguishable from an omitted override. + /// Whether `deviceResponse` is an override at all. True with a null `deviceResponse` asserts the device layer is empty; false discovers it from the machine. + /// Whether to ignore a cached server policy when reading, forcing the resolution to come from a live fetch. + /// How this resolution may use the on-disk server-policy cache. Defaults to the runtime's own policy when omitted. Rejected when combined with `bypassCacheRead`, because the two express the same decision and accepting both would leave the caller unsure which one the runtime honored. + /// Whether a configured platform policy helper may be run to produce an enforced helper layer. + /// Directory the policy helper runs in when this resolution is not owned by a registered session. A host resolving policy before its session exists must pass this, or a directory-sensitive helper judges the wrong repository. + /// Name of the embedding client, reported to the managed-settings service as `client_id` so it can refuse to deliver a policy this client is too old to enforce. + /// The to monitor for cancellation requests. The default is . + /// The startup policy a resolution composed, the per-layer outcome flags a host needs to decide how much to trust it, and the enterprise sign-in verdict for the credential it ran against. The flags are reported rather than folded into the settings because a host must distinguish an unmanaged device from a managed device whose policy could not be confirmed, and apply its fail-closed gates in the second case. + internal async Task ResolveStartupPolicyAsync(string userAgent, object? environment, string platform, string homeDirectory, bool retainAndApply, ManagedSettingsPolicyHelperReason? policyHelperReason = null, bool? selfFetch = null, object? authInfo = null, string? sessionId = null, object? deviceResponse = null, bool? hasDeviceResponse = null, bool? bypassCacheRead = null, ManagedSettingsServerPolicyCacheMode? serverPolicyCacheMode = null, bool? policyHelperEnabled = null, string? policyHelperWorkingDirectory = null, string? clientName = null, CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(userAgent); + ArgumentNullException.ThrowIfNull(platform); + ArgumentNullException.ThrowIfNull(homeDirectory); - /// SessionFs APIs. - public ServerSessionFsApi SessionFs => - field ?? - Interlocked.CompareExchange(ref field, new(_rpc), null) ?? - field; + var request = new ManagedSettingsResolveStartupPolicyParams { UserAgent = userAgent, Environment = CopilotClient.ToJsonElementForWire(environment) ?? JsonElement.Parse("null"), Platform = platform, HomeDirectory = homeDirectory, RetainAndApply = retainAndApply, PolicyHelperReason = policyHelperReason, SelfFetch = selfFetch, AuthInfo = CopilotClient.ToJsonElementForWire(authInfo), SessionId = sessionId, DeviceResponse = CopilotClient.ToJsonElementForWire(deviceResponse), HasDeviceResponse = hasDeviceResponse, BypassCacheRead = bypassCacheRead, ServerPolicyCacheMode = serverPolicyCacheMode, PolicyHelperEnabled = policyHelperEnabled, PolicyHelperWorkingDirectory = policyHelperWorkingDirectory, ClientName = clientName }; + return await CopilotClient.InvokeRpcAsync(_rpc, "managedSettings.resolveStartupPolicy", [request], cancellationToken); + } - /// LlmInference APIs. - public ServerLlmInferenceApi LlmInference => - field ?? - Interlocked.CompareExchange(ref field, new(_rpc), null) ?? - field; + /// Resolves the managed `remoteControl` policy for an account on its own, for a host deciding whether a remote-control session may start. Composes the same layers as `managedSettings.ingest` and returns only that one key, so a host does not have to know which layer won. An absent policy means the account is unmanaged for this capability and the host may proceed; that default is safe only while absence really does mean "no policy", so a service refusal that says an account policy exists which this client is too old to enforce resolves to an explicit disabled policy instead of to absence. A resolution that cannot be completed is rejected rather than answered as absence, for the same reason. + /// User agent to send with the policy fetch. + /// The host's environment variables, read for cache location and policy overrides. + /// The host's platform, in Node's `process.platform` spelling, used to locate platform-specific cache and policy paths. + /// The host's home directory, used to resolve cache and managed-file locations. + /// The policy-helper invocation reason. Defaults to sessionStart. + /// Whether to run ingestion. Omit or set false to return an empty result without discovering or fetching policy. + /// The authenticated identity to resolve policy for. Omitted before sign-in, in which case no server layer is fetched. + /// A device layer supplied by the host instead of discovered from the machine. Pair it with `hasDeviceResponse` so an explicit absence is distinguishable from an omitted override. + /// Whether `deviceResponse` is an override at all. True with a null `deviceResponse` asserts the device layer is empty; false discovers it from the machine. + /// Whether a configured platform policy helper may be run to produce an enforced helper layer. + /// Name of the embedding client, reported to the managed-settings service as `client_id` so it can refuse to deliver a policy this client is too old to enforce. That refusal is exactly what turns into an explicit `disabled` below, so a host that omits this field forfeits the protection. + /// The to monitor for cancellation requests. The default is . + /// The managed remote-control policy in force for an account, if any. An absent policy means the account is unmanaged for this capability, which a host reads as allowed — so absence here is a positive statement that no policy applies, never a stand-in for a resolution that failed. A resolution that cannot be completed is rejected instead. + internal async Task LoadRemoteControlAsync(string userAgent, object? environment, string platform, string homeDirectory, ManagedSettingsPolicyHelperReason? policyHelperReason = null, bool? selfFetch = null, object? authInfo = null, object? deviceResponse = null, bool? hasDeviceResponse = null, bool? policyHelperEnabled = null, string? clientName = null, CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(userAgent); + ArgumentNullException.ThrowIfNull(platform); + ArgumentNullException.ThrowIfNull(homeDirectory); + + var request = new ManagedSettingsLoadRemoteControlParams { UserAgent = userAgent, Environment = CopilotClient.ToJsonElementForWire(environment) ?? JsonElement.Parse("null"), Platform = platform, HomeDirectory = homeDirectory, PolicyHelperReason = policyHelperReason, SelfFetch = selfFetch, AuthInfo = CopilotClient.ToJsonElementForWire(authInfo), DeviceResponse = CopilotClient.ToJsonElementForWire(deviceResponse), HasDeviceResponse = hasDeviceResponse, PolicyHelperEnabled = policyHelperEnabled, ClientName = clientName }; + return await CopilotClient.InvokeRpcAsync(_rpc, "managedSettings.loadRemoteControl", [request], cancellationToken); + } + + /// Evaluates the device-channel enterprise sign-in pin (MDM plist, registry, or managed-settings file) against a credential, without resolving any server-delivered policy. This is the gate an entry point runs before it branches, and the only one that can refuse a developer's first sign-in: it is local, needs no credential, and a server-delivered pin only ever reaches accounts already inside the organization. It fails closed by construction — a discovery that cannot determine whether a pin exists refuses rather than allows, because a control that fails open under a triggerable condition is not a control. Hosts that also want the server channel should call `managedSettings.resolveStartupPolicy`, which folds both channels into one verdict. + /// The host's environment variables. Read for the variables that would make an identity's membership snapshot forgeable, so a credential whose provenance cannot be trusted is refused rather than accepted. + /// The authenticated identity to judge. Omitted when signed out, which is the case the device channel exists to serve: it can refuse a first sign-in before any credential exists. + /// The to monitor for cancellation requests. The default is . + /// The verdict of the `forceLoginOrgs` enterprise sign-in pin for one credential. A managed device may pin Copilot to an approved set of GitHub organizations so a developer cannot sign in with a personal — or another enterprise's — account and escape every control that keys off the signed-in identity. A host must refuse to proceed when `allowed` is false; the remaining fields explain the refusal rather than qualifying it. + internal async Task ResolveDeviceForceLoginOrgsGateAsync(object? environment, object? authInfo = null, CancellationToken cancellationToken = default) + { + var request = new ManagedSettingsResolveDeviceForceLoginOrgsGateParams { Environment = CopilotClient.ToJsonElementForWire(environment) ?? JsonElement.Parse("null"), AuthInfo = CopilotClient.ToJsonElementForWire(authInfo) }; + return await CopilotClient.InvokeRpcAsync(_rpc, "managedSettings.resolveDeviceForceLoginOrgsGate", [request], cancellationToken); + } - /// Sessions APIs. - public ServerSessionsApi Sessions => - field ?? - Interlocked.CompareExchange(ref field, new(_rpc), null) ?? - field; + /// 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. `managedSettings.resolve` returns the same device settings together with the account's server policy. + /// 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); + } - /// AgentRegistry APIs. - public ServerAgentRegistryApi AgentRegistry => - field ?? - Interlocked.CompareExchange(ref field, new(_rpc), null) ?? - field; + /// Force-refreshes enterprise managed settings for every account: wipes the persistent server-policy cache (the whole `<cacheHome>/managed-settings` directory) and drops this runtime process's in-memory retained server policy. It does not itself fetch policy — the effect is that the next time a session resolves managed settings for an account, that resolution re-fetches the account's org policy from the network instead of serving a cached response. Note that `managedSettings.read` returns only device/MDM settings and never triggers the account server-policy fetch, so a host implementing "sync account policy" should call `managedSettings.resolve` or start a fresh session resolution rather than treat a subsequent `managedSettings.read` as the refreshed org policy. Mirrors the invalidation a sign-out performs, broadened from the one signing-out account to all of them; device/MDM layers describe the machine, not the account, and are left untouched. Rejects if the on-disk cache cannot be removed. + /// The to monitor for cancellation requests. The default is . + public async Task ClearCacheAsync(CancellationToken cancellationToken = default) + { + await CopilotClient.InvokeRpcAsync(_rpc, "managedSettings.clearCache", [], cancellationToken); + } - /// Connectors APIs. - public ServerConnectorsApi Connectors => + /// Resolves the effective enterprise managed settings without a session, from the device channel and, when an account is available, the account's server policy through the same per-account cache sessions use. A cached server policy less than an hour old is used without a fetch; otherwise the policy is fetched, and when the fetch fails a cached policy up to 24 hours old is used instead, unless `forceRemoteSettingsRefresh` requires a live fetch. With no account requested or signed in, it reports device policy only; signing out removes the account's cached policy. It can fetch server policy over the network when the cache is stale, so call it off latency-critical paths such as startup rather than before listing models. The policy helper is not run. `layers` lists each channel's document before merging, and `values` and `meta` carry typed effective values and their lock state for the keys typed so far. + /// Opaque account identifier returned by `account.getAllUsers`. When omitted, the current account is used, or device policy only when no account is signed in. + /// GitHub token to resolve instead of the current account. The call fails when the token cannot be resolved. + /// Embedding client identity for server policy requests, as in session creation. Omit for the CLI identity. + /// Working directory used to run an organization policy helper. When omitted, sessionless resolution does not run the helper. + /// The to monitor for cancellation requests. The default is . + /// Effective enterprise managed settings for an account, resolved without a session. + public async Task ResolveAsync(string? selectionId = null, string? gitHubToken = null, string? clientName = null, string? workingDirectory = null, CancellationToken cancellationToken = default) + { + var request = new ManagedSettingsResolveRequest { SelectionId = selectionId, GitHubToken = gitHubToken, ClientName = clientName, WorkingDirectory = workingDirectory }; + return await CopilotClient.InvokeRpcAsync(_rpc, "managedSettings.resolve", [request], cancellationToken); + } + + /// Compatibility overload preserving the positional CancellationToken parameter from before workingDirectory was added. + public Task ResolveAsync(string? selectionId, string? gitHubToken, string? clientName, CancellationToken cancellationToken) + => ResolveAsync(selectionId, gitHubToken, clientName, workingDirectory: null, cancellationToken); + + /// Returns the managed-settings authoring JSON schema with descriptive `x-composition` annotations aligned with the shared settings-engine vocabulary. These annotations are not a complete runtime composition contract: model, effortLevel, and contextTier remain coupled. Use `managedSettings.compose` for the runtime's effective result. Performs no I/O. + /// The to monitor for cancellation requests. The default is . + /// The authoring JSON schema for managed settings recognized by this runtime. + public async Task SchemaAsync(CancellationToken cancellationToken = default) + { + return await CopilotClient.InvokeRpcAsync(_rpc, "managedSettings.schema", [], cancellationToken); + } + + /// Validates a candidate managed-settings document the way the runtime validates delivered policy, without applying it. Reports errors that would reject the document, warnings for content the runtime ignores, and the canonical document it would apply. Document text nested more than 64 levels deep is rejected. Performs no I/O. + /// The document to validate: a JSON object, or a string containing the document's JSON text. Preview documents are limited to 1 MiB and 64 levels of nesting, a stricter resource limit than delivered-policy parsing; violations are returned as diagnostics. + /// Channel the document is meant for (`device`, `server`, or `policyHelper`). Some keys are only honored in some channels; for example, a `policyHelper` registration is ignored in policy-helper output. When omitted, no channel-specific checks run. + /// The to monitor for cancellation requests. The default is . + /// Result of validating a managed-settings document. + public async Task ValidateAsync(object? content, string? layer = null, CancellationToken cancellationToken = default) + { + var request = new ManagedSettingsValidateRequest { Content = CopilotClient.ToJsonElementForWire(content) ?? JsonElement.Parse("null"), Layer = layer }; + return await CopilotClient.InvokeRpcAsync(_rpc, "managedSettings.validate", [request], cancellationToken); + } + + /// Merges candidate managed-settings documents for the device, server, and policy-helper channels into the effective settings the runtime would enforce on this host, using the same precedence and composition rules as live resolution, without applying them. Like live resolution, a server's advisory sandbox force-enable is declined on a host that cannot run the sandbox. Does not fetch policy or read policy files, but may perform blocking OS or subprocess probes for sandbox support. Preview documents are limited to 1 MiB and 64 levels of nesting. + /// One entry per channel. `source` must be `device`, `server`, or `policyHelper`, each at most once (checked at runtime); order does not matter, because channel precedence is fixed. To preview documents from resolve output, map recognized source strings to ManagedSettingsChannel and copy their settings; generated resolve and compose layer types are distinct. Omitted settings means this channel delivered no document. Supplied documents must be valid within the preview limits; warnings are returned in diagnostics. Compose does not reproduce source-failure state or retained enforcement floors from resolve. + /// The to monitor for cancellation requests. The default is . + /// The effective managed settings the runtime would enforce for the given documents, in the same shape `managedSettings.resolve` returns. + public async Task ComposeAsync(IList layers, CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(layers); + + var request = new ManagedSettingsComposeRequest { Layers = layers }; + return await CopilotClient.InvokeRpcAsync(_rpc, "managedSettings.compose", [request], cancellationToken); + } + + /// Permissions APIs. + public ServerManagedSettingsPermissionsApi Permissions => field ?? Interlocked.CompareExchange(ref field, new(_rpc), null) ?? field; } +/// Provides server-scoped ManagedSettingsPermissions APIs. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public sealed class ServerManagedSettingsPermissionsApi +{ + private readonly JsonRpc _rpc; + + internal ServerManagedSettingsPermissionsApi(JsonRpc rpc) + { + _rpc = rpc; + } + + /// Pure, sessionless evaluation of operations against a supplied permissionsContext. Performs no policy discovery, I/O, prompting, navigation, permission grants, or sandbox changes. Obtain the context from managedSettings.resolve or session.managedSettings.get, or reuse an in-memory context. Results preserve input order and duplicates. Consumers must block deny, obtain approval for ask, and use their normal permission flow for unmanaged. Unknown policy returns failClosed with every operation denied. This is not a substitute for user permissions or other security controls. + /// Trusted, reusable managed permission policy snapshot; no policy is discovered during evaluation. + /// Operations to evaluate, preserving input order and duplicates. An empty batch is valid. + /// The to monitor for cancellation requests. The default is . + /// Ordered managed permission verdicts and the supplied policy's fail-closed posture. + public async Task EvaluateAsync(ManagedPermissionsContext context, IList operations, CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(context); + ArgumentNullException.ThrowIfNull(operations); + + var request = new ManagedSettingsPermissionsEvaluateRequest { Context = context, Operations = operations }; + return await CopilotClient.InvokeRpcAsync(_rpc, "managedSettings.permissions.evaluate", [request], cancellationToken); + } +} + /// Provides server-scoped Environments APIs. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] public sealed class ServerEnvironmentsApi @@ -43713,6 +48601,16 @@ public async Task DiscoverAsync(IList? projectPaths var request = new HooksDiscoverRequest { ProjectPaths = projectPaths, ExcludeHostHooks = excludeHostHooks }; return await CopilotClient.InvokeRpcAsync(_rpc, "hooks.discover", [request], cancellationToken); } + + /// Discovers hook actions enabled under server-side discovery settings from user, repository, plugin, and managed-policy sources. + /// Optional project paths and host-exclusion behavior for server-scoped hook discovery. + /// 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(HooksDiscoverRequest request, CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(request); + return await CopilotClient.InvokeRpcAsync(_rpc, "hooks.discover", [request], cancellationToken); + } } /// Provides server-scoped Models APIs. @@ -43746,89 +48644,27 @@ public async Task GetBuiltInCatalogAsync(CancellationToken } } -/// Provides server-scoped Sandbox APIs. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class ServerSandboxApi -{ - private readonly JsonRpc _rpc; - - internal ServerSandboxApi(JsonRpc rpc) - { - _rpc = rpc; - } - - /// Reports whether the host running this runtime can run the command sandbox, without starting a session or spawning a sandboxed command. - /// The to monitor for cancellation requests. The default is . - /// Whether the host running this runtime can run the command sandbox. The runtime checks `supported` once per process. A capability answer can change while the process runs, for example after the user installs a missing package. - public async Task GetHostSupportAsync(CancellationToken cancellationToken = default) - { - return await CopilotClient.InvokeRpcAsync(_rpc, "sandbox.getHostSupport", [], cancellationToken); - } - - /// ProxyCa APIs. - public ServerSandboxProxyCaApi ProxyCa => - field ?? - Interlocked.CompareExchange(ref field, new(_rpc), null) ?? - field; -} - -/// Provides server-scoped SandboxProxyCa APIs. +/// Provides server-scoped ContentExclusion APIs. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class ServerSandboxProxyCaApi +public sealed class ServerContentExclusionApi { private readonly JsonRpc _rpc; - internal ServerSandboxProxyCaApi(JsonRpc rpc) + internal ServerContentExclusionApi(JsonRpc rpc) { _rpc = rpc; } - /// Reports whether the persistent certificate authority of the sandbox credential proxy exists, whether OS trust includes it, and whether it must be rotated. Changes nothing. - /// The sandbox configuration that the host gives its sessions. The runtime reads the credential hosts from `auth` and `credentials`; it ignores `enabled` and the other fields. - /// The to monitor for cancellation requests. The default is . - /// Status of the persistent certificate authority of the sandbox credential proxy. - public async Task GetStatusAsync(SandboxConfig? sandboxConfig = null, CancellationToken cancellationToken = default) - { - var request = new SandboxProxyCaRequest { SandboxConfig = sandboxConfig }; - return await CopilotClient.InvokeRpcAsync(_rpc, "sandbox.proxyCa.getStatus", [request], cancellationToken); - } - - /// Creates the persistent certificate authority of the sandbox credential proxy if none is stored, without changing OS trust, and returns the path of its public certificate. Keeps an existing certificate authority, even one that must be rotated. Fails where OS trust is unsupported. Trust it with sandbox.proxyCa.trust: the CLI trusts only the hosts in the saved user settings, so it refuses a certificate authority that also covers hosts from sandboxConfig. - /// The sandbox configuration that the host gives its sessions. The runtime reads the credential hosts from `auth` and `credentials`; it ignores `enabled` and the other fields. - /// The to monitor for cancellation requests. The default is . - /// Result of creating the persistent certificate authority of the sandbox credential proxy. - public async Task CreateAsync(SandboxConfig? sandboxConfig = null, CancellationToken cancellationToken = default) - { - var request = new SandboxProxyCaRequest { SandboxConfig = sandboxConfig }; - return await CopilotClient.InvokeRpcAsync(_rpc, "sandbox.proxyCa.create", [request], cancellationToken); - } - - /// Replaces the persistent certificate authority of the sandbox credential proxy with a new one for the current credential hosts. If OS trust included the old one, removes it and trusts the new one, which can show an OS authentication prompt. Running sandboxed tools keep the old certificate authority until they restart. - /// The sandbox configuration that the host gives its sessions. The runtime reads the credential hosts from `auth` and `credentials`; it ignores `enabled` and the other fields. - /// The to monitor for cancellation requests. The default is . - /// Status of the persistent certificate authority of the sandbox credential proxy. - public async Task RotateAsync(SandboxConfig? sandboxConfig = null, CancellationToken cancellationToken = default) - { - var request = new SandboxProxyCaRequest { SandboxConfig = sandboxConfig }; - return await CopilotClient.InvokeRpcAsync(_rpc, "sandbox.proxyCa.rotate", [request], cancellationToken); - } - - /// Adds the persistent certificate authority of the sandbox credential proxy to OS trust, so sandboxed clients that read only OS trust accept the proxy. Call create first. Refuses a certificate authority that is not constrained to the current credential hosts. Can show an OS authentication prompt. - /// The sandbox configuration that the host gives its sessions. The runtime reads the credential hosts from `auth` and `credentials`; it ignores `enabled` and the other fields. + /// Validates candidate content-exclusion policies and returns them normalized, without starting a session. This runs the same parser the runtime's own policy enforcement uses, so a host cannot accept a policy set the runtime would later reject, nor reject one it would have honored. Call it when policies arrive from outside the runtime — a command-line flag, a configuration file, a host's own settings — and must be checked before a session exists to apply them to. A malformed document fails with a message prefixed `Invalid JSON: `; a well-formed document that violates the policy schema fails with the offending field paths. + /// Unparsed JSON text expected to hold an array of content-exclusion policies. /// The to monitor for cancellation requests. The default is . - /// Status of the persistent certificate authority of the sandbox credential proxy. - public async Task TrustAsync(SandboxConfig? sandboxConfig = null, CancellationToken cancellationToken = default) + /// The accepted policies, parsed and normalized. A rejected input is reported as an error rather than in this result. + public async Task ValidatePoliciesAsync(string policiesJson, CancellationToken cancellationToken = default) { - var request = new SandboxProxyCaRequest { SandboxConfig = sandboxConfig }; - return await CopilotClient.InvokeRpcAsync(_rpc, "sandbox.proxyCa.trust", [request], cancellationToken); - } + ArgumentNullException.ThrowIfNull(policiesJson); - /// Removes the persistent certificate authority of the sandbox credential proxy from OS trust. Keeps the stored certificate authority. Can show an OS authentication prompt. Sandboxed clients that read only OS trust then reject the proxy; clients that read the per-process certificate bundle continue to work. - /// The to monitor for cancellation requests. The default is . - /// Status of the persistent certificate authority of the sandbox credential proxy. - public async Task RemoveAsync(CancellationToken cancellationToken = default) - { - return await CopilotClient.InvokeRpcAsync(_rpc, "sandbox.proxyCa.remove", [], cancellationToken); + var request = new ContentExclusionValidatePoliciesRequest { PoliciesJson = policiesJson }; + return await CopilotClient.InvokeRpcAsync(_rpc, "contentExclusion.validatePolicies", [request], cancellationToken); } } @@ -43917,6 +48753,51 @@ public async Task LogoutAsync(string? selectionId = null, A var request = new AccountLogoutRequest { SelectionId = selectionId, AuthInfo = authInfo }; return await CopilotClient.InvokeRpcAsync(_rpc, "account.logout", [request], cancellationToken); } + + /// Renders the sign-in banner and the one-line account description the runtime uses for an authentication identity. The runtime words these in several places for its own callers, so a host that announces a sign-in or an account switch should render them from here rather than composing its own, which would let the two spellings drift apart. + /// Identity to describe. + /// The to monitor for cancellation requests. The default is . + /// The wording the runtime uses for an authentication identity. + internal async Task DescribeAuthInfoAsync(AuthIdentity identity, CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(identity); + + var request = new AccountDescribeAuthInfoRequest { Identity = identity }; + return await CopilotClient.InvokeRpcAsync(_rpc, "account.describeAuthInfo", [request], cancellationToken); + } + + /// Reports whether the supplied identity can create gists. Gists are unavailable on GitHub Enterprise Cloud with data residency, and to Enterprise Managed Users on github.com, so a host that offers gist actions should hide or disable them when this reports no support. + /// Identity to evaluate. + /// The to monitor for cancellation requests. The default is . + /// Whether the identity can create gists, and why not when it cannot. + internal async Task GetGistSupportAsync(AuthIdentity identity, CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(identity); + + var request = new AccountGetGistSupportRequest { Identity = identity }; + return await CopilotClient.InvokeRpcAsync(_rpc, "account.getGistSupport", [request], cancellationToken); + } + + /// Resolves a path on the identity's GitHub host into an absolute URL, so a host can link to settings or documentation on the enterprise instance the user is signed in to rather than to github.com. Falls back to the public host when no identity is supplied. + /// Host-relative path, without a leading slash, for example `settings/copilot/features`. + /// Identity whose host the path resolves against. Omit to resolve against the public github.com host. + /// The to monitor for cancellation requests. The default is . + /// An absolute URL on the identity's GitHub host. + internal async Task GetWebUrlAsync(string path, AuthIdentity? identity = null, CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(path); + + var request = new AccountGetWebUrlRequest { Path = path, Identity = identity }; + return await CopilotClient.InvokeRpcAsync(_rpc, "account.getWebUrl", [request], cancellationToken); + } + + /// Names the environment variable supplying a classic personal access token, if one is set. A classic token cannot be upgraded in place, so a host that finds one should tell the user which variable to unset rather than starting an interactive sign-in that the variable would override. + /// The to monitor for cancellation requests. The default is . + /// The environment variable supplying a classic personal access token, when one is set. + internal async Task GetTokenEnvVarAsync(CancellationToken cancellationToken = default) + { + return await CopilotClient.InvokeRpcAsync(_rpc, "account.getTokenEnvVar", [], cancellationToken); + } } /// Provides server-scoped Secrets APIs. @@ -43965,6 +48846,26 @@ public async Task DiscoverAsync(string? workingDirectory = nu return await CopilotClient.InvokeRpcAsync(_rpc, "mcp.discover", [request], cancellationToken); } + /// Lists the MCP servers the runtime supplies itself, so a host that disables the built-ins wholesale suppresses exactly the set the runtime would otherwise add. The names are fixed for a build and depend on no configuration, session or account. + /// The to monitor for cancellation requests. The default is . + /// Names of the MCP servers the runtime supplies itself. + internal async Task ListBuiltinServerNamesAsync(CancellationToken cancellationToken = default) + { + return await CopilotClient.InvokeRpcAsync(_rpc, "mcp.listBuiltinServerNames", [], cancellationToken); + } + + /// Reports whether the gh-replaceable GitHub MCP tools may be clipped for a session, which a host feeds back as the `excludeGhReplaceableTools` build option. The tools are redundant only when the session can reach a shell *and* the host has the `gh` that would replace them, so both halves are decided here. The shell half runs the platform shell tool through the runtime's own tool-filter matcher, so entry forms like `builtin:bash` and `builtin:*` behave exactly as they do when a session builds its tool catalog. The host half probes for `gh`, and is skipped entirely when the filters already rule the shell out. Host presence alone is not enough: a session restricted to, say, `view` would otherwise lose the built-in issue-read fallback while having no shell to replace it with. The answer describes the host the runtime runs on, so it is never forwarded to a remote engine. + /// The session's tool allowlist, when it set one. Omitted means the session constrains nothing this way. + /// The session's tool denylist, when it set one. Omitted means the session constrains nothing this way. + /// How the allowlist and denylist combine when both are set. Omitted means the default every session gets, so a caller that never chose a precedence is answered as its sessions behave. + /// The to monitor for cancellation requests. The default is . + /// Whether the gh-replaceable GitHub MCP tools may be clipped for the session described by the request. + internal async Task ShouldExcludeGitHubToolsAsync(IList? availableTools = null, IList? excludedTools = null, OptionsUpdateToolFilterPrecedence? toolFilterPrecedence = null, CancellationToken cancellationToken = default) + { + var request = new McpShouldExcludeGitHubToolsRequest { AvailableTools = availableTools, ExcludedTools = excludedTools, ToolFilterPrecedence = toolFilterPrecedence }; + return await CopilotClient.InvokeRpcAsync(_rpc, "mcp.shouldExcludeGitHubTools", [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. @@ -44077,6 +48978,12 @@ public async Task ApplyUninstallAsync(CatalogClientContra Interlocked.CompareExchange(ref field, new(_rpc), null) ?? field; + /// Registry APIs. + public ServerMcpRegistryApi Registry => + field ?? + Interlocked.CompareExchange(ref field, new(_rpc), null) ?? + field; + /// Installations APIs. public ServerMcpInstallationsApi Installations => field ?? @@ -44107,12 +49014,11 @@ public async Task ListAsync(CancellationToken cancellationToken = /// Unique name for the MCP server. /// MCP server configuration (stdio process or remote HTTP/SSE). /// The to monitor for cancellation requests. The default is . - public async Task AddAsync(string name, object config, CancellationToken cancellationToken = default) + public async Task AddAsync(string name, object? config, CancellationToken cancellationToken = default) { ArgumentNullException.ThrowIfNull(name); - ArgumentNullException.ThrowIfNull(config); - var request = new McpConfigAddRequest { Name = name, Config = CopilotClient.ToJsonElementForWire(config)!.Value }; + var request = new McpConfigAddRequest { Name = name, Config = CopilotClient.ToJsonElementForWire(config) ?? JsonElement.Parse("null") }; await CopilotClient.InvokeRpcAsync(_rpc, "mcp.config.add", [request], cancellationToken); } @@ -44120,12 +49026,11 @@ public async Task AddAsync(string name, object config, CancellationToken cancell /// Name of the MCP server to update. /// MCP server configuration (stdio process or remote HTTP/SSE). /// The to monitor for cancellation requests. The default is . - public async Task UpdateAsync(string name, object config, CancellationToken cancellationToken = default) + public async Task UpdateAsync(string name, object? config, CancellationToken cancellationToken = default) { ArgumentNullException.ThrowIfNull(name); - ArgumentNullException.ThrowIfNull(config); - var request = new McpConfigUpdateRequest { Name = name, Config = CopilotClient.ToJsonElementForWire(config)!.Value }; + var request = new McpConfigUpdateRequest { Name = name, Config = CopilotClient.ToJsonElementForWire(config) ?? JsonElement.Parse("null") }; await CopilotClient.InvokeRpcAsync(_rpc, "mcp.config.update", [request], cancellationToken); } @@ -44171,6 +49076,50 @@ public async Task ReloadAsync(CancellationToken cancellationToken = default) } } +/// Provides server-scoped McpRegistry APIs. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public sealed class ServerMcpRegistryApi +{ + private readonly JsonRpc _rpc; + + internal ServerMcpRegistryApi(JsonRpc rpc) + { + _rpc = rpc; + } + + /// Allocates an ID for one cancellable MCP registry search. The ID exists before the search starts, so callers can cancel before it starts. The networking stack supplies the cancellation namespace. The runtime retains at most 1,024 unused IDs. At capacity, another allocation can reclaim an unused ID. Active searches retain their IDs until they finish. + /// The to monitor for cancellation requests. The default is . + /// Request id naming a cancellable registry search. + internal async Task AllocateRequestIdAsync(CancellationToken cancellationToken = default) + { + return await CopilotClient.InvokeRpcAsync(_rpc, "mcp.registry.allocateRequestId", [], cancellationToken); + } + + /// Searches the MCP registry the supplied credential may read, resolving the registry endpoint from policy first. Hosts usually send credential-free `AuthIdentity`; a `token` identity can be resolved only when it embeds a token, while `env` and `gh-cli` identities can use a token embedded in the request first. The runtime follows registry pages, keeps the newest entry per server name, cuts the list to `limit`, and sorts an empty-query result by GitHub stars. Each server object is carried opaquely. + /// Request ID from `mcp.registry.allocateRequestId`. The search refuses unknown, reclaimed, or canceled IDs and IDs that another search or request already uses. + /// The credential the search runs under, carried opaquely. Hosts usually send credential-free `AuthIdentity`; a `token` identity can be resolved only when it embeds a token, while `env` and `gh-cli` identities can use a token embedded in the request first. + /// Maximum number of servers to return. + /// Free-text query. Omitted or empty asks the registry for its top servers rather than searching. A value that is not a string is refused. + /// Repository used for the policy lookup, as `owner/name`. The policy selects the registry URL and whether the user token goes to the registry. The registry receives this repository only when the policy entry lists it as required context. A value that is not a string is refused. + /// The to monitor for cancellation requests. The default is . + /// Servers selected from the registry response. + internal async Task SearchAsync(long requestId, object? authInfo, long limit, string? query = null, string? repository = null, CancellationToken cancellationToken = default) + { + var request = new McpRegistrySearchRequest { RequestId = requestId, AuthInfo = CopilotClient.ToJsonElementForWire(authInfo) ?? JsonElement.Parse("null"), Limit = limit, Query = query, Repository = repository }; + return await CopilotClient.InvokeRpcAsync(_rpc, "mcp.registry.search", [request], cancellationToken); + } + + /// Abandons the registry search that uses the given request ID. It acts only on IDs from `mcp.registry.allocateRequestId`, so it never cancels another component's request. Answers `canceled: true` when it stops a running search. Answers `canceled: false` for unknown or reclaimed IDs, completed or canceled searches, and unused reservations. It releases an unused reservation, so a later search with that ID is refused. + /// Request ID from `mcp.registry.allocateRequestId` that the search uses. + /// The to monitor for cancellation requests. The default is . + /// Whether the cancel reached a live search. + internal async Task CancelAsync(long requestId, CancellationToken cancellationToken = default) + { + var request = new McpRegistryCancelRequest { RequestId = requestId }; + return await CopilotClient.InvokeRpcAsync(_rpc, "mcp.registry.cancel", [request], cancellationToken); + } +} + /// Provides server-scoped McpInstallations APIs. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] public sealed class ServerMcpInstallationsApi @@ -44281,6 +49230,92 @@ public async Task DisableAsync(IList ids, CancellationToken cancellation } } +/// Provides server-scoped Instructions APIs. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public sealed class ServerInstructionsApi +{ + private readonly JsonRpc _rpc; + + internal ServerInstructionsApi(JsonRpc rpc) + { + _rpc = rpc; + } + + /// Returns the prompt the `init` command runs to author repository instructions. A host offering its own initialize action sends this text so the instructions it produces match the ones the CLI produces. The text is fixed for a build and depends on no configuration, session or repository. + /// The to monitor for cancellation requests. The default is . + /// The prompt the init command runs. + internal async Task GetInitPromptAsync(CancellationToken cancellationToken = default) + { + return await CopilotClient.InvokeRpcAsync(_rpc, "instructions.getInitPrompt", [], cancellationToken); + } + + /// Discovers instruction sources across user, repository, and plugin sources. + /// 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). + /// 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. + /// The to monitor for cancellation requests. The default is . + /// Instruction sources discovered across user, repository, and plugin sources. + public async Task DiscoverAsync(IList? projectPaths = null, bool? excludeHostInstructions = null, CancellationToken cancellationToken = default) + { + var request = new InstructionsDiscoverRequest { ProjectPaths = projectPaths, ExcludeHostInstructions = excludeHostInstructions }; + return await CopilotClient.InvokeRpcAsync(_rpc, "instructions.discover", [request], cancellationToken); + } + + /// Discovers instruction sources across user, repository, and plugin sources. + /// Optional project paths to include in instruction discovery. + /// The to monitor for cancellation requests. The default is . + /// Instruction sources discovered across user, repository, and plugin sources. + public async Task DiscoverAsync(InstructionsDiscoverRequest request, CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(request); + return await CopilotClient.InvokeRpcAsync(_rpc, "instructions.discover", [request], cancellationToken); + } + + /// Discovers the instruction sources for one caller-named location and returns them in the order the runtime applies them. `discover` answers for a set of project paths and derives the rest of its scope itself; this one instead takes every argument from the caller — the extra directories to scan and whether to descend into nested instruction files — and honors them as given. It also includes instruction directories from trusted repository settings, matching native repository discovery. Use it when the caller knows its own scope and no session exists to derive that scope from, such as a host reporting how many instruction files a directory contributes before any session starts. Marked internal because it exists to carry a CLI call site off the napi boundary onto the SDK contract; it is migration plumbing, not a surface consumers are meant to depend on. + /// Repository location to discover. An empty string selects no repository scope; cwdPath can still supply working-directory sources. + /// When true, include the sources a command-line host reads. Hosts that are not the CLI omit it. + /// Working directory whose instruction sources should also be included. Omission adds no separate working-directory scope. + /// Copilot configuration directory for user-level sources. An omitted or empty value uses the server's configuration home. + /// Extra directories to scan alongside instruction directories configured in the runtime environment. + /// When true, also discover instruction files nested below the location. + /// The to monitor for cancellation requests. The default is . + /// Instruction sources discovered across user, repository, and plugin sources. + internal async Task DiscoverRepoSourcesAsync(string location, bool? isCli = null, string? cwdPath = null, string? configDir = null, IList? additionalDirs = null, bool? enableChildInstructions = null, CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(location); + + var request = new InstructionsDiscoverRepoSourcesRequest { Location = location, IsCli = isCli, CwdPath = cwdPath, ConfigDir = configDir, AdditionalDirs = additionalDirs, EnableChildInstructions = enableChildInstructions }; + return await CopilotClient.InvokeRpcAsync(_rpc, "instructions.discoverRepoSources", [request], cancellationToken); + } + + /// Returns the canonical files and directories where a client may create custom instructions that the runtime will recognize, including ones that do not exist yet. Repository targets become active once created. + /// Optional list of project directory paths. When omitted or empty, only the user-level targets are returned. + /// When true, omit the host's user-level instruction targets, leaving only repository targets. For multitenant deployments (mirrors `discover`'s `excludeHostInstructions`). + /// The to monitor for cancellation requests. The default is . + /// Canonical files and directories where custom instructions can be created so the runtime will recognize them. + public async Task GetDiscoveryPathsAsync(IList? projectPaths = null, bool? excludeHostInstructions = null, CancellationToken cancellationToken = default) + { + var request = new InstructionsGetDiscoveryPathsRequest { ProjectPaths = projectPaths, ExcludeHostInstructions = excludeHostInstructions }; + return await CopilotClient.InvokeRpcAsync(_rpc, "instructions.getDiscoveryPaths", [request], cancellationToken); + } + + /// Returns the canonical files and directories where a client may create custom instructions that the runtime will recognize, including ones that do not exist yet. Repository targets become active once created. + /// Optional project paths to include when enumerating instruction discovery targets. + /// The to monitor for cancellation requests. The default is . + /// Canonical files and directories where custom instructions can be created so the runtime will recognize them. + public async Task GetDiscoveryPathsAsync(InstructionsGetDiscoveryPathsRequest request, CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(request); + return await CopilotClient.InvokeRpcAsync(_rpc, "instructions.getDiscoveryPaths", [request], cancellationToken); + } + + /// Drops the runtime's in-memory cache of discovered custom instructions so the next discovery reads them from disk again. A host calls this after it changes instruction files, or after it trusts a folder whose instructions an earlier discovery skipped, so that a session started afterwards sees the current files instead of a cached earlier answer. The call performs no I/O of its own and removes nothing from disk; it only invalidates, so the cost lands on the next discovery. Returns once the cache is clear, which is what lets a caller order a session start after it. Marked internal because it exists to carry a CLI call site off the napi boundary onto the SDK contract; it is migration plumbing, not a surface consumers are meant to depend on. + /// The to monitor for cancellation requests. The default is . + internal async Task ClearCacheAsync(CancellationToken cancellationToken = default) + { + await CopilotClient.InvokeRpcAsync(_rpc, "instructions.clearCache", [], cancellationToken); + } +} + /// Provides server-scoped Skills APIs. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] public sealed class ServerSkillsApi @@ -44308,6 +49343,22 @@ public async Task PlanInstallAsync(CatalogCli return await CopilotClient.InvokeRpcAsync(_rpc, "skills.planInstall", [request], cancellationToken); } + /// Plans installation of the Skill pinned by an Agent Finder materialisation reference without writing files. The runtime retrieves the descriptor only from its fixed Agent Finder authority and applies the same verification as skills.planInstall; the review shows only verified data. Apply the returned plan with skills.applyInstall. + /// Required authenticated bound catalogue, skill-confirmed-installation and skill-reference-installation capabilities. + /// Materialisation reference to verify and plan. + /// Existing local session attached to this connection. + /// The to monitor for cancellation requests. The default is . + /// Skill installation management result with the honoured contract, or a typed refusal. + public async Task PlanInstallFromReferenceAsync(CatalogClientContract contract, CatalogMaterializationReference reference, string policySessionId, CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(contract); + ArgumentNullException.ThrowIfNull(reference); + ArgumentNullException.ThrowIfNull(policySessionId); + + var request = new SkillPlanInstallFromReferenceRequest { Contract = contract, Reference = reference, PolicySessionId = policySessionId }; + return await CopilotClient.InvokeRpcAsync(_rpc, "skills.planInstallFromReference", [request], cancellationToken); + } + /// Consumes one verified Skill installation plan, requests explicit human consent through installations.confirm on the original connection, then revalidates and installs the Skill disabled. /// Required authenticated bound catalogue and Skill installation capabilities. /// Opaque original plan, consumed once. @@ -44358,7 +49409,7 @@ public async Task ApplyUninstallAsync(CatalogClientCont /// Discovers skills across global and project sources. /// Optional list of project directory paths to scan for project-scoped skills. - /// Optional list of additional skill directory paths to include. + /// Explicit discovery-only skill roots, not filesystem grants. Available without ambient discovery, subject to skill/managed disables. Relative paths resolve against the request's effective working directory when supplied. Null and omission are equivalent; an empty array contributes no explicit roots. /// Optional skill scan paths to exclude from discovery. /// When true, omit skills from the host's global sources (personal, custom, plugin, and built-in), returning only project-scoped skills. For multitenant deployments. /// The to monitor for cancellation requests. The default is . @@ -44381,6 +49432,16 @@ public async Task GetDiscoveryPathsAsync(IList? return await CopilotClient.InvokeRpcAsync(_rpc, "skills.getDiscoveryPaths", [request], cancellationToken); } + /// Returns the canonical directories where a client may create skills that the runtime will recognize, including ones that do not exist yet. Project directories become active once created. + /// Optional project paths to enumerate. + /// The to monitor for cancellation requests. The default is . + /// Canonical locations where skills can be created so the runtime will recognize them. + public async Task GetDiscoveryPathsAsync(SkillsGetDiscoveryPathsRequest request, CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(request); + return await CopilotClient.InvokeRpcAsync(_rpc, "skills.getDiscoveryPaths", [request], cancellationToken); + } + /// Installations APIs. public ServerSkillsInstallationsApi Installations => field ?? @@ -44786,6 +49847,16 @@ public async Task DiscoverAsync(IList? projectPaths = n return await CopilotClient.InvokeRpcAsync(_rpc, "agents.discover", [request], cancellationToken); } + /// Discovers custom agents across user, project, plugin, and remote sources. + /// Optional project paths to include in agent discovery. + /// The to monitor for cancellation requests. The default is . + /// Agents discovered across user, project, plugin, and remote sources. + public async Task DiscoverAsync(AgentsDiscoverRequest request, CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(request); + return await CopilotClient.InvokeRpcAsync(_rpc, "agents.discover", [request], cancellationToken); + } + /// Returns the canonical directories where a client may create custom agents that the runtime will recognize, including ones that do not exist yet. Project directories become active once created. /// Optional list of project directory paths. When omitted or empty, only the user-level directory is returned. /// When true, omit the host's user-level agent directory, leaving only project directories. For multitenant deployments (mirrors `discover`'s `excludeHostAgents`). @@ -44797,6 +49868,16 @@ public async Task GetDiscoveryPathsAsync(IList? return await CopilotClient.InvokeRpcAsync(_rpc, "agents.getDiscoveryPaths", [request], cancellationToken); } + /// Returns the canonical directories where a client may create custom agents that the runtime will recognize, including ones that do not exist yet. Project directories become active once created. + /// Optional project paths to include when enumerating agent discovery directories. + /// The to monitor for cancellation requests. The default is . + /// Canonical locations where custom agents can be created so the runtime will recognize them. + public async Task GetDiscoveryPathsAsync(AgentsGetDiscoveryPathsRequest request, CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(request); + return await CopilotClient.InvokeRpcAsync(_rpc, "agents.getDiscoveryPaths", [request], cancellationToken); + } + /// Lists the agents this runtime ships, by name. A consumer separating shipped agents from ones the user or a plugin authored should compare against these names rather than against `AgentInfo.source`: an authored agent may carry the `builtin` source while not being one of these, and the runtime treats the two as separate questions. `disableableNames` is the subset a user may turn off, which a client needs to decide whether to offer a toggle. `yamlBasedNames` is the subset backed by a shipped YAML definition, which a client needs before asking the runtime to load one. /// The to monitor for cancellation requests. The default is . /// The agents this runtime ships, named so a consumer can tell them apart from authored ones. @@ -44856,40 +49937,6 @@ internal async Task CustomAgentInit } } -/// Provides server-scoped Instructions APIs. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class ServerInstructionsApi -{ - private readonly JsonRpc _rpc; - - internal ServerInstructionsApi(JsonRpc rpc) - { - _rpc = rpc; - } - - /// Discovers instruction sources across user, repository, and plugin sources. - /// 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). - /// 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. - /// The to monitor for cancellation requests. The default is . - /// Instruction sources discovered across user, repository, and plugin sources. - public async Task DiscoverAsync(IList? projectPaths = null, bool? excludeHostInstructions = null, CancellationToken cancellationToken = default) - { - var request = new InstructionsDiscoverRequest { ProjectPaths = projectPaths, ExcludeHostInstructions = excludeHostInstructions }; - return await CopilotClient.InvokeRpcAsync(_rpc, "instructions.discover", [request], cancellationToken); - } - - /// Returns the canonical files and directories where a client may create custom instructions that the runtime will recognize, including ones that do not exist yet. Repository targets become active once created. - /// Optional list of project directory paths. When omitted or empty, only the user-level targets are returned. - /// When true, omit the host's user-level instruction targets, leaving only repository targets. For multitenant deployments (mirrors `discover`'s `excludeHostInstructions`). - /// The to monitor for cancellation requests. The default is . - /// Canonical files and directories where custom instructions can be created so the runtime will recognize them. - public async Task GetDiscoveryPathsAsync(IList? projectPaths = null, bool? excludeHostInstructions = null, CancellationToken cancellationToken = default) - { - var request = new InstructionsGetDiscoveryPathsRequest { ProjectPaths = projectPaths, ExcludeHostInstructions = excludeHostInstructions }; - return await CopilotClient.InvokeRpcAsync(_rpc, "instructions.getDiscoveryPaths", [request], cancellationToken); - } -} - /// Provides server-scoped GlobalState APIs. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] public sealed class ServerGlobalStateApi @@ -44919,7 +49966,7 @@ internal async Task LoadForConfigDirAsync(string? configD return await CopilotClient.InvokeRpcAsync(_rpc, "globalState.loadForConfigDir", [request], cancellationToken); } - /// Records one top-level key in the host's machine-wide state, the counterpart to `globalState.load`. A host calls this to remember that it has shown an onboarding step, asked a one-off question, or completed a migration, so the next run can skip it. Only the named key is replaced and the rest of the document is preserved, which lets two writers record different flags without overwriting each other; passing no value removes the key instead. Only the keys a host records itself are writable: `appInstallNudgeResponded`, `appTipShown`, `askedSetupTerminals`, `autoFeedbackLastPromptedAt`, `firstLaunchAt`, `recentModelIds`, `sandboxCredentialProxyCaDeclined` and `sandboxOnboardingShown`. Every other key is refused, including `installedPlugins`, the stored credentials, `trustedFolders`, the staff flags and the signed-in accounts. Plugin enablement must use the plugin APIs, which apply repository and managed-policy checks. + /// Records one top-level key in the host's machine-wide state, the counterpart to `globalState.load`. A host calls this to remember that it has shown an onboarding step, asked a one-off question, or completed a migration, so the next run can skip it. Only the named key is replaced and the rest of the document is preserved, which lets two writers record different flags without overwriting each other; passing no value removes the key instead. Only the keys a host records itself are writable: `appTipShown`, `askedSetupTerminals`, `autoFeedbackLastPromptedAt`, `firstLaunchAt`, `recentModelIds`, `sandboxCredentialProxyCaDeclined` and `sandboxOnboardingShown`. Every other key is refused, including `installedPlugins`, the stored credentials, `trustedFolders`, the staff flags and the signed-in accounts. Plugin enablement must use the plugin APIs, which apply repository and managed-policy checks. /// Top-level key to write, named as it appears in the result of `globalState.load`. It must be one of the writable keys that `globalState.writeKey` lists. /// Copilot configuration directory to write the state document in, taking precedence over the server's own `COPILOT_HOME` and default home. Omit it, or pass an empty string, to write the directory the server resolved for itself. Mirrors `globalState.loadForConfigDir`, so a caller can read and write the same directory. /// Value to store for the key. Omit it, or pass null, to remove the key instead. @@ -44953,55 +50000,6 @@ public async Task ListAsync(CancellationToken cancellationToken = d } } -/// Provides server-scoped User APIs. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class ServerUserApi -{ - private readonly JsonRpc _rpc; - - internal ServerUserApi(JsonRpc rpc) - { - _rpc = rpc; - } - - /// Settings APIs. - public ServerUserSettingsApi Settings => - field ?? - Interlocked.CompareExchange(ref field, new(_rpc), null) ?? - field; -} - -/// Provides server-scoped UserSettings APIs. -[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] -public sealed class ServerUserSettingsApi -{ - private readonly JsonRpc _rpc; - - internal ServerUserSettingsApi(JsonRpc rpc) - { - _rpc = rpc; - } - - /// Lists every known user setting from settings.json, each with its effective value, its default, and whether it is at the default — so settings the user has never set still appear with their default value. Does not include repository- or enterprise-managed overrides that the runtime layers on top at session time. - /// The to monitor for cancellation requests. The default is . - /// Per-key metadata for every known user setting in settings.json, including settings left at their default. Excludes repository- and enterprise-managed overrides. - public async Task GetAsync(CancellationToken cancellationToken = default) - { - return await CopilotClient.InvokeRpcAsync(_rpc, "user.settings.get", [], cancellationToken); - } - - /// Writes one or more user settings to settings.json, replacing each provided top-level key. A key whose value is null is removed. - /// Partial user settings to write, as a free-form object keyed by setting name. - /// The to monitor for cancellation requests. The default is . - public async Task SetAsync(object settings, CancellationToken cancellationToken = default) - { - ArgumentNullException.ThrowIfNull(settings); - - var request = new UserSettingsSetRequest { Settings = CopilotClient.ToJsonElementForWire(settings)!.Value }; - await CopilotClient.InvokeRpcAsync(_rpc, "user.settings.set", [request], cancellationToken); - } -} - /// Provides server-scoped GitHubRepository APIs. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] public sealed class ServerGitHubRepositoryApi @@ -45050,11 +50048,9 @@ internal async Task NextRequestIdAsync(Cancellation /// The credential the listing runs under, carried opaquely because its shape is the host's own and the runtime only resolves a token and a GitHub host from it. No credential travels: this selects one the runtime already holds. /// The to monitor for cancellation requests. The default is . /// Outcome of an owner listing. Exactly one of `owners` and `message` is present, except that `throwError` reports a failure the caller is expected to raise rather than render. - internal async Task ListAsync(long requestId, object authInfo, CancellationToken cancellationToken = default) + internal async Task ListAsync(long requestId, object? authInfo, CancellationToken cancellationToken = default) { - ArgumentNullException.ThrowIfNull(authInfo); - - var request = new GitHubOwnersListRequest { RequestId = requestId, AuthInfo = CopilotClient.ToJsonElementForWire(authInfo)!.Value }; + var request = new GitHubOwnersListRequest { RequestId = requestId, AuthInfo = CopilotClient.ToJsonElementForWire(authInfo) ?? JsonElement.Parse("null") }; return await CopilotClient.InvokeRpcAsync(_rpc, "gitHubOwners.list", [request], cancellationToken); } @@ -45117,83 +50113,6 @@ internal async Task ReposFromRemotesAsync(string gitR } } -/// Provides server-scoped ManagedSettings APIs. -[Experimental(global::GitHub.Copilot.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. `managedSettings.resolve` returns the same device settings together with the account's server policy. - /// 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); - } - - /// Force-refreshes enterprise managed settings for every account: wipes the persistent server-policy cache (the whole `<cacheHome>/managed-settings` directory) and drops this runtime process's in-memory retained server policy. It does not itself fetch policy — the effect is that the next time a session resolves managed settings for an account, that resolution re-fetches the account's org policy from the network instead of serving a cached response. Note that `managedSettings.read` returns only device/MDM settings and never triggers the account server-policy fetch, so a host implementing "sync account policy" should call `managedSettings.resolve` or start a fresh session resolution rather than treat a subsequent `managedSettings.read` as the refreshed org policy. Mirrors the invalidation a sign-out performs, broadened from the one signing-out account to all of them; device/MDM layers describe the machine, not the account, and are left untouched. Rejects if the on-disk cache cannot be removed. - /// The to monitor for cancellation requests. The default is . - public async Task ClearCacheAsync(CancellationToken cancellationToken = default) - { - await CopilotClient.InvokeRpcAsync(_rpc, "managedSettings.clearCache", [], cancellationToken); - } - - /// Resolves the effective enterprise managed settings without a session, from the device channel and, when an account is available, the account's server policy through the same per-account cache sessions use. A cached server policy less than an hour old is used without a fetch; otherwise the policy is fetched, and when the fetch fails a cached policy up to 24 hours old is used instead, unless `forceRemoteSettingsRefresh` requires a live fetch. With no account requested or signed in, it reports device policy only; signing out removes the account's cached policy. It can fetch server policy over the network when the cache is stale, so call it off latency-critical paths such as startup rather than before listing models. The policy helper is not run. `layers` lists each channel's document before merging, and `values` and `meta` carry typed effective values and their lock state for the keys typed so far. - /// Opaque account identifier returned by `account.getAllUsers`. When omitted, the current account is used, or device policy only when no account is signed in. - /// GitHub token to resolve instead of the current account. The call fails when the token cannot be resolved. - /// Embedding client identity for server policy requests, as in session creation. Omit for the CLI identity. - /// Working directory used to run an organization policy helper. When omitted, sessionless resolution does not run the helper. - /// The to monitor for cancellation requests. The default is . - /// Effective enterprise managed settings for an account, resolved without a session. - public async Task ResolveAsync(string? selectionId = null, string? gitHubToken = null, string? clientName = null, string? workingDirectory = null, CancellationToken cancellationToken = default) - { - var request = new ManagedSettingsResolveRequest { SelectionId = selectionId, GitHubToken = gitHubToken, ClientName = clientName, WorkingDirectory = workingDirectory }; - return await CopilotClient.InvokeRpcAsync(_rpc, "managedSettings.resolve", [request], cancellationToken); - } - - /// Compatibility overload preserving the positional CancellationToken parameter from before workingDirectory was added. - public Task ResolveAsync(string? selectionId, string? gitHubToken, string? clientName, CancellationToken cancellationToken) - => ResolveAsync(selectionId, gitHubToken, clientName, workingDirectory: null, cancellationToken); - - /// Returns the managed-settings authoring JSON schema with descriptive `x-composition` annotations aligned with the shared settings-engine vocabulary. These annotations are not a complete runtime composition contract: model, effortLevel, and contextTier remain coupled. Use `managedSettings.compose` for the runtime's effective result. Performs no I/O. - /// The to monitor for cancellation requests. The default is . - /// The authoring JSON schema for managed settings recognized by this runtime. - public async Task SchemaAsync(CancellationToken cancellationToken = default) - { - return await CopilotClient.InvokeRpcAsync(_rpc, "managedSettings.schema", [], cancellationToken); - } - - /// Validates a candidate managed-settings document the way the runtime validates delivered policy, without applying it. Reports errors that would reject the document, warnings for content the runtime ignores, and the canonical document it would apply. Document text nested more than 64 levels deep is rejected. Performs no I/O. - /// The document to validate: a JSON object, or a string containing the document's JSON text. Preview documents are limited to 1 MiB and 64 levels of nesting, a stricter resource limit than delivered-policy parsing; violations are returned as diagnostics. - /// Channel the document is meant for (`device`, `server`, or `policyHelper`). Some keys are only honored in some channels; for example, a `policyHelper` registration is ignored in policy-helper output. When omitted, no channel-specific checks run. - /// The to monitor for cancellation requests. The default is . - /// Result of validating a managed-settings document. - public async Task ValidateAsync(object content, string? layer = null, CancellationToken cancellationToken = default) - { - ArgumentNullException.ThrowIfNull(content); - - var request = new ManagedSettingsValidateRequest { Content = CopilotClient.ToJsonElementForWire(content)!.Value, Layer = layer }; - return await CopilotClient.InvokeRpcAsync(_rpc, "managedSettings.validate", [request], cancellationToken); - } - - /// Merges candidate managed-settings documents for the device, server, and policy-helper channels into the effective settings the runtime would enforce on this host, using the same precedence and composition rules as live resolution, without applying them. Like live resolution, a server's advisory sandbox force-enable is declined on a host that cannot run the sandbox. Does not fetch policy or read policy files, but may perform blocking OS or subprocess probes for sandbox support. Preview documents are limited to 1 MiB and 64 levels of nesting. - /// One entry per channel. `source` must be `device`, `server`, or `policyHelper`, each at most once (checked at runtime); order does not matter, because channel precedence is fixed. To preview documents from resolve output, map recognized source strings to ManagedSettingsChannel and copy their settings; generated resolve and compose layer types are distinct. Omitted settings means this channel delivered no document. Supplied documents must be valid within the preview limits; warnings are returned in diagnostics. Compose does not reproduce source-failure state or retained enforcement floors from resolve. - /// The to monitor for cancellation requests. The default is . - /// The effective managed settings the runtime would enforce for the given documents, in the same shape `managedSettings.resolve` returns. - public async Task ComposeAsync(IList layers, CancellationToken cancellationToken = default) - { - ArgumentNullException.ThrowIfNull(layers); - - var request = new ManagedSettingsComposeRequest { Layers = layers }; - return await CopilotClient.InvokeRpcAsync(_rpc, "managedSettings.compose", [request], cancellationToken); - } -} - /// Provides server-scoped Runtime APIs. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] public sealed class ServerRuntimeApi @@ -46371,6 +51290,17 @@ public async Task GetEnforcementStatusAsync(Cancellati return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.sandbox.getEnforcementStatus", [request], cancellationToken); } + /// Lists possible secret-bearing environment variables available to new sandboxed shells that are not already covered by credential masking. Uses local name rules and known-provider host suggestions, never returns secret values, and changes no settings or network permissions. Returns no suggestions while the session is not sandboxed. + /// The to monitor for cancellation requests. The default is . + /// Possible unconfigured secrets in the sandbox shell environment, sorted by variable name, with no secret values. + public async Task GetCredentialSuggestionsAsync(CancellationToken cancellationToken = default) + { + _session.ThrowIfDisposed(); + + var request = new SessionSandboxGetCredentialSuggestionsRequest { SessionId = _session.SessionId }; + return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.sandbox.getCredentialSuggestions", [request], cancellationToken); + } + /// Disables sandboxing for the remainder of the current session and approves the referenced pending sandbox-bypass permission request. The request is rejected unless the exact request is still pending and the effective sandbox policy permits bypass. /// Identifier of the exact pending sandbox-bypass permission request that authorized the session opt-out. /// Optional attribution for the permission decision. @@ -46787,14 +51717,13 @@ internal CanvasProviderApi(CopilotSession session) /// 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) + 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() }; + var request = new CanvasProviderRegisterRequest { SessionId = _session.SessionId, ConnectionId = connectionId, Info = CopilotClient.ToJsonElementForWire(info) ?? JsonElement.Parse("null"), Canvases = canvases.Select(static v => CopilotClient.ToJsonElementForWire(v)!.Value).ToList() }; await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.canvas.provider.register", [request], cancellationToken); } @@ -46828,13 +51757,12 @@ internal WorkflowApi(CopilotSession session) /// Workflow invocation options. /// The to monitor for cancellation requests. The default is . /// Complete current or terminal workflow run envelope. - public async Task RunAsync(string name, object args, WorkflowRunOptions? options = null, CancellationToken cancellationToken = default) + public async Task RunAsync(string name, object? args, WorkflowRunOptions? options = null, CancellationToken cancellationToken = default) { ArgumentNullException.ThrowIfNull(name); - ArgumentNullException.ThrowIfNull(args); _session.ThrowIfDisposed(); - var request = new WorkflowRunRequest { SessionId = _session.SessionId, Name = name, Args = CopilotClient.ToJsonElementForWire(args)!.Value, Options = options }; + var request = new WorkflowRunRequest { SessionId = _session.SessionId, Name = name, Args = CopilotClient.ToJsonElementForWire(args) ?? JsonElement.Parse("null"), Options = options }; return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.workflow.run", [request], cancellationToken); } @@ -46861,13 +51789,12 @@ public async Task ResumeAsync(string runId, WorkflowRunLim /// Opaque identifier of the originating tool call. /// The to monitor for cancellation requests. The default is . /// Complete current or terminal workflow run envelope. - internal async Task RunFromToolAsync(string name, object args, WorkflowToolRunOptions? options = null, string? toolCallId = null, CancellationToken cancellationToken = default) + internal async Task RunFromToolAsync(string name, object? args, WorkflowToolRunOptions? options = null, string? toolCallId = null, CancellationToken cancellationToken = default) { ArgumentNullException.ThrowIfNull(name); - ArgumentNullException.ThrowIfNull(args); _session.ThrowIfDisposed(); - var request = new WorkflowToolRunRequest { SessionId = _session.SessionId, Name = name, Args = CopilotClient.ToJsonElementForWire(args)!.Value, Options = options, ToolCallId = toolCallId }; + var request = new WorkflowToolRunRequest { SessionId = _session.SessionId, Name = name, Args = CopilotClient.ToJsonElementForWire(args) ?? JsonElement.Parse("null"), Options = options, ToolCallId = toolCallId }; return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.workflow.runFromTool", [request], cancellationToken); } @@ -47063,15 +51990,14 @@ public async Task GetAsync(string runId, string execut /// JSON result to memoize. /// The to monitor for cancellation requests. The default is . /// Acknowledgement that a workflow request was accepted. - public async Task PutAsync(string runId, string executionToken, string key, object resultJson, CancellationToken cancellationToken = default) + public async Task PutAsync(string runId, string executionToken, string key, object? resultJson, CancellationToken cancellationToken = default) { ArgumentNullException.ThrowIfNull(runId); ArgumentNullException.ThrowIfNull(executionToken); ArgumentNullException.ThrowIfNull(key); - ArgumentNullException.ThrowIfNull(resultJson); _session.ThrowIfDisposed(); - var request = new WorkflowJournalPutRequest { SessionId = _session.SessionId, RunId = runId, ExecutionToken = executionToken, Key = key, ResultJson = CopilotClient.ToJsonElementForWire(resultJson)!.Value }; + var request = new WorkflowJournalPutRequest { SessionId = _session.SessionId, RunId = runId, ExecutionToken = executionToken, Key = key, ResultJson = CopilotClient.ToJsonElementForWire(resultJson) ?? JsonElement.Parse("null") }; return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.workflow.journal.put", [request], cancellationToken); } } @@ -47213,6 +52139,19 @@ public async Task SetReasoningEffortAsync(string return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.model.setReasoningEffort", [request], cancellationToken); } + /// Computes whether the session's current model shows reasoning summaries and how a host should present them: the default Ctrl+T visibility, reasoning headers, the hidden-summaries hint, and whether a persisted `showReasoning` setting is redundant. The runtime folds in the session's model catalog, selection and reasoning-summaries experiment assignment; the host supplies only its own persisted setting and current visibility. This method reads the session's resolved model catalog rather than resolving it, so call `session.model.list` first on a session that has not listed models or run a turn; until then the result is `ready: false`. A session that resolves models without a Copilot catalog, that is a BYOK registry or an alternate provider, never caches one and stays `ready: false`. + /// The host's persisted `showReasoning` user setting, omitted when unset. + /// Whether the host is currently showing reasoning summaries. Defaults to false. + /// The to monitor for cancellation requests. The default is . + /// Reasoning-summaries presentation policy for the session's current model. + public async Task GetReasoningSummariesAsync(bool? persistedShowReasoning = null, bool? summariesVisible = null, CancellationToken cancellationToken = default) + { + _session.ThrowIfDisposed(); + + var request = new ModelGetReasoningSummariesRequest { SessionId = _session.SessionId, PersistedShowReasoning = persistedShowReasoning, SummariesVisible = summariesVisible }; + return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.model.getReasoningSummaries", [request], cancellationToken); + } + /// Lists models available to this session using its own auth and integration context. Connected hosts (CLI TUI, GitHub App) should call this through the session client so remote sessions return the remote CLI's available models rather than the caller's. /// Optional listing options. /// The to monitor for cancellation requests. The default is . @@ -48301,15 +53240,13 @@ internal async Task ReloadWithConfigAsync(CancellationTok /// 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. /// The to monitor for cancellation requests. The default is . /// Outcome of an MCP sampling execution: success result, failure error, or cancellation. - public async Task ExecuteSamplingAsync(string requestId, string serverName, object mcpRequestId, McpExecuteSamplingRequest request, CancellationToken cancellationToken = default) + public async Task ExecuteSamplingAsync(string requestId, string serverName, object? mcpRequestId, object? request, CancellationToken cancellationToken = default) { ArgumentNullException.ThrowIfNull(requestId); ArgumentNullException.ThrowIfNull(serverName); - ArgumentNullException.ThrowIfNull(mcpRequestId); - ArgumentNullException.ThrowIfNull(request); _session.ThrowIfDisposed(); - var rpcRequest = new McpExecuteSamplingParams { SessionId = _session.SessionId, RequestId = requestId, ServerName = serverName, McpRequestId = CopilotClient.ToJsonElementForWire(mcpRequestId)!.Value, Request = request }; + var rpcRequest = new McpExecuteSamplingParams { SessionId = _session.SessionId, RequestId = requestId, ServerName = serverName, McpRequestId = CopilotClient.ToJsonElementForWire(mcpRequestId) ?? JsonElement.Parse("null"), Request = CopilotClient.ToJsonElementForWire(request) ?? JsonElement.Parse("null") }; return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.mcp.executeSampling", [rpcRequest], cancellationToken); } @@ -49223,6 +54160,18 @@ public async Task ReloadAsync(SessionPluginsReloadRequest? request = null, Cance await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.plugins.reload", [rpcRequest], cancellationToken); } + /// Installs or updates the plugins the session's managed settings require now, without waiting for the next automatic attempt after a failure, then reloads plugins. Reports progress and failures with the same `session.info` and `session.warning` events as automatic preparation. Fails when the runtime does not prepare managed plugins for the session. + /// Plugins required by the session's managed settings to install or update again. + /// The to monitor for cancellation requests. The default is . + /// Per-plugin outcomes of retrying the plugins required by the session's managed settings. + public async Task RetryManagedAsync(SessionPluginsRetryManagedRequest? request = null, CancellationToken cancellationToken = default) + { + _session.ThrowIfDisposed(); + + var rpcRequest = new SessionPluginsRetryManagedRequestWithSession { SessionId = _session.SessionId, Plugins = request?.Plugins }; + return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.plugins.retryManaged", [rpcRequest], cancellationToken); + } + /// Marketplaces APIs. public PluginsMarketplacesApi Marketplaces => field ?? @@ -49408,7 +54357,10 @@ internal OptionsApi(CopilotSession session) /// 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. + /// Explicit discovery-only skill roots, not filesystem grants. Available without enableConfigDiscovery, subject to skill/managed disables. Relative SDK paths resolve once when supplied against the request's effective workingDirectory; retained roots do not move on cwd changes. Null and omission are equivalent; [] clears only the explicit SDK contribution. + /// Explicit discovery-only agent roots, separate from customAgents and filesystem grants. Available without enableConfigDiscovery, subject to managed/source disables. Relative SDK paths resolve once when supplied against the request's effective workingDirectory; retained roots do not move on cwd changes. Null and omission are equivalent; [] clears only the explicit SDK contribution. + /// Explicit discovery-only instruction roots using existing SDK instruction formats, not filesystem grants. Available without enableConfigDiscovery, subject to instruction/managed disables. Relative SDK paths resolve once when supplied against the request's effective workingDirectory; retained roots do not move on cwd changes. Null and omission are equivalent; [] clears only the explicit SDK contribution. + /// Explicit discovery-only file-hook roots, separate from callback hooks and filesystem grants. Available without enableConfigDiscovery or enableFileHooks, which controls repository-origin hooks only; hook/managed disables still apply. Relative SDK paths resolve once when supplied against the request's effective workingDirectory; retained roots do not move on cwd changes. Null and omission are equivalent; [] clears only the explicit SDK contribution. /// Skill scan directories and descendants excluded from discovery. Supports `~`-relative paths. /// 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. @@ -49435,19 +54387,19 @@ internal OptionsApi(CopilotSession session) /// 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. /// Whether to skip embedding retrieval pipeline initialization and execution. /// Organization-level custom instructions to inject into the system prompt. - /// Whether to enable loading of `.github/hooks/` filesystem hooks. Separate from the SDK callback hook mechanism. + /// Whether to enable repository-origin file hooks from `.github/hooks/` and trusted repository-configured roots. Does not control explicit hookDirectories or SDK callback hooks. /// 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 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. + /// Whether skill loading is enabled. Explicit false disables every source, including explicit SDK roots and a bound SDK provider; changing the value invalidates the loaded skill snapshot. When omitted, creation falls back to enableConfigDiscovery unless explicit SDK skill roots or an SDK skill provider are supplied. /// 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, SandboxConfigSource? sandboxConfigSource = null, bool? logInteractiveShells = null, OptionsUpdateEnvValueMode? envValueMode = null, bool? allowAllMcpServerInstructions = null, IList? skillDirectories = null, IList? ignoredSkillsLocations = 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) + 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? agentDirectories = null, IList? instructionDirectories = null, IList? hookDirectories = null, IList? ignoredSkillsLocations = 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, SandboxConfigSource = sandboxConfigSource, LogInteractiveShells = logInteractiveShells, EnvValueMode = envValueMode, AllowAllMcpServerInstructions = allowAllMcpServerInstructions, SkillDirectories = skillDirectories, IgnoredSkillsLocations = ignoredSkillsLocations, 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 }; + 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, AgentDirectories = agentDirectories, InstructionDirectories = instructionDirectories, HookDirectories = hookDirectories, IgnoredSkillsLocations = ignoredSkillsLocations, 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); } } @@ -49575,13 +54527,12 @@ internal ToolsApi(CopilotSession session) /// 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) + 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 }; + var request = new ToolsExecuteRequest { SessionId = _session.SessionId, Name = name, Arguments = CopilotClient.ToJsonElementForWire(arguments) ?? JsonElement.Parse("null"), ToolCallId = toolCallId }; return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.tools.execute", [request], cancellationToken); } @@ -49608,13 +54559,12 @@ public async Task GetBuiltinDescriptorsAsync(b /// 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) + 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 }; + var request = new ToolsTaskCompleteEventDataRequest { SessionId = _session.SessionId, ToolArgs = CopilotClient.ToJsonElementForWire(toolArgs) ?? JsonElement.Parse("null"), FinalResult = finalResult }; return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.tools.taskCompleteEventData", [request], cancellationToken); } @@ -49735,7 +54685,7 @@ public async Task InvokeAsync(string name, string? /// 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) + internal async Task FinalizeInvocationEffectAsync(IDictionary effect, CommandsInvocationEffectOutcome outcome, CancellationToken cancellationToken = default) { ArgumentNullException.ThrowIfNull(effect); _session.ThrowIfDisposed(); @@ -49945,12 +54895,12 @@ internal async Task HandleHumanUserInputAsync(string requ /// Optional sampling result payload. Omit to reject/cancel the sampling request without providing a result. /// The to monitor for cancellation requests. The default is . /// Indicates whether the pending UI request was resolved by this call. - public async Task HandlePendingSamplingAsync(string requestId, UIHandlePendingSamplingResponse? response = null, CancellationToken cancellationToken = default) + public async Task HandlePendingSamplingAsync(string requestId, object? response = null, CancellationToken cancellationToken = default) { ArgumentNullException.ThrowIfNull(requestId); _session.ThrowIfDisposed(); - var request = new UIHandlePendingSamplingRequest { SessionId = _session.SessionId, RequestId = requestId, Response = response }; + var request = new UIHandlePendingSamplingRequest { SessionId = _session.SessionId, RequestId = requestId, Response = CopilotClient.ToJsonElementForWire(response) }; return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.ui.handlePendingSampling", [request], cancellationToken); } @@ -51032,12 +55982,11 @@ public async Task ClearAsync(CancellationToken cancellationToken = default) /// Opaque runtime-owned filter object. /// The to monitor for cancellation requests. The default is . /// Indicates whether a user-facing pending item was removed. - internal async Task ConsumeSystemNotificationsAsync(object filter, CancellationToken cancellationToken = default) + internal async Task ConsumeSystemNotificationsAsync(object? filter, CancellationToken cancellationToken = default) { - ArgumentNullException.ThrowIfNull(filter); _session.ThrowIfDisposed(); - var request = new QueueConsumeSystemNotificationsRequest { SessionId = _session.SessionId, Filter = CopilotClient.ToJsonElementForWire(filter)!.Value }; + var request = new QueueConsumeSystemNotificationsRequest { SessionId = _session.SessionId, Filter = CopilotClient.ToJsonElementForWire(filter) ?? JsonElement.Parse("null") }; return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.queue.consumeSystemNotifications", [request], cancellationToken); } @@ -52081,6 +57030,7 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH [JsonSerializable(typeof(GitHub.Copilot.IndexedSearchIncrementalPhase), TypeInfoPropertyName = "SessionEventsIndexedSearchIncrementalPhase")] [JsonSerializable(typeof(GitHub.Copilot.IndexedSearchOutcome), TypeInfoPropertyName = "SessionEventsIndexedSearchOutcome")] [JsonSerializable(typeof(GitHub.Copilot.IndexedSearchState), TypeInfoPropertyName = "SessionEventsIndexedSearchState")] +[JsonSerializable(typeof(GitHub.Copilot.ManagedPermissionsContext), TypeInfoPropertyName = "SessionEventsManagedPermissionsContext")] [JsonSerializable(typeof(GitHub.Copilot.ManagedPluginProgressPhase), TypeInfoPropertyName = "SessionEventsManagedPluginProgressPhase")] [JsonSerializable(typeof(GitHub.Copilot.ManagedSettingsEnforcedAction), TypeInfoPropertyName = "SessionEventsManagedSettingsEnforcedAction")] [JsonSerializable(typeof(GitHub.Copilot.ManagedSettingsEnforcedEscalation), TypeInfoPropertyName = "SessionEventsManagedSettingsEnforcedEscalation")] @@ -52126,8 +57076,10 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH [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.ModelCallRequestBodyEncoding), TypeInfoPropertyName = "SessionEventsModelCallRequestBodyEncoding")] [JsonSerializable(typeof(GitHub.Copilot.ModelCallStartData), TypeInfoPropertyName = "SessionEventsModelCallStartData")] [JsonSerializable(typeof(GitHub.Copilot.ModelCallStartEvent), TypeInfoPropertyName = "SessionEventsModelCallStartEvent")] +[JsonSerializable(typeof(GitHub.Copilot.ModelCallWebSocketFallbackErrorKind), TypeInfoPropertyName = "SessionEventsModelCallWebSocketFallbackErrorKind")] [JsonSerializable(typeof(GitHub.Copilot.ModelCallWebSocketFallbackReason), TypeInfoPropertyName = "SessionEventsModelCallWebSocketFallbackReason")] [JsonSerializable(typeof(GitHub.Copilot.ModelChangeSource), TypeInfoPropertyName = "SessionEventsModelChangeSource")] [JsonSerializable(typeof(GitHub.Copilot.ModelDeselectedReason), TypeInfoPropertyName = "SessionEventsModelDeselectedReason")] @@ -52214,6 +57166,9 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH [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.ProviderMonthlyUsage), TypeInfoPropertyName = "SessionEventsProviderMonthlyUsage")] +[JsonSerializable(typeof(GitHub.Copilot.ProviderMonthlyUsageScope), TypeInfoPropertyName = "SessionEventsProviderMonthlyUsageScope")] +[JsonSerializable(typeof(GitHub.Copilot.ProviderMonthlyUsageState), TypeInfoPropertyName = "SessionEventsProviderMonthlyUsageState")] [JsonSerializable(typeof(GitHub.Copilot.ProviderQuotaAccessState), TypeInfoPropertyName = "SessionEventsProviderQuotaAccessState")] [JsonSerializable(typeof(GitHub.Copilot.ProviderQuotaAcquisitionStatus), TypeInfoPropertyName = "SessionEventsProviderQuotaAcquisitionStatus")] [JsonSerializable(typeof(GitHub.Copilot.ProviderQuotaBudgetMetadata), TypeInfoPropertyName = "SessionEventsProviderQuotaBudgetMetadata")] @@ -52405,9 +57360,16 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH [JsonSerializable(typeof(AbortRequest))] [JsonSerializable(typeof(AbortResult))] [JsonSerializable(typeof(AccountAllUsers))] +[JsonSerializable(typeof(AccountDescribeAuthInfoRequest))] +[JsonSerializable(typeof(AccountDescribeAuthInfoResult))] [JsonSerializable(typeof(AccountGetCurrentAuthResult))] +[JsonSerializable(typeof(AccountGetGistSupportRequest))] +[JsonSerializable(typeof(AccountGetGistSupportResult))] [JsonSerializable(typeof(AccountGetQuotaRequest))] [JsonSerializable(typeof(AccountGetQuotaResult))] +[JsonSerializable(typeof(AccountGetTokenEnvVarResult))] +[JsonSerializable(typeof(AccountGetWebUrlRequest))] +[JsonSerializable(typeof(AccountGetWebUrlResult))] [JsonSerializable(typeof(AccountLoginRequest))] [JsonSerializable(typeof(AccountLoginResult))] [JsonSerializable(typeof(AccountLogoutRequest))] @@ -52491,10 +57453,13 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH [JsonSerializable(typeof(CapiSessionOptions))] [JsonSerializable(typeof(CardDigest))] [JsonSerializable(typeof(CatalogAgentPluginCandidateProvenance))] +[JsonSerializable(typeof(CatalogAgentPluginInstallPlan))] +[JsonSerializable(typeof(CatalogAgentPluginInstallSource))] [JsonSerializable(typeof(CatalogAiSkillCandidateProvenance))] [JsonSerializable(typeof(CatalogCandidate))] [JsonSerializable(typeof(CatalogCandidateSource))] [JsonSerializable(typeof(CatalogClientContract))] +[JsonSerializable(typeof(CatalogMaterializationReference))] [JsonSerializable(typeof(CatalogMcpServerCandidateProvenance))] [JsonSerializable(typeof(CatalogNegotiatedContract))] [JsonSerializable(typeof(CatalogPluginRepositorySource))] @@ -52510,7 +57475,6 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH [JsonSerializable(typeof(ClientTaskCancelResult))] [JsonSerializable(typeof(CommandList))] [JsonSerializable(typeof(CommandsFinalizeInvocationEffectRequest))] -[JsonSerializable(typeof(CommandsFinalizeInvocationEffectRequestEffect))] [JsonSerializable(typeof(CommandsFinalizeInvocationEffectResult))] [JsonSerializable(typeof(CommandsHandlePendingCommandRequest))] [JsonSerializable(typeof(CommandsHandlePendingCommandResult))] @@ -52551,6 +57515,8 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH [JsonSerializable(typeof(ContentExclusionCheckPathsRequest))] [JsonSerializable(typeof(ContentExclusionCheckPathsResult))] [JsonSerializable(typeof(ContentExclusionPathCheck))] +[JsonSerializable(typeof(ContentExclusionValidatePoliciesRequest))] +[JsonSerializable(typeof(ContentExclusionValidatePoliciesResult))] [JsonSerializable(typeof(ContextHeaviestMessage))] [JsonSerializable(typeof(CopilotUserResponse))] [JsonSerializable(typeof(CopilotUserResponseEndpoints))] @@ -52612,11 +57578,17 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH [JsonSerializable(typeof(ExternalToolTextResultForLlmBinaryResultsForLlm))] [JsonSerializable(typeof(ExternalToolTextResultForLlmContent))] [JsonSerializable(typeof(ExternalToolTextResultForLlmContentResourceLinkIcon))] +[JsonSerializable(typeof(FeatureFlagMetadata))] +[JsonSerializable(typeof(FeatureFlagsMetadataResult))] +[JsonSerializable(typeof(FeatureFlagsResolveRequest))] +[JsonSerializable(typeof(FeatureFlagsResolveStartupGatesRequest))] [JsonSerializable(typeof(FleetStartRequest))] [JsonSerializable(typeof(FleetStartResult))] [JsonSerializable(typeof(FolderTrustAddParams))] [JsonSerializable(typeof(FolderTrustCheckParams))] [JsonSerializable(typeof(FolderTrustCheckResult))] +[JsonSerializable(typeof(FolderTrustIsTrustedParams))] +[JsonSerializable(typeof(ForceLoginOrgsDecision))] [JsonSerializable(typeof(GitCurrentBranchRemoteResult))] [JsonSerializable(typeof(GitCwdRequest))] [JsonSerializable(typeof(GitHubEnvironment))] @@ -52683,6 +57655,7 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH [JsonSerializable(typeof(HostStartRequest))] [JsonSerializable(typeof(HostStartResult))] [JsonSerializable(typeof(IDictionary))] +[JsonSerializable(typeof(IDictionary))] [JsonSerializable(typeof(IDictionary))] [JsonSerializable(typeof(IList))] [JsonSerializable(typeof(IList))] @@ -52698,9 +57671,11 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH [JsonSerializable(typeof(InstructionDiscoveryPath))] [JsonSerializable(typeof(InstructionDiscoveryPathList))] [JsonSerializable(typeof(InstructionSource))] +[JsonSerializable(typeof(InstructionsDiscoverRepoSourcesRequest))] [JsonSerializable(typeof(InstructionsDiscoverRequest))] [JsonSerializable(typeof(InstructionsGetDiscoveryPathsRequest))] [JsonSerializable(typeof(InstructionsGetSourcesResult))] +[JsonSerializable(typeof(InstructionsInitPromptResult))] [JsonSerializable(typeof(InterruptMainTurnRequest))] [JsonSerializable(typeof(InterruptMainTurnResult))] [JsonSerializable(typeof(JsonSchemaResponseFormat))] @@ -52715,21 +57690,45 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH [JsonSerializable(typeof(LlmInferenceHttpResponseStartResult))] [JsonSerializable(typeof(LlmInferenceSetProviderResult))] [JsonSerializable(typeof(LocalSessionMetadataValue))] +[JsonSerializable(typeof(LocationPermissionsCleanupResult))] [JsonSerializable(typeof(LogRequest))] [JsonSerializable(typeof(LogResult))] [JsonSerializable(typeof(LoggedInUser))] [JsonSerializable(typeof(LspInitializeRequest))] +[JsonSerializable(typeof(ManagedPermissionEvaluation))] +[JsonSerializable(typeof(ManagedPermissionOperation))] +[JsonSerializable(typeof(ManagedPluginRetryEntry))] [JsonSerializable(typeof(ManagedSettingMeta))] +[JsonSerializable(typeof(ManagedSettingsApplyControlsParams))] +[JsonSerializable(typeof(ManagedSettingsApplyControlsResult))] [JsonSerializable(typeof(ManagedSettingsComposeLayer))] [JsonSerializable(typeof(ManagedSettingsComposeRequest))] [JsonSerializable(typeof(ManagedSettingsComposeResult))] [JsonSerializable(typeof(ManagedSettingsDiagnostic))] +[JsonSerializable(typeof(ManagedSettingsIngestParams))] +[JsonSerializable(typeof(ManagedSettingsIngestResult))] [JsonSerializable(typeof(ManagedSettingsLayer))] +[JsonSerializable(typeof(ManagedSettingsLoadRemoteControlParams))] +[JsonSerializable(typeof(ManagedSettingsLoadRemoteControlResult))] +[JsonSerializable(typeof(ManagedSettingsLogMessage))] [JsonSerializable(typeof(ManagedSettingsMeta))] +[JsonSerializable(typeof(ManagedSettingsParseClientValueParams))] +[JsonSerializable(typeof(ManagedSettingsParseClientValueResult))] +[JsonSerializable(typeof(ManagedSettingsPermissionsEvaluateRequest))] +[JsonSerializable(typeof(ManagedSettingsPermissionsEvaluateResult))] +[JsonSerializable(typeof(ManagedSettingsReadDeviceResult))] [JsonSerializable(typeof(ManagedSettingsReadResult))] +[JsonSerializable(typeof(ManagedSettingsResolveBypassDisableTransitionParams))] +[JsonSerializable(typeof(ManagedSettingsResolveBypassDisableTransitionResult))] +[JsonSerializable(typeof(ManagedSettingsResolveDeviceForceLoginOrgsGateParams))] +[JsonSerializable(typeof(ManagedSettingsResolveEffectiveValueParams))] +[JsonSerializable(typeof(ManagedSettingsResolveEffectiveValueResult))] [JsonSerializable(typeof(ManagedSettingsResolveRequest))] [JsonSerializable(typeof(ManagedSettingsResolveResult))] +[JsonSerializable(typeof(ManagedSettingsResolveStartupPolicyParams))] +[JsonSerializable(typeof(ManagedSettingsResolveStartupPolicyResult))] [JsonSerializable(typeof(ManagedSettingsResolvedData))] +[JsonSerializable(typeof(ManagedSettingsRetainPolicyParams))] [JsonSerializable(typeof(ManagedSettingsSchemaResult))] [JsonSerializable(typeof(ManagedSettingsValidateRequest))] [JsonSerializable(typeof(ManagedSettingsValidateResult))] @@ -52781,8 +57780,6 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH [JsonSerializable(typeof(McpEnableRequest))] [JsonSerializable(typeof(McpEnableRequestWithSession))] [JsonSerializable(typeof(McpExecuteSamplingParams))] -[JsonSerializable(typeof(McpExecuteSamplingRequest))] -[JsonSerializable(typeof(McpExecuteSamplingResult))] [JsonSerializable(typeof(McpFailedServer))] [JsonSerializable(typeof(McpFilteredServer))] [JsonSerializable(typeof(McpHeadersHandlePendingHeadersRefreshRequest))] @@ -52804,6 +57801,7 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH [JsonSerializable(typeof(McpInstallationsRequest))] [JsonSerializable(typeof(McpIsServerRunningRequest))] [JsonSerializable(typeof(McpIsServerRunningResult))] +[JsonSerializable(typeof(McpListBuiltinServerNamesResult))] [JsonSerializable(typeof(McpListToolsRequest))] [JsonSerializable(typeof(McpListToolsResult))] [JsonSerializable(typeof(McpOauthAuthenticationStateChangedRequest))] @@ -52842,6 +57840,11 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH [JsonSerializable(typeof(McpPromptsListRequest))] [JsonSerializable(typeof(McpPromptsListResult))] [JsonSerializable(typeof(McpRegisterExternalClientRequest))] +[JsonSerializable(typeof(McpRegistryCancelRequest))] +[JsonSerializable(typeof(McpRegistryCancelResult))] +[JsonSerializable(typeof(McpRegistryRequestIdResult))] +[JsonSerializable(typeof(McpRegistrySearchRequest))] +[JsonSerializable(typeof(McpRegistrySearchResult))] [JsonSerializable(typeof(McpReloadWithConfigRequest))] [JsonSerializable(typeof(McpRemoveGitHubResult))] [JsonSerializable(typeof(McpResource))] @@ -52866,6 +57869,8 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH [JsonSerializable(typeof(McpServerOwnership))] [JsonSerializable(typeof(McpSetEnvValueModeParams))] [JsonSerializable(typeof(McpSetEnvValueModeResult))] +[JsonSerializable(typeof(McpShouldExcludeGitHubToolsRequest))] +[JsonSerializable(typeof(McpShouldExcludeGitHubToolsResult))] [JsonSerializable(typeof(McpSourceFile))] [JsonSerializable(typeof(McpSourcePlugin))] [JsonSerializable(typeof(McpSourceRef))] @@ -52916,11 +57921,12 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH [JsonSerializable(typeof(ModelCapabilitiesOverrideLimitsVision))] [JsonSerializable(typeof(ModelCapabilitiesOverrideSupports))] [JsonSerializable(typeof(ModelCapabilitiesSupports))] +[JsonSerializable(typeof(ModelGetReasoningSummariesRequest))] +[JsonSerializable(typeof(ModelGetReasoningSummariesResult))] [JsonSerializable(typeof(ModelList))] [JsonSerializable(typeof(ModelMessage))] [JsonSerializable(typeof(ModelPickerPersistenceRequest))] [JsonSerializable(typeof(ModelPickerSettingsContext))] -[JsonSerializable(typeof(ModelPickerSettingsContextEnvironment))] [JsonSerializable(typeof(ModelPolicy))] [JsonSerializable(typeof(ModelProviderAdapterCatalog))] [JsonSerializable(typeof(ModelProviderAdapterDescriptor))] @@ -52994,6 +58000,8 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH [JsonSerializable(typeof(PermissionsConfigureAdditionalContentExclusionPolicyRuleSource))] [JsonSerializable(typeof(PermissionsConfigureParams))] [JsonSerializable(typeof(PermissionsConfigureResult))] +[JsonSerializable(typeof(PermissionsEvaluateManagedPolicyParams))] +[JsonSerializable(typeof(PermissionsEvaluateManagedPolicyResult))] [JsonSerializable(typeof(PermissionsFolderTrustAddTrustedResult))] [JsonSerializable(typeof(PermissionsGetModeRequest))] [JsonSerializable(typeof(PermissionsGetModeResult))] @@ -53015,6 +58023,8 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH [JsonSerializable(typeof(PermissionsSetRequiredRequest))] [JsonSerializable(typeof(PermissionsSetRequiredResult))] [JsonSerializable(typeof(PermissionsUrlsSetUnrestrictedModeResult))] +[JsonSerializable(typeof(PersistenceWriteRawJsonLockedRequest))] +[JsonSerializable(typeof(PersistenceWriteRawJsonLockedResult))] [JsonSerializable(typeof(PingRequest))] [JsonSerializable(typeof(PingResult))] [JsonSerializable(typeof(PlanReadResult))] @@ -53122,7 +58132,13 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH [JsonSerializable(typeof(RemoteSessionConnectionResult))] [JsonSerializable(typeof(RemoteSessionMetadataRepository))] [JsonSerializable(typeof(RemoteSessionMetadataValue))] +[JsonSerializable(typeof(RepoSettingsEditRequest))] +[JsonSerializable(typeof(RepoSettingsReadRawRequest))] +[JsonSerializable(typeof(RepoSettingsReadRawResult))] +[JsonSerializable(typeof(RepoSettingsWriteAllRequest))] +[JsonSerializable(typeof(RepoSettingsWriteKeyRequest))] [JsonSerializable(typeof(ResponseFormat))] +[JsonSerializable(typeof(SandboxApplyDefaultsRequest))] [JsonSerializable(typeof(SandboxConfig))] [JsonSerializable(typeof(SandboxConfigAuth))] [JsonSerializable(typeof(SandboxConfigUserPolicy))] @@ -53132,18 +58148,37 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH [JsonSerializable(typeof(SandboxConfigUserPolicyNetwork))] [JsonSerializable(typeof(SandboxConfigUserPolicyNetworkProxy))] [JsonSerializable(typeof(SandboxConfigUserPolicySeatbelt))] +[JsonSerializable(typeof(SandboxCredentialSuggestion))] +[JsonSerializable(typeof(SandboxCredentialSuggestionsResult))] [JsonSerializable(typeof(SandboxCredentialsConfig))] [JsonSerializable(typeof(SandboxDisableForSessionRequest))] [JsonSerializable(typeof(SandboxDisableForSessionResult))] +[JsonSerializable(typeof(SandboxDocsUrlResult))] +[JsonSerializable(typeof(SandboxEffectiveForOptionalParams))] +[JsonSerializable(typeof(SandboxEffectiveForOptionalResult))] +[JsonSerializable(typeof(SandboxEnforcementCommandAllowedParams))] [JsonSerializable(typeof(SandboxEnforcementStatus))] +[JsonSerializable(typeof(SandboxFilterPickerCandidatesParams))] +[JsonSerializable(typeof(SandboxFilterPickerCandidatesResult))] [JsonSerializable(typeof(SandboxGrantPathForRequestRequest))] [JsonSerializable(typeof(SandboxGrantPathForRequestResult))] [JsonSerializable(typeof(SandboxHostCapability))] [JsonSerializable(typeof(SandboxHostSupport))] [JsonSerializable(typeof(SandboxMaskedEnvVar))] +[JsonSerializable(typeof(SandboxPickerCandidate))] +[JsonSerializable(typeof(SandboxPreparePickerPolicyParams))] +[JsonSerializable(typeof(SandboxPreparePickerPolicyResult))] [JsonSerializable(typeof(SandboxProxyCaCreateResult))] [JsonSerializable(typeof(SandboxProxyCaRequest))] [JsonSerializable(typeof(SandboxProxyCaStatus))] +[JsonSerializable(typeof(SandboxProxySecretGetResult))] +[JsonSerializable(typeof(SandboxProxySecretLocation))] +[JsonSerializable(typeof(SandboxProxySecretSaveRequest))] +[JsonSerializable(typeof(SandboxProxySecretSaveResult))] +[JsonSerializable(typeof(SandboxResolveFlooredConfigParams))] +[JsonSerializable(typeof(SandboxResolveFlooredConfigResult))] +[JsonSerializable(typeof(SandboxUnsupportedStartupNoticeParams))] +[JsonSerializable(typeof(SandboxUnsupportedStartupNoticeResult))] [JsonSerializable(typeof(ScheduleAddAtRequest))] [JsonSerializable(typeof(ScheduleAddCronRequest))] [JsonSerializable(typeof(ScheduleAddRequest))] @@ -53297,6 +58332,9 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH [JsonSerializable(typeof(SessionPluginsMarketplacesRefreshRequestWithSession))] [JsonSerializable(typeof(SessionPluginsReloadRequest))] [JsonSerializable(typeof(SessionPluginsReloadRequestWithSession))] +[JsonSerializable(typeof(SessionPluginsRetryManagedRequest))] +[JsonSerializable(typeof(SessionPluginsRetryManagedRequestWithSession))] +[JsonSerializable(typeof(SessionPluginsRetryManagedResult))] [JsonSerializable(typeof(SessionProviderGetEndpointRequest))] [JsonSerializable(typeof(SessionProviderGetEndpointRequestWithSession))] [JsonSerializable(typeof(SessionProvidersGetCatalogRequest))] @@ -53316,6 +58354,7 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH [JsonSerializable(typeof(SessionQuotaSnapshot))] [JsonSerializable(typeof(SessionQuotaTakeWarningsRequest))] [JsonSerializable(typeof(SessionRemoteDisableRequest))] +[JsonSerializable(typeof(SessionSandboxGetCredentialSuggestionsRequest))] [JsonSerializable(typeof(SessionSandboxGetEnforcementStatusRequest))] [JsonSerializable(typeof(SessionScheduleHasSelfPacedRequest))] [JsonSerializable(typeof(SessionScheduleHydrateRequest))] @@ -53413,6 +58452,7 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH [JsonSerializable(typeof(SessionsUpdateWorkspaceFieldsRequest))] [JsonSerializable(typeof(SessionsUpdateWorkspaceFieldsResult))] [JsonSerializable(typeof(SettableAuthInfo))] +[JsonSerializable(typeof(SettingsEditResult))] [JsonSerializable(typeof(ShellCancelUserRequestedRequest))] [JsonSerializable(typeof(ShellCredentials))] [JsonSerializable(typeof(ShellExecRequest))] @@ -53442,6 +58482,7 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH [JsonSerializable(typeof(SkillInstallationSummary))] [JsonSerializable(typeof(SkillInstallationsRequest))] [JsonSerializable(typeof(SkillList))] +[JsonSerializable(typeof(SkillPlanInstallFromReferenceRequest))] [JsonSerializable(typeof(SkillPlanInstallRequest))] [JsonSerializable(typeof(SkillPlanUninstallRequest))] [JsonSerializable(typeof(SkillSetEnabledRequest))] @@ -53459,7 +58500,6 @@ 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))] @@ -53523,7 +58563,6 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH [JsonSerializable(typeof(UIHandlePendingExitPlanModeRequest))] [JsonSerializable(typeof(UIHandlePendingResult))] [JsonSerializable(typeof(UIHandlePendingSamplingRequest))] -[JsonSerializable(typeof(UIHandlePendingSamplingResponse))] [JsonSerializable(typeof(UIHandlePendingSessionLimitsExhaustedRequest))] [JsonSerializable(typeof(UIHandlePendingUserInputRequest))] [JsonSerializable(typeof(UIRegisterDirectAutoModeSwitchHandlerResult))] @@ -53535,8 +58574,52 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH [JsonSerializable(typeof(UpdateSubagentSettingsRequestSubagents))] [JsonSerializable(typeof(UserRequestedShellCommandResult))] [JsonSerializable(typeof(UserSettingMetadata))] +[JsonSerializable(typeof(UserSettingsApplyTopKeyRequest))] +[JsonSerializable(typeof(UserSettingsApplyTopKeyResult))] +[JsonSerializable(typeof(UserSettingsBuildRuntimeRequest))] +[JsonSerializable(typeof(UserSettingsEditRequest))] +[JsonSerializable(typeof(UserSettingsGetPathsRequest))] +[JsonSerializable(typeof(UserSettingsGetPathsResult))] [JsonSerializable(typeof(UserSettingsGetResult))] +[JsonSerializable(typeof(UserSettingsGovernanceKeysResult))] +[JsonSerializable(typeof(UserSettingsHostContext))] +[JsonSerializable(typeof(UserSettingsLoadAndMergeRepoRequest))] +[JsonSerializable(typeof(UserSettingsLoadAndMergeRepoResult))] +[JsonSerializable(typeof(UserSettingsLoadForConfigDirRequest))] +[JsonSerializable(typeof(UserSettingsLoadForEditResult))] +[JsonSerializable(typeof(UserSettingsLoadRepoRequest))] +[JsonSerializable(typeof(UserSettingsLoadRepoResult))] +[JsonSerializable(typeof(UserSettingsLoadWithWarningResult))] +[JsonSerializable(typeof(UserSettingsMergeManagedAndUserParams))] +[JsonSerializable(typeof(UserSettingsMergeManagedAndUserResult))] +[JsonSerializable(typeof(UserSettingsMergeRepoValueParams))] +[JsonSerializable(typeof(UserSettingsMergeRepoValueResult))] +[JsonSerializable(typeof(UserSettingsMigrateObsoleteOnDiskRequest))] +[JsonSerializable(typeof(UserSettingsMigrateObsoleteOnDiskResult))] +[JsonSerializable(typeof(UserSettingsMigrateStaffLogLevelOnceRequest))] +[JsonSerializable(typeof(UserSettingsMigrateStaffLogLevelOnceResult))] +[JsonSerializable(typeof(UserSettingsMigrateStaffUpdateChannelOnceResult))] +[JsonSerializable(typeof(UserSettingsNormalizeRequest))] +[JsonSerializable(typeof(UserSettingsNormalizeResult))] +[JsonSerializable(typeof(UserSettingsPathResult))] +[JsonSerializable(typeof(UserSettingsProblem))] +[JsonSerializable(typeof(UserSettingsProblemLocation))] +[JsonSerializable(typeof(UserSettingsProblemSource))] +[JsonSerializable(typeof(UserSettingsProblemsRequest))] +[JsonSerializable(typeof(UserSettingsProblemsResult))] +[JsonSerializable(typeof(UserSettingsResetStaffModelSelectionOnceRequest))] +[JsonSerializable(typeof(UserSettingsResetStaffModelSelectionOnceResult))] +[JsonSerializable(typeof(UserSettingsSetHookDisabledRequest))] +[JsonSerializable(typeof(UserSettingsSetHookDisabledResult))] [JsonSerializable(typeof(UserSettingsSetRequest))] +[JsonSerializable(typeof(UserSettingsUnknownKeysWarningRequest))] +[JsonSerializable(typeof(UserSettingsUnknownKeysWarningResult))] +[JsonSerializable(typeof(UserSettingsUpdateRequest))] +[JsonSerializable(typeof(UserSettingsUpdateResult))] +[JsonSerializable(typeof(UserSettingsValidateRequest))] +[JsonSerializable(typeof(UserSettingsValidateResult))] +[JsonSerializable(typeof(UserSettingsValidationIssue))] +[JsonSerializable(typeof(UserSettingsWriteAllRequest))] [JsonSerializable(typeof(VisibilityGetResult))] [JsonSerializable(typeof(VisibilitySetRequest))] [JsonSerializable(typeof(VisibilitySetResult))] @@ -53585,8 +58668,6 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH [JsonSerializable(typeof(WorkspaceDiffResult))] [JsonSerializable(typeof(WorkspacesAddSummaryRequest))] [JsonSerializable(typeof(WorkspacesAddSummaryResult))] -[JsonSerializable(typeof(WorkspacesAddSummaryResultSummary))] -[JsonSerializable(typeof(WorkspacesAddSummaryResultWorkspace))] [JsonSerializable(typeof(WorkspacesAutopilotObjectiveExistsResult))] [JsonSerializable(typeof(WorkspacesCheckpoints))] [JsonSerializable(typeof(WorkspacesCreateDirectoryRequest))] diff --git a/dotnet/src/Generated/SessionEvents.cs b/dotnet/src/Generated/SessionEvents.cs index fb23ccb58a..b5fb20b790 100644 --- a/dotnet/src/Generated/SessionEvents.cs +++ b/dotnet/src/Generated/SessionEvents.cs @@ -3014,7 +3014,7 @@ public sealed partial class SessionErrorData [JsonPropertyName("errorCode")] public string? ErrorCode { get; set; } - /// Category of error (e.g., "authentication", "authorization", "quota", "rate_limit", "context_limit", "query"). + /// Category of error (e.g., "authentication", "authorization", "policy", "quota", "rate_limit", "context_limit", "query"). [JsonPropertyName("errorType")] public required string ErrorType { get; set; } @@ -3027,6 +3027,16 @@ public sealed partial class SessionErrorData [JsonPropertyName("providerCallId")] public string? ProviderCallId { get; set; } + /// Sanitized provider-defined error parameter preserved from a structured upstream error envelope, when available. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("providerErrorParam")] + public string? ProviderErrorParam { get; set; } + + /// Provider-defined error type preserved from a structured upstream error envelope, when available (for example, `invalid_request_error`). + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("providerErrorType")] + public string? ProviderErrorType { get; set; } + /// What the user must do to recover, when the runtime knows of an action. The `message` never names a client affordance, so a client that offers one — a slash command, a settings pane, a link — renders it from this value. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("remediation")] @@ -3678,7 +3688,7 @@ public sealed partial class SessionUsageCheckpointData [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] public sealed partial class SessionQuotaObservationData { - /// The admitted provider's state observation. Admission observations never contain quantities or reset/percentage semantics. + /// The admitted provider's state observation. Admission balances and percentages remain absent; separate monthly usage may report its own amount and cycle. [JsonPropertyName("observation")] public required ProviderQuotaState Observation { get; set; } } @@ -3822,12 +3832,6 @@ public sealed partial class SessionCompactionStartData /// Conversation compaction results including success status, metrics, and optional error details. public sealed partial class SessionCompactionCompleteData { - /// Legacy active-workflow reminder retained for replay compatibility. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonInclude] - [JsonPropertyName("activeFactorySummary")] - internal string? ActiveFactorySummary { get; set; } - /// Authoritative active-workflow reminder appended to the compacted context. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonInclude] @@ -4296,6 +4300,16 @@ public sealed partial class SessionPermissionRecoveryData [JsonPropertyName("onBlocked")] public required PermissionRecoveryOnBlocked OnBlocked { get; set; } + /// Subagent identifier that owns this recovery episode when the request was delegated through a parent permission service. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("ownerAgentId")] + public string? OwnerAgentId { get; set; } + + /// Root or child execution-session identifier that owns this recovery episode; omitted for legacy snapshots. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("ownerSessionId")] + public string? OwnerSessionId { get; set; } + /// Controlled reason for the latest episode transition. [JsonPropertyName("reason")] public required PermissionRecoveryReason Reason { get; set; } @@ -5218,11 +5232,21 @@ public sealed partial class AssistantUsageData [JsonPropertyName("rejectedPredictionTokens")] public long? RejectedPredictionTokens { get; set; } - /// Serialized (uncompressed) byte length of the request body. A content-free size signal. + /// Serialized (uncompressed) byte length of the request body, before any Content-Encoding. A content-free size signal. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("requestBodyBytes")] public long? RequestBodyBytes { get; set; } + /// Content-Encoding applied to the request body of the attempt that produced this outcome; identity when uncompressed. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("requestBodyEncoding")] + public ModelCallRequestBodyEncoding? RequestBodyEncoding { get; set; } + + /// Byte length of the request body actually sent on the wire for the attempt that produced this outcome, after any Content-Encoding. Equals requestBodyBytes when the body was not compressed. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("requestBodyWireBytes")] + public long? RequestBodyWireBytes { 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")] @@ -5274,10 +5298,35 @@ public sealed partial class AssistantUsageData [JsonPropertyName("websocketFallbackAfterMs")] public TimeSpan? WebsocketFallbackAfter { get; set; } + /// WebSocket close code received before the HTTP fallback. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("websocketFallbackCloseCode")] + public int? WebsocketFallbackCloseCode { get; set; } + + /// Normalized cause of the WebSocket failure that triggered the HTTP fallback; absent when no fallback occurred or the cause is unknown. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("websocketFallbackErrorKind")] + public ModelCallWebSocketFallbackErrorKind? WebsocketFallbackErrorKind { get; set; } + /// Why the call was carried by the HTTP fallback of a WebSocket-capable dispatcher; absent when no fallback occurred. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("websocketFallbackReason")] public ModelCallWebSocketFallbackReason? WebsocketFallbackReason { get; set; } + + /// Whether a WebSocket reconnect was attempted before falling back to HTTP. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("websocketFallbackReconnectAttempted")] + public bool? WebsocketFallbackReconnectAttempted { get; set; } + + /// Whether the HTTP fallback started during this model call, rather than being inherited from an earlier call. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("websocketFallbackStartedThisCall")] + public bool? WebsocketFallbackStartedThisCall { get; set; } + + /// HTTP status of the WebSocket upgrade rejection or WebSocket API error that triggered the HTTP fallback. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("websocketFallbackStatusCode")] + public int? WebsocketFallbackStatusCode { get; set; } } /// A detected loss of a previously cached prompt prefix. @@ -5532,11 +5581,21 @@ public sealed partial class ModelCallFailureData [JsonPropertyName("reasoningEffort")] public string? ReasoningEffort { get; set; } - /// Serialized (uncompressed) byte length of the failed request body. A content-free size signal. + /// Serialized (uncompressed) byte length of the failed request body, before any Content-Encoding. A content-free size signal. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("requestBodyBytes")] public long? RequestBodyBytes { get; set; } + /// Content-Encoding applied to the failed request body; identity when uncompressed. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("requestBodyEncoding")] + public ModelCallRequestBodyEncoding? RequestBodyEncoding { get; set; } + + /// Byte length of the failed request body actually sent on the wire, after any Content-Encoding. Equals requestBodyBytes when the body was not compressed. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("requestBodyWireBytes")] + public long? RequestBodyWireBytes { 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")] @@ -5577,10 +5636,35 @@ public sealed partial class ModelCallFailureData [JsonPropertyName("websocketFallbackAfterMs")] public TimeSpan? WebsocketFallbackAfter { get; set; } + /// WebSocket close code received before the HTTP fallback. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("websocketFallbackCloseCode")] + public int? WebsocketFallbackCloseCode { get; set; } + + /// Normalized cause of the WebSocket failure that triggered the HTTP fallback; absent when no fallback occurred or the cause is unknown. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("websocketFallbackErrorKind")] + public ModelCallWebSocketFallbackErrorKind? WebsocketFallbackErrorKind { get; set; } + /// Why the failed call was carried by the HTTP fallback of a WebSocket-capable dispatcher; absent when no fallback occurred. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("websocketFallbackReason")] public ModelCallWebSocketFallbackReason? WebsocketFallbackReason { get; set; } + + /// Whether a WebSocket reconnect was attempted before falling back to HTTP. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("websocketFallbackReconnectAttempted")] + public bool? WebsocketFallbackReconnectAttempted { get; set; } + + /// Whether the HTTP fallback started during this model call, rather than being inherited from an earlier call. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("websocketFallbackStartedThisCall")] + public bool? WebsocketFallbackStartedThisCall { get; set; } + + /// HTTP status of the WebSocket upgrade rejection or WebSocket API error that triggered the HTTP fallback. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("websocketFallbackStatusCode")] + public int? WebsocketFallbackStatusCode { get; set; } } /// Internal telemetry result for one logical model operation after all orchestrator-owned retries settle. @@ -6162,11 +6246,6 @@ public sealed partial class SubagentStartedData [JsonPropertyName("executionMode")] public string? ExecutionMode { get; set; } - /// Legacy root id of the workflow run that spawned this sub-agent. New consumers should use workflowRunId. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("factoryRunId")] - public string? FactoryRunId { get; set; } - /// Model the sub-agent will run with, when known at start. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("model")] @@ -6556,6 +6635,16 @@ public sealed partial class PermissionRequestedData [JsonPropertyName("agentMode")] public SessionMode? AgentMode { get; set; } + /// Delegated agent that owns permission recovery when the owner session multiplexes remote agents. Absent otherwise. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("ownerAgentId")] + public string? OwnerAgentId { get; set; } + + /// Execution session that owns permission recovery for this request. Absent on historical events. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("ownerSessionId")] + public string? OwnerSessionId { get; set; } + /// Permission mode captured when evaluation began. Absent on historical events. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("permissionMode")] @@ -7295,7 +7384,7 @@ public sealed partial class SessionManagedPluginProgressData [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] public sealed partial class SessionManagedSettingsResolvedData { - /// Whether enterprise policy disables bypass-permissions ("yolo") mode for this session. Deny-wins across layers, and forced on when `failClosed` is true. + /// Whether an explicit enterprise policy restriction disables bypass-permissions ("yolo") mode for this session. Deny-wins across layers; an unresolved policy does not force this on by itself. [JsonPropertyName("bypassPermissionsDisabled")] public required bool BypassPermissionsDisabled { get; set; } @@ -7308,7 +7397,7 @@ public sealed partial class SessionManagedSettingsResolvedData [JsonPropertyName("deviceManaged")] public required bool DeviceManaged { 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. + /// Whether managed policy could not be determined (e.g. a failed server fetch) and unresolved-policy safeguards remain active. This does not by itself disable bypass-permissions; `bypassPermissionsDisabled` reports only an explicit policy restriction. [JsonPropertyName("failClosed")] public required bool FailClosed { get; set; } @@ -7321,6 +7410,11 @@ public sealed partial class SessionManagedSettingsResolvedData [JsonPropertyName("permissionsAllowIntersected")] public bool? PermissionsAllowIntersected { get; set; } + /// Reusable retained managed permission policy for managedSettings.permissions.evaluate, including source composition and session-local injection. Provided by live session snapshots; absent in older events. Refresh the context when managed policy changes. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("permissionsContext")] + public ManagedPermissionsContext? PermissionsContext { get; set; } + /// 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("policyHelperManaged")] @@ -8489,6 +8583,44 @@ public sealed partial class ProviderQuotaBudgetMetadata public required bool UsageAllowedWhenExhausted { get; set; } } +/// An authoritative monthly usage reading for the observation's provider, account, and service. +/// Nested data type for ProviderMonthlyUsage. +[Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] +public sealed partial class ProviderMonthlyUsage +{ + /// Nonnegative finite consumption reported by the service, preserving zero and fractions. Present only when state is available; never computed from tokens or balance differences. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("consumedQuantity")] + public double? ConsumedQuantity { get; set; } + + /// Start of the service-reported monthly billing cycle, in UTC RFC 3339. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("cycleStart")] + public string? CycleStart { get; set; } + + /// Service read time in UTC RFC 3339, not a ledger reconciliation watermark or confirmation of the latest inference charge. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("queriedAt")] + public string? QueriedAt { get; set; } + + /// End of the service-reported monthly billing cycle, in UTC RFC 3339. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("resetOn")] + public string? ResetOn { get; set; } + + /// Identity scope of the reading within its provider and service. + [JsonPropertyName("scope")] + public required ProviderMonthlyUsageScope Scope { get; set; } + + /// Availability of monthly consumption. Missing or unavailable usage must never be interpreted as zero. + [JsonPropertyName("state")] + public required ProviderMonthlyUsageState State { get; set; } + + /// Unit of consumedQuantity; independent of the parent observation's balance unit. + [JsonPropertyName("unit")] + public required ProviderQuotaUnit Unit { get; set; } +} + /// An account quota reading. Absence of a quantity is unknown, never zero. /// Nested data type for ProviderQuotaState. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] @@ -8542,6 +8674,11 @@ public sealed partial class ProviderQuotaState [JsonPropertyName("httpStatus")] public long? HttpStatus { get; set; } + /// Service-reported monthly consumption, independent of quota balances and per-call or session cost. Omitted when the service does not report monthly usage. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("monthlyUsage")] + public ProviderMonthlyUsage? MonthlyUsage { get; set; } + /// Whether this is a GET account reading or a pre-response admission observation. Observations are never merged across kinds. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("observationKind")] @@ -8687,6 +8824,11 @@ public sealed partial class CompactionCompleteCompactionTokensUsed [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("provider")] public ModelProviderRef? Provider { get; set; } + + /// Number of model requests represented by this aggregated compaction usage. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("requestCount")] + public long? RequestCount { get; set; } } /// Original request-level and effective conversation reasoning effort for a provider history boundary; the historical type name is retained for compatibility. @@ -8763,6 +8905,16 @@ public sealed partial class PermissionRecoveryData [JsonPropertyName("onBlocked")] public required PermissionRecoveryOnBlocked OnBlocked { get; set; } + /// Subagent identifier that owns this recovery episode when the request was delegated through a parent permission service. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("ownerAgentId")] + public string? OwnerAgentId { get; set; } + + /// Root or child execution-session identifier that owns this recovery episode; omitted for legacy snapshots. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("ownerSessionId")] + public string? OwnerSessionId { get; set; } + /// Controlled reason for the latest episode transition. [JsonPropertyName("reason")] public required PermissionRecoveryReason Reason { get; set; } @@ -13548,6 +13700,20 @@ public sealed partial class SessionLimitsExhaustedResponse public double? MaxAiCredits { get; set; } } +/// Reusable managed permission evaluation context. Treat permissions as runtime-owned policy data and retain it verbatim; it includes source-aware composition metadata. This is a snapshot, not a capability or approval token; only use contexts obtained from trusted policy sources. +/// Nested data type for ManagedPermissionsContext. +public sealed partial class ManagedPermissionsContext +{ + /// Policy could not be determined; evaluation must deny every operation. + [JsonPropertyName("failClosed")] + public required bool FailClosed { get; set; } + + /// Runtime-owned composed permissions object. Absent when no permission policy is configured. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("permissions")] + public JsonElement? Permissions { 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 @@ -14311,6 +14477,9 @@ public RemediationAction(string value) /// Permit outbound network access in the sandbox policy. public static RemediationAction AllowSandboxOutbound { get; } = new("allow_sandbox_outbound"); + /// Discard conversation history back through the blocked request before continuing. + public static RemediationAction RewindConversation { get; } = new("rewind_conversation"); + /// Returns a value indicating whether two instances are equivalent. public static bool operator ==(RemediationAction left, RemediationAction right) => left.Equals(right); @@ -15884,42 +16053,42 @@ public override void Write(Utf8JsonWriter writer, ProviderQuotaCapacityState val } } -/// Timing and purpose of a provider quota observation. +/// Identity scope of a monthly usage reading. [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct ProviderQuotaObservationKind : IEquatable +public readonly struct ProviderMonthlyUsageScope : 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 ProviderQuotaObservationKind(string value) + public ProviderMonthlyUsageScope(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; - /// An explicit account read; consumption may still be subject to service aggregation delay. - public static ProviderQuotaObservationKind AccountSnapshot { get; } = new("account_snapshot"); + /// Consumption of the authenticated user within the parent observation's service and returned cycle. + public static ProviderMonthlyUsageScope User { get; } = new("user"); - /// A verdict attached before an inference response. May be cached; never a post-charge balance. - public static ProviderQuotaObservationKind AdmissionState { get; } = new("admission_state"); + /// The service did not establish a recognized usage scope. + public static ProviderMonthlyUsageScope Unknown { get; } = new("unknown"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(ProviderQuotaObservationKind left, ProviderQuotaObservationKind right) => left.Equals(right); + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(ProviderMonthlyUsageScope left, ProviderMonthlyUsageScope right) => left.Equals(right); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(ProviderQuotaObservationKind left, ProviderQuotaObservationKind right) => !(left == right); + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(ProviderMonthlyUsageScope left, ProviderMonthlyUsageScope right) => !(left == right); /// - public override bool Equals(object? obj) => obj is ProviderQuotaObservationKind other && Equals(other); + public override bool Equals(object? obj) => obj is ProviderMonthlyUsageScope other && Equals(other); /// - public bool Equals(ProviderQuotaObservationKind other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(ProviderMonthlyUsageScope other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -15927,63 +16096,66 @@ public ProviderQuotaObservationKind(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 ProviderQuotaObservationKind Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override ProviderMonthlyUsageScope Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, ProviderQuotaObservationKind value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, ProviderMonthlyUsageScope value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ProviderQuotaObservationKind)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ProviderMonthlyUsageScope)); } } } -/// Interpretation permitted for independently reported quantities. +/// Availability of a service's monthly consumption reading. [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct ProviderQuotaQuantityKind : IEquatable +public readonly struct ProviderMonthlyUsageState : 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 ProviderQuotaQuantityKind(string value) + public ProviderMonthlyUsageState(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; - /// Quantities form an authoritative available-of-entitled budget suitable for percentage presentation. - public static ProviderQuotaQuantityKind AuthoritativeBudget { get; } = new("authoritative_budget"); + /// The service reported a usable consumption reading, including an explicit zero. + public static ProviderMonthlyUsageState Available { get; } = new("available"); - /// Raw advisory quantities; do not infer permission, usage percentage, reset cadence, or post-charge balance. - public static ProviderQuotaQuantityKind AdvisoryBalance { get; } = new("advisory_balance"); + /// The service found no usage policy; no consumption amount is known. + public static ProviderMonthlyUsageState NoPolicy { get; } = new("no_policy"); - /// No quantity measurement, as with admission-only response headers. - public static ProviderQuotaQuantityKind None { get; } = new("none"); + /// The read failed or its data could not be used; consumption is unknown. + public static ProviderMonthlyUsageState Unavailable { get; } = new("unavailable"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(ProviderQuotaQuantityKind left, ProviderQuotaQuantityKind right) => left.Equals(right); + /// The service reported an unrecognized usage state. + public static ProviderMonthlyUsageState Unknown { get; } = new("unknown"); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(ProviderQuotaQuantityKind left, ProviderQuotaQuantityKind right) => !(left == right); + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(ProviderMonthlyUsageState left, ProviderMonthlyUsageState right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(ProviderMonthlyUsageState left, ProviderMonthlyUsageState right) => !(left == right); /// - public override bool Equals(object? obj) => obj is ProviderQuotaQuantityKind other && Equals(other); + public override bool Equals(object? obj) => obj is ProviderMonthlyUsageState other && Equals(other); /// - public bool Equals(ProviderQuotaQuantityKind other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(ProviderMonthlyUsageState other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -15991,20 +16163,20 @@ public ProviderQuotaQuantityKind(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 ProviderQuotaQuantityKind Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override ProviderMonthlyUsageState Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, ProviderQuotaQuantityKind value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, ProviderMonthlyUsageState value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ProviderQuotaQuantityKind)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ProviderMonthlyUsageState)); } } } @@ -16076,6 +16248,131 @@ public override void Write(Utf8JsonWriter writer, ProviderQuotaUnit value, JsonS } } +/// Timing and purpose of a provider quota observation. +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct ProviderQuotaObservationKind : IEquatable +{ + private readonly string? _value; + + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public ProviderQuotaObservationKind(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } + + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; + + /// An explicit account read; consumption may still be subject to service aggregation delay. + public static ProviderQuotaObservationKind AccountSnapshot { get; } = new("account_snapshot"); + + /// A verdict attached before an inference response. May be cached; never a post-charge balance. + public static ProviderQuotaObservationKind AdmissionState { get; } = new("admission_state"); + + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(ProviderQuotaObservationKind left, ProviderQuotaObservationKind right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(ProviderQuotaObservationKind left, ProviderQuotaObservationKind right) => !(left == right); + + /// + public override bool Equals(object? obj) => obj is ProviderQuotaObservationKind other && Equals(other); + + /// + public bool Equals(ProviderQuotaObservationKind 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 ProviderQuotaObservationKind Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } + + /// + public override void Write(Utf8JsonWriter writer, ProviderQuotaObservationKind value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ProviderQuotaObservationKind)); + } + } +} + +/// Interpretation permitted for independently reported quantities. +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct ProviderQuotaQuantityKind : IEquatable +{ + private readonly string? _value; + + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public ProviderQuotaQuantityKind(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } + + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; + + /// Quantities form an authoritative available-of-entitled budget suitable for percentage presentation. + public static ProviderQuotaQuantityKind AuthoritativeBudget { get; } = new("authoritative_budget"); + + /// Raw advisory quantities; do not infer permission, usage percentage, reset cadence, or post-charge balance. + public static ProviderQuotaQuantityKind AdvisoryBalance { get; } = new("advisory_balance"); + + /// No quantity measurement, as with admission-only response headers. + public static ProviderQuotaQuantityKind None { get; } = new("none"); + + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(ProviderQuotaQuantityKind left, ProviderQuotaQuantityKind right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(ProviderQuotaQuantityKind left, ProviderQuotaQuantityKind right) => !(left == right); + + /// + public override bool Equals(object? obj) => obj is ProviderQuotaQuantityKind other && Equals(other); + + /// + public bool Equals(ProviderQuotaQuantityKind 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 ProviderQuotaQuantityKind Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } + + /// + public override void Write(Utf8JsonWriter writer, ProviderQuotaQuantityKind value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ProviderQuotaQuantityKind)); + } + } +} + /// What initiated a conversation compaction. [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] @@ -18038,6 +18335,70 @@ public override void Write(Utf8JsonWriter writer, AssistantUsageApiEndpoint valu } } +/// Content-Encoding applied to the request body sent on the wire. +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct ModelCallRequestBodyEncoding : IEquatable +{ + private readonly string? _value; + + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public ModelCallRequestBodyEncoding(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } + + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; + + /// The request body was sent uncompressed (including every WebSocket request). + public static ModelCallRequestBodyEncoding Identity { get; } = new("identity"); + + /// The request body was sent gzip-compressed. + public static ModelCallRequestBodyEncoding Gzip { get; } = new("gzip"); + + /// The request body was sent zstd-compressed. + public static ModelCallRequestBodyEncoding Zstd { get; } = new("zstd"); + + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(ModelCallRequestBodyEncoding left, ModelCallRequestBodyEncoding right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(ModelCallRequestBodyEncoding left, ModelCallRequestBodyEncoding right) => !(left == right); + + /// + public override bool Equals(object? obj) => obj is ModelCallRequestBodyEncoding other && Equals(other); + + /// + public bool Equals(ModelCallRequestBodyEncoding 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 ModelCallRequestBodyEncoding Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } + + /// + public override void Write(Utf8JsonWriter writer, ModelCallRequestBodyEncoding value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ModelCallRequestBodyEncoding)); + } + } +} + /// Transport used for a successful model call. [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] @@ -18099,6 +18460,97 @@ public override void Write(Utf8JsonWriter writer, AssistantUsageTransport value, } } +/// Normalized cause of the WebSocket failure that triggered the HTTP fallback. +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct ModelCallWebSocketFallbackErrorKind : IEquatable +{ + private readonly string? _value; + + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public ModelCallWebSocketFallbackErrorKind(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } + + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; + + /// DNS resolution of the WebSocket host failed. + public static ModelCallWebSocketFallbackErrorKind Dns { get; } = new("dns"); + + /// The TLS handshake or a TLS record failed. + public static ModelCallWebSocketFallbackErrorKind Tls { get; } = new("tls"); + + /// A WebSocket connect or read timed out. + public static ModelCallWebSocketFallbackErrorKind Timeout { get; } = new("timeout"); + + /// The peer refused the TCP connection. + public static ModelCallWebSocketFallbackErrorKind ConnectionRefused { get; } = new("connection_refused"); + + /// The connection was reset, aborted, or ended mid-stream without a close. + public static ModelCallWebSocketFallbackErrorKind ConnectionReset { get; } = new("connection_reset"); + + /// The peer closed the WebSocket. + public static ModelCallWebSocketFallbackErrorKind ClosedByPeer { get; } = new("closed_by_peer"); + + /// The runtime closed the WebSocket itself. + public static ModelCallWebSocketFallbackErrorKind ClosedLocally { get; } = new("closed_locally"); + + /// The socket was already closed before it was used. + public static ModelCallWebSocketFallbackErrorKind NotConnected { get; } = new("not_connected"); + + /// The upgrade or request was rejected with an HTTP status. + public static ModelCallWebSocketFallbackErrorKind HttpStatus { get; } = new("http_status"); + + /// A WebSocket protocol violation or an undecodable frame. + public static ModelCallWebSocketFallbackErrorKind Protocol { get; } = new("protocol"); + + /// The WebSocket request could not be built. + public static ModelCallWebSocketFallbackErrorKind Configuration { get; } = new("configuration"); + + /// A failure that maps onto none of the other kinds. + public static ModelCallWebSocketFallbackErrorKind Other { get; } = new("other"); + + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(ModelCallWebSocketFallbackErrorKind left, ModelCallWebSocketFallbackErrorKind right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(ModelCallWebSocketFallbackErrorKind left, ModelCallWebSocketFallbackErrorKind right) => !(left == right); + + /// + public override bool Equals(object? obj) => obj is ModelCallWebSocketFallbackErrorKind other && Equals(other); + + /// + public bool Equals(ModelCallWebSocketFallbackErrorKind 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 ModelCallWebSocketFallbackErrorKind Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } + + /// + public override void Write(Utf8JsonWriter writer, ModelCallWebSocketFallbackErrorKind value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ModelCallWebSocketFallbackErrorKind)); + } + } +} + /// Why a WebSocket-capable model call was carried by the HTTP fallback. [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] @@ -23184,6 +23636,7 @@ public override void Write(Utf8JsonWriter writer, ExtensionsLoadedExtensionStatu [JsonSerializable(typeof(HumanResponseRecordedResponseAskUser))] [JsonSerializable(typeof(HumanResponseRecordedResponseExitPlanMode))] [JsonSerializable(typeof(HumanResponseRecordedResponseUserInput))] +[JsonSerializable(typeof(ManagedPermissionsContext))] [JsonSerializable(typeof(McpAppToolCallCompleteData))] [JsonSerializable(typeof(McpAppToolCallCompleteError))] [JsonSerializable(typeof(McpAppToolCallCompleteEvent))] @@ -23290,6 +23743,7 @@ public override void Write(Utf8JsonWriter writer, ExtensionsLoadedExtensionStatu [JsonSerializable(typeof(PersistedBinaryResult))] [JsonSerializable(typeof(PromptCacheBreakData))] [JsonSerializable(typeof(PromptCacheBreakEvent))] +[JsonSerializable(typeof(ProviderMonthlyUsage))] [JsonSerializable(typeof(ProviderQuotaBudgetMetadata))] [JsonSerializable(typeof(ProviderQuotaState))] [JsonSerializable(typeof(ResponsesReasoning))] diff --git a/dotnet/src/HookErrorTextConverter.cs b/dotnet/src/HookErrorTextConverter.cs new file mode 100644 index 0000000000..fb798790dd --- /dev/null +++ b/dotnet/src/HookErrorTextConverter.cs @@ -0,0 +1,39 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace GitHub.Copilot; + +/// +/// Reads the error of an errorOccurred or sessionEnd hook input as a string. +/// The CLI sends an error object ({"name", "message", "stack"} or {"message"}): +/// its message becomes the error text, and an object without a string message becomes its JSON. +/// A string passes through. +/// +internal sealed class HookErrorTextConverter : JsonConverter +{ + /// + public override string? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (reader.TokenType == JsonTokenType.StartObject) + { + using var document = JsonDocument.ParseValue(ref reader); + var error = document.RootElement; + return error.TryGetProperty("message", out var message) && message.ValueKind == JsonValueKind.String + ? message.GetString() + : error.GetRawText(); + } + + return reader.TokenType == JsonTokenType.String + ? reader.GetString() + : throw new JsonException( + $"Expected a string or an error object for a hook error, got {reader.TokenType}."); + } + + /// + public override void Write(Utf8JsonWriter writer, string? value, JsonSerializerOptions options) => + writer.WriteStringValue(value); +} diff --git a/dotnet/src/PermissionHandlers.cs b/dotnet/src/PermissionHandlers.cs index 6ff3abbda3..17b003f8a9 100644 --- a/dotnet/src/PermissionHandlers.cs +++ b/dotnet/src/PermissionHandlers.cs @@ -40,7 +40,7 @@ private static bool RequiresManagedApproval(PermissionRequest request) or "custom-tool" or "hook" or "extension-management" - or "factory" + or "workflow" or "extension-permission-access" or "extension-env-access"); } diff --git a/dotnet/src/Types.cs b/dotnet/src/Types.cs index f4ba58fe4b..2ebc2cc4cd 100644 --- a/dotnet/src/Types.cs +++ b/dotnet/src/Types.cs @@ -1473,6 +1473,18 @@ public sealed class HookInvocation /// public sealed class PreToolUseHookInput { + /// Subagent actor ID, absent for root hooks and older runtimes. + [JsonPropertyName("agentId")] + public string? AgentId { get; set; } + + /// Subagent type, when known by the runtime. + [JsonPropertyName("agentType")] + public string? AgentType { get; set; } + + /// Immediate parent actor ID, when available. + [JsonPropertyName("parentAgentId")] + public string? ParentAgentId { get; set; } + /// /// The runtime session ID of the session that triggered the hook. /// @@ -1551,6 +1563,18 @@ public sealed class PreToolUseHookOutput /// public sealed class PreMcpToolCallHookInput { + /// Subagent actor ID, absent for root hooks and older runtimes. + [JsonPropertyName("agentId")] + public string? AgentId { get; set; } + + /// Subagent type, when known by the runtime. + [JsonPropertyName("agentType")] + public string? AgentType { get; set; } + + /// Immediate parent actor ID, when available. + [JsonPropertyName("parentAgentId")] + public string? ParentAgentId { get; set; } + /// /// The runtime session ID of the session that triggered the hook. /// @@ -1628,6 +1652,18 @@ public sealed class PreMcpToolCallHookOutput /// public sealed class PostToolUseHookInput { + /// Subagent actor ID, absent for root hooks and older runtimes. + [JsonPropertyName("agentId")] + public string? AgentId { get; set; } + + /// Subagent type, when known by the runtime. + [JsonPropertyName("agentType")] + public string? AgentType { get; set; } + + /// Immediate parent actor ID, when available. + [JsonPropertyName("parentAgentId")] + public string? ParentAgentId { get; set; } + /// /// The runtime session ID of the session that triggered the hook. /// @@ -1699,6 +1735,18 @@ public sealed class PostToolUseHookOutput /// public sealed class PostToolUseFailureHookInput { + /// Subagent actor ID, absent for root hooks and older runtimes. + [JsonPropertyName("agentId")] + public string? AgentId { get; set; } + + /// Subagent type, when known by the runtime. + [JsonPropertyName("agentType")] + public string? AgentType { get; set; } + + /// Immediate parent actor ID, when available. + [JsonPropertyName("parentAgentId")] + public string? ParentAgentId { get; set; } + /// /// The runtime session ID of the session that triggered the hook. /// @@ -1757,6 +1805,18 @@ public sealed class PostToolUseFailureHookOutput /// public sealed class UserPromptSubmittedHookInput { + /// Subagent actor ID, absent for root hooks and older runtimes. + [JsonPropertyName("agentId")] + public string? AgentId { get; set; } + + /// Subagent type, when known by the runtime. + [JsonPropertyName("agentType")] + public string? AgentType { get; set; } + + /// Immediate parent actor ID, when available. + [JsonPropertyName("parentAgentId")] + public string? ParentAgentId { get; set; } + /// /// The runtime session ID of the session that triggered the hook. /// @@ -1812,6 +1872,18 @@ public sealed class UserPromptSubmittedHookOutput /// public sealed class UserPromptTransformedHookInput { + /// Subagent actor ID, absent for root hooks and older runtimes. + [JsonPropertyName("agentId")] + public string? AgentId { get; set; } + + /// Subagent type, when known by the runtime. + [JsonPropertyName("agentType")] + public string? AgentType { get; set; } + + /// Immediate parent actor ID, when available. + [JsonPropertyName("parentAgentId")] + public string? ParentAgentId { get; set; } + /// /// The runtime session ID of the session that triggered the hook. /// @@ -1861,6 +1933,18 @@ public sealed class UserPromptTransformedHookOutput /// public sealed class SessionStartHookInput { + /// Subagent actor ID, absent for root hooks and older runtimes. + [JsonPropertyName("agentId")] + public string? AgentId { get; set; } + + /// Subagent type, when known by the runtime. + [JsonPropertyName("agentType")] + public string? AgentType { get; set; } + + /// Immediate parent actor ID, when available. + [JsonPropertyName("parentAgentId")] + public string? ParentAgentId { get; set; } + /// /// The runtime session ID of the session that triggered the hook. /// @@ -1921,6 +2005,18 @@ public sealed class SessionStartHookOutput /// public sealed class SessionEndHookInput { + /// Subagent actor ID, absent for root hooks and older runtimes. + [JsonPropertyName("agentId")] + public string? AgentId { get; set; } + + /// Subagent type, when known by the runtime. + [JsonPropertyName("agentType")] + public string? AgentType { get; set; } + + /// Immediate parent actor ID, when available. + [JsonPropertyName("parentAgentId")] + public string? ParentAgentId { get; set; } + /// /// The runtime session ID of the session that triggered the hook. /// @@ -1963,6 +2059,7 @@ public sealed class SessionEndHookInput /// Error message if the session ended due to an error. /// [JsonPropertyName("error")] + [JsonConverter(typeof(HookErrorTextConverter))] public string? Error { get; set; } } @@ -1995,6 +2092,18 @@ public sealed class SessionEndHookOutput /// public sealed class ErrorOccurredHookInput { + /// Subagent actor ID, absent for root hooks and older runtimes. + [JsonPropertyName("agentId")] + public string? AgentId { get; set; } + + /// Subagent type, when known by the runtime. + [JsonPropertyName("agentType")] + public string? AgentType { get; set; } + + /// Immediate parent actor ID, when available. + [JsonPropertyName("parentAgentId")] + public string? ParentAgentId { get; set; } + /// /// The runtime session ID of the session that triggered the hook. /// @@ -2018,6 +2127,7 @@ public sealed class ErrorOccurredHookInput /// Error message describing what went wrong. /// [JsonPropertyName("error")] + [JsonConverter(typeof(HookErrorTextConverter))] public string Error { get; set; } = string.Empty; /// @@ -2079,6 +2189,18 @@ public sealed class ErrorOccurredHookOutput /// public sealed class AgentStopHookInput { + /// Subagent actor ID, absent for root hooks and older runtimes. + [JsonPropertyName("agentId")] + public string? AgentId { get; set; } + + /// Subagent type, when known by the runtime. + [JsonPropertyName("agentType")] + public string? AgentType { get; set; } + + /// Immediate parent actor ID, when available. + [JsonPropertyName("parentAgentId")] + public string? ParentAgentId { get; set; } + /// /// The runtime session ID of the session that triggered the hook. /// @@ -2140,6 +2262,18 @@ public sealed class AgentStopHookOutput /// public sealed class SubagentStartHookInput { + /// Subagent actor ID, absent for root hooks and older runtimes. + [JsonPropertyName("agentId")] + public string? AgentId { get; set; } + + /// Subagent type, when known by the runtime. + [JsonPropertyName("agentType")] + public string? AgentType { get; set; } + + /// Immediate parent actor ID, when available. + [JsonPropertyName("parentAgentId")] + public string? ParentAgentId { get; set; } + /// /// The runtime session ID of the parent session. /// @@ -2201,6 +2335,10 @@ public sealed class SubagentStartHookOutput /// public sealed class SubagentStopHookInput { + /// Immediate parent actor ID, when available. + [JsonPropertyName("parentAgentId")] + public string? ParentAgentId { get; set; } + /// /// The runtime session ID of the parent session. /// @@ -3131,6 +3269,12 @@ public sealed class McpHttpServerConfig : McpServerConfig /// public sealed class CustomAgentConfig { + /// + /// Declarative hook actions keyed by event name, active only for this agent. + /// + [JsonPropertyName("hooks")] + public IDictionary>>? Hooks { get; set; } + /// /// Unique name of the custom agent. /// @@ -3276,6 +3420,21 @@ public sealed class LargeToolOutputConfig public string? OutputDirectory { get; set; } } +/// Non-persisted opt-in to image generation. +/// Experimental. This API may change or be removed in a future release. +[Experimental(Diagnostics.Experimental)] +public sealed class ImageGenerationConfig +{ + /// + /// Opt in to an authorized Copilot image model. False disables image generation. + /// Omission disables on create/cold resume and preserves resident resume state. + /// Not persisted: re-supply after runtime restart. Policy, permissions, offline + /// mode, and tool filters still apply. BYOK image generation is not supported. + /// + [JsonPropertyName("enabled")] + public bool? Enabled { get; set; } +} + /// /// Overrides the runtime's built-in tool-search behavior. /// Defers tools to keep the model's active tool set small. @@ -3608,6 +3767,7 @@ protected SessionConfigBase(SessionConfigBase? other) EnableFileHooks = other.EnableFileHooks; EnableHostGitOperations = other.EnableHostGitOperations; EnableSessionStore = other.EnableSessionStore; + EnableGitHubCacheStore = other.EnableGitHubCacheStore; EnableSkills = other.EnableSkills; EnableMcpApps = other.EnableMcpApps; GitHubMcpToolConfig = other.GitHubMcpToolConfig is null @@ -3630,6 +3790,7 @@ protected SessionConfigBase(SessionConfigBase? other) InfiniteSessions = other.InfiniteSessions; LargeOutput = other.LargeOutput; ToolSearch = other.ToolSearch; + ImageGeneration = other.ImageGeneration; Memory = other.Memory; McpServers = other.McpServers is not null ? (other.McpServers is Dictionary dict @@ -3672,6 +3833,7 @@ protected SessionConfigBase(SessionConfigBase? other) ExpAssignments = other.ExpAssignments; EnableManagedSettings = other.EnableManagedSettings; EnforceManagedModelDefaults = other.EnforceManagedModelDefaults; + InteractiveUser = other.InteractiveUser; ManagedSettings = other.ManagedSettings; SkillProvider = other.SkillProvider; #pragma warning disable GHCP001 @@ -3684,6 +3846,8 @@ protected SessionConfigBase(SessionConfigBase? other) CanvasHandler = other.CanvasHandler; #pragma warning restore GHCP001 SkillDirectories = other.SkillDirectories is not null ? [.. other.SkillDirectories] : null; + AgentDirectories = other.AgentDirectories is not null ? [.. other.AgentDirectories] : null; + HookDirectories = other.HookDirectories is not null ? [.. other.HookDirectories] : null; PluginDirectories = other.PluginDirectories is not null ? [.. other.PluginDirectories] : null; InstructionDirectories = other.InstructionDirectories is not null ? [.. other.InstructionDirectories] : null; SessionLimits = other.SessionLimits; @@ -3788,9 +3952,9 @@ protected SessionConfigBase(SessionConfigBase? other) public bool? EnableOnDemandInstructionDiscovery { get; set; } /// - /// When , enables loading of file-based hooks from - /// .github/hooks/. This is separate from , which - /// controls SDK hook callback registration. + /// When , enables repository-origin file hooks from + /// .github/hooks/ and trusted repository-configured roots. + /// Does not control explicit or callback . /// public bool? EnableFileHooks { get; set; } @@ -3808,6 +3972,14 @@ protected SessionConfigBase(SessionConfigBase? other) /// public bool? EnableSessionStore { get; set; } + /// + /// When , requests the read-only GitHub cache store + /// (the github-cache-store capability). The runtime registers the + /// github_sql tool only when its GITHUB_CACHE_STORE feature flag + /// is also on. Defaults to disabled. + /// + public bool? EnableGitHubCacheStore { get; set; } + /// /// When , enables skill loading, including built-in /// skills and discovered skill directories. When , no @@ -4079,9 +4251,34 @@ protected SessionConfigBase(SessionConfigBase? other) /// public string? Agent { get; set; } - /// Directories to load skills from. + /// Explicit discovery-only skill roots, not general filesystem grants. + /// + /// Available without , subject to skill/managed disables. + /// Relative paths resolve once when supplied against the request's effective + /// ; retained roots do not move when it changes. + /// Null and omission are equivalent; an empty list clears only the explicit SDK contribution. + /// public IList? SkillDirectories { get; set; } + /// Explicit discovery-only agent roots, separate from . + /// + /// No general filesystem grant. Available without , + /// subject to source/managed disables. Relative paths resolve once when supplied against + /// the request's effective ; retained roots do not move. + /// Null and omission are equivalent; an empty list clears only the explicit SDK contribution. + /// + public IList? AgentDirectories { get; set; } + + /// Explicit discovery-only file-hook roots, separate from callback . + /// + /// No general filesystem grant. Available without or + /// (repository-origin hooks only), subject to hook/managed disables. + /// Relative paths resolve once when supplied against the request's effective + /// ; retained roots do not move when it changes. + /// Null and omission are equivalent; an empty list clears only the explicit SDK contribution. + /// + public IList? HookDirectories { get; set; } + /// /// Local filesystem paths to Open Plugins-format directories /// (https://open-plugins.com/) to load for this session. @@ -4093,7 +4290,13 @@ protected SessionConfigBase(SessionConfigBase? other) /// public IList? PluginDirectories { get; set; } - /// Additional directories to search for custom instruction files. + /// Explicit discovery-only instruction roots using existing SDK formats. + /// + /// No general filesystem grant. Available without , + /// subject to instruction/managed disables. Relative paths resolve once when supplied + /// against the request's effective ; retained roots do not move. + /// Null and omission are equivalent; an empty list clears only the explicit SDK contribution. + /// public IList? InstructionDirectories { get; set; } /// List of skill names to disable. @@ -4137,6 +4340,10 @@ protected SessionConfigBase(SessionConfigBase? other) /// public ToolSearchConfig? ToolSearch { get; set; } + /// Non-persisted image generation opt-in. Re-supply after runtime restart. + [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] + public ImageGenerationConfig? ImageGeneration { get; set; } + /// /// Configuration for session memory. When set, controls whether the /// session can read and write persistent memory. @@ -4222,6 +4429,15 @@ protected SessionConfigBase(SessionConfigBase? other) /// public bool? EnforceManagedModelDefaults { get; set; } + /// + /// Declares that a human drives this session's conversation, enabling + /// user-message analysis telemetry for user-authored messages. Automated + /// messages remain excluded. Defaults to false for SDK sessions; ignored + /// for cloud sessions and when resuming a session already active in the + /// runtime process. + /// + public bool? InteractiveUser { get; set; } + /// /// Optional managed-settings layer injected at session bootstrap. Currently /// carries a permissions object that composes restrictively with any @@ -4320,6 +4536,7 @@ private SessionConfig(SessionConfig? other) : base(other) if (other is null) return; SessionId = other.SessionId; + Name = other.Name; Cloud = other.Cloud; RefreshCustomInstructions = other.RefreshCustomInstructions; } @@ -4327,6 +4544,15 @@ private SessionConfig(SessionConfig? other) : base(other) /// Optional session identifier; a new ID is generated if not provided. public string? SessionId { get; set; } + /// + /// Names the session at creation, as session.name.set would, and is validated the + /// same way: non-empty, no leading or trailing whitespace, at most 100 UTF-16 units, no + /// control characters, and no double quotes. A name also makes the session persist from + /// the start, so it can be listed and resumed without ever running a turn. + /// Local sessions only: cloud creation rejects it. + /// + public string? Name { get; set; } + /// /// Creates a remote session in the cloud instead of a local session. /// The optional repository is associated with the cloud session. diff --git a/dotnet/test/E2E/ClientOptionsE2ETests.cs b/dotnet/test/E2E/ClientOptionsE2ETests.cs index d15dccc457..2c3888011d 100644 --- a/dotnet/test/E2E/ClientOptionsE2ETests.cs +++ b/dotnet/test/E2E/ClientOptionsE2ETests.cs @@ -278,6 +278,7 @@ public async Task Should_Forward_Granular_Multitenancy_Fields_In_Create_Wire_Req var session = await Ctx.CreateSessionAsync(client, new SessionConfig { + Name = "named at create", SkipEmbeddingRetrieval = false, OrganizationCustomInstructions = "Follow org policy.", EnableOnDemandInstructionDiscovery = true, @@ -291,6 +292,7 @@ public async Task Should_Forward_Granular_Multitenancy_Fields_In_Create_Wire_Req using var capture = JsonDocument.Parse(await File.ReadAllTextAsync(capturePath)); var createRequest = GetCapturedRequestParams(capture.RootElement, "session.create"); + Assert.Equal("named at create", createRequest.GetProperty("name").GetString()); Assert.False(createRequest.GetProperty("skipEmbeddingRetrieval").GetBoolean()); Assert.Equal("Follow org policy.", createRequest.GetProperty("organizationCustomInstructions").GetString()); Assert.True(createRequest.GetProperty("enableOnDemandInstructionDiscovery").GetBoolean()); @@ -537,6 +539,7 @@ public async Task Should_Apply_Empty_Mode_Defaults_To_CreateSession_Wire_Request Assert.False(createRequest.GetProperty("enableSkills").GetBoolean()); Assert.True(createRequest.GetProperty("customAgentsLocalOnly").GetBoolean()); Assert.False(createRequest.TryGetProperty("organizationCustomInstructions", out _)); + Assert.False(createRequest.TryGetProperty("name", out _)); await session.DisposeAsync(); } diff --git a/dotnet/test/E2E/PendingWorkResumeE2ETests.cs b/dotnet/test/E2E/PendingWorkResumeE2ETests.cs index b2e3a19d26..f6b539fa9d 100644 --- a/dotnet/test/E2E/PendingWorkResumeE2ETests.cs +++ b/dotnet/test/E2E/PendingWorkResumeE2ETests.cs @@ -55,7 +55,7 @@ await session1.SendAsync(new MessageOptions var permissionEvent = await permissionRequested; Assert.IsType(initialRequest); - await suspendedClient.ForceStopAsync(); + await ForceStopAfterSessionLockObservedAsync(suspendedClient, sessionId); await using var resumedTcpClient = Ctx.CreateClient(options: new CopilotClientOptions { Connection = RuntimeConnection.ForUri(cliUrl, connectionToken: SharedToken) }); var session2 = await Ctx.ResumeSessionAsync(resumedTcpClient, sessionId, new ResumeSessionConfig @@ -70,6 +70,8 @@ await session1.SendAsync(new MessageOptions "resume_permission_tool") ], }); + var resumeEvent = await GetSingleResumeEventAsync(session2); + Assert.False(resumeEvent.Data.SessionWasActive); var permissionResult = await session2.Rpc.Permissions.HandlePendingPermissionRequestAsync( permissionEvent.Data.RequestId, @@ -172,6 +174,34 @@ public Task Should_Keep_Pending_External_Tool_Handleable_On_Cold_Resume_When_Con expectedSessionWasActive: false, expectedHandleResult: false); + private async Task ForceStopAfterSessionLockObservedAsync(CopilotClient suspendedClient, string sessionId) + { + 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."); + } + private async Task AssertPendingExternalToolHandleableOnResumeAsync( bool disconnectOriginalClient, bool expectedSessionWasActive, @@ -213,30 +243,7 @@ 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 ForceStopAfterSessionLockObservedAsync(suspendedClient, sessionId); } await using var resumedClient = Ctx.CreateClient(options: new CopilotClientOptions { Connection = RuntimeConnection.ForUri(cliUrl, connectionToken: SharedToken) }); diff --git a/dotnet/test/E2E/PermissionE2ETests.cs b/dotnet/test/E2E/PermissionE2ETests.cs index 78e792c2c2..77b0e588ff 100644 --- a/dotnet/test/E2E/PermissionE2ETests.cs +++ b/dotnet/test/E2E/PermissionE2ETests.cs @@ -5,6 +5,7 @@ using GitHub.Copilot.Rpc; using GitHub.Copilot.Test.Harness; using Microsoft.Extensions.AI; +using Microsoft.Extensions.Logging; using System.Text.Json; using System.Text.Json.Serialization; using Xunit; @@ -173,23 +174,64 @@ internal static async Task AssertApproveAllPermissionHandlerAsync(CopilotSession [Fact] public async Task Should_Handle_Async_Permission_Handler() { + await Ctx.ConfigureForTestAsync("permissions", "Should_Handle_Async_Permission_Handler_Dotnet"); + + const string toolName = "async_permission_echo"; + const string expectedResult = "DOTNET_ASYNC_PERMISSION_RESULT"; var permissionRequestReceived = false; - var session = await CreateSessionAsync(new SessionConfig + var toolCalls = 0; + string? permissionToolCallId = null; + CopilotSession? session = null; + session = await CreateSessionAsync(new SessionConfig { + Tools = + [ + AIFunctionFactory.Create( + Echo, + toolName, + "Returns the result for the asynchronous permission test.", + serializerOptions: PermissionJsonContext.Default.Options), + ], + AvailableTools = [toolName], OnPermissionRequest = async (request, invocation) => { - permissionRequestReceived = true; + var customToolRequest = Assert.IsType(request); + Assert.Equal(toolName, customToolRequest.ToolName); + Assert.Equal(session!.SessionId, invocation.SessionId); + Assert.False(string.IsNullOrWhiteSpace(customToolRequest.ToolCallId)); + permissionToolCallId = customToolRequest.ToolCallId; await Task.Yield(); + permissionRequestReceived = true; return PermissionDecision.ApproveOnce(); } }); - await TestHelper.SendAndGetFinalAssistantMessageAsync(session, new MessageOptions + ToolExecutionCompleteEvent? toolCompleted = null; + using var subscription = session.On(evt => + { + if (evt.Data.ToolCallId == permissionToolCallId) + { + toolCompleted = evt; + } + }); + var message = await TestHelper.SendAndGetFinalAssistantMessageAsync(session, new MessageOptions { - Prompt = "Run 'echo test' and tell me what happens" + Prompt = "Call async_permission_echo once and reply with only its returned text." }); Assert.True(permissionRequestReceived, "Permission request should have been received"); + Assert.Equal(1, toolCalls); + Assert.NotNull(toolCompleted); + Assert.True(toolCompleted.Data.Success); + Assert.Equal(expectedResult, toolCompleted.Data.Result?.Content); + Assert.Contains(expectedResult, message.Data.Content); + + string Echo() + { + Assert.True(permissionRequestReceived, "The yielding permission handler must finish before the tool runs."); + Interlocked.Increment(ref toolCalls); + return expectedResult; + } } [Fact] @@ -330,6 +372,7 @@ public async Task Should_Receive_ToolCallId_In_Permission_Requests() [Fact] public async Task Should_Wait_For_Slow_Permission_Handler() + => await WithSdkDiagnosticsAsync(async () => { var handlerEntered = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); var releaseHandler = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); @@ -388,7 +431,21 @@ void AddLifecycleEvent(string phase, string? toolCallId) releaseHandler.SetResult(); - var message = await sendTask; + AssistantMessageEvent? message; + try + { + message = await sendTask; + } + catch (TimeoutException) + { + lock (lifecycleLock) + { + Logger.LogWarning( + "Slow-permission timeout lifecycle: {Lifecycle}", + string.Join(", ", lifecycle.Select(evt => $"{evt.Phase}:{evt.ToolCallId}"))); + } + throw; + } var persistedEvents = await WaitForPersistedEventsAsync( session, events => @@ -424,7 +481,7 @@ void AddLifecycleEvent(string phase, string? toolCallId) // The tool should have actually run after permission was granted Assert.Contains("slow_handler_test", message?.Data.Content ?? string.Empty); - } + }); [Fact] public async Task Should_Handle_Concurrent_Permission_Requests_From_Parallel_Tools() diff --git a/dotnet/test/E2E/SessionE2ETests.cs b/dotnet/test/E2E/SessionE2ETests.cs index 377bb0147d..c3b9e7bdd9 100644 --- a/dotnet/test/E2E/SessionE2ETests.cs +++ b/dotnet/test/E2E/SessionE2ETests.cs @@ -478,14 +478,20 @@ public async Task Should_Receive_Session_Events() public async Task Send_Returns_Immediately_While_Events_Stream_In_Background() => await WithSdkDiagnosticsAsync(async () => { - var session = await CreateSessionAsync(new SessionConfig + // Reuse a custom-tool capture instead of depending on a native shell completing within its wait. + await Ctx.ConfigureForTestAsync( + "pending_work_resume", "should_continue_pending_external_tool_request_after_resume", replayOnly: true); + var toolStarted = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var releaseTool = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + await using var session = await CreateSessionAsync(new SessionConfig { + Tools = [AIFunctionFactory.Create(BlockingTool, "resume_external_tool")], OnPermissionRequest = PermissionHandler.ApproveAll, }); var events = new ConcurrentQueue(); AssistantMessageEvent? message = null; - session.On(evt => + using var subscription = session.On(evt => { events.Enqueue(evt.Type); if (evt is AssistantMessageEvent assistantMessage) @@ -495,18 +501,35 @@ public async Task Send_Returns_Immediately_While_Events_Stream_In_Background() }); var idle = TestHelper.GetNextEventOfTypeAsync(session); - // Use a slow command so we can verify SendAsync() returns before completion - await session.SendAsync(new MessageOptions { Prompt = "Run 'sleep 2 && echo done'" }); - - // SendAsync() should return before turn completes (no session.idle yet) - Assert.DoesNotContain("session.idle", events); - - // Wait for turn to complete - await idle; + try + { + var send = session.SendAsync(new MessageOptions + { + Prompt = "Use resume_external_tool with value 'beta', then reply with the result.", + }); + Assert.Equal("beta", await toolStarted.Task.WaitAsync(TimeSpan.FromSeconds(60))); + await send.WaitAsync(TimeSpan.FromSeconds(60)); + Assert.DoesNotContain("session.idle", events); + Assert.False(idle.IsCompleted); + + releaseTool.TrySetResult("EXTERNAL_RESUMED_BETA"); + await idle; + + Assert.Contains("EXTERNAL_RESUMED_BETA", message?.Data.Content ?? string.Empty); + Assert.Contains("session.idle", events); + Assert.Contains("assistant.message", events); + } + finally + { + releaseTool.TrySetResult("EXTERNAL_RESUMED_BETA"); + } - Assert.Contains("done", message?.Data.Content ?? string.Empty); - Assert.Contains("session.idle", events); - Assert.Contains("assistant.message", events); + [Description("Blocks until the test releases the active turn")] + async Task BlockingTool([Description("Value to return")] string value, CancellationToken cancellationToken) + { + toolStarted.TrySetResult(value); + return await releaseTool.Task.WaitAsync(Timeout.InfiniteTimeSpan, cancellationToken); + } }); [Fact] diff --git a/dotnet/test/E2E/SubagentHooksE2ETests.cs b/dotnet/test/E2E/SubagentHooksE2ETests.cs index fdbb779639..84f61e9273 100644 --- a/dotnet/test/E2E/SubagentHooksE2ETests.cs +++ b/dotnet/test/E2E/SubagentHooksE2ETests.cs @@ -167,28 +167,38 @@ public async Task Should_Apply_Subagent_Lifecycle_Hook_Outputs() { if (string.IsNullOrEmpty(r.ParentAgentId)) return false; using var body = JsonDocument.Parse(r.Body); - if (!body.RootElement.TryGetProperty("messages", out var messages) - && !body.RootElement.TryGetProperty("input", out messages)) - return false; - return messages.ValueKind == JsonValueKind.Array && messages.EnumerateArray().Any(message => - message.ValueKind == JsonValueKind.Object - && message.TryGetProperty("role", out var role) - && role.ValueKind == JsonValueKind.String - && role.GetString() == "user" - && message.TryGetProperty("content", out var content) - && (content.ValueKind == JsonValueKind.String - ? content.GetString()!.Contains(ChildContext + "\n\nRead the file \"subagent-test.txt\"", StringComparison.Ordinal) - : content.ValueKind == JsonValueKind.Array && content.EnumerateArray().Any(part => - part.ValueKind == JsonValueKind.Object - && part.TryGetProperty("text", out var text) - && text.ValueKind == JsonValueKind.String - && text.GetString()!.Contains(ChildContext + "\n\nRead the file \"subagent-test.txt\"", StringComparison.Ordinal)))); + return HasChildContext(body.RootElement); }); Assert.Contains(requestHandler.InferenceRequests, r => string.IsNullOrEmpty(r.ParentAgentId) && r.Body.Contains(StopResponsePrefix, StringComparison.Ordinal)); } + internal static bool HasChildContext(JsonElement body) + { + const string expected = ChildContext + "\n\nRead the file \"subagent-test.txt\""; + if (body.TryGetProperty("input", out var input) && input.ValueKind == JsonValueKind.String) + return input.GetString()!.Contains(expected, StringComparison.Ordinal); + if (!body.TryGetProperty("messages", out var messages) || messages.ValueKind == JsonValueKind.Null) + messages = input; + return messages.ValueKind == JsonValueKind.Array && messages.EnumerateArray().Any(message => + message.ValueKind == JsonValueKind.Object + && message.TryGetProperty("role", out var role) + && role.ValueKind == JsonValueKind.String + && role.GetString() == "user" + && message.TryGetProperty("content", out var content) + && (content.ValueKind == JsonValueKind.String + ? content.GetString()!.Contains(expected, StringComparison.Ordinal) + : content.ValueKind == JsonValueKind.Array && content.EnumerateArray().Any(part => + part.ValueKind == JsonValueKind.Object + && part.TryGetProperty("type", out var type) + && type.ValueKind == JsonValueKind.String + && (type.GetString() == "text" || type.GetString() == "input_text") + && part.TryGetProperty("text", out var text) + && text.ValueKind == JsonValueKind.String + && text.GetString()!.Contains(expected, StringComparison.Ordinal)))); + } + private static void AssertSubagentRequestMetadata(IReadOnlyCollection records) { Assert.NotEmpty(records); @@ -230,3 +240,33 @@ private sealed record RequestRecord( string? InteractionType, string Body); } + +public class SubagentRequestObserverTests +{ + [Fact] + public void Recognizes_User_Context_Across_Request_Formats() + { + var encoded = "\"" + JsonEncodedText.Encode("Subagent start hook verified: read the requested file.\n\nRead the file \"subagent-test.txt\"") + "\""; + var user = "{\"role\":\"user\",\"content\":" + encoded + "}"; + foreach (var json in new[] + { + "{\"messages\":[" + user + "]}", + "{\"input\":" + encoded + "}", + "{\"input\":[" + user + "]}", + "{\"input\":[{\"role\":\"user\",\"content\":[{\"type\":\"text\",\"text\":" + encoded + "}]}]}", + "{\"input\":[{\"role\":\"user\",\"content\":[{\"type\":\"input_text\",\"text\":" + encoded + "}]}]}", + }) + { + using var body = JsonDocument.Parse(json); + Assert.True(SubagentHooksE2ETests.HasChildContext(body.RootElement), json); + } + using var ignored = JsonDocument.Parse("{\"input\":[" + + "{\"role\":\"assistant\",\"content\":" + encoded + "}," + + "{\"type\":\"function_call_output\",\"output\":" + encoded + "}," + + "{\"role\":\"user\",\"content\":[null,\"ignored\"," + + "{\"type\":\"input_text\",\"text\":42},{\"type\":\"input_image\",\"text\":" + encoded + "}]}]}"); + Assert.False(SubagentHooksE2ETests.HasChildContext(ignored.RootElement)); + using var empty = JsonDocument.Parse("{}"); + Assert.False(SubagentHooksE2ETests.HasChildContext(empty.RootElement)); + } +} diff --git a/dotnet/test/Harness/E2ETestBase.cs b/dotnet/test/Harness/E2ETestBase.cs index 679fbb9c6b..008bbc8074 100644 --- a/dotnet/test/Harness/E2ETestBase.cs +++ b/dotnet/test/Harness/E2ETestBase.cs @@ -39,7 +39,7 @@ protected E2ETestBase( // Wire logger into the shared context so all clients created via Ctx.CreateClient get it. Ctx.Logger = Logger; - Ctx.DiagnosticsEnabled = snapshotCategory is "pending_work_resume" or "session"; + Ctx.DiagnosticsEnabled = snapshotCategory is "pending_work_resume" or "session" or "permissions"; } /// Logger that forwards warnings and above to xunit test output. diff --git a/dotnet/test/Harness/E2ETestContext.cs b/dotnet/test/Harness/E2ETestContext.cs index a5b76fbcd2..f35fe7a659 100644 --- a/dotnet/test/Harness/E2ETestContext.cs +++ b/dotnet/test/Harness/E2ETestContext.cs @@ -85,7 +85,7 @@ await proxy.ConfigureAsync( await proxy.SetCopilotUserByTokenAsync(DefaultGitHubToken, new CopilotUserConfig( Login: "e2e-test-user", CopilotPlan: "individual_pro", - Endpoints: new CopilotUserEndpoints(Api: context.ProxyUrl, Telemetry: "https://localhost:1/telemetry"), + Endpoints: new CopilotUserEndpoints(Api: context.ProxyUrl, Telemetry: context.ProxyUrl), AnalyticsTrackingId: "e2e-test-tracking-id", Id: 12345)); return context; diff --git a/dotnet/test/Harness/ReplayProxy.cs b/dotnet/test/Harness/ReplayProxy.cs index 140642a2f7..6891aeae71 100644 --- a/dotnet/test/Harness/ReplayProxy.cs +++ b/dotnet/test/Harness/ReplayProxy.cs @@ -32,7 +32,7 @@ async Task StartCoreAsync() { FileName = "node", WorkingDirectory = Path.Join(FindRepoRoot(), "test", "harness"), - Arguments = "--import tsx server.ts", + Arguments = "startup.mjs", UseShellExecute = false, RedirectStandardOutput = true, RedirectStandardError = true, @@ -89,7 +89,10 @@ async Task StartCoreAsync() { if (e.Data == null) return; diagnostics.AppendError(e.Data); - Console.Error.WriteLine(e.Data); + if (!e.Data.StartsWith("[SDK proxy startup]", StringComparison.Ordinal)) + { + Console.Error.WriteLine(e.Data); + } }; try diff --git a/dotnet/test/Unit/ClientSessionLifetimeTests.CustomizationDirectories.cs b/dotnet/test/Unit/ClientSessionLifetimeTests.CustomizationDirectories.cs new file mode 100644 index 0000000000..7e7aee511f --- /dev/null +++ b/dotnet/test/Unit/ClientSessionLifetimeTests.CustomizationDirectories.cs @@ -0,0 +1,157 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. + +#if NET8_0_OR_GREATER +using GitHub.Copilot.Rpc; +using Xunit; + +namespace GitHub.Copilot.Test.Unit; + +public sealed partial class ClientSessionLifetimeTests +{ + [Fact] + public async Task CustomizationDirectories_Preserve_Positional_Discovery_Calls() + { + await using var server = await FakeCopilotServer.StartAsync(); + server.ResponseFactory = _ => new Dictionary + { + ["agents"] = Array.Empty(), + ["hooks"] = Array.Empty(), + ["sources"] = Array.Empty(), + ["paths"] = Array.Empty() + }; + await using var client = new CopilotClient(new CopilotClientOptions + { + Connection = RuntimeConnection.ForUri(server.Url) + }); + IList projects = ["project-root"]; + await client.StartAsync(); + await client.Rpc.Agents.DiscoverAsync(projects, true, CancellationToken.None); + await client.Rpc.Agents.GetDiscoveryPathsAsync(projects, true, CancellationToken.None); + await client.Rpc.Hooks.DiscoverAsync(projects, true, CancellationToken.None); + await client.Rpc.Instructions.DiscoverAsync(projects, true, CancellationToken.None); + await client.Rpc.Instructions.GetDiscoveryPathsAsync(projects, true, CancellationToken.None); + await client.Rpc.Skills.GetDiscoveryPathsAsync(projects, ["ignored"], true, CancellationToken.None); + foreach (var method in new[] + { + "agents.discover", "agents.getDiscoveryPaths", "hooks.discover", + "instructions.discover", "instructions.getDiscoveryPaths", "skills.getDiscoveryPaths" + }) + { + var payload = Assert.Single(server.Requests, request => request.Method == method).Params; + Assert.Equal("project-root", Assert.Single(payload.GetProperty("projectPaths").EnumerateArray()).GetString()); + foreach (var key in new[] { "agentDirectories", "hookDirectories", "instructionDirectories", "skillDirectories" }) + { + Assert.False(payload.TryGetProperty(key, out _)); + } + } + await client.Rpc.Agents.DiscoverAsync(new AgentsDiscoverRequest + { + AgentDirectories = ["explicit-agent-root"] + }); + var explicitPayload = server.Requests.Last(request => request.Method == "agents.discover").Params; + Assert.Equal("explicit-agent-root", Assert.Single(explicitPayload.GetProperty("agentDirectories").EnumerateArray()).GetString()); + Assert.False(explicitPayload.TryGetProperty("projectPaths", out _)); + } + + [Theory] + [InlineData(0)] + [InlineData(1)] + [InlineData(2)] + public async Task CustomizationDirectories_Are_Preserved_On_Create_And_Resume(int kind) + { + IList? roots = kind switch + { + 0 => null, + 1 => [], + _ => ["relative-root", "other-root"] + }; + var directories = new Dictionary?> + { + ["skillDirectories"] = roots?.Select(root => root + "-skills").ToList(), + ["agentDirectories"] = roots?.Select(root => root + "-agents").ToList(), + ["instructionDirectories"] = roots?.Select(root => root + "-instructions").ToList(), + ["hookDirectories"] = roots?.Select(root => root + "-hooks").ToList() + }; + 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 + { + WorkingDirectory = "request-cwd", + EnableConfigDiscovery = false, + EnableFileHooks = false, + SkillDirectories = directories["skillDirectories"], + AgentDirectories = directories["agentDirectories"], + InstructionDirectories = directories["instructionDirectories"], + HookDirectories = directories["hookDirectories"] + }); + await using var resumed = await client.ResumeSessionAsync("resumed", new ResumeSessionConfig + { + WorkingDirectory = "resume-cwd", + EnableConfigDiscovery = false, + EnableFileHooks = false, + SkillDirectories = directories["skillDirectories"], + AgentDirectories = directories["agentDirectories"], + InstructionDirectories = directories["instructionDirectories"], + HookDirectories = directories["hookDirectories"] + }); + foreach (var method in new[] { "session.create", "session.resume" }) + { + var payload = Assert.Single(server.Requests, request => request.Method == method).Params; + foreach (var entry in directories) + { + if (entry.Value is null) + { + Assert.False(payload.TryGetProperty(entry.Key, out _)); + } + else + { + Assert.Equal(entry.Value, payload.GetProperty(entry.Key).EnumerateArray().Select(item => item.GetString())); + } + } + Assert.False(payload.GetProperty("enableConfigDiscovery").GetBoolean()); + Assert.False(payload.GetProperty("enableFileHooks").GetBoolean()); + Assert.False(payload.TryGetProperty("additionalDirectories", out _)); + Assert.False(payload.TryGetProperty("customAgents", out _)); + Assert.False(payload.TryGetProperty("hooks", out _)); + } + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void CustomizationDirectories_Clone_Owns_Its_Lists(bool resume) + { + SessionConfigBase original; + SessionConfigBase clone; + if (resume) + { + var config = new ResumeSessionConfig + { + AgentDirectories = ["agents"], + HookDirectories = ["hooks"] + }; + original = config; + clone = config.Clone(); + } + else + { + var config = new SessionConfig + { + AgentDirectories = ["agents"], + HookDirectories = ["hooks"] + }; + original = config; + clone = config.Clone(); + } + clone.AgentDirectories!.Add("clone-agents"); + clone.HookDirectories!.Add("clone-hooks"); + Assert.Equal("agents", Assert.Single(original.AgentDirectories!)); + Assert.Equal("hooks", Assert.Single(original.HookDirectories!)); + Assert.Collection(clone.AgentDirectories, item => Assert.Equal("agents", item), item => Assert.Equal("clone-agents", item)); + Assert.Collection(clone.HookDirectories, item => Assert.Equal("hooks", item), item => Assert.Equal("clone-hooks", item)); + } +} +#endif diff --git a/dotnet/test/Unit/ClientSessionLifetimeTests.GitHubCacheStore.cs b/dotnet/test/Unit/ClientSessionLifetimeTests.GitHubCacheStore.cs new file mode 100644 index 0000000000..95c8d4f977 --- /dev/null +++ b/dotnet/test/Unit/ClientSessionLifetimeTests.GitHubCacheStore.cs @@ -0,0 +1,43 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. + +#if NET8_0_OR_GREATER +using Xunit; + +namespace GitHub.Copilot.Test.Unit; + +public sealed partial class ClientSessionLifetimeTests +{ + [Theory] + [InlineData(null)] + [InlineData(false)] + [InlineData(true)] + public async Task EnableGitHubCacheStore_Is_Forwarded_On_Create_And_Resume(bool? enabled) + { + 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 + { + EnableGitHubCacheStore = enabled + }); + await using var resumed = await client.ResumeSessionAsync("resumed", new ResumeSessionConfig + { + EnableGitHubCacheStore = enabled + }); + foreach (var method in new[] { "session.create", "session.resume" }) + { + var payload = Assert.Single(server.Requests, request => request.Method == method).Params; + if (enabled is null) + { + Assert.False(payload.TryGetProperty("enableGitHubCacheStore", out _)); + } + else + { + Assert.Equal(enabled.Value, payload.GetProperty("enableGitHubCacheStore").GetBoolean()); + } + } + } +} +#endif diff --git a/dotnet/test/Unit/ClientSessionLifetimeTests.cs b/dotnet/test/Unit/ClientSessionLifetimeTests.cs index 5beb89ae05..98d2453186 100644 --- a/dotnet/test/Unit/ClientSessionLifetimeTests.cs +++ b/dotnet/test/Unit/ClientSessionLifetimeTests.cs @@ -24,6 +24,111 @@ public sealed partial class ClientSessionLifetimeTests { private sealed record RpcRequestRecord(string Method, JsonElement Params); + [Fact] + public async Task TypedHookDispatch_RetainsChildIdentityAndOldRootInputs() + { + await using var server = await FakeCopilotServer.StartAsync(); + await using var client = new CopilotClient(new CopilotClientOptions { Connection = RuntimeConnection.ForUri(server.Url) }); + var captured = new List(); + Task Capture(object input, HookInvocation invocation) where T : class + { + captured.Add(input); + Assert.False(string.IsNullOrEmpty(invocation.SessionId)); + return Task.FromResult(null); + } + var hooks = new SessionHooks + { + OnPreToolUse = (i, c) => Capture(i, c), + OnPreMcpToolCall = (i, c) => Capture(i, c), + OnPostToolUse = (i, c) => Capture(i, c), + OnPostToolUseFailure = (i, c) => Capture(i, c), + OnUserPromptSubmitted = (i, c) => Capture(i, c), + OnUserPromptTransformed = (i, c) => Capture(i, c), + OnSessionStart = (i, c) => Capture(i, c), + OnSessionEnd = (i, c) => Capture(i, c), + OnErrorOccurred = (i, c) => Capture(i, c), + OnAgentStop = (i, c) => Capture(i, c), + OnSubagentStart = (i, c) => Capture(i, c), + OnSubagentStop = (i, c) => Capture(i, c) + }; + await using var session = await client.CreateSessionAsync(new SessionConfig { Hooks = hooks }); + foreach (var hook in new[] { "preToolUse", "preMcpToolCall", "postToolUse", "postToolUseFailure", + "userPromptSubmitted", "userPromptTransformed", "sessionStart", "sessionEnd", "errorOccurred", + "agentStop", "subagentStart", "subagentStop" }) + { + foreach (var enriched in new[] { true, false }) + { + var input = new Dictionary + { + ["sessionId"] = "reported-session", + ["timestamp"] = 1L, + ["cwd"] = "work" + }; + if (enriched) + { + input["agentId"] = "child-actor"; + input["agentType"] = "explore"; + input["parentAgentId"] = "parent-actor"; + } + captured.Clear(); + await server.SendRequestAsync("hooks.invoke", new Dictionary + { + ["sessionId"] = session.SessionId, + ["hookType"] = hook, + ["input"] = input + }).WaitAsync(TimeSpan.FromSeconds(5)); + var actual = Assert.Single(captured); + Assert.Equal("reported-session", actual.GetType().GetProperty("SessionId")!.GetValue(actual)); + foreach (var field in new Dictionary + { + ["AgentId"] = "child-actor", + ["AgentType"] = "explore", + ["ParentAgentId"] = "parent-actor" + }) + { + var property = actual.GetType().GetProperty(field.Key); + Assert.NotNull(property); + var value = property.GetValue(actual) as string; + Assert.Equal(enriched ? field.Value : "", value ?? ""); + } + } + } + } + + [Theory] + [InlineData(false, true)] + [InlineData(false, false)] + [InlineData(false, null)] + [InlineData(true, true)] + [InlineData(true, false)] + [InlineData(true, null)] + public async Task ImageGeneration_ForwardsCreateAndResumeOptIn(bool resume, bool? enabled) + { + await using var server = await FakeCopilotServer.StartAsync(); + await using var client = new CopilotClient(new CopilotClientOptions { Connection = RuntimeConnection.ForUri(server.Url) }); + var imageGeneration = enabled is null ? null : new ImageGenerationConfig { Enabled = enabled }; + await using var session = resume + ? await client.ResumeSessionAsync("image-test", new ResumeSessionConfig + { + OnPermissionRequest = PermissionHandler.ApproveAll, + ImageGeneration = imageGeneration + }.Clone()) + : await client.CreateSessionAsync(new SessionConfig + { + OnPermissionRequest = PermissionHandler.ApproveAll, + ImageGeneration = imageGeneration + }.Clone()); + var request = Assert.Single(server.Requests, r => r.Method == (resume ? "session.resume" : "session.create")).Params; + if (enabled is null) + { + Assert.False(request.TryGetProperty("imageGeneration", out _), request.ToString()); + } + else + { + Assert.Equal(enabled.Value, request.GetProperty("imageGeneration").GetProperty("enabled").GetBoolean()); + } + } + [Theory] [InlineData(CopilotClientMode.CopilotCli, null, null)] [InlineData(CopilotClientMode.Empty, null, null)] @@ -3296,6 +3401,7 @@ public async Task CreateSessionAsync_Serializes_ManagedSettings_Permissions() await using var session = await client.CreateSessionAsync(new SessionConfig { EnforceManagedModelDefaults = true, + InteractiveUser = true, ManagedSettings = new ManagedSettings { Permissions = new ManagedSettingsPermissions @@ -3318,6 +3424,7 @@ public async Task CreateSessionAsync_Serializes_ManagedSettings_Permissions() var request = Assert.Single(server.Requests, request => request.Method == "session.create"); Assert.True(request.Params.GetProperty("enforceManagedModelDefaults").GetBoolean()); Assert.False(request.Params.TryGetProperty("enableManagedSettings", out _)); + Assert.True(request.Params.GetProperty("interactiveUser").GetBoolean()); var permissions = request.Params.GetProperty("managedSettings").GetProperty("permissions"); Assert.Equal("disable", permissions.GetProperty("disableBypassPermissionsMode").GetString()); Assert.True(permissions.GetProperty("disableAssistedPermissionsMode").GetBoolean()); @@ -3582,6 +3689,7 @@ public async Task CreateSessionAsync_Omits_ManagedSettings_When_Unset() var request = Assert.Single(server.Requests, request => request.Method == "session.create"); Assert.False(request.Params.TryGetProperty("managedSettings", out _)); + Assert.False(request.Params.TryGetProperty("interactiveUser", out _)); } [Fact] @@ -3593,6 +3701,7 @@ public async Task ResumeSessionAsync_Serializes_ManagedSettings_Permissions() await using var session = await client.ResumeSessionAsync("session-managed", new ResumeSessionConfig { EnforceManagedModelDefaults = true, + InteractiveUser = true, ManagedSettings = new ManagedSettings { Permissions = new ManagedSettingsPermissions @@ -3606,10 +3715,53 @@ public async Task ResumeSessionAsync_Serializes_ManagedSettings_Permissions() var request = Assert.Single(server.Requests, request => request.Method == "session.resume"); Assert.True(request.Params.GetProperty("enforceManagedModelDefaults").GetBoolean()); + Assert.True(request.Params.GetProperty("interactiveUser").GetBoolean()); var permissions = request.Params.GetProperty("managedSettings").GetProperty("permissions"); Assert.Equal("shell(rm*)", Assert.Single(permissions.GetProperty("deny").EnumerateArray()).GetString()); } + [Fact] + public async Task ResumeSessionAsync_Omits_InteractiveUser_When_Unset() + { + 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-no-interactive", new ResumeSessionConfig + { + OnPermissionRequest = PermissionHandler.ApproveAll, + OnEvent = _ => { } + }); + + var request = Assert.Single(server.Requests, request => request.Method == "session.resume"); + Assert.False(request.Params.TryGetProperty("interactiveUser", out _)); + } + + [Fact] + public async Task CreateAndResumeSessionAsync_Serialize_Explicit_InteractiveUser_False() + { + 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 created = await client.CreateSessionAsync(new SessionConfig + { + InteractiveUser = false, + OnPermissionRequest = PermissionHandler.ApproveAll + }); + await using var resumed = await client.ResumeSessionAsync("session-declined-interactive", new ResumeSessionConfig + { + InteractiveUser = false, + OnPermissionRequest = PermissionHandler.ApproveAll, + OnEvent = _ => { } + }); + + foreach (var method in new[] { "session.create", "session.resume" }) + { + var request = Assert.Single(server.Requests, request => request.Method == method); + Assert.Equal(JsonValueKind.False, request.Params.GetProperty("interactiveUser").ValueKind); + } + } + [Theory] [InlineData(true)] [InlineData(false)] diff --git a/dotnet/test/Unit/CloneTests.cs b/dotnet/test/Unit/CloneTests.cs index 3ae4c0b009..b473fe7dab 100644 --- a/dotnet/test/Unit/CloneTests.cs +++ b/dotnet/test/Unit/CloneTests.cs @@ -665,6 +665,32 @@ public void ResumeSessionConfig_Clone_PreservesEnableOnDemandInstructionDiscover Assert.Null(clone.EnableOnDemandInstructionDiscovery); } + [Fact] + public void SessionConfig_Clone_CopiesEnableGitHubCacheStore() + { + var original = new SessionConfig + { + EnableGitHubCacheStore = true, + }; + + var clone = original.Clone(); + + Assert.True(clone.EnableGitHubCacheStore); + } + + [Fact] + public void ResumeSessionConfig_Clone_CopiesEnableGitHubCacheStore() + { + var original = new ResumeSessionConfig + { + EnableGitHubCacheStore = false, + }; + + var clone = original.Clone(); + + Assert.False(clone.EnableGitHubCacheStore); + } + [Fact] public void SessionConfig_Clone_CopiesMcpOAuthTokenStorage() { diff --git a/dotnet/test/Unit/E2ETestFixtureTests.cs b/dotnet/test/Unit/E2ETestFixtureTests.cs index 933b82c1fc..afbb87fd6f 100644 --- a/dotnet/test/Unit/E2ETestFixtureTests.cs +++ b/dotnet/test/Unit/E2ETestFixtureTests.cs @@ -4,7 +4,11 @@ using System.ComponentModel; using System.Diagnostics; +using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; using System.Reflection; +using System.Text.Json; using GitHub.Copilot.Test.E2E; using GitHub.Copilot.Test.Harness; using Xunit; @@ -13,6 +17,25 @@ namespace GitHub.Copilot.Test.Unit; public class E2ETestFixtureTests { + [Fact] + public async Task Default_Telemetry_Endpoint_Uses_The_Owned_Proxy() + { + await using var context = await E2ETestContext.CreateAsync(); + using var client = new HttpClient(); + using var userRequest = new HttpRequestMessage(HttpMethod.Get, context.ProxyUrl + "/copilot_internal/user"); + userRequest.Headers.Authorization = new AuthenticationHeaderValue("Bearer", context.GetEnvironment()["GH_TOKEN"]); + using var userResponse = await client.SendAsync(userRequest); + Assert.Equal(HttpStatusCode.OK, userResponse.StatusCode); + using var user = JsonDocument.Parse(await userResponse.Content.ReadAsStringAsync()); + var telemetryEndpoint = user.RootElement.GetProperty("endpoints").GetProperty("telemetry").GetString(); + Assert.Equal(context.ProxyUrl, telemetryEndpoint); + using var content = new StringContent("{}\n"); + using var telemetryResponse = await client.PostAsync(telemetryEndpoint + "/telemetry", content); + Assert.Equal(HttpStatusCode.NoContent, telemetryResponse.StatusCode); + Assert.Equal(string.Empty, await telemetryResponse.Content.ReadAsStringAsync()); + Assert.Empty(await context.GetExchangesAsync()); + } + [Fact] public async Task Failed_Process_Launch_Reclaims_Context_Directories() { diff --git a/dotnet/test/Unit/InProcessRuntimeArgumentsTests.cs b/dotnet/test/Unit/InProcessRuntimeArgumentsTests.cs new file mode 100644 index 0000000000..1dc5e4d93e --- /dev/null +++ b/dotnet/test/Unit/InProcessRuntimeArgumentsTests.cs @@ -0,0 +1,27 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +using System.Reflection; +using GitHub.Copilot.Test.Harness; +using Xunit; + +namespace GitHub.Copilot.Test.Unit; + +public sealed class InProcessRuntimeArgumentsTests +{ + [Fact] + public void Integration_Id_Is_Forwarded_To_The_Native_Host() + { + InProcessEnvIsolation.Apply("GITHUB_COPILOT_INTEGRATION_ID", " microsoft/vscode "); + var method = typeof(CopilotClient).GetMethod( + "BuildInProcessRuntimeArguments", + BindingFlags.Static | BindingFlags.NonPublic); + Assert.NotNull(method); + + var args = Assert.IsType>( + method!.Invoke(null, [new CopilotClientOptions()])); + + Assert.Contains("--cli-login-sync-integration-id=microsoft/vscode", args); + } +} diff --git a/dotnet/test/Unit/RuntimeWrapperTests.cs b/dotnet/test/Unit/RuntimeWrapperTests.cs index e7c77ab92d..47bcba2ab6 100644 --- a/dotnet/test/Unit/RuntimeWrapperTests.cs +++ b/dotnet/test/Unit/RuntimeWrapperTests.cs @@ -93,7 +93,8 @@ public async Task Marked_Bundled_Explicit_Cli_Does_Not_Require_Runtime_Pair() 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"); + // Nonempty invalid images can stall Windows process creation before it reports the launch error. + await File.WriteAllTextAsync(cliPath, string.Empty); await File.WriteAllTextAsync(Path.Combine(nativeDirectory, ".copilot-explicit-cli"), "explicit"); try diff --git a/dotnet/test/Unit/SerializationTests.cs b/dotnet/test/Unit/SerializationTests.cs index ef41cfa4aa..085e6dfd2c 100644 --- a/dotnet/test/Unit/SerializationTests.cs +++ b/dotnet/test/Unit/SerializationTests.cs @@ -1105,6 +1105,42 @@ public void AgentStopHookInput_DeserializesWireFields_WithSdkOptions() Assert.Equal(DateTimeOffset.FromUnixTimeMilliseconds(1700000000000), input.Timestamp); } + [Theory] + [InlineData("""{"name":"Error","message":"model timeout","stack":"Error: model timeout"}""", "model timeout")] + [InlineData("\"boom\"", "boom")] + [InlineData("""{"code":7}""", """{"code":7}""")] + public void ErrorOccurredHookInput_DecodesCliErrorShapes(string error, string expected) + { + var options = GetSerializerOptions(); + var input = JsonSerializer.Deserialize( + $$"""{"sessionId":"session-1","timestamp":1700000000000,"cwd":"/repo","error":{{error}},"errorContext":"model_call","recoverable":true}""", + options); + + Assert.NotNull(input); + Assert.Equal(expected, input.Error); + Assert.Equal("model_call", input.ErrorContext); + Assert.True(input.Recoverable); + } + + [Theory] + [InlineData("""{"message":"model timeout"}""", "model timeout")] + [InlineData("\"boom\"", "boom")] + [InlineData("null", null)] + [InlineData(null, null)] + [InlineData("""{"code":7}""", """{"code":7}""")] + public void SessionEndHookInput_DecodesCliErrorShapes(string? error, string? expected) + { + var options = GetSerializerOptions(); + var errorField = error is null ? string.Empty : $",\"error\":{error}"; + var input = JsonSerializer.Deserialize( + $$"""{"sessionId":"session-1","timestamp":1700000000000,"cwd":"/repo","reason":"error"{{errorField}}}""", + options); + + Assert.NotNull(input); + Assert.Equal(expected, input.Error); + Assert.Equal("error", input.Reason); + } + [Fact] public void AgentStopHookOutput_SerializesBlockDecision_WithSdkOptions() { diff --git a/go/README.md b/go/README.md index fe297f069b..7e9d78b71f 100644 --- a/go/README.md +++ b/go/README.md @@ -115,6 +115,13 @@ tool name is `-`. For `AvailableTools` and `mcp:-`. For `CustomAgents[].Tools` and `DefaultAgent.ExcludedTools`, use `-` directly. +## Untyped JSON numbers + +Only `ContentExclusionValidatePoliciesResult` decodes untyped JSON numbers as `json.Number` to preserve policy values without precision loss. +Existing RPC results, requests, callbacks, and session events keep their existing numeric representation, including `float64` for untyped numbers. +Typed numeric fields keep their declared Go types. +Use `json.Number.Int64()` or `json.Number.Float64()` when you need a numeric conversion, and handle the returned error. + ## JSON-RPC errors Use `errors.As` to inspect a runtime error without parsing its message, including @@ -383,10 +390,12 @@ 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. +- `EnableGitHubCacheStore` (\*bool): Requests the read-only GitHub cache store (`github-cache-store` capability) on create or resume. The runtime registers the `github_sql` tool only when its `GITHUB_CACHE_STORE` feature flag is also on. Defaults to disabled in every mode. - `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 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`. +- `InteractiveUser` (\*bool): Declares that a person types this session's messages, as in an editor chat panel. Enables the runtime's user-message analysis telemetry for user-authored messages; automated messages stay excluded. Nil or false keeps the SDK default of off. Ignored for cloud sessions. - `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. @@ -401,6 +410,7 @@ Event types: `SessionLifecycleCreated`, `SessionLifecycleDeleted`, `SessionLifec - `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. +- `InteractiveUser` (\*bool): Re-supplies the interactive-user declaration on cold resume. It is ignored when resuming a session that is already active. - `GitHubTokenProvider` (GitHubTokenProvider): Replaces the session-scoped token provider when resuming. Cannot be combined with `GitHubToken`. ```go diff --git a/go/client.go b/go/client.go index b924a9b6c2..09695d5471 100644 --- a/go/client.go +++ b/go/client.go @@ -932,6 +932,7 @@ func (c *Client) CreateSession(ctx context.Context, config *SessionConfig) (*Ses req.EnableFileHooks = config.EnableFileHooks req.EnableHostGitOperations = config.EnableHostGitOperations req.EnableSessionStore = config.EnableSessionStore + req.EnableGitHubCacheStore = config.EnableGitHubCacheStore req.EnableSkills = config.EnableSkills if config.SkillProvider != nil { req.HasSkillProvider = Bool(true) @@ -975,6 +976,8 @@ func (c *Client) CreateSession(ctx context.Context, config *SessionConfig) (*Ses req.DefaultAgent = config.DefaultAgent req.Agent = config.Agent req.SkillDirectories = config.SkillDirectories + req.AgentDirectories = config.AgentDirectories + req.HookDirectories = config.HookDirectories req.PluginDirectories = config.PluginDirectories req.InstructionDirectories = config.InstructionDirectories req.DisabledSkills = config.DisabledSkills @@ -984,6 +987,7 @@ func (c *Client) CreateSession(ctx context.Context, config *SessionConfig) (*Ses req.InfiniteSessions = config.InfiniteSessions req.LargeOutput = config.LargeOutput req.ToolSearch = config.ToolSearch + req.ImageGeneration = config.ImageGeneration req.Memory = config.Memory req.GitHubToken = config.GitHubToken req.GitHubTokenProviderRegistrationID = registrationID @@ -1002,6 +1006,7 @@ func (c *Client) CreateSession(ctx context.Context, config *SessionConfig) (*Ses } req.EnableManagedSettings = config.EnableManagedSettings req.EnforceManagedModelDefaults = config.EnforceManagedModelDefaults + req.InteractiveUser = config.InteractiveUser req.ManagedSettings = config.ManagedSettings if len(config.Commands) > 0 { @@ -1078,6 +1083,7 @@ func (c *Client) CreateSession(ctx context.Context, config *SessionConfig) (*Ses localSessionID = uuid.NewString() } req.SessionID = localSessionID + req.Name = config.Name // unregisterSession removes only the session created by this call and stops // its event consumer. The latter is essential on CreateSession error paths: @@ -1437,6 +1443,7 @@ func (c *Client) ResumeSessionWithOptions(ctx context.Context, sessionID string, req.EnableFileHooks = config.EnableFileHooks req.EnableHostGitOperations = config.EnableHostGitOperations req.EnableSessionStore = config.EnableSessionStore + req.EnableGitHubCacheStore = config.EnableGitHubCacheStore req.EnableSkills = config.EnableSkills if config.SkillProvider != nil { req.HasSkillProvider = Bool(true) @@ -1455,6 +1462,8 @@ func (c *Client) ResumeSessionWithOptions(ctx context.Context, sessionID string, req.DefaultAgent = config.DefaultAgent req.Agent = config.Agent req.SkillDirectories = config.SkillDirectories + req.AgentDirectories = config.AgentDirectories + req.HookDirectories = config.HookDirectories req.PluginDirectories = config.PluginDirectories req.InstructionDirectories = config.InstructionDirectories req.DisabledSkills = config.DisabledSkills @@ -1464,6 +1473,7 @@ func (c *Client) ResumeSessionWithOptions(ctx context.Context, sessionID string, req.InfiniteSessions = config.InfiniteSessions req.LargeOutput = config.LargeOutput req.ToolSearch = config.ToolSearch + req.ImageGeneration = config.ImageGeneration req.Memory = config.Memory req.GitHubToken = config.GitHubToken req.GitHubTokenProviderRegistrationID = registrationID @@ -1482,6 +1492,7 @@ func (c *Client) ResumeSessionWithOptions(ctx context.Context, sessionID string, } req.EnableManagedSettings = config.EnableManagedSettings req.EnforceManagedModelDefaults = config.EnforceManagedModelDefaults + req.InteractiveUser = config.InteractiveUser req.ManagedSettings = config.ManagedSettings if config.OnPermissionRequest != nil { req.RequestPermission = Bool(true) @@ -2424,7 +2435,8 @@ func (c *Client) startInProcess(ctx context.Context) error { 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() + integrationID := strings.TrimSpace(getEnvValue(c.options.Env, "GITHUB_COPILOT_INTEGRATION_ID")) + config := c.inProcessHostConfig(integrationID) host, err := createInProcessHost(runtimePath, cliEntrypoint, config) if err != nil { @@ -2462,7 +2474,7 @@ func (c *Client) startInProcess(ctx context.Context) error { return nil } -func (c *Client) inProcessHostConfig() inProcessHostConfig { +func (c *Client) inProcessHostConfig(cliLoginSyncIntegrationID string) inProcessHostConfig { args := make([]string, 0, 8) if c.options.LogLevel != "" { args = append(args, "--log-level", c.options.LogLevel) @@ -2485,6 +2497,9 @@ func (c *Client) inProcessHostConfig() inProcessHostConfig { if c.options.EnableRemoteSessions { args = append(args, "--remote") } + if cliLoginSyncIntegrationID != "" { + args = append(args, "--cli-login-sync-integration-id="+cliLoginSyncIntegrationID) + } environment := make(map[string]string) if c.options.GitHubToken != "" { diff --git a/go/client_test.go b/go/client_test.go index c9a6225cde..bf579c5d6c 100644 --- a/go/client_test.go +++ b/go/client_test.go @@ -796,6 +796,73 @@ func TestClient_ForwardsCapiOptionsToSessionRequests(t *testing.T) { } } +func TestClient_ForwardsInteractiveUserToSessionRequests(t *testing.T) { + tests := []struct { + name string + value *bool + }{ + {"true", Bool(true)}, + {"false", Bool(false)}, + {"omitted", nil}, + } + + 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{InteractiveUser: tt.value}) + if err != nil { + t.Fatalf("CreateSession failed: %v", err) + } + assertOptionalBoolJSONField(t, <-createParams, "interactiveUser", tt.value) + + 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":"interactive-resume","workspacePath":"/workspace"}`), nil + }) + + _, err = client.ResumeSessionWithOptions(t.Context(), "interactive-resume", &ResumeSessionConfig{InteractiveUser: tt.value}) + if err != nil { + t.Fatalf("ResumeSessionWithOptions failed: %v", err) + } + assertOptionalBoolJSONField(t, <-resumeParams, "interactiveUser", tt.value) + }) + } +} + +func assertOptionalBoolJSONField(t *testing.T, raw json.RawMessage, field string, want *bool) { + t.Helper() + + var params map[string]any + if err := json.Unmarshal(raw, ¶ms); err != nil { + t.Fatal(err) + } + got, present := params[field] + if want == nil { + if present { + t.Fatalf("%s = %v, want omitted", field, got) + } + return + } + if got != *want { + t.Fatalf("%s = %v, want %v", field, got, *want) + } +} + func TestClient_RefreshCustomInstructionsIsCreateOnly(t *testing.T) { if _, ok := reflect.TypeOf(ResumeSessionConfig{}).FieldByName("RefreshCustomInstructions"); ok { t.Fatal("ResumeSessionConfig must not expose RefreshCustomInstructions") @@ -1150,6 +1217,68 @@ func assertAdditionalDirectories(t *testing.T, params json.RawMessage, want []st } } +func TestClient_ForwardsGitHubCacheStoreToSessionRequests(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, 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 + }) + resumeParams := make(chan json.RawMessage, 2) + server.SetRequestHandler("session.resume", func(params json.RawMessage) (json.RawMessage, *jsonrpc2.Error) { + resumeParams <- append(json.RawMessage(nil), params...) + return []byte(`{"sessionId":"` + sessionIDFromParams(t, params) + `","workspacePath":"/workspace"}`), nil + }) + + if _, err := client.CreateSession(t.Context(), &SessionConfig{EnableGitHubCacheStore: Bool(true)}); err != nil { + t.Fatalf("CreateSession failed: %v", err) + } + assertGitHubCacheStoreParam(t, <-createParams, Bool(true)) + if _, err := client.ResumeSessionWithOptions( + t.Context(), + "resumed-cache-store", + &ResumeSessionConfig{EnableGitHubCacheStore: Bool(false)}, + ); err != nil { + t.Fatalf("ResumeSessionWithOptions failed: %v", err) + } + assertGitHubCacheStoreParam(t, <-resumeParams, Bool(false)) + + if _, err := client.CreateSession(t.Context(), &SessionConfig{}); err != nil { + t.Fatalf("CreateSession without option failed: %v", err) + } + assertGitHubCacheStoreParam(t, <-createParams, nil) + if _, err := client.ResumeSessionWithOptions(t.Context(), "resumed-cache-store-unset", &ResumeSessionConfig{}); err != nil { + t.Fatalf("ResumeSessionWithOptions without option failed: %v", err) + } + assertGitHubCacheStoreParam(t, <-resumeParams, nil) +} + +func assertGitHubCacheStoreParam(t *testing.T, params json.RawMessage, want *bool) { + 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["enableGitHubCacheStore"] + if want == nil { + if present { + t.Fatalf("enableGitHubCacheStore = %v, want omitted", got) + } + return + } + if got != *want { + t.Fatalf("enableGitHubCacheStore = %v (present=%v), want %v", got, present, *want) + } +} + func TestClient_ForwardsCanvasProviderToSessionRequests(t *testing.T) { rpcClient, server, _ := newRuntimeShutdownRpcPair(t) t.Cleanup(server.Stop) @@ -2099,7 +2228,7 @@ func TestClient_InProcessConnection(t *testing.T) { Mode: ModeEmpty, }) - config := client.inProcessHostConfig() + config := client.inProcessHostConfig("") expectedArgs := []string{ "--log-level", "debug", "--auth-token-env", "COPILOT_SDK_AUTH_TOKEN", @@ -2119,6 +2248,33 @@ func TestClient_InProcessConnection(t *testing.T) { t.Fatalf("Expected managed environment %v, got %v", expectedEnvironment, config.Environment) } }) + + t.Run("forwards supported CLI login sync integrations", func(t *testing.T) { + t.Setenv("GITHUB_COPILOT_INTEGRATION_ID", "github/github-app") + client := NewClient(&ClientOptions{Connection: InProcessConnection{}}) + t.Setenv("GITHUB_COPILOT_INTEGRATION_ID", "changed/after-construction") + + integrationID := getEnvValue(client.options.Env, "GITHUB_COPILOT_INTEGRATION_ID") + config := client.inProcessHostConfig(integrationID) + + expectedArgs := []string{"--cli-login-sync-integration-id=github/github-app"} + if !reflect.DeepEqual(config.Args, expectedArgs) { + t.Fatalf("Expected CLI login sync arguments %v, got %v", expectedArgs, config.Args) + } + }) + + t.Run("forwards integration validation to the runtime", func(t *testing.T) { + t.Setenv("GITHUB_COPILOT_INTEGRATION_ID", "third-party/integration") + client := NewClient(&ClientOptions{Connection: InProcessConnection{}}) + + integrationID := getEnvValue(client.options.Env, "GITHUB_COPILOT_INTEGRATION_ID") + config := client.inProcessHostConfig(integrationID) + + expectedArgs := []string{"--cli-login-sync-integration-id=third-party/integration"} + if !reflect.DeepEqual(config.Args, expectedArgs) { + t.Fatalf("Expected CLI login sync arguments %v, got %v", expectedArgs, config.Args) + } + }) } func TestClient_DefaultConnection(t *testing.T) { @@ -2285,6 +2441,38 @@ func TestCreateSessionRequest_ClientName(t *testing.T) { }) } +func TestCreateSessionRequest_Name(t *testing.T) { + t.Run("includes name in JSON when set", func(t *testing.T) { + req := createSessionRequest{Name: "named at create"} + 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["name"] != "named at create" { + t.Errorf("Expected name to be 'named at create', got %v", m["name"]) + } + }) + + t.Run("omits name from JSON when empty", func(t *testing.T) { + req := createSessionRequest{} + 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["name"]; ok { + t.Error("Expected name to be omitted when empty") + } + }) +} + func TestResumeSessionRequest_ClientName(t *testing.T) { t.Run("includes clientName in JSON when set", func(t *testing.T) { req := resumeSessionRequest{SessionID: "s1", ClientName: "my-app"} diff --git a/go/customization_directories_test.go b/go/customization_directories_test.go new file mode 100644 index 0000000000..7e7a638868 --- /dev/null +++ b/go/customization_directories_test.go @@ -0,0 +1,116 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. + +package copilot + +import ( + "encoding/json" + "reflect" + "testing" + + "github.com/github/copilot-sdk/go/internal/jsonrpc2" + "github.com/github/copilot-sdk/go/rpc" +) + +func TestClient_CustomizationDirectoryAdapters(t *testing.T) { + for _, tt := range []struct { + name string + roots []string + }{ + {"omitted", nil}, + {"empty", []string{}}, + {"relative", []string{"relative-root", "other-root"}}, + } { + t.Run(tt.name, func(t *testing.T) { + directories := make(map[string][]string) + for key, suffix := range map[string]string{ + "skillDirectories": "skills", + "agentDirectories": "agents", + "instructionDirectories": "instructions", + "hookDirectories": "hooks", + } { + if tt.roots != nil { + directories[key] = make([]string, 0, len(tt.roots)) + for _, root := range tt.roots { + directories[key] = append(directories[key], root+"-"+suffix) + } + } + } + rpcClient, server, _ := newRuntimeShutdownRpcPair(t) + t.Cleanup(server.Stop) + t.Cleanup(rpcClient.Stop) + client := &Client{ + client: rpcClient, + RPC: rpc.NewServerRPC(rpcClient), + sessions: make(map[string]*Session), + } + requests := make(chan json.RawMessage, 2) + for _, method := range []string{"session.create", "session.resume"} { + server.SetRequestHandler(method, func(params json.RawMessage) (json.RawMessage, *jsonrpc2.Error) { + requests <- append(json.RawMessage(nil), params...) + id := sessionIDFromParams(t, params) + response, err := json.Marshal(map[string]string{"sessionId": id}) + if err != nil { + return nil, &jsonrpc2.Error{Code: -32603, Message: err.Error()} + } + return response, nil + }) + } + _, err := client.CreateSession(t.Context(), &SessionConfig{ + WorkingDirectory: "request-cwd", + EnableConfigDiscovery: Bool(false), + EnableFileHooks: Bool(false), + SkillDirectories: directories["skillDirectories"], + AgentDirectories: directories["agentDirectories"], + InstructionDirectories: directories["instructionDirectories"], + HookDirectories: directories["hookDirectories"], + }) + if err != nil { + t.Fatal(err) + } + _, err = client.ResumeSessionWithOptions(t.Context(), "resumed", &ResumeSessionConfig{ + WorkingDirectory: "resume-cwd", + EnableConfigDiscovery: Bool(false), + EnableFileHooks: Bool(false), + SkillDirectories: directories["skillDirectories"], + AgentDirectories: directories["agentDirectories"], + InstructionDirectories: directories["instructionDirectories"], + HookDirectories: directories["hookDirectories"], + }) + if err != nil { + t.Fatal(err) + } + for range 2 { + var payload map[string]json.RawMessage + if err := json.Unmarshal(<-requests, &payload); err != nil { + t.Fatal(err) + } + for _, key := range []string{"skillDirectories", "agentDirectories", "instructionDirectories", "hookDirectories"} { + value, present := payload[key] + if tt.roots == nil { + if present { + t.Errorf("unexpected %s: %s", key, value) + } + } else { + var actual []string + if err := json.Unmarshal(value, &actual); err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(actual, directories[key]) { + t.Errorf("%s = %#v, want %#v", key, actual, directories[key]) + } + } + } + for _, key := range []string{"enableConfigDiscovery", "enableFileHooks"} { + if string(payload[key]) != "false" { + t.Errorf("%s = %s, want false", key, payload[key]) + } + } + for _, key := range []string{"additionalDirectories", "customAgents", "hooks"} { + if _, present := payload[key]; present { + t.Errorf("unexpected %s in %s", key, payload[key]) + } + } + } + }) + } +} diff --git a/go/hook_identity_test.go b/go/hook_identity_test.go new file mode 100644 index 0000000000..6667fb00c5 --- /dev/null +++ b/go/hook_identity_test.go @@ -0,0 +1,103 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. + +package copilot + +import ( + "encoding/json" + "reflect" + "testing" +) + +func TestHookIdentityTypedDispatch(t *testing.T) { + session, cleanup := newTestSession() + defer cleanup() + session.SessionID = "callback-owner" + var captured any + capture := func(input any, invocation HookInvocation) { + captured = input + if invocation.SessionID != "callback-owner" { + t.Fatalf("callback routing changed: %+v", invocation) + } + } + session.registerHooks(&SessionHooks{ + OnPreToolUse: func(i PreToolUseHookInput, c HookInvocation) (*PreToolUseHookOutput, error) { + capture(i, c) + return nil, nil + }, + OnPreMCPToolCall: func(i PreMCPToolCallHookInput, c HookInvocation) (*PreMCPToolCallHookOutput, error) { + capture(i, c) + return nil, nil + }, + OnPostToolUse: func(i PostToolUseHookInput, c HookInvocation) (*PostToolUseHookOutput, error) { + capture(i, c) + return nil, nil + }, + OnPostToolUseFailure: func(i PostToolUseFailureHookInput, c HookInvocation) (*PostToolUseFailureHookOutput, error) { + capture(i, c) + return nil, nil + }, + OnUserPromptSubmitted: func(i UserPromptSubmittedHookInput, c HookInvocation) (*UserPromptSubmittedHookOutput, error) { + capture(i, c) + return nil, nil + }, + OnUserPromptTransformed: func(i UserPromptTransformedHookInput, c HookInvocation) (*UserPromptTransformedHookOutput, error) { + capture(i, c) + return nil, nil + }, + OnSessionStart: func(i SessionStartHookInput, c HookInvocation) (*SessionStartHookOutput, error) { + capture(i, c) + return nil, nil + }, + OnSessionEnd: func(i SessionEndHookInput, c HookInvocation) (*SessionEndHookOutput, error) { + capture(i, c) + return nil, nil + }, + OnErrorOccurred: func(i ErrorOccurredHookInput, c HookInvocation) (*ErrorOccurredHookOutput, error) { + capture(i, c) + return nil, nil + }, + OnAgentStop: func(i AgentStopHookInput, c HookInvocation) (*AgentStopHookOutput, error) { + capture(i, c) + return nil, nil + }, + OnSubagentStart: func(i SubagentStartHookInput, c HookInvocation) (*SubagentStartHookOutput, error) { + capture(i, c) + return nil, nil + }, + OnSubagentStop: func(i SubagentStopHookInput, c HookInvocation) (*SubagentStopHookOutput, error) { + capture(i, c) + return nil, nil + }, + }) + for _, hook := range []string{"preToolUse", "preMcpToolCall", "postToolUse", "postToolUseFailure", "userPromptSubmitted", "userPromptTransformed", "sessionStart", "sessionEnd", "errorOccurred", "agentStop", "subagentStart", "subagentStop"} { + for _, enriched := range []bool{true, false} { + t.Run(hook+map[bool]string{true: "/child", false: "/old-root"}[enriched], func(t *testing.T) { + input := map[string]any{"sessionId": "reported-session", "timestamp": 1, "cwd": "work"} + if enriched { + input["agentId"], input["agentType"], input["parentAgentId"] = "child-actor", "explore", "parent-actor" + } + raw, err := json.Marshal(input) + if err != nil { + t.Fatal(err) + } + captured = nil + if _, err := session.handleHooksInvoke(hook, raw); err != nil { + t.Fatal(err) + } + value := reflect.ValueOf(captured) + for field, expected := range map[string]string{"AgentID": "child-actor", "AgentType": "explore", "ParentAgentID": "parent-actor"} { + if !enriched { + expected = "" + } + actual := value.FieldByName(field) + if !actual.IsValid() || actual.Kind() != reflect.String || actual.String() != expected { + t.Errorf("%s: actual=%v expected=%q input=%+v", field, actual, expected, captured) + } + } + if value.FieldByName("SessionID").String() != "reported-session" { + t.Errorf("report ID changed: %+v", captured) + } + }) + } + } +} diff --git a/go/image_generation_test.go b/go/image_generation_test.go new file mode 100644 index 0000000000..1c6586692c --- /dev/null +++ b/go/image_generation_test.go @@ -0,0 +1,48 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. + +package copilot + +import ( + "encoding/json" + "testing" +) + +func TestImageGenerationSerialization(t *testing.T) { + for _, enabled := range []*bool{Bool(true), Bool(false), nil} { + var config *ImageGenerationConfig + if enabled != nil { + config = &ImageGenerationConfig{Enabled: enabled} + } + for _, request := range []any{ + createSessionRequest{ImageGeneration: config}, + resumeSessionRequest{SessionID: "image-test", ImageGeneration: config}, + } { + data, err := json.Marshal(request) + if err != nil { + t.Fatal(err) + } + var payload map[string]json.RawMessage + if err := json.Unmarshal(data, &payload); err != nil { + t.Fatal(err) + } + value, present := payload["imageGeneration"] + if enabled == nil { + if present { + t.Fatalf("expected omitted imageGeneration, got %s", data) + } + } else { + expected, err := json.Marshal(map[string]bool{"enabled": *enabled}) + if err != nil { + t.Fatal(err) + } + if string(value) != string(expected) { + t.Fatalf("imageGeneration = %s, want %s", value, expected) + } + } + } + } + data, err := json.Marshal(ImageGenerationConfig{}) + if err != nil || string(data) != "{}" { + t.Fatalf("unset enabled = %s, err = %v", data, err) + } +} diff --git a/go/internal/e2e/client_options_e2e_test.go b/go/internal/e2e/client_options_e2e_test.go index 739f1a58da..49294ca052 100644 --- a/go/internal/e2e/client_options_e2e_test.go +++ b/go/internal/e2e/client_options_e2e_test.go @@ -412,6 +412,7 @@ func TestClientOptionsE2E(t *testing.T) { extensionSDKPath := filepath.Join(ctx.WorkDir, "extension-sdk") session, err := client.CreateSession(t.Context(), &copilot.SessionConfig{ SessionID: sessionID, + Name: "go named at create", ClientName: "go-sdk-e2e-client", Model: "claude-sonnet-5", ReasoningEffort: "low", @@ -471,6 +472,7 @@ func TestClientOptionsE2E(t *testing.T) { } expectedValues := map[string]any{ "sessionId": sessionID, + "name": "go named at create", "clientName": "go-sdk-e2e-client", "model": "claude-sonnet-5", "reasoningEffort": "low", diff --git a/go/internal/e2e/subagent_hooks_e2e_test.go b/go/internal/e2e/subagent_hooks_e2e_test.go index f74cb6355d..50806325e3 100644 --- a/go/internal/e2e/subagent_hooks_e2e_test.go +++ b/go/internal/e2e/subagent_hooks_e2e_test.go @@ -7,6 +7,7 @@ import ( "net/http" "os" "path/filepath" + "slices" "strings" "sync" "testing" @@ -55,41 +56,13 @@ func (rt *recordingForwardingTransport) RoundTrip(req *http.Request) (*http.Resp record.interactionType = rctx.InteractionType } if record.parentAgentID != "" { - var payload struct { - Messages []struct { - Role string `json:"role"` - Content json.RawMessage `json:"content"` - } `json:"messages"` - } + var payload map[string]any if err := json.Unmarshal(body, &payload); err != nil { return nil, err } - for _, message := range payload.Messages { - if message.Role == "user" && len(message.Content) > 0 { - var prompts []string - switch message.Content[0] { - case '"': - var prompt string - if err := json.Unmarshal(message.Content, &prompt); err != nil { - return nil, err - } - prompts = append(prompts, prompt) - case '[': - var parts []struct { - Text string `json:"text"` - } - if err := json.Unmarshal(message.Content, &parts); err != nil { - return nil, err - } - for _, part := range parts { - prompts = append(prompts, part.Text) - } - } - for _, prompt := range prompts { - if strings.Contains(prompt, childContext+"\n\n") { - record.childPrompt = prompt - } - } + for _, prompt := range userMessageText(payload) { + if strings.Contains(prompt, childContext+"\n\n") { + record.childPrompt = prompt } } } else if bytes.Contains(body, []byte(stopResponsePrefix)) { @@ -105,6 +78,62 @@ func (rt *recordingForwardingTransport) RoundTrip(req *http.Request) (*http.Resp return rt.inner.RoundTrip(req) } +func userMessageText(body map[string]any) []string { + if input, ok := body["input"].(string); ok { + return []string{input} + } + messages := body["messages"] + if messages == nil { + messages = body["input"] + } + items, _ := messages.([]any) + var texts []string + for _, item := range items { + message, ok := item.(map[string]any) + if !ok || message["role"] != "user" { + continue + } + switch content := message["content"].(type) { + case string: + texts = append(texts, content) + case []any: + for _, item := range content { + part, ok := item.(map[string]any) + if !ok || (part["type"] != "text" && part["type"] != "input_text") { + continue + } + if text, ok := part["text"].(string); ok { + texts = append(texts, text) + } + } + } + } + return texts +} + +func TestUserMessageText(t *testing.T) { + for _, tc := range []struct { + body string + want []string + }{ + {`{"messages":[{"role":"user","content":"chat"}]}`, []string{"chat"}}, + {`{"input":"responses"}`, []string{"responses"}}, + {`{"input":[{"role":"user","content":"responses"}]}`, []string{"responses"}}, + {`{"input":[{"role":"assistant","content":"ignored"},{"type":"function_call_output","output":"ignored"},{"role":"user","content":[{"type":"text","text":"chat"},{"type":"input_text","text":"responses"},{"type":"input_image","text":"ignored"},{"type":"input_text","text":42},null,"ignored"]}]}`, []string{"chat", "responses"}}, + {`{}`, nil}, + } { + t.Run(tc.body, func(t *testing.T) { + var body map[string]any + if err := json.Unmarshal([]byte(tc.body), &body); err != nil { + t.Fatal(err) + } + if got := userMessageText(body); !slices.Equal(got, tc.want) { + t.Fatalf("userMessageText() = %q, want %q", got, tc.want) + } + }) + } +} + func (rt *recordingForwardingTransport) inferenceRecords() []subagentRequestRecord { rt.mu.Lock() defer rt.mu.Unlock() diff --git a/go/internal/jsonrpc2/jsonrpc2.go b/go/internal/jsonrpc2/jsonrpc2.go index d4898b6a9c..14566f7bea 100644 --- a/go/internal/jsonrpc2/jsonrpc2.go +++ b/go/internal/jsonrpc2/jsonrpc2.go @@ -329,11 +329,11 @@ func (c *Client) RequestWithInlineResponse(ctx context.Context, method string, p return response.Result, nil case <-c.processDone: if err := c.getProcessError(); err != nil { - return nil, err + return responseAfterShutdown(responseChan, err) } - return nil, fmt.Errorf("process exited unexpectedly") + return responseAfterShutdown(responseChan, fmt.Errorf("process exited unexpectedly")) case <-c.stopChan: - return nil, fmt.Errorf("client stopped") + return responseAfterShutdown(responseChan, fmt.Errorf("client stopped")) } } select { @@ -345,9 +345,22 @@ func (c *Client) RequestWithInlineResponse(ctx context.Context, method string, p } return response.Result, nil case <-c.connectionClosed: - return nil, fmt.Errorf("connection closed") + return responseAfterShutdown(responseChan, fmt.Errorf("connection closed")) case <-c.stopChan: - return nil, fmt.Errorf("client stopped") + return responseAfterShutdown(responseChan, fmt.Errorf("client stopped")) + } +} + +func responseAfterShutdown(responseChan <-chan *Response, shutdownErr error) (json.RawMessage, error) { + // Shutdown can win the outer select after the read loop has already admitted a reply. + select { + case response := <-responseChan: + if response.Error != nil { + return nil, response.Error + } + return response.Result, nil + default: + return nil, shutdownErr } } diff --git a/go/internal/jsonrpc2/jsonrpc2_test.go b/go/internal/jsonrpc2/jsonrpc2_test.go index 258992e101..0c42f185aa 100644 --- a/go/internal/jsonrpc2/jsonrpc2_test.go +++ b/go/internal/jsonrpc2/jsonrpc2_test.go @@ -3,7 +3,9 @@ package jsonrpc2 import ( "bytes" "context" + "encoding/json" "errors" + "fmt" "io" "sync" "testing" @@ -16,6 +18,10 @@ type writeCloser struct { func (w writeCloser) Close() error { return nil } +type writeFunc func([]byte) (int, error) + +func (w writeFunc) Write(data []byte) (int, error) { return w(data) } + func TestOnCloseCalledOnUnexpectedExit(t *testing.T) { stdinR, stdinW := io.Pipe() stdoutR, stdoutW := io.Pipe() @@ -110,6 +116,135 @@ func TestRequestReturnsWhenConnectionCloses(t *testing.T) { } } +func TestRequestUnacknowledgedShutdown(t *testing.T) { + for _, shutdown := range []string{"stop", "connection closed", "process exited", "process error"} { + t.Run(shutdown, func(t *testing.T) { + stdinR, stdinW := io.Pipe() + defer stdinR.Close() + defer stdinW.Close() + client := NewClient(stdinW, io.NopCloser(bytes.NewReader(nil))) + done := make(chan struct{}) + var processErr error + if shutdown == "process error" { + processErr = errors.New("CLI process exited: exit status 1") + } + if shutdown == "process exited" || shutdown == "process error" { + client.SetProcessDone(done, &processErr) + } + ctx, cancel := context.WithCancel(t.Context()) + finished := make(chan struct{}) + t.Cleanup(func() { + cancel() + stdinR.Close() + select { + case <-finished: + case <-time.After(time.Second): + t.Error("request did not finish during cleanup") + } + }) + result := make(chan error, 1) + go func() { + defer close(finished) + _, err := client.Request(ctx, "test.method", nil) + result <- err + }() + if _, err := newHeaderReader(stdinR).Read(); err != nil { + t.Fatalf("Read request failed: %v", err) + } + var expected string + switch shutdown { + case "stop": + close(client.stopChan) + expected = "client stopped" + case "connection closed": + close(client.connectionClosed) + expected = "connection closed" + case "process exited": + close(done) + expected = "process exited unexpectedly" + case "process error": + close(done) + expected = processErr.Error() + } + select { + case err := <-result: + if err == nil || err.Error() != expected { + t.Fatalf("unacknowledged request error = %v, want %s", err, expected) + } + case <-time.After(time.Second): + t.Fatal("unacknowledged request did not stop") + } + client.mu.Lock() + pending, callbacks := len(client.pendingRequests), len(client.pendingInlineCallbacks) + client.mu.Unlock() + if pending != 0 || callbacks != 0 { + t.Fatalf("request cleanup left %d pending requests and %d callbacks", pending, callbacks) + } + }) + } +} + +func TestRequestAdmittedResponseSurvivesShutdown(t *testing.T) { + for _, shutdown := range []string{"stop", "connection closed", "process exited", "process error"} { + t.Run(shutdown, func(t *testing.T) { + for _, rpcError := range []bool{false, true} { + for iteration := range 32 { + t.Run(fmt.Sprintf("RPC_error=%t/%d", rpcError, iteration), func(t *testing.T) { + expected := json.RawMessage(`{"messageId":"acknowledged-message"}`) + var expectedErr *Error + if rpcError { + expectedErr = &Error{Code: -32001, Message: "acknowledged failure", Data: json.RawMessage(`{"detail":"original"}`)} + } + done := make(chan struct{}) + var client *Client + writer := writeFunc(func(data []byte) (int, error) { + if bytes.HasPrefix(data, []byte("Content-Length:")) { + return len(data), nil + } + var request Request + if err := json.Unmarshal(data, &request); err != nil { + return 0, err + } + // Both outcomes are ready before Request reaches its response select. + client.handleResponse(&Response{ID: request.ID, Result: expected, Error: expectedErr}) + switch shutdown { + case "stop": + close(client.stopChan) + case "connection closed": + close(client.connectionClosed) + default: + close(done) + } + return len(data), nil + }) + client = NewClient(writeCloser{Writer: writer}, io.NopCloser(bytes.NewReader(nil))) + if shutdown == "process exited" || shutdown == "process error" { + var processErr error + if shutdown == "process error" { + processErr = errors.New("CLI process exited: exit status 1") + } + client.SetProcessDone(done, &processErr) + } + + result, err := client.Request(t.Context(), "test.method", nil) + + if expectedErr != nil { + if result != nil || err != expectedErr { + t.Fatalf("admitted error = (%s, %v), want original RPC error %v", result, err, expectedErr) + } + } else if err != nil || !bytes.Equal(result, expected) { + t.Fatalf("admitted response = (%s, %v), want (%s, nil)", result, err, expected) + } + if len(client.pendingRequests) != 0 || len(client.pendingInlineCallbacks) != 0 { + t.Fatal("completed request retained its pending response or callback") + } + }) + } + } + }) + } +} + // TestSetProcessDone_ErrorAvailableImmediately validates that getProcessError() // returns the correct error immediately after processDone is closed. // The current implementation stores a pointer to the process error diff --git a/go/rpc/callback_numbers_test.go b/go/rpc/callback_numbers_test.go new file mode 100644 index 0000000000..1f3fae65df --- /dev/null +++ b/go/rpc/callback_numbers_test.go @@ -0,0 +1,33 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. + +package rpc_test + +import ( + "encoding/json" + "testing" + + "github.com/github/copilot-sdk/go/rpc" +) + +func TestCanvasCallbackNumbersRetainFloat64(t *testing.T) { + var opened rpc.CanvasProviderOpenRequest + if err := json.Unmarshal([]byte(`{"extensionId":"counter","canvasId":"counter","instanceId":"panel","sessionId":"session","input":{"startValue":3}}`), &opened); err != nil { + t.Fatal(err) + } + var action rpc.CanvasProviderInvokeActionRequest + if err := json.Unmarshal([]byte(`{"extensionId":"counter","canvasId":"counter","instanceId":"panel","sessionId":"session","actionName":"increment","input":{"amount":2}}`), &action); err != nil { + t.Fatal(err) + } + number := func(input any, name string) float64 { + t.Helper() + value := input.(map[string]any)[name] + number, ok := value.(float64) + if !ok { + t.Fatalf("%s: expected float64, got %T (%v)", name, value, value) + } + return number + } + if got := number(opened.Input, "startValue") + number(action.Input, "amount"); got != 5 { + t.Fatalf("counter result: got %v, want 5", got) + } +} diff --git a/go/rpc/content_exclusion_test.go b/go/rpc/content_exclusion_test.go new file mode 100644 index 0000000000..63dd00b120 --- /dev/null +++ b/go/rpc/content_exclusion_test.go @@ -0,0 +1,45 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. + +package rpc_test + +import ( + "bytes" + "encoding/json" + "reflect" + "testing" + + "github.com/github/copilot-sdk/go/rpc" +) + +func TestValidatedPoliciesPreserveJSONExtensions(t *testing.T) { + for _, input := range []string{ + `{"policies":[]}`, + `{"policies":[{"rules":[{"paths":["src/**"],"source":{"name":"Security","type":"Organization"},"futureRuleField":{"items":[true,null,12.5]}}],"last_updated_at":1720000000,"scope":"all","futurePolicyField":{"nullable":null}}]}`, + `{"policies":[{"rules":[{"paths":["src/**"],"source":{"name":"Security","type":"Organization"},"futureRuleField":9007199254740993}],"last_updated_at":1720000000,"scope":"all","futurePolicyField":9007199254740993}]}`, + } { + t.Run(input, func(t *testing.T) { + var decoded rpc.ContentExclusionValidatePoliciesResult + if err := json.Unmarshal([]byte(input), &decoded); err != nil { + t.Fatal(err) + } + output, err := json.Marshal(decoded) + if err != nil { + t.Fatal(err) + } + var expected, actual any + expectedDecoder := json.NewDecoder(bytes.NewBufferString(input)) + expectedDecoder.UseNumber() + if err := expectedDecoder.Decode(&expected); err != nil { + t.Fatal(err) + } + actualDecoder := json.NewDecoder(bytes.NewReader(output)) + actualDecoder.UseNumber() + if err := actualDecoder.Decode(&actual); err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(actual, expected) { + t.Fatalf("policy fields changed:\nactual: %s\nexpected: %s", output, input) + } + }) + } +} diff --git a/go/rpc/repo_settings_test.go b/go/rpc/repo_settings_test.go new file mode 100644 index 0000000000..529daa40ff --- /dev/null +++ b/go/rpc/repo_settings_test.go @@ -0,0 +1,54 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. + +package rpc_test + +import ( + "encoding/json" + "fmt" + "reflect" + "testing" + + "github.com/github/copilot-sdk/go/rpc" +) + +func ExampleRepoSettingsReadRawResult() { + const input = `{"config":{"custom":{"items":[true,null,12.5]}},"invalid":false}` + var result rpc.RepoSettingsReadRawResult + if err := json.Unmarshal([]byte(input), &result); err != nil { + panic(err) + } + output, err := json.Marshal(result) + if err != nil { + panic(err) + } + fmt.Println(string(output)) + // Output: {"config":{"custom":{"items":[true,null,12.5]}},"invalid":false} +} + +func TestRawRepoSettingsPreserveUnknownJSON(t *testing.T) { + for _, input := range []string{ + `{"config":{"custom":{"items":[true,null,12.5],"text":"preserved"}},"invalid":false}`, + `{"config":{},"invalid":true}`, + } { + t.Run(input, func(t *testing.T) { + var decoded rpc.RepoSettingsReadRawResult + if err := json.Unmarshal([]byte(input), &decoded); err != nil { + t.Fatal(err) + } + output, err := json.Marshal(decoded) + if err != nil { + t.Fatal(err) + } + var expected, actual any + if err := json.Unmarshal([]byte(input), &expected); err != nil { + t.Fatal(err) + } + if err := json.Unmarshal(output, &actual); err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(actual, expected) { + t.Fatalf("repository settings changed:\nactual: %s\nexpected: %s", output, input) + } + }) + } +} diff --git a/go/rpc/result_numbers_test.go b/go/rpc/result_numbers_test.go new file mode 100644 index 0000000000..40b8803146 --- /dev/null +++ b/go/rpc/result_numbers_test.go @@ -0,0 +1,72 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. + +package rpc_test + +import ( + "encoding/json" + "testing" + + "github.com/github/copilot-sdk/go/rpc" +) + +func TestExistingRPCResultNumbersRetainFloat64(t *testing.T) { + var settings rpc.UserSettingsGetResult + var models rpc.ModelList + var prompt rpc.MCPPrompt + var workflow rpc.WorkflowRunResult + var canvas rpc.CanvasActionInvokeResult + for _, test := range []struct { + name string + input string + result any + value func() any + }{ + { + name: "user setting value", + input: `{"settings":{"maxSessionCount":{"value":42,"default":10,"isDefault":false}}}`, + result: &settings, + value: func() any { return settings.Settings["maxSessionCount"].Value }, + }, + { + name: "user setting default", + input: `{"settings":{"maxSessionCount":{"value":10,"default":42,"isDefault":false}}}`, + result: &settings, + value: func() any { return settings.Settings["maxSessionCount"].Default }, + }, + { + name: "model metadata", + input: `{"models":[{"id":"model","name":"Model","capabilities":{},"metadata":{"revision":42}}]}`, + result: &models, + value: func() any { return models.Models[0].Metadata["revision"] }, + }, + { + name: "MCP prompt metadata", + input: `{"name":"prompt","_meta":{"revision":42}}`, + result: &prompt, + value: func() any { return prompt.Meta["revision"] }, + }, + { + name: "workflow result", + input: `{"runId":"run","status":"completed","result":42}`, + result: &workflow, + value: func() any { return workflow.Result }, + }, + { + name: "canvas action result", + input: `{"result":42}`, + result: &canvas, + value: func() any { return canvas.Result }, + }, + } { + t.Run(test.name, func(t *testing.T) { + if err := json.Unmarshal([]byte(test.input), test.result); err != nil { + t.Fatal(err) + } + value := test.value() + number, ok := value.(float64) + if !ok || number != 42 { + t.Fatalf("expected float64(42), got %T (%v)", value, value) + } + }) + } +} diff --git a/go/rpc/settings_presence_test.go b/go/rpc/settings_presence_test.go new file mode 100644 index 0000000000..86c562e194 --- /dev/null +++ b/go/rpc/settings_presence_test.go @@ -0,0 +1,107 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. + +package rpc + +import ( + "context" + "encoding/json" + "net" + "testing" + "time" + + "github.com/github/copilot-sdk/go/internal/jsonrpc2" +) + +func TestSettingsValuePresenceOnWire(t *testing.T) { + for _, method := range []string{"user.settings.applyTopKey", "repo.settings.writeKey", "user.settings.edit"} { + for _, test := range []struct { + name string + value any + present bool + delete bool + }{ + {name: "omitted"}, + {name: "explicit null", value: json.RawMessage("null"), present: true}, + {name: "value", value: "chosen", present: true}, + {name: "delete", delete: true}, + } { + t.Run(method+"/"+test.name, func(t *testing.T) { + clientConn, serverConn := net.Pipe() + defer clientConn.Close() + defer serverConn.Close() + client := jsonrpc2.NewClient(clientConn, clientConn) + server := jsonrpc2.NewClient(serverConn, serverConn) + requests := make(chan json.RawMessage, 1) + server.SetRequestHandler(method, func(params json.RawMessage) (json.RawMessage, *jsonrpc2.Error) { + requests <- params + return json.RawMessage(`{}`), nil + }) + client.Start() + server.Start() + defer client.Stop() + defer server.Stop() + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + api := NewInternalServerRPC(client) + var err error + switch method { + case "user.settings.applyTopKey": + var value *any + if test.present { + value = &test.value + } + _, err = api.User.Settings().ApplyTopKey(ctx, &UserSettingsApplyTopKeyRequest{ + CanonicalPath: []string{"model"}, + TopKey: "model", + Value: value, + Delete: &test.delete, + }) + case "repo.settings.writeKey": + _, err = api.Repo.Settings().WriteKey(ctx, &RepoSettingsWriteKeyRequest{ + RepoRoot: t.TempDir(), + Scope: "repo", + Key: "model", + Value: test.value, + Delete: &test.delete, + }) + case "user.settings.edit": + operation := SettingsEditOperationSet + if test.delete { + operation = SettingsEditOperationRemove + } + _, err = api.User.Settings().Edit(ctx, &UserSettingsEditRequest{ + CanonicalPath: []string{"model"}, + Operation: operation, + Value: test.value, + }) + } + if err != nil { + t.Fatal(err) + } + select { + case raw := <-requests: + var fields map[string]json.RawMessage + if err := json.Unmarshal(raw, &fields); err != nil { + t.Fatal(err) + } + value, present := fields["value"] + wantPresent := test.present || (method == "user.settings.edit" && !test.delete) + if present != wantPresent { + t.Fatalf("value presence = %v, want %v; wire params: %s", present, wantPresent, raw) + } + if present { + expected, err := json.Marshal(test.value) + if err != nil { + t.Fatal(err) + } + if string(value) != string(expected) { + t.Fatalf("value = %s, want %s; wire params: %s", value, expected, raw) + } + } + case <-ctx.Done(): + t.Fatal(ctx.Err()) + } + }) + } + } +} diff --git a/go/rpc/zrpc.go b/go/rpc/zrpc.go index 9ac9b92597..33cd6e0cbb 100644 --- a/go/rpc/zrpc.go +++ b/go/rpc/zrpc.go @@ -39,6 +39,28 @@ type AccountAllUsers struct { Token *string `json:"token,omitempty"` } +// The authentication identity whose shared wording should be rendered. +// Experimental: AccountDescribeAuthInfoRequest is part of an experimental API and may +// change or be removed. +// Internal: AccountDescribeAuthInfoRequest is an internal SDK API and is not part of the +// public surface. +type AccountDescribeAuthInfoRequest struct { + // Identity to describe. + Identity AuthIdentity `json:"identity"` +} + +// The wording the runtime uses for an authentication identity. +// Experimental: AccountDescribeAuthInfoResult is part of an experimental API and may change +// or be removed. +// Internal: AccountDescribeAuthInfoResult is an internal SDK API and is not part of the +// public surface. +type AccountDescribeAuthInfoResult struct { + // One-line description of the identity, suitable after a verb such as "Switched to". + Description string `json:"description"` + // Sign-in banner announcing the authenticated user. + Message string `json:"message"` +} + // List of all authenticated users // Experimental: AccountGetAllUsersResult is part of an experimental API and may change or // be removed. @@ -54,6 +76,30 @@ type AccountGetCurrentAuthResult struct { AuthInfo AuthInfo `json:"authInfo,omitempty"` } +// The authentication identity whose gist support should be reported. +// Experimental: AccountGetGistSupportRequest is part of an experimental API and may change +// or be removed. +// Internal: AccountGetGistSupportRequest is an internal SDK API and is not part of the +// public surface. +type AccountGetGistSupportRequest struct { + // Identity to evaluate. + Identity AuthIdentity `json:"identity"` +} + +// Whether the identity can create gists, and why not when it cannot. +// Experimental: AccountGetGistSupportResult is part of an experimental API and may change +// or be removed. +// Internal: AccountGetGistSupportResult is an internal SDK API and is not part of the +// public surface. +type AccountGetGistSupportResult struct { + // Machine-readable reason category when unsupported. + Kind *string `json:"kind,omitempty"` + // Human-readable explanation to show where the gist action would have been. + Reason *string `json:"reason,omitempty"` + // True when the identity can create gists. + Supported bool `json:"supported"` +} + // Experimental: AccountGetQuotaRequest is part of an experimental API and may change or be // removed. type AccountGetQuotaRequest struct { @@ -73,6 +119,40 @@ type AccountGetQuotaResult struct { QuotaSnapshots map[string]AccountQuotaSnapshot `json:"quotaSnapshots"` } +// The environment variable supplying a classic personal access token, when one is set. +// Experimental: AccountGetTokenEnvVarResult is part of an experimental API and may change +// or be removed. +// Internal: AccountGetTokenEnvVarResult is an internal SDK API and is not part of the +// public surface. +type AccountGetTokenEnvVarResult struct { + // Variable name, for example `GH_TOKEN`. Absent when no classic token is present in the + // environment. + Name *string `json:"name,omitempty"` +} + +// A path to resolve against the GitHub host an identity is signed in to. +// Experimental: AccountGetWebURLRequest is part of an experimental API and may change or be +// removed. +// Internal: AccountGetWebURLRequest is an internal SDK API and is not part of the public +// surface. +type AccountGetWebURLRequest struct { + // Identity whose host the path resolves against. Omit to resolve against the public + // github.com host. + Identity *AuthIdentity `json:"identity,omitempty"` + // Host-relative path, without a leading slash, for example `settings/copilot/features`. + Path string `json:"path"` +} + +// An absolute URL on the identity's GitHub host. +// Experimental: AccountGetWebURLResult is part of an experimental API and may change or be +// removed. +// Internal: AccountGetWebURLResult is an internal SDK API and is not part of the public +// surface. +type AccountGetWebURLResult struct { + // Absolute URL for the requested path. + URL string `json:"url"` +} + // 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 @@ -167,6 +247,9 @@ type AccountsSetRequest struct { type AccountStatus struct { // Whether this is the active account. Active bool `json:"active"` + // Human-readable credential source used to authenticate this account, such as `gh` or + // `GITHUB_TOKEN`. + AuthSource *string `json:"authSource,omitempty"` // Opaque id of the account this one was derived from (e.g. an EMU account's base Entra // identity); absent for a root account. Matches the base identity account's selectionId, // forming the derivation edge. @@ -223,6 +306,8 @@ type AgentInfo struct { DisableModelInvocation *bool `json:"disableModelInvocation,omitempty"` // Human-readable display name DisplayName string `json:"displayName"` + // Declarative hook actions keyed by event name, executed only while this agent is active. + Hooks map[string][]any `json:"hooks,omitzero"` // 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. @@ -503,6 +588,11 @@ type AgentsCustomAgentInitialModelDecisionResult struct { // Experimental: AgentsDiscoverRequest is part of an experimental API and may change or be // removed. type AgentsDiscoverRequest struct { + // Explicit discovery-only agent roots, separate from customAgents and filesystem grants. + // Available without ambient discovery, subject to managed/source disables. Relative paths + // resolve against the request's effective working directory when supplied. Null and + // omission are equivalent; an empty array contributes no explicit roots. + AgentDirectories []string `json:"agentDirectories,omitzero"` // 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. ExcludeHostAgents *bool `json:"excludeHostAgents,omitempty"` @@ -636,6 +726,11 @@ type AgentsGetBuiltinsResult struct { // Experimental: AgentsGetDiscoveryPathsRequest is part of an experimental API and may // change or be removed. type AgentsGetDiscoveryPathsRequest struct { + // Explicit discovery-only agent roots to enumerate, including configured missing + // directories. Relative paths resolve against the request's effective working directory + // when supplied. These are not filesystem grants or preferred creation targets. Null and + // omission are equivalent. + AgentDirectories []string `json:"agentDirectories,omitzero"` // When true, omit the host's user-level agent directory, leaving only project directories. // For multitenant deployments (mirrors `discover`'s `excludeHostAgents`). ExcludeHostAgents *bool `json:"excludeHostAgents,omitempty"` @@ -2089,13 +2184,107 @@ type CatalogAgentPluginCandidateProvenance struct { // Host of the catalog authority that advertised the reference, without path, query, or // credentials. Inert untrusted data. Authority string `json:"authority"` - // Canonical Agent Plugin media type. + // Media type naming the plugin's format. MediaType CatalogAgentPluginMediaType `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"` } +// What installing an Agent Plugin would do, for the user to review and confirm. +// Provisional: the catalogue install data it is built from is not final. +// Experimental: CatalogAgentPluginInstallPlan is part of an experimental API and may change +// or be removed. +type CatalogAgentPluginInstallPlan struct { + // Canvas IDs the plugin contributes. Non-empty exactly when components include `canvas`. + CanvasIDs []string `json:"canvasIds"` + // Exact full commit ID to install, in lowercase hex. Always present when the plan has a + // repository source; a plan with a marketplace source carries at least one of version and + // commit. + Commit *string `json:"commit,omitempty"` + // Components the plugin contributes, in canonical order. + Components []CatalogAgentPluginComponent `json:"components"` + // Connectors the plugin depends on. + ConnectorDependencies []string `json:"connectorDependencies"` + // Extension IDs the plugin contributes. Non-empty exactly when components include + // `extension`. + ExtensionIDs []string `json:"extensionIds"` + // MCP servers the plugin depends on but does not bundle. + MCPDependencies []string `json:"mcpDependencies"` + // The plugin's name, as named in its marketplace or manifest. + PluginName string `json:"pluginName"` + // Every valid install source, in preference order: materialization, then marketplace, then + // repository. Each kind appears at most once. + Sources []CatalogAgentPluginInstallSource `json:"sources"` + // Exact plugin version to install. A plan with a marketplace source carries at least one of + // version and commit. + Version *string `json:"version,omitempty"` +} + +// One place an Agent Plugin can install from: Agent Finder's materialisation, a +// marketplace, or a GitHub repository. Each variant carries only its own fields. +// Experimental: CatalogAgentPluginInstallSource is part of an experimental API and may +// change or be removed. +type CatalogAgentPluginInstallSource interface { + catalogAgentPluginInstallSource() + Kind() CatalogAgentPluginInstallSourceKind +} + +type RawCatalogAgentPluginInstallSourceData struct { + Discriminator CatalogAgentPluginInstallSourceKind + Raw json.RawMessage +} + +func (RawCatalogAgentPluginInstallSourceData) catalogAgentPluginInstallSource() {} +func (r RawCatalogAgentPluginInstallSourceData) Kind() CatalogAgentPluginInstallSourceKind { + return r.Discriminator +} + +// A plugin marketplace named by verified catalogue install data, installed as +// `pluginName@name`. +// Experimental: CatalogAgentPluginMarketplaceSource is part of an experimental API and may +// change or be removed. +type CatalogAgentPluginMarketplaceSource struct { + // Marketplace name. + Name string `json:"name"` + // Canonical lowercase GitHub owner/repository hosting the marketplace. + Repository string `json:"repository"` +} + +func (CatalogAgentPluginMarketplaceSource) catalogAgentPluginInstallSource() {} +func (CatalogAgentPluginMarketplaceSource) Kind() CatalogAgentPluginInstallSourceKind { + return CatalogAgentPluginInstallSourceKindMarketplace +} + +// Agent Finder's verified materialisation of the plugin. Present only when the candidate +// carries a complete materialisation reference; the reference itself stays runtime-private. +// Experimental: CatalogAgentPluginMaterializationSource is part of an experimental API and +// may change or be removed. +type CatalogAgentPluginMaterializationSource struct { +} + +func (CatalogAgentPluginMaterializationSource) catalogAgentPluginInstallSource() {} +func (CatalogAgentPluginMaterializationSource) Kind() CatalogAgentPluginInstallSourceKind { + return CatalogAgentPluginInstallSourceKindMaterialization +} + +// A GitHub repository holding the plugin, installed directly as `owner/repo[:path]` at the +// plan's commit, without a marketplace. +// Experimental: CatalogAgentPluginRepositorySource is part of an experimental API and may +// change or be removed. +type CatalogAgentPluginRepositorySource struct { + // Relative POSIX path of the plugin directory in the repository. Omitted when the plugin is + // at the repository root. + Path *string `json:"path,omitempty"` + // Canonical lowercase GitHub owner/repository holding the plugin. + Repository string `json:"repository"` +} + +func (CatalogAgentPluginRepositorySource) catalogAgentPluginInstallSource() {} +func (CatalogAgentPluginRepositorySource) Kind() CatalogAgentPluginInstallSourceKind { + return CatalogAgentPluginInstallSourceKindRepository +} + // 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. @@ -2150,7 +2339,14 @@ type CatalogAgentPluginCandidate struct { // Validated, normalised catalogue resource URN. This identity comes only from the catalog // identifier and is never inferred from display text or installed-plugin state. Identity string `json:"identity"` - // Canonical Agent Plugin media type. + // Whether this plugin can be installed in the selected session. Present only when the + // caller requires the agent-plugin-confirmed-installation capability. + Installability *CatalogAgentPluginInstallability `json:"installability,omitempty"` + // Reviewable install plan built only from verified catalogue install data. Present exactly + // when installability is `installable`; the runtime never derives it from the repository + // source, display text, or identity. + InstallPlan *CatalogAgentPluginInstallPlan `json:"installPlan,omitempty"` + // Media type naming the plugin's format. MediaType CatalogAgentPluginMediaType `json:"mediaType"` // Where the Agent Plugin catalog reference was observed, without its descriptor, URL, raw // data, or content digest. @@ -2323,6 +2519,28 @@ type CatalogClientContract struct { RequiredCapabilities []string `json:"requiredCapabilities"` } +// An Agent Finder materialisation reference pinning one exact resource revision. +// +// It carries no origin or URL: the runtime always builds the descriptor URL on its fixed +// Agent +// Finder authority. It carries no display data either; every reviewed field comes from the +// verified descriptor and Skill files. +// Experimental: CatalogMaterializationReference is part of an experimental API and may +// change or be removed. +type CatalogMaterializationReference struct { + // Expected `sha256:` digest of the descriptor's bundle manifest. + BundleDigest string `json:"bundleDigest"` + // Expected `sha256:` digest of the canonical descriptor. + DescriptorDigest string `json:"descriptorDigest"` + // Agent Finder resource identifier, a positive decimal integer. + ResourceID string `json:"resourceId"` + // Agent Finder materialisation revision identifier, a positive decimal integer. This is not + // a Git commit. + RevisionID string `json:"revisionId"` + // Materialisation descriptor schema version, currently `1.0.0`. + SchemaVersion string `json:"schemaVersion"` +} + // 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. @@ -3683,6 +3901,26 @@ type ContentExclusionPathCheck struct { Path string `json:"path"` } +// Candidate content-exclusion policies to validate, as the raw JSON text a host received. +// The text is supplied unparsed so that a syntax error and a schema violation are both +// reported by the runtime's own validator rather than by the caller's JSON parser. +// Experimental: ContentExclusionValidatePoliciesRequest is part of an experimental API and +// may change or be removed. +type ContentExclusionValidatePoliciesRequest struct { + // Unparsed JSON text expected to hold an array of content-exclusion policies. + PoliciesJSON string `json:"policiesJson"` +} + +// The accepted policies, parsed and normalized. A rejected input is reported as an error +// rather than in this result. +// Experimental: ContentExclusionValidatePoliciesResult is part of an experimental API and +// may change or be removed. +type ContentExclusionValidatePoliciesResult struct { + // Validated policy objects in input order. Raw JSON preserves every field returned by the + // runtime's policy parser. + Policies []any `json:"policies"` +} + // A single large message currently in context. // Experimental: ContextHeaviestMessage is part of an experimental API and may change or be // removed. @@ -4951,6 +5189,126 @@ type ExternalToolTextResultForLlmContentResourceLinkIcon struct { Theme *ExternalToolTextResultForLlmContentResourceLinkIconTheme `json:"theme,omitempty"` } +// The declared shape of one feature flag, independent of any caller population. +// Experimental: FeatureFlagMetadata is part of an experimental API and may change or be +// removed. +// Internal: FeatureFlagMetadata is an internal SDK API and is not part of the public +// surface. +type FeatureFlagMetadata struct { + // The population the flag is declared for before any environment or user-settings override + // is applied. + // Internal: Availability is part of the SDK's internal API surface and is not intended for + // external use. + Availability FeatureFlagAvailability `json:"availability"` + // Whether the flag participates in the CAPI sanity suite. + CapiSanity bool `json:"capiSanity"` + // Text shown to a user choosing whether to turn the flag on in experimental mode. Absent + // when the flag is not user-facing. + ExperimentalDescription *string `json:"experimentalDescription,omitempty"` + // The online-experiment (ExP) flag this maps to, when the flag is assigned by an experiment + // rather than declared statically. + ExpFlag *string `json:"expFlag,omitempty"` + // Environment variable that overrides this flag's experiment assignment locally in the + // experiment service, not in `featureFlags.resolve`. Present exactly when `expFlag` is, and + // derived from it, so a caller reads a local override without reproducing the naming rule. + ExpFlagEnvVar *string `json:"expFlagEnvVar,omitempty"` + // The flag's name, matching the key it is listed under. + Name string `json:"name"` +} + +// Every feature flag this runtime declares, with the shape each one was declared with. +// Experimental: FeatureFlagsMetadataResult is part of an experimental API and may change or +// be removed. +// Internal: FeatureFlagsMetadataResult is an internal SDK API and is not part of the public +// surface. +type FeatureFlagsMetadataResult struct { + // Declared feature flags keyed by flag name. + // Internal: Flags is part of the SDK's internal API surface and is not intended for + // external use. + Flags map[string]FeatureFlagMetadata `json:"flags"` +} + +// Caller population and overrides for static feature-flag resolution, independent of +// experiment assignments and active runtime state. +// Experimental: FeatureFlagsResolveRequest is part of an experimental API and may change or +// be removed. +// Internal: FeatureFlagsResolveRequest is an internal SDK API and is not part of the public +// surface. +type FeatureFlagsResolveRequest struct { + // Resolve against this already-computed flag set rather than each flag's declared + // availability. Keyed by flag name. + BaseFlags map[string]bool `json:"baseFlags,omitzero"` + // User settings to apply, including `enabledFeatureFlags`, legacy `feature_flags.enabled`, + // and supported convenience settings. + Config any `json:"config,omitempty"` + // Environment for direct feature-flag overrides and `COPILOT_CLI_ENABLED_FEATURE_FLAGS`. + // Does not apply `COPILOT_EXP_*` experiment assignments. Defaults to this runtime process's + // own environment. + Environment map[string]string `json:"environment,omitzero"` + // Flags forced to a value after every other layer, keyed by flag name. + FlagOverrides map[string]bool `json:"flagOverrides,omitzero"` + // Whether the caller is in an experimental population. + IsExperimental bool `json:"isExperimental"` + // Whether the caller is GitHub or Microsoft staff. + IsStaff bool `json:"isStaff"` + // GitHub-staff membership. Omit when it was never recorded: a legacy staff account then + // counts as both staff populations and keeps every staff feature. + IsStaffGitHub *bool `json:"isStaffGithub,omitempty"` + // Microsoft-staff membership. Omitted the same way as `isStaffGitHub`. + IsStaffMicrosoft *bool `json:"isStaffMicrosoft,omitempty"` + // Whether the caller belongs to the team population used by feature availability. + IsTeam bool `json:"isTeam"` +} + +// The resolved feature flags, keyed by flag name. +// Experimental: FeatureFlagsResolveResult is part of an experimental API and may change or +// be removed. +type FeatureFlagsResolveResult map[string]bool + +// Caller population and startup inputs to resolve named online-experiment gates for. +// Carries no account: the runtime resolves the assignment for the authentication it already +// holds. +// Experimental: FeatureFlagsResolveStartupGatesRequest is part of an experimental API and +// may change or be removed. +// Internal: FeatureFlagsResolveStartupGatesRequest is an internal SDK API and is not part +// of the public surface. +type FeatureFlagsResolveStartupGatesRequest struct { + // User settings document whose `featureFlags` block is layered on top of the resolved set. + Config any `json:"config,omitempty"` + // Environment to read flag overrides from. Defaults to this runtime process's own + // environment. + Environment map[string]string `json:"environment,omitzero"` + // When this installation first launched, in milliseconds since the Unix epoch. Experiments + // that segment on installation age read it. Omit when the first launch was never recorded. + FirstLaunchAtMillis *float64 `json:"firstLaunchAtMillis,omitempty"` + // The feature flags to resolve. A name this runtime does not declare is reported as an + // error rather than as a disabled flag. + Flags []string `json:"flags"` + // Whether the caller is in an experimental population. + IsExperimental bool `json:"isExperimental"` + // Whether the caller is GitHub or Microsoft staff. + IsStaff bool `json:"isStaff"` + // GitHub-staff membership. Omit when it was never recorded: a legacy staff account then + // counts as both staff populations and keeps every staff feature. + IsStaffGitHub *bool `json:"isStaffGithub,omitempty"` + // Microsoft-staff membership. Omitted the same way as `isStaffGitHub`. + IsStaffMicrosoft *bool `json:"isStaffMicrosoft,omitempty"` + // Whether the caller belongs to a team plan. + IsTeam bool `json:"isTeam"` + // Whether the runtime may refresh the cached assignment over the network without an active + // session. The refresh never blocks this call: the answer comes from the cache either way, + // so a refresh only affects a later launch. + RefreshWithoutSession bool `json:"refreshWithoutSession"` + // Whether streamer mode is on, which withholds the populations an experiment would + // otherwise segment on. + StreamerMode bool `json:"streamerMode"` +} + +// The resolved gates, keyed by flag name. +// Experimental: FeatureFlagsResolveStartupGatesResult is part of an experimental API and +// may change or be removed. +type FeatureFlagsResolveStartupGatesResult map[string]bool + // Content filtering mode to apply to all tools, or a map of tool name to content filtering // mode. // Experimental: FilterMapping is part of an experimental API and may change or be removed. @@ -5016,6 +5374,59 @@ type FolderTrustCheckResult struct { Trusted bool `json:"trusted"` } +// A folder to check against a persisted trusted-folders list, and the configuration +// directory holding that list. +// Experimental: FolderTrustIsTrustedParams is part of an experimental API and may change or +// be removed. +// Internal: FolderTrustIsTrustedParams is an internal SDK API and is not part of the public +// surface. +type FolderTrustIsTrustedParams struct { + // Configuration directory to read the trusted-folders list from. Omit it to read the + // default location; supply it when the host has redirected its configuration, or the check + // answers from a list the host is not using. + ConfigDir *string `json:"configDir,omitempty"` + // Folder to check. A folder counts as trusted when it is itself listed, when an ancestor is + // listed, or when its git root is listed. + Path string `json:"path"` +} + +// The verdict of the `forceLoginOrgs` enterprise sign-in pin for one credential. A managed +// device may pin Copilot to an approved set of GitHub organizations so a developer cannot +// sign in with a personal — or another enterprise's — account and escape every control that +// keys off the signed-in identity. A host must refuse to proceed when `allowed` is false; +// the remaining fields explain the refusal rather than qualifying it. +// Experimental: ForceLoginOrgsDecision is part of an experimental API and may change or be +// removed. +// Internal: ForceLoginOrgsDecision is an internal SDK API and is not part of the public +// surface. +type ForceLoginOrgsDecision struct { + // Whether the client may proceed. True whenever no pin is in force, so an unmanaged host is + // never blocked. + Allowed bool `json:"allowed"` + // A stable kebab-case reason code when refused: `org-not-approved`, + // `unverifiable-credential`, `policy-undetermined`, `policy-invalid`, `not-signed-in`, or + // `membership-unknown`. Omitted when allowed. Treat an unrecognized code as a refusal + // rather than mapping it to an allow: a code this host has not been taught about still + // denied the credential. + Denial *string `json:"denial,omitempty"` + // Whether a pin applies at all. Distinguishes "allowed because unmanaged" from "allowed + // because the account is approved", which a diagnostics surface needs and which also + // decides whether a bring-your-own-key provider may serve inference. + Enforced bool `json:"enforced"` + // The approved organization the account matched, when allowed under a pin. + MatchedOrg *string `json:"matchedOrg,omitempty"` + // A developer-facing explanation of the refusal. Omitted when allowed. + Message *string `json:"message,omitempty"` + // Error-level detail from managed-policy discovery — which file failed to load, which field + // failed validation. Carried on the decision rather than left to the log because a refusal + // can exit the process before a log sink exists, which would discard the only actionable + // diagnostic a misconfigured managed device produces. + PolicyDiagnostics []string `json:"policyDiagnostics"` + // The approved organization logins, as configured. Enterprise configuration rather than + // user data, so it is safe to name in a message or a log line. + RequiredOrgs []string `json:"requiredOrgs"` +} + // The remote the checked-out branch tracks. // Experimental: GitCurrentBranchRemoteResult is part of an experimental API and may change // or be removed. @@ -5142,6 +5553,8 @@ type GitHubOwnersListResult struct { // listing only. Long-abandoned unused ids can be released by later allocations. // Experimental: GitHubOwnersRequestIDResult is part of an experimental API and may change // or be removed. +// Internal: GitHubOwnersRequestIDResult is an internal SDK API and is not part of the +// public surface. type GitHubOwnersRequestIDResult struct { // Request id to pass to `gitHubOwners.list` and, to abandon it, `gitHubOwners.cancel`. RequestID int64 `json:"requestId"` @@ -5389,8 +5802,6 @@ type GlobalStateLoadForConfigDirRequest struct { // Internal: GlobalStateLoadResult is an internal SDK API and is not part of the public // surface. type GlobalStateLoadResult struct { - // Whether the user has answered the prompt suggesting they install the desktop app. - AppInstallNudgeResponded *bool `json:"appInstallNudgeResponded,omitempty"` // Whether the app tip has been shown. AppTipShown *bool `json:"appTipShown,omitempty"` // Terminals the user has already been asked to set up, so the host does not ask twice. @@ -5769,6 +6180,12 @@ type HooksDiscoverRequest struct { // 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"` + // Explicit discovery-only file-hook roots, separate from callback hooks and filesystem + // grants. Available without ambient discovery or repository file-hook enablement, subject + // to managed/hook disables. Relative paths resolve against the request's effective working + // directory when supplied. Null and omission are equivalent; an empty array contributes no + // explicit roots. + HookDirectories []string `json:"hookDirectories,omitzero"` // 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. @@ -6412,6 +6829,37 @@ type InstructionDiscoveryPathList struct { Paths []InstructionDiscoveryPath `json:"paths"` } +// Experimental: InstructionsClearCacheResult is part of an experimental API and may change +// or be removed. +type InstructionsClearCacheResult struct { +} + +// Explicit discovery arguments for one location, supplied by the caller rather than derived +// from a session. +// Experimental: InstructionsDiscoverRepoSourcesRequest is part of an experimental API and +// may change or be removed. +// Internal: InstructionsDiscoverRepoSourcesRequest is an internal SDK API and is not part +// of the public surface. +type InstructionsDiscoverRepoSourcesRequest struct { + // Extra directories to scan alongside instruction directories configured in the runtime + // environment. + AdditionalDirs []string `json:"additionalDirs,omitzero"` + // Copilot configuration directory for user-level sources. An omitted or empty value uses + // the server's configuration home. + ConfigDir *string `json:"configDir,omitempty"` + // Working directory whose instruction sources should also be included. Omission adds no + // separate working-directory scope. + CwdPath *string `json:"cwdPath,omitempty"` + // When true, also discover instruction files nested below the location. + EnableChildInstructions *bool `json:"enableChildInstructions,omitempty"` + // When true, include the sources a command-line host reads. Hosts that are not the CLI omit + // it. + IsCLI *bool `json:"isCli,omitempty"` + // Repository location to discover. An empty string selects no repository scope; cwdPath can + // still supply working-directory sources. + Location string `json:"location"` +} + // Optional project paths to include in instruction discovery. // Experimental: InstructionsDiscoverRequest is part of an experimental API and may change // or be removed. @@ -6419,6 +6867,11 @@ type InstructionsDiscoverRequest struct { // 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. ExcludeHostInstructions *bool `json:"excludeHostInstructions,omitempty"` + // Explicit discovery-only instruction roots using existing SDK instruction formats, not + // filesystem grants. Available without ambient discovery, subject to instruction/managed + // disables. Relative paths resolve against the request's effective working directory when + // supplied. Null and omission are equivalent; an empty array contributes no explicit roots. + InstructionDirectories []string `json:"instructionDirectories,omitzero"` // 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). @@ -6432,6 +6885,11 @@ type InstructionsGetDiscoveryPathsRequest struct { // When true, omit the host's user-level instruction targets, leaving only repository // targets. For multitenant deployments (mirrors `discover`'s `excludeHostInstructions`). ExcludeHostInstructions *bool `json:"excludeHostInstructions,omitempty"` + // Explicit discovery-only instruction roots to enumerate, including configured missing + // directories. Relative paths resolve against the request's effective working directory + // when supplied. These are not filesystem grants or preferred creation targets. Null and + // omission are equivalent. + InstructionDirectories []string `json:"instructionDirectories,omitzero"` // Optional list of project directory paths. When omitted or empty, only the user-level // targets are returned. ProjectPaths []string `json:"projectPaths,omitzero"` @@ -6445,6 +6903,16 @@ type InstructionsGetSourcesResult struct { Sources []InstructionSource `json:"sources"` } +// The prompt the init command runs. +// Experimental: InstructionsInitPromptResult is part of an experimental API and may change +// or be removed. +// Internal: InstructionsInitPromptResult is an internal SDK API and is not part of the +// public surface. +type InstructionsInitPromptResult struct { + // Prompt text authoring repository instructions. + Prompt string `json:"prompt"` +} + // Loaded instruction source for a session, including path, content, category, location, // applicability, and optional description. // Experimental: InstructionSource is part of an experimental API and may change or be @@ -6712,6 +7180,16 @@ type LocalSessionMetadataValue struct { Summary *string `json:"summary,omitempty"` } +// Which stored location permissions were dropped because their directory is gone. +// Experimental: LocationPermissionsCleanupResult is part of an experimental API and may +// change or be removed. +// Internal: LocationPermissionsCleanupResult is an internal SDK API and is not part of the +// public surface. +type LocationPermissionsCleanupResult struct { + // Location keys whose grants were removed. Empty when every stored location still exists. + RemovedLocationKeys []string `json:"removedLocationKeys"` +} + // An account the host has signed in to, identified by the server it lives on and the login // it uses there. The same person can appear more than once when they use both github.com // and an Enterprise server. @@ -6787,6 +7265,70 @@ type ManagedMCPServerConfig struct { URL string `json:"url"` } +// Experimental: ManagedPermissionEvaluation is part of an experimental API and may change +// or be removed. +type ManagedPermissionEvaluation struct { + // Original input operation associated with this verdict. + Operation ManagedPermissionOperation `json:"operation"` + // Managed-policy decision only; never bypasses user permissions or other security controls. + Verdict ManagedPermissionVerdict `json:"verdict"` +} + +// Experimental: ManagedPermissionOperation is part of an experimental API and may change or +// be removed. +type ManagedPermissionOperation struct { + // Operation to evaluate. Currently only url is supported. + Kind ManagedPermissionOperationKind `json:"kind"` + // Absolute HTTP(S) URL with a host. Invalid URLs reject the entire evaluation batch. + URL string `json:"url"` +} + +// Reusable managed permission evaluation context. Treat permissions as runtime-owned policy +// data and retain it verbatim; it includes source-aware composition metadata. This is a +// snapshot, not a capability or approval token; only use contexts obtained from trusted +// policy sources. +// Experimental: ManagedPermissionsContext is part of an experimental API and may change or +// be removed. +type ManagedPermissionsContext struct { + // Policy could not be determined; evaluation must deny every operation. + FailClosed bool `json:"failClosed"` + // Runtime-owned composed permissions object. Absent when no permission policy is configured. + Permissions any `json:"permissions,omitempty"` +} + +// Managed policy context and ordered operations for pure, sessionless evaluation. +// Experimental: ManagedPermissionsEvaluateRequest is part of an experimental API and may +// change or be removed. +type ManagedPermissionsEvaluateRequest struct { + // Trusted, reusable managed permission policy snapshot; no policy is discovered during + // evaluation. + Context ManagedPermissionsContext `json:"context"` + // Operations to evaluate, preserving input order and duplicates. An empty batch is valid. + Operations []ManagedPermissionOperation `json:"operations"` +} + +// Ordered managed permission verdicts and the supplied policy's fail-closed posture. +// Experimental: ManagedPermissionsEvaluateResult is part of an experimental API and may +// change or be removed. +type ManagedPermissionsEvaluateResult struct { + // Managed permission policy could not be determined; every operation is denied. + FailClosed bool `json:"failClosed"` + // One verdict per input operation, in the same order, including duplicates. + Results []ManagedPermissionEvaluation `json:"results"` +} + +// Retry outcome for one plugin required by managed settings. +// Experimental: ManagedPluginRetryEntry is part of an experimental API and may change or be +// removed. +type ManagedPluginRetryEntry struct { + // Why the plugin was not prepared. Present when `status` is `failed` or `deferred`. + Error *string `json:"error,omitempty"` + // Plugin spec in "plugin@marketplace" form. + Spec string `json:"spec"` + // What the retry did for this plugin. + Status ManagedPluginRetryStatus `json:"status"` +} + // Lock state and provenance of one managed setting. // Experimental: ManagedSettingMeta is part of an experimental API and may change or be // removed. @@ -6803,6 +7345,31 @@ type ManagedSettingMeta struct { Source string `json:"source"` } +// A user settings document and the effective managed policy whose controlled entries should +// be stamped onto it. +// Experimental: ManagedSettingsApplyControlsParams is part of an experimental API and may +// change or be removed. +// Internal: ManagedSettingsApplyControlsParams is an internal SDK API and is not part of +// the public surface. +type ManagedSettingsApplyControlsParams struct { + // The effective managed policy, as composed by `managedSettings.resolveEffectiveValue`. + // Omit it on an unmanaged host: the settings then come back unchanged. + Managed any `json:"managed,omitempty"` + // The user settings document to stamp. Entries the managed layer does not name are returned + // exactly as supplied. + Settings any `json:"settings"` +} + +// The user settings with the managed layer's controlled entries stamped on. +// Experimental: ManagedSettingsApplyControlsResult is part of an experimental API and may +// change or be removed. +// Internal: ManagedSettingsApplyControlsResult is an internal SDK API and is not part of +// the public surface. +type ManagedSettingsApplyControlsResult struct { + // The resulting settings document. + SettingsJSON any `json:"settingsJson"` +} + // Experimental: ManagedSettingsClearCacheResult is part of an experimental API and may // change or be removed. type ManagedSettingsClearCacheResult struct { @@ -6865,6 +7432,151 @@ type ManagedSettingsDiagnostic struct { Severity ManagedSettingsDiagnosticSeverity `json:"severity"` } +// Inputs describing the host and account an ingestion should resolve managed policy for. +// Experimental: ManagedSettingsIngestParams is part of an experimental API and may change +// or be removed. +// Internal: ManagedSettingsIngestParams is an internal SDK API and is not part of the +// public surface. +type ManagedSettingsIngestParams struct { + // The authenticated identity to resolve policy for. Omitted before sign-in, in which case + // no server layer is fetched. + AuthInfo any `json:"authInfo,omitempty"` + // Whether to ignore a cached server policy when reading, forcing the resolution to come + // from a live fetch. + BypassCacheRead *bool `json:"bypassCacheRead,omitempty"` + // Name of the embedding client, reported to the managed-settings service as `client_id` so + // it can refuse to deliver a policy this client is too old to enforce. Omit it and the + // runtime reports a generic identity; the runtime version sent alongside is the gate that + // actually matters. + ClientName *string `json:"clientName,omitempty"` + // A device layer supplied by the host instead of discovered from the machine. Pair it with + // `hasDeviceResponse` so an explicit absence is distinguishable from an omitted override. + DeviceResponse any `json:"deviceResponse,omitempty"` + // The host's environment variables, read for cache location and policy overrides. Supplied + // by the host rather than read from the runtime process so a command that selected its own + // configuration home is honored. + Environment any `json:"environment"` + // Whether `deviceResponse` is an override at all. True with a null `deviceResponse` asserts + // the device layer is empty; false discovers it from the machine. + HasDeviceResponse *bool `json:"hasDeviceResponse,omitempty"` + // The host's home directory, used to resolve cache and managed-file locations. + HomeDirectory string `json:"homeDirectory"` + // The host's platform, in Node's `process.platform` spelling, used to locate + // platform-specific cache and policy paths. + Platform string `json:"platform"` + // Whether a configured platform policy helper may be run to produce an enforced helper + // layer. + PolicyHelperEnabled *bool `json:"policyHelperEnabled,omitempty"` + // The policy-helper invocation reason. Defaults to sessionStart. + // Internal: PolicyHelperReason is part of the SDK's internal API surface and is not + // intended for external use. + PolicyHelperReason *ManagedSettingsPolicyHelperReason `json:"policyHelperReason,omitempty"` + // Directory the policy helper runs in when this ingestion is not owned by a registered + // session. A host resolving policy before its session exists must pass this, or a + // directory-sensitive helper judges the wrong repository. + PolicyHelperWorkingDirectory *string `json:"policyHelperWorkingDirectory,omitempty"` + // Whether to run ingestion. Omit or set false to return an empty result without discovering + // or fetching policy. + SelfFetch *bool `json:"selfFetch,omitempty"` + // How this ingestion may use the on-disk server-policy cache. Defaults to the runtime's own + // policy when omitted. + // Internal: ServerPolicyCacheMode is part of the SDK's internal API surface and is not + // intended for external use. + ServerPolicyCacheMode *ManagedSettingsServerPolicyCacheMode `json:"serverPolicyCacheMode,omitempty"` + // The session whose credential store and working directory the policy helper should use. + // Omit it when no session exists yet and the process-wide store applies. + SessionID *string `json:"sessionId,omitempty"` + // User agent to send with the policy fetch. + UserAgent string `json:"userAgent"` +} + +// The managed policy layers an ingestion resolved, together with the per-layer outcome +// flags a host needs to decide how much to trust the result. The flags are reported rather +// than folded into the settings because a host must be able to distinguish an unmanaged +// device from a managed device whose policy could not be confirmed, and apply its +// fail-closed gates in the second case. +// Experimental: ManagedSettingsIngestResult is part of an experimental API and may change +// or be removed. +// Internal: ManagedSettingsIngestResult is an internal SDK API and is not part of the +// public surface. +type ManagedSettingsIngestResult struct { + // The device layer could not be read, so it is unconfirmed rather than known-absent. + DeviceLoadFailed bool `json:"deviceLoadFailed"` + // The device layer discovered from the machine, when one exists. + DeviceResponse any `json:"deviceResponse,omitempty"` + // The sandbox floor contributed by the device layer. + DeviceSandboxFloor any `json:"deviceSandboxFloor,omitempty"` + // Whether the device layer's sandbox intent could not be determined, so a consumer must not + // read its absence as permission to run unsandboxed. + DeviceSandboxUndetermined bool `json:"deviceSandboxUndetermined"` + // A device-managed policy is known to exist. + DeviceSourceFound bool `json:"deviceSourceFound"` + // Diagnostics emitted while resolving the layers, in the order they occurred. + // Internal: LogMessages is part of the SDK's internal API surface and is not intended for + // external use. + LogMessages []ManagedSettingsLogMessage `json:"logMessages"` + // The effective managed permissions layer, when the resolved policy carries one. + Permissions any `json:"permissions,omitempty"` + // Which channel registered the policy helper that produced the helper layer. + // Internal: PolicyHelperAuthority is part of the SDK's internal API surface and is not + // intended for external use. + PolicyHelperAuthority *ManagedSettingsPolicyHelperAuthority `json:"policyHelperAuthority,omitempty"` + // A policy helper is configured for this host. + PolicyHelperConfigured bool `json:"policyHelperConfigured"` + // The policy helper's failure must be treated as fail-closed rather than ignored. + PolicyHelperFailClosed bool `json:"policyHelperFailClosed"` + // The policy helper was configured but could not be run or its output could not be read. + PolicyHelperLoadFailed bool `json:"policyHelperLoadFailed"` + // The layer produced by the platform policy helper, when one ran. + PolicyHelperResponse any `json:"policyHelperResponse,omitempty"` + // A live fetch failed but a recent cached entry was served in its place, so + // `serverFetchFailed` stays false. Lets a consumer distinguish a clean fetch from a + // cache-backed fallback. + ServerCacheFallback bool `json:"serverCacheFallback"` + // Minimum client version requested by a successful response. Advisory only; the delivered + // policy remains active. + ServerClientUpdateRequested *string `json:"serverClientUpdateRequested,omitempty"` + // The server refused to deliver a policy because this client is too old to enforce it, + // carrying the user-facing sentence describing the refusal. No retry will help; the remedy + // is a client update. Gates that exist only because of managed policy should deny on this + // rather than falling back to their unmanaged default. + ServerClientUpdateRequired *string `json:"serverClientUpdateRequired,omitempty"` + // The error that made the server fetch fail, when one did. + ServerFetchError *string `json:"serverFetchError,omitempty"` + // The server fetch failed and no cached entry stood in for it, so the server layer is + // unconfirmed and fail-closed gates apply. + ServerFetchFailed bool `json:"serverFetchFailed"` + // The server fetch failed while a policy demanded a fresh confirmation, which is the strict + // posture an enterprise opted into rather than an ordinary transient failure. + ServerForcedRefreshFailure bool `json:"serverForcedRefreshFailure"` + // The synthesized fail-closed permissions layer applied in place of a policy a refused + // client may not enforce. + ServerPermissionsFloor any `json:"serverPermissionsFloor,omitempty"` + // How the server layer was resolved. + // Internal: ServerResolution is part of the SDK's internal API surface and is not intended + // for external use. + ServerResolution ManagedSettingsServerPolicyResolution `json:"serverResolution"` + // The account's server-delivered policy, when one was resolved. + ServerResponse any `json:"serverResponse,omitempty"` + // The server fetch failed in a way a later retry could succeed at, as opposed to a refusal + // that no retry will change. + ServerRetryableFailure bool `json:"serverRetryableFailure"` + // The sandbox object from a cached server policy that a forced refresh declined to serve in + // full. Folded back into the effective sandbox layer so an outage during a forced refresh + // still synthesizes the fail-closed sandbox floor from the administrator's known intent. + ServerSandboxFloor any `json:"serverSandboxFloor,omitempty"` + // A server-managed policy is known to exist even though `serverResponse` is intentionally + // unset. Consumers should OR this with the presence of `serverResponse` when deciding + // whether the device is managed, so a fresh start during an outage still applies the + // fail-closed gates instead of treating the device as unmanaged. + ServerSourceFound bool `json:"serverSourceFound"` + // No usable token was available, so no server fetch was attempted. + ServerTokenUnavailable bool `json:"serverTokenUnavailable"` + // The effective managed settings composed from every resolved layer. Omitted when no layer + // set anything, so an unmanaged host locks nothing. + Settings any `json:"settings,omitempty"` +} + // One managed-settings channel and the document it delivered. // Experimental: ManagedSettingsLayer is part of an experimental API and may change or be // removed. @@ -6878,6 +7590,80 @@ type ManagedSettingsLayer struct { Source string `json:"source"` } +// Inputs describing the host and account whose managed remote-control policy should be +// resolved. Mirrors `managedSettings.ingest`, and deliberately carries no retention +// decision: reading this gate never becomes this runtime process's startup policy. +// Experimental: ManagedSettingsLoadRemoteControlParams is part of an experimental API and +// may change or be removed. +// Internal: ManagedSettingsLoadRemoteControlParams is an internal SDK API and is not part +// of the public surface. +type ManagedSettingsLoadRemoteControlParams struct { + // The authenticated identity to resolve policy for. Omitted before sign-in, in which case + // no server layer is fetched. + AuthInfo any `json:"authInfo,omitempty"` + // Name of the embedding client, reported to the managed-settings service as `client_id` so + // it can refuse to deliver a policy this client is too old to enforce. That refusal is + // exactly what turns into an explicit `disabled` below, so a host that omits this field + // forfeits the protection. + ClientName *string `json:"clientName,omitempty"` + // A device layer supplied by the host instead of discovered from the machine. Pair it with + // `hasDeviceResponse` so an explicit absence is distinguishable from an omitted override. + DeviceResponse any `json:"deviceResponse,omitempty"` + // The host's environment variables, read for cache location and policy overrides. + Environment any `json:"environment"` + // Whether `deviceResponse` is an override at all. True with a null `deviceResponse` asserts + // the device layer is empty; false discovers it from the machine. + HasDeviceResponse *bool `json:"hasDeviceResponse,omitempty"` + // The host's home directory, used to resolve cache and managed-file locations. + HomeDirectory string `json:"homeDirectory"` + // The host's platform, in Node's `process.platform` spelling, used to locate + // platform-specific cache and policy paths. + Platform string `json:"platform"` + // Whether a configured platform policy helper may be run to produce an enforced helper + // layer. + PolicyHelperEnabled *bool `json:"policyHelperEnabled,omitempty"` + // The policy-helper invocation reason. Defaults to sessionStart. + // Internal: PolicyHelperReason is part of the SDK's internal API surface and is not + // intended for external use. + PolicyHelperReason *ManagedSettingsPolicyHelperReason `json:"policyHelperReason,omitempty"` + // Whether to run ingestion. Omit or set false to return an empty result without discovering + // or fetching policy. + SelfFetch *bool `json:"selfFetch,omitempty"` + // User agent to send with the policy fetch. + UserAgent string `json:"userAgent"` +} + +// The managed remote-control policy in force for an account, if any. An absent policy means +// the account is unmanaged for this capability, which a host reads as allowed — so absence +// here is a positive statement that no policy applies, never a stand-in for a resolution +// that failed. A resolution that cannot be completed is rejected instead. +// Experimental: ManagedSettingsLoadRemoteControlResult is part of an experimental API and +// may change or be removed. +// Internal: ManagedSettingsLoadRemoteControlResult is an internal SDK API and is not part +// of the public surface. +type ManagedSettingsLoadRemoteControlResult struct { + // The account's `remoteControl` policy document. Absent when no managed policy governs + // remote control for this account. Reported as an explicit disabled policy, rather than as + // absence, when the service refused to deliver a policy this client is too old to enforce. + RemoteControl any `json:"remoteControl,omitempty"` +} + +// One diagnostic emitted while ingesting managed settings. Returned rather than logged by +// the runtime so the host decides where ingestion diagnostics go, which matters because an +// ingestion runs before a session exists to carry a log sink. +// Experimental: ManagedSettingsLogMessage is part of an experimental API and may change or +// be removed. +// Internal: ManagedSettingsLogMessage is an internal SDK API and is not part of the public +// surface. +type ManagedSettingsLogMessage struct { + // Severity of this diagnostic. + // Internal: Level is part of the SDK's internal API surface and is not intended for + // external use. + Level ManagedSettingsLogLevel `json:"level"` + // Human-readable diagnostic text. + Message string `json:"message"` +} + // Per-key lock state and provenance for `ManagedSettingsValues`, with the same field names. // Producers emit each typed key in values and meta together; both outer objects are omitted // when no typed key is set. @@ -6894,6 +7680,59 @@ type ManagedSettingsMeta struct { Model *ManagedSettingMeta `json:"model,omitempty"` } +// The client-supplied managed settings to validate and normalize. Omitting them is valid +// and yields no policy. +// Experimental: ManagedSettingsParseClientValueParams is part of an experimental API and +// may change or be removed. +// Internal: ManagedSettingsParseClientValueParams is an internal SDK API and is not part of +// the public surface. +type ManagedSettingsParseClientValueParams struct { + // The managed settings a client is supplying for a session it is about to open. Omit them + // when the client is imposing no policy of its own. + Settings any `json:"settings,omitempty"` +} + +// The accepted managed settings, reduced to the keys a client is allowed to set. +// Experimental: ManagedSettingsParseClientValueResult is part of an experimental API and +// may change or be removed. +// Internal: ManagedSettingsParseClientValueResult is an internal SDK API and is not part of +// the public surface. +type ManagedSettingsParseClientValueResult struct { + // The normalized settings. Omitted when the client supplied none. An accepted document that + // sets nothing comes back as an empty object, which is distinct from omission. + SettingsJSON any `json:"settingsJson,omitempty"` +} + +// Ordered managed permission verdicts and the supplied policy's fail-closed posture. +// Experimental: ManagedSettingsPermissionsEvaluateResult is part of an experimental API and +// may change or be removed. +type ManagedSettingsPermissionsEvaluateResult struct { + // Managed permission policy could not be determined; every operation is denied. + FailClosed bool `json:"failClosed"` + // One verdict per input operation, in the same order, including duplicates. + Results []ManagedPermissionEvaluation `json:"results"` +} + +// The device-managed settings layer, with the sandbox floor and the load outcome a host +// needs to compose it into an effective policy. +// Experimental: ManagedSettingsReadDeviceResult is part of an experimental API and may +// change or be removed. +// Internal: ManagedSettingsReadDeviceResult is an internal SDK API and is not part of the +// public surface. +type ManagedSettingsReadDeviceResult struct { + // Whether discovery or validation failed. A host must treat this as "policy unknown" rather + // than "no policy": absent settings with this set false mean the device genuinely has none. + LoadFailed bool `json:"loadFailed"` + // Sandbox floor read from the discovered document before validation, so a document that is + // otherwise rejected can still raise the floor it declared. + SandboxFloor any `json:"sandboxFloor,omitempty"` + // Validated, canonical device-managed settings. Omitted when no device policy was + // discovered, when the discovered document failed validation, or when it validated to an + // empty object — an empty document carries no policy, and reporting it as present would + // outrank layers that do. + Settings any `json:"settings,omitempty"` +} + // Validated device-managed settings discovered before a session exists. // Experimental: ManagedSettingsReadResult is part of an experimental API and may change or // be removed. @@ -6905,6 +7744,53 @@ type ManagedSettingsReadResult struct { SettingsJSON any `json:"settingsJson,omitempty"` } +// The bypass-permissions state a host holds today, the account now signed in, and what the +// managed-policy resolution said. +// Experimental: ManagedSettingsResolveBypassDisableTransitionParams is part of an +// experimental API and may change or be removed. +// Internal: ManagedSettingsResolveBypassDisableTransitionParams is an internal SDK API and +// is not part of the public surface. +type ManagedSettingsResolveBypassDisableTransitionParams struct { + // The account signed in now. A `managed` disable pinned to a different account is lifted. + ActiveIdentity string `json:"activeIdentity"` + // The account a `managed` disable is pinned to. Omit it when the current source is not + // `managed`; it is ignored for the other sources. + CurrentManagedIdentity *string `json:"currentManagedIdentity,omitempty"` + // Which layer currently disables bypass mode. Omit it when nothing does. + // Internal: CurrentSource is part of the SDK's internal API surface and is not intended for + // external use. + CurrentSource *ManagedSettingsBypassDisableSource `json:"currentSource,omitempty"` + // What the resolution said about bypass mode. Send `unknown` rather than omitting the field + // when there is no verdict. + // Internal: Policy is part of the SDK's internal API surface and is not intended for + // external use. + Policy ManagedSettingsBypassPolicyResolution `json:"policy"` +} + +// The bypass-permissions state a host should hold after the resolution, and what it must do +// to get there. +// Experimental: ManagedSettingsResolveBypassDisableTransitionResult is part of an +// experimental API and may change or be removed. +// Internal: ManagedSettingsResolveBypassDisableTransitionResult is an internal SDK API and +// is not part of the public surface. +type ManagedSettingsResolveBypassDisableTransitionResult struct { + // The account the returned `managed` source is pinned to. Store it alongside `source`; the + // next call needs it to decide whether an account switch lifts the disable. + ManagedIdentity *string `json:"managedIdentity,omitempty"` + // The value to store in the host's own bypass-disabled flag. Omitted means leave it exactly + // as it is, which is not the same as being told to store `false`. + SetDisabledState *bool `json:"setDisabledState,omitempty"` + // Which layer disables bypass mode after this resolution. Omitted when none does, which is + // what a lifted disable looks like. + // Internal: Source is part of the SDK's internal API surface and is not intended for + // external use. + Source *ManagedSettingsBypassDisableSource `json:"source,omitempty"` + // Whether a bypass-permissions session already running must be turned off. True only when + // this resolution newly disables bypass mode, so a host that is already compliant is not + // interrupted. + SuppressRuntimeAllowAll bool `json:"suppressRuntimeAllowAll"` +} + // Effective enterprise managed settings and contributing channels. Session events report // applied policy; sessionless resolve reports an account/device snapshot, and compose // reports a non-applying preview of candidate documents. Device values take precedence over @@ -6914,16 +7800,18 @@ type ManagedSettingsReadResult struct { // Experimental: ManagedSettingsResolvedData is part of an experimental API and may change // or be removed. type ManagedSettingsResolvedData struct { - // Whether enterprise policy disables bypass-permissions ("yolo") mode for this session. - // Deny-wins across layers, and forced on when `failClosed` is true. + // Whether an explicit enterprise policy restriction disables bypass-permissions ("yolo") + // mode for this session. Deny-wins across layers; an unresolved policy does not force this + // on by itself. BypassPermissionsDisabled bool `json:"bypassPermissionsDisabled"` // 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. + // Whether managed policy could not be determined (e.g. a failed server fetch) and + // unresolved-policy safeguards remain active. This does not by itself disable + // bypass-permissions; `bypassPermissionsDisabled` reports only an explicit policy + // restriction. 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. @@ -6931,6 +7819,10 @@ type ManagedSettingsResolvedData struct { // 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"` + // Reusable retained managed permission policy for managedSettings.permissions.evaluate, + // including source composition and session-local injection. Provided by live session + // snapshots; absent in older events. Refresh the context when managed policy changes. + PermissionsContext *ManagedPermissionsContext `json:"permissionsContext,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"` @@ -6950,6 +7842,54 @@ type ManagedSettingsResolvedData struct { Source ManagedSettingsResolvedSource `json:"source"` } +// The credential and environment to evaluate the device-channel sign-in pin against. +// Experimental: ManagedSettingsResolveDeviceForceLoginOrgsGateParams is part of an +// experimental API and may change or be removed. +// Internal: ManagedSettingsResolveDeviceForceLoginOrgsGateParams is an internal SDK API and +// is not part of the public surface. +type ManagedSettingsResolveDeviceForceLoginOrgsGateParams struct { + // The authenticated identity to judge. Omitted when signed out, which is the case the + // device channel exists to serve: it can refuse a first sign-in before any credential + // exists. + AuthInfo any `json:"authInfo,omitempty"` + // The host's environment variables. Read for the variables that would make an identity's + // membership snapshot forgeable, so a credential whose provenance cannot be trusted is + // refused rather than accepted. + Environment any `json:"environment"` +} + +// The managed-settings layers to compose into an effective policy. Every layer is optional; +// omitting all of them yields no effective policy. +// Experimental: ManagedSettingsResolveEffectiveValueParams is part of an experimental API +// and may change or be removed. +// Internal: ManagedSettingsResolveEffectiveValueParams is an internal SDK API and is not +// part of the public surface. +type ManagedSettingsResolveEffectiveValueParams struct { + // The device sandbox floor, folded into the device layer under `sandbox` before composition. + DeviceSandboxFloor any `json:"deviceSandboxFloor,omitempty"` + // The device layer: native MDM and managed-file settings describing the machine rather than + // the account. + Mdm any `json:"mdm,omitempty"` + // The policy-helper layer, an enforced channel sourced from a platform policy helper + // process. + PolicyHelper any `json:"policyHelper,omitempty"` + // The account's server-delivered org policy, as returned by a managed-settings fetch. + // Omitted before sign-in or when the fetch has not landed. + Server any `json:"server,omitempty"` +} + +// The effective managed policy composed from the supplied layers. +// Experimental: ManagedSettingsResolveEffectiveValueResult is part of an experimental API +// and may change or be removed. +// Internal: ManagedSettingsResolveEffectiveValueResult is an internal SDK API and is not +// part of the public surface. +type ManagedSettingsResolveEffectiveValueResult struct { + // The composed effective managed settings. Omitted when no layer sets anything, so an + // unmanaged host locks nothing. Carries `sandboxAdvisoryFloorDeclined: true` when an + // advisory server force-on was dropped because this host cannot run the sandbox. + SettingsJSON any `json:"settingsJson,omitempty"` +} + // Experimental: ManagedSettingsResolveRequest is part of an experimental API and may change // or be removed. type ManagedSettingsResolveRequest struct { @@ -6985,6 +7925,11 @@ type ManagedSettingsResolveResult struct { Layers []ManagedSettingsLayer `json:"layers"` // Per-key lock state and provenance for the entries in `values`, using the same key names. Meta *ManagedSettingsMeta `json:"meta,omitempty"` + // Reusable source-composed permission policy for managedSettings.permissions.evaluate. + // Unlike resolved.settings.permissions, this retains every source's allowlist and + // default-prompt semantics. Refresh through resolve when account/device policy changes. + // Absent on runtimes that do not support permission evaluation. + PermissionsContext *ManagedPermissionsContext `json:"permissionsContext,omitempty"` // Effective managed settings from the device and account (server) channels, in the same // shape as `session.managedSettings.get`, excluding session-local injection. Resolved ManagedSettingsResolvedData `json:"resolved"` @@ -6995,6 +7940,197 @@ type ManagedSettingsResolveResult struct { Values *ManagedSettingsValues `json:"values,omitempty"` } +// Inputs describing the host and account a startup policy resolution should run for. +// Mirrors `managedSettings.ingest`, and adds the decision of whether the resolved policy is +// retained for this runtime process. +// Experimental: ManagedSettingsResolveStartupPolicyParams is part of an experimental API +// and may change or be removed. +// Internal: ManagedSettingsResolveStartupPolicyParams is an internal SDK API and is not +// part of the public surface. +type ManagedSettingsResolveStartupPolicyParams struct { + // The authenticated identity to resolve policy for. Omitted before sign-in, in which case + // no server layer is fetched and the sign-in pin is evaluated against no credential. + AuthInfo any `json:"authInfo,omitempty"` + // Whether to ignore a cached server policy when reading, forcing the resolution to come + // from a live fetch. + BypassCacheRead *bool `json:"bypassCacheRead,omitempty"` + // Name of the embedding client, reported to the managed-settings service as `client_id` so + // it can refuse to deliver a policy this client is too old to enforce. + ClientName *string `json:"clientName,omitempty"` + // A device layer supplied by the host instead of discovered from the machine. Pair it with + // `hasDeviceResponse` so an explicit absence is distinguishable from an omitted override. + DeviceResponse any `json:"deviceResponse,omitempty"` + // The host's environment variables, read for cache location, policy overrides, and the + // variables that would make a membership snapshot forgeable. + Environment any `json:"environment"` + // Whether `deviceResponse` is an override at all. True with a null `deviceResponse` asserts + // the device layer is empty; false discovers it from the machine. + HasDeviceResponse *bool `json:"hasDeviceResponse,omitempty"` + // The host's home directory, used to resolve cache and managed-file locations. + HomeDirectory string `json:"homeDirectory"` + // The host's platform, in Node's `process.platform` spelling, used to locate + // platform-specific cache and policy paths. + Platform string `json:"platform"` + // Whether a configured platform policy helper may be run to produce an enforced helper + // layer. + PolicyHelperEnabled *bool `json:"policyHelperEnabled,omitempty"` + // The policy-helper invocation reason. Defaults to sessionStart. + // Internal: PolicyHelperReason is part of the SDK's internal API surface and is not + // intended for external use. + PolicyHelperReason *ManagedSettingsPolicyHelperReason `json:"policyHelperReason,omitempty"` + // Directory the policy helper runs in when this resolution is not owned by a registered + // session. A host resolving policy before its session exists must pass this, or a + // directory-sensitive helper judges the wrong repository. + PolicyHelperWorkingDirectory *string `json:"policyHelperWorkingDirectory,omitempty"` + // Whether to retain the resolved policy as this runtime process's startup policy, so later + // work in the process enforces it. Set false to resolve without side effects, which is what + // a protocol host wants before session creation: its sessions apply the complete policy + // through the normal lifecycle, and retaining here would let a readiness credential affect + // unrelated sessions. The returned verdict is identical either way — this decides only + // whether the process keeps it. + RetainAndApply bool `json:"retainAndApply"` + // Whether to run ingestion. Omit or set false to return an empty result without discovering + // or fetching policy. + SelfFetch *bool `json:"selfFetch,omitempty"` + // How this resolution may use the on-disk server-policy cache. Defaults to the runtime's + // own policy when omitted. Rejected when combined with `bypassCacheRead`, because the two + // express the same decision and accepting both would leave the caller unsure which one the + // runtime honored. + // Internal: ServerPolicyCacheMode is part of the SDK's internal API surface and is not + // intended for external use. + ServerPolicyCacheMode *ManagedSettingsServerPolicyCacheMode `json:"serverPolicyCacheMode,omitempty"` + // The session whose credential store, working directory and client kind the resolution + // should use. Omit it when no session exists yet and the process-wide store applies. A + // resolution whose session names a credential other than `authInfo` is returned but not + // retained, so a stale sign-in cannot apply policy for an account that has already been + // switched away from. + SessionID *string `json:"sessionId,omitempty"` + // User agent to send with the policy fetch. + UserAgent string `json:"userAgent"` +} + +// The startup policy a resolution composed, the per-layer outcome flags a host needs to +// decide how much to trust it, and the enterprise sign-in verdict for the credential it ran +// against. The flags are reported rather than folded into the settings because a host must +// distinguish an unmanaged device from a managed device whose policy could not be +// confirmed, and apply its fail-closed gates in the second case. +// Experimental: ManagedSettingsResolveStartupPolicyResult is part of an experimental API +// and may change or be removed. +// Internal: ManagedSettingsResolveStartupPolicyResult is an internal SDK API and is not +// part of the public surface. +type ManagedSettingsResolveStartupPolicyResult struct { + // Advisory minimum client version from a successful server response. + ClientUpdateRequested *string `json:"clientUpdateRequested,omitempty"` + // The server refused to deliver a policy because this client is too old to enforce it, + // carrying the user-facing sentence describing the refusal. No retry will help; the remedy + // is a client update. + ClientUpdateRequired *string `json:"clientUpdateRequired,omitempty"` + // The device layer could not be read, so it is unconfirmed rather than known-absent. + DeviceLoadFailed bool `json:"deviceLoadFailed"` + // The device layer discovered from the machine, when one exists. + DeviceResponse any `json:"deviceResponse,omitempty"` + // The sandbox floor contributed by the device layer. + DeviceSandboxFloor any `json:"deviceSandboxFloor,omitempty"` + // The enterprise sign-in verdict for the credential this resolution ran against, evaluated + // over the effective policy so a server-delivered pin is honored alongside a device one. + // Computed on every resolution, including one that was not retained, so a host that + // declines to apply policy still cannot proceed past a pin. + // Internal: ForceLoginOrgs is part of the SDK's internal API surface and is not intended + // for external use. + ForceLoginOrgs ForceLoginOrgsDecision `json:"forceLoginOrgs"` + // A managed policy is known to apply to this host, from a discovered source or a resolved + // document. + ManagedPolicyPresent bool `json:"managedPolicyPresent"` + // The permissions layer specifically could not be confirmed, so permission gates apply + // their fail-closed posture. + PermissionPolicyUndetermined bool `json:"permissionPolicyUndetermined"` + // The policy helper was configured but could not be run, or its output could not be read. + PolicyHelperLoadFailed bool `json:"policyHelperLoadFailed"` + // The layer produced by the platform policy helper, when one ran. + PolicyHelperResponse any `json:"policyHelperResponse,omitempty"` + // Some layer of the policy could not be confirmed, so its absence must not be read as + // permission. Folds in a failed server fetch as well as an unreadable device layer. + PolicyUndetermined bool `json:"policyUndetermined"` + // The sandbox was enabled because policy could not be confirmed rather than because an + // administrator asked for it. Lets a host explain a sandbox the user did not configure. + SandboxEnabledByUndeterminedPolicy bool `json:"sandboxEnabledByUndeterminedPolicy"` + // The effective sandbox floor composed across every layer, which a host must apply rather + // than its own default. + SandboxFloor any `json:"sandboxFloor,omitempty"` + // The server fetch failed and no cached entry stood in for it, so the server layer is + // unconfirmed and fail-closed gates apply. + ServerFetchFailed bool `json:"serverFetchFailed"` + // How the current runtime resolved the server layer. + // Internal: ServerResolution is part of the SDK's internal API surface and is not intended + // for external use. + ServerResolution ManagedSettingsServerPolicyResolution `json:"serverResolution"` + // The account's server-delivered policy, when one was resolved. + ServerResponse any `json:"serverResponse,omitempty"` + // Telemetry describing which layers answered and how, for the host to report alongside its + // own startup events. + Telemetry any `json:"telemetry"` + // Warnings raised while resolving, in the order they occurred. + Warnings []string `json:"warnings"` +} + +// The managed layers to retain as this process's startup policy, with the outcome of +// fetching each one. +// Experimental: ManagedSettingsRetainPolicyParams is part of an experimental API and may +// change or be removed. +// Internal: ManagedSettingsRetainPolicyParams is an internal SDK API and is not part of the +// public surface. +type ManagedSettingsRetainPolicyParams struct { + // Whether the device load failed. Report it honestly, for the same reason as + // `serverFetchFailed`. + DeviceLoadFailed bool `json:"deviceLoadFailed"` + // The device/MDM policy layer. + DeviceResponse any `json:"deviceResponse,omitempty"` + // Sandbox floor declared by the device layer, carried separately so a document that failed + // validation can still raise the floor. + DeviceSandboxFloor any `json:"deviceSandboxFloor,omitempty"` + // Whether the device sandbox intent is unreadable independently of its other settings. + DeviceSandboxUndetermined bool `json:"deviceSandboxUndetermined"` + // Whether a device policy source demonstrably exists. This, not a read error, is the + // evidence a device is managed, so a fail-closed policy gates on it. + DeviceSourceFound bool `json:"deviceSourceFound"` + // Whether any managed policy source exists, independently of fetch failures. + ManagedPolicyPresent bool `json:"managedPolicyPresent"` + // Whether permission policy is undetermined. Sandbox-only uncertainty does not imply this + // flag. + PermissionPolicyUndetermined bool `json:"permissionPolicyUndetermined"` + // The policy helper's response: the third managed layer that a process owning its startup + // policy resolves. It is retained with the other two so that every ambient read in this + // process, including the mutation guards and the governed marketplace allowlist that the + // `plugins.*` handlers derive, sees the whole policy the process fetched. A session-owning + // process omits it, because it resolves a helper per session against that session's working + // directory. + PolicyHelperResponse any `json:"policyHelperResponse,omitempty"` + // Preserve the retained server layer during a device-only resolution. + PreserveServerState bool `json:"preserveServerState"` + // Whether uncertainty alone enabled the shared sandbox floor. + SandboxEnabledByUndeterminedPolicy bool `json:"sandboxEnabledByUndeterminedPolicy"` + // Whether the shared sandbox policy is undetermined. Diagnostic errors alone do not imply + // this flag. + SandboxFloorUndetermined bool `json:"sandboxFloorUndetermined"` + // Whether the server fetch failed. Report it honestly: true keeps the already-retained + // server layer rather than replacing it with an absent one. + ServerFetchFailed bool `json:"serverFetchFailed"` + // Whether a required fresh server-policy confirmation failed. + ServerForcedRefreshFailure bool `json:"serverForcedRefreshFailure"` + // The resolved server policy. A known source can lack a response after a forced-refresh + // failure. + ServerResponse any `json:"serverResponse,omitempty"` + // Whether the server policy source exists, even when its response is unavailable. + ServerSourceFound bool `json:"serverSourceFound"` + // Shared server/device sandbox floor, excluding session-local helper policy. + SharedSandboxFloor any `json:"sharedSandboxFloor,omitempty"` +} + +// Experimental: ManagedSettingsRetainPolicyResult is part of an experimental API and may +// change or be removed. +type ManagedSettingsRetainPolicyResult struct { +} + // The authoring JSON schema for managed settings recognized by this runtime. // Experimental: ManagedSettingsSchemaResult is part of an experimental API and may change // or be removed. @@ -7493,6 +8629,8 @@ type MCPConfiguredServerState struct { // Credential-free authentication identity used to configure GitHub MCP. // Experimental: MCPConfigureGitHubRequest is part of an experimental API and may change or // be removed. +// Internal: MCPConfigureGitHubRequest is an internal SDK API and is not part of the public +// surface. type MCPConfigureGitHubRequest struct { // Opaque runtime auth info for GitHub MCP configuration. Marked internal: an in-process // runtime shape (configureGitHubMcp is a no-op over the wire). @@ -8301,6 +9439,17 @@ type MCPIsServerRunningResult struct { Running bool `json:"running"` } +// Names of the MCP servers the runtime supplies itself. +// Experimental: MCPListBuiltinServerNamesResult is part of an experimental API and may +// change or be removed. +// Internal: MCPListBuiltinServerNamesResult is an internal SDK API and is not part of the +// public surface. +type MCPListBuiltinServerNamesResult struct { + // Every built-in server name. A host disabling the built-ins wholesale suppresses exactly + // this set. + Names []string `json:"names"` +} + // Server name whose tool list should be returned. // Experimental: MCPListToolsRequest is part of an experimental API and may change or be // removed. @@ -9213,6 +10362,8 @@ type MCPPromptsListResult struct { // Registration parameters for an external MCP client. // Experimental: MCPRegisterExternalClientRequest is part of an experimental API and may // change or be removed. +// Internal: MCPRegisterExternalClientRequest is an internal SDK API and is not part of the +// public surface. type MCPRegisterExternalClientRequest struct { // In-process MCP Client instance. Marked internal: cannot be serialized across the JSON-RPC // boundary. @@ -9233,8 +10384,80 @@ type MCPRegisterExternalClientRequest struct { Transport any `json:"transport"` } +// Registry search to abandon. +// Experimental: MCPRegistryCancelRequest is part of an experimental API and may change or +// be removed. +// Internal: MCPRegistryCancelRequest is an internal SDK API and is not part of the public +// surface. +type MCPRegistryCancelRequest struct { + // Request ID from `mcp.registry.allocateRequestId` that the search uses. + RequestID int64 `json:"requestId"` +} + +// Whether the cancel reached a live search. +// Experimental: MCPRegistryCancelResult is part of an experimental API and may change or be +// removed. +// Internal: MCPRegistryCancelResult is an internal SDK API and is not part of the public +// surface. +type MCPRegistryCancelResult struct { + // True when the cancel stopped a running search. False for unknown or reclaimed IDs, + // completed or canceled searches, and unused reservations. The cancel releases an unused + // reservation. + Canceled bool `json:"canceled"` +} + +// Request id naming a cancellable registry search. +// Experimental: MCPRegistryRequestIDResult is part of an experimental API and may change or +// be removed. +// Internal: MCPRegistryRequestIDResult is an internal SDK API and is not part of the public +// surface. +type MCPRegistryRequestIDResult struct { + // Request ID for `mcp.registry.search` and `mcp.registry.cancel`. It serves one search. + // Allocation can reclaim unused IDs at the 1,024-ID reservation limit. + RequestID int64 `json:"requestId"` +} + +// Registry search terms, the credential to search under, and the request id that makes the +// search cancellable. +// Experimental: MCPRegistrySearchRequest is part of an experimental API and may change or +// be removed. +// Internal: MCPRegistrySearchRequest is an internal SDK API and is not part of the public +// surface. +type MCPRegistrySearchRequest struct { + // The credential the search runs under, carried opaquely. Hosts usually send + // credential-free `AuthIdentity`; a `token` identity can be resolved only when it embeds a + // token, while `env` and `gh-cli` identities can use a token embedded in the request first. + AuthInfo any `json:"authInfo"` + // Maximum number of servers to return. + Limit int64 `json:"limit"` + // Free-text query. Omitted or empty asks the registry for its top servers rather than + // searching. A value that is not a string is refused. + Query *string `json:"query,omitempty"` + // Repository used for the policy lookup, as `owner/name`. The policy selects the registry + // URL and whether the user token goes to the registry. The registry receives this + // repository only when the policy entry lists it as required context. A value that is not a + // string is refused. + Repository *string `json:"repository,omitempty"` + // Request ID from `mcp.registry.allocateRequestId`. The search refuses unknown, reclaimed, + // or canceled IDs and IDs that another search or request already uses. + RequestID int64 `json:"requestId"` +} + +// Servers selected from the registry response. +// Experimental: MCPRegistrySearchResult is part of an experimental API and may change or be +// removed. +// Internal: MCPRegistrySearchResult is an internal SDK API and is not part of the public +// surface. +type MCPRegistrySearchResult struct { + // The server objects, carried opaquely. The runtime follows pages, keeps the newest entry + // per server name, cuts the list to `limit`, and sorts an empty-query result by GitHub + // stars. Each server object remains unchanged because the registry owns that shape. + Servers any `json:"servers"` +} + // In-process MCP reload configuration. // Experimental: MCPReloadConfig is part of an experimental API and may change or be removed. +// Internal: MCPReloadConfig is an internal SDK API and is not part of the public surface. type MCPReloadConfig struct { ActiveGitHubToken *string `json:"activeGitHubToken,omitempty"` // Server names the CLI enabled for this session via `--enable-mcp-server`. @@ -9255,6 +10478,8 @@ type MCPReloadConfig struct { // Opaque MCP reload configuration. // Experimental: MCPReloadWithConfigRequest is part of an experimental API and may change or // be removed. +// Internal: MCPReloadWithConfigRequest is an internal SDK API and is not part of the public +// surface. type MCPReloadWithConfigRequest struct { // Opaque runtime MCP reload configuration. Marked internal: an in-process runtime shape // (reloadMcpServers throws over the wire). @@ -9760,6 +10985,8 @@ 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. +// Internal: MCPServerConfigMemory is an internal SDK API and is not part of the public +// surface. type MCPServerConfigMemory struct { // Configuration warnings recorded while loading the server. ConfigWarnings []string `json:"configWarnings,omitzero"` @@ -9880,6 +11107,38 @@ type MCPSetEnvValueModeResult struct { Mode MCPSetEnvValueModeDetails `json:"mode"` } +// The session tool filters that decide whether the session still has a shell to run `gh` +// with. +// Experimental: MCPShouldExcludeGitHubToolsRequest is part of an experimental API and may +// change or be removed. +// Internal: MCPShouldExcludeGitHubToolsRequest is an internal SDK API and is not part of +// the public surface. +type MCPShouldExcludeGitHubToolsRequest struct { + // The session's tool allowlist, when it set one. Omitted means the session constrains + // nothing this way. + AvailableTools []string `json:"availableTools,omitzero"` + // The session's tool denylist, when it set one. Omitted means the session constrains + // nothing this way. + ExcludedTools []string `json:"excludedTools,omitzero"` + // How the allowlist and denylist combine when both are set. Omitted means the default every + // session gets, so a caller that never chose a precedence is answered as its sessions + // behave. + ToolFilterPrecedence *OptionsUpdateToolFilterPrecedence `json:"toolFilterPrecedence,omitempty"` +} + +// Whether the gh-replaceable GitHub MCP tools may be clipped for the session described by +// the request. +// Experimental: MCPShouldExcludeGitHubToolsResult is part of an experimental API and may +// change or be removed. +// Internal: MCPShouldExcludeGitHubToolsResult is an internal SDK API and is not part of the +// public surface. +type MCPShouldExcludeGitHubToolsResult struct { + // True only when both halves hold: the session's filters still reach the platform shell + // tool, and the host actually has the `gh` those tools would be replaced by. Feed it + // straight back as the `excludeGhReplaceableTools` build option. + ExcludeGhReplaceableTools bool `json:"excludeGhReplaceableTools"` +} + // Concrete configuration file containing an MCP server declaration. // Experimental: MCPSourceFile is part of an experimental API and may change or be removed. type MCPSourceFile struct { @@ -10006,6 +11265,8 @@ type MCPUninstallPlan struct { // Server name identifying the external client to remove. // Experimental: MCPUnregisterExternalClientRequest is part of an experimental API and may // change or be removed. +// Internal: MCPUnregisterExternalClientRequest is an internal SDK API and is not part of +// the public surface. type MCPUnregisterExternalClientRequest struct { // Server name of the external client to unregister ServerName string `json:"serverName"` @@ -10546,6 +11807,68 @@ type ModelClearStartupSeedResult struct { Cleared bool `json:"cleared"` } +// Host presentation state the reasoning-summaries policy depends on. +// Experimental: ModelGetReasoningSummariesRequest is part of an experimental API and may +// change or be removed. +type ModelGetReasoningSummariesRequest struct { + // The host's persisted `showReasoning` user setting, omitted when unset. + PersistedShowReasoning *bool `json:"persistedShowReasoning,omitempty"` + // Whether the host is currently showing reasoning summaries. Defaults to false. + SummariesVisible *bool `json:"summariesVisible,omitempty"` +} + +// Reasoning-summaries presentation policy for the session's current model. +// Experimental: ModelGetReasoningSummariesResult is part of an experimental API and may +// change or be removed. +type ModelGetReasoningSummariesResult struct { + // Whether the supplied `persistedShowReasoning` setting only restates the model default. + // This reports a redundancy, not a standing instruction to delete the setting: it stays + // true for as long as the setting and the model default agree, including for a user who + // later chose that value deliberately. It exists for a one-time migration of a legacy + // setting written before the model default made it redundant, so a host that acts on it + // owns the cleanup and must gate it behind its own persisted `done` marker, delete only a + // setting whose value still matches the one it read, and never repeat the deletion on a + // later launch. Under an off-by-default reasoning-summaries experiment an explicit + // `showReasoning: true` opt-in also reads as redundant on a summary-producing model, so an + // ungated host would keep deleting that opt-in and hide summaries again on every launch. + ClearRedundantSetting bool `json:"clearRedundantSetting"` + // Whether the session's reasoning-summaries experiment assignment has settled. False means + // this read does not wait for a pending assignment and reports the experiment-derived + // fields conservatively: `showHiddenHint` is false and `visibility` falls back to + // `persistedShowReasoning`, or is omitted when none was supplied. A host that wants the + // assigned answer re-reads until this is true. + ExperimentSettled bool `json:"experimentSettled"` + // Whether the selected model shows reasoning summaries by default. + ModelDefaultShowsSummaries bool `json:"modelDefaultShowsSummaries"` + // False until the session has a model catalog and a selected model. This read never + // resolves the catalog itself, and the runtime emits no readiness notification, so + // repeating this call alone leaves `ready` false indefinitely on a session that has neither + // listed models nor run a turn. `session.model.list` is the prerequisite and the signal: it + // resolves and caches the catalog for the session, so a successful return means the next + // read is ready. The exception is a session that resolves models without a Copilot catalog, + // that is a BYOK registry or an alternate provider: `session.model.list` returns an empty + // list without caching a catalog, so `ready` stays false and this policy is unavailable + // there. While false, every model-policy field is false and `visibility` is omitted; + // `experimentSettled` is independent of readiness and still reports the assignment state. + Ready bool `json:"ready"` + // Whether turns on this session produce reasoning summaries, honouring an explicit session + // reasoning-summary override. + SessionShowsSummaries bool `json:"sessionShowsSummaries"` + // Whether to show the hint that reasoning summaries are hidden. Always false while + // `experimentSettled` is false. + ShowHiddenHint bool `json:"showHiddenHint"` + // Whether the selected model emits reasoning headers. + ShowReasoningHeaders bool `json:"showReasoningHeaders"` + // Default reasoning visibility, and only meaningful once `ready` is true: false when the + // session produces no summaries, else the persisted setting, else the experiment's default. + // While `ready` is true it is omitted only when all three are unavailable, that is when the + // session produces summaries, no `persistedShowReasoning` was supplied, and + // `experimentSettled` is false. While `ready` is false there is no model policy to report + // and this field is always omitted, so an absent value alone does not imply a pending + // experiment. + Visibility *bool `json:"visibility,omitempty"` +} + // List of Copilot models available to the resolved user, including capabilities and billing // metadata. // Experimental: ModelList is part of an experimental API and may change or be removed. @@ -12147,6 +13470,12 @@ type PermissionRecoveryData struct { // Policy selected from the current client's response capability; mode or client changes may // update it during recovery OnBlocked PermissionRecoveryOnBlocked `json:"onBlocked"` + // Subagent identifier that owns this recovery episode when the request was delegated + // through a parent permission service + OwnerAgentID *string `json:"ownerAgentId,omitempty"` + // Root or child execution-session identifier that owns this recovery episode; omitted for + // legacy snapshots + OwnerSessionID *string `json:"ownerSessionId,omitempty"` // Controlled reason for the latest episode transition Reason PermissionRecoveryReason `json:"reason"` // Current lifecycle state of the recovery episode @@ -12260,6 +13589,32 @@ type PermissionsConfigureResult struct { Success bool `json:"success"` } +// The verbatim managed `permissions` slice to interpret. +// Experimental: PermissionsEvaluateManagedPolicyParams is part of an experimental API and +// may change or be removed. +// Internal: PermissionsEvaluateManagedPolicyParams is an internal SDK API and is not part +// of the public surface. +type PermissionsEvaluateManagedPolicyParams struct { + // The `permissions` object from a managed settings layer, for example + // `{"disableBypassPermissionsMode":"disable"}`. Omitted or null means no managed policy + // applies. + Permissions any `json:"permissions,omitempty"` +} + +// The policy questions a host asks before granting elevated permissions. +// Experimental: PermissionsEvaluateManagedPolicyResult is part of an experimental API and +// may change or be removed. +type PermissionsEvaluateManagedPolicyResult struct { + // True when managed policy forbids bypass-permissions mode, so allow-all style flags must + // not take effect. + BypassPermissionsDisabled bool `json:"bypassPermissionsDisabled"` + // True when the managed layer supplies permission rules that the session must enforce. + PermissionRulesActive bool `json:"permissionRulesActive"` + // True when a managed policy exists but this build could not determine its meaning. Hosts + // fail closed on it rather than assuming permission was granted. + PolicyUndetermined bool `json:"policyUndetermined"` +} + // Indicates whether the operation succeeded. // Experimental: PermissionsFolderTrustAddTrustedResult is part of an experimental API and // may change or be removed. @@ -12639,6 +13994,31 @@ type PermissionURLsSetUnrestrictedModeParams struct { Enabled bool `json:"enabled"` } +// A complete JSON snapshot to write under the runtime's per-path file lock. +// Experimental: PersistenceWriteRawJSONLockedRequest is part of an experimental API and may +// change or be removed. +type PersistenceWriteRawJSONLockedRequest struct { + // The serialized JSON document to write. It replaces the file's contents entirely; nothing + // is merged. + DataJSON string `json:"dataJson"` + // Comment lines to emit above the document, for a format that tolerates them. Omit for + // strict JSON. + FileHeader *string `json:"fileHeader,omitempty"` + // Absolute path of the file to replace. Parent directories are created when missing. + Path string `json:"path"` +} + +// The outcome of a locked snapshot write. +// Experimental: PersistenceWriteRawJSONLockedResult is part of an experimental API and may +// change or be removed. +type PersistenceWriteRawJSONLockedResult struct { + // Why the write failed. Absent when `success` is true. A lock the runtime could not acquire + // in time reports here rather than raising. + Error *string `json:"error,omitempty"` + // Whether the file now holds the requested contents. + Success bool `json:"success"` +} + // Optional message to echo back to the caller. // Experimental: PingRequest is part of an experimental API and may change or be removed. type PingRequest struct { @@ -13261,6 +14641,30 @@ type ProviderModelConfig struct { WireModel *string `json:"wireModel,omitempty"` } +// An authoritative monthly usage reading for the observation's provider, account, and +// service. +// Experimental: ProviderMonthlyUsage is part of an experimental API and may change or be +// removed. +type ProviderMonthlyUsage struct { + // Nonnegative finite consumption reported by the service, preserving zero and fractions. + // Present only when state is available; never computed from tokens or balance differences. + ConsumedQuantity *float64 `json:"consumedQuantity,omitempty"` + // Start of the service-reported monthly billing cycle, in UTC RFC 3339. + CycleStart *string `json:"cycleStart,omitempty"` + // Service read time in UTC RFC 3339, not a ledger reconciliation watermark or confirmation + // of the latest inference charge. + QueriedAt *string `json:"queriedAt,omitempty"` + // End of the service-reported monthly billing cycle, in UTC RFC 3339. + ResetOn *string `json:"resetOn,omitempty"` + // Identity scope of the reading within its provider and service. + Scope ProviderMonthlyUsageScope `json:"scope"` + // Availability of monthly consumption. Missing or unavailable usage must never be + // interpreted as zero. + State ProviderMonthlyUsageState `json:"state"` + // Unit of consumedQuantity; independent of the parent observation's balance unit. + Unit ProviderQuotaUnit `json:"unit"` +} + // Authoritative budget measurements and policy metadata, independent of provider. // Experimental: ProviderQuotaBudgetMetadata is part of an experimental API and may change // or be removed. @@ -13319,6 +14723,9 @@ type ProviderQuotaState struct { HasQuota *bool `json:"hasQuota,omitempty"` // HTTP status from acquisition, when available. HTTPStatus *int64 `json:"httpStatus,omitempty"` + // Service-reported monthly consumption, independent of quota balances and per-call or + // session cost. Omitted when the service does not report monthly usage. + MonthlyUsage *ProviderMonthlyUsage `json:"monthlyUsage,omitempty"` // Whether this is a GET account reading or a pre-response admission observation. // Observations are never merged across kinds. ObservationKind *ProviderQuotaObservationKind `json:"observationKind,omitempty"` @@ -14456,6 +15863,100 @@ type RemoteSessionRepository struct { Owner string `json:"owner"` } +// One atomic edit of an allowed top-level repository setting. +// Experimental: RepoSettingsEditRequest is part of an experimental API and may change or be +// removed. +// Internal: RepoSettingsEditRequest is an internal SDK API and is not part of the public +// surface. +type RepoSettingsEditRequest struct { + // One canonical top-level repository setting that the allowlist permits. + Key string `json:"key"` + // Set a scalar, add or remove a string list item, or remove the setting. The handler + // rejects a missing or incompatible value. + // Internal: Operation is part of the SDK's internal API surface and is not intended for + // external use. + Operation SettingsEditOperation `json:"operation"` + // Absolute path of the repository root. + RepoRoot string `json:"repoRoot"` + // Shared or local repository settings file. + // Internal: Scope is part of the SDK's internal API surface and is not intended for + // external use. + Scope RepoSettingsEditScope `json:"scope"` + // Required boolean, string or number for set, required string for add-item and remove-item, + // and forbidden for remove. The runtime rejects all other JSON values. + Value any `json:"value,omitempty"` +} + +// Which repository settings file to read. +// Experimental: RepoSettingsReadRawRequest is part of an experimental API and may change or +// be removed. +// Internal: RepoSettingsReadRawRequest is an internal SDK API and is not part of the public +// surface. +type RepoSettingsReadRawRequest struct { + // Absolute path of the repository root. + RepoRoot string `json:"repoRoot"` + // Use repo for shared settings or local for settings.local.json. Other scopes are rejected. + Scope string `json:"scope"` +} + +// Parsed repository settings without normalization or layering. +// Experimental: RepoSettingsReadRawResult is part of an experimental API and may change or +// be removed. +// Internal: RepoSettingsReadRawResult is an internal SDK API and is not part of the public +// surface. +type RepoSettingsReadRawResult struct { + // Stored JSON values after the existing JSONC parser and prototype-safety checks. Empty + // when the file is missing, blank, or invalid. + Config any `json:"config"` + // True when the file exists but did not parse, so `config` is empty for that reason rather + // than because nothing is set. + Invalid bool `json:"invalid"` +} + +// The complete set of repository-overridable settings to store. +// Experimental: RepoSettingsWriteAllRequest is part of an experimental API and may change +// or be removed. +// Internal: RepoSettingsWriteAllRequest is an internal SDK API and is not part of the +// public surface. +type RepoSettingsWriteAllRequest struct { + // Absolute path of the repository root. + RepoRoot string `json:"repoRoot"` + // Use repo for shared settings or local for settings.local.json. Other scopes are rejected. + Scope string `json:"scope"` + // Object of allowed settings to store. Missing allowlisted keys are removed. Other input + // keys are ignored, while unknown stored keys are preserved. + Settings any `json:"settings"` +} + +// Experimental: RepoSettingsWriteAllResult is part of an experimental API and may change or +// be removed. +type RepoSettingsWriteAllResult struct { +} + +// One key to write or remove in a repository settings file. +// Experimental: RepoSettingsWriteKeyRequest is part of an experimental API and may change +// or be removed. +// Internal: RepoSettingsWriteKeyRequest is an internal SDK API and is not part of the +// public surface. +type RepoSettingsWriteKeyRequest struct { + // True to remove the key instead of writing it. + Delete *bool `json:"delete,omitempty"` + // Canonical top-level key permitted by the repository settings allowlist. + Key string `json:"key"` + // Absolute path of the repository root. + RepoRoot string `json:"repoRoot"` + // Use repo for shared settings or local for settings.local.json. Other scopes are rejected. + Scope string `json:"scope"` + // Value to validate and write. Required unless delete is true; a null value is validated, + // not treated as deletion. + Value any `json:"value,omitempty"` +} + +// Experimental: RepoSettingsWriteKeyResult is part of an experimental API and may change or +// be removed. +type RepoSettingsWriteKeyResult struct { +} + // Experimental: ResponseFormat is part of an experimental API and may change or be removed. type ResponseFormat struct { // JSON Schema and provider options for the turn's output. @@ -14469,6 +15970,30 @@ type ResponseFormat struct { type RuntimeShutdownResult struct { } +// The stored sandbox settings to resolve against the defaults. +// Experimental: SandboxApplyDefaultsRequest is part of an experimental API and may change +// or be removed. +type SandboxApplyDefaultsRequest struct { + // The sandbox settings as stored in the settings file. An empty object resolves to the + // defaults alone. + Stored any `json:"stored"` +} + +// True when the command stays available while enforcement is blocked. +// Experimental: SandboxCheckEnforcementCommandResult is part of an experimental API and may +// change or be removed. +type SandboxCheckEnforcementCommandResult bool + +// True when an OS-level sandbox can run on this host. +// Experimental: SandboxCheckPlatformSupportResult is part of an experimental API and may +// change or be removed. +type SandboxCheckPlatformSupportResult bool + +// True when the sandbox on this host can reach host loopback directly. +// Experimental: SandboxCheckWindowsHostLoopbackSupportResult is part of an experimental API +// and may change or be removed. +type SandboxCheckWindowsHostLoopbackSupportResult bool + // Resolved sandbox configuration. // Experimental: SandboxConfig is part of an experimental API and may change or be removed. type SandboxConfig struct { @@ -14658,6 +16183,29 @@ type SandboxCredentialsConfig struct { EnvVars map[string]SandboxMaskedEnvVar `json:"envVars"` } +// A possible secret-bearing environment variable and optional locally suggested HTTPS +// injection hosts. This is a draft for user review, not an active grant. +// Experimental: SandboxCredentialSuggestion is part of an experimental API and may change +// or be removed. +type SandboxCredentialSuggestion struct { + // Environment variable name. The secret value is never returned. + Name string `json:"name"` + // HTTPS hostnames suggested by a local known-provider mapping. An empty list requires the + // user to supply injection hosts before adding a masking entry. These suggestions do not + // allow network access. + SuggestedInjectHosts []string `json:"suggestedInjectHosts"` +} + +// Possible unconfigured secrets in the sandbox shell environment, sorted by variable name, +// with no secret values. +// Experimental: SandboxCredentialSuggestionsResult is part of an experimental API and may +// change or be removed. +type SandboxCredentialSuggestionsResult struct { + // Candidates for user review. Empty when sandboxing is disabled or no unconfigured + // candidates are present. + Suggestions []SandboxCredentialSuggestion `json:"suggestions"` +} + // Request to disable sandboxing for the current session while resolving an active // sandbox-bypass permission prompt. // Experimental: SandboxDisableForSessionRequest is part of an experimental API and may @@ -14680,6 +16228,52 @@ type SandboxDisableForSessionResult struct { Success bool `json:"success"` } +// The documentation URL a host cites when it declines or downgrades a sandbox setting. +// Experimental: SandboxDocsURLResult is part of an experimental API and may change or be +// removed. +type SandboxDocsURLResult struct { + // Documentation URL describing the sandbox and the settings that control it. + URL string `json:"url"` +} + +// A sandbox config and the per-feature routing flag to resolve it for. +// Experimental: SandboxEffectiveForOptionalParams is part of an experimental API and may +// change or be removed. +// Internal: SandboxEffectiveForOptionalParams is an internal SDK API and is not part of the +// public surface. +type SandboxEffectiveForOptionalParams struct { + // The routing flag naming the feature being launched, such as `sandboxMcpServers` or + // `sandboxLspServers`. + RoutingFlag string `json:"routingFlag"` + // The session's resolved `sandbox` slice. Omitted when the host has none, which yields a + // disabled sandbox rather than an error. + Sandbox any `json:"sandbox,omitempty"` +} + +// The sandbox a named feature actually launches under. +// Experimental: SandboxEffectiveForOptionalResult is part of an experimental API and may +// change or be removed. +// Internal: SandboxEffectiveForOptionalResult is an internal SDK API and is not part of the +// public surface. +type SandboxEffectiveForOptionalResult struct { + // The effective sandbox config for the requested feature, or a disabled sandbox when the + // feature is routed out of it. + SandboxJSON any `json:"sandboxJson"` +} + +// One slash command, as typed, to test against the enforcement-block allowlist. +// Experimental: SandboxEnforcementCommandAllowedParams is part of an experimental API and +// may change or be removed. +// Internal: SandboxEnforcementCommandAllowedParams is an internal SDK API and is not part +// of the public surface. +type SandboxEnforcementCommandAllowedParams struct { + // The command's arguments, in order. Pass an empty array for a bare command: several + // entries in the allowlist admit the bare form and refuse every argument. + Args []string `json:"args"` + // The command name as the user typed it, with or without its leading slash. + CommandName string `json:"commandName"` +} + // Managed sandbox enforcement state for a session. // Experimental: SandboxEnforcementStatus is part of an experimental API and may change or // be removed. @@ -14692,6 +16286,43 @@ type SandboxEnforcementStatus struct { Required bool `json:"required"` } +// A batch of file-picker candidates to check against a prepared policy. +// Experimental: SandboxFilterPickerCandidatesParams is part of an experimental API and may +// change or be removed. +// Internal: SandboxFilterPickerCandidatesParams is an internal SDK API and is not part of +// the public surface. +type SandboxFilterPickerCandidatesParams struct { + // The candidates to check, in the order the answers are returned. + // Internal: Candidates is part of the SDK's internal API surface and is not intended for + // external use. + Candidates []SandboxPickerCandidate `json:"candidates"` + // The session's working directory, matching the one the policy was prepared from. + Cwd string `json:"cwd"` + // The policy returned by `sandbox.preparePickerPolicy`. Omitted keeps every candidate. + Sandbox any `json:"sandbox,omitempty"` +} + +// Per-candidate allow decisions, positionally aligned with the request. +// Experimental: SandboxFilterPickerCandidatesResult is part of an experimental API and may +// change or be removed. +// Internal: SandboxFilterPickerCandidatesResult is an internal SDK API and is not part of +// the public surface. +type SandboxFilterPickerCandidatesResult struct { + // One decision per candidate, in the same order. Always the same length as the request's + // `candidates`. + Allowed []bool `json:"allowed"` +} + +// The user-facing refusal, including the platform remedy and the documentation link. +// Experimental: SandboxGetUnsupportedMessageResult is part of an experimental API and may +// change or be removed. +type SandboxGetUnsupportedMessageResult string + +// The user-facing notice that a saved sandbox setting is inert for this session. +// Experimental: SandboxGetUnsupportedSettingDisabledNoticeResult is part of an experimental +// API and may change or be removed. +type SandboxGetUnsupportedSettingDisabledNoticeResult string + // Request to accept the sandbox path grant offered on an active sandbox escalation // permission prompt. // Experimental: SandboxGrantPathForRequestRequest is part of an experimental API and may @@ -14795,6 +16426,56 @@ type SandboxMaskedEnvVar struct { InjectHosts []string `json:"injectHosts"` } +// One file-picker suggestion offered up for the sandbox filesystem check. +// Experimental: SandboxPickerCandidate is part of an experimental API and may change or be +// removed. +// Internal: SandboxPickerCandidate is an internal SDK API and is not part of the public +// surface. +type SandboxPickerCandidate struct { + // Whether the suggestion is a directory. A directory the policy does not itself allow may + // still be needed as a waypoint to an allowed subtree. + IsDirectory bool `json:"isDirectory"` + // Absolute path the suggestion would attach. + Path string `json:"path"` +} + +// The working directory and sandbox settings to build a file-picker policy from. +// Experimental: SandboxPreparePickerPolicyParams is part of an experimental API and may +// change or be removed. +// Internal: SandboxPreparePickerPolicyParams is an internal SDK API and is not part of the +// public surface. +type SandboxPreparePickerPolicyParams struct { + // The session's working directory. Git discovery and grant canonicalization are resolved + // against it. + Cwd string `json:"cwd"` + // The session's resolved `sandbox` slice. Omitted, or disabled, yields no policy and every + // candidate is kept. + Sandbox any `json:"sandbox,omitempty"` +} + +// The prepared file-picker policy, or none when the sandbox is disabled. +// Experimental: SandboxPreparePickerPolicyResult is part of an experimental API and may +// change or be removed. +// Internal: SandboxPreparePickerPolicyResult is an internal SDK API and is not part of the +// public surface. +type SandboxPreparePickerPolicyResult struct { + // The prepared policy to pass back to `sandbox.filterPickerCandidates`. Absent when the + // sandbox is disabled or absent, in which case filtering keeps every candidate. + SandboxJSON any `json:"sandboxJson,omitempty"` +} + +// True when this process can offer installing the sandbox credential-proxy certificate +// authority into OS trust. +// Experimental: SandboxProxyCaCheckInstallationSupportResult is part of an experimental API +// and may change or be removed. +type SandboxProxyCaCheckInstallationSupportResult bool + +// True when the sandbox configuration uses the credential proxy and can need +// certificate-authority setup. +// Experimental: SandboxProxyCaCheckRelevanceResult is part of an experimental API and may +// change or be removed. +type SandboxProxyCaCheckRelevanceResult bool + // Result of creating the persistent certificate authority of the sandbox credential proxy. // Experimental: SandboxProxyCaCreateResult is part of an experimental API and may change or // be removed. @@ -14830,6 +16511,108 @@ type SandboxProxyCaStatus struct { State SandboxProxyCaState `json:"state"` } +// Experimental: SandboxProxySecretDeleteResult is part of an experimental API and may +// change or be removed. +type SandboxProxySecretDeleteResult struct { +} + +// The stored sandbox proxy password, if one is present. +// Experimental: SandboxProxySecretGetResult is part of an experimental API and may change +// or be removed. +type SandboxProxySecretGetResult struct { + // The stored password. Absent when no entry exists in either the keychain or the on-disk + // fallback store. + Value *string `json:"value,omitempty"` +} + +// Where this runtime keeps the sandbox proxy password. +// Experimental: SandboxProxySecretLocation is part of an experimental API and may change or +// be removed. +type SandboxProxySecretLocation struct { + // Absolute path of the credential store holding the sandbox proxy password. The proxy + // password belongs to no MCP server, so it is kept in its own directory rather than in the + // MCP secret store. + ConfigDir string `json:"configDir"` + // Identifier of the proxy password entry within that store. Derived from the configuration + // directory, so two Copilot homes on one machine never share an entry. Use it to recognize + // a `${secret:…}` reference in stored settings as this runtime's own proxy password. + SecretID string `json:"secretId"` +} + +// The sandbox proxy password to store. +// Experimental: SandboxProxySecretSaveRequest is part of an experimental API and may change +// or be removed. +type SandboxProxySecretSaveRequest struct { + // The password to store for the sandbox proxy. + Value string `json:"value"` +} + +// Where the sandbox proxy password was stored. +// Experimental: SandboxProxySecretSaveResult is part of an experimental API and may change +// or be removed. +type SandboxProxySecretSaveResult struct { + // Whether the OS keychain accepted the value. False means it was written to the on-disk + // fallback store instead, which is still readable by `sandbox.proxySecret.get`. + StoredInKeychain bool `json:"storedInKeychain"` +} + +// The user's sandbox settings and the managed sandbox floor to raise them to. +// Experimental: SandboxResolveFlooredConfigParams is part of an experimental API and may +// change or be removed. +// Internal: SandboxResolveFlooredConfigParams is an internal SDK API and is not part of the +// public surface. +type SandboxResolveFlooredConfigParams struct { + // The effective managed `sandbox` slice acting as a floor. Omitted when no administrator + // policy applies, in which case the user's own settings stand unchanged. + ManagedSandbox any `json:"managedSandbox,omitempty"` + // The user's own `sandbox` settings slice. Omitted when the user has configured none, which + // still yields the managed floor rather than a disabled sandbox. + UserSandbox any `json:"userSandbox,omitempty"` +} + +// The user's sandbox settings raised to the managed floor. +// Experimental: SandboxResolveFlooredConfigResult is part of an experimental API and may +// change or be removed. +type SandboxResolveFlooredConfigResult struct { + // The floored sandbox config, normalized through the declared sandbox shape so only + // declared fields in a fixed order are present. + SandboxJSON any `json:"sandboxJson"` +} + +// The effective sandbox settings, with every unset value resolved to its default. +// Experimental: SandboxSettingsApplyDefaultsResult is part of an experimental API and may +// change or be removed. +type SandboxSettingsApplyDefaultsResult map[string]any + +// The managed sandbox policy in force, which selects which remedy the notice offers. +// Experimental: SandboxUnsupportedStartupNoticeParams is part of an experimental API and +// may change or be removed. +// Internal: SandboxUnsupportedStartupNoticeParams is an internal SDK API and is not part of +// the public surface. +type SandboxUnsupportedStartupNoticeParams struct { + // True when the sandbox stayed on only because the managed policy could not be determined. + // Distinguishes a policy that positively enforces the sandbox from one that merely failed + // to resolve, which is the difference between telling the user to contact an administrator + // and telling them to wait. Defaults to false. + EnabledByUndeterminedPolicy *bool `json:"enabledByUndeterminedPolicy,omitempty"` + // The managed `sandbox` slice, when a policy supplies one. The notice treats the sandbox as + // enforced only when this both enables it and sets `failIfUnavailable`, because a managed + // sandbox that tolerates being unavailable still leaves the user able to turn it off. + ManagedSandbox any `json:"managedSandbox,omitempty"` +} + +// The startup warning, absent when the machine can run a sandbox. +// Experimental: SandboxUnsupportedStartupNoticeResult is part of an experimental API and +// may change or be removed. +// Internal: SandboxUnsupportedStartupNoticeResult is an internal SDK API and is not part of +// the public surface. +type SandboxUnsupportedStartupNoticeResult struct { + // The user-facing warning, carrying the platform remedy and the recovery the policy in + // force actually allows. Absent when this machine supports a sandbox, so a host can call + // unconditionally and print only what it gets. + Notice *string `json:"notice,omitempty"` +} + // Register an absolute-time scheduled prompt. // Experimental: ScheduleAddAtRequest is part of an experimental API and may change or be // removed. @@ -16540,6 +18323,12 @@ type SessionOpenOptions struct { AdditionalDirectories []string `json:"additionalDirectories,omitzero"` // Runtime context discriminator for agent filtering. AgentContext *string `json:"agentContext,omitempty"` + // Explicit discovery-only agent roots, separate from customAgents and filesystem grants. + // Available without enableConfigDiscovery, subject to managed/source disables. Relative SDK + // paths resolve once when supplied against the request's effective workingDirectory; + // retained roots do not move on cwd changes. Null and omission are equivalent; [] clears + // only the explicit SDK contribution. + AgentDirectories []string `json:"agentDirectories,omitzero"` // Whether to include instructions from every MCP server in the system prompt instead of // only allowlisted servers. AllowAllMCPServerInstructions *bool `json:"allowAllMcpServerInstructions,omitempty"` @@ -16617,8 +18406,8 @@ 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. + // Whether skill loading is enabled. When omitted, explicit SDK skill roots or an SDK skill + // provider enable skills independently of enableConfigDiscovery. EnableSkills *bool `json:"enableSkills,omitempty"` // Whether model responses stream as delta events. EnableStreaming *bool `json:"enableStreaming,omitempty"` @@ -16657,6 +18446,13 @@ type SessionOpenOptions struct { // Internal: HasSkillProvider is part of the SDK's internal API surface and is not intended // for external use. HasSkillProvider *bool `json:"hasSkillProvider,omitempty"` + // Explicit discovery-only file-hook roots, separate from callback hooks and filesystem + // grants. Available without enableConfigDiscovery or enableFileHooks, which controls + // repository-origin hooks only; hook/managed disables still apply. Relative SDK paths + // resolve once when supplied against the request's effective workingDirectory; retained + // roots do not move on cwd changes. Null and omission are equivalent; [] clears only the + // explicit SDK contribution. + HookDirectories []string `json:"hookDirectories,omitzero"` // Skill scan directories and descendants excluded from discovery. Supports `~`-relative // paths. IgnoredSkillsLocations []string `json:"ignoredSkillsLocations,omitzero"` @@ -16670,8 +18466,20 @@ type SessionOpenOptions struct { IncludedBuiltinSkills []string `json:"includedBuiltinSkills,omitzero"` // Installed plugins visible to the session. InstalledPlugins []InstalledPlugin `json:"installedPlugins,omitzero"` + // Explicit discovery-only instruction roots using existing SDK instruction formats, not + // filesystem grants. Available without enableConfigDiscovery, subject to + // instruction/managed disables. Relative SDK paths resolve once when supplied against the + // request's effective workingDirectory; retained roots do not move on cwd changes. Null and + // omission are equivalent; [] clears only the explicit SDK contribution. + InstructionDirectories []string `json:"instructionDirectories,omitzero"` // Stable integration identifier for analytics. IntegrationID *string `json:"integrationId,omitempty"` + // Declares that a human drives this session's conversation, enabling user-message analysis + // (sentiment, task completion criteria, and task analytics) for user-authored messages. + // Automated messages remain excluded. Defaults to false for SDK sessions. Applied when the + // session is created or loaded from storage; ignored when resuming a session that is + // already active, and for cloud sessions. + InteractiveUser *bool `json:"interactiveUser,omitempty"` // Whether experimental behavior is enabled. IsExperimentalMode *bool `json:"isExperimentalMode,omitempty"` // Whether interactive shell sessions are logged. @@ -16747,7 +18555,11 @@ type SessionOpenOptions struct { ShellInitProfile *string `json:"shellInitProfile,omitempty"` // PowerShell process flags applied to built-in and user-requested shell commands. ShellProcessFlags []string `json:"shellProcessFlags,omitzero"` - // Additional directories to search for skills. + // Explicit discovery-only skill roots, not filesystem grants. Available without + // enableConfigDiscovery, subject to skill/managed disables. Relative SDK paths resolve once + // when supplied against the request's effective workingDirectory; retained roots do not + // move on cwd changes. Null and omission are equivalent; [] clears only the explicit SDK + // contribution. SkillDirectories []string `json:"skillDirectories,omitzero"` // Whether to skip custom instruction sources. SkipCustomInstructions *bool `json:"skipCustomInstructions,omitempty"` @@ -17067,6 +18879,23 @@ type SessionPluginsReloadRequest struct { type SessionPluginsReloadResult struct { } +// Experimental: SessionPluginsRetryManagedRequest is part of an experimental API and may +// change or be removed. +type SessionPluginsRetryManagedRequest struct { + // "plugin@marketplace" specs to retry. Omit to retry every plugin the session's managed + // settings require. + Plugins []string `json:"plugins,omitzero"` +} + +// Per-plugin outcomes of retrying the plugins required by the session's managed settings. +// Experimental: SessionPluginsRetryManagedResult is part of an experimental API and may +// change or be removed. +type SessionPluginsRetryManagedResult struct { + // One entry per requested plugin, in request order. When no plugins were requested, one + // entry per plugin the managed settings require. + Plugins []ManagedPluginRetryEntry `json:"plugins"` +} + // Experimental: SessionPluginsUninstallResult is part of an experimental API and may change // or be removed. type SessionPluginsUninstallResult struct { @@ -18080,6 +19909,12 @@ type SessionUpdateOptionsParams struct { AdditionalContentExclusionPolicies []OptionsUpdateAdditionalContentExclusionPolicy `json:"additionalContentExclusionPolicies,omitzero"` // Runtime context discriminator (e.g., `cli`, `actions`). AgentContext *string `json:"agentContext,omitempty"` + // Explicit discovery-only agent roots, separate from customAgents and filesystem grants. + // Available without enableConfigDiscovery, subject to managed/source disables. Relative SDK + // paths resolve once when supplied against the request's effective workingDirectory; + // retained roots do not move on cwd changes. Null and omission are equivalent; [] clears + // only the explicit SDK contribution. + AgentDirectories []string `json:"agentDirectories,omitzero"` // Whether to include instructions from every MCP server in the system prompt instead of // only allowlisted servers. AllowAllMCPServerInstructions *bool `json:"allowAllMcpServerInstructions,omitempty"` @@ -18107,8 +19942,9 @@ type SessionUpdateOptionsParams struct { DisabledInstructionSources []string `json:"disabledInstructionSources,omitzero"` // Skill IDs that should be excluded from this session. DisabledSkills []string `json:"disabledSkills,omitzero"` - // Whether to enable loading of `.github/hooks/` filesystem hooks. Separate from the SDK - // callback hook mechanism. + // Whether to enable repository-origin file hooks from `.github/hooks/` and trusted + // repository-configured roots. Does not control explicit hookDirectories or SDK callback + // hooks. EnableFileHooks *bool `json:"enableFileHooks,omitempty"` // Whether to enable host git operations (context resolution, child repo scanning, git info // in system prompt). @@ -18123,9 +19959,10 @@ type SessionUpdateOptionsParams struct { EnableScriptSafety *bool `json:"enableScriptSafety,omitempty"` // Whether to enable cross-session store writes and reads. EnableSessionStore *bool `json:"enableSessionStore,omitempty"` - // 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. + // Whether skill loading is enabled. Explicit false disables every source, including + // explicit SDK roots and a bound SDK provider; changing the value invalidates the loaded + // skill snapshot. When omitted, creation falls back to enableConfigDiscovery unless + // explicit SDK skill roots or an SDK skill provider are supplied. EnableSkills *bool `json:"enableSkills,omitempty"` // Whether to stream model responses. EnableStreaming *bool `json:"enableStreaming,omitempty"` @@ -18145,6 +19982,13 @@ type SessionUpdateOptionsParams struct { ExcludedTools []string `json:"excludedTools,omitzero"` // Map of feature-flag IDs to their boolean enabled state. FeatureFlags map[string]bool `json:"featureFlags,omitzero"` + // Explicit discovery-only file-hook roots, separate from callback hooks and filesystem + // grants. Available without enableConfigDiscovery or enableFileHooks, which controls + // repository-origin hooks only; hook/managed disables still apply. Relative SDK paths + // resolve once when supplied against the request's effective workingDirectory; retained + // roots do not move on cwd changes. Null and omission are equivalent; [] clears only the + // explicit SDK contribution. + HookDirectories []string `json:"hookDirectories,omitzero"` // Skill scan directories and descendants excluded from discovery. Supports `~`-relative // paths. IgnoredSkillsLocations []string `json:"ignoredSkillsLocations,omitzero"` @@ -18159,6 +20003,12 @@ type SessionUpdateOptionsParams struct { // 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"` + // Explicit discovery-only instruction roots using existing SDK instruction formats, not + // filesystem grants. Available without enableConfigDiscovery, subject to + // instruction/managed disables. Relative SDK paths resolve once when supplied against the + // request's effective workingDirectory; retained roots do not move on cwd changes. Null and + // omission are equivalent; [] clears only the explicit SDK contribution. + InstructionDirectories []string `json:"instructionDirectories,omitzero"` // Stable integration identifier used for analytics and rate-limit attribution. IntegrationID *string `json:"integrationId,omitempty"` // Whether experimental capabilities are enabled. @@ -18214,7 +20064,11 @@ type SessionUpdateOptionsParams struct { ShellInitProfile *string `json:"shellInitProfile,omitempty"` // PowerShell process flags applied to built-in and user-requested shell commands. ShellProcessFlags []string `json:"shellProcessFlags,omitzero"` - // Additional directories to search for skills. + // Explicit discovery-only skill roots, not filesystem grants. Available without + // enableConfigDiscovery, subject to skill/managed disables. Relative SDK paths resolve once + // when supplied against the request's effective workingDirectory; retained roots do not + // move on cwd changes. Null and omission are equivalent; [] clears only the explicit SDK + // contribution. SkillDirectories []string `json:"skillDirectories,omitzero"` // Whether to skip loading custom instruction sources. SkipCustomInstructions *bool `json:"skipCustomInstructions,omitempty"` @@ -18405,6 +20259,18 @@ func (UserAuthInfo) settableAuthInfoType() SettableAuthInfoType { return SettableAuthInfoTypeUser } +// Outcome of an atomic user or repository settings edit. +// Experimental: SettingsEditResult is part of an experimental API and may change or be +// removed. +// Internal: SettingsEditResult is an internal SDK API and is not part of the public surface. +type SettingsEditResult struct { + // Changed, unchanged or invalid existing file. Rejected input, validation failures and I/O + // errors remain RPC errors. + // Internal: Status is part of the SDK's internal API surface and is not intended for + // external use. + Status SettingsEditStatus `json:"status"` +} + // User-requested shell execution cancellation handle. // Experimental: ShellCancelUserRequestedRequest is part of an experimental API and may // change or be removed. @@ -19223,6 +21089,20 @@ type SkillList struct { Skills []Skill `json:"skills"` } +// Side-effect-free planning of one Skill from a materialisation reference rather than a +// search candidate. +// Experimental: SkillPlanInstallFromReferenceRequest is part of an experimental API and may +// change or be removed. +type SkillPlanInstallFromReferenceRequest struct { + // Required authenticated bound catalogue, skill-confirmed-installation and + // skill-reference-installation capabilities. + Contract CatalogClientContract `json:"contract"` + // Existing local session attached to this connection. + PolicySessionID string `json:"policySessionId"` + // Materialisation reference to verify and plan. + Reference CatalogMaterializationReference `json:"reference"` +} + // Side-effect-free planning of one verified Agent Finder Skill candidate. // Experimental: SkillPlanInstallRequest is part of an experimental API and may change or be // removed. @@ -19358,7 +21238,10 @@ type SkillsDiscoverRequest struct { IgnoredSkillsLocations []string `json:"ignoredSkillsLocations,omitzero"` // Optional list of project directory paths to scan for project-scoped skills ProjectPaths []string `json:"projectPaths,omitzero"` - // Optional list of additional skill directory paths to include + // Explicit discovery-only skill roots, not filesystem grants. Available without ambient + // discovery, subject to skill/managed disables. Relative paths resolve against the + // request's effective working directory when supplied. Null and omission are equivalent; an + // empty array contributes no explicit roots. SkillDirectories []string `json:"skillDirectories,omitzero"` } @@ -19396,6 +21279,11 @@ type SkillsGetDiscoveryPathsRequest struct { // Optional list of project directory paths. When omitted or empty, only personal and custom // directories are returned. ProjectPaths []string `json:"projectPaths,omitzero"` + // Explicit discovery-only skill roots to enumerate, including configured missing + // directories. Relative paths resolve against the request's effective working directory + // when supplied. These are not filesystem grants or preferred creation targets. Null and + // omission are equivalent. + SkillDirectories []string `json:"skillDirectories,omitzero"` } // Skills invoked during this session, ordered by invocation time (most recent last). @@ -21274,6 +23162,113 @@ type UserSettingMetadata struct { Value any `json:"value"` } +// A single top-level setting subtree to validate and write. +// Experimental: UserSettingsApplyTopKeyRequest is part of an experimental API and may +// change or be removed. +type UserSettingsApplyTopKeyRequest struct { + // Canonical path of the setting the caller is editing. Currently informational only. + CanonicalPath []string `json:"canonicalPath"` + // Records that the user chose an update channel explicitly, so the one-time staff channel + // migration stops reapplying. Honored only for the `autoUpdatesChannel` key. + CompleteStaffUpdateChannelMigration *bool `json:"completeStaffUpdateChannelMigration,omitempty"` + // True to remove the top-level key instead of writing it. + Delete *bool `json:"delete,omitempty"` + // Top-level setting name to write. + TopKey string `json:"topKey"` + // Value to write for the whole top-level key. Omitted when `delete` is true. Explicit null + // is preserved and validated as null. + Value *any `json:"value,omitempty"` +} + +// Outcome of validating and writing one top-level setting subtree. +// Experimental: UserSettingsApplyTopKeyResult is part of an experimental API and may change +// or be removed. +type UserSettingsApplyTopKeyResult struct { + // Why the write failed, present only when `status` is `write-error`. + Error *string `json:"error,omitempty"` + // Problems found, present only when `status` is `validation-error`. + Issues []UserSettingsValidationIssue `json:"issues,omitzero"` + // `ok` when the value was written, `validation-error` when it was rejected before any + // write, `write-error` when validation passed but the write failed. + Status string `json:"status"` +} + +// The overrides and host context to resolve effective runtime settings from. +// Experimental: UserSettingsBuildRuntimeRequest is part of an experimental API and may +// change or be removed. +// Internal: UserSettingsBuildRuntimeRequest is an internal SDK API and is not part of the +// public surface. +type UserSettingsBuildRuntimeRequest struct { + // The environment to read settings-bearing variables from. Passed explicitly rather than + // read from the server process so a host can resolve settings for an environment it is + // about to create rather than the one it runs in. + Environment map[string]string `json:"environment"` + // Settings that take precedence over every document and environment layer, such as the + // values a host derived from its own command line. Keyed by setting name, with each value + // carried verbatim, so a host may pass keys this runtime does not recognize. Absent when + // the host contributes no overrides of its own. + Overrides map[string]any `json:"overrides,omitzero"` + // The platform to apply platform-specific defaults for, in the same spelling the host's own + // runtime reports. + Platform string `json:"platform"` + // The directory the settings are being resolved for. This selects the repository-scoped + // document, so a host resolving settings for a directory other than its own process working + // directory must pass that directory here. + WorkingDirectory string `json:"workingDirectory"` +} + +// The effective runtime settings, keyed by setting name. Keys this runtime does not +// recognize are preserved rather than dropped, so a caller reading a document written by a +// newer runtime keeps every value. +// Experimental: UserSettingsBuildRuntimeResult is part of an experimental API and may +// change or be removed. +type UserSettingsBuildRuntimeResult map[string]any + +// One atomic edit of a canonical setting in settings.json. +// Experimental: UserSettingsEditRequest is part of an experimental API and may change or be +// removed. +// Internal: UserSettingsEditRequest is an internal SDK API and is not part of the public +// surface. +type UserSettingsEditRequest struct { + // Nonempty canonical path segments of the setting to edit, starting with its top-level key. + CanonicalPath []string `json:"canonicalPath"` + // Configuration directory to edit. Omitted or empty resolves the server's own home, as in + // `user.settings.loadForConfigDir`. + ConfigDir *string `json:"configDir,omitempty"` + // Set a scalar, add or remove a string list item, or remove the setting. The handler + // rejects a missing or incompatible value. + // Internal: Operation is part of the SDK's internal API surface and is not intended for + // external use. + Operation SettingsEditOperation `json:"operation"` + // Required boolean, string or number for set, required string for add-item and remove-item, + // and forbidden for remove. The runtime rejects all other JSON values. + Value any `json:"value,omitempty"` +} + +// Selects which settings file paths to resolve. +// Experimental: UserSettingsGetPathsRequest is part of an experimental API and may change +// or be removed. +type UserSettingsGetPathsRequest struct { + // Absolute path of a repository root. Supply it to also resolve that repository's two + // settings file paths; omit it to resolve only the user path. + RepoRoot *string `json:"repoRoot,omitempty"` +} + +// On-disk locations of the settings files this runtime reads. A path is reported whether or +// not the file exists. +// Experimental: UserSettingsGetPathsResult is part of an experimental API and may change or +// be removed. +type UserSettingsGetPathsResult struct { + // The repository's local, untracked settings file, or null when no repository root was + // supplied. + RepoLocalSettings *string `json:"repoLocalSettings,omitempty"` + // The repository's checked-in settings file, or null when no repository root was supplied. + RepoSettings *string `json:"repoSettings,omitempty"` + // The user settings file: `settings.json` under the resolved Copilot home. This is the file + // `user.settings.get`, `user.settings.load`, and `user.settings.set` read and write. + UserSettings string `json:"userSettings"` +} + // Per-key metadata for every known user setting in settings.json, including settings left // at their default. Excludes repository- and enterprise-managed overrides. // Experimental: UserSettingsGetResult is part of an experimental API and may change or be @@ -21284,6 +23279,418 @@ type UserSettingsGetResult struct { Settings map[string]UserSettingMetadata `json:"settings"` } +// Top-level user-setting keys grouped by who may govern them. +// Experimental: UserSettingsGovernanceKeysResult is part of an experimental API and may +// change or be removed. +type UserSettingsGovernanceKeysResult struct { + // Top-level keys a repository may override through a Claude-compatible repository config. + ClaudeRepo []string `json:"claudeRepo"` + // Top-level keys an enterprise policy may fix through managed settings. + Managed []string `json:"managed"` + // Top-level keys a repository may override through `.github/copilot/settings.json` or + // `.github/copilot/settings.local.json`. + Repo []string `json:"repo"` +} + +// The filesystem and environment facts a user-settings file is resolved from. Passed +// explicitly rather than read from the runtime's own process, so a host whose configuration +// directory differs from its process environment resolves the file it means. +// Experimental: UserSettingsHostContext is part of an experimental API and may change or be +// removed. +type UserSettingsHostContext struct { + // The Copilot configuration directory, when the host set one. Takes precedence over + // `COPILOT_HOME` and the home directory. + ConfigDir *string `json:"configDir,omitempty"` + // The host's environment variables. `COPILOT_HOME` is read from here, as are the variables + // a settings hook may expand. + Environment any `json:"environment"` + // The user's home directory, used when neither a configuration directory nor `COPILOT_HOME` + // is set. + HomeDirectory string `json:"homeDirectory"` +} + +// A repository to read settings from, and the user layer they are merged over. +// Experimental: UserSettingsLoadAndMergeRepoRequest is part of an experimental API and may +// change or be removed. +// Internal: UserSettingsLoadAndMergeRepoRequest is an internal SDK API and is not part of +// the public surface. +type UserSettingsLoadAndMergeRepoRequest struct { + // Absolute path of the repository root whose settings to read. When this is the user's home + // directory, the Claude-compatible configuration is skipped, so a home-directory session + // does not inherit a personal config as if it were repository policy. + RepoRoot string `json:"repoRoot"` + // The user's own settings, keyed by setting name. The repository layer is merged over this; + // a repository may only add to the collection settings it is allowed to influence, so a + // user setting it does not name survives unchanged. + UserSettings map[string]any `json:"userSettings"` +} + +// The settings a consumer should act on inside a repository, together with any diagnostic +// raised while reading the repository layer. +// Experimental: UserSettingsLoadAndMergeRepoResult is part of an experimental API and may +// change or be removed. +// Internal: UserSettingsLoadAndMergeRepoResult is an internal SDK API and is not part of +// the public surface. +type UserSettingsLoadAndMergeRepoResult struct { + // The merged settings document, keyed by setting name. Equals the supplied user layer when + // the repository declares no settings. + Settings map[string]any `json:"settings"` + // Why part of the repository document was ignored, as a single human-readable string + // joining every diagnostic with `; `. Null when it loaded cleanly. The settings that did + // load are still merged, so a consumer should surface this and continue rather than treat + // it as a failure. + Warning *string `json:"warning,omitempty"` +} + +// Selects the configuration directory whose user settings to read. +// Experimental: UserSettingsLoadForConfigDirRequest is part of an experimental API and may +// change or be removed. +// Internal: UserSettingsLoadForConfigDirRequest is an internal SDK API and is not part of +// the public surface. +type UserSettingsLoadForConfigDirRequest struct { + // Copilot configuration directory to read the settings document from, taking precedence + // over the server's own `COPILOT_HOME` and default home. Omit it, or pass an empty string, + // to read the directory the server resolved for itself. + ConfigDir *string `json:"configDir,omitempty"` +} + +// The stored user settings document, keyed by setting name. +// Experimental: UserSettingsLoadForConfigDirResult is part of an experimental API and may +// change or be removed. +type UserSettingsLoadForConfigDirResult map[string]any + +// Contents of settings.json as stored, for a caller about to rewrite it. +// Experimental: UserSettingsLoadForEditResult is part of an experimental API and may change +// or be removed. +type UserSettingsLoadForEditResult struct { + // Parsed contents, present only when `status` is `ok`. + Settings any `json:"settings,omitempty"` + // `ok` when the file parsed, `missing` when there is no file yet, `invalid` when the file + // exists but will not parse. + Status string `json:"status"` +} + +// Selects the repository whose checked-in settings to read. +// Experimental: UserSettingsLoadRepoRequest is part of an experimental API and may change +// or be removed. +type UserSettingsLoadRepoRequest struct { + // Absolute path of the repository root whose settings to read. When this is the user's home + // directory, the Claude-compatible configuration is skipped, so a home-directory session + // does not inherit a personal config as if it were repository policy. + RepoRoot string `json:"repoRoot"` +} + +// A repository's settings as stored, together with any diagnostic raised while reading them. +// Experimental: UserSettingsLoadRepoResult is part of an experimental API and may change or +// be removed. +type UserSettingsLoadRepoResult struct { + // The repository's settings document, keyed by setting name, with each value carried + // verbatim. Null when the repository declares no settings. + Settings map[string]any `json:"settings,omitzero"` + // Why part of the document was ignored, as a single human-readable string joining every + // diagnostic with `; `. Null when the document loaded cleanly. The settings that did load + // are still reported, so a consumer should surface this and continue rather than treat it + // as a failure. + Warning *string `json:"warning,omitempty"` +} + +// The user's stored settings, together with any diagnostic raised while reading them. +// Experimental: UserSettingsLoadWithWarningResult is part of an experimental API and may +// change or be removed. +type UserSettingsLoadWithWarningResult struct { + // The stored user settings document, keyed by setting name, with each value carried + // verbatim. Empty when the file could not be read at all. + Settings map[string]any `json:"settings"` + // Why part of the document was ignored, phrased for display to the user. Null when the + // document loaded cleanly. The settings that did load are still reported, so a consumer may + // surface this and continue rather than treat it as a failure. + Warning *string `json:"warning,omitempty"` +} + +// The managed (enterprise policy) layer and the user layer to fold together. The field +// names carry the layer identity: nothing about a settings object identifies which layer +// produced it, so a generic base/override pair would let a caller transpose them and +// silently resolve the governance keys in the user's favour. +// Experimental: UserSettingsMergeManagedAndUserParams is part of an experimental API and +// may change or be removed. +// Internal: UserSettingsMergeManagedAndUserParams is an internal SDK API and is not part of +// the public surface. +type UserSettingsMergeManagedAndUserParams struct { + // The managed layer. Its governance keys win, and the per-entry `enabledPlugins` and + // `extraKnownMarketplaces` control maps are stamped back from it after the union. Omitted + // when the host is unmanaged. + Managed any `json:"managed,omitempty"` + // The user's own settings layer. Wins on every key the managed layer does not govern. + // Omitted when the user has no settings yet. + User any `json:"user,omitempty"` +} + +// The user layer folded over the managed layer. +// Experimental: UserSettingsMergeManagedAndUserResult is part of an experimental API and +// may change or be removed. +type UserSettingsMergeManagedAndUserResult struct { + // The merged settings. An empty object when neither layer was supplied, so an unmanaged + // host with no user settings governs nothing. + SettingsJSON any `json:"settingsJson"` +} + +// The user layer and the repository layer to fold together. +// Experimental: UserSettingsMergeRepoValueParams is part of an experimental API and may +// change or be removed. +// Internal: UserSettingsMergeRepoValueParams is an internal SDK API and is not part of the +// public surface. +type UserSettingsMergeRepoValueParams struct { + // The repository's checked-in settings layer. + RepoSettings any `json:"repoSettings"` + // The user's settings, already resolved against any managed layer. + UserSettings any `json:"userSettings"` +} + +// The repository layer folded into the user layer. +// Experimental: UserSettingsMergeRepoValueResult is part of an experimental API and may +// change or be removed. +type UserSettingsMergeRepoValueResult struct { + // The merged settings, with the per-entry managed control maps re-stamped from the + // runtime's ambient managed policy. + SettingsJSON any `json:"settingsJson"` +} + +// Selects the configuration directory whose settings document to clean. +// Experimental: UserSettingsMigrateObsoleteOnDiskRequest is part of an experimental API and +// may change or be removed. +// Internal: UserSettingsMigrateObsoleteOnDiskRequest is an internal SDK API and is not part +// of the public surface. +type UserSettingsMigrateObsoleteOnDiskRequest struct { + // The configuration directory holding the settings document to clean. A host that scopes a + // session to its own Copilot home passes that directory here; omit it to clean the document + // under the home the server resolved for itself. The home directory and environment are + // deliberately not caller-supplied: they decide only the settings path root and the two + // hook-parsing flags, and those describe the process doing the reading rather than the + // caller. + ConfigDir *string `json:"configDir,omitempty"` +} + +// The outcome of the one-time obsolete-settings cleanup. +// Experimental: UserSettingsMigrateObsoleteOnDiskResult is part of an experimental API and +// may change or be removed. +// Internal: UserSettingsMigrateObsoleteOnDiskResult is an internal SDK API and is not part +// of the public surface. +type UserSettingsMigrateObsoleteOnDiskResult struct { + // What the cleanup did. Only `changed` rewrote the document. The cleaned settings are not + // returned here: the document is written before this replies, so `user.settings.load` reads + // it authoritatively. + // Internal: Status is part of the SDK's internal API surface and is not intended for + // external use. + Status UserSettingsMigrateObsoleteOnDiskStatus `json:"status"` +} + +// Selects the configuration directory to run the one-time staff log-level migration +// against, and supplies the caller's record of whether it has already run. +// Experimental: UserSettingsMigrateStaffLogLevelOnceRequest is part of an experimental API +// and may change or be removed. +// Internal: UserSettingsMigrateStaffLogLevelOnceRequest is an internal SDK API and is not +// part of the public surface. +type UserSettingsMigrateStaffLogLevelOnceRequest struct { + // When the caller last recorded this migration, as an ISO-8601 timestamp. Supplying it lets + // the runtime answer `current` without re-running the evaluation. + CachedCompletedAt *string `json:"cachedCompletedAt,omitempty"` + // The configuration directory, home directory and environment to resolve the settings and + // global-state documents from. A host that scopes a session to its own Copilot home must + // pass that directory here, or the migration is evaluated against the server's own home + // instead. + Context UserSettingsHostContext `json:"context"` +} + +// The outcome of the one-time staff log-level migration. +// Experimental: UserSettingsMigrateStaffLogLevelOnceResult is part of an experimental API +// and may change or be removed. +// Internal: UserSettingsMigrateStaffLogLevelOnceResult is an internal SDK API and is not +// part of the public surface. +type UserSettingsMigrateStaffLogLevelOnceResult struct { + // When the migration was recorded as done, as an ISO-8601 timestamp. Absent only for + // `ineligible`, which records nothing. + CompletedAt *string `json:"completedAt,omitempty"` + // The settings document as it stands after the migration, keyed by setting name. Present + // only for `refreshed`, and only when the document could be re-read cleanly. + Settings any `json:"settings,omitempty"` + // What the migration did. Only `refreshed` can have changed the persisted settings. + Status StaffLogLevelMigrationStatus `json:"status"` +} + +// The outcome of the one-time staff update-channel migration. +// Experimental: UserSettingsMigrateStaffUpdateChannelOnceResult is part of an experimental +// API and may change or be removed. +// Internal: UserSettingsMigrateStaffUpdateChannelOnceResult is an internal SDK API and is +// not part of the public surface. +type UserSettingsMigrateStaffUpdateChannelOnceResult struct { + // When the migration was recorded as done, as an ISO-8601 timestamp. Absent only for + // `ineligible`, which records nothing. + CompletedAt *string `json:"completedAt,omitempty"` + // What the migration did. Only `migrated` means the persisted settings changed. + Status StaffUpdateChannelMigrationStatus `json:"status"` +} + +// Settings object whose key names should be canonicalized. +// Experimental: UserSettingsNormalizeRequest is part of an experimental API and may change +// or be removed. +type UserSettingsNormalizeRequest struct { + // Free-form settings object, possibly using legacy or snake_case key names. + Settings any `json:"settings"` +} + +// Settings object with canonical key names. +// Experimental: UserSettingsNormalizeResult is part of an experimental API and may change +// or be removed. +type UserSettingsNormalizeResult struct { + // The same settings with every legacy or snake_case key renamed to its canonical spelling. + Settings any `json:"settings"` +} + +// The on-disk location of the user's settings document. +// Experimental: UserSettingsPathResult is part of an experimental API and may change or be +// removed. +type UserSettingsPathResult struct { + // Absolute path to the settings file this runtime reads and writes. Normally + // `settings.json` under the Copilot configuration directory; an extensionless legacy + // `settings` file is reported instead when it exists and `settings.json` does not. + Path string `json:"path"` +} + +// One setting that will not take effect, and why. +// Experimental: UserSettingsProblem is part of an experimental API and may change or be +// removed. +type UserSettingsProblem struct { + // Coarse grouping for the problem, for a caller that presents problems in sections. + Category string `json:"category"` + // Why the setting will not take effect. Never contains the setting's value, which may be a + // token or another secret. + Explanation string `json:"explanation"` + // Stable identifier for this problem within the reported set, so a caller can track a + // selection across refreshes. + ID string `json:"id"` + // What is wrong in machine-readable form, for a caller that wants to branch rather than + // display. + Kind string `json:"kind"` + // The offending key as written in the file, when the problem is attributable to a single + // key. Absent when the whole file is at fault. + RawKey *string `json:"rawKey,omitempty"` + // The settings layer the problem was found in. + Source UserSettingsProblemLocation `json:"source"` +} + +// The settings layer a problem was found in. +// Experimental: UserSettingsProblemLocation is part of an experimental API and may change +// or be removed. +type UserSettingsProblemLocation struct { + // Absolute path of the file backing this layer. + FilePath string `json:"filePath"` + // Which layer this is: `user`, `repo`, or `local`. + Kind string `json:"kind"` + // The caller-supplied display label for the layer. + Label string `json:"label"` +} + +// One already-loaded settings layer to diagnose. +// Experimental: UserSettingsProblemSource is part of an experimental API and may change or +// be removed. +type UserSettingsProblemSource struct { + // The layer's settings exactly as loaded, unnormalized. Empty when the file is missing or + // failed to parse. + Config map[string]any `json:"config,omitzero"` + // Absolute path of the file backing this layer. + FilePath string `json:"filePath"` + // True when the file itself failed to parse, so its keys could not be read at all. + Invalid *bool `json:"invalid,omitempty"` + // Which layer this is: `user`, `repo`, or `local`. + Kind string `json:"kind"` + // Caller-chosen display label for the layer, echoed back on each problem so a caller can + // group by layer without re-deriving its own naming. + Label string `json:"label"` +} + +// The settings layers to diagnose. +// Experimental: UserSettingsProblemsRequest is part of an experimental API and may change +// or be removed. +type UserSettingsProblemsRequest struct { + // Layers to diagnose, in the precedence order the caller applies them. + Sources []UserSettingsProblemSource `json:"sources,omitzero"` +} + +// The settings that will not take effect across the given layers. +// Experimental: UserSettingsProblemsResult is part of an experimental API and may change or +// be removed. +type UserSettingsProblemsResult struct { + // Each setting that will not take effect, and why. Empty when every layer is healthy. + Problems []UserSettingsProblem `json:"problems,omitzero"` +} + +// Selects the configuration directory to run the one-time staff model-selection reset +// against, and supplies the caller's record of whether it has already run. +// Experimental: UserSettingsResetStaffModelSelectionOnceRequest is part of an experimental +// API and may change or be removed. +// Internal: UserSettingsResetStaffModelSelectionOnceRequest is an internal SDK API and is +// not part of the public surface. +type UserSettingsResetStaffModelSelectionOnceRequest struct { + // The configuration directory, home directory and environment to resolve the settings and + // global-state documents from. A host that scopes a session to its own Copilot home must + // pass that directory here, or the reset is evaluated against the server's own home instead. + Context UserSettingsHostContext `json:"context"` + // Whether the signed-in account is a staff account. The reset applies only to staff, so + // anything other than `true` returns `didReset: false` without touching the settings + // document. + Staff *bool `json:"staff,omitempty"` + // When the caller last recorded this reset, as an ISO-8601 timestamp. A timestamp at or + // after the cutoff means the reset already ran and is returned unchanged. + StaffModelResetAt *string `json:"staffModelResetAt,omitempty"` +} + +// The outcome of the one-time staff model-selection reset. +// Experimental: UserSettingsResetStaffModelSelectionOnceResult is part of an experimental +// API and may change or be removed. +// Internal: UserSettingsResetStaffModelSelectionOnceResult is an internal SDK API and is +// not part of the public surface. +type UserSettingsResetStaffModelSelectionOnceResult struct { + // The cutoff this reset is gated on, as an ISO-8601 timestamp. A recorded timestamp at or + // after it means the reset is already done. + Cutoff string `json:"cutoff"` + // Whether the persisted model selection was cleared. Only `true` means the settings + // document changed and the host should adopt the returned settings. + DidReset bool `json:"didReset"` + // The timestamp the host should now record, as an ISO-8601 timestamp. Carries the caller's + // own value back unchanged when nothing was reset. + ResetAt *string `json:"resetAt,omitempty"` + // The settings document as it stands after the reset, keyed by setting name. Present only + // when `didReset` is true. + Settings any `json:"settings,omitempty"` +} + +// One hook to disable or re-enable. +// Experimental: UserSettingsSetHookDisabledRequest is part of an experimental API and may +// change or be removed. +// Internal: UserSettingsSetHookDisabledRequest is an internal SDK API and is not part of +// the public surface. +type UserSettingsSetHookDisabledRequest struct { + // True adds the key to `disabledHooks`, false removes it. + Disabled bool `json:"disabled"` + // Durable content-hash key of the hook, as `hooks.loadResources` reports it. + DisableKey string `json:"disableKey"` + // Path of the settings.json to rewrite. + SettingsPath string `json:"settingsPath"` +} + +// Outcome of rewriting the `disabledHooks` setting. +// Experimental: UserSettingsSetHookDisabledResult is part of an experimental API and may +// change or be removed. +// Internal: UserSettingsSetHookDisabledResult is an internal SDK API and is not part of the +// public surface. +type UserSettingsSetHookDisabledResult struct { + // Why the write failed. Absent when the status is `ok`. + ErrorMessage *string `json:"errorMessage,omitempty"` + // `ok` when the setting was written, `invalid` when settings.json could not be parsed and + // was left alone, `error` when the write itself failed. + Status string `json:"status"` +} + // 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: UserSettingsSetRequest is part of an experimental API and may change or be @@ -21298,6 +23705,113 @@ type UserSettingsSetRequest struct { type UserSettingsSetResult struct { } +// Selects the configuration directory whose user settings document should be inspected for +// unrecognized top-level keys. +// Experimental: UserSettingsUnknownKeysWarningRequest is part of an experimental API and +// may change or be removed. +// Internal: UserSettingsUnknownKeysWarningRequest is an internal SDK API and is not part of +// the public surface. +type UserSettingsUnknownKeysWarningRequest struct { + // The configuration directory, home directory and environment to resolve the settings + // document from. A host that scopes a session to its own Copilot home must pass that + // directory here, or the server's own home is inspected instead. + Context UserSettingsHostContext `json:"context"` +} + +// The unrecognized top-level keys found in the user settings document, already phrased for +// display. +// Experimental: UserSettingsUnknownKeysWarningResult is part of an experimental API and may +// change or be removed. +// Internal: UserSettingsUnknownKeysWarningResult is an internal SDK API and is not part of +// the public surface. +type UserSettingsUnknownKeysWarningResult struct { + // The warning to show the user, naming the unrecognized keys and the file they were found + // in. Absent when every top-level key is recognized, and also when the document is missing + // or could not be read, because an unknown-key list taken from a document the runtime could + // not load would name keys that may not be there. + Warning *string `json:"warning,omitempty"` +} + +// Names the user setting to merge into, and the partial object to merge. +// Experimental: UserSettingsUpdateRequest is part of an experimental API and may change or +// be removed. +// Internal: UserSettingsUpdateRequest is an internal SDK API and is not part of the public +// surface. +type UserSettingsUpdateRequest struct { + // Copilot configuration directory to write the settings document in, taking precedence over + // the server's own `COPILOT_HOME` and default home. Omit it, or pass an empty string, to + // use the directory the server resolved for itself. + ConfigDir *string `json:"configDir,omitempty"` + // Top-level user-setting key to merge into, such as `extensions`. + Key string `json:"key"` + // Single-component name for the settings document at `/settings/.json`; + // path separators, `.` and `..` are rejected. Omit it, or pass an empty string, to use the + // main settings document. + SubDir *string `json:"subDir,omitempty"` + // Value to store for `key`. When both the stored value and this value are objects, their + // fields are shallow-merged and fields this object does not name are preserved. Otherwise + // this value replaces the stored value. + Value any `json:"value"` +} + +// The stored value of the user setting after the merge. +// Experimental: UserSettingsUpdateResult is part of an experimental API and may change or +// be removed. +// Internal: UserSettingsUpdateResult is an internal SDK API and is not part of the public +// surface. +type UserSettingsUpdateResult struct { + // The key's stored value after the write: the merged object when both the stored value and + // the supplied value are objects, otherwise the supplied value. + Value any `json:"value"` +} + +// A single top-level setting subtree to validate. +// Experimental: UserSettingsValidateRequest is part of an experimental API and may change +// or be removed. +type UserSettingsValidateRequest struct { + // True to validate a removal of this key. The current settings file is not read. + Delete *bool `json:"delete,omitempty"` + // Top-level setting name to validate. + TopKey string `json:"topKey"` + // Value to validate. Omitted when `delete` is true. + Value any `json:"value,omitempty"` +} + +// Outcome of validating one top-level setting subtree. +// Experimental: UserSettingsValidateResult is part of an experimental API and may change or +// be removed. +type UserSettingsValidateResult struct { + // Problems found, empty when `ok` is true. + Issues []UserSettingsValidationIssue `json:"issues,omitzero"` + // True when the subtree is valid. + Ok bool `json:"ok"` +} + +// One problem found while validating a setting value. +// Experimental: UserSettingsValidationIssue is part of an experimental API and may change +// or be removed. +type UserSettingsValidationIssue struct { + // Human-readable description of the problem. + Message string `json:"message"` + // Where in the settings tree the problem sits, as object keys and array indices. The first + // element is the top-level key; a whole-value problem is reported as that single key. + Path []any `json:"path"` +} + +// The whole content to store in the user settings file. +// Experimental: UserSettingsWriteAllRequest is part of an experimental API and may change +// or be removed. +type UserSettingsWriteAllRequest struct { + // The settings object to store. It replaces the file's entire content, so a key absent here + // is removed. + Settings any `json:"settings"` +} + +// Experimental: UserSettingsWriteAllResult is part of an experimental API and may change or +// be removed. +type UserSettingsWriteAllResult struct { +} + // The approval to add as a session-scoped rule // Experimental: UserToolSessionApproval is part of an experimental API and may change or be // removed. @@ -23085,16 +25599,104 @@ const ( CatalogAgentPluginCompatibilityTagGitHubCopilot CatalogAgentPluginCompatibilityTag = "github-copilot" ) -// Canonical Agent Plugin media type +// A kind of component an Agent Plugin contributes +// Experimental: CatalogAgentPluginComponent is part of an experimental API and may change +// or be removed. +type CatalogAgentPluginComponent string + +const ( + // Custom agents. + CatalogAgentPluginComponentAgent CatalogAgentPluginComponent = "agent" + // Canvases. + CatalogAgentPluginComponentCanvas CatalogAgentPluginComponent = "canvas" + // Slash commands. + CatalogAgentPluginComponentCommand CatalogAgentPluginComponent = "command" + // Extensions. + CatalogAgentPluginComponentExtension CatalogAgentPluginComponent = "extension" + // Hooks. + CatalogAgentPluginComponentHook CatalogAgentPluginComponent = "hook" + // Bundled LSP servers. + CatalogAgentPluginComponentLspServer CatalogAgentPluginComponent = "lsp-server" + // Bundled MCP servers. + CatalogAgentPluginComponentMCPServer CatalogAgentPluginComponent = "mcp-server" + // Skills. + CatalogAgentPluginComponentSkill CatalogAgentPluginComponent = "skill" +) + +// Typed installability state for an Agent Plugin candidate +// Experimental: CatalogAgentPluginInstallability is part of an experimental API and may +// change or be removed. +type CatalogAgentPluginInstallability string + +const ( + // Plugin installation is understood but disabled for the selected session. + CatalogAgentPluginInstallabilityFeatureDisabled CatalogAgentPluginInstallability = "feature-disabled" + // The candidate carries complete, verified install data and the selected session may + // install it. Its install plan is present. + CatalogAgentPluginInstallabilityInstallable CatalogAgentPluginInstallability = "installable" + // The catalogue supplied no complete, verified install data, so no install target is + // offered. + CatalogAgentPluginInstallabilityInstallDataUnavailable CatalogAgentPluginInstallability = "install-data-unavailable" + // Reserved for plugin formats this runtime cannot install. Every format surfaced today + // installs through a marketplace. + CatalogAgentPluginInstallabilityNotInstallableKind CatalogAgentPluginInstallability = "not-installable-kind" +) + +// Kind discriminator for CatalogAgentPluginInstallSource. +type CatalogAgentPluginInstallSourceKind string + +const ( + CatalogAgentPluginInstallSourceKindMarketplace CatalogAgentPluginInstallSourceKind = "marketplace" + CatalogAgentPluginInstallSourceKindMaterialization CatalogAgentPluginInstallSourceKind = "materialization" + CatalogAgentPluginInstallSourceKindRepository CatalogAgentPluginInstallSourceKind = "repository" +) + +// Discriminator for a marketplace install source +// Experimental: CatalogAgentPluginMarketplaceSourceKind is part of an experimental API and +// may change or be removed. +type CatalogAgentPluginMarketplaceSourceKind string + +const ( + // Install from a plugin marketplace. + CatalogAgentPluginMarketplaceSourceKindMarketplace CatalogAgentPluginMarketplaceSourceKind = "marketplace" +) + +// Discriminator for a materialisation install source +// Experimental: CatalogAgentPluginMaterializationSourceKind is part of an experimental API +// and may change or be removed. +type CatalogAgentPluginMaterializationSourceKind string + +const ( + // Install from Agent Finder's verified materialisation. + CatalogAgentPluginMaterializationSourceKindMaterialization CatalogAgentPluginMaterializationSourceKind = "materialization" +) + +// Media type naming the plugin format of an Agent Plugin candidate. Values other than +// `application/vnd.github.copilot-plugin` appear only when the caller requires the +// agent-plugin-formats capability. // Experimental: CatalogAgentPluginMediaType is part of an experimental API and may change // or be removed. type CatalogAgentPluginMediaType string const ( + // A portable Agent Plugin descriptor. + CatalogAgentPluginMediaTypeApplicationAgentPluginJSON CatalogAgentPluginMediaType = "application/agent-plugin+json" + // A Claude plugin descriptor (`.claude-plugin/plugin.json` layout). + CatalogAgentPluginMediaTypeApplicationVndAnthropicClaudePluginJSON CatalogAgentPluginMediaType = "application/vnd.anthropic.claude-plugin+json" // A GitHub Copilot Agent Plugin descriptor. CatalogAgentPluginMediaTypeApplicationVndGitHubCopilotPlugin CatalogAgentPluginMediaType = "application/vnd.github.copilot-plugin" ) +// Discriminator for a repository install source +// Experimental: CatalogAgentPluginRepositorySourceKind is part of an experimental API and +// may change or be removed. +type CatalogAgentPluginRepositorySourceKind string + +const ( + // Install directly from a GitHub repository. + CatalogAgentPluginRepositorySourceKindRepository CatalogAgentPluginRepositorySourceKind = "repository" +) + // Discriminator for an AI skill candidate // Experimental: CatalogAiSkillCandidateKind is part of an experimental API and may change // or be removed. @@ -23178,9 +25780,20 @@ const ( type CatalogCapability string const ( + // Understands `installability` and the reviewable `installPlan` on Agent Plugin candidates. + // Requires agent-plugin-discovery; without it, plugin candidates are unchanged. A plugin is + // installable only with complete, verified catalogue install data, and may still be + // reported as feature-disabled per selected session. + CatalogCapabilityAgentPluginConfirmedInstallation CatalogCapability = "agent-plugin-confirmed-installation" // Understands opt-in `application/vnd.github.copilot-plugin` candidates and their typed // identity, version, source, and compatibility fields. CatalogCapabilityAgentPluginDiscovery CatalogCapability = "agent-plugin-discovery" + // Extends agent-plugin-discovery to Claude plugin + // (`application/vnd.anthropic.claude-plugin+json`) and portable Agent Plugin + // (`application/agent-plugin+json`) candidates, each reporting its format as its media + // type. Requires agent-plugin-discovery; without it, plugin searches return only Copilot + // plugins. + CatalogCapabilityAgentPluginFormats CatalogCapability = "agent-plugin-formats" // Understands `application/ai-skill` candidates as discovery-only and typed non-installable. CatalogCapabilityAiSkillDiscovery CatalogCapability = "ai-skill-discovery" // Requires an eligible credential for the selected GitHub.com account before search egress @@ -23227,6 +25840,11 @@ const ( // installation engine linked; acquisition may still be refused as feature-disabled per // selected session. CatalogCapabilitySkillConfirmedInstallation CatalogCapability = "skill-confirmed-installation" + // Extends skill-confirmed-installation to planning from an Agent Finder materialisation + // reference through skills.planInstallFromReference, and to its distinct reference failure + // reasons. Requires skill-confirmed-installation. Advertised only by runtimes with the + // Skill installation engine linked. + CatalogCapabilitySkillReferenceInstallation CatalogCapability = "skill-reference-installation" // Understands versioned candidate trust snapshots. Protocol-3 callers must require this // capability before the runtime adds the optional snapshot field. CatalogCapabilityTrustSnapshot CatalogCapability = "trust-snapshot" @@ -23362,12 +25980,18 @@ const ( type CatalogMediaType string const ( + // An inert portable Agent Plugin descriptor, surfaced only under the agent-plugin-formats + // capability. + CatalogMediaTypeApplicationAgentPluginJSON CatalogMediaType = "application/agent-plugin+json" // 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" + // An inert Claude plugin descriptor, surfaced only under the agent-plugin-formats + // capability. + CatalogMediaTypeApplicationVndAnthropicClaudePluginJSON CatalogMediaType = "application/vnd.anthropic.claude-plugin+json" // An inert Agent Plugin descriptor. CatalogMediaTypeApplicationVndGitHubCopilotPlugin CatalogMediaType = "application/vnd.github.copilot-plugin" ) @@ -24184,6 +26808,32 @@ const ( ExternalToolTextResultForLlmContentTypeText ExternalToolTextResultForLlmContentType = "text" ) +// The population a feature flag is declared for, before overrides. +// Experimental: FeatureFlagAvailability is part of an experimental API and may change or be +// removed. +type FeatureFlagAvailability string + +const ( + // Enabled only for accounts that opted into experimental features. + FeatureFlagAvailabilityExperimental FeatureFlagAvailability = "experimental" + // Disabled for everyone, before any override. + FeatureFlagAvailabilityOff FeatureFlagAvailability = "off" + // Enabled for everyone, before any override. + FeatureFlagAvailabilityOn FeatureFlagAvailability = "on" + // Enabled for any staff account, GitHub or Microsoft. + FeatureFlagAvailabilityStaff FeatureFlagAvailability = "staff" + // Enabled only for staff accounts that have also opted into experimental features. + FeatureFlagAvailabilityStaffAndExperimental FeatureFlagAvailability = "staff-and-experimental" + // Enabled for GitHub staff accounts only. + FeatureFlagAvailabilityStaffGitHub FeatureFlagAvailability = "staff-github" + // Enabled for Microsoft staff accounts only. + FeatureFlagAvailabilityStaffMicrosoft FeatureFlagAvailability = "staff-microsoft" + // Enabled for staff accounts and for accounts that opted into experimental features. + FeatureFlagAvailabilityStaffOrExperimental FeatureFlagAvailability = "staff-or-experimental" + // Enabled only for accounts on the owning team. + FeatureFlagAvailabilityTeam FeatureFlagAvailability = "team" +) + // Why the runtime is requesting a GitHub credential. // Experimental: GitHubTokenAcquireReason is part of an experimental API and may change or // be removed. @@ -24551,6 +27201,87 @@ const ( LoginProviderKindProxima LoginProviderKind = "proxima" ) +// Operation to evaluate. Currently only url is supported. +type ManagedPermissionOperationKind string + +const ( + ManagedPermissionOperationKindURL ManagedPermissionOperationKind = "url" +) + +// Managed-policy verdict only: deny blocks the URL, ask requires approval, allow approves +// under managed policy, and unmanaged leaves the normal consumer permission flow in effect. +// No verdict bypasses other security controls. +// Experimental: ManagedPermissionVerdict is part of an experimental API and may change or +// be removed. +type ManagedPermissionVerdict string + +const ( + // Managed policy allows the operation, subject to other security controls. + ManagedPermissionVerdictAllow ManagedPermissionVerdict = "allow" + // Managed policy requires the consumer to obtain approval. + ManagedPermissionVerdictAsk ManagedPermissionVerdict = "ask" + // Managed policy prohibits the operation; do not offer approval. + ManagedPermissionVerdictDeny ManagedPermissionVerdict = "deny" + // Managed policy does not decide the operation; use the consumer's normal permission flow. + ManagedPermissionVerdictUnmanaged ManagedPermissionVerdict = "unmanaged" +) + +// What retrying did for one plugin required by managed settings. +// Experimental: ManagedPluginRetryStatus is part of an experimental API and may change or +// be removed. +type ManagedPluginRetryStatus string + +const ( + // The plugin was already installed and current, so nothing was done. + ManagedPluginRetryStatusAlreadyPresent ManagedPluginRetryStatus = "already_present" + // The plugin was not prepared because another session was changing plugins or the managed + // settings changed during the retry. It is prepared again before a later message. + ManagedPluginRetryStatusDeferred ManagedPluginRetryStatus = "deferred" + // Checking, installing, or updating the plugin failed. See `error`. + ManagedPluginRetryStatusFailed ManagedPluginRetryStatus = "failed" + // The plugin was missing and is now installed. + ManagedPluginRetryStatusInstalled ManagedPluginRetryStatus = "installed" + // The session's managed settings do not require this plugin, so nothing was done. + ManagedPluginRetryStatusNotRequired ManagedPluginRetryStatus = "not_required" + // An update was available and is now installed. + ManagedPluginRetryStatusUpdated ManagedPluginRetryStatus = "updated" +) + +// Which layer disabled bypass-permissions mode. The layer matters because only a +// server-delivered disable is pinned to an account and can later be lifted by signing in +// elsewhere. +// Experimental: ManagedSettingsBypassDisableSource is part of an experimental API and may +// change or be removed. +type ManagedSettingsBypassDisableSource string + +const ( + // The account's server-delivered org policy disabled it. Pinned to the account named by + // `currentManagedIdentity`. + ManagedSettingsBypassDisableSourceManaged ManagedSettingsBypassDisableSource = "managed" + // The device/MDM layer disabled it. Outranks the account, so no server answer lifts it. + ManagedSettingsBypassDisableSourceMdm ManagedSettingsBypassDisableSource = "mdm" + // The user turned bypass mode off themselves. + ManagedSettingsBypassDisableSourceUser ManagedSettingsBypassDisableSource = "user" +) + +// What the managed-policy resolution said about bypass-permissions mode. `unknown` is a +// third answer rather than a missing one, and the caller must send it explicitly when it +// has no verdict. +// Experimental: ManagedSettingsBypassPolicyResolution is part of an experimental API and +// may change or be removed. +type ManagedSettingsBypassPolicyResolution string + +const ( + // The policy resolved and does not disable bypass mode. + ManagedSettingsBypassPolicyResolutionAllowed ManagedSettingsBypassPolicyResolution = "allowed" + // The policy resolved and disables bypass mode. + ManagedSettingsBypassPolicyResolutionDisabled ManagedSettingsBypassPolicyResolution = "disabled" + // No verdict: the fetch failed, or the service declared a policy this build may not + // enforce. Whatever is in force is preserved, because reverting to permissive on an + // unreadable policy would widen access. + ManagedSettingsBypassPolicyResolutionUnknown ManagedSettingsBypassPolicyResolution = "unknown" +) + // A channel accepted by managedSettings.compose. // Experimental: ManagedSettingsChannel is part of an experimental API and may change or be // removed. @@ -24577,6 +27308,46 @@ const ( ManagedSettingsDiagnosticSeverityWarning ManagedSettingsDiagnosticSeverity = "warning" ) +// Severity of a diagnostic emitted while ingesting managed settings. +// Experimental: ManagedSettingsLogLevel is part of an experimental API and may change or be +// removed. +type ManagedSettingsLogLevel string + +const ( + // Detail useful only when diagnosing an ingestion. + ManagedSettingsLogLevelDebug ManagedSettingsLogLevel = "debug" + // A failure that changed the outcome of the ingestion. + ManagedSettingsLogLevelError ManagedSettingsLogLevel = "error" + // Ordinary progress worth surfacing under a verbose flag. + ManagedSettingsLogLevelInfo ManagedSettingsLogLevel = "info" + // A condition the operator should see but which did not stop the ingestion. + ManagedSettingsLogLevelWarning ManagedSettingsLogLevel = "warning" +) + +// Which channel registered the policy helper that produced a helper layer. +// Experimental: ManagedSettingsPolicyHelperAuthority is part of an experimental API and may +// change or be removed. +type ManagedSettingsPolicyHelperAuthority string + +const ( + // The helper was registered by the device (MDM or managed file) layer. + ManagedSettingsPolicyHelperAuthorityDevice ManagedSettingsPolicyHelperAuthority = "device" + // The helper was registered by the account's server-delivered policy. + ManagedSettingsPolicyHelperAuthorityServer ManagedSettingsPolicyHelperAuthority = "server" +) + +// The operation that requires a policy-helper response. +// Experimental: ManagedSettingsPolicyHelperReason is part of an experimental API and may +// change or be removed. +type ManagedSettingsPolicyHelperReason string + +const ( + // Resolve policy before plugin activation. + ManagedSettingsPolicyHelperReasonPluginActivation ManagedSettingsPolicyHelperReason = "pluginActivation" + // Resolve policy for session startup. + ManagedSettingsPolicyHelperReasonSessionStart ManagedSettingsPolicyHelperReason = "sessionStart" +) + // Summary of which managed-settings channels contributed to the effective session policy. // Use the per-channel booleans for exact provenance. // Experimental: ManagedSettingsResolvedSource is part of an experimental API and may change @@ -24600,6 +27371,44 @@ const ( ManagedSettingsResolvedSourceServer ManagedSettingsResolvedSource = "server" ) +// How an ingestion may use the on-disk server-policy cache. +// Experimental: ManagedSettingsServerPolicyCacheMode is part of an experimental API and may +// change or be removed. +type ManagedSettingsServerPolicyCacheMode string + +const ( + // Read cached policy without a network request. + ManagedSettingsServerPolicyCacheModeCacheOnly ManagedSettingsServerPolicyCacheMode = "cacheOnly" + // Ignore the cache entirely and rely on the live fetch alone. + ManagedSettingsServerPolicyCacheModeDisabled ManagedSettingsServerPolicyCacheMode = "disabled" + // Serve a cached entry only as a fallback after a failed live fetch. + ManagedSettingsServerPolicyCacheModeFallbackOnly ManagedSettingsServerPolicyCacheMode = "fallbackOnly" + // Serve a recent cache entry before a live fetch. + ManagedSettingsServerPolicyCacheModePreferFresh ManagedSettingsServerPolicyCacheMode = "preferFresh" +) + +// How the server layer of an ingestion was resolved. +// Experimental: ManagedSettingsServerPolicyResolution is part of an experimental API and +// may change or be removed. +type ManagedSettingsServerPolicyResolution string + +const ( + // No cached policy or confirmed no-policy result exists. + ManagedSettingsServerPolicyResolutionCacheMiss ManagedSettingsServerPolicyResolution = "cacheMiss" + // No policy could be resolved and the host applies the fail-closed gates. + ManagedSettingsServerPolicyResolutionFailClosed ManagedSettingsServerPolicyResolution = "failClosed" + // No policy could be resolved and the host proceeds unmanaged. + ManagedSettingsServerPolicyResolutionFailOpen ManagedSettingsServerPolicyResolution = "failOpen" + // A live fetch failed and a cached entry was served in its place. + ManagedSettingsServerPolicyResolutionFallbackCache ManagedSettingsServerPolicyResolution = "fallbackCache" + // A recent cached entry was served without a live fetch. + ManagedSettingsServerPolicyResolutionFreshCache ManagedSettingsServerPolicyResolution = "freshCache" + // A live fetch succeeded. + ManagedSettingsServerPolicyResolutionLive ManagedSettingsServerPolicyResolution = "live" + // No fetch was attempted because no usable token was available. + ManagedSettingsServerPolicyResolutionSkippedNoToken ManagedSettingsServerPolicyResolution = "skippedNoToken" +) + // Allowed values for the `McpAppsHostContextDetailsAvailableDisplayMode` enumeration. // Experimental: MCPAppsHostContextDetailsAvailableDisplayMode is part of an experimental // API and may change or be removed. @@ -26203,6 +29012,35 @@ const ( ProviderEndpointWireAPIResponses ProviderEndpointWireAPI = "responses" ) +// Identity scope of a monthly usage reading. +// Experimental: ProviderMonthlyUsageScope is part of an experimental API and may change or +// be removed. +type ProviderMonthlyUsageScope string + +const ( + // The service did not establish a recognized usage scope. + ProviderMonthlyUsageScopeUnknown ProviderMonthlyUsageScope = "unknown" + // Consumption of the authenticated user within the parent observation's service and + // returned cycle. + ProviderMonthlyUsageScopeUser ProviderMonthlyUsageScope = "user" +) + +// Availability of a service's monthly consumption reading. +// Experimental: ProviderMonthlyUsageState is part of an experimental API and may change or +// be removed. +type ProviderMonthlyUsageState string + +const ( + // The service reported a usable consumption reading, including an explicit zero. + ProviderMonthlyUsageStateAvailable ProviderMonthlyUsageState = "available" + // The service found no usage policy; no consumption amount is known. + ProviderMonthlyUsageStateNoPolicy ProviderMonthlyUsageState = "no_policy" + // The read failed or its data could not be used; consumption is unknown. + ProviderMonthlyUsageStateUnavailable ProviderMonthlyUsageState = "unavailable" + // The service reported an unrecognized usage state. + ProviderMonthlyUsageStateUnknown ProviderMonthlyUsageState = "unknown" +) + // Access policy reported by the quota service. // Experimental: ProviderQuotaAccessState is part of an experimental API and may change or // be removed. @@ -26381,6 +29219,8 @@ const ( // Review or widen the sandbox policy. The blocked path or host is named by the accompanying // message or by the tool result the action arrived with. RemediationActionReviewSandboxPolicy RemediationAction = "review_sandbox_policy" + // Discard conversation history back through the blocked request before continuing. + RemediationActionRewindConversation RemediationAction = "rewind_conversation" // Inspect which account is currently authenticated before deciding what to change. RemediationActionShowAccount RemediationAction = "show_account" // Authenticate again with the Copilot backend. The current credential is absent, expired, @@ -26448,6 +29288,18 @@ const ( RemoteSessionModeOn RemoteSessionMode = "on" ) +// Which repository settings file to edit. +// Experimental: RepoSettingsEditScope is part of an experimental API and may change or be +// removed. +type RepoSettingsEditScope string + +const ( + // The local repository settings file. + RepoSettingsEditScopeLocal RepoSettingsEditScope = "local" + // The shared repository settings file. + RepoSettingsEditScopeRepo RepoSettingsEditScope = "repo" +) + // Output format discriminator. Currently only json_schema is supported. type ResponseFormatType string @@ -26550,6 +29402,8 @@ const ( SessionCapabilityCLIDocumentation SessionCapability = "cli-documentation" // SDK elicitation support. SessionCapabilityElicitation SessionCapability = "elicitation" + // Read-only GitHub cache SQL tool (github_sql). + SessionCapabilityGitHubCacheStore SessionCapability = "github-cache-store" // Interactive CLI identity and behavior. SessionCapabilityInteractiveMode SessionCapability = "interactive-mode" // MCP Apps UI passthrough. @@ -27018,6 +29872,36 @@ const ( SettableAuthInfoTypeUser SettableAuthInfoType = "user" ) +// One atomic settings mutation. +// Experimental: SettingsEditOperation is part of an experimental API and may change or be +// removed. +type SettingsEditOperation string + +const ( + // Add one string to a list, unless present. + SettingsEditOperationAddItem SettingsEditOperation = "add-item" + // Remove the setting without supplying a value. + SettingsEditOperationRemove SettingsEditOperation = "remove" + // Remove one string from a list, unless absent. + SettingsEditOperationRemoveItem SettingsEditOperation = "remove-item" + // Set one scalar value. + SettingsEditOperationSet SettingsEditOperation = "set" +) + +// Whether one atomic settings edit changed a file. +// Experimental: SettingsEditStatus is part of an experimental API and may change or be +// removed. +type SettingsEditStatus string + +const ( + // The edit changed the setting and was stored. + SettingsEditStatusChanged SettingsEditStatus = "changed" + // The existing settings file could not be edited safely; nothing was written. + SettingsEditStatusInvalid SettingsEditStatus = "invalid" + // The setting already had the requested state. + SettingsEditStatusUnchanged SettingsEditStatus = "unchanged" +) + // 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 @@ -27104,8 +29988,17 @@ const ( SkillInstallationFailureReasonConfirmationUnavailable SkillInstallationFailureReason = "confirmation-unavailable" // The verified Skill descriptor failed validation. SkillInstallationFailureReasonDescriptorInvalid SkillInstallationFailureReason = "descriptor-invalid" + // Agent Finder has no materialisation for the referenced resource revision (HTTP 404). + // Returned only to callers that require skill-reference-installation. + SkillInstallationFailureReasonDescriptorNotFound SkillInstallationFailureReason = "descriptor-not-found" // The verified Skill descriptor could not be retrieved safely. SkillInstallationFailureReasonDescriptorUnavailable SkillInstallationFailureReason = "descriptor-unavailable" + // Agent Finder withdrew the referenced resource revision (HTTP 410). Returned only to + // callers that require skill-reference-installation. + SkillInstallationFailureReasonDescriptorWithdrawn SkillInstallationFailureReason = "descriptor-withdrawn" + // The retrieved descriptor does not match the referenced descriptor or bundle digest. + // Returned only to callers that require skill-reference-installation. + SkillInstallationFailureReasonDigestMismatch SkillInstallationFailureReason = "digest-mismatch" // The Skill entrypoint could not be retrieved or verified. SkillInstallationFailureReasonEntrypointUnavailable SkillInstallationFailureReason = "entrypoint-unavailable" // The handle or operation expired. @@ -27117,6 +30010,9 @@ const ( SkillInstallationFailureReasonForeignRuntime SkillInstallationFailureReason = "foreign-runtime" // The candidate handle is not a verified installable Skill candidate. SkillInstallationFailureReasonInvalidCandidate SkillInstallationFailureReason = "invalid-candidate" + // The materialisation reference is malformed or uses an unsupported schema version. + // Returned only to callers that require skill-reference-installation. + SkillInstallationFailureReasonInvalidReference SkillInstallationFailureReason = "invalid-reference" // The request is unsupported or malformed. SkillInstallationFailureReasonInvalidRequest SkillInstallationFailureReason = "invalid-request" // The Skill entrypoint is not a valid Skill. @@ -27147,6 +30043,9 @@ const ( SkillInstallationFailureReasonSearchMismatch SkillInstallationFailureReason = "search-mismatch" // The retained Skill source changed after planning. SkillInstallationFailureReasonSourceChanged SkillInstallationFailureReason = "source-changed" + // The referenced resource is not a Skill. Returned only to callers that require + // skill-reference-installation. + SkillInstallationFailureReasonUnsupportedResource SkillInstallationFailureReason = "unsupported-resource" // A storage operation failed; inspect durable state before retrying. SkillInstallationFailureReasonWriteFailed SkillInstallationFailureReason = "write-failed" // The handle is not the expected Skill handle kind. @@ -27327,6 +30226,45 @@ const ( SlashCommandModelPickerDialogKindModelPicker SlashCommandModelPickerDialogKind = "model-picker" ) +// What the one-time staff log-level migration did. +// Experimental: StaffLogLevelMigrationStatus is part of an experimental API and may change +// or be removed. +type StaffLogLevelMigrationStatus string + +const ( + // The migration already ran; the stored completion timestamp is returned unchanged. + StaffLogLevelMigrationStatusCurrent StaffLogLevelMigrationStatus = "current" + // The account is not a staff account, so the migration does not apply and nothing was + // recorded. + StaffLogLevelMigrationStatusIneligible StaffLogLevelMigrationStatus = "ineligible" + // The migration ran now. The persisted settings may have changed, so the host should adopt + // the returned settings when they are present. + StaffLogLevelMigrationStatusRefreshed StaffLogLevelMigrationStatus = "refreshed" +) + +// What the one-time staff update-channel migration did. +// Experimental: StaffUpdateChannelMigrationStatus is part of an experimental API and may +// change or be removed. +type StaffUpdateChannelMigrationStatus string + +const ( + // The persisted channel was already stable, so nothing changed, but the migration is now + // recorded as done. + StaffUpdateChannelMigrationStatusCompleted StaffUpdateChannelMigrationStatus = "completed" + // The migration already ran within the cutoff window; the stored completion timestamp is + // returned unchanged. + StaffUpdateChannelMigrationStatusCurrent StaffUpdateChannelMigrationStatus = "current" + // The account is on the experimental channel, which the migration deliberately leaves + // alone, and the migration is recorded as done. + StaffUpdateChannelMigrationStatusExcludedExperimental StaffUpdateChannelMigrationStatus = "excluded-experimental" + // The account is not a staff account, so the migration does not apply and nothing was + // recorded. + StaffUpdateChannelMigrationStatusIneligible StaffUpdateChannelMigrationStatus = "ineligible" + // The persisted update channel was changed to stable. Only this outcome requires the host + // to reload its configuration. + StaffUpdateChannelMigrationStatusMigrated StaffUpdateChannelMigrationStatus = "migrated" +) + // Context tier override for matching subagents // Experimental: SubagentSettingsEntryContextTier is part of an experimental API and may // change or be removed. @@ -27653,6 +30591,23 @@ const ( UISessionLimitsExhaustedResponseActionUnset UISessionLimitsExhaustedResponseAction = "unset" ) +// What the one-time obsolete-settings cleanup did to the document. +// Experimental: UserSettingsMigrateObsoleteOnDiskStatus is part of an experimental API and +// may change or be removed. +type UserSettingsMigrateObsoleteOnDiskStatus string + +const ( + // Obsolete keys were removed and the document was rewritten. + UserSettingsMigrateObsoleteOnDiskStatusChanged UserSettingsMigrateObsoleteOnDiskStatus = "changed" + // The document could not be parsed, so it was left untouched rather than rewritten from a + // partial read. + UserSettingsMigrateObsoleteOnDiskStatusInvalid UserSettingsMigrateObsoleteOnDiskStatus = "invalid" + // No settings document exists, so there was nothing to clean. + UserSettingsMigrateObsoleteOnDiskStatusMissing UserSettingsMigrateObsoleteOnDiskStatus = "missing" + // The document parsed and held nothing obsolete, so nothing was written. + UserSettingsMigrateObsoleteOnDiskStatusUnchanged UserSettingsMigrateObsoleteOnDiskStatus = "unchanged" +) + // Kind discriminator for UserToolSessionApproval. type UserToolSessionApprovalKind string @@ -28194,6 +31149,39 @@ func (a *ServerConnectorsAPI) Refresh(ctx context.Context, params *ConnectorDisc return &result, nil } +// Experimental: ServerContentExclusionAPI contains experimental APIs that may change or be +// removed. +type ServerContentExclusionAPI serverAPI + +// ValidatePolicies validates candidate content-exclusion policies and returns them +// normalized, without starting a session. This runs the same parser the runtime's own +// policy enforcement uses, so a host cannot accept a policy set the runtime would later +// reject, nor reject one it would have honored. Call it when policies arrive from outside +// the runtime — a command-line flag, a configuration file, a host's own settings — and must +// be checked before a session exists to apply them to. A malformed document fails with a +// message prefixed `Invalid JSON: `; a well-formed document that violates the policy schema +// fails with the offending field paths. +// +// RPC method: contentExclusion.validatePolicies. +// +// Parameters: Candidate content-exclusion policies to validate, as the raw JSON text a host +// received. The text is supplied unparsed so that a syntax error and a schema violation are +// both reported by the runtime's own validator rather than by the caller's JSON parser. +// +// Returns: The accepted policies, parsed and normalized. A rejected input is reported as an +// error rather than in this result. +func (a *ServerContentExclusionAPI) ValidatePolicies(ctx context.Context, params *ContentExclusionValidatePoliciesRequest) (*ContentExclusionValidatePoliciesResult, error) { + raw, err := a.client.Request(ctx, "contentExclusion.validatePolicies", params) + if err != nil { + return nil, err + } + var result ContentExclusionValidatePoliciesResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + // Experimental: ServerEnvironmentsAPI contains experimental APIs that may change or be // removed. type ServerEnvironmentsAPI serverAPI @@ -28695,6 +31683,42 @@ func (a *ServerManagedSettingsAPI) Validate(ctx context.Context, params *Managed return &result, nil } +// Experimental: ServerManagedSettingsPermissionsAPI contains experimental APIs that may +// change or be removed. +type ServerManagedSettingsPermissionsAPI serverAPI + +// Evaluate pure, sessionless evaluation of operations against a supplied +// permissionsContext. Performs no policy discovery, I/O, prompting, navigation, permission +// grants, or sandbox changes. Obtain the context from managedSettings.resolve or +// session.managedSettings.get, or reuse an in-memory context. Results preserve input order +// and duplicates. Consumers must block deny, obtain approval for ask, and use their normal +// permission flow for unmanaged. Unknown policy returns failClosed with every operation +// denied. This is not a substitute for user permissions or other security controls. +// +// RPC method: managedSettings.permissions.evaluate. +// +// Parameters: Managed policy context and ordered operations for pure, sessionless +// evaluation. +// +// Returns: Ordered managed permission verdicts and the supplied policy's fail-closed +// posture. +func (a *ServerManagedSettingsPermissionsAPI) Evaluate(ctx context.Context, params *ManagedPermissionsEvaluateRequest) (*ManagedSettingsPermissionsEvaluateResult, error) { + raw, err := a.client.Request(ctx, "managedSettings.permissions.evaluate", params) + if err != nil { + return nil, err + } + var result ManagedSettingsPermissionsEvaluateResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +// Experimental: Permissions returns experimental APIs that may change or be removed. +func (s *ServerManagedSettingsAPI) Permissions() *ServerManagedSettingsPermissionsAPI { + return (*ServerManagedSettingsPermissionsAPI)(s) +} + // Experimental: ServerMCPAPI contains experimental APIs that may change or be removed. type ServerMCPAPI serverAPI @@ -29410,6 +32434,48 @@ func (a *ServerSandboxAPI) GetHostSupport(ctx context.Context) (*SandboxHostSupp // removed. type ServerSandboxProxyCaAPI serverAPI +// CheckInstallationSupport reports whether this runtime process can offer installing the +// sandbox credential-proxy certificate authority into OS trust. +// +// RPC method: sandbox.proxyCa.checkInstallationSupport. +// +// Returns: True when this process can offer installing the sandbox credential-proxy +// certificate authority into OS trust. +func (a *ServerSandboxProxyCaAPI) CheckInstallationSupport(ctx context.Context) (*SandboxProxyCaCheckInstallationSupportResult, error) { + raw, err := a.client.Request(ctx, "sandbox.proxyCa.checkInstallationSupport", nil) + if err != nil { + return nil, err + } + var result SandboxProxyCaCheckInstallationSupportResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +// CheckRelevance reports whether this sandbox configuration uses the credential proxy and +// can need certificate-authority setup. +// +// RPC method: sandbox.proxyCa.checkRelevance. +// +// Parameters: Identifies the credential hosts that the persistent certificate authority of +// the sandbox credential proxy must cover. The runtime always adds the hosts from the saved +// user settings. +// +// Returns: True when the sandbox configuration uses the credential proxy and can need +// certificate-authority setup. +func (a *ServerSandboxProxyCaAPI) CheckRelevance(ctx context.Context, params *SandboxProxyCaRequest) (*SandboxProxyCaCheckRelevanceResult, error) { + raw, err := a.client.Request(ctx, "sandbox.proxyCa.checkRelevance", params) + if err != nil { + return nil, err + } + var result SandboxProxyCaCheckRelevanceResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + // Creates the persistent certificate authority of the sandbox credential proxy if none is // stored, without changing OS trust, and returns the path of its public certificate. Keeps // an existing certificate authority, even one that must be rotated. Fails where OS trust is @@ -30241,6 +33307,31 @@ func (a *ServerSkillsAPI) PlanInstall(ctx context.Context, params *SkillPlanInst return result, nil } +// PlanInstallFromReference plans installation of the Skill pinned by an Agent Finder +// materialisation reference without writing files. The runtime retrieves the descriptor +// only from its fixed Agent Finder authority and applies the same verification as +// skills.planInstall; the review shows only verified data. Apply the returned plan with +// skills.applyInstall. +// +// RPC method: skills.planInstallFromReference. +// +// Parameters: Side-effect-free planning of one Skill from a materialisation reference +// rather than a search candidate. +// +// Returns: Skill installation management result with the honoured contract, or a typed +// refusal. +func (a *ServerSkillsAPI) PlanInstallFromReference(ctx context.Context, params *SkillPlanInstallFromReferenceRequest) (SkillInstallationManagementResult, error) { + raw, err := a.client.Request(ctx, "skills.planInstallFromReference", params) + if err != nil { + return nil, err + } + result, err := unmarshalSkillInstallationManagementResult(raw) + if err != nil { + return nil, err + } + return result, nil +} + // PlanUninstall prepares a read-only removal plan for an owned verified Agent Finder Skill // installation. Uninstall planning is never gated by the Skill-install feature flag. // @@ -30507,30 +33598,31 @@ 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 - Catalog *ServerCatalogAPI - Commands *ServerCommandsAPI - Connectors *ServerConnectorsAPI - Environments *ServerEnvironmentsAPI - Extensions *ServerExtensionsAPI - Hooks *ServerHooksAPI - Host *ServerHostAPI - Instructions *ServerInstructionsAPI - LlmInference *ServerLlmInferenceAPI - ManagedSettings *ServerManagedSettingsAPI - MCP *ServerMCPAPI - Models *ServerModelsAPI - Plugins *ServerPluginsAPI - Runtime *ServerRuntimeAPI - Sandbox *ServerSandboxAPI - Secrets *ServerSecretsAPI - SessionFS *ServerSessionFSAPI - Sessions *ServerSessionsAPI - Skills *ServerSkillsAPI - Tools *ServerToolsAPI - User *ServerUserAPI + Account *ServerAccountAPI + AgentRegistry *ServerAgentRegistryAPI + Agents *ServerAgentsAPI + Catalog *ServerCatalogAPI + Commands *ServerCommandsAPI + Connectors *ServerConnectorsAPI + ContentExclusion *ServerContentExclusionAPI + Environments *ServerEnvironmentsAPI + Extensions *ServerExtensionsAPI + Hooks *ServerHooksAPI + Host *ServerHostAPI + Instructions *ServerInstructionsAPI + LlmInference *ServerLlmInferenceAPI + ManagedSettings *ServerManagedSettingsAPI + MCP *ServerMCPAPI + Models *ServerModelsAPI + Plugins *ServerPluginsAPI + Runtime *ServerRuntimeAPI + Sandbox *ServerSandboxAPI + Secrets *ServerSecretsAPI + SessionFS *ServerSessionFSAPI + Sessions *ServerSessionsAPI + Skills *ServerSkillsAPI + Tools *ServerToolsAPI + User *ServerUserAPI } // Ping checks server responsiveness and returns protocol information. @@ -30582,6 +33674,7 @@ func NewServerRPC(client *jsonrpc2.Client) *ServerRPC { r.Catalog = (*ServerCatalogAPI)(&r.common) r.Commands = (*ServerCommandsAPI)(&r.common) r.Connectors = (*ServerConnectorsAPI)(&r.common) + r.ContentExclusion = (*ServerContentExclusionAPI)(&r.common) r.Environments = (*ServerEnvironmentsAPI)(&r.common) r.Extensions = (*ServerExtensionsAPI)(&r.common) r.Hooks = (*ServerHooksAPI)(&r.common) @@ -30607,6 +33700,105 @@ type internalServerAPI struct { client *jsonrpc2.Client } +// Experimental: InternalServerAccountAPI contains experimental APIs that may change or be +// removed. +type InternalServerAccountAPI internalServerAPI + +// DescribeAuthInfo renders the sign-in banner and the one-line account description the +// runtime uses for an authentication identity. The runtime words these in several places +// for its own callers, so a host that announces a sign-in or an account switch should +// render them from here rather than composing its own, which would let the two spellings +// drift apart. +// +// RPC method: account.describeAuthInfo. +// +// Parameters: The authentication identity whose shared wording should be rendered. +// +// Returns: The wording the runtime uses for an authentication identity. +// Internal: DescribeAuthInfo is part of the SDK's internal handshake/plumbing; external +// callers should not use it. +func (a *InternalServerAccountAPI) DescribeAuthInfo(ctx context.Context, params *AccountDescribeAuthInfoRequest) (*AccountDescribeAuthInfoResult, error) { + raw, err := a.client.Request(ctx, "account.describeAuthInfo", params) + if err != nil { + return nil, err + } + var result AccountDescribeAuthInfoResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +// GetGistSupport reports whether the supplied identity can create gists. Gists are +// unavailable on GitHub Enterprise Cloud with data residency, and to Enterprise Managed +// Users on github.com, so a host that offers gist actions should hide or disable them when +// this reports no support. +// +// RPC method: account.getGistSupport. +// +// Parameters: The authentication identity whose gist support should be reported. +// +// Returns: Whether the identity can create gists, and why not when it cannot. +// Internal: GetGistSupport is part of the SDK's internal handshake/plumbing; external +// callers should not use it. +func (a *InternalServerAccountAPI) GetGistSupport(ctx context.Context, params *AccountGetGistSupportRequest) (*AccountGetGistSupportResult, error) { + raw, err := a.client.Request(ctx, "account.getGistSupport", params) + if err != nil { + return nil, err + } + var result AccountGetGistSupportResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +// GetTokenEnvVar names the environment variable supplying a classic personal access token, +// if one is set. A classic token cannot be upgraded in place, so a host that finds one +// should tell the user which variable to unset rather than starting an interactive sign-in +// that the variable would override. +// +// RPC method: account.getTokenEnvVar. +// +// Returns: The environment variable supplying a classic personal access token, when one is +// set. +// Internal: GetTokenEnvVar is part of the SDK's internal handshake/plumbing; external +// callers should not use it. +func (a *InternalServerAccountAPI) GetTokenEnvVar(ctx context.Context) (*AccountGetTokenEnvVarResult, error) { + raw, err := a.client.Request(ctx, "account.getTokenEnvVar", nil) + if err != nil { + return nil, err + } + var result AccountGetTokenEnvVarResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +// GetWebUrl resolves a path on the identity's GitHub host into an absolute URL, so a host +// can link to settings or documentation on the enterprise instance the user is signed in to +// rather than to github.com. Falls back to the public host when no identity is supplied. +// +// RPC method: account.getWebUrl. +// +// Parameters: A path to resolve against the GitHub host an identity is signed in to. +// +// Returns: An absolute URL on the identity's GitHub host. +// Internal: GetWebUrl is part of the SDK's internal handshake/plumbing; external callers +// should not use it. +func (a *InternalServerAccountAPI) GetWebUrl(ctx context.Context, params *AccountGetWebURLRequest) (*AccountGetWebURLResult, error) { + raw, err := a.client.Request(ctx, "account.getWebUrl", params) + if err != nil { + return nil, err + } + var result AccountGetWebURLResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + // Experimental: InternalServerAgentsAPI contains experimental APIs that may change or be // removed. type InternalServerAgentsAPI internalServerAPI @@ -30745,6 +33937,95 @@ func (a *InternalServerAgentsAPI) GetBuiltins(ctx context.Context) (*AgentsGetBu return &result, nil } +// Experimental: InternalServerFeatureFlagsAPI contains experimental APIs that may change or +// be removed. +type InternalServerFeatureFlagsAPI internalServerAPI + +// ListMetadata lists every feature flag this runtime declares, with the population each one +// is declared for, its experimental description, and the online-experiment flag it maps to. +// This is the static catalog: it describes how each flag is defined, not whether it is on +// for anyone. Use `featureFlags.resolve` for static values with caller overrides, not +// active runtime values or experiment assignments. +// +// RPC method: featureFlags.listMetadata. +// +// Returns: Every feature flag this runtime declares, with the shape each one was declared +// with. +// Internal: ListMetadata is part of the SDK's internal handshake/plumbing; external callers +// should not use it. +func (a *InternalServerFeatureFlagsAPI) ListMetadata(ctx context.Context) (*FeatureFlagsMetadataResult, error) { + raw, err := a.client.Request(ctx, "featureFlags.listMetadata", nil) + if err != nil { + return nil, err + } + var result FeatureFlagsMetadataResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +// Resolve computes static feature flags from the supplied population and overrides without +// changing runtime state or verifying audience membership. Starts from declared +// availability, then applies direct feature-flag environment values, user settings, and +// explicit `flagOverrides`. It does not apply `COPILOT_EXP_*` experiment assignments, +// authenticated startup gates, or active runtime state. Settings include +// `enabledFeatureFlags`, the legacy `feature_flags.enabled` list, and supported convenience +// settings. Supply `baseFlags` to use an existing flag set instead of declared +// availability. Omit `isStaffGitHub` or `isStaffMicrosoft` when membership was never +// recorded: a legacy staff account then counts as both populations. +// +// RPC method: featureFlags.resolve. +// +// Parameters: Caller population and overrides for static feature-flag resolution, +// independent of experiment assignments and active runtime state. +// +// Returns: The resolved feature flags, keyed by flag name. +// Internal: Resolve is part of the SDK's internal handshake/plumbing; external callers +// should not use it. +func (a *InternalServerFeatureFlagsAPI) Resolve(ctx context.Context, params *FeatureFlagsResolveRequest) (*FeatureFlagsResolveResult, error) { + raw, err := a.client.Request(ctx, "featureFlags.resolve", params) + if err != nil { + return nil, err + } + var result FeatureFlagsResolveResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +// ResolveStartupGates resolves named gates against this installation's cached +// online-experiment assignment, for the account the runtime is already authenticated as. +// Unlike `featureFlags.resolve`, which layers static availability with the populations +// supplied, this reads the experiment assignment the runtime cached for its own current +// authentication, so a launch can honor a rollout before any session exists. The account is +// never taken from the request: naming one would read another account's cached assignments. +// The cached assignment answers immediately and any network refresh happens behind it, so +// this call does not wait on the network. Not available to an extension, which cannot carry +// the user's consent to read their assignment. +// +// RPC method: featureFlags.resolveStartupGates. +// +// Parameters: Caller population and startup inputs to resolve named online-experiment gates +// for. Carries no account: the runtime resolves the assignment for the authentication it +// already holds. +// +// Returns: The resolved gates, keyed by flag name. +// Internal: ResolveStartupGates is part of the SDK's internal handshake/plumbing; external +// callers should not use it. +func (a *InternalServerFeatureFlagsAPI) ResolveStartupGates(ctx context.Context, params *FeatureFlagsResolveStartupGatesRequest) (*FeatureFlagsResolveStartupGatesResult, error) { + raw, err := a.client.Request(ctx, "featureFlags.resolveStartupGates", params) + if err != nil { + return nil, err + } + var result FeatureFlagsResolveStartupGatesResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + // Experimental: InternalServerGitAPI contains experimental APIs that may change or be // removed. type InternalServerGitAPI internalServerAPI @@ -31016,12 +34297,12 @@ func (a *InternalServerGlobalStateAPI) LoadForConfigDir(ctx context.Context, par // asked a one-off question, or completed a migration, so the next run can skip it. Only the // named key is replaced and the rest of the document is preserved, which lets two writers // record different flags without overwriting each other; passing no value removes the key -// instead. Only the keys a host records itself are writable: `appInstallNudgeResponded`, -// `appTipShown`, `askedSetupTerminals`, `autoFeedbackLastPromptedAt`, `firstLaunchAt`, -// `recentModelIds`, `sandboxCredentialProxyCaDeclined` and `sandboxOnboardingShown`. Every -// other key is refused, including `installedPlugins`, the stored credentials, -// `trustedFolders`, the staff flags and the signed-in accounts. Plugin enablement must use -// the plugin APIs, which apply repository and managed-policy checks. +// instead. Only the keys a host records itself are writable: `appTipShown`, +// `askedSetupTerminals`, `autoFeedbackLastPromptedAt`, `firstLaunchAt`, `recentModelIds`, +// `sandboxCredentialProxyCaDeclined` and `sandboxOnboardingShown`. Every other key is +// refused, including `installedPlugins`, the stored credentials, `trustedFolders`, the +// staff flags and the signed-in accounts. Plugin enablement must use the plugin APIs, which +// apply repository and managed-policy checks. // // RPC method: globalState.writeKey. // @@ -31155,6 +34436,1235 @@ func (a *InternalServerHostAPI) ReleaseSession(ctx context.Context, params *Host return &result, nil } +// Experimental: InternalServerInstructionsAPI contains experimental APIs that may change or +// be removed. +type InternalServerInstructionsAPI internalServerAPI + +// ClearCache drops the runtime's in-memory cache of discovered custom instructions so the +// next discovery reads them from disk again. A host calls this after it changes instruction +// files, or after it trusts a folder whose instructions an earlier discovery skipped, so +// that a session started afterwards sees the current files instead of a cached earlier +// answer. The call performs no I/O of its own and removes nothing from disk; it only +// invalidates, so the cost lands on the next discovery. Returns once the cache is +// clear, which is what lets a caller order a session start after it. Marked internal +// because it exists to carry a CLI call site off the napi boundary onto the SDK contract; +// it is migration plumbing, not a surface consumers are meant to depend on. +// +// RPC method: instructions.clearCache. +// Internal: ClearCache is part of the SDK's internal handshake/plumbing; external callers +// should not use it. +func (a *InternalServerInstructionsAPI) ClearCache(ctx context.Context) (*InstructionsClearCacheResult, error) { + raw, err := a.client.Request(ctx, "instructions.clearCache", nil) + if err != nil { + return nil, err + } + var result InstructionsClearCacheResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +// DiscoverRepoSources discovers the instruction sources for one caller-named location and +// returns them in the order the runtime applies them. `discover` answers for a set of +// project paths and derives the rest of its scope itself; this one instead takes every +// argument from the caller — the extra directories to scan and whether to descend into +// nested instruction files — and honors them as given. It also includes instruction +// directories from trusted repository settings, matching native repository discovery. Use +// it when the caller knows its own scope and no session exists to derive that scope from, +// such as a host reporting how many instruction files a directory contributes before any +// session starts. Marked internal because it exists to carry a CLI call site off the napi +// boundary onto the SDK contract; it is migration plumbing, not a surface consumers are +// meant to depend on. +// +// RPC method: instructions.discoverRepoSources. +// +// Parameters: Explicit discovery arguments for one location, supplied by the caller rather +// than derived from a session. +// +// Returns: Instruction sources discovered across user, repository, and plugin sources. +// Internal: DiscoverRepoSources is part of the SDK's internal handshake/plumbing; external +// callers should not use it. +func (a *InternalServerInstructionsAPI) DiscoverRepoSources(ctx context.Context, params *InstructionsDiscoverRepoSourcesRequest) (*ServerInstructionSourceList, error) { + raw, err := a.client.Request(ctx, "instructions.discoverRepoSources", params) + if err != nil { + return nil, err + } + var result ServerInstructionSourceList + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +// GetInitPrompt returns the prompt the `init` command runs to author repository +// instructions. A host offering its own initialize action sends this text so the +// instructions it produces match the ones the CLI produces. The text is fixed for a build +// and depends on no configuration, session or repository. +// +// RPC method: instructions.getInitPrompt. +// +// Returns: The prompt the init command runs. +// Internal: GetInitPrompt is part of the SDK's internal handshake/plumbing; external +// callers should not use it. +func (a *InternalServerInstructionsAPI) GetInitPrompt(ctx context.Context) (*InstructionsInitPromptResult, error) { + raw, err := a.client.Request(ctx, "instructions.getInitPrompt", nil) + if err != nil { + return nil, err + } + var result InstructionsInitPromptResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +// Experimental: InternalServerManagedSettingsAPI contains experimental APIs that may change +// or be removed. +type InternalServerManagedSettingsAPI internalServerAPI + +// ApplyControls stamps an effective managed policy's individually-controlled entries onto a +// user settings document, returning the settings a host should actually act on. The managed +// layer wins on every plugin spec and marketplace name it names, and leaves everything it +// does not name as the user wrote it. Hosts use this rather than merging the two documents +// themselves, because the merge is per entry rather than per document and a whole-document +// overlay would either discard the user's unmanaged entries or let a user entry outrank a +// managed one. Omitting the managed layer returns the settings unchanged, which is what an +// unmanaged host wants. Rejects when either document is present but is not a JSON object +// rather than treating it as absent, since silently dropping a malformed policy hands the +// user's layer the win on exactly the entries the policy exists to control. +// +// RPC method: managedSettings.applyControls. +// +// Parameters: A user settings document and the effective managed policy whose controlled +// entries should be stamped onto it. +// +// Returns: The user settings with the managed layer's controlled entries stamped on. +// Internal: ApplyControls is part of the SDK's internal handshake/plumbing; external +// callers should not use it. +func (a *InternalServerManagedSettingsAPI) ApplyControls(ctx context.Context, params *ManagedSettingsApplyControlsParams) (*ManagedSettingsApplyControlsResult, error) { + raw, err := a.client.Request(ctx, "managedSettings.applyControls", params) + if err != nil { + return nil, err + } + var result ManagedSettingsApplyControlsResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +// Ingest resolves managed (enterprise policy) settings for a host from every layer at once: +// the account's server-delivered policy, the device/MDM layer, and a platform policy +// helper. Hosts use this instead of fetching and composing the layers themselves, because +// the per-layer precedence and the fail-closed rules that apply when a layer cannot be +// confirmed are the runtime's own and must match the policy a session will later enforce. +// It does not retain or apply anything to process-global policy state, so a host may call +// it before any session exists — which is the point, since plugin activation and one-shot +// commands resolve policy before a session is available to carry it. The returned per-layer +// outcome flags are load-bearing rather than diagnostic: a host must distinguish an +// unmanaged device from a managed device whose policy could not be confirmed, and apply its +// fail-closed gates in the second case. Reports its diagnostics in `logMessages` rather +// than logging them, so a host that runs before a log sink exists still sees them. +// +// RPC method: managedSettings.ingest. +// +// Parameters: Inputs describing the host and account an ingestion should resolve managed +// policy for. +// +// Returns: The managed policy layers an ingestion resolved, together with the per-layer +// outcome flags a host needs to decide how much to trust the result. The flags are reported +// rather than folded into the settings because a host must be able to distinguish an +// unmanaged device from a managed device whose policy could not be confirmed, and apply its +// fail-closed gates in the second case. +// Internal: Ingest is part of the SDK's internal handshake/plumbing; external callers +// should not use it. +func (a *InternalServerManagedSettingsAPI) Ingest(ctx context.Context, params *ManagedSettingsIngestParams) (*ManagedSettingsIngestResult, error) { + raw, err := a.client.Request(ctx, "managedSettings.ingest", params) + if err != nil { + return nil, err + } + var result ManagedSettingsIngestResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +// LoadRemoteControl resolves the managed `remoteControl` policy for an account on its own, +// for a host deciding whether a remote-control session may start. Composes the same layers +// as `managedSettings.ingest` and returns only that one key, so a host does not have to +// know which layer won. An absent policy means the account is unmanaged for this capability +// and the host may proceed; that default is safe only while absence really does mean "no +// policy", so a service refusal that says an account policy exists which this client is too +// old to enforce resolves to an explicit disabled policy instead of to absence. A +// resolution that cannot be completed is rejected rather than answered as absence, for the +// same reason. +// +// RPC method: managedSettings.loadRemoteControl. +// +// Parameters: Inputs describing the host and account whose managed remote-control policy +// should be resolved. Mirrors `managedSettings.ingest`, and deliberately carries no +// retention decision: reading this gate never becomes this runtime process's startup policy. +// +// Returns: The managed remote-control policy in force for an account, if any. An absent +// policy means the account is unmanaged for this capability, which a host reads as allowed +// — so absence here is a positive statement that no policy applies, never a stand-in for a +// resolution that failed. A resolution that cannot be completed is rejected instead. +// Internal: LoadRemoteControl is part of the SDK's internal handshake/plumbing; external +// callers should not use it. +func (a *InternalServerManagedSettingsAPI) LoadRemoteControl(ctx context.Context, params *ManagedSettingsLoadRemoteControlParams) (*ManagedSettingsLoadRemoteControlResult, error) { + raw, err := a.client.Request(ctx, "managedSettings.loadRemoteControl", params) + if err != nil { + return nil, err + } + var result ManagedSettingsLoadRemoteControlResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +// ParseClientValue validates the managed settings a client is supplying for a session it is +// about to open, and returns them reduced to the keys a client is allowed to set. A +// client-supplied policy is not the same thing as an administrator's: it may only narrow +// permissions, so this accepts `permissions` and rejects every other key outright rather +// than ignoring it. Rejecting is the point — a host that silently dropped an unsupported +// key would report a policy as applied while running without it. Hosts call this before +// opening the session rather than letting the open fail, so a malformed policy is reported +// against the request that carried it. The runtime applies the same rule again when the +// session starts, so this cannot be used to smuggle a wider document past it. An accepted +// document that sets nothing comes back as an empty object, which is distinct from +// supplying nothing at all. +// +// RPC method: managedSettings.parseClientValue. +// +// Parameters: The client-supplied managed settings to validate and normalize. Omitting them +// is valid and yields no policy. +// +// Returns: The accepted managed settings, reduced to the keys a client is allowed to set. +// Internal: ParseClientValue is part of the SDK's internal handshake/plumbing; external +// callers should not use it. +func (a *InternalServerManagedSettingsAPI) ParseClientValue(ctx context.Context, params *ManagedSettingsParseClientValueParams) (*ManagedSettingsParseClientValueResult, error) { + raw, err := a.client.Request(ctx, "managedSettings.parseClientValue", params) + if err != nil { + return nil, err + } + var result ManagedSettingsParseClientValueResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +// ReadDevice discovers device-managed settings the way the runtime's own device layer does, +// and reports the sandbox floor and the load outcome alongside the validated document. This +// differs from `managedSettings.read` in three ways a policy-enforcing host depends on: a +// document that validates to an empty object is reported as absent rather than as an empty +// policy, a discovery error does not suppress a document that still validates, and a +// failure is reported as a flag rather than only as error text. Hosts that compose the +// device layer into an effective policy use this, because `managedSettings.read` cannot +// distinguish "no device policy" from "device policy could not be read" and would silently +// hand the user's own layer the win. +// +// RPC method: managedSettings.readDevice. +// +// Returns: The device-managed settings layer, with the sandbox floor and the load outcome a +// host needs to compose it into an effective policy. +// Internal: ReadDevice is part of the SDK's internal handshake/plumbing; external callers +// should not use it. +func (a *InternalServerManagedSettingsAPI) ReadDevice(ctx context.Context) (*ManagedSettingsReadDeviceResult, error) { + raw, err := a.client.Request(ctx, "managedSettings.readDevice", nil) + if err != nil { + return nil, err + } + var result ManagedSettingsReadDeviceResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +// ResolveBypassDisableTransition decides what a host must do to its bypass-permissions +// state after a managed-policy resolution lands, given the state already in force and the +// account now signed in. A host holds two pieces of state — which layer disabled bypass +// mode, and for a server-delivered disable, the account it was pinned to — and cannot +// decide the next step from the new policy alone. Three rules make that so, and each one is +// a control rather than a preference. A device/MDM disable outranks the account, so a later +// server answer never lifts it. An unknown policy, which is what a failed fetch or a +// refusal this build cannot enforce yields, preserves whatever is in force instead of +// reverting to permissive. And a server disable is lifted only when the account it was +// pinned to is no longer the signed-in one, which is what makes signing in to an +// unrestricted account recover without a restart while a reconnect on the same restricted +// account does not. Hosts call this rather than reimplementing it because getting the +// pinning backwards either strands a disable on an account that never carried one or drops +// one that still applies. `setDisabledState` is omitted when the host must leave its stored +// flag untouched, which is distinct from being told to set it to false. +// +// RPC method: managedSettings.resolveBypassDisableTransition. +// +// Parameters: The bypass-permissions state a host holds today, the account now signed in, +// and what the managed-policy resolution said. +// +// Returns: The bypass-permissions state a host should hold after the resolution, and what +// it must do to get there. +// Internal: ResolveBypassDisableTransition is part of the SDK's internal +// handshake/plumbing; external callers should not use it. +func (a *InternalServerManagedSettingsAPI) ResolveBypassDisableTransition(ctx context.Context, params *ManagedSettingsResolveBypassDisableTransitionParams) (*ManagedSettingsResolveBypassDisableTransitionResult, error) { + raw, err := a.client.Request(ctx, "managedSettings.resolveBypassDisableTransition", params) + if err != nil { + return nil, err + } + var result ManagedSettingsResolveBypassDisableTransitionResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +// ResolveDeviceForceLoginOrgsGate evaluates the device-channel enterprise sign-in pin (MDM +// plist, registry, or managed-settings file) against a credential, without resolving any +// server-delivered policy. This is the gate an entry point runs before it branches, and the +// only one that can refuse a developer's first sign-in: it is local, needs no credential, +// and a server-delivered pin only ever reaches accounts already inside the organization. It +// fails closed by construction — a discovery that cannot determine whether a pin exists +// refuses rather than allows, because a control that fails open under a triggerable +// condition is not a control. Hosts that also want the server channel should call +// `managedSettings.resolveStartupPolicy`, which folds both channels into one verdict. +// +// RPC method: managedSettings.resolveDeviceForceLoginOrgsGate. +// +// Parameters: The credential and environment to evaluate the device-channel sign-in pin +// against. +// +// Returns: The verdict of the `forceLoginOrgs` enterprise sign-in pin for one credential. A +// managed device may pin Copilot to an approved set of GitHub organizations so a developer +// cannot sign in with a personal — or another enterprise's — account and escape every +// control that keys off the signed-in identity. A host must refuse to proceed when +// `allowed` is false; the remaining fields explain the refusal rather than qualifying it. +// Internal: ResolveDeviceForceLoginOrgsGate is part of the SDK's internal +// handshake/plumbing; external callers should not use it. +func (a *InternalServerManagedSettingsAPI) ResolveDeviceForceLoginOrgsGate(ctx context.Context, params *ManagedSettingsResolveDeviceForceLoginOrgsGateParams) (*ForceLoginOrgsDecision, error) { + raw, err := a.client.Request(ctx, "managedSettings.resolveDeviceForceLoginOrgsGate", params) + if err != nil { + return nil, err + } + var result ForceLoginOrgsDecision + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +// ResolveEffectiveValue composes the supplied server, device/MDM, device sandbox floor and +// policy-helper layers into the single effective managed policy a host should enforce, +// resolving each key across the layers by the runtime's own precedence rules. Hosts that +// assemble managed layers themselves use this so their precedence matches the runtime's +// exactly instead of reimplementing it. The composition is not a pure function of its +// inputs: an advisory sandbox force-on that arrived over the server channel is declined +// when this host cannot run a sandbox, so the result reflects the runtime host's +// capabilities, and the returned settings carry `sandboxAdvisoryFloorDeclined: true` when +// that happened. Enforced channels (device/MDM and policy helper) are never declined, and a +// server force-on carrying `failIfUnavailable` is preserved so the session fails loudly +// instead of quietly running unsandboxed. Rejects when a supplied layer is present but is +// not a JSON object, rather than treating it as unmanaged, because silently dropping a +// malformed policy would hand the user's own layer the win on exactly the keys the policy +// exists to control. +// +// RPC method: managedSettings.resolveEffectiveValue. +// +// Parameters: The managed-settings layers to compose into an effective policy. Every layer +// is optional; omitting all of them yields no effective policy. +// +// Returns: The effective managed policy composed from the supplied layers. +// Internal: ResolveEffectiveValue is part of the SDK's internal handshake/plumbing; +// external callers should not use it. +func (a *InternalServerManagedSettingsAPI) ResolveEffectiveValue(ctx context.Context, params *ManagedSettingsResolveEffectiveValueParams) (*ManagedSettingsResolveEffectiveValueResult, error) { + raw, err := a.client.Request(ctx, "managedSettings.resolveEffectiveValue", params) + if err != nil { + return nil, err + } + var result ManagedSettingsResolveEffectiveValueResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +// ResolveStartupPolicy resolves the managed (enterprise policy) settings a host must +// enforce from startup, and judges the account against the enterprise sign-in pin in the +// same pass. This composes the layers the way `managedSettings.ingest` does and then +// answers the two questions a host cannot answer for itself: which fail-closed floors apply +// when a layer could not be confirmed, and whether this credential is allowed to sign in at +// all. The sign-in verdict is the reason a host must not assemble this itself from +// `managedSettings.ingest` — a pinned organization is an access control, and a host that +// composed the layers but skipped the pin would let a developer sign in with a personal or +// another enterprise's account and escape every control that keys off the signed-in +// identity. The verdict is computed on every resolution, including one that is not +// retained, so declining to apply policy never widens access. Set `retainAndApply` to keep +// the resolved policy as this runtime process's startup policy; leave it false to resolve +// without side effects. +// +// RPC method: managedSettings.resolveStartupPolicy. +// +// Parameters: Inputs describing the host and account a startup policy resolution should run +// for. Mirrors `managedSettings.ingest`, and adds the decision of whether the resolved +// policy is retained for this runtime process. +// +// Returns: The startup policy a resolution composed, the per-layer outcome flags a host +// needs to decide how much to trust it, and the enterprise sign-in verdict for the +// credential it ran against. The flags are reported rather than folded into the settings +// because a host must distinguish an unmanaged device from a managed device whose policy +// could not be confirmed, and apply its fail-closed gates in the second case. +// Internal: ResolveStartupPolicy is part of the SDK's internal handshake/plumbing; external +// callers should not use it. +func (a *InternalServerManagedSettingsAPI) ResolveStartupPolicy(ctx context.Context, params *ManagedSettingsResolveStartupPolicyParams) (*ManagedSettingsResolveStartupPolicyResult, error) { + raw, err := a.client.Request(ctx, "managedSettings.resolveStartupPolicy", params) + if err != nil { + return nil, err + } + var result ManagedSettingsResolveStartupPolicyResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +// RetainPolicy retains the supplied managed layers as this runtime process's startup policy +// and re-applies the result to every live session. Hosts that own their own short-lived +// process policy use this, so a guarded mutation is checked against the policy the host +// actually fetched. Retaining is a whole-process replacement rather than a merge, so a host +// that already resolved a good policy must not retain a partial one over it. The failure +// flags are honest inputs rather than assumptions: reporting a fetch or load that failed +// keeps the layers already retained, and the interim fail-closed state, instead of clearing +// them. +// +// RPC method: managedSettings.retainPolicy. +// +// Parameters: The managed layers to retain as this process's startup policy, with the +// outcome of fetching each one. +// Internal: RetainPolicy is part of the SDK's internal handshake/plumbing; external callers +// should not use it. +func (a *InternalServerManagedSettingsAPI) RetainPolicy(ctx context.Context, params *ManagedSettingsRetainPolicyParams) (*ManagedSettingsRetainPolicyResult, error) { + raw, err := a.client.Request(ctx, "managedSettings.retainPolicy", params) + if err != nil { + return nil, err + } + var result ManagedSettingsRetainPolicyResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +// Experimental: InternalServerMCPAPI contains experimental APIs that may change or be +// removed. +type InternalServerMCPAPI internalServerAPI + +// ListBuiltinServerNames lists the MCP servers the runtime supplies itself, so a host that +// disables the built-ins wholesale suppresses exactly the set the runtime would otherwise +// add. The names are fixed for a build and depend on no configuration, session or account. +// +// RPC method: mcp.listBuiltinServerNames. +// +// Returns: Names of the MCP servers the runtime supplies itself. +// Internal: ListBuiltinServerNames is part of the SDK's internal handshake/plumbing; +// external callers should not use it. +func (a *InternalServerMCPAPI) ListBuiltinServerNames(ctx context.Context) (*MCPListBuiltinServerNamesResult, error) { + raw, err := a.client.Request(ctx, "mcp.listBuiltinServerNames", nil) + if err != nil { + return nil, err + } + var result MCPListBuiltinServerNamesResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +// ShouldExcludeGitHubTools reports whether the gh-replaceable GitHub MCP tools may be +// clipped for a session, which a host feeds back as the `excludeGhReplaceableTools` build +// option. The tools are redundant only when the session can reach a shell *and* the host +// has the `gh` that would replace them, so both halves are decided here. The shell half +// runs the platform shell tool through the runtime's own tool-filter matcher, so entry +// forms like `builtin:bash` and `builtin:*` behave exactly as they do when a session builds +// its tool catalog. The host half probes for `gh`, and is skipped entirely when the filters +// already rule the shell out. Host presence alone is not enough: a session restricted to, +// say, `view` would otherwise lose the built-in issue-read fallback while having no shell +// to replace it with. The answer describes the host the runtime runs on, so it is never +// forwarded to a remote engine. +// +// RPC method: mcp.shouldExcludeGitHubTools. +// +// Parameters: The session tool filters that decide whether the session still has a shell to +// run `gh` with. +// +// Returns: Whether the gh-replaceable GitHub MCP tools may be clipped for the session +// described by the request. +// Internal: ShouldExcludeGitHubTools is part of the SDK's internal handshake/plumbing; +// external callers should not use it. +func (a *InternalServerMCPAPI) ShouldExcludeGitHubTools(ctx context.Context, params *MCPShouldExcludeGitHubToolsRequest) (*MCPShouldExcludeGitHubToolsResult, error) { + raw, err := a.client.Request(ctx, "mcp.shouldExcludeGitHubTools", params) + if err != nil { + return nil, err + } + var result MCPShouldExcludeGitHubToolsResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +// Experimental: InternalServerMCPRegistryAPI contains experimental APIs that may change or +// be removed. +type InternalServerMCPRegistryAPI internalServerAPI + +// AllocateRequestId allocates an ID for one cancellable MCP registry search. The ID exists +// before the search starts, so callers can cancel before it starts. The networking stack +// supplies the cancellation namespace. The runtime retains at most 1,024 unused IDs. At +// capacity, another allocation can reclaim an unused ID. Active searches retain their IDs +// until they finish. +// +// RPC method: mcp.registry.allocateRequestId. +// +// Returns: Request id naming a cancellable registry search. +// Internal: AllocateRequestId is part of the SDK's internal handshake/plumbing; external +// callers should not use it. +func (a *InternalServerMCPRegistryAPI) AllocateRequestId(ctx context.Context) (*MCPRegistryRequestIDResult, error) { + raw, err := a.client.Request(ctx, "mcp.registry.allocateRequestId", nil) + if err != nil { + return nil, err + } + var result MCPRegistryRequestIDResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +// Cancel abandons the registry search that uses the given request ID. It acts only on IDs +// from `mcp.registry.allocateRequestId`, so it never cancels another component's request. +// Answers `canceled: true` when it stops a running search. Answers `canceled: false` for +// unknown or reclaimed IDs, completed or canceled searches, and unused reservations. It +// releases an unused reservation, so a later search with that ID is refused. +// +// RPC method: mcp.registry.cancel. +// +// Parameters: Registry search to abandon. +// +// Returns: Whether the cancel reached a live search. +// Internal: Cancel is part of the SDK's internal handshake/plumbing; external callers +// should not use it. +func (a *InternalServerMCPRegistryAPI) Cancel(ctx context.Context, params *MCPRegistryCancelRequest) (*MCPRegistryCancelResult, error) { + raw, err := a.client.Request(ctx, "mcp.registry.cancel", params) + if err != nil { + return nil, err + } + var result MCPRegistryCancelResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +// Searches the MCP registry the supplied credential may read, resolving the registry +// endpoint from policy first. Hosts usually send credential-free `AuthIdentity`; a `token` +// identity can be resolved only when it embeds a token, while `env` and `gh-cli` identities +// can use a token embedded in the request first. The runtime follows registry pages, keeps +// the newest entry per server name, cuts the list to `limit`, and sorts an empty-query +// result by GitHub stars. Each server object is carried opaquely. +// +// RPC method: mcp.registry.search. +// +// Parameters: Registry search terms, the credential to search under, and the request id +// that makes the search cancellable. +// +// Returns: Servers selected from the registry response. +// Internal: Search is part of the SDK's internal handshake/plumbing; external callers +// should not use it. +func (a *InternalServerMCPRegistryAPI) Search(ctx context.Context, params *MCPRegistrySearchRequest) (*MCPRegistrySearchResult, error) { + raw, err := a.client.Request(ctx, "mcp.registry.search", params) + if err != nil { + return nil, err + } + var result MCPRegistrySearchResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +// Experimental: Registry returns experimental APIs that may change or be removed. +func (s *InternalServerMCPAPI) Registry() *InternalServerMCPRegistryAPI { + return (*InternalServerMCPRegistryAPI)(s) +} + +// Experimental: InternalServerPermissionsAPI contains experimental APIs that may change or +// be removed. +type InternalServerPermissionsAPI internalServerAPI + +// EvaluateManagedPolicy interprets a verbatim managed `permissions` slice into the three +// policy questions a host asks before granting elevated permissions: whether +// bypass-permissions mode is forbidden, whether a policy exists that this build could not +// determine, and whether the layer supplies permission rules the session must enforce. An +// absent or empty slice means no managed policy applies. The runtime and its permission +// service share one policy type, so a new managed permission setting is interpreted in +// exactly one place; a host that reimplements the reading would silently diverge the first +// time that set grows. Hosts must fail closed on an undetermined policy rather than +// treating it as permission granted. +// +// RPC method: permissions.evaluateManagedPolicy. +// +// Parameters: The verbatim managed `permissions` slice to interpret. +// +// Returns: The policy questions a host asks before granting elevated permissions. +// Internal: EvaluateManagedPolicy is part of the SDK's internal handshake/plumbing; +// external callers should not use it. +func (a *InternalServerPermissionsAPI) EvaluateManagedPolicy(ctx context.Context, params *PermissionsEvaluateManagedPolicyParams) (*PermissionsEvaluateManagedPolicyResult, error) { + raw, err := a.client.Request(ctx, "permissions.evaluateManagedPolicy", params) + if err != nil { + return nil, err + } + var result PermissionsEvaluateManagedPolicyResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +// Experimental: InternalServerPermissionsFolderTrustAPI contains experimental APIs that may +// change or be removed. +type InternalServerPermissionsFolderTrustAPI internalServerAPI + +// CheckTrust reports whether a folder is trusted, reading the persisted trusted-folders +// list directly rather than through a session. Use this from a host that has no session +// yet, such as a one-shot command deciding whether to honor a checkout's own settings. It +// differs from the session-scoped check in two ways that matter: it takes the configuration +// directory to read the list from, instead of inheriting the session's, and it answers only +// from the persisted list, so trust a user granted for the lifetime of one session is not +// visible here. A host that has a session should prefer the session-scoped check, which +// also covers session-only trust. +// +// RPC method: permissions.folderTrust.checkTrust. +// +// Parameters: A folder to check against a persisted trusted-folders list, and the +// configuration directory holding that list. +// +// Returns: Folder trust check result. +// Internal: CheckTrust is part of the SDK's internal handshake/plumbing; external callers +// should not use it. +func (a *InternalServerPermissionsFolderTrustAPI) CheckTrust(ctx context.Context, params *FolderTrustIsTrustedParams) (*FolderTrustCheckResult, error) { + raw, err := a.client.Request(ctx, "permissions.folderTrust.checkTrust", params) + if err != nil { + return nil, err + } + var result FolderTrustCheckResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +// Experimental: FolderTrust returns experimental APIs that may change or be removed. +func (s *InternalServerPermissionsAPI) FolderTrust() *InternalServerPermissionsFolderTrustAPI { + return (*InternalServerPermissionsFolderTrustAPI)(s) +} + +// Experimental: InternalServerPermissionsLocationsAPI contains experimental APIs that may +// change or be removed. +type InternalServerPermissionsLocationsAPI internalServerAPI + +// CleanupOrphaned drops stored per-location permission grants whose directory no longer +// exists, and reports which location keys were removed. Runs against the permission store +// under the runtime's own Copilot home, the same store +// `session.permissions.locations.resolve` reads. Marked internal because it exists to carry +// a CLI call site off the napi boundary onto the SDK contract; it is migration plumbing, +// not a surface consumers are meant to depend on. It can become public once an SDK consumer +// needs to prune its own permission store. +// +// RPC method: permissions.locations.cleanupOrphaned. +// +// Returns: Which stored location permissions were dropped because their directory is gone. +// Internal: CleanupOrphaned is part of the SDK's internal handshake/plumbing; external +// callers should not use it. +func (a *InternalServerPermissionsLocationsAPI) CleanupOrphaned(ctx context.Context) (*LocationPermissionsCleanupResult, error) { + raw, err := a.client.Request(ctx, "permissions.locations.cleanupOrphaned", nil) + if err != nil { + return nil, err + } + var result LocationPermissionsCleanupResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +// Experimental: Locations returns experimental APIs that may change or be removed. +func (s *InternalServerPermissionsAPI) Locations() *InternalServerPermissionsLocationsAPI { + return (*InternalServerPermissionsLocationsAPI)(s) +} + +// Experimental: InternalServerPersistenceAPI contains experimental APIs that may change or +// be removed. +type InternalServerPersistenceAPI internalServerAPI + +// WriteRawJsonLocked atomically replaces a JSON file's entire contents while holding the +// runtime's per-path lock. The lock lives in the runtime process, so a host that writes +// these files itself takes no part in it and can tear a concurrent runtime write; route the +// write here instead. Unlike a merging write this never reads or parses the existing file, +// so a previously corrupted snapshot is replaced wholesale rather than failing every later +// write. Use it only for callers that always persist a complete snapshot. +// +// RPC method: persistence.writeRawJsonLocked. +// +// Parameters: A complete JSON snapshot to write under the runtime's per-path file lock. +// +// Returns: The outcome of a locked snapshot write. +// Internal: WriteRawJsonLocked is part of the SDK's internal handshake/plumbing; external +// callers should not use it. +func (a *InternalServerPersistenceAPI) WriteRawJsonLocked(ctx context.Context, params *PersistenceWriteRawJSONLockedRequest) (*PersistenceWriteRawJSONLockedResult, error) { + raw, err := a.client.Request(ctx, "persistence.writeRawJsonLocked", params) + if err != nil { + return nil, err + } + var result PersistenceWriteRawJSONLockedResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +// Experimental: InternalServerRepoAPI contains experimental APIs that may change or be +// removed. +type InternalServerRepoAPI internalServerAPI + +// Experimental: InternalServerRepoSettingsAPI contains experimental APIs that may change or +// be removed. +type InternalServerRepoSettingsAPI internalServerAPI + +// Edit atomically edits one allowed top-level repository setting. Reads the raw file, +// preserves unrelated keys, applies one scalar or list edit, validates the result and +// writes under one file lock, so concurrent sibling or list edits are not lost. A malformed +// or unsafe existing file returns `invalid` without a write; rejected keys, invalid values +// and I/O errors reject the call. +// +// RPC method: repo.settings.edit. +// +// Parameters: One atomic edit of an allowed top-level repository setting. +// +// Returns: Outcome of an atomic user or repository settings edit. +// Internal: Edit is part of the SDK's internal handshake/plumbing; external callers should +// not use it. +func (a *InternalServerRepoSettingsAPI) Edit(ctx context.Context, params *RepoSettingsEditRequest) (*SettingsEditResult, error) { + raw, err := a.client.Request(ctx, "repo.settings.edit", params) + if err != nil { + return nil, err + } + var result SettingsEditResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +// ReadRaw reads one repository settings file without settings normalization or layering. +// The existing JSONC parser accepts comments and trailing commas and applies its +// prototype-safety rules. Missing or blank files return an empty valid config. Invalid +// files return an empty config with invalid=true; other read failures reject. +// +// RPC method: repo.settings.readRaw. +// +// Parameters: Which repository settings file to read. +// +// Returns: Parsed repository settings without normalization or layering. +// Internal: ReadRaw is part of the SDK's internal handshake/plumbing; external callers +// should not use it. +func (a *InternalServerRepoSettingsAPI) ReadRaw(ctx context.Context, params *RepoSettingsReadRawRequest) (*RepoSettingsReadRawResult, error) { + raw, err := a.client.Request(ctx, "repo.settings.readRaw", params) + if err != nil { + return nil, err + } + var result RepoSettingsReadRawResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +// WriteAll replaces the repository-overridable settings in one locked write. Allowed keys +// present in settings are validated and written; allowed keys absent from settings are +// removed. Other input keys are ignored, and unknown keys already stored in the file remain +// unchanged. Invalid values or an invalid existing file reject without a partial write. +// +// RPC method: repo.settings.writeAll. +// +// Parameters: The complete set of repository-overridable settings to store. +// Internal: WriteAll is part of the SDK's internal handshake/plumbing; external callers +// should not use it. +func (a *InternalServerRepoSettingsAPI) WriteAll(ctx context.Context, params *RepoSettingsWriteAllRequest) (*RepoSettingsWriteAllResult, error) { + raw, err := a.client.Request(ctx, "repo.settings.writeAll", params) + if err != nil { + return nil, err + } + var result RepoSettingsWriteAllResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +// WriteKey writes or removes one allowed top-level repository setting through the existing +// validator and file lock. Preserves unrelated stored keys. Invalid keys, values, existing +// documents, and I/O errors reject without replacing the file. Deletion requires +// delete=true; otherwise value must be present. +// +// RPC method: repo.settings.writeKey. +// +// Parameters: One key to write or remove in a repository settings file. +// Internal: WriteKey is part of the SDK's internal handshake/plumbing; external callers +// should not use it. +func (a *InternalServerRepoSettingsAPI) WriteKey(ctx context.Context, params *RepoSettingsWriteKeyRequest) (*RepoSettingsWriteKeyResult, error) { + raw, err := a.client.Request(ctx, "repo.settings.writeKey", params) + if err != nil { + return nil, err + } + var result RepoSettingsWriteKeyResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +// Experimental: Settings returns experimental APIs that may change or be removed. +func (s *InternalServerRepoAPI) Settings() *InternalServerRepoSettingsAPI { + return (*InternalServerRepoSettingsAPI)(s) +} + +// Experimental: InternalServerSandboxAPI contains experimental APIs that may change or be +// removed. +type InternalServerSandboxAPI internalServerAPI + +// CheckEnforcementCommand whether one slash command stays reachable while sandbox +// enforcement is unavailable and the session is otherwise blocked. The permitted set is +// deliberately narrow — commands that only report state, and the few that let the user +// repair or leave the situation — and it is keyed on the arguments as well as the name, so +// `/sandbox status` is admitted where `/sandbox enable` is not. A host must ask rather than +// keep its own list: a command this returns false for is one the runtime would refuse +// anyway, and a divergent list either strands the user with no way out or admits a command +// that acts on an unenforced session. +// +// RPC method: sandbox.checkEnforcementCommand. +// +// Parameters: One slash command, as typed, to test against the enforcement-block allowlist. +// +// Returns: True when the command stays available while enforcement is blocked. +// Internal: CheckEnforcementCommand is part of the SDK's internal handshake/plumbing; +// external callers should not use it. +func (a *InternalServerSandboxAPI) CheckEnforcementCommand(ctx context.Context, params *SandboxEnforcementCommandAllowedParams) (*SandboxCheckEnforcementCommandResult, error) { + raw, err := a.client.Request(ctx, "sandbox.checkEnforcementCommand", params) + if err != nil { + return nil, err + } + var result SandboxCheckEnforcementCommandResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +// CheckPlatformSupport reports whether this machine can run an OS-level command sandbox at +// all. A host must ask before it offers sandboxing, because the answer is a property of the +// machine rather than of any setting: on Linux it depends on bubblewrap being installed, +// and on Windows on the OS build supporting BaseContainer. Probing it requires spawning a +// process the first time, so the answer is cached for the life of the runtime. A host that +// assumes support and enables the sandbox anyway produces a session in which every shell +// command fails. +// +// RPC method: sandbox.checkPlatformSupport. +// +// Returns: True when an OS-level sandbox can run on this host. +// Internal: CheckPlatformSupport is part of the SDK's internal handshake/plumbing; external +// callers should not use it. +func (a *InternalServerSandboxAPI) CheckPlatformSupport(ctx context.Context) (*SandboxCheckPlatformSupportResult, error) { + raw, err := a.client.Request(ctx, "sandbox.checkPlatformSupport", nil) + if err != nil { + return nil, err + } + var result SandboxCheckPlatformSupportResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +// CheckWindowsHostLoopbackSupport reports whether this Windows host's BaseContainer +// implementation can reach the host's loopback interface directly, which is what a +// local-network policy needs to avoid routing through the egress proxy. Like +// `sandbox.checkPlatformSupport`, the answer is a property of the machine's OS build rather +// than of any setting, and resolving it the first time spawns a platform probe. A host that +// offers the direct local-network option on a machine that lacks the capability produces a +// session whose sandboxed commands cannot reach local services. Always false off Windows. +// +// RPC method: sandbox.checkWindowsHostLoopbackSupport. +// +// Returns: True when the sandbox on this host can reach host loopback directly. +// Internal: CheckWindowsHostLoopbackSupport is part of the SDK's internal +// handshake/plumbing; external callers should not use it. +func (a *InternalServerSandboxAPI) CheckWindowsHostLoopbackSupport(ctx context.Context) (*SandboxCheckWindowsHostLoopbackSupportResult, error) { + raw, err := a.client.Request(ctx, "sandbox.checkWindowsHostLoopbackSupport", nil) + if err != nil { + return nil, err + } + var result SandboxCheckWindowsHostLoopbackSupportResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +// FilterPickerCandidates checks a batch of `@`-file picker suggestions against a policy +// prepared by `sandbox.preparePickerPolicy` and returns one allow decision per candidate, +// positionally aligned with the request. Batched deliberately: each check canonicalizes a +// path component-by-component through the real filesystem, so one call amortizes that over +// a single hop rather than dozens while the user types. A directory the policy does not +// itself allow can still be kept as a waypoint to an allowed subtree. An absent policy +// keeps every candidate. +// +// RPC method: sandbox.filterPickerCandidates. +// +// Parameters: A batch of file-picker candidates to check against a prepared policy. +// +// Returns: Per-candidate allow decisions, positionally aligned with the request. +// Internal: FilterPickerCandidates is part of the SDK's internal handshake/plumbing; +// external callers should not use it. +func (a *InternalServerSandboxAPI) FilterPickerCandidates(ctx context.Context, params *SandboxFilterPickerCandidatesParams) (*SandboxFilterPickerCandidatesResult, error) { + raw, err := a.client.Request(ctx, "sandbox.filterPickerCandidates", params) + if err != nil { + return nil, err + } + var result SandboxFilterPickerCandidatesResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +// GetDocsUrl returns the documentation URL a host should point a user at when it declines +// or downgrades a sandbox setting. The runtime owns the address so every surface cites the +// same page and a host does not hard-code a link that later moves. +// +// RPC method: sandbox.getDocsUrl. +// +// Returns: The documentation URL a host cites when it declines or downgrades a sandbox +// setting. +// Internal: GetDocsUrl is part of the SDK's internal handshake/plumbing; external callers +// should not use it. +func (a *InternalServerSandboxAPI) GetDocsUrl(ctx context.Context) (*SandboxDocsURLResult, error) { + raw, err := a.client.Request(ctx, "sandbox.getDocsUrl", nil) + if err != nil { + return nil, err + } + var result SandboxDocsURLResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +// GetUnsupportedMessage the explanation a host shows when it refuses an explicit request to +// turn the sandbox on for a machine that cannot run one. Carries the platform-specific +// remedy — install bubblewrap, update Windows — and the documentation link, so a host that +// got `false` from `sandbox.checkPlatformSupport` does not have to compose its own reason +// and cannot describe the same machine differently from the runtime's own spawn path. +// +// RPC method: sandbox.getUnsupportedMessage. +// +// Returns: The user-facing refusal, including the platform remedy and the documentation +// link. +// Internal: GetUnsupportedMessage is part of the SDK's internal handshake/plumbing; +// external callers should not use it. +func (a *InternalServerSandboxAPI) GetUnsupportedMessage(ctx context.Context) (*SandboxGetUnsupportedMessageResult, error) { + raw, err := a.client.Request(ctx, "sandbox.getUnsupportedMessage", nil) + if err != nil { + return nil, err + } + var result SandboxGetUnsupportedMessageResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +// GetUnsupportedSettingDisabledNotice the notice a host shows when a stored `sandbox` +// setting is being ignored for this session because the machine cannot enforce it. Distinct +// from `sandbox.getUnsupportedMessage`, which refuses an explicit request: this one reports +// that a setting the user already saved is inert, states that commands will run +// unsandboxed, and confirms the stored setting itself is left alone. A host that stays +// silent here leaves the user believing a sandbox is active. +// +// RPC method: sandbox.getUnsupportedSettingDisabledNotice. +// +// Returns: The user-facing notice that a saved sandbox setting is inert for this session. +// Internal: GetUnsupportedSettingDisabledNotice is part of the SDK's internal +// handshake/plumbing; external callers should not use it. +func (a *InternalServerSandboxAPI) GetUnsupportedSettingDisabledNotice(ctx context.Context) (*SandboxGetUnsupportedSettingDisabledNoticeResult, error) { + raw, err := a.client.Request(ctx, "sandbox.getUnsupportedSettingDisabledNotice", nil) + if err != nil { + return nil, err + } + var result SandboxGetUnsupportedSettingDisabledNoticeResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +// GetUnsupportedStartupNotice the warning a host prints at startup when a session asked for +// a sandbox on a machine that cannot run one. The third of the unsupported notices, and the +// only one that depends on the policy in force: it names the remedy the reader can actually +// act on, which is the stored setting for an unmanaged host, an administrator for a managed +// one, and waiting for the policy to resolve when the sandbox stayed on only because the +// policy could not be determined. Returns no notice when the machine does support a +// sandbox, so a host can call it unconditionally on the startup path. A host that composes +// its own warning tells a managed user to change a setting their policy will overwrite. +// +// RPC method: sandbox.getUnsupportedStartupNotice. +// +// Parameters: The managed sandbox policy in force, which selects which remedy the notice +// offers. +// +// Returns: The startup warning, absent when the machine can run a sandbox. +// Internal: GetUnsupportedStartupNotice is part of the SDK's internal handshake/plumbing; +// external callers should not use it. +func (a *InternalServerSandboxAPI) GetUnsupportedStartupNotice(ctx context.Context, params *SandboxUnsupportedStartupNoticeParams) (*SandboxUnsupportedStartupNoticeResult, error) { + raw, err := a.client.Request(ctx, "sandbox.getUnsupportedStartupNotice", params) + if err != nil { + return nil, err + } + var result SandboxUnsupportedStartupNoticeResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +// PreparePickerPolicy builds the effective filesystem policy one `@`-file picker checks its +// suggestions against, so Git discovery and grant canonicalization run once for the +// picker's lifetime instead of on every debounced keystroke. Returns no policy when the +// sandbox is absent or disabled, which the paired filter reads as "keep every candidate". A +// host must pair this with `sandbox.filterPickerCandidates` rather than resolve a grant +// list itself, because the picker's grants are narrower than a file tool's — the same path +// a built-in tool may read can be one the picker must not offer. +// +// RPC method: sandbox.preparePickerPolicy. +// +// Parameters: The working directory and sandbox settings to build a file-picker policy from. +// +// Returns: The prepared file-picker policy, or none when the sandbox is disabled. +// Internal: PreparePickerPolicy is part of the SDK's internal handshake/plumbing; external +// callers should not use it. +func (a *InternalServerSandboxAPI) PreparePickerPolicy(ctx context.Context, params *SandboxPreparePickerPolicyParams) (*SandboxPreparePickerPolicyResult, error) { + raw, err := a.client.Request(ctx, "sandbox.preparePickerPolicy", params) + if err != nil { + return nil, err + } + var result SandboxPreparePickerPolicyResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +// ResolveFlooredConfig raises a user's sandbox settings to an administrator's managed +// sandbox floor and returns the result a session should actually run under. The floor can +// only tighten: an enterprise force-on is reflected even when the user's own `enabled` is +// false, and a user cannot lower a setting the floor raised. The result is normalized +// through the declared sandbox shape, so a key reorder or an undeclared passthrough key is +// not mistaken for a policy change by callers that diff two results. A host must call this +// rather than merge the two layers itself, because reimplementing the direction of the fold +// is what turns an administrator's floor into a suggestion. +// +// RPC method: sandbox.resolveFlooredConfig. +// +// Parameters: The user's sandbox settings and the managed sandbox floor to raise them to. +// +// Returns: The user's sandbox settings raised to the managed floor. +// Internal: ResolveFlooredConfig is part of the SDK's internal handshake/plumbing; external +// callers should not use it. +func (a *InternalServerSandboxAPI) ResolveFlooredConfig(ctx context.Context, params *SandboxResolveFlooredConfigParams) (*SandboxResolveFlooredConfigResult, error) { + raw, err := a.client.Request(ctx, "sandbox.resolveFlooredConfig", params) + if err != nil { + return nil, err + } + var result SandboxResolveFlooredConfigResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +// ResolveForOptional the sandbox a single named feature actually launches under, which is +// not the same as the session's sandbox. A feature is routed out of the sandbox by its own +// flag — `sandboxMcpServers`, `sandboxLspServers` — so an enabled session can still start +// one server confined and another free. Returns a disabled sandbox rather than an error +// when the host has no sandbox slice at all, so an absent config and an opted-out feature +// read alike at the launch site. A host must ask per feature rather than reuse the session +// config, because reading only `enabled` confines a server the user opted out of, or +// reports as sandboxed one that is not. +// +// RPC method: sandbox.resolveForOptional. +// +// Parameters: A sandbox config and the per-feature routing flag to resolve it for. +// +// Returns: The sandbox a named feature actually launches under. +// Internal: ResolveForOptional is part of the SDK's internal handshake/plumbing; external +// callers should not use it. +func (a *InternalServerSandboxAPI) ResolveForOptional(ctx context.Context, params *SandboxEffectiveForOptionalParams) (*SandboxEffectiveForOptionalResult, error) { + raw, err := a.client.Request(ctx, "sandbox.resolveForOptional", params) + if err != nil { + return nil, err + } + var result SandboxEffectiveForOptionalResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +// Experimental: InternalServerSandboxProxySecretAPI contains experimental APIs that may +// change or be removed. +type InternalServerSandboxProxySecretAPI internalServerAPI + +// Delete removes the stored sandbox proxy password from the credential store +// `sandbox.proxySecret.getLocation` names. Succeeds when no entry exists, so it is safe to +// call while clearing a proxy password whose origin is unknown. Marked internal because it +// exists to carry a CLI call site off the napi boundary onto the SDK contract. +// +// RPC method: sandbox.proxySecret.delete. +// Internal: Delete is part of the SDK's internal handshake/plumbing; external callers +// should not use it. +func (a *InternalServerSandboxProxySecretAPI) Delete(ctx context.Context) (*SandboxProxySecretDeleteResult, error) { + raw, err := a.client.Request(ctx, "sandbox.proxySecret.delete", nil) + if err != nil { + return nil, err + } + var result SandboxProxySecretDeleteResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +// Get reads the stored sandbox proxy password from the credential store +// `sandbox.proxySecret.getLocation` names, preferring the OS keychain and falling back to +// the on-disk store. Reports an absent value rather than failing when no entry exists. +// Marked internal because it exists to carry a CLI call site off the napi boundary onto the +// SDK contract. +// +// RPC method: sandbox.proxySecret.get. +// +// Returns: The stored sandbox proxy password, if one is present. +// Internal: Get is part of the SDK's internal handshake/plumbing; external callers should +// not use it. +func (a *InternalServerSandboxProxySecretAPI) Get(ctx context.Context) (*SandboxProxySecretGetResult, error) { + raw, err := a.client.Request(ctx, "sandbox.proxySecret.get", nil) + if err != nil { + return nil, err + } + var result SandboxProxySecretGetResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +// GetLocation reports where this runtime keeps the sandbox proxy password: the credential +// store directory and the entry identifier within it. Both are derived from the +// configuration directory this runtime was started with, so a host does not resolve them +// itself. Read this to recognize a `${secret:…}` value in stored sandbox settings as this +// runtime's own proxy password rather than a reference the user wrote by hand. Marked +// internal because it exists to carry a CLI call site off the napi boundary onto the SDK +// contract. +// +// RPC method: sandbox.proxySecret.getLocation. +// +// Returns: Where this runtime keeps the sandbox proxy password. +// Internal: GetLocation is part of the SDK's internal handshake/plumbing; external callers +// should not use it. +func (a *InternalServerSandboxProxySecretAPI) GetLocation(ctx context.Context) (*SandboxProxySecretLocation, error) { + raw, err := a.client.Request(ctx, "sandbox.proxySecret.getLocation", nil) + if err != nil { + return nil, err + } + var result SandboxProxySecretLocation + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +// Save stores the sandbox proxy password in the credential store +// `sandbox.proxySecret.getLocation` names, replacing any existing entry. Reports whether +// the OS keychain accepted it or it fell back to the on-disk store. Marked internal because +// it exists to carry a CLI call site off the napi boundary onto the SDK contract. +// +// RPC method: sandbox.proxySecret.save. +// +// Parameters: The sandbox proxy password to store. +// +// Returns: Where the sandbox proxy password was stored. +// Internal: Save is part of the SDK's internal handshake/plumbing; external callers should +// not use it. +func (a *InternalServerSandboxProxySecretAPI) Save(ctx context.Context, params *SandboxProxySecretSaveRequest) (*SandboxProxySecretSaveResult, error) { + raw, err := a.client.Request(ctx, "sandbox.proxySecret.save", params) + if err != nil { + return nil, err + } + var result SandboxProxySecretSaveResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +// Experimental: ProxySecret returns experimental APIs that may change or be removed. +func (s *InternalServerSandboxAPI) ProxySecret() *InternalServerSandboxProxySecretAPI { + return (*InternalServerSandboxProxySecretAPI)(s) +} + +// Experimental: InternalServerSandboxSettingsAPI contains experimental APIs that may change +// or be removed. +type InternalServerSandboxSettingsAPI internalServerAPI + +// ApplyDefaults resolves a stored sandbox settings object into the effective one, filling +// every value the user left unset with its default. Read this to render or reason about the +// sandbox configuration a session would actually run under, rather than the sparse document +// the user wrote. +// +// RPC method: sandbox.settings.applyDefaults. +// +// Parameters: The stored sandbox settings to resolve against the defaults. +// +// Returns: The effective sandbox settings, with every unset value resolved to its default. +// Internal: ApplyDefaults is part of the SDK's internal handshake/plumbing; external +// callers should not use it. +func (a *InternalServerSandboxSettingsAPI) ApplyDefaults(ctx context.Context, params *SandboxApplyDefaultsRequest) (*SandboxSettingsApplyDefaultsResult, error) { + raw, err := a.client.Request(ctx, "sandbox.settings.applyDefaults", params) + if err != nil { + return nil, err + } + var result SandboxSettingsApplyDefaultsResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +// Experimental: Settings returns experimental APIs that may change or be removed. +func (s *InternalServerSandboxAPI) Settings() *InternalServerSandboxSettingsAPI { + return (*InternalServerSandboxSettingsAPI)(s) +} + // Experimental: InternalServerSessionsAPI contains experimental APIs that may change or be // removed. type InternalServerSessionsAPI internalServerAPI @@ -31402,19 +35912,707 @@ func (a *InternalServerSessionsAPI) UpdateWorkspaceFields(ctx context.Context, p return &result, nil } +// Experimental: InternalServerUserAPI contains experimental APIs that may change or be +// removed. +type InternalServerUserAPI internalServerAPI + +// Experimental: InternalServerUserSettingsAPI contains experimental APIs that may change or +// be removed. +type InternalServerUserSettingsAPI internalServerAPI + +// ApplyTopKey validates one top-level setting subtree and writes just that key to +// settings.json as a single step. Doing both here closes the race a caller cannot close +// itself: between a separate preflight read and the write, the file may change. Rewriting +// only the touched key keeps legacy siblings out of settings.json. Answers a status rather +// than failing, so a validation problem and a write problem stay distinguishable. +// +// RPC method: user.settings.applyTopKey. +// +// Parameters: A single top-level setting subtree to validate and write. +// +// Returns: Outcome of validating and writing one top-level setting subtree. +// Internal: ApplyTopKey is part of the SDK's internal handshake/plumbing; external callers +// should not use it. +func (a *InternalServerUserSettingsAPI) ApplyTopKey(ctx context.Context, params *UserSettingsApplyTopKeyRequest) (*UserSettingsApplyTopKeyResult, error) { + raw, err := a.client.Request(ctx, "user.settings.applyTopKey", params) + if err != nil { + return nil, err + } + var result UserSettingsApplyTopKeyResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +// BuildRuntime resolves the effective runtime settings for a host that is about to start a +// session, layering the given overrides over the settings documents, the environment and +// the platform defaults. A host cannot assemble this itself: the precedence between a user +// document, a repository document, the managed layers and the environment is the runtime's +// own rule, and a host that re-implements it drifts from the server it is configuring. The +// working directory, environment and platform are taken as arguments rather than read from +// the server process, so a host resolving settings for a directory other than its own gets +// that directory's answer. Nothing is written; this only computes. +// +// RPC method: user.settings.buildRuntime. +// +// Parameters: The overrides and host context to resolve effective runtime settings from. +// +// Returns: The effective runtime settings, keyed by setting name. Keys this runtime does +// not recognize are preserved rather than dropped, so a caller reading a document written +// by a newer runtime keeps every value. +// Internal: BuildRuntime is part of the SDK's internal handshake/plumbing; external callers +// should not use it. +func (a *InternalServerUserSettingsAPI) BuildRuntime(ctx context.Context, params *UserSettingsBuildRuntimeRequest) (*UserSettingsBuildRuntimeResult, error) { + raw, err := a.client.Request(ctx, "user.settings.buildRuntime", params) + if err != nil { + return nil, err + } + var result UserSettingsBuildRuntimeResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +// DiagnoseProblems diagnoses a set of already-loaded settings layers, reporting each key +// that will not take effect and why. Settings load tolerantly so that one bad key does not +// stop a session from starting; this method surfaces what was dropped so a caller can +// report it instead of silently ignoring the user's intent. Takes the layers as given +// rather than reading them, so a caller can diagnose exactly the content it is showing, +// including unsaved edits. Never echoes a setting's value, because a value may be a token +// or another secret. Marked internal because it exists to carry a CLI call site off the +// native addon, not to serve a consumer-facing need; it becomes public once an SDK consumer +// needs to inspect settings health itself. +// +// RPC method: user.settings.diagnoseProblems. +// +// Parameters: The settings layers to diagnose. +// +// Returns: The settings that will not take effect across the given layers. +// Internal: DiagnoseProblems is part of the SDK's internal handshake/plumbing; external +// callers should not use it. +func (a *InternalServerUserSettingsAPI) DiagnoseProblems(ctx context.Context, params *UserSettingsProblemsRequest) (*UserSettingsProblemsResult, error) { + raw, err := a.client.Request(ctx, "user.settings.diagnoseProblems", params) + if err != nil { + return nil, err + } + var result UserSettingsProblemsResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +// Edit atomically edits one canonical user setting inside its settings-file lock. Reads, +// canonicalizes touched aliases, applies the edit, validates the resulting top-level +// setting and writes before releasing the lock. An explicit autoUpdatesChannel set +// completes the staff migration in the same transaction. Only a malformed existing file +// returns `invalid`; refused keys, invalid values and I/O errors reject the call. +// +// RPC method: user.settings.edit. +// +// Parameters: One atomic edit of a canonical setting in settings.json. +// +// Returns: Outcome of an atomic user or repository settings edit. +// Internal: Edit is part of the SDK's internal handshake/plumbing; external callers should +// not use it. +func (a *InternalServerUserSettingsAPI) Edit(ctx context.Context, params *UserSettingsEditRequest) (*SettingsEditResult, error) { + req := map[string]any{} + if params != nil { + req["canonicalPath"] = params.CanonicalPath + if params.ConfigDir != nil { + req["configDir"] = *params.ConfigDir + } + req["operation"] = params.Operation + if params.Value != nil { + req["value"] = params.Value + } + if params.Operation != SettingsEditOperationRemove { + if _, ok := req["value"]; !ok { + req["value"] = nil + } + } + } + raw, err := a.client.Request(ctx, "user.settings.edit", req) + if err != nil { + return nil, err + } + var result SettingsEditResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +// GetPath reports the on-disk path of the settings document `user.settings.load` and +// `user.settings.set` read and write. Resolved the same way those methods resolve it, so a +// host that shows or opens the settings file names the file the runtime actually uses, +// including the extensionless legacy `settings` file when that is what is present. +// +// RPC method: user.settings.getPath. +// +// Returns: The on-disk location of the user's settings document. +// Internal: GetPath is part of the SDK's internal handshake/plumbing; external callers +// should not use it. +func (a *InternalServerUserSettingsAPI) GetPath(ctx context.Context) (*UserSettingsPathResult, error) { + raw, err := a.client.Request(ctx, "user.settings.getPath", nil) + if err != nil { + return nil, err + } + var result UserSettingsPathResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +// GetPaths resolves the on-disk locations of the settings files this runtime reads, so a +// consumer can show or open them. The user path is the same file `user.settings.get`, +// `user.settings.load`, and `user.settings.set` operate on. Supply `repoRoot` to also +// resolve that repository's checked-in and local settings paths. A path is reported whether +// or not the file exists. +// +// RPC method: user.settings.getPaths. +// +// Parameters: Selects which settings file paths to resolve. +// +// Returns: On-disk locations of the settings files this runtime reads. A path is reported +// whether or not the file exists. +// Internal: GetPaths is part of the SDK's internal handshake/plumbing; external callers +// should not use it. +func (a *InternalServerUserSettingsAPI) GetPaths(ctx context.Context, params *UserSettingsGetPathsRequest) (*UserSettingsGetPathsResult, error) { + raw, err := a.client.Request(ctx, "user.settings.getPaths", params) + if err != nil { + return nil, err + } + var result UserSettingsGetPathsResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +// GetUnknownKeysWarning reports the top-level keys in the user settings document that the +// runtime does not recognize, phrased for display to the user. This is a separate question +// from the warning `user.settings.loadWithWarning` returns: that one describes parts of the +// document the loader had to ignore to load the rest, whereas an unknown key parses cleanly +// and is simply not a setting, so a host that surfaces only the load warning silently +// accepts a misspelled key that will never take effect. Nothing is reported when the +// document is absent, unreadable, or failed to load, because an unknown-key list derived +// from a document the runtime could not read would be misleading rather than helpful. +// Inspection never modifies the document. +// +// RPC method: user.settings.getUnknownKeysWarning. +// +// Parameters: Selects the configuration directory whose user settings document should be +// inspected for unrecognized top-level keys. +// +// Returns: The unrecognized top-level keys found in the user settings document, already +// phrased for display. +// Internal: GetUnknownKeysWarning is part of the SDK's internal handshake/plumbing; +// external callers should not use it. +func (a *InternalServerUserSettingsAPI) GetUnknownKeysWarning(ctx context.Context, params *UserSettingsUnknownKeysWarningRequest) (*UserSettingsUnknownKeysWarningResult, error) { + raw, err := a.client.Request(ctx, "user.settings.getUnknownKeysWarning", params) + if err != nil { + return nil, err + } + var result UserSettingsUnknownKeysWarningResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +// ListGovernanceKeys lists the top-level user-setting keys by who is allowed to govern +// them: `repo` keys a repository may override through `.github/copilot/settings.json`, +// `claudeRepo` keys a repository may override through a Claude-compatible config, and +// `managed` keys an enterprise policy may fix. The lists are static for a given runtime +// build and fail closed: a key absent from a list cannot be governed that way. +// +// RPC method: user.settings.listGovernanceKeys. +// +// Returns: Top-level user-setting keys grouped by who may govern them. +// Internal: ListGovernanceKeys is part of the SDK's internal handshake/plumbing; external +// callers should not use it. +func (a *InternalServerUserSettingsAPI) ListGovernanceKeys(ctx context.Context) (*UserSettingsGovernanceKeysResult, error) { + raw, err := a.client.Request(ctx, "user.settings.listGovernanceKeys", nil) + if err != nil { + return nil, err + } + var result UserSettingsGovernanceKeysResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +// LoadAndMergeRepo reads a repository's settings and merges them over a supplied user layer +// in one step, returning the settings a consumer should act on inside that repository. Use +// this rather than reading the two layers separately: the merge is per setting and +// collection-aware, so a repository adds to the collections it is allowed to influence +// without replacing the user's own entries, and a whole-document overlay would get that +// wrong. Any diagnostic raised while reading the repository layer is returned rather than +// printed, so the consumer decides how to surface it; the settings that did load are merged +// regardless. +// +// RPC method: user.settings.loadAndMergeRepo. +// +// Parameters: A repository to read settings from, and the user layer they are merged over. +// +// Returns: The settings a consumer should act on inside a repository, together with any +// diagnostic raised while reading the repository layer. +// Internal: LoadAndMergeRepo is part of the SDK's internal handshake/plumbing; external +// callers should not use it. +func (a *InternalServerUserSettingsAPI) LoadAndMergeRepo(ctx context.Context, params *UserSettingsLoadAndMergeRepoRequest) (*UserSettingsLoadAndMergeRepoResult, error) { + raw, err := a.client.Request(ctx, "user.settings.loadAndMergeRepo", params) + if err != nil { + return nil, err + } + var result UserSettingsLoadAndMergeRepoResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +// LoadForConfigDir reads the user's settings document exactly as `user.settings.load` does, +// but from a caller-supplied configuration directory instead of the one the server resolved +// for itself. Use this when a consumer scopes a session to its own Copilot home — the SDK's +// per-session `configDir` override — so the settings read match the directory that session +// actually uses. An absent or empty `configDir` resolves the server's own home, making this +// identical to `user.settings.load`. Hook-validation relaxations are deliberately not +// caller-supplied: they continue to come from the server's environment, so a consumer +// cannot widen the host's hook policy through this method. +// +// RPC method: user.settings.loadForConfigDir. +// +// Parameters: Selects the configuration directory whose user settings to read. +// +// Returns: The stored user settings document, keyed by setting name. +// Internal: LoadForConfigDir is part of the SDK's internal handshake/plumbing; external +// callers should not use it. +func (a *InternalServerUserSettingsAPI) LoadForConfigDir(ctx context.Context, params *UserSettingsLoadForConfigDirRequest) (*UserSettingsLoadForConfigDirResult, error) { + raw, err := a.client.Request(ctx, "user.settings.loadForConfigDir", params) + if err != nil { + return nil, err + } + var result UserSettingsLoadForConfigDirResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +// LoadForEdit reads settings.json as stored for a caller that is about to edit it. Unlike +// `user.settings.get`, this returns file values rather than per-key metadata and defaults. +// Reports `missing` for an absent file and `invalid` when the existing file will not parse. +// Retired config.json and extensionless files are not read. +// +// RPC method: user.settings.loadForEdit. +// +// Returns: Contents of settings.json as stored, for a caller about to rewrite it. +// Internal: LoadForEdit is part of the SDK's internal handshake/plumbing; external callers +// should not use it. +func (a *InternalServerUserSettingsAPI) LoadForEdit(ctx context.Context) (*UserSettingsLoadForEditResult, error) { + raw, err := a.client.Request(ctx, "user.settings.loadForEdit", nil) + if err != nil { + return nil, err + } + var result UserSettingsLoadForEditResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +// LoadRepo reads a repository's checked-in settings document as stored, normalized, +// together with any diagnostic raised while reading it. This is the repository half of the +// layering `user.settings.get` reports: `user.settings.load` reads the user's own document, +// and this reads the one a repository checks in to govern the keys +// `user.settings.governanceKeys` lists as repo-governable. A document that fails to parse, +// or that sets a key a repository may not govern, is reported through `warning` while the +// settings that did load are still returned, so a consumer surfaces the diagnostic and +// continues rather than failing the session. When `repoRoot` is the user's home directory +// the Claude-compatible configuration is skipped, so a home-directory session does not +// inherit a personal config as repository policy. +// +// RPC method: user.settings.loadRepo. +// +// Parameters: Selects the repository whose checked-in settings to read. +// +// Returns: A repository's settings as stored, together with any diagnostic raised while +// reading them. +// Internal: LoadRepo is part of the SDK's internal handshake/plumbing; external callers +// should not use it. +func (a *InternalServerUserSettingsAPI) LoadRepo(ctx context.Context, params *UserSettingsLoadRepoRequest) (*UserSettingsLoadRepoResult, error) { + raw, err := a.client.Request(ctx, "user.settings.loadRepo", params) + if err != nil { + return nil, err + } + var result UserSettingsLoadRepoResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +// LoadWithWarning reads the user's stored settings together with the diagnostic raised +// while reading them. This is `user.settings.load` plus the reason part of the document was +// ignored: that one answers the document alone, so a caller cannot tell a clean read from a +// salvaged one. Read this when the caller is about to write the file back, where accepting +// a partial parse would discard every value the loader could not read. The settings that +// did load are still reported alongside the warning, so a consumer may surface it and +// continue instead of failing. +// +// RPC method: user.settings.loadWithWarning. +// +// Returns: The user's stored settings, together with any diagnostic raised while reading +// them. +// Internal: LoadWithWarning is part of the SDK's internal handshake/plumbing; external +// callers should not use it. +func (a *InternalServerUserSettingsAPI) LoadWithWarning(ctx context.Context) (*UserSettingsLoadWithWarningResult, error) { + raw, err := a.client.Request(ctx, "user.settings.loadWithWarning", nil) + if err != nil { + return nil, err + } + var result UserSettingsLoadWithWarningResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +// MergeManagedAndUser folds a user settings layer over a managed (enterprise policy) layer +// and returns the merged settings. The managed layer's governance keys win, `autoTier` +// resolves through its overridable form, and the two per-entry control maps +// (`enabledPlugins`, `extraKnownMarketplaces`) union across the layers and are then stamped +// back from the managed layer, so each entry an administrator declares wins while the user +// keeps control of the rest. The parameters are named for their layers rather than being a +// generic base/override pair: nothing about a settings object identifies which layer +// produced it, so the runtime cannot detect a transposed call, and transposing one resolves +// the governance keys in the user's favour instead of the administrator's. Supplying only +// one layer returns it unchanged, and supplying neither returns an empty object. Rejects a +// managed `autoTier` that is neither a user-configurable tier nor a single-key overridable +// form, rather than silently dropping it. +// +// RPC method: user.settings.mergeManagedAndUser. +// +// Parameters: The managed (enterprise policy) layer and the user layer to fold together. +// The field names carry the layer identity: nothing about a settings object identifies +// which layer produced it, so a generic base/override pair would let a caller transpose +// them and silently resolve the governance keys in the user's favour. +// +// Returns: The user layer folded over the managed layer. +// Internal: MergeManagedAndUser is part of the SDK's internal handshake/plumbing; external +// callers should not use it. +func (a *InternalServerUserSettingsAPI) MergeManagedAndUser(ctx context.Context, params *UserSettingsMergeManagedAndUserParams) (*UserSettingsMergeManagedAndUserResult, error) { + raw, err := a.client.Request(ctx, "user.settings.mergeManagedAndUser", params) + if err != nil { + return nil, err + } + var result UserSettingsMergeManagedAndUserResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +// MergeRepoValue folds a repository's settings layer into the user layer. Each +// repo-overridable key carries a directional policy chosen so a repository can only add +// restrictions and never relax the user's own. The two per-entry managed control maps are +// the exception those directional rules cannot express — a repository may legitimately +// enable plugins and declare marketplaces — so they are re-stamped afterwards from the +// runtime's ambient managed policy. That makes the result depend on the runtime process's +// managed state and not on the arguments alone: a host must call this rather than +// reimplement the fold, or a repository could relax a restriction the administrator +// imposed. Rejects when either layer is not a JSON object. +// +// RPC method: user.settings.mergeRepoValue. +// +// Parameters: The user layer and the repository layer to fold together. +// +// Returns: The repository layer folded into the user layer. +// Internal: MergeRepoValue is part of the SDK's internal handshake/plumbing; external +// callers should not use it. +func (a *InternalServerUserSettingsAPI) MergeRepoValue(ctx context.Context, params *UserSettingsMergeRepoValueParams) (*UserSettingsMergeRepoValueResult, error) { + raw, err := a.client.Request(ctx, "user.settings.mergeRepoValue", params) + if err != nil { + return nil, err + } + var result UserSettingsMergeRepoValueResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +// MigrateObsoleteOnDisk runs the one-time cleanup that removes obsolete CLI-owned settings +// and legacy rubber-duck keys from the user's settings document, and reports what it did. +// Idempotent and safe to call on every startup: with nothing obsolete left it reports +// `unchanged` and writes nothing. The cleanup preserves every key it does not recognize, so +// a setting written by a newer host is never dropped by an older one, and a document that +// fails to parse is reported `invalid` and left untouched rather than rewritten from a +// partial read. The cleaned document is not returned: it is written before this replies, so +// `user.settings.load` reads it authoritatively. A host that surfaces unrecognized keys +// through `user.settings.unknownKeys` wants this first, because the keys this retires would +// otherwise be shown to the user as if they were misspelled settings. +// +// RPC method: user.settings.migrateObsoleteOnDisk. +// +// Parameters: Selects the configuration directory whose settings document to clean. +// +// Returns: The outcome of the one-time obsolete-settings cleanup. +// Internal: MigrateObsoleteOnDisk is part of the SDK's internal handshake/plumbing; +// external callers should not use it. +func (a *InternalServerUserSettingsAPI) MigrateObsoleteOnDisk(ctx context.Context, params *UserSettingsMigrateObsoleteOnDiskRequest) (*UserSettingsMigrateObsoleteOnDiskResult, error) { + raw, err := a.client.Request(ctx, "user.settings.migrateObsoleteOnDisk", params) + if err != nil { + return nil, err + } + var result UserSettingsMigrateObsoleteOnDiskResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +// MigrateStaffLogLevelOnce runs the one-time migration that moves a staff account's log +// level to the current default, and records that it ran. Idempotent and self-gating: it +// takes a lock over the global-state file and re-reads the completion stamp inside it, so +// two hosts racing at startup cannot both migrate. Only `refreshed` can have changed the +// persisted settings, and the settings it returns are the document re-read after the write, +// so a host should adopt them rather than reloading separately and racing the next writer. +// +// RPC method: user.settings.migrateStaffLogLevelOnce. +// +// Parameters: Selects the configuration directory to run the one-time staff log-level +// migration against, and supplies the caller's record of whether it has already run. +// +// Returns: The outcome of the one-time staff log-level migration. +// Internal: MigrateStaffLogLevelOnce is part of the SDK's internal handshake/plumbing; +// external callers should not use it. +func (a *InternalServerUserSettingsAPI) MigrateStaffLogLevelOnce(ctx context.Context, params *UserSettingsMigrateStaffLogLevelOnceRequest) (*UserSettingsMigrateStaffLogLevelOnceResult, error) { + raw, err := a.client.Request(ctx, "user.settings.migrateStaffLogLevelOnce", params) + if err != nil { + return nil, err + } + var result UserSettingsMigrateStaffLogLevelOnceResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +// MigrateStaffUpdateChannelOnce runs the one-time migration that moves a staff account's +// update channel to stable, and records that it ran. Idempotent and self-gating: it takes a +// lock over the global-state file, re-reads the completion stamp inside it, and returns +// `current` rather than migrating twice, so two hosts racing at startup cannot both +// migrate. Only a `migrated` status means the persisted settings changed and the caller +// should reload them. An account on the experimental channel is deliberately left alone and +// reported as `excluded-experimental`. +// +// RPC method: user.settings.migrateStaffUpdateChannelOnce. +// +// Parameters: The filesystem and environment facts a user-settings file is resolved from. +// Passed explicitly rather than read from the runtime's own process, so a host whose +// configuration directory differs from its process environment resolves the file it means. +// +// Returns: The outcome of the one-time staff update-channel migration. +// Internal: MigrateStaffUpdateChannelOnce is part of the SDK's internal handshake/plumbing; +// external callers should not use it. +func (a *InternalServerUserSettingsAPI) MigrateStaffUpdateChannelOnce(ctx context.Context, params *UserSettingsHostContext) (*UserSettingsMigrateStaffUpdateChannelOnceResult, error) { + raw, err := a.client.Request(ctx, "user.settings.migrateStaffUpdateChannelOnce", params) + if err != nil { + return nil, err + } + var result UserSettingsMigrateStaffUpdateChannelOnceResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +// Normalize rewrites legacy and snake_case setting names in a settings object to their +// canonical spelling. A caller editing settings.json must normalize before writing: a +// legacy key left in place survives the write, is re-normalized on the next load, and +// silently shadows the canonical value the write just stored. +// +// RPC method: user.settings.normalize. +// +// Parameters: Settings object whose key names should be canonicalized. +// +// Returns: Settings object with canonical key names. +// Internal: Normalize is part of the SDK's internal handshake/plumbing; external callers +// should not use it. +func (a *InternalServerUserSettingsAPI) Normalize(ctx context.Context, params *UserSettingsNormalizeRequest) (*UserSettingsNormalizeResult, error) { + raw, err := a.client.Request(ctx, "user.settings.normalize", params) + if err != nil { + return nil, err + } + var result UserSettingsNormalizeResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +// ResetStaffModelSelectionOnce runs the one-time reset that clears a staff account's pinned +// model selection, and reports the timestamp the host should record. Idempotent and +// self-gating: the reset is gated on a fixed cutoff, so a caller that passes back the +// timestamp it recorded gets `didReset: false` rather than a second reset. It applies to +// staff accounts only; any other account is reported as not reset without the settings +// document being opened. Only `didReset: true` means the persisted settings changed, and +// only then are the updated settings returned for the host to adopt. +// +// RPC method: user.settings.resetStaffModelSelectionOnce. +// +// Parameters: Selects the configuration directory to run the one-time staff model-selection +// reset against, and supplies the caller's record of whether it has already run. +// +// Returns: The outcome of the one-time staff model-selection reset. +// Internal: ResetStaffModelSelectionOnce is part of the SDK's internal handshake/plumbing; +// external callers should not use it. +func (a *InternalServerUserSettingsAPI) ResetStaffModelSelectionOnce(ctx context.Context, params *UserSettingsResetStaffModelSelectionOnceRequest) (*UserSettingsResetStaffModelSelectionOnceResult, error) { + raw, err := a.client.Request(ctx, "user.settings.resetStaffModelSelectionOnce", params) + if err != nil { + return nil, err + } + var result UserSettingsResetStaffModelSelectionOnceResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +// SetHookDisabled adds or removes one hook key in the user `disabledHooks` setting, +// rewriting settings.json in place and leaving every other setting untouched. Reports a +// typed outcome rather than failing the call, so a host can tell an unparseable settings +// file apart from a write that could not complete. +// +// RPC method: user.settings.setHookDisabled. +// +// Parameters: One hook to disable or re-enable. +// +// Returns: Outcome of rewriting the `disabledHooks` setting. +// Internal: SetHookDisabled is part of the SDK's internal handshake/plumbing; external +// callers should not use it. +func (a *InternalServerUserSettingsAPI) SetHookDisabled(ctx context.Context, params *UserSettingsSetHookDisabledRequest) (*UserSettingsSetHookDisabledResult, error) { + raw, err := a.client.Request(ctx, "user.settings.setHookDisabled", params) + if err != nil { + return nil, err + } + var result UserSettingsSetHookDisabledResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +// Update merges a partial object into one top-level user setting, re-reading the stored +// value inside this runtime process's settings.json write lock so that concurrent writers +// in this process to different sub-fields of the same key do not clobber each other. Unlike +// `user.settings.set`, which replaces a key's whole value with what the caller last read, +// this keeps sub-fields the caller did not name when both the stored value and supplied +// value are objects; otherwise the supplied value replaces the stored value. Use it +// whenever two independent surfaces own different sub-fields of one setting. Returns the +// key's stored value after the write, in `value`. +// +// RPC method: user.settings.update. +// +// Parameters: Names the user setting to merge into, and the partial object to merge. +// +// Returns: The stored value of the user setting after the merge. +// Internal: Update is part of the SDK's internal handshake/plumbing; external callers +// should not use it. +func (a *InternalServerUserSettingsAPI) Update(ctx context.Context, params *UserSettingsUpdateRequest) (*UserSettingsUpdateResult, error) { + raw, err := a.client.Request(ctx, "user.settings.update", params) + if err != nil { + return nil, err + } + var result UserSettingsUpdateResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +// Validates one top-level setting subtree without writing it, reporting each problem with +// the path it applies to. Scoped to a single top-level key on purpose: validating the whole +// file would reject an edit whenever an unrelated pre-existing field is malformed, locking +// the user out of the very tool that could fix it. +// +// RPC method: user.settings.validate. +// +// Parameters: A single top-level setting subtree to validate. +// +// Returns: Outcome of validating one top-level setting subtree. +// Internal: Validate is part of the SDK's internal handshake/plumbing; external callers +// should not use it. +func (a *InternalServerUserSettingsAPI) Validate(ctx context.Context, params *UserSettingsValidateRequest) (*UserSettingsValidateResult, error) { + raw, err := a.client.Request(ctx, "user.settings.validate", params) + if err != nil { + return nil, err + } + var result UserSettingsValidateResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +// WriteAll replaces the user settings file's entire content in one write. A key absent from +// `settings` is removed, which is what distinguishes this from `user.settings.set`: that +// one merges, so it can never clear a key the caller has dropped. For a caller saving the +// result of a raw edit, a merge would silently resurrect every key the user deleted. +// Records the same one-time mutation marks a per-key write records, so a setting the user +// has now chosen explicitly stops being migrated for them. +// +// RPC method: user.settings.writeAll. +// +// Parameters: The whole content to store in the user settings file. +// Internal: WriteAll is part of the SDK's internal handshake/plumbing; external callers +// should not use it. +func (a *InternalServerUserSettingsAPI) WriteAll(ctx context.Context, params *UserSettingsWriteAllRequest) (*UserSettingsWriteAllResult, error) { + raw, err := a.client.Request(ctx, "user.settings.writeAll", params) + if err != nil { + return nil, err + } + var result UserSettingsWriteAllResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +// Experimental: Settings returns experimental APIs that may change or be removed. +func (s *InternalServerUserAPI) Settings() *InternalServerUserSettingsAPI { + return (*InternalServerUserSettingsAPI)(s) +} + // InternalServerRPC provides internal SDK server-scoped RPC methods (handshake helpers // etc.). Not part of the public API. type InternalServerRPC struct { // Reuse a single struct instead of allocating one for each service on the heap. common internalServerAPI + Account *InternalServerAccountAPI Agents *InternalServerAgentsAPI + FeatureFlags *InternalServerFeatureFlagsAPI Git *InternalServerGitAPI GitHubOwners *InternalServerGitHubOwnersAPI GitHubRepository *InternalServerGitHubRepositoryAPI GlobalState *InternalServerGlobalStateAPI Host *InternalServerHostAPI + Instructions *InternalServerInstructionsAPI + ManagedSettings *InternalServerManagedSettingsAPI + MCP *InternalServerMCPAPI + Permissions *InternalServerPermissionsAPI + Persistence *InternalServerPersistenceAPI + Repo *InternalServerRepoAPI + Sandbox *InternalServerSandboxAPI Sessions *InternalServerSessionsAPI + User *InternalServerUserAPI } // Connect performs the SDK server connection handshake and validates the optional @@ -31449,13 +36647,23 @@ func (a *InternalServerRPC) Connect(ctx context.Context, params *ConnectRequest) func NewInternalServerRPC(client *jsonrpc2.Client) *InternalServerRPC { r := &InternalServerRPC{} r.common = internalServerAPI{client: client} + r.Account = (*InternalServerAccountAPI)(&r.common) r.Agents = (*InternalServerAgentsAPI)(&r.common) + r.FeatureFlags = (*InternalServerFeatureFlagsAPI)(&r.common) r.Git = (*InternalServerGitAPI)(&r.common) r.GitHubOwners = (*InternalServerGitHubOwnersAPI)(&r.common) r.GitHubRepository = (*InternalServerGitHubRepositoryAPI)(&r.common) r.GlobalState = (*InternalServerGlobalStateAPI)(&r.common) r.Host = (*InternalServerHostAPI)(&r.common) + r.Instructions = (*InternalServerInstructionsAPI)(&r.common) + r.ManagedSettings = (*InternalServerManagedSettingsAPI)(&r.common) + r.MCP = (*InternalServerMCPAPI)(&r.common) + r.Permissions = (*InternalServerPermissionsAPI)(&r.common) + r.Persistence = (*InternalServerPersistenceAPI)(&r.common) + r.Repo = (*InternalServerRepoAPI)(&r.common) + r.Sandbox = (*InternalServerSandboxAPI)(&r.common) r.Sessions = (*InternalServerSessionsAPI)(&r.common) + r.User = (*InternalServerUserAPI)(&r.common) return r } @@ -34621,6 +39829,43 @@ func (a *ModelAPI) GetCurrent(ctx context.Context) (*CurrentModel, error) { return &result, nil } +// GetReasoningSummaries computes whether the session's current model shows reasoning +// summaries and how a host should present them: the default Ctrl+T visibility, reasoning +// headers, the hidden-summaries hint, and whether a persisted `showReasoning` setting is +// redundant. The runtime folds in the session's model catalog, selection and +// reasoning-summaries experiment assignment; the host supplies only its own persisted +// setting and current visibility. This method reads the session's resolved model catalog +// rather than resolving it, so call `session.model.list` first on a session that has not +// listed models or run a turn; until then the result is `ready: false`. A session that +// resolves models without a Copilot catalog, that is a BYOK registry or an alternate +// provider, never caches one and stays `ready: false`. +// +// RPC method: session.model.getReasoningSummaries. +// +// Parameters: Host presentation state the reasoning-summaries policy depends on. +// +// Returns: Reasoning-summaries presentation policy for the session's current model. +func (a *ModelAPI) GetReasoningSummaries(ctx context.Context, params *ModelGetReasoningSummariesRequest) (*ModelGetReasoningSummariesResult, error) { + req := map[string]any{"sessionId": a.sessionID} + if params != nil { + if params.PersistedShowReasoning != nil { + req["persistedShowReasoning"] = *params.PersistedShowReasoning + } + if params.SummariesVisible != nil { + req["summariesVisible"] = *params.SummariesVisible + } + } + raw, err := a.client.Request(ctx, "session.model.getReasoningSummaries", req) + if err != nil { + return nil, err + } + var result ModelGetReasoningSummariesResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + // Lists models available to this session using its own auth and integration context. // Connected hosts (CLI TUI, GitHub App) should call this through the session client so // remote sessions return the remote CLI's available models rather than the caller's. @@ -34900,6 +40145,9 @@ func (a *OptionsAPI) Update(ctx context.Context, params *SessionUpdateOptionsPar if params.AgentContext != nil { req["agentContext"] = *params.AgentContext } + if params.AgentDirectories != nil { + req["agentDirectories"] = params.AgentDirectories + } if params.AllowAllMCPServerInstructions != nil { req["allowAllMcpServerInstructions"] = *params.AllowAllMCPServerInstructions } @@ -34978,6 +40226,9 @@ func (a *OptionsAPI) Update(ctx context.Context, params *SessionUpdateOptionsPar if params.FeatureFlags != nil { req["featureFlags"] = params.FeatureFlags } + if params.HookDirectories != nil { + req["hookDirectories"] = params.HookDirectories + } if params.IgnoredSkillsLocations != nil { req["ignoredSkillsLocations"] = params.IgnoredSkillsLocations } @@ -34990,6 +40241,9 @@ func (a *OptionsAPI) Update(ctx context.Context, params *SessionUpdateOptionsPar if params.InstalledPlugins != nil { req["installedPlugins"] = params.InstalledPlugins } + if params.InstructionDirectories != nil { + req["instructionDirectories"] = params.InstructionDirectories + } if params.IntegrationID != nil { req["integrationId"] = *params.IntegrationID } @@ -35891,6 +41145,40 @@ func (a *PluginsAPI) Reload(ctx context.Context, params ...*SessionPluginsReload return &result, nil } +// RetryManaged installs or updates the plugins the session's managed settings require now, +// without waiting for the next automatic attempt after a failure, then reloads plugins. +// Reports progress and failures with the same `session.info` and `session.warning` events +// as automatic preparation. Fails when the runtime does not prepare managed plugins for the +// session. +// +// RPC method: session.plugins.retryManaged. +// +// Parameters: Plugins required by the session's managed settings to install or update again. +// +// Returns: Per-plugin outcomes of retrying the plugins required by the session's managed +// settings. +func (a *PluginsAPI) RetryManaged(ctx context.Context, params ...*SessionPluginsRetryManagedRequest) (*SessionPluginsRetryManagedResult, error) { + var requestParams *SessionPluginsRetryManagedRequest + if len(params) > 0 { + requestParams = params[0] + } + req := map[string]any{"sessionId": a.sessionID} + if requestParams != nil { + if requestParams.Plugins != nil { + req["plugins"] = requestParams.Plugins + } + } + raw, err := a.client.Request(ctx, "session.plugins.retryManaged", req) + if err != nil { + return nil, err + } + var result SessionPluginsRetryManagedResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + // Uninstalls a plugin when permitted by the live session's retained managed policy. // // RPC method: session.plugins.uninstall. @@ -36800,6 +42088,29 @@ func (a *SandboxAPI) DisableForSession(ctx context.Context, params *SandboxDisab return &result, nil } +// GetCredentialSuggestions lists possible secret-bearing environment variables available to +// new sandboxed shells that are not already covered by credential masking. Uses local name +// rules and known-provider host suggestions, never returns secret values, and changes no +// settings or network permissions. Returns no suggestions while the session is not +// sandboxed. +// +// RPC method: session.sandbox.getCredentialSuggestions. +// +// Returns: Possible unconfigured secrets in the sandbox shell environment, sorted by +// variable name, with no secret values. +func (a *SandboxAPI) GetCredentialSuggestions(ctx context.Context) (*SandboxCredentialSuggestionsResult, error) { + req := map[string]any{"sessionId": a.sessionID} + raw, err := a.client.Request(ctx, "session.sandbox.getCredentialSuggestions", req) + if err != nil { + return nil, err + } + var result SandboxCredentialSuggestionsResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + // GetEnforcementStatus returns whether managed policy requires sandbox enforcement and // whether an enforcement failure has permanently blocked the session. // diff --git a/go/rpc/zrpc_encoding.go b/go/rpc/zrpc_encoding.go index 471e491a87..bb207c0c38 100644 --- a/go/rpc/zrpc_encoding.go +++ b/go/rpc/zrpc_encoding.go @@ -4,6 +4,7 @@ package rpc import ( + "bytes" "encoding/json" "errors" ) @@ -1259,6 +1260,123 @@ func (r *BuiltinToolDescriptor) UnmarshalJSON(data []byte) error { return nil } +func unmarshalCatalogAgentPluginInstallSource(data []byte) (CatalogAgentPluginInstallSource, error) { + if string(data) == "null" { + return nil, nil + } + type rawUnion struct { + Kind CatalogAgentPluginInstallSourceKind `json:"kind"` + } + var raw rawUnion + if err := json.Unmarshal(data, &raw); err != nil { + return nil, err + } + + switch raw.Kind { + case CatalogAgentPluginInstallSourceKindMarketplace: + var d CatalogAgentPluginMarketplaceSource + if err := json.Unmarshal(data, &d); err != nil { + return nil, err + } + return &d, nil + case CatalogAgentPluginInstallSourceKindMaterialization: + var d CatalogAgentPluginMaterializationSource + if err := json.Unmarshal(data, &d); err != nil { + return nil, err + } + return &d, nil + case CatalogAgentPluginInstallSourceKindRepository: + var d CatalogAgentPluginRepositorySource + if err := json.Unmarshal(data, &d); err != nil { + return nil, err + } + return &d, nil + default: + return &RawCatalogAgentPluginInstallSourceData{Discriminator: raw.Kind, Raw: data}, nil + } +} + +func (r RawCatalogAgentPluginInstallSourceData) MarshalJSON() ([]byte, error) { + if r.Raw != nil { + return r.Raw, nil + } + return json.Marshal(struct { + Kind CatalogAgentPluginInstallSourceKind `json:"kind"` + }{ + Kind: r.Discriminator, + }) +} + +func (r CatalogAgentPluginMarketplaceSource) MarshalJSON() ([]byte, error) { + type alias CatalogAgentPluginMarketplaceSource + return json.Marshal(struct { + Kind CatalogAgentPluginInstallSourceKind `json:"kind"` + alias + }{ + Kind: r.Kind(), + alias: alias(r), + }) +} + +func (r CatalogAgentPluginMaterializationSource) MarshalJSON() ([]byte, error) { + type alias CatalogAgentPluginMaterializationSource + return json.Marshal(struct { + Kind CatalogAgentPluginInstallSourceKind `json:"kind"` + alias + }{ + Kind: r.Kind(), + alias: alias(r), + }) +} + +func (r CatalogAgentPluginRepositorySource) MarshalJSON() ([]byte, error) { + type alias CatalogAgentPluginRepositorySource + return json.Marshal(struct { + Kind CatalogAgentPluginInstallSourceKind `json:"kind"` + alias + }{ + Kind: r.Kind(), + alias: alias(r), + }) +} + +func (r *CatalogAgentPluginInstallPlan) UnmarshalJSON(data []byte) error { + type rawCatalogAgentPluginInstallPlan struct { + CanvasIDs []string `json:"canvasIds"` + Commit *string `json:"commit,omitempty"` + Components []CatalogAgentPluginComponent `json:"components"` + ConnectorDependencies []string `json:"connectorDependencies"` + ExtensionIDs []string `json:"extensionIds"` + MCPDependencies []string `json:"mcpDependencies"` + PluginName string `json:"pluginName"` + Sources []json.RawMessage `json:"sources"` + Version *string `json:"version,omitempty"` + } + var raw rawCatalogAgentPluginInstallPlan + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + r.CanvasIDs = raw.CanvasIDs + r.Commit = raw.Commit + r.Components = raw.Components + r.ConnectorDependencies = raw.ConnectorDependencies + r.ExtensionIDs = raw.ExtensionIDs + r.MCPDependencies = raw.MCPDependencies + r.PluginName = raw.PluginName + if raw.Sources != nil { + r.Sources = make([]CatalogAgentPluginInstallSource, 0, len(raw.Sources)) + for _, rawItem := range raw.Sources { + value, err := unmarshalCatalogAgentPluginInstallSource(rawItem) + if err != nil { + return err + } + r.Sources = append(r.Sources, value) + } + } + r.Version = raw.Version + return nil +} + func unmarshalCatalogCandidate(data []byte) (CatalogCandidate, error) { if string(data) == "null" { return nil, nil @@ -1642,6 +1760,8 @@ func (r *CatalogAgentPluginCandidate) UnmarshalJSON(data []byte) error { Description *string `json:"description,omitempty"` DisplayName string `json:"displayName"` Identity string `json:"identity"` + Installability *CatalogAgentPluginInstallability `json:"installability,omitempty"` + InstallPlan *CatalogAgentPluginInstallPlan `json:"installPlan,omitempty"` MediaType CatalogAgentPluginMediaType `json:"mediaType"` Provenance CatalogAgentPluginCandidateProvenance `json:"provenance"` Publisher *string `json:"publisher,omitempty"` @@ -1657,6 +1777,8 @@ func (r *CatalogAgentPluginCandidate) UnmarshalJSON(data []byte) error { r.Description = raw.Description r.DisplayName = raw.DisplayName r.Identity = raw.Identity + r.Installability = raw.Installability + r.InstallPlan = raw.InstallPlan r.MediaType = raw.MediaType r.Provenance = raw.Provenance r.Publisher = raw.Publisher @@ -2451,6 +2573,13 @@ func (r ConnectorConnectResultPending) MarshalJSON() ([]byte, error) { }) } +func (r *ContentExclusionValidatePoliciesResult) UnmarshalJSON(data []byte) error { + type wire ContentExclusionValidatePoliciesResult + decoder := json.NewDecoder(bytes.NewReader(data)) + decoder.UseNumber() + return decoder.Decode((*wire)(r)) +} + func unmarshalDebugCollectLogsDestination(data []byte) (DebugCollectLogsDestination, error) { if string(data) == "null" { return nil, nil @@ -7721,6 +7850,7 @@ func (r *SessionOpenOptions) UnmarshalJSON(data []byte) error { AdditionalContentExclusionPolicies []SessionOpenOptionsAdditionalContentExclusionPolicy `json:"additionalContentExclusionPolicies,omitzero"` AdditionalDirectories []string `json:"additionalDirectories,omitzero"` AgentContext *string `json:"agentContext,omitempty"` + AgentDirectories []string `json:"agentDirectories,omitzero"` AllowAllMCPServerInstructions *bool `json:"allowAllMcpServerInstructions,omitempty"` AskUserDisabled *bool `json:"askUserDisabled,omitempty"` AuthClientIDMetadataURL *string `json:"authClientIdMetadataUrl,omitempty"` @@ -7756,11 +7886,14 @@ func (r *SessionOpenOptions) UnmarshalJSON(data []byte) error { ExpAssignments any `json:"expAssignments,omitempty"` FeatureFlags map[string]bool `json:"featureFlags,omitzero"` HasSkillProvider *bool `json:"hasSkillProvider,omitempty"` + HookDirectories []string `json:"hookDirectories,omitzero"` IgnoredSkillsLocations []string `json:"ignoredSkillsLocations,omitzero"` IncludedBuiltinAgents []string `json:"includedBuiltinAgents,omitzero"` IncludedBuiltinSkills []string `json:"includedBuiltinSkills,omitzero"` InstalledPlugins []InstalledPlugin `json:"installedPlugins,omitzero"` + InstructionDirectories []string `json:"instructionDirectories,omitzero"` IntegrationID *string `json:"integrationId,omitempty"` + InteractiveUser *bool `json:"interactiveUser,omitempty"` IsExperimentalMode *bool `json:"isExperimentalMode,omitempty"` LogInteractiveShells *bool `json:"logInteractiveShells,omitempty"` LspClientName *string `json:"lspClientName,omitempty"` @@ -7803,6 +7936,7 @@ func (r *SessionOpenOptions) UnmarshalJSON(data []byte) error { r.AdditionalContentExclusionPolicies = raw.AdditionalContentExclusionPolicies r.AdditionalDirectories = raw.AdditionalDirectories r.AgentContext = raw.AgentContext + r.AgentDirectories = raw.AgentDirectories r.AllowAllMCPServerInstructions = raw.AllowAllMCPServerInstructions r.AskUserDisabled = raw.AskUserDisabled r.AuthClientIDMetadataURL = raw.AuthClientIDMetadataURL @@ -7844,11 +7978,14 @@ func (r *SessionOpenOptions) UnmarshalJSON(data []byte) error { r.ExpAssignments = raw.ExpAssignments r.FeatureFlags = raw.FeatureFlags r.HasSkillProvider = raw.HasSkillProvider + r.HookDirectories = raw.HookDirectories r.IgnoredSkillsLocations = raw.IgnoredSkillsLocations r.IncludedBuiltinAgents = raw.IncludedBuiltinAgents r.IncludedBuiltinSkills = raw.IncludedBuiltinSkills r.InstalledPlugins = raw.InstalledPlugins + r.InstructionDirectories = raw.InstructionDirectories r.IntegrationID = raw.IntegrationID + r.InteractiveUser = raw.InteractiveUser r.IsExperimentalMode = raw.IsExperimentalMode r.LogInteractiveShells = raw.LogInteractiveShells r.LspClientName = raw.LspClientName diff --git a/go/rpc/zsession_encoding.go b/go/rpc/zsession_encoding.go index 2db5b44582..89810fdf9b 100644 --- a/go/rpc/zsession_encoding.go +++ b/go/rpc/zsession_encoding.go @@ -2363,6 +2363,8 @@ func (r PermissionPromptRequestWrite) MarshalJSON() ([]byte, error) { func (r *PermissionRequestedData) UnmarshalJSON(data []byte) error { type rawPermissionRequestedData struct { AgentMode *SessionMode `json:"agentMode,omitempty"` + OwnerAgentID *string `json:"ownerAgentId,omitempty"` + OwnerSessionID *string `json:"ownerSessionId,omitempty"` PermissionMode *PermissionMode `json:"permissionMode,omitempty"` PermissionRequest json.RawMessage `json:"permissionRequest"` PromptRequest json.RawMessage `json:"promptRequest,omitempty"` @@ -2376,6 +2378,8 @@ func (r *PermissionRequestedData) UnmarshalJSON(data []byte) error { return err } r.AgentMode = raw.AgentMode + r.OwnerAgentID = raw.OwnerAgentID + r.OwnerSessionID = raw.OwnerSessionID r.PermissionMode = raw.PermissionMode if raw.PermissionRequest != nil { value, err := unmarshalPermissionRequest(raw.PermissionRequest) diff --git a/go/rpc/zsession_events.go b/go/rpc/zsession_events.go index a9bc3e7a63..eeec88c09a 100644 --- a/go/rpc/zsession_events.go +++ b/go/rpc/zsession_events.go @@ -358,7 +358,7 @@ func (*PromptCacheBreakData) Type() SessionEventType { return SessionEventTypePr // A provider-owned quota observation, distinct from per-call usage and charge accounting. // Experimental: SessionQuotaObservationData is part of an experimental API and may change or be removed. type SessionQuotaObservationData struct { - // The admitted provider's state observation. Admission observations never contain quantities or reset/percentage semantics. + // The admitted provider's state observation. Admission balances and percentages remain absent; separate monthly usage may report its own amount and cycle. Observation ProviderQuotaState `json:"observation"` } @@ -487,6 +487,10 @@ type SessionPermissionRecoveryData struct { MaxAttempts int64 `json:"maxAttempts"` // Policy selected from the current client's response capability; mode or client changes may update it during recovery OnBlocked PermissionRecoveryOnBlocked `json:"onBlocked"` + // Subagent identifier that owns this recovery episode when the request was delegated through a parent permission service + OwnerAgentID *string `json:"ownerAgentId,omitempty"` + // Root or child execution-session identifier that owns this recovery episode; omitted for legacy snapshots + OwnerSessionID *string `json:"ownerSessionId,omitempty"` // Controlled reason for the latest episode transition Reason PermissionRecoveryReason `json:"reason"` // Current lifecycle state of the recovery episode @@ -668,9 +672,6 @@ func (*SessionContextClearedData) Type() SessionEventType { // Conversation compaction results including success status, metrics, and optional error details type SessionCompactionCompleteData struct { - // Legacy active-workflow reminder retained for replay compatibility - // Internal: ActiveFactorySummary is part of the SDK's internal API surface and is not intended for external use. - ActiveFactorySummary *string `json:"activeFactorySummary,omitempty"` // Authoritative active-workflow reminder appended to the compacted context // Internal: ActiveWorkflowSummary is part of the SDK's internal API surface and is not intended for external use. ActiveWorkflowSummary *string `json:"activeWorkflowSummary,omitempty"` @@ -895,18 +896,20 @@ func (*MCPHeadersRefreshRequiredData) Type() SessionEventType { // Effective enterprise managed settings and contributing channels. Session events report applied policy; sessionless resolve reports an account/device snapshot, and compose reports a non-applying preview of candidate documents. Device values take precedence over server values, then the policy helper, per ordinary key, while permissions compose restrictively. Session-local SDK-client policy is included only in session results. 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. + // Whether an explicit enterprise policy restriction disables bypass-permissions ("yolo") mode for this session. Deny-wins across layers; an unresolved policy does not force this on by itself. BypassPermissionsDisabled bool `json:"bypassPermissionsDisabled"` // 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. + // Whether managed policy could not be determined (e.g. a failed server fetch) and unresolved-policy safeguards remain active. This does not by itself disable bypass-permissions; `bypassPermissionsDisabled` reports only an explicit policy restriction. FailClosed bool `json:"failClosed"` // The setting keys under enterprise management in the effective managed settings (e.g. `model`, `enabledPlugins`, `permissions`). Empty when no managed settings are in force. ManagedKeys []string `json:"managedKeys"` // Whether at least two managed sources supplied permission allowlists, so enforcement intersects them and the flattened settings payload omits `permissions.allow`. PermissionsAllowIntersected *bool `json:"permissionsAllowIntersected,omitempty"` + // Reusable retained managed permission policy for managedSettings.permissions.evaluate, including source composition and session-local injection. Provided by live session snapshots; absent in older events. Refresh the context when managed policy changes. + PermissionsContext *ManagedPermissionsContext `json:"permissionsContext,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. @@ -1046,12 +1049,16 @@ type SessionErrorData struct { EligibleForAutoSwitch *bool `json:"eligibleForAutoSwitch,omitempty"` // Fine-grained error code from the upstream provider, when available. For `errorType: "rate_limit"`, this is one of the `RateLimitErrorCode` values (e.g., `"user_weekly_rate_limited"`, `"user_global_rate_limited"`, `"rate_limited"`, `"user_model_rate_limited"`, `"integration_rate_limited"`). For `errorType: "quota"`, this is the CAPI quota error code (e.g., `"quota_exceeded"`, `"session_quota_exceeded"`, `"billing_not_configured"`). ErrorCode *string `json:"errorCode,omitempty"` - // Category of error (e.g., "authentication", "authorization", "quota", "rate_limit", "context_limit", "query") + // Category of error (e.g., "authentication", "authorization", "policy", "quota", "rate_limit", "context_limit", "query") ErrorType string `json:"errorType"` // Human-readable error message Message string `json:"message"` // GitHub request tracing ID (x-github-request-id header) for correlating with server-side logs ProviderCallID *string `json:"providerCallId,omitempty"` + // Sanitized provider-defined error parameter preserved from a structured upstream error envelope, when available. + ProviderErrorParam *string `json:"providerErrorParam,omitempty"` + // Provider-defined error type preserved from a structured upstream error envelope, when available (for example, `invalid_request_error`). + ProviderErrorType *string `json:"providerErrorType,omitempty"` // What the user must do to recover, when the runtime knows of an action. The `message` never names a client affordance, so a client that offers one — a slash command, a settings pane, a link — renders it from this value. Remediation *RemediationAction `json:"remediation,omitempty"` // Copilot service request ID (x-copilot-service-request-id header) for CAPI log correlation @@ -1471,8 +1478,12 @@ type ModelCallFailureData struct { QuotaSnapshots map[string]AssistantUsageQuotaSnapshot `json:"quotaSnapshots,omitzero"` // Reasoning effort level used for the failed model call, if applicable ReasoningEffort *string `json:"reasoningEffort,omitempty"` - // Serialized (uncompressed) byte length of the failed request body. A content-free size signal. + // Serialized (uncompressed) byte length of the failed request body, before any Content-Encoding. A content-free size signal. RequestBodyBytes *int64 `json:"requestBodyBytes,omitempty"` + // Content-Encoding applied to the failed request body; identity when uncompressed + RequestBodyEncoding *ModelCallRequestBodyEncoding `json:"requestBodyEncoding,omitempty"` + // Byte length of the failed request body actually sent on the wire, after any Content-Encoding. Equals requestBodyBytes when the body was not compressed. + RequestBodyWireBytes *int64 `json:"requestBodyWireBytes,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"` // Zero-based orchestrator retry index of the failed attempt @@ -1489,8 +1500,18 @@ type ModelCallFailureData struct { Transport *ModelCallFailureTransport `json:"transport,omitempty"` // Milliseconds spent on the WebSocket attempt before falling back to HTTP WebsocketFallbackAfterMs *int64 `json:"websocketFallbackAfterMs,omitempty"` + // WebSocket close code received before the HTTP fallback + WebsocketFallbackCloseCode *int32 `json:"websocketFallbackCloseCode,omitempty"` + // Normalized cause of the WebSocket failure that triggered the HTTP fallback; absent when no fallback occurred or the cause is unknown + WebsocketFallbackErrorKind *ModelCallWebSocketFallbackErrorKind `json:"websocketFallbackErrorKind,omitempty"` // Why the failed call was carried by the HTTP fallback of a WebSocket-capable dispatcher; absent when no fallback occurred WebsocketFallbackReason *ModelCallWebSocketFallbackReason `json:"websocketFallbackReason,omitempty"` + // Whether a WebSocket reconnect was attempted before falling back to HTTP + WebsocketFallbackReconnectAttempted *bool `json:"websocketFallbackReconnectAttempted,omitempty"` + // Whether the HTTP fallback started during this model call, rather than being inherited from an earlier call + WebsocketFallbackStartedThisCall *bool `json:"websocketFallbackStartedThisCall,omitempty"` + // HTTP status of the WebSocket upgrade rejection or WebSocket API error that triggered the HTTP fallback + WebsocketFallbackStatusCode *int32 `json:"websocketFallbackStatusCode,omitempty"` } func (*ModelCallFailureData) sessionEventData() {} @@ -1854,8 +1875,12 @@ type AssistantUsageData struct { ReasoningTokens *int64 `json:"reasoningTokens,omitempty"` // Number of rejected speculative prediction tokens RejectedPredictionTokens *int64 `json:"rejectedPredictionTokens,omitempty"` - // Serialized (uncompressed) byte length of the request body. A content-free size signal. + // Serialized (uncompressed) byte length of the request body, before any Content-Encoding. A content-free size signal. RequestBodyBytes *int64 `json:"requestBodyBytes,omitempty"` + // Content-Encoding applied to the request body of the attempt that produced this outcome; identity when uncompressed + RequestBodyEncoding *ModelCallRequestBodyEncoding `json:"requestBodyEncoding,omitempty"` + // Byte length of the request body actually sent on the wire for the attempt that produced this outcome, after any Content-Encoding. Equals requestBodyBytes when the body was not compressed. + RequestBodyWireBytes *int64 `json:"requestBodyWireBytes,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 @@ -1878,8 +1903,18 @@ type AssistantUsageData struct { Transport *AssistantUsageTransport `json:"transport,omitempty"` // Milliseconds spent on the WebSocket attempt before falling back to HTTP WebsocketFallbackAfterMs *int64 `json:"websocketFallbackAfterMs,omitempty"` + // WebSocket close code received before the HTTP fallback + WebsocketFallbackCloseCode *int32 `json:"websocketFallbackCloseCode,omitempty"` + // Normalized cause of the WebSocket failure that triggered the HTTP fallback; absent when no fallback occurred or the cause is unknown + WebsocketFallbackErrorKind *ModelCallWebSocketFallbackErrorKind `json:"websocketFallbackErrorKind,omitempty"` // Why the call was carried by the HTTP fallback of a WebSocket-capable dispatcher; absent when no fallback occurred WebsocketFallbackReason *ModelCallWebSocketFallbackReason `json:"websocketFallbackReason,omitempty"` + // Whether a WebSocket reconnect was attempted before falling back to HTTP + WebsocketFallbackReconnectAttempted *bool `json:"websocketFallbackReconnectAttempted,omitempty"` + // Whether the HTTP fallback started during this model call, rather than being inherited from an earlier call + WebsocketFallbackStartedThisCall *bool `json:"websocketFallbackStartedThisCall,omitempty"` + // HTTP status of the WebSocket upgrade rejection or WebSocket API error that triggered the HTTP fallback + WebsocketFallbackStatusCode *int32 `json:"websocketFallbackStatusCode,omitempty"` } func (*AssistantUsageData) sessionEventData() {} @@ -2435,6 +2470,10 @@ func (*PermissionCompletedData) Type() SessionEventType { return SessionEventTyp type PermissionRequestedData struct { // Agent mode captured from the owning turn when permission evaluation began. AgentMode *SessionMode `json:"agentMode,omitempty"` + // Delegated agent that owns permission recovery when the owner session multiplexes remote agents. Absent otherwise. + OwnerAgentID *string `json:"ownerAgentId,omitempty"` + // Execution session that owns permission recovery for this request. Absent on historical events. + OwnerSessionID *string `json:"ownerSessionId,omitempty"` // Permission mode captured when evaluation began. Absent on historical events. PermissionMode *PermissionMode `json:"permissionMode,omitempty"` // Details of the permission being requested @@ -3133,8 +3172,6 @@ type SubagentStartedData struct { AgentType *string `json:"agentType,omitempty"` // Whether the sub-agent runs synchronously or in the background. ExecutionMode *string `json:"executionMode,omitempty"` - // Legacy root id of the workflow run that spawned this sub-agent. New consumers should use workflowRunId. - FactoryRunID *string `json:"factoryRunId,omitempty"` // Model the sub-agent will run with, when known at start. Model *string `json:"model,omitempty"` // Authority or runtime mechanism responsible for sub-agent model selection, when known at start. @@ -3796,6 +3833,8 @@ type CompactionCompleteCompactionTokensUsed struct { OutputTokens *int64 `json:"outputTokens,omitempty"` // Provider captured for the compaction call; absent when attribution is unknown. Provider *ModelProviderRef `json:"provider,omitempty"` + // Number of model requests represented by this aggregated compaction usage + RequestCount *int64 `json:"requestCount,omitempty"` } // Per-request cost and usage data from the CAPI copilot_usage response field @@ -6531,6 +6570,48 @@ const ( ModelCallFinishedOutcomeSuccess ModelCallFinishedOutcome = "success" ) +// Content-Encoding applied to the request body sent on the wire +type ModelCallRequestBodyEncoding string + +const ( + // The request body was sent gzip-compressed. + ModelCallRequestBodyEncodingGzip ModelCallRequestBodyEncoding = "gzip" + // The request body was sent uncompressed (including every WebSocket request). + ModelCallRequestBodyEncodingIdentity ModelCallRequestBodyEncoding = "identity" + // The request body was sent zstd-compressed. + ModelCallRequestBodyEncodingZstd ModelCallRequestBodyEncoding = "zstd" +) + +// Normalized cause of the WebSocket failure that triggered the HTTP fallback +type ModelCallWebSocketFallbackErrorKind string + +const ( + // The peer closed the WebSocket. + ModelCallWebSocketFallbackErrorKindClosedByPeer ModelCallWebSocketFallbackErrorKind = "closed_by_peer" + // The runtime closed the WebSocket itself. + ModelCallWebSocketFallbackErrorKindClosedLocally ModelCallWebSocketFallbackErrorKind = "closed_locally" + // The WebSocket request could not be built. + ModelCallWebSocketFallbackErrorKindConfiguration ModelCallWebSocketFallbackErrorKind = "configuration" + // The peer refused the TCP connection. + ModelCallWebSocketFallbackErrorKindConnectionRefused ModelCallWebSocketFallbackErrorKind = "connection_refused" + // The connection was reset, aborted, or ended mid-stream without a close. + ModelCallWebSocketFallbackErrorKindConnectionReset ModelCallWebSocketFallbackErrorKind = "connection_reset" + // DNS resolution of the WebSocket host failed. + ModelCallWebSocketFallbackErrorKindDns ModelCallWebSocketFallbackErrorKind = "dns" + // The upgrade or request was rejected with an HTTP status. + ModelCallWebSocketFallbackErrorKindHTTPStatus ModelCallWebSocketFallbackErrorKind = "http_status" + // The socket was already closed before it was used. + ModelCallWebSocketFallbackErrorKindNotConnected ModelCallWebSocketFallbackErrorKind = "not_connected" + // A failure that maps onto none of the other kinds. + ModelCallWebSocketFallbackErrorKindOther ModelCallWebSocketFallbackErrorKind = "other" + // A WebSocket protocol violation or an undecodable frame. + ModelCallWebSocketFallbackErrorKindProtocol ModelCallWebSocketFallbackErrorKind = "protocol" + // A WebSocket connect or read timed out. + ModelCallWebSocketFallbackErrorKindTimeout ModelCallWebSocketFallbackErrorKind = "timeout" + // The TLS handshake or a TLS record failed. + ModelCallWebSocketFallbackErrorKindTls ModelCallWebSocketFallbackErrorKind = "tls" +) + // Why a WebSocket-capable model call was carried by the HTTP fallback type ModelCallWebSocketFallbackReason string diff --git a/go/session_test.go b/go/session_test.go index 38a365fbd6..39eb312021 100644 --- a/go/session_test.go +++ b/go/session_test.go @@ -1985,6 +1985,77 @@ func TestSession_PostToolUseFailureHook(t *testing.T) { }) } +func TestSession_ErrorHooksDecodeCliErrorObject(t *testing.T) { + cases := []struct { + name string + error string + want string + }{ + {"error object", `"error": {"name": "Error", "message": "model timeout", "stack": "Error: model timeout"},`, "model timeout"}, + {"string", `"error": "boom",`, "boom"}, + {"null", `"error": null,`, ""}, + {"absent", ``, ""}, + {"object without a string message", `"error": {"code": 7},`, `{"code":7}`}, + } + for _, tc := range cases { + t.Run("errorOccurred/"+tc.name, func(t *testing.T) { + session, cleanup := newTestSession() + defer cleanup() + + var captured *ErrorOccurredHookInput + session.registerHooks(&SessionHooks{ + OnErrorOccurred: func(input ErrorOccurredHookInput, _ HookInvocation) (*ErrorOccurredHookOutput, error) { + captured = &input + return nil, nil + }, + }) + + raw := json.RawMessage(`{"sessionId": "sess-1", "timestamp": 1700000000, "cwd": "/work", ` + tc.error + + ` "errorContext": "model_call", "recoverable": true}`) + if _, err := session.handleHooksInvoke("errorOccurred", raw); err != nil { + t.Fatalf("unexpected error: %v", err) + } + if captured == nil { + t.Fatal("OnErrorOccurred did not run") + } + if captured.Error != tc.want { + t.Errorf("expected error %q, got %q", tc.want, captured.Error) + } + if captured.ErrorContext != "model_call" || !captured.Recoverable { + t.Errorf("unexpected input: %+v", *captured) + } + }) + + t.Run("sessionEnd/"+tc.name, func(t *testing.T) { + session, cleanup := newTestSession() + defer cleanup() + + var captured *SessionEndHookInput + session.registerHooks(&SessionHooks{ + OnSessionEnd: func(input SessionEndHookInput, _ HookInvocation) (*SessionEndHookOutput, error) { + captured = &input + return nil, nil + }, + }) + + raw := json.RawMessage(`{"sessionId": "sess-1", "timestamp": 1700000000, "cwd": "/work", ` + tc.error + + ` "reason": "error"}`) + if _, err := session.handleHooksInvoke("sessionEnd", raw); err != nil { + t.Fatalf("unexpected error: %v", err) + } + if captured == nil { + t.Fatal("OnSessionEnd did not run") + } + if captured.Error != tc.want { + t.Errorf("expected error %q, got %q", tc.want, captured.Error) + } + if captured.Reason != "error" { + t.Errorf("expected reason 'error', got %q", captured.Reason) + } + }) + } +} + func TestSession_AgentStopHook(t *testing.T) { session, cleanup := newTestSession() defer cleanup() diff --git a/go/types.go b/go/types.go index 19b263f7f0..1ef6152946 100644 --- a/go/types.go +++ b/go/types.go @@ -1,6 +1,7 @@ package copilot import ( + "bytes" "context" "encoding/json" "errors" @@ -645,6 +646,9 @@ type AutoModeSwitchRequestHandler func(request AutoModeSwitchRequest, invocation // PreToolUseHookInput is the input for a pre-tool-use hook type PreToolUseHookInput struct { + AgentID string `json:"agentId,omitempty"` + AgentType string `json:"agentType,omitempty"` + ParentAgentID string `json:"parentAgentId,omitempty"` SessionID string `json:"sessionId"` Timestamp time.Time `json:"-"` WorkingDirectory string `json:"cwd"` @@ -689,6 +693,9 @@ type PreToolUseHandler func(input PreToolUseHookInput, invocation HookInvocation // PostToolUseHookInput is the input for a post-tool-use hook type PostToolUseHookInput struct { + AgentID string `json:"agentId,omitempty"` + AgentType string `json:"agentType,omitempty"` + ParentAgentID string `json:"parentAgentId,omitempty"` SessionID string `json:"sessionId"` Timestamp time.Time `json:"-"` WorkingDirectory string `json:"cwd"` @@ -736,6 +743,9 @@ type PostToolUseHandler func(input PostToolUseHookInput, invocation HookInvocati // the failure message from the tool result and passes it as the Error field // (rather than passing the full result object). type PostToolUseFailureHookInput struct { + AgentID string `json:"agentId,omitempty"` + AgentType string `json:"agentType,omitempty"` + ParentAgentID string `json:"parentAgentId,omitempty"` SessionID string `json:"sessionId"` Timestamp time.Time `json:"-"` WorkingDirectory string `json:"cwd"` @@ -781,6 +791,9 @@ type PostToolUseFailureHandler func(input PostToolUseFailureHookInput, invocatio // UserPromptSubmittedHookInput is the input for a user-prompt-submitted hook type UserPromptSubmittedHookInput struct { + AgentID string `json:"agentId,omitempty"` + AgentType string `json:"agentType,omitempty"` + ParentAgentID string `json:"parentAgentId,omitempty"` SessionID string `json:"sessionId"` Timestamp time.Time `json:"-"` WorkingDirectory string `json:"cwd"` @@ -822,6 +835,9 @@ type UserPromptSubmittedHandler func(input UserPromptSubmittedHookInput, invocat // UserPromptTransformedHookInput is the input for a user-prompt-transformed hook. type UserPromptTransformedHookInput struct { + AgentID string `json:"agentId,omitempty"` + AgentType string `json:"agentType,omitempty"` + ParentAgentID string `json:"parentAgentId,omitempty"` SessionID string `json:"sessionId"` Timestamp time.Time `json:"-"` WorkingDirectory string `json:"cwd"` @@ -862,6 +878,9 @@ type UserPromptTransformedHandler func(input UserPromptTransformedHookInput, inv // SessionStartHookInput is the input for a session-start hook type SessionStartHookInput struct { + AgentID string `json:"agentId,omitempty"` + AgentType string `json:"agentType,omitempty"` + ParentAgentID string `json:"parentAgentId,omitempty"` SessionID string `json:"sessionId"` Timestamp time.Time `json:"-"` WorkingDirectory string `json:"cwd"` @@ -903,6 +922,9 @@ type SessionStartHandler func(input SessionStartHookInput, invocation HookInvoca // SessionEndHookInput is the input for a session-end hook type SessionEndHookInput struct { + AgentID string `json:"agentId,omitempty"` + AgentType string `json:"agentType,omitempty"` + ParentAgentID string `json:"parentAgentId,omitempty"` SessionID string `json:"sessionId"` Timestamp time.Time `json:"-"` WorkingDirectory string `json:"cwd"` @@ -920,17 +942,24 @@ func (h SessionEndHookInput) MarshalJSON() ([]byte, error) { }{Timestamp: h.Timestamp.UnixMilli(), alias: alias(h)}) } -// UnmarshalJSON implements json.Unmarshaler, parsing Timestamp from Unix milliseconds. +// UnmarshalJSON implements json.Unmarshaler, parsing Timestamp from Unix milliseconds +// and Error from the CLI's error object (see hookErrorText). func (h *SessionEndHookInput) UnmarshalJSON(data []byte) error { type alias SessionEndHookInput aux := &struct { - Timestamp int64 `json:"timestamp"` + Timestamp int64 `json:"timestamp"` + Error json.RawMessage `json:"error"` *alias }{alias: (*alias)(h)} if err := json.Unmarshal(data, aux); err != nil { return err } h.Timestamp = time.UnixMilli(aux.Timestamp) + errorText, err := hookErrorText(aux.Error) + if err != nil { + return err + } + h.Error = errorText return nil } @@ -946,6 +975,9 @@ type SessionEndHandler func(input SessionEndHookInput, invocation HookInvocation // ErrorOccurredHookInput is the input for an error-occurred hook type ErrorOccurredHookInput struct { + AgentID string `json:"agentId,omitempty"` + AgentType string `json:"agentType,omitempty"` + ParentAgentID string `json:"parentAgentId,omitempty"` SessionID string `json:"sessionId"` Timestamp time.Time `json:"-"` WorkingDirectory string `json:"cwd"` @@ -963,20 +995,62 @@ func (h ErrorOccurredHookInput) MarshalJSON() ([]byte, error) { }{Timestamp: h.Timestamp.UnixMilli(), alias: alias(h)}) } -// UnmarshalJSON implements json.Unmarshaler, parsing Timestamp from Unix milliseconds. +// UnmarshalJSON implements json.Unmarshaler, parsing Timestamp from Unix milliseconds +// and Error from the CLI's error object (see hookErrorText). func (h *ErrorOccurredHookInput) UnmarshalJSON(data []byte) error { type alias ErrorOccurredHookInput aux := &struct { - Timestamp int64 `json:"timestamp"` + Timestamp int64 `json:"timestamp"` + Error json.RawMessage `json:"error"` *alias }{alias: (*alias)(h)} if err := json.Unmarshal(data, aux); err != nil { return err } h.Timestamp = time.UnixMilli(aux.Timestamp) + errorText, err := hookErrorText(aux.Error) + if err != nil { + return err + } + h.Error = errorText return nil } +// hookErrorText decodes the error of an errorOccurred or sessionEnd hook input. +// The CLI sends an error object ({"name", "message", "stack"} or {"message"}): +// its message becomes the error text, and an object without a string message +// becomes its compact JSON. A string passes through, and null or an absent +// error leaves the text empty. +func hookErrorText(raw json.RawMessage) (string, error) { + raw = bytes.TrimSpace(raw) + if len(raw) == 0 || bytes.Equal(raw, []byte("null")) { + return "", nil + } + if raw[0] == '{' { + var object map[string]json.RawMessage + if err := json.Unmarshal(raw, &object); err != nil { + return "", err + } + if message := bytes.TrimSpace(object["message"]); len(message) > 0 && message[0] == '"' { + var text string + if err := json.Unmarshal(message, &text); err != nil { + return "", err + } + return text, nil + } + var compact bytes.Buffer + if err := json.Compact(&compact, raw); err != nil { + return "", err + } + return compact.String(), nil + } + var text string + if err := json.Unmarshal(raw, &text); err != nil { + return "", err + } + return text, nil +} + // ErrorOccurredHookOutput is the output for an error-occurred hook type ErrorOccurredHookOutput struct { SuppressOutput bool `json:"suppressOutput,omitempty"` @@ -990,6 +1064,9 @@ type ErrorOccurredHandler func(input ErrorOccurredHookInput, invocation HookInvo // AgentStopHookInput is the input for an agent-stop hook. type AgentStopHookInput struct { + AgentID string `json:"agentId,omitempty"` + AgentType string `json:"agentType,omitempty"` + ParentAgentID string `json:"parentAgentId,omitempty"` SessionID string `json:"sessionId"` Timestamp time.Time `json:"-"` WorkingDirectory string `json:"cwd"` @@ -1032,6 +1109,9 @@ type AgentStopHandler func(input AgentStopHookInput, invocation HookInvocation) // SubagentStartHookInput is the input before a subagent's first turn. type SubagentStartHookInput struct { + AgentID string `json:"agentId,omitempty"` + AgentType string `json:"agentType,omitempty"` + ParentAgentID string `json:"parentAgentId,omitempty"` SessionID string `json:"sessionId"` Timestamp time.Time `json:"-"` WorkingDirectory string `json:"cwd"` @@ -1074,6 +1154,7 @@ type SubagentStartHandler func(input SubagentStartHookInput, invocation HookInvo // SubagentStopHookInput is the input after a subagent completes a turn. type SubagentStopHookInput struct { + ParentAgentID string `json:"parentAgentId,omitempty"` SessionID string `json:"sessionId"` Timestamp time.Time `json:"-"` WorkingDirectory string `json:"cwd"` @@ -1124,6 +1205,9 @@ type SubagentStopHandler func(input SubagentStopHookInput, invocation HookInvoca // PreMCPToolCallHookInput is the input for a pre-mcp-tool-call hook type PreMCPToolCallHookInput struct { + AgentID string `json:"agentId,omitempty"` + AgentType string `json:"agentType,omitempty"` + ParentAgentID string `json:"parentAgentId,omitempty"` SessionID string `json:"sessionId"` Timestamp time.Time `json:"-"` WorkingDirectory string `json:"cwd"` @@ -1252,6 +1336,8 @@ func (c MCPHTTPServerConfig) MarshalJSON() ([]byte, error) { // CustomAgentConfig configures a custom agent. type CustomAgentConfig struct { + // Hooks contains declarative actions keyed by event name, active only for this agent. + Hooks map[string][]map[string]any `json:"hooks,omitempty"` // Name is the unique name of the custom agent Name string `json:"name"` // DisplayName is the display name for UI purposes @@ -1323,6 +1409,16 @@ type LargeToolOutputConfig struct { OutputDirectory string `json:"outputDir,omitempty"` } +// ImageGenerationConfig opts in to an authorized Copilot image model. +// Experimental: this API may change or be removed in a future release. +// Omission disables on create/cold resume and preserves resident resume state. +// Not persisted: re-supply after runtime restart. Policy, permissions, offline +// mode, and tool filters still apply. BYOK image generation is not supported. +type ImageGenerationConfig struct { + // Enabled opts in when true and disables image generation when false. + Enabled *bool `json:"enabled,omitempty"` +} + // ToolSearchConfig allows to configure tool search behavior. // Tool search defers tools to keep the model's active tool set small. // To override the tool-search tool's implementation, register a @@ -1467,6 +1563,13 @@ type SkillProvider interface { type SessionConfig struct { // SessionID is an optional custom session ID SessionID string + // Name names the session at creation, as session.name.set would, and is + // validated the same way: non-empty, no leading or trailing whitespace, at + // most 100 UTF-16 units, no control characters, and no double quotes. A name + // also makes the session persist from the start, so it can be listed and + // resumed without ever running a turn. Local sessions only: cloud creation + // rejects it. + Name string // ClientName identifies the application using the SDK. // Included in the User-Agent header for API requests. ClientName string @@ -1505,9 +1608,9 @@ type SessionConfig struct { // of instruction files (AGENTS.md, .github/copilot-instructions.md, etc.) after // successful file views. EnableOnDemandInstructionDiscovery *bool - // EnableFileHooks, when non-nil, controls loading of file-based hooks from - // .github/hooks/. This is separate from the Hooks callback parameter which - // gates SDK hook event registration. + // EnableFileHooks, when non-nil, controls repository-origin file hooks from + // .github/hooks/ and trusted repository-configured roots. It does not control + // explicit HookDirectories or Hooks callbacks. EnableFileHooks *bool // EnableHostGitOperations, when non-nil, controls git operations on the host // filesystem (branch detection, file status, commit history). When false, no @@ -1517,6 +1620,11 @@ type SessionConfig struct { // and retrieval. When false, session content is not written to or read from the // shared session store. EnableSessionStore *bool + // EnableGitHubCacheStore, when true, requests the read-only GitHub cache store + // (the github-cache-store capability). The runtime registers the github_sql + // tool only when its GITHUB_CACHE_STORE feature flag is also on. Defaults to + // disabled. + EnableGitHubCacheStore *bool // EnableSkills, when non-nil, controls skill loading (including builtin skills // and discovered skill directories). When false, no skills are loaded regardless // of SkillDirectories or EnableConfigDiscovery settings. @@ -1660,7 +1768,11 @@ type SessionConfig struct { // Agent is the name of the custom agent to activate when the session starts. // Must match the Name of one of the agents in CustomAgents. Agent string - // SkillDirectories is a list of directories to load skills from + // SkillDirectories contains explicit discovery-only skill roots, not filesystem grants. + // Available without EnableConfigDiscovery, subject to skill/managed disables. + // Relative paths resolve once when supplied against the request's effective WorkingDirectory; + // retained roots do not move on cwd changes. Nil and omission are equivalent; + // an empty non-nil list clears only the explicit SDK contribution. SkillDirectories []string // SkillProvider supplies ephemeral SDK-hosted skills for this session. The // provider is never serialized or persisted; re-supply it when resuming. @@ -1668,13 +1780,29 @@ type SessionConfig struct { // Experimental: this API may change or be removed in future SDK or CLI // releases. Implementations must be safe for concurrent calls. SkillProvider SkillProvider `json:"-"` + // AgentDirectories contains explicit discovery-only agent roots, separate from CustomAgents. + // No filesystem grant. Available without EnableConfigDiscovery, subject to source/managed + // disables. Relative paths resolve once when supplied against the request's effective + // WorkingDirectory; retained roots do not move. Nil and omission are equivalent; + // an empty non-nil list clears only the explicit SDK contribution. + AgentDirectories []string + // HookDirectories contains explicit discovery-only file-hook roots, separate from Hooks callbacks. + // No filesystem grant. Available without EnableConfigDiscovery or EnableFileHooks + // (repository-origin hooks only), subject to hook/managed disables. Relative paths resolve + // once when supplied against the request's effective WorkingDirectory; retained roots do not + // move. Nil and omission are equivalent; an empty non-nil list clears only the explicit SDK contribution. + HookDirectories []string // PluginDirectories is a list of local filesystem paths to Open Plugins-format // directories (https://open-plugins.com/) to load for this session. // Relative paths resolve against WorkingDirectory (or the runtime cwd if unset). // Treated as an explicit opt-in: plugin agents and rules load even when // EnableConfigDiscovery is false. PluginDirectories []string - // InstructionDirectories is a list of additional directories to search for custom instruction files + // InstructionDirectories contains explicit discovery-only instruction roots using existing SDK formats. + // No filesystem grant. Available without EnableConfigDiscovery, subject to instruction/managed + // disables. Relative paths resolve once when supplied against the request's effective + // WorkingDirectory; retained roots do not move. Nil and omission are equivalent; + // an empty non-nil list clears only the explicit SDK contribution. InstructionDirectories []string // DisabledSkills is a list of skill names to disable DisabledSkills []string @@ -1693,6 +1821,9 @@ type SessionConfig struct { // defers rarely used tools behind a searchable index. When nil, the runtime // default applies. ToolSearch *ToolSearchConfig + // ImageGeneration is the non-persisted image generation opt-in. + // Experimental: this API may change or be removed in a future release. + ImageGeneration *ImageGenerationConfig // Memory configures the memory feature for the session. When omitted, the // runtime default applies. Memory *MemoryConfiguration @@ -1807,6 +1938,11 @@ type SessionConfig struct { // controls for this session. Conflicting changes are rejected; overridable // managed defaults remain mutable. EnforceManagedModelDefaults *bool + // InteractiveUser declares that a human drives this session's conversation, + // enabling user-message analysis telemetry for user-authored messages. + // Automated messages remain excluded. Defaults to false for SDK sessions. + // Ignored for cloud sessions. + InteractiveUser *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 @@ -2217,9 +2353,9 @@ type ResumeSessionConfig struct { // EnableOnDemandInstructionDiscovery, when non-nil, controls on-demand discovery // of instruction files after successful file views. EnableOnDemandInstructionDiscovery *bool - // EnableFileHooks, when non-nil, controls loading of file-based hooks from - // .github/hooks/. This is separate from the Hooks callback parameter which - // gates SDK hook event registration. + // EnableFileHooks, when non-nil, controls repository-origin file hooks from + // .github/hooks/ and trusted repository-configured roots. It does not control + // explicit HookDirectories or Hooks callbacks. EnableFileHooks *bool // EnableHostGitOperations, when non-nil, controls git operations on the host // filesystem. @@ -2227,6 +2363,11 @@ type ResumeSessionConfig struct { // EnableSessionStore, when non-nil, controls the cross-session store for search // and retrieval across sessions. EnableSessionStore *bool + // EnableGitHubCacheStore, when true, requests the read-only GitHub cache store + // (the github-cache-store capability). The runtime registers the github_sql + // tool only when its GITHUB_CACHE_STORE feature flag is also on. Defaults to + // disabled. + EnableGitHubCacheStore *bool // EnableSkills, when non-nil, controls skill loading. EnableSkills *bool // Streaming enables streaming of assistant message and reasoning chunks. @@ -2259,7 +2400,11 @@ type ResumeSessionConfig struct { // Agent is the name of the custom agent to activate when the session starts. // Must match the Name of one of the agents in CustomAgents. Agent string - // SkillDirectories is a list of directories to load skills from + // SkillDirectories contains explicit discovery-only skill roots, not filesystem grants. + // Available without EnableConfigDiscovery, subject to skill/managed disables. + // Relative paths resolve once when supplied against the request's effective WorkingDirectory; + // retained roots do not move on cwd changes. Nil and omission are equivalent; + // an empty non-nil list clears only the explicit SDK contribution. SkillDirectories []string // SkillProvider supplies ephemeral SDK-hosted skills for this resumed // session. Re-supply it on every resume; omitting it unbinds any previous @@ -2268,13 +2413,29 @@ type ResumeSessionConfig struct { // Experimental: this API may change or be removed in future SDK or CLI // releases. Implementations must be safe for concurrent calls. SkillProvider SkillProvider `json:"-"` + // AgentDirectories contains explicit discovery-only agent roots, separate from CustomAgents. + // No filesystem grant. Available without EnableConfigDiscovery, subject to source/managed + // disables. Relative paths resolve once when supplied against the request's effective + // WorkingDirectory; retained roots do not move. Nil and omission are equivalent; + // an empty non-nil list clears only the explicit SDK contribution. + AgentDirectories []string + // HookDirectories contains explicit discovery-only file-hook roots, separate from Hooks callbacks. + // No filesystem grant. Available without EnableConfigDiscovery or EnableFileHooks + // (repository-origin hooks only), subject to hook/managed disables. Relative paths resolve + // once when supplied against the request's effective WorkingDirectory; retained roots do not + // move. Nil and omission are equivalent; an empty non-nil list clears only the explicit SDK contribution. + HookDirectories []string // PluginDirectories is a list of local filesystem paths to Open Plugins-format // directories (https://open-plugins.com/) to load for this session. // Relative paths resolve against WorkingDirectory (or the runtime cwd if unset). // Treated as an explicit opt-in: plugin agents and rules load even when // EnableConfigDiscovery is false. PluginDirectories []string - // InstructionDirectories is a list of additional directories to search for custom instruction files + // InstructionDirectories contains explicit discovery-only instruction roots using existing SDK formats. + // No filesystem grant. Available without EnableConfigDiscovery, subject to instruction/managed + // disables. Relative paths resolve once when supplied against the request's effective + // WorkingDirectory; retained roots do not move. Nil and omission are equivalent; + // an empty non-nil list clears only the explicit SDK contribution. InstructionDirectories []string // DisabledSkills is a list of skill names to disable DisabledSkills []string @@ -2292,6 +2453,9 @@ type ResumeSessionConfig struct { // defers rarely used tools behind a searchable index. When nil, the runtime // default applies. ToolSearch *ToolSearchConfig + // ImageGeneration is the non-persisted image generation opt-in. + // Experimental: this API may change or be removed in a future release. + ImageGeneration *ImageGenerationConfig // Memory configures the memory feature for the session. When omitted, the // runtime default applies. Memory *MemoryConfiguration @@ -2386,6 +2550,9 @@ type ResumeSessionConfig struct { // EnforceManagedModelDefaults re-applies managed model lock enforcement on // resume. See SessionConfig.EnforceManagedModelDefaults. EnforceManagedModelDefaults *bool + // InteractiveUser re-supplies that a human drives this session. It is not + // persisted and is ignored if the session is already active in the runtime. + InteractiveUser *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 @@ -2837,6 +3004,7 @@ type SessionLifecycleHandler func(event SessionLifecycleEvent) type createSessionRequest struct { Model string `json:"model,omitempty"` SessionID string `json:"sessionId,omitempty"` + Name string `json:"name,omitempty"` ClientName string `json:"clientName,omitempty"` ReasoningEffort string `json:"reasoningEffort,omitempty"` ReasoningSummary ReasoningSummary `json:"reasoningSummary,omitempty"` @@ -2890,16 +3058,20 @@ type createSessionRequest struct { EnableFileHooks *bool `json:"enableFileHooks,omitempty"` EnableHostGitOperations *bool `json:"enableHostGitOperations,omitempty"` EnableSessionStore *bool `json:"enableSessionStore,omitempty"` + EnableGitHubCacheStore *bool `json:"enableGitHubCacheStore,omitempty"` EnableSkills *bool `json:"enableSkills,omitempty"` HasSkillProvider *bool `json:"hasSkillProvider,omitempty"` - SkillDirectories []string `json:"skillDirectories,omitempty"` + SkillDirectories []string `json:"skillDirectories,omitzero"` + AgentDirectories []string `json:"agentDirectories,omitzero"` + HookDirectories []string `json:"hookDirectories,omitzero"` PluginDirectories []string `json:"pluginDirectories,omitempty"` - InstructionDirectories []string `json:"instructionDirectories,omitempty"` + InstructionDirectories []string `json:"instructionDirectories,omitzero"` 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"` + ImageGeneration *ImageGenerationConfig `json:"imageGeneration,omitempty"` Memory *MemoryConfiguration `json:"memory,omitempty"` Commands []wireCommand `json:"commands,omitempty"` RequestElicitation *bool `json:"requestElicitation,omitempty"` @@ -2919,6 +3091,7 @@ type createSessionRequest struct { FeatureFlags *map[string]bool `json:"featureFlags,omitempty"` EnableManagedSettings *bool `json:"enableManagedSettings,omitempty"` EnforceManagedModelDefaults *bool `json:"enforceManagedModelDefaults,omitempty"` + InteractiveUser *bool `json:"interactiveUser,omitempty"` ManagedSettings *ManagedSettings `json:"managedSettings,omitempty"` Traceparent string `json:"traceparent,omitempty"` Tracestate string `json:"tracestate,omitempty"` @@ -2982,6 +3155,7 @@ type resumeSessionRequest struct { EnableFileHooks *bool `json:"enableFileHooks,omitempty"` EnableHostGitOperations *bool `json:"enableHostGitOperations,omitempty"` EnableSessionStore *bool `json:"enableSessionStore,omitempty"` + EnableGitHubCacheStore *bool `json:"enableGitHubCacheStore,omitempty"` EnableSkills *bool `json:"enableSkills,omitempty"` HasSkillProvider *bool `json:"hasSkillProvider,omitempty"` DisableResume *bool `json:"disableResume,omitempty"` @@ -2998,14 +3172,17 @@ type resumeSessionRequest struct { CustomAgents []CustomAgentConfig `json:"customAgents,omitempty"` DefaultAgent *DefaultAgentConfig `json:"defaultAgent,omitempty"` Agent string `json:"agent,omitempty"` - SkillDirectories []string `json:"skillDirectories,omitempty"` + SkillDirectories []string `json:"skillDirectories,omitzero"` + AgentDirectories []string `json:"agentDirectories,omitzero"` + HookDirectories []string `json:"hookDirectories,omitzero"` PluginDirectories []string `json:"pluginDirectories,omitempty"` - InstructionDirectories []string `json:"instructionDirectories,omitempty"` + InstructionDirectories []string `json:"instructionDirectories,omitzero"` 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"` + ImageGeneration *ImageGenerationConfig `json:"imageGeneration,omitempty"` Memory *MemoryConfiguration `json:"memory,omitempty"` Commands []wireCommand `json:"commands,omitempty"` RequestElicitation *bool `json:"requestElicitation,omitempty"` @@ -3025,6 +3202,7 @@ type resumeSessionRequest struct { FeatureFlags *map[string]bool `json:"featureFlags,omitempty"` EnableManagedSettings *bool `json:"enableManagedSettings,omitempty"` EnforceManagedModelDefaults *bool `json:"enforceManagedModelDefaults,omitempty"` + InteractiveUser *bool `json:"interactiveUser,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 0c4775d386..81bb0f2c25 100644 --- a/go/zsession_events.go +++ b/go/zsession_events.go @@ -152,6 +152,7 @@ type ( HumanResponseRecordedResponseResponseKind = rpc.HumanResponseRecordedResponseResponseKind HumanResponseRecordedResponseUserInput = rpc.HumanResponseRecordedResponseUserInput IndexedSearchState = rpc.IndexedSearchState + ManagedPermissionsContext = rpc.ManagedPermissionsContext ManagedPluginProgressPhase = rpc.ManagedPluginProgressPhase ManagedSettingsEnforcedAction = rpc.ManagedSettingsEnforcedAction ManagedSettingsEnforcedEscalation = rpc.ManagedSettingsEnforcedEscalation @@ -191,7 +192,9 @@ type ( ModelCallFinalResultData = rpc.ModelCallFinalResultData ModelCallFinishedData = rpc.ModelCallFinishedData ModelCallFinishedOutcome = rpc.ModelCallFinishedOutcome + ModelCallRequestBodyEncoding = rpc.ModelCallRequestBodyEncoding ModelCallStartData = rpc.ModelCallStartData + ModelCallWebSocketFallbackErrorKind = rpc.ModelCallWebSocketFallbackErrorKind ModelCallWebSocketFallbackReason = rpc.ModelCallWebSocketFallbackReason ModelChangeSource = rpc.ModelChangeSource ModelDeselectedReason = rpc.ModelDeselectedReason @@ -286,6 +289,9 @@ type ( PlanChangedOperation = rpc.PlanChangedOperation PossibleURL = rpc.PossibleURL PromptCacheBreakData = rpc.PromptCacheBreakData + ProviderMonthlyUsage = rpc.ProviderMonthlyUsage + ProviderMonthlyUsageScope = rpc.ProviderMonthlyUsageScope + ProviderMonthlyUsageState = rpc.ProviderMonthlyUsageState ProviderQuotaAccessState = rpc.ProviderQuotaAccessState ProviderQuotaAcquisitionStatus = rpc.ProviderQuotaAcquisitionStatus ProviderQuotaBudgetMetadata = rpc.ProviderQuotaBudgetMetadata @@ -738,6 +744,21 @@ const ( ModelCallFinishedOutcomeError = rpc.ModelCallFinishedOutcomeError ModelCallFinishedOutcomeRejected = rpc.ModelCallFinishedOutcomeRejected ModelCallFinishedOutcomeSuccess = rpc.ModelCallFinishedOutcomeSuccess + ModelCallRequestBodyEncodingGzip = rpc.ModelCallRequestBodyEncodingGzip + ModelCallRequestBodyEncodingIdentity = rpc.ModelCallRequestBodyEncodingIdentity + ModelCallRequestBodyEncodingZstd = rpc.ModelCallRequestBodyEncodingZstd + ModelCallWebSocketFallbackErrorKindClosedByPeer = rpc.ModelCallWebSocketFallbackErrorKindClosedByPeer + ModelCallWebSocketFallbackErrorKindClosedLocally = rpc.ModelCallWebSocketFallbackErrorKindClosedLocally + ModelCallWebSocketFallbackErrorKindConfiguration = rpc.ModelCallWebSocketFallbackErrorKindConfiguration + ModelCallWebSocketFallbackErrorKindConnectionRefused = rpc.ModelCallWebSocketFallbackErrorKindConnectionRefused + ModelCallWebSocketFallbackErrorKindConnectionReset = rpc.ModelCallWebSocketFallbackErrorKindConnectionReset + ModelCallWebSocketFallbackErrorKindDns = rpc.ModelCallWebSocketFallbackErrorKindDns + ModelCallWebSocketFallbackErrorKindHTTPStatus = rpc.ModelCallWebSocketFallbackErrorKindHTTPStatus + ModelCallWebSocketFallbackErrorKindNotConnected = rpc.ModelCallWebSocketFallbackErrorKindNotConnected + ModelCallWebSocketFallbackErrorKindOther = rpc.ModelCallWebSocketFallbackErrorKindOther + ModelCallWebSocketFallbackErrorKindProtocol = rpc.ModelCallWebSocketFallbackErrorKindProtocol + ModelCallWebSocketFallbackErrorKindTimeout = rpc.ModelCallWebSocketFallbackErrorKindTimeout + ModelCallWebSocketFallbackErrorKindTls = rpc.ModelCallWebSocketFallbackErrorKindTls ModelCallWebSocketFallbackReasonAPIError = rpc.ModelCallWebSocketFallbackReasonAPIError ModelCallWebSocketFallbackReasonConnectFailed = rpc.ModelCallWebSocketFallbackReasonConnectFailed ModelCallWebSocketFallbackReasonConnectionUnavailable = rpc.ModelCallWebSocketFallbackReasonConnectionUnavailable @@ -896,6 +917,12 @@ const ( PlanChangedOperationCreate = rpc.PlanChangedOperationCreate PlanChangedOperationDelete = rpc.PlanChangedOperationDelete PlanChangedOperationUpdate = rpc.PlanChangedOperationUpdate + ProviderMonthlyUsageScopeUnknown = rpc.ProviderMonthlyUsageScopeUnknown + ProviderMonthlyUsageScopeUser = rpc.ProviderMonthlyUsageScopeUser + ProviderMonthlyUsageStateAvailable = rpc.ProviderMonthlyUsageStateAvailable + ProviderMonthlyUsageStateNoPolicy = rpc.ProviderMonthlyUsageStateNoPolicy + ProviderMonthlyUsageStateUnavailable = rpc.ProviderMonthlyUsageStateUnavailable + ProviderMonthlyUsageStateUnknown = rpc.ProviderMonthlyUsageStateUnknown ProviderQuotaAccessStateAllowed = rpc.ProviderQuotaAccessStateAllowed ProviderQuotaAccessStateDenied = rpc.ProviderQuotaAccessStateDenied ProviderQuotaAccessStateNotRequired = rpc.ProviderQuotaAccessStateNotRequired @@ -928,6 +955,7 @@ const ( RecommendedAutoTierIntelligence = rpc.RecommendedAutoTierIntelligence RemediationActionAllowSandboxOutbound = rpc.RemediationActionAllowSandboxOutbound RemediationActionReviewSandboxPolicy = rpc.RemediationActionReviewSandboxPolicy + RemediationActionRewindConversation = rpc.RemediationActionRewindConversation RemediationActionShowAccount = rpc.RemediationActionShowAccount RemediationActionSignIn = rpc.RemediationActionSignIn RemediationActionSwitchAccount = rpc.RemediationActionSwitchAccount diff --git a/java/README.md b/java/README.md index f8c0605b90..ecb3bff3e5 100644 --- a/java/README.md +++ b/java/README.md @@ -289,6 +289,12 @@ Pass `new ResumeSessionConfig().setContinuePendingWork(true)` to instead. Leave the option unset to use the runtime default. Re-register any external tools needed to handle continued work. +`SessionConfig.setInteractiveUser(true)` declares that a person types the +session's messages, as in an editor chat panel. This enables the runtime's +user-message analysis telemetry for user-authored messages. Automated messages +stay excluded. The default is off, and cloud sessions ignore it. Re-supply it +through `ResumeSessionConfig` on a cold resume. + For rotating per-session GitHub credentials, use `SessionConfig.setGitHubTokenProvider(...)` (or the equivalent `ResumeSessionConfig` setter) instead of `setGitHubToken(...)`: @@ -682,10 +688,25 @@ session.setAutoTier(null).get(); `setModel(SetModelOptions)` accepts the same preference through `SetModelOptions.setAutoTier(...)`, which stages the tier atomically with selecting `auto`. Call `setResetAutoTier(true)` instead to return to provider-default routing; the two options are mutually exclusive. +## Reasoning-summary presentation (experimental) + +Read the current model's reasoning-summary policy through `session.getRpc().model.getReasoningSummaries(...)`. Load the model catalog with `session.getRpc().model.list().get()` first, or accept a result with `ready() == false`. + +```java +import com.github.copilot.generated.rpc.SessionModelGetReasoningSummariesParams; + +var presentation = new SessionModelGetReasoningSummariesParams(null, false); +var policy = session.getRpc().model.getReasoningSummaries(presentation).get(); +``` + +The two constructor arguments are the persisted visibility setting (`null` when unset) and whether the host currently shows summaries (`null` defaults to false). The session wrapper supplies the session ID; callers do not need a placeholder ID. The original three-argument constructor remains available. `visibility()` can be `null` while experiment assignment is pending, and `clearRedundantSetting()` tells the host when its persisted setting matches the model default. + ## 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. +`enableGitHubCacheStore` on `SessionConfig` and `ResumeSessionConfig` requests the read-only GitHub cache store (`github-cache-store` capability) on create or resume. The runtime registers the `github_sql` tool only when its `GITHUB_CACHE_STORE` feature flag is also on. Defaults to disabled in every mode. + ## Memory Sessions can opt into persistent memory, allowing the agent to read and write memory across turns. Memory is configured per session and applies to both `createSession` and `resumeSession`. diff --git a/java/scripts/codegen/java.test.ts b/java/scripts/codegen/java.test.ts index ba931e7132..1a795e94d6 100644 --- a/java/scripts/codegen/java.test.ts +++ b/java/scripts/codegen/java.test.ts @@ -24,6 +24,64 @@ import { } from "./java.js"; import { RPC_VARIANT_OWNERS } from "./rpc-variant-owners.js"; +test("managed permission contexts share the session-event model in RPC inputs and outputs", () => { + const generated = "../../sdk/src/generated/java/com/github/copilot/generated/"; + for (const name of ["ManagedSettingsResolveResult", "ManagedSettingsPermissionsEvaluateParams"]) { + const source = fs.readFileSync(new URL(`${generated}rpc/${name}.java`, import.meta.url), "utf8"); + assert.match(source, /import com\.github\.copilot\.generated\.ManagedPermissionsContext;/); + assert.match(source, /ManagedPermissionsContext (?:context|permissionsContext)/); + } + assert.equal(fs.existsSync(new URL(`${generated}rpc/ManagedPermissionsContext.java`, import.meta.url)), false); + const event = fs.readFileSync(new URL(`${generated}SessionManagedSettingsResolvedEvent.java`, import.meta.url), "utf8"); + assert.match(event, /ManagedPermissionsContext permissionsContext/); +}); + +test("managed permission requests and results reuse one named operation and kind", async () => { + const files = await renderRpcTypes({ + definitions: { + ManagedPermissionOperation: { + anyOf: [{ + type: "object", + properties: { kind: { type: "string", const: "url" }, url: { type: "string" } }, + required: ["kind", "url"], + }], + }, + ManagedPermissionEvaluation: { + type: "object", + properties: { operation: { $ref: "#/definitions/ManagedPermissionOperation" } }, + required: ["operation"], + }, + }, + server: { + managedSettings: { + permissions: { + evaluate: { + rpcMethod: "managedSettings.permissions.evaluate", + params: { + type: "object", + properties: { operations: { type: "array", items: { $ref: "#/definitions/ManagedPermissionOperation" } } }, + required: ["operations"], + }, + result: { + type: "object", + properties: { results: { type: "array", items: { $ref: "#/definitions/ManagedPermissionEvaluation" } } }, + required: ["results"], + }, + }, + }, + }, + }, + }, {}); + const operation = generatedFile(files, "ManagedPermissionOperation"); + assert.ok(operation); + assert.match(operation, /record ManagedPermissionOperation\(/); + assert.match(operation, /ManagedPermissionOperationKind kind/); + assert.match(operation, /enum ManagedPermissionOperationKind/); + assert.match(operation, /URL\("url"\)/); + assert.match(generatedFile(files, "ManagedSettingsPermissionsEvaluateParams")!, /List operations/); + assert.match(generatedFile(files, "ManagedPermissionEvaluation")!, /ManagedPermissionOperation operation/); +}); + test("wrapper Javadoc preserves the operation contract ahead of parameter documentation", () => { const tree = buildNamespaceTree({ reconcile: { diff --git a/java/scripts/codegen/java.ts b/java/scripts/codegen/java.ts index d327ec4409..9f932aef75 100644 --- a/java/scripts/codegen/java.ts +++ b/java/scripts/codegen/java.ts @@ -1343,7 +1343,34 @@ export function schemaTypeToJava( const name = schema.$ref.replace(/^#\/definitions\//, ""); const resolved = resolution.definitions[name]; if (resolved) { + if ( + name === "ManagedPermissionsContext" + && generatedSessionEventTypeNames.has(name) + && crossSchemaDefinitions.get("session-events.schema.json")?.[name] + ) { + imports.add(`com.github.copilot.generated.${name}`); + return { javaType: name, imports }; + } const directUnion = unionVariants(resolved); + if (name === "ManagedPermissionOperation" && directUnion) { + const variants = resolveAnyOfVariants(directUnion, resolution.definitions); + const [variant] = variants; + if (variants.length === 1 && variant?.properties) { + const kind = variant.properties.kind; + if (!kind || typeof kind !== "object" || typeof kind.const !== "string") { + throw new Error("Expected a string discriminator for ManagedPermissionOperation.kind"); + } + resolution.standaloneTypes.set(name, { + ...resolved, + ...variant, + properties: { + ...variant.properties, + kind: { ...kind, enum: [kind.const] }, + }, + }); + return { javaType: name, imports }; + } + } if (directUnion && ["InstallationReview", "McpInstallationReview", "SkillInstallationReview"].includes(name)) { const variants = resolveAnyOfVariants(directUnion, resolution.definitions); if (variants.length > 1 && findDiscriminator(variants)) { @@ -2371,6 +2398,9 @@ function validateSharedResultOwners(sections: Record[]): void { } function schemaAllowsNull(schema: JSONSchema7): boolean { + if ((schema as Record)["x-opaque-json"] === true && schema.type === undefined) { + return true; + } if (schema.type === "null" || (Array.isArray(schema.type) && schema.type.includes("null"))) { return true; } @@ -2440,6 +2470,18 @@ export function generateRpcClass( } if (localNestedTypes.size > 0 && lines[lines.length - 1] === "") lines.pop(); + if (className === "SessionModelGetReasoningSummariesParams") { + lines.push(``); + lines.push(` /**`); + lines.push(` * Creates host presentation state; the session-scoped wrapper supplies the session ID.`); + lines.push(` *`); + lines.push(` * @param persistedShowReasoning the persisted visibility setting, or null when unset`); + lines.push(` * @param summariesVisible whether summaries are visible, or null for the default`); + lines.push(` */`); + lines.push(` public ${className}(Boolean persistedShowReasoning, Boolean summariesVisible) {`); + lines.push(` this(null, persistedShowReasoning, summariesVisible);`); + lines.push(` }`); + } const legacyConstructor = className === "McpOauthRequiredStaticClientConfig" ? { @@ -3131,6 +3173,10 @@ function wrapperResultClassName(method: RpcMethodNode): string { // Inline result schema with properties let result = originalResult; if (result.$ref) result = resolveRef(result) as JSONSchema7; + if (result.type === "boolean" || result.type === "integer" || result.type === "number" + || (result.type === "string" && !result.enum)) { + return schemaTypeToJava(result, false, rpcResultClassName(method.rpcMethod), "value", new Map()).javaType; + } if ( result && typeof result === "object" && @@ -3145,7 +3191,7 @@ function wrapperResultClassName(method: RpcMethodNode): string { return javaResult.javaType; } - // Free-form object with additionalProperties (e.g., x-opaque-json) → JsonNode + // Boolean maps have a typed value contract; free-form JSON remains a JsonNode. if ( result && typeof result === "object" && @@ -3153,6 +3199,9 @@ function wrapperResultClassName(method: RpcMethodNode): string { result.additionalProperties && !result.properties ) { + if (typeof result.additionalProperties === "object" && result.additionalProperties.type === "boolean") { + return "Map"; + } return "JsonNode"; } @@ -3160,6 +3209,9 @@ function wrapperResultClassName(method: RpcMethodNode): string { } function wrapperResultTypeExpression(resultType: string): string { + if (resultType === "Map") { + return "RpcMapper.INSTANCE.getTypeFactory().constructMapType(Map.class, String.class, Boolean.class)"; + } const listMatch = resultType.match(/^List<([^<>]+)>$/); if (listMatch) { return `RpcMapper.INSTANCE.getTypeFactory().constructCollectionType(List.class, ${javaClassLiteral(listMatch[1])})`; @@ -3182,11 +3234,19 @@ function addWrapperResultImports(resultType: string, allImports: Set, pa return; } + if (resultType === "UUID" || resultType === "OffsetDateTime") { + allImports.add(resultType === "UUID" ? "java.util.UUID" : "java.time.OffsetDateTime"); + return; + } + if (resultType.startsWith("List<")) { allImports.add("java.util.List"); } + if (resultType.startsWith("Map<")) { + allImports.add("java.util.Map"); + } - const builtInTypes = new Set(["Boolean", "Double", "Long", "List", "Object", "String", "Void"]); + const builtInTypes = new Set(["Boolean", "Double", "Long", "List", "Map", "Object", "String", "Void"]); for (const typeName of resultType.match(/\b[A-Z][A-Za-z0-9_]*\b/g) ?? []) { if (!builtInTypes.has(typeName)) { allImports.add(`${packageName}.${typeName}`); diff --git a/java/sdk/pom.xml b/java/sdk/pom.xml index a87f2537ed..bbcaf41d13 100644 --- a/java/sdk/pom.xml +++ b/java/sdk/pom.xml @@ -77,7 +77,7 @@ com.fasterxml.jackson.core jackson-databind - 2.22.2 + 2.22.3 com.fasterxml.jackson.core @@ -87,7 +87,7 @@ com.fasterxml.jackson.datatype jackson-datatype-jsr310 - 2.22.2 + 2.22.3 diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/AssistantUsageEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/AssistantUsageEvent.java index 75eb6f53f0..a392f290be 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/AssistantUsageEvent.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/AssistantUsageEvent.java @@ -97,12 +97,26 @@ public record AssistantUsageEventData( @JsonProperty("rejectedPredictionTokens") Long rejectedPredictionTokens, /** Transport used for this model call (http or websocket) */ @JsonProperty("transport") AssistantUsageTransport transport, - /** Serialized (uncompressed) byte length of the request body. A content-free size signal. */ + /** Serialized (uncompressed) byte length of the request body, before any Content-Encoding. A content-free size signal. */ @JsonProperty("requestBodyBytes") Long requestBodyBytes, + /** Byte length of the request body actually sent on the wire for the attempt that produced this outcome, after any Content-Encoding. Equals requestBodyBytes when the body was not compressed. */ + @JsonProperty("requestBodyWireBytes") Long requestBodyWireBytes, + /** Content-Encoding applied to the request body of the attempt that produced this outcome; identity when uncompressed */ + @JsonProperty("requestBodyEncoding") ModelCallRequestBodyEncoding requestBodyEncoding, /** Why the call was carried by the HTTP fallback of a WebSocket-capable dispatcher; absent when no fallback occurred */ @JsonProperty("websocketFallbackReason") ModelCallWebSocketFallbackReason websocketFallbackReason, /** Milliseconds spent on the WebSocket attempt before falling back to HTTP */ @JsonProperty("websocketFallbackAfterMs") Long websocketFallbackAfterMs, + /** Normalized cause of the WebSocket failure that triggered the HTTP fallback; absent when no fallback occurred or the cause is unknown */ + @JsonProperty("websocketFallbackErrorKind") ModelCallWebSocketFallbackErrorKind websocketFallbackErrorKind, + /** HTTP status of the WebSocket upgrade rejection or WebSocket API error that triggered the HTTP fallback */ + @JsonProperty("websocketFallbackStatusCode") Long websocketFallbackStatusCode, + /** WebSocket close code received before the HTTP fallback */ + @JsonProperty("websocketFallbackCloseCode") Long websocketFallbackCloseCode, + /** Whether a WebSocket reconnect was attempted before falling back to HTTP */ + @JsonProperty("websocketFallbackReconnectAttempted") Boolean websocketFallbackReconnectAttempted, + /** Whether the HTTP fallback started during this model call, rather than being inherited from an earlier call */ + @JsonProperty("websocketFallbackStartedThisCall") Boolean websocketFallbackStartedThisCall, /** 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 */ diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/CompactionCompleteCompactionTokensUsed.java b/java/sdk/src/generated/java/com/github/copilot/generated/CompactionCompleteCompactionTokensUsed.java index c325857e59..cd6f610465 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/CompactionCompleteCompactionTokensUsed.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/CompactionCompleteCompactionTokensUsed.java @@ -21,6 +21,8 @@ @JsonInclude(JsonInclude.Include.NON_NULL) @JsonIgnoreProperties(ignoreUnknown = true) public record CompactionCompleteCompactionTokensUsed( + /** Number of model requests represented by this aggregated compaction usage */ + @JsonProperty("requestCount") Long requestCount, /** Whether the compaction's credit amount was reported for all, some, or none of its calls. */ @JsonProperty("aiCreditsStatus") AiCreditsStatus aiCreditsStatus, /** Provider captured for the compaction call; absent when attribution is unknown. */ diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/ManagedPermissionsContext.java b/java/sdk/src/generated/java/com/github/copilot/generated/ManagedPermissionsContext.java new file mode 100644 index 0000000000..990ae9ace0 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/ManagedPermissionsContext.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; + +/** + * Reusable managed permission evaluation context. Treat permissions as runtime-owned policy data and retain it verbatim; it includes source-aware composition metadata. This is a snapshot, not a capability or approval token; only use contexts obtained from trusted policy sources. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record ManagedPermissionsContext( + /** Runtime-owned composed permissions object. Absent when no permission policy is configured. */ + @JsonProperty("permissions") Object permissions, + /** Policy could not be determined; evaluation must deny every operation. */ + @JsonProperty("failClosed") Boolean failClosed +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/ModelCallFailureEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/ModelCallFailureEvent.java index 717523a2ee..9bfa1cc686 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/ModelCallFailureEvent.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/ModelCallFailureEvent.java @@ -60,14 +60,28 @@ public record ModelCallFailureEventData( @JsonProperty("transport") ModelCallFailureTransport transport, /** Whether the failure originated from an API response or the request transport */ @JsonProperty("failureKind") ModelCallFailureKind failureKind, - /** Serialized (uncompressed) byte length of the failed request body. A content-free size signal. */ + /** Serialized (uncompressed) byte length of the failed request body, before any Content-Encoding. A content-free size signal. */ @JsonProperty("requestBodyBytes") Long requestBodyBytes, + /** Byte length of the failed request body actually sent on the wire, after any Content-Encoding. Equals requestBodyBytes when the body was not compressed. */ + @JsonProperty("requestBodyWireBytes") Long requestBodyWireBytes, + /** Content-Encoding applied to the failed request body; identity when uncompressed */ + @JsonProperty("requestBodyEncoding") ModelCallRequestBodyEncoding requestBodyEncoding, /** Zero-based orchestrator retry index of the failed attempt */ @JsonProperty("retryAttempt") Long retryAttempt, /** Why the failed call was carried by the HTTP fallback of a WebSocket-capable dispatcher; absent when no fallback occurred */ @JsonProperty("websocketFallbackReason") ModelCallWebSocketFallbackReason websocketFallbackReason, /** Milliseconds spent on the WebSocket attempt before falling back to HTTP */ @JsonProperty("websocketFallbackAfterMs") Long websocketFallbackAfterMs, + /** Normalized cause of the WebSocket failure that triggered the HTTP fallback; absent when no fallback occurred or the cause is unknown */ + @JsonProperty("websocketFallbackErrorKind") ModelCallWebSocketFallbackErrorKind websocketFallbackErrorKind, + /** HTTP status of the WebSocket upgrade rejection or WebSocket API error that triggered the HTTP fallback */ + @JsonProperty("websocketFallbackStatusCode") Long websocketFallbackStatusCode, + /** WebSocket close code received before the HTTP fallback */ + @JsonProperty("websocketFallbackCloseCode") Long websocketFallbackCloseCode, + /** Whether a WebSocket reconnect was attempted before falling back to HTTP */ + @JsonProperty("websocketFallbackReconnectAttempted") Boolean websocketFallbackReconnectAttempted, + /** Whether the HTTP fallback started during this model call, rather than being inherited from an earlier call */ + @JsonProperty("websocketFallbackStartedThisCall") Boolean websocketFallbackStartedThisCall, /** Effective maximum prompt-token limit for the failed call */ @JsonProperty("maxPromptTokens") Long maxPromptTokens, /** Effective maximum output-token limit for the failed call */ diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/ModelCallRequestBodyEncoding.java b/java/sdk/src/generated/java/com/github/copilot/generated/ModelCallRequestBodyEncoding.java new file mode 100644 index 0000000000..ad1b96ad9d --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/ModelCallRequestBodyEncoding.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-Encoding applied to the request body sent on the wire + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public enum ModelCallRequestBodyEncoding { + /** The {@code identity} variant. */ + IDENTITY("identity"), + /** The {@code gzip} variant. */ + GZIP("gzip"), + /** The {@code zstd} variant. */ + ZSTD("zstd"); + + private final String value; + ModelCallRequestBodyEncoding(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static ModelCallRequestBodyEncoding fromValue(String value) { + for (ModelCallRequestBodyEncoding v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown ModelCallRequestBodyEncoding value: " + value); + } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/ModelCallWebSocketFallbackErrorKind.java b/java/sdk/src/generated/java/com/github/copilot/generated/ModelCallWebSocketFallbackErrorKind.java new file mode 100644 index 0000000000..cb429a7d6d --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/ModelCallWebSocketFallbackErrorKind.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 javax.annotation.processing.Generated; + +/** + * Normalized cause of the WebSocket failure that triggered the HTTP fallback + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public enum ModelCallWebSocketFallbackErrorKind { + /** The {@code dns} variant. */ + DNS("dns"), + /** The {@code tls} variant. */ + TLS("tls"), + /** The {@code timeout} variant. */ + TIMEOUT("timeout"), + /** The {@code connection_refused} variant. */ + CONNECTION_REFUSED("connection_refused"), + /** The {@code connection_reset} variant. */ + CONNECTION_RESET("connection_reset"), + /** The {@code closed_by_peer} variant. */ + CLOSED_BY_PEER("closed_by_peer"), + /** The {@code closed_locally} variant. */ + CLOSED_LOCALLY("closed_locally"), + /** The {@code not_connected} variant. */ + NOT_CONNECTED("not_connected"), + /** The {@code http_status} variant. */ + HTTP_STATUS("http_status"), + /** The {@code protocol} variant. */ + PROTOCOL("protocol"), + /** The {@code configuration} variant. */ + CONFIGURATION("configuration"), + /** The {@code other} variant. */ + OTHER("other"); + + private final String value; + ModelCallWebSocketFallbackErrorKind(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static ModelCallWebSocketFallbackErrorKind fromValue(String value) { + for (ModelCallWebSocketFallbackErrorKind v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown ModelCallWebSocketFallbackErrorKind value: " + value); + } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/PermissionRecoveryData.java b/java/sdk/src/generated/java/com/github/copilot/generated/PermissionRecoveryData.java index ed38c3c168..796da04191 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/PermissionRecoveryData.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/PermissionRecoveryData.java @@ -24,6 +24,10 @@ public record PermissionRecoveryData( /** Stable identifier shared by every transition in this recovery episode */ @JsonProperty("episodeId") String episodeId, + /** Root or child execution-session identifier that owns this recovery episode; omitted for legacy snapshots */ + @JsonProperty("ownerSessionId") String ownerSessionId, + /** Subagent identifier that owns this recovery episode when the request was delegated through a parent permission service */ + @JsonProperty("ownerAgentId") String ownerAgentId, /** Current lifecycle state of the recovery episode */ @JsonProperty("status") PermissionRecoveryStatus status, /** Policy selected from the current client's response capability; mode or client changes may update it during recovery */ diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/PermissionRequestedEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/PermissionRequestedEvent.java index dd18d2af2e..5dbb6300af 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/PermissionRequestedEvent.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/PermissionRequestedEvent.java @@ -44,6 +44,10 @@ public record PermissionRequestedEventData( @JsonProperty("permissionMode") PermissionMode permissionMode, /** Agent mode captured from the owning turn when permission evaluation began. */ @JsonProperty("agentMode") SessionMode agentMode, + /** Execution session that owns permission recovery for this request. Absent on historical events. */ + @JsonProperty("ownerSessionId") String ownerSessionId, + /** Delegated agent that owns permission recovery when the owner session multiplexes remote agents. Absent otherwise. */ + @JsonProperty("ownerAgentId") String ownerAgentId, /** 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/sdk/src/generated/java/com/github/copilot/generated/ProviderMonthlyUsage.java b/java/sdk/src/generated/java/com/github/copilot/generated/ProviderMonthlyUsage.java new file mode 100644 index 0000000000..b0c3fdf5b6 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/ProviderMonthlyUsage.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 com.github.copilot.CopilotExperimental; +import javax.annotation.processing.Generated; + +/** + * An authoritative monthly usage reading for the observation's provider, account, and service. + * + * @apiNote This type 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 ProviderMonthlyUsage( + /** Availability of monthly consumption. Missing or unavailable usage must never be interpreted as zero. */ + @JsonProperty("state") ProviderMonthlyUsageState state, + /** Unit of consumedQuantity; independent of the parent observation's balance unit. */ + @JsonProperty("unit") ProviderQuotaUnit unit, + /** Identity scope of the reading within its provider and service. */ + @JsonProperty("scope") ProviderMonthlyUsageScope scope, + /** Nonnegative finite consumption reported by the service, preserving zero and fractions. Present only when state is available; never computed from tokens or balance differences. */ + @JsonProperty("consumedQuantity") Double consumedQuantity, + /** Service read time in UTC RFC 3339, not a ledger reconciliation watermark or confirmation of the latest inference charge. */ + @JsonProperty("queriedAt") String queriedAt, + /** Start of the service-reported monthly billing cycle, in UTC RFC 3339. */ + @JsonProperty("cycleStart") String cycleStart, + /** End of the service-reported monthly billing cycle, in UTC RFC 3339. */ + @JsonProperty("resetOn") String resetOn +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/ProviderMonthlyUsageScope.java b/java/sdk/src/generated/java/com/github/copilot/generated/ProviderMonthlyUsageScope.java new file mode 100644 index 0000000000..fb61e9283e --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/ProviderMonthlyUsageScope.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; + +/** + * Identity scope of a monthly usage reading. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public enum ProviderMonthlyUsageScope { + /** The {@code user} variant. */ + USER("user"), + /** The {@code unknown} variant. */ + UNKNOWN("unknown"); + + private final String value; + ProviderMonthlyUsageScope(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static ProviderMonthlyUsageScope fromValue(String value) { + for (ProviderMonthlyUsageScope v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown ProviderMonthlyUsageScope value: " + value); + } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/ProviderMonthlyUsageState.java b/java/sdk/src/generated/java/com/github/copilot/generated/ProviderMonthlyUsageState.java new file mode 100644 index 0000000000..27df08fdb1 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/ProviderMonthlyUsageState.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; + +/** + * Availability of a service's monthly consumption reading. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public enum ProviderMonthlyUsageState { + /** The {@code available} variant. */ + AVAILABLE("available"), + /** The {@code no_policy} variant. */ + NO_POLICY("no_policy"), + /** The {@code unavailable} variant. */ + UNAVAILABLE("unavailable"), + /** The {@code unknown} variant. */ + UNKNOWN("unknown"); + + private final String value; + ProviderMonthlyUsageState(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static ProviderMonthlyUsageState fromValue(String value) { + for (ProviderMonthlyUsageState v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown ProviderMonthlyUsageState value: " + value); + } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/ProviderQuotaState.java b/java/sdk/src/generated/java/com/github/copilot/generated/ProviderQuotaState.java index ddb7fe9aa9..6beeefee69 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/ProviderQuotaState.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/ProviderQuotaState.java @@ -49,6 +49,8 @@ public record ProviderQuotaState( @JsonProperty("acquisitionError") String acquisitionError, /** Optional metadata for an authoritative budget. Advisory balances and admission-only observations do not populate this. */ @JsonProperty("budgetMetadata") ProviderQuotaBudgetMetadata budgetMetadata, + /** Service-reported monthly consumption, independent of quota balances and per-call or session cost. Omitted when the service does not report monthly usage. */ + @JsonProperty("monthlyUsage") ProviderMonthlyUsage monthlyUsage, /** Key for the backwards-compatible snapshots projection, when the authoritative budget supports that contract. */ @JsonProperty("compatibilityKey") String compatibilityKey, /** Explicit service admission flag, when reported. */ diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/RemediationAction.java b/java/sdk/src/generated/java/com/github/copilot/generated/RemediationAction.java index 5fba9dfc33..f009f516f1 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/RemediationAction.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/RemediationAction.java @@ -25,7 +25,9 @@ public enum RemediationAction { /** The {@code review_sandbox_policy} variant. */ REVIEW_SANDBOX_POLICY("review_sandbox_policy"), /** The {@code allow_sandbox_outbound} variant. */ - ALLOW_SANDBOX_OUTBOUND("allow_sandbox_outbound"); + ALLOW_SANDBOX_OUTBOUND("allow_sandbox_outbound"), + /** The {@code rewind_conversation} variant. */ + REWIND_CONVERSATION("rewind_conversation"); private final String value; RemediationAction(String value) { this.value = value; } diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/SessionCompactionCompleteEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/SessionCompactionCompleteEvent.java index 046317ee26..5f82874b0c 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/SessionCompactionCompleteEvent.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/SessionCompactionCompleteEvent.java @@ -56,8 +56,6 @@ public record SessionCompactionCompleteEventData( @JsonProperty("responsesReasoning") ResponsesReasoning responsesReasoning, /** Authoritative active-workflow reminder appended to the compacted context */ @JsonProperty("activeWorkflowSummary") String activeWorkflowSummary, - /** Legacy active-workflow reminder retained for replay compatibility */ - @JsonProperty("activeFactorySummary") String activeFactorySummary, /** Canonical model identifier used for model-specific behavior when replaying compaction */ @JsonProperty("behaviorModelId") String behaviorModelId, /** Checkpoint snapshot number created for recovery */ diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/SessionErrorEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/SessionErrorEvent.java index 3ef32dfd13..9c6af84ab2 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/SessionErrorEvent.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/SessionErrorEvent.java @@ -34,10 +34,14 @@ public final class SessionErrorEvent extends SessionEvent { @JsonIgnoreProperties(ignoreUnknown = true) @JsonInclude(JsonInclude.Include.NON_NULL) public record SessionErrorEventData( - /** Category of error (e.g., "authentication", "authorization", "quota", "rate_limit", "context_limit", "query") */ + /** Category of error (e.g., "authentication", "authorization", "policy", "quota", "rate_limit", "context_limit", "query") */ @JsonProperty("errorType") String errorType, /** Fine-grained error code from the upstream provider, when available. For `errorType: "rate_limit"`, this is one of the `RateLimitErrorCode` values (e.g., `"user_weekly_rate_limited"`, `"user_global_rate_limited"`, `"rate_limited"`, `"user_model_rate_limited"`, `"integration_rate_limited"`). For `errorType: "quota"`, this is the CAPI quota error code (e.g., `"quota_exceeded"`, `"session_quota_exceeded"`, `"billing_not_configured"`). */ @JsonProperty("errorCode") String errorCode, + /** Provider-defined error type preserved from a structured upstream error envelope, when available (for example, `invalid_request_error`). */ + @JsonProperty("providerErrorType") String providerErrorType, + /** Sanitized provider-defined error parameter preserved from a structured upstream error envelope, when available. */ + @JsonProperty("providerErrorParam") String providerErrorParam, /** 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. */ @JsonProperty("eligibleForAutoSwitch") Boolean eligibleForAutoSwitch, /** Human-readable error message */ diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/SessionManagedSettingsResolvedEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/SessionManagedSettingsResolvedEvent.java index 27f062596c..007e911ef8 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/SessionManagedSettingsResolvedEvent.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/SessionManagedSettingsResolvedEvent.java @@ -40,6 +40,8 @@ public final class SessionManagedSettingsResolvedEvent extends SessionEvent { @JsonIgnoreProperties(ignoreUnknown = true) @JsonInclude(JsonInclude.Include.NON_NULL) public record SessionManagedSettingsResolvedEventData( + /** Reusable retained managed permission policy for managedSettings.permissions.evaluate, including source composition and session-local injection. Provided by live session snapshots; absent in older events. Refresh the context when managed policy changes. */ + @JsonProperty("permissionsContext") ManagedPermissionsContext permissionsContext, /** 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 */ @@ -50,11 +52,11 @@ public record SessionManagedSettingsResolvedEventData( @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. */ + /** Whether managed policy could not be determined (e.g. a failed server fetch) and unresolved-policy safeguards remain active. This does not by itself disable bypass-permissions; `bypassPermissionsDisabled` reports only an explicit policy restriction. */ @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. */ + /** Whether an explicit enterprise policy restriction disables bypass-permissions ("yolo") mode for this session. Deny-wins across layers; an unresolved policy does not force this on by itself. */ @JsonProperty("bypassPermissionsDisabled") Boolean bypassPermissionsDisabled, /** Whether at least two managed sources supplied permission allowlists, so enforcement intersects them and the flattened settings payload omits `permissions.allow`. */ @JsonProperty("permissionsAllowIntersected") Boolean permissionsAllowIntersected, diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/SessionPermissionRecoveryEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/SessionPermissionRecoveryEvent.java index 56d08f53b4..262bf4578d 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/SessionPermissionRecoveryEvent.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/SessionPermissionRecoveryEvent.java @@ -37,6 +37,10 @@ public final class SessionPermissionRecoveryEvent extends SessionEvent { public record SessionPermissionRecoveryEventData( /** Stable identifier shared by every transition in this recovery episode */ @JsonProperty("episodeId") String episodeId, + /** Root or child execution-session identifier that owns this recovery episode; omitted for legacy snapshots */ + @JsonProperty("ownerSessionId") String ownerSessionId, + /** Subagent identifier that owns this recovery episode when the request was delegated through a parent permission service */ + @JsonProperty("ownerAgentId") String ownerAgentId, /** Current lifecycle state of the recovery episode */ @JsonProperty("status") PermissionRecoveryStatus status, /** Policy selected from the current client's response capability; mode or client changes may update it during recovery */ diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/SessionQuotaObservationEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/SessionQuotaObservationEvent.java index c7b26660ca..00f710af94 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/SessionQuotaObservationEvent.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/SessionQuotaObservationEvent.java @@ -39,7 +39,7 @@ public final class SessionQuotaObservationEvent extends SessionEvent { @JsonIgnoreProperties(ignoreUnknown = true) @JsonInclude(JsonInclude.Include.NON_NULL) public record SessionQuotaObservationEventData( - /** The admitted provider's state observation. Admission observations never contain quantities or reset/percentage semantics. */ + /** The admitted provider's state observation. Admission balances and percentages remain absent; separate monthly usage may report its own amount and cycle. */ @JsonProperty("observation") ProviderQuotaState observation ) { } diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/SubagentStartedEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/SubagentStartedEvent.java index 8c8cde1c90..a2e907111d 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/SubagentStartedEvent.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/SubagentStartedEvent.java @@ -48,8 +48,6 @@ public record SubagentStartedEventData( @JsonProperty("taskModelSource") SubagentTaskModelSource taskModelSource, /** Authority or runtime mechanism responsible for sub-agent model selection, when known at start. */ @JsonProperty("modelSelectionSource") SubagentModelSelectionSource modelSelectionSource, - /** Legacy root id of the workflow run that spawned this sub-agent. New consumers should use workflowRunId. */ - @JsonProperty("factoryRunId") String factoryRunId, /** Root id of the workflow run that spawned this sub-agent, when it was spawned by one. */ @JsonProperty("workflowRunId") String workflowRunId, /** Task-registry ID of the spawning sub-agent. Absent when the root session spawned this child. */ diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/AccountDescribeAuthInfoParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/AccountDescribeAuthInfoParams.java new file mode 100644 index 0000000000..83a49d849a --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/AccountDescribeAuthInfoParams.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 authentication identity whose shared wording should be rendered. + * + * @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) +record AccountDescribeAuthInfoParams( + /** Identity to describe. */ + @JsonProperty("identity") AuthIdentity identity +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/AccountDescribeAuthInfoResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/AccountDescribeAuthInfoResult.java new file mode 100644 index 0000000000..8fcc083bbb --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/AccountDescribeAuthInfoResult.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; + +/** + * The wording the runtime uses for an authentication identity. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +record AccountDescribeAuthInfoResult( + /** Sign-in banner announcing the authenticated user. */ + @JsonProperty("message") String message, + /** One-line description of the identity, suitable after a verb such as "Switched to". */ + @JsonProperty("description") String description +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/AccountGetGistSupportParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/AccountGetGistSupportParams.java new file mode 100644 index 0000000000..76c1a53037 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/AccountGetGistSupportParams.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 authentication identity whose gist support should be reported. + * + * @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) +record AccountGetGistSupportParams( + /** Identity to evaluate. */ + @JsonProperty("identity") AuthIdentity identity +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/AccountGetGistSupportResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/AccountGetGistSupportResult.java new file mode 100644 index 0000000000..03862487a0 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/AccountGetGistSupportResult.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; + +/** + * Whether the identity can create gists, and why not when it cannot. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +record AccountGetGistSupportResult( + /** True when the identity can create gists. */ + @JsonProperty("supported") Boolean supported, + /** Machine-readable reason category when unsupported. */ + @JsonProperty("kind") String kind, + /** Human-readable explanation to show where the gist action would have been. */ + @JsonProperty("reason") String reason +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/AccountGetTokenEnvVarResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/AccountGetTokenEnvVarResult.java new file mode 100644 index 0000000000..5e7c2aacf5 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/AccountGetTokenEnvVarResult.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; + +/** + * The environment variable supplying a classic personal access token, when one is set. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +record AccountGetTokenEnvVarResult( + /** Variable name, for example `GH_TOKEN`. Absent when no classic token is present in the environment. */ + @JsonProperty("name") String name +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/AccountGetWebUrlParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/AccountGetWebUrlParams.java new file mode 100644 index 0000000000..7b4c7686c4 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/AccountGetWebUrlParams.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; + +/** + * A path to resolve against the GitHub host an identity is signed in to. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ +@CopilotExperimental +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +record AccountGetWebUrlParams( + /** Host-relative path, without a leading slash, for example `settings/copilot/features`. */ + @JsonProperty("path") String path, + /** Identity whose host the path resolves against. Omit to resolve against the public github.com host. */ + @JsonProperty("identity") AuthIdentity identity +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/AccountGetWebUrlResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/AccountGetWebUrlResult.java new file mode 100644 index 0000000000..5aa680cacd --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/AccountGetWebUrlResult.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; + +/** + * An absolute URL on the identity's GitHub host. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +record AccountGetWebUrlResult( + /** Absolute URL for the requested path. */ + @JsonProperty("url") String url +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/AccountStatus.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/AccountStatus.java index cb0868ce74..7cdebb3d29 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/AccountStatus.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/AccountStatus.java @@ -31,6 +31,8 @@ public record AccountStatus( @JsonProperty("login") String login, /** The provider kind of this account. */ @JsonProperty("kind") AccountKind kind, + /** Human-readable credential source used to authenticate this account, such as `gh` or `GITHUB_TOKEN`. */ + @JsonProperty("authSource") String authSource, /** Opaque id of the account this one was derived from (e.g. an EMU account's base Entra identity); absent for a root account. Matches the base identity account's selectionId, forming the derivation edge. */ @JsonProperty("derivedFrom") String derivedFrom, /** Whether this is the active account. */ diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/AgentInfo.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/AgentInfo.java index 27fd56a428..17debc1a4c 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/AgentInfo.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/AgentInfo.java @@ -54,6 +54,8 @@ public record AgentInfo( /** Skill names preloaded into this agent's context. Omitted means none. */ @JsonProperty("skills") List skills, /** Authored base prompt for the agent. Runtime prompt assembly may add dynamic context at invocation time. Omitted from `session.agent.list` unless `includePrompt` is true. */ - @JsonProperty("prompt") String prompt + @JsonProperty("prompt") String prompt, + /** Declarative hook actions keyed by event name, executed only while this agent is active. */ + @JsonProperty("hooks") Map> hooks ) { } diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/AgentsDiscoverRequest.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/AgentsDiscoverRequest.java new file mode 100644 index 0000000000..26ead3977a --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/AgentsDiscoverRequest.java @@ -0,0 +1,107 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import java.util.List; +import java.util.Objects; +import javax.annotation.processing.Generated; + +/** + * Optional project paths to include in agent discovery. + *

+ * Required inputs are constructor arguments. Optional inputs have fluent setters. + * + * @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) +public final class AgentsDiscoverRequest { + + /** 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). */ + @JsonProperty("projectPaths") + private List projectPaths; + + /** 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. */ + @JsonProperty("excludeHostAgents") + private Boolean excludeHostAgents; + + /** Explicit discovery-only agent roots, separate from customAgents and filesystem grants. Available without ambient discovery, subject to managed/source disables. Relative paths resolve against the request's effective working directory when supplied. Null and omission are equivalent; an empty array contributes no explicit roots. */ + @JsonProperty("agentDirectories") + private List agentDirectories; + + /** + * Creates a request with its required inputs. + */ + public AgentsDiscoverRequest() { + } + + /** + * Returns the {@code projectPaths} property. + * + * @return 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). + */ + public List getProjectPaths() { + return projectPaths; + } + + /** + * Returns the {@code excludeHostAgents} property. + * + * @return 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. + */ + public Boolean getExcludeHostAgents() { + return excludeHostAgents; + } + + /** + * Returns the {@code agentDirectories} property. + * + * @return Explicit discovery-only agent roots, separate from customAgents and filesystem grants. Available without ambient discovery, subject to managed/source disables. Relative paths resolve against the request's effective working directory when supplied. Null and omission are equivalent; an empty array contributes no explicit roots. + */ + public List getAgentDirectories() { + return agentDirectories; + } + + /** + * Sets the {@code projectPaths} property. + * + * @param value 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). + * @return this request + */ + public AgentsDiscoverRequest setProjectPaths(List value) { + this.projectPaths = value; + return this; + } + + /** + * Sets the {@code excludeHostAgents} property. + * + * @param value 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. + * @return this request + */ + public AgentsDiscoverRequest setExcludeHostAgents(Boolean value) { + this.excludeHostAgents = value; + return this; + } + + /** + * Sets the {@code agentDirectories} property. + * + * @param value Explicit discovery-only agent roots, separate from customAgents and filesystem grants. Available without ambient discovery, subject to managed/source disables. Relative paths resolve against the request's effective working directory when supplied. Null and omission are equivalent; an empty array contributes no explicit roots. + * @return this request + */ + public AgentsDiscoverRequest setAgentDirectories(List value) { + this.agentDirectories = value; + return this; + } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/AgentsGetDiscoveryPathsRequest.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/AgentsGetDiscoveryPathsRequest.java new file mode 100644 index 0000000000..fe9123f9ba --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/AgentsGetDiscoveryPathsRequest.java @@ -0,0 +1,107 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import java.util.List; +import java.util.Objects; +import javax.annotation.processing.Generated; + +/** + * Optional project paths to include when enumerating agent discovery directories. + *

+ * Required inputs are constructor arguments. Optional inputs have fluent setters. + * + * @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) +public final class AgentsGetDiscoveryPathsRequest { + + /** Optional list of project directory paths. When omitted or empty, only the user-level directory is returned. */ + @JsonProperty("projectPaths") + private List projectPaths; + + /** When true, omit the host's user-level agent directory, leaving only project directories. For multitenant deployments (mirrors `discover`'s `excludeHostAgents`). */ + @JsonProperty("excludeHostAgents") + private Boolean excludeHostAgents; + + /** Explicit discovery-only agent roots to enumerate, including configured missing directories. Relative paths resolve against the request's effective working directory when supplied. These are not filesystem grants or preferred creation targets. Null and omission are equivalent. */ + @JsonProperty("agentDirectories") + private List agentDirectories; + + /** + * Creates a request with its required inputs. + */ + public AgentsGetDiscoveryPathsRequest() { + } + + /** + * Returns the {@code projectPaths} property. + * + * @return Optional list of project directory paths. When omitted or empty, only the user-level directory is returned. + */ + public List getProjectPaths() { + return projectPaths; + } + + /** + * Returns the {@code excludeHostAgents} property. + * + * @return When true, omit the host's user-level agent directory, leaving only project directories. For multitenant deployments (mirrors `discover`'s `excludeHostAgents`). + */ + public Boolean getExcludeHostAgents() { + return excludeHostAgents; + } + + /** + * Returns the {@code agentDirectories} property. + * + * @return Explicit discovery-only agent roots to enumerate, including configured missing directories. Relative paths resolve against the request's effective working directory when supplied. These are not filesystem grants or preferred creation targets. Null and omission are equivalent. + */ + public List getAgentDirectories() { + return agentDirectories; + } + + /** + * Sets the {@code projectPaths} property. + * + * @param value Optional list of project directory paths. When omitted or empty, only the user-level directory is returned. + * @return this request + */ + public AgentsGetDiscoveryPathsRequest setProjectPaths(List value) { + this.projectPaths = value; + return this; + } + + /** + * Sets the {@code excludeHostAgents} property. + * + * @param value When true, omit the host's user-level agent directory, leaving only project directories. For multitenant deployments (mirrors `discover`'s `excludeHostAgents`). + * @return this request + */ + public AgentsGetDiscoveryPathsRequest setExcludeHostAgents(Boolean value) { + this.excludeHostAgents = value; + return this; + } + + /** + * Sets the {@code agentDirectories} property. + * + * @param value Explicit discovery-only agent roots to enumerate, including configured missing directories. Relative paths resolve against the request's effective working directory when supplied. These are not filesystem grants or preferred creation targets. Null and omission are equivalent. + * @return this request + */ + public AgentsGetDiscoveryPathsRequest setAgentDirectories(List value) { + this.agentDirectories = value; + return this; + } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogAgentPluginCandidate.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogAgentPluginCandidate.java index 929a3924b4..b260b27c22 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogAgentPluginCandidate.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogAgentPluginCandidate.java @@ -29,7 +29,7 @@ public final class CatalogAgentPluginCandidate extends CatalogCandidate { @Override public String getKind() { return kind; } - /** Canonical Agent Plugin media type. */ + /** Media type naming the plugin's format. */ @JsonProperty("mediaType") private CatalogAgentPluginMediaType mediaType; @@ -61,6 +61,14 @@ public final class CatalogAgentPluginCandidate extends CatalogCandidate { @JsonProperty("compatibilityTags") private List compatibilityTags; + /** Whether this plugin can be installed in the selected session. Present only when the caller requires the agent-plugin-confirmed-installation capability. */ + @JsonProperty("installability") + private CatalogAgentPluginInstallability installability; + + /** Reviewable install plan built only from verified catalogue install data. Present exactly when installability is `installable`; the runtime never derives it from the repository source, display text, or identity. */ + @JsonProperty("installPlan") + private CatalogAgentPluginInstallPlan installPlan; + /** Where the Agent Plugin catalog reference was observed, without its descriptor, URL, raw data, or content digest. */ @JsonProperty("provenance") private CatalogAgentPluginCandidateProvenance provenance; @@ -93,6 +101,12 @@ public final class CatalogAgentPluginCandidate extends CatalogCandidate { public List getCompatibilityTags() { return compatibilityTags; } public void setCompatibilityTags(List compatibilityTags) { this.compatibilityTags = compatibilityTags; } + public CatalogAgentPluginInstallability getInstallability() { return installability; } + public void setInstallability(CatalogAgentPluginInstallability installability) { this.installability = installability; } + + public CatalogAgentPluginInstallPlan getInstallPlan() { return installPlan; } + public void setInstallPlan(CatalogAgentPluginInstallPlan installPlan) { this.installPlan = installPlan; } + public CatalogAgentPluginCandidateProvenance getProvenance() { return provenance; } public void setProvenance(CatalogAgentPluginCandidateProvenance provenance) { this.provenance = provenance; } diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogAgentPluginCandidateProvenance.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogAgentPluginCandidateProvenance.java index d3e65be949..ff6316dc13 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogAgentPluginCandidateProvenance.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogAgentPluginCandidateProvenance.java @@ -25,7 +25,7 @@ public record CatalogAgentPluginCandidateProvenance( @JsonProperty("authority") String authority, /** ISO 8601 timestamp at which the runtime observed the catalog reference. This is not a retrieval or validation timestamp. */ @JsonProperty("observedAt") String observedAt, - /** Canonical Agent Plugin media type. */ + /** Media type naming the plugin's format. */ @JsonProperty("mediaType") CatalogAgentPluginMediaType mediaType ) { } diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogAgentPluginComponent.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogAgentPluginComponent.java new file mode 100644 index 0000000000..e5d44f67f2 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogAgentPluginComponent.java @@ -0,0 +1,47 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (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 kind of component an Agent Plugin contributes + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public enum CatalogAgentPluginComponent { + /** The {@code agent} variant. */ + AGENT("agent"), + /** The {@code command} variant. */ + COMMAND("command"), + /** The {@code skill} variant. */ + SKILL("skill"), + /** The {@code hook} variant. */ + HOOK("hook"), + /** The {@code mcp-server} variant. */ + MCP_SERVER("mcp-server"), + /** The {@code lsp-server} variant. */ + LSP_SERVER("lsp-server"), + /** The {@code extension} variant. */ + EXTENSION("extension"), + /** The {@code canvas} variant. */ + CANVAS("canvas"); + + private final String value; + CatalogAgentPluginComponent(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static CatalogAgentPluginComponent fromValue(String value) { + for (CatalogAgentPluginComponent v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown CatalogAgentPluginComponent value: " + value); + } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogAgentPluginInstallPlan.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogAgentPluginInstallPlan.java new file mode 100644 index 0000000000..d488838951 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogAgentPluginInstallPlan.java @@ -0,0 +1,44 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; +import javax.annotation.processing.Generated; + +/** + * What installing an Agent Plugin would do, for the user to review and confirm. Provisional: the catalogue install data it is built from is not final. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record CatalogAgentPluginInstallPlan( + /** Every valid install source, in preference order: materialization, then marketplace, then repository. Each kind appears at most once. */ + @JsonProperty("sources") List sources, + /** The plugin's name, as named in its marketplace or manifest. */ + @JsonProperty("pluginName") String pluginName, + /** Exact plugin version to install. A plan with a marketplace source carries at least one of version and commit. */ + @JsonProperty("version") String version, + /** Exact full commit ID to install, in lowercase hex. Always present when the plan has a repository source; a plan with a marketplace source carries at least one of version and commit. */ + @JsonProperty("commit") String commit, + /** Components the plugin contributes, in canonical order. */ + @JsonProperty("components") List components, + /** Canvas IDs the plugin contributes. Non-empty exactly when components include `canvas`. */ + @JsonProperty("canvasIds") List canvasIds, + /** Extension IDs the plugin contributes. Non-empty exactly when components include `extension`. */ + @JsonProperty("extensionIds") List extensionIds, + /** MCP servers the plugin depends on but does not bundle. */ + @JsonProperty("mcpDependencies") List mcpDependencies, + /** Connectors the plugin depends on. */ + @JsonProperty("connectorDependencies") List connectorDependencies +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogAgentPluginInstallSource.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogAgentPluginInstallSource.java new file mode 100644 index 0000000000..8ba3e644c7 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogAgentPluginInstallSource.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.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import javax.annotation.processing.Generated; + +/** + * One place an Agent Plugin can install from: Agent Finder's materialisation, a marketplace, or a GitHub repository. Each variant carries only its own fields. + * + * @since 1.0.0 + */ +@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXISTING_PROPERTY, property = "kind", visible = true) +@JsonSubTypes({ + @JsonSubTypes.Type(value = CatalogAgentPluginMaterializationSource.class, name = "materialization"), + @JsonSubTypes.Type(value = CatalogAgentPluginMarketplaceSource.class, name = "marketplace"), + @JsonSubTypes.Type(value = CatalogAgentPluginRepositorySource.class, name = "repository") +}) +@JsonIgnoreProperties(ignoreUnknown = true) +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public abstract class CatalogAgentPluginInstallSource { + + /** + * 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/CatalogAgentPluginInstallability.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogAgentPluginInstallability.java new file mode 100644 index 0000000000..3da72fd410 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogAgentPluginInstallability.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; + +/** + * Typed installability state for an Agent Plugin candidate + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public enum CatalogAgentPluginInstallability { + /** The {@code installable} variant. */ + INSTALLABLE("installable"), + /** The {@code install-data-unavailable} variant. */ + INSTALL_DATA_UNAVAILABLE("install-data-unavailable"), + /** The {@code feature-disabled} variant. */ + FEATURE_DISABLED("feature-disabled"), + /** The {@code not-installable-kind} variant. */ + NOT_INSTALLABLE_KIND("not-installable-kind"); + + private final String value; + CatalogAgentPluginInstallability(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static CatalogAgentPluginInstallability fromValue(String value) { + for (CatalogAgentPluginInstallability v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown CatalogAgentPluginInstallability value: " + value); + } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogAgentPluginMarketplaceSource.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogAgentPluginMarketplaceSource.java new file mode 100644 index 0000000000..df5f25e4b2 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogAgentPluginMarketplaceSource.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 plugin marketplace named by verified catalogue install data, installed as `pluginName@name`. + * + * @since 1.0.0 + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public final class CatalogAgentPluginMarketplaceSource extends CatalogAgentPluginInstallSource { + + @JsonProperty("kind") + private final String kind = "marketplace"; + + @Override + public String getKind() { return kind; } + + /** Marketplace name. */ + @JsonProperty("name") + private String name; + + /** Canonical lowercase GitHub owner/repository hosting the marketplace. */ + @JsonProperty("repository") + private String repository; + + public String getName() { return name; } + public void setName(String name) { this.name = name; } + + public String getRepository() { return repository; } + public void setRepository(String repository) { this.repository = repository; } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogAgentPluginMaterializationSource.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogAgentPluginMaterializationSource.java new file mode 100644 index 0000000000..5ee9b1a83d --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogAgentPluginMaterializationSource.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; + +/** + * Agent Finder's verified materialisation of the plugin. Present only when the candidate carries a complete materialisation reference; the reference itself stays runtime-private. + * + * @since 1.0.0 + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public final class CatalogAgentPluginMaterializationSource extends CatalogAgentPluginInstallSource { + + @JsonProperty("kind") + private final String kind = "materialization"; + + @Override + public String getKind() { return kind; } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogAgentPluginMediaType.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogAgentPluginMediaType.java index 62a1e5e70f..8e1cb2c536 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogAgentPluginMediaType.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogAgentPluginMediaType.java @@ -10,14 +10,18 @@ import javax.annotation.processing.Generated; /** - * Canonical Agent Plugin media type + * Media type naming the plugin format of an Agent Plugin candidate. Values other than `application/vnd.github.copilot-plugin` appear only when the caller requires the agent-plugin-formats capability. * * @since 1.0.0 */ @javax.annotation.processing.Generated("copilot-sdk-codegen") public enum CatalogAgentPluginMediaType { /** The {@code application/vnd.github.copilot-plugin} variant. */ - APPLICATION_VND_GITHUB_COPILOT_PLUGIN("application/vnd.github.copilot-plugin"); + APPLICATION_VND_GITHUB_COPILOT_PLUGIN("application/vnd.github.copilot-plugin"), + /** The {@code application/vnd.anthropic.claude-plugin+json} variant. */ + APPLICATION_VND_ANTHROPIC_CLAUDE_PLUGIN_JSON("application/vnd.anthropic.claude-plugin+json"), + /** The {@code application/agent-plugin+json} variant. */ + APPLICATION_AGENT_PLUGIN_JSON("application/agent-plugin+json"); private final String value; CatalogAgentPluginMediaType(String value) { this.value = value; } diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogAgentPluginRepositorySource.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogAgentPluginRepositorySource.java new file mode 100644 index 0000000000..f6ecd5ba4a --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogAgentPluginRepositorySource.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 GitHub repository holding the plugin, installed directly as `owner/repo[:path]` at the plan's commit, without a marketplace. + * + * @since 1.0.0 + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public final class CatalogAgentPluginRepositorySource extends CatalogAgentPluginInstallSource { + + @JsonProperty("kind") + private final String kind = "repository"; + + @Override + public String getKind() { return kind; } + + /** Canonical lowercase GitHub owner/repository holding the plugin. */ + @JsonProperty("repository") + private String repository; + + /** Relative POSIX path of the plugin directory in the repository. Omitted when the plugin is at the repository root. */ + @JsonProperty("path") + private String path; + + public String getRepository() { return repository; } + public void setRepository(String repository) { this.repository = repository; } + + public String getPath() { return path; } + public void setPath(String path) { this.path = path; } +} 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 index e782f16aae..7fae5f5f06 100644 --- 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 @@ -24,6 +24,8 @@ public enum CatalogCapability { AI_SKILL_DISCOVERY("ai-skill-discovery"), /** The {@code agent-plugin-discovery} variant. */ AGENT_PLUGIN_DISCOVERY("agent-plugin-discovery"), + /** The {@code agent-plugin-formats} variant. */ + AGENT_PLUGIN_FORMATS("agent-plugin-formats"), /** The {@code mcp-install-planning} variant. */ MCP_INSTALL_PLANNING("mcp-install-planning"), /** The {@code multiple-transport-choice} variant. */ @@ -43,7 +45,11 @@ public enum CatalogCapability { /** The {@code mcp-configured-remote-installation} variant. */ MCP_CONFIGURED_REMOTE_INSTALLATION("mcp-configured-remote-installation"), /** The {@code skill-confirmed-installation} variant. */ - SKILL_CONFIRMED_INSTALLATION("skill-confirmed-installation"); + SKILL_CONFIRMED_INSTALLATION("skill-confirmed-installation"), + /** The {@code agent-plugin-confirmed-installation} variant. */ + AGENT_PLUGIN_CONFIRMED_INSTALLATION("agent-plugin-confirmed-installation"), + /** The {@code skill-reference-installation} variant. */ + SKILL_REFERENCE_INSTALLATION("skill-reference-installation"); private final String value; CatalogCapability(String value) { this.value = value; } diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogMaterializationReference.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogMaterializationReference.java new file mode 100644 index 0000000000..08803d045a --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogMaterializationReference.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; + +/** + * An Agent Finder materialisation reference pinning one exact resource revision. + +It carries no origin or URL: the runtime always builds the descriptor URL on its fixed Agent +Finder authority. It carries no display data either; every reviewed field comes from the +verified descriptor and Skill files. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record CatalogMaterializationReference( + /** Agent Finder resource identifier, a positive decimal integer. */ + @JsonProperty("resourceId") String resourceId, + /** Agent Finder materialisation revision identifier, a positive decimal integer. This is not a Git commit. */ + @JsonProperty("revisionId") String revisionId, + /** Materialisation descriptor schema version, currently `1.0.0`. */ + @JsonProperty("schemaVersion") String schemaVersion, + /** Expected `sha256:` digest of the canonical descriptor. */ + @JsonProperty("descriptorDigest") String descriptorDigest, + /** Expected `sha256:` digest of the descriptor's bundle manifest. */ + @JsonProperty("bundleDigest") String bundleDigest +) { +} 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 index ddb2841fca..bc42b926e2 100644 --- 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 @@ -23,7 +23,11 @@ public enum CatalogMediaType { /** The {@code application/ai-skill} variant. */ APPLICATION_AI_SKILL("application/ai-skill"), /** The {@code application/vnd.github.copilot-plugin} variant. */ - APPLICATION_VND_GITHUB_COPILOT_PLUGIN("application/vnd.github.copilot-plugin"); + APPLICATION_VND_GITHUB_COPILOT_PLUGIN("application/vnd.github.copilot-plugin"), + /** The {@code application/vnd.anthropic.claude-plugin+json} variant. */ + APPLICATION_VND_ANTHROPIC_CLAUDE_PLUGIN_JSON("application/vnd.anthropic.claude-plugin+json"), + /** The {@code application/agent-plugin+json} variant. */ + APPLICATION_AGENT_PLUGIN_JSON("application/agent-plugin+json"); private final String value; CatalogMediaType(String value) { this.value = value; } diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ContentExclusionValidatePoliciesParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ContentExclusionValidatePoliciesParams.java new file mode 100644 index 0000000000..a1c8d712e5 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ContentExclusionValidatePoliciesParams.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; + +/** + * Candidate content-exclusion policies to validate, as the raw JSON text a host received. The text is supplied unparsed so that a syntax error and a schema violation are both reported by the runtime's own validator rather than by the caller's JSON parser. + * + * @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 ContentExclusionValidatePoliciesParams( + /** Unparsed JSON text expected to hold an array of content-exclusion policies. */ + @JsonProperty("policiesJson") String policiesJson +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ContentExclusionValidatePoliciesResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ContentExclusionValidatePoliciesResult.java new file mode 100644 index 0000000000..b45bf2329a --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ContentExclusionValidatePoliciesResult.java @@ -0,0 +1,31 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import java.util.List; +import javax.annotation.processing.Generated; + +/** + * The accepted policies, parsed and normalized. A rejected input is reported as an error rather than in this result. + * + * @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 ContentExclusionValidatePoliciesResult( + /** Validated policy objects in input order. Raw JSON preserves every field returned by the runtime's policy parser. */ + @JsonProperty("policies") List policies +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/FeatureFlagAvailability.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/FeatureFlagAvailability.java new file mode 100644 index 0000000000..953a52226c --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/FeatureFlagAvailability.java @@ -0,0 +1,49 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import javax.annotation.processing.Generated; + +/** + * The population a feature flag is declared for, before overrides. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +enum FeatureFlagAvailability { + /** The {@code on} variant. */ + ON("on"), + /** The {@code team} variant. */ + TEAM("team"), + /** The {@code staff-and-experimental} variant. */ + STAFF_AND_EXPERIMENTAL("staff-and-experimental"), + /** The {@code staff-or-experimental} variant. */ + STAFF_OR_EXPERIMENTAL("staff-or-experimental"), + /** The {@code staff} variant. */ + STAFF("staff"), + /** The {@code staff-github} variant. */ + STAFF_GITHUB("staff-github"), + /** The {@code staff-microsoft} variant. */ + STAFF_MICROSOFT("staff-microsoft"), + /** The {@code experimental} variant. */ + EXPERIMENTAL("experimental"), + /** The {@code off} variant. */ + OFF("off"); + + private final String value; + FeatureFlagAvailability(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static FeatureFlagAvailability fromValue(String value) { + for (FeatureFlagAvailability v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown FeatureFlagAvailability value: " + value); + } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/FeatureFlagMetadata.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/FeatureFlagMetadata.java new file mode 100644 index 0000000000..49ac33f82f --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/FeatureFlagMetadata.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; + +/** + * The declared shape of one feature flag, independent of any caller population. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +record FeatureFlagMetadata( + /** The flag's name, matching the key it is listed under. */ + @JsonProperty("name") String name, + /** The population the flag is declared for before any environment or user-settings override is applied. */ + @JsonProperty("availability") FeatureFlagAvailability availability, + /** Whether the flag participates in the CAPI sanity suite. */ + @JsonProperty("capiSanity") Boolean capiSanity, + /** Text shown to a user choosing whether to turn the flag on in experimental mode. Absent when the flag is not user-facing. */ + @JsonProperty("experimentalDescription") String experimentalDescription, + /** The online-experiment (ExP) flag this maps to, when the flag is assigned by an experiment rather than declared statically. */ + @JsonProperty("expFlag") String expFlag, + /** Environment variable that overrides this flag's experiment assignment locally in the experiment service, not in `featureFlags.resolve`. Present exactly when `expFlag` is, and derived from it, so a caller reads a local override without reproducing the naming rule. */ + @JsonProperty("expFlagEnvVar") String expFlagEnvVar +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/FeatureFlagsMetadataResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/FeatureFlagsMetadataResult.java new file mode 100644 index 0000000000..01e738c670 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/FeatureFlagsMetadataResult.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 java.util.Map; +import javax.annotation.processing.Generated; + +/** + * Every feature flag this runtime declares, with the shape each one was declared with. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +record FeatureFlagsMetadataResult( + /** Declared feature flags keyed by flag name. */ + @JsonProperty("flags") Map flags +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/FeatureFlagsResolveParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/FeatureFlagsResolveParams.java new file mode 100644 index 0000000000..bf459ff033 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/FeatureFlagsResolveParams.java @@ -0,0 +1,47 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package 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; + +/** + * Caller population and overrides for static feature-flag resolution, independent of experiment assignments and active runtime state. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ +@CopilotExperimental +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +record FeatureFlagsResolveParams( + /** Whether the caller is GitHub or Microsoft staff. */ + @JsonProperty("isStaff") Boolean isStaff, + /** Whether the caller is in an experimental population. */ + @JsonProperty("isExperimental") Boolean isExperimental, + /** Whether the caller belongs to the team population used by feature availability. */ + @JsonProperty("isTeam") Boolean isTeam, + /** GitHub-staff membership. Omit when it was never recorded: a legacy staff account then counts as both staff populations and keeps every staff feature. */ + @JsonProperty("isStaffGithub") Boolean isStaffGitHub, + /** Microsoft-staff membership. Omitted the same way as `isStaffGitHub`. */ + @JsonProperty("isStaffMicrosoft") Boolean isStaffMicrosoft, + /** Resolve against this already-computed flag set rather than each flag's declared availability. Keyed by flag name. */ + @JsonProperty("baseFlags") Map baseFlags, + /** Environment for direct feature-flag overrides and `COPILOT_CLI_ENABLED_FEATURE_FLAGS`. Does not apply `COPILOT_EXP_*` experiment assignments. Defaults to this runtime process's own environment. */ + @JsonProperty("environment") Map environment, + /** User settings to apply, including `enabledFeatureFlags`, legacy `feature_flags.enabled`, and supported convenience settings. */ + @JsonProperty("config") Object config, + /** Flags forced to a value after every other layer, keyed by flag name. */ + @JsonProperty("flagOverrides") Map flagOverrides +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/FeatureFlagsResolveStartupGatesParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/FeatureFlagsResolveStartupGatesParams.java new file mode 100644 index 0000000000..3a08c2e5b8 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/FeatureFlagsResolveStartupGatesParams.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 java.util.List; +import java.util.Map; +import javax.annotation.processing.Generated; + +/** + * Caller population and startup inputs to resolve named online-experiment gates for. Carries no account: the runtime resolves the assignment for the authentication it already holds. + * + * @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) +record FeatureFlagsResolveStartupGatesParams( + /** The feature flags to resolve. A name this runtime does not declare is reported as an error rather than as a disabled flag. */ + @JsonProperty("flags") List flags, + /** Whether the caller is GitHub or Microsoft staff. */ + @JsonProperty("isStaff") Boolean isStaff, + /** Whether the caller is in an experimental population. */ + @JsonProperty("isExperimental") Boolean isExperimental, + /** Whether the caller belongs to a team plan. */ + @JsonProperty("isTeam") Boolean isTeam, + /** GitHub-staff membership. Omit when it was never recorded: a legacy staff account then counts as both staff populations and keeps every staff feature. */ + @JsonProperty("isStaffGithub") Boolean isStaffGitHub, + /** Microsoft-staff membership. Omitted the same way as `isStaffGitHub`. */ + @JsonProperty("isStaffMicrosoft") Boolean isStaffMicrosoft, + /** Environment to read flag overrides from. Defaults to this runtime process's own environment. */ + @JsonProperty("environment") Map environment, + /** User settings document whose `featureFlags` block is layered on top of the resolved set. */ + @JsonProperty("config") Object config, + /** Whether streamer mode is on, which withholds the populations an experiment would otherwise segment on. */ + @JsonProperty("streamerMode") Boolean streamerMode, + /** When this installation first launched, in milliseconds since the Unix epoch. Experiments that segment on installation age read it. Omit when the first launch was never recorded. */ + @JsonProperty("firstLaunchAtMillis") Double firstLaunchAtMillis, + /** Whether the runtime may refresh the cached assignment over the network without an active session. The refresh never blocks this call: the answer comes from the cache either way, so a refresh only affects a later launch. */ + @JsonProperty("refreshWithoutSession") Boolean refreshWithoutSession +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/FolderTrustCheckResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/FolderTrustCheckResult.java new file mode 100644 index 0000000000..746ad554f5 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/FolderTrustCheckResult.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; + +/** + * Folder trust check result. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record FolderTrustCheckResult( + /** Whether the folder is trusted */ + @JsonProperty("trusted") Boolean trusted +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ForceLoginOrgsDecision.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ForceLoginOrgsDecision.java new file mode 100644 index 0000000000..24218c69e5 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ForceLoginOrgsDecision.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; + +/** + * The verdict of the `forceLoginOrgs` enterprise sign-in pin for one credential. A managed device may pin Copilot to an approved set of GitHub organizations so a developer cannot sign in with a personal — or another enterprise's — account and escape every control that keys off the signed-in identity. A host must refuse to proceed when `allowed` is false; the remaining fields explain the refusal rather than qualifying it. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +record ForceLoginOrgsDecision( + /** Whether the client may proceed. True whenever no pin is in force, so an unmanaged host is never blocked. */ + @JsonProperty("allowed") Boolean allowed, + /** Whether a pin applies at all. Distinguishes "allowed because unmanaged" from "allowed because the account is approved", which a diagnostics surface needs and which also decides whether a bring-your-own-key provider may serve inference. */ + @JsonProperty("enforced") Boolean enforced, + /** The approved organization logins, as configured. Enterprise configuration rather than user data, so it is safe to name in a message or a log line. */ + @JsonProperty("requiredOrgs") List requiredOrgs, + /** The approved organization the account matched, when allowed under a pin. */ + @JsonProperty("matchedOrg") String matchedOrg, + /** A stable kebab-case reason code when refused: `org-not-approved`, `unverifiable-credential`, `policy-undetermined`, `policy-invalid`, `not-signed-in`, or `membership-unknown`. Omitted when allowed. Treat an unrecognized code as a refusal rather than mapping it to an allow: a code this host has not been taught about still denied the credential. */ + @JsonProperty("denial") String denial, + /** A developer-facing explanation of the refusal. Omitted when allowed. */ + @JsonProperty("message") String message, + /** Error-level detail from managed-policy discovery — which file failed to load, which field failed validation. Carried on the decision rather than left to the log because a refusal can exit the process before a log sink exists, which would discard the only actionable diagnostic a misconfigured managed device produces. */ + @JsonProperty("policyDiagnostics") List policyDiagnostics +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/GitHubOwnersListParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/GitHubOwnersListParams.java index 224b85f6dc..fd1489b805 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/GitHubOwnersListParams.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/GitHubOwnersListParams.java @@ -27,6 +27,7 @@ record GitHubOwnersListParams( /** Request id from `gitHubOwners.nextRequestId`. An id that was never registered, canceled before use, released after being abandoned, or already used is refused rather than silently running uncancellable. */ @JsonProperty("requestId") Long requestId, /** The credential the listing runs under, carried opaquely because its shape is the host's own and the runtime only resolves a token and a GitHub host from it. No credential travels: this selects one the runtime already holds. */ + @JsonInclude(JsonInclude.Include.ALWAYS) @JsonProperty("authInfo") Object authInfo ) { } diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/GlobalStateLoadResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/GlobalStateLoadResult.java index 0b6dbb9d53..9db799b57e 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/GlobalStateLoadResult.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/GlobalStateLoadResult.java @@ -58,8 +58,6 @@ record GlobalStateLoadResult( @JsonProperty("staffUpdateChannelMigrationAt") String staffUpdateChannelMigrationAt, /** Folders the user has marked as trusted. */ @JsonProperty("trustedFolders") List trustedFolders, - /** Whether the user has answered the prompt suggesting they install the desktop app. */ - @JsonProperty("appInstallNudgeResponded") Boolean appInstallNudgeResponded, /** When the Auto-feedback hint was last shown, as an ISO 8601 timestamp. It enforces the once-per-day cap for non-staff users across restarts. */ @JsonProperty("autoFeedbackLastPromptedAt") String autoFeedbackLastPromptedAt ) { diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/HooksDiscoverRequest.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/HooksDiscoverRequest.java new file mode 100644 index 0000000000..5d76a6d222 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/HooksDiscoverRequest.java @@ -0,0 +1,107 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import java.util.List; +import java.util.Objects; +import javax.annotation.processing.Generated; + +/** + * Optional project paths and host-exclusion behavior for server-scoped hook discovery. + *

+ * Required inputs are constructor arguments. Optional inputs have fluent setters. + * + * @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) +public final class 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. */ + @JsonProperty("projectPaths") + private 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") + private Boolean excludeHostHooks; + + /** Explicit discovery-only file-hook roots, separate from callback hooks and filesystem grants. Available without ambient discovery or repository file-hook enablement, subject to managed/hook disables. Relative paths resolve against the request's effective working directory when supplied. Null and omission are equivalent; an empty array contributes no explicit roots. */ + @JsonProperty("hookDirectories") + private List hookDirectories; + + /** + * Creates a request with its required inputs. + */ + public HooksDiscoverRequest() { + } + + /** + * Returns the {@code projectPaths} property. + * + * @return 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. + */ + public List getProjectPaths() { + return projectPaths; + } + + /** + * Returns the {@code excludeHostHooks} property. + * + * @return 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. + */ + public Boolean getExcludeHostHooks() { + return excludeHostHooks; + } + + /** + * Returns the {@code hookDirectories} property. + * + * @return Explicit discovery-only file-hook roots, separate from callback hooks and filesystem grants. Available without ambient discovery or repository file-hook enablement, subject to managed/hook disables. Relative paths resolve against the request's effective working directory when supplied. Null and omission are equivalent; an empty array contributes no explicit roots. + */ + public List getHookDirectories() { + return hookDirectories; + } + + /** + * Sets the {@code projectPaths} property. + * + * @param value 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. + * @return this request + */ + public HooksDiscoverRequest setProjectPaths(List value) { + this.projectPaths = value; + return this; + } + + /** + * Sets the {@code excludeHostHooks} property. + * + * @param value 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. + * @return this request + */ + public HooksDiscoverRequest setExcludeHostHooks(Boolean value) { + this.excludeHostHooks = value; + return this; + } + + /** + * Sets the {@code hookDirectories} property. + * + * @param value Explicit discovery-only file-hook roots, separate from callback hooks and filesystem grants. Available without ambient discovery or repository file-hook enablement, subject to managed/hook disables. Relative paths resolve against the request's effective working directory when supplied. Null and omission are equivalent; an empty array contributes no explicit roots. + * @return this request + */ + public HooksDiscoverRequest setHookDirectories(List value) { + this.hookDirectories = value; + return this; + } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/InstructionsDiscoverRepoSourcesParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/InstructionsDiscoverRepoSourcesParams.java new file mode 100644 index 0000000000..5aee113bf8 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/InstructionsDiscoverRepoSourcesParams.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.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import java.util.List; +import javax.annotation.processing.Generated; + +/** + * Explicit discovery arguments for one location, supplied by the caller rather than derived from 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) +record InstructionsDiscoverRepoSourcesParams( + /** Repository location to discover. An empty string selects no repository scope; cwdPath can still supply working-directory sources. */ + @JsonProperty("location") String location, + /** When true, include the sources a command-line host reads. Hosts that are not the CLI omit it. */ + @JsonProperty("isCli") Boolean isCli, + /** Working directory whose instruction sources should also be included. Omission adds no separate working-directory scope. */ + @JsonProperty("cwdPath") String cwdPath, + /** Copilot configuration directory for user-level sources. An omitted or empty value uses the server's configuration home. */ + @JsonProperty("configDir") String configDir, + /** Extra directories to scan alongside instruction directories configured in the runtime environment. */ + @JsonProperty("additionalDirs") List additionalDirs, + /** When true, also discover instruction files nested below the location. */ + @JsonProperty("enableChildInstructions") Boolean enableChildInstructions +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/InstructionsDiscoverRequest.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/InstructionsDiscoverRequest.java new file mode 100644 index 0000000000..34175da41b --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/InstructionsDiscoverRequest.java @@ -0,0 +1,107 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import java.util.List; +import java.util.Objects; +import javax.annotation.processing.Generated; + +/** + * Optional project paths to include in instruction discovery. + *

+ * Required inputs are constructor arguments. Optional inputs have fluent setters. + * + * @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) +public final class InstructionsDiscoverRequest { + + /** 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). */ + @JsonProperty("projectPaths") + private List projectPaths; + + /** 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. */ + @JsonProperty("excludeHostInstructions") + private Boolean excludeHostInstructions; + + /** Explicit discovery-only instruction roots using existing SDK instruction formats, not filesystem grants. Available without ambient discovery, subject to instruction/managed disables. Relative paths resolve against the request's effective working directory when supplied. Null and omission are equivalent; an empty array contributes no explicit roots. */ + @JsonProperty("instructionDirectories") + private List instructionDirectories; + + /** + * Creates a request with its required inputs. + */ + public InstructionsDiscoverRequest() { + } + + /** + * Returns the {@code projectPaths} property. + * + * @return 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). + */ + public List getProjectPaths() { + return projectPaths; + } + + /** + * Returns the {@code excludeHostInstructions} property. + * + * @return 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. + */ + public Boolean getExcludeHostInstructions() { + return excludeHostInstructions; + } + + /** + * Returns the {@code instructionDirectories} property. + * + * @return Explicit discovery-only instruction roots using existing SDK instruction formats, not filesystem grants. Available without ambient discovery, subject to instruction/managed disables. Relative paths resolve against the request's effective working directory when supplied. Null and omission are equivalent; an empty array contributes no explicit roots. + */ + public List getInstructionDirectories() { + return instructionDirectories; + } + + /** + * Sets the {@code projectPaths} property. + * + * @param value 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). + * @return this request + */ + public InstructionsDiscoverRequest setProjectPaths(List value) { + this.projectPaths = value; + return this; + } + + /** + * Sets the {@code excludeHostInstructions} property. + * + * @param value 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. + * @return this request + */ + public InstructionsDiscoverRequest setExcludeHostInstructions(Boolean value) { + this.excludeHostInstructions = value; + return this; + } + + /** + * Sets the {@code instructionDirectories} property. + * + * @param value Explicit discovery-only instruction roots using existing SDK instruction formats, not filesystem grants. Available without ambient discovery, subject to instruction/managed disables. Relative paths resolve against the request's effective working directory when supplied. Null and omission are equivalent; an empty array contributes no explicit roots. + * @return this request + */ + public InstructionsDiscoverRequest setInstructionDirectories(List value) { + this.instructionDirectories = value; + return this; + } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/InstructionsGetDiscoveryPathsRequest.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/InstructionsGetDiscoveryPathsRequest.java new file mode 100644 index 0000000000..fc328be97e --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/InstructionsGetDiscoveryPathsRequest.java @@ -0,0 +1,107 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import java.util.List; +import java.util.Objects; +import javax.annotation.processing.Generated; + +/** + * Optional project paths to include when enumerating instruction discovery targets. + *

+ * Required inputs are constructor arguments. Optional inputs have fluent setters. + * + * @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) +public final class InstructionsGetDiscoveryPathsRequest { + + /** Optional list of project directory paths. When omitted or empty, only the user-level targets are returned. */ + @JsonProperty("projectPaths") + private List projectPaths; + + /** When true, omit the host's user-level instruction targets, leaving only repository targets. For multitenant deployments (mirrors `discover`'s `excludeHostInstructions`). */ + @JsonProperty("excludeHostInstructions") + private Boolean excludeHostInstructions; + + /** Explicit discovery-only instruction roots to enumerate, including configured missing directories. Relative paths resolve against the request's effective working directory when supplied. These are not filesystem grants or preferred creation targets. Null and omission are equivalent. */ + @JsonProperty("instructionDirectories") + private List instructionDirectories; + + /** + * Creates a request with its required inputs. + */ + public InstructionsGetDiscoveryPathsRequest() { + } + + /** + * Returns the {@code projectPaths} property. + * + * @return Optional list of project directory paths. When omitted or empty, only the user-level targets are returned. + */ + public List getProjectPaths() { + return projectPaths; + } + + /** + * Returns the {@code excludeHostInstructions} property. + * + * @return When true, omit the host's user-level instruction targets, leaving only repository targets. For multitenant deployments (mirrors `discover`'s `excludeHostInstructions`). + */ + public Boolean getExcludeHostInstructions() { + return excludeHostInstructions; + } + + /** + * Returns the {@code instructionDirectories} property. + * + * @return Explicit discovery-only instruction roots to enumerate, including configured missing directories. Relative paths resolve against the request's effective working directory when supplied. These are not filesystem grants or preferred creation targets. Null and omission are equivalent. + */ + public List getInstructionDirectories() { + return instructionDirectories; + } + + /** + * Sets the {@code projectPaths} property. + * + * @param value Optional list of project directory paths. When omitted or empty, only the user-level targets are returned. + * @return this request + */ + public InstructionsGetDiscoveryPathsRequest setProjectPaths(List value) { + this.projectPaths = value; + return this; + } + + /** + * Sets the {@code excludeHostInstructions} property. + * + * @param value When true, omit the host's user-level instruction targets, leaving only repository targets. For multitenant deployments (mirrors `discover`'s `excludeHostInstructions`). + * @return this request + */ + public InstructionsGetDiscoveryPathsRequest setExcludeHostInstructions(Boolean value) { + this.excludeHostInstructions = value; + return this; + } + + /** + * Sets the {@code instructionDirectories} property. + * + * @param value Explicit discovery-only instruction roots to enumerate, including configured missing directories. Relative paths resolve against the request's effective working directory when supplied. These are not filesystem grants or preferred creation targets. Null and omission are equivalent. + * @return this request + */ + public InstructionsGetDiscoveryPathsRequest setInstructionDirectories(List value) { + this.instructionDirectories = value; + return this; + } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/InstructionsInitPromptResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/InstructionsInitPromptResult.java new file mode 100644 index 0000000000..20eb197498 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/InstructionsInitPromptResult.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; + +/** + * The prompt the init command runs. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +record InstructionsInitPromptResult( + /** Prompt text authoring repository instructions. */ + @JsonProperty("prompt") String prompt +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/LocationPermissionsCleanupResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/LocationPermissionsCleanupResult.java new file mode 100644 index 0000000000..ea8b72d7aa --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/LocationPermissionsCleanupResult.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 java.util.List; +import javax.annotation.processing.Generated; + +/** + * Which stored location permissions were dropped because their directory is gone. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +record LocationPermissionsCleanupResult( + /** Location keys whose grants were removed. Empty when every stored location still exists. */ + @JsonProperty("removedLocationKeys") List removedLocationKeys +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ManagedPermissionEvaluation.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ManagedPermissionEvaluation.java new file mode 100644 index 0000000000..40f74da843 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ManagedPermissionEvaluation.java @@ -0,0 +1,24 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package 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 ManagedPermissionEvaluation( + /** Original input operation associated with this verdict. */ + @JsonProperty("operation") ManagedPermissionOperation operation, + /** Managed-policy decision only; never bypasses user permissions or other security controls. */ + @JsonProperty("verdict") ManagedPermissionVerdict verdict +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ManagedPermissionOperation.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ManagedPermissionOperation.java new file mode 100644 index 0000000000..ba60365d3c --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ManagedPermissionOperation.java @@ -0,0 +1,42 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import javax.annotation.processing.Generated; + +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record ManagedPermissionOperation( + /** Absolute HTTP(S) URL with a host. Invalid URLs reject the entire evaluation batch. */ + @JsonProperty("url") String url, + /** Operation to evaluate. Currently only url is supported. */ + @JsonProperty("kind") ManagedPermissionOperationKind kind +) { + + /** Operation to evaluate. Currently only url is supported. */ + public enum ManagedPermissionOperationKind { + /** The {@code url} variant. */ + URL("url"); + + private final String value; + ManagedPermissionOperationKind(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static ManagedPermissionOperationKind fromValue(String value) { + for (ManagedPermissionOperationKind v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown ManagedPermissionOperationKind value: " + value); + } + } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ManagedPermissionVerdict.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ManagedPermissionVerdict.java new file mode 100644 index 0000000000..1254c36718 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ManagedPermissionVerdict.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; + +/** + * Managed-policy verdict only: deny blocks the URL, ask requires approval, allow approves under managed policy, and unmanaged leaves the normal consumer permission flow in effect. No verdict bypasses other security controls. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public enum ManagedPermissionVerdict { + /** The {@code deny} variant. */ + DENY("deny"), + /** The {@code ask} variant. */ + ASK("ask"), + /** The {@code allow} variant. */ + ALLOW("allow"), + /** The {@code unmanaged} variant. */ + UNMANAGED("unmanaged"); + + private final String value; + ManagedPermissionVerdict(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static ManagedPermissionVerdict fromValue(String value) { + for (ManagedPermissionVerdict v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown ManagedPermissionVerdict value: " + value); + } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ManagedPluginRetryEntry.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ManagedPluginRetryEntry.java new file mode 100644 index 0000000000..9fb16e5a55 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ManagedPluginRetryEntry.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 javax.annotation.processing.Generated; + +/** + * Retry outcome for one plugin required by managed settings. + * + * @apiNote This type 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 ManagedPluginRetryEntry( + /** Plugin spec in "plugin@marketplace" form. */ + @JsonProperty("spec") String spec, + /** What the retry did for this plugin. */ + @JsonProperty("status") ManagedPluginRetryStatus status, + /** Why the plugin was not prepared. Present when `status` is `failed` or `deferred`. */ + @JsonProperty("error") String error +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ManagedPluginRetryStatus.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ManagedPluginRetryStatus.java new file mode 100644 index 0000000000..43a46d2625 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ManagedPluginRetryStatus.java @@ -0,0 +1,47 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) 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 javax.annotation.processing.Generated; + +/** + * What retrying did for one plugin required by managed settings. + * + * @apiNote This type is experimental and may change in a future version. + * + * @since 1.0.0 + */ +@CopilotExperimental +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public enum ManagedPluginRetryStatus { + /** The {@code installed} variant. */ + INSTALLED("installed"), + /** The {@code updated} variant. */ + UPDATED("updated"), + /** The {@code already_present} variant. */ + ALREADY_PRESENT("already_present"), + /** The {@code failed} variant. */ + FAILED("failed"), + /** The {@code deferred} variant. */ + DEFERRED("deferred"), + /** The {@code not_required} variant. */ + NOT_REQUIRED("not_required"); + + private final String value; + ManagedPluginRetryStatus(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static ManagedPluginRetryStatus fromValue(String value) { + for (ManagedPluginRetryStatus v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown ManagedPluginRetryStatus value: " + value); + } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ManagedSettingsApplyControlsParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ManagedSettingsApplyControlsParams.java new file mode 100644 index 0000000000..fa5802b6f7 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ManagedSettingsApplyControlsParams.java @@ -0,0 +1,33 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import javax.annotation.processing.Generated; + +/** + * A user settings document and the effective managed policy whose controlled entries should be stamped onto 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) +record ManagedSettingsApplyControlsParams( + /** The user settings document to stamp. Entries the managed layer does not name are returned exactly as supplied. */ + @JsonInclude(JsonInclude.Include.ALWAYS) + @JsonProperty("settings") Object settings, + /** The effective managed policy, as composed by `managedSettings.resolveEffectiveValue`. Omit it on an unmanaged host: the settings then come back unchanged. */ + @JsonProperty("managed") Object managed +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ManagedSettingsApplyControlsResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ManagedSettingsApplyControlsResult.java new file mode 100644 index 0000000000..c5a85c72bb --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ManagedSettingsApplyControlsResult.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; + +/** + * The user settings with the managed layer's controlled entries stamped on. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +record ManagedSettingsApplyControlsResult( + /** The resulting settings document. */ + @JsonProperty("settingsJson") Object settingsJson +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ManagedSettingsBypassDisableSource.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ManagedSettingsBypassDisableSource.java new file mode 100644 index 0000000000..f2f64e075f --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ManagedSettingsBypassDisableSource.java @@ -0,0 +1,37 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import javax.annotation.processing.Generated; + +/** + * Which layer disabled bypass-permissions mode. The layer matters because only a server-delivered disable is pinned to an account and can later be lifted by signing in elsewhere. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +enum ManagedSettingsBypassDisableSource { + /** The {@code user} variant. */ + USER("user"), + /** The {@code managed} variant. */ + MANAGED("managed"), + /** The {@code mdm} variant. */ + MDM("mdm"); + + private final String value; + ManagedSettingsBypassDisableSource(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static ManagedSettingsBypassDisableSource fromValue(String value) { + for (ManagedSettingsBypassDisableSource v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown ManagedSettingsBypassDisableSource value: " + value); + } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ManagedSettingsBypassPolicyResolution.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ManagedSettingsBypassPolicyResolution.java new file mode 100644 index 0000000000..3101cf80fd --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ManagedSettingsBypassPolicyResolution.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 the managed-policy resolution said about bypass-permissions mode. `unknown` is a third answer rather than a missing one, and the caller must send it explicitly when it has no verdict. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +enum ManagedSettingsBypassPolicyResolution { + /** The {@code allowed} variant. */ + ALLOWED("allowed"), + /** The {@code disabled} variant. */ + DISABLED("disabled"), + /** The {@code unknown} variant. */ + UNKNOWN("unknown"); + + private final String value; + ManagedSettingsBypassPolicyResolution(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static ManagedSettingsBypassPolicyResolution fromValue(String value) { + for (ManagedSettingsBypassPolicyResolution v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown ManagedSettingsBypassPolicyResolution value: " + value); + } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ManagedSettingsIngestParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ManagedSettingsIngestParams.java new file mode 100644 index 0000000000..78e71c35cf --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ManagedSettingsIngestParams.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 com.github.copilot.CopilotExperimental; +import javax.annotation.processing.Generated; + +/** + * Inputs describing the host and account an ingestion should resolve managed policy for. + * + * @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) +record ManagedSettingsIngestParams( + /** The policy-helper invocation reason. Defaults to sessionStart. */ + @JsonProperty("policyHelperReason") ManagedSettingsPolicyHelperReason policyHelperReason, + /** Whether to run ingestion. Omit or set false to return an empty result without discovering or fetching policy. */ + @JsonProperty("selfFetch") Boolean selfFetch, + /** The authenticated identity to resolve policy for. Omitted before sign-in, in which case no server layer is fetched. */ + @JsonProperty("authInfo") Object authInfo, + /** The session whose credential store and working directory the policy helper should use. Omit it when no session exists yet and the process-wide store applies. */ + @JsonProperty("sessionId") String sessionId, + /** A device layer supplied by the host instead of discovered from the machine. Pair it with `hasDeviceResponse` so an explicit absence is distinguishable from an omitted override. */ + @JsonProperty("deviceResponse") Object deviceResponse, + /** Whether `deviceResponse` is an override at all. True with a null `deviceResponse` asserts the device layer is empty; false discovers it from the machine. */ + @JsonProperty("hasDeviceResponse") Boolean hasDeviceResponse, + /** Whether to ignore a cached server policy when reading, forcing the resolution to come from a live fetch. */ + @JsonProperty("bypassCacheRead") Boolean bypassCacheRead, + /** How this ingestion may use the on-disk server-policy cache. Defaults to the runtime's own policy when omitted. */ + @JsonProperty("serverPolicyCacheMode") ManagedSettingsServerPolicyCacheMode serverPolicyCacheMode, + /** Whether a configured platform policy helper may be run to produce an enforced helper layer. */ + @JsonProperty("policyHelperEnabled") Boolean policyHelperEnabled, + /** Directory the policy helper runs in when this ingestion is not owned by a registered session. A host resolving policy before its session exists must pass this, or a directory-sensitive helper judges the wrong repository. */ + @JsonProperty("policyHelperWorkingDirectory") String policyHelperWorkingDirectory, + /** User agent to send with the policy fetch. */ + @JsonProperty("userAgent") String userAgent, + /** Name of the embedding client, reported to the managed-settings service as `client_id` so it can refuse to deliver a policy this client is too old to enforce. Omit it and the runtime reports a generic identity; the runtime version sent alongside is the gate that actually matters. */ + @JsonProperty("clientName") String clientName, + /** The host's environment variables, read for cache location and policy overrides. Supplied by the host rather than read from the runtime process so a command that selected its own configuration home is honored. */ + @JsonInclude(JsonInclude.Include.ALWAYS) + @JsonProperty("environment") Object environment, + /** The host's platform, in Node's `process.platform` spelling, used to locate platform-specific cache and policy paths. */ + @JsonProperty("platform") String platform, + /** The host's home directory, used to resolve cache and managed-file locations. */ + @JsonProperty("homeDirectory") String homeDirectory +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ManagedSettingsIngestResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ManagedSettingsIngestResult.java new file mode 100644 index 0000000000..d5372cf860 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ManagedSettingsIngestResult.java @@ -0,0 +1,78 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package 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 managed policy layers an ingestion resolved, together with the per-layer outcome flags a host needs to decide how much to trust the result. The flags are reported rather than folded into the settings because a host must be able to distinguish an unmanaged device from a managed device whose policy could not be confirmed, and apply its fail-closed gates in the second case. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +record ManagedSettingsIngestResult( + /** The effective managed settings composed from every resolved layer. Omitted when no layer set anything, so an unmanaged host locks nothing. */ + @JsonProperty("settings") Object settings, + /** The effective managed permissions layer, when the resolved policy carries one. */ + @JsonProperty("permissions") Object permissions, + /** The server fetch failed and no cached entry stood in for it, so the server layer is unconfirmed and fail-closed gates apply. */ + @JsonProperty("serverFetchFailed") Boolean serverFetchFailed, + /** No usable token was available, so no server fetch was attempted. */ + @JsonProperty("serverTokenUnavailable") Boolean serverTokenUnavailable, + /** The error that made the server fetch fail, when one did. */ + @JsonProperty("serverFetchError") String serverFetchError, + /** How the server layer was resolved. */ + @JsonProperty("serverResolution") ManagedSettingsServerPolicyResolution serverResolution, + /** The server fetch failed in a way a later retry could succeed at, as opposed to a refusal that no retry will change. */ + @JsonProperty("serverRetryableFailure") Boolean serverRetryableFailure, + /** A live fetch failed but a recent cached entry was served in its place, so `serverFetchFailed` stays false. Lets a consumer distinguish a clean fetch from a cache-backed fallback. */ + @JsonProperty("serverCacheFallback") Boolean serverCacheFallback, + /** A server-managed policy is known to exist even though `serverResponse` is intentionally unset. Consumers should OR this with the presence of `serverResponse` when deciding whether the device is managed, so a fresh start during an outage still applies the fail-closed gates instead of treating the device as unmanaged. */ + @JsonProperty("serverSourceFound") Boolean serverSourceFound, + /** The account's server-delivered policy, when one was resolved. */ + @JsonProperty("serverResponse") Object serverResponse, + /** The sandbox object from a cached server policy that a forced refresh declined to serve in full. Folded back into the effective sandbox layer so an outage during a forced refresh still synthesizes the fail-closed sandbox floor from the administrator's known intent. */ + @JsonProperty("serverSandboxFloor") Object serverSandboxFloor, + /** The server fetch failed while a policy demanded a fresh confirmation, which is the strict posture an enterprise opted into rather than an ordinary transient failure. */ + @JsonProperty("serverForcedRefreshFailure") Boolean serverForcedRefreshFailure, + /** The server refused to deliver a policy because this client is too old to enforce it, carrying the user-facing sentence describing the refusal. No retry will help; the remedy is a client update. Gates that exist only because of managed policy should deny on this rather than falling back to their unmanaged default. */ + @JsonProperty("serverClientUpdateRequired") String serverClientUpdateRequired, + /** Minimum client version requested by a successful response. Advisory only; the delivered policy remains active. */ + @JsonProperty("serverClientUpdateRequested") String serverClientUpdateRequested, + /** The synthesized fail-closed permissions layer applied in place of a policy a refused client may not enforce. */ + @JsonProperty("serverPermissionsFloor") Object serverPermissionsFloor, + /** The device layer discovered from the machine, when one exists. */ + @JsonProperty("deviceResponse") Object deviceResponse, + /** The device layer could not be read, so it is unconfirmed rather than known-absent. */ + @JsonProperty("deviceLoadFailed") Boolean deviceLoadFailed, + /** The sandbox floor contributed by the device layer. */ + @JsonProperty("deviceSandboxFloor") Object deviceSandboxFloor, + /** A device-managed policy is known to exist. */ + @JsonProperty("deviceSourceFound") Boolean deviceSourceFound, + /** Whether the device layer's sandbox intent could not be determined, so a consumer must not read its absence as permission to run unsandboxed. */ + @JsonProperty("deviceSandboxUndetermined") Boolean deviceSandboxUndetermined, + /** The layer produced by the platform policy helper, when one ran. */ + @JsonProperty("policyHelperResponse") Object policyHelperResponse, + /** Which channel registered the policy helper that produced the helper layer. */ + @JsonProperty("policyHelperAuthority") ManagedSettingsPolicyHelperAuthority policyHelperAuthority, + /** The policy helper was configured but could not be run or its output could not be read. */ + @JsonProperty("policyHelperLoadFailed") Boolean policyHelperLoadFailed, + /** A policy helper is configured for this host. */ + @JsonProperty("policyHelperConfigured") Boolean policyHelperConfigured, + /** The policy helper's failure must be treated as fail-closed rather than ignored. */ + @JsonProperty("policyHelperFailClosed") Boolean policyHelperFailClosed, + /** Diagnostics emitted while resolving the layers, in the order they occurred. */ + @JsonProperty("logMessages") List logMessages +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ManagedSettingsLoadRemoteControlParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ManagedSettingsLoadRemoteControlParams.java new file mode 100644 index 0000000000..ff434c10ae --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ManagedSettingsLoadRemoteControlParams.java @@ -0,0 +1,51 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import javax.annotation.processing.Generated; + +/** + * Inputs describing the host and account whose managed remote-control policy should be resolved. Mirrors `managedSettings.ingest`, and deliberately carries no retention decision: reading this gate never becomes this runtime process's startup policy. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ +@CopilotExperimental +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +record ManagedSettingsLoadRemoteControlParams( + /** The policy-helper invocation reason. Defaults to sessionStart. */ + @JsonProperty("policyHelperReason") ManagedSettingsPolicyHelperReason policyHelperReason, + /** Whether to run ingestion. Omit or set false to return an empty result without discovering or fetching policy. */ + @JsonProperty("selfFetch") Boolean selfFetch, + /** The authenticated identity to resolve policy for. Omitted before sign-in, in which case no server layer is fetched. */ + @JsonProperty("authInfo") Object authInfo, + /** A device layer supplied by the host instead of discovered from the machine. Pair it with `hasDeviceResponse` so an explicit absence is distinguishable from an omitted override. */ + @JsonProperty("deviceResponse") Object deviceResponse, + /** Whether `deviceResponse` is an override at all. True with a null `deviceResponse` asserts the device layer is empty; false discovers it from the machine. */ + @JsonProperty("hasDeviceResponse") Boolean hasDeviceResponse, + /** Whether a configured platform policy helper may be run to produce an enforced helper layer. */ + @JsonProperty("policyHelperEnabled") Boolean policyHelperEnabled, + /** User agent to send with the policy fetch. */ + @JsonProperty("userAgent") String userAgent, + /** Name of the embedding client, reported to the managed-settings service as `client_id` so it can refuse to deliver a policy this client is too old to enforce. That refusal is exactly what turns into an explicit `disabled` below, so a host that omits this field forfeits the protection. */ + @JsonProperty("clientName") String clientName, + /** The host's environment variables, read for cache location and policy overrides. */ + @JsonInclude(JsonInclude.Include.ALWAYS) + @JsonProperty("environment") Object environment, + /** The host's platform, in Node's `process.platform` spelling, used to locate platform-specific cache and policy paths. */ + @JsonProperty("platform") String platform, + /** The host's home directory, used to resolve cache and managed-file locations. */ + @JsonProperty("homeDirectory") String homeDirectory +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ManagedSettingsLoadRemoteControlResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ManagedSettingsLoadRemoteControlResult.java new file mode 100644 index 0000000000..2acf5180c5 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ManagedSettingsLoadRemoteControlResult.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; + +/** + * The managed remote-control policy in force for an account, if any. An absent policy means the account is unmanaged for this capability, which a host reads as allowed — so absence here is a positive statement that no policy applies, never a stand-in for a resolution that failed. A resolution that cannot be completed is rejected instead. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +record ManagedSettingsLoadRemoteControlResult( + /** The account's `remoteControl` policy document. Absent when no managed policy governs remote control for this account. Reported as an explicit disabled policy, rather than as absence, when the service refused to deliver a policy this client is too old to enforce. */ + @JsonProperty("remoteControl") Object remoteControl +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ManagedSettingsLogLevel.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ManagedSettingsLogLevel.java new file mode 100644 index 0000000000..1775a5db19 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ManagedSettingsLogLevel.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; + +/** + * Severity of a diagnostic emitted while ingesting managed settings. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +enum ManagedSettingsLogLevel { + /** The {@code debug} variant. */ + DEBUG("debug"), + /** The {@code info} variant. */ + INFO("info"), + /** The {@code warning} variant. */ + WARNING("warning"), + /** The {@code error} variant. */ + ERROR("error"); + + private final String value; + ManagedSettingsLogLevel(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static ManagedSettingsLogLevel fromValue(String value) { + for (ManagedSettingsLogLevel v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown ManagedSettingsLogLevel value: " + value); + } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ManagedSettingsLogMessage.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ManagedSettingsLogMessage.java new file mode 100644 index 0000000000..be859afde3 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ManagedSettingsLogMessage.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; + +/** + * One diagnostic emitted while ingesting managed settings. Returned rather than logged by the runtime so the host decides where ingestion diagnostics go, which matters because an ingestion runs before a session exists to carry a log sink. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +record ManagedSettingsLogMessage( + /** Severity of this diagnostic. */ + @JsonProperty("level") ManagedSettingsLogLevel level, + /** Human-readable diagnostic text. */ + @JsonProperty("message") String message +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ManagedSettingsParseClientValueParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ManagedSettingsParseClientValueParams.java new file mode 100644 index 0000000000..27effeabae --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ManagedSettingsParseClientValueParams.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 client-supplied managed settings to validate and normalize. Omitting them is valid and yields no policy. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ +@CopilotExperimental +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +record ManagedSettingsParseClientValueParams( + /** The managed settings a client is supplying for a session it is about to open. Omit them when the client is imposing no policy of its own. */ + @JsonProperty("settings") Object settings +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ManagedSettingsParseClientValueResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ManagedSettingsParseClientValueResult.java new file mode 100644 index 0000000000..695248ea4a --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ManagedSettingsParseClientValueResult.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; + +/** + * The accepted managed settings, reduced to the keys a client is allowed to set. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +record ManagedSettingsParseClientValueResult( + /** The normalized settings. Omitted when the client supplied none. An accepted document that sets nothing comes back as an empty object, which is distinct from omission. */ + @JsonProperty("settingsJson") Object settingsJson +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ManagedSettingsPermissionsEvaluateParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ManagedSettingsPermissionsEvaluateParams.java new file mode 100644 index 0000000000..0a5d0c53ab --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ManagedSettingsPermissionsEvaluateParams.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 com.github.copilot.generated.ManagedPermissionsContext; +import java.util.List; +import javax.annotation.processing.Generated; + +/** + * Managed policy context and ordered operations for pure, sessionless evaluation. + * + * @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 ManagedSettingsPermissionsEvaluateParams( + /** Trusted, reusable managed permission policy snapshot; no policy is discovered during evaluation. */ + @JsonProperty("context") ManagedPermissionsContext context, + /** Operations to evaluate, preserving input order and duplicates. An empty batch is valid. */ + @JsonProperty("operations") List operations +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ManagedSettingsPermissionsEvaluateResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ManagedSettingsPermissionsEvaluateResult.java new file mode 100644 index 0000000000..099a216b48 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ManagedSettingsPermissionsEvaluateResult.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; + +/** + * Ordered managed permission verdicts and the supplied policy's fail-closed posture. + * + * @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 ManagedSettingsPermissionsEvaluateResult( + /** One verdict per input operation, in the same order, including duplicates. */ + @JsonProperty("results") List results, + /** Managed permission policy could not be determined; every operation is denied. */ + @JsonProperty("failClosed") Boolean failClosed +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ManagedSettingsPolicyHelperAuthority.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ManagedSettingsPolicyHelperAuthority.java new file mode 100644 index 0000000000..34b33ad56f --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ManagedSettingsPolicyHelperAuthority.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 channel registered the policy helper that produced a helper layer. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +enum ManagedSettingsPolicyHelperAuthority { + /** The {@code device} variant. */ + DEVICE("device"), + /** The {@code server} variant. */ + SERVER("server"); + + private final String value; + ManagedSettingsPolicyHelperAuthority(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static ManagedSettingsPolicyHelperAuthority fromValue(String value) { + for (ManagedSettingsPolicyHelperAuthority v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown ManagedSettingsPolicyHelperAuthority value: " + value); + } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ManagedSettingsPolicyHelperReason.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ManagedSettingsPolicyHelperReason.java new file mode 100644 index 0000000000..509e98f5e7 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ManagedSettingsPolicyHelperReason.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; + +/** + * The operation that requires a policy-helper response. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +enum ManagedSettingsPolicyHelperReason { + /** The {@code sessionStart} variant. */ + SESSIONSTART("sessionStart"), + /** The {@code pluginActivation} variant. */ + PLUGINACTIVATION("pluginActivation"); + + private final String value; + ManagedSettingsPolicyHelperReason(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static ManagedSettingsPolicyHelperReason fromValue(String value) { + for (ManagedSettingsPolicyHelperReason v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown ManagedSettingsPolicyHelperReason value: " + value); + } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ManagedSettingsReadDeviceResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ManagedSettingsReadDeviceResult.java new file mode 100644 index 0000000000..880044dc83 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ManagedSettingsReadDeviceResult.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; + +/** + * The device-managed settings layer, with the sandbox floor and the load outcome a host needs to compose it into an effective policy. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +record ManagedSettingsReadDeviceResult( + /** Validated, canonical device-managed settings. Omitted when no device policy was discovered, when the discovered document failed validation, or when it validated to an empty object — an empty document carries no policy, and reporting it as present would outrank layers that do. */ + @JsonProperty("settings") Object settings, + /** Sandbox floor read from the discovered document before validation, so a document that is otherwise rejected can still raise the floor it declared. */ + @JsonProperty("sandboxFloor") Object sandboxFloor, + /** Whether discovery or validation failed. A host must treat this as "policy unknown" rather than "no policy": absent settings with this set false mean the device genuinely has none. */ + @JsonProperty("loadFailed") Boolean loadFailed +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ManagedSettingsResolveBypassDisableTransitionParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ManagedSettingsResolveBypassDisableTransitionParams.java new file mode 100644 index 0000000000..3b56f24c92 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ManagedSettingsResolveBypassDisableTransitionParams.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; + +/** + * The bypass-permissions state a host holds today, the account now signed in, and what the managed-policy resolution said. + * + * @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) +record ManagedSettingsResolveBypassDisableTransitionParams( + /** Which layer currently disables bypass mode. Omit it when nothing does. */ + @JsonProperty("currentSource") ManagedSettingsBypassDisableSource currentSource, + /** The account a `managed` disable is pinned to. Omit it when the current source is not `managed`; it is ignored for the other sources. */ + @JsonProperty("currentManagedIdentity") String currentManagedIdentity, + /** The account signed in now. A `managed` disable pinned to a different account is lifted. */ + @JsonProperty("activeIdentity") String activeIdentity, + /** What the resolution said about bypass mode. Send `unknown` rather than omitting the field when there is no verdict. */ + @JsonProperty("policy") ManagedSettingsBypassPolicyResolution policy +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ManagedSettingsResolveBypassDisableTransitionResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ManagedSettingsResolveBypassDisableTransitionResult.java new file mode 100644 index 0000000000..2557cd5cb3 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ManagedSettingsResolveBypassDisableTransitionResult.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; + +/** + * The bypass-permissions state a host should hold after the resolution, and what it must do to get there. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +record ManagedSettingsResolveBypassDisableTransitionResult( + /** Which layer disables bypass mode after this resolution. Omitted when none does, which is what a lifted disable looks like. */ + @JsonProperty("source") ManagedSettingsBypassDisableSource source, + /** The account the returned `managed` source is pinned to. Store it alongside `source`; the next call needs it to decide whether an account switch lifts the disable. */ + @JsonProperty("managedIdentity") String managedIdentity, + /** The value to store in the host's own bypass-disabled flag. Omitted means leave it exactly as it is, which is not the same as being told to store `false`. */ + @JsonProperty("setDisabledState") Boolean setDisabledState, + /** Whether a bypass-permissions session already running must be turned off. True only when this resolution newly disables bypass mode, so a host that is already compliant is not interrupted. */ + @JsonProperty("suppressRuntimeAllowAll") Boolean suppressRuntimeAllowAll +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ManagedSettingsResolveDeviceForceLoginOrgsGateParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ManagedSettingsResolveDeviceForceLoginOrgsGateParams.java new file mode 100644 index 0000000000..75b89cb8bd --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ManagedSettingsResolveDeviceForceLoginOrgsGateParams.java @@ -0,0 +1,33 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import javax.annotation.processing.Generated; + +/** + * The credential and environment to evaluate the device-channel sign-in pin against. + * + * @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) +record ManagedSettingsResolveDeviceForceLoginOrgsGateParams( + /** The authenticated identity to judge. Omitted when signed out, which is the case the device channel exists to serve: it can refuse a first sign-in before any credential exists. */ + @JsonProperty("authInfo") Object authInfo, + /** The host's environment variables. Read for the variables that would make an identity's membership snapshot forgeable, so a credential whose provenance cannot be trusted is refused rather than accepted. */ + @JsonInclude(JsonInclude.Include.ALWAYS) + @JsonProperty("environment") Object environment +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ManagedSettingsResolveEffectiveValueParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ManagedSettingsResolveEffectiveValueParams.java new file mode 100644 index 0000000000..7e82bf0b3c --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ManagedSettingsResolveEffectiveValueParams.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; + +/** + * The managed-settings layers to compose into an effective policy. Every layer is optional; omitting all of them yields no effective policy. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ +@CopilotExperimental +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +record ManagedSettingsResolveEffectiveValueParams( + /** The account's server-delivered org policy, as returned by a managed-settings fetch. Omitted before sign-in or when the fetch has not landed. */ + @JsonProperty("server") Object server, + /** The device layer: native MDM and managed-file settings describing the machine rather than the account. */ + @JsonProperty("mdm") Object mdm, + /** The device sandbox floor, folded into the device layer under `sandbox` before composition. */ + @JsonProperty("deviceSandboxFloor") Object deviceSandboxFloor, + /** The policy-helper layer, an enforced channel sourced from a platform policy helper process. */ + @JsonProperty("policyHelper") Object policyHelper +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ManagedSettingsResolveEffectiveValueResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ManagedSettingsResolveEffectiveValueResult.java new file mode 100644 index 0000000000..441bb9039f --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ManagedSettingsResolveEffectiveValueResult.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; + +/** + * The effective managed policy composed from the supplied layers. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +record ManagedSettingsResolveEffectiveValueResult( + /** The composed effective managed settings. Omitted when no layer sets anything, so an unmanaged host locks nothing. Carries `sandboxAdvisoryFloorDeclined: true` when an advisory server force-on was dropped because this host cannot run the sandbox. */ + @JsonProperty("settingsJson") Object settingsJson +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ManagedSettingsResolveResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ManagedSettingsResolveResult.java index 6d9941f4a9..d13b9a555b 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ManagedSettingsResolveResult.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ManagedSettingsResolveResult.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.ManagedPermissionsContext; import java.util.List; import javax.annotation.processing.Generated; @@ -25,6 +26,8 @@ @JsonInclude(JsonInclude.Include.NON_NULL) @JsonIgnoreProperties(ignoreUnknown = true) public record ManagedSettingsResolveResult( + /** Reusable source-composed permission policy for managedSettings.permissions.evaluate. Unlike resolved.settings.permissions, this retains every source's allowlist and default-prompt semantics. Refresh through resolve when account/device policy changes. Absent on runtimes that do not support permission evaluation. */ + @JsonProperty("permissionsContext") ManagedPermissionsContext permissionsContext, /** Printable opaque identity of the account the settings were resolved for, suitable for comparison and storage, not an account selectionId. Absent when no account was available, in which case only device policy is reported. */ @JsonProperty("account") String account, /** Effective managed settings from the device and account (server) channels, in the same shape as `session.managedSettings.get`, excluding session-local injection. */ diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ManagedSettingsResolveStartupPolicyParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ManagedSettingsResolveStartupPolicyParams.java new file mode 100644 index 0000000000..e0074bb8bc --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ManagedSettingsResolveStartupPolicyParams.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 com.github.copilot.CopilotExperimental; +import javax.annotation.processing.Generated; + +/** + * Inputs describing the host and account a startup policy resolution should run for. Mirrors `managedSettings.ingest`, and adds the decision of whether the resolved policy is retained 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) +record ManagedSettingsResolveStartupPolicyParams( + /** The policy-helper invocation reason. Defaults to sessionStart. */ + @JsonProperty("policyHelperReason") ManagedSettingsPolicyHelperReason policyHelperReason, + /** Whether to run ingestion. Omit or set false to return an empty result without discovering or fetching policy. */ + @JsonProperty("selfFetch") Boolean selfFetch, + /** The authenticated identity to resolve policy for. Omitted before sign-in, in which case no server layer is fetched and the sign-in pin is evaluated against no credential. */ + @JsonProperty("authInfo") Object authInfo, + /** The session whose credential store, working directory and client kind the resolution should use. Omit it when no session exists yet and the process-wide store applies. A resolution whose session names a credential other than `authInfo` is returned but not retained, so a stale sign-in cannot apply policy for an account that has already been switched away from. */ + @JsonProperty("sessionId") String sessionId, + /** A device layer supplied by the host instead of discovered from the machine. Pair it with `hasDeviceResponse` so an explicit absence is distinguishable from an omitted override. */ + @JsonProperty("deviceResponse") Object deviceResponse, + /** Whether `deviceResponse` is an override at all. True with a null `deviceResponse` asserts the device layer is empty; false discovers it from the machine. */ + @JsonProperty("hasDeviceResponse") Boolean hasDeviceResponse, + /** Whether to ignore a cached server policy when reading, forcing the resolution to come from a live fetch. */ + @JsonProperty("bypassCacheRead") Boolean bypassCacheRead, + /** How this resolution may use the on-disk server-policy cache. Defaults to the runtime's own policy when omitted. Rejected when combined with `bypassCacheRead`, because the two express the same decision and accepting both would leave the caller unsure which one the runtime honored. */ + @JsonProperty("serverPolicyCacheMode") ManagedSettingsServerPolicyCacheMode serverPolicyCacheMode, + /** Whether a configured platform policy helper may be run to produce an enforced helper layer. */ + @JsonProperty("policyHelperEnabled") Boolean policyHelperEnabled, + /** Directory the policy helper runs in when this resolution is not owned by a registered session. A host resolving policy before its session exists must pass this, or a directory-sensitive helper judges the wrong repository. */ + @JsonProperty("policyHelperWorkingDirectory") String policyHelperWorkingDirectory, + /** User agent to send with the policy fetch. */ + @JsonProperty("userAgent") String userAgent, + /** Name of the embedding client, reported to the managed-settings service as `client_id` so it can refuse to deliver a policy this client is too old to enforce. */ + @JsonProperty("clientName") String clientName, + /** The host's environment variables, read for cache location, policy overrides, and the variables that would make a membership snapshot forgeable. */ + @JsonInclude(JsonInclude.Include.ALWAYS) + @JsonProperty("environment") Object environment, + /** The host's platform, in Node's `process.platform` spelling, used to locate platform-specific cache and policy paths. */ + @JsonProperty("platform") String platform, + /** The host's home directory, used to resolve cache and managed-file locations. */ + @JsonProperty("homeDirectory") String homeDirectory, + /** Whether to retain the resolved policy as this runtime process's startup policy, so later work in the process enforces it. Set false to resolve without side effects, which is what a protocol host wants before session creation: its sessions apply the complete policy through the normal lifecycle, and retaining here would let a readiness credential affect unrelated sessions. The returned verdict is identical either way — this decides only whether the process keeps it. */ + @JsonProperty("retainAndApply") Boolean retainAndApply +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ManagedSettingsResolveStartupPolicyResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ManagedSettingsResolveStartupPolicyResult.java new file mode 100644 index 0000000000..69def35f89 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ManagedSettingsResolveStartupPolicyResult.java @@ -0,0 +1,62 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.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 startup policy a resolution composed, the per-layer outcome flags a host needs to decide how much to trust it, and the enterprise sign-in verdict for the credential it ran against. The flags are reported rather than folded into the settings because a host must distinguish an unmanaged device from a managed device whose policy could not be confirmed, and apply its fail-closed gates in the second case. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +record ManagedSettingsResolveStartupPolicyResult( + /** How the current runtime resolved the server layer. */ + @JsonProperty("serverResolution") ManagedSettingsServerPolicyResolution serverResolution, + /** Advisory minimum client version from a successful server response. */ + @JsonProperty("clientUpdateRequested") String clientUpdateRequested, + /** The account's server-delivered policy, when one was resolved. */ + @JsonProperty("serverResponse") Object serverResponse, + /** The device layer discovered from the machine, when one exists. */ + @JsonProperty("deviceResponse") Object deviceResponse, + /** The sandbox floor contributed by the device layer. */ + @JsonProperty("deviceSandboxFloor") Object deviceSandboxFloor, + /** The layer produced by the platform policy helper, when one ran. */ + @JsonProperty("policyHelperResponse") Object policyHelperResponse, + /** The server fetch failed and no cached entry stood in for it, so the server layer is unconfirmed and fail-closed gates apply. */ + @JsonProperty("serverFetchFailed") Boolean serverFetchFailed, + /** The device layer could not be read, so it is unconfirmed rather than known-absent. */ + @JsonProperty("deviceLoadFailed") Boolean deviceLoadFailed, + /** The policy helper was configured but could not be run, or its output could not be read. */ + @JsonProperty("policyHelperLoadFailed") Boolean policyHelperLoadFailed, + /** Telemetry describing which layers answered and how, for the host to report alongside its own startup events. */ + @JsonProperty("telemetry") Object telemetry, + /** The effective sandbox floor composed across every layer, which a host must apply rather than its own default. */ + @JsonProperty("sandboxFloor") Object sandboxFloor, + /** Some layer of the policy could not be confirmed, so its absence must not be read as permission. Folds in a failed server fetch as well as an unreadable device layer. */ + @JsonProperty("policyUndetermined") Boolean policyUndetermined, + /** The permissions layer specifically could not be confirmed, so permission gates apply their fail-closed posture. */ + @JsonProperty("permissionPolicyUndetermined") Boolean permissionPolicyUndetermined, + /** A managed policy is known to apply to this host, from a discovered source or a resolved document. */ + @JsonProperty("managedPolicyPresent") Boolean managedPolicyPresent, + /** The sandbox was enabled because policy could not be confirmed rather than because an administrator asked for it. Lets a host explain a sandbox the user did not configure. */ + @JsonProperty("sandboxEnabledByUndeterminedPolicy") Boolean sandboxEnabledByUndeterminedPolicy, + /** The enterprise sign-in verdict for the credential this resolution ran against, evaluated over the effective policy so a server-delivered pin is honored alongside a device one. Computed on every resolution, including one that was not retained, so a host that declines to apply policy still cannot proceed past a pin. */ + @JsonProperty("forceLoginOrgs") ForceLoginOrgsDecision forceLoginOrgs, + /** The server refused to deliver a policy because this client is too old to enforce it, carrying the user-facing sentence describing the refusal. No retry will help; the remedy is a client update. */ + @JsonProperty("clientUpdateRequired") String clientUpdateRequired, + /** Warnings raised while resolving, in the order they occurred. */ + @JsonProperty("warnings") List warnings +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ManagedSettingsResolvedData.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ManagedSettingsResolvedData.java index 2020e9d434..aaef086d58 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ManagedSettingsResolvedData.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ManagedSettingsResolvedData.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.ManagedPermissionsContext; import java.util.List; import javax.annotation.processing.Generated; @@ -26,6 +27,8 @@ @JsonInclude(JsonInclude.Include.NON_NULL) @JsonIgnoreProperties(ignoreUnknown = true) public record ManagedSettingsResolvedData( + /** Reusable retained managed permission policy for managedSettings.permissions.evaluate, including source composition and session-local injection. Provided by live session snapshots; absent in older events. Refresh the context when managed policy changes. */ + @JsonProperty("permissionsContext") ManagedPermissionsContext permissionsContext, /** 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 */ @@ -36,11 +39,11 @@ public record ManagedSettingsResolvedData( @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. */ + /** Whether managed policy could not be determined (e.g. a failed server fetch) and unresolved-policy safeguards remain active. This does not by itself disable bypass-permissions; `bypassPermissionsDisabled` reports only an explicit policy restriction. */ @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. */ + /** Whether an explicit enterprise policy restriction disables bypass-permissions ("yolo") mode for this session. Deny-wins across layers; an unresolved policy does not force this on by itself. */ @JsonProperty("bypassPermissionsDisabled") Boolean bypassPermissionsDisabled, /** Whether at least two managed sources supplied permission allowlists, so enforcement intersects them and the flattened settings payload omits `permissions.allow`. */ @JsonProperty("permissionsAllowIntersected") Boolean permissionsAllowIntersected, diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ManagedSettingsRetainPolicyParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ManagedSettingsRetainPolicyParams.java new file mode 100644 index 0000000000..5ea352e817 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ManagedSettingsRetainPolicyParams.java @@ -0,0 +1,60 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package 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 managed layers to retain as this process's startup policy, with the outcome of fetching each one. + * + * @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) +record ManagedSettingsRetainPolicyParams( + /** The resolved server policy. A known source can lack a response after a forced-refresh failure. */ + @JsonProperty("serverResponse") Object serverResponse, + /** The device/MDM policy layer. */ + @JsonProperty("deviceResponse") Object deviceResponse, + /** Sandbox floor declared by the device layer, carried separately so a document that failed validation can still raise the floor. */ + @JsonProperty("deviceSandboxFloor") Object deviceSandboxFloor, + /** Whether a device policy source demonstrably exists. This, not a read error, is the evidence a device is managed, so a fail-closed policy gates on it. */ + @JsonProperty("deviceSourceFound") Boolean deviceSourceFound, + /** Whether any managed policy source exists, independently of fetch failures. */ + @JsonProperty("managedPolicyPresent") Boolean managedPolicyPresent, + /** Whether the server policy source exists, even when its response is unavailable. */ + @JsonProperty("serverSourceFound") Boolean serverSourceFound, + /** Whether a required fresh server-policy confirmation failed. */ + @JsonProperty("serverForcedRefreshFailure") Boolean serverForcedRefreshFailure, + /** Preserve the retained server layer during a device-only resolution. */ + @JsonProperty("preserveServerState") Boolean preserveServerState, + /** Whether the device sandbox intent is unreadable independently of its other settings. */ + @JsonProperty("deviceSandboxUndetermined") Boolean deviceSandboxUndetermined, + /** Whether the shared sandbox policy is undetermined. Diagnostic errors alone do not imply this flag. */ + @JsonProperty("sandboxFloorUndetermined") Boolean sandboxFloorUndetermined, + /** Whether permission policy is undetermined. Sandbox-only uncertainty does not imply this flag. */ + @JsonProperty("permissionPolicyUndetermined") Boolean permissionPolicyUndetermined, + /** Whether uncertainty alone enabled the shared sandbox floor. */ + @JsonProperty("sandboxEnabledByUndeterminedPolicy") Boolean sandboxEnabledByUndeterminedPolicy, + /** Shared server/device sandbox floor, excluding session-local helper policy. */ + @JsonProperty("sharedSandboxFloor") Object sharedSandboxFloor, + /** Whether the server fetch failed. Report it honestly: true keeps the already-retained server layer rather than replacing it with an absent one. */ + @JsonProperty("serverFetchFailed") Boolean serverFetchFailed, + /** Whether the device load failed. Report it honestly, for the same reason as `serverFetchFailed`. */ + @JsonProperty("deviceLoadFailed") Boolean deviceLoadFailed, + /** The policy helper's response: the third managed layer that a process owning its startup policy resolves. It is retained with the other two so that every ambient read in this process, including the mutation guards and the governed marketplace allowlist that the `plugins.*` handlers derive, sees the whole policy the process fetched. A session-owning process omits it, because it resolves a helper per session against that session's working directory. */ + @JsonProperty("policyHelperResponse") Object policyHelperResponse +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ManagedSettingsServerPolicyCacheMode.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ManagedSettingsServerPolicyCacheMode.java new file mode 100644 index 0000000000..7258e376b5 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ManagedSettingsServerPolicyCacheMode.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; + +/** + * How an ingestion may use the on-disk server-policy cache. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +enum ManagedSettingsServerPolicyCacheMode { + /** The {@code preferFresh} variant. */ + PREFERFRESH("preferFresh"), + /** The {@code cacheOnly} variant. */ + CACHEONLY("cacheOnly"), + /** The {@code fallbackOnly} variant. */ + FALLBACKONLY("fallbackOnly"), + /** The {@code disabled} variant. */ + DISABLED("disabled"); + + private final String value; + ManagedSettingsServerPolicyCacheMode(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static ManagedSettingsServerPolicyCacheMode fromValue(String value) { + for (ManagedSettingsServerPolicyCacheMode v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown ManagedSettingsServerPolicyCacheMode value: " + value); + } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ManagedSettingsServerPolicyResolution.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ManagedSettingsServerPolicyResolution.java new file mode 100644 index 0000000000..8315739063 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ManagedSettingsServerPolicyResolution.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; + +/** + * How the server layer of an ingestion was resolved. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +enum ManagedSettingsServerPolicyResolution { + /** The {@code live} variant. */ + LIVE("live"), + /** The {@code freshCache} variant. */ + FRESHCACHE("freshCache"), + /** The {@code cacheMiss} variant. */ + CACHEMISS("cacheMiss"), + /** The {@code fallbackCache} variant. */ + FALLBACKCACHE("fallbackCache"), + /** The {@code failOpen} variant. */ + FAILOPEN("failOpen"), + /** The {@code failClosed} variant. */ + FAILCLOSED("failClosed"), + /** The {@code skippedNoToken} variant. */ + SKIPPEDNOTOKEN("skippedNoToken"); + + private final String value; + ManagedSettingsServerPolicyResolution(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static ManagedSettingsServerPolicyResolution fromValue(String value) { + for (ManagedSettingsServerPolicyResolution v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown ManagedSettingsServerPolicyResolution value: " + value); + } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ManagedSettingsValidateParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ManagedSettingsValidateParams.java index 32879f689c..40a4e02992 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ManagedSettingsValidateParams.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ManagedSettingsValidateParams.java @@ -25,6 +25,7 @@ @JsonIgnoreProperties(ignoreUnknown = true) public record ManagedSettingsValidateParams( /** The document to validate: a JSON object, or a string containing the document's JSON text. Preview documents are limited to 1 MiB and 64 levels of nesting, a stricter resource limit than delivered-policy parsing; violations are returned as diagnostics. */ + @JsonInclude(JsonInclude.Include.ALWAYS) @JsonProperty("content") Object content, /** Channel the document is meant for (`device`, `server`, or `policyHelper`). Some keys are only honored in some channels; for example, a `policyHelper` registration is ignored in policy-helper output. When omitted, no channel-specific checks run. */ @JsonProperty("layer") String layer diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpListBuiltinServerNamesResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpListBuiltinServerNamesResult.java new file mode 100644 index 0000000000..2a65a679d9 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpListBuiltinServerNamesResult.java @@ -0,0 +1,32 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import java.util.List; +import javax.annotation.processing.Generated; + +/** + * Names of the MCP servers the runtime supplies itself. + * + * @apiNote This type 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) +record McpListBuiltinServerNamesResult( + /** Every built-in server name. A host disabling the built-ins wholesale suppresses exactly this set. */ + @JsonProperty("names") List names +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpRegistryCancelParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpRegistryCancelParams.java new file mode 100644 index 0000000000..0a12a5c26b --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpRegistryCancelParams.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; + +/** + * Registry search to abandon. + * + * @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) +record McpRegistryCancelParams( + /** Request ID from `mcp.registry.allocateRequestId` that the search uses. */ + @JsonProperty("requestId") Long requestId +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpRegistryCancelResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpRegistryCancelResult.java new file mode 100644 index 0000000000..c7f581f4cc --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpRegistryCancelResult.java @@ -0,0 +1,31 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import javax.annotation.processing.Generated; + +/** + * Whether the cancel reached a live search. + * + * @apiNote This type 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) +record McpRegistryCancelResult( + /** True when the cancel stopped a running search. False for unknown or reclaimed IDs, completed or canceled searches, and unused reservations. The cancel releases an unused reservation. */ + @JsonProperty("canceled") Boolean canceled +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpRegistryRequestIdResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpRegistryRequestIdResult.java new file mode 100644 index 0000000000..53234b3598 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpRegistryRequestIdResult.java @@ -0,0 +1,31 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import javax.annotation.processing.Generated; + +/** + * Request id naming a cancellable registry search. + * + * @apiNote This type 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) +record McpRegistryRequestIdResult( + /** Request ID for `mcp.registry.search` and `mcp.registry.cancel`. It serves one search. Allocation can reclaim unused IDs at the 1,024-ID reservation limit. */ + @JsonProperty("requestId") Long requestId +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpRegistrySearchParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpRegistrySearchParams.java new file mode 100644 index 0000000000..11a06de374 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpRegistrySearchParams.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 javax.annotation.processing.Generated; + +/** + * Registry search terms, the credential to search under, and the request id that makes the search cancellable. + * + * @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) +record McpRegistrySearchParams( + /** Request ID from `mcp.registry.allocateRequestId`. The search refuses unknown, reclaimed, or canceled IDs and IDs that another search or request already uses. */ + @JsonProperty("requestId") Long requestId, + /** The credential the search runs under, carried opaquely. Hosts usually send credential-free `AuthIdentity`; a `token` identity can be resolved only when it embeds a token, while `env` and `gh-cli` identities can use a token embedded in the request first. */ + @JsonInclude(JsonInclude.Include.ALWAYS) + @JsonProperty("authInfo") Object authInfo, + /** Free-text query. Omitted or empty asks the registry for its top servers rather than searching. A value that is not a string is refused. */ + @JsonProperty("query") String query, + /** Repository used for the policy lookup, as `owner/name`. The policy selects the registry URL and whether the user token goes to the registry. The registry receives this repository only when the policy entry lists it as required context. A value that is not a string is refused. */ + @JsonProperty("repository") String repository, + /** Maximum number of servers to return. */ + @JsonProperty("limit") Long limit +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpRegistrySearchResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpRegistrySearchResult.java new file mode 100644 index 0000000000..2682402d76 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpRegistrySearchResult.java @@ -0,0 +1,31 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import javax.annotation.processing.Generated; + +/** + * Servers selected from the registry response. + * + * @apiNote This type 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) +record McpRegistrySearchResult( + /** The server objects, carried opaquely. The runtime follows pages, keeps the newest entry per server name, cuts the list to `limit`, and sorts an empty-query result by GitHub stars. Each server object remains unchanged because the registry owns that shape. */ + @JsonProperty("servers") Object servers +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpShouldExcludeGitHubToolsParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpShouldExcludeGitHubToolsParams.java new file mode 100644 index 0000000000..bb435663bf --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpShouldExcludeGitHubToolsParams.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; + +/** + * The session tool filters that decide whether the session still has a shell to run `gh` with. + * + * @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) +record McpShouldExcludeGitHubToolsParams( + /** The session's tool allowlist, when it set one. Omitted means the session constrains nothing this way. */ + @JsonProperty("availableTools") List availableTools, + /** The session's tool denylist, when it set one. Omitted means the session constrains nothing this way. */ + @JsonProperty("excludedTools") List excludedTools, + /** How the allowlist and denylist combine when both are set. Omitted means the default every session gets, so a caller that never chose a precedence is answered as its sessions behave. */ + @JsonProperty("toolFilterPrecedence") OptionsUpdateToolFilterPrecedence toolFilterPrecedence +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpShouldExcludeGitHubToolsResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpShouldExcludeGitHubToolsResult.java new file mode 100644 index 0000000000..05f92ab309 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpShouldExcludeGitHubToolsResult.java @@ -0,0 +1,31 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import javax.annotation.processing.Generated; + +/** + * Whether the gh-replaceable GitHub MCP tools may be clipped for the session described by the request. + * + * @apiNote This type 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) +record McpShouldExcludeGitHubToolsResult( + /** True only when both halves hold: the session's filters still reach the platform shell tool, and the host actually has the `gh` those tools would be replaced by. Feed it straight back as the `excludeGhReplaceableTools` build option. */ + @JsonProperty("excludeGhReplaceableTools") Boolean excludeGhReplaceableTools +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/PermissionRecoveryData.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/PermissionRecoveryData.java index e4c502116d..990349ebaf 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/PermissionRecoveryData.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/PermissionRecoveryData.java @@ -24,6 +24,10 @@ public record PermissionRecoveryData( /** Stable identifier shared by every transition in this recovery episode */ @JsonProperty("episodeId") String episodeId, + /** Root or child execution-session identifier that owns this recovery episode; omitted for legacy snapshots */ + @JsonProperty("ownerSessionId") String ownerSessionId, + /** Subagent identifier that owns this recovery episode when the request was delegated through a parent permission service */ + @JsonProperty("ownerAgentId") String ownerAgentId, /** Current lifecycle state of the recovery episode */ @JsonProperty("status") PermissionRecoveryStatus status, /** Policy selected from the current client's response capability; mode or client changes may update it during recovery */ diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/PermissionsEvaluateManagedPolicyParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/PermissionsEvaluateManagedPolicyParams.java new file mode 100644 index 0000000000..65a15a044e --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/PermissionsEvaluateManagedPolicyParams.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 verbatim managed `permissions` slice to interpret. + * + * @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) +record PermissionsEvaluateManagedPolicyParams( + /** The `permissions` object from a managed settings layer, for example `{"disableBypassPermissionsMode":"disable"}`. Omitted or null means no managed policy applies. */ + @JsonProperty("permissions") Object permissions +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/PermissionsEvaluateManagedPolicyResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/PermissionsEvaluateManagedPolicyResult.java new file mode 100644 index 0000000000..2690dad76f --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/PermissionsEvaluateManagedPolicyResult.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; + +/** + * The policy questions a host asks before granting elevated permissions. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +record PermissionsEvaluateManagedPolicyResult( + /** True when managed policy forbids bypass-permissions mode, so allow-all style flags must not take effect. */ + @JsonProperty("bypassPermissionsDisabled") Boolean bypassPermissionsDisabled, + /** True when a managed policy exists but this build could not determine its meaning. Hosts fail closed on it rather than assuming permission was granted. */ + @JsonProperty("policyUndetermined") Boolean policyUndetermined, + /** True when the managed layer supplies permission rules that the session must enforce. */ + @JsonProperty("permissionRulesActive") Boolean permissionRulesActive +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/PermissionsFolderTrustCheckTrustParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/PermissionsFolderTrustCheckTrustParams.java new file mode 100644 index 0000000000..5fadba2eec --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/PermissionsFolderTrustCheckTrustParams.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; + +/** + * A folder to check against a persisted trusted-folders list, and the configuration directory holding that list. + * + * @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) +record PermissionsFolderTrustCheckTrustParams( + /** Folder to check. A folder counts as trusted when it is itself listed, when an ancestor is listed, or when its git root is listed. */ + @JsonProperty("path") String path, + /** Configuration directory to read the trusted-folders list from. Omit it to read the default location; supply it when the host has redirected its configuration, or the check answers from a list the host is not using. */ + @JsonProperty("configDir") String configDir +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/PersistenceWriteRawJsonLockedParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/PersistenceWriteRawJsonLockedParams.java new file mode 100644 index 0000000000..eff9c61d51 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/PersistenceWriteRawJsonLockedParams.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 complete JSON snapshot to write under the runtime's per-path file lock. + * + * @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) +record PersistenceWriteRawJsonLockedParams( + /** Absolute path of the file to replace. Parent directories are created when missing. */ + @JsonProperty("path") String path, + /** The serialized JSON document to write. It replaces the file's contents entirely; nothing is merged. */ + @JsonProperty("dataJson") String dataJson, + /** Comment lines to emit above the document, for a format that tolerates them. Omit for strict JSON. */ + @JsonProperty("fileHeader") String fileHeader +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/PersistenceWriteRawJsonLockedResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/PersistenceWriteRawJsonLockedResult.java new file mode 100644 index 0000000000..5f8616d042 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/PersistenceWriteRawJsonLockedResult.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; + +/** + * The outcome of a locked snapshot write. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +record PersistenceWriteRawJsonLockedResult( + /** Whether the file now holds the requested contents. */ + @JsonProperty("success") Boolean success, + /** Why the write failed. Absent when `success` is true. A lock the runtime could not acquire in time reports here rather than raising. */ + @JsonProperty("error") String error +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ProviderMonthlyUsage.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ProviderMonthlyUsage.java new file mode 100644 index 0000000000..355f7e38f0 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ProviderMonthlyUsage.java @@ -0,0 +1,43 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import javax.annotation.processing.Generated; + +/** + * An authoritative monthly usage reading for the observation's provider, account, and service. + * + * @apiNote This type 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 ProviderMonthlyUsage( + /** Availability of monthly consumption. Missing or unavailable usage must never be interpreted as zero. */ + @JsonProperty("state") ProviderMonthlyUsageState state, + /** Unit of consumedQuantity; independent of the parent observation's balance unit. */ + @JsonProperty("unit") ProviderQuotaUnit unit, + /** Identity scope of the reading within its provider and service. */ + @JsonProperty("scope") ProviderMonthlyUsageScope scope, + /** Nonnegative finite consumption reported by the service, preserving zero and fractions. Present only when state is available; never computed from tokens or balance differences. */ + @JsonProperty("consumedQuantity") Double consumedQuantity, + /** Service read time in UTC RFC 3339, not a ledger reconciliation watermark or confirmation of the latest inference charge. */ + @JsonProperty("queriedAt") String queriedAt, + /** Start of the service-reported monthly billing cycle, in UTC RFC 3339. */ + @JsonProperty("cycleStart") String cycleStart, + /** End of the service-reported monthly billing cycle, in UTC RFC 3339. */ + @JsonProperty("resetOn") String resetOn +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ProviderMonthlyUsageScope.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ProviderMonthlyUsageScope.java new file mode 100644 index 0000000000..56d9b4dd38 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ProviderMonthlyUsageScope.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; + +/** + * Identity scope of a monthly usage reading. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public enum ProviderMonthlyUsageScope { + /** The {@code user} variant. */ + USER("user"), + /** The {@code unknown} variant. */ + UNKNOWN("unknown"); + + private final String value; + ProviderMonthlyUsageScope(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static ProviderMonthlyUsageScope fromValue(String value) { + for (ProviderMonthlyUsageScope v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown ProviderMonthlyUsageScope value: " + value); + } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ProviderMonthlyUsageState.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ProviderMonthlyUsageState.java new file mode 100644 index 0000000000..7788fbc219 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ProviderMonthlyUsageState.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; + +/** + * Availability of a service's monthly consumption reading. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public enum ProviderMonthlyUsageState { + /** The {@code available} variant. */ + AVAILABLE("available"), + /** The {@code no_policy} variant. */ + NO_POLICY("no_policy"), + /** The {@code unavailable} variant. */ + UNAVAILABLE("unavailable"), + /** The {@code unknown} variant. */ + UNKNOWN("unknown"); + + private final String value; + ProviderMonthlyUsageState(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static ProviderMonthlyUsageState fromValue(String value) { + for (ProviderMonthlyUsageState v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown ProviderMonthlyUsageState value: " + value); + } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ProviderQuotaState.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ProviderQuotaState.java index 253d64bbe8..5473712f9c 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ProviderQuotaState.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ProviderQuotaState.java @@ -49,6 +49,8 @@ public record ProviderQuotaState( @JsonProperty("acquisitionError") String acquisitionError, /** Optional metadata for an authoritative budget. Advisory balances and admission-only observations do not populate this. */ @JsonProperty("budgetMetadata") ProviderQuotaBudgetMetadata budgetMetadata, + /** Service-reported monthly consumption, independent of quota balances and per-call or session cost. Omitted when the service does not report monthly usage. */ + @JsonProperty("monthlyUsage") ProviderMonthlyUsage monthlyUsage, /** Key for the backwards-compatible snapshots projection, when the authoritative budget supports that contract. */ @JsonProperty("compatibilityKey") String compatibilityKey, /** Explicit service admission flag, when reported. */ diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/RemediationAction.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/RemediationAction.java index 50f6256352..2da0b13624 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/RemediationAction.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/RemediationAction.java @@ -25,7 +25,9 @@ public enum RemediationAction { /** The {@code review_sandbox_policy} variant. */ REVIEW_SANDBOX_POLICY("review_sandbox_policy"), /** The {@code allow_sandbox_outbound} variant. */ - ALLOW_SANDBOX_OUTBOUND("allow_sandbox_outbound"); + ALLOW_SANDBOX_OUTBOUND("allow_sandbox_outbound"), + /** The {@code rewind_conversation} variant. */ + REWIND_CONVERSATION("rewind_conversation"); private final String value; RemediationAction(String value) { this.value = value; } diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/RepoSettingsEditParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/RepoSettingsEditParams.java new file mode 100644 index 0000000000..c871a3aed7 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/RepoSettingsEditParams.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; + +/** + * One atomic edit of an allowed top-level repository setting. + * + * @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) +record RepoSettingsEditParams( + /** Absolute path of the repository root. */ + @JsonProperty("repoRoot") String repoRoot, + /** Shared or local repository settings file. */ + @JsonProperty("scope") RepoSettingsEditScope scope, + /** One canonical top-level repository setting that the allowlist permits. */ + @JsonProperty("key") String key, + /** Set a scalar, add or remove a string list item, or remove the setting. The handler rejects a missing or incompatible value. */ + @JsonProperty("operation") SettingsEditOperation operation, + /** Required boolean, string or number for set, required string for add-item and remove-item, and forbidden for remove. The runtime rejects all other JSON values. */ + @JsonProperty("value") Object value +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/RepoSettingsEditScope.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/RepoSettingsEditScope.java new file mode 100644 index 0000000000..43b64573d5 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/RepoSettingsEditScope.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.github.copilot.CopilotExperimental; +import javax.annotation.processing.Generated; + +/** + * Which repository settings file to edit. + * + * @apiNote This type is experimental and may change in a future version. + * + * @since 1.0.0 + */ +@CopilotExperimental +@javax.annotation.processing.Generated("copilot-sdk-codegen") +enum RepoSettingsEditScope { + /** The {@code repo} variant. */ + REPO("repo"), + /** The {@code local} variant. */ + LOCAL("local"); + + private final String value; + RepoSettingsEditScope(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static RepoSettingsEditScope fromValue(String value) { + for (RepoSettingsEditScope v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown RepoSettingsEditScope value: " + value); + } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/RepoSettingsReadRawParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/RepoSettingsReadRawParams.java new file mode 100644 index 0000000000..40d25ea68c --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/RepoSettingsReadRawParams.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; + +/** + * Which repository settings file to read. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ +@CopilotExperimental +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +record RepoSettingsReadRawParams( + /** Absolute path of the repository root. */ + @JsonProperty("repoRoot") String repoRoot, + /** Use repo for shared settings or local for settings.local.json. Other scopes are rejected. */ + @JsonProperty("scope") String scope +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/RepoSettingsReadRawResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/RepoSettingsReadRawResult.java new file mode 100644 index 0000000000..65d5884033 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/RepoSettingsReadRawResult.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; + +/** + * Parsed repository settings without normalization or layering. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +record RepoSettingsReadRawResult( + /** Stored JSON values after the existing JSONC parser and prototype-safety checks. Empty when the file is missing, blank, or invalid. */ + @JsonProperty("config") Object config, + /** True when the file exists but did not parse, so `config` is empty for that reason rather than because nothing is set. */ + @JsonProperty("invalid") Boolean invalid +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/RepoSettingsWriteAllParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/RepoSettingsWriteAllParams.java new file mode 100644 index 0000000000..8b94ff9be4 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/RepoSettingsWriteAllParams.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 javax.annotation.processing.Generated; + +/** + * The complete set of repository-overridable settings to store. + * + * @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) +record RepoSettingsWriteAllParams( + /** Absolute path of the repository root. */ + @JsonProperty("repoRoot") String repoRoot, + /** Use repo for shared settings or local for settings.local.json. Other scopes are rejected. */ + @JsonProperty("scope") String scope, + /** Object of allowed settings to store. Missing allowlisted keys are removed. Other input keys are ignored, while unknown stored keys are preserved. */ + @JsonInclude(JsonInclude.Include.ALWAYS) + @JsonProperty("settings") Object settings +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/RepoSettingsWriteKeyParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/RepoSettingsWriteKeyParams.java new file mode 100644 index 0000000000..24bc205bc3 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/RepoSettingsWriteKeyParams.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; + +/** + * One key to write or remove in a repository settings file. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ +@CopilotExperimental +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +record RepoSettingsWriteKeyParams( + /** Absolute path of the repository root. */ + @JsonProperty("repoRoot") String repoRoot, + /** Use repo for shared settings or local for settings.local.json. Other scopes are rejected. */ + @JsonProperty("scope") String scope, + /** Canonical top-level key permitted by the repository settings allowlist. */ + @JsonProperty("key") String key, + /** Value to validate and write. Required unless delete is true; a null value is validated, not treated as deletion. */ + @JsonProperty("value") Object value, + /** True to remove the key instead of writing it. */ + @JsonProperty("delete") Boolean delete +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SandboxCheckEnforcementCommandParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SandboxCheckEnforcementCommandParams.java new file mode 100644 index 0000000000..b7401231f7 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SandboxCheckEnforcementCommandParams.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; + +/** + * One slash command, as typed, to test against the enforcement-block allowlist. + * + * @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) +record SandboxCheckEnforcementCommandParams( + /** The command name as the user typed it, with or without its leading slash. */ + @JsonProperty("commandName") String commandName, + /** The command's arguments, in order. Pass an empty array for a bare command: several entries in the allowlist admit the bare form and refuse every argument. */ + @JsonProperty("args") List args +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SandboxCredentialSuggestion.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SandboxCredentialSuggestion.java new file mode 100644 index 0000000000..927dcdf5e9 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SandboxCredentialSuggestion.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 java.util.List; +import javax.annotation.processing.Generated; + +/** + * A possible secret-bearing environment variable and optional locally suggested HTTPS injection hosts. This is a draft for user review, not an active grant. + * + * @apiNote This type 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 SandboxCredentialSuggestion( + /** Environment variable name. The secret value is never returned. */ + @JsonProperty("name") String name, + /** HTTPS hostnames suggested by a local known-provider mapping. An empty list requires the user to supply injection hosts before adding a masking entry. These suggestions do not allow network access. */ + @JsonProperty("suggestedInjectHosts") List suggestedInjectHosts +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SandboxDocsUrlResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SandboxDocsUrlResult.java new file mode 100644 index 0000000000..bb2ed5c1a2 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SandboxDocsUrlResult.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; + +/** + * The documentation URL a host cites when it declines or downgrades a sandbox setting. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +record SandboxDocsUrlResult( + /** Documentation URL describing the sandbox and the settings that control it. */ + @JsonProperty("url") String url +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SandboxEffectiveForOptionalResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SandboxEffectiveForOptionalResult.java new file mode 100644 index 0000000000..2336d4f195 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SandboxEffectiveForOptionalResult.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; + +/** + * The sandbox a named feature actually launches under. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +record SandboxEffectiveForOptionalResult( + /** The effective sandbox config for the requested feature, or a disabled sandbox when the feature is routed out of it. */ + @JsonProperty("sandboxJson") Object sandboxJson +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SandboxFilterPickerCandidatesParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SandboxFilterPickerCandidatesParams.java new file mode 100644 index 0000000000..663040c87e --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SandboxFilterPickerCandidatesParams.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; + +/** + * A batch of file-picker candidates to check against a prepared policy. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ +@CopilotExperimental +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +record SandboxFilterPickerCandidatesParams( + /** The candidates to check, in the order the answers are returned. */ + @JsonProperty("candidates") List candidates, + /** The session's working directory, matching the one the policy was prepared from. */ + @JsonProperty("cwd") String cwd, + /** The policy returned by `sandbox.preparePickerPolicy`. Omitted keeps every candidate. */ + @JsonProperty("sandbox") Object sandbox +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SandboxFilterPickerCandidatesResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SandboxFilterPickerCandidatesResult.java new file mode 100644 index 0000000000..cbf29a25f3 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SandboxFilterPickerCandidatesResult.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 java.util.List; +import javax.annotation.processing.Generated; + +/** + * Per-candidate allow decisions, positionally aligned with the request. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +record SandboxFilterPickerCandidatesResult( + /** One decision per candidate, in the same order. Always the same length as the request's `candidates`. */ + @JsonProperty("allowed") List allowed +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SandboxGetUnsupportedStartupNoticeParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SandboxGetUnsupportedStartupNoticeParams.java new file mode 100644 index 0000000000..c082b52e7d --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SandboxGetUnsupportedStartupNoticeParams.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; + +/** + * The managed sandbox policy in force, which selects which remedy the notice offers. + * + * @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) +record SandboxGetUnsupportedStartupNoticeParams( + /** The managed `sandbox` slice, when a policy supplies one. The notice treats the sandbox as enforced only when this both enables it and sets `failIfUnavailable`, because a managed sandbox that tolerates being unavailable still leaves the user able to turn it off. */ + @JsonProperty("managedSandbox") Object managedSandbox, + /** True when the sandbox stayed on only because the managed policy could not be determined. Distinguishes a policy that positively enforces the sandbox from one that merely failed to resolve, which is the difference between telling the user to contact an administrator and telling them to wait. Defaults to false. */ + @JsonProperty("enabledByUndeterminedPolicy") Boolean enabledByUndeterminedPolicy +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SandboxPickerCandidate.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SandboxPickerCandidate.java new file mode 100644 index 0000000000..f3c7712a82 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SandboxPickerCandidate.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; + +/** + * One file-picker suggestion offered up for the sandbox filesystem check. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +record SandboxPickerCandidate( + /** Absolute path the suggestion would attach. */ + @JsonProperty("path") String path, + /** Whether the suggestion is a directory. A directory the policy does not itself allow may still be needed as a waypoint to an allowed subtree. */ + @JsonProperty("isDirectory") Boolean isDirectory +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SandboxPreparePickerPolicyParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SandboxPreparePickerPolicyParams.java new file mode 100644 index 0000000000..2895a57e73 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SandboxPreparePickerPolicyParams.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; + +/** + * The working directory and sandbox settings to build a file-picker policy from. + * + * @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) +record SandboxPreparePickerPolicyParams( + /** The session's working directory. Git discovery and grant canonicalization are resolved against it. */ + @JsonProperty("cwd") String cwd, + /** The session's resolved `sandbox` slice. Omitted, or disabled, yields no policy and every candidate is kept. */ + @JsonProperty("sandbox") Object sandbox +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SandboxPreparePickerPolicyResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SandboxPreparePickerPolicyResult.java new file mode 100644 index 0000000000..07cae03587 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SandboxPreparePickerPolicyResult.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; + +/** + * The prepared file-picker policy, or none when the sandbox is disabled. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +record SandboxPreparePickerPolicyResult( + /** The prepared policy to pass back to `sandbox.filterPickerCandidates`. Absent when the sandbox is disabled or absent, in which case filtering keeps every candidate. */ + @JsonProperty("sandboxJson") Object sandboxJson +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SandboxProxyCaCheckRelevanceParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SandboxProxyCaCheckRelevanceParams.java new file mode 100644 index 0000000000..6d66db0a30 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SandboxProxyCaCheckRelevanceParams.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 credential hosts that the persistent certificate authority of the sandbox credential proxy must cover. The runtime always adds the hosts from the saved user settings. + * + * @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 SandboxProxyCaCheckRelevanceParams( + /** The sandbox configuration that the host gives its sessions. The runtime reads the credential hosts from `auth` and `credentials`; it ignores `enabled` and the other fields. */ + @JsonProperty("sandboxConfig") SandboxConfig sandboxConfig +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SandboxProxySecretGetResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SandboxProxySecretGetResult.java new file mode 100644 index 0000000000..5afa3f89d3 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SandboxProxySecretGetResult.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; + +/** + * The stored sandbox proxy password, if one is present. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +record SandboxProxySecretGetResult( + /** The stored password. Absent when no entry exists in either the keychain or the on-disk fallback store. */ + @JsonProperty("value") String value +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SandboxProxySecretLocation.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SandboxProxySecretLocation.java new file mode 100644 index 0000000000..d72162ee2e --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SandboxProxySecretLocation.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 this runtime keeps the sandbox proxy password. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +record SandboxProxySecretLocation( + /** Absolute path of the credential store holding the sandbox proxy password. The proxy password belongs to no MCP server, so it is kept in its own directory rather than in the MCP secret store. */ + @JsonProperty("configDir") String configDir, + /** Identifier of the proxy password entry within that store. Derived from the configuration directory, so two Copilot homes on one machine never share an entry. Use it to recognize a `${secret:…}` reference in stored settings as this runtime's own proxy password. */ + @JsonProperty("secretId") String secretId +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SandboxProxySecretSaveParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SandboxProxySecretSaveParams.java new file mode 100644 index 0000000000..610fb09681 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SandboxProxySecretSaveParams.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 sandbox proxy password to store. + * + * @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) +record SandboxProxySecretSaveParams( + /** The password to store for the sandbox proxy. */ + @JsonProperty("value") String value +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SandboxProxySecretSaveResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SandboxProxySecretSaveResult.java new file mode 100644 index 0000000000..543b38083f --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SandboxProxySecretSaveResult.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; + +/** + * Where the sandbox proxy password was stored. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +record SandboxProxySecretSaveResult( + /** Whether the OS keychain accepted the value. False means it was written to the on-disk fallback store instead, which is still readable by `sandbox.proxySecret.get`. */ + @JsonProperty("storedInKeychain") Boolean storedInKeychain +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SandboxResolveFlooredConfigParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SandboxResolveFlooredConfigParams.java new file mode 100644 index 0000000000..738642fa05 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SandboxResolveFlooredConfigParams.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; + +/** + * The user's sandbox settings and the managed sandbox floor to raise them to. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ +@CopilotExperimental +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +record SandboxResolveFlooredConfigParams( + /** The user's own `sandbox` settings slice. Omitted when the user has configured none, which still yields the managed floor rather than a disabled sandbox. */ + @JsonProperty("userSandbox") Object userSandbox, + /** The effective managed `sandbox` slice acting as a floor. Omitted when no administrator policy applies, in which case the user's own settings stand unchanged. */ + @JsonProperty("managedSandbox") Object managedSandbox +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SandboxResolveFlooredConfigResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SandboxResolveFlooredConfigResult.java new file mode 100644 index 0000000000..043eaaead9 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SandboxResolveFlooredConfigResult.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; + +/** + * The user's sandbox settings raised to the managed floor. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +record SandboxResolveFlooredConfigResult( + /** The floored sandbox config, normalized through the declared sandbox shape so only declared fields in a fixed order are present. */ + @JsonProperty("sandboxJson") Object sandboxJson +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SandboxResolveForOptionalParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SandboxResolveForOptionalParams.java new file mode 100644 index 0000000000..9a523b274e --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SandboxResolveForOptionalParams.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; + +/** + * A sandbox config and the per-feature routing flag to resolve it for. + * + * @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) +record SandboxResolveForOptionalParams( + /** The session's resolved `sandbox` slice. Omitted when the host has none, which yields a disabled sandbox rather than an error. */ + @JsonProperty("sandbox") Object sandbox, + /** The routing flag naming the feature being launched, such as `sandboxMcpServers` or `sandboxLspServers`. */ + @JsonProperty("routingFlag") String routingFlag +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SandboxSettingsApplyDefaultsParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SandboxSettingsApplyDefaultsParams.java new file mode 100644 index 0000000000..4b129d0d41 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SandboxSettingsApplyDefaultsParams.java @@ -0,0 +1,31 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import javax.annotation.processing.Generated; + +/** + * The stored sandbox settings to resolve against the defaults. + * + * @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) +record SandboxSettingsApplyDefaultsParams( + /** The sandbox settings as stored in the settings file. An empty object resolves to the defaults alone. */ + @JsonInclude(JsonInclude.Include.ALWAYS) + @JsonProperty("stored") Object stored +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SandboxUnsupportedStartupNoticeResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SandboxUnsupportedStartupNoticeResult.java new file mode 100644 index 0000000000..2d31660a33 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SandboxUnsupportedStartupNoticeResult.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; + +/** + * The startup warning, absent when the machine can run a sandbox. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +record SandboxUnsupportedStartupNoticeResult( + /** The user-facing warning, carrying the platform remedy and the recovery the policy in force actually allows. Absent when this machine supports a sandbox, so a host can call unconditionally and print only what it gets. */ + @JsonProperty("notice") String notice +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerAccountApi.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerAccountApi.java index 714ef4ebb6..8c25672e33 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerAccountApi.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerAccountApi.java @@ -95,4 +95,48 @@ public CompletableFuture logout(AccountLogoutParams params) return caller.invoke("account.logout", params, AccountLogoutResult.class); } + /** + * Renders the sign-in banner and the one-line account description the runtime uses for an authentication identity. The runtime words these in several places for its own callers, so a host that announces a sign-in or an account switch should render them from here rather than composing its own, which would let the two spellings drift apart. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + CompletableFuture describeAuthInfo(AccountDescribeAuthInfoParams params) { + return caller.invoke("account.describeAuthInfo", params, AccountDescribeAuthInfoResult.class); + } + + /** + * Reports whether the supplied identity can create gists. Gists are unavailable on GitHub Enterprise Cloud with data residency, and to Enterprise Managed Users on github.com, so a host that offers gist actions should hide or disable them when this reports no support. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + CompletableFuture getGistSupport(AccountGetGistSupportParams params) { + return caller.invoke("account.getGistSupport", params, AccountGetGistSupportResult.class); + } + + /** + * Resolves a path on the identity's GitHub host into an absolute URL, so a host can link to settings or documentation on the enterprise instance the user is signed in to rather than to github.com. Falls back to the public host when no identity is supplied. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + CompletableFuture getWebUrl(AccountGetWebUrlParams params) { + return caller.invoke("account.getWebUrl", params, AccountGetWebUrlResult.class); + } + + /** + * Names the environment variable supplying a classic personal access token, if one is set. A classic token cannot be upgraded in place, so a host that finds one should tell the user which variable to unset rather than starting an interactive sign-in that the variable would override. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + CompletableFuture getTokenEnvVar() { + return caller.invoke("account.getTokenEnvVar", java.util.Map.of(), AccountGetTokenEnvVarResult.class); + } + } diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerAgentsApi.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerAgentsApi.java index ddff436555..287c099c93 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerAgentsApi.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerAgentsApi.java @@ -8,6 +8,7 @@ package com.github.copilot.generated.rpc; import com.github.copilot.CopilotExperimental; +import java.util.Objects; import java.util.concurrent.CompletableFuture; import javax.annotation.processing.Generated; @@ -37,6 +38,19 @@ public CompletableFuture discover(AgentsDiscoverParams par return caller.invoke("agents.discover", params, AgentsDiscoverResult.class); } + /** + * Discovers custom agents across user, project, plugin, and remote sources. + *

+ * Accepts the extensible request, including inputs added after the params record. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + public CompletableFuture discover(AgentsDiscoverRequest request) { + return caller.invoke("agents.discover", Objects.requireNonNull(request, "request"), AgentsDiscoverResult.class); + } + /** * Returns the canonical directories where a client may create custom agents that the runtime will recognize, including ones that do not exist yet. Project directories become active once created. * @@ -48,6 +62,19 @@ public CompletableFuture getDiscoveryPaths(Agents return caller.invoke("agents.getDiscoveryPaths", params, AgentsGetDiscoveryPathsResult.class); } + /** + * Returns the canonical directories where a client may create custom agents that the runtime will recognize, including ones that do not exist yet. Project directories become active once created. + *

+ * Accepts the extensible request, including inputs added after the params record. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + public CompletableFuture getDiscoveryPaths(AgentsGetDiscoveryPathsRequest request) { + return caller.invoke("agents.getDiscoveryPaths", Objects.requireNonNull(request, "request"), AgentsGetDiscoveryPathsResult.class); + } + /** * Lists the agents this runtime ships, by name. A consumer separating shipped agents from ones the user or a plugin authored should compare against these names rather than against `AgentInfo.source`: an authored agent may carry the `builtin` source while not being one of these, and the runtime treats the two as separate questions. `disableableNames` is the subset a user may turn off, which a client needs to decide whether to offer a toggle. `yamlBasedNames` is the subset backed by a shipped YAML definition, which a client needs before asking the runtime to load one. * diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerContentExclusionApi.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerContentExclusionApi.java new file mode 100644 index 0000000000..4df2fb93e5 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerContentExclusionApi.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 contentExclusion} namespace. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public final class ServerContentExclusionApi { + + private final RpcCaller caller; + + /** @param caller the RPC transport function */ + ServerContentExclusionApi(RpcCaller caller) { + this.caller = caller; + } + + /** + * Validates candidate content-exclusion policies and returns them normalized, without starting a session. This runs the same parser the runtime's own policy enforcement uses, so a host cannot accept a policy set the runtime would later reject, nor reject one it would have honored. Call it when policies arrive from outside the runtime — a command-line flag, a configuration file, a host's own settings — and must be checked before a session exists to apply them to. A malformed document fails with a message prefixed `Invalid JSON: `; a well-formed document that violates the policy schema fails with the offending field paths. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + public CompletableFuture validatePolicies(ContentExclusionValidatePoliciesParams params) { + return caller.invoke("contentExclusion.validatePolicies", params, ContentExclusionValidatePoliciesResult.class); + } + +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerFeatureFlagsApi.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerFeatureFlagsApi.java new file mode 100644 index 0000000000..b2f3444b0a --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerFeatureFlagsApi.java @@ -0,0 +1,63 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) 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.Map; +import java.util.concurrent.CompletableFuture; +import javax.annotation.processing.Generated; + +/** + * API methods for the {@code featureFlags} namespace. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +final class ServerFeatureFlagsApi { + + private final RpcCaller caller; + + /** @param caller the RPC transport function */ + ServerFeatureFlagsApi(RpcCaller caller) { + this.caller = caller; + } + + /** + * Resolves named gates against this installation's cached online-experiment assignment, for the account the runtime is already authenticated as. Unlike `featureFlags.resolve`, which layers static availability with the populations supplied, this reads the experiment assignment the runtime cached for its own current authentication, so a launch can honor a rollout before any session exists. The account is never taken from the request: naming one would read another account's cached assignments. The cached assignment answers immediately and any network refresh happens behind it, so this call does not wait on the network. Not available to an extension, which cannot carry the user's consent to read their assignment. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + CompletableFuture> resolveStartupGates(FeatureFlagsResolveStartupGatesParams params) { + return caller.invoke("featureFlags.resolveStartupGates", params, RpcMapper.INSTANCE.getTypeFactory().constructMapType(Map.class, String.class, Boolean.class)); + } + + /** + * Lists every feature flag this runtime declares, with the population each one is declared for, its experimental description, and the online-experiment flag it maps to. This is the static catalog: it describes how each flag is defined, not whether it is on for anyone. Use `featureFlags.resolve` for static values with caller overrides, not active runtime values or experiment assignments. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + CompletableFuture listMetadata() { + return caller.invoke("featureFlags.listMetadata", java.util.Map.of(), FeatureFlagsMetadataResult.class); + } + + /** + * Computes static feature flags from the supplied population and overrides without changing runtime state or verifying audience membership. Starts from declared availability, then applies direct feature-flag environment values, user settings, and explicit `flagOverrides`. It does not apply `COPILOT_EXP_*` experiment assignments, authenticated startup gates, or active runtime state. Settings include `enabledFeatureFlags`, the legacy `feature_flags.enabled` list, and supported convenience settings. Supply `baseFlags` to use an existing flag set instead of declared availability. Omit `isStaffGitHub` or `isStaffMicrosoft` when membership was never recorded: a legacy staff account then counts as both populations. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + CompletableFuture> resolve(FeatureFlagsResolveParams params) { + return caller.invoke("featureFlags.resolve", params, RpcMapper.INSTANCE.getTypeFactory().constructMapType(Map.class, String.class, Boolean.class)); + } + +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerGlobalStateApi.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerGlobalStateApi.java index 3c215395b8..2d41182ae0 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerGlobalStateApi.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerGlobalStateApi.java @@ -49,7 +49,7 @@ CompletableFuture loadForConfigDir(GlobalStateLoadForConf } /** - * Records one top-level key in the host's machine-wide state, the counterpart to `globalState.load`. A host calls this to remember that it has shown an onboarding step, asked a one-off question, or completed a migration, so the next run can skip it. Only the named key is replaced and the rest of the document is preserved, which lets two writers record different flags without overwriting each other; passing no value removes the key instead. Only the keys a host records itself are writable: `appInstallNudgeResponded`, `appTipShown`, `askedSetupTerminals`, `autoFeedbackLastPromptedAt`, `firstLaunchAt`, `recentModelIds`, `sandboxCredentialProxyCaDeclined` and `sandboxOnboardingShown`. Every other key is refused, including `installedPlugins`, the stored credentials, `trustedFolders`, the staff flags and the signed-in accounts. Plugin enablement must use the plugin APIs, which apply repository and managed-policy checks. + * Records one top-level key in the host's machine-wide state, the counterpart to `globalState.load`. A host calls this to remember that it has shown an onboarding step, asked a one-off question, or completed a migration, so the next run can skip it. Only the named key is replaced and the rest of the document is preserved, which lets two writers record different flags without overwriting each other; passing no value removes the key instead. Only the keys a host records itself are writable: `appTipShown`, `askedSetupTerminals`, `autoFeedbackLastPromptedAt`, `firstLaunchAt`, `recentModelIds`, `sandboxCredentialProxyCaDeclined` and `sandboxOnboardingShown`. Every other key is refused, including `installedPlugins`, the stored credentials, `trustedFolders`, the staff flags and the signed-in accounts. Plugin enablement must use the plugin APIs, which apply repository and managed-policy checks. * * @apiNote This method is experimental and may change in a future version. * @since 1.0.0 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 index f41fbd63ac..0997c6588f 100644 --- 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 @@ -8,6 +8,7 @@ package com.github.copilot.generated.rpc; import com.github.copilot.CopilotExperimental; +import java.util.Objects; import java.util.concurrent.CompletableFuture; import javax.annotation.processing.Generated; @@ -37,4 +38,17 @@ public CompletableFuture discover(HooksDiscoverParams param return caller.invoke("hooks.discover", params, HooksDiscoverResult.class); } + /** + * Discovers hook actions enabled under server-side discovery settings from user, repository, plugin, and managed-policy sources. + *

+ * Accepts the extensible request, including inputs added after the params record. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + public CompletableFuture discover(HooksDiscoverRequest request) { + return caller.invoke("hooks.discover", Objects.requireNonNull(request, "request"), HooksDiscoverResult.class); + } + } diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerInstructionSourceList.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerInstructionSourceList.java new file mode 100644 index 0000000000..cddf0f9f88 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerInstructionSourceList.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 java.util.List; +import javax.annotation.processing.Generated; + +/** + * Instruction sources discovered across user, repository, and plugin sources. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record ServerInstructionSourceList( + /** All discovered instruction sources */ + @JsonProperty("sources") List sources +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerInstructionsApi.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerInstructionsApi.java index 23d8c7e2de..55298f8c4a 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerInstructionsApi.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerInstructionsApi.java @@ -8,6 +8,7 @@ package com.github.copilot.generated.rpc; import com.github.copilot.CopilotExperimental; +import java.util.Objects; import java.util.concurrent.CompletableFuture; import javax.annotation.processing.Generated; @@ -26,6 +27,17 @@ public final class ServerInstructionsApi { this.caller = caller; } + /** + * Returns the prompt the `init` command runs to author repository instructions. A host offering its own initialize action sends this text so the instructions it produces match the ones the CLI produces. The text is fixed for a build and depends on no configuration, session or repository. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + CompletableFuture getInitPrompt() { + return caller.invoke("instructions.getInitPrompt", java.util.Map.of(), InstructionsInitPromptResult.class); + } + /** * Discovers instruction sources across user, repository, and plugin sources. * @@ -37,6 +49,30 @@ public CompletableFuture discover(InstructionsDiscov return caller.invoke("instructions.discover", params, InstructionsDiscoverResult.class); } + /** + * Discovers instruction sources across user, repository, and plugin sources. + *

+ * Accepts the extensible request, including inputs added after the params record. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + public CompletableFuture discover(InstructionsDiscoverRequest request) { + return caller.invoke("instructions.discover", Objects.requireNonNull(request, "request"), InstructionsDiscoverResult.class); + } + + /** + * Discovers the instruction sources for one caller-named location and returns them in the order the runtime applies them. `discover` answers for a set of project paths and derives the rest of its scope itself; this one instead takes every argument from the caller — the extra directories to scan and whether to descend into nested instruction files — and honors them as given. It also includes instruction directories from trusted repository settings, matching native repository discovery. Use it when the caller knows its own scope and no session exists to derive that scope from, such as a host reporting how many instruction files a directory contributes before any session starts. Marked internal because it exists to carry a CLI call site off the napi boundary onto the SDK contract; it is migration plumbing, not a surface consumers are meant to depend on. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + CompletableFuture discoverRepoSources(InstructionsDiscoverRepoSourcesParams params) { + return caller.invoke("instructions.discoverRepoSources", params, ServerInstructionSourceList.class); + } + /** * Returns the canonical files and directories where a client may create custom instructions that the runtime will recognize, including ones that do not exist yet. Repository targets become active once created. * @@ -48,4 +84,28 @@ public CompletableFuture getDiscoveryPaths( return caller.invoke("instructions.getDiscoveryPaths", params, InstructionsGetDiscoveryPathsResult.class); } + /** + * Returns the canonical files and directories where a client may create custom instructions that the runtime will recognize, including ones that do not exist yet. Repository targets become active once created. + *

+ * Accepts the extensible request, including inputs added after the params record. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + public CompletableFuture getDiscoveryPaths(InstructionsGetDiscoveryPathsRequest request) { + return caller.invoke("instructions.getDiscoveryPaths", Objects.requireNonNull(request, "request"), InstructionsGetDiscoveryPathsResult.class); + } + + /** + * Drops the runtime's in-memory cache of discovered custom instructions so the next discovery reads them from disk again. A host calls this after it changes instruction files, or after it trusts a folder whose instructions an earlier discovery skipped, so that a session started afterwards sees the current files instead of a cached earlier answer. The call performs no I/O of its own and removes nothing from disk; it only invalidates, so the cost lands on the next discovery. Returns once the cache is clear, which is what lets a caller order a session start after it. Marked internal because it exists to carry a CLI call site off the napi boundary onto the SDK contract; it is migration plumbing, not a surface consumers are meant to depend on. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + CompletableFuture clearCache() { + return caller.invoke("instructions.clearCache", java.util.Map.of(), Void.class); + } + } 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 index a686df708c..666a1052e0 100644 --- 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 @@ -21,9 +21,123 @@ public final class ServerManagedSettingsApi { private final RpcCaller caller; + /** API methods for the {@code managedSettings.permissions} sub-namespace. */ + public final ServerManagedSettingsPermissionsApi permissions; + /** @param caller the RPC transport function */ ServerManagedSettingsApi(RpcCaller caller) { this.caller = caller; + this.permissions = new ServerManagedSettingsPermissionsApi(caller); + } + + /** + * Discovers device-managed settings the way the runtime's own device layer does, and reports the sandbox floor and the load outcome alongside the validated document. This differs from `managedSettings.read` in three ways a policy-enforcing host depends on: a document that validates to an empty object is reported as absent rather than as an empty policy, a discovery error does not suppress a document that still validates, and a failure is reported as a flag rather than only as error text. Hosts that compose the device layer into an effective policy use this, because `managedSettings.read` cannot distinguish "no device policy" from "device policy could not be read" and would silently hand the user's own layer the win. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + CompletableFuture readDevice() { + return caller.invoke("managedSettings.readDevice", java.util.Map.of(), ManagedSettingsReadDeviceResult.class); + } + + /** + * Retains the supplied managed layers as this runtime process's startup policy and re-applies the result to every live session. Hosts that own their own short-lived process policy use this, so a guarded mutation is checked against the policy the host actually fetched. Retaining is a whole-process replacement rather than a merge, so a host that already resolved a good policy must not retain a partial one over it. The failure flags are honest inputs rather than assumptions: reporting a fetch or load that failed keeps the layers already retained, and the interim fail-closed state, instead of clearing them. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + CompletableFuture retainPolicy(ManagedSettingsRetainPolicyParams params) { + return caller.invoke("managedSettings.retainPolicy", params, Void.class); + } + + /** + * Stamps an effective managed policy's individually-controlled entries onto a user settings document, returning the settings a host should actually act on. The managed layer wins on every plugin spec and marketplace name it names, and leaves everything it does not name as the user wrote it. Hosts use this rather than merging the two documents themselves, because the merge is per entry rather than per document and a whole-document overlay would either discard the user's unmanaged entries or let a user entry outrank a managed one. Omitting the managed layer returns the settings unchanged, which is what an unmanaged host wants. Rejects when either document is present but is not a JSON object rather than treating it as absent, since silently dropping a malformed policy hands the user's layer the win on exactly the entries the policy exists to control. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + CompletableFuture applyControls(ManagedSettingsApplyControlsParams params) { + return caller.invoke("managedSettings.applyControls", params, ManagedSettingsApplyControlsResult.class); + } + + /** + * Validates the managed settings a client is supplying for a session it is about to open, and returns them reduced to the keys a client is allowed to set. A client-supplied policy is not the same thing as an administrator's: it may only narrow permissions, so this accepts `permissions` and rejects every other key outright rather than ignoring it. Rejecting is the point — a host that silently dropped an unsupported key would report a policy as applied while running without it. Hosts call this before opening the session rather than letting the open fail, so a malformed policy is reported against the request that carried it. The runtime applies the same rule again when the session starts, so this cannot be used to smuggle a wider document past it. An accepted document that sets nothing comes back as an empty object, which is distinct from supplying nothing at all. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + CompletableFuture parseClientValue(ManagedSettingsParseClientValueParams params) { + return caller.invoke("managedSettings.parseClientValue", params, ManagedSettingsParseClientValueResult.class); + } + + /** + * Composes the supplied server, device/MDM, device sandbox floor and policy-helper layers into the single effective managed policy a host should enforce, resolving each key across the layers by the runtime's own precedence rules. Hosts that assemble managed layers themselves use this so their precedence matches the runtime's exactly instead of reimplementing it. The composition is not a pure function of its inputs: an advisory sandbox force-on that arrived over the server channel is declined when this host cannot run a sandbox, so the result reflects the runtime host's capabilities, and the returned settings carry `sandboxAdvisoryFloorDeclined: true` when that happened. Enforced channels (device/MDM and policy helper) are never declined, and a server force-on carrying `failIfUnavailable` is preserved so the session fails loudly instead of quietly running unsandboxed. Rejects when a supplied layer is present but is not a JSON object, rather than treating it as unmanaged, because silently dropping a malformed policy would hand the user's own layer the win on exactly the keys the policy exists to control. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + CompletableFuture resolveEffectiveValue(ManagedSettingsResolveEffectiveValueParams params) { + return caller.invoke("managedSettings.resolveEffectiveValue", params, ManagedSettingsResolveEffectiveValueResult.class); + } + + /** + * Decides what a host must do to its bypass-permissions state after a managed-policy resolution lands, given the state already in force and the account now signed in. A host holds two pieces of state — which layer disabled bypass mode, and for a server-delivered disable, the account it was pinned to — and cannot decide the next step from the new policy alone. Three rules make that so, and each one is a control rather than a preference. A device/MDM disable outranks the account, so a later server answer never lifts it. An unknown policy, which is what a failed fetch or a refusal this build cannot enforce yields, preserves whatever is in force instead of reverting to permissive. And a server disable is lifted only when the account it was pinned to is no longer the signed-in one, which is what makes signing in to an unrestricted account recover without a restart while a reconnect on the same restricted account does not. Hosts call this rather than reimplementing it because getting the pinning backwards either strands a disable on an account that never carried one or drops one that still applies. `setDisabledState` is omitted when the host must leave its stored flag untouched, which is distinct from being told to set it to false. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + CompletableFuture resolveBypassDisableTransition(ManagedSettingsResolveBypassDisableTransitionParams params) { + return caller.invoke("managedSettings.resolveBypassDisableTransition", params, ManagedSettingsResolveBypassDisableTransitionResult.class); + } + + /** + * Resolves managed (enterprise policy) settings for a host from every layer at once: the account's server-delivered policy, the device/MDM layer, and a platform policy helper. Hosts use this instead of fetching and composing the layers themselves, because the per-layer precedence and the fail-closed rules that apply when a layer cannot be confirmed are the runtime's own and must match the policy a session will later enforce. It does not retain or apply anything to process-global policy state, so a host may call it before any session exists — which is the point, since plugin activation and one-shot commands resolve policy before a session is available to carry it. The returned per-layer outcome flags are load-bearing rather than diagnostic: a host must distinguish an unmanaged device from a managed device whose policy could not be confirmed, and apply its fail-closed gates in the second case. Reports its diagnostics in `logMessages` rather than logging them, so a host that runs before a log sink exists still sees them. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + CompletableFuture ingest(ManagedSettingsIngestParams params) { + return caller.invoke("managedSettings.ingest", params, ManagedSettingsIngestResult.class); + } + + /** + * Resolves the managed (enterprise policy) settings a host must enforce from startup, and judges the account against the enterprise sign-in pin in the same pass. This composes the layers the way `managedSettings.ingest` does and then answers the two questions a host cannot answer for itself: which fail-closed floors apply when a layer could not be confirmed, and whether this credential is allowed to sign in at all. The sign-in verdict is the reason a host must not assemble this itself from `managedSettings.ingest` — a pinned organization is an access control, and a host that composed the layers but skipped the pin would let a developer sign in with a personal or another enterprise's account and escape every control that keys off the signed-in identity. The verdict is computed on every resolution, including one that is not retained, so declining to apply policy never widens access. Set `retainAndApply` to keep the resolved policy as this runtime process's startup policy; leave it false to resolve without side effects. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + CompletableFuture resolveStartupPolicy(ManagedSettingsResolveStartupPolicyParams params) { + return caller.invoke("managedSettings.resolveStartupPolicy", params, ManagedSettingsResolveStartupPolicyResult.class); + } + + /** + * Resolves the managed `remoteControl` policy for an account on its own, for a host deciding whether a remote-control session may start. Composes the same layers as `managedSettings.ingest` and returns only that one key, so a host does not have to know which layer won. An absent policy means the account is unmanaged for this capability and the host may proceed; that default is safe only while absence really does mean "no policy", so a service refusal that says an account policy exists which this client is too old to enforce resolves to an explicit disabled policy instead of to absence. A resolution that cannot be completed is rejected rather than answered as absence, for the same reason. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + CompletableFuture loadRemoteControl(ManagedSettingsLoadRemoteControlParams params) { + return caller.invoke("managedSettings.loadRemoteControl", params, ManagedSettingsLoadRemoteControlResult.class); + } + + /** + * Evaluates the device-channel enterprise sign-in pin (MDM plist, registry, or managed-settings file) against a credential, without resolving any server-delivered policy. This is the gate an entry point runs before it branches, and the only one that can refuse a developer's first sign-in: it is local, needs no credential, and a server-delivered pin only ever reaches accounts already inside the organization. It fails closed by construction — a discovery that cannot determine whether a pin exists refuses rather than allows, because a control that fails open under a triggerable condition is not a control. Hosts that also want the server channel should call `managedSettings.resolveStartupPolicy`, which folds both channels into one verdict. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + CompletableFuture resolveDeviceForceLoginOrgsGate(ManagedSettingsResolveDeviceForceLoginOrgsGateParams params) { + return caller.invoke("managedSettings.resolveDeviceForceLoginOrgsGate", params, ForceLoginOrgsDecision.class); } /** diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerManagedSettingsPermissionsApi.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerManagedSettingsPermissionsApi.java new file mode 100644 index 0000000000..11526bde65 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerManagedSettingsPermissionsApi.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.permissions} namespace. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public final class ServerManagedSettingsPermissionsApi { + + private final RpcCaller caller; + + /** @param caller the RPC transport function */ + ServerManagedSettingsPermissionsApi(RpcCaller caller) { + this.caller = caller; + } + + /** + * Pure, sessionless evaluation of operations against a supplied permissionsContext. Performs no policy discovery, I/O, prompting, navigation, permission grants, or sandbox changes. Obtain the context from managedSettings.resolve or session.managedSettings.get, or reuse an in-memory context. Results preserve input order and duplicates. Consumers must block deny, obtain approval for ask, and use their normal permission flow for unmanaged. Unknown policy returns failClosed with every operation denied. This is not a substitute for user permissions or other security controls. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + public CompletableFuture evaluate(ManagedSettingsPermissionsEvaluateParams params) { + return caller.invoke("managedSettings.permissions.evaluate", params, ManagedSettingsPermissionsEvaluateResult.class); + } + +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerMcpApi.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerMcpApi.java index 553a644214..c35de551fc 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerMcpApi.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerMcpApi.java @@ -24,6 +24,8 @@ public final class ServerMcpApi { /** API methods for the {@code mcp.config} sub-namespace. */ public final ServerMcpConfigApi config; + /** API methods for the {@code mcp.registry} sub-namespace. */ + final ServerMcpRegistryApi registry; /** API methods for the {@code mcp.installations} sub-namespace. */ public final ServerMcpInstallationsApi installations; @@ -31,6 +33,7 @@ public final class ServerMcpApi { ServerMcpApi(RpcCaller caller) { this.caller = caller; this.config = new ServerMcpConfigApi(caller); + this.registry = new ServerMcpRegistryApi(caller); this.installations = new ServerMcpInstallationsApi(caller); } @@ -45,6 +48,28 @@ public CompletableFuture discover(McpDiscoverParams params) { return caller.invoke("mcp.discover", params, McpDiscoverResult.class); } + /** + * Lists the MCP servers the runtime supplies itself, so a host that disables the built-ins wholesale suppresses exactly the set the runtime would otherwise add. The names are fixed for a build and depend on no configuration, session or account. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + CompletableFuture listBuiltinServerNames() { + return caller.invoke("mcp.listBuiltinServerNames", java.util.Map.of(), McpListBuiltinServerNamesResult.class); + } + + /** + * Reports whether the gh-replaceable GitHub MCP tools may be clipped for a session, which a host feeds back as the `excludeGhReplaceableTools` build option. The tools are redundant only when the session can reach a shell *and* the host has the `gh` that would replace them, so both halves are decided here. The shell half runs the platform shell tool through the runtime's own tool-filter matcher, so entry forms like `builtin:bash` and `builtin:*` behave exactly as they do when a session builds its tool catalog. The host half probes for `gh`, and is skipped entirely when the filters already rule the shell out. Host presence alone is not enough: a session restricted to, say, `view` would otherwise lose the built-in issue-read fallback while having no shell to replace it with. The answer describes the host the runtime runs on, so it is never forwarded to a remote engine. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + CompletableFuture shouldExcludeGitHubTools(McpShouldExcludeGitHubToolsParams params) { + return caller.invoke("mcp.shouldExcludeGitHubTools", params, McpShouldExcludeGitHubToolsResult.class); + } + /** * 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. * diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerMcpRegistryApi.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerMcpRegistryApi.java new file mode 100644 index 0000000000..26b7b2cc7f --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerMcpRegistryApi.java @@ -0,0 +1,62 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.github.copilot.CopilotExperimental; +import java.util.concurrent.CompletableFuture; +import javax.annotation.processing.Generated; + +/** + * API methods for the {@code mcp.registry} namespace. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +final class ServerMcpRegistryApi { + + private final RpcCaller caller; + + /** @param caller the RPC transport function */ + ServerMcpRegistryApi(RpcCaller caller) { + this.caller = caller; + } + + /** + * Allocates an ID for one cancellable MCP registry search. The ID exists before the search starts, so callers can cancel before it starts. The networking stack supplies the cancellation namespace. The runtime retains at most 1,024 unused IDs. At capacity, another allocation can reclaim an unused ID. Active searches retain their IDs until they finish. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + CompletableFuture allocateRequestId() { + return caller.invoke("mcp.registry.allocateRequestId", java.util.Map.of(), McpRegistryRequestIdResult.class); + } + + /** + * Searches the MCP registry the supplied credential may read, resolving the registry endpoint from policy first. Hosts usually send credential-free `AuthIdentity`; a `token` identity can be resolved only when it embeds a token, while `env` and `gh-cli` identities can use a token embedded in the request first. The runtime follows registry pages, keeps the newest entry per server name, cuts the list to `limit`, and sorts an empty-query result by GitHub stars. Each server object is carried opaquely. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + CompletableFuture search(McpRegistrySearchParams params) { + return caller.invoke("mcp.registry.search", params, McpRegistrySearchResult.class); + } + + /** + * Abandons the registry search that uses the given request ID. It acts only on IDs from `mcp.registry.allocateRequestId`, so it never cancels another component's request. Answers `canceled: true` when it stops a running search. Answers `canceled: false` for unknown or reclaimed IDs, completed or canceled searches, and unused reservations. It releases an unused reservation, so a later search with that ID is refused. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + CompletableFuture cancel(McpRegistryCancelParams params) { + return caller.invoke("mcp.registry.cancel", params, McpRegistryCancelResult.class); + } + +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerPermissionsApi.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerPermissionsApi.java new file mode 100644 index 0000000000..7e1c94d468 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerPermissionsApi.java @@ -0,0 +1,47 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) 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 permissions} namespace. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +final class ServerPermissionsApi { + + private final RpcCaller caller; + + /** API methods for the {@code permissions.locations} sub-namespace. */ + final ServerPermissionsLocationsApi locations; + /** API methods for the {@code permissions.folderTrust} sub-namespace. */ + final ServerPermissionsFolderTrustApi folderTrust; + + /** @param caller the RPC transport function */ + ServerPermissionsApi(RpcCaller caller) { + this.caller = caller; + this.locations = new ServerPermissionsLocationsApi(caller); + this.folderTrust = new ServerPermissionsFolderTrustApi(caller); + } + + /** + * Interprets a verbatim managed `permissions` slice into the three policy questions a host asks before granting elevated permissions: whether bypass-permissions mode is forbidden, whether a policy exists that this build could not determine, and whether the layer supplies permission rules the session must enforce. An absent or empty slice means no managed policy applies. The runtime and its permission service share one policy type, so a new managed permission setting is interpreted in exactly one place; a host that reimplements the reading would silently diverge the first time that set grows. Hosts must fail closed on an undetermined policy rather than treating it as permission granted. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + CompletableFuture evaluateManagedPolicy(PermissionsEvaluateManagedPolicyParams params) { + return caller.invoke("permissions.evaluateManagedPolicy", params, PermissionsEvaluateManagedPolicyResult.class); + } + +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerPermissionsFolderTrustApi.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerPermissionsFolderTrustApi.java new file mode 100644 index 0000000000..f27b5f0ed1 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerPermissionsFolderTrustApi.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 permissions.folderTrust} namespace. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +final class ServerPermissionsFolderTrustApi { + + private final RpcCaller caller; + + /** @param caller the RPC transport function */ + ServerPermissionsFolderTrustApi(RpcCaller caller) { + this.caller = caller; + } + + /** + * Reports whether a folder is trusted, reading the persisted trusted-folders list directly rather than through a session. Use this from a host that has no session yet, such as a one-shot command deciding whether to honor a checkout's own settings. It differs from the session-scoped check in two ways that matter: it takes the configuration directory to read the list from, instead of inheriting the session's, and it answers only from the persisted list, so trust a user granted for the lifetime of one session is not visible here. A host that has a session should prefer the session-scoped check, which also covers session-only trust. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + CompletableFuture checkTrust(PermissionsFolderTrustCheckTrustParams params) { + return caller.invoke("permissions.folderTrust.checkTrust", params, FolderTrustCheckResult.class); + } + +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerPermissionsLocationsApi.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerPermissionsLocationsApi.java new file mode 100644 index 0000000000..7b23a73b3f --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerPermissionsLocationsApi.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 permissions.locations} namespace. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +final class ServerPermissionsLocationsApi { + + private final RpcCaller caller; + + /** @param caller the RPC transport function */ + ServerPermissionsLocationsApi(RpcCaller caller) { + this.caller = caller; + } + + /** + * Drops stored per-location permission grants whose directory no longer exists, and reports which location keys were removed. Runs against the permission store under the runtime's own Copilot home, the same store `session.permissions.locations.resolve` reads. Marked internal because it exists to carry a CLI call site off the napi boundary onto the SDK contract; it is migration plumbing, not a surface consumers are meant to depend on. It can become public once an SDK consumer needs to prune its own permission store. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + CompletableFuture cleanupOrphaned() { + return caller.invoke("permissions.locations.cleanupOrphaned", java.util.Map.of(), LocationPermissionsCleanupResult.class); + } + +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerPersistenceApi.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerPersistenceApi.java new file mode 100644 index 0000000000..a98b036d8a --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerPersistenceApi.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 persistence} namespace. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +final class ServerPersistenceApi { + + private final RpcCaller caller; + + /** @param caller the RPC transport function */ + ServerPersistenceApi(RpcCaller caller) { + this.caller = caller; + } + + /** + * Atomically replaces a JSON file's entire contents while holding the runtime's per-path lock. The lock lives in the runtime process, so a host that writes these files itself takes no part in it and can tear a concurrent runtime write; route the write here instead. Unlike a merging write this never reads or parses the existing file, so a previously corrupted snapshot is replaced wholesale rather than failing every later write. Use it only for callers that always persist a complete snapshot. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + CompletableFuture writeRawJsonLocked(PersistenceWriteRawJsonLockedParams params) { + return caller.invoke("persistence.writeRawJsonLocked", params, PersistenceWriteRawJsonLockedResult.class); + } + +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerRepoApi.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerRepoApi.java new file mode 100644 index 0000000000..3db04fe14a --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerRepoApi.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 java.util.concurrent.CompletableFuture; +import javax.annotation.processing.Generated; + +/** + * API methods for the {@code repo} namespace. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +final class ServerRepoApi { + + private final RpcCaller caller; + + /** API methods for the {@code repo.settings} sub-namespace. */ + final ServerRepoSettingsApi settings; + + /** @param caller the RPC transport function */ + ServerRepoApi(RpcCaller caller) { + this.caller = caller; + this.settings = new ServerRepoSettingsApi(caller); + } + +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerRepoSettingsApi.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerRepoSettingsApi.java new file mode 100644 index 0000000000..247545adbc --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerRepoSettingsApi.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.github.copilot.CopilotExperimental; +import java.util.concurrent.CompletableFuture; +import javax.annotation.processing.Generated; + +/** + * API methods for the {@code repo.settings} namespace. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +final class ServerRepoSettingsApi { + + private final RpcCaller caller; + + /** @param caller the RPC transport function */ + ServerRepoSettingsApi(RpcCaller caller) { + this.caller = caller; + } + + /** + * Atomically edits one allowed top-level repository setting. Reads the raw file, preserves unrelated keys, applies one scalar or list edit, validates the result and writes under one file lock, so concurrent sibling or list edits are not lost. A malformed or unsafe existing file returns `invalid` without a write; rejected keys, invalid values and I/O errors reject the call. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + CompletableFuture edit(RepoSettingsEditParams params) { + return caller.invoke("repo.settings.edit", params, SettingsEditResult.class); + } + + /** + * Reads one repository settings file without settings normalization or layering. The existing JSONC parser accepts comments and trailing commas and applies its prototype-safety rules. Missing or blank files return an empty valid config. Invalid files return an empty config with invalid=true; other read failures reject. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + CompletableFuture readRaw(RepoSettingsReadRawParams params) { + return caller.invoke("repo.settings.readRaw", params, RepoSettingsReadRawResult.class); + } + + /** + * Writes or removes one allowed top-level repository setting through the existing validator and file lock. Preserves unrelated stored keys. Invalid keys, values, existing documents, and I/O errors reject without replacing the file. Deletion requires delete=true; otherwise value must be present. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + CompletableFuture writeKey(RepoSettingsWriteKeyParams params) { + return caller.invoke("repo.settings.writeKey", params, Void.class); + } + + /** + * Replaces the repository-overridable settings in one locked write. Allowed keys present in settings are validated and written; allowed keys absent from settings are removed. Other input keys are ignored, and unknown keys already stored in the file remain unchanged. Invalid values or an invalid existing file reject without a partial write. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + CompletableFuture writeAll(RepoSettingsWriteAllParams params) { + return caller.invoke("repo.settings.writeAll", params, Void.class); + } + +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerRpc.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerRpc.java index 99f65a5f89..07f368980b 100644 --- a/java/sdk/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,20 @@ public final class ServerRpc { private final RpcCaller caller; + /** API methods for the {@code sandbox} namespace. */ + public final ServerSandboxApi sandbox; + /** API methods for the {@code persistence} namespace. */ + final ServerPersistenceApi persistence; + /** API methods for the {@code user} namespace. */ + public final ServerUserApi user; + /** API methods for the {@code repo} namespace. */ + final ServerRepoApi repo; + /** API methods for the {@code featureFlags} namespace. */ + final ServerFeatureFlagsApi featureFlags; + /** API methods for the {@code permissions} namespace. */ + final ServerPermissionsApi permissions; + /** API methods for the {@code managedSettings} namespace. */ + public final ServerManagedSettingsApi managedSettings; /** API methods for the {@code environments} namespace. */ public final ServerEnvironmentsApi environments; /** API methods for the {@code host} namespace. */ @@ -33,8 +47,8 @@ public final class ServerRpc { public final ServerHooksApi hooks; /** API methods for the {@code models} namespace. */ public final ServerModelsApi models; - /** API methods for the {@code sandbox} namespace. */ - public final ServerSandboxApi sandbox; + /** API methods for the {@code contentExclusion} namespace. */ + public final ServerContentExclusionApi contentExclusion; /** API methods for the {@code tools} namespace. */ public final ServerToolsApi tools; /** API methods for the {@code account} namespace. */ @@ -45,6 +59,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 instructions} namespace. */ + public final ServerInstructionsApi instructions; /** API methods for the {@code skills} namespace. */ public final ServerSkillsApi skills; /** API methods for the {@code catalog} namespace. */ @@ -53,22 +69,16 @@ public final class ServerRpc { public final ServerPluginsApi plugins; /** API methods for the {@code agents} namespace. */ public final ServerAgentsApi agents; - /** API methods for the {@code instructions} namespace. */ - public final ServerInstructionsApi instructions; /** API methods for the {@code globalState} namespace. */ final ServerGlobalStateApi globalState; /** API methods for the {@code commands} namespace. */ public final ServerCommandsApi commands; - /** API methods for the {@code user} namespace. */ - public final ServerUserApi user; /** API methods for the {@code gitHubRepository} namespace. */ final ServerGitHubRepositoryApi gitHubRepository; /** API methods for the {@code gitHubOwners} namespace. */ final ServerGitHubOwnersApi gitHubOwners; /** API methods for the {@code git} namespace. */ final ServerGitApi git; - /** 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. */ @@ -89,28 +99,33 @@ public final class ServerRpc { */ public ServerRpc(RpcCaller caller) { this.caller = caller; + this.sandbox = new ServerSandboxApi(caller); + this.persistence = new ServerPersistenceApi(caller); + this.user = new ServerUserApi(caller); + this.repo = new ServerRepoApi(caller); + this.featureFlags = new ServerFeatureFlagsApi(caller); + this.permissions = new ServerPermissionsApi(caller); + this.managedSettings = new ServerManagedSettingsApi(caller); this.environments = new ServerEnvironmentsApi(caller); this.host = new ServerHostApi(caller); this.hooks = new ServerHooksApi(caller); this.models = new ServerModelsApi(caller); - this.sandbox = new ServerSandboxApi(caller); + this.contentExclusion = new ServerContentExclusionApi(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.instructions = new ServerInstructionsApi(caller); this.skills = new ServerSkillsApi(caller); this.catalog = new ServerCatalogApi(caller); this.plugins = new ServerPluginsApi(caller); this.agents = new ServerAgentsApi(caller); - this.instructions = new ServerInstructionsApi(caller); this.globalState = new ServerGlobalStateApi(caller); this.commands = new ServerCommandsApi(caller); - this.user = new ServerUserApi(caller); this.gitHubRepository = new ServerGitHubRepositoryApi(caller); this.gitHubOwners = new ServerGitHubOwnersApi(caller); this.git = new ServerGitApi(caller); - this.managedSettings = new ServerManagedSettingsApi(caller); this.runtime = new ServerRuntimeApi(caller); this.sessionFs = new ServerSessionFsApi(caller); this.llmInference = new ServerLlmInferenceApi(caller); diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerSandboxApi.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerSandboxApi.java index 1484140f6a..2db9adfedd 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerSandboxApi.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerSandboxApi.java @@ -23,11 +23,138 @@ public final class ServerSandboxApi { /** API methods for the {@code sandbox.proxyCa} sub-namespace. */ public final ServerSandboxProxyCaApi proxyCa; + /** API methods for the {@code sandbox.proxySecret} sub-namespace. */ + final ServerSandboxProxySecretApi proxySecret; + /** API methods for the {@code sandbox.settings} sub-namespace. */ + final ServerSandboxSettingsApi settings; /** @param caller the RPC transport function */ ServerSandboxApi(RpcCaller caller) { this.caller = caller; this.proxyCa = new ServerSandboxProxyCaApi(caller); + this.proxySecret = new ServerSandboxProxySecretApi(caller); + this.settings = new ServerSandboxSettingsApi(caller); + } + + /** + * Raises a user's sandbox settings to an administrator's managed sandbox floor and returns the result a session should actually run under. The floor can only tighten: an enterprise force-on is reflected even when the user's own `enabled` is false, and a user cannot lower a setting the floor raised. The result is normalized through the declared sandbox shape, so a key reorder or an undeclared passthrough key is not mistaken for a policy change by callers that diff two results. A host must call this rather than merge the two layers itself, because reimplementing the direction of the fold is what turns an administrator's floor into a suggestion. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + CompletableFuture resolveFlooredConfig(SandboxResolveFlooredConfigParams params) { + return caller.invoke("sandbox.resolveFlooredConfig", params, SandboxResolveFlooredConfigResult.class); + } + + /** + * Builds the effective filesystem policy one `@`-file picker checks its suggestions against, so Git discovery and grant canonicalization run once for the picker's lifetime instead of on every debounced keystroke. Returns no policy when the sandbox is absent or disabled, which the paired filter reads as "keep every candidate". A host must pair this with `sandbox.filterPickerCandidates` rather than resolve a grant list itself, because the picker's grants are narrower than a file tool's — the same path a built-in tool may read can be one the picker must not offer. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + CompletableFuture preparePickerPolicy(SandboxPreparePickerPolicyParams params) { + return caller.invoke("sandbox.preparePickerPolicy", params, SandboxPreparePickerPolicyResult.class); + } + + /** + * Checks a batch of `@`-file picker suggestions against a policy prepared by `sandbox.preparePickerPolicy` and returns one allow decision per candidate, positionally aligned with the request. Batched deliberately: each check canonicalizes a path component-by-component through the real filesystem, so one call amortizes that over a single hop rather than dozens while the user types. A directory the policy does not itself allow can still be kept as a waypoint to an allowed subtree. An absent policy keeps every candidate. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + CompletableFuture filterPickerCandidates(SandboxFilterPickerCandidatesParams params) { + return caller.invoke("sandbox.filterPickerCandidates", params, SandboxFilterPickerCandidatesResult.class); + } + + /** + * The sandbox a single named feature actually launches under, which is not the same as the session's sandbox. A feature is routed out of the sandbox by its own flag — `sandboxMcpServers`, `sandboxLspServers` — so an enabled session can still start one server confined and another free. Returns a disabled sandbox rather than an error when the host has no sandbox slice at all, so an absent config and an opted-out feature read alike at the launch site. A host must ask per feature rather than reuse the session config, because reading only `enabled` confines a server the user opted out of, or reports as sandboxed one that is not. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + CompletableFuture resolveForOptional(SandboxResolveForOptionalParams params) { + return caller.invoke("sandbox.resolveForOptional", params, SandboxEffectiveForOptionalResult.class); + } + + /** + * Reports whether this machine can run an OS-level command sandbox at all. A host must ask before it offers sandboxing, because the answer is a property of the machine rather than of any setting: on Linux it depends on bubblewrap being installed, and on Windows on the OS build supporting BaseContainer. Probing it requires spawning a process the first time, so the answer is cached for the life of the runtime. A host that assumes support and enables the sandbox anyway produces a session in which every shell command fails. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + CompletableFuture checkPlatformSupport() { + return caller.invoke("sandbox.checkPlatformSupport", java.util.Map.of(), Boolean.class); + } + + /** + * Reports whether this Windows host's BaseContainer implementation can reach the host's loopback interface directly, which is what a local-network policy needs to avoid routing through the egress proxy. Like `sandbox.checkPlatformSupport`, the answer is a property of the machine's OS build rather than of any setting, and resolving it the first time spawns a platform probe. A host that offers the direct local-network option on a machine that lacks the capability produces a session whose sandboxed commands cannot reach local services. Always false off Windows. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + CompletableFuture checkWindowsHostLoopbackSupport() { + return caller.invoke("sandbox.checkWindowsHostLoopbackSupport", java.util.Map.of(), Boolean.class); + } + + /** + * The explanation a host shows when it refuses an explicit request to turn the sandbox on for a machine that cannot run one. Carries the platform-specific remedy — install bubblewrap, update Windows — and the documentation link, so a host that got `false` from `sandbox.checkPlatformSupport` does not have to compose its own reason and cannot describe the same machine differently from the runtime's own spawn path. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + CompletableFuture getUnsupportedMessage() { + return caller.invoke("sandbox.getUnsupportedMessage", java.util.Map.of(), String.class); + } + + /** + * The notice a host shows when a stored `sandbox` setting is being ignored for this session because the machine cannot enforce it. Distinct from `sandbox.getUnsupportedMessage`, which refuses an explicit request: this one reports that a setting the user already saved is inert, states that commands will run unsandboxed, and confirms the stored setting itself is left alone. A host that stays silent here leaves the user believing a sandbox is active. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + CompletableFuture getUnsupportedSettingDisabledNotice() { + return caller.invoke("sandbox.getUnsupportedSettingDisabledNotice", java.util.Map.of(), String.class); + } + + /** + * The warning a host prints at startup when a session asked for a sandbox on a machine that cannot run one. The third of the unsupported notices, and the only one that depends on the policy in force: it names the remedy the reader can actually act on, which is the stored setting for an unmanaged host, an administrator for a managed one, and waiting for the policy to resolve when the sandbox stayed on only because the policy could not be determined. Returns no notice when the machine does support a sandbox, so a host can call it unconditionally on the startup path. A host that composes its own warning tells a managed user to change a setting their policy will overwrite. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + CompletableFuture getUnsupportedStartupNotice(SandboxGetUnsupportedStartupNoticeParams params) { + return caller.invoke("sandbox.getUnsupportedStartupNotice", params, SandboxUnsupportedStartupNoticeResult.class); + } + + /** + * Whether one slash command stays reachable while sandbox enforcement is unavailable and the session is otherwise blocked. The permitted set is deliberately narrow — commands that only report state, and the few that let the user repair or leave the situation — and it is keyed on the arguments as well as the name, so `/sandbox status` is admitted where `/sandbox enable` is not. A host must ask rather than keep its own list: a command this returns false for is one the runtime would refuse anyway, and a divergent list either strands the user with no way out or admits a command that acts on an unenforced session. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + CompletableFuture checkEnforcementCommand(SandboxCheckEnforcementCommandParams params) { + return caller.invoke("sandbox.checkEnforcementCommand", params, Boolean.class); + } + + /** + * Returns the documentation URL a host should point a user at when it declines or downgrades a sandbox setting. The runtime owns the address so every surface cites the same page and a host does not hard-code a link that later moves. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + CompletableFuture getDocsUrl() { + return caller.invoke("sandbox.getDocsUrl", java.util.Map.of(), SandboxDocsUrlResult.class); } /** diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerSandboxProxyCaApi.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerSandboxProxyCaApi.java index 0cc59738e9..5559a0dbcb 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerSandboxProxyCaApi.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerSandboxProxyCaApi.java @@ -26,6 +26,28 @@ public final class ServerSandboxProxyCaApi { this.caller = caller; } + /** + * Reports whether this runtime process can offer installing the sandbox credential-proxy certificate authority into OS trust. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + public CompletableFuture checkInstallationSupport() { + return caller.invoke("sandbox.proxyCa.checkInstallationSupport", java.util.Map.of(), Boolean.class); + } + + /** + * Reports whether this sandbox configuration uses the credential proxy and can need certificate-authority setup. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + public CompletableFuture checkRelevance(SandboxProxyCaCheckRelevanceParams params) { + return caller.invoke("sandbox.proxyCa.checkRelevance", params, Boolean.class); + } + /** * Reports whether the persistent certificate authority of the sandbox credential proxy exists, whether OS trust includes it, and whether it must be rotated. Changes nothing. * diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerSandboxProxySecretApi.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerSandboxProxySecretApi.java new file mode 100644 index 0000000000..1432755fac --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerSandboxProxySecretApi.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.github.copilot.CopilotExperimental; +import java.util.concurrent.CompletableFuture; +import javax.annotation.processing.Generated; + +/** + * API methods for the {@code sandbox.proxySecret} namespace. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +final class ServerSandboxProxySecretApi { + + private final RpcCaller caller; + + /** @param caller the RPC transport function */ + ServerSandboxProxySecretApi(RpcCaller caller) { + this.caller = caller; + } + + /** + * Reports where this runtime keeps the sandbox proxy password: the credential store directory and the entry identifier within it. Both are derived from the configuration directory this runtime was started with, so a host does not resolve them itself. Read this to recognize a `${secret:…}` value in stored sandbox settings as this runtime's own proxy password rather than a reference the user wrote by hand. Marked internal because it exists to carry a CLI call site off the napi boundary onto the SDK contract. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + CompletableFuture getLocation() { + return caller.invoke("sandbox.proxySecret.getLocation", java.util.Map.of(), SandboxProxySecretLocation.class); + } + + /** + * Reads the stored sandbox proxy password from the credential store `sandbox.proxySecret.getLocation` names, preferring the OS keychain and falling back to the on-disk store. Reports an absent value rather than failing when no entry exists. Marked internal because it exists to carry a CLI call site off the napi boundary onto the SDK contract. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + CompletableFuture get() { + return caller.invoke("sandbox.proxySecret.get", java.util.Map.of(), SandboxProxySecretGetResult.class); + } + + /** + * Stores the sandbox proxy password in the credential store `sandbox.proxySecret.getLocation` names, replacing any existing entry. Reports whether the OS keychain accepted it or it fell back to the on-disk store. Marked internal because it exists to carry a CLI call site off the napi boundary onto the SDK contract. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + CompletableFuture save(SandboxProxySecretSaveParams params) { + return caller.invoke("sandbox.proxySecret.save", params, SandboxProxySecretSaveResult.class); + } + + /** + * Removes the stored sandbox proxy password from the credential store `sandbox.proxySecret.getLocation` names. Succeeds when no entry exists, so it is safe to call while clearing a proxy password whose origin is unknown. Marked internal because it exists to carry a CLI call site off the napi boundary onto the SDK contract. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + CompletableFuture delete() { + return caller.invoke("sandbox.proxySecret.delete", java.util.Map.of(), Void.class); + } + +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerSandboxSettingsApi.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerSandboxSettingsApi.java new file mode 100644 index 0000000000..4d323a03ac --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerSandboxSettingsApi.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.databind.JsonNode; +import com.github.copilot.CopilotExperimental; +import java.util.concurrent.CompletableFuture; +import javax.annotation.processing.Generated; + +/** + * API methods for the {@code sandbox.settings} namespace. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +final class ServerSandboxSettingsApi { + + private final RpcCaller caller; + + /** @param caller the RPC transport function */ + ServerSandboxSettingsApi(RpcCaller caller) { + this.caller = caller; + } + + /** + * Resolves a stored sandbox settings object into the effective one, filling every value the user left unset with its default. Read this to render or reason about the sandbox configuration a session would actually run under, rather than the sparse document the user wrote. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + CompletableFuture applyDefaults(SandboxSettingsApplyDefaultsParams params) { + return caller.invoke("sandbox.settings.applyDefaults", params, JsonNode.class); + } + +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerSkillsApi.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerSkillsApi.java index 30b35346bb..cd229d5943 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerSkillsApi.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerSkillsApi.java @@ -8,6 +8,7 @@ package com.github.copilot.generated.rpc; import com.github.copilot.CopilotExperimental; +import java.util.Objects; import java.util.concurrent.CompletableFuture; import javax.annotation.processing.Generated; @@ -44,6 +45,17 @@ public CompletableFuture planInstall(SkillsPl return caller.invoke("skills.planInstall", params, SkillInstallationManagementResult.class); } + /** + * Plans installation of the Skill pinned by an Agent Finder materialisation reference without writing files. The runtime retrieves the descriptor only from its fixed Agent Finder authority and applies the same verification as skills.planInstall; the review shows only verified data. Apply the returned plan with skills.applyInstall. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + public CompletableFuture planInstallFromReference(SkillsPlanInstallFromReferenceParams params) { + return caller.invoke("skills.planInstallFromReference", params, SkillInstallationManagementResult.class); + } + /** * Consumes one verified Skill installation plan, requests explicit human consent through installations.confirm on the original connection, then revalidates and installs the Skill disabled. * @@ -99,4 +111,17 @@ public CompletableFuture getDiscoveryPaths(Skills return caller.invoke("skills.getDiscoveryPaths", params, SkillsGetDiscoveryPathsResult.class); } + /** + * Returns the canonical directories where a client may create skills that the runtime will recognize, including ones that do not exist yet. Project directories become active once created. + *

+ * Accepts the extensible request, including inputs added after the params record. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + public CompletableFuture getDiscoveryPaths(SkillsGetDiscoveryPathsRequest request) { + return caller.invoke("skills.getDiscoveryPaths", Objects.requireNonNull(request, "request"), SkillsGetDiscoveryPathsResult.class); + } + } diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerUserSettingsApi.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerUserSettingsApi.java index 9c5656ff14..75c8e38934 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerUserSettingsApi.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerUserSettingsApi.java @@ -7,6 +7,7 @@ package com.github.copilot.generated.rpc; +import com.fasterxml.jackson.databind.JsonNode; import com.github.copilot.CopilotExperimental; import java.util.concurrent.CompletableFuture; import javax.annotation.processing.Generated; @@ -26,6 +27,160 @@ public final class ServerUserSettingsApi { this.caller = caller; } + /** + * Adds or removes one hook key in the user `disabledHooks` setting, rewriting settings.json in place and leaving every other setting untouched. Reports a typed outcome rather than failing the call, so a host can tell an unparseable settings file apart from a write that could not complete. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + CompletableFuture setHookDisabled(UserSettingsSetHookDisabledParams params) { + return caller.invoke("user.settings.setHookDisabled", params, UserSettingsSetHookDisabledResult.class); + } + + /** + * Reads the user's settings document exactly as `user.settings.load` does, but from a caller-supplied configuration directory instead of the one the server resolved for itself. Use this when a consumer scopes a session to its own Copilot home — the SDK's per-session `configDir` override — so the settings read match the directory that session actually uses. An absent or empty `configDir` resolves the server's own home, making this identical to `user.settings.load`. Hook-validation relaxations are deliberately not caller-supplied: they continue to come from the server's environment, so a consumer cannot widen the host's hook policy through this method. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + CompletableFuture loadForConfigDir(UserSettingsLoadForConfigDirParams params) { + return caller.invoke("user.settings.loadForConfigDir", params, JsonNode.class); + } + + /** + * Reads a repository's checked-in settings document as stored, normalized, together with any diagnostic raised while reading it. This is the repository half of the layering `user.settings.get` reports: `user.settings.load` reads the user's own document, and this reads the one a repository checks in to govern the keys `user.settings.governanceKeys` lists as repo-governable. A document that fails to parse, or that sets a key a repository may not govern, is reported through `warning` while the settings that did load are still returned, so a consumer surfaces the diagnostic and continues rather than failing the session. When `repoRoot` is the user's home directory the Claude-compatible configuration is skipped, so a home-directory session does not inherit a personal config as repository policy. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + CompletableFuture loadRepo(UserSettingsLoadRepoParams params) { + return caller.invoke("user.settings.loadRepo", params, UserSettingsLoadRepoResult.class); + } + + /** + * Reads a repository's settings and merges them over a supplied user layer in one step, returning the settings a consumer should act on inside that repository. Use this rather than reading the two layers separately: the merge is per setting and collection-aware, so a repository adds to the collections it is allowed to influence without replacing the user's own entries, and a whole-document overlay would get that wrong. Any diagnostic raised while reading the repository layer is returned rather than printed, so the consumer decides how to surface it; the settings that did load are merged regardless. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + CompletableFuture loadAndMergeRepo(UserSettingsLoadAndMergeRepoParams params) { + return caller.invoke("user.settings.loadAndMergeRepo", params, UserSettingsLoadAndMergeRepoResult.class); + } + + /** + * Reports the on-disk path of the settings document `user.settings.load` and `user.settings.set` read and write. Resolved the same way those methods resolve it, so a host that shows or opens the settings file names the file the runtime actually uses, including the extensionless legacy `settings` file when that is what is present. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + CompletableFuture getPath() { + return caller.invoke("user.settings.getPath", java.util.Map.of(), UserSettingsPathResult.class); + } + + /** + * Reads the user's stored settings together with the diagnostic raised while reading them. This is `user.settings.load` plus the reason part of the document was ignored: that one answers the document alone, so a caller cannot tell a clean read from a salvaged one. Read this when the caller is about to write the file back, where accepting a partial parse would discard every value the loader could not read. The settings that did load are still reported alongside the warning, so a consumer may surface it and continue instead of failing. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + CompletableFuture loadWithWarning() { + return caller.invoke("user.settings.loadWithWarning", java.util.Map.of(), UserSettingsLoadWithWarningResult.class); + } + + /** + * Folds a user settings layer over a managed (enterprise policy) layer and returns the merged settings. The managed layer's governance keys win, `autoTier` resolves through its overridable form, and the two per-entry control maps (`enabledPlugins`, `extraKnownMarketplaces`) union across the layers and are then stamped back from the managed layer, so each entry an administrator declares wins while the user keeps control of the rest. The parameters are named for their layers rather than being a generic base/override pair: nothing about a settings object identifies which layer produced it, so the runtime cannot detect a transposed call, and transposing one resolves the governance keys in the user's favour instead of the administrator's. Supplying only one layer returns it unchanged, and supplying neither returns an empty object. Rejects a managed `autoTier` that is neither a user-configurable tier nor a single-key overridable form, rather than silently dropping it. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + CompletableFuture mergeManagedAndUser(UserSettingsMergeManagedAndUserParams params) { + return caller.invoke("user.settings.mergeManagedAndUser", params, UserSettingsMergeManagedAndUserResult.class); + } + + /** + * Folds a repository's settings layer into the user layer. Each repo-overridable key carries a directional policy chosen so a repository can only add restrictions and never relax the user's own. The two per-entry managed control maps are the exception those directional rules cannot express — a repository may legitimately enable plugins and declare marketplaces — so they are re-stamped afterwards from the runtime's ambient managed policy. That makes the result depend on the runtime process's managed state and not on the arguments alone: a host must call this rather than reimplement the fold, or a repository could relax a restriction the administrator imposed. Rejects when either layer is not a JSON object. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + CompletableFuture mergeRepoValue(UserSettingsMergeRepoValueParams params) { + return caller.invoke("user.settings.mergeRepoValue", params, UserSettingsMergeRepoValueResult.class); + } + + /** + * Runs the one-time migration that moves a staff account's update channel to stable, and records that it ran. Idempotent and self-gating: it takes a lock over the global-state file, re-reads the completion stamp inside it, and returns `current` rather than migrating twice, so two hosts racing at startup cannot both migrate. Only a `migrated` status means the persisted settings changed and the caller should reload them. An account on the experimental channel is deliberately left alone and reported as `excluded-experimental`. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + CompletableFuture migrateStaffUpdateChannelOnce(UserSettingsMigrateStaffUpdateChannelOnceParams params) { + return caller.invoke("user.settings.migrateStaffUpdateChannelOnce", params, UserSettingsMigrateStaffUpdateChannelOnceResult.class); + } + + /** + * Runs the one-time reset that clears a staff account's pinned model selection, and reports the timestamp the host should record. Idempotent and self-gating: the reset is gated on a fixed cutoff, so a caller that passes back the timestamp it recorded gets `didReset: false` rather than a second reset. It applies to staff accounts only; any other account is reported as not reset without the settings document being opened. Only `didReset: true` means the persisted settings changed, and only then are the updated settings returned for the host to adopt. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + CompletableFuture resetStaffModelSelectionOnce(UserSettingsResetStaffModelSelectionOnceParams params) { + return caller.invoke("user.settings.resetStaffModelSelectionOnce", params, UserSettingsResetStaffModelSelectionOnceResult.class); + } + + /** + * Reports the top-level keys in the user settings document that the runtime does not recognize, phrased for display to the user. This is a separate question from the warning `user.settings.loadWithWarning` returns: that one describes parts of the document the loader had to ignore to load the rest, whereas an unknown key parses cleanly and is simply not a setting, so a host that surfaces only the load warning silently accepts a misspelled key that will never take effect. Nothing is reported when the document is absent, unreadable, or failed to load, because an unknown-key list derived from a document the runtime could not read would be misleading rather than helpful. Inspection never modifies the document. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + CompletableFuture getUnknownKeysWarning(UserSettingsGetUnknownKeysWarningParams params) { + return caller.invoke("user.settings.getUnknownKeysWarning", params, UserSettingsUnknownKeysWarningResult.class); + } + + /** + * Resolves the effective runtime settings for a host that is about to start a session, layering the given overrides over the settings documents, the environment and the platform defaults. A host cannot assemble this itself: the precedence between a user document, a repository document, the managed layers and the environment is the runtime's own rule, and a host that re-implements it drifts from the server it is configuring. The working directory, environment and platform are taken as arguments rather than read from the server process, so a host resolving settings for a directory other than its own gets that directory's answer. Nothing is written; this only computes. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + CompletableFuture buildRuntime(UserSettingsBuildRuntimeParams params) { + return caller.invoke("user.settings.buildRuntime", params, JsonNode.class); + } + + /** + * Runs the one-time migration that moves a staff account's log level to the current default, and records that it ran. Idempotent and self-gating: it takes a lock over the global-state file and re-reads the completion stamp inside it, so two hosts racing at startup cannot both migrate. Only `refreshed` can have changed the persisted settings, and the settings it returns are the document re-read after the write, so a host should adopt them rather than reloading separately and racing the next writer. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + CompletableFuture migrateStaffLogLevelOnce(UserSettingsMigrateStaffLogLevelOnceParams params) { + return caller.invoke("user.settings.migrateStaffLogLevelOnce", params, UserSettingsMigrateStaffLogLevelOnceResult.class); + } + + /** + * Runs the one-time cleanup that removes obsolete CLI-owned settings and legacy rubber-duck keys from the user's settings document, and reports what it did. Idempotent and safe to call on every startup: with nothing obsolete left it reports `unchanged` and writes nothing. The cleanup preserves every key it does not recognize, so a setting written by a newer host is never dropped by an older one, and a document that fails to parse is reported `invalid` and left untouched rather than rewritten from a partial read. The cleaned document is not returned: it is written before this replies, so `user.settings.load` reads it authoritatively. A host that surfaces unrecognized keys through `user.settings.unknownKeys` wants this first, because the keys this retires would otherwise be shown to the user as if they were misspelled settings. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + CompletableFuture migrateObsoleteOnDisk(UserSettingsMigrateObsoleteOnDiskParams params) { + return caller.invoke("user.settings.migrateObsoleteOnDisk", params, UserSettingsMigrateObsoleteOnDiskResult.class); + } + /** * Lists every known user setting from settings.json, each with its effective value, its default, and whether it is at the default — so settings the user has never set still appear with their default value. Does not include repository- or enterprise-managed overrides that the runtime layers on top at session time. * @@ -37,6 +192,17 @@ public CompletableFuture get() { return caller.invoke("user.settings.get", java.util.Map.of(), UserSettingsGetResult.class); } + /** + * Resolves the on-disk locations of the settings files this runtime reads, so a consumer can show or open them. The user path is the same file `user.settings.get`, `user.settings.load`, and `user.settings.set` operate on. Supply `repoRoot` to also resolve that repository's checked-in and local settings paths. A path is reported whether or not the file exists. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + CompletableFuture getPaths(UserSettingsGetPathsParams params) { + return caller.invoke("user.settings.getPaths", params, UserSettingsGetPathsResult.class); + } + /** * Writes one or more user settings to settings.json, replacing each provided top-level key. A key whose value is null is removed. * @@ -48,4 +214,103 @@ public CompletableFuture set(UserSettingsSetParams params) { return caller.invoke("user.settings.set", params, Void.class); } + /** + * Lists the top-level user-setting keys by who is allowed to govern them: `repo` keys a repository may override through `.github/copilot/settings.json`, `claudeRepo` keys a repository may override through a Claude-compatible config, and `managed` keys an enterprise policy may fix. The lists are static for a given runtime build and fail closed: a key absent from a list cannot be governed that way. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + CompletableFuture listGovernanceKeys() { + return caller.invoke("user.settings.listGovernanceKeys", java.util.Map.of(), UserSettingsGovernanceKeysResult.class); + } + + /** + * Atomically edits one canonical user setting inside its settings-file lock. Reads, canonicalizes touched aliases, applies the edit, validates the resulting top-level setting and writes before releasing the lock. An explicit autoUpdatesChannel set completes the staff migration in the same transaction. Only a malformed existing file returns `invalid`; refused keys, invalid values and I/O errors reject the call. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + CompletableFuture edit(UserSettingsEditParams params) { + return caller.invoke("user.settings.edit", params, SettingsEditResult.class); + } + + /** + * Rewrites legacy and snake_case setting names in a settings object to their canonical spelling. A caller editing settings.json must normalize before writing: a legacy key left in place survives the write, is re-normalized on the next load, and silently shadows the canonical value the write just stored. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + CompletableFuture normalize(UserSettingsNormalizeParams params) { + return caller.invoke("user.settings.normalize", params, UserSettingsNormalizeResult.class); + } + + /** + * Diagnoses a set of already-loaded settings layers, reporting each key that will not take effect and why. Settings load tolerantly so that one bad key does not stop a session from starting; this method surfaces what was dropped so a caller can report it instead of silently ignoring the user's intent. Takes the layers as given rather than reading them, so a caller can diagnose exactly the content it is showing, including unsaved edits. Never echoes a setting's value, because a value may be a token or another secret. Marked internal because it exists to carry a CLI call site off the native addon, not to serve a consumer-facing need; it becomes public once an SDK consumer needs to inspect settings health itself. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + CompletableFuture diagnoseProblems(UserSettingsDiagnoseProblemsParams params) { + return caller.invoke("user.settings.diagnoseProblems", params, UserSettingsProblemsResult.class); + } + + /** + * Validates one top-level setting subtree and writes just that key to settings.json as a single step. Doing both here closes the race a caller cannot close itself: between a separate preflight read and the write, the file may change. Rewriting only the touched key keeps legacy siblings out of settings.json. Answers a status rather than failing, so a validation problem and a write problem stay distinguishable. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + CompletableFuture applyTopKey(UserSettingsApplyTopKeyParams params) { + return caller.invoke("user.settings.applyTopKey", params, UserSettingsApplyTopKeyResult.class); + } + + /** + * Replaces the user settings file's entire content in one write. A key absent from `settings` is removed, which is what distinguishes this from `user.settings.set`: that one merges, so it can never clear a key the caller has dropped. For a caller saving the result of a raw edit, a merge would silently resurrect every key the user deleted. Records the same one-time mutation marks a per-key write records, so a setting the user has now chosen explicitly stops being migrated for them. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + CompletableFuture writeAll(UserSettingsWriteAllParams params) { + return caller.invoke("user.settings.writeAll", params, Void.class); + } + + /** + * Merges a partial object into one top-level user setting, re-reading the stored value inside this runtime process's settings.json write lock so that concurrent writers in this process to different sub-fields of the same key do not clobber each other. Unlike `user.settings.set`, which replaces a key's whole value with what the caller last read, this keeps sub-fields the caller did not name when both the stored value and supplied value are objects; otherwise the supplied value replaces the stored value. Use it whenever two independent surfaces own different sub-fields of one setting. Returns the key's stored value after the write, in `value`. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + CompletableFuture update(UserSettingsUpdateParams params) { + return caller.invoke("user.settings.update", params, UserSettingsUpdateResult.class); + } + + /** + * Reads settings.json as stored for a caller that is about to edit it. Unlike `user.settings.get`, this returns file values rather than per-key metadata and defaults. Reports `missing` for an absent file and `invalid` when the existing file will not parse. Retired config.json and extensionless files are not read. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + CompletableFuture loadForEdit() { + return caller.invoke("user.settings.loadForEdit", java.util.Map.of(), UserSettingsLoadForEditResult.class); + } + + /** + * Validates one top-level setting subtree without writing it, reporting each problem with the path it applies to. Scoped to a single top-level key on purpose: validating the whole file would reject an edit whenever an unrelated pre-existing field is malformed, locking the user out of the very tool that could fix it. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + CompletableFuture validate(UserSettingsValidateParams params) { + return caller.invoke("user.settings.validate", params, UserSettingsValidateResult.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 index a9213671aa..4843040c6e 100644 --- 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 @@ -30,6 +30,7 @@ record SessionCanvasProviderRegisterParams( /** Connection identifier for callback routing */ @JsonProperty("connectionId") String connectionId, /** Provider metadata supplied by the host */ + @JsonInclude(JsonInclude.Include.ALWAYS) @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/SessionCapability.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionCapability.java index 3611b0680a..baff32f2e4 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionCapability.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionCapability.java @@ -34,6 +34,8 @@ public enum SessionCapability { ELICITATION("elicitation"), /** The {@code session-store} variant. */ SESSION_STORE("session-store"), + /** The {@code github-cache-store} variant. */ + GITHUB_CACHE_STORE("github-cache-store"), /** The {@code mcp-apps} variant. */ MCP_APPS("mcp-apps"), /** The {@code canvas-renderer} variant. */ 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 index 388576154a..f98aa7791d 100644 --- 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 @@ -138,8 +138,8 @@ CompletableFuture switchToAuth(SessionGitHubAuthSwitchToAuthParams params) * @since 1.0.0 */ @CopilotExperimental - CompletableFuture logout() { - return caller.invoke("session.gitHubAuth.logout", java.util.Map.of("sessionId", this.sessionId), Void.class); + CompletableFuture logout() { + return caller.invoke("session.gitHubAuth.logout", java.util.Map.of("sessionId", this.sessionId), Boolean.class); } /** @@ -152,10 +152,10 @@ CompletableFuture logout() { * @since 1.0.0 */ @CopilotExperimental - CompletableFuture logoutUser(SessionGitHubAuthLogoutUserParams params) { + 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); + return caller.invoke("session.gitHubAuth.logoutUser", _p, Boolean.class); } /** diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionManagedSettingsGetResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionManagedSettingsGetResult.java index faf08c57d6..cdd674ab24 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionManagedSettingsGetResult.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionManagedSettingsGetResult.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.ManagedPermissionsContext; import java.util.List; import javax.annotation.processing.Generated; @@ -25,6 +26,8 @@ @JsonInclude(JsonInclude.Include.NON_NULL) @JsonIgnoreProperties(ignoreUnknown = true) public record SessionManagedSettingsGetResult( + /** Reusable retained managed permission policy for managedSettings.permissions.evaluate, including source composition and session-local injection. Provided by live session snapshots; absent in older events. Refresh the context when managed policy changes. */ + @JsonProperty("permissionsContext") ManagedPermissionsContext permissionsContext, /** 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 */ @@ -35,11 +38,11 @@ public record SessionManagedSettingsGetResult( @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. */ + /** Whether managed policy could not be determined (e.g. a failed server fetch) and unresolved-policy safeguards remain active. This does not by itself disable bypass-permissions; `bypassPermissionsDisabled` reports only an explicit policy restriction. */ @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. */ + /** Whether an explicit enterprise policy restriction disables bypass-permissions ("yolo") mode for this session. Deny-wins across layers; an unresolved policy does not force this on by itself. */ @JsonProperty("bypassPermissionsDisabled") Boolean bypassPermissionsDisabled, /** Whether at least two managed sources supplied permission allowlists, so enforcement intersects them and the flattened settings payload omits `permissions.allow`. */ @JsonProperty("permissionsAllowIntersected") Boolean permissionsAllowIntersected, diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpExecuteSamplingParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpExecuteSamplingParams.java index 54950ec4f4..7978c03553 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpExecuteSamplingParams.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpExecuteSamplingParams.java @@ -31,6 +31,7 @@ public record SessionMcpExecuteSamplingParams( /** Name of the MCP server that initiated the sampling request */ @JsonProperty("serverName") String serverName, /** 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). */ + @JsonInclude(JsonInclude.Include.ALWAYS) @JsonProperty("mcpRequestId") Object mcpRequestId, /** 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. */ @JsonProperty("request") McpExecuteSamplingRequest request diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionModelApi.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionModelApi.java index 1c5c1c3d39..bfd5d8a89e 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionModelApi.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionModelApi.java @@ -153,6 +153,22 @@ public CompletableFuture setReasoningEffor return caller.invoke("session.model.setReasoningEffort", _p, SessionModelSetReasoningEffortResult.class); } + /** + * Computes whether the session's current model shows reasoning summaries and how a host should present them: the default Ctrl+T visibility, reasoning headers, the hidden-summaries hint, and whether a persisted `showReasoning` setting is redundant. The runtime folds in the session's model catalog, selection and reasoning-summaries experiment assignment; the host supplies only its own persisted setting and current visibility. This method reads the session's resolved model catalog rather than resolving it, so call `session.model.list` first on a session that has not listed models or run a turn; until then the result is `ready: false`. A session that resolves models without a Copilot catalog, that is a BYOK registry or an alternate provider, never caches one and stays `ready: false`. + *

+ * 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 getReasoningSummaries(SessionModelGetReasoningSummariesParams params) { + com.fasterxml.jackson.databind.node.ObjectNode _p = MAPPER.valueToTree(params); + _p.put("sessionId", this.sessionId); + return caller.invoke("session.model.getReasoningSummaries", _p, SessionModelGetReasoningSummariesResult.class); + } + /** * Lists models available to this session using its own auth and integration context. Connected hosts (CLI TUI, GitHub App) should call this through the session client so remote sessions return the remote CLI's available models rather than the caller's. *

diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionModelGetReasoningSummariesParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionModelGetReasoningSummariesParams.java new file mode 100644 index 0000000000..488fe67616 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionModelGetReasoningSummariesParams.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; + +/** + * Host presentation state the reasoning-summaries policy depends on. + * + * @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 SessionModelGetReasoningSummariesParams( + /** Target session identifier */ + @JsonProperty("sessionId") String sessionId, + /** The host's persisted `showReasoning` user setting, omitted when unset. */ + @JsonProperty("persistedShowReasoning") Boolean persistedShowReasoning, + /** Whether the host is currently showing reasoning summaries. Defaults to false. */ + @JsonProperty("summariesVisible") Boolean summariesVisible +) { + + /** + * Creates host presentation state; the session-scoped wrapper supplies the session ID. + * + * @param persistedShowReasoning the persisted visibility setting, or null when unset + * @param summariesVisible whether summaries are visible, or null for the default + */ + public SessionModelGetReasoningSummariesParams(Boolean persistedShowReasoning, Boolean summariesVisible) { + this(null, persistedShowReasoning, summariesVisible); + } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionModelGetReasoningSummariesResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionModelGetReasoningSummariesResult.java new file mode 100644 index 0000000000..c23a1af9ea --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionModelGetReasoningSummariesResult.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; + +/** + * Reasoning-summaries presentation policy for the session's current model. + * + * @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 SessionModelGetReasoningSummariesResult( + /** False until the session has a model catalog and a selected model. This read never resolves the catalog itself, and the runtime emits no readiness notification, so repeating this call alone leaves `ready` false indefinitely on a session that has neither listed models nor run a turn. `session.model.list` is the prerequisite and the signal: it resolves and caches the catalog for the session, so a successful return means the next read is ready. The exception is a session that resolves models without a Copilot catalog, that is a BYOK registry or an alternate provider: `session.model.list` returns an empty list without caching a catalog, so `ready` stays false and this policy is unavailable there. While false, every model-policy field is false and `visibility` is omitted; `experimentSettled` is independent of readiness and still reports the assignment state. */ + @JsonProperty("ready") Boolean ready, + /** Whether the session's reasoning-summaries experiment assignment has settled. False means this read does not wait for a pending assignment and reports the experiment-derived fields conservatively: `showHiddenHint` is false and `visibility` falls back to `persistedShowReasoning`, or is omitted when none was supplied. A host that wants the assigned answer re-reads until this is true. */ + @JsonProperty("experimentSettled") Boolean experimentSettled, + /** Whether turns on this session produce reasoning summaries, honouring an explicit session reasoning-summary override. */ + @JsonProperty("sessionShowsSummaries") Boolean sessionShowsSummaries, + /** Whether the selected model shows reasoning summaries by default. */ + @JsonProperty("modelDefaultShowsSummaries") Boolean modelDefaultShowsSummaries, + /** Whether the selected model emits reasoning headers. */ + @JsonProperty("showReasoningHeaders") Boolean showReasoningHeaders, + /** Default reasoning visibility, and only meaningful once `ready` is true: false when the session produces no summaries, else the persisted setting, else the experiment's default. While `ready` is true it is omitted only when all three are unavailable, that is when the session produces summaries, no `persistedShowReasoning` was supplied, and `experimentSettled` is false. While `ready` is false there is no model policy to report and this field is always omitted, so an absent value alone does not imply a pending experiment. */ + @JsonProperty("visibility") Boolean visibility, + /** Whether to show the hint that reasoning summaries are hidden. Always false while `experimentSettled` is false. */ + @JsonProperty("showHiddenHint") Boolean showHiddenHint, + /** Whether the supplied `persistedShowReasoning` setting only restates the model default. This reports a redundancy, not a standing instruction to delete the setting: it stays true for as long as the setting and the model default agree, including for a user who later chose that value deliberately. It exists for a one-time migration of a legacy setting written before the model default made it redundant, so a host that acts on it owns the cleanup and must gate it behind its own persisted `done` marker, delete only a setting whose value still matches the one it read, and never repeat the deletion on a later launch. Under an off-by-default reasoning-summaries experiment an explicit `showReasoning: true` opt-in also reads as redundant on a summary-producing model, so an ungated host would keep deleting that opt-in and hide summaries again on every launch. */ + @JsonProperty("clearRedundantSetting") Boolean clearRedundantSetting +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionOpenOptions.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionOpenOptions.java index 5d14a1458a..3375bd2a2b 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionOpenOptions.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionOpenOptions.java @@ -53,6 +53,8 @@ public record SessionOpenOptions( @JsonProperty("enforceManagedModelDefaults") Boolean enforceManagedModelDefaults, /** 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, + /** Declares that a human drives this session's conversation, enabling user-message analysis (sentiment, task completion criteria, and task analytics) for user-authored messages. Automated messages remain excluded. Defaults to false for SDK sessions. Applied when the session is created or loaded from storage; ignored when resuming a session that is already active, and for cloud sessions. */ + @JsonProperty("interactiveUser") Boolean interactiveUser, /** 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. */ @@ -117,11 +119,17 @@ public record SessionOpenOptions( @JsonProperty("managedMcpServers") Map managedMcpServers, /** 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. */ + /** Explicit discovery-only skill roots, not filesystem grants. Available without enableConfigDiscovery, subject to skill/managed disables. Relative SDK paths resolve once when supplied against the request's effective workingDirectory; retained roots do not move on cwd changes. Null and omission are equivalent; [] clears only the explicit SDK contribution. */ @JsonProperty("skillDirectories") List skillDirectories, + /** Explicit discovery-only agent roots, separate from customAgents and filesystem grants. Available without enableConfigDiscovery, subject to managed/source disables. Relative SDK paths resolve once when supplied against the request's effective workingDirectory; retained roots do not move on cwd changes. Null and omission are equivalent; [] clears only the explicit SDK contribution. */ + @JsonProperty("agentDirectories") List agentDirectories, + /** Explicit discovery-only instruction roots using existing SDK instruction formats, not filesystem grants. Available without enableConfigDiscovery, subject to instruction/managed disables. Relative SDK paths resolve once when supplied against the request's effective workingDirectory; retained roots do not move on cwd changes. Null and omission are equivalent; [] clears only the explicit SDK contribution. */ + @JsonProperty("instructionDirectories") List instructionDirectories, + /** Explicit discovery-only file-hook roots, separate from callback hooks and filesystem grants. Available without enableConfigDiscovery or enableFileHooks, which controls repository-origin hooks only; hook/managed disables still apply. Relative SDK paths resolve once when supplied against the request's effective workingDirectory; retained roots do not move on cwd changes. Null and omission are equivalent; [] clears only the explicit SDK contribution. */ + @JsonProperty("hookDirectories") List hookDirectories, /** Skill scan directories and descendants excluded from discovery. Supports `~`-relative paths. */ @JsonProperty("ignoredSkillsLocations") List ignoredSkillsLocations, - /** Whether skill loading is enabled. When omitted, an SDK skill provider enables skills by default. */ + /** Whether skill loading is enabled. When omitted, explicit SDK skill roots or an SDK skill provider enable skills independently of enableConfigDiscovery. */ @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, diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionOptionsUpdateParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionOptionsUpdateParams.java index 3a30a13527..a5862263ae 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionOptionsUpdateParams.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionOptionsUpdateParams.java @@ -82,8 +82,14 @@ public record SessionOptionsUpdateParams( @JsonProperty("envValueMode") OptionsUpdateEnvValueMode envValueMode, /** 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. */ + /** Explicit discovery-only skill roots, not filesystem grants. Available without enableConfigDiscovery, subject to skill/managed disables. Relative SDK paths resolve once when supplied against the request's effective workingDirectory; retained roots do not move on cwd changes. Null and omission are equivalent; [] clears only the explicit SDK contribution. */ @JsonProperty("skillDirectories") List skillDirectories, + /** Explicit discovery-only agent roots, separate from customAgents and filesystem grants. Available without enableConfigDiscovery, subject to managed/source disables. Relative SDK paths resolve once when supplied against the request's effective workingDirectory; retained roots do not move on cwd changes. Null and omission are equivalent; [] clears only the explicit SDK contribution. */ + @JsonProperty("agentDirectories") List agentDirectories, + /** Explicit discovery-only instruction roots using existing SDK instruction formats, not filesystem grants. Available without enableConfigDiscovery, subject to instruction/managed disables. Relative SDK paths resolve once when supplied against the request's effective workingDirectory; retained roots do not move on cwd changes. Null and omission are equivalent; [] clears only the explicit SDK contribution. */ + @JsonProperty("instructionDirectories") List instructionDirectories, + /** Explicit discovery-only file-hook roots, separate from callback hooks and filesystem grants. Available without enableConfigDiscovery or enableFileHooks, which controls repository-origin hooks only; hook/managed disables still apply. Relative SDK paths resolve once when supplied against the request's effective workingDirectory; retained roots do not move on cwd changes. Null and omission are equivalent; [] clears only the explicit SDK contribution. */ + @JsonProperty("hookDirectories") List hookDirectories, /** Skill scan directories and descendants excluded from discovery. Supports `~`-relative paths. */ @JsonProperty("ignoredSkillsLocations") List ignoredSkillsLocations, /** 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. */ @@ -136,13 +142,13 @@ public record SessionOptionsUpdateParams( @JsonProperty("skipEmbeddingRetrieval") Boolean skipEmbeddingRetrieval, /** Organization-level custom instructions to inject into the system prompt. */ @JsonProperty("organizationCustomInstructions") String organizationCustomInstructions, - /** Whether to enable loading of `.github/hooks/` filesystem hooks. Separate from the SDK callback hook mechanism. */ + /** Whether to enable repository-origin file hooks from `.github/hooks/` and trusted repository-configured roots. Does not control explicit hookDirectories or SDK callback hooks. */ @JsonProperty("enableFileHooks") Boolean enableFileHooks, /** Whether to enable host git operations (context resolution, child repo scanning, git info in system prompt). */ @JsonProperty("enableHostGitOperations") Boolean enableHostGitOperations, /** Whether to enable cross-session store writes and reads. */ @JsonProperty("enableSessionStore") Boolean enableSessionStore, - /** 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. */ + /** Whether skill loading is enabled. Explicit false disables every source, including explicit SDK roots and a bound SDK provider; changing the value invalidates the loaded skill snapshot. When omitted, creation falls back to enableConfigDiscovery unless explicit SDK skill roots or an SDK skill provider are supplied. */ @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/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPluginsApi.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPluginsApi.java index e041a149c1..3235108161 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPluginsApi.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPluginsApi.java @@ -154,4 +154,33 @@ public CompletableFuture reload(SessionPluginsReloadParams params) { return caller.invoke("session.plugins.reload", _p, Void.class); } + /** + * Installs or updates the plugins the session's managed settings require now, without waiting for the next automatic attempt after a failure, then reloads plugins. Reports progress and failures with the same `session.info` and `session.warning` events as automatic preparation. Fails when the runtime does not prepare managed plugins for the session. + *

+ * Invokes the method with no params, applying the runtime defaults. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + public CompletableFuture retryManaged() { + return retryManaged(null); + } + + /** + * Installs or updates the plugins the session's managed settings require now, without waiting for the next automatic attempt after a failure, then reloads plugins. Reports progress and failures with the same `session.info` and `session.warning` events as automatic preparation. Fails when the runtime does not prepare managed plugins for the session. + *

+ * 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 retryManaged(SessionPluginsRetryManagedParams params) { + com.fasterxml.jackson.databind.node.ObjectNode _p = params == null ? MAPPER.createObjectNode() : MAPPER.valueToTree(params); + _p.put("sessionId", this.sessionId); + return caller.invoke("session.plugins.retryManaged", _p, SessionPluginsRetryManagedResult.class); + } + } diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPluginsRetryManagedParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPluginsRetryManagedParams.java new file mode 100644 index 0000000000..24b687ca75 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPluginsRetryManagedParams.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; + +/** + * Request parameters for the {@code session.plugins.retryManaged} RPC method. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ +@CopilotExperimental +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record SessionPluginsRetryManagedParams( + /** Target session identifier */ + @JsonProperty("sessionId") String sessionId, + /** "plugin@marketplace" specs to retry. Omit to retry every plugin the session's managed settings require. */ + @JsonProperty("plugins") List plugins +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPluginsRetryManagedResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPluginsRetryManagedResult.java new file mode 100644 index 0000000000..f2fc9b73cb --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPluginsRetryManagedResult.java @@ -0,0 +1,31 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import java.util.List; +import javax.annotation.processing.Generated; + +/** + * Per-plugin outcomes of retrying the plugins required by the session's managed settings. + * + * @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 SessionPluginsRetryManagedResult( + /** One entry per requested plugin, in request order. When no plugins were requested, one entry per plugin the managed settings require. */ + @JsonProperty("plugins") List plugins +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueConsumeSystemNotificationsParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueConsumeSystemNotificationsParams.java index 3b1d844881..850afce76e 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueConsumeSystemNotificationsParams.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueConsumeSystemNotificationsParams.java @@ -27,6 +27,7 @@ record SessionQueueConsumeSystemNotificationsParams( /** Target session identifier */ @JsonProperty("sessionId") String sessionId, /** Opaque runtime-owned filter object. */ + @JsonInclude(JsonInclude.Include.ALWAYS) @JsonProperty("filter") Object filter ) { } diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionRpc.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionRpc.java index 21050a5317..9a56863901 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionRpc.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionRpc.java @@ -280,8 +280,8 @@ public CompletableFuture interruptMainTurn(Sessi * @since 1.0.0 */ @CopilotExperimental - public CompletableFuture cancelAllBackgroundAgents() { - return caller.invoke("session.cancelAllBackgroundAgents", java.util.Map.of("sessionId", this.sessionId), Void.class); + public CompletableFuture cancelAllBackgroundAgents() { + return caller.invoke("session.cancelAllBackgroundAgents", java.util.Map.of("sessionId", this.sessionId), Long.class); } /** 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 index 2ace81bb8e..b780e59b38 100644 --- 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 @@ -41,6 +41,17 @@ public CompletableFuture getEnforcemen return caller.invoke("session.sandbox.getEnforcementStatus", java.util.Map.of("sessionId", this.sessionId), SessionSandboxGetEnforcementStatusResult.class); } + /** + * Lists possible secret-bearing environment variables available to new sandboxed shells that are not already covered by credential masking. Uses local name rules and known-provider host suggestions, never returns secret values, and changes no settings or network permissions. Returns no suggestions while the session is not sandboxed. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + public CompletableFuture getCredentialSuggestions() { + return caller.invoke("session.sandbox.getCredentialSuggestions", java.util.Map.of("sessionId", this.sessionId), SessionSandboxGetCredentialSuggestionsResult.class); + } + /** * Disables sandboxing for the remainder of the current session and approves the referenced pending sandbox-bypass permission request. The request is rejected unless the exact request is still pending and the effective sandbox policy permits bypass. *

diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionSandboxGetCredentialSuggestionsParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionSandboxGetCredentialSuggestionsParams.java new file mode 100644 index 0000000000..c587908619 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionSandboxGetCredentialSuggestionsParams.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 SessionSandboxGetCredentialSuggestionsParams( + /** Target session identifier */ + @JsonProperty("sessionId") String sessionId +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionSandboxGetCredentialSuggestionsResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionSandboxGetCredentialSuggestionsResult.java new file mode 100644 index 0000000000..2f883728f1 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionSandboxGetCredentialSuggestionsResult.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; + +/** + * Possible unconfigured secrets in the sandbox shell environment, sorted by variable name, with no secret values. + * + * @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 SessionSandboxGetCredentialSuggestionsResult( + /** Candidates for user review. Empty when sandboxing is disabled or no unconfigured candidates are present. */ + @JsonProperty("suggestions") List suggestions +) { +} 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 index e60023c3aa..0e118ff815 100644 --- 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 @@ -29,6 +29,7 @@ public record SessionToolsExecuteParams( /** Name of the currently offered tool to execute. */ @JsonProperty("name") String name, /** Arguments supplied to the tool. */ + @JsonInclude(JsonInclude.Include.ALWAYS) @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/SessionToolsTaskCompleteEventDataParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionToolsTaskCompleteEventDataParams.java index 9902fb8a99..56fbf48a11 100644 --- 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 @@ -27,6 +27,7 @@ public record SessionToolsTaskCompleteEventDataParams( /** Target session identifier */ @JsonProperty("sessionId") String sessionId, /** Arguments supplied to the completed task_complete tool call. */ + @JsonInclude(JsonInclude.Include.ALWAYS) @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/SessionWorkflowJournalPutParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkflowJournalPutParams.java index 890c69647e..6cd69c5476 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkflowJournalPutParams.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkflowJournalPutParams.java @@ -33,6 +33,7 @@ public record SessionWorkflowJournalPutParams( /** Namespaced journal key. */ @JsonProperty("key") String key, /** JSON result to memoize. */ + @JsonInclude(JsonInclude.Include.ALWAYS) @JsonProperty("resultJson") Object resultJson ) { } diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkflowRunFromToolParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkflowRunFromToolParams.java index d68976fb76..78b199562e 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkflowRunFromToolParams.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkflowRunFromToolParams.java @@ -29,6 +29,7 @@ record SessionWorkflowRunFromToolParams( /** Registered workflow name. */ @JsonProperty("name") String name, /** Workflow input value. */ + @JsonInclude(JsonInclude.Include.ALWAYS) @JsonProperty("args") Object args, /** Tool-originated workflow invocation options. */ @JsonProperty("options") WorkflowToolRunOptions options, diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkflowRunParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkflowRunParams.java index 642e2f7fcf..84883913ff 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkflowRunParams.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkflowRunParams.java @@ -29,6 +29,7 @@ public record SessionWorkflowRunParams( /** Registered workflow name. */ @JsonProperty("name") String name, /** Workflow input value. */ + @JsonInclude(JsonInclude.Include.ALWAYS) @JsonProperty("args") Object args, /** Workflow invocation options. */ @JsonProperty("options") WorkflowRunOptions options diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SettingsEditOperation.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SettingsEditOperation.java new file mode 100644 index 0000000000..ff6d7a5c69 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SettingsEditOperation.java @@ -0,0 +1,43 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.github.copilot.CopilotExperimental; +import javax.annotation.processing.Generated; + +/** + * One atomic settings mutation. + * + * @apiNote This type is experimental and may change in a future version. + * + * @since 1.0.0 + */ +@CopilotExperimental +@javax.annotation.processing.Generated("copilot-sdk-codegen") +enum SettingsEditOperation { + /** The {@code set} variant. */ + SET("set"), + /** The {@code add-item} variant. */ + ADD_ITEM("add-item"), + /** The {@code remove-item} variant. */ + REMOVE_ITEM("remove-item"), + /** The {@code remove} variant. */ + REMOVE("remove"); + + private final String value; + SettingsEditOperation(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static SettingsEditOperation fromValue(String value) { + for (SettingsEditOperation v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown SettingsEditOperation value: " + value); + } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SettingsEditResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SettingsEditResult.java new file mode 100644 index 0000000000..ae05fb4237 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SettingsEditResult.java @@ -0,0 +1,31 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import javax.annotation.processing.Generated; + +/** + * Outcome of an atomic user or repository settings edit. + * + * @apiNote This type 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) +record SettingsEditResult( + /** Changed, unchanged or invalid existing file. Rejected input, validation failures and I/O errors remain RPC errors. */ + @JsonProperty("status") SettingsEditStatus status +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SettingsEditStatus.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SettingsEditStatus.java new file mode 100644 index 0000000000..4bf228fc66 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SettingsEditStatus.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.github.copilot.CopilotExperimental; +import javax.annotation.processing.Generated; + +/** + * Whether one atomic settings edit changed a file. + * + * @apiNote This type is experimental and may change in a future version. + * + * @since 1.0.0 + */ +@CopilotExperimental +@javax.annotation.processing.Generated("copilot-sdk-codegen") +enum SettingsEditStatus { + /** The {@code changed} variant. */ + CHANGED("changed"), + /** The {@code unchanged} variant. */ + UNCHANGED("unchanged"), + /** The {@code invalid} variant. */ + INVALID("invalid"); + + private final String value; + SettingsEditStatus(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static SettingsEditStatus fromValue(String value) { + for (SettingsEditStatus v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown SettingsEditStatus value: " + value); + } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SkillInstallationFailureReason.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SkillInstallationFailureReason.java index 7751666ecd..eaece40dbc 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SkillInstallationFailureReason.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SkillInstallationFailureReason.java @@ -75,7 +75,17 @@ public enum SkillInstallationFailureReason { /** The {@code configuration-modified} variant. */ CONFIGURATION_MODIFIED("configuration-modified"), /** The {@code write-failed} variant. */ - WRITE_FAILED("write-failed"); + WRITE_FAILED("write-failed"), + /** The {@code invalid-reference} variant. */ + INVALID_REFERENCE("invalid-reference"), + /** The {@code descriptor-not-found} variant. */ + DESCRIPTOR_NOT_FOUND("descriptor-not-found"), + /** The {@code descriptor-withdrawn} variant. */ + DESCRIPTOR_WITHDRAWN("descriptor-withdrawn"), + /** The {@code digest-mismatch} variant. */ + DIGEST_MISMATCH("digest-mismatch"), + /** The {@code unsupported-resource} variant. */ + UNSUPPORTED_RESOURCE("unsupported-resource"); private final String value; SkillInstallationFailureReason(String value) { this.value = value; } diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SkillsDiscoverParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SkillsDiscoverParams.java index 03b9d85c1b..5dbde0dfd8 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SkillsDiscoverParams.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SkillsDiscoverParams.java @@ -27,7 +27,7 @@ public record SkillsDiscoverParams( /** Optional list of project directory paths to scan for project-scoped skills */ @JsonProperty("projectPaths") List projectPaths, - /** Optional list of additional skill directory paths to include */ + /** Explicit discovery-only skill roots, not filesystem grants. Available without ambient discovery, subject to skill/managed disables. Relative paths resolve against the request's effective working directory when supplied. Null and omission are equivalent; an empty array contributes no explicit roots. */ @JsonProperty("skillDirectories") List skillDirectories, /** Optional skill scan paths to exclude from discovery. */ @JsonProperty("ignoredSkillsLocations") List ignoredSkillsLocations, diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SkillsGetDiscoveryPathsRequest.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SkillsGetDiscoveryPathsRequest.java new file mode 100644 index 0000000000..9b58939685 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SkillsGetDiscoveryPathsRequest.java @@ -0,0 +1,131 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import java.util.List; +import java.util.Objects; +import javax.annotation.processing.Generated; + +/** + * Optional project paths to enumerate. + *

+ * Required inputs are constructor arguments. Optional inputs have fluent setters. + * + * @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) +public final class SkillsGetDiscoveryPathsRequest { + + /** Optional list of project directory paths. When omitted or empty, only personal and custom directories are returned. */ + @JsonProperty("projectPaths") + private List projectPaths; + + /** Optional skill scan paths to exclude from discovery. */ + @JsonProperty("ignoredSkillsLocations") + private List ignoredSkillsLocations; + + /** When true, omit the host's personal and custom skill directories, leaving only project directories. For multitenant deployments. */ + @JsonProperty("excludeHostSkills") + private Boolean excludeHostSkills; + + /** Explicit discovery-only skill roots to enumerate, including configured missing directories. Relative paths resolve against the request's effective working directory when supplied. These are not filesystem grants or preferred creation targets. Null and omission are equivalent. */ + @JsonProperty("skillDirectories") + private List skillDirectories; + + /** + * Creates a request with its required inputs. + */ + public SkillsGetDiscoveryPathsRequest() { + } + + /** + * Returns the {@code projectPaths} property. + * + * @return Optional list of project directory paths. When omitted or empty, only personal and custom directories are returned. + */ + public List getProjectPaths() { + return projectPaths; + } + + /** + * Returns the {@code ignoredSkillsLocations} property. + * + * @return Optional skill scan paths to exclude from discovery. + */ + public List getIgnoredSkillsLocations() { + return ignoredSkillsLocations; + } + + /** + * Returns the {@code excludeHostSkills} property. + * + * @return When true, omit the host's personal and custom skill directories, leaving only project directories. For multitenant deployments. + */ + public Boolean getExcludeHostSkills() { + return excludeHostSkills; + } + + /** + * Returns the {@code skillDirectories} property. + * + * @return Explicit discovery-only skill roots to enumerate, including configured missing directories. Relative paths resolve against the request's effective working directory when supplied. These are not filesystem grants or preferred creation targets. Null and omission are equivalent. + */ + public List getSkillDirectories() { + return skillDirectories; + } + + /** + * Sets the {@code projectPaths} property. + * + * @param value Optional list of project directory paths. When omitted or empty, only personal and custom directories are returned. + * @return this request + */ + public SkillsGetDiscoveryPathsRequest setProjectPaths(List value) { + this.projectPaths = value; + return this; + } + + /** + * Sets the {@code ignoredSkillsLocations} property. + * + * @param value Optional skill scan paths to exclude from discovery. + * @return this request + */ + public SkillsGetDiscoveryPathsRequest setIgnoredSkillsLocations(List value) { + this.ignoredSkillsLocations = value; + return this; + } + + /** + * Sets the {@code excludeHostSkills} property. + * + * @param value When true, omit the host's personal and custom skill directories, leaving only project directories. For multitenant deployments. + * @return this request + */ + public SkillsGetDiscoveryPathsRequest setExcludeHostSkills(Boolean value) { + this.excludeHostSkills = value; + return this; + } + + /** + * Sets the {@code skillDirectories} property. + * + * @param value Explicit discovery-only skill roots to enumerate, including configured missing directories. Relative paths resolve against the request's effective working directory when supplied. These are not filesystem grants or preferred creation targets. Null and omission are equivalent. + * @return this request + */ + public SkillsGetDiscoveryPathsRequest setSkillDirectories(List value) { + this.skillDirectories = value; + return this; + } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SkillsPlanInstallFromReferenceParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SkillsPlanInstallFromReferenceParams.java new file mode 100644 index 0000000000..432d505989 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SkillsPlanInstallFromReferenceParams.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; + +/** + * Side-effect-free planning of one Skill from a materialisation reference rather than a search candidate. + * + * @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 SkillsPlanInstallFromReferenceParams( + /** Required authenticated bound catalogue, skill-confirmed-installation and skill-reference-installation capabilities. */ + @JsonProperty("contract") CatalogClientContract contract, + /** Materialisation reference to verify and plan. */ + @JsonProperty("reference") CatalogMaterializationReference reference, + /** Existing local session attached to this connection. */ + @JsonProperty("policySessionId") String policySessionId +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/StaffLogLevelMigrationStatus.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/StaffLogLevelMigrationStatus.java new file mode 100644 index 0000000000..1538068712 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/StaffLogLevelMigrationStatus.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 the one-time staff log-level migration did. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +enum StaffLogLevelMigrationStatus { + /** The {@code ineligible} variant. */ + INELIGIBLE("ineligible"), + /** The {@code current} variant. */ + CURRENT("current"), + /** The {@code refreshed} variant. */ + REFRESHED("refreshed"); + + private final String value; + StaffLogLevelMigrationStatus(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static StaffLogLevelMigrationStatus fromValue(String value) { + for (StaffLogLevelMigrationStatus v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown StaffLogLevelMigrationStatus value: " + value); + } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/StaffUpdateChannelMigrationStatus.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/StaffUpdateChannelMigrationStatus.java new file mode 100644 index 0000000000..a3cb8f83d7 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/StaffUpdateChannelMigrationStatus.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; + +/** + * What the one-time staff update-channel migration did. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +enum StaffUpdateChannelMigrationStatus { + /** The {@code ineligible} variant. */ + INELIGIBLE("ineligible"), + /** The {@code current} variant. */ + CURRENT("current"), + /** The {@code migrated} variant. */ + MIGRATED("migrated"), + /** The {@code completed} variant. */ + COMPLETED("completed"), + /** The {@code excluded-experimental} variant. */ + EXCLUDED_EXPERIMENTAL("excluded-experimental"); + + private final String value; + StaffUpdateChannelMigrationStatus(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static StaffUpdateChannelMigrationStatus fromValue(String value) { + for (StaffUpdateChannelMigrationStatus v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown StaffUpdateChannelMigrationStatus value: " + value); + } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsApplyTopKeyParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsApplyTopKeyParams.java new file mode 100644 index 0000000000..c81c4d218b --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsApplyTopKeyParams.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 single top-level setting subtree to validate and write. + * + * @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) +record UserSettingsApplyTopKeyParams( + /** Canonical path of the setting the caller is editing. Currently informational only. */ + @JsonProperty("canonicalPath") List canonicalPath, + /** Top-level setting name to write. */ + @JsonProperty("topKey") String topKey, + /** Value to write for the whole top-level key. Omitted when `delete` is true. Explicit null is preserved and validated as null. */ + @JsonProperty("value") Object value, + /** True to remove the top-level key instead of writing it. */ + @JsonProperty("delete") Boolean delete, + /** Records that the user chose an update channel explicitly, so the one-time staff channel migration stops reapplying. Honored only for the `autoUpdatesChannel` key. */ + @JsonProperty("completeStaffUpdateChannelMigration") Boolean completeStaffUpdateChannelMigration +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsApplyTopKeyResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsApplyTopKeyResult.java new file mode 100644 index 0000000000..85bdfd1d1a --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsApplyTopKeyResult.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.List; +import javax.annotation.processing.Generated; + +/** + * Outcome of validating and writing one top-level setting subtree. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +record UserSettingsApplyTopKeyResult( + /** `ok` when the value was written, `validation-error` when it was rejected before any write, `write-error` when validation passed but the write failed. */ + @JsonProperty("status") String status, + /** Problems found, present only when `status` is `validation-error`. */ + @JsonProperty("issues") List issues, + /** Why the write failed, present only when `status` is `write-error`. */ + @JsonProperty("error") String error +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsBuildRuntimeParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsBuildRuntimeParams.java new file mode 100644 index 0000000000..7b95e8df04 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsBuildRuntimeParams.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.Map; +import javax.annotation.processing.Generated; + +/** + * The overrides and host context to resolve effective runtime settings from. + * + * @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) +record UserSettingsBuildRuntimeParams( + /** Settings that take precedence over every document and environment layer, such as the values a host derived from its own command line. Keyed by setting name, with each value carried verbatim, so a host may pass keys this runtime does not recognize. Absent when the host contributes no overrides of its own. */ + @JsonProperty("overrides") Map overrides, + /** The directory the settings are being resolved for. This selects the repository-scoped document, so a host resolving settings for a directory other than its own process working directory must pass that directory here. */ + @JsonProperty("workingDirectory") String workingDirectory, + /** The environment to read settings-bearing variables from. Passed explicitly rather than read from the server process so a host can resolve settings for an environment it is about to create rather than the one it runs in. */ + @JsonProperty("environment") Map environment, + /** The platform to apply platform-specific defaults for, in the same spelling the host's own runtime reports. */ + @JsonProperty("platform") String platform +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsDiagnoseProblemsParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsDiagnoseProblemsParams.java new file mode 100644 index 0000000000..de7ccae6c7 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsDiagnoseProblemsParams.java @@ -0,0 +1,31 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import java.util.List; +import javax.annotation.processing.Generated; + +/** + * The settings layers to diagnose. + * + * @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) +record UserSettingsDiagnoseProblemsParams( + /** Layers to diagnose, in the precedence order the caller applies them. */ + @JsonProperty("sources") List sources +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsEditParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsEditParams.java new file mode 100644 index 0000000000..24f1cbd7f4 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsEditParams.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; + +/** + * One atomic edit of a canonical setting in settings.json. + * + * @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) +record UserSettingsEditParams( + /** Configuration directory to edit. Omitted or empty resolves the server's own home, as in `user.settings.loadForConfigDir`. */ + @JsonProperty("configDir") String configDir, + /** Nonempty canonical path segments of the setting to edit, starting with its top-level key. */ + @JsonProperty("canonicalPath") List canonicalPath, + /** Set a scalar, add or remove a string list item, or remove the setting. The handler rejects a missing or incompatible value. */ + @JsonProperty("operation") SettingsEditOperation operation, + /** Required boolean, string or number for set, required string for add-item and remove-item, and forbidden for remove. The runtime rejects all other JSON values. */ + @JsonProperty("value") Object value +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsGetPathsParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsGetPathsParams.java new file mode 100644 index 0000000000..d9fca8e81a --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsGetPathsParams.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; + +/** + * Selects which settings file paths to resolve. + * + * @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) +record UserSettingsGetPathsParams( + /** Absolute path of a repository root. Supply it to also resolve that repository's two settings file paths; omit it to resolve only the user path. */ + @JsonProperty("repoRoot") String repoRoot +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsGetPathsResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsGetPathsResult.java new file mode 100644 index 0000000000..2ed0ae2d5e --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsGetPathsResult.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; + +/** + * On-disk locations of the settings files this runtime reads. A path is reported whether or not the file exists. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +record UserSettingsGetPathsResult( + /** The user settings file: `settings.json` under the resolved Copilot home. This is the file `user.settings.get`, `user.settings.load`, and `user.settings.set` read and write. */ + @JsonProperty("userSettings") String userSettings, + /** The repository's checked-in settings file, or null when no repository root was supplied. */ + @JsonProperty("repoSettings") String repoSettings, + /** The repository's local, untracked settings file, or null when no repository root was supplied. */ + @JsonProperty("repoLocalSettings") String repoLocalSettings +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsGetUnknownKeysWarningParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsGetUnknownKeysWarningParams.java new file mode 100644 index 0000000000..a90c4a6853 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsGetUnknownKeysWarningParams.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; + +/** + * Selects the configuration directory whose user settings document should be inspected for unrecognized top-level keys. + * + * @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) +record UserSettingsGetUnknownKeysWarningParams( + /** The configuration directory, home directory and environment to resolve the settings document from. A host that scopes a session to its own Copilot home must pass that directory here, or the server's own home is inspected instead. */ + @JsonProperty("context") UserSettingsHostContext context +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsGovernanceKeysResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsGovernanceKeysResult.java new file mode 100644 index 0000000000..d4c0891e8f --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsGovernanceKeysResult.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.List; +import javax.annotation.processing.Generated; + +/** + * Top-level user-setting keys grouped by who may govern them. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +record UserSettingsGovernanceKeysResult( + /** Top-level keys a repository may override through `.github/copilot/settings.json` or `.github/copilot/settings.local.json`. */ + @JsonProperty("repo") List repo, + /** Top-level keys a repository may override through a Claude-compatible repository config. */ + @JsonProperty("claudeRepo") List claudeRepo, + /** Top-level keys an enterprise policy may fix through managed settings. */ + @JsonProperty("managed") List managed +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsHostContext.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsHostContext.java new file mode 100644 index 0000000000..441023ed14 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsHostContext.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; + +/** + * The filesystem and environment facts a user-settings file is resolved from. Passed explicitly rather than read from the runtime's own process, so a host whose configuration directory differs from its process environment resolves the file it means. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +record UserSettingsHostContext( + /** The Copilot configuration directory, when the host set one. Takes precedence over `COPILOT_HOME` and the home directory. */ + @JsonProperty("configDir") String configDir, + /** The user's home directory, used when neither a configuration directory nor `COPILOT_HOME` is set. */ + @JsonProperty("homeDirectory") String homeDirectory, + /** The host's environment variables. `COPILOT_HOME` is read from here, as are the variables a settings hook may expand. */ + @JsonProperty("environment") Map environment +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsLoadAndMergeRepoParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsLoadAndMergeRepoParams.java new file mode 100644 index 0000000000..56af230edf --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsLoadAndMergeRepoParams.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.Map; +import javax.annotation.processing.Generated; + +/** + * A repository to read settings from, and the user layer they are merged over. + * + * @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) +record UserSettingsLoadAndMergeRepoParams( + /** Absolute path of the repository root whose settings to read. When this is the user's home directory, the Claude-compatible configuration is skipped, so a home-directory session does not inherit a personal config as if it were repository policy. */ + @JsonProperty("repoRoot") String repoRoot, + /** The user's own settings, keyed by setting name. The repository layer is merged over this; a repository may only add to the collection settings it is allowed to influence, so a user setting it does not name survives unchanged. */ + @JsonProperty("userSettings") Map userSettings +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsLoadAndMergeRepoResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsLoadAndMergeRepoResult.java new file mode 100644 index 0000000000..15b319f3d9 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsLoadAndMergeRepoResult.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.Map; +import javax.annotation.processing.Generated; + +/** + * The settings a consumer should act on inside a repository, together with any diagnostic raised while reading the repository layer. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +record UserSettingsLoadAndMergeRepoResult( + /** The merged settings document, keyed by setting name. Equals the supplied user layer when the repository declares no settings. */ + @JsonProperty("settings") Map settings, + /** Why part of the repository document was ignored, as a single human-readable string joining every diagnostic with `; `. Null when it loaded cleanly. The settings that did load are still merged, so a consumer should surface this and continue rather than treat it as a failure. */ + @JsonProperty("warning") String warning +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsLoadForConfigDirParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsLoadForConfigDirParams.java new file mode 100644 index 0000000000..0f745e7002 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsLoadForConfigDirParams.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; + +/** + * Selects the configuration directory whose user settings to read. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ +@CopilotExperimental +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +record UserSettingsLoadForConfigDirParams( + /** Copilot configuration directory to read the settings document from, taking precedence over the server's own `COPILOT_HOME` and default home. Omit it, or pass an empty string, to read the directory the server resolved for itself. */ + @JsonProperty("configDir") String configDir +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsLoadForEditResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsLoadForEditResult.java new file mode 100644 index 0000000000..0a5b05d3c8 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsLoadForEditResult.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; + +/** + * Contents of settings.json as stored, for a caller about to rewrite it. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +record UserSettingsLoadForEditResult( + /** `ok` when the file parsed, `missing` when there is no file yet, `invalid` when the file exists but will not parse. */ + @JsonProperty("status") String status, + /** Parsed contents, present only when `status` is `ok`. */ + @JsonProperty("settings") Object settings +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsLoadRepoParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsLoadRepoParams.java new file mode 100644 index 0000000000..2f809ae5b6 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsLoadRepoParams.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; + +/** + * Selects the repository whose checked-in settings to read. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ +@CopilotExperimental +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +record UserSettingsLoadRepoParams( + /** Absolute path of the repository root whose settings to read. When this is the user's home directory, the Claude-compatible configuration is skipped, so a home-directory session does not inherit a personal config as if it were repository policy. */ + @JsonProperty("repoRoot") String repoRoot +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsLoadRepoResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsLoadRepoResult.java new file mode 100644 index 0000000000..eb3f16cb85 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsLoadRepoResult.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.Map; +import javax.annotation.processing.Generated; + +/** + * A repository's settings as stored, together with any diagnostic raised while reading them. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +record UserSettingsLoadRepoResult( + /** The repository's settings document, keyed by setting name, with each value carried verbatim. Null when the repository declares no settings. */ + @JsonProperty("settings") Map settings, + /** Why part of the document was ignored, as a single human-readable string joining every diagnostic with `; `. Null when the document loaded cleanly. The settings that did load are still reported, so a consumer should surface this and continue rather than treat it as a failure. */ + @JsonProperty("warning") String warning +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsLoadWithWarningResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsLoadWithWarningResult.java new file mode 100644 index 0000000000..1f9ba38d07 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsLoadWithWarningResult.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.Map; +import javax.annotation.processing.Generated; + +/** + * The user's stored settings, together with any diagnostic raised while reading them. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +record UserSettingsLoadWithWarningResult( + /** The stored user settings document, keyed by setting name, with each value carried verbatim. Empty when the file could not be read at all. */ + @JsonProperty("settings") Map settings, + /** Why part of the document was ignored, phrased for display to the user. Null when the document loaded cleanly. The settings that did load are still reported, so a consumer may surface this and continue rather than treat it as a failure. */ + @JsonProperty("warning") String warning +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsMergeManagedAndUserParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsMergeManagedAndUserParams.java new file mode 100644 index 0000000000..740de3bc35 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsMergeManagedAndUserParams.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; + +/** + * The managed (enterprise policy) layer and the user layer to fold together. The field names carry the layer identity: nothing about a settings object identifies which layer produced it, so a generic base/override pair would let a caller transpose them and silently resolve the governance keys in the user's favour. + * + * @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) +record UserSettingsMergeManagedAndUserParams( + /** The managed layer. Its governance keys win, and the per-entry `enabledPlugins` and `extraKnownMarketplaces` control maps are stamped back from it after the union. Omitted when the host is unmanaged. */ + @JsonProperty("managed") Object managed, + /** The user's own settings layer. Wins on every key the managed layer does not govern. Omitted when the user has no settings yet. */ + @JsonProperty("user") Object user +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsMergeManagedAndUserResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsMergeManagedAndUserResult.java new file mode 100644 index 0000000000..c28bbddba3 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsMergeManagedAndUserResult.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; + +/** + * The user layer folded over the managed layer. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +record UserSettingsMergeManagedAndUserResult( + /** The merged settings. An empty object when neither layer was supplied, so an unmanaged host with no user settings governs nothing. */ + @JsonProperty("settingsJson") Object settingsJson +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsMergeRepoValueParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsMergeRepoValueParams.java new file mode 100644 index 0000000000..30c47590c4 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsMergeRepoValueParams.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; + +/** + * The user layer and the repository layer to fold together. + * + * @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) +record UserSettingsMergeRepoValueParams( + /** The user's settings, already resolved against any managed layer. */ + @JsonInclude(JsonInclude.Include.ALWAYS) + @JsonProperty("userSettings") Object userSettings, + /** The repository's checked-in settings layer. */ + @JsonInclude(JsonInclude.Include.ALWAYS) + @JsonProperty("repoSettings") Object repoSettings +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsMergeRepoValueResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsMergeRepoValueResult.java new file mode 100644 index 0000000000..136bf12f30 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsMergeRepoValueResult.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; + +/** + * The repository layer folded into the user layer. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +record UserSettingsMergeRepoValueResult( + /** The merged settings, with the per-entry managed control maps re-stamped from the runtime's ambient managed policy. */ + @JsonProperty("settingsJson") Object settingsJson +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsMigrateObsoleteOnDiskParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsMigrateObsoleteOnDiskParams.java new file mode 100644 index 0000000000..8c7fce5dd4 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsMigrateObsoleteOnDiskParams.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; + +/** + * Selects the configuration directory whose settings document to clean. + * + * @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) +record UserSettingsMigrateObsoleteOnDiskParams( + /** The configuration directory holding the settings document to clean. A host that scopes a session to its own Copilot home passes that directory here; omit it to clean the document under the home the server resolved for itself. The home directory and environment are deliberately not caller-supplied: they decide only the settings path root and the two hook-parsing flags, and those describe the process doing the reading rather than the caller. */ + @JsonProperty("configDir") String configDir +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsMigrateObsoleteOnDiskResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsMigrateObsoleteOnDiskResult.java new file mode 100644 index 0000000000..5e894430ed --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsMigrateObsoleteOnDiskResult.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; + +/** + * The outcome of the one-time obsolete-settings cleanup. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +record UserSettingsMigrateObsoleteOnDiskResult( + /** What the cleanup did. Only `changed` rewrote the document. The cleaned settings are not returned here: the document is written before this replies, so `user.settings.load` reads it authoritatively. */ + @JsonProperty("status") UserSettingsMigrateObsoleteOnDiskStatus status +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsMigrateObsoleteOnDiskStatus.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsMigrateObsoleteOnDiskStatus.java new file mode 100644 index 0000000000..38ef9d9a42 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsMigrateObsoleteOnDiskStatus.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 the one-time obsolete-settings cleanup did to the document. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +enum UserSettingsMigrateObsoleteOnDiskStatus { + /** The {@code missing} variant. */ + MISSING("missing"), + /** The {@code invalid} variant. */ + INVALID("invalid"), + /** The {@code unchanged} variant. */ + UNCHANGED("unchanged"), + /** The {@code changed} variant. */ + CHANGED("changed"); + + private final String value; + UserSettingsMigrateObsoleteOnDiskStatus(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static UserSettingsMigrateObsoleteOnDiskStatus fromValue(String value) { + for (UserSettingsMigrateObsoleteOnDiskStatus v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown UserSettingsMigrateObsoleteOnDiskStatus value: " + value); + } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsMigrateStaffLogLevelOnceParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsMigrateStaffLogLevelOnceParams.java new file mode 100644 index 0000000000..24220b8882 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsMigrateStaffLogLevelOnceParams.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; + +/** + * Selects the configuration directory to run the one-time staff log-level migration against, and supplies the caller's record of whether it has already run. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ +@CopilotExperimental +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +record UserSettingsMigrateStaffLogLevelOnceParams( + /** The configuration directory, home directory and environment to resolve the settings and global-state documents from. A host that scopes a session to its own Copilot home must pass that directory here, or the migration is evaluated against the server's own home instead. */ + @JsonProperty("context") UserSettingsHostContext context, + /** When the caller last recorded this migration, as an ISO-8601 timestamp. Supplying it lets the runtime answer `current` without re-running the evaluation. */ + @JsonProperty("cachedCompletedAt") String cachedCompletedAt +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsMigrateStaffLogLevelOnceResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsMigrateStaffLogLevelOnceResult.java new file mode 100644 index 0000000000..2ffc6ba552 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsMigrateStaffLogLevelOnceResult.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; + +/** + * The outcome of the one-time staff log-level migration. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +record UserSettingsMigrateStaffLogLevelOnceResult( + /** What the migration did. Only `refreshed` can have changed the persisted settings. */ + @JsonProperty("status") StaffLogLevelMigrationStatus status, + /** When the migration was recorded as done, as an ISO-8601 timestamp. Absent only for `ineligible`, which records nothing. */ + @JsonProperty("completedAt") String completedAt, + /** The settings document as it stands after the migration, keyed by setting name. Present only for `refreshed`, and only when the document could be re-read cleanly. */ + @JsonProperty("settings") Map settings +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsMigrateStaffUpdateChannelOnceParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsMigrateStaffUpdateChannelOnceParams.java new file mode 100644 index 0000000000..77a3287abe --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsMigrateStaffUpdateChannelOnceParams.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 filesystem and environment facts a user-settings file is resolved from. Passed explicitly rather than read from the runtime's own process, so a host whose configuration directory differs from its process environment resolves the file it means. + * + * @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) +record UserSettingsMigrateStaffUpdateChannelOnceParams( + /** The Copilot configuration directory, when the host set one. Takes precedence over `COPILOT_HOME` and the home directory. */ + @JsonProperty("configDir") String configDir, + /** The user's home directory, used when neither a configuration directory nor `COPILOT_HOME` is set. */ + @JsonProperty("homeDirectory") String homeDirectory, + /** The host's environment variables. `COPILOT_HOME` is read from here, as are the variables a settings hook may expand. */ + @JsonProperty("environment") Map environment +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsMigrateStaffUpdateChannelOnceResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsMigrateStaffUpdateChannelOnceResult.java new file mode 100644 index 0000000000..8d21eca4f1 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsMigrateStaffUpdateChannelOnceResult.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; + +/** + * The outcome of the one-time staff update-channel migration. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +record UserSettingsMigrateStaffUpdateChannelOnceResult( + /** What the migration did. Only `migrated` means the persisted settings changed. */ + @JsonProperty("status") StaffUpdateChannelMigrationStatus status, + /** When the migration was recorded as done, as an ISO-8601 timestamp. Absent only for `ineligible`, which records nothing. */ + @JsonProperty("completedAt") String completedAt +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsNormalizeParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsNormalizeParams.java new file mode 100644 index 0000000000..f43d774226 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsNormalizeParams.java @@ -0,0 +1,31 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import javax.annotation.processing.Generated; + +/** + * Settings object whose key names should be canonicalized. + * + * @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) +record UserSettingsNormalizeParams( + /** Free-form settings object, possibly using legacy or snake_case key names. */ + @JsonInclude(JsonInclude.Include.ALWAYS) + @JsonProperty("settings") Object settings +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsNormalizeResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsNormalizeResult.java new file mode 100644 index 0000000000..badb433c2a --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsNormalizeResult.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; + +/** + * Settings object with canonical key names. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +record UserSettingsNormalizeResult( + /** The same settings with every legacy or snake_case key renamed to its canonical spelling. */ + @JsonProperty("settings") Object settings +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsPathResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsPathResult.java new file mode 100644 index 0000000000..5c4ac0d88e --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsPathResult.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; + +/** + * The on-disk location of the user's settings document. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +record UserSettingsPathResult( + /** Absolute path to the settings file this runtime reads and writes. Normally `settings.json` under the Copilot configuration directory; an extensionless legacy `settings` file is reported instead when it exists and `settings.json` does not. */ + @JsonProperty("path") String path +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsProblem.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsProblem.java new file mode 100644 index 0000000000..057a5ac6a5 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsProblem.java @@ -0,0 +1,37 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import javax.annotation.processing.Generated; + +/** + * One setting that will not take effect, and why. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +record UserSettingsProblem( + /** Stable identifier for this problem within the reported set, so a caller can track a selection across refreshes. */ + @JsonProperty("id") String id, + /** What is wrong in machine-readable form, for a caller that wants to branch rather than display. */ + @JsonProperty("kind") String kind, + /** Coarse grouping for the problem, for a caller that presents problems in sections. */ + @JsonProperty("category") String category, + /** The settings layer the problem was found in. */ + @JsonProperty("source") UserSettingsProblemLocation source, + /** The offending key as written in the file, when the problem is attributable to a single key. Absent when the whole file is at fault. */ + @JsonProperty("rawKey") String rawKey, + /** Why the setting will not take effect. Never contains the setting's value, which may be a token or another secret. */ + @JsonProperty("explanation") String explanation +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsProblemLocation.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsProblemLocation.java new file mode 100644 index 0000000000..454408eb8b --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsProblemLocation.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; + +/** + * The settings layer a problem was found in. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +record UserSettingsProblemLocation( + /** Which layer this is: `user`, `repo`, or `local`. */ + @JsonProperty("kind") String kind, + /** The caller-supplied display label for the layer. */ + @JsonProperty("label") String label, + /** Absolute path of the file backing this layer. */ + @JsonProperty("filePath") String filePath +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsProblemSource.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsProblemSource.java new file mode 100644 index 0000000000..1c471916c5 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsProblemSource.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; + +/** + * One already-loaded settings layer to diagnose. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +record UserSettingsProblemSource( + /** Which layer this is: `user`, `repo`, or `local`. */ + @JsonProperty("kind") String kind, + /** Caller-chosen display label for the layer, echoed back on each problem so a caller can group by layer without re-deriving its own naming. */ + @JsonProperty("label") String label, + /** Absolute path of the file backing this layer. */ + @JsonProperty("filePath") String filePath, + /** The layer's settings exactly as loaded, unnormalized. Empty when the file is missing or failed to parse. */ + @JsonProperty("config") Map config, + /** True when the file itself failed to parse, so its keys could not be read at all. */ + @JsonProperty("invalid") Boolean invalid +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsProblemsResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsProblemsResult.java new file mode 100644 index 0000000000..6a5dcf5728 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsProblemsResult.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 java.util.List; +import javax.annotation.processing.Generated; + +/** + * The settings that will not take effect across the given layers. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +record UserSettingsProblemsResult( + /** Each setting that will not take effect, and why. Empty when every layer is healthy. */ + @JsonProperty("problems") List problems +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsResetStaffModelSelectionOnceParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsResetStaffModelSelectionOnceParams.java new file mode 100644 index 0000000000..98ec0b7fb3 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsResetStaffModelSelectionOnceParams.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; + +/** + * Selects the configuration directory to run the one-time staff model-selection reset against, and supplies the caller's record of whether it has already run. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ +@CopilotExperimental +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +record UserSettingsResetStaffModelSelectionOnceParams( + /** The configuration directory, home directory and environment to resolve the settings and global-state documents from. A host that scopes a session to its own Copilot home must pass that directory here, or the reset is evaluated against the server's own home instead. */ + @JsonProperty("context") UserSettingsHostContext context, + /** Whether the signed-in account is a staff account. The reset applies only to staff, so anything other than `true` returns `didReset: false` without touching the settings document. */ + @JsonProperty("staff") Boolean staff, + /** When the caller last recorded this reset, as an ISO-8601 timestamp. A timestamp at or after the cutoff means the reset already ran and is returned unchanged. */ + @JsonProperty("staffModelResetAt") String staffModelResetAt +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsResetStaffModelSelectionOnceResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsResetStaffModelSelectionOnceResult.java new file mode 100644 index 0000000000..90f9668f4f --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsResetStaffModelSelectionOnceResult.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.Map; +import javax.annotation.processing.Generated; + +/** + * The outcome of the one-time staff model-selection reset. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +record UserSettingsResetStaffModelSelectionOnceResult( + /** Whether the persisted model selection was cleared. Only `true` means the settings document changed and the host should adopt the returned settings. */ + @JsonProperty("didReset") Boolean didReset, + /** The timestamp the host should now record, as an ISO-8601 timestamp. Carries the caller's own value back unchanged when nothing was reset. */ + @JsonProperty("resetAt") String resetAt, + /** The cutoff this reset is gated on, as an ISO-8601 timestamp. A recorded timestamp at or after it means the reset is already done. */ + @JsonProperty("cutoff") String cutoff, + /** The settings document as it stands after the reset, keyed by setting name. Present only when `didReset` is true. */ + @JsonProperty("settings") Map settings +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsSetHookDisabledParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsSetHookDisabledParams.java new file mode 100644 index 0000000000..e8541c2841 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsSetHookDisabledParams.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; + +/** + * One hook to disable or re-enable. + * + * @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) +record UserSettingsSetHookDisabledParams( + /** Path of the settings.json to rewrite. */ + @JsonProperty("settingsPath") String settingsPath, + /** Durable content-hash key of the hook, as `hooks.loadResources` reports it. */ + @JsonProperty("disableKey") String disableKey, + /** True adds the key to `disabledHooks`, false removes it. */ + @JsonProperty("disabled") Boolean disabled +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsSetHookDisabledResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsSetHookDisabledResult.java new file mode 100644 index 0000000000..5548816405 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsSetHookDisabledResult.java @@ -0,0 +1,33 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import javax.annotation.processing.Generated; + +/** + * Outcome of rewriting the `disabledHooks` setting. + * + * @apiNote This type 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) +record UserSettingsSetHookDisabledResult( + /** `ok` when the setting was written, `invalid` when settings.json could not be parsed and was left alone, `error` when the write itself failed. */ + @JsonProperty("status") String status, + /** Why the write failed. Absent when the status is `ok`. */ + @JsonProperty("errorMessage") String errorMessage +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsSetParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsSetParams.java index ba19886c24..d56ecaa225 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsSetParams.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsSetParams.java @@ -25,6 +25,7 @@ @JsonIgnoreProperties(ignoreUnknown = true) public record UserSettingsSetParams( /** Partial user settings to write, as a free-form object keyed by setting name */ + @JsonInclude(JsonInclude.Include.ALWAYS) @JsonProperty("settings") Object settings ) { } diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsUnknownKeysWarningResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsUnknownKeysWarningResult.java new file mode 100644 index 0000000000..404b8c57c0 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsUnknownKeysWarningResult.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; + +/** + * The unrecognized top-level keys found in the user settings document, already phrased for display. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +record UserSettingsUnknownKeysWarningResult( + /** The warning to show the user, naming the unrecognized keys and the file they were found in. Absent when every top-level key is recognized, and also when the document is missing or could not be read, because an unknown-key list taken from a document the runtime could not load would name keys that may not be there. */ + @JsonProperty("warning") String warning +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsUpdateParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsUpdateParams.java new file mode 100644 index 0000000000..9aed7c14b3 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsUpdateParams.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 javax.annotation.processing.Generated; + +/** + * Names the user setting to merge into, and the partial object to merge. + * + * @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) +record UserSettingsUpdateParams( + /** Top-level user-setting key to merge into, such as `extensions`. */ + @JsonProperty("key") String key, + /** Value to store for `key`. When both the stored value and this value are objects, their fields are shallow-merged and fields this object does not name are preserved. Otherwise this value replaces the stored value. */ + @JsonInclude(JsonInclude.Include.ALWAYS) + @JsonProperty("value") Object value, + /** Copilot configuration directory to write the settings document in, taking precedence over the server's own `COPILOT_HOME` and default home. Omit it, or pass an empty string, to use the directory the server resolved for itself. */ + @JsonProperty("configDir") String configDir, + /** Single-component name for the settings document at `/settings/.json`; path separators, `.` and `..` are rejected. Omit it, or pass an empty string, to use the main settings document. */ + @JsonProperty("subDir") String subDir +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsUpdateResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsUpdateResult.java new file mode 100644 index 0000000000..2d13157a3b --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsUpdateResult.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; + +/** + * The stored value of the user setting after the merge. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +record UserSettingsUpdateResult( + /** The key's stored value after the write: the merged object when both the stored value and the supplied value are objects, otherwise the supplied value. */ + @JsonProperty("value") Object value +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsValidateParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsValidateParams.java new file mode 100644 index 0000000000..3e69f4b138 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsValidateParams.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 single top-level setting subtree to validate. + * + * @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) +record UserSettingsValidateParams( + /** Top-level setting name to validate. */ + @JsonProperty("topKey") String topKey, + /** Value to validate. Omitted when `delete` is true. */ + @JsonProperty("value") Object value, + /** True to validate a removal of this key. The current settings file is not read. */ + @JsonProperty("delete") Boolean delete +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsValidateResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsValidateResult.java new file mode 100644 index 0000000000..730fb00195 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsValidateResult.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; + +/** + * Outcome of validating one top-level setting subtree. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +record UserSettingsValidateResult( + /** True when the subtree is valid. */ + @JsonProperty("ok") Boolean ok, + /** Problems found, empty when `ok` is true. */ + @JsonProperty("issues") List issues +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsValidationIssue.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsValidationIssue.java new file mode 100644 index 0000000000..68404e53a7 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsValidationIssue.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; + +/** + * One problem found while validating a setting value. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +record UserSettingsValidationIssue( + /** Where in the settings tree the problem sits, as object keys and array indices. The first element is the top-level key; a whole-value problem is reported as that single key. */ + @JsonProperty("path") List path, + /** Human-readable description of the problem. */ + @JsonProperty("message") String message +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsWriteAllParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsWriteAllParams.java new file mode 100644 index 0000000000..e5929a8289 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsWriteAllParams.java @@ -0,0 +1,31 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import javax.annotation.processing.Generated; + +/** + * The whole content to store in the user settings file. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ +@CopilotExperimental +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +record UserSettingsWriteAllParams( + /** The settings object to store. It replaces the file's entire content, so a key absent here is removed. */ + @JsonInclude(JsonInclude.Include.ALWAYS) + @JsonProperty("settings") Object settings +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/WorkflowExecuteParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/WorkflowExecuteParams.java index 5c53ad155f..e4001bb82e 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/WorkflowExecuteParams.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/WorkflowExecuteParams.java @@ -33,6 +33,7 @@ public record WorkflowExecuteParams( /** Opaque token identifying this workflow execution attempt. */ @JsonProperty("executionToken") String executionToken, /** Workflow input value. */ + @JsonInclude(JsonInclude.Include.ALWAYS) @JsonProperty("args") Object args ) { } diff --git a/java/sdk/src/main/java/com/github/copilot/CliServerManager.java b/java/sdk/src/main/java/com/github/copilot/CliServerManager.java index 92203ab755..0ccd2995e8 100644 --- a/java/sdk/src/main/java/com/github/copilot/CliServerManager.java +++ b/java/sdk/src/main/java/com/github/copilot/CliServerManager.java @@ -11,6 +11,8 @@ import java.net.Socket; import java.net.URI; import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.InvalidPathException; import java.nio.file.Path; import java.util.ArrayList; import java.util.Arrays; @@ -353,9 +355,17 @@ private List resolveCliCommand(String cliPath, List args) { return result; } - // On Windows, use cmd /c to resolve the executable + // Own native executables directly so terminating the process cannot orphan + // a child behind cmd.exe. Batch shims still require the shell. String os = System.getProperty("os.name").toLowerCase(); if (os.contains("win") && !new File(cliPath).isAbsolute()) { + String executable = resolveWindowsExecutable(cliPath); + if (executable != null) { + var result = new ArrayList(); + result.add(executable); + result.addAll(args); + return result; + } var result = new ArrayList(); result.add("cmd"); result.add("/c"); @@ -370,6 +380,59 @@ private List resolveCliCommand(String cliPath, List args) { return result; } + private String resolveWindowsExecutable(String cliPath) { + Path cwd = Path.of(options.getCwd() != null ? options.getCwd() : System.getProperty("user.dir")) + .toAbsolutePath(); + var directories = new ArrayList(); + var environment = options.getEnvironment() != null ? options.getEnvironment() : System.getenv(); + boolean hasDirectory = cliPath.contains("\\") || cliPath.contains("/"); + if (hasDirectory || environment.keySet().stream() + .noneMatch(key -> key.equalsIgnoreCase("NoDefaultCurrentDirectoryInExePath"))) { + directories.add(cwd.toString()); + } + if (!hasDirectory) { + String pathValue = environment.entrySet().stream().filter(entry -> entry.getKey().equalsIgnoreCase("PATH")) + .map(java.util.Map.Entry::getValue).findFirst().orElse(""); + directories.addAll(Arrays.asList(pathValue.split(File.pathSeparator))); + } + String pathExtensions = environment.entrySet().stream() + .filter(entry -> entry.getKey().equalsIgnoreCase("PATHEXT")).map(java.util.Map.Entry::getValue) + .findFirst().orElse(".COM;.EXE;.BAT;.CMD"); + String[] names = new File(cliPath).getName().contains(".") + ? new String[]{cliPath} + : Arrays.stream(pathExtensions.split(";")).map(extension -> cliPath + extension).toArray(String[]::new); + // Preserve invalid-command diagnostics even when implicit cwd search is + // disabled. + if (names.length > 0) { + Path.of(names[0]); + } + for (String directory : directories) { + if (directory.isBlank()) { + continue; + } + if (directory.startsWith("\"") && directory.endsWith("\"")) { + directory = directory.substring(1, directory.length() - 1); + } + Path searchDirectory; + try { + searchDirectory = cwd.resolve(directory); + } catch (InvalidPathException error) { + LOG.log(Level.FINE, "Skipping invalid PATH entry while resolving CLI executable", error); + continue; + } + for (String name : names) { + Path candidate = searchDirectory.resolve(name); + if (Files.isRegularFile(candidate)) { + String lowerName = name.toLowerCase(java.util.Locale.ROOT); + return lowerName.endsWith(".exe") || lowerName.endsWith(".com") + ? candidate.toAbsolutePath().toString() + : null; + } + } + } + return null; + } + void configureProcessEnvironment(ProcessBuilder pb) { if (options.getEnvironment() != null) { pb.environment().clear(); diff --git a/java/sdk/src/main/java/com/github/copilot/CopilotClient.java b/java/sdk/src/main/java/com/github/copilot/CopilotClient.java index baccd4ad19..de6081b39d 100644 --- a/java/sdk/src/main/java/com/github/copilot/CopilotClient.java +++ b/java/sdk/src/main/java/com/github/copilot/CopilotClient.java @@ -488,8 +488,10 @@ void setInProcessTransportFactory(InProcessTransportFactory factory) { 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); + String integrationId = System.getenv("GITHUB_COPILOT_INTEGRATION_ID"); + integrationId = integrationId == null || integrationId.isBlank() ? null : integrationId.trim(); + Path cliEntrypoint = NativeRuntimeLoader.resolveConfiguredEntrypoint(); + host.start(cliEntrypoint == null ? null : cliEntrypoint.toString(), integrationId, options); } catch (RuntimeException | Error e) { host.close(); throw e; @@ -1440,6 +1442,9 @@ CompletableFuture updateSessionOptionsForMode(CopilotSession session, Bool null, // envValueMode null, // allowAllMcpServerInstructions null, // skillDirectories + null, // agentDirectories + null, // instructionDirectories + null, // hookDirectories null, // ignoredSkillsLocations patchSkills, // includedBuiltinSkills null, // disabledSkills diff --git a/java/sdk/src/main/java/com/github/copilot/SessionRequestBuilder.java b/java/sdk/src/main/java/com/github/copilot/SessionRequestBuilder.java index 8d77086746..156f496b57 100644 --- a/java/sdk/src/main/java/com/github/copilot/SessionRequestBuilder.java +++ b/java/sdk/src/main/java/com/github/copilot/SessionRequestBuilder.java @@ -153,6 +153,7 @@ static CreateSessionRequest buildCreateRequest(SessionConfig config, String sess } request.setModel(config.getModel()); + request.setName(config.getName()); request.setClientName(config.getClientName()); request.setReasoningEffort(config.getReasoningEffort()); request.setReasoningSummary(config.getReasoningSummary()); @@ -196,10 +197,13 @@ static CreateSessionRequest buildCreateRequest(SessionConfig config, String sess request.setAgent(config.getAgent()); request.setInfiniteSessions(config.getInfiniteSessions()); request.setSkillDirectories(config.getSkillDirectories()); + request.setAgentDirectories(config.getAgentDirectories()); + request.setHookDirectories(config.getHookDirectories()); request.setInstructionDirectories(config.getInstructionDirectories()); request.setPluginDirectories(config.getPluginDirectories()); request.setLargeOutput(config.getLargeOutput()); request.setToolSearch(config.getToolSearch()); + request.setImageGeneration(config.getImageGeneration()); request.setMemory(config.getMemory()); request.setDisabledSkills(config.getDisabledSkills()); request.setDisabledMcpServers(config.getDisabledMcpServers()); @@ -214,6 +218,7 @@ static CreateSessionRequest buildCreateRequest(SessionConfig config, String sess config.getEnableFileHooks().ifPresent(request::setEnableFileHooks); config.getEnableHostGitOperations().ifPresent(request::setEnableHostGitOperations); config.getEnableSessionStore().ifPresent(request::setEnableSessionStore); + config.getEnableGitHubCacheStore().ifPresent(request::setEnableGitHubCacheStore); config.getEnableSkills().ifPresent(request::setEnableSkills); if (config.getSkillProvider() != null) { request.setHasSkillProvider(true); @@ -249,6 +254,7 @@ static CreateSessionRequest buildCreateRequest(SessionConfig config, String sess request.setExpAssignments(config.getExpAssignments()); config.getEnableManagedSettings().ifPresent(request::setEnableManagedSettings); config.getEnforceManagedModelDefaults().ifPresent(request::setEnforceManagedModelDefaults); + config.getInteractiveUser().ifPresent(request::setInteractiveUser); request.setManagedSettings(config.getManagedSettings()); return request; @@ -338,6 +344,7 @@ static ResumeSessionRequest buildResumeRequest(String sessionId, ResumeSessionCo config.getEnableFileHooks().ifPresent(request::setEnableFileHooks); config.getEnableHostGitOperations().ifPresent(request::setEnableHostGitOperations); config.getEnableSessionStore().ifPresent(request::setEnableSessionStore); + config.getEnableGitHubCacheStore().ifPresent(request::setEnableGitHubCacheStore); config.getEnableSkills().ifPresent(request::setEnableSkills); if (config.getSkillProvider() != null) { request.setHasSkillProvider(true); @@ -362,10 +369,13 @@ static ResumeSessionRequest buildResumeRequest(String sessionId, ResumeSessionCo request.setDefaultAgent(config.getDefaultAgent()); request.setAgent(config.getAgent()); request.setSkillDirectories(config.getSkillDirectories()); + request.setAgentDirectories(config.getAgentDirectories()); + request.setHookDirectories(config.getHookDirectories()); request.setInstructionDirectories(config.getInstructionDirectories()); request.setPluginDirectories(config.getPluginDirectories()); request.setLargeOutput(config.getLargeOutput()); request.setToolSearch(config.getToolSearch()); + request.setImageGeneration(config.getImageGeneration()); request.setMemory(config.getMemory()); request.setDisabledSkills(config.getDisabledSkills()); request.setDisabledMcpServers(config.getDisabledMcpServers()); @@ -397,6 +407,7 @@ static ResumeSessionRequest buildResumeRequest(String sessionId, ResumeSessionCo request.setExpAssignments(config.getExpAssignments()); config.getEnableManagedSettings().ifPresent(request::setEnableManagedSettings); config.getEnforceManagedModelDefaults().ifPresent(request::setEnforceManagedModelDefaults); + config.getInteractiveUser().ifPresent(request::setInteractiveUser); request.setManagedSettings(config.getManagedSettings()); return request; 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 index e28a573bc8..825a44497a 100644 --- a/java/sdk/src/main/java/com/github/copilot/ffi/FfiRuntimeHost.java +++ b/java/sdk/src/main/java/com/github/copilot/ffi/FfiRuntimeHost.java @@ -98,6 +98,23 @@ private static Path resolveLibraryPath() throws IOException { * {@code env_json} */ public void start(String entrypointPath, CopilotClientOptions options) { + start(entrypointPath, null, options); + } + + /** + * Starts the in-process runtime and opens a connection. + * + * @param entrypointPath + * optional explicit legacy CLI entrypoint passed in + * {@code argv_json} + * @param cliLoginSyncIntegrationId + * optional integration identifier used to synchronize login state + * with the Copilot CLI credential store + * @param options + * client options used to construct {@code argv_json} and + * {@code env_json} + */ + public void start(String entrypointPath, String cliLoginSyncIntegrationId, CopilotClientOptions options) { Objects.requireNonNull(options, "options must not be null"); if (disposed.get()) { throw new IllegalStateException("FfiRuntimeHost is already closed."); @@ -106,7 +123,7 @@ public void start(String entrypointPath, CopilotClientOptions options) { throw new IllegalStateException("FfiRuntimeHost has already been started."); } - byte[] argvJson = buildArgvJson(entrypointPath, options); + byte[] argvJson = buildArgvJson(entrypointPath, cliLoginSyncIntegrationId, options); byte[] envJson = buildEnvJson(options); int hostHandle = runHostStartOnBlockingThread(argvJson, envJson); if (hostHandle == 0) { @@ -275,7 +292,7 @@ private int runHostStartOnBlockingThread(byte[] argvJson, byte[] envJson) { } } - private static byte[] buildArgvJson(String entrypointPath, CopilotClientOptions options) { + static byte[] buildArgvJson(String entrypointPath, String cliLoginSyncIntegrationId, CopilotClientOptions options) { List argv = new ArrayList<>(); if (entrypointPath != null) { if (entrypointPath.toLowerCase().endsWith(".js")) { @@ -312,6 +329,9 @@ private static byte[] buildArgvJson(String entrypointPath, CopilotClientOptions if (options.isRemote()) { argv.add("--remote"); } + if (cliLoginSyncIntegrationId != null) { + argv.add("--cli-login-sync-integration-id=" + cliLoginSyncIntegrationId); + } String[] cliArgs = options.getCliArgs(); if (cliArgs != null && cliArgs.length > 0) { diff --git a/java/sdk/src/main/java/com/github/copilot/rpc/AgentInfo.java b/java/sdk/src/main/java/com/github/copilot/rpc/AgentInfo.java index 1f6f1688fe..96820af0ef 100644 --- a/java/sdk/src/main/java/com/github/copilot/rpc/AgentInfo.java +++ b/java/sdk/src/main/java/com/github/copilot/rpc/AgentInfo.java @@ -4,6 +4,10 @@ package com.github.copilot.rpc; +import java.util.Collections; +import java.util.List; +import java.util.Map; + import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; @@ -27,6 +31,30 @@ public class AgentInfo { @JsonProperty("model") private String model; + @JsonProperty("hooks") + private Map>> hooks; + + /** + * Gets declarative hook actions executed only while this agent is active. + * + * @return actions keyed by event name, or {@code null} if unset + */ + public Map>> getHooks() { + return hooks == null ? null : Collections.unmodifiableMap(hooks); + } + + /** + * Sets declarative hook actions scoped to this agent. + * + * @param hooks + * actions keyed by event name + * @return this instance for chaining + */ + public AgentInfo setHooks(Map>> hooks) { + this.hooks = hooks; + return this; + } + /** * Gets the unique identifier of the agent. * diff --git a/java/sdk/src/main/java/com/github/copilot/rpc/AgentStopHookInput.java b/java/sdk/src/main/java/com/github/copilot/rpc/AgentStopHookInput.java index fceea8b72c..78a693f34e 100644 --- a/java/sdk/src/main/java/com/github/copilot/rpc/AgentStopHookInput.java +++ b/java/sdk/src/main/java/com/github/copilot/rpc/AgentStopHookInput.java @@ -15,6 +15,42 @@ @JsonIgnoreProperties(ignoreUnknown = true) public class AgentStopHookInput { + @JsonProperty("agentId") + private String agentId; + + public String getAgentId() { + return agentId; + } + + public AgentStopHookInput setAgentId(String agentId) { + this.agentId = agentId; + return this; + } + + @JsonProperty("agentType") + private String agentType; + + public String getAgentType() { + return agentType; + } + + public AgentStopHookInput setAgentType(String agentType) { + this.agentType = agentType; + return this; + } + + @JsonProperty("parentAgentId") + private String parentAgentId; + + public String getParentAgentId() { + return parentAgentId; + } + + public AgentStopHookInput setParentAgentId(String parentAgentId) { + this.parentAgentId = parentAgentId; + return this; + } + @JsonProperty("sessionId") private String sessionId; diff --git a/java/sdk/src/main/java/com/github/copilot/rpc/CreateSessionRequest.java b/java/sdk/src/main/java/com/github/copilot/rpc/CreateSessionRequest.java index d18de07fb1..dab6bd415c 100644 --- a/java/sdk/src/main/java/com/github/copilot/rpc/CreateSessionRequest.java +++ b/java/sdk/src/main/java/com/github/copilot/rpc/CreateSessionRequest.java @@ -34,6 +34,9 @@ public final class CreateSessionRequest { @JsonProperty("sessionId") private String sessionId; + @JsonProperty("name") + private String name; + @JsonProperty("clientName") private String clientName; @@ -147,6 +150,12 @@ public final class CreateSessionRequest { @JsonProperty("skillDirectories") private List skillDirectories; + @JsonProperty("agentDirectories") + private List agentDirectories; + + @JsonProperty("hookDirectories") + private List hookDirectories; + @JsonProperty("instructionDirectories") private List instructionDirectories; @@ -159,6 +168,9 @@ public final class CreateSessionRequest { @JsonProperty("toolSearch") private ToolSearchConfig toolSearch; + @JsonProperty("imageGeneration") + private ImageGenerationConfig imageGeneration; + @JsonProperty("memory") private MemoryConfiguration memory; @@ -201,6 +213,10 @@ public final class CreateSessionRequest { @JsonInclude(JsonInclude.Include.NON_NULL) private Boolean enableSessionStore; + @JsonProperty("enableGitHubCacheStore") + @JsonInclude(JsonInclude.Include.NON_NULL) + private Boolean enableGitHubCacheStore; + @JsonProperty("enableSkills") @JsonInclude(JsonInclude.Include.NON_NULL) private Boolean enableSkills; @@ -264,6 +280,10 @@ public final class CreateSessionRequest { @JsonInclude(JsonInclude.Include.NON_NULL) private Boolean enforceManagedModelDefaults; + @JsonProperty("interactiveUser") + @JsonInclude(JsonInclude.Include.NON_NULL) + private Boolean interactiveUser; + @JsonProperty("managedSettings") @JsonInclude(JsonInclude.Include.NON_NULL) private ManagedSettings managedSettings; @@ -288,6 +308,16 @@ public void setSessionId(String sessionId) { this.sessionId = sessionId; } + /** Gets the create-time session name. @return the session name */ + public String getName() { + return name; + } + + /** Sets the create-time session name. @param name the session name */ + public void setName(String name) { + this.name = name; + } + /** Gets the client name. @return the client name */ public String getClientName() { return clientName; @@ -720,6 +750,31 @@ public void setSkillDirectories(List skillDirectories) { this.skillDirectories = skillDirectories; } + /** Gets explicit agent discovery roots. @return the directories */ + public List getAgentDirectories() { + return agentDirectories == null ? null : Collections.unmodifiableList(agentDirectories); + } + + /** + * Sets explicit agent discovery roots. @param agentDirectories the directories + */ + public void setAgentDirectories(List agentDirectories) { + this.agentDirectories = agentDirectories; + } + + /** Gets explicit file-hook discovery roots. @return the directories */ + public List getHookDirectories() { + return hookDirectories == null ? null : Collections.unmodifiableList(hookDirectories); + } + + /** + * Sets explicit file-hook discovery roots. @param hookDirectories the + * directories + */ + public void setHookDirectories(List hookDirectories) { + this.hookDirectories = hookDirectories; + } + /** Gets instruction directories. @return the instruction directories */ public List getInstructionDirectories() { return instructionDirectories == null ? null : Collections.unmodifiableList(instructionDirectories); @@ -757,6 +812,19 @@ public ToolSearchConfig getToolSearch() { return toolSearch; } + /** Gets image generation config. @return the image generation config */ + public ImageGenerationConfig getImageGeneration() { + return imageGeneration; + } + + /** + * Sets image generation config. @param imageGeneration the image generation + * config + */ + public void setImageGeneration(ImageGenerationConfig imageGeneration) { + this.imageGeneration = imageGeneration; + } + /** Sets tool-search config. @param toolSearch the tool-search config */ public void setToolSearch(ToolSearchConfig toolSearch) { this.toolSearch = toolSearch; @@ -949,6 +1017,25 @@ public void clearEnableSessionStore() { this.enableSessionStore = null; } + /** Gets enable GitHub cache store flag. @return the flag */ + public Boolean getEnableGitHubCacheStore() { + return enableGitHubCacheStore; + } + + /** + * Sets enable GitHub cache store flag. @param enableGitHubCacheStore the flag + */ + public void setEnableGitHubCacheStore(boolean enableGitHubCacheStore) { + this.enableGitHubCacheStore = enableGitHubCacheStore; + } + + /** + * Clears the enableGitHubCacheStore setting, reverting to the default behavior. + */ + public void clearEnableGitHubCacheStore() { + this.enableGitHubCacheStore = null; + } + /** Gets enable skills flag. @return the flag */ public Boolean getEnableSkills() { return enableSkills; @@ -1260,6 +1347,24 @@ public void clearEnforceManagedModelDefaults() { this.enforceManagedModelDefaults = null; } + /** @return whether this session is human-driven, or {@code null} when unset */ + public Boolean getInteractiveUser() { + return interactiveUser; + } + + /** + * @param interactiveUser + * whether a human drives this session + */ + public void setInteractiveUser(boolean interactiveUser) { + this.interactiveUser = interactiveUser; + } + + /** Clears the interactiveUser setting, reverting to the default behavior. */ + public void clearInteractiveUser() { + this.interactiveUser = null; + } + /** @return host-injected managed settings, or {@code null} when unset */ public ManagedSettings getManagedSettings() { return managedSettings; diff --git a/java/sdk/src/main/java/com/github/copilot/rpc/CustomAgentConfig.java b/java/sdk/src/main/java/com/github/copilot/rpc/CustomAgentConfig.java index 62de19b6a0..9f9c428ab4 100644 --- a/java/sdk/src/main/java/com/github/copilot/rpc/CustomAgentConfig.java +++ b/java/sdk/src/main/java/com/github/copilot/rpc/CustomAgentConfig.java @@ -36,6 +36,30 @@ @JsonInclude(JsonInclude.Include.NON_NULL) public class CustomAgentConfig { + @JsonProperty("hooks") + private Map>> hooks; + + /** + * Gets declarative hooks scoped to this agent. + * + * @return actions keyed by event name, or null when unset + */ + public Map>> getHooks() { + return hooks == null ? null : Collections.unmodifiableMap(hooks); + } + + /** + * Sets declarative hooks scoped to this agent. + * + * @param hooks + * actions keyed by event name + * @return this config for method chaining + */ + public CustomAgentConfig setHooks(Map>> hooks) { + this.hooks = hooks; + return this; + } + @JsonProperty("name") private String name; diff --git a/java/sdk/src/main/java/com/github/copilot/rpc/ImageGenerationConfig.java b/java/sdk/src/main/java/com/github/copilot/rpc/ImageGenerationConfig.java new file mode 100644 index 0000000000..79b336b4da --- /dev/null +++ b/java/sdk/src/main/java/com/github/copilot/rpc/ImageGenerationConfig.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. + */ + +package com.github.copilot.rpc; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; + +/** + * Experimental. This API may change or be removed in a future + * release. Opt in to image generation through an authorized Copilot image + * model. Omission disables on create/cold resume and preserves resident resume + * state. Not persisted: re-supply after runtime restart. Policy, permissions, + * offline mode, and tool filters still apply. BYOK image generation is not + * supported. + */ +@JsonInclude(JsonInclude.Include.NON_NULL) +@CopilotExperimental +public class ImageGenerationConfig { + @JsonProperty("enabled") + private Boolean enabled; + + /** + * Gets the image generation opt-in. + * + * @return true to opt in, false to disable, or null when unset + */ + public Boolean getEnabled() { + return enabled; + } + + /** + * Enables or disables image generation. + * + * @param enabled + * the opt-in, or null when unset + * @return this config for method chaining + */ + public ImageGenerationConfig setEnabled(Boolean enabled) { + this.enabled = enabled; + return this; + } +} diff --git a/java/sdk/src/main/java/com/github/copilot/rpc/PostToolUseFailureHookInput.java b/java/sdk/src/main/java/com/github/copilot/rpc/PostToolUseFailureHookInput.java index 820976b96f..c7e627a590 100644 --- a/java/sdk/src/main/java/com/github/copilot/rpc/PostToolUseFailureHookInput.java +++ b/java/sdk/src/main/java/com/github/copilot/rpc/PostToolUseFailureHookInput.java @@ -20,6 +20,42 @@ @JsonIgnoreProperties(ignoreUnknown = true) public class PostToolUseFailureHookInput { + @JsonProperty("agentId") + private String agentId; + + public String getAgentId() { + return agentId; + } + + public PostToolUseFailureHookInput setAgentId(String agentId) { + this.agentId = agentId; + return this; + } + + @JsonProperty("agentType") + private String agentType; + + public String getAgentType() { + return agentType; + } + + public PostToolUseFailureHookInput setAgentType(String agentType) { + this.agentType = agentType; + return this; + } + + @JsonProperty("parentAgentId") + private String parentAgentId; + + public String getParentAgentId() { + return parentAgentId; + } + + public PostToolUseFailureHookInput setParentAgentId(String parentAgentId) { + this.parentAgentId = parentAgentId; + return this; + } + @JsonProperty("sessionId") private String sessionId; diff --git a/java/sdk/src/main/java/com/github/copilot/rpc/PostToolUseHookInput.java b/java/sdk/src/main/java/com/github/copilot/rpc/PostToolUseHookInput.java index 9da5aee883..90bc779b82 100644 --- a/java/sdk/src/main/java/com/github/copilot/rpc/PostToolUseHookInput.java +++ b/java/sdk/src/main/java/com/github/copilot/rpc/PostToolUseHookInput.java @@ -16,6 +16,42 @@ @JsonIgnoreProperties(ignoreUnknown = true) public class PostToolUseHookInput { + @JsonProperty("agentId") + private String agentId; + + public String getAgentId() { + return agentId; + } + + public PostToolUseHookInput setAgentId(String agentId) { + this.agentId = agentId; + return this; + } + + @JsonProperty("agentType") + private String agentType; + + public String getAgentType() { + return agentType; + } + + public PostToolUseHookInput setAgentType(String agentType) { + this.agentType = agentType; + return this; + } + + @JsonProperty("parentAgentId") + private String parentAgentId; + + public String getParentAgentId() { + return parentAgentId; + } + + public PostToolUseHookInput setParentAgentId(String parentAgentId) { + this.parentAgentId = parentAgentId; + return this; + } + @JsonProperty("sessionId") private String sessionId; diff --git a/java/sdk/src/main/java/com/github/copilot/rpc/PreMcpToolCallHookInput.java b/java/sdk/src/main/java/com/github/copilot/rpc/PreMcpToolCallHookInput.java index 881065aa86..219f7ba50b 100644 --- a/java/sdk/src/main/java/com/github/copilot/rpc/PreMcpToolCallHookInput.java +++ b/java/sdk/src/main/java/com/github/copilot/rpc/PreMcpToolCallHookInput.java @@ -21,6 +21,42 @@ @JsonIgnoreProperties(ignoreUnknown = true) public class PreMcpToolCallHookInput { + @JsonProperty("agentId") + private String agentId; + + public String getAgentId() { + return agentId; + } + + public PreMcpToolCallHookInput setAgentId(String agentId) { + this.agentId = agentId; + return this; + } + + @JsonProperty("agentType") + private String agentType; + + public String getAgentType() { + return agentType; + } + + public PreMcpToolCallHookInput setAgentType(String agentType) { + this.agentType = agentType; + return this; + } + + @JsonProperty("parentAgentId") + private String parentAgentId; + + public String getParentAgentId() { + return parentAgentId; + } + + public PreMcpToolCallHookInput setParentAgentId(String parentAgentId) { + this.parentAgentId = parentAgentId; + return this; + } + @JsonProperty("sessionId") private String sessionId; diff --git a/java/sdk/src/main/java/com/github/copilot/rpc/PreToolUseHookInput.java b/java/sdk/src/main/java/com/github/copilot/rpc/PreToolUseHookInput.java index b6cb118590..c3840fc922 100644 --- a/java/sdk/src/main/java/com/github/copilot/rpc/PreToolUseHookInput.java +++ b/java/sdk/src/main/java/com/github/copilot/rpc/PreToolUseHookInput.java @@ -16,6 +16,42 @@ @JsonIgnoreProperties(ignoreUnknown = true) public class PreToolUseHookInput { + @JsonProperty("agentId") + private String agentId; + + public String getAgentId() { + return agentId; + } + + public PreToolUseHookInput setAgentId(String agentId) { + this.agentId = agentId; + return this; + } + + @JsonProperty("agentType") + private String agentType; + + public String getAgentType() { + return agentType; + } + + public PreToolUseHookInput setAgentType(String agentType) { + this.agentType = agentType; + return this; + } + + @JsonProperty("parentAgentId") + private String parentAgentId; + + public String getParentAgentId() { + return parentAgentId; + } + + public PreToolUseHookInput setParentAgentId(String parentAgentId) { + this.parentAgentId = parentAgentId; + return this; + } + @JsonProperty("sessionId") private String sessionId; diff --git a/java/sdk/src/main/java/com/github/copilot/rpc/ResumeSessionConfig.java b/java/sdk/src/main/java/com/github/copilot/rpc/ResumeSessionConfig.java index 927aa9555d..61f0e5a4b8 100644 --- a/java/sdk/src/main/java/com/github/copilot/rpc/ResumeSessionConfig.java +++ b/java/sdk/src/main/java/com/github/copilot/rpc/ResumeSessionConfig.java @@ -83,6 +83,7 @@ public class ResumeSessionConfig { private Boolean enableFileHooks; private Boolean enableHostGitOperations; private Boolean enableSessionStore; + private Boolean enableGitHubCacheStore; private Boolean enableSkills; private SkillProvider skillProvider; private String embeddingCacheStorage; @@ -97,11 +98,14 @@ public class ResumeSessionConfig { private DefaultAgentConfig defaultAgent; private String agent; private List skillDirectories; + private List agentDirectories; + private List hookDirectories; private List includedBuiltinSkills; private List instructionDirectories; private List pluginDirectories; private LargeToolOutputConfig largeOutput; private ToolSearchConfig toolSearch; + private ImageGenerationConfig imageGeneration; private MemoryConfiguration memory; private List disabledSkills; private List disabledMcpServers; @@ -120,6 +124,7 @@ public class ResumeSessionConfig { private CopilotExpAssignmentResponse expAssignments; private Boolean enableManagedSettings; private Boolean enforceManagedModelDefaults; + private Boolean interactiveUser; private Map featureFlags; private ManagedSettings managedSettings; @@ -1176,7 +1181,9 @@ public ResumeSessionConfig clearEnableOnDemandInstructionDiscovery() { } /** - * Gets whether file-based hooks are enabled. + * Gets whether repository-origin file hooks from {@code .github/hooks/} and + * trusted repository-configured roots are enabled. Does not control explicit + * {@link #getHookDirectories()} or callback hooks. * * @return an {@link java.util.Optional} containing {@code true} to enable file * hooks or {@code false} to disable them, or @@ -1280,6 +1287,45 @@ public ResumeSessionConfig clearEnableSessionStore() { return this; } + /** + * Gets whether the read-only GitHub cache store is requested. + * + * @return an {@link java.util.Optional} containing {@code true} to request the + * {@code github-cache-store} capability or {@code false} to decline it, + * or {@link java.util.Optional#empty()} to use the default behavior + * (disabled) + */ + @JsonIgnore + public Optional getEnableGitHubCacheStore() { + return Optional.ofNullable(enableGitHubCacheStore); + } + + /** + * Sets whether the read-only GitHub cache store is requested. + *

+ * The runtime registers the {@code github_sql} tool only when its + * {@code GITHUB_CACHE_STORE} feature flag is also on. + * + * @param enableGitHubCacheStore + * {@code true} to request the {@code github-cache-store} capability, + * {@code false} to decline it + * @return this config for method chaining + */ + public ResumeSessionConfig setEnableGitHubCacheStore(boolean enableGitHubCacheStore) { + this.enableGitHubCacheStore = enableGitHubCacheStore; + return this; + } + + /** + * Clears the enableGitHubCacheStore setting, reverting to the default behavior. + * + * @return this instance for method chaining + */ + public ResumeSessionConfig clearEnableGitHubCacheStore() { + this.enableGitHubCacheStore = null; + return this; + } + /** * Gets whether skill loading is enabled. * @@ -1659,7 +1705,11 @@ public ResumeSessionConfig setAgent(String agent) { } /** - * Gets the skill directories. + * Gets explicit discovery-only skill roots, not general filesystem grants. + * Available without configuration discovery, subject to skill/managed disables. + * Relative paths resolve once when supplied against the request's effective + * working directory; retained roots do not move when it changes. Null and + * omission are equivalent; an empty list clears only the SDK contribution. * * @return the list of skill directory paths */ @@ -1679,6 +1729,59 @@ public ResumeSessionConfig setSkillDirectories(List skillDirectories) { return this; } + /** + * Gets explicit discovery-only agent roots, separate from custom agent objects. + * No general filesystem grant. Available without configuration discovery, + * subject to source/managed disables. Relative paths resolve once when supplied + * against the request's effective working directory; retained roots do not + * move. Null and omission are equivalent; an empty list clears only the SDK + * contribution. + * + * @return the agent directory paths + */ + public List getAgentDirectories() { + return agentDirectories == null ? null : Collections.unmodifiableList(agentDirectories); + } + + /** + * Sets explicit discovery-only agent roots. See {@link #getAgentDirectories()}. + * + * @param agentDirectories + * the agent directory paths + * @return this config for method chaining + */ + public ResumeSessionConfig setAgentDirectories(List agentDirectories) { + this.agentDirectories = agentDirectories == null ? null : new ArrayList<>(agentDirectories); + return this; + } + + /** + * Gets explicit discovery-only file-hook roots, separate from callback hooks. + * No general filesystem grant. Available without configuration discovery or + * file-hook enablement (repository-origin hooks only), subject to hook/managed + * disables. Relative paths resolve once when supplied against the request's + * effective working directory; retained roots do not move. Null and omission + * are equivalent; an empty list clears only the SDK contribution. + * + * @return the file-hook directory paths + */ + public List getHookDirectories() { + return hookDirectories == null ? null : Collections.unmodifiableList(hookDirectories); + } + + /** + * Sets explicit discovery-only file-hook roots. See + * {@link #getHookDirectories()}. + * + * @param hookDirectories + * the file-hook directory paths + * @return this config for method chaining + */ + public ResumeSessionConfig setHookDirectories(List hookDirectories) { + this.hookDirectories = hookDirectories == null ? null : new ArrayList<>(hookDirectories); + return this; + } + /** * Gets the runtime-bundled skill allowlist. * @@ -1702,7 +1805,12 @@ public ResumeSessionConfig setIncludedBuiltinSkills(List includedBuiltin } /** - * Gets the additional directories to search for custom instruction files. + * Gets explicit discovery-only instruction roots using existing SDK formats. No + * general filesystem grant. Available without configuration discovery, subject + * to instruction/managed disables. Relative paths resolve once when supplied + * against the request's effective working directory; retained roots do not + * move. Null and omission are equivalent; an empty list clears only the SDK + * contribution. * * @return the list of instruction directory paths */ @@ -1773,6 +1881,29 @@ public ToolSearchConfig getToolSearch() { return toolSearch; } + /** + * Gets the non-persisted image generation opt-in. + * + * @return the image generation config, or null when unset + */ + @CopilotExperimental + public ImageGenerationConfig getImageGeneration() { + return imageGeneration; + } + + /** + * Sets the image generation opt-in. Re-supply after runtime restart. + * + * @param imageGeneration + * the image generation config + * @return this config for method chaining + */ + @CopilotExperimental + public ResumeSessionConfig setImageGeneration(ImageGenerationConfig imageGeneration) { + this.imageGeneration = imageGeneration; + return this; + } + /** * Sets the tool-search configuration. * @@ -2225,6 +2356,27 @@ public ResumeSessionConfig setEnforceManagedModelDefaults(boolean enforceManaged return this; } + /** @return optional declaration that this session is human-driven */ + @JsonIgnore + public Optional getInteractiveUser() { + return Optional.ofNullable(interactiveUser); + } + + /** + * Re-supplies that a human drives this session so a session loaded from storage + * keeps the declaration. It is not persisted and is ignored if the session is + * already active in the runtime process. + * + * @param interactiveUser + * {@code true} for interactive hosts such as editors or IDE chat + * panels + * @return this config for method chaining + */ + public ResumeSessionConfig setInteractiveUser(boolean interactiveUser) { + this.interactiveUser = interactiveUser; + return this; + } + /** @return host-injected managed settings, or {@code null} when unset */ public ManagedSettings getManagedSettings() { return managedSettings; @@ -2297,6 +2449,7 @@ public ResumeSessionConfig clone() { copy.enableFileHooks = this.enableFileHooks; copy.enableHostGitOperations = this.enableHostGitOperations; copy.enableSessionStore = this.enableSessionStore; + copy.enableGitHubCacheStore = this.enableGitHubCacheStore; copy.enableSkills = this.enableSkills; copy.skillProvider = this.skillProvider; copy.embeddingCacheStorage = this.embeddingCacheStorage; @@ -2311,6 +2464,8 @@ public ResumeSessionConfig clone() { copy.defaultAgent = this.defaultAgent; copy.agent = this.agent; copy.skillDirectories = this.skillDirectories != null ? new ArrayList<>(this.skillDirectories) : null; + copy.agentDirectories = this.agentDirectories != null ? new ArrayList<>(this.agentDirectories) : null; + copy.hookDirectories = this.hookDirectories != null ? new ArrayList<>(this.hookDirectories) : null; copy.includedBuiltinSkills = this.includedBuiltinSkills != null ? new ArrayList<>(this.includedBuiltinSkills) : null; @@ -2320,6 +2475,7 @@ public ResumeSessionConfig clone() { copy.pluginDirectories = this.pluginDirectories != null ? new ArrayList<>(this.pluginDirectories) : null; copy.largeOutput = this.largeOutput; copy.toolSearch = this.toolSearch; + copy.imageGeneration = this.imageGeneration; copy.memory = this.memory; copy.disabledSkills = this.disabledSkills != null ? new ArrayList<>(this.disabledSkills) : null; copy.disabledMcpServers = this.disabledMcpServers != null ? new ArrayList<>(this.disabledMcpServers) : null; @@ -2339,6 +2495,7 @@ public ResumeSessionConfig clone() { copy.expAssignments = this.expAssignments; copy.enableManagedSettings = this.enableManagedSettings; copy.enforceManagedModelDefaults = this.enforceManagedModelDefaults; + copy.interactiveUser = this.interactiveUser; copy.managedSettings = this.managedSettings; return copy; } diff --git a/java/sdk/src/main/java/com/github/copilot/rpc/ResumeSessionRequest.java b/java/sdk/src/main/java/com/github/copilot/rpc/ResumeSessionRequest.java index 2d6e2474b6..ae91dc71a1 100644 --- a/java/sdk/src/main/java/com/github/copilot/rpc/ResumeSessionRequest.java +++ b/java/sdk/src/main/java/com/github/copilot/rpc/ResumeSessionRequest.java @@ -143,6 +143,10 @@ public final class ResumeSessionRequest { @JsonInclude(JsonInclude.Include.NON_NULL) private Boolean enableSessionStore; + @JsonProperty("enableGitHubCacheStore") + @JsonInclude(JsonInclude.Include.NON_NULL) + private Boolean enableGitHubCacheStore; + @JsonProperty("enableSkills") @JsonInclude(JsonInclude.Include.NON_NULL) private Boolean enableSkills; @@ -197,6 +201,12 @@ public final class ResumeSessionRequest { @JsonProperty("skillDirectories") private List skillDirectories; + @JsonProperty("agentDirectories") + private List agentDirectories; + + @JsonProperty("hookDirectories") + private List hookDirectories; + @JsonProperty("instructionDirectories") private List instructionDirectories; @@ -209,6 +219,9 @@ public final class ResumeSessionRequest { @JsonProperty("toolSearch") private ToolSearchConfig toolSearch; + @JsonProperty("imageGeneration") + private ImageGenerationConfig imageGeneration; + @JsonProperty("memory") private MemoryConfiguration memory; @@ -269,6 +282,10 @@ public final class ResumeSessionRequest { @JsonInclude(JsonInclude.Include.NON_NULL) private Boolean enforceManagedModelDefaults; + @JsonProperty("interactiveUser") + @JsonInclude(JsonInclude.Include.NON_NULL) + private Boolean interactiveUser; + @JsonProperty("managedSettings") @JsonInclude(JsonInclude.Include.NON_NULL) private ManagedSettings managedSettings; @@ -757,6 +774,25 @@ public void clearEnableSessionStore() { this.enableSessionStore = null; } + /** Gets enable GitHub cache store flag. @return the flag */ + public Boolean getEnableGitHubCacheStore() { + return enableGitHubCacheStore; + } + + /** + * Sets enable GitHub cache store flag. @param enableGitHubCacheStore the flag + */ + public void setEnableGitHubCacheStore(boolean enableGitHubCacheStore) { + this.enableGitHubCacheStore = enableGitHubCacheStore; + } + + /** + * Clears the enableGitHubCacheStore setting, reverting to the default behavior. + */ + public void clearEnableGitHubCacheStore() { + this.enableGitHubCacheStore = null; + } + /** Gets enable skills flag. @return the flag */ public Boolean getEnableSkills() { return enableSkills; @@ -993,6 +1029,31 @@ public void setSkillDirectories(List skillDirectories) { this.skillDirectories = skillDirectories; } + /** Gets explicit agent discovery roots. @return the directories */ + public List getAgentDirectories() { + return agentDirectories == null ? null : Collections.unmodifiableList(agentDirectories); + } + + /** + * Sets explicit agent discovery roots. @param agentDirectories the directories + */ + public void setAgentDirectories(List agentDirectories) { + this.agentDirectories = agentDirectories; + } + + /** Gets explicit file-hook discovery roots. @return the directories */ + public List getHookDirectories() { + return hookDirectories == null ? null : Collections.unmodifiableList(hookDirectories); + } + + /** + * Sets explicit file-hook discovery roots. @param hookDirectories the + * directories + */ + public void setHookDirectories(List hookDirectories) { + this.hookDirectories = hookDirectories; + } + /** Gets instruction directories. @return the instruction directories */ public List getInstructionDirectories() { return instructionDirectories == null ? null : Collections.unmodifiableList(instructionDirectories); @@ -1030,6 +1091,19 @@ public ToolSearchConfig getToolSearch() { return toolSearch; } + /** Gets image generation config. @return the image generation config */ + public ImageGenerationConfig getImageGeneration() { + return imageGeneration; + } + + /** + * Sets image generation config. @param imageGeneration the image generation + * config + */ + public void setImageGeneration(ImageGenerationConfig imageGeneration) { + this.imageGeneration = imageGeneration; + } + /** Sets tool-search config. @param toolSearch the tool-search config */ public void setToolSearch(ToolSearchConfig toolSearch) { this.toolSearch = toolSearch; @@ -1290,6 +1364,24 @@ public void clearEnforceManagedModelDefaults() { this.enforceManagedModelDefaults = null; } + /** @return whether this session is human-driven, or {@code null} when unset */ + public Boolean getInteractiveUser() { + return interactiveUser; + } + + /** + * @param interactiveUser + * whether a human drives this session + */ + public void setInteractiveUser(boolean interactiveUser) { + this.interactiveUser = interactiveUser; + } + + /** Clears the interactiveUser setting, reverting to the default behavior. */ + public void clearInteractiveUser() { + this.interactiveUser = null; + } + /** @return host-injected managed settings, or {@code null} when unset */ public ManagedSettings getManagedSettings() { return managedSettings; diff --git a/java/sdk/src/main/java/com/github/copilot/rpc/SessionConfig.java b/java/sdk/src/main/java/com/github/copilot/rpc/SessionConfig.java index 926706872b..8ae3f46b75 100644 --- a/java/sdk/src/main/java/com/github/copilot/rpc/SessionConfig.java +++ b/java/sdk/src/main/java/com/github/copilot/rpc/SessionConfig.java @@ -43,6 +43,7 @@ public class SessionConfig { private String sessionId; + private String name; private String clientName; private String model; private String reasoningEffort; @@ -84,11 +85,14 @@ public class SessionConfig { private String agent; private InfiniteSessionConfig infiniteSessions; private List skillDirectories; + private List agentDirectories; + private List hookDirectories; private List includedBuiltinSkills; private List instructionDirectories; private List pluginDirectories; private LargeToolOutputConfig largeOutput; private ToolSearchConfig toolSearch; + private ImageGenerationConfig imageGeneration; private MemoryConfiguration memory; private List disabledSkills; private List disabledMcpServers; @@ -101,6 +105,7 @@ public class SessionConfig { private Boolean enableFileHooks; private Boolean enableHostGitOperations; private Boolean enableSessionStore; + private Boolean enableGitHubCacheStore; private Boolean enableSkills; private SkillProvider skillProvider; private String embeddingCacheStorage; @@ -120,6 +125,7 @@ public class SessionConfig { private CopilotExpAssignmentResponse expAssignments; private Boolean enableManagedSettings; private Boolean enforceManagedModelDefaults; + private Boolean interactiveUser; private Map featureFlags; private ManagedSettings managedSettings; @@ -146,6 +152,31 @@ public SessionConfig setSessionId(String sessionId) { return this; } + /** + * Gets the create-time session name. + * + * @return the session name, or {@code null} if the session is unnamed + */ + public String getName() { + return name; + } + + /** + * Names the session at creation, as {@code session.name.set} would, and is + * validated the same way: non-empty, no leading or trailing whitespace, at most + * 100 UTF-16 units, no control characters, and no double quotes. A name also + * makes the session persist from the start, so it can be listed and resumed + * without ever running a turn. Local sessions only: cloud creation rejects it. + * + * @param name + * the session name + * @return this config instance for method chaining + */ + public SessionConfig setName(String name) { + this.name = name; + return this; + } + /** * Gets the client name used to identify the application using the SDK. * @@ -1235,7 +1266,11 @@ public SessionConfig setInfiniteSessions(InfiniteSessionConfig infiniteSessions) } /** - * Gets the skill directories. + * Gets explicit discovery-only skill roots, not general filesystem grants. + * Available without configuration discovery, subject to skill/managed disables. + * Relative paths resolve once when supplied against the request's effective + * working directory; retained roots do not move when it changes. Null and + * omission are equivalent; an empty list clears only the SDK contribution. * * @return the list of skill directory paths */ @@ -1259,6 +1294,59 @@ public SessionConfig setSkillDirectories(List skillDirectories) { return this; } + /** + * Gets explicit discovery-only agent roots, separate from custom agent objects. + * No general filesystem grant. Available without configuration discovery, + * subject to source/managed disables. Relative paths resolve once when supplied + * against the request's effective working directory; retained roots do not + * move. Null and omission are equivalent; an empty list clears only the SDK + * contribution. + * + * @return the agent directory paths + */ + public List getAgentDirectories() { + return agentDirectories == null ? null : Collections.unmodifiableList(agentDirectories); + } + + /** + * Sets explicit discovery-only agent roots. See {@link #getAgentDirectories()}. + * + * @param agentDirectories + * the agent directory paths + * @return this config for method chaining + */ + public SessionConfig setAgentDirectories(List agentDirectories) { + this.agentDirectories = agentDirectories == null ? null : new ArrayList<>(agentDirectories); + return this; + } + + /** + * Gets explicit discovery-only file-hook roots, separate from callback hooks. + * No general filesystem grant. Available without configuration discovery or + * file-hook enablement (repository-origin hooks only), subject to hook/managed + * disables. Relative paths resolve once when supplied against the request's + * effective working directory; retained roots do not move. Null and omission + * are equivalent; an empty list clears only the SDK contribution. + * + * @return the file-hook directory paths + */ + public List getHookDirectories() { + return hookDirectories == null ? null : Collections.unmodifiableList(hookDirectories); + } + + /** + * Sets explicit discovery-only file-hook roots. See + * {@link #getHookDirectories()}. + * + * @param hookDirectories + * the file-hook directory paths + * @return this config for method chaining + */ + public SessionConfig setHookDirectories(List hookDirectories) { + this.hookDirectories = hookDirectories == null ? null : new ArrayList<>(hookDirectories); + return this; + } + /** * Gets the runtime-bundled skill allowlist. * @@ -1282,7 +1370,12 @@ public SessionConfig setIncludedBuiltinSkills(List includedBuiltinSkills } /** - * Gets the additional directories to search for custom instruction files. + * Gets explicit discovery-only instruction roots using existing SDK formats. No + * general filesystem grant. Available without configuration discovery, subject + * to instruction/managed disables. Relative paths resolve once when supplied + * against the request's effective working directory; retained roots do not + * move. Null and omission are equivalent; an empty list clears only the SDK + * contribution. * * @return the list of instruction directory paths */ @@ -1353,6 +1446,29 @@ public ToolSearchConfig getToolSearch() { return toolSearch; } + /** + * Gets the non-persisted image generation opt-in. + * + * @return the image generation config, or null when unset + */ + @CopilotExperimental + public ImageGenerationConfig getImageGeneration() { + return imageGeneration; + } + + /** + * Sets the image generation opt-in. Re-supply after runtime restart. + * + * @param imageGeneration + * the image generation config + * @return this config for method chaining + */ + @CopilotExperimental + public SessionConfig setImageGeneration(ImageGenerationConfig imageGeneration) { + this.imageGeneration = imageGeneration; + return this; + } + /** * Sets the tool-search override configuration. When {@code null}, the runtime * default tool-search behavior applies. @@ -1624,7 +1740,9 @@ public SessionConfig clearRefreshCustomInstructions() { } /** - * Gets whether file-based hooks are enabled. + * Gets whether repository-origin file hooks from {@code .github/hooks/} and + * trusted repository-configured roots are enabled. Does not control explicit + * {@link #getHookDirectories()} or callback hooks. * * @return an {@link java.util.Optional} containing {@code true} to enable file * hooks or {@code false} to disable them, or @@ -1728,6 +1846,45 @@ public SessionConfig clearEnableSessionStore() { return this; } + /** + * Gets whether the read-only GitHub cache store is requested. + * + * @return an {@link java.util.Optional} containing {@code true} to request the + * {@code github-cache-store} capability or {@code false} to decline it, + * or {@link java.util.Optional#empty()} to use the default behavior + * (disabled) + */ + @JsonIgnore + public Optional getEnableGitHubCacheStore() { + return Optional.ofNullable(enableGitHubCacheStore); + } + + /** + * Sets whether the read-only GitHub cache store is requested. + *

+ * The runtime registers the {@code github_sql} tool only when its + * {@code GITHUB_CACHE_STORE} feature flag is also on. + * + * @param enableGitHubCacheStore + * {@code true} to request the {@code github-cache-store} capability, + * {@code false} to decline it + * @return this config instance for method chaining + */ + public SessionConfig setEnableGitHubCacheStore(boolean enableGitHubCacheStore) { + this.enableGitHubCacheStore = enableGitHubCacheStore; + return this; + } + + /** + * Clears the enableGitHubCacheStore setting, reverting to the default behavior. + * + * @return this instance for method chaining + */ + public SessionConfig clearEnableGitHubCacheStore() { + this.enableGitHubCacheStore = null; + return this; + } + /** * Gets whether skill loading is enabled. * @@ -2317,6 +2474,32 @@ public SessionConfig setEnforceManagedModelDefaults(boolean enforceManagedModelD return this; } + /** + * Gets whether this session is human-driven. + * + * @return an optional containing the declaration, or empty when unset + */ + @JsonIgnore + public Optional getInteractiveUser() { + return Optional.ofNullable(interactiveUser); + } + + /** + * Declares that a human drives this session's conversation, enabling + * user-message analysis telemetry for user-authored messages. Automated + * messages remain excluded. Defaults to false for SDK sessions. Ignored for + * cloud sessions. + * + * @param interactiveUser + * {@code true} for interactive hosts such as editors or IDE chat + * panels + * @return this config instance for method chaining + */ + public SessionConfig setInteractiveUser(boolean interactiveUser) { + this.interactiveUser = interactiveUser; + return this; + } + /** * Gets host-injected managed settings for this session. * @@ -2399,6 +2582,8 @@ public SessionConfig clone() { copy.agent = this.agent; copy.infiniteSessions = this.infiniteSessions; copy.skillDirectories = this.skillDirectories != null ? new ArrayList<>(this.skillDirectories) : null; + copy.agentDirectories = this.agentDirectories != null ? new ArrayList<>(this.agentDirectories) : null; + copy.hookDirectories = this.hookDirectories != null ? new ArrayList<>(this.hookDirectories) : null; copy.includedBuiltinSkills = this.includedBuiltinSkills != null ? new ArrayList<>(this.includedBuiltinSkills) : null; @@ -2408,6 +2593,7 @@ public SessionConfig clone() { copy.pluginDirectories = this.pluginDirectories != null ? new ArrayList<>(this.pluginDirectories) : null; copy.largeOutput = this.largeOutput; copy.toolSearch = this.toolSearch; + copy.imageGeneration = this.imageGeneration; copy.memory = this.memory; copy.disabledSkills = this.disabledSkills != null ? new ArrayList<>(this.disabledSkills) : null; copy.disabledMcpServers = this.disabledMcpServers != null ? new ArrayList<>(this.disabledMcpServers) : null; @@ -2420,6 +2606,7 @@ public SessionConfig clone() { copy.enableFileHooks = this.enableFileHooks; copy.enableHostGitOperations = this.enableHostGitOperations; copy.enableSessionStore = this.enableSessionStore; + copy.enableGitHubCacheStore = this.enableGitHubCacheStore; copy.enableSkills = this.enableSkills; copy.skillProvider = this.skillProvider; copy.embeddingCacheStorage = this.embeddingCacheStorage; @@ -2440,6 +2627,7 @@ public SessionConfig clone() { copy.expAssignments = this.expAssignments; copy.enableManagedSettings = this.enableManagedSettings; copy.enforceManagedModelDefaults = this.enforceManagedModelDefaults; + copy.interactiveUser = this.interactiveUser; copy.managedSettings = this.managedSettings; return copy; } diff --git a/java/sdk/src/main/java/com/github/copilot/rpc/SessionEndHookInput.java b/java/sdk/src/main/java/com/github/copilot/rpc/SessionEndHookInput.java index f29b698385..0a96bb520c 100644 --- a/java/sdk/src/main/java/com/github/copilot/rpc/SessionEndHookInput.java +++ b/java/sdk/src/main/java/com/github/copilot/rpc/SessionEndHookInput.java @@ -4,8 +4,17 @@ package com.github.copilot.rpc; +import java.io.IOException; + import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.deser.std.StringDeserializer; /** * Input for a session-end hook. @@ -25,12 +34,45 @@ * @param finalMessage * the final message, or {@code null} * @param error - * the error message, or {@code null} + * the error message, or {@code null}; the CLI sends an error object, + * whose {@code message} this is * @since 1.0.7 + * @param agentId + * optional subagent identity, absent for root hooks and older + * runtimes + * @param agentType + * optional subagent identity, absent for root hooks and older + * runtimes + * @param parentAgentId + * optional subagent identity, absent for root hooks and older + * runtimes */ @JsonIgnoreProperties(ignoreUnknown = true) public record SessionEndHookInput(@JsonProperty("sessionId") String sessionId, @JsonProperty("timestamp") long timestamp, @JsonProperty("cwd") String cwd, @JsonProperty("reason") String reason, @JsonProperty("finalMessage") String finalMessage, - @JsonProperty("error") String error) { + @JsonProperty("error") @JsonDeserialize(using = HookErrorTextDeserializer.class) String error, + @JsonProperty("agentId") String agentId, @JsonProperty("agentType") String agentType, + @JsonProperty("parentAgentId") String parentAgentId) { + public SessionEndHookInput(String sessionId, long timestamp, String cwd, String reason, String finalMessage, + String error) { + this(sessionId, timestamp, cwd, reason, finalMessage, error, null, null, null); + } + + /** + * Reads the CLI's error object ({@code {"message"}}) as its message, and an + * object without a string message as its JSON. A string passes through. + */ + private static final class HookErrorTextDeserializer extends JsonDeserializer { + + @Override + public String deserialize(JsonParser parser, DeserializationContext context) throws IOException { + if (parser.currentToken() == JsonToken.START_OBJECT) { + JsonNode error = context.readTree(parser); + JsonNode message = error.get("message"); + return message != null && message.isTextual() ? message.textValue() : error.toString(); + } + return StringDeserializer.instance.deserialize(parser, context); + } + } } diff --git a/java/sdk/src/main/java/com/github/copilot/rpc/SessionStartHookInput.java b/java/sdk/src/main/java/com/github/copilot/rpc/SessionStartHookInput.java index d6e5b37596..1a044d289f 100644 --- a/java/sdk/src/main/java/com/github/copilot/rpc/SessionStartHookInput.java +++ b/java/sdk/src/main/java/com/github/copilot/rpc/SessionStartHookInput.java @@ -24,9 +24,23 @@ * @param initialPrompt * the initial prompt, or {@code null} * @since 1.0.7 + * @param agentId + * optional subagent identity, absent for root hooks and older + * runtimes + * @param agentType + * optional subagent identity, absent for root hooks and older + * runtimes + * @param parentAgentId + * optional subagent identity, absent for root hooks and older + * runtimes */ @JsonIgnoreProperties(ignoreUnknown = true) public record SessionStartHookInput(@JsonProperty("sessionId") String sessionId, @JsonProperty("timestamp") long timestamp, @JsonProperty("cwd") String cwd, - @JsonProperty("source") String source, @JsonProperty("initialPrompt") String initialPrompt) { + @JsonProperty("source") String source, @JsonProperty("initialPrompt") String initialPrompt, + @JsonProperty("agentId") String agentId, @JsonProperty("agentType") String agentType, + @JsonProperty("parentAgentId") String parentAgentId) { + public SessionStartHookInput(String sessionId, long timestamp, String cwd, String source, String initialPrompt) { + this(sessionId, timestamp, cwd, source, initialPrompt, null, null, null); + } } diff --git a/java/sdk/src/main/java/com/github/copilot/rpc/SubagentStartHookInput.java b/java/sdk/src/main/java/com/github/copilot/rpc/SubagentStartHookInput.java index 0ab4d426b4..5e67057469 100644 --- a/java/sdk/src/main/java/com/github/copilot/rpc/SubagentStartHookInput.java +++ b/java/sdk/src/main/java/com/github/copilot/rpc/SubagentStartHookInput.java @@ -24,11 +24,26 @@ * the optional subagent display name * @param agentDescription * the optional subagent description + * @param agentId + * optional subagent identity, absent for root hooks and older + * runtimes + * @param agentType + * optional subagent identity, absent for root hooks and older + * runtimes + * @param parentAgentId + * optional subagent identity, absent for root hooks and older + * runtimes */ @JsonIgnoreProperties(ignoreUnknown = true) public record SubagentStartHookInput(@JsonProperty("sessionId") String sessionId, @JsonProperty("timestamp") long timestamp, @JsonProperty("cwd") String cwd, @JsonProperty("transcriptPath") String transcriptPath, @JsonProperty("agentName") String agentName, @JsonProperty("agentDisplayName") String agentDisplayName, - @JsonProperty("agentDescription") String agentDescription) { + @JsonProperty("agentDescription") String agentDescription, @JsonProperty("agentId") String agentId, + @JsonProperty("agentType") String agentType, @JsonProperty("parentAgentId") String parentAgentId) { + public SubagentStartHookInput(String sessionId, long timestamp, String cwd, String transcriptPath, String agentName, + String agentDisplayName, String agentDescription) { + this(sessionId, timestamp, cwd, transcriptPath, agentName, agentDisplayName, agentDescription, null, null, + null); + } } diff --git a/java/sdk/src/main/java/com/github/copilot/rpc/SubagentStopHookInput.java b/java/sdk/src/main/java/com/github/copilot/rpc/SubagentStopHookInput.java index 299238e034..6fea33e288 100644 --- a/java/sdk/src/main/java/com/github/copilot/rpc/SubagentStopHookInput.java +++ b/java/sdk/src/main/java/com/github/copilot/rpc/SubagentStopHookInput.java @@ -32,6 +32,9 @@ * the reason the subagent stopped * @param response * the subagent's last assistant response + * @param parentAgentId + * optional subagent identity, absent for root hooks and older + * runtimes */ @JsonIgnoreProperties(ignoreUnknown = true) public record SubagentStopHookInput(@JsonProperty("sessionId") String sessionId, @@ -40,5 +43,11 @@ public record SubagentStopHookInput(@JsonProperty("sessionId") String sessionId, @JsonProperty("agentType") String agentType, @JsonProperty("agentId") String agentId, @JsonProperty("agentDisplayName") String agentDisplayName, @JsonProperty("agentDescription") String agentDescription, @JsonProperty("stopReason") String stopReason, - @JsonProperty("response") String response) { + @JsonProperty("response") String response, @JsonProperty("parentAgentId") String parentAgentId) { + public SubagentStopHookInput(String sessionId, long timestamp, String cwd, String transcriptPath, String agentName, + String agentType, String agentId, String agentDisplayName, String agentDescription, String stopReason, + String response) { + this(sessionId, timestamp, cwd, transcriptPath, agentName, agentType, agentId, agentDisplayName, + agentDescription, stopReason, response, null); + } } diff --git a/java/sdk/src/main/java/com/github/copilot/rpc/UserPromptSubmittedHookInput.java b/java/sdk/src/main/java/com/github/copilot/rpc/UserPromptSubmittedHookInput.java index 8b37df6773..7ed11ecd02 100644 --- a/java/sdk/src/main/java/com/github/copilot/rpc/UserPromptSubmittedHookInput.java +++ b/java/sdk/src/main/java/com/github/copilot/rpc/UserPromptSubmittedHookInput.java @@ -23,9 +23,22 @@ * @param prompt * the user's prompt text * @since 1.0.7 + * @param agentId + * optional subagent identity, absent for root hooks and older + * runtimes + * @param agentType + * optional subagent identity, absent for root hooks and older + * runtimes + * @param parentAgentId + * optional subagent identity, absent for root hooks and older + * runtimes */ @JsonIgnoreProperties(ignoreUnknown = true) public record UserPromptSubmittedHookInput(@JsonProperty("sessionId") String sessionId, @JsonProperty("timestamp") long timestamp, @JsonProperty("cwd") String cwd, - @JsonProperty("prompt") String prompt) { + @JsonProperty("prompt") String prompt, @JsonProperty("agentId") String agentId, + @JsonProperty("agentType") String agentType, @JsonProperty("parentAgentId") String parentAgentId) { + public UserPromptSubmittedHookInput(String sessionId, long timestamp, String cwd, String prompt) { + this(sessionId, timestamp, cwd, prompt, null, null, null); + } } diff --git a/java/sdk/src/main/java/com/github/copilot/rpc/UserPromptTransformedHookInput.java b/java/sdk/src/main/java/com/github/copilot/rpc/UserPromptTransformedHookInput.java index ea17596585..abb064c00d 100644 --- a/java/sdk/src/main/java/com/github/copilot/rpc/UserPromptTransformedHookInput.java +++ b/java/sdk/src/main/java/com/github/copilot/rpc/UserPromptTransformedHookInput.java @@ -21,9 +21,24 @@ * @param transformedPrompt * the model-facing prompt after runtime transformations * @since 1.0.11 + * @param agentId + * optional subagent identity, absent for root hooks and older + * runtimes + * @param agentType + * optional subagent identity, absent for root hooks and older + * runtimes + * @param parentAgentId + * optional subagent identity, absent for root hooks and older + * runtimes */ @JsonIgnoreProperties(ignoreUnknown = true) public record UserPromptTransformedHookInput(@JsonProperty("sessionId") String sessionId, @JsonProperty("timestamp") long timestamp, @JsonProperty("cwd") String cwd, - @JsonProperty("prompt") String prompt, @JsonProperty("transformedPrompt") String transformedPrompt) { + @JsonProperty("prompt") String prompt, @JsonProperty("transformedPrompt") String transformedPrompt, + @JsonProperty("agentId") String agentId, @JsonProperty("agentType") String agentType, + @JsonProperty("parentAgentId") String parentAgentId) { + public UserPromptTransformedHookInput(String sessionId, long timestamp, String cwd, String prompt, + String transformedPrompt) { + this(sessionId, timestamp, cwd, prompt, transformedPrompt, null, null, null); + } } diff --git a/java/sdk/src/test/java/com/github/copilot/AgentInfoTest.java b/java/sdk/src/test/java/com/github/copilot/AgentInfoTest.java index 40654292f8..f2735cae7d 100644 --- a/java/sdk/src/test/java/com/github/copilot/AgentInfoTest.java +++ b/java/sdk/src/test/java/com/github/copilot/AgentInfoTest.java @@ -6,6 +6,9 @@ import static org.junit.jupiter.api.Assertions.*; +import java.util.List; +import java.util.Map; + import org.junit.jupiter.api.Test; import com.github.copilot.rpc.AgentInfo; @@ -22,6 +25,7 @@ void defaultValuesAreNull() { assertNull(agent.getDisplayName()); assertNull(agent.getDescription()); assertNull(agent.getModel()); + assertNull(agent.getHooks()); } @Test @@ -52,6 +56,21 @@ void modelGetterSetter() { assertEquals("alpha/sonnet", agent.getModel()); } + @Test + void hooksGetterSetterAndJsonRoundTrip() throws Exception { + Map>> hooks = Map.of("preToolUse", + List.of(Map.of("type", "command", "bash", "echo scoped-hook", "timeoutSec", 10))); + var agent = new AgentInfo().setName("coder").setHooks(hooks); + assertEquals(hooks, agent.getHooks()); + + var mapper = JsonRpcClient.getObjectMapper(); + var json = mapper.valueToTree(agent); + assertEquals(mapper.valueToTree(hooks), json.get("hooks")); + var restored = mapper.treeToValue(json, AgentInfo.class); + assertEquals(hooks, restored.getHooks()); + assertNull(mapper.readValue("{\"name\":\"plain-agent\"}", AgentInfo.class).getHooks()); + } + @Test void fluentChainingReturnsThis() { var agent = new AgentInfo().setName("coder").setDisplayName("Code Assistant") @@ -70,5 +89,6 @@ void fluentChainingReturnsSameInstance() { assertSame(agent, agent.setDisplayName("Test")); assertSame(agent, agent.setDescription("A test agent")); assertSame(agent, agent.setModel("alpha/sonnet")); + assertSame(agent, agent.setHooks(Map.of())); } } diff --git a/java/sdk/src/test/java/com/github/copilot/AutoTierIT.java b/java/sdk/src/test/java/com/github/copilot/AutoTierIT.java index d93cbf995b..1ce94b4b19 100644 --- a/java/sdk/src/test/java/com/github/copilot/AutoTierIT.java +++ b/java/sdk/src/test/java/com/github/copilot/AutoTierIT.java @@ -5,6 +5,8 @@ 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.CompletableFuture; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; @@ -20,6 +22,7 @@ import com.github.copilot.generated.rpc.SessionEventLogReadParams; import com.github.copilot.rpc.AutoTier; import com.github.copilot.rpc.CapiSessionOptions; +import com.github.copilot.rpc.CopilotClientOptions; import com.github.copilot.rpc.MessageOptions; import com.github.copilot.rpc.PermissionHandler; import com.github.copilot.rpc.ResumeSessionConfig; @@ -200,12 +203,19 @@ void shouldRestoreAndOverrideFastAutoTierOnColdResume() throws Exception { void shouldCommitFastAutoTierAfterSuccessfulTurn() throws Exception { ctx.configureForTest("auto_tier", "should_commit_fast_auto_tier_after_successful_turn"); - try (CopilotClient client = ctx.createClient(); + // Keep this invocation's detach trace outside the home removed by context + // teardown. + Path logDirectory = Files.createTempDirectory(Files.createDirectories(Path.of("target", "failsafe-reports")), + "auto-tier-detach-").toAbsolutePath(); + try (CopilotClient client = ctx.createClient(new CopilotClientOptions().setLogLevel("debug") + .setCliArgs(new String[]{"--log-dir", logDirectory.toString()})); CopilotSession session = client .createSession(new SessionConfig().setModel(MODEL_ID) .setOnPermissionRequest(PermissionHandler.APPROVE_ALL).setCapi(new CapiSessionOptions() .setAutoTier(AutoTier.EFFICIENCY).setEnableWebSocketResponses(false))) .get(30, TimeUnit.SECONDS)) { + System.err.println( + "AutoTierIT detach diagnostics: session=" + session.getSessionId() + " logs=" + logDirectory); var modelChangedFuture = new CompletableFuture(); try (var subscription = session.on(SessionModelChangeEvent.class, event -> { if (event.getData().autoTier() == com.github.copilot.generated.AutoTier.FAST) { diff --git a/java/sdk/src/test/java/com/github/copilot/CliServerManagerTest.java b/java/sdk/src/test/java/com/github/copilot/CliServerManagerTest.java index 462fbc17cd..9dea8d8c2a 100644 --- a/java/sdk/src/test/java/com/github/copilot/CliServerManagerTest.java +++ b/java/sdk/src/test/java/com/github/copilot/CliServerManagerTest.java @@ -7,13 +7,22 @@ import static org.junit.jupiter.api.Assertions.*; import java.io.IOException; +import java.lang.reflect.InvocationTargetException; import java.net.ServerSocket; import java.net.URI; +import java.nio.file.Files; +import java.nio.file.InvalidPathException; import java.nio.file.Path; +import java.util.List; import java.util.Map; +import java.util.concurrent.CompletionException; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledOnOs; +import org.junit.jupiter.api.condition.OS; import org.junit.jupiter.api.io.TempDir; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; import com.github.copilot.ffi.NativeRuntimeLoader; import com.github.copilot.rpc.CopilotClientMode; @@ -190,9 +199,167 @@ void processInfoWithNullPort() { assertNull(info.port()); } - // ===== resolveCliCommand tests (via startCliServer) ===== - // resolveCliCommand is private, so we test indirectly through startCliServer - // with specific cliPath values. + // ===== resolveCliCommand tests ===== + + @ParameterizedTest + @ValueSource(strings = {"NoDefaultCurrentDirectoryInExePath", "nodefaultcurrentdirectoryinexepath"}) + @EnabledOnOs(OS.WINDOWS) + void bareWindowsCliPathHonorsCurrentDirectorySearchOptOut(String optOutKey) throws Exception { + Path bin = Files.createDirectory(tempDir.resolve("bin")); + Files.createFile(tempDir.resolve("copilot.EXE")); + Path executable = Files.createFile(bin.resolve("copilot.EXE")); + var options = new CopilotClientOptions().setCliPath("copilot").setCwd(tempDir.toString()).setUseStdio(true) + .setEnvironment(Map.of("Path", bin.toString(), "PATHEXT", ".EXE", optOutKey, "")); + + assertClientAttemptsExecutable(options, executable); + } + + @Test + @EnabledOnOs(OS.WINDOWS) + void bareWindowsCliPathSearchesCurrentDirectoryWithoutOptOut() throws Exception { + Path bin = Files.createDirectory(tempDir.resolve("bin")); + Path executable = Files.createFile(tempDir.resolve("copilot.EXE")); + Files.createFile(bin.resolve("copilot.EXE")); + var options = new CopilotClientOptions().setCliPath("copilot").setCwd(tempDir.toString()).setUseStdio(true) + .setEnvironment(Map.of("PATH", bin.toString(), "PATHEXT", ".EXE")); + + assertClientAttemptsExecutable(options, executable); + } + + @ParameterizedTest + @ValueSource(booleans = {false, true}) + @EnabledOnOs(OS.WINDOWS) + void explicitWindowsCurrentDirectorySearchIsPreservedWithOptOut(boolean relativeCliPath) throws Exception { + Path executable = Files.createFile(tempDir.resolve(".").resolve("copilot.EXE")); + var options = new CopilotClientOptions().setCliPath(relativeCliPath ? ".\\copilot.EXE" : "copilot") + .setCwd(tempDir.toString()).setUseStdio(true).setEnvironment(Map.of("PATH", relativeCliPath ? "" : ".", + "PATHEXT", ".EXE", "NoDefaultCurrentDirectoryInExePath", "")); + + assertClientAttemptsExecutable(options, executable); + } + + @ParameterizedTest + @ValueSource(strings = {"", "C:\\invalid|entry"}) + @EnabledOnOs(OS.WINDOWS) + void invalidWindowsCommandIsNotPassedToShellWhenCurrentDirectorySearchIsDisabled(String path) { + var options = new CopilotClientOptions().setCliPath("copilot|invalid").setCwd(tempDir.toString()) + .setUseStdio(true) + .setEnvironment(Map.of("PATH", path, "PATHEXT", ".EXE", "NoDefaultCurrentDirectoryInExePath", "")); + + try (var client = new CopilotClient(options)) { + Throwable error = assertThrows(CompletionException.class, () -> client.start().join()); + while (error.getCause() != null) { + error = error.getCause(); + } + var invalidPath = assertInstanceOf(InvalidPathException.class, error); + assertEquals("copilot|invalid.EXE", invalidPath.getInput()); + } + } + + private static void assertClientAttemptsExecutable(CopilotClientOptions options, Path executable) { + // Empty executables expose the attempted OS launch path without running a + // child. + try (var client = new CopilotClient(options)) { + Throwable error = assertThrows(CompletionException.class, () -> client.start().join()); + while (!(error instanceof IOException) && error.getCause() != null) { + error = error.getCause(); + } + var launchError = assertInstanceOf(IOException.class, error); + assertTrue(launchError.getMessage().contains("\"" + executable + "\""), launchError::getMessage); + } + } + + @ParameterizedTest + @ValueSource(strings = {"tools/copilot.exe", "tools\\copilot", ".\\tools\\copilot.EXE"}) + @EnabledOnOs(OS.WINDOWS) + void relativeWindowsCliPathDoesNotSearchPathDirectories(String cliPath) throws Exception { + Path bin = Files.createDirectory(tempDir.resolve("bin")); + Path tools = Files.createDirectory(bin.resolve("tools")); + Path unrelated = Files.createFile(tools.resolve("copilot.EXE")); + var options = new CopilotClientOptions().setCliPath(cliPath).setCwd(tempDir.toString()).setUseStdio(false) + .setEnvironment(Map.of("PATH", bin.toString(), "PATHEXT", ".EXE")); + + try (var client = new CopilotClient(options)) { + Throwable error = assertThrows(CompletionException.class, () -> client.start().join()); + while (error.getCause() != null) { + error = error.getCause(); + } + var launchError = assertInstanceOf(IOException.class, error); + assertTrue(launchError.getMessage().startsWith("CLI process exited unexpectedly."), + launchError::getMessage); + assertFalse(launchError.getMessage().contains(unrelated.toString()), launchError::getMessage); + } + } + + @ParameterizedTest + @ValueSource(strings = {" C:\\invalid", "C:\\invalid ", "C:\\invalidentry", + "C:\\invalid|entry", "C:\\invalid?entry", "C:\\invalid*entry", "C:\\invalid\"entry"}) + @EnabledOnOs(OS.WINDOWS) + void windowsNativeExecutableSkipsMalformedPathEntries(String malformedEntry) throws Exception { + Path bin = Files.createDirectory(tempDir.resolve("bin with spaces")); + Path executable = Files.createFile(bin.resolve("copilot.EXE")); + var options = new CopilotClientOptions().setCliPath("copilot").setCwd(tempDir.toString()) + .setEnvironment(Map.of("Path", malformedEntry + ";\"" + bin + "\"", "PATHEXT", ".CMD;.EXE")); + + assertEquals(List.of(executable.toString(), "--server"), resolveCliCommand(new CliServerManager(options))); + } + + @ParameterizedTest + @ValueSource(strings = {".CMD", ".BAT"}) + @EnabledOnOs(OS.WINDOWS) + void windowsCommandWrapperAfterMalformedPathEntryUsesShell(String extension) throws Exception { + Path bin = Files.createDirectory(tempDir.resolve("bin")); + Files.createFile(bin.resolve("copilot" + extension)); + Files.createFile(bin.resolve("copilot.EXE")); + var options = new CopilotClientOptions().setCwd(tempDir.toString()) + .setEnvironment(Map.of(NativeRuntimeLoader.COPILOT_CLI_PATH_ENV, "copilot", "PATH", + "C:\\invalid|entry;" + bin, "PATHEXT", extension + ";.EXE")); + + assertEquals(List.of("cmd", "/c", "copilot", "--server"), resolveCliCommand(new CliServerManager(options))); + } + + @Test + @EnabledOnOs(OS.WINDOWS) + void windowsCommandFallsBackToShellWhenPathEntriesAreMalformed() throws Exception { + var options = new CopilotClientOptions().setCliPath("copilot").setCwd(tempDir.toString()) + .setEnvironment(Map.of("PATH", " C:\\invalid;C:\\invalid|entry")); + + assertEquals(List.of("cmd", "/c", "copilot", "--server"), resolveCliCommand(new CliServerManager(options))); + } + + @ParameterizedTest + @ValueSource(booleans = {false, true}) + @EnabledOnOs(OS.WINDOWS) + void explicitWindowsExecutablePathDoesNotDependOnPath(boolean absolute) throws Exception { + Path bin = Files.createDirectory(tempDir.resolve("bin")); + Path executable = Files.createFile(bin.resolve("copilot.exe")); + String cliPath = (absolute ? executable : tempDir.relativize(executable)).toString(); + var options = new CopilotClientOptions().setCliPath(cliPath).setCwd(tempDir.toString()) + .setEnvironment(Map.of("PATH", "C:\\invalid|entry")); + + assertEquals(List.of(executable.toString(), "--server"), resolveCliCommand(new CliServerManager(options))); + } + + @Test + @EnabledOnOs(OS.WINDOWS) + void malformedWindowsCliPathIsNotSkipped() { + var options = new CopilotClientOptions().setCliPath("copilot|invalid").setCwd(tempDir.toString()) + .setEnvironment(Map.of("PATH", "C:\\invalid|entry", "PATHEXT", ".EXE")); + + var error = assertThrows(InvocationTargetException.class, + () -> resolveCliCommand(new CliServerManager(options))); + var cause = assertInstanceOf(InvalidPathException.class, error.getCause()); + assertEquals("copilot|invalid.EXE", cause.getInput()); + } + + @SuppressWarnings("unchecked") + private static List resolveCliCommand(CliServerManager manager) throws Exception { + var resolve = CliServerManager.class.getDeclaredMethod("resolveCliCommand", String.class, List.class); + resolve.setAccessible(true); + return (List) resolve.invoke(manager, manager.resolveCliLaunch().executable(), List.of("--server")); + } + + // ===== startCliServer tests ===== // On Windows, "/nonexistent/copilot" is not an absolute path (no drive letter), // so resolveCliCommand wraps it with "cmd /c" and ProcessBuilder.start() diff --git a/java/sdk/src/test/java/com/github/copilot/ClientOptionsE2ETest.java b/java/sdk/src/test/java/com/github/copilot/ClientOptionsE2ETest.java index 04afa58596..169c39fdcb 100644 --- a/java/sdk/src/test/java/com/github/copilot/ClientOptionsE2ETest.java +++ b/java/sdk/src/test/java/com/github/copilot/ClientOptionsE2ETest.java @@ -80,8 +80,8 @@ void testShouldForwardAdvancedSessionCreationOptionsToTheCli() throws Exception try (var client = fake.createClient()) { var session = client.createSession(new SessionConfig().setSessionId("java-create-session") - .setClientName("java-e2e-client").setModel("gpt-5-mini").setReasoningEffort("low") - .setReasoningSummary("none").setContextTier("long_context") + .setName("java named at create").setClientName("java-e2e-client").setModel("gpt-5-mini") + .setReasoningEffort("low").setReasoningSummary("none").setContextTier("long_context") .setAvailableTools(java.util.List.of("bash")).setExcludedTools(java.util.List.of("grep")) .setExcludedBuiltInAgents(java.util.List.of("explore")).setEnableSessionTelemetry(true) .setEnableCitations(true).setSessionLimits(new SessionLimitsConfig(42.0)) @@ -100,6 +100,7 @@ void testShouldForwardAdvancedSessionCreationOptionsToTheCli() throws Exception var create = fake.capturedRequest("session.create").path("params"); assertEquals("java-create-session", create.path("sessionId").asText()); + assertEquals("java named at create", create.path("name").asText()); assertEquals("java-e2e-client", create.path("clientName").asText()); assertEquals("gpt-5-mini", create.path("model").asText()); assertEquals("low", create.path("reasoningEffort").asText()); diff --git a/java/sdk/src/test/java/com/github/copilot/E2ETestContext.java b/java/sdk/src/test/java/com/github/copilot/E2ETestContext.java index 8a0e335758..fa96957d26 100644 --- a/java/sdk/src/test/java/com/github/copilot/E2ETestContext.java +++ b/java/sdk/src/test/java/com/github/copilot/E2ETestContext.java @@ -121,7 +121,7 @@ public static E2ETestContext create() throws IOException, InterruptedException { defaultUser.put("login", "e2e-test-user"); defaultUser.put("id", 12345); defaultUser.put("copilot_plan", "individual_pro"); - defaultUser.put("endpoints", Map.of("api", proxyUrl, "telemetry", "https://localhost:1/telemetry")); + defaultUser.put("endpoints", Map.of("api", proxyUrl, "telemetry", proxyUrl)); defaultUser.put("analytics_tracking_id", "e2e-test-tracking-id"); proxy.setCopilotUserByToken(DEFAULT_GITHUB_TOKEN, defaultUser); diff --git a/java/sdk/src/test/java/com/github/copilot/E2ETestContextTest.java b/java/sdk/src/test/java/com/github/copilot/E2ETestContextTest.java index bf307f6547..6e99145ac4 100644 --- a/java/sdk/src/test/java/com/github/copilot/E2ETestContextTest.java +++ b/java/sdk/src/test/java/com/github/copilot/E2ETestContextTest.java @@ -6,12 +6,37 @@ import static org.junit.jupiter.api.Assertions.assertEquals; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; import java.util.List; import org.junit.jupiter.api.Test; +import com.fasterxml.jackson.databind.ObjectMapper; + class E2ETestContextTest { + @Test + void defaultTelemetryEndpointUsesTheOwnedProxy() throws Exception { + try (E2ETestContext ctx = E2ETestContext.create()) { + var client = HttpClient.newHttpClient(); + var userRequest = HttpRequest.newBuilder(URI.create(ctx.getProxyUrl() + "/copilot_internal/user")) + .header("Authorization", "Bearer " + ctx.getEnvironment().get("GH_TOKEN")).build(); + var userResponse = client.send(userRequest, HttpResponse.BodyHandlers.ofString()); + assertEquals(200, userResponse.statusCode()); + var telemetryEndpoint = new ObjectMapper().readTree(userResponse.body()).path("endpoints").path("telemetry") + .asText(); + assertEquals(ctx.getProxyUrl(), telemetryEndpoint); + var telemetryRequest = HttpRequest.newBuilder(URI.create(telemetryEndpoint + "/telemetry")) + .POST(HttpRequest.BodyPublishers.ofString("{}\n")).build(); + var telemetryResponse = client.send(telemetryRequest, HttpResponse.BodyHandlers.ofString()); + assertEquals(204, telemetryResponse.statusCode()); + assertEquals("", telemetryResponse.body()); + } + } + @Test void expectedUserPromptsParseFoldedBlockScalars() { String snapshot = """ diff --git a/java/sdk/src/test/java/com/github/copilot/ImageGenerationConfigTest.java b/java/sdk/src/test/java/com/github/copilot/ImageGenerationConfigTest.java new file mode 100644 index 0000000000..5acace1e7a --- /dev/null +++ b/java/sdk/src/test/java/com/github/copilot/ImageGenerationConfigTest.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. + */ + +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.assertSame; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import com.fasterxml.jackson.databind.JsonNode; +import com.github.copilot.rpc.ImageGenerationConfig; +import com.github.copilot.rpc.ResumeSessionConfig; +import com.github.copilot.rpc.SessionConfig; + +class ImageGenerationConfigTest { + @ParameterizedTest + @ValueSource(booleans = {true, false}) + void createAndResumeForwardExplicitOptIn(boolean enabled) { + var imageGeneration = new ImageGenerationConfig().setEnabled(enabled); + var createConfig = new SessionConfig().setImageGeneration(imageGeneration).clone(); + var resumeConfig = new ResumeSessionConfig().setImageGeneration(imageGeneration).clone(); + assertSame(imageGeneration, createConfig.getImageGeneration()); + assertSame(imageGeneration, resumeConfig.getImageGeneration()); + var create = SessionRequestBuilder.buildCreateRequest(createConfig, "image-test"); + var resume = SessionRequestBuilder.buildResumeRequest("image-test", resumeConfig); + for (Object request : new Object[]{create, resume}) { + JsonNode json = JsonRpcClient.getObjectMapper().valueToTree(request); + assertEquals(enabled, json.get("imageGeneration").get("enabled").booleanValue()); + assertEquals(1, json.get("imageGeneration").size()); + } + } + + @Test + void createAndResumeOmitUnsetOptIn() { + var create = SessionRequestBuilder.buildCreateRequest(new SessionConfig(), "image-test"); + var resume = SessionRequestBuilder.buildResumeRequest("image-test", new ResumeSessionConfig()); + for (Object request : new Object[]{create, resume}) { + JsonNode json = JsonRpcClient.getObjectMapper().valueToTree(request); + assertFalse(json.has("imageGeneration")); + } + JsonNode empty = JsonRpcClient.getObjectMapper().valueToTree(new ImageGenerationConfig()); + assertEquals(0, empty.size()); + } +} diff --git a/java/sdk/src/test/java/com/github/copilot/JsonRpcClientTest.java b/java/sdk/src/test/java/com/github/copilot/JsonRpcClientTest.java index 8db208b53f..f296aba2e5 100644 --- a/java/sdk/src/test/java/com/github/copilot/JsonRpcClientTest.java +++ b/java/sdk/src/test/java/com/github/copilot/JsonRpcClientTest.java @@ -24,6 +24,8 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import com.github.copilot.generated.rpc.ServerRpc; +import com.github.copilot.generated.rpc.SandboxProxyCaCheckRelevanceParams; class JsonRpcClientTest { @@ -104,6 +106,26 @@ private String readRpcMessage(InputStream in) throws IOException { // ---- notify() ---- + @Test + void testGeneratedProxyCaQueriesPreserveBooleanResponses() throws Exception { + try (var pair = createSocketPair()) { + var server = new ServerRpc(pair.client::invoke); + for (boolean expected : new boolean[]{true, false}) { + for (String method : new String[]{"sandbox.proxyCa.checkInstallationSupport", + "sandbox.proxyCa.checkRelevance"}) { + CompletableFuture result = method.endsWith("checkInstallationSupport") + ? server.sandbox.proxyCa.checkInstallationSupport() + : server.sandbox.proxyCa.checkRelevance(new SandboxProxyCaCheckRelevanceParams(null)); + JsonNode request = MAPPER.readTree(readRpcMessage(pair.serverSide.getInputStream())); + assertEquals(method, request.get("method").asText()); + writeRpcMessage(pair.serverSide.getOutputStream(), + "{\"jsonrpc\":\"2.0\",\"id\":" + request.get("id") + ",\"result\":" + expected + "}"); + assertEquals(expected, result.get(5, TimeUnit.SECONDS)); + } + } + } + } + @Test void testNotify() throws Exception { try (var pair = createSocketPair()) { diff --git a/java/sdk/src/test/java/com/github/copilot/ManagedSettingsTest.java b/java/sdk/src/test/java/com/github/copilot/ManagedSettingsTest.java index 7b1910753f..16caf45a1f 100644 --- a/java/sdk/src/test/java/com/github/copilot/ManagedSettingsTest.java +++ b/java/sdk/src/test/java/com/github/copilot/ManagedSettingsTest.java @@ -30,24 +30,38 @@ void forwardsManagedSettingsOnCreateAndResume() throws Exception { var managedSettings = new ManagedSettings().setPermissions(permissions); var create = SessionRequestBuilder.buildCreateRequest(new SessionConfig().setEnableManagedSettings(true) - .setEnforceManagedModelDefaults(true).setManagedSettings(managedSettings), "managed-create"); + .setEnforceManagedModelDefaults(true).setInteractiveUser(true).setManagedSettings(managedSettings), + "managed-create"); var resume = SessionRequestBuilder.buildResumeRequest("managed-resume", new ResumeSessionConfig().setEnableManagedSettings(true).setEnforceManagedModelDefaults(true) - .setManagedSettings(managedSettings)); + .setInteractiveUser(true).setManagedSettings(managedSettings)); assertEquals(managedSettings, create.getManagedSettings()); assertEquals(managedSettings, resume.getManagedSettings()); assertTrue(create.getEnforceManagedModelDefaults()); assertTrue(resume.getEnforceManagedModelDefaults()); + assertEquals(Boolean.TRUE, create.getInteractiveUser()); + assertEquals(Boolean.TRUE, resume.getInteractiveUser()); var json = new ObjectMapper().writeValueAsString(create); assertTrue(json.contains("\"enableManagedSettings\":true")); assertTrue(json.contains("\"enforceManagedModelDefaults\":true")); + assertTrue(json.contains("\"interactiveUser\":true")); assertTrue(json.contains("\"managedSettings\":{\"permissions\"")); assertTrue(json.contains("\"disableBypassPermissionsMode\":\"disable\"")); assertTrue(json.contains("\"limitTo\":[\"Domain(github.com)\"]")); assertTrue(json.contains("\"disableAssistedPermissionsMode\":true")); } + @Test + void omitsInteractiveUserWhenUnset() throws Exception { + var create = SessionRequestBuilder.buildCreateRequest(new SessionConfig(), "default-create"); + var resume = SessionRequestBuilder.buildResumeRequest("default-resume", new ResumeSessionConfig()); + var mapper = new ObjectMapper(); + + assertFalse(mapper.writeValueAsString(create).contains("interactiveUser")); + assertFalse(mapper.writeValueAsString(resume).contains("interactiveUser")); + } + @Test void acceptsFutureBypassPermissionsModes() throws Exception { var permissions = new ManagedSettingsPermissions().setDisableBypassPermissionsMode("future-fail-closed-mode"); diff --git a/java/sdk/src/test/java/com/github/copilot/RpcHandlerDispatcherTest.java b/java/sdk/src/test/java/com/github/copilot/RpcHandlerDispatcherTest.java index a92a4b561d..a610007f0e 100644 --- a/java/sdk/src/test/java/com/github/copilot/RpcHandlerDispatcherTest.java +++ b/java/sdk/src/test/java/com/github/copilot/RpcHandlerDispatcherTest.java @@ -580,6 +580,10 @@ void skillProviderReadNullReturnsNullMarkdown() throws Exception { @Test void skillProviderFailuresUseStableMessagesWithoutData() throws Exception { + // Keep dispatch and logging outside the socket-read timeout. + var immediateDispatcher = new RpcHandlerDispatcher(sessions, lifecycleEvents::add, Runnable::run, + gitHubTokenProviders); + immediateDispatcher.registerHandlers(rpc); CopilotSession session = createSession("s1"); session.registerSkillProvider(skillProvider( () -> CompletableFuture.failedFuture(new IllegalStateException("secret list failure")), name -> { diff --git a/java/sdk/src/test/java/com/github/copilot/RpcWrappersTest.java b/java/sdk/src/test/java/com/github/copilot/RpcWrappersTest.java index 3fc2b499d1..c5309a20c0 100644 --- a/java/sdk/src/test/java/com/github/copilot/RpcWrappersTest.java +++ b/java/sdk/src/test/java/com/github/copilot/RpcWrappersTest.java @@ -50,9 +50,12 @@ import com.github.copilot.generated.rpc.SessionConnectorsRefreshParams; import com.github.copilot.generated.rpc.SessionConnectorsRefreshResult; import com.github.copilot.generated.rpc.SessionMcpListConfiguredResult; +import com.github.copilot.generated.rpc.SessionModelGetReasoningSummariesParams; +import com.github.copilot.generated.rpc.SessionModelGetReasoningSummariesResult; import com.github.copilot.generated.rpc.SessionModelSwitchToParams; import com.github.copilot.generated.rpc.SessionModelSwitchToRequest; import com.github.copilot.generated.rpc.SessionRpc; +import com.github.copilot.rpc.AgentInfo; /** * Unit tests for the generated RPC wrapper classes ({@link ServerRpc} and @@ -190,6 +193,33 @@ void serverRpc_mcp_discover_passes_params() { // ── SessionRpc tests ────────────────────────────────────────────────────── + @ParameterizedTest + @CsvSource({"true,false", "false,true", "null,null"}) + void sessionRpc_reasoning_summaries_accepts_only_host_state(String persisted, String visible) { + var stub = new StubCaller(); + var session = new SessionRpc(stub, "reasoning-session"); + Boolean persistedShowReasoning = "null".equals(persisted) ? null : Boolean.valueOf(persisted); + Boolean summariesVisible = "null".equals(visible) ? null : Boolean.valueOf(visible); + var params = new SessionModelGetReasoningSummariesParams(persistedShowReasoning, summariesVisible); + + assertNull(params.sessionId()); + session.model.getReasoningSummaries(params).join(); + + assertEquals(1, stub.calls.size()); + var call = stub.calls.get(0); + assertEquals("session.model.getReasoningSummaries", call.method()); + assertEquals(SessionModelGetReasoningSummariesResult.class, call.resultType()); + var payload = assertInstanceOf(JsonNode.class, call.params()); + var expected = new ObjectMapper().createObjectNode().put("sessionId", "reasoning-session"); + if (persistedShowReasoning != null) { + expected.put("persistedShowReasoning", persistedShowReasoning); + } + if (summariesVisible != null) { + expected.put("summariesVisible", summariesVisible); + } + assertEquals(expected, payload); + } + @Test @AllowCopilotExperimental void sessionRpc_mcp_listConfigured_preserves_runtime_lifecycle() throws Exception { @@ -575,6 +605,56 @@ void serverRpc_account_getQuota_invokes_correct_method() { // RpcHandlerDispatcherTest) to construct a real CopilotSession and verify // that getRpc() returns a correctly wired SessionRpc. + @ParameterizedTest + @ValueSource(strings = {"list", "getCurrent", "select"}) + void copilotSession_agent_methods_deserialize_scoped_hooks(String method) throws Exception { + var mapper = JsonRpcClient.getObjectMapper(); + var agentJson = mapper.readTree(""" + { + "name": "hook-agent", + "displayName": "Hook Agent", + "description": "Agent with scoped hooks", + "hooks": { + "preToolUse": [{"type":"command","bash":"echo scoped-hook","timeoutSec":10}], + "sessionStart": [{"type":"command","bash":"echo started"}] + } + } + """); + try (var sockets = new SocketPair()) { + var stub = sockets.stubServer(); + var session = new CopilotSession("sess-agent-hooks", sockets.client()); + try { + CompletableFuture> future = switch (method) { + case "list" -> session.listAgents(); + case "getCurrent" -> session.getCurrentAgent().thenApply(List::of); + case "select" -> session.selectAgent("hook-agent").thenApply(List::of); + default -> throw new IllegalArgumentException(method); + }; + var request = stub.readOneMessage(); + assertEquals("session.agent." + method, request.get("method").asText()); + assertEquals("sess-agent-hooks", request.get("params").get("sessionId").asText()); + if ("select".equals(method)) { + assertEquals("hook-agent", request.get("params").get("name").asText()); + } + var result = mapper.createObjectNode(); + if ("list".equals(method)) { + result.putArray("agents").add(agentJson); + } else { + result.set("agent", agentJson); + } + stub.respond(request, result); + + var agents = future.get(3, TimeUnit.SECONDS); + assertEquals(1, agents.size()); + assertEquals("hook-agent", agents.get(0).getName()); + assertNotNull(agents.get(0).getHooks()); + assertEquals(agentJson.get("hooks"), mapper.valueToTree(agents.get(0).getHooks())); + } finally { + session.forceStopLocally(); + } + } + } + @Test void copilotSession_getRpc_returns_non_null_session_rpc() throws Exception { try (var sockets = new SocketPair()) { diff --git a/java/sdk/src/test/java/com/github/copilot/SessionEventHandlingTest.java b/java/sdk/src/test/java/com/github/copilot/SessionEventHandlingTest.java index 8f2edf52e0..64c32b4a74 100644 --- a/java/sdk/src/test/java/com/github/copilot/SessionEventHandlingTest.java +++ b/java/sdk/src/test/java/com/github/copilot/SessionEventHandlingTest.java @@ -295,8 +295,8 @@ void testSendAndWaitIgnoresChildEvents() throws Exception { dispatchEvent(childMessage); var childError = new SessionErrorEvent(); childError.setAgentId("child-1"); - childError.setData(new SessionErrorEvent.SessionErrorEventData("query", null, null, "child failed", null, - null, null, null, null, null)); + childError.setData(new SessionErrorEvent.SessionErrorEventData("query", null, null, null, null, + "child failed", null, null, null, null, null, null)); dispatchEvent(childError); var childIdle = createSessionIdleEvent(); childIdle.setAgentId("child-1"); diff --git a/java/sdk/src/test/java/com/github/copilot/SessionHandlerTest.java b/java/sdk/src/test/java/com/github/copilot/SessionHandlerTest.java index 001489e41f..e77ca0adcb 100644 --- a/java/sdk/src/test/java/com/github/copilot/SessionHandlerTest.java +++ b/java/sdk/src/test/java/com/github/copilot/SessionHandlerTest.java @@ -11,6 +11,7 @@ import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; +import java.util.concurrent.atomic.AtomicReference; import java.util.logging.Handler; import java.util.logging.Level; import java.util.logging.LogRecord; @@ -21,6 +22,7 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; import com.github.copilot.rpc.AgentStopHookOutput; import com.github.copilot.rpc.PermissionRequestResult; import com.github.copilot.rpc.PermissionRequestResultKind; @@ -47,6 +49,75 @@ public class SessionHandlerTest { private CopilotSession session; + @Test + void typedHookDispatchRetainsChildIdentityAndOldRootInputs() throws Exception { + var captured = new ArrayList(); + var hooks = new SessionHooks().setOnPreToolUse((i, c) -> { + captured.add(i); + return CompletableFuture.completedFuture(null); + }).setOnPreMcpToolCall((i, c) -> { + captured.add(i); + return CompletableFuture.completedFuture(null); + }).setOnPostToolUse((i, c) -> { + captured.add(i); + return CompletableFuture.completedFuture(null); + }).setOnPostToolUseFailure((i, c) -> { + captured.add(i); + return CompletableFuture.completedFuture(null); + }).setOnUserPromptSubmitted((i, c) -> { + captured.add(i); + return CompletableFuture.completedFuture(null); + }).setOnUserPromptTransformed((i, c) -> { + captured.add(i); + return CompletableFuture.completedFuture(null); + }).setOnSessionStart((i, c) -> { + captured.add(i); + return CompletableFuture.completedFuture(null); + }).setOnSessionEnd((i, c) -> { + captured.add(i); + return CompletableFuture.completedFuture(null); + }).setOnAgentStop((i, c) -> { + captured.add(i); + return CompletableFuture.completedFuture(null); + }).setOnSubagentStart((i, c) -> { + captured.add(i); + return CompletableFuture.completedFuture(null); + }).setOnSubagentStop((i, c) -> { + captured.add(i); + return CompletableFuture.completedFuture(null); + }); + session.registerHooks(hooks); + for (String hook : List.of("preToolUse", "preMcpToolCall", "postToolUse", "postToolUseFailure", + "userPromptSubmitted", "userPromptTransformed", "sessionStart", "sessionEnd", "agentStop", + "subagentStart", "subagentStop")) { + for (boolean enriched : List.of(true, false)) { + var input = MAPPER.createObjectNode().put("sessionId", "reported-session").put("timestamp", 1) + .put("cwd", "work"); + if (enriched) { + input.put("agentId", "child-actor").put("agentType", "explore").put("parentAgentId", + "parent-actor"); + } + if (hook.equals("sessionEnd")) { + input.putObject("error").put("message", "session-end-error"); + } + captured.clear(); + session.handleHooksInvoke(hook, input).get(); + assertEquals(1, captured.size(), hook); + JsonNode actual = MAPPER.valueToTree(captured.get(0)); + assertEquals("reported-session", actual.path("sessionId").asText(), hook); + if (hook.equals("sessionEnd")) { + assertEquals("session-end-error", actual.path("error").asText(), actual.toString()); + } + for (var field : Map + .of("agentId", "child-actor", "agentType", "explore", "parentAgentId", "parent-actor") + .entrySet()) { + assertEquals(enriched ? field.getValue() : "", actual.path(field.getKey()).asText(""), + hook + ": " + actual); + } + } + } + } + @BeforeEach void setup() throws Exception { var constructor = CopilotSession.class.getDeclaredConstructor(String.class, JsonRpcClient.class, String.class); @@ -291,6 +362,34 @@ void testHandleHooksInvokeSessionEnd() throws Exception { assertEquals("summary", output.sessionSummary()); } + @Test + void testHandleHooksInvokeSessionEndErrorShapes() throws Exception { + assertEquals("model timeout", sessionEndError(MAPPER.readTree("{\"message\":\"model timeout\"}"))); + assertEquals("boom", sessionEndError(MAPPER.readTree("\"boom\""))); + assertNull(sessionEndError(MAPPER.nullNode())); + assertNull(sessionEndError(null)); + assertEquals("{\"code\":7}", sessionEndError(MAPPER.readTree("{\"code\":7}"))); + } + + /** + * Runs a sessionEnd hook with the given wire {@code error} (absent when null) + * and returns the handler's error. + */ + private String sessionEndError(JsonNode error) throws Exception { + var captured = new AtomicReference("handler did not run"); + session.registerHooks(new SessionHooks().setOnSessionEnd((hookInput, invocation) -> { + captured.set(hookInput.error()); + return CompletableFuture.completedFuture(new SessionEndHookOutput(false, null, null)); + })); + ObjectNode input = MAPPER.createObjectNode().put("timestamp", 1735689600L).put("cwd", "/tmp").put("reason", + "error"); + if (error != null) { + input.set("error", error); + } + session.handleHooksInvoke("sessionEnd", input).get(); + return captured.get(); + } + // ===== handleHooksInvoke: agentStop ===== @Test diff --git a/java/sdk/src/test/java/com/github/copilot/SessionRequestBuilderTest.java b/java/sdk/src/test/java/com/github/copilot/SessionRequestBuilderTest.java index 978346e486..d28a6ee332 100644 --- a/java/sdk/src/test/java/com/github/copilot/SessionRequestBuilderTest.java +++ b/java/sdk/src/test/java/com/github/copilot/SessionRequestBuilderTest.java @@ -6,6 +6,8 @@ import static org.junit.jupiter.api.Assertions.*; +import java.util.ArrayList; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.CompletableFuture; @@ -83,6 +85,38 @@ void transcriptRecoveryOverridesAndResponseInAllModes() throws Exception { assertNull(new ResumeSessionResponse("s1", null, null, null).transcriptRecovery()); } + @Test + void enableGitHubCacheStoreIsForwardedOnCreateAndResumeInAllModes() throws Exception { + var mapper = JsonRpcClient.getObjectMapper(); + for (var mode : new CopilotClientMode[]{CopilotClientMode.COPILOT_CLI, CopilotClientMode.EMPTY}) { + for (Boolean choice : new Boolean[]{null, false, true}) { + var create = new SessionConfig(); + var resume = new ResumeSessionConfig(); + if (choice != null) { + create.setEnableGitHubCacheStore(choice); + resume.setEnableGitHubCacheStore(choice); + } + var createJson = mapper.readTree( + mapper.writeValueAsBytes(SessionRequestBuilder.buildCreateRequest(create.clone(), "s1", mode))); + var resumeJson = mapper.readTree( + mapper.writeValueAsBytes(SessionRequestBuilder.buildResumeRequest("s1", resume.clone(), mode))); + for (var requestJson : List.of(createJson, resumeJson)) { + if (choice == null) { + assertFalse(requestJson.has("enableGitHubCacheStore")); + } else { + assertTrue(requestJson.path("enableGitHubCacheStore").isBoolean()); + assertEquals(choice.booleanValue(), requestJson.path("enableGitHubCacheStore").asBoolean()); + } + } + } + } + + var cleared = new SessionConfig().setEnableGitHubCacheStore(true).clearEnableGitHubCacheStore(); + assertTrue(cleared.getEnableGitHubCacheStore().isEmpty()); + var clearedResume = new ResumeSessionConfig().setEnableGitHubCacheStore(true).clearEnableGitHubCacheStore(); + assertTrue(clearedResume.getEnableGitHubCacheStore().isEmpty()); + } + @Test void clearingTranscriptRecoveryRestoresRuntimeDefaultInAllModes() throws Exception { var mapper = JsonRpcClient.getObjectMapper(); @@ -100,6 +134,71 @@ void clearingTranscriptRecoveryRestoresRuntimeDefaultInAllModes() throws Excepti } } + @Test + void customizationDirectoriesArePreservedOnCreateAndResume() throws Exception { + var mapper = JsonRpcClient.getObjectMapper(); + for (int kind = 0; kind < 3; kind++) { + List roots = kind == 0 ? null : kind == 1 ? List.of() : List.of("relative-root", "other-root"); + var directories = new HashMap>(); + for (var type : List.of("skill", "agent", "instruction", "hook")) { + directories.put(type + "Directories", + roots == null ? null : roots.stream().map(root -> root + "-" + type).toList()); + } + var create = SessionRequestBuilder.buildCreateRequest( + new SessionConfig().setWorkingDirectory("request-cwd").setEnableConfigDiscovery(false) + .setEnableFileHooks(false).setSkillDirectories(directories.get("skillDirectories")) + .setAgentDirectories(directories.get("agentDirectories")) + .setInstructionDirectories(directories.get("instructionDirectories")) + .setHookDirectories(directories.get("hookDirectories")), + "created"); + var resume = SessionRequestBuilder.buildResumeRequest("resumed", + new ResumeSessionConfig().setWorkingDirectory("resume-cwd").setEnableConfigDiscovery(false) + .setEnableFileHooks(false).setSkillDirectories(directories.get("skillDirectories")) + .setAgentDirectories(directories.get("agentDirectories")) + .setInstructionDirectories(directories.get("instructionDirectories")) + .setHookDirectories(directories.get("hookDirectories"))); + for (var request : List.of(create, resume)) { + var wire = mapper.readTree(mapper.writeValueAsBytes(request)); + for (var entry : directories.entrySet()) { + if (entry.getValue() == null) { + assertFalse(wire.has(entry.getKey()), wire.toString()); + } else { + assertEquals(mapper.valueToTree(entry.getValue()), wire.get(entry.getKey())); + } + } + assertFalse(wire.path("enableConfigDiscovery").asBoolean(true)); + assertFalse(wire.path("enableFileHooks").asBoolean(true)); + assertFalse(wire.has("additionalDirectories")); + assertFalse(wire.has("customAgents")); + assertFalse(wire.path("hooks").asBoolean()); + } + } + } + + @Test + void customizationDirectoryClonesOwnTheirLists() { + var agents = new ArrayList<>(List.of("agents")); + var hooks = new ArrayList<>(List.of("hooks")); + var create = new SessionConfig().setAgentDirectories(agents).setHookDirectories(hooks); + var resume = new ResumeSessionConfig().setAgentDirectories(agents).setHookDirectories(hooks); + var createClone = create.clone(); + var resumeClone = resume.clone(); + agents.add("added-agents"); + hooks.add("added-hooks"); + assertEquals(List.of("agents"), create.getAgentDirectories()); + assertEquals(List.of("hooks"), create.getHookDirectories()); + assertEquals(List.of("agents"), resume.getAgentDirectories()); + assertEquals(List.of("hooks"), resume.getHookDirectories()); + assertEquals(List.of("agents"), createClone.getAgentDirectories()); + assertEquals(List.of("hooks"), createClone.getHookDirectories()); + assertEquals(List.of("agents"), resumeClone.getAgentDirectories()); + assertEquals(List.of("hooks"), resumeClone.getHookDirectories()); + assertThrows(UnsupportedOperationException.class, () -> createClone.getAgentDirectories().add("mutation")); + assertThrows(UnsupportedOperationException.class, () -> resumeClone.getHookDirectories().add("mutation")); + assertThrows(UnsupportedOperationException.class, () -> create.getAgentDirectories().add("mutation")); + assertThrows(UnsupportedOperationException.class, () -> resume.getHookDirectories().add("mutation")); + } + // ========================================================================= // buildCreateRequest // ========================================================================= diff --git a/java/sdk/src/test/java/com/github/copilot/SubagentHooksE2ETest.java b/java/sdk/src/test/java/com/github/copilot/SubagentHooksE2ETest.java index bc83502f87..dc398512b5 100644 --- a/java/sdk/src/test/java/com/github/copilot/SubagentHooksE2ETest.java +++ b/java/sdk/src/test/java/com/github/copilot/SubagentHooksE2ETest.java @@ -138,10 +138,8 @@ void shouldApplySubagentLifecycleHookOutputs() throws Exception { if (request.parentAgentId() == null || request.parentAgentId().isEmpty()) { continue; } - for (JsonNode message : mapper.readTree(request.body()).path("messages")) { - if (hasChildContext(message)) { - childContextObserved = true; - } + if (hasChildContext(mapper.readTree(request.body()))) { + childContextObserved = true; } } assertTrue(childContextObserved, @@ -187,7 +185,22 @@ void shouldApplySubagentLifecycleHookOutputs() throws Exception { } } - private static boolean hasChildContext(JsonNode message) { + private static boolean hasChildContext(JsonNode body) { + if (body.path("input").isTextual()) { + return body.path("input").asText().contains(CHILD_CONTEXT + "\n\nRead the file \"subagent-test.txt\""); + } + JsonNode messages = body.hasNonNull("messages") ? body.path("messages") : body.path("input"); + if (messages.isArray()) { + for (JsonNode message : messages) { + if (messageHasChildContext(message)) { + return true; + } + } + } + return false; + } + + private static boolean messageHasChildContext(JsonNode message) { if (!"user".equals(message.path("role").asText())) { return false; } @@ -198,7 +211,8 @@ private static boolean hasChildContext(JsonNode message) { } if (content.isArray()) { for (JsonNode part : content) { - if ("text".equals(part.path("type").asText()) && part.path("text").asText().contains(expected)) { + if (("text".equals(part.path("type").asText()) || "input_text".equals(part.path("type").asText())) + && part.path("text").isTextual() && part.path("text").asText().contains(expected)) { return true; } } @@ -206,6 +220,26 @@ private static boolean hasChildContext(JsonNode message) { return false; } + @Test + void recognizesUserContextAcrossRequestFormats() throws Exception { + ObjectMapper mapper = new ObjectMapper(); + String text = CHILD_CONTEXT + "\n\nRead the file \"subagent-test.txt\""; + String encoded = mapper.writeValueAsString(text); + String user = "{\"role\":\"user\",\"content\":" + encoded + "}"; + assertTrue(hasChildContext(mapper.readTree("{\"messages\":[" + user + "]}"))); + assertTrue(hasChildContext(mapper.readTree("{\"input\":" + encoded + "}"))); + assertTrue(hasChildContext(mapper.readTree("{\"input\":[" + user + "]}"))); + for (String type : List.of("text", "input_text")) { + assertTrue(hasChildContext(mapper.readTree("{\"input\":[{\"role\":\"user\",\"content\":[{\"type\":\"" + type + + "\",\"text\":" + encoded + "}]}]}"))); + } + assertFalse(hasChildContext(mapper.readTree("{\"input\":[" + "{\"role\":\"assistant\",\"content\":" + encoded + + "}," + "{\"type\":\"function_call_output\",\"output\":" + encoded + "}," + + "{\"role\":\"user\",\"content\":[null,\"ignored\"," + + "{\"type\":\"input_text\",\"text\":42},{\"type\":\"input_image\",\"text\":" + encoded + "}]}]}"))); + assertFalse(hasChildContext(mapper.readTree("{}"))); + } + private static void assertSubagentRequestMetadata(List records) { assertFalse(records.isEmpty(), "request handler should observe inference requests"); RequestRecord subagentRequest = records.stream() 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 index 19d9694ce2..3891a9d483 100644 --- a/java/sdk/src/test/java/com/github/copilot/ffi/FfiRuntimeHostTest.java +++ b/java/sdk/src/test/java/com/github/copilot/ffi/FfiRuntimeHostTest.java @@ -77,7 +77,7 @@ public boolean connectionClose(int connectionId) { .setRemote(true).setMode(CopilotClientMode.EMPTY).setCliArgs(new String[]{"--extra-flag"}); FfiRuntimeHost host = new FfiRuntimeHost(binding, "/tmp/runtime.node"); - host.start("/tmp/entrypoint.js", options); + host.start("/tmp/entrypoint.js", "github/github-app", options); List argv = MAPPER.readValue(binding.argv, new TypeReference>() { }); @@ -92,6 +92,7 @@ public boolean connectionClose(int connectionId) { assertTrue(argv.contains("42")); assertTrue(argv.contains("--remote")); assertTrue(argv.contains("--extra-flag")); + assertTrue(argv.contains("--cli-login-sync-integration-id=github/github-app")); Map env = MAPPER.readValue(binding.env, new TypeReference>() { }); @@ -133,7 +134,7 @@ public boolean connectionClose(int connectionId) { }; try (FfiRuntimeHost host = new FfiRuntimeHost(binding, "/tmp/runtime.node")) { - host.start(null, new CopilotClientOptions().setLogLevel("debug")); + host.start(null, null, new CopilotClientOptions().setLogLevel("debug")); } List argv = MAPPER.readValue(argvJson.get(), new TypeReference>() { @@ -184,7 +185,7 @@ void enqueue(byte[] bytes) { }; FfiRuntimeHost host = new FfiRuntimeHost(binding, "test-lib", throwingStream); - assertDoesNotThrow(() -> host.start("/tmp/entrypoint", new CopilotClientOptions())); + assertDoesNotThrow(() -> host.start("/tmp/entrypoint", null, new CopilotClientOptions())); assertTrue(callbackReturned.get(), "callback should return normally even when enqueue throws"); } @@ -219,7 +220,7 @@ public boolean connectionClose(int connectionId) { }; FfiRuntimeHost host = new FfiRuntimeHost(binding, "test-lib"); - host.start("/tmp/entrypoint", new CopilotClientOptions()); + host.start("/tmp/entrypoint", null, new CopilotClientOptions()); assertDoesNotThrow(host::close); } @@ -310,7 +311,7 @@ public boolean connectionClose(int connectionId) { }; FfiRuntimeHost host = new FfiRuntimeHost(binding, "test-lib"); - host.start("/tmp/entrypoint", new CopilotClientOptions()); + host.start("/tmp/entrypoint", null, new CopilotClientOptions()); CompletableFuture writer = CompletableFuture.runAsync(() -> { try { @@ -389,7 +390,7 @@ void enqueue(byte[] bytes) { }; FfiRuntimeHost host = new FfiRuntimeHost(binding, "test-lib", blockingStream); - host.start("/tmp/entrypoint", new CopilotClientOptions()); + host.start("/tmp/entrypoint", null, new CopilotClientOptions()); assertNotNull(callbackRef.get()); ExecutorService executor = Executors.newFixedThreadPool(2); diff --git a/java/sdk/src/test/java/com/github/copilot/generated/rpc/GeneratedRpcRecordsCoverageTest.java b/java/sdk/src/test/java/com/github/copilot/generated/rpc/GeneratedRpcRecordsCoverageTest.java index 348a2d9f1a..ed47710539 100644 --- a/java/sdk/src/test/java/com/github/copilot/generated/rpc/GeneratedRpcRecordsCoverageTest.java +++ b/java/sdk/src/test/java/com/github/copilot/generated/rpc/GeneratedRpcRecordsCoverageTest.java @@ -21,6 +21,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.github.copilot.AllowCopilotExperimental; import com.github.copilot.TestUtil; +import com.github.copilot.generated.SessionManagedSettingsResolvedEvent.SessionManagedSettingsResolvedEventData; /** * Tests for generated RPC param and result record types. Exercises @@ -29,6 +30,27 @@ */ class GeneratedRpcRecordsCoverageTest { + @Test + @AllowCopilotExperimental + void managedPermissions_reuses_event_context_and_evaluated_operations() throws Exception { + var mapper = new ObjectMapper(); + var eventJson = mapper.readTree(""" + {"permissionsContext":{"permissions":{"_limitToLists":[["https://example.com"]]},"failClosed":false}} + """); + var eventData = mapper.treeToValue(eventJson, SessionManagedSettingsResolvedEventData.class); + var evaluation = mapper.readValue(""" + {"operation":{"kind":"url","url":"https://example.com/a"},"verdict":"allow"} + """, ManagedPermissionEvaluation.class); + var params = new ManagedSettingsPermissionsEvaluateParams(eventData.permissionsContext(), + List.of(evaluation.operation())); + var paramsJson = mapper.valueToTree(params); + assertEquals(eventJson.get("permissionsContext"), paramsJson.get("context")); + assertEquals("url", paramsJson.get("operations").get(0).get("kind").asText()); + assertEquals("https://example.com/a", paramsJson.get("operations").get(0).get("url").asText()); + assertEquals(ManagedPermissionOperation.ManagedPermissionOperationKind.URL, evaluation.operation().kind()); + assertEquals(params, mapper.treeToValue(paramsJson, ManagedSettingsPermissionsEvaluateParams.class)); + } + // ── Params records ───────────────────────────────────────────────────── @Test @@ -701,7 +723,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); + 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()); @@ -713,7 +735,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, null); + null, null, null, null, null, null); var result = new SessionAgentGetCurrentResult(agent); assertEquals("agent-1", result.agent().name()); assertEquals("Agent One", result.agent().displayName()); @@ -730,18 +752,25 @@ 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, 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()); } @Test - void sessionAgentSelectResult_nested() { + void sessionAgentSelectResult_nested() throws Exception { + Map> hooks = Map.of("Stop", + List.of(Map.of("type", "command", "command", "echo scoped-stop"))); 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, null, null, null, null, null, null, hooks); var result = new SessionAgentSelectResult(agent); assertEquals("selected", result.agent().name()); + var mapper = new ObjectMapper(); + var json = mapper.writeValueAsString(result); + assertEquals("echo scoped-stop", + mapper.readTree(json).get("agent").get("hooks").get("Stop").get(0).get("command").asText()); + assertEquals(hooks, mapper.readValue(json, SessionAgentSelectResult.class).agent().hooks()); } @Test diff --git a/nodejs/README.md b/nodejs/README.md index 1566ea9cad..c323700adf 100644 --- a/nodejs/README.md +++ b/nodejs/README.md @@ -375,11 +375,13 @@ Create a new conversation session. - `workingDirectory?: string` - Working directory for the session (default: runtime process cwd). - `refreshCustomInstructions?: boolean` - Invalidates the process-wide custom-instruction discovery cache before creating this session, so instruction-file edits made in the same runtime are read again. Defaults to `false` (cache reuse). Other sessions in this runtime may observe updated instructions on later turns or discovery. This does not watch files or enable disabled instruction loading. Available on `SessionConfig`, not `ResumeSessionConfig`. - `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. +- `enableGitHubCacheStore?: boolean` - Requests the read-only GitHub cache store (`github-cache-store` capability) on create or resume. The runtime registers the `github_sql` tool only when its `GITHUB_CACHE_STORE` feature flag is also on. Defaults to disabled in every mode. - `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 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`. +- `interactiveUser?: boolean` - Declares that a person types this session's messages, as in an editor chat panel. Enables the runtime's user-message analysis telemetry for user-authored messages; automated messages stay excluded. Defaults to `false`. Ignored for cloud sessions. Re-supply it when cold-resuming a session; it is ignored when resuming a session that is already active. - `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. - `skillProvider?: SkillProvider` - **Experimental.** Serves skills from host storage instead of skill directories. Not persisted; pass it again on resume. See [Skill providers](#skill-providers-experimental). @@ -671,6 +673,23 @@ if (result.status === "pending") { See [Auto tier persistence](../docs/features/session-persistence.md#auto-tier-persistence) for the full lifecycle rules. +##### `rpc.model.getReasoningSummaries(params)` (experimental) + +Read the current model's reasoning-summary presentation policy without changing session state. Call `session.rpc.model.list()` first to load the model catalog; otherwise the result may have `ready: false`. The request and result types are exported from `@github/copilot-sdk`. + +```typescript +import type { + ModelGetReasoningSummariesRequest, + ModelGetReasoningSummariesResult, +} from "@github/copilot-sdk"; + +const presentation: ModelGetReasoningSummariesRequest = { summariesVisible: false }; +const policy: ModelGetReasoningSummariesResult = + await session.rpc.model.getReasoningSummaries(presentation); +``` + +Omit `persistedShowReasoning` when no visibility setting is saved. `visibility` can remain unset while experiment assignment is pending; `clearRedundantSetting` tells the host when its persisted setting matches the model default. + ##### `setTools(tools: Tool[]): Promise` Replace the tools this client supplies to the session, together with the handlers that serve them (experimental). `tools` takes the same definitions as `createSession` and becomes this client's complete tool set; pass `[]` to remove all of this client's tools. Built-in, MCP, and plugin tools, and tools other connected clients supply, are unaffected. @@ -1377,6 +1396,36 @@ const session = await client.createSession({ }); ``` +### Managed network policy for client-owned browsers + +Obtain a reusable `permissionsContext` from +`client.rpc.managedSettings.resolve({ selectionId })` for account and device +policy, or `session.rpc.managedSettings.get()` for the session's retained policy, +including SDK injection and policy-helper rules. Omit `selectionId` for the current +account, or device policy only when signed out. Resolution uses the managed-policy +cache and can fetch server policy; keep it off latency-critical startup paths. +Older runtimes may omit `permissionsContext`. Treat an absent context as unsupported +evaluation, not unmanaged policy; do not navigate without a policy decision. + +Call `client.rpc.managedSettings.permissions.evaluate({ context: permissionsContext, operations: [{ kind: "url", url: "https://docs.example.com/guide" }] })`. +This is pure, sessionless evaluation: it performs no policy discovery, I/O, +prompting, or sandbox changes. Reuse an in-memory context for repeated checks and +refresh it when managed policy changes. Retain the runtime-owned `permissions` +object verbatim rather than rebuilding it from the flattened settings display. + +The result is `{ results: [{ operation, verdict }], failClosed }`, with one entry per +input operation in the same order. `deny` blocks navigation, `ask` requires approval, +`allow` approves under managed policy, and `unmanaged` leaves the consumer's normal +permission flow in effect. A check never prompts or grants a saved approval. +Invalid URLs reject the batch. Unknown policy rejects or returns `failClosed: true` +with all URLs denied; do not navigate on an error. + +Check every navigation, redirect, and new-window destination before opening it. +These APIs evaluate managed permission rules only: they do not replace browser +security checks, user permissions, or sandbox policy. `permissions.limitTo` neither +enables the sandbox nor changes `sandbox.userPolicy.network`; configure sandbox +network restrictions explicitly. + ### Permission Result Kinds 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: @@ -1605,6 +1654,26 @@ npm ci npm test ``` +### Internal feature-flag example + +[`examples/internal-feature-flags.ts`](examples/internal-feature-flags.ts) uses +the generated internal RPC factory against a real runtime over stdio. +It lists the catalog and shows environment, settings, and explicit-override +precedence. It also checks that static resolution does not apply `COPILOT_EXP_*` +experiment assignments. It creates no session and sends no model request. + +After you prepare matching runtime artifacts, run this from the runtime repository root: + +```bash +COPILOT_RUNTIME_PROVIDER_LIB="$PWD/bazel-bin/src/native/runtime/runtime.node" \ + src/sdk/nodejs/node_modules/.bin/tsx \ + src/sdk/nodejs/examples/internal-feature-flags.ts \ + "$PWD/bazel-bin/src/runtime-bin/copilot-runtime" +``` + +The JSON output includes `staticResolution` values of `false`, `true`, `false`, +and `true`, in that order. These methods are internal, not public SDK APIs. + ## License MIT diff --git a/nodejs/examples/content-exclusion.ts b/nodejs/examples/content-exclusion.ts new file mode 100644 index 0000000000..24d51452c2 --- /dev/null +++ b/nodejs/examples/content-exclusion.ts @@ -0,0 +1,26 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +import type { CopilotClient } from "../src/index.js"; + +// Validate host-supplied policies before creating a session. +export async function validateContentExclusionPolicies(client: CopilotClient) { + const policiesJson = JSON.stringify([ + { + rules: [ + { + paths: ["secrets/**"], + source: { name: "Example", type: "Organization" }, + futureRuleField: { enabled: true }, + }, + ], + last_updated_at: "2026-10-05T12:00:00Z", + scope: "all", + futurePolicyField: { nullable: null }, + }, + ]); + const result = await client.rpc.contentExclusion.validatePolicies({ policiesJson }); + console.log("Validated content-exclusion policies:", JSON.stringify(result.policies)); + return result; +} diff --git a/nodejs/examples/internal-feature-flags.ts b/nodejs/examples/internal-feature-flags.ts new file mode 100644 index 0000000000..83824012fc --- /dev/null +++ b/nodejs/examples/internal-feature-flags.ts @@ -0,0 +1,95 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +import assert from "node:assert/strict"; +import { spawn } from "node:child_process"; +import { once } from "node:events"; +import { mkdtemp, rm } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { join, resolve } from "node:path"; +import { createMessageConnection } from "vscode-jsonrpc/node.js"; +import { createInternalServerRpc } from "../src/generated/rpc.js"; + +async function showFeatureFlags(rpc: ReturnType) { + await rpc.connect({}); + const { flags } = await rpc.featureFlags.listMetadata(); + assert.equal(flags.TASK_ANALYTICS.name, "TASK_ANALYTICS"); + assert.equal(flags.TASK_ANALYTICS.expFlagEnvVar, "COPILOT_EXP_COPILOT_CLI_TASK_ANALYTICS"); + + const population = { isStaff: false, isExperimental: false, isTeam: false }; + const experimentOnly = await rpc.featureFlags.resolve({ + ...population, + environment: { COPILOT_EXP_COPILOT_CLI_TASK_ANALYTICS: "true" }, + }); + assert.equal(experimentOnly.TASK_ANALYTICS, false); + + const request = { + ...population, + baseFlags: { TASK_ANALYTICS: false }, + environment: { TASK_ANALYTICS: "true" }, + }; + const environment = await rpc.featureFlags.resolve(request); + const config = { enabledFeatureFlags: { TASK_ANALYTICS: false } }; + const settings = await rpc.featureFlags.resolve({ ...request, config }); + const explicit = await rpc.featureFlags.resolve({ + ...request, + config, + flagOverrides: { TASK_ANALYTICS: true }, + }); + assert.deepEqual(environment, { TASK_ANALYTICS: true }); + assert.deepEqual(settings, { TASK_ANALYTICS: false }); + assert.deepEqual(explicit, { TASK_ANALYTICS: true }); + + console.log( + JSON.stringify({ + declaredFlags: Object.keys(flags).length, + metadata: flags.TASK_ANALYTICS, + staticResolution: { + experimentOnly: experimentOnly.TASK_ANALYTICS, + environment: environment.TASK_ANALYTICS, + settings: settings.TASK_ANALYTICS, + explicit: explicit.TASK_ANALYTICS, + }, + }) + ); +} + +const runtimePath = process.argv[2]; +if (!runtimePath) { + throw new Error("Usage: tsx examples/internal-feature-flags.ts "); +} + +const home = await mkdtemp(join(tmpdir(), "copilot-feature-flags-")); +const child = spawn( + resolve(runtimePath), + ["--server", "--stdio", "--no-auto-login", "--no-auto-update", "--no-remote"], + { + cwd: home, + env: { ...process.env, COPILOT_HOME: home, COPILOT_CONNECTION_TOKEN: "" }, + stdio: ["pipe", "pipe", "inherit"], + windowsHide: true, + } +); +const closed = once(child, "close"); +const connection = createMessageConnection(child.stdout, child.stdin); +connection.listen(); +try { + await Promise.race([ + showFeatureFlags(createInternalServerRpc(connection)), + closed.then(([code, signal]) => { + throw new Error( + `Runtime exited before the example completed: code=${code}, signal=${signal}` + ); + }), + ]); +} finally { + connection.dispose(); + child.stdin.end(); + try { + const [code, signal] = await closed; + assert.equal(code, 0, `Runtime exit signal: ${signal}`); + } finally { + await rm(home, { recursive: true, force: true }); + } +} diff --git a/nodejs/examples/internal-user-settings.ts b/nodejs/examples/internal-user-settings.ts new file mode 100644 index 0000000000..8bc4641903 --- /dev/null +++ b/nodejs/examples/internal-user-settings.ts @@ -0,0 +1,29 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +import type { createInternalServerRpc } from "../src/generated/rpc.js"; + +// Internal host example; these methods are not part of the public SDK API. +export async function editUserSettings( + settings: ReturnType["user"]["settings"], + configDir: string +) { + const validation = await settings.validate({ + topKey: "voice", + value: { enabled: true }, + }); + if (!validation.ok) { + throw new Error(JSON.stringify(validation.issues)); + } + await settings.update({ + key: "voice", + value: { enabled: true }, + configDir, + }); + return settings.update({ + key: "voice", + value: { selectedModel: null }, + configDir, + }); +} diff --git a/nodejs/package-lock.json b/nodejs/package-lock.json index e8104e0779..fc233f8b94 100644 --- a/nodejs/package-lock.json +++ b/nodejs/package-lock.json @@ -28,13 +28,12 @@ "json-schema": "^0.4.0", "json-schema-to-typescript": "^15.0.4", "prettier": "^3.8.1", - "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", + "vitest": "^4.1.11", "ws": "^8.21.0" }, "engines": { @@ -58,37 +57,6 @@ "url": "https://github.com/sponsors/philsturgeon" } }, - "node_modules/@emnapi/core": { - "version": "1.10.0", - "integrity": "sha1-OAzMjyQS6iLR2XLff47iOjucdGc=", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/wasi-threads": "1.2.1", - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/runtime": { - "version": "1.10.0", - "integrity": "sha1-SyYMDTU0IE6YxhELjbGph9JuyHw=", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/wasi-threads": { - "version": "1.2.1", - "integrity": "sha1-KP7SGhuhznl8RKBwq8lNQvOuhUg=", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, "node_modules/@esbuild/aix-ppc64": { "version": "0.28.1", "integrity": "sha1-egGo0uwvuy2seK2tCbD6eB5Agr4=", @@ -660,12 +628,6 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@glideapps/ts-necessities": { - "version": "2.2.3", - "integrity": "sha512-gXi0awOZLHk3TbW55GZLCPP6O+y/b5X1pBXKBVckFONSwF1z1E5ND2BGJsghQFah+pW7pkkyFb2VhUQI2qhL5w==", - "dev": true, - "license": "MIT" - }, "node_modules/@hpke/chacha20poly1305": { "version": "1.8.0", "integrity": "sha1-1NL248zgi0xVU5IOFbTlmMildy4=", @@ -724,27 +686,40 @@ } }, "node_modules/@humanfs/core": { - "version": "0.19.1", - "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "version": "0.19.2", + "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", "dev": true, "license": "Apache-2.0", + "dependencies": { + "@humanfs/types": "^0.15.0" + }, "engines": { "node": ">=18.18.0" } }, "node_modules/@humanfs/node": { - "version": "0.16.7", - "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", + "version": "0.16.8", + "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@humanfs/core": "^0.19.1", + "@humanfs/core": "^0.19.2", + "@humanfs/types": "^0.15.0", "@humanwhocodes/retry": "^0.4.0" }, "engines": { "node": ">=18.18.0" } }, + "node_modules/@humanfs/types": { + "version": "0.15.0", + "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, "node_modules/@humanwhocodes/module-importer": { "version": "1.0.1", "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", @@ -784,8 +759,8 @@ } }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "version": "1.6.0", + "integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==", "dev": true, "license": "MIT" }, @@ -1072,34 +1047,13 @@ "dev": true, "license": "MIT" }, - "node_modules/@napi-rs/wasm-runtime": { - "version": "1.2.3", - "integrity": "sha1-l+PUXXQk3F2h1OMvO/OykvbBtEw=", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@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 || ^2.0.0-alpha.4", - "@emnapi/runtime": "^1.7.1 || ^2.0.0-alpha.4" - } - }, "node_modules/@oxc-project/types": { - "version": "0.133.0", - "integrity": "sha512-KzkdCd6Uxqnf6l3HOw1xfatAlUURA0g14cvBYFyJ5SaNOQbOUvBr9PKArcPcrNIeRsBdgcUzOGrhKveVpvOIGA==", + "version": "0.153.0", + "integrity": "sha512-jSNrRIyvPVLL5E1z/tS5zLAprosGz/FuI1PWwEOz59j3E4TTTzSpqKb/RDGqJhhW0QHYISGXxq582wGx6T11Tw==", "dev": true, "license": "MIT", "funding": { - "url": "https://github.com/sponsors/Boshen" + "url": "https://github.com/sponsors/oxc-project" } }, "node_modules/@platformatic/vfs": { @@ -1111,9 +1065,25 @@ "node": ">= 22" } }, + "node_modules/@rolldown/binding-android-arm-eabi": { + "version": "1.2.13", + "integrity": "sha512-Vu/NICrgdRvUOeaJZIyB2FP63s6fO/4ud5l8dXmxDx9yDyn+mN0y+gsvsVviSli+T361Aqo0h46EuMoobrwawA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, "node_modules/@rolldown/binding-android-arm64": { - "version": "1.0.3", - "integrity": "sha1-VM6Pg4IhP0oxSgwve6g/gf/q5ZI=", + "version": "1.2.13", + "integrity": "sha512-6LEoIrGy5vvCd2boNnv2soPSUZj/PRvhp8R/t9KlX+mIZxThIE/ZqxrGXxmuNX0FuvQFvLdZr7S4HrYpYBVhEQ==", "cpu": [ "arm64" ], @@ -1128,8 +1098,8 @@ } }, "node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.0.3", - "integrity": "sha512-PcAhP+ynjURNyy8SKGl5DQP94aGuB/7JrXJb/t7P+hanXvQVMWzUvRRhBAcg/lNRadBhoUPqSoP4xw5tR/KBEA==", + "version": "1.2.13", + "integrity": "sha512-CaJSRzlQ6Q+zkDMVA9OkVxBjVoPRCDEE3v0FSJ9kIQBgRjFApA3Si4ijhbNVUji7ZJt+PfO0e46qy4nPInHUPA==", "cpu": [ "arm64" ], @@ -1144,8 +1114,8 @@ } }, "node_modules/@rolldown/binding-darwin-x64": { - "version": "1.0.3", - "integrity": "sha1-U/V94fWZ7PHbE4I8/IjBj7gJVK0=", + "version": "1.2.13", + "integrity": "sha512-e4r340I5hxSzJkD3YJBxgzq/rJ3wwxwdLLb2FPt1Zpyem20nrQPT11DDEEkazwxaHumH8wpglsp/Ozgj/wT6mw==", "cpu": [ "x64" ], @@ -1160,8 +1130,8 @@ } }, "node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.0.3", - "integrity": "sha1-bz/dobeuqsnSaKUmgEtPuW5ONfE=", + "version": "1.2.13", + "integrity": "sha512-hoBWDZ4/K/mJKVMbsgUd1jfpA2DiOrJ54mpegObU9TBHBBrtX4FLIXJJQRDPZ/2Dn91GvPob+Z2hwKJzf4E5SQ==", "cpu": [ "x64" ], @@ -1176,8 +1146,8 @@ } }, "node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.0.3", - "integrity": "sha1-2HpFS/WFzJZ2hJN36R1uN1KXMm8=", + "version": "1.2.13", + "integrity": "sha512-oF9fZu3vTWnTiVAkDFxU9PJLt1nh20K9RRpuHsR7ZnE2L7XookBbDBVLojCVSLGbvtSlxeUNnjAj6DbNsKyzbg==", "cpu": [ "arm" ], @@ -1192,12 +1162,15 @@ } }, "node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.0.3", - "integrity": "sha1-QZ/Wv2Es80jxBSjLzZTrq5YH2NE=", + "version": "1.2.13", + "integrity": "sha512-DTpb/+jMiqWMPgphn4ksW1zW+XLJ6dJJ9FMvnIk9I291LhCctMPhMYJrP5t9p6C9qa3KXadDdUEZHBaPh3kNxQ==", "cpu": [ "arm64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -1208,12 +1181,15 @@ } }, "node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.0.3", - "integrity": "sha1-/MaRhpa7doRId+HkkwoY/Q03QGk=", + "version": "1.2.13", + "integrity": "sha512-xU+Kvv9N2A3SY35zFh1FWWNjxbadE/BKeqnkXe7vScU4GZsw757BlVmDLITZEq8DPpk6m7uoDYkK7P+x/uU5Ow==", "cpu": [ "arm64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -1224,12 +1200,15 @@ } }, "node_modules/@rolldown/binding-linux-ppc64-gnu": { - "version": "1.0.3", - "integrity": "sha1-Mq7LfI2uXU8qjN5XoFjshpkVQvg=", + "version": "1.2.13", + "integrity": "sha512-XMk/hnhh25oQ756Lclb1Q11h4ll0xC0Esa5w4Vzp+mAE2S3AGJgeM0SonrIgY8fHhjarb6hcoTuwoFG0VVP2ig==", "cpu": [ "ppc64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -1240,12 +1219,15 @@ } }, "node_modules/@rolldown/binding-linux-s390x-gnu": { - "version": "1.0.3", - "integrity": "sha1-vtk0bqgea7i5PPEfXYi3fbiQt2M=", + "version": "1.2.13", + "integrity": "sha512-U1BoTHYmlywE2n3HUWjzZ+8EQsKr+OwpxYuDWaubx42tUL8QSL7nOqRz/NVHanibbFP3M1Rxuprz/wNcbMKjqw==", "cpu": [ "s390x" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -1256,12 +1238,15 @@ } }, "node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.0.3", - "integrity": "sha1-ZMLSb3Xf/ZtaH5dVegCudyUMjLc=", + "version": "1.2.13", + "integrity": "sha512-J4cm80Pc6wEwV5AZ42o62sIQgDk+Y7R2s646jIwuvO+4V46QB58N4vVuMPWCBaNgursXLpmUWJV//WQAkPvHBQ==", "cpu": [ "x64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -1272,12 +1257,15 @@ } }, "node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.0.3", - "integrity": "sha1-WkUTLopHZZ7qrztUDClUqXyGD/M=", + "version": "1.2.13", + "integrity": "sha512-W0euZS40U4V6JkmEnVCIX9mcsL6cbXQ4X8c2w+MMOIjFg68TXUW+u3RT+2e1t1pv9mg/EP3pDtK3POXdQ7Rxyw==", "cpu": [ "x64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -1288,8 +1276,8 @@ } }, "node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.0.3", - "integrity": "sha1-KQUTBoxV6EnchFejKv7h17Csswk=", + "version": "1.2.13", + "integrity": "sha512-do/AF/4n9MRshia3a5aXoy8xM2c6mgfT6D7H/dY6my5z+hfCIR4ab7mk4IRj++nsEQpKy39kd0DQnDq+r692tw==", "cpu": [ "arm64" ], @@ -1303,27 +1291,9 @@ "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@rolldown/binding-wasm32-wasi": { - "version": "1.0.3", - "integrity": "sha1-PZly2/GpU9PHr6pKDyDvKy458xs=", - "cpu": [ - "wasm32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/core": "1.10.0", - "@emnapi/runtime": "1.10.0", - "@napi-rs/wasm-runtime": "^1.1.4" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, "node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.0.3", - "integrity": "sha1-oASrYHoW1vA7y1VXKP+IivdXc60=", + "version": "1.2.13", + "integrity": "sha512-6FtUf2cJZobutJdxnGYX8z0vtbgqU0b2v8vfBhkXb9wp2MppMFtkunE+IaOntyi01rkIQoPW2GHABtfzsC0BRQ==", "cpu": [ "arm64" ], @@ -1338,8 +1308,8 @@ } }, "node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.0.3", - "integrity": "sha1-4qJbNGkaHMihIJ195wkGMCbdDNs=", + "version": "1.2.13", + "integrity": "sha512-UKhFaZTeTcbKrb3iU82m17jVcPEAoqDkCN5MBqgPyZSwp/cQmib21crTS0DdH7T1B7wZRWEI89YQAOJMet5yhg==", "cpu": [ "x64" ], @@ -1365,16 +1335,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@tybys/wasm-util": { - "version": "0.10.3", - "integrity": "sha1-AVy6np3UfOFNA9KoxdVHv7FpZl0=", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, "node_modules/@types/chai": { "version": "5.2.3", "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", @@ -1656,15 +1616,15 @@ } }, "node_modules/@vitest/expect": { - "version": "4.1.8", - "integrity": "sha512-h3nDO677RDLEGlBxyQ5CW8RlMThSKSRLUePLOx09gNIWRL40edgA1GCZSZgf1W55MFAG6/Sw14KeaAnqv0NKdQ==", + "version": "4.1.11", + "integrity": "sha512-VX2x5vNJXET47KAFzwERI+KRMtTTCSWTfSMKsW7JsUsXV4psq++e3DvZpuTDOpHcxytiDs6p2nhVb2tVDiiUYw==", "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", + "@vitest/spy": "4.1.11", + "@vitest/utils": "4.1.11", "chai": "^6.2.2", "tinyrainbow": "^3.1.0" }, @@ -1673,12 +1633,12 @@ } }, "node_modules/@vitest/mocker": { - "version": "4.1.8", - "integrity": "sha512-LEiN/xe4OSIbKe9HQIp5OC24agGD9J5CnmMgsLohVVoOPWL9a2sBoR6VBx43jQZb7Kr1l4RCuyCJzcAa0+dojw==", + "version": "4.1.11", + "integrity": "sha512-2XJVD55d1o5AZous5CCGKS74g/riOj9odEt2bQpCVZeblHyHdnMeFl4jl0XjU21stf4mbjUkew2eXQZt65g5CQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "4.1.8", + "@vitest/spy": "4.1.11", "estree-walker": "^3.0.3", "magic-string": "^0.30.21" }, @@ -1699,8 +1659,8 @@ } }, "node_modules/@vitest/pretty-format": { - "version": "4.1.8", - "integrity": "sha512-9GasEBxpZ1VYIpqHf/0+YGg121uSNwCKOJqIrTwWP/TB7DmFCiaBpNl3aPZzoLWfWkuqhbH8vJIVobZkvdo2cA==", + "version": "4.1.11", + "integrity": "sha512-yiZzPbGTS9Sr/JpFl8zHrcIkAofNbFV6k21vIgQN/cY/oxZeXhJv5sc/MBJ5jFKWmWs+oJHw0UXLZjmf931+Vw==", "dev": true, "license": "MIT", "dependencies": { @@ -1711,12 +1671,12 @@ } }, "node_modules/@vitest/runner": { - "version": "4.1.8", - "integrity": "sha512-EmVxeBAfMJvycdjd6Hm+RbFBbA9fKvo0Kx37hNpBYoYeavH3RNsBXWDooR1mgD52dCrxIIuP7UotpfiwOikvcg==", + "version": "4.1.11", + "integrity": "sha512-LztvUgdwMNJMIkj3hQnnxiC2Xy1zNxq928W/xhjCLaNCzqTZOudjwbQf6v9IntZGPw132i2Lq2rgTRZHD3JHNw==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "4.1.8", + "@vitest/utils": "4.1.11", "pathe": "^2.0.3" }, "funding": { @@ -1724,13 +1684,13 @@ } }, "node_modules/@vitest/snapshot": { - "version": "4.1.8", - "integrity": "sha512-acfZboRmAIf05DEKcBQy33VXojFJjtUdLyo7oOmV9kebb2xdU01UknNiPuPZoJZQyO7DF0gZdTGTpeAzET9QPQ==", + "version": "4.1.11", + "integrity": "sha512-pN7ikn1ON7h8ee4gIAp4AzyK+zBtJPzVbqOgu5LCEh4VaJVbPQcgYQYJIMGQPXVeJJq1fnfazis7a5pFNPahog==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "4.1.8", - "@vitest/utils": "4.1.8", + "@vitest/pretty-format": "4.1.11", + "@vitest/utils": "4.1.11", "magic-string": "^0.30.21", "pathe": "^2.0.3" }, @@ -1739,8 +1699,8 @@ } }, "node_modules/@vitest/spy": { - "version": "4.1.8", - "integrity": "sha512-6EevtBp6OZOPF7bmz36HrGMeP3txgVSrgebWxHOafDXGkhIzfXK14f8KF6MuFfgXXUeHxmpD3BQxkV00/3s5mA==", + "version": "4.1.11", + "integrity": "sha512-apNa/prQy2qCeywhnixOHPRCgGNhvg7T4Dapfl1GahLp/R+uhBm5cPyFoNVyqsNd2h1nJxL6BqqdIjiABL60YA==", "dev": true, "license": "MIT", "funding": { @@ -1748,12 +1708,12 @@ } }, "node_modules/@vitest/utils": { - "version": "4.1.8", - "integrity": "sha512-uOJamYALNhfJ6iolExyQM40yIQwDqYnkKtQ5VCiSe17E33H0aQ/u+1GlRuz4LZBk6Mm3sg90G9hEbmEt37C1Zg==", + "version": "4.1.11", + "integrity": "sha512-zTCVGpyFsGWBhllOyKlTw/vnr6D9qxsfSDyfbyZmTyjHw5N/VuvzHpHoQjm2ZJzn4RJgx5w4r7V0er69CmLgPQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "4.1.8", + "@vitest/pretty-format": "4.1.11", "convert-source-map": "^2.0.0", "tinyrainbow": "^3.1.0" }, @@ -1761,18 +1721,6 @@ "url": "https://opencollective.com/vitest" } }, - "node_modules/abort-controller": { - "version": "3.0.0", - "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", - "dev": true, - "license": "MIT", - "dependencies": { - "event-target-shim": "^5.0.0" - }, - "engines": { - "node": ">=6.5" - } - }, "node_modules/acorn": { "version": "8.15.0", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", @@ -1831,29 +1779,9 @@ "dev": true, "license": "MIT" }, - "node_modules/base64-js": { - "version": "1.5.1", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "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", - "integrity": "sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw==", + "version": "1.1.21", + "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", "dev": true, "license": "MIT", "dependencies": { @@ -1861,36 +1789,6 @@ "concat-map": "0.0.1" } }, - "node_modules/browser-or-node": { - "version": "3.0.0", - "integrity": "sha512-iczIdVJzGEYhP5DqQxYM9Hh7Ztpqqi+CXZpSmX8ALFs9ecXkQIeqRyM6TfxEfMVpwhl3dSuDvxdzzo9sUOIVBQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/buffer": { - "version": "6.0.3", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "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", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", @@ -1901,8 +1799,8 @@ } }, "node_modules/chai": { - "version": "6.2.2", - "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", + "version": "6.3.0", + "integrity": "sha512-XWAtwJ6OHO+tj0EKCs0Y2UamnyOxseZWltU4x2U2wh8g4AigdjwvtUjvLP2tqkA/avxHEtzxNaqGq/YGNwckKg==", "dev": true, "license": "MIT", "engines": { @@ -1949,12 +1847,6 @@ "node": ">=18" } }, - "node_modules/collection-utils": { - "version": "1.0.1", - "integrity": "sha512-LA2YTIlR7biSpXkKYwwuzGjwL5rjWEZVOSnvdUc7gObvWe4WkjxOpfrdhoP7Hs09YWDVfg0Mal9BpAqLfVEzQg==", - "dev": true, - "license": "Apache-2.0" - }, "node_modules/color-convert": { "version": "2.0.1", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", @@ -1985,15 +1877,6 @@ "dev": true, "license": "MIT" }, - "node_modules/cross-fetch": { - "version": "4.1.0", - "integrity": "sha512-uKm5PU+MHTootlWEY+mZ4vvXoCn4fLQxT9dSc1sXVMSFkINTJVN8cAQROpwcKm8bJ/c7rgZVIBWzH5T78sNZZw==", - "dev": true, - "license": "MIT", - "dependencies": { - "node-fetch": "^2.7.0" - } - }, "node_modules/cross-spawn": { "version": "7.0.6", "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", @@ -2299,24 +2182,6 @@ "node": ">=0.10.0" } }, - "node_modules/event-target-shim": { - "version": "5.0.1", - "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/events": { - "version": "3.3.0", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.x" - } - }, "node_modules/expect-type": { "version": "1.3.0", "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", @@ -2488,26 +2353,6 @@ "node": ">=16.0.0" } }, - "node_modules/ieee754": { - "version": "1.2.1", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "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", "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", @@ -2563,24 +2408,12 @@ "node": ">=0.10.0" } }, - "node_modules/is-url": { - "version": "1.2.4", - "integrity": "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==", - "dev": true, - "license": "MIT" - }, "node_modules/isexe": { "version": "2.0.0", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true, "license": "ISC" }, - "node_modules/js-base64": { - "version": "3.7.8", - "integrity": "sha512-hNngCeKxIUQiEUN3GPJOkz4wF/YvdUdbNL9hsBcMQTkKzboD7T/q3OYOuuPZLUE6dBxSGpwhk5mwuDud7JVAow==", - "dev": true, - "license": "BSD-3-Clause" - }, "node_modules/js-yaml": { "version": "4.3.2", "integrity": "sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==", @@ -2702,8 +2535,8 @@ } }, "node_modules/lightningcss": { - "version": "1.32.0", - "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "version": "1.33.0", + "integrity": "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==", "dev": true, "license": "MPL-2.0", "dependencies": { @@ -2717,22 +2550,22 @@ "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" + "lightningcss-android-arm64": "1.33.0", + "lightningcss-darwin-arm64": "1.33.0", + "lightningcss-darwin-x64": "1.33.0", + "lightningcss-freebsd-x64": "1.33.0", + "lightningcss-linux-arm-gnueabihf": "1.33.0", + "lightningcss-linux-arm64-gnu": "1.33.0", + "lightningcss-linux-arm64-musl": "1.33.0", + "lightningcss-linux-x64-gnu": "1.33.0", + "lightningcss-linux-x64-musl": "1.33.0", + "lightningcss-win32-arm64-msvc": "1.33.0", + "lightningcss-win32-x64-msvc": "1.33.0" } }, "node_modules/lightningcss-android-arm64": { - "version": "1.32.0", - "integrity": "sha1-8DOIURbf79nG9UeHUj41FLYeGWg=", + "version": "1.33.0", + "integrity": "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==", "cpu": [ "arm64" ], @@ -2751,8 +2584,8 @@ } }, "node_modules/lightningcss-darwin-arm64": { - "version": "1.32.0", - "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "version": "1.33.0", + "integrity": "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==", "cpu": [ "arm64" ], @@ -2771,8 +2604,8 @@ } }, "node_modules/lightningcss-darwin-x64": { - "version": "1.32.0", - "integrity": "sha1-NfPpczLRMLnKGB4RtWje1q68bV4=", + "version": "1.33.0", + "integrity": "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==", "cpu": [ "x64" ], @@ -2791,8 +2624,8 @@ } }, "node_modules/lightningcss-freebsd-x64": { - "version": "1.32.0", - "integrity": "sha1-l3enZHK2Ttb/lDQq1kx7r9eUpXU=", + "version": "1.33.0", + "integrity": "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==", "cpu": [ "x64" ], @@ -2811,8 +2644,8 @@ } }, "node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.32.0", - "integrity": "sha1-E65lLhq3O5E117faFy9mbEEK1T0=", + "version": "1.33.0", + "integrity": "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==", "cpu": [ "arm" ], @@ -2831,12 +2664,15 @@ } }, "node_modules/lightningcss-linux-arm64-gnu": { - "version": "1.32.0", - "integrity": "sha1-QXhYeVqUWS9oASOhsfnaig4e8zU=", + "version": "1.33.0", + "integrity": "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==", "cpu": [ "arm64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MPL-2.0", "optional": true, "os": [ @@ -2851,12 +2687,15 @@ } }, "node_modules/lightningcss-linux-arm64-musl": { - "version": "1.32.0", - "integrity": "sha1-a+NmkugQtxgECAL9gJYjz/5zITM=", + "version": "1.33.0", + "integrity": "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==", "cpu": [ "arm64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MPL-2.0", "optional": true, "os": [ @@ -2871,12 +2710,15 @@ } }, "node_modules/lightningcss-linux-x64-gnu": { - "version": "1.32.0", - "integrity": "sha1-C3gDr06yHP043Tn+Kru1PH3QkfY=", + "version": "1.33.0", + "integrity": "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==", "cpu": [ "x64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MPL-2.0", "optional": true, "os": [ @@ -2891,12 +2733,15 @@ } }, "node_modules/lightningcss-linux-x64-musl": { - "version": "1.32.0", - "integrity": "sha1-iNyLqGXd3bGsXvBLDxYYBEGMFjs=", + "version": "1.33.0", + "integrity": "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==", "cpu": [ "x64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MPL-2.0", "optional": true, "os": [ @@ -2911,8 +2756,8 @@ } }, "node_modules/lightningcss-win32-arm64-msvc": { - "version": "1.32.0", - "integrity": "sha1-TzC6P6XpJfW3n5RejMDRdsOxqzg=", + "version": "1.33.0", + "integrity": "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==", "cpu": [ "arm64" ], @@ -2931,8 +2776,8 @@ } }, "node_modules/lightningcss-win32-x64-msvc": { - "version": "1.32.0", - "integrity": "sha1-FBqlYFZFBkkokCu0rwRfp9n0Igo=", + "version": "1.33.0", + "integrity": "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==", "cpu": [ "x64" ], @@ -3020,8 +2865,8 @@ } }, "node_modules/minimatch/node_modules/brace-expansion": { - "version": "5.0.8", - "integrity": "sha512-JZyDyq3D4AUifKTPOB7DELf6XsB3WdPuNxCtob1vFXPsSXhdAiHBWJ/tJ8HAc9aH84BK+5JFZLNkJKx3G9kzQg==", + "version": "5.0.12", + "integrity": "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==", "dev": true, "license": "MIT", "dependencies": { @@ -3068,8 +2913,8 @@ "license": "MIT" }, "node_modules/nanoid": { - "version": "3.3.17", - "integrity": "sha512-xQLf0A3HOMlgHq0n247/LRuAOYmB7dXJ/DvAxGvsSBij45XtBSmQycu+F8ODbHwns/XyFZagyL1+J0Offw1E0g==", + "version": "3.3.20", + "integrity": "sha512-uKdg2G3GNCKQn9byYOpxbGqrT2fGO5KRt5J/8b3pok8rT6qxGWF6hxMyJiEYtAf+FVyYuD9hRaDqX5uPFYJ4ZQ==", "dev": true, "funding": [ { @@ -3091,26 +2936,6 @@ "dev": true, "license": "MIT" }, - "node_modules/node-fetch": { - "version": "2.7.0", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", - "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", "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==", @@ -3174,12 +2999,6 @@ "dev": true, "license": "BlueOak-1.0.0" }, - "node_modules/pako": { - "version": "1.0.11", - "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", - "dev": true, - "license": "(MIT AND Zlib)" - }, "node_modules/parent-module": { "version": "1.0.1", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", @@ -3239,8 +3058,8 @@ "license": "ISC" }, "node_modules/picomatch": { - "version": "4.0.4", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "version": "4.0.7", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", "dev": true, "license": "MIT", "engines": { @@ -3250,18 +3069,9 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/pluralize": { - "version": "8.0.0", - "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/postcss": { - "version": "8.5.25", - "integrity": "sha512-DTPx3RWSSnWyzLxQnlH0rJP+EW5ekl16ZU4/psbIhA0e53kJfdgaN5vKM+xP7yJtXVu+nfdVFmlgFDEKAe4Pyw==", + "version": "8.5.29", + "integrity": "sha512-49cGhUbXj8Qenv0iTMxA1cFBzxXoctpC9Ujd77t1WcbJIr6nF/eI7g/8MgxrYldFRuAXvja7xQRwavoW7kgrxQ==", "dev": true, "funding": [ { @@ -3279,9 +3089,9 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.16", + "nanoid": "^3.3.19", "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" + "source-map-js": "^1.2.2" }, "engines": { "node": "^10 || ^12 || >=14" @@ -3311,15 +3121,6 @@ "url": "https://github.com/prettier/prettier?sponsor=1" } }, - "node_modules/process": { - "version": "0.11.10", - "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6.0" - } - }, "node_modules/punycode": { "version": "2.3.1", "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", @@ -3329,44 +3130,6 @@ "node": ">=6" } }, - "node_modules/quicktype-core": { - "version": "23.2.6", - "integrity": "sha512-asfeSv7BKBNVb9WiYhFRBvBZHcRutPRBwJMxW0pefluK4kkKu4lv0IvZBwFKvw2XygLcL1Rl90zxWDHYgkwCmA==", - "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", - "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==", - "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", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", @@ -3396,12 +3159,12 @@ } }, "node_modules/rolldown": { - "version": "1.0.3", - "integrity": "sha512-i00lAJ2ks1BYr7rjNjKC7BcqAS7nVfiT3QX1SI5aY+AFHblCmaUf9OE9dbdzDvW6dJxbi2ZCZiy9v3CcwOiX3g==", + "version": "1.2.13", + "integrity": "sha512-/5apOkwWxtACcQtZ5WlrAsKS4JwXVThQPJZ3RqIruRY9UG9M8e0VlXN068xum/JRKA4aIUoJ13ghe/kGCvidiQ==", "dev": true, "license": "MIT", "dependencies": { - "@oxc-project/types": "=0.133.0", + "@oxc-project/types": "=0.153.0", "@rolldown/pluginutils": "^1.0.0" }, "bin": { @@ -3411,42 +3174,22 @@ "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", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "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" + "@rolldown/binding-android-arm-eabi": "1.2.13", + "@rolldown/binding-android-arm64": "1.2.13", + "@rolldown/binding-darwin-arm64": "1.2.13", + "@rolldown/binding-darwin-x64": "1.2.13", + "@rolldown/binding-freebsd-x64": "1.2.13", + "@rolldown/binding-linux-arm-gnueabihf": "1.2.13", + "@rolldown/binding-linux-arm64-gnu": "1.2.13", + "@rolldown/binding-linux-arm64-musl": "1.2.13", + "@rolldown/binding-linux-ppc64-gnu": "1.2.13", + "@rolldown/binding-linux-s390x-gnu": "1.2.13", + "@rolldown/binding-linux-x64-gnu": "1.2.13", + "@rolldown/binding-linux-x64-musl": "1.2.13", + "@rolldown/binding-openharmony-arm64": "1.2.13", + "@rolldown/binding-win32-arm64-msvc": "1.2.13", + "@rolldown/binding-win32-x64-msvc": "1.2.13" + } }, "node_modules/semver": { "version": "7.7.3", @@ -3488,8 +3231,8 @@ "license": "ISC" }, "node_modules/source-map-js": { - "version": "1.2.1", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "version": "1.2.2", + "integrity": "sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==", "dev": true, "license": "BSD-3-Clause", "engines": { @@ -3508,15 +3251,6 @@ "dev": true, "license": "MIT" }, - "node_modules/string_decoder": { - "version": "1.3.0", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dev": true, - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, "node_modules/strip-json-comments": { "version": "3.1.1", "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", @@ -3557,12 +3291,6 @@ "node": ">=18" } }, - "node_modules/tiny-inflate": { - "version": "1.0.3", - "integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==", - "dev": true, - "license": "MIT" - }, "node_modules/tinybench": { "version": "2.9.0", "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", @@ -3595,20 +3323,14 @@ } }, "node_modules/tinyrainbow": { - "version": "3.1.0", - "integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==", + "version": "3.2.0", + "integrity": "sha512-LgO3D9yZJjApUiuUfl9iFAwrtaX4+lok3wJIqttGoKCHlWUqHqbQpnxCf82L8FjgKsh4iGo78hqJwgL8F6To2A==", "dev": true, "license": "MIT", "engines": { "node": ">=14.0.0" } }, - "node_modules/tr46": { - "version": "0.0.3", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", - "dev": true, - "license": "MIT" - }, "node_modules/ts-api-utils": { "version": "2.4.0", "integrity": "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==", @@ -3621,13 +3343,6 @@ "typescript": ">=4.8.4" } }, - "node_modules/tslib": { - "version": "2.8.1", - "integrity": "sha1-YS7+TtI11Wfoq6Xypfq3AoCt6D8=", - "dev": true, - "license": "0BSD", - "optional": true - }, "node_modules/tsx": { "version": "4.22.4", "integrity": "sha512-X8EX+XV4QR5xCsrgxaED954zTDfY8KqlDtskKEL0cHhyS/P8b4IFOvGDQpsC9Q1XnLq915wEfwwY/zzskCtmhg==", @@ -3677,32 +3392,6 @@ "dev": true, "license": "MIT" }, - "node_modules/unicode-properties": { - "version": "1.4.1", - "integrity": "sha512-CLjCCLQ6UuMxWnbIylkisbRj31qxHPAurvena/0iwSVbQ2G1VY5/HjV0IRabOEbDHlzZlRdCrD4NhB0JtU40Pg==", - "dev": true, - "license": "MIT", - "dependencies": { - "base64-js": "^1.3.0", - "unicode-trie": "^2.0.0" - } - }, - "node_modules/unicode-trie": { - "version": "2.0.0", - "integrity": "sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ==", - "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", - "integrity": "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==", - "dev": true, - "license": "MIT" - }, "node_modules/uri-js": { "version": "4.4.1", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", @@ -3712,22 +3401,16 @@ "punycode": "^2.1.0" } }, - "node_modules/urijs": { - "version": "1.19.11", - "integrity": "sha512-HXgFDgDommxn5/bIv0cnQZsPhHDA90NPHD6+c/v21U5+Sx5hoP8+dP9IZXBU1gIfvdRfhG8cel9QNPeionfcCQ==", - "dev": true, - "license": "MIT" - }, "node_modules/vite": { - "version": "8.0.16", - "integrity": "sha512-h9bXPmJichP5fLmVQo3PyaGSDE2n3aPuomeAlVRm0JLmt4rY6zmPKd59HYI4LNW8oTK7tlTsuC7l/m7awx9Jcw==", + "version": "8.3.4", + "integrity": "sha512-IftLE8phJhdNRIGNEV4JCCvfNScoZQVfcXaN8TQropWGJtOHcu+iAB9pHcuGvmEb/kddw0LBe/jS8ztdC6xdiQ==", "dev": true, "license": "MIT", "dependencies": { - "lightningcss": "^1.32.0", - "picomatch": "^4.0.4", - "postcss": "^8.5.15", - "rolldown": "1.0.3", + "lightningcss": "^1.33.0", + "picomatch": "^4.0.7", + "postcss": "^8.5.29", + "rolldown": "~1.2.12", "tinyglobby": "^0.2.17" }, "bin": { @@ -3744,7 +3427,7 @@ }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", - "@vitejs/devtools": "^0.1.18", + "@vitejs/devtools": "^0.7.1", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", @@ -3796,18 +3479,18 @@ } }, "node_modules/vitest": { - "version": "4.1.8", - "integrity": "sha512-flY6ScbCIt9HThs+C5HS7jvGOB560DJtk/Z15IQROTA6zEy49Nh8T/dofWTQL+n3vswqn87sbJNiuqw1SDp5Ig==", + "version": "4.1.11", + "integrity": "sha512-fhACrNXUidIbGSBr5FlbuBkO7VWC1ZyLl0DO4CU2DrQoAPxX84Ysxs+HeGQpii5lZWV1Q4gBZTTu49mF+A6Edw==", "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", + "@vitest/expect": "4.1.11", + "@vitest/mocker": "4.1.11", + "@vitest/pretty-format": "4.1.11", + "@vitest/runner": "4.1.11", + "@vitest/snapshot": "4.1.11", + "@vitest/spy": "4.1.11", + "@vitest/utils": "4.1.11", "es-module-lexer": "^2.0.0", "expect-type": "^1.3.0", "magic-string": "^0.30.21", @@ -3835,12 +3518,12 @@ "@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", + "@vitest/browser-playwright": "4.1.11", + "@vitest/browser-preview": "4.1.11", + "@vitest/browser-webdriverio": "4.1.11", + "@vitest/coverage-istanbul": "4.1.11", + "@vitest/coverage-v8": "4.1.11", + "@vitest/ui": "4.1.11", "happy-dom": "*", "jsdom": "*", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" @@ -3892,22 +3575,6 @@ "node": ">=14.0.0" } }, - "node_modules/webidl-conversions": { - "version": "3.0.1", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", - "dev": true, - "license": "BSD-2-Clause" - }, - "node_modules/whatwg-url": { - "version": "5.0.0", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "dev": true, - "license": "MIT", - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, "node_modules/which": { "version": "2.0.2", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", @@ -3948,12 +3615,6 @@ "node": ">=0.10.0" } }, - "node_modules/wordwrap": { - "version": "1.0.0", - "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", - "dev": true, - "license": "MIT" - }, "node_modules/ws": { "version": "8.21.0", "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==", @@ -3984,21 +3645,6 @@ "node": ">=18" } }, - "node_modules/yaml": { - "version": "2.9.0", - "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==", - "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", "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", diff --git a/nodejs/package.json b/nodejs/package.json index bf0b7222c8..10c4f52cad 100644 --- a/nodejs/package.json +++ b/nodejs/package.json @@ -5,7 +5,7 @@ "url": "https://github.com/github/copilot-sdk.git" }, "version": "0.0.0-dev", - "copilotCliVersion": "1.0.95-0", + "copilotCliVersion": "1.0.98-2", "description": "TypeScript SDK for programmatic control of GitHub Copilot CLI via JSON-RPC", "main": "./dist/cjs/index.js", "types": "./dist/index.d.ts", @@ -84,13 +84,12 @@ "json-schema": "^0.4.0", "json-schema-to-typescript": "^15.0.4", "prettier": "^3.8.1", - "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", + "vitest": "^4.1.11", "ws": "^8.21.0" }, "engines": { diff --git a/nodejs/samples/package-lock.json b/nodejs/samples/package-lock.json index 3215d8b297..8b911dbef4 100644 --- a/nodejs/samples/package-lock.json +++ b/nodejs/samples/package-lock.json @@ -23,6 +23,7 @@ "zod": "4.3.6" }, "devDependencies": { + "@microsoft/agent-host-protocol-v09": "npm:@microsoft/agent-host-protocol@0.9.0", "@platformatic/vfs": "^0.3.0", "@types/node": "^25.2.0", "@types/semver": "7.8.0", @@ -32,16 +33,16 @@ "esbuild": "^0.28.1", "eslint": "^9.0.0", "glob": "^13.0.1", + "hpke-js": "^1.8.0", "json-schema": "^0.4.0", "json-schema-to-typescript": "^15.0.4", "prettier": "^3.8.1", - "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", + "vitest": "^4.1.11", "ws": "^8.21.0" }, "engines": { @@ -222,31 +223,106 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "../node_modules/@glideapps/ts-necessities": { - "version": "2.2.3", + "../node_modules/@hpke/chacha20poly1305": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@hpke/chacha20poly1305/-/chacha20poly1305-1.8.0.tgz", + "integrity": "sha512-FcBfAQ+Y99vMNJP2yrZ9wpL8V0GOwp1+zMyzvc6alasrBygfFjFm1yeUtyADJCu/27C3Lm5mJzx6u7pwg+cX5w==", "dev": true, - "license": "MIT" + "license": "MIT", + "dependencies": { + "@hpke/common": "^1.10.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "../node_modules/@hpke/common": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/@hpke/common/-/common-1.10.1.tgz", + "integrity": "sha512-moJwhmtLtuxiUzzNp1jpfBfx8yefKoO9D/RCR9dmwrnc7qjJqId1rEtQz+lSlU5cabX8daToMSx/7HayXOiaFw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16.0.0" + } + }, + "../node_modules/@hpke/core": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@hpke/core/-/core-1.9.0.tgz", + "integrity": "sha512-pFxWl1nNJeQCSUFs7+GAblHvXBCjn9EPN65vdKlYQil2aURaRxfGMO6vBKGqm1YHTKwiAxJQNEI70PbSowMP9Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@hpke/common": "^1.10.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "../node_modules/@hpke/dhkem-x25519": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@hpke/dhkem-x25519/-/dhkem-x25519-1.8.0.tgz", + "integrity": "sha512-S1MWWkAfu+TFxySgv5+2P3O4Mx/jk7BsoplzQaA1s3sfUJVJ2UsZsSzSsMc+FXJumLXncoJFlO6mK6mDGspfmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@hpke/common": "^1.10.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "../node_modules/@hpke/dhkem-x448": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@hpke/dhkem-x448/-/dhkem-x448-1.8.0.tgz", + "integrity": "sha512-mFfnZfgp4OKkUIS/FKikfUgdnDKRy25ytCKBQiV+N+HbYy3I4v4ZCPBQ69QL+TYmKmCZJeUEnYeS5K+OBRP+Eg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@hpke/common": "^1.10.0" + }, + "engines": { + "node": ">=16.0.0" + } }, "../node_modules/@humanfs/core": { - "version": "0.19.1", + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", + "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", "dev": true, "license": "Apache-2.0", + "dependencies": { + "@humanfs/types": "^0.15.0" + }, "engines": { "node": ">=18.18.0" } }, "../node_modules/@humanfs/node": { - "version": "0.16.7", + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz", + "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@humanfs/core": "^0.19.1", + "@humanfs/core": "^0.19.2", + "@humanfs/types": "^0.15.0", "@humanwhocodes/retry": "^0.4.0" }, "engines": { "node": ">=18.18.0" } }, + "../node_modules/@humanfs/types": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz", + "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, "../node_modules/@humanwhocodes/module-importer": { "version": "1.0.1", "dev": true, @@ -283,7 +359,9 @@ } }, "../node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz", + "integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==", "dev": true, "license": "MIT" }, @@ -580,12 +658,22 @@ "url": "https://liberapay.com/Koromix" } }, + "../node_modules/@microsoft/agent-host-protocol-v09": { + "name": "@microsoft/agent-host-protocol", + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@microsoft/agent-host-protocol/-/agent-host-protocol-0.9.0.tgz", + "integrity": "sha512-XY6zOqLSqlNianiWbEpZkzKRB2AKwcDPk9yAbp+afxyxhWzTfpJKy+03T5rIipJDnqf45rXiJ5JV/dzUA8JHLw==", + "dev": true, + "license": "MIT" + }, "../node_modules/@oxc-project/types": { - "version": "0.133.0", + "version": "0.153.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.153.0.tgz", + "integrity": "sha512-jSNrRIyvPVLL5E1z/tS5zLAprosGz/FuI1PWwEOz59j3E4TTTzSpqKb/RDGqJhhW0QHYISGXxq582wGx6T11Tw==", "dev": true, "license": "MIT", "funding": { - "url": "https://github.com/sponsors/Boshen" + "url": "https://github.com/sponsors/oxc-project" } }, "../node_modules/@platformatic/vfs": { @@ -596,8 +684,44 @@ "node": ">= 22" } }, + "../node_modules/@rolldown/binding-android-arm-eabi": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.13.tgz", + "integrity": "sha512-Vu/NICrgdRvUOeaJZIyB2FP63s6fO/4ud5l8dXmxDx9yDyn+mN0y+gsvsVviSli+T361Aqo0h46EuMoobrwawA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "../node_modules/@rolldown/binding-android-arm64": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.13.tgz", + "integrity": "sha512-6LEoIrGy5vvCd2boNnv2soPSUZj/PRvhp8R/t9KlX+mIZxThIE/ZqxrGXxmuNX0FuvQFvLdZr7S4HrYpYBVhEQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, "../node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.0.3", + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.13.tgz", + "integrity": "sha512-CaJSRzlQ6Q+zkDMVA9OkVxBjVoPRCDEE3v0FSJ9kIQBgRjFApA3Si4ijhbNVUji7ZJt+PfO0e46qy4nPInHUPA==", "cpu": [ "arm64" ], @@ -611,18 +735,246 @@ "node": "^20.19.0 || >=22.12.0" } }, + "../node_modules/@rolldown/binding-darwin-x64": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.13.tgz", + "integrity": "sha512-e4r340I5hxSzJkD3YJBxgzq/rJ3wwxwdLLb2FPt1Zpyem20nrQPT11DDEEkazwxaHumH8wpglsp/Ozgj/wT6mw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "../node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.13.tgz", + "integrity": "sha512-hoBWDZ4/K/mJKVMbsgUd1jfpA2DiOrJ54mpegObU9TBHBBrtX4FLIXJJQRDPZ/2Dn91GvPob+Z2hwKJzf4E5SQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "../node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.13.tgz", + "integrity": "sha512-oF9fZu3vTWnTiVAkDFxU9PJLt1nh20K9RRpuHsR7ZnE2L7XookBbDBVLojCVSLGbvtSlxeUNnjAj6DbNsKyzbg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "../node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.13.tgz", + "integrity": "sha512-DTpb/+jMiqWMPgphn4ksW1zW+XLJ6dJJ9FMvnIk9I291LhCctMPhMYJrP5t9p6C9qa3KXadDdUEZHBaPh3kNxQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "../node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.13.tgz", + "integrity": "sha512-xU+Kvv9N2A3SY35zFh1FWWNjxbadE/BKeqnkXe7vScU4GZsw757BlVmDLITZEq8DPpk6m7uoDYkK7P+x/uU5Ow==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "../node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.13.tgz", + "integrity": "sha512-XMk/hnhh25oQ756Lclb1Q11h4ll0xC0Esa5w4Vzp+mAE2S3AGJgeM0SonrIgY8fHhjarb6hcoTuwoFG0VVP2ig==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "../node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.13.tgz", + "integrity": "sha512-U1BoTHYmlywE2n3HUWjzZ+8EQsKr+OwpxYuDWaubx42tUL8QSL7nOqRz/NVHanibbFP3M1Rxuprz/wNcbMKjqw==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "../node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.13.tgz", + "integrity": "sha512-J4cm80Pc6wEwV5AZ42o62sIQgDk+Y7R2s646jIwuvO+4V46QB58N4vVuMPWCBaNgursXLpmUWJV//WQAkPvHBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "../node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.13.tgz", + "integrity": "sha512-W0euZS40U4V6JkmEnVCIX9mcsL6cbXQ4X8c2w+MMOIjFg68TXUW+u3RT+2e1t1pv9mg/EP3pDtK3POXdQ7Rxyw==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "../node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.13.tgz", + "integrity": "sha512-do/AF/4n9MRshia3a5aXoy8xM2c6mgfT6D7H/dY6my5z+hfCIR4ab7mk4IRj++nsEQpKy39kd0DQnDq+r692tw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "../node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.13.tgz", + "integrity": "sha512-6FtUf2cJZobutJdxnGYX8z0vtbgqU0b2v8vfBhkXb9wp2MppMFtkunE+IaOntyi01rkIQoPW2GHABtfzsC0BRQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "../node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.13.tgz", + "integrity": "sha512-UKhFaZTeTcbKrb3iU82m17jVcPEAoqDkCN5MBqgPyZSwp/cQmib21crTS0DdH7T1B7wZRWEI89YQAOJMet5yhg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, "../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/@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": { @@ -632,6 +984,8 @@ }, "../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" }, @@ -658,6 +1012,13 @@ "undici-types": "~7.18.0" } }, + "../node_modules/@types/semver": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.8.0.tgz", + "integrity": "sha512-1mAINjtQCXXeLkJ9ehXkwOcBpqtLxiVtKhpUf83DdRNdQKV0iXZpaHYqRr7nj+wvxuJzoAmAwXI+sCNMv1CzLQ==", + "dev": true, + "license": "MIT" + }, "../node_modules/@types/ws": { "version": "8.18.1", "dev": true, @@ -878,14 +1239,16 @@ } }, "../node_modules/@vitest/expect": { - "version": "4.1.8", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.11.tgz", + "integrity": "sha512-VX2x5vNJXET47KAFzwERI+KRMtTTCSWTfSMKsW7JsUsXV4psq++e3DvZpuTDOpHcxytiDs6p2nhVb2tVDiiUYw==", "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", + "@vitest/spy": "4.1.11", + "@vitest/utils": "4.1.11", "chai": "^6.2.2", "tinyrainbow": "^3.1.0" }, @@ -894,11 +1257,13 @@ } }, "../node_modules/@vitest/mocker": { - "version": "4.1.8", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.11.tgz", + "integrity": "sha512-2XJVD55d1o5AZous5CCGKS74g/riOj9odEt2bQpCVZeblHyHdnMeFl4jl0XjU21stf4mbjUkew2eXQZt65g5CQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "4.1.8", + "@vitest/spy": "4.1.11", "estree-walker": "^3.0.3", "magic-string": "^0.30.21" }, @@ -919,7 +1284,9 @@ } }, "../node_modules/@vitest/pretty-format": { - "version": "4.1.8", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.11.tgz", + "integrity": "sha512-yiZzPbGTS9Sr/JpFl8zHrcIkAofNbFV6k21vIgQN/cY/oxZeXhJv5sc/MBJ5jFKWmWs+oJHw0UXLZjmf931+Vw==", "dev": true, "license": "MIT", "dependencies": { @@ -930,11 +1297,13 @@ } }, "../node_modules/@vitest/runner": { - "version": "4.1.8", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.11.tgz", + "integrity": "sha512-LztvUgdwMNJMIkj3hQnnxiC2Xy1zNxq928W/xhjCLaNCzqTZOudjwbQf6v9IntZGPw132i2Lq2rgTRZHD3JHNw==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "4.1.8", + "@vitest/utils": "4.1.11", "pathe": "^2.0.3" }, "funding": { @@ -942,12 +1311,14 @@ } }, "../node_modules/@vitest/snapshot": { - "version": "4.1.8", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.11.tgz", + "integrity": "sha512-pN7ikn1ON7h8ee4gIAp4AzyK+zBtJPzVbqOgu5LCEh4VaJVbPQcgYQYJIMGQPXVeJJq1fnfazis7a5pFNPahog==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "4.1.8", - "@vitest/utils": "4.1.8", + "@vitest/pretty-format": "4.1.11", + "@vitest/utils": "4.1.11", "magic-string": "^0.30.21", "pathe": "^2.0.3" }, @@ -956,7 +1327,9 @@ } }, "../node_modules/@vitest/spy": { - "version": "4.1.8", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.11.tgz", + "integrity": "sha512-apNa/prQy2qCeywhnixOHPRCgGNhvg7T4Dapfl1GahLp/R+uhBm5cPyFoNVyqsNd2h1nJxL6BqqdIjiABL60YA==", "dev": true, "license": "MIT", "funding": { @@ -964,11 +1337,13 @@ } }, "../node_modules/@vitest/utils": { - "version": "4.1.8", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.11.tgz", + "integrity": "sha512-zTCVGpyFsGWBhllOyKlTw/vnr6D9qxsfSDyfbyZmTyjHw5N/VuvzHpHoQjm2ZJzn4RJgx5w4r7V0er69CmLgPQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "4.1.8", + "@vitest/pretty-format": "4.1.11", "convert-source-map": "^2.0.0", "tinyrainbow": "^3.1.0" }, @@ -976,17 +1351,6 @@ "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, @@ -1028,6 +1392,8 @@ }, "../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": { @@ -1039,27 +1405,10 @@ "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", + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz", + "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", "dev": true, "license": "MIT", "dependencies": { @@ -1067,34 +1416,6 @@ "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, @@ -1104,7 +1425,9 @@ } }, "../node_modules/chai": { - "version": "6.2.2", + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.3.0.tgz", + "integrity": "sha512-XWAtwJ6OHO+tj0EKCs0Y2UamnyOxseZWltU4x2U2wh8g4AigdjwvtUjvLP2tqkA/avxHEtzxNaqGq/YGNwckKg==", "dev": true, "license": "MIT", "engines": { @@ -1148,11 +1471,6 @@ "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, @@ -1176,17 +1494,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/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, @@ -1223,6 +1535,8 @@ }, "../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": { @@ -1458,6 +1772,8 @@ }, "../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": { @@ -1472,22 +1788,6 @@ "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, @@ -1628,24 +1928,22 @@ "node": ">=8" } }, - "../node_modules/ieee754": { - "version": "1.2.1", + "../node_modules/hpke-js": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/hpke-js/-/hpke-js-1.8.0.tgz", + "integrity": "sha512-N0PFQlUQsIPS9++nUNn2ZsxTPSv8pONyyrXIGZl0iiherRfS0XW1SvTd+RmepD0TN1S9zzTJkEutMIWWYt0/4w==", "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" + "license": "MIT", + "dependencies": { + "@hpke/chacha20poly1305": "^1.8.0", + "@hpke/common": "^1.10.0", + "@hpke/core": "^1.9.0", + "@hpke/dhkem-x25519": "^1.8.0", + "@hpke/dhkem-x448": "^1.8.0" + }, + "engines": { + "node": ">=16.0.0" + } }, "../node_modules/ignore": { "version": "7.0.5", @@ -1697,21 +1995,11 @@ "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.3.2", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.2.tgz", @@ -1764,107 +2052,333 @@ "json2ts": "dist/src/cli.js" }, "engines": { - "node": ">=16.0.0" + "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.2.1", + "resolved": "https://registry.npmjs.org/koffi/-/koffi-3.2.1.tgz", + "integrity": "sha512-0qE3lZ8jllRqPN4Ob6Ajl7c2bJSJDhQWuKLGP5hIEpHLllJWv1ydHFMhHmHc5p/W9GticKVDbYzZd7TBoQ4CZg==", + "hasInstallScript": true, + "license": "MIT", + "funding": { + "url": "https://liberapay.com/Koromix" + }, + "optionalDependencies": { + "@koromix/koffi-android-arm64": "3.2.1", + "@koromix/koffi-android-x64": "3.2.1", + "@koromix/koffi-darwin-arm64": "3.2.1", + "@koromix/koffi-darwin-x64": "3.2.1", + "@koromix/koffi-freebsd-arm64": "3.2.1", + "@koromix/koffi-freebsd-ia32": "3.2.1", + "@koromix/koffi-freebsd-x64": "3.2.1", + "@koromix/koffi-linux-arm": "3.2.1", + "@koromix/koffi-linux-arm64": "3.2.1", + "@koromix/koffi-linux-ia32": "3.2.1", + "@koromix/koffi-linux-loong64": "3.2.1", + "@koromix/koffi-linux-riscv64": "3.2.1", + "@koromix/koffi-linux-x64": "3.2.1", + "@koromix/koffi-openbsd-ia32": "3.2.1", + "@koromix/koffi-openbsd-x64": "3.2.1", + "@koromix/koffi-win32-arm64": "3.2.1", + "@koromix/koffi-win32-ia32": "3.2.1", + "@koromix/koffi-win32-x64": "3.2.1" + } + }, + "../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.33.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz", + "integrity": "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==", + "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.33.0", + "lightningcss-darwin-arm64": "1.33.0", + "lightningcss-darwin-x64": "1.33.0", + "lightningcss-freebsd-x64": "1.33.0", + "lightningcss-linux-arm-gnueabihf": "1.33.0", + "lightningcss-linux-arm64-gnu": "1.33.0", + "lightningcss-linux-arm64-musl": "1.33.0", + "lightningcss-linux-x64-gnu": "1.33.0", + "lightningcss-linux-x64-musl": "1.33.0", + "lightningcss-win32-arm64-msvc": "1.33.0", + "lightningcss-win32-x64-msvc": "1.33.0" + } + }, + "../node_modules/lightningcss-android-arm64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.33.0.tgz", + "integrity": "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "../node_modules/lightningcss-darwin-arm64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.33.0.tgz", + "integrity": "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==", + "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/lightningcss-darwin-x64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.33.0.tgz", + "integrity": "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==", + "cpu": [ + "x64" + ], + "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/lightningcss-freebsd-x64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.33.0.tgz", + "integrity": "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "../node_modules/json-schema-traverse": { - "version": "0.4.1", + "../node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.33.0.tgz", + "integrity": "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==", + "cpu": [ + "arm" + ], "dev": true, - "license": "MIT" + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } }, - "../node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", + "../node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.33.0.tgz", + "integrity": "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "MIT" + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } }, - "../node_modules/keyv": { - "version": "4.5.4", + "../node_modules/lightningcss-linux-arm64-musl": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.33.0.tgz", + "integrity": "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "MIT", - "dependencies": { - "json-buffer": "3.0.1" + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "../node_modules/koffi": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/koffi/-/koffi-3.2.1.tgz", - "integrity": "sha512-0qE3lZ8jllRqPN4Ob6Ajl7c2bJSJDhQWuKLGP5hIEpHLllJWv1ydHFMhHmHc5p/W9GticKVDbYzZd7TBoQ4CZg==", - "hasInstallScript": true, - "license": "MIT", - "funding": { - "url": "https://liberapay.com/Koromix" + "../node_modules/lightningcss-linux-x64-gnu": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.33.0.tgz", + "integrity": "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" }, - "optionalDependencies": { - "@koromix/koffi-android-arm64": "3.2.1", - "@koromix/koffi-android-x64": "3.2.1", - "@koromix/koffi-darwin-arm64": "3.2.1", - "@koromix/koffi-darwin-x64": "3.2.1", - "@koromix/koffi-freebsd-arm64": "3.2.1", - "@koromix/koffi-freebsd-ia32": "3.2.1", - "@koromix/koffi-freebsd-x64": "3.2.1", - "@koromix/koffi-linux-arm": "3.2.1", - "@koromix/koffi-linux-arm64": "3.2.1", - "@koromix/koffi-linux-ia32": "3.2.1", - "@koromix/koffi-linux-loong64": "3.2.1", - "@koromix/koffi-linux-riscv64": "3.2.1", - "@koromix/koffi-linux-x64": "3.2.1", - "@koromix/koffi-openbsd-ia32": "3.2.1", - "@koromix/koffi-openbsd-x64": "3.2.1", - "@koromix/koffi-win32-arm64": "3.2.1", - "@koromix/koffi-win32-ia32": "3.2.1", - "@koromix/koffi-win32-x64": "3.2.1" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "../node_modules/levn": { - "version": "0.4.1", + "../node_modules/lightningcss-linux-x64-musl": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.33.0.tgz", + "integrity": "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==", + "cpu": [ + "x64" + ], "dev": true, - "license": "MIT", - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">= 0.8.0" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "../node_modules/lightningcss": { - "version": "1.32.0", + "../node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.33.0.tgz", + "integrity": "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MPL-2.0", - "dependencies": { - "detect-libc": "^2.0.3" - }, + "optional": true, + "os": [ + "win32" + ], "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", + "../node_modules/lightningcss-win32-x64-msvc": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.33.0.tgz", + "integrity": "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==", "cpu": [ - "arm64" + "x64" ], "dev": true, "license": "MPL-2.0", "optional": true, "os": [ - "darwin" + "win32" ], "engines": { "node": ">= 12.0.0" @@ -1908,6 +2422,8 @@ }, "../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": { @@ -1937,7 +2453,9 @@ } }, "../node_modules/minimatch/node_modules/brace-expansion": { - "version": "5.0.8", + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.12.tgz", + "integrity": "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1980,7 +2498,9 @@ "license": "MIT" }, "../node_modules/nanoid": { - "version": "3.3.17", + "version": "3.3.20", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.20.tgz", + "integrity": "sha512-uKdg2G3GNCKQn9byYOpxbGqrT2fGO5KRt5J/8b3pok8rT6qxGWF6hxMyJiEYtAf+FVyYuD9hRaDqX5uPFYJ4ZQ==", "dev": true, "funding": [ { @@ -2001,25 +2521,6 @@ "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, @@ -2078,11 +2579,6 @@ "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, @@ -2127,16 +2623,22 @@ }, "../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", + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", "dev": true, "license": "MIT", "engines": { @@ -2146,16 +2648,10 @@ "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", + "version": "8.5.29", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.29.tgz", + "integrity": "sha512-49cGhUbXj8Qenv0iTMxA1cFBzxXoctpC9Ujd77t1WcbJIr6nF/eI7g/8MgxrYldFRuAXvja7xQRwavoW7kgrxQ==", "dev": true, "funding": [ { @@ -2173,9 +2669,9 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.16", + "nanoid": "^3.3.19", "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" + "source-map-js": "^1.2.2" }, "engines": { "node": "^10 || ^12 || >=14" @@ -2203,14 +2699,6 @@ "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, @@ -2219,42 +2707,6 @@ "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, @@ -2282,11 +2734,13 @@ } }, "../node_modules/rolldown": { - "version": "1.0.3", + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.13.tgz", + "integrity": "sha512-/5apOkwWxtACcQtZ5WlrAsKS4JwXVThQPJZ3RqIruRY9UG9M8e0VlXN068xum/JRKA4aIUoJ13ghe/kGCvidiQ==", "dev": true, "license": "MIT", "dependencies": { - "@oxc-project/types": "=0.133.0", + "@oxc-project/types": "=0.153.0", "@rolldown/pluginutils": "^1.0.0" }, "bin": { @@ -2296,41 +2750,22 @@ "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" + "@rolldown/binding-android-arm-eabi": "1.2.13", + "@rolldown/binding-android-arm64": "1.2.13", + "@rolldown/binding-darwin-arm64": "1.2.13", + "@rolldown/binding-darwin-x64": "1.2.13", + "@rolldown/binding-freebsd-x64": "1.2.13", + "@rolldown/binding-linux-arm-gnueabihf": "1.2.13", + "@rolldown/binding-linux-arm64-gnu": "1.2.13", + "@rolldown/binding-linux-arm64-musl": "1.2.13", + "@rolldown/binding-linux-ppc64-gnu": "1.2.13", + "@rolldown/binding-linux-s390x-gnu": "1.2.13", + "@rolldown/binding-linux-x64-gnu": "1.2.13", + "@rolldown/binding-linux-x64-musl": "1.2.13", + "@rolldown/binding-openharmony-arm64": "1.2.13", + "@rolldown/binding-win32-arm64-msvc": "1.2.13", + "@rolldown/binding-win32-x64-msvc": "1.2.13" + } }, "../node_modules/semver": { "version": "7.7.3", @@ -2368,7 +2803,9 @@ "license": "ISC" }, "../node_modules/source-map-js": { - "version": "1.2.1", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.2.tgz", + "integrity": "sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==", "dev": true, "license": "BSD-3-Clause", "engines": { @@ -2385,14 +2822,6 @@ "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, @@ -2430,11 +2859,6 @@ "node": ">=18" } }, - "../node_modules/tiny-inflate": { - "version": "1.0.3", - "dev": true, - "license": "MIT" - }, "../node_modules/tinybench": { "version": "2.9.0", "dev": true, @@ -2464,18 +2888,15 @@ } }, "../node_modules/tinyrainbow": { - "version": "3.1.0", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.2.0.tgz", + "integrity": "sha512-LgO3D9yZJjApUiuUfl9iFAwrtaX4+lok3wJIqttGoKCHlWUqHqbQpnxCf82L8FjgKsh4iGo78hqJwgL8F6To2A==", "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, @@ -2532,29 +2953,6 @@ "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, @@ -2563,20 +2961,17 @@ "punycode": "^2.1.0" } }, - "../node_modules/urijs": { - "version": "1.19.11", - "dev": true, - "license": "MIT" - }, "../node_modules/vite": { - "version": "8.0.16", + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.3.4.tgz", + "integrity": "sha512-IftLE8phJhdNRIGNEV4JCCvfNScoZQVfcXaN8TQropWGJtOHcu+iAB9pHcuGvmEb/kddw0LBe/jS8ztdC6xdiQ==", "dev": true, "license": "MIT", "dependencies": { - "lightningcss": "^1.32.0", - "picomatch": "^4.0.4", - "postcss": "^8.5.15", - "rolldown": "1.0.3", + "lightningcss": "^1.33.0", + "picomatch": "^4.0.7", + "postcss": "^8.5.29", + "rolldown": "~1.2.12", "tinyglobby": "^0.2.17" }, "bin": { @@ -2593,7 +2988,7 @@ }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", - "@vitejs/devtools": "^0.1.18", + "@vitejs/devtools": "^0.7.1", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", @@ -2645,17 +3040,19 @@ } }, "../node_modules/vitest": { - "version": "4.1.8", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.11.tgz", + "integrity": "sha512-fhACrNXUidIbGSBr5FlbuBkO7VWC1ZyLl0DO4CU2DrQoAPxX84Ysxs+HeGQpii5lZWV1Q4gBZTTu49mF+A6Edw==", "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", + "@vitest/expect": "4.1.11", + "@vitest/mocker": "4.1.11", + "@vitest/pretty-format": "4.1.11", + "@vitest/runner": "4.1.11", + "@vitest/snapshot": "4.1.11", + "@vitest/spy": "4.1.11", + "@vitest/utils": "4.1.11", "es-module-lexer": "^2.0.0", "expect-type": "^1.3.0", "magic-string": "^0.30.21", @@ -2683,12 +3080,12 @@ "@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", + "@vitest/browser-playwright": "4.1.11", + "@vitest/browser-preview": "4.1.11", + "@vitest/browser-webdriverio": "4.1.11", + "@vitest/coverage-istanbul": "4.1.11", + "@vitest/coverage-v8": "4.1.11", + "@vitest/ui": "4.1.11", "happy-dom": "*", "jsdom": "*", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" @@ -2739,20 +3136,6 @@ "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, @@ -2790,11 +3173,6 @@ "node": ">=0.10.0" } }, - "../node_modules/wordwrap": { - "version": "1.0.0", - "dev": true, - "license": "MIT" - }, "../node_modules/ws": { "version": "8.21.0", "dev": true, @@ -2823,20 +3201,6 @@ "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, diff --git a/nodejs/src/cliVersion.ts b/nodejs/src/cliVersion.ts index ff5a2eeb37..9cb702ca48 100644 --- a/nodejs/src/cliVersion.ts +++ b/nodejs/src/cliVersion.ts @@ -1,3 +1,3 @@ -export const COPILOT_CLI_VERSION = "1.0.95-0"; +export const COPILOT_CLI_VERSION = "1.0.98-2"; export const COPILOT_CLI_USE_NPM_PACKAGE = false; diff --git a/nodejs/src/client.ts b/nodejs/src/client.ts index 2f00114da9..d728605e48 100644 --- a/nodejs/src/client.ts +++ b/nodejs/src/client.ts @@ -1790,6 +1790,7 @@ export class CopilotClient { model: config.model, allowedModels: config.allowedModels, sessionId: localSessionId, + name: config.name, clientName: config.clientName, reasoningEffort: config.reasoningEffort, reasoningSummary: config.reasoningSummary, @@ -1806,6 +1807,7 @@ export class CopilotClient { isTerminal: tool.isTerminal, })), toolSearch: config.toolSearch, + imageGeneration: config.imageGeneration, canvases: config.canvases?.map((canvas) => canvas.declaration), requestCanvasRenderer: config.requestCanvasRenderer, requestExtensions: config.requestExtensions, @@ -1868,9 +1870,12 @@ export class CopilotClient { enableFileHooks: config.enableFileHooks, enableHostGitOperations: config.enableHostGitOperations, enableSessionStore: config.enableSessionStore, + enableGitHubCacheStore: config.enableGitHubCacheStore, enableSkills: config.enableSkills, skillDirectories: config.skillDirectories, ...(config.skillProvider ? { hasSkillProvider: true } : {}), + agentDirectories: config.agentDirectories, + hookDirectories: config.hookDirectories, pluginDirectories: config.pluginDirectories, instructionDirectories: config.instructionDirectories, disabledSkills: config.disabledSkills, @@ -1885,6 +1890,7 @@ export class CopilotClient { expAssignments: config.expAssignments, enableManagedSettings: config.enableManagedSettings, enforceManagedModelDefaults: config.enforceManagedModelDefaults, + interactiveUser: config.interactiveUser, managedSettings: config.managedSettings, }); @@ -2119,6 +2125,7 @@ export class CopilotClient { isTerminal: tool.isTerminal, })), toolSearch: config.toolSearch, + imageGeneration: config.imageGeneration, canvases: config.canvases?.map((canvas) => canvas.declaration), workflows: workflows?.map((workflow) => workflow.meta), requestCanvasRenderer: config.requestCanvasRenderer, @@ -2159,6 +2166,7 @@ export class CopilotClient { enableFileHooks: config.enableFileHooks, enableHostGitOperations: config.enableHostGitOperations, enableSessionStore: config.enableSessionStore, + enableGitHubCacheStore: config.enableGitHubCacheStore, enableSkills: config.enableSkills, streaming: config.streaming, includeSubAgentStreamingEvents: config.includeSubAgentStreamingEvents ?? true, @@ -2176,6 +2184,8 @@ export class CopilotClient { agent: config.agent, skillDirectories: config.skillDirectories, ...(config.skillProvider ? { hasSkillProvider: true } : {}), + agentDirectories: config.agentDirectories, + hookDirectories: config.hookDirectories, pluginDirectories: config.pluginDirectories, instructionDirectories: config.instructionDirectories, disabledSkills: config.disabledSkills, @@ -2192,6 +2202,7 @@ export class CopilotClient { expAssignments: config.expAssignments, enableManagedSettings: config.enableManagedSettings, enforceManagedModelDefaults: config.enforceManagedModelDefaults, + interactiveUser: config.interactiveUser, managedSettings: config.managedSettings, ...(extensionOptions?.requestedEnvironmentVariables ? { @@ -3253,6 +3264,8 @@ export class CopilotClient { /** Starts the in-process FFI runtime with SDK-managed typed options. */ private async startInProcessFfi(): Promise { const explicitEntrypoint = this.resolvedEnv.COPILOT_CLI_PATH; + const cliLoginSyncIntegrationId = + this.resolvedEnv.GITHUB_COPILOT_INTEGRATION_ID?.trim() || undefined; let runtimeLibrary: string; if (explicitEntrypoint) { const entrypointDirectory = dirname(resolve(explicitEntrypoint)); @@ -3266,7 +3279,8 @@ export class CopilotClient { "runtime.node" ); } else { - runtimeLibrary = join(dirname(await getBundledRuntimePath()), "runtime.node"); + const bundledRuntimePath = await getBundledRuntimePath(); + runtimeLibrary = join(dirname(bundledRuntimePath), "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; @@ -3301,6 +3315,9 @@ export class CopilotClient { if (this.options.enableRemoteSessions) { args.push("--remote"); } + if (cliLoginSyncIntegrationId) { + args.push(`--cli-login-sync-integration-id=${cliLoginSyncIntegrationId}`); + } const host = FfiRuntimeHost.create(runtimeLibrary, explicitEntrypoint, environment, args); this.ffiHost = host; diff --git a/nodejs/src/extension.ts b/nodejs/src/extension.ts index 99a1c0a3be..f6adc32745 100644 --- a/nodejs/src/extension.ts +++ b/nodejs/src/extension.ts @@ -24,7 +24,7 @@ export { export type JoinSessionConfig = Omit< ResumeSessionConfig, - "onPermissionRequest" | "extensionSdkPath" | "skillProvider" + "onPermissionRequest" | "extensionSdkPath" | "skillProvider" | "imageGeneration" > & { onPermissionRequest?: PermissionHandler; /** @@ -130,15 +130,19 @@ export async function joinSession(config: JoinSessionConfig = {}): Promise` digest of the canonical descriptor. + */ + descriptorDigest: string; + /** + * Expected `sha256:` digest of the descriptor's bundle manifest. + */ + bundleDigest: string; +} /** * The protocol version and capability set the runtime actually honoured for a successful catalog operation. * @@ -10467,6 +11162,32 @@ export interface ContentExclusionPathCheck { */ excluded: boolean; } +/** + * Candidate content-exclusion policies to validate, as the raw JSON text a host received. The text is supplied unparsed so that a syntax error and a schema violation are both reported by the runtime's own validator rather than by the caller's JSON parser. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "ContentExclusionValidatePoliciesRequest". + */ +/** @experimental */ +export interface ContentExclusionValidatePoliciesRequest { + /** + * Unparsed JSON text expected to hold an array of content-exclusion policies. + */ + policiesJson: string; +} +/** + * The accepted policies, parsed and normalized. A rejected input is reported as an error rather than in this result. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "ContentExclusionValidatePoliciesResult". + */ +/** @experimental */ +export interface ContentExclusionValidatePoliciesResult { + /** + * Validated policy objects in input order. Raw JSON preserves every field returned by the runtime's policy parser. + */ + policies: JsonValue[]; +} /** * A single large message currently in context. * @@ -11969,6 +12690,161 @@ export interface ExternalToolTextResultForLlmContentResource { type: "resource"; resource: ExternalToolTextResultForLlmContentResourceDetails; } +/** + * The declared shape of one feature flag, independent of any caller population. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "FeatureFlagMetadata". + */ +/** @experimental */ +/** @internal */ +export interface FeatureFlagMetadata { + /** + * The flag's name, matching the key it is listed under. + */ + name: string; + availability: FeatureFlagAvailability; + /** + * Whether the flag participates in the CAPI sanity suite. + */ + capiSanity: boolean; + /** + * Text shown to a user choosing whether to turn the flag on in experimental mode. Absent when the flag is not user-facing. + */ + experimentalDescription?: string; + /** + * The online-experiment (ExP) flag this maps to, when the flag is assigned by an experiment rather than declared statically. + */ + expFlag?: string; + /** + * Environment variable that overrides this flag's experiment assignment locally in the experiment service, not in `featureFlags.resolve`. Present exactly when `expFlag` is, and derived from it, so a caller reads a local override without reproducing the naming rule. + */ + expFlagEnvVar?: string; +} +/** + * Every feature flag this runtime declares, with the shape each one was declared with. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "FeatureFlagsMetadataResult". + */ +/** @experimental */ +/** @internal */ +export interface FeatureFlagsMetadataResult { + /** + * Declared feature flags keyed by flag name. + */ + flags: { + [k: string]: FeatureFlagMetadata; + }; +} +/** + * Caller population and overrides for static feature-flag resolution, independent of experiment assignments and active runtime state. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "FeatureFlagsResolveRequest". + */ +/** @experimental */ +/** @internal */ +export interface FeatureFlagsResolveRequest { + /** + * Whether the caller is GitHub or Microsoft staff. + */ + isStaff: boolean; + /** + * Whether the caller is in an experimental population. + */ + isExperimental: boolean; + /** + * Whether the caller belongs to the team population used by feature availability. + */ + isTeam: boolean; + /** + * GitHub-staff membership. Omit when it was never recorded: a legacy staff account then counts as both staff populations and keeps every staff feature. + */ + isStaffGithub?: boolean; + /** + * Microsoft-staff membership. Omitted the same way as `isStaffGitHub`. + */ + isStaffMicrosoft?: boolean; + /** + * Resolve against this already-computed flag set rather than each flag's declared availability. Keyed by flag name. + */ + baseFlags?: { + [k: string]: boolean | undefined; + }; + /** + * Environment for direct feature-flag overrides and `COPILOT_CLI_ENABLED_FEATURE_FLAGS`. Does not apply `COPILOT_EXP_*` experiment assignments. Defaults to this runtime process's own environment. + */ + environment?: { + [k: string]: string | undefined; + }; + /** + * User settings to apply, including `enabledFeatureFlags`, legacy `feature_flags.enabled`, and supported convenience settings. + */ + config?: JsonValue; + /** + * Flags forced to a value after every other layer, keyed by flag name. + */ + flagOverrides?: { + [k: string]: boolean | undefined; + }; +} +/** + * Caller population and startup inputs to resolve named online-experiment gates for. Carries no account: the runtime resolves the assignment for the authentication it already holds. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "FeatureFlagsResolveStartupGatesRequest". + */ +/** @experimental */ +/** @internal */ +export interface FeatureFlagsResolveStartupGatesRequest { + /** + * The feature flags to resolve. A name this runtime does not declare is reported as an error rather than as a disabled flag. + */ + flags: string[]; + /** + * Whether the caller is GitHub or Microsoft staff. + */ + isStaff: boolean; + /** + * Whether the caller is in an experimental population. + */ + isExperimental: boolean; + /** + * Whether the caller belongs to a team plan. + */ + isTeam: boolean; + /** + * GitHub-staff membership. Omit when it was never recorded: a legacy staff account then counts as both staff populations and keeps every staff feature. + */ + isStaffGithub?: boolean; + /** + * Microsoft-staff membership. Omitted the same way as `isStaffGitHub`. + */ + isStaffMicrosoft?: boolean; + /** + * Environment to read flag overrides from. Defaults to this runtime process's own environment. + */ + environment?: { + [k: string]: string | undefined; + }; + /** + * User settings document whose `featureFlags` block is layered on top of the resolved set. + */ + config?: JsonValue; + /** + * Whether streamer mode is on, which withholds the populations an experiment would otherwise segment on. + */ + streamerMode: boolean; + /** + * When this installation first launched, in milliseconds since the Unix epoch. Experiments that segment on installation age read it. Omit when the first launch was never recorded. + */ + firstLaunchAtMillis?: number; + /** + * Whether the runtime may refresh the cached assignment over the network without an active session. The refresh never blocks this call: the answer comes from the cache either way, so a refresh only affects a later launch. + */ + refreshWithoutSession: boolean; +} /** * Parameters for starting fleet orchestration: an optional user prompt combined with the fleet instructions, plus the send options forwarded to the resulting turn. * @@ -12048,6 +12924,62 @@ export interface FolderTrustCheckResult { */ trusted: boolean; } +/** + * A folder to check against a persisted trusted-folders list, and the configuration directory holding that list. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "FolderTrustIsTrustedParams". + */ +/** @experimental */ +/** @internal */ +export interface FolderTrustIsTrustedParams { + /** + * Folder to check. A folder counts as trusted when it is itself listed, when an ancestor is listed, or when its git root is listed. + */ + path: string; + /** + * Configuration directory to read the trusted-folders list from. Omit it to read the default location; supply it when the host has redirected its configuration, or the check answers from a list the host is not using. + */ + configDir?: string; +} +/** + * The verdict of the `forceLoginOrgs` enterprise sign-in pin for one credential. A managed device may pin Copilot to an approved set of GitHub organizations so a developer cannot sign in with a personal — or another enterprise's — account and escape every control that keys off the signed-in identity. A host must refuse to proceed when `allowed` is false; the remaining fields explain the refusal rather than qualifying it. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "ForceLoginOrgsDecision". + */ +/** @experimental */ +/** @internal */ +export interface ForceLoginOrgsDecision { + /** + * Whether the client may proceed. True whenever no pin is in force, so an unmanaged host is never blocked. + */ + allowed: boolean; + /** + * Whether a pin applies at all. Distinguishes "allowed because unmanaged" from "allowed because the account is approved", which a diagnostics surface needs and which also decides whether a bring-your-own-key provider may serve inference. + */ + enforced: boolean; + /** + * The approved organization logins, as configured. Enterprise configuration rather than user data, so it is safe to name in a message or a log line. + */ + requiredOrgs: string[]; + /** + * The approved organization the account matched, when allowed under a pin. + */ + matchedOrg?: string; + /** + * A stable kebab-case reason code when refused: `org-not-approved`, `unverifiable-credential`, `policy-undetermined`, `policy-invalid`, `not-signed-in`, or `membership-unknown`. Omitted when allowed. Treat an unrecognized code as a refusal rather than mapping it to an allow: a code this host has not been taught about still denied the credential. + */ + denial?: string; + /** + * A developer-facing explanation of the refusal. Omitted when allowed. + */ + message?: string; + /** + * Error-level detail from managed-policy discovery — which file failed to load, which field failed validation. Carried on the decision rather than left to the log because a refusal can exit the process before a log sink exists, which would discard the only actionable diagnostic a misconfigured managed device produces. + */ + policyDiagnostics: string[]; +} /** * The remote the checked-out branch tracks. * @@ -12524,10 +13456,6 @@ export interface GlobalStateLoadResult { * Folders the user has marked as trusted. */ trustedFolders?: string[]; - /** - * Whether the user has answered the prompt suggesting they install the desktop app. - */ - appInstallNudgeResponded?: boolean; /** * When the Auto-feedback hint was last shown, as an ISO 8601 timestamp. It enforces the once-per-day cap for non-staff users across restarts. */ @@ -13093,6 +14021,10 @@ export interface HookInvokeResponse { */ /** @experimental */ export interface HooksDiscoverRequest { + /** + * Explicit discovery-only file-hook roots, separate from callback hooks and filesystem grants. Available without ambient discovery or repository file-hook enablement, subject to managed/hook disables. Relative paths resolve against the request's effective working directory when supplied. Null and omission are equivalent; an empty array contributes no explicit roots. + */ + hookDirectories?: string[]; /** * 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. */ @@ -14177,6 +15109,40 @@ export interface InstructionDiscoveryPathList { */ paths: InstructionDiscoveryPath[]; } +/** + * Explicit discovery arguments for one location, supplied by the caller rather than derived from a session. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "InstructionsDiscoverRepoSourcesRequest". + */ +/** @experimental */ +/** @internal */ +export interface InstructionsDiscoverRepoSourcesRequest { + /** + * Repository location to discover. An empty string selects no repository scope; cwdPath can still supply working-directory sources. + */ + location: string; + /** + * When true, include the sources a command-line host reads. Hosts that are not the CLI omit it. + */ + isCli?: boolean; + /** + * Working directory whose instruction sources should also be included. Omission adds no separate working-directory scope. + */ + cwdPath?: string; + /** + * Copilot configuration directory for user-level sources. An omitted or empty value uses the server's configuration home. + */ + configDir?: string; + /** + * Extra directories to scan alongside instruction directories configured in the runtime environment. + */ + additionalDirs?: string[]; + /** + * When true, also discover instruction files nested below the location. + */ + enableChildInstructions?: boolean; +} /** * Optional project paths to include in instruction discovery. * @@ -14185,6 +15151,10 @@ export interface InstructionDiscoveryPathList { */ /** @experimental */ export interface InstructionsDiscoverRequest { + /** + * Explicit discovery-only instruction roots using existing SDK instruction formats, not filesystem grants. Available without ambient discovery, subject to instruction/managed disables. Relative paths resolve against the request's effective working directory when supplied. Null and omission are equivalent; an empty array contributes no explicit roots. + */ + instructionDirectories?: string[]; /** * 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). */ @@ -14202,6 +15172,10 @@ export interface InstructionsDiscoverRequest { */ /** @experimental */ export interface InstructionsGetDiscoveryPathsRequest { + /** + * Explicit discovery-only instruction roots to enumerate, including configured missing directories. Relative paths resolve against the request's effective working directory when supplied. These are not filesystem grants or preferred creation targets. Null and omission are equivalent. + */ + instructionDirectories?: string[]; /** * Optional list of project directory paths. When omitted or empty, only the user-level targets are returned. */ @@ -14267,6 +15241,20 @@ export interface InstructionSource { */ projectPath?: string; } +/** + * The prompt the init command runs. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "InstructionsInitPromptResult". + */ +/** @experimental */ +/** @internal */ +export interface InstructionsInitPromptResult { + /** + * Prompt text authoring repository instructions. + */ + prompt: string; +} /** * Parameters for interrupting the main agent turn. * @@ -14600,6 +15588,20 @@ export interface SessionContext { */ branch?: string; } +/** + * Which stored location permissions were dropped because their directory is gone. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "LocationPermissionsCleanupResult". + */ +/** @experimental */ +/** @internal */ +export interface LocationPermissionsCleanupResult { + /** + * Location keys whose grants were removed. Empty when every stored location still exists. + */ + removedLocationKeys: string[]; +} /** * Message text, optional severity level, persistence flag, optional follow-up URL, and optional tip. * @@ -14693,6 +15695,61 @@ export interface ManagedMcpServerConfig { */ headersRefreshTtlMs?: number; } + +/** @experimental */ +export interface ManagedPermissionEvaluation { + operation: ManagedPermissionOperation; + verdict: ManagedPermissionVerdict; +} +/** + * Managed policy context and ordered operations for pure, sessionless evaluation. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "ManagedPermissionsEvaluateRequest". + */ +/** @experimental */ +export interface ManagedPermissionsEvaluateRequest { + context: ManagedPermissionsContext; + /** + * Operations to evaluate, preserving input order and duplicates. An empty batch is valid. + */ + operations: ManagedPermissionOperation[]; +} +/** + * Ordered managed permission verdicts and the supplied policy's fail-closed posture. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "ManagedPermissionsEvaluateResult". + */ +/** @experimental */ +export interface ManagedPermissionsEvaluateResult { + /** + * One verdict per input operation, in the same order, including duplicates. + */ + results: ManagedPermissionEvaluation[]; + /** + * Managed permission policy could not be determined; every operation is denied. + */ + failClosed: boolean; +} +/** + * Retry outcome for one plugin required by managed settings. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "ManagedPluginRetryEntry". + */ +/** @experimental */ +export interface ManagedPluginRetryEntry { + /** + * Plugin spec in "plugin@marketplace" form. + */ + spec: string; + status: ManagedPluginRetryStatus; + /** + * Why the plugin was not prepared. Present when `status` is `failed` or `deferred`. + */ + error?: string; +} /** * Lock state and provenance of one managed setting. * @@ -14714,6 +15771,38 @@ export interface ManagedSettingMeta { */ requested?: string; } +/** + * A user settings document and the effective managed policy whose controlled entries should be stamped onto it. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "ManagedSettingsApplyControlsParams". + */ +/** @experimental */ +/** @internal */ +export interface ManagedSettingsApplyControlsParams { + /** + * The user settings document to stamp. Entries the managed layer does not name are returned exactly as supplied. + */ + settings: JsonValue; + /** + * The effective managed policy, as composed by `managedSettings.resolveEffectiveValue`. Omit it on an unmanaged host: the settings then come back unchanged. + */ + managed?: JsonValue; +} +/** + * The user settings with the managed layer's controlled entries stamped on. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "ManagedSettingsApplyControlsResult". + */ +/** @experimental */ +/** @internal */ +export interface ManagedSettingsApplyControlsResult { + /** + * The resulting settings document. + */ + settingsJson: JsonValue; +} /** * One candidate channel; absent settings represents a channel that delivered no document. * @@ -14828,6 +15917,308 @@ export interface ManagedSettingsDiagnostic { */ message: string; } +/** + * Inputs describing the host and account an ingestion should resolve managed policy for. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "ManagedSettingsIngestParams". + */ +/** @experimental */ +/** @internal */ +export interface ManagedSettingsIngestParams { + policyHelperReason?: ManagedSettingsPolicyHelperReason; + /** + * Whether to run ingestion. Omit or set false to return an empty result without discovering or fetching policy. + */ + selfFetch?: boolean; + /** + * The authenticated identity to resolve policy for. Omitted before sign-in, in which case no server layer is fetched. + */ + authInfo?: JsonValue; + /** + * The session whose credential store and working directory the policy helper should use. Omit it when no session exists yet and the process-wide store applies. + */ + sessionId?: string; + /** + * A device layer supplied by the host instead of discovered from the machine. Pair it with `hasDeviceResponse` so an explicit absence is distinguishable from an omitted override. + */ + deviceResponse?: JsonValue; + /** + * Whether `deviceResponse` is an override at all. True with a null `deviceResponse` asserts the device layer is empty; false discovers it from the machine. + */ + hasDeviceResponse?: boolean; + /** + * Whether to ignore a cached server policy when reading, forcing the resolution to come from a live fetch. + */ + bypassCacheRead?: boolean; + serverPolicyCacheMode?: ManagedSettingsServerPolicyCacheMode; + /** + * Whether a configured platform policy helper may be run to produce an enforced helper layer. + */ + policyHelperEnabled?: boolean; + /** + * Directory the policy helper runs in when this ingestion is not owned by a registered session. A host resolving policy before its session exists must pass this, or a directory-sensitive helper judges the wrong repository. + */ + policyHelperWorkingDirectory?: string; + /** + * User agent to send with the policy fetch. + */ + userAgent: string; + /** + * Name of the embedding client, reported to the managed-settings service as `client_id` so it can refuse to deliver a policy this client is too old to enforce. Omit it and the runtime reports a generic identity; the runtime version sent alongside is the gate that actually matters. + */ + clientName?: string; + /** + * The host's environment variables, read for cache location and policy overrides. Supplied by the host rather than read from the runtime process so a command that selected its own configuration home is honored. + */ + environment: JsonValue; + /** + * The host's platform, in Node's `process.platform` spelling, used to locate platform-specific cache and policy paths. + */ + platform: string; + /** + * The host's home directory, used to resolve cache and managed-file locations. + */ + homeDirectory: string; +} +/** + * The managed policy layers an ingestion resolved, together with the per-layer outcome flags a host needs to decide how much to trust the result. The flags are reported rather than folded into the settings because a host must be able to distinguish an unmanaged device from a managed device whose policy could not be confirmed, and apply its fail-closed gates in the second case. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "ManagedSettingsIngestResult". + */ +/** @experimental */ +/** @internal */ +export interface ManagedSettingsIngestResult { + /** + * The effective managed settings composed from every resolved layer. Omitted when no layer set anything, so an unmanaged host locks nothing. + */ + settings?: JsonValue; + /** + * The effective managed permissions layer, when the resolved policy carries one. + */ + permissions?: JsonValue; + /** + * The server fetch failed and no cached entry stood in for it, so the server layer is unconfirmed and fail-closed gates apply. + */ + serverFetchFailed: boolean; + /** + * No usable token was available, so no server fetch was attempted. + */ + serverTokenUnavailable: boolean; + /** + * The error that made the server fetch fail, when one did. + */ + serverFetchError?: string; + serverResolution: ManagedSettingsServerPolicyResolution; + /** + * The server fetch failed in a way a later retry could succeed at, as opposed to a refusal that no retry will change. + */ + serverRetryableFailure: boolean; + /** + * A live fetch failed but a recent cached entry was served in its place, so `serverFetchFailed` stays false. Lets a consumer distinguish a clean fetch from a cache-backed fallback. + */ + serverCacheFallback: boolean; + /** + * A server-managed policy is known to exist even though `serverResponse` is intentionally unset. Consumers should OR this with the presence of `serverResponse` when deciding whether the device is managed, so a fresh start during an outage still applies the fail-closed gates instead of treating the device as unmanaged. + */ + serverSourceFound: boolean; + /** + * The account's server-delivered policy, when one was resolved. + */ + serverResponse?: JsonValue; + /** + * The sandbox object from a cached server policy that a forced refresh declined to serve in full. Folded back into the effective sandbox layer so an outage during a forced refresh still synthesizes the fail-closed sandbox floor from the administrator's known intent. + */ + serverSandboxFloor?: JsonValue; + /** + * The server fetch failed while a policy demanded a fresh confirmation, which is the strict posture an enterprise opted into rather than an ordinary transient failure. + */ + serverForcedRefreshFailure: boolean; + /** + * The server refused to deliver a policy because this client is too old to enforce it, carrying the user-facing sentence describing the refusal. No retry will help; the remedy is a client update. Gates that exist only because of managed policy should deny on this rather than falling back to their unmanaged default. + */ + serverClientUpdateRequired?: string; + /** + * Minimum client version requested by a successful response. Advisory only; the delivered policy remains active. + */ + serverClientUpdateRequested?: string; + /** + * The synthesized fail-closed permissions layer applied in place of a policy a refused client may not enforce. + */ + serverPermissionsFloor?: JsonValue; + /** + * The device layer discovered from the machine, when one exists. + */ + deviceResponse?: JsonValue; + /** + * The device layer could not be read, so it is unconfirmed rather than known-absent. + */ + deviceLoadFailed: boolean; + /** + * The sandbox floor contributed by the device layer. + */ + deviceSandboxFloor?: JsonValue; + /** + * A device-managed policy is known to exist. + */ + deviceSourceFound: boolean; + /** + * Whether the device layer's sandbox intent could not be determined, so a consumer must not read its absence as permission to run unsandboxed. + */ + deviceSandboxUndetermined: boolean; + /** + * The layer produced by the platform policy helper, when one ran. + */ + policyHelperResponse?: JsonValue; + policyHelperAuthority?: ManagedSettingsPolicyHelperAuthority; + /** + * The policy helper was configured but could not be run or its output could not be read. + */ + policyHelperLoadFailed: boolean; + /** + * A policy helper is configured for this host. + */ + policyHelperConfigured: boolean; + /** + * The policy helper's failure must be treated as fail-closed rather than ignored. + */ + policyHelperFailClosed: boolean; + /** + * Diagnostics emitted while resolving the layers, in the order they occurred. + */ + logMessages: ManagedSettingsLogMessage[]; +} +/** + * One diagnostic emitted while ingesting managed settings. Returned rather than logged by the runtime so the host decides where ingestion diagnostics go, which matters because an ingestion runs before a session exists to carry a log sink. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "ManagedSettingsLogMessage". + */ +/** @experimental */ +/** @internal */ +export interface ManagedSettingsLogMessage { + level: ManagedSettingsLogLevel; + /** + * Human-readable diagnostic text. + */ + message: string; +} +/** + * Inputs describing the host and account whose managed remote-control policy should be resolved. Mirrors `managedSettings.ingest`, and deliberately carries no retention decision: reading this gate never becomes this runtime process's startup policy. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "ManagedSettingsLoadRemoteControlParams". + */ +/** @experimental */ +/** @internal */ +export interface ManagedSettingsLoadRemoteControlParams { + policyHelperReason?: ManagedSettingsPolicyHelperReason; + /** + * Whether to run ingestion. Omit or set false to return an empty result without discovering or fetching policy. + */ + selfFetch?: boolean; + /** + * The authenticated identity to resolve policy for. Omitted before sign-in, in which case no server layer is fetched. + */ + authInfo?: JsonValue; + /** + * A device layer supplied by the host instead of discovered from the machine. Pair it with `hasDeviceResponse` so an explicit absence is distinguishable from an omitted override. + */ + deviceResponse?: JsonValue; + /** + * Whether `deviceResponse` is an override at all. True with a null `deviceResponse` asserts the device layer is empty; false discovers it from the machine. + */ + hasDeviceResponse?: boolean; + /** + * Whether a configured platform policy helper may be run to produce an enforced helper layer. + */ + policyHelperEnabled?: boolean; + /** + * User agent to send with the policy fetch. + */ + userAgent: string; + /** + * Name of the embedding client, reported to the managed-settings service as `client_id` so it can refuse to deliver a policy this client is too old to enforce. That refusal is exactly what turns into an explicit `disabled` below, so a host that omits this field forfeits the protection. + */ + clientName?: string; + /** + * The host's environment variables, read for cache location and policy overrides. + */ + environment: JsonValue; + /** + * The host's platform, in Node's `process.platform` spelling, used to locate platform-specific cache and policy paths. + */ + platform: string; + /** + * The host's home directory, used to resolve cache and managed-file locations. + */ + homeDirectory: string; +} +/** + * The managed remote-control policy in force for an account, if any. An absent policy means the account is unmanaged for this capability, which a host reads as allowed — so absence here is a positive statement that no policy applies, never a stand-in for a resolution that failed. A resolution that cannot be completed is rejected instead. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "ManagedSettingsLoadRemoteControlResult". + */ +/** @experimental */ +/** @internal */ +export interface ManagedSettingsLoadRemoteControlResult { + /** + * The account's `remoteControl` policy document. Absent when no managed policy governs remote control for this account. Reported as an explicit disabled policy, rather than as absence, when the service refused to deliver a policy this client is too old to enforce. + */ + remoteControl?: JsonValue; +} +/** + * The client-supplied managed settings to validate and normalize. Omitting them is valid and yields no policy. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "ManagedSettingsParseClientValueParams". + */ +/** @experimental */ +/** @internal */ +export interface ManagedSettingsParseClientValueParams { + /** + * The managed settings a client is supplying for a session it is about to open. Omit them when the client is imposing no policy of its own. + */ + settings?: JsonValue; +} +/** + * The accepted managed settings, reduced to the keys a client is allowed to set. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "ManagedSettingsParseClientValueResult". + */ +/** @experimental */ +/** @internal */ +export interface ManagedSettingsParseClientValueResult { + /** + * The normalized settings. Omitted when the client supplied none. An accepted document that sets nothing comes back as an empty object, which is distinct from omission. + */ + settingsJson?: JsonValue; +} +/** + * The device-managed settings layer, with the sandbox floor and the load outcome a host needs to compose it into an effective policy. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "ManagedSettingsReadDeviceResult". + */ +/** @experimental */ +/** @internal */ +export interface ManagedSettingsReadDeviceResult { + /** + * Validated, canonical device-managed settings. Omitted when no device policy was discovered, when the discovered document failed validation, or when it validated to an empty object — an empty document carries no policy, and reporting it as present would outrank layers that do. + */ + settings?: JsonValue; + /** + * Sandbox floor read from the discovered document before validation, so a document that is otherwise rejected can still raise the floor it declared. + */ + sandboxFloor?: JsonValue; + /** + * Whether discovery or validation failed. A host must treat this as "policy unknown" rather than "no policy": absent settings with this set false mean the device genuinely has none. + */ + loadFailed: boolean; +} /** * Validated device-managed settings discovered before a session exists. * @@ -14845,6 +16236,107 @@ export interface ManagedSettingsReadResult { */ errorMessage?: string; } +/** + * The bypass-permissions state a host holds today, the account now signed in, and what the managed-policy resolution said. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "ManagedSettingsResolveBypassDisableTransitionParams". + */ +/** @experimental */ +/** @internal */ +export interface ManagedSettingsResolveBypassDisableTransitionParams { + currentSource?: ManagedSettingsBypassDisableSource; + /** + * The account a `managed` disable is pinned to. Omit it when the current source is not `managed`; it is ignored for the other sources. + */ + currentManagedIdentity?: string; + /** + * The account signed in now. A `managed` disable pinned to a different account is lifted. + */ + activeIdentity: string; + policy: ManagedSettingsBypassPolicyResolution; +} +/** + * The bypass-permissions state a host should hold after the resolution, and what it must do to get there. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "ManagedSettingsResolveBypassDisableTransitionResult". + */ +/** @experimental */ +/** @internal */ +export interface ManagedSettingsResolveBypassDisableTransitionResult { + source?: ManagedSettingsBypassDisableSource; + /** + * The account the returned `managed` source is pinned to. Store it alongside `source`; the next call needs it to decide whether an account switch lifts the disable. + */ + managedIdentity?: string; + /** + * The value to store in the host's own bypass-disabled flag. Omitted means leave it exactly as it is, which is not the same as being told to store `false`. + */ + setDisabledState?: boolean; + /** + * Whether a bypass-permissions session already running must be turned off. True only when this resolution newly disables bypass mode, so a host that is already compliant is not interrupted. + */ + suppressRuntimeAllowAll: boolean; +} +/** + * The credential and environment to evaluate the device-channel sign-in pin against. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "ManagedSettingsResolveDeviceForceLoginOrgsGateParams". + */ +/** @experimental */ +/** @internal */ +export interface ManagedSettingsResolveDeviceForceLoginOrgsGateParams { + /** + * The authenticated identity to judge. Omitted when signed out, which is the case the device channel exists to serve: it can refuse a first sign-in before any credential exists. + */ + authInfo?: JsonValue; + /** + * The host's environment variables. Read for the variables that would make an identity's membership snapshot forgeable, so a credential whose provenance cannot be trusted is refused rather than accepted. + */ + environment: JsonValue; +} +/** + * The managed-settings layers to compose into an effective policy. Every layer is optional; omitting all of them yields no effective policy. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "ManagedSettingsResolveEffectiveValueParams". + */ +/** @experimental */ +/** @internal */ +export interface ManagedSettingsResolveEffectiveValueParams { + /** + * The account's server-delivered org policy, as returned by a managed-settings fetch. Omitted before sign-in or when the fetch has not landed. + */ + server?: JsonValue; + /** + * The device layer: native MDM and managed-file settings describing the machine rather than the account. + */ + mdm?: JsonValue; + /** + * The device sandbox floor, folded into the device layer under `sandbox` before composition. + */ + deviceSandboxFloor?: JsonValue; + /** + * The policy-helper layer, an enforced channel sourced from a platform policy helper process. + */ + policyHelper?: JsonValue; +} +/** + * The effective managed policy composed from the supplied layers. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "ManagedSettingsResolveEffectiveValueResult". + */ +/** @experimental */ +/** @internal */ +export interface ManagedSettingsResolveEffectiveValueResult { + /** + * The composed effective managed settings. Omitted when no layer sets anything, so an unmanaged host locks nothing. Carries `sandboxAdvisoryFloorDeclined: true` when an advisory server force-on was dropped because this host cannot run the sandbox. + */ + settingsJson?: JsonValue; +} /** @experimental */ export interface ManagedSettingsResolveRequest { @@ -14873,6 +16365,7 @@ export interface ManagedSettingsResolveRequest { */ /** @experimental */ export interface ManagedSettingsResolveResult { + permissionsContext?: ManagedPermissionsContext; /** * Printable opaque identity of the account the settings were resolved for, suitable for comparison and storage, not an account selectionId. Absent when no account was available, in which case only device policy is reported. */ @@ -14889,6 +16382,224 @@ export interface ManagedSettingsResolveResult { */ diagnostics: ManagedSettingsDiagnostic[]; } +/** + * Inputs describing the host and account a startup policy resolution should run for. Mirrors `managedSettings.ingest`, and adds the decision of whether the resolved policy is retained for this runtime process. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "ManagedSettingsResolveStartupPolicyParams". + */ +/** @experimental */ +/** @internal */ +export interface ManagedSettingsResolveStartupPolicyParams { + policyHelperReason?: ManagedSettingsPolicyHelperReason; + /** + * Whether to run ingestion. Omit or set false to return an empty result without discovering or fetching policy. + */ + selfFetch?: boolean; + /** + * The authenticated identity to resolve policy for. Omitted before sign-in, in which case no server layer is fetched and the sign-in pin is evaluated against no credential. + */ + authInfo?: JsonValue; + /** + * The session whose credential store, working directory and client kind the resolution should use. Omit it when no session exists yet and the process-wide store applies. A resolution whose session names a credential other than `authInfo` is returned but not retained, so a stale sign-in cannot apply policy for an account that has already been switched away from. + */ + sessionId?: string; + /** + * A device layer supplied by the host instead of discovered from the machine. Pair it with `hasDeviceResponse` so an explicit absence is distinguishable from an omitted override. + */ + deviceResponse?: JsonValue; + /** + * Whether `deviceResponse` is an override at all. True with a null `deviceResponse` asserts the device layer is empty; false discovers it from the machine. + */ + hasDeviceResponse?: boolean; + /** + * Whether to ignore a cached server policy when reading, forcing the resolution to come from a live fetch. + */ + bypassCacheRead?: boolean; + serverPolicyCacheMode?: ManagedSettingsServerPolicyCacheMode; + /** + * Whether a configured platform policy helper may be run to produce an enforced helper layer. + */ + policyHelperEnabled?: boolean; + /** + * Directory the policy helper runs in when this resolution is not owned by a registered session. A host resolving policy before its session exists must pass this, or a directory-sensitive helper judges the wrong repository. + */ + policyHelperWorkingDirectory?: string; + /** + * User agent to send with the policy fetch. + */ + userAgent: string; + /** + * Name of the embedding client, reported to the managed-settings service as `client_id` so it can refuse to deliver a policy this client is too old to enforce. + */ + clientName?: string; + /** + * The host's environment variables, read for cache location, policy overrides, and the variables that would make a membership snapshot forgeable. + */ + environment: JsonValue; + /** + * The host's platform, in Node's `process.platform` spelling, used to locate platform-specific cache and policy paths. + */ + platform: string; + /** + * The host's home directory, used to resolve cache and managed-file locations. + */ + homeDirectory: string; + /** + * Whether to retain the resolved policy as this runtime process's startup policy, so later work in the process enforces it. Set false to resolve without side effects, which is what a protocol host wants before session creation: its sessions apply the complete policy through the normal lifecycle, and retaining here would let a readiness credential affect unrelated sessions. The returned verdict is identical either way — this decides only whether the process keeps it. + */ + retainAndApply: boolean; +} +/** + * The startup policy a resolution composed, the per-layer outcome flags a host needs to decide how much to trust it, and the enterprise sign-in verdict for the credential it ran against. The flags are reported rather than folded into the settings because a host must distinguish an unmanaged device from a managed device whose policy could not be confirmed, and apply its fail-closed gates in the second case. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "ManagedSettingsResolveStartupPolicyResult". + */ +/** @experimental */ +/** @internal */ +export interface ManagedSettingsResolveStartupPolicyResult { + serverResolution: ManagedSettingsServerPolicyResolution; + /** + * Advisory minimum client version from a successful server response. + */ + clientUpdateRequested?: string; + /** + * The account's server-delivered policy, when one was resolved. + */ + serverResponse?: JsonValue; + /** + * The device layer discovered from the machine, when one exists. + */ + deviceResponse?: JsonValue; + /** + * The sandbox floor contributed by the device layer. + */ + deviceSandboxFloor?: JsonValue; + /** + * The layer produced by the platform policy helper, when one ran. + */ + policyHelperResponse?: JsonValue; + /** + * The server fetch failed and no cached entry stood in for it, so the server layer is unconfirmed and fail-closed gates apply. + */ + serverFetchFailed: boolean; + /** + * The device layer could not be read, so it is unconfirmed rather than known-absent. + */ + deviceLoadFailed: boolean; + /** + * The policy helper was configured but could not be run, or its output could not be read. + */ + policyHelperLoadFailed: boolean; + /** + * Telemetry describing which layers answered and how, for the host to report alongside its own startup events. + */ + telemetry: JsonValue; + /** + * The effective sandbox floor composed across every layer, which a host must apply rather than its own default. + */ + sandboxFloor?: JsonValue; + /** + * Some layer of the policy could not be confirmed, so its absence must not be read as permission. Folds in a failed server fetch as well as an unreadable device layer. + */ + policyUndetermined: boolean; + /** + * The permissions layer specifically could not be confirmed, so permission gates apply their fail-closed posture. + */ + permissionPolicyUndetermined: boolean; + /** + * A managed policy is known to apply to this host, from a discovered source or a resolved document. + */ + managedPolicyPresent: boolean; + /** + * The sandbox was enabled because policy could not be confirmed rather than because an administrator asked for it. Lets a host explain a sandbox the user did not configure. + */ + sandboxEnabledByUndeterminedPolicy: boolean; + forceLoginOrgs: ForceLoginOrgsDecision; + /** + * The server refused to deliver a policy because this client is too old to enforce it, carrying the user-facing sentence describing the refusal. No retry will help; the remedy is a client update. + */ + clientUpdateRequired?: string; + /** + * Warnings raised while resolving, in the order they occurred. + */ + warnings: string[]; +} +/** + * The managed layers to retain as this process's startup policy, with the outcome of fetching each one. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "ManagedSettingsRetainPolicyParams". + */ +/** @experimental */ +/** @internal */ +export interface ManagedSettingsRetainPolicyParams { + /** + * The resolved server policy. A known source can lack a response after a forced-refresh failure. + */ + serverResponse?: JsonValue; + /** + * The device/MDM policy layer. + */ + deviceResponse?: JsonValue; + /** + * Sandbox floor declared by the device layer, carried separately so a document that failed validation can still raise the floor. + */ + deviceSandboxFloor?: JsonValue; + /** + * Whether a device policy source demonstrably exists. This, not a read error, is the evidence a device is managed, so a fail-closed policy gates on it. + */ + deviceSourceFound: boolean; + /** + * Whether any managed policy source exists, independently of fetch failures. + */ + managedPolicyPresent: boolean; + /** + * Whether the server policy source exists, even when its response is unavailable. + */ + serverSourceFound: boolean; + /** + * Whether a required fresh server-policy confirmation failed. + */ + serverForcedRefreshFailure: boolean; + /** + * Preserve the retained server layer during a device-only resolution. + */ + preserveServerState: boolean; + /** + * Whether the device sandbox intent is unreadable independently of its other settings. + */ + deviceSandboxUndetermined: boolean; + /** + * Whether the shared sandbox policy is undetermined. Diagnostic errors alone do not imply this flag. + */ + sandboxFloorUndetermined: boolean; + /** + * Whether permission policy is undetermined. Sandbox-only uncertainty does not imply this flag. + */ + permissionPolicyUndetermined: boolean; + /** + * Whether uncertainty alone enabled the shared sandbox floor. + */ + sandboxEnabledByUndeterminedPolicy: boolean; + /** + * Shared server/device sandbox floor, excluding session-local helper policy. + */ + sharedSandboxFloor?: JsonValue; + /** + * Whether the server fetch failed. Report it honestly: true keeps the already-retained server layer rather than replacing it with an absent one. + */ + serverFetchFailed: boolean; + /** + * Whether the device load failed. Report it honestly, for the same reason as `serverFetchFailed`. + */ + deviceLoadFailed: boolean; + /** + * The policy helper's response: the third managed layer that a process owning its startup policy resolves. It is retained with the other two so that every ambient read in this process, including the mutation guards and the governed marketplace allowlist that the `plugins.*` handlers derive, sees the whole policy the process fetched. A session-owning process omits it, because it resolves a helper per session against that session's working directory. + */ + policyHelperResponse?: JsonValue; +} /** * The authoring JSON schema for managed settings recognized by this runtime. * @@ -16265,6 +17976,20 @@ export interface McpIsServerRunningResult { */ running: boolean; } +/** + * Names of the MCP servers the runtime supplies itself. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "McpListBuiltinServerNamesResult". + */ +/** @experimental */ +/** @internal */ +export interface McpListBuiltinServerNamesResult { + /** + * Every built-in server name. A host disabling the built-ins wholesale suppresses exactly this set. + */ + names: string[]; +} /** * Server name whose tool list should be returned. * @@ -16982,6 +18707,92 @@ export interface McpRegisterExternalClientRequest { */ config: OpaqueInProcessValue; } +/** + * Registry search to abandon. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "McpRegistryCancelRequest". + */ +/** @experimental */ +/** @internal */ +export interface McpRegistryCancelRequest { + /** + * Request ID from `mcp.registry.allocateRequestId` that the search uses. + */ + requestId: number; +} +/** + * Whether the cancel reached a live search. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "McpRegistryCancelResult". + */ +/** @experimental */ +/** @internal */ +export interface McpRegistryCancelResult { + /** + * True when the cancel stopped a running search. False for unknown or reclaimed IDs, completed or canceled searches, and unused reservations. The cancel releases an unused reservation. + */ + canceled: boolean; +} +/** + * Request id naming a cancellable registry search. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "McpRegistryRequestIdResult". + */ +/** @experimental */ +/** @internal */ +export interface McpRegistryRequestIdResult { + /** + * Request ID for `mcp.registry.search` and `mcp.registry.cancel`. It serves one search. Allocation can reclaim unused IDs at the 1,024-ID reservation limit. + */ + requestId: number; +} +/** + * Registry search terms, the credential to search under, and the request id that makes the search cancellable. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "McpRegistrySearchRequest". + */ +/** @experimental */ +/** @internal */ +export interface McpRegistrySearchRequest { + /** + * Request ID from `mcp.registry.allocateRequestId`. The search refuses unknown, reclaimed, or canceled IDs and IDs that another search or request already uses. + */ + requestId: number; + /** + * The credential the search runs under, carried opaquely. Hosts usually send credential-free `AuthIdentity`; a `token` identity can be resolved only when it embeds a token, while `env` and `gh-cli` identities can use a token embedded in the request first. + */ + authInfo: JsonValue; + /** + * Free-text query. Omitted or empty asks the registry for its top servers rather than searching. A value that is not a string is refused. + */ + query?: string; + /** + * Repository used for the policy lookup, as `owner/name`. The policy selects the registry URL and whether the user token goes to the registry. The registry receives this repository only when the policy entry lists it as required context. A value that is not a string is refused. + */ + repository?: string; + /** + * Maximum number of servers to return. + */ + limit: number; +} +/** + * Servers selected from the registry response. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "McpRegistrySearchResult". + */ +/** @experimental */ +/** @internal */ +export interface McpRegistrySearchResult { + /** + * The server objects, carried opaquely. The runtime follows pages, keeps the newest entry per server name, cuts the list to `limit`, and sorts an empty-query result by GitHub stars. Each server object remains unchanged because the registry owns that shape. + */ + servers: JsonValue; +} /** * In-process MCP reload configuration. * @@ -17516,6 +19327,42 @@ export interface McpSetEnvValueModeParams { export interface McpSetEnvValueModeResult { mode: McpSetEnvValueModeDetails; } +/** + * The session tool filters that decide whether the session still has a shell to run `gh` with. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "McpShouldExcludeGitHubToolsRequest". + */ +/** @experimental */ +/** @internal */ +export interface McpShouldExcludeGitHubToolsRequest { + /** + * The session's tool allowlist, when it set one. Omitted means the session constrains nothing this way. + */ + availableTools?: string[]; + /** + * The session's tool denylist, when it set one. Omitted means the session constrains nothing this way. + */ + excludedTools?: string[]; + /** + * How the allowlist and denylist combine when both are set. Omitted means the default every session gets, so a caller that never chose a precedence is answered as its sessions behave. + */ + toolFilterPrecedence?: OptionsUpdateToolFilterPrecedence | null; +} +/** + * Whether the gh-replaceable GitHub MCP tools may be clipped for the session described by the request. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "McpShouldExcludeGitHubToolsResult". + */ +/** @experimental */ +/** @internal */ +export interface McpShouldExcludeGitHubToolsResult { + /** + * True only when both halves hold: the session's filters still reach the platform shell tool, and the host actually has the `gh` those tools would be replaced by. Feed it straight back as the `excludeGhReplaceableTools` build option. + */ + excludeGhReplaceableTools: boolean; +} /** * 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. * @@ -18274,6 +20121,64 @@ export interface ModelClearStartupSeedRequest { export interface ModelClearStartupSeedResult { cleared: boolean; } +/** + * Host presentation state the reasoning-summaries policy depends on. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "ModelGetReasoningSummariesRequest". + */ +/** @experimental */ +export interface ModelGetReasoningSummariesRequest { + /** + * The host's persisted `showReasoning` user setting, omitted when unset. + */ + persistedShowReasoning?: boolean; + /** + * Whether the host is currently showing reasoning summaries. Defaults to false. + */ + summariesVisible?: boolean; +} +/** + * Reasoning-summaries presentation policy for the session's current model. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "ModelGetReasoningSummariesResult". + */ +/** @experimental */ +export interface ModelGetReasoningSummariesResult { + /** + * False until the session has a model catalog and a selected model. This read never resolves the catalog itself, and the runtime emits no readiness notification, so repeating this call alone leaves `ready` false indefinitely on a session that has neither listed models nor run a turn. `session.model.list` is the prerequisite and the signal: it resolves and caches the catalog for the session, so a successful return means the next read is ready. The exception is a session that resolves models without a Copilot catalog, that is a BYOK registry or an alternate provider: `session.model.list` returns an empty list without caching a catalog, so `ready` stays false and this policy is unavailable there. While false, every model-policy field is false and `visibility` is omitted; `experimentSettled` is independent of readiness and still reports the assignment state. + */ + ready: boolean; + /** + * Whether the session's reasoning-summaries experiment assignment has settled. False means this read does not wait for a pending assignment and reports the experiment-derived fields conservatively: `showHiddenHint` is false and `visibility` falls back to `persistedShowReasoning`, or is omitted when none was supplied. A host that wants the assigned answer re-reads until this is true. + */ + experimentSettled: boolean; + /** + * Whether turns on this session produce reasoning summaries, honouring an explicit session reasoning-summary override. + */ + sessionShowsSummaries: boolean; + /** + * Whether the selected model shows reasoning summaries by default. + */ + modelDefaultShowsSummaries: boolean; + /** + * Whether the selected model emits reasoning headers. + */ + showReasoningHeaders: boolean; + /** + * Default reasoning visibility, and only meaningful once `ready` is true: false when the session produces no summaries, else the persisted setting, else the experiment's default. While `ready` is true it is omitted only when all three are unavailable, that is when the session produces summaries, no `persistedShowReasoning` was supplied, and `experimentSettled` is false. While `ready` is false there is no model policy to report and this field is always omitted, so an absent value alone does not imply a pending experiment. + */ + visibility?: boolean; + /** + * Whether to show the hint that reasoning summaries are hidden. Always false while `experimentSettled` is false. + */ + showHiddenHint: boolean; + /** + * Whether the supplied `persistedShowReasoning` setting only restates the model default. This reports a redundancy, not a standing instruction to delete the setting: it stays true for as long as the setting and the model default agree, including for a user who later chose that value deliberately. It exists for a one-time migration of a legacy setting written before the model default made it redundant, so a host that acts on it owns the cleanup and must gate it behind its own persisted `done` marker, delete only a setting whose value still matches the one it read, and never repeat the deletion on a later launch. Under an off-by-default reasoning-summaries experiment an explicit `showReasoning: true` opt-in also reads as redundant on a summary-producing model, so an ungated host would keep deleting that opt-in and hide summaries again on every launch. + */ + clearRedundantSetting: boolean; +} /** * List of Copilot models available to the resolved user, including capabilities and billing metadata. * @@ -20489,6 +22394,41 @@ export interface PermissionsConfigureResult { */ success: boolean; } +/** + * The verbatim managed `permissions` slice to interpret. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "PermissionsEvaluateManagedPolicyParams". + */ +/** @experimental */ +/** @internal */ +export interface PermissionsEvaluateManagedPolicyParams { + /** + * The `permissions` object from a managed settings layer, for example `{"disableBypassPermissionsMode":"disable"}`. Omitted or null means no managed policy applies. + */ + permissions?: JsonValue; +} +/** + * The policy questions a host asks before granting elevated permissions. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "PermissionsEvaluateManagedPolicyResult". + */ +/** @experimental */ +export interface PermissionsEvaluateManagedPolicyResult { + /** + * True when managed policy forbids bypass-permissions mode, so allow-all style flags must not take effect. + */ + bypassPermissionsDisabled: boolean; + /** + * True when a managed policy exists but this build could not determine its meaning. Hosts fail closed on it rather than assuming permission was granted. + */ + policyUndetermined: boolean; + /** + * True when the managed layer supplies permission rules that the session must enforce. + */ + permissionRulesActive: boolean; +} /** * Indicates whether the operation succeeded. * @@ -20757,6 +22697,44 @@ export interface PermissionUrlsSetUnrestrictedModeParams { */ enabled: boolean; } +/** + * A complete JSON snapshot to write under the runtime's per-path file lock. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "PersistenceWriteRawJsonLockedRequest". + */ +/** @experimental */ +export interface PersistenceWriteRawJsonLockedRequest { + /** + * Absolute path of the file to replace. Parent directories are created when missing. + */ + path: string; + /** + * The serialized JSON document to write. It replaces the file's contents entirely; nothing is merged. + */ + dataJson: string; + /** + * Comment lines to emit above the document, for a format that tolerates them. Omit for strict JSON. + */ + fileHeader?: string | null; +} +/** + * The outcome of a locked snapshot write. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "PersistenceWriteRawJsonLockedResult". + */ +/** @experimental */ +export interface PersistenceWriteRawJsonLockedResult { + /** + * Whether the file now holds the requested contents. + */ + success: boolean; + /** + * Why the write failed. Absent when `success` is true. A lock the runtime could not acquire in time reports here rather than raising. + */ + error?: string | null; +} /** * Optional message to echo back to the caller. * @@ -22874,6 +24852,131 @@ export interface RemoteSessionRepository { */ branch?: string; } +/** + * One atomic edit of an allowed top-level repository setting. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "RepoSettingsEditRequest". + */ +/** @experimental */ +/** @internal */ +export interface RepoSettingsEditRequest { + /** + * Absolute path of the repository root. + */ + repoRoot: string; + scope: RepoSettingsEditScope; + /** + * One canonical top-level repository setting that the allowlist permits. + */ + key: string; + operation: SettingsEditOperation; + /** + * Required boolean, string or number for set, required string for add-item and remove-item, and forbidden for remove. The runtime rejects all other JSON values. + */ + value?: JsonValue; +} +/** + * Which repository settings file to read. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "RepoSettingsReadRawRequest". + */ +/** @experimental */ +/** @internal */ +export interface RepoSettingsReadRawRequest { + /** + * Absolute path of the repository root. + */ + repoRoot: string; + /** + * Use repo for shared settings or local for settings.local.json. Other scopes are rejected. + */ + scope: string; +} +/** + * Parsed repository settings without normalization or layering. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "RepoSettingsReadRawResult". + */ +/** @experimental */ +/** @internal */ +export interface RepoSettingsReadRawResult { + /** + * Stored JSON values after the existing JSONC parser and prototype-safety checks. Empty when the file is missing, blank, or invalid. + */ + config: JsonValue; + /** + * True when the file exists but did not parse, so `config` is empty for that reason rather than because nothing is set. + */ + invalid: boolean; +} +/** + * The complete set of repository-overridable settings to store. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "RepoSettingsWriteAllRequest". + */ +/** @experimental */ +/** @internal */ +export interface RepoSettingsWriteAllRequest { + /** + * Absolute path of the repository root. + */ + repoRoot: string; + /** + * Use repo for shared settings or local for settings.local.json. Other scopes are rejected. + */ + scope: string; + /** + * Object of allowed settings to store. Missing allowlisted keys are removed. Other input keys are ignored, while unknown stored keys are preserved. + */ + settings: JsonValue; +} +/** + * One key to write or remove in a repository settings file. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "RepoSettingsWriteKeyRequest". + */ +/** @experimental */ +/** @internal */ +export interface RepoSettingsWriteKeyRequest { + /** + * Absolute path of the repository root. + */ + repoRoot: string; + /** + * Use repo for shared settings or local for settings.local.json. Other scopes are rejected. + */ + scope: string; + /** + * Canonical top-level key permitted by the repository settings allowlist. + */ + key: string; + /** + * Value to validate and write. Required unless delete is true; a null value is validated, not treated as deletion. + */ + value?: JsonValue; + /** + * True to remove the key instead of writing it. + */ + delete?: boolean; +} +/** + * The stored sandbox settings to resolve against the defaults. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "SandboxApplyDefaultsRequest". + */ +/** @experimental */ +export interface SandboxApplyDefaultsRequest { + /** + * The sandbox settings as stored in the settings file. An empty object resolves to the defaults alone. + */ + stored: JsonValue; +} /** * Resolved sandbox configuration. * @@ -23088,6 +25191,36 @@ export interface SandboxMaskedEnvVar { */ injectHosts: string[]; } +/** + * A possible secret-bearing environment variable and optional locally suggested HTTPS injection hosts. This is a draft for user review, not an active grant. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "SandboxCredentialSuggestion". + */ +/** @experimental */ +export interface SandboxCredentialSuggestion { + /** + * Environment variable name. The secret value is never returned. + */ + name: string; + /** + * HTTPS hostnames suggested by a local known-provider mapping. An empty list requires the user to supply injection hosts before adding a masking entry. These suggestions do not allow network access. + */ + suggestedInjectHosts: string[]; +} +/** + * Possible unconfigured secrets in the sandbox shell environment, sorted by variable name, with no secret values. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "SandboxCredentialSuggestionsResult". + */ +/** @experimental */ +export interface SandboxCredentialSuggestionsResult { + /** + * Candidates for user review. Empty when sandboxing is disabled or no unconfigured candidates are present. + */ + suggestions: SandboxCredentialSuggestion[]; +} /** * Request to disable sandboxing for the current session while resolving an active sandbox-bypass permission prompt. * @@ -23119,6 +25252,69 @@ export interface SandboxDisableForSessionResult { */ enabled: boolean; } +/** + * The documentation URL a host cites when it declines or downgrades a sandbox setting. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "SandboxDocsUrlResult". + */ +/** @experimental */ +export interface SandboxDocsUrlResult { + /** + * Documentation URL describing the sandbox and the settings that control it. + */ + url: string; +} +/** + * A sandbox config and the per-feature routing flag to resolve it for. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "SandboxEffectiveForOptionalParams". + */ +/** @experimental */ +/** @internal */ +export interface SandboxEffectiveForOptionalParams { + /** + * The session's resolved `sandbox` slice. Omitted when the host has none, which yields a disabled sandbox rather than an error. + */ + sandbox?: JsonValue; + /** + * The routing flag naming the feature being launched, such as `sandboxMcpServers` or `sandboxLspServers`. + */ + routingFlag: string; +} +/** + * The sandbox a named feature actually launches under. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "SandboxEffectiveForOptionalResult". + */ +/** @experimental */ +/** @internal */ +export interface SandboxEffectiveForOptionalResult { + /** + * The effective sandbox config for the requested feature, or a disabled sandbox when the feature is routed out of it. + */ + sandboxJson: JsonValue; +} +/** + * One slash command, as typed, to test against the enforcement-block allowlist. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "SandboxEnforcementCommandAllowedParams". + */ +/** @experimental */ +/** @internal */ +export interface SandboxEnforcementCommandAllowedParams { + /** + * The command name as the user typed it, with or without its leading slash. + */ + commandName: string; + /** + * The command's arguments, in order. Pass an empty array for a bare command: several entries in the allowlist admit the bare form and refuse every argument. + */ + args: string[]; +} /** * Managed sandbox enforcement state for a session. * @@ -23140,6 +25336,60 @@ export interface SandboxEnforcementStatus { */ reason?: string; } +/** + * A batch of file-picker candidates to check against a prepared policy. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "SandboxFilterPickerCandidatesParams". + */ +/** @experimental */ +/** @internal */ +export interface SandboxFilterPickerCandidatesParams { + /** + * The candidates to check, in the order the answers are returned. + */ + candidates: SandboxPickerCandidate[]; + /** + * The session's working directory, matching the one the policy was prepared from. + */ + cwd: string; + /** + * The policy returned by `sandbox.preparePickerPolicy`. Omitted keeps every candidate. + */ + sandbox?: JsonValue; +} +/** + * One file-picker suggestion offered up for the sandbox filesystem check. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "SandboxPickerCandidate". + */ +/** @experimental */ +/** @internal */ +export interface SandboxPickerCandidate { + /** + * Absolute path the suggestion would attach. + */ + path: string; + /** + * Whether the suggestion is a directory. A directory the policy does not itself allow may still be needed as a waypoint to an allowed subtree. + */ + isDirectory: boolean; +} +/** + * Per-candidate allow decisions, positionally aligned with the request. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "SandboxFilterPickerCandidatesResult". + */ +/** @experimental */ +/** @internal */ +export interface SandboxFilterPickerCandidatesResult { + /** + * One decision per candidate, in the same order. Always the same length as the request's `candidates`. + */ + allowed: boolean[]; +} /** * Request to accept the sandbox path grant offered on an active sandbox escalation permission prompt. * @@ -23206,6 +25456,38 @@ export interface SandboxHostSupport { */ capabilities: SandboxHostCapability[]; } +/** + * The working directory and sandbox settings to build a file-picker policy from. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "SandboxPreparePickerPolicyParams". + */ +/** @experimental */ +/** @internal */ +export interface SandboxPreparePickerPolicyParams { + /** + * The session's working directory. Git discovery and grant canonicalization are resolved against it. + */ + cwd: string; + /** + * The session's resolved `sandbox` slice. Omitted, or disabled, yields no policy and every candidate is kept. + */ + sandbox?: JsonValue; +} +/** + * The prepared file-picker policy, or none when the sandbox is disabled. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "SandboxPreparePickerPolicyResult". + */ +/** @experimental */ +/** @internal */ +export interface SandboxPreparePickerPolicyResult { + /** + * The prepared policy to pass back to `sandbox.filterPickerCandidates`. Absent when the sandbox is disabled or absent, in which case filtering keeps every candidate. + */ + sandboxJson?: JsonValue; +} /** * Result of creating the persistent certificate authority of the sandbox credential proxy. * @@ -23247,6 +25529,125 @@ export interface SandboxProxyCaStatus { */ canInstall: boolean; } +/** + * The stored sandbox proxy password, if one is present. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "SandboxProxySecretGetResult". + */ +/** @experimental */ +export interface SandboxProxySecretGetResult { + /** + * The stored password. Absent when no entry exists in either the keychain or the on-disk fallback store. + */ + value?: string; +} +/** + * Where this runtime keeps the sandbox proxy password. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "SandboxProxySecretLocation". + */ +/** @experimental */ +export interface SandboxProxySecretLocation { + /** + * Absolute path of the credential store holding the sandbox proxy password. The proxy password belongs to no MCP server, so it is kept in its own directory rather than in the MCP secret store. + */ + configDir: string; + /** + * Identifier of the proxy password entry within that store. Derived from the configuration directory, so two Copilot homes on one machine never share an entry. Use it to recognize a `${secret:…}` reference in stored settings as this runtime's own proxy password. + */ + secretId: string; +} +/** + * The sandbox proxy password to store. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "SandboxProxySecretSaveRequest". + */ +/** @experimental */ +export interface SandboxProxySecretSaveRequest { + /** + * The password to store for the sandbox proxy. + */ + value: string; +} +/** + * Where the sandbox proxy password was stored. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "SandboxProxySecretSaveResult". + */ +/** @experimental */ +export interface SandboxProxySecretSaveResult { + /** + * Whether the OS keychain accepted the value. False means it was written to the on-disk fallback store instead, which is still readable by `sandbox.proxySecret.get`. + */ + storedInKeychain: boolean; +} +/** + * The user's sandbox settings and the managed sandbox floor to raise them to. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "SandboxResolveFlooredConfigParams". + */ +/** @experimental */ +/** @internal */ +export interface SandboxResolveFlooredConfigParams { + /** + * The user's own `sandbox` settings slice. Omitted when the user has configured none, which still yields the managed floor rather than a disabled sandbox. + */ + userSandbox?: JsonValue; + /** + * The effective managed `sandbox` slice acting as a floor. Omitted when no administrator policy applies, in which case the user's own settings stand unchanged. + */ + managedSandbox?: JsonValue; +} +/** + * The user's sandbox settings raised to the managed floor. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "SandboxResolveFlooredConfigResult". + */ +/** @experimental */ +export interface SandboxResolveFlooredConfigResult { + /** + * The floored sandbox config, normalized through the declared sandbox shape so only declared fields in a fixed order are present. + */ + sandboxJson: JsonValue; +} +/** + * The managed sandbox policy in force, which selects which remedy the notice offers. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "SandboxUnsupportedStartupNoticeParams". + */ +/** @experimental */ +/** @internal */ +export interface SandboxUnsupportedStartupNoticeParams { + /** + * The managed `sandbox` slice, when a policy supplies one. The notice treats the sandbox as enforced only when this both enables it and sets `failIfUnavailable`, because a managed sandbox that tolerates being unavailable still leaves the user able to turn it off. + */ + managedSandbox?: JsonValue; + /** + * True when the sandbox stayed on only because the managed policy could not be determined. Distinguishes a policy that positively enforces the sandbox from one that merely failed to resolve, which is the difference between telling the user to contact an administrator and telling them to wait. Defaults to false. + */ + enabledByUndeterminedPolicy?: boolean; +} +/** + * The startup warning, absent when the machine can run a sandbox. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "SandboxUnsupportedStartupNoticeResult". + */ +/** @experimental */ +/** @internal */ +export interface SandboxUnsupportedStartupNoticeResult { + /** + * The user-facing warning, carrying the platform remedy and the recovery the policy in force actually allows. Absent when this machine supports a sandbox, so a host can call unconditionally and print only what it gets. + */ + notice?: string; +} /** * Register an absolute-time scheduled prompt. * @@ -24940,6 +27341,10 @@ export interface SessionOpenOptions { */ enforceManagedModelDefaults?: boolean; managedSettings?: SessionManagedSettings; + /** + * Declares that a human drives this session's conversation, enabling user-message analysis (sentiment, task completion criteria, and task analytics) for user-authored messages. Automated messages remain excluded. Defaults to false for SDK sessions. Applied when the session is created or loaded from storage; ignored when resuming a session that is already active, and for cloud sessions. + */ + interactiveUser?: boolean; /** * 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. */ @@ -25063,15 +27468,27 @@ export interface SessionOpenOptions { */ allowAllMcpServerInstructions?: boolean; /** - * Additional directories to search for skills. + * Explicit discovery-only skill roots, not filesystem grants. Available without enableConfigDiscovery, subject to skill/managed disables. Relative SDK paths resolve once when supplied against the request's effective workingDirectory; retained roots do not move on cwd changes. Null and omission are equivalent; [] clears only the explicit SDK contribution. */ skillDirectories?: string[]; + /** + * Explicit discovery-only agent roots, separate from customAgents and filesystem grants. Available without enableConfigDiscovery, subject to managed/source disables. Relative SDK paths resolve once when supplied against the request's effective workingDirectory; retained roots do not move on cwd changes. Null and omission are equivalent; [] clears only the explicit SDK contribution. + */ + agentDirectories?: string[]; + /** + * Explicit discovery-only instruction roots using existing SDK instruction formats, not filesystem grants. Available without enableConfigDiscovery, subject to instruction/managed disables. Relative SDK paths resolve once when supplied against the request's effective workingDirectory; retained roots do not move on cwd changes. Null and omission are equivalent; [] clears only the explicit SDK contribution. + */ + instructionDirectories?: string[]; + /** + * Explicit discovery-only file-hook roots, separate from callback hooks and filesystem grants. Available without enableConfigDiscovery or enableFileHooks, which controls repository-origin hooks only; hook/managed disables still apply. Relative SDK paths resolve once when supplied against the request's effective workingDirectory; retained roots do not move on cwd changes. Null and omission are equivalent; [] clears only the explicit SDK contribution. + */ + hookDirectories?: string[]; /** * Skill scan directories and descendants excluded from discovery. Supports `~`-relative paths. */ ignoredSkillsLocations?: string[]; /** - * Whether skill loading is enabled. When omitted, an SDK skill provider enables skills by default. + * Whether skill loading is enabled. When omitted, explicit SDK skill roots or an SDK skill provider enable skills independently of enableConfigDiscovery. */ enableSkills?: boolean; /** @@ -25567,6 +27984,27 @@ export interface SessionPluginsInstallRequest { */ source: string; } + +/** @experimental */ +export interface SessionPluginsRetryManagedRequest { + /** + * "plugin@marketplace" specs to retry. Omit to retry every plugin the session's managed settings require. + */ + plugins?: string[]; +} +/** + * Per-plugin outcomes of retrying the plugins required by the session's managed settings. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "SessionPluginsRetryManagedResult". + */ +/** @experimental */ +export interface SessionPluginsRetryManagedResult { + /** + * One entry per requested plugin, in request order. When no plugins were requested, one entry per plugin the managed settings require. + */ + plugins: ManagedPluginRetryEntry[]; +} /** * Outcome of the prune operation: deleted IDs, dry-run candidates, skipped IDs, total bytes freed, and the dry-run flag. * @@ -26856,9 +29294,21 @@ export interface SessionUpdateOptionsParams { */ allowAllMcpServerInstructions?: boolean; /** - * Additional directories to search for skills. + * Explicit discovery-only skill roots, not filesystem grants. Available without enableConfigDiscovery, subject to skill/managed disables. Relative SDK paths resolve once when supplied against the request's effective workingDirectory; retained roots do not move on cwd changes. Null and omission are equivalent; [] clears only the explicit SDK contribution. */ skillDirectories?: string[]; + /** + * Explicit discovery-only agent roots, separate from customAgents and filesystem grants. Available without enableConfigDiscovery, subject to managed/source disables. Relative SDK paths resolve once when supplied against the request's effective workingDirectory; retained roots do not move on cwd changes. Null and omission are equivalent; [] clears only the explicit SDK contribution. + */ + agentDirectories?: string[]; + /** + * Explicit discovery-only instruction roots using existing SDK instruction formats, not filesystem grants. Available without enableConfigDiscovery, subject to instruction/managed disables. Relative SDK paths resolve once when supplied against the request's effective workingDirectory; retained roots do not move on cwd changes. Null and omission are equivalent; [] clears only the explicit SDK contribution. + */ + instructionDirectories?: string[]; + /** + * Explicit discovery-only file-hook roots, separate from callback hooks and filesystem grants. Available without enableConfigDiscovery or enableFileHooks, which controls repository-origin hooks only; hook/managed disables still apply. Relative SDK paths resolve once when supplied against the request's effective workingDirectory; retained roots do not move on cwd changes. Null and omission are equivalent; [] clears only the explicit SDK contribution. + */ + hookDirectories?: string[]; /** * Skill scan directories and descendants excluded from discovery. Supports `~`-relative paths. */ @@ -26966,7 +29416,7 @@ export interface SessionUpdateOptionsParams { */ organizationCustomInstructions?: string; /** - * Whether to enable loading of `.github/hooks/` filesystem hooks. Separate from the SDK callback hook mechanism. + * Whether to enable repository-origin file hooks from `.github/hooks/` and trusted repository-configured roots. Does not control explicit hookDirectories or SDK callback hooks. */ enableFileHooks?: boolean; /** @@ -26978,7 +29428,7 @@ export interface SessionUpdateOptionsParams { */ enableSessionStore?: boolean; /** - * 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. + * Whether skill loading is enabled. Explicit false disables every source, including explicit SDK roots and a bound SDK provider; changing the value invalidates the loaded skill snapshot. When omitted, creation falls back to enableConfigDiscovery unless explicit SDK skill roots or an SDK skill provider are supplied. */ enableSkills?: boolean; contextTier?: OptionsUpdateContextTier; @@ -27004,6 +29454,17 @@ export interface SessionUpdateOptionsResult { */ pluginHookCount?: number; } +/** + * Outcome of an atomic user or repository settings edit. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "SettingsEditResult". + */ +/** @experimental */ +/** @internal */ +export interface SettingsEditResult { + status: SettingsEditStatus; +} /** * User-requested shell execution cancellation handle. * @@ -27312,6 +29773,21 @@ export interface SkillList { */ skills: Skill[]; } +/** + * Side-effect-free planning of one Skill from a materialisation reference rather than a search candidate. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "SkillPlanInstallFromReferenceRequest". + */ +/** @experimental */ +export interface SkillPlanInstallFromReferenceRequest { + contract: CatalogClientContract; + reference: CatalogMaterializationReference; + /** + * Existing local session attached to this connection. + */ + policySessionId: string; +} /** * Side-effect-free planning of one verified Agent Finder Skill candidate. * @@ -27481,7 +29957,7 @@ export interface SkillsDiscoverRequest { */ projectPaths?: string[]; /** - * Optional list of additional skill directory paths to include + * Explicit discovery-only skill roots, not filesystem grants. Available without ambient discovery, subject to skill/managed disables. Relative paths resolve against the request's effective working directory when supplied. Null and omission are equivalent; an empty array contributes no explicit roots. */ skillDirectories?: string[]; /** @@ -27536,6 +30012,10 @@ export interface SkillSetEnabledRequest { */ /** @experimental */ export interface SkillsGetDiscoveryPathsRequest { + /** + * Explicit discovery-only skill roots to enumerate, including configured missing directories. Relative paths resolve against the request's effective working directory when supplied. These are not filesystem grants or preferred creation targets. Null and omission are equivalent. + */ + skillDirectories?: string[]; /** * Optional list of project directory paths. When omitted or empty, only personal and custom directories are returned. */ @@ -29623,6 +32103,162 @@ export interface UserSettingMetadata { */ isDefault: boolean; } +/** + * A single top-level setting subtree to validate and write. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "UserSettingsApplyTopKeyRequest". + */ +/** @experimental */ +export interface UserSettingsApplyTopKeyRequest { + /** + * Canonical path of the setting the caller is editing. Currently informational only. + */ + canonicalPath: string[]; + /** + * Top-level setting name to write. + */ + topKey: string; + /** + * Value to write for the whole top-level key. Omitted when `delete` is true. Explicit null is preserved and validated as null. + */ + value?: JsonValue | null; + /** + * True to remove the top-level key instead of writing it. + */ + delete?: boolean; + /** + * Records that the user chose an update channel explicitly, so the one-time staff channel migration stops reapplying. Honored only for the `autoUpdatesChannel` key. + */ + completeStaffUpdateChannelMigration?: boolean; +} +/** + * Outcome of validating and writing one top-level setting subtree. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "UserSettingsApplyTopKeyResult". + */ +/** @experimental */ +export interface UserSettingsApplyTopKeyResult { + /** + * `ok` when the value was written, `validation-error` when it was rejected before any write, `write-error` when validation passed but the write failed. + */ + status: string; + /** + * Problems found, present only when `status` is `validation-error`. + */ + issues?: UserSettingsValidationIssue[]; + /** + * Why the write failed, present only when `status` is `write-error`. + */ + error?: string | null; +} +/** + * One problem found while validating a setting value. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "UserSettingsValidationIssue". + */ +/** @experimental */ +export interface UserSettingsValidationIssue { + /** + * Where in the settings tree the problem sits, as object keys and array indices. The first element is the top-level key; a whole-value problem is reported as that single key. + */ + path: JsonValue[]; + /** + * Human-readable description of the problem. + */ + message: string; +} +/** + * The overrides and host context to resolve effective runtime settings from. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "UserSettingsBuildRuntimeRequest". + */ +/** @experimental */ +/** @internal */ +export interface UserSettingsBuildRuntimeRequest { + /** + * Settings that take precedence over every document and environment layer, such as the values a host derived from its own command line. Keyed by setting name, with each value carried verbatim, so a host may pass keys this runtime does not recognize. Absent when the host contributes no overrides of its own. + */ + overrides?: { + [k: string]: JsonValue | undefined; + }; + /** + * The directory the settings are being resolved for. This selects the repository-scoped document, so a host resolving settings for a directory other than its own process working directory must pass that directory here. + */ + workingDirectory: string; + /** + * The environment to read settings-bearing variables from. Passed explicitly rather than read from the server process so a host can resolve settings for an environment it is about to create rather than the one it runs in. + */ + environment: { + [k: string]: string | undefined; + }; + /** + * The platform to apply platform-specific defaults for, in the same spelling the host's own runtime reports. + */ + platform: string; +} +/** + * One atomic edit of a canonical setting in settings.json. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "UserSettingsEditRequest". + */ +/** @experimental */ +/** @internal */ +export interface UserSettingsEditRequest { + /** + * Configuration directory to edit. Omitted or empty resolves the server's own home, as in `user.settings.loadForConfigDir`. + */ + configDir?: string; + /** + * Nonempty canonical path segments of the setting to edit, starting with its top-level key. + * + * @minItems 1 + */ + canonicalPath: [string, ...string[]]; + operation: SettingsEditOperation; + /** + * Required boolean, string or number for set, required string for add-item and remove-item, and forbidden for remove. The runtime rejects all other JSON values. + */ + value?: JsonValue; +} +/** + * Selects which settings file paths to resolve. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "UserSettingsGetPathsRequest". + */ +/** @experimental */ +export interface UserSettingsGetPathsRequest { + /** + * Absolute path of a repository root. Supply it to also resolve that repository's two settings file paths; omit it to resolve only the user path. + */ + repoRoot?: string; +} +/** + * On-disk locations of the settings files this runtime reads. A path is reported whether or not the file exists. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "UserSettingsGetPathsResult". + */ +/** @experimental */ +export interface UserSettingsGetPathsResult { + /** + * The user settings file: `settings.json` under the resolved Copilot home. This is the file `user.settings.get`, `user.settings.load`, and `user.settings.set` read and write. + */ + userSettings: string; + /** + * The repository's checked-in settings file, or null when no repository root was supplied. + */ + repoSettings?: string | null; + /** + * The repository's local, untracked settings file, or null when no repository root was supplied. + */ + repoLocalSettings?: string | null; +} /** * Per-key metadata for every known user setting in settings.json, including settings left at their default. Excludes repository- and enterprise-managed overrides. * @@ -29638,6 +32274,538 @@ export interface UserSettingsGetResult { [k: string]: UserSettingMetadata; }; } +/** + * Top-level user-setting keys grouped by who may govern them. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "UserSettingsGovernanceKeysResult". + */ +/** @experimental */ +export interface UserSettingsGovernanceKeysResult { + /** + * Top-level keys a repository may override through `.github/copilot/settings.json` or `.github/copilot/settings.local.json`. + */ + repo: string[]; + /** + * Top-level keys a repository may override through a Claude-compatible repository config. + */ + claudeRepo: string[]; + /** + * Top-level keys an enterprise policy may fix through managed settings. + */ + managed: string[]; +} +/** + * The filesystem and environment facts a user-settings file is resolved from. Passed explicitly rather than read from the runtime's own process, so a host whose configuration directory differs from its process environment resolves the file it means. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "UserSettingsHostContext". + */ +/** @experimental */ +export interface UserSettingsHostContext { + /** + * The Copilot configuration directory, when the host set one. Takes precedence over `COPILOT_HOME` and the home directory. + */ + configDir?: string; + /** + * The user's home directory, used when neither a configuration directory nor `COPILOT_HOME` is set. + */ + homeDirectory: string; + /** + * The host's environment variables. `COPILOT_HOME` is read from here, as are the variables a settings hook may expand. + */ + environment: {}; +} +/** + * A repository to read settings from, and the user layer they are merged over. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "UserSettingsLoadAndMergeRepoRequest". + */ +/** @experimental */ +/** @internal */ +export interface UserSettingsLoadAndMergeRepoRequest { + /** + * Absolute path of the repository root whose settings to read. When this is the user's home directory, the Claude-compatible configuration is skipped, so a home-directory session does not inherit a personal config as if it were repository policy. + */ + repoRoot: string; + /** + * The user's own settings, keyed by setting name. The repository layer is merged over this; a repository may only add to the collection settings it is allowed to influence, so a user setting it does not name survives unchanged. + */ + userSettings: { + [k: string]: JsonValue | undefined; + }; +} +/** + * The settings a consumer should act on inside a repository, together with any diagnostic raised while reading the repository layer. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "UserSettingsLoadAndMergeRepoResult". + */ +/** @experimental */ +/** @internal */ +export interface UserSettingsLoadAndMergeRepoResult { + /** + * The merged settings document, keyed by setting name. Equals the supplied user layer when the repository declares no settings. + */ + settings: { + [k: string]: JsonValue | undefined; + }; + /** + * Why part of the repository document was ignored, as a single human-readable string joining every diagnostic with `; `. Null when it loaded cleanly. The settings that did load are still merged, so a consumer should surface this and continue rather than treat it as a failure. + */ + warning?: string | null; +} +/** + * Selects the configuration directory whose user settings to read. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "UserSettingsLoadForConfigDirRequest". + */ +/** @experimental */ +/** @internal */ +export interface UserSettingsLoadForConfigDirRequest { + /** + * Copilot configuration directory to read the settings document from, taking precedence over the server's own `COPILOT_HOME` and default home. Omit it, or pass an empty string, to read the directory the server resolved for itself. + */ + configDir?: string; +} +/** + * Contents of settings.json as stored, for a caller about to rewrite it. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "UserSettingsLoadForEditResult". + */ +/** @experimental */ +export interface UserSettingsLoadForEditResult { + /** + * `ok` when the file parsed, `missing` when there is no file yet, `invalid` when the file exists but will not parse. + */ + status: string; + /** + * Parsed contents, present only when `status` is `ok`. + */ + settings?: JsonValue; +} +/** + * Selects the repository whose checked-in settings to read. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "UserSettingsLoadRepoRequest". + */ +/** @experimental */ +export interface UserSettingsLoadRepoRequest { + /** + * Absolute path of the repository root whose settings to read. When this is the user's home directory, the Claude-compatible configuration is skipped, so a home-directory session does not inherit a personal config as if it were repository policy. + */ + repoRoot: string; +} +/** + * A repository's settings as stored, together with any diagnostic raised while reading them. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "UserSettingsLoadRepoResult". + */ +/** @experimental */ +export interface UserSettingsLoadRepoResult { + /** + * The repository's settings document, keyed by setting name, with each value carried verbatim. Null when the repository declares no settings. + */ + settings?: { + [k: string]: JsonValue | undefined; + } | null; + /** + * Why part of the document was ignored, as a single human-readable string joining every diagnostic with `; `. Null when the document loaded cleanly. The settings that did load are still reported, so a consumer should surface this and continue rather than treat it as a failure. + */ + warning?: string | null; +} +/** + * The user's stored settings, together with any diagnostic raised while reading them. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "UserSettingsLoadWithWarningResult". + */ +/** @experimental */ +export interface UserSettingsLoadWithWarningResult { + /** + * The stored user settings document, keyed by setting name, with each value carried verbatim. Empty when the file could not be read at all. + */ + settings: { + [k: string]: JsonValue | undefined; + }; + /** + * Why part of the document was ignored, phrased for display to the user. Null when the document loaded cleanly. The settings that did load are still reported, so a consumer may surface this and continue rather than treat it as a failure. + */ + warning?: string | null; +} +/** + * The managed (enterprise policy) layer and the user layer to fold together. The field names carry the layer identity: nothing about a settings object identifies which layer produced it, so a generic base/override pair would let a caller transpose them and silently resolve the governance keys in the user's favour. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "UserSettingsMergeManagedAndUserParams". + */ +/** @experimental */ +/** @internal */ +export interface UserSettingsMergeManagedAndUserParams { + /** + * The managed layer. Its governance keys win, and the per-entry `enabledPlugins` and `extraKnownMarketplaces` control maps are stamped back from it after the union. Omitted when the host is unmanaged. + */ + managed?: JsonValue; + /** + * The user's own settings layer. Wins on every key the managed layer does not govern. Omitted when the user has no settings yet. + */ + user?: JsonValue; +} +/** + * The user layer folded over the managed layer. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "UserSettingsMergeManagedAndUserResult". + */ +/** @experimental */ +export interface UserSettingsMergeManagedAndUserResult { + /** + * The merged settings. An empty object when neither layer was supplied, so an unmanaged host with no user settings governs nothing. + */ + settingsJson: JsonValue; +} +/** + * The user layer and the repository layer to fold together. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "UserSettingsMergeRepoValueParams". + */ +/** @experimental */ +/** @internal */ +export interface UserSettingsMergeRepoValueParams { + /** + * The user's settings, already resolved against any managed layer. + */ + userSettings: JsonValue; + /** + * The repository's checked-in settings layer. + */ + repoSettings: JsonValue; +} +/** + * The repository layer folded into the user layer. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "UserSettingsMergeRepoValueResult". + */ +/** @experimental */ +export interface UserSettingsMergeRepoValueResult { + /** + * The merged settings, with the per-entry managed control maps re-stamped from the runtime's ambient managed policy. + */ + settingsJson: JsonValue; +} +/** + * Selects the configuration directory whose settings document to clean. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "UserSettingsMigrateObsoleteOnDiskRequest". + */ +/** @experimental */ +/** @internal */ +export interface UserSettingsMigrateObsoleteOnDiskRequest { + /** + * The configuration directory holding the settings document to clean. A host that scopes a session to its own Copilot home passes that directory here; omit it to clean the document under the home the server resolved for itself. The home directory and environment are deliberately not caller-supplied: they decide only the settings path root and the two hook-parsing flags, and those describe the process doing the reading rather than the caller. + */ + configDir?: string; +} +/** + * The outcome of the one-time obsolete-settings cleanup. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "UserSettingsMigrateObsoleteOnDiskResult". + */ +/** @experimental */ +/** @internal */ +export interface UserSettingsMigrateObsoleteOnDiskResult { + status: UserSettingsMigrateObsoleteOnDiskStatus; +} +/** + * Selects the configuration directory to run the one-time staff log-level migration against, and supplies the caller's record of whether it has already run. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "UserSettingsMigrateStaffLogLevelOnceRequest". + */ +/** @experimental */ +/** @internal */ +export interface UserSettingsMigrateStaffLogLevelOnceRequest { + context: UserSettingsHostContext; + /** + * When the caller last recorded this migration, as an ISO-8601 timestamp. Supplying it lets the runtime answer `current` without re-running the evaluation. + */ + cachedCompletedAt?: string; +} +/** + * The outcome of the one-time staff log-level migration. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "UserSettingsMigrateStaffLogLevelOnceResult". + */ +/** @experimental */ +/** @internal */ +export interface UserSettingsMigrateStaffLogLevelOnceResult { + status: StaffLogLevelMigrationStatus; + /** + * When the migration was recorded as done, as an ISO-8601 timestamp. Absent only for `ineligible`, which records nothing. + */ + completedAt?: string; + /** + * The settings document as it stands after the migration, keyed by setting name. Present only for `refreshed`, and only when the document could be re-read cleanly. + */ + settings?: {}; +} +/** + * The outcome of the one-time staff update-channel migration. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "UserSettingsMigrateStaffUpdateChannelOnceResult". + */ +/** @experimental */ +/** @internal */ +export interface UserSettingsMigrateStaffUpdateChannelOnceResult { + status: StaffUpdateChannelMigrationStatus; + /** + * When the migration was recorded as done, as an ISO-8601 timestamp. Absent only for `ineligible`, which records nothing. + */ + completedAt?: string; +} +/** + * Settings object whose key names should be canonicalized. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "UserSettingsNormalizeRequest". + */ +/** @experimental */ +export interface UserSettingsNormalizeRequest { + /** + * Free-form settings object, possibly using legacy or snake_case key names. + */ + settings: JsonValue; +} +/** + * Settings object with canonical key names. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "UserSettingsNormalizeResult". + */ +/** @experimental */ +export interface UserSettingsNormalizeResult { + /** + * The same settings with every legacy or snake_case key renamed to its canonical spelling. + */ + settings: JsonValue; +} +/** + * The on-disk location of the user's settings document. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "UserSettingsPathResult". + */ +/** @experimental */ +export interface UserSettingsPathResult { + /** + * Absolute path to the settings file this runtime reads and writes. Normally `settings.json` under the Copilot configuration directory; an extensionless legacy `settings` file is reported instead when it exists and `settings.json` does not. + */ + path: string; +} +/** + * One setting that will not take effect, and why. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "UserSettingsProblem". + */ +/** @experimental */ +export interface UserSettingsProblem { + /** + * Stable identifier for this problem within the reported set, so a caller can track a selection across refreshes. + */ + id: string; + /** + * What is wrong in machine-readable form, for a caller that wants to branch rather than display. + */ + kind: string; + /** + * Coarse grouping for the problem, for a caller that presents problems in sections. + */ + category: string; + source: UserSettingsProblemLocation; + /** + * The offending key as written in the file, when the problem is attributable to a single key. Absent when the whole file is at fault. + */ + rawKey?: string; + /** + * Why the setting will not take effect. Never contains the setting's value, which may be a token or another secret. + */ + explanation: string; +} +/** + * The settings layer a problem was found in. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "UserSettingsProblemLocation". + */ +/** @experimental */ +export interface UserSettingsProblemLocation { + /** + * Which layer this is: `user`, `repo`, or `local`. + */ + kind: string; + /** + * The caller-supplied display label for the layer. + */ + label: string; + /** + * Absolute path of the file backing this layer. + */ + filePath: string; +} +/** + * One already-loaded settings layer to diagnose. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "UserSettingsProblemSource". + */ +/** @experimental */ +export interface UserSettingsProblemSource { + /** + * Which layer this is: `user`, `repo`, or `local`. + */ + kind: string; + /** + * Caller-chosen display label for the layer, echoed back on each problem so a caller can group by layer without re-deriving its own naming. + */ + label: string; + /** + * Absolute path of the file backing this layer. + */ + filePath: string; + /** + * The layer's settings exactly as loaded, unnormalized. Empty when the file is missing or failed to parse. + */ + config?: { + [k: string]: unknown | undefined; + }; + /** + * True when the file itself failed to parse, so its keys could not be read at all. + */ + invalid?: boolean; +} +/** + * The settings layers to diagnose. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "UserSettingsProblemsRequest". + */ +/** @experimental */ +export interface UserSettingsProblemsRequest { + /** + * Layers to diagnose, in the precedence order the caller applies them. + */ + sources?: UserSettingsProblemSource[]; +} +/** + * The settings that will not take effect across the given layers. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "UserSettingsProblemsResult". + */ +/** @experimental */ +export interface UserSettingsProblemsResult { + /** + * Each setting that will not take effect, and why. Empty when every layer is healthy. + */ + problems?: UserSettingsProblem[]; +} +/** + * Selects the configuration directory to run the one-time staff model-selection reset against, and supplies the caller's record of whether it has already run. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "UserSettingsResetStaffModelSelectionOnceRequest". + */ +/** @experimental */ +/** @internal */ +export interface UserSettingsResetStaffModelSelectionOnceRequest { + context: UserSettingsHostContext; + /** + * Whether the signed-in account is a staff account. The reset applies only to staff, so anything other than `true` returns `didReset: false` without touching the settings document. + */ + staff?: boolean; + /** + * When the caller last recorded this reset, as an ISO-8601 timestamp. A timestamp at or after the cutoff means the reset already ran and is returned unchanged. + */ + staffModelResetAt?: string; +} +/** + * The outcome of the one-time staff model-selection reset. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "UserSettingsResetStaffModelSelectionOnceResult". + */ +/** @experimental */ +/** @internal */ +export interface UserSettingsResetStaffModelSelectionOnceResult { + /** + * Whether the persisted model selection was cleared. Only `true` means the settings document changed and the host should adopt the returned settings. + */ + didReset: boolean; + /** + * The timestamp the host should now record, as an ISO-8601 timestamp. Carries the caller's own value back unchanged when nothing was reset. + */ + resetAt?: string; + /** + * The cutoff this reset is gated on, as an ISO-8601 timestamp. A recorded timestamp at or after it means the reset is already done. + */ + cutoff: string; + /** + * The settings document as it stands after the reset, keyed by setting name. Present only when `didReset` is true. + */ + settings?: {}; +} +/** + * One hook to disable or re-enable. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "UserSettingsSetHookDisabledRequest". + */ +/** @experimental */ +/** @internal */ +export interface UserSettingsSetHookDisabledRequest { + /** + * Path of the settings.json to rewrite. + */ + settingsPath: string; + /** + * Durable content-hash key of the hook, as `hooks.loadResources` reports it. + */ + disableKey: string; + /** + * True adds the key to `disabledHooks`, false removes it. + */ + disabled: boolean; +} +/** + * Outcome of rewriting the `disabledHooks` setting. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "UserSettingsSetHookDisabledResult". + */ +/** @experimental */ +/** @internal */ +export interface UserSettingsSetHookDisabledResult { + /** + * `ok` when the setting was written, `invalid` when settings.json could not be parsed and was left alone, `error` when the write itself failed. + */ + status: string; + /** + * Why the write failed. Absent when the status is `ok`. + */ + errorMessage?: string; +} /** * 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. * @@ -29651,6 +32819,122 @@ export interface UserSettingsSetRequest { */ settings: JsonValue; } +/** + * Selects the configuration directory whose user settings document should be inspected for unrecognized top-level keys. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "UserSettingsUnknownKeysWarningRequest". + */ +/** @experimental */ +/** @internal */ +export interface UserSettingsUnknownKeysWarningRequest { + context: UserSettingsHostContext; +} +/** + * The unrecognized top-level keys found in the user settings document, already phrased for display. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "UserSettingsUnknownKeysWarningResult". + */ +/** @experimental */ +/** @internal */ +export interface UserSettingsUnknownKeysWarningResult { + /** + * The warning to show the user, naming the unrecognized keys and the file they were found in. Absent when every top-level key is recognized, and also when the document is missing or could not be read, because an unknown-key list taken from a document the runtime could not load would name keys that may not be there. + */ + warning?: string; +} +/** + * Names the user setting to merge into, and the partial object to merge. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "UserSettingsUpdateRequest". + */ +/** @experimental */ +/** @internal */ +export interface UserSettingsUpdateRequest { + /** + * Top-level user-setting key to merge into, such as `extensions`. + */ + key: string; + /** + * Value to store for `key`. When both the stored value and this value are objects, their fields are shallow-merged and fields this object does not name are preserved. Otherwise this value replaces the stored value. + */ + value: JsonValue; + /** + * Copilot configuration directory to write the settings document in, taking precedence over the server's own `COPILOT_HOME` and default home. Omit it, or pass an empty string, to use the directory the server resolved for itself. + */ + configDir?: string; + /** + * Single-component name for the settings document at `/settings/.json`; path separators, `.` and `..` are rejected. Omit it, or pass an empty string, to use the main settings document. + */ + subDir?: string; +} +/** + * The stored value of the user setting after the merge. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "UserSettingsUpdateResult". + */ +/** @experimental */ +/** @internal */ +export interface UserSettingsUpdateResult { + /** + * The key's stored value after the write: the merged object when both the stored value and the supplied value are objects, otherwise the supplied value. + */ + value: JsonValue; +} +/** + * A single top-level setting subtree to validate. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "UserSettingsValidateRequest". + */ +/** @experimental */ +export interface UserSettingsValidateRequest { + /** + * Top-level setting name to validate. + */ + topKey: string; + /** + * Value to validate. Omitted when `delete` is true. + */ + value?: JsonValue; + /** + * True to validate a removal of this key. The current settings file is not read. + */ + delete?: boolean; +} +/** + * Outcome of validating one top-level setting subtree. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "UserSettingsValidateResult". + */ +/** @experimental */ +export interface UserSettingsValidateResult { + /** + * True when the subtree is valid. + */ + ok: boolean; + /** + * Problems found, empty when `ok` is true. + */ + issues?: UserSettingsValidationIssue[]; +} +/** + * The whole content to store in the user settings file. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "UserSettingsWriteAllRequest". + */ +/** @experimental */ +export interface UserSettingsWriteAllRequest { + /** + * The settings object to store. It replaces the file's entire content, so a key absent here is removed. + */ + settings: JsonValue; +} /** * Current sharing status and shareable GitHub URL for a session. * @@ -31281,6 +34565,102 @@ export interface WorkspacesWriteAutopilotObjectiveResult { */ operation: string; } +/** + * The effective sandbox settings, with every unset value resolved to its default. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "SandboxSettingsApplyDefaultsResult". + */ +/** @experimental */ +export interface SandboxSettingsApplyDefaultsResult { + /** + * One resolved sandbox setting's value, carried verbatim. + */ + [k: string]: JsonValue | undefined; +} +/** + * The stored user settings document, keyed by setting name. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "UserSettingsLoadForConfigDirResult". + */ +/** @experimental */ +export interface UserSettingsLoadForConfigDirResult { + /** + * One setting's stored value, carried verbatim. + */ + [k: string]: JsonValue | undefined; +} +/** + * The effective runtime settings, keyed by setting name. Keys this runtime does not recognize are preserved rather than dropped, so a caller reading a document written by a newer runtime keeps every value. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "UserSettingsBuildRuntimeResult". + */ +/** @experimental */ +export interface UserSettingsBuildRuntimeResult { + /** + * One resolved setting's effective value, carried verbatim. + */ + [k: string]: JsonValue | undefined; +} +/** + * The resolved gates, keyed by flag name. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "FeatureFlagsResolveStartupGatesResult". + */ +/** @experimental */ +export interface FeatureFlagsResolveStartupGatesResult { + /** + * Whether the gate is on for this installation. + */ + [k: string]: boolean | undefined; +} +/** + * The resolved feature flags, keyed by flag name. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "FeatureFlagsResolveResult". + */ +/** @experimental */ +export interface FeatureFlagsResolveResult { + /** + * Whether the flag is enabled for this population. + */ + [k: string]: boolean | undefined; +} +/** + * Ordered managed permission verdicts and the supplied policy's fail-closed posture. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "ManagedSettingsPermissionsEvaluateResult". + */ +/** @experimental */ +export interface ManagedSettingsPermissionsEvaluateResult { + /** + * One verdict per input operation, in the same order, including duplicates. + */ + results: ManagedPermissionEvaluation[]; + /** + * Managed permission policy could not be determined; every operation is denied. + */ + failClosed: boolean; +} +/** + * Managed policy context and ordered operations for pure, sessionless evaluation. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "ManagedSettingsPermissionsEvaluateRequest". + */ +/** @experimental */ +export interface ManagedSettingsPermissionsEvaluateRequest { + context: ManagedPermissionsContext; + /** + * Operations to evaluate, preserving input order and duplicates. An empty batch is valid. + */ + operations: ManagedPermissionOperation[]; +} /** * The resident session the application has materialized on its own connection. * @@ -31930,6 +35310,159 @@ export interface InstallationsConfirmRequest { /** Create typed server-scoped RPC methods (no session required). */ export function createServerRpc(connection: MessageConnection) { return { + /** @experimental */ + sandbox: { + /** @experimental */ + proxyCa: { + /** + * Reports whether this runtime process can offer installing the sandbox credential-proxy certificate authority into OS trust. + * + * @returns True when this process can offer installing the sandbox credential-proxy certificate authority into OS trust. + */ + checkInstallationSupport: async (): Promise => + connection.sendRequest("sandbox.proxyCa.checkInstallationSupport", {}), + /** + * Reports whether this sandbox configuration uses the credential proxy and can need certificate-authority setup. + * + * @param params Identifies the credential hosts that the persistent certificate authority of the sandbox credential proxy must cover. The runtime always adds the hosts from the saved user settings. + * + * @returns True when the sandbox configuration uses the credential proxy and can need certificate-authority setup. + */ + checkRelevance: async (params: SandboxProxyCaRequest): Promise => + connection.sendRequest("sandbox.proxyCa.checkRelevance", params), + /** + * Reports whether the persistent certificate authority of the sandbox credential proxy exists, whether OS trust includes it, and whether it must be rotated. Changes nothing. + * + * @param params Identifies the credential hosts that the persistent certificate authority of the sandbox credential proxy must cover. The runtime always adds the hosts from the saved user settings. + * + * @returns Status of the persistent certificate authority of the sandbox credential proxy. + */ + getStatus: async (params: SandboxProxyCaRequest): Promise => + connection.sendRequest("sandbox.proxyCa.getStatus", params), + /** + * Creates the persistent certificate authority of the sandbox credential proxy if none is stored, without changing OS trust, and returns the path of its public certificate. Keeps an existing certificate authority, even one that must be rotated. Fails where OS trust is unsupported. Trust it with sandbox.proxyCa.trust: the CLI trusts only the hosts in the saved user settings, so it refuses a certificate authority that also covers hosts from sandboxConfig. + * + * @param params Identifies the credential hosts that the persistent certificate authority of the sandbox credential proxy must cover. The runtime always adds the hosts from the saved user settings. + * + * @returns Result of creating the persistent certificate authority of the sandbox credential proxy. + */ + create: async (params: SandboxProxyCaRequest): Promise => + connection.sendRequest("sandbox.proxyCa.create", params), + /** + * Replaces the persistent certificate authority of the sandbox credential proxy with a new one for the current credential hosts. If OS trust included the old one, removes it and trusts the new one, which can show an OS authentication prompt. Running sandboxed tools keep the old certificate authority until they restart. + * + * @param params Identifies the credential hosts that the persistent certificate authority of the sandbox credential proxy must cover. The runtime always adds the hosts from the saved user settings. + * + * @returns Status of the persistent certificate authority of the sandbox credential proxy. + */ + rotate: async (params: SandboxProxyCaRequest): Promise => + connection.sendRequest("sandbox.proxyCa.rotate", params), + /** + * Adds the persistent certificate authority of the sandbox credential proxy to OS trust, so sandboxed clients that read only OS trust accept the proxy. Call create first. Refuses a certificate authority that is not constrained to the current credential hosts. Can show an OS authentication prompt. + * + * @param params Identifies the credential hosts that the persistent certificate authority of the sandbox credential proxy must cover. The runtime always adds the hosts from the saved user settings. + * + * @returns Status of the persistent certificate authority of the sandbox credential proxy. + */ + trust: async (params: SandboxProxyCaRequest): Promise => + connection.sendRequest("sandbox.proxyCa.trust", params), + /** + * Removes the persistent certificate authority of the sandbox credential proxy from OS trust. Keeps the stored certificate authority. Can show an OS authentication prompt. Sandboxed clients that read only OS trust then reject the proxy; clients that read the per-process certificate bundle continue to work. + * + * @returns Status of the persistent certificate authority of the sandbox credential proxy. + */ + remove: async (): Promise => + connection.sendRequest("sandbox.proxyCa.remove", {}), + }, + /** + * Reports whether the host running this runtime can run the command sandbox, without starting a session or spawning a sandboxed command. + * + * @returns Whether the host running this runtime can run the command sandbox. The runtime checks `supported` once per process. A capability answer can change while the process runs, for example after the user installs a missing package. + */ + getHostSupport: async (): Promise => + connection.sendRequest("sandbox.getHostSupport", {}), + }, + /** @experimental */ + user: { + /** @experimental */ + settings: { + /** + * Lists every known user setting from settings.json, each with its effective value, its default, and whether it is at the default — so settings the user has never set still appear with their default value. Does not include repository- or enterprise-managed overrides that the runtime layers on top at session time. + * + * @returns Per-key metadata for every known user setting in settings.json, including settings left at their default. Excludes repository- and enterprise-managed overrides. + */ + get: async (): Promise => + connection.sendRequest("user.settings.get", {}), + /** + * Writes one or more user settings to settings.json, replacing each provided top-level key. A key whose value is null is removed. + * + * @param params 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. + */ + set: async (params: UserSettingsSetRequest): Promise => + connection.sendRequest("user.settings.set", params), + }, + }, + /** @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. `managedSettings.resolve` returns the same device settings together with the account's server policy. + * + * @returns Validated device-managed settings discovered before a session exists. + */ + read: async (): Promise => + connection.sendRequest("managedSettings.read", {}), + /** + * Force-refreshes enterprise managed settings for every account: wipes the persistent server-policy cache (the whole `/managed-settings` directory) and drops this runtime process's in-memory retained server policy. It does not itself fetch policy — the effect is that the next time a session resolves managed settings for an account, that resolution re-fetches the account's org policy from the network instead of serving a cached response. Note that `managedSettings.read` returns only device/MDM settings and never triggers the account server-policy fetch, so a host implementing "sync account policy" should call `managedSettings.resolve` or start a fresh session resolution rather than treat a subsequent `managedSettings.read` as the refreshed org policy. Mirrors the invalidation a sign-out performs, broadened from the one signing-out account to all of them; device/MDM layers describe the machine, not the account, and are left untouched. Rejects if the on-disk cache cannot be removed. + */ + clearCache: async (): Promise => + connection.sendRequest("managedSettings.clearCache", {}), + /** + * Resolves the effective enterprise managed settings without a session, from the device channel and, when an account is available, the account's server policy through the same per-account cache sessions use. A cached server policy less than an hour old is used without a fetch; otherwise the policy is fetched, and when the fetch fails a cached policy up to 24 hours old is used instead, unless `forceRemoteSettingsRefresh` requires a live fetch. With no account requested or signed in, it reports device policy only; signing out removes the account's cached policy. It can fetch server policy over the network when the cache is stale, so call it off latency-critical paths such as startup rather than before listing models. The policy helper is not run. `layers` lists each channel's document before merging, and `values` and `meta` carry typed effective values and their lock state for the keys typed so far. + * + * @param params Optional opaque account selection or GitHub token whose managed settings are resolved. + * + * @returns Effective enterprise managed settings for an account, resolved without a session. + */ + resolve: async (params: ManagedSettingsResolveRequest): Promise => + connection.sendRequest("managedSettings.resolve", params), + /** @experimental */ + permissions: { + /** + * Pure, sessionless evaluation of operations against a supplied permissionsContext. Performs no policy discovery, I/O, prompting, navigation, permission grants, or sandbox changes. Obtain the context from managedSettings.resolve or session.managedSettings.get, or reuse an in-memory context. Results preserve input order and duplicates. Consumers must block deny, obtain approval for ask, and use their normal permission flow for unmanaged. Unknown policy returns failClosed with every operation denied. This is not a substitute for user permissions or other security controls. + * + * @param params Managed policy context and ordered operations for pure, sessionless evaluation. + * + * @returns Ordered managed permission verdicts and the supplied policy's fail-closed posture. + */ + evaluate: async (params: ManagedSettingsPermissionsEvaluateRequest): Promise => + connection.sendRequest("managedSettings.permissions.evaluate", params), + }, + /** + * Returns the managed-settings authoring JSON schema with descriptive `x-composition` annotations aligned with the shared settings-engine vocabulary. These annotations are not a complete runtime composition contract: model, effortLevel, and contextTier remain coupled. Use `managedSettings.compose` for the runtime's effective result. Performs no I/O. + * + * @returns The authoring JSON schema for managed settings recognized by this runtime. + */ + schema: async (): Promise => + connection.sendRequest("managedSettings.schema", {}), + /** + * Validates a candidate managed-settings document the way the runtime validates delivered policy, without applying it. Reports errors that would reject the document, warnings for content the runtime ignores, and the canonical document it would apply. Document text nested more than 64 levels deep is rejected. Performs no I/O. + * + * @param params A candidate managed-settings document to validate without applying it. + * + * @returns Result of validating a managed-settings document. + */ + validate: async (params: ManagedSettingsValidateRequest): Promise => + connection.sendRequest("managedSettings.validate", params), + /** + * Merges candidate managed-settings documents for the device, server, and policy-helper channels into the effective settings the runtime would enforce on this host, using the same precedence and composition rules as live resolution, without applying them. Like live resolution, a server's advisory sandbox force-enable is declined on a host that cannot run the sandbox. Does not fetch policy or read policy files, but may perform blocking OS or subprocess probes for sandbox support. Preview documents are limited to 1 MiB and 64 levels of nesting. + * + * @param params Candidate managed-settings documents to merge without applying them. + * + * @returns The effective managed settings the runtime would enforce for the given documents, in the same shape `managedSettings.resolve` returns. + */ + compose: async (params: ManagedSettingsComposeRequest): Promise => + connection.sendRequest("managedSettings.compose", params), + }, /** @experimental */ environments: { /** @@ -32042,60 +35575,16 @@ export function createServerRpc(connection: MessageConnection) { connection.sendRequest("models.getBuiltInCatalog", {}), }, /** @experimental */ - sandbox: { + contentExclusion: { /** - * Reports whether the host running this runtime can run the command sandbox, without starting a session or spawning a sandboxed command. + * Validates candidate content-exclusion policies and returns them normalized, without starting a session. This runs the same parser the runtime's own policy enforcement uses, so a host cannot accept a policy set the runtime would later reject, nor reject one it would have honored. Call it when policies arrive from outside the runtime — a command-line flag, a configuration file, a host's own settings — and must be checked before a session exists to apply them to. A malformed document fails with a message prefixed `Invalid JSON: `; a well-formed document that violates the policy schema fails with the offending field paths. * - * @returns Whether the host running this runtime can run the command sandbox. The runtime checks `supported` once per process. A capability answer can change while the process runs, for example after the user installs a missing package. + * @param params Candidate content-exclusion policies to validate, as the raw JSON text a host received. The text is supplied unparsed so that a syntax error and a schema violation are both reported by the runtime's own validator rather than by the caller's JSON parser. + * + * @returns The accepted policies, parsed and normalized. A rejected input is reported as an error rather than in this result. */ - getHostSupport: async (): Promise => - connection.sendRequest("sandbox.getHostSupport", {}), - /** @experimental */ - proxyCa: { - /** - * Reports whether the persistent certificate authority of the sandbox credential proxy exists, whether OS trust includes it, and whether it must be rotated. Changes nothing. - * - * @param params Identifies the credential hosts that the persistent certificate authority of the sandbox credential proxy must cover. The runtime always adds the hosts from the saved user settings. - * - * @returns Status of the persistent certificate authority of the sandbox credential proxy. - */ - getStatus: async (params: SandboxProxyCaRequest): Promise => - connection.sendRequest("sandbox.proxyCa.getStatus", params), - /** - * Creates the persistent certificate authority of the sandbox credential proxy if none is stored, without changing OS trust, and returns the path of its public certificate. Keeps an existing certificate authority, even one that must be rotated. Fails where OS trust is unsupported. Trust it with sandbox.proxyCa.trust: the CLI trusts only the hosts in the saved user settings, so it refuses a certificate authority that also covers hosts from sandboxConfig. - * - * @param params Identifies the credential hosts that the persistent certificate authority of the sandbox credential proxy must cover. The runtime always adds the hosts from the saved user settings. - * - * @returns Result of creating the persistent certificate authority of the sandbox credential proxy. - */ - create: async (params: SandboxProxyCaRequest): Promise => - connection.sendRequest("sandbox.proxyCa.create", params), - /** - * Replaces the persistent certificate authority of the sandbox credential proxy with a new one for the current credential hosts. If OS trust included the old one, removes it and trusts the new one, which can show an OS authentication prompt. Running sandboxed tools keep the old certificate authority until they restart. - * - * @param params Identifies the credential hosts that the persistent certificate authority of the sandbox credential proxy must cover. The runtime always adds the hosts from the saved user settings. - * - * @returns Status of the persistent certificate authority of the sandbox credential proxy. - */ - rotate: async (params: SandboxProxyCaRequest): Promise => - connection.sendRequest("sandbox.proxyCa.rotate", params), - /** - * Adds the persistent certificate authority of the sandbox credential proxy to OS trust, so sandboxed clients that read only OS trust accept the proxy. Call create first. Refuses a certificate authority that is not constrained to the current credential hosts. Can show an OS authentication prompt. - * - * @param params Identifies the credential hosts that the persistent certificate authority of the sandbox credential proxy must cover. The runtime always adds the hosts from the saved user settings. - * - * @returns Status of the persistent certificate authority of the sandbox credential proxy. - */ - trust: async (params: SandboxProxyCaRequest): Promise => - connection.sendRequest("sandbox.proxyCa.trust", params), - /** - * Removes the persistent certificate authority of the sandbox credential proxy from OS trust. Keeps the stored certificate authority. Can show an OS authentication prompt. Sandboxed clients that read only OS trust then reject the proxy; clients that read the per-process certificate bundle continue to work. - * - * @returns Status of the persistent certificate authority of the sandbox credential proxy. - */ - remove: async (): Promise => - connection.sendRequest("sandbox.proxyCa.remove", {}), - }, + validatePolicies: async (params: ContentExclusionValidatePoliciesRequest): Promise => + connection.sendRequest("contentExclusion.validatePolicies", params), }, /** @experimental */ tools: { @@ -32336,6 +35825,27 @@ export function createServerRpc(connection: MessageConnection) { connection.sendRequest("extensions.disable", params), }, /** @experimental */ + instructions: { + /** + * Discovers instruction sources across user, repository, and plugin sources. + * + * @param params Optional project paths to include in instruction discovery. + * + * @returns Instruction sources discovered across user, repository, and plugin sources. + */ + discover: async (params: InstructionsDiscoverRequest): Promise => + connection.sendRequest("instructions.discover", params), + /** + * Returns the canonical files and directories where a client may create custom instructions that the runtime will recognize, including ones that do not exist yet. Repository targets become active once created. + * + * @param params Optional project paths to include when enumerating instruction discovery targets. + * + * @returns Canonical files and directories where custom instructions can be created so the runtime will recognize them. + */ + getDiscoveryPaths: async (params: InstructionsGetDiscoveryPathsRequest): Promise => + connection.sendRequest("instructions.getDiscoveryPaths", params), + }, + /** @experimental */ skills: { /** * Plans installation of a verified Agent Finder Skill candidate without writing files. The returned review is safe to present to a user and installing always leaves the Skill disabled until separately enabled. @@ -32346,6 +35856,15 @@ export function createServerRpc(connection: MessageConnection) { */ planInstall: async (params: SkillPlanInstallRequest): Promise => connection.sendRequest("skills.planInstall", params), + /** + * Plans installation of the Skill pinned by an Agent Finder materialisation reference without writing files. The runtime retrieves the descriptor only from its fixed Agent Finder authority and applies the same verification as skills.planInstall; the review shows only verified data. Apply the returned plan with skills.applyInstall. + * + * @param params Side-effect-free planning of one Skill from a materialisation reference rather than a search candidate. + * + * @returns Skill installation management result with the honoured contract, or a typed refusal. + */ + planInstallFromReference: async (params: SkillPlanInstallFromReferenceRequest): Promise => + connection.sendRequest("skills.planInstallFromReference", params), /** * Consumes one verified Skill installation plan, requests explicit human consent through installations.confirm on the original connection, then revalidates and installs the Skill disabled. * @@ -32619,27 +36138,6 @@ export function createServerRpc(connection: MessageConnection) { connection.sendRequest("agents.getDiscoveryPaths", params), }, /** @experimental */ - instructions: { - /** - * Discovers instruction sources across user, repository, and plugin sources. - * - * @param params Optional project paths to include in instruction discovery. - * - * @returns Instruction sources discovered across user, repository, and plugin sources. - */ - discover: async (params: InstructionsDiscoverRequest): Promise => - connection.sendRequest("instructions.discover", params), - /** - * Returns the canonical files and directories where a client may create custom instructions that the runtime will recognize, including ones that do not exist yet. Repository targets become active once created. - * - * @param params Optional project paths to include when enumerating instruction discovery targets. - * - * @returns Canonical files and directories where custom instructions can be created so the runtime will recognize them. - */ - getDiscoveryPaths: async (params: InstructionsGetDiscoveryPathsRequest): Promise => - connection.sendRequest("instructions.getDiscoveryPaths", params), - }, - /** @experimental */ commands: { /** * Lists the well-known built-in slash commands that work as the first message in a new session (e.g. /plan, /env), without requiring an active session. Commands that depend on session state, authentication, or a synced session are omitted. @@ -32650,75 +36148,6 @@ export function createServerRpc(connection: MessageConnection) { connection.sendRequest("commands.list", {}), }, /** @experimental */ - user: { - /** @experimental */ - settings: { - /** - * Lists every known user setting from settings.json, each with its effective value, its default, and whether it is at the default — so settings the user has never set still appear with their default value. Does not include repository- or enterprise-managed overrides that the runtime layers on top at session time. - * - * @returns Per-key metadata for every known user setting in settings.json, including settings left at their default. Excludes repository- and enterprise-managed overrides. - */ - get: async (): Promise => - connection.sendRequest("user.settings.get", {}), - /** - * Writes one or more user settings to settings.json, replacing each provided top-level key. A key whose value is null is removed. - * - * @param params 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. - */ - set: async (params: UserSettingsSetRequest): Promise => - connection.sendRequest("user.settings.set", params), - }, - }, - /** @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. `managedSettings.resolve` returns the same device settings together with the account's server policy. - * - * @returns Validated device-managed settings discovered before a session exists. - */ - read: async (): Promise => - connection.sendRequest("managedSettings.read", {}), - /** - * Force-refreshes enterprise managed settings for every account: wipes the persistent server-policy cache (the whole `/managed-settings` directory) and drops this runtime process's in-memory retained server policy. It does not itself fetch policy — the effect is that the next time a session resolves managed settings for an account, that resolution re-fetches the account's org policy from the network instead of serving a cached response. Note that `managedSettings.read` returns only device/MDM settings and never triggers the account server-policy fetch, so a host implementing "sync account policy" should call `managedSettings.resolve` or start a fresh session resolution rather than treat a subsequent `managedSettings.read` as the refreshed org policy. Mirrors the invalidation a sign-out performs, broadened from the one signing-out account to all of them; device/MDM layers describe the machine, not the account, and are left untouched. Rejects if the on-disk cache cannot be removed. - */ - clearCache: async (): Promise => - connection.sendRequest("managedSettings.clearCache", {}), - /** - * Resolves the effective enterprise managed settings without a session, from the device channel and, when an account is available, the account's server policy through the same per-account cache sessions use. A cached server policy less than an hour old is used without a fetch; otherwise the policy is fetched, and when the fetch fails a cached policy up to 24 hours old is used instead, unless `forceRemoteSettingsRefresh` requires a live fetch. With no account requested or signed in, it reports device policy only; signing out removes the account's cached policy. It can fetch server policy over the network when the cache is stale, so call it off latency-critical paths such as startup rather than before listing models. The policy helper is not run. `layers` lists each channel's document before merging, and `values` and `meta` carry typed effective values and their lock state for the keys typed so far. - * - * @param params Optional opaque account selection or GitHub token whose managed settings are resolved. - * - * @returns Effective enterprise managed settings for an account, resolved without a session. - */ - resolve: async (params: ManagedSettingsResolveRequest): Promise => - connection.sendRequest("managedSettings.resolve", params), - /** - * Returns the managed-settings authoring JSON schema with descriptive `x-composition` annotations aligned with the shared settings-engine vocabulary. These annotations are not a complete runtime composition contract: model, effortLevel, and contextTier remain coupled. Use `managedSettings.compose` for the runtime's effective result. Performs no I/O. - * - * @returns The authoring JSON schema for managed settings recognized by this runtime. - */ - schema: async (): Promise => - connection.sendRequest("managedSettings.schema", {}), - /** - * Validates a candidate managed-settings document the way the runtime validates delivered policy, without applying it. Reports errors that would reject the document, warnings for content the runtime ignores, and the canonical document it would apply. Document text nested more than 64 levels deep is rejected. Performs no I/O. - * - * @param params A candidate managed-settings document to validate without applying it. - * - * @returns Result of validating a managed-settings document. - */ - validate: async (params: ManagedSettingsValidateRequest): Promise => - connection.sendRequest("managedSettings.validate", params), - /** - * Merges candidate managed-settings documents for the device, server, and policy-helper channels into the effective settings the runtime would enforce on this host, using the same precedence and composition rules as live resolution, without applying them. Like live resolution, a server's advisory sandbox force-enable is declined on a host that cannot run the sandbox. Does not fetch policy or read policy files, but may perform blocking OS or subprocess probes for sandbox support. Preview documents are limited to 1 MiB and 64 levels of nesting. - * - * @param params Candidate managed-settings documents to merge without applying them. - * - * @returns The effective managed settings the runtime would enforce for the given documents, in the same shape `managedSettings.resolve` returns. - */ - compose: async (params: ManagedSettingsComposeRequest): Promise => - connection.sendRequest("managedSettings.compose", params), - }, - /** @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. @@ -33047,6 +36476,554 @@ export function createServerRpc(connection: MessageConnection) { */ export function createInternalServerRpc(connection: MessageConnection) { return { + /** @experimental */ + sandbox: { + /** @experimental */ + proxySecret: { + /** + * Reports where this runtime keeps the sandbox proxy password: the credential store directory and the entry identifier within it. Both are derived from the configuration directory this runtime was started with, so a host does not resolve them itself. Read this to recognize a `${secret:…}` value in stored sandbox settings as this runtime's own proxy password rather than a reference the user wrote by hand. Marked internal because it exists to carry a CLI call site off the napi boundary onto the SDK contract. + * + * @returns Where this runtime keeps the sandbox proxy password. + */ + getLocation: async (): Promise => + connection.sendRequest("sandbox.proxySecret.getLocation", {}), + /** + * Reads the stored sandbox proxy password from the credential store `sandbox.proxySecret.getLocation` names, preferring the OS keychain and falling back to the on-disk store. Reports an absent value rather than failing when no entry exists. Marked internal because it exists to carry a CLI call site off the napi boundary onto the SDK contract. + * + * @returns The stored sandbox proxy password, if one is present. + */ + get: async (): Promise => + connection.sendRequest("sandbox.proxySecret.get", {}), + /** + * Stores the sandbox proxy password in the credential store `sandbox.proxySecret.getLocation` names, replacing any existing entry. Reports whether the OS keychain accepted it or it fell back to the on-disk store. Marked internal because it exists to carry a CLI call site off the napi boundary onto the SDK contract. + * + * @param params The sandbox proxy password to store. + * + * @returns Where the sandbox proxy password was stored. + */ + save: async (params: SandboxProxySecretSaveRequest): Promise => + connection.sendRequest("sandbox.proxySecret.save", params), + /** + * Removes the stored sandbox proxy password from the credential store `sandbox.proxySecret.getLocation` names. Succeeds when no entry exists, so it is safe to call while clearing a proxy password whose origin is unknown. Marked internal because it exists to carry a CLI call site off the napi boundary onto the SDK contract. + */ + delete: async (): Promise => + connection.sendRequest("sandbox.proxySecret.delete", {}), + }, + /** @experimental */ + settings: { + /** + * Resolves a stored sandbox settings object into the effective one, filling every value the user left unset with its default. Read this to render or reason about the sandbox configuration a session would actually run under, rather than the sparse document the user wrote. + * + * @param params The stored sandbox settings to resolve against the defaults. + * + * @returns The effective sandbox settings, with every unset value resolved to its default. + */ + applyDefaults: async (params: SandboxApplyDefaultsRequest): Promise => + connection.sendRequest("sandbox.settings.applyDefaults", params), + }, + /** + * Raises a user's sandbox settings to an administrator's managed sandbox floor and returns the result a session should actually run under. The floor can only tighten: an enterprise force-on is reflected even when the user's own `enabled` is false, and a user cannot lower a setting the floor raised. The result is normalized through the declared sandbox shape, so a key reorder or an undeclared passthrough key is not mistaken for a policy change by callers that diff two results. A host must call this rather than merge the two layers itself, because reimplementing the direction of the fold is what turns an administrator's floor into a suggestion. + * + * @param params The user's sandbox settings and the managed sandbox floor to raise them to. + * + * @returns The user's sandbox settings raised to the managed floor. + */ + resolveFlooredConfig: async (params: SandboxResolveFlooredConfigParams): Promise => + connection.sendRequest("sandbox.resolveFlooredConfig", params), + /** + * Builds the effective filesystem policy one `@`-file picker checks its suggestions against, so Git discovery and grant canonicalization run once for the picker's lifetime instead of on every debounced keystroke. Returns no policy when the sandbox is absent or disabled, which the paired filter reads as "keep every candidate". A host must pair this with `sandbox.filterPickerCandidates` rather than resolve a grant list itself, because the picker's grants are narrower than a file tool's — the same path a built-in tool may read can be one the picker must not offer. + * + * @param params The working directory and sandbox settings to build a file-picker policy from. + * + * @returns The prepared file-picker policy, or none when the sandbox is disabled. + */ + preparePickerPolicy: async (params: SandboxPreparePickerPolicyParams): Promise => + connection.sendRequest("sandbox.preparePickerPolicy", params), + /** + * Checks a batch of `@`-file picker suggestions against a policy prepared by `sandbox.preparePickerPolicy` and returns one allow decision per candidate, positionally aligned with the request. Batched deliberately: each check canonicalizes a path component-by-component through the real filesystem, so one call amortizes that over a single hop rather than dozens while the user types. A directory the policy does not itself allow can still be kept as a waypoint to an allowed subtree. An absent policy keeps every candidate. + * + * @param params A batch of file-picker candidates to check against a prepared policy. + * + * @returns Per-candidate allow decisions, positionally aligned with the request. + */ + filterPickerCandidates: async (params: SandboxFilterPickerCandidatesParams): Promise => + connection.sendRequest("sandbox.filterPickerCandidates", params), + /** + * The sandbox a single named feature actually launches under, which is not the same as the session's sandbox. A feature is routed out of the sandbox by its own flag — `sandboxMcpServers`, `sandboxLspServers` — so an enabled session can still start one server confined and another free. Returns a disabled sandbox rather than an error when the host has no sandbox slice at all, so an absent config and an opted-out feature read alike at the launch site. A host must ask per feature rather than reuse the session config, because reading only `enabled` confines a server the user opted out of, or reports as sandboxed one that is not. + * + * @param params A sandbox config and the per-feature routing flag to resolve it for. + * + * @returns The sandbox a named feature actually launches under. + */ + resolveForOptional: async (params: SandboxEffectiveForOptionalParams): Promise => + connection.sendRequest("sandbox.resolveForOptional", params), + /** + * Reports whether this machine can run an OS-level command sandbox at all. A host must ask before it offers sandboxing, because the answer is a property of the machine rather than of any setting: on Linux it depends on bubblewrap being installed, and on Windows on the OS build supporting BaseContainer. Probing it requires spawning a process the first time, so the answer is cached for the life of the runtime. A host that assumes support and enables the sandbox anyway produces a session in which every shell command fails. + * + * @returns True when an OS-level sandbox can run on this host. + */ + checkPlatformSupport: async (): Promise => + connection.sendRequest("sandbox.checkPlatformSupport", {}), + /** + * Reports whether this Windows host's BaseContainer implementation can reach the host's loopback interface directly, which is what a local-network policy needs to avoid routing through the egress proxy. Like `sandbox.checkPlatformSupport`, the answer is a property of the machine's OS build rather than of any setting, and resolving it the first time spawns a platform probe. A host that offers the direct local-network option on a machine that lacks the capability produces a session whose sandboxed commands cannot reach local services. Always false off Windows. + * + * @returns True when the sandbox on this host can reach host loopback directly. + */ + checkWindowsHostLoopbackSupport: async (): Promise => + connection.sendRequest("sandbox.checkWindowsHostLoopbackSupport", {}), + /** + * The explanation a host shows when it refuses an explicit request to turn the sandbox on for a machine that cannot run one. Carries the platform-specific remedy — install bubblewrap, update Windows — and the documentation link, so a host that got `false` from `sandbox.checkPlatformSupport` does not have to compose its own reason and cannot describe the same machine differently from the runtime's own spawn path. + * + * @returns The user-facing refusal, including the platform remedy and the documentation link. + */ + getUnsupportedMessage: async (): Promise => + connection.sendRequest("sandbox.getUnsupportedMessage", {}), + /** + * The notice a host shows when a stored `sandbox` setting is being ignored for this session because the machine cannot enforce it. Distinct from `sandbox.getUnsupportedMessage`, which refuses an explicit request: this one reports that a setting the user already saved is inert, states that commands will run unsandboxed, and confirms the stored setting itself is left alone. A host that stays silent here leaves the user believing a sandbox is active. + * + * @returns The user-facing notice that a saved sandbox setting is inert for this session. + */ + getUnsupportedSettingDisabledNotice: async (): Promise => + connection.sendRequest("sandbox.getUnsupportedSettingDisabledNotice", {}), + /** + * The warning a host prints at startup when a session asked for a sandbox on a machine that cannot run one. The third of the unsupported notices, and the only one that depends on the policy in force: it names the remedy the reader can actually act on, which is the stored setting for an unmanaged host, an administrator for a managed one, and waiting for the policy to resolve when the sandbox stayed on only because the policy could not be determined. Returns no notice when the machine does support a sandbox, so a host can call it unconditionally on the startup path. A host that composes its own warning tells a managed user to change a setting their policy will overwrite. + * + * @param params The managed sandbox policy in force, which selects which remedy the notice offers. + * + * @returns The startup warning, absent when the machine can run a sandbox. + */ + getUnsupportedStartupNotice: async (params: SandboxUnsupportedStartupNoticeParams): Promise => + connection.sendRequest("sandbox.getUnsupportedStartupNotice", params), + /** + * Whether one slash command stays reachable while sandbox enforcement is unavailable and the session is otherwise blocked. The permitted set is deliberately narrow — commands that only report state, and the few that let the user repair or leave the situation — and it is keyed on the arguments as well as the name, so `/sandbox status` is admitted where `/sandbox enable` is not. A host must ask rather than keep its own list: a command this returns false for is one the runtime would refuse anyway, and a divergent list either strands the user with no way out or admits a command that acts on an unenforced session. + * + * @param params One slash command, as typed, to test against the enforcement-block allowlist. + * + * @returns True when the command stays available while enforcement is blocked. + */ + checkEnforcementCommand: async (params: SandboxEnforcementCommandAllowedParams): Promise => + connection.sendRequest("sandbox.checkEnforcementCommand", params), + /** + * Returns the documentation URL a host should point a user at when it declines or downgrades a sandbox setting. The runtime owns the address so every surface cites the same page and a host does not hard-code a link that later moves. + * + * @returns The documentation URL a host cites when it declines or downgrades a sandbox setting. + */ + getDocsUrl: async (): Promise => + connection.sendRequest("sandbox.getDocsUrl", {}), + }, + /** @experimental */ + persistence: { + /** + * Atomically replaces a JSON file's entire contents while holding the runtime's per-path lock. The lock lives in the runtime process, so a host that writes these files itself takes no part in it and can tear a concurrent runtime write; route the write here instead. Unlike a merging write this never reads or parses the existing file, so a previously corrupted snapshot is replaced wholesale rather than failing every later write. Use it only for callers that always persist a complete snapshot. + * + * @param params A complete JSON snapshot to write under the runtime's per-path file lock. + * + * @returns The outcome of a locked snapshot write. + */ + writeRawJsonLocked: async (params: PersistenceWriteRawJsonLockedRequest): Promise => + connection.sendRequest("persistence.writeRawJsonLocked", params), + }, + /** @experimental */ + user: { + /** @experimental */ + settings: { + /** + * Adds or removes one hook key in the user `disabledHooks` setting, rewriting settings.json in place and leaving every other setting untouched. Reports a typed outcome rather than failing the call, so a host can tell an unparseable settings file apart from a write that could not complete. + * + * @param params One hook to disable or re-enable. + * + * @returns Outcome of rewriting the `disabledHooks` setting. + */ + setHookDisabled: async (params: UserSettingsSetHookDisabledRequest): Promise => + connection.sendRequest("user.settings.setHookDisabled", params), + /** + * Reads the user's settings document exactly as `user.settings.load` does, but from a caller-supplied configuration directory instead of the one the server resolved for itself. Use this when a consumer scopes a session to its own Copilot home — the SDK's per-session `configDir` override — so the settings read match the directory that session actually uses. An absent or empty `configDir` resolves the server's own home, making this identical to `user.settings.load`. Hook-validation relaxations are deliberately not caller-supplied: they continue to come from the server's environment, so a consumer cannot widen the host's hook policy through this method. + * + * @param params Selects the configuration directory whose user settings to read. + * + * @returns The stored user settings document, keyed by setting name. + */ + loadForConfigDir: async (params: UserSettingsLoadForConfigDirRequest): Promise => + connection.sendRequest("user.settings.loadForConfigDir", params), + /** + * Reads a repository's checked-in settings document as stored, normalized, together with any diagnostic raised while reading it. This is the repository half of the layering `user.settings.get` reports: `user.settings.load` reads the user's own document, and this reads the one a repository checks in to govern the keys `user.settings.governanceKeys` lists as repo-governable. A document that fails to parse, or that sets a key a repository may not govern, is reported through `warning` while the settings that did load are still returned, so a consumer surfaces the diagnostic and continues rather than failing the session. When `repoRoot` is the user's home directory the Claude-compatible configuration is skipped, so a home-directory session does not inherit a personal config as repository policy. + * + * @param params Selects the repository whose checked-in settings to read. + * + * @returns A repository's settings as stored, together with any diagnostic raised while reading them. + */ + loadRepo: async (params: UserSettingsLoadRepoRequest): Promise => + connection.sendRequest("user.settings.loadRepo", params), + /** + * Reads a repository's settings and merges them over a supplied user layer in one step, returning the settings a consumer should act on inside that repository. Use this rather than reading the two layers separately: the merge is per setting and collection-aware, so a repository adds to the collections it is allowed to influence without replacing the user's own entries, and a whole-document overlay would get that wrong. Any diagnostic raised while reading the repository layer is returned rather than printed, so the consumer decides how to surface it; the settings that did load are merged regardless. + * + * @param params A repository to read settings from, and the user layer they are merged over. + * + * @returns The settings a consumer should act on inside a repository, together with any diagnostic raised while reading the repository layer. + */ + loadAndMergeRepo: async (params: UserSettingsLoadAndMergeRepoRequest): Promise => + connection.sendRequest("user.settings.loadAndMergeRepo", params), + /** + * Reports the on-disk path of the settings document `user.settings.load` and `user.settings.set` read and write. Resolved the same way those methods resolve it, so a host that shows or opens the settings file names the file the runtime actually uses, including the extensionless legacy `settings` file when that is what is present. + * + * @returns The on-disk location of the user's settings document. + */ + getPath: async (): Promise => + connection.sendRequest("user.settings.getPath", {}), + /** + * Reads the user's stored settings together with the diagnostic raised while reading them. This is `user.settings.load` plus the reason part of the document was ignored: that one answers the document alone, so a caller cannot tell a clean read from a salvaged one. Read this when the caller is about to write the file back, where accepting a partial parse would discard every value the loader could not read. The settings that did load are still reported alongside the warning, so a consumer may surface it and continue instead of failing. + * + * @returns The user's stored settings, together with any diagnostic raised while reading them. + */ + loadWithWarning: async (): Promise => + connection.sendRequest("user.settings.loadWithWarning", {}), + /** + * Folds a user settings layer over a managed (enterprise policy) layer and returns the merged settings. The managed layer's governance keys win, `autoTier` resolves through its overridable form, and the two per-entry control maps (`enabledPlugins`, `extraKnownMarketplaces`) union across the layers and are then stamped back from the managed layer, so each entry an administrator declares wins while the user keeps control of the rest. The parameters are named for their layers rather than being a generic base/override pair: nothing about a settings object identifies which layer produced it, so the runtime cannot detect a transposed call, and transposing one resolves the governance keys in the user's favour instead of the administrator's. Supplying only one layer returns it unchanged, and supplying neither returns an empty object. Rejects a managed `autoTier` that is neither a user-configurable tier nor a single-key overridable form, rather than silently dropping it. + * + * @param params The managed (enterprise policy) layer and the user layer to fold together. The field names carry the layer identity: nothing about a settings object identifies which layer produced it, so a generic base/override pair would let a caller transpose them and silently resolve the governance keys in the user's favour. + * + * @returns The user layer folded over the managed layer. + */ + mergeManagedAndUser: async (params: UserSettingsMergeManagedAndUserParams): Promise => + connection.sendRequest("user.settings.mergeManagedAndUser", params), + /** + * Folds a repository's settings layer into the user layer. Each repo-overridable key carries a directional policy chosen so a repository can only add restrictions and never relax the user's own. The two per-entry managed control maps are the exception those directional rules cannot express — a repository may legitimately enable plugins and declare marketplaces — so they are re-stamped afterwards from the runtime's ambient managed policy. That makes the result depend on the runtime process's managed state and not on the arguments alone: a host must call this rather than reimplement the fold, or a repository could relax a restriction the administrator imposed. Rejects when either layer is not a JSON object. + * + * @param params The user layer and the repository layer to fold together. + * + * @returns The repository layer folded into the user layer. + */ + mergeRepoValue: async (params: UserSettingsMergeRepoValueParams): Promise => + connection.sendRequest("user.settings.mergeRepoValue", params), + /** + * Runs the one-time migration that moves a staff account's update channel to stable, and records that it ran. Idempotent and self-gating: it takes a lock over the global-state file, re-reads the completion stamp inside it, and returns `current` rather than migrating twice, so two hosts racing at startup cannot both migrate. Only a `migrated` status means the persisted settings changed and the caller should reload them. An account on the experimental channel is deliberately left alone and reported as `excluded-experimental`. + * + * @param params The filesystem and environment facts a user-settings file is resolved from. Passed explicitly rather than read from the runtime's own process, so a host whose configuration directory differs from its process environment resolves the file it means. + * + * @returns The outcome of the one-time staff update-channel migration. + */ + migrateStaffUpdateChannelOnce: async (params: UserSettingsHostContext): Promise => + connection.sendRequest("user.settings.migrateStaffUpdateChannelOnce", params), + /** + * Runs the one-time reset that clears a staff account's pinned model selection, and reports the timestamp the host should record. Idempotent and self-gating: the reset is gated on a fixed cutoff, so a caller that passes back the timestamp it recorded gets `didReset: false` rather than a second reset. It applies to staff accounts only; any other account is reported as not reset without the settings document being opened. Only `didReset: true` means the persisted settings changed, and only then are the updated settings returned for the host to adopt. + * + * @param params Selects the configuration directory to run the one-time staff model-selection reset against, and supplies the caller's record of whether it has already run. + * + * @returns The outcome of the one-time staff model-selection reset. + */ + resetStaffModelSelectionOnce: async (params: UserSettingsResetStaffModelSelectionOnceRequest): Promise => + connection.sendRequest("user.settings.resetStaffModelSelectionOnce", params), + /** + * Reports the top-level keys in the user settings document that the runtime does not recognize, phrased for display to the user. This is a separate question from the warning `user.settings.loadWithWarning` returns: that one describes parts of the document the loader had to ignore to load the rest, whereas an unknown key parses cleanly and is simply not a setting, so a host that surfaces only the load warning silently accepts a misspelled key that will never take effect. Nothing is reported when the document is absent, unreadable, or failed to load, because an unknown-key list derived from a document the runtime could not read would be misleading rather than helpful. Inspection never modifies the document. + * + * @param params Selects the configuration directory whose user settings document should be inspected for unrecognized top-level keys. + * + * @returns The unrecognized top-level keys found in the user settings document, already phrased for display. + */ + getUnknownKeysWarning: async (params: UserSettingsUnknownKeysWarningRequest): Promise => + connection.sendRequest("user.settings.getUnknownKeysWarning", params), + /** + * Resolves the effective runtime settings for a host that is about to start a session, layering the given overrides over the settings documents, the environment and the platform defaults. A host cannot assemble this itself: the precedence between a user document, a repository document, the managed layers and the environment is the runtime's own rule, and a host that re-implements it drifts from the server it is configuring. The working directory, environment and platform are taken as arguments rather than read from the server process, so a host resolving settings for a directory other than its own gets that directory's answer. Nothing is written; this only computes. + * + * @param params The overrides and host context to resolve effective runtime settings from. + * + * @returns The effective runtime settings, keyed by setting name. Keys this runtime does not recognize are preserved rather than dropped, so a caller reading a document written by a newer runtime keeps every value. + */ + buildRuntime: async (params: UserSettingsBuildRuntimeRequest): Promise => + connection.sendRequest("user.settings.buildRuntime", params), + /** + * Runs the one-time migration that moves a staff account's log level to the current default, and records that it ran. Idempotent and self-gating: it takes a lock over the global-state file and re-reads the completion stamp inside it, so two hosts racing at startup cannot both migrate. Only `refreshed` can have changed the persisted settings, and the settings it returns are the document re-read after the write, so a host should adopt them rather than reloading separately and racing the next writer. + * + * @param params Selects the configuration directory to run the one-time staff log-level migration against, and supplies the caller's record of whether it has already run. + * + * @returns The outcome of the one-time staff log-level migration. + */ + migrateStaffLogLevelOnce: async (params: UserSettingsMigrateStaffLogLevelOnceRequest): Promise => + connection.sendRequest("user.settings.migrateStaffLogLevelOnce", params), + /** + * Runs the one-time cleanup that removes obsolete CLI-owned settings and legacy rubber-duck keys from the user's settings document, and reports what it did. Idempotent and safe to call on every startup: with nothing obsolete left it reports `unchanged` and writes nothing. The cleanup preserves every key it does not recognize, so a setting written by a newer host is never dropped by an older one, and a document that fails to parse is reported `invalid` and left untouched rather than rewritten from a partial read. The cleaned document is not returned: it is written before this replies, so `user.settings.load` reads it authoritatively. A host that surfaces unrecognized keys through `user.settings.unknownKeys` wants this first, because the keys this retires would otherwise be shown to the user as if they were misspelled settings. + * + * @param params Selects the configuration directory whose settings document to clean. + * + * @returns The outcome of the one-time obsolete-settings cleanup. + */ + migrateObsoleteOnDisk: async (params: UserSettingsMigrateObsoleteOnDiskRequest): Promise => + connection.sendRequest("user.settings.migrateObsoleteOnDisk", params), + /** + * Resolves the on-disk locations of the settings files this runtime reads, so a consumer can show or open them. The user path is the same file `user.settings.get`, `user.settings.load`, and `user.settings.set` operate on. Supply `repoRoot` to also resolve that repository's checked-in and local settings paths. A path is reported whether or not the file exists. + * + * @param params Selects which settings file paths to resolve. + * + * @returns On-disk locations of the settings files this runtime reads. A path is reported whether or not the file exists. + */ + getPaths: async (params: UserSettingsGetPathsRequest): Promise => + connection.sendRequest("user.settings.getPaths", params), + /** + * Lists the top-level user-setting keys by who is allowed to govern them: `repo` keys a repository may override through `.github/copilot/settings.json`, `claudeRepo` keys a repository may override through a Claude-compatible config, and `managed` keys an enterprise policy may fix. The lists are static for a given runtime build and fail closed: a key absent from a list cannot be governed that way. + * + * @returns Top-level user-setting keys grouped by who may govern them. + */ + listGovernanceKeys: async (): Promise => + connection.sendRequest("user.settings.listGovernanceKeys", {}), + /** + * Atomically edits one canonical user setting inside its settings-file lock. Reads, canonicalizes touched aliases, applies the edit, validates the resulting top-level setting and writes before releasing the lock. An explicit autoUpdatesChannel set completes the staff migration in the same transaction. Only a malformed existing file returns `invalid`; refused keys, invalid values and I/O errors reject the call. + * + * @param params One atomic edit of a canonical setting in settings.json. + * + * @returns Outcome of an atomic user or repository settings edit. + */ + edit: async (params: UserSettingsEditRequest): Promise => + connection.sendRequest("user.settings.edit", params), + /** + * Rewrites legacy and snake_case setting names in a settings object to their canonical spelling. A caller editing settings.json must normalize before writing: a legacy key left in place survives the write, is re-normalized on the next load, and silently shadows the canonical value the write just stored. + * + * @param params Settings object whose key names should be canonicalized. + * + * @returns Settings object with canonical key names. + */ + normalize: async (params: UserSettingsNormalizeRequest): Promise => + connection.sendRequest("user.settings.normalize", params), + /** + * Diagnoses a set of already-loaded settings layers, reporting each key that will not take effect and why. Settings load tolerantly so that one bad key does not stop a session from starting; this method surfaces what was dropped so a caller can report it instead of silently ignoring the user's intent. Takes the layers as given rather than reading them, so a caller can diagnose exactly the content it is showing, including unsaved edits. Never echoes a setting's value, because a value may be a token or another secret. Marked internal because it exists to carry a CLI call site off the native addon, not to serve a consumer-facing need; it becomes public once an SDK consumer needs to inspect settings health itself. + * + * @param params The settings layers to diagnose. + * + * @returns The settings that will not take effect across the given layers. + */ + diagnoseProblems: async (params: UserSettingsProblemsRequest): Promise => + connection.sendRequest("user.settings.diagnoseProblems", params), + /** + * Validates one top-level setting subtree and writes just that key to settings.json as a single step. Doing both here closes the race a caller cannot close itself: between a separate preflight read and the write, the file may change. Rewriting only the touched key keeps legacy siblings out of settings.json. Answers a status rather than failing, so a validation problem and a write problem stay distinguishable. + * + * @param params A single top-level setting subtree to validate and write. + * + * @returns Outcome of validating and writing one top-level setting subtree. + */ + applyTopKey: async (params: UserSettingsApplyTopKeyRequest): Promise => + connection.sendRequest("user.settings.applyTopKey", params), + /** + * Replaces the user settings file's entire content in one write. A key absent from `settings` is removed, which is what distinguishes this from `user.settings.set`: that one merges, so it can never clear a key the caller has dropped. For a caller saving the result of a raw edit, a merge would silently resurrect every key the user deleted. Records the same one-time mutation marks a per-key write records, so a setting the user has now chosen explicitly stops being migrated for them. + * + * @param params The whole content to store in the user settings file. + */ + writeAll: async (params: UserSettingsWriteAllRequest): Promise => + connection.sendRequest("user.settings.writeAll", params), + /** + * Merges a partial object into one top-level user setting, re-reading the stored value inside this runtime process's settings.json write lock so that concurrent writers in this process to different sub-fields of the same key do not clobber each other. Unlike `user.settings.set`, which replaces a key's whole value with what the caller last read, this keeps sub-fields the caller did not name when both the stored value and supplied value are objects; otherwise the supplied value replaces the stored value. Use it whenever two independent surfaces own different sub-fields of one setting. Returns the key's stored value after the write, in `value`. + * + * @param params Names the user setting to merge into, and the partial object to merge. + * + * @returns The stored value of the user setting after the merge. + */ + update: async (params: UserSettingsUpdateRequest): Promise => + connection.sendRequest("user.settings.update", params), + /** + * Reads settings.json as stored for a caller that is about to edit it. Unlike `user.settings.get`, this returns file values rather than per-key metadata and defaults. Reports `missing` for an absent file and `invalid` when the existing file will not parse. Retired config.json and extensionless files are not read. + * + * @returns Contents of settings.json as stored, for a caller about to rewrite it. + */ + loadForEdit: async (): Promise => + connection.sendRequest("user.settings.loadForEdit", {}), + /** + * Validates one top-level setting subtree without writing it, reporting each problem with the path it applies to. Scoped to a single top-level key on purpose: validating the whole file would reject an edit whenever an unrelated pre-existing field is malformed, locking the user out of the very tool that could fix it. + * + * @param params A single top-level setting subtree to validate. + * + * @returns Outcome of validating one top-level setting subtree. + */ + validate: async (params: UserSettingsValidateRequest): Promise => + connection.sendRequest("user.settings.validate", params), + }, + }, + /** @experimental */ + repo: { + /** @experimental */ + settings: { + /** + * Atomically edits one allowed top-level repository setting. Reads the raw file, preserves unrelated keys, applies one scalar or list edit, validates the result and writes under one file lock, so concurrent sibling or list edits are not lost. A malformed or unsafe existing file returns `invalid` without a write; rejected keys, invalid values and I/O errors reject the call. + * + * @param params One atomic edit of an allowed top-level repository setting. + * + * @returns Outcome of an atomic user or repository settings edit. + */ + edit: async (params: RepoSettingsEditRequest): Promise => + connection.sendRequest("repo.settings.edit", params), + /** + * Reads one repository settings file without settings normalization or layering. The existing JSONC parser accepts comments and trailing commas and applies its prototype-safety rules. Missing or blank files return an empty valid config. Invalid files return an empty config with invalid=true; other read failures reject. + * + * @param params Which repository settings file to read. + * + * @returns Parsed repository settings without normalization or layering. + */ + readRaw: async (params: RepoSettingsReadRawRequest): Promise => + connection.sendRequest("repo.settings.readRaw", params), + /** + * Writes or removes one allowed top-level repository setting through the existing validator and file lock. Preserves unrelated stored keys. Invalid keys, values, existing documents, and I/O errors reject without replacing the file. Deletion requires delete=true; otherwise value must be present. + * + * @param params One key to write or remove in a repository settings file. + */ + writeKey: async (params: RepoSettingsWriteKeyRequest): Promise => + connection.sendRequest("repo.settings.writeKey", params), + /** + * Replaces the repository-overridable settings in one locked write. Allowed keys present in settings are validated and written; allowed keys absent from settings are removed. Other input keys are ignored, and unknown keys already stored in the file remain unchanged. Invalid values or an invalid existing file reject without a partial write. + * + * @param params The complete set of repository-overridable settings to store. + */ + writeAll: async (params: RepoSettingsWriteAllRequest): Promise => + connection.sendRequest("repo.settings.writeAll", params), + }, + }, + /** @experimental */ + featureFlags: { + /** + * Resolves named gates against this installation's cached online-experiment assignment, for the account the runtime is already authenticated as. Unlike `featureFlags.resolve`, which layers static availability with the populations supplied, this reads the experiment assignment the runtime cached for its own current authentication, so a launch can honor a rollout before any session exists. The account is never taken from the request: naming one would read another account's cached assignments. The cached assignment answers immediately and any network refresh happens behind it, so this call does not wait on the network. Not available to an extension, which cannot carry the user's consent to read their assignment. + * + * @param params Caller population and startup inputs to resolve named online-experiment gates for. Carries no account: the runtime resolves the assignment for the authentication it already holds. + * + * @returns The resolved gates, keyed by flag name. + */ + resolveStartupGates: async (params: FeatureFlagsResolveStartupGatesRequest): Promise => + connection.sendRequest("featureFlags.resolveStartupGates", params), + /** + * Lists every feature flag this runtime declares, with the population each one is declared for, its experimental description, and the online-experiment flag it maps to. This is the static catalog: it describes how each flag is defined, not whether it is on for anyone. Use `featureFlags.resolve` for static values with caller overrides, not active runtime values or experiment assignments. + * + * @returns Every feature flag this runtime declares, with the shape each one was declared with. + */ + listMetadata: async (): Promise => + connection.sendRequest("featureFlags.listMetadata", {}), + /** + * Computes static feature flags from the supplied population and overrides without changing runtime state or verifying audience membership. Starts from declared availability, then applies direct feature-flag environment values, user settings, and explicit `flagOverrides`. It does not apply `COPILOT_EXP_*` experiment assignments, authenticated startup gates, or active runtime state. Settings include `enabledFeatureFlags`, the legacy `feature_flags.enabled` list, and supported convenience settings. Supply `baseFlags` to use an existing flag set instead of declared availability. Omit `isStaffGitHub` or `isStaffMicrosoft` when membership was never recorded: a legacy staff account then counts as both populations. + * + * @param params Caller population and overrides for static feature-flag resolution, independent of experiment assignments and active runtime state. + * + * @returns The resolved feature flags, keyed by flag name. + */ + resolve: async (params: FeatureFlagsResolveRequest): Promise => + connection.sendRequest("featureFlags.resolve", params), + }, + /** @experimental */ + permissions: { + /** @experimental */ + locations: { + /** + * Drops stored per-location permission grants whose directory no longer exists, and reports which location keys were removed. Runs against the permission store under the runtime's own Copilot home, the same store `session.permissions.locations.resolve` reads. Marked internal because it exists to carry a CLI call site off the napi boundary onto the SDK contract; it is migration plumbing, not a surface consumers are meant to depend on. It can become public once an SDK consumer needs to prune its own permission store. + * + * @returns Which stored location permissions were dropped because their directory is gone. + */ + cleanupOrphaned: async (): Promise => + connection.sendRequest("permissions.locations.cleanupOrphaned", {}), + }, + /** @experimental */ + folderTrust: { + /** + * Reports whether a folder is trusted, reading the persisted trusted-folders list directly rather than through a session. Use this from a host that has no session yet, such as a one-shot command deciding whether to honor a checkout's own settings. It differs from the session-scoped check in two ways that matter: it takes the configuration directory to read the list from, instead of inheriting the session's, and it answers only from the persisted list, so trust a user granted for the lifetime of one session is not visible here. A host that has a session should prefer the session-scoped check, which also covers session-only trust. + * + * @param params A folder to check against a persisted trusted-folders list, and the configuration directory holding that list. + * + * @returns Folder trust check result. + */ + checkTrust: async (params: FolderTrustIsTrustedParams): Promise => + connection.sendRequest("permissions.folderTrust.checkTrust", params), + }, + /** + * Interprets a verbatim managed `permissions` slice into the three policy questions a host asks before granting elevated permissions: whether bypass-permissions mode is forbidden, whether a policy exists that this build could not determine, and whether the layer supplies permission rules the session must enforce. An absent or empty slice means no managed policy applies. The runtime and its permission service share one policy type, so a new managed permission setting is interpreted in exactly one place; a host that reimplements the reading would silently diverge the first time that set grows. Hosts must fail closed on an undetermined policy rather than treating it as permission granted. + * + * @param params The verbatim managed `permissions` slice to interpret. + * + * @returns The policy questions a host asks before granting elevated permissions. + */ + evaluateManagedPolicy: async (params: PermissionsEvaluateManagedPolicyParams): Promise => + connection.sendRequest("permissions.evaluateManagedPolicy", params), + }, + /** @experimental */ + managedSettings: { + /** + * Discovers device-managed settings the way the runtime's own device layer does, and reports the sandbox floor and the load outcome alongside the validated document. This differs from `managedSettings.read` in three ways a policy-enforcing host depends on: a document that validates to an empty object is reported as absent rather than as an empty policy, a discovery error does not suppress a document that still validates, and a failure is reported as a flag rather than only as error text. Hosts that compose the device layer into an effective policy use this, because `managedSettings.read` cannot distinguish "no device policy" from "device policy could not be read" and would silently hand the user's own layer the win. + * + * @returns The device-managed settings layer, with the sandbox floor and the load outcome a host needs to compose it into an effective policy. + */ + readDevice: async (): Promise => + connection.sendRequest("managedSettings.readDevice", {}), + /** + * Retains the supplied managed layers as this runtime process's startup policy and re-applies the result to every live session. Hosts that own their own short-lived process policy use this, so a guarded mutation is checked against the policy the host actually fetched. Retaining is a whole-process replacement rather than a merge, so a host that already resolved a good policy must not retain a partial one over it. The failure flags are honest inputs rather than assumptions: reporting a fetch or load that failed keeps the layers already retained, and the interim fail-closed state, instead of clearing them. + * + * @param params The managed layers to retain as this process's startup policy, with the outcome of fetching each one. + */ + retainPolicy: async (params: ManagedSettingsRetainPolicyParams): Promise => + connection.sendRequest("managedSettings.retainPolicy", params), + /** + * Stamps an effective managed policy's individually-controlled entries onto a user settings document, returning the settings a host should actually act on. The managed layer wins on every plugin spec and marketplace name it names, and leaves everything it does not name as the user wrote it. Hosts use this rather than merging the two documents themselves, because the merge is per entry rather than per document and a whole-document overlay would either discard the user's unmanaged entries or let a user entry outrank a managed one. Omitting the managed layer returns the settings unchanged, which is what an unmanaged host wants. Rejects when either document is present but is not a JSON object rather than treating it as absent, since silently dropping a malformed policy hands the user's layer the win on exactly the entries the policy exists to control. + * + * @param params A user settings document and the effective managed policy whose controlled entries should be stamped onto it. + * + * @returns The user settings with the managed layer's controlled entries stamped on. + */ + applyControls: async (params: ManagedSettingsApplyControlsParams): Promise => + connection.sendRequest("managedSettings.applyControls", params), + /** + * Validates the managed settings a client is supplying for a session it is about to open, and returns them reduced to the keys a client is allowed to set. A client-supplied policy is not the same thing as an administrator's: it may only narrow permissions, so this accepts `permissions` and rejects every other key outright rather than ignoring it. Rejecting is the point — a host that silently dropped an unsupported key would report a policy as applied while running without it. Hosts call this before opening the session rather than letting the open fail, so a malformed policy is reported against the request that carried it. The runtime applies the same rule again when the session starts, so this cannot be used to smuggle a wider document past it. An accepted document that sets nothing comes back as an empty object, which is distinct from supplying nothing at all. + * + * @param params The client-supplied managed settings to validate and normalize. Omitting them is valid and yields no policy. + * + * @returns The accepted managed settings, reduced to the keys a client is allowed to set. + */ + parseClientValue: async (params: ManagedSettingsParseClientValueParams): Promise => + connection.sendRequest("managedSettings.parseClientValue", params), + /** + * Composes the supplied server, device/MDM, device sandbox floor and policy-helper layers into the single effective managed policy a host should enforce, resolving each key across the layers by the runtime's own precedence rules. Hosts that assemble managed layers themselves use this so their precedence matches the runtime's exactly instead of reimplementing it. The composition is not a pure function of its inputs: an advisory sandbox force-on that arrived over the server channel is declined when this host cannot run a sandbox, so the result reflects the runtime host's capabilities, and the returned settings carry `sandboxAdvisoryFloorDeclined: true` when that happened. Enforced channels (device/MDM and policy helper) are never declined, and a server force-on carrying `failIfUnavailable` is preserved so the session fails loudly instead of quietly running unsandboxed. Rejects when a supplied layer is present but is not a JSON object, rather than treating it as unmanaged, because silently dropping a malformed policy would hand the user's own layer the win on exactly the keys the policy exists to control. + * + * @param params The managed-settings layers to compose into an effective policy. Every layer is optional; omitting all of them yields no effective policy. + * + * @returns The effective managed policy composed from the supplied layers. + */ + resolveEffectiveValue: async (params: ManagedSettingsResolveEffectiveValueParams): Promise => + connection.sendRequest("managedSettings.resolveEffectiveValue", params), + /** + * Decides what a host must do to its bypass-permissions state after a managed-policy resolution lands, given the state already in force and the account now signed in. A host holds two pieces of state — which layer disabled bypass mode, and for a server-delivered disable, the account it was pinned to — and cannot decide the next step from the new policy alone. Three rules make that so, and each one is a control rather than a preference. A device/MDM disable outranks the account, so a later server answer never lifts it. An unknown policy, which is what a failed fetch or a refusal this build cannot enforce yields, preserves whatever is in force instead of reverting to permissive. And a server disable is lifted only when the account it was pinned to is no longer the signed-in one, which is what makes signing in to an unrestricted account recover without a restart while a reconnect on the same restricted account does not. Hosts call this rather than reimplementing it because getting the pinning backwards either strands a disable on an account that never carried one or drops one that still applies. `setDisabledState` is omitted when the host must leave its stored flag untouched, which is distinct from being told to set it to false. + * + * @param params The bypass-permissions state a host holds today, the account now signed in, and what the managed-policy resolution said. + * + * @returns The bypass-permissions state a host should hold after the resolution, and what it must do to get there. + */ + resolveBypassDisableTransition: async (params: ManagedSettingsResolveBypassDisableTransitionParams): Promise => + connection.sendRequest("managedSettings.resolveBypassDisableTransition", params), + /** + * Resolves managed (enterprise policy) settings for a host from every layer at once: the account's server-delivered policy, the device/MDM layer, and a platform policy helper. Hosts use this instead of fetching and composing the layers themselves, because the per-layer precedence and the fail-closed rules that apply when a layer cannot be confirmed are the runtime's own and must match the policy a session will later enforce. It does not retain or apply anything to process-global policy state, so a host may call it before any session exists — which is the point, since plugin activation and one-shot commands resolve policy before a session is available to carry it. The returned per-layer outcome flags are load-bearing rather than diagnostic: a host must distinguish an unmanaged device from a managed device whose policy could not be confirmed, and apply its fail-closed gates in the second case. Reports its diagnostics in `logMessages` rather than logging them, so a host that runs before a log sink exists still sees them. + * + * @param params Inputs describing the host and account an ingestion should resolve managed policy for. + * + * @returns The managed policy layers an ingestion resolved, together with the per-layer outcome flags a host needs to decide how much to trust the result. The flags are reported rather than folded into the settings because a host must be able to distinguish an unmanaged device from a managed device whose policy could not be confirmed, and apply its fail-closed gates in the second case. + */ + ingest: async (params: ManagedSettingsIngestParams): Promise => + connection.sendRequest("managedSettings.ingest", params), + /** + * Resolves the managed (enterprise policy) settings a host must enforce from startup, and judges the account against the enterprise sign-in pin in the same pass. This composes the layers the way `managedSettings.ingest` does and then answers the two questions a host cannot answer for itself: which fail-closed floors apply when a layer could not be confirmed, and whether this credential is allowed to sign in at all. The sign-in verdict is the reason a host must not assemble this itself from `managedSettings.ingest` — a pinned organization is an access control, and a host that composed the layers but skipped the pin would let a developer sign in with a personal or another enterprise's account and escape every control that keys off the signed-in identity. The verdict is computed on every resolution, including one that is not retained, so declining to apply policy never widens access. Set `retainAndApply` to keep the resolved policy as this runtime process's startup policy; leave it false to resolve without side effects. + * + * @param params Inputs describing the host and account a startup policy resolution should run for. Mirrors `managedSettings.ingest`, and adds the decision of whether the resolved policy is retained for this runtime process. + * + * @returns The startup policy a resolution composed, the per-layer outcome flags a host needs to decide how much to trust it, and the enterprise sign-in verdict for the credential it ran against. The flags are reported rather than folded into the settings because a host must distinguish an unmanaged device from a managed device whose policy could not be confirmed, and apply its fail-closed gates in the second case. + */ + resolveStartupPolicy: async (params: ManagedSettingsResolveStartupPolicyParams): Promise => + connection.sendRequest("managedSettings.resolveStartupPolicy", params), + /** + * Resolves the managed `remoteControl` policy for an account on its own, for a host deciding whether a remote-control session may start. Composes the same layers as `managedSettings.ingest` and returns only that one key, so a host does not have to know which layer won. An absent policy means the account is unmanaged for this capability and the host may proceed; that default is safe only while absence really does mean "no policy", so a service refusal that says an account policy exists which this client is too old to enforce resolves to an explicit disabled policy instead of to absence. A resolution that cannot be completed is rejected rather than answered as absence, for the same reason. + * + * @param params Inputs describing the host and account whose managed remote-control policy should be resolved. Mirrors `managedSettings.ingest`, and deliberately carries no retention decision: reading this gate never becomes this runtime process's startup policy. + * + * @returns The managed remote-control policy in force for an account, if any. An absent policy means the account is unmanaged for this capability, which a host reads as allowed — so absence here is a positive statement that no policy applies, never a stand-in for a resolution that failed. A resolution that cannot be completed is rejected instead. + */ + loadRemoteControl: async (params: ManagedSettingsLoadRemoteControlParams): Promise => + connection.sendRequest("managedSettings.loadRemoteControl", params), + /** + * Evaluates the device-channel enterprise sign-in pin (MDM plist, registry, or managed-settings file) against a credential, without resolving any server-delivered policy. This is the gate an entry point runs before it branches, and the only one that can refuse a developer's first sign-in: it is local, needs no credential, and a server-delivered pin only ever reaches accounts already inside the organization. It fails closed by construction — a discovery that cannot determine whether a pin exists refuses rather than allows, because a control that fails open under a triggerable condition is not a control. Hosts that also want the server channel should call `managedSettings.resolveStartupPolicy`, which folds both channels into one verdict. + * + * @param params The credential and environment to evaluate the device-channel sign-in pin against. + * + * @returns The verdict of the `forceLoginOrgs` enterprise sign-in pin for one credential. A managed device may pin Copilot to an approved set of GitHub organizations so a developer cannot sign in with a personal — or another enterprise's — account and escape every control that keys off the signed-in identity. A host must refuse to proceed when `allowed` is false; the remaining fields explain the refusal rather than qualifying it. + */ + resolveDeviceForceLoginOrgsGate: async (params: ManagedSettingsResolveDeviceForceLoginOrgsGateParams): Promise => + connection.sendRequest("managedSettings.resolveDeviceForceLoginOrgsGate", params), + }, /** @experimental */ host: { /** @@ -33103,6 +37080,114 @@ export function createInternalServerRpc(connection: MessageConnection) { connect: async (params: ConnectRequest): Promise => connection.sendRequest("connect", params), /** @experimental */ + account: { + /** + * Renders the sign-in banner and the one-line account description the runtime uses for an authentication identity. The runtime words these in several places for its own callers, so a host that announces a sign-in or an account switch should render them from here rather than composing its own, which would let the two spellings drift apart. + * + * @param params The authentication identity whose shared wording should be rendered. + * + * @returns The wording the runtime uses for an authentication identity. + */ + describeAuthInfo: async (params: AccountDescribeAuthInfoRequest): Promise => + connection.sendRequest("account.describeAuthInfo", params), + /** + * Reports whether the supplied identity can create gists. Gists are unavailable on GitHub Enterprise Cloud with data residency, and to Enterprise Managed Users on github.com, so a host that offers gist actions should hide or disable them when this reports no support. + * + * @param params The authentication identity whose gist support should be reported. + * + * @returns Whether the identity can create gists, and why not when it cannot. + */ + getGistSupport: async (params: AccountGetGistSupportRequest): Promise => + connection.sendRequest("account.getGistSupport", params), + /** + * Resolves a path on the identity's GitHub host into an absolute URL, so a host can link to settings or documentation on the enterprise instance the user is signed in to rather than to github.com. Falls back to the public host when no identity is supplied. + * + * @param params A path to resolve against the GitHub host an identity is signed in to. + * + * @returns An absolute URL on the identity's GitHub host. + */ + getWebUrl: async (params: AccountGetWebUrlRequest): Promise => + connection.sendRequest("account.getWebUrl", params), + /** + * Names the environment variable supplying a classic personal access token, if one is set. A classic token cannot be upgraded in place, so a host that finds one should tell the user which variable to unset rather than starting an interactive sign-in that the variable would override. + * + * @returns The environment variable supplying a classic personal access token, when one is set. + */ + getTokenEnvVar: async (): Promise => + connection.sendRequest("account.getTokenEnvVar", {}), + }, + /** @experimental */ + mcp: { + /** @experimental */ + registry: { + /** + * Allocates an ID for one cancellable MCP registry search. The ID exists before the search starts, so callers can cancel before it starts. The networking stack supplies the cancellation namespace. The runtime retains at most 1,024 unused IDs. At capacity, another allocation can reclaim an unused ID. Active searches retain their IDs until they finish. + * + * @returns Request id naming a cancellable registry search. + */ + allocateRequestId: async (): Promise => + connection.sendRequest("mcp.registry.allocateRequestId", {}), + /** + * Searches the MCP registry the supplied credential may read, resolving the registry endpoint from policy first. Hosts usually send credential-free `AuthIdentity`; a `token` identity can be resolved only when it embeds a token, while `env` and `gh-cli` identities can use a token embedded in the request first. The runtime follows registry pages, keeps the newest entry per server name, cuts the list to `limit`, and sorts an empty-query result by GitHub stars. Each server object is carried opaquely. + * + * @param params Registry search terms, the credential to search under, and the request id that makes the search cancellable. + * + * @returns Servers selected from the registry response. + */ + search: async (params: McpRegistrySearchRequest): Promise => + connection.sendRequest("mcp.registry.search", params), + /** + * Abandons the registry search that uses the given request ID. It acts only on IDs from `mcp.registry.allocateRequestId`, so it never cancels another component's request. Answers `canceled: true` when it stops a running search. Answers `canceled: false` for unknown or reclaimed IDs, completed or canceled searches, and unused reservations. It releases an unused reservation, so a later search with that ID is refused. + * + * @param params Registry search to abandon. + * + * @returns Whether the cancel reached a live search. + */ + cancel: async (params: McpRegistryCancelRequest): Promise => + connection.sendRequest("mcp.registry.cancel", params), + }, + /** + * Lists the MCP servers the runtime supplies itself, so a host that disables the built-ins wholesale suppresses exactly the set the runtime would otherwise add. The names are fixed for a build and depend on no configuration, session or account. + * + * @returns Names of the MCP servers the runtime supplies itself. + */ + listBuiltinServerNames: async (): Promise => + connection.sendRequest("mcp.listBuiltinServerNames", {}), + /** + * Reports whether the gh-replaceable GitHub MCP tools may be clipped for a session, which a host feeds back as the `excludeGhReplaceableTools` build option. The tools are redundant only when the session can reach a shell *and* the host has the `gh` that would replace them, so both halves are decided here. The shell half runs the platform shell tool through the runtime's own tool-filter matcher, so entry forms like `builtin:bash` and `builtin:*` behave exactly as they do when a session builds its tool catalog. The host half probes for `gh`, and is skipped entirely when the filters already rule the shell out. Host presence alone is not enough: a session restricted to, say, `view` would otherwise lose the built-in issue-read fallback while having no shell to replace it with. The answer describes the host the runtime runs on, so it is never forwarded to a remote engine. + * + * @param params The session tool filters that decide whether the session still has a shell to run `gh` with. + * + * @returns Whether the gh-replaceable GitHub MCP tools may be clipped for the session described by the request. + */ + shouldExcludeGitHubTools: async (params: McpShouldExcludeGitHubToolsRequest): Promise => + connection.sendRequest("mcp.shouldExcludeGitHubTools", params), + }, + /** @experimental */ + instructions: { + /** + * Returns the prompt the `init` command runs to author repository instructions. A host offering its own initialize action sends this text so the instructions it produces match the ones the CLI produces. The text is fixed for a build and depends on no configuration, session or repository. + * + * @returns The prompt the init command runs. + */ + getInitPrompt: async (): Promise => + connection.sendRequest("instructions.getInitPrompt", {}), + /** + * Discovers the instruction sources for one caller-named location and returns them in the order the runtime applies them. `discover` answers for a set of project paths and derives the rest of its scope itself; this one instead takes every argument from the caller — the extra directories to scan and whether to descend into nested instruction files — and honors them as given. It also includes instruction directories from trusted repository settings, matching native repository discovery. Use it when the caller knows its own scope and no session exists to derive that scope from, such as a host reporting how many instruction files a directory contributes before any session starts. Marked internal because it exists to carry a CLI call site off the napi boundary onto the SDK contract; it is migration plumbing, not a surface consumers are meant to depend on. + * + * @param params Explicit discovery arguments for one location, supplied by the caller rather than derived from a session. + * + * @returns Instruction sources discovered across user, repository, and plugin sources. + */ + discoverRepoSources: async (params: InstructionsDiscoverRepoSourcesRequest): Promise => + connection.sendRequest("instructions.discoverRepoSources", params), + /** + * Drops the runtime's in-memory cache of discovered custom instructions so the next discovery reads them from disk again. A host calls this after it changes instruction files, or after it trusts a folder whose instructions an earlier discovery skipped, so that a session started afterwards sees the current files instead of a cached earlier answer. The call performs no I/O of its own and removes nothing from disk; it only invalidates, so the cost lands on the next discovery. Returns once the cache is clear, which is what lets a caller order a session start after it. Marked internal because it exists to carry a CLI call site off the napi boundary onto the SDK contract; it is migration plumbing, not a surface consumers are meant to depend on. + */ + clearCache: async (): Promise => + connection.sendRequest("instructions.clearCache", {}), + }, + /** @experimental */ agents: { /** * Lists the agents this runtime ships, by name. A consumer separating shipped agents from ones the user or a plugin authored should compare against these names rather than against `AgentInfo.source`: an authored agent may carry the `builtin` source while not being one of these, and the runtime treats the two as separate questions. `disableableNames` is the subset a user may turn off, which a client needs to decide whether to offer a toggle. `yamlBasedNames` is the subset backed by a shipped YAML definition, which a client needs before asking the runtime to load one. @@ -33167,7 +37252,7 @@ export function createInternalServerRpc(connection: MessageConnection) { loadForConfigDir: async (params: GlobalStateLoadForConfigDirRequest): Promise => connection.sendRequest("globalState.loadForConfigDir", params), /** - * Records one top-level key in the host's machine-wide state, the counterpart to `globalState.load`. A host calls this to remember that it has shown an onboarding step, asked a one-off question, or completed a migration, so the next run can skip it. Only the named key is replaced and the rest of the document is preserved, which lets two writers record different flags without overwriting each other; passing no value removes the key instead. Only the keys a host records itself are writable: `appInstallNudgeResponded`, `appTipShown`, `askedSetupTerminals`, `autoFeedbackLastPromptedAt`, `firstLaunchAt`, `recentModelIds`, `sandboxCredentialProxyCaDeclined` and `sandboxOnboardingShown`. Every other key is refused, including `installedPlugins`, the stored credentials, `trustedFolders`, the staff flags and the signed-in accounts. Plugin enablement must use the plugin APIs, which apply repository and managed-policy checks. + * Records one top-level key in the host's machine-wide state, the counterpart to `globalState.load`. A host calls this to remember that it has shown an onboarding step, asked a one-off question, or completed a migration, so the next run can skip it. Only the named key is replaced and the rest of the document is preserved, which lets two writers record different flags without overwriting each other; passing no value removes the key instead. Only the keys a host records itself are writable: `appTipShown`, `askedSetupTerminals`, `autoFeedbackLastPromptedAt`, `firstLaunchAt`, `recentModelIds`, `sandboxCredentialProxyCaDeclined` and `sandboxOnboardingShown`. Every other key is refused, including `installedPlugins`, the stored credentials, `trustedFolders`, the staff flags and the signed-in accounts. Plugin enablement must use the plugin APIs, which apply repository and managed-policy checks. * * @param params A single top-level key to record in the host's machine-wide state. The write replaces only that key and leaves the rest of the document untouched, so two writers recording different one-off flags do not overwrite each other. The stored credential keys cannot be written through this method. */ @@ -33426,6 +37511,13 @@ export function createSessionRpc(connection: MessageConnection, sessionId: strin */ getEnforcementStatus: async (): Promise => connection.sendRequest("session.sandbox.getEnforcementStatus", { sessionId }), + /** + * Lists possible secret-bearing environment variables available to new sandboxed shells that are not already covered by credential masking. Uses local name rules and known-provider host suggestions, never returns secret values, and changes no settings or network permissions. Returns no suggestions while the session is not sandboxed. + * + * @returns Possible unconfigured secrets in the sandbox shell environment, sorted by variable name, with no secret values. + */ + getCredentialSuggestions: async (): Promise => + connection.sendRequest("session.sandbox.getCredentialSuggestions", { sessionId }), /** * Disables sandboxing for the remainder of the current session and approves the referenced pending sandbox-bypass permission request. The request is rejected unless the exact request is still pending and the effective sandbox policy permits bypass. * @@ -33778,6 +37870,15 @@ export function createSessionRpc(connection: MessageConnection, sessionId: strin */ setReasoningEffort: async (params: ModelSetReasoningEffortRequest): Promise => connection.sendRequest("session.model.setReasoningEffort", { ...params, sessionId }), + /** + * Computes whether the session's current model shows reasoning summaries and how a host should present them: the default Ctrl+T visibility, reasoning headers, the hidden-summaries hint, and whether a persisted `showReasoning` setting is redundant. The runtime folds in the session's model catalog, selection and reasoning-summaries experiment assignment; the host supplies only its own persisted setting and current visibility. This method reads the session's resolved model catalog rather than resolving it, so call `session.model.list` first on a session that has not listed models or run a turn; until then the result is `ready: false`. A session that resolves models without a Copilot catalog, that is a BYOK registry or an alternate provider, never caches one and stays `ready: false`. + * + * @param params Host presentation state the reasoning-summaries policy depends on. + * + * @returns Reasoning-summaries presentation policy for the session's current model. + */ + getReasoningSummaries: async (params: ModelGetReasoningSummariesRequest): Promise => + connection.sendRequest("session.model.getReasoningSummaries", { ...params, sessionId }), /** * Lists models available to this session using its own auth and integration context. Connected hosts (CLI TUI, GitHub App) should call this through the session client so remote sessions return the remote CLI's available models rather than the caller's. * @@ -34822,6 +38923,15 @@ export function createSessionRpc(connection: MessageConnection, sessionId: strin */ reload: async (params?: SessionPluginsReloadRequest): Promise => connection.sendRequest("session.plugins.reload", { ...params, sessionId }), + /** + * Installs or updates the plugins the session's managed settings require now, without waiting for the next automatic attempt after a failure, then reloads plugins. Reports progress and failures with the same `session.info` and `session.warning` events as automatic preparation. Fails when the runtime does not prepare managed plugins for the session. + * + * @param params Plugins required by the session's managed settings to install or update again. + * + * @returns Per-plugin outcomes of retrying the plugins required by the session's managed settings. + */ + retryManaged: async (params?: SessionPluginsRetryManagedRequest): Promise => + connection.sendRequest("session.plugins.retryManaged", { ...params, sessionId }), }, /** @experimental */ provider: { diff --git a/nodejs/src/generated/session-events.ts b/nodejs/src/generated/session-events.ts index 0938ae9744..b799ac55f5 100644 --- a/nodejs/src/generated/session-events.ts +++ b/nodejs/src/generated/session-events.ts @@ -217,7 +217,9 @@ export type RemediationAction = /** Review or widen the sandbox policy. The blocked path or host is named by the accompanying message or by the tool result the action arrived with. */ | "review_sandbox_policy" /** Permit outbound network access in the sandbox policy. */ - | "allow_sandbox_outbound"; + | "allow_sandbox_outbound" + /** Discard conversation history back through the blocked request before continuing. */ + | "rewind_conversation"; /** * The session mode the agent is operating in */ @@ -586,6 +588,38 @@ export type ProviderQuotaCapacityState = | "unknown" /** Client-only compatibility placeholder when acquisitionStatus is unavailable or failed; not an observed service verdict. */ | "unavailable"; +/** + * Identity scope of a monthly usage reading. + */ +export type ProviderMonthlyUsageScope = + /** Consumption of the authenticated user within the parent observation's service and returned cycle. */ + | "user" + /** The service did not establish a recognized usage scope. */ + | "unknown"; +/** + * Availability of a service's monthly consumption reading. + */ +export type ProviderMonthlyUsageState = + /** The service reported a usable consumption reading, including an explicit zero. */ + | "available" + /** The service found no usage policy; no consumption amount is known. */ + | "no_policy" + /** The read failed or its data could not be used; consumption is unknown. */ + | "unavailable" + /** The service reported an unrecognized usage state. */ + | "unknown"; +/** + * Units explicitly reported by a quota provider. + */ +export type ProviderQuotaUnit = + /** AI credits, not tokens or currency. */ + | "ai_credits" + /** Requests charged against an entitlement. */ + | "requests" + /** Model tokens. */ + | "tokens" + /** The observation does not establish a quantity unit. */ + | "unknown"; /** * Timing and purpose of a provider quota observation. */ @@ -604,18 +638,6 @@ export type ProviderQuotaQuantityKind = | "advisory_balance" /** No quantity measurement, as with admission-only response headers. */ | "none"; -/** - * Units explicitly reported by a quota provider. - */ -export type ProviderQuotaUnit = - /** AI credits, not tokens or currency. */ - | "ai_credits" - /** Requests charged against an entitlement. */ - | "requests" - /** Model tokens. */ - | "tokens" - /** The observation does not establish a quantity unit. */ - | "unknown"; /** * What initiated a conversation compaction */ @@ -942,6 +964,16 @@ export type AssistantUsageApiEndpoint = | "/responses" /** WebSocket Responses API endpoint. */ | "ws:/responses"; +/** + * Content-Encoding applied to the request body sent on the wire + */ +export type ModelCallRequestBodyEncoding = + /** The request body was sent uncompressed (including every WebSocket request). */ + | "identity" + /** The request body was sent gzip-compressed. */ + | "gzip" + /** The request body was sent zstd-compressed. */ + | "zstd"; /** * Transport used for a successful model call */ @@ -950,6 +982,34 @@ export type AssistantUsageTransport = | "http" /** WebSocket transport. */ | "websocket"; +/** + * Normalized cause of the WebSocket failure that triggered the HTTP fallback + */ +export type ModelCallWebSocketFallbackErrorKind = + /** DNS resolution of the WebSocket host failed. */ + | "dns" + /** The TLS handshake or a TLS record failed. */ + | "tls" + /** A WebSocket connect or read timed out. */ + | "timeout" + /** The peer refused the TCP connection. */ + | "connection_refused" + /** The connection was reset, aborted, or ended mid-stream without a close. */ + | "connection_reset" + /** The peer closed the WebSocket. */ + | "closed_by_peer" + /** The runtime closed the WebSocket itself. */ + | "closed_locally" + /** The socket was already closed before it was used. */ + | "not_connected" + /** The upgrade or request was rejected with an HTTP status. */ + | "http_status" + /** A WebSocket protocol violation or an undecodable frame. */ + | "protocol" + /** The WebSocket request could not be built. */ + | "configuration" + /** A failure that maps onto none of the other kinds. */ + | "other"; /** * Why a WebSocket-capable model call was carried by the HTTP fallback */ @@ -2129,7 +2189,7 @@ export interface ErrorData { */ errorCode?: string; /** - * Category of error (e.g., "authentication", "authorization", "quota", "rate_limit", "context_limit", "query") + * Category of error (e.g., "authentication", "authorization", "policy", "quota", "rate_limit", "context_limit", "query") */ errorType: string; /** @@ -2140,6 +2200,14 @@ export interface ErrorData { * GitHub request tracing ID (x-github-request-id header) for correlating with server-side logs */ providerCallId?: string; + /** + * Sanitized provider-defined error parameter preserved from a structured upstream error envelope, when available. + */ + providerErrorParam?: string; + /** + * Provider-defined error type preserved from a structured upstream error envelope, when available (for example, `invalid_request_error`). + */ + providerErrorType?: string; remediation?: RemediationAction; /** * Copilot service request ID (x-copilot-service-request-id header) for CAPI log correlation @@ -3869,6 +3937,7 @@ export interface ProviderQuotaState { * HTTP status from acquisition, when available. */ httpStatus?: number; + monthlyUsage?: ProviderMonthlyUsage; observationKind?: ProviderQuotaObservationKind; /** * When the runtime observed this reading, not a charge timestamp or guarantee that consumption has settled. @@ -3943,6 +4012,31 @@ export interface ProviderQuotaBudgetMetadata { */ usageAllowedWhenExhausted: boolean; } +/** + * An authoritative monthly usage reading for the observation's provider, account, and service. + */ +/** @experimental */ +export interface ProviderMonthlyUsage { + /** + * Nonnegative finite consumption reported by the service, preserving zero and fractions. Present only when state is available; never computed from tokens or balance differences. + */ + consumedQuantity?: number; + /** + * Start of the service-reported monthly billing cycle, in UTC RFC 3339. + */ + cycleStart?: string; + /** + * Service read time in UTC RFC 3339, not a ledger reconciliation watermark or confirmation of the latest inference charge. + */ + queriedAt?: string; + /** + * End of the service-reported monthly billing cycle, in UTC RFC 3339. + */ + resetOn?: string; + scope: ProviderMonthlyUsageScope; + state: ProviderMonthlyUsageState; + unit: ProviderQuotaUnit; +} /** * Session event "session.context_changed". Updated working directory and git context after the change */ @@ -4173,12 +4267,6 @@ export interface CompactionCompleteEvent { * Conversation compaction results including success status, metrics, and optional error details */ export interface CompactionCompleteData { - /** - * Legacy active-workflow reminder retained for replay compatibility - * - * @internal - */ - activeFactorySummary?: string; /** * Authoritative active-workflow reminder appended to the compacted context * @@ -4305,6 +4393,10 @@ export interface CompactionCompleteCompactionTokensUsed { */ outputTokens?: number; provider?: ModelProviderRef; + /** + * Number of model requests represented by this aggregated compaction usage + */ + requestCount?: number; } /** * Per-request cost and usage data from the CAPI copilot_usage response field @@ -4453,6 +4545,14 @@ export interface PermissionRecoveryData { */ maxAttempts: number; onBlocked: PermissionRecoveryOnBlocked; + /** + * Subagent identifier that owns this recovery episode when the request was delegated through a parent permission service + */ + ownerAgentId?: string; + /** + * Root or child execution-session identifier that owns this recovery episode; omitted for legacy snapshots + */ + ownerSessionId?: string; reason: PermissionRecoveryReason; status: PermissionRecoveryStatus; } @@ -7021,9 +7121,14 @@ export interface AssistantUsageData { */ rejectedPredictionTokens?: number; /** - * Serialized (uncompressed) byte length of the request body. A content-free size signal. + * Serialized (uncompressed) byte length of the request body, before any Content-Encoding. A content-free size signal. */ requestBodyBytes?: number; + requestBodyEncoding?: ModelCallRequestBodyEncoding; + /** + * Byte length of the request body actually sent on the wire for the attempt that produced this outcome, after any Content-Encoding. Equals requestBodyBytes when the body was not compressed. + */ + requestBodyWireBytes?: 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. */ @@ -7067,7 +7172,24 @@ export interface AssistantUsageData { * Milliseconds spent on the WebSocket attempt before falling back to HTTP */ websocketFallbackAfterMs?: number; + /** + * WebSocket close code received before the HTTP fallback + */ + websocketFallbackCloseCode?: number; + websocketFallbackErrorKind?: ModelCallWebSocketFallbackErrorKind; websocketFallbackReason?: ModelCallWebSocketFallbackReason; + /** + * Whether a WebSocket reconnect was attempted before falling back to HTTP + */ + websocketFallbackReconnectAttempted?: boolean; + /** + * Whether the HTTP fallback started during this model call, rather than being inherited from an earlier call + */ + websocketFallbackStartedThisCall?: boolean; + /** + * HTTP status of the WebSocket upgrade rejection or WebSocket API error that triggered the HTTP fallback + */ + websocketFallbackStatusCode?: number; } /** * Ordered accounting identity assigned under the source session's emission lock. @@ -7322,9 +7444,14 @@ export interface ModelCallFailureData { */ reasoningEffort?: string; /** - * Serialized (uncompressed) byte length of the failed request body. A content-free size signal. + * Serialized (uncompressed) byte length of the failed request body, before any Content-Encoding. A content-free size signal. */ requestBodyBytes?: number; + requestBodyEncoding?: ModelCallRequestBodyEncoding; + /** + * Byte length of the failed request body actually sent on the wire, after any Content-Encoding. Equals requestBodyBytes when the body was not compressed. + */ + requestBodyWireBytes?: number; requestFingerprint?: ModelCallFailureRequestFingerprint; /** * Zero-based orchestrator retry index of the failed attempt @@ -7348,7 +7475,24 @@ export interface ModelCallFailureData { * Milliseconds spent on the WebSocket attempt before falling back to HTTP */ websocketFallbackAfterMs?: number; + /** + * WebSocket close code received before the HTTP fallback + */ + websocketFallbackCloseCode?: number; + websocketFallbackErrorKind?: ModelCallWebSocketFallbackErrorKind; websocketFallbackReason?: ModelCallWebSocketFallbackReason; + /** + * Whether a WebSocket reconnect was attempted before falling back to HTTP + */ + websocketFallbackReconnectAttempted?: boolean; + /** + * Whether the HTTP fallback started during this model call, rather than being inherited from an earlier call + */ + websocketFallbackStartedThisCall?: boolean; + /** + * HTTP status of the WebSocket upgrade rejection or WebSocket API error that triggered the HTTP fallback + */ + websocketFallbackStatusCode?: number; } /** * Content-free structural summary of the failing request for diagnosing malformed 4xx calls @@ -8607,10 +8751,6 @@ export interface SubagentStartedData { * Whether the sub-agent runs synchronously or in the background. */ executionMode?: string; - /** - * Legacy root id of the workflow run that spawned this sub-agent. New consumers should use workflowRunId. - */ - factoryRunId?: string; /** * Model the sub-agent will run with, when known at start. */ @@ -9544,6 +9684,14 @@ export interface PermissionRequestedEvent { */ export interface PermissionRequestedData { agentMode?: SessionMode; + /** + * Delegated agent that owns permission recovery when the owner session multiplexes remote agents. Absent otherwise. + */ + ownerAgentId?: string; + /** + * Execution session that owns permission recovery for this request. Absent on historical events. + */ + ownerSessionId?: string; permissionMode?: PermissionMode; permissionRequest: PermissionRequest; promptRequest?: PermissionPromptRequest; @@ -12795,7 +12943,7 @@ export interface ManagedSettingsResolvedEvent { /** @experimental */ export interface ManagedSettingsResolvedData { /** - * Whether enterprise policy disables bypass-permissions ("yolo") mode for this session. Deny-wins across layers, and forced on when `failClosed` is true. + * Whether an explicit enterprise policy restriction disables bypass-permissions ("yolo") mode for this session. Deny-wins across layers; an unresolved policy does not force this on by itself. */ bypassPermissionsDisabled: boolean; /** @@ -12807,7 +12955,7 @@ export interface ManagedSettingsResolvedData { */ deviceManaged: boolean; /** - * 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. + * Whether managed policy could not be determined (e.g. a failed server fetch) and unresolved-policy safeguards remain active. This does not by itself disable bypass-permissions; `bypassPermissionsDisabled` reports only an explicit policy restriction. */ failClosed: boolean; /** @@ -12818,6 +12966,7 @@ export interface ManagedSettingsResolvedData { * Whether at least two managed sources supplied permission allowlists, so enforcement intersects them and the flattened settings payload omits `permissions.allow`. */ permissionsAllowIntersected?: boolean; + permissionsContext?: ManagedPermissionsContext; /** * 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. */ @@ -12836,6 +12985,19 @@ export interface ManagedSettingsResolvedData { settings?: JsonValue; source: ManagedSettingsResolvedSource; } +/** + * Reusable managed permission evaluation context. Treat permissions as runtime-owned policy data and retain it verbatim; it includes source-aware composition metadata. This is a snapshot, not a capability or approval token; only use contexts obtained from trusted policy sources. + */ +export interface ManagedPermissionsContext { + /** + * Policy could not be determined; evaluation must deny every operation. + */ + failClosed: boolean; + /** + * Runtime-owned composed permissions object. Absent when no permission policy is configured. + */ + permissions?: JsonValue; +} /** * Session event "session.managed_settings_enforced". 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 bypass permissions or Assisted Permissions while the corresponding 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. */ diff --git a/nodejs/src/index.ts b/nodejs/src/index.ts index ef91cd9689..e95b5362de 100644 --- a/nodejs/src/index.ts +++ b/nodejs/src/index.ts @@ -9,7 +9,12 @@ */ export { CopilotClient } from "./client.js"; -export type { HostLocalServerOptions, HostGitHubEnvironmentOptions } from "./generated/rpc.js"; +export type { + HostLocalServerOptions, + HostGitHubEnvironmentOptions, + ModelGetReasoningSummariesRequest, + ModelGetReasoningSummariesResult, +} from "./generated/rpc.js"; export { AhpHost, type AhpHostExit, @@ -245,6 +250,7 @@ export type { ToolTelemetry, ToolResultObject, ToolSearchConfig, + ImageGenerationConfig, TypedSessionEventHandler, TypedSessionLifecycleHandler, ZodSchema, diff --git a/nodejs/src/session.ts b/nodejs/src/session.ts index 02e60dfbcf..6dd16d357b 100644 --- a/nodejs/src/session.ts +++ b/nodejs/src/session.ts @@ -183,6 +183,27 @@ function deserializeHookInput(raw: unknown): unknown { }; } +/** + * The CLI sends the `error` of an errorOccurred or sessionEnd hook input as an + * error object (`{ name, message, stack }` or `{ message }`), where the hook + * input types declare a string. The object's message becomes the error, and an + * object without a string message becomes its JSON. Other values pass through. + */ +function normalizeHookError(hookType: string, input: unknown): unknown { + if (hookType !== "errorOccurred" && hookType !== "sessionEnd") { + return input; + } + if (!input || typeof input !== "object" || !("error" in input)) { + return input; + } + const { error } = input as { error: unknown }; + if (!error || typeof error !== "object" || Array.isArray(error)) { + return input; + } + const { message } = error as { message?: unknown }; + return { ...input, error: typeof message === "string" ? message : JSON.stringify(error) }; +} + function isOpenCanvasInstance(value: unknown): value is OpenCanvasInstance { if (!value || typeof value !== "object") { return false; @@ -2364,7 +2385,7 @@ export class CopilotSession { // is left alone — the user-facing handler types still cast unknown to the // specific HookInput, so a runtime type mismatch surfaces as a normal // TypeError in user code rather than being silently masked. - const normalized = deserializeHookInput(input); + const normalized = normalizeHookError(hookType, deserializeHookInput(input)); type GenericHandler = ( input: unknown, diff --git a/nodejs/src/types.ts b/nodejs/src/types.ts index 5a807c8896..1d8fdb3247 100644 --- a/nodejs/src/types.ts +++ b/nodejs/src/types.ts @@ -863,6 +863,21 @@ export function defineTool( return { name, ...config }; } +/** + * Non-persisted opt-in to image generation. + * @experimental This API may change or be removed in a future release. + */ +export interface ImageGenerationConfig { + /** + * Opt in to image generation through an authorized Copilot image model. + * False disables it; omission defaults to disabled on create/cold resume and + * preserves the setting on resident resume. Not persisted: re-supply after + * runtime restart. Policy, permissions, offline mode, and tool filters still + * apply. BYOK image generation is not supported. + */ + enabled?: boolean; +} + /** * SDK-supplied override for the runtime's built-in tool-search behavior. * @@ -1504,6 +1519,12 @@ export type AutoModeSwitchHandler = ( * Base interface for all hook inputs */ export interface BaseHookInput { + /** Subagent actor ID, absent for root hooks and older runtimes. */ + agentId?: string; + /** Subagent type, when known by the runtime. */ + agentType?: string; + /** Immediate parent actor ID, when available. */ + parentAgentId?: string; /** The runtime session ID associated with the hook. Child tool hooks use * the child session ID; sub-agent lifecycle hooks use the parent session ID, * matching `invocation.sessionId`. */ @@ -2010,6 +2031,8 @@ export type MCPServerConfig = MCPStdioServerConfig | MCPHTTPServerConfig; * Configuration for a custom agent. */ export interface CustomAgentConfig { + /** Declarative hook actions keyed by event name, active only for this agent. */ + hooks?: Record[]>; /** * Unique name of the custom agent. */ @@ -2628,6 +2651,12 @@ export interface SessionConfigBase { */ toolSearch?: ToolSearchConfig; + /** + * Image generation opt-in. Re-supply after runtime restart. + * @experimental This API may change or be removed in a future release. + */ + imageGeneration?: ImageGenerationConfig; + /** * List of tool names to allow. When specified, only these tools will be available. * @@ -2939,7 +2968,11 @@ export interface SessionConfigBase { agent?: string; /** - * Directories to load skills from. + * Explicit discovery-only skill roots. No general filesystem access is granted. + * Available without `enableConfigDiscovery`; skill and managed disables still apply. + * Relative paths resolve once when supplied against the request's effective + * `workingDirectory`, and retained roots do not move when it changes. + * Null and omission are equivalent; `[]` clears only the explicit SDK contribution. */ skillDirectories?: string[]; @@ -2960,6 +2993,27 @@ export interface SessionConfigBase { */ skillProvider?: SkillProvider; + /** + * Explicit discovery-only agent roots, separate from `customAgents` objects. + * No general filesystem access is granted. Available without `enableConfigDiscovery`; + * source and managed disables still apply. + * Relative paths resolve once when supplied against the request's effective + * `workingDirectory`, and retained roots do not move when it changes. + * Null and omission are equivalent; `[]` clears only the explicit SDK contribution. + */ + agentDirectories?: string[]; + + /** + * Explicit discovery-only file-hook roots, separate from callback `hooks`. + * No general filesystem access is granted. Available without `enableConfigDiscovery` + * or `enableFileHooks` (which controls repository-origin hooks only); + * hook and managed disables still apply. + * Relative paths resolve once when supplied against the request's effective + * `workingDirectory`, and retained roots do not move when it changes. + * Null and omission are equivalent; `[]` clears only the explicit SDK contribution. + */ + hookDirectories?: string[]; + /** * Local filesystem paths to Open Plugins-format directories * (https://open-plugins.com/) to load for this session. @@ -2976,7 +3030,12 @@ export interface SessionConfigBase { pluginDirectories?: string[]; /** - * Additional directories to search for custom instruction files. + * Explicit discovery-only instruction roots using the existing SDK instruction formats. + * No general filesystem access is granted. Available without `enableConfigDiscovery`; + * instruction and managed disables still apply. + * Relative paths resolve once when supplied against the request's effective + * `workingDirectory`, and retained roots do not move when it changes. + * Null and omission are equivalent; `[]` clears only the explicit SDK contribution. */ instructionDirectories?: string[]; @@ -3041,6 +3100,14 @@ export interface SessionConfigBase { */ enforceManagedModelDefaults?: boolean; + /** + * Declares that a human drives this session's conversation, enabling + * user-message analysis telemetry for user-authored messages. Automated + * messages remain excluded. Defaults to false for SDK sessions; ignored + * for cloud sessions and when resuming a session that is already active. + */ + interactiveUser?: boolean; + /** * Host-injected enterprise managed settings for this session. * @@ -3094,9 +3161,9 @@ export interface SessionConfigBase { enableOnDemandInstructionDiscovery?: boolean; /** - * When true, enables loading of file-based hooks from `.github/hooks/`. - * This is separate from the `hooks` callback parameter which gates SDK - * hook event registration. + * When true, enables repository-origin file hooks from `.github/hooks/` and + * trusted repository-configured roots. Does not control explicit `hookDirectories` + * or the `hooks` callback parameter. */ enableFileHooks?: boolean; @@ -3114,6 +3181,14 @@ export interface SessionConfigBase { */ enableSessionStore?: boolean; + /** + * When true, requests the read-only GitHub cache store for this session + * (the `github-cache-store` capability). The runtime registers the + * `github_sql` tool only when its `GITHUB_CACHE_STORE` feature flag is + * also on. Defaults to disabled. + */ + enableGitHubCacheStore?: boolean; + /** * When true, enables skill loading (including builtin skills and discovered * skill directories). When false, no skills are loaded regardless of @@ -3176,6 +3251,15 @@ export interface SessionConfig extends SessionConfigBase { */ sessionId?: string; + /** + * Names the session at creation, as `session.name.set` would, and is validated the + * same way: non-empty, no leading or trailing whitespace, at most 100 UTF-16 units, + * no control characters, and no double quotes. A name also makes the session persist + * from the start, so it can be listed and resumed without ever running a turn. + * Local sessions only: cloud creation rejects it. + */ + name?: string; + /** * Invalidates the process-wide custom-instruction discovery cache before * creating this session. Use when instruction files changed in the same runtime. diff --git a/nodejs/test/account-facts-projection.test.ts b/nodejs/test/account-facts-projection.test.ts new file mode 100644 index 0000000000..5e689bb423 --- /dev/null +++ b/nodejs/test/account-facts-projection.test.ts @@ -0,0 +1,45 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +import { readFileSync } from "node:fs"; +import { describe, expect, it } from "vitest"; + +describe("account facts Go projection", () => { + const source = readFileSync(new URL("../../go/rpc/zrpc.go", import.meta.url), "utf8"); + + for (const name of ["AccountGetWebURLRequest", "AccountGetWebURLResult"]) { + it(`marks ${name} as internal after Go initialism normalization`, () => { + const comment = source.match(new RegExp(`((?://[^\\n]*\\n)+)type ${name} struct`))?.[1]; + expect(comment).toContain(`// Internal: ${name} `); + }); + } +}); + +describe("account facts Python projection", () => { + const source = readFileSync( + new URL("../../python/copilot/generated/rpc.py", import.meta.url), + "utf8" + ); + const exports = source.match(/^__all__ = \[([\s\S]*?)^\]/m)?.[1]; + + for (const [name, keys] of [ + ["AccountGetWebURLRequest", ["identity", "path"]], + ["AccountGetWebURLResult", ["url"]], + ] as const) { + it(`keeps ${name} private without changing its wire keys`, () => { + const declaration = source.match( + new RegExp(`^class _${name}:\\n[\\s\\S]*?(?=^class )`, "m") + )?.[0]; + expect(declaration).toBeDefined(); + expect(source).not.toMatch(new RegExp(`^class ${name}\\b`, "m")); + expect(exports).toBeDefined(); + expect(exports).not.toContain(`"${name}"`); + expect(exports).not.toContain(`"_${name}"`); + for (const key of keys) { + expect(declaration).toContain(`obj.get("${key}")`); + expect(declaration).toContain(`result["${key}"]`); + } + }); + } +}); diff --git a/nodejs/test/client.test.ts b/nodejs/test/client.test.ts index 22831ad930..0eb703015b 100644 --- a/nodejs/test/client.test.ts +++ b/nodejs/test/client.test.ts @@ -4895,6 +4895,105 @@ connection.listen(); }); expect(topLevelStop).not.toHaveBeenCalled(); }); + + it.each([ + [ + "an error object", + { name: "Error", message: "model timeout", stack: "Error: model timeout" }, + "model timeout", + ], + ["a string", "boom", "boom"], + ["null", null, null], + ["an object without a string message", { code: 7 }, '{"code":7}'], + ])("decodes %s in the error hook inputs", async (_name, error, expected) => { + const client = new CopilotClient({ autoStart: false }); + const received: { hookType: string; error: unknown }[] = []; + const session = new CopilotSession("session-1", {} as any); + session.registerHooks({ + onErrorOccurred: (input) => { + received.push({ hookType: "errorOccurred", error: input.error }); + }, + onSessionEnd: (input) => { + received.push({ hookType: "sessionEnd", error: input.error }); + }, + }); + (client as any).sessions.set(session.sessionId, session); + const base = { sessionId: session.sessionId, timestamp: 1700000000000, cwd: "/repo" }; + + await (client as any).handleHooksInvoke({ + sessionId: session.sessionId, + hookType: "errorOccurred", + input: { ...base, error, errorContext: "model_call", recoverable: true }, + }); + await (client as any).handleHooksInvoke({ + sessionId: session.sessionId, + hookType: "sessionEnd", + input: { ...base, reason: "error", error }, + }); + + expect(received).toEqual([ + { hookType: "errorOccurred", error: expected }, + { hookType: "sessionEnd", error: expected }, + ]); + }); + + it("keeps the decoded timestamp and working directory when it decodes the error", async () => { + const client = new CopilotClient({ autoStart: false }); + const received: { timestamp: unknown; workingDirectory: unknown }[] = []; + const session = new CopilotSession("session-1", {} as any); + session.registerHooks({ + onErrorOccurred: (input) => { + received.push({ + timestamp: input.timestamp, + workingDirectory: input.workingDirectory, + }); + }, + }); + (client as any).sessions.set(session.sessionId, session); + + await (client as any).handleHooksInvoke({ + sessionId: session.sessionId, + hookType: "errorOccurred", + input: { + sessionId: session.sessionId, + timestamp: 1700000000000, + cwd: "/repo", + error: { message: "model timeout" }, + errorContext: "model_call", + recoverable: true, + }, + }); + + expect(received).toEqual([ + { timestamp: new Date(1700000000000), workingDirectory: "/repo" }, + ]); + }); + + it("leaves an absent sessionEnd error absent", async () => { + const client = new CopilotClient({ autoStart: false }); + const received: unknown[] = []; + const session = new CopilotSession("session-1", {} as any); + session.registerHooks({ + onSessionEnd: (input) => { + received.push(input); + }, + }); + (client as any).sessions.set(session.sessionId, session); + + await (client as any).handleHooksInvoke({ + sessionId: session.sessionId, + hookType: "sessionEnd", + input: { + sessionId: session.sessionId, + timestamp: 1700000000000, + cwd: "/repo", + reason: "complete", + }, + }); + + expect(received).toHaveLength(1); + expect(received[0]).not.toHaveProperty("error"); + }); }); describe("shutdown", () => { @@ -5458,6 +5557,44 @@ describe("managedSettings serialization", () => { }); }); +describe("interactiveUser serialization", () => { + async function captureParams( + method: "session.create" | "session.resume", + config: Record + ): Promise { + const client = new CopilotClient(); + await client.start(); + onTestFinished(() => stopClient(client)); + const spy = vi + .spyOn((client as any).connection!, "sendRequest") + .mockImplementation(async (requested: string, params: any) => { + if (requested === method) return { sessionId: params.sessionId }; + throw new Error(`Unexpected method: ${requested}`); + }); + if (method === "session.create") { + await client.createSession({ onPermissionRequest: approveAll, ...config }); + } else { + await client.resumeSession("session-1", { onPermissionRequest: approveAll, ...config }); + } + const call = spy.mock.calls.find(([requested]) => requested === method); + return call![1]; + } + + for (const method of ["session.create", "session.resume"] as const) { + for (const interactiveUser of [true, false]) { + it(`forwards interactiveUser: ${interactiveUser} on ${method}`, async () => { + const params = await captureParams(method, { interactiveUser }); + expect(params.interactiveUser).toBe(interactiveUser); + }); + } + + it(`omits interactiveUser from ${method} when unset`, async () => { + const params = await captureParams(method, {}); + expect(params.interactiveUser).toBeUndefined(); + }); + } +}); + describe("connect handshake clientInfo", () => { // Drives verifyProtocolVersion() against a stubbed connection so we can // observe the `connect` params without spawning a runtime. `connect` maps to diff --git a/nodejs/test/computer-use-oauth-fixture.test.ts b/nodejs/test/computer-use-oauth-fixture.test.ts new file mode 100644 index 0000000000..419563d9dd --- /dev/null +++ b/nodejs/test/computer-use-oauth-fixture.test.ts @@ -0,0 +1,68 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +import { test } from "vitest"; +import { startComputerUseOAuthServer } from "../../test/harness/test-mcp-computer-use-oauth-server.mjs"; + +test("rejects token exchanges without a registered client", async (context) => { + const { expect } = context; + const starting = startComputerUseOAuthServer(); + context.onTestFinished(async () => { + const [result] = await Promise.allSettled([starting]); + if (result.status === "fulfilled") await result.value.close(); + }); + const server = await starting; + context.signal.throwIfAborted(); + const exchange = (clientId: string) => + fetch(`${server.url}/token`, { + method: "POST", + body: new URLSearchParams({ + grant_type: "authorization_code", + code: "accepted-code", + code_verifier: "test-verifier", + ...(clientId ? { client_id: clientId } : {}), + }), + }); + for (const clientId of ["", "wrong-client", "registered-client"]) { + const response = await exchange(clientId); + expect(response.status).toBe(400); + expect(await response.json()).toEqual({ error: "invalid_client" }); + } + const registration = await fetch(`${server.url}/register`, { method: "POST" }); + expect(registration.status).toBe(201); + const { client_id: clientId } = await registration.json(); + for (const invalidClient of ["", "wrong-client"]) { + const response = await exchange(invalidClient); + expect(response.status).toBe(400); + expect(await response.json()).toEqual({ error: "invalid_client" }); + } + expect((await exchange(clientId)).status).toBe(200); +}); + +test("binds preconfigured-client token exchanges without dynamic registration", async (context) => { + const { expect } = context; + const starting = startComputerUseOAuthServer({ preconfiguredClientId: "app-client" }); + context.onTestFinished(async () => { + const [result] = await Promise.allSettled([starting]); + if (result.status === "fulfilled") await result.value.close(); + }); + const server = await starting; + context.signal.throwIfAborted(); + for (const clientId of ["", "wrong-client", "app-client"]) { + const response = await fetch(`${server.url}/token`, { + method: "POST", + body: new URLSearchParams({ + grant_type: "authorization_code", + code: "accepted-code", + code_verifier: "test-verifier", + ...(clientId ? { client_id: clientId } : {}), + }), + }); + expect(response.status).toBe(clientId === "app-client" ? 200 : 400); + if (clientId !== "app-client") { + expect(await response.json()).toEqual({ error: "invalid_client" }); + } + } + expect(server.requests.map((request) => request.path)).not.toContain("/register"); +}); diff --git a/nodejs/test/csharp-codegen.test.ts b/nodejs/test/csharp-codegen.test.ts index d234e4a107..4d215e1b11 100644 --- a/nodejs/test/csharp-codegen.test.ts +++ b/nodejs/test/csharp-codegen.test.ts @@ -6,6 +6,60 @@ import type { ApiSchema, RpcMethod } from "../../scripts/codegen/utils.ts"; import { postProcessSchema } from "../../scripts/codegen/utils.ts"; import { legacyRequestSchema } from "./legacy-parameters-fixture.ts"; +describe("C# RPC dictionaries", () => { + it.each([undefined, true, { type: "string" } as const])( + "retains open object entries with additionalProperties=%j", + (additionalProperties) => { + const dictionary: JSONSchema7 = { + type: "object", + properties: {}, + ...(additionalProperties === undefined ? {} : { additionalProperties }), + }; + const schema: ApiSchema = { + server: { + sample: { + read: { + rpcMethod: "sample.read", + params: { + title: "SampleRequest", + type: "object", + properties: { environment: dictionary }, + required: ["environment"], + }, + result: { + title: "SampleResult", + type: "object", + properties: { + settings: dictionary, + referenced: { $ref: "#/definitions/Dictionary" }, + entries: { type: "array", items: dictionary }, + closed: { + type: "object", + properties: {}, + additionalProperties: false, + }, + }, + }, + }, + }, + }, + definitions: { Dictionary: dictionary }, + }; + const before = structuredClone(schema); + const code = generateRpcCode(schema); + const valueType = typeof additionalProperties === "object" ? "string" : "JsonElement"; + expect(code).toContain(`IDictionary Environment`); + expect(code).toContain(`IDictionary? Settings`); + expect(code).toContain(`IDictionary? Referenced`); + expect(code).toContain(`IList>? Entries`); + expect(code).toContain("class SampleResultClosed"); + expect(code).not.toContain("class SampleRequestEnvironment"); + expect(code).not.toContain("class SampleResultSettings"); + expect(schema).toEqual(before); + } + ); +}); + function legacySignatureFixture(scope: "server" | "session", legacy: unknown) { const params: JSONSchema7 & { "x-legacy-parameters"?: unknown } = { title: "SampleRequest", @@ -31,6 +85,21 @@ function legacySignatureFixture(scope: "server" | "session", legacy: unknown) { return { schema, params, methods }; } +describe("C# required opaque JSON parameters", () => { + it.each(["server", "session"] as const)("preserves JSON null in %s requests", (scope) => { + const { schema, params } = legacySignatureFixture(scope, undefined); + params.required!.push("config"); + const code = generateRpcCode(schema); + + expect(code).toContain("RunAsync(string name, object? config,"); + expect(code).toContain( + 'Config = CopilotClient.ToJsonElementForWire(config) ?? JsonElement.Parse("null")' + ); + expect(code).not.toContain("ArgumentNullException.ThrowIfNull(config);"); + expect(code).toContain("ArgumentNullException.ThrowIfNull(name);"); + }); +}); + describe("C# additive request entry compatibility", () => { it.each(["server", "session"] as const)( "preserves the exact legacy %s signature and adds a same-name request overload", diff --git a/nodejs/test/customization-directories.test.ts b/nodejs/test/customization-directories.test.ts new file mode 100644 index 0000000000..af06711fa1 --- /dev/null +++ b/nodejs/test/customization-directories.test.ts @@ -0,0 +1,100 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. + +import { once } from "node:events"; +import { createServer, type Socket } from "node:net"; +import { describe, expect, it, onTestFinished } from "vitest"; +import { + createMessageConnection, + StreamMessageReader, + StreamMessageWriter, + type MessageConnection, +} from "vscode-jsonrpc/node.js"; +import { CopilotClient, RuntimeConnection, approveAll } from "../src/index.js"; +import { getSdkProtocolVersion } from "../src/sdkProtocolVersion.js"; + +describe("customization directory adapters", () => { + it.each([ + { name: "omitted", roots: undefined }, + { name: "empty", roots: [] }, + { name: "relative", roots: ["relative-root", "other-root"] }, + ])("preserves supplied roots on create and resume: $name", async ({ roots }) => { + const requests = new Map>(); + const peers: { connection: MessageConnection; socket: Socket }[] = []; + let client: CopilotClient | undefined; + const server = createServer((socket) => { + const connection = createMessageConnection( + new StreamMessageReader(socket), + new StreamMessageWriter(socket) + ); + peers.push({ connection, socket }); + connection.onRequest("connect", () => ({ + ok: true, + protocolVersion: getSdkProtocolVersion(), + version: "adapter-test", + })); + for (const method of ["session.create", "session.resume"]) { + connection.onRequest(method, (params: Record) => { + requests.set(method, params); + return { sessionId: params.sessionId }; + }); + } + connection.onRequest("session.options.update", () => ({})); + connection.onRequest("session.detach", () => ({ success: true })); + connection.onRequest("session.skills.reload", () => ({})); + connection.listen(); + }); + onTestFinished(async () => { + try { + await client?.forceStop(); + } finally { + for (const peer of peers) { + peer.connection.dispose(); + peer.socket.destroy(); + } + if (server.listening) { + await new Promise((resolve, reject) => + server.close((error) => (error ? reject(error) : resolve())) + ); + } + } + }); + server.listen(0, "127.0.0.1"); + await once(server, "listening"); + const address = server.address(); + if (!address || typeof address === "string") throw new Error("Missing test listener"); + client = new CopilotClient({ + connection: RuntimeConnection.forUri(`127.0.0.1:${address.port}`), + }); + const directories = { + skillDirectories: roots?.map((root) => `${root}-skills`), + agentDirectories: roots?.map((root) => `${root}-agents`), + instructionDirectories: roots?.map((root) => `${root}-instructions`), + hookDirectories: roots?.map((root) => `${root}-hooks`), + }; + const config = { + workingDirectory: "request-cwd", + enableConfigDiscovery: false, + enableFileHooks: false, + ...directories, + onPermissionRequest: approveAll, + }; + const session = await client.createSession(config); + await client.resumeSession(session.sessionId, config); + for (const method of ["session.create", "session.resume"]) { + const request = requests.get(method); + expect(request).toBeDefined(); + for (const [key, expected] of Object.entries(directories)) { + if (expected === undefined) { + expect(request).not.toHaveProperty(key); + } else { + expect(request).toHaveProperty(key, expected); + } + } + expect(request).toHaveProperty("enableConfigDiscovery", false); + expect(request).toHaveProperty("enableFileHooks", false); + expect(request).not.toHaveProperty("additionalDirectories"); + expect(request).not.toHaveProperty("customAgents"); + expect(request).toHaveProperty("hooks", false); + } + }); +}); diff --git a/nodejs/test/e2e/accounts_login.e2e.test.ts b/nodejs/test/e2e/accounts_login.e2e.test.ts index f0b08e7759..2440111bca 100644 --- a/nodejs/test/e2e/accounts_login.e2e.test.ts +++ b/nodejs/test/e2e/accounts_login.e2e.test.ts @@ -43,7 +43,7 @@ function clientForTest(): CopilotClient { return client; } -describe("Shared Entra login", () => { +describe.skipIf(process.platform === "linux")("Shared Entra login", () => { it("returns a cancellable flow before discovery and preserves session ownership", async () => { const client = clientForTest(); await using session = await client.createSession({ onPermissionRequest: approveAll }); diff --git a/nodejs/test/e2e/accounts_platform.e2e.test.ts b/nodejs/test/e2e/accounts_platform.e2e.test.ts new file mode 100644 index 0000000000..ae9bf93726 --- /dev/null +++ b/nodejs/test/e2e/accounts_platform.e2e.test.ts @@ -0,0 +1,180 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +import { readFile, writeFile } from "node:fs/promises"; +import { join } from "node:path"; +import { describe, expect, it } from "vitest"; +import { approveAll } from "../../src/index.js"; +import { createSdkTestContext } from "./harness/sdkTestContext.js"; + +describe("Account provider platform support", async () => { + const { copilotClient: client } = await createSdkTestContext({ + useStdio: true, + copilotClientOptions: { + env: { + COPILOT_ENTRA_AUTH_AUD: "api://platform-gate-override", + COPILOT_DEBUG_ENTRA_BROKER_URL: "http://127.0.0.1:1/unused-broker", + }, + }, + }); + + it("keeps ordinary providers available and rejects unsupported Entra login", async () => { + const session = await client.createSession({ onPermissionRequest: approveAll }); + expect( + await session.rpc.accounts.enumerate({ query: { kind: "providers" } }) + ).toMatchObject({ + kind: "providers", + items: expect.arrayContaining([ + { kind: "githubDotCom", label: "GitHub.com", available: true }, + { kind: "proxima", label: "GitHub Enterprise Cloud (*.ghe.com)", available: true }, + ]), + }); + const flow = await session.rpc.accounts.login.begin({ kind: "proxima" }); + try { + expect(flow.step.kind).toBe("input-required"); + } finally { + await session.rpc.accounts.login.cancel({ flowId: flow.flowId }); + } + if (process.platform === "linux") { + expect( + await session.rpc.accounts.enumerate({ + query: { kind: "providers", brokerAvailable: true }, + }) + ).toMatchObject({ + kind: "providers", + items: expect.arrayContaining([ + expect.objectContaining({ kind: "entra", available: false }), + ]), + }); + await expect(session.rpc.accounts.login.begin({ kind: "entra" })).rejects.toThrow( + "only on Windows and macOS" + ); + } + }); +}); + +describe("Saved account platform support", async () => { + const { createClient, env, openAiEndpoint } = await createSdkTestContext({ + useStdio: true, + copilotClientOptions: { + gitHubToken: undefined, + useLoggedInUser: true, + env: { + COPILOT_GITHUB_TOKEN: "", + GH_TOKEN: "", + GITHUB_TOKEN: "", + GITHUB_ASKPASS: "", + COPILOT_GH_HOST: "github.com", + GH_HOST: "github.com", + COPILOT_HMAC_KEY: "", + CAPI_HMAC_KEY: "", + GITHUB_COPILOT_API_TOKEN: "", + COPILOT_ENABLE_ALT_PROVIDERS: "false", + COPILOT_DISABLE_KEYTAR: "1", + }, + }, + }); + it.each(["entraEmu", "githubDotCom"] as const)( + "restores %s only where supported", + async (kind) => { + const path = join(env.COPILOT_HOME, "config.json"); + const authTokens = { + "https://github.com:emu:entra": { + token: "ghu_platform_emu", + expiresAt: "2099-01-01T00:00:00Z", + accountType: "entra", + }, + "https://github.com:entra:github": { token: "ghu_platform_github" }, + }; + const login = kind === "entraEmu" ? "emu" : "entra"; + const profile = { + storeTokenPlaintext: true, + loggedInUsers: [ + { + host: "https://github.com", + login: "emu", + kind: "entraEmu", + derivedFrom: "base@tenant", + }, + { host: "https://github.com", login: "entra", kind: "githubDotCom" }, + ], + lastLoggedInUser: { host: "https://github.com", login }, + authTokens, + }; + await writeFile(path, JSON.stringify(profile)); + await openAiEndpoint.setCopilotUserByToken("ghu_platform_emu", { + login: "emu", + copilot_plan: "individual", + }); + await openAiEndpoint.setCopilotUserByToken("ghu_platform_github", { + login: "entra", + copilot_plan: "individual", + }); + const client = createClient({ gitHubToken: undefined, useLoggedInUser: true }); + try { + await client.start(); + expect(await client.getAuthStatus()).toMatchObject({ + isAuthenticated: kind === "githubDotCom" || process.platform !== "linux", + }); + } finally { + await client.stop(); + } + // The runtime adds a comment header to this JSON fixture. + const saved = (await readFile(path, "utf8")).replace(/^\/\/.*$/gm, ""); + expect(JSON.parse(saved).authTokens).toEqual(authTokens); + } + ); + + it("checks platform support before setCredentials replaces the selected account", async () => { + const path = join(env.COPILOT_HOME, "config.json"); + const authTokens = { + "https://github.com:emu:entra": { token: "ghu_platform_emu" }, + "https://github.com:ordinary:github": { token: "ghu_platform_github" }, + }; + await writeFile( + path, + JSON.stringify({ + storeTokenPlaintext: true, + loggedInUsers: [ + { host: "https://github.com", login: "emu" }, + { host: "https://github.com", login: "ordinary", kind: "githubDotCom" }, + ], + lastLoggedInUser: { host: "https://github.com", login: "ordinary" }, + authTokens, + }) + ); + await openAiEndpoint.setCopilotUserByToken("ghu_platform_github", { + login: "ordinary", + copilot_plan: "individual", + }); + const client = createClient({ gitHubToken: undefined, useLoggedInUser: true }); + try { + const session = await client.createSession({ onPermissionRequest: approveAll }); + const original = await session.rpc.gitHubAuth.getStatus(); + expect(original).toMatchObject({ isAuthenticated: true, login: "ordinary" }); + const update = session.rpc.gitHubAuth.setCredentials({ + credentials: { + type: "user", + host: "https://github.com", + login: "emu", + copilotUser: { login: "emu" }, + }, + }); + if (process.platform === "linux") { + await expect(update).rejects.toThrow("only on Windows and macOS"); + expect(await session.rpc.gitHubAuth.getStatus()).toEqual(original); + } else { + expect(await update).toMatchObject({ success: true }); + expect(await session.rpc.gitHubAuth.getStatus()).toMatchObject({ + isAuthenticated: true, + login: "emu", + }); + } + } finally { + await client.stop(); + } + const saved = (await readFile(path, "utf8")).replace(/^\/\/.*$/gm, ""); + expect(JSON.parse(saved).authTokens).toEqual(authTokens); + }); +}); diff --git a/nodejs/test/e2e/agent_scoped_hooks.e2e.test.ts b/nodejs/test/e2e/agent_scoped_hooks.e2e.test.ts new file mode 100644 index 0000000000..56664421bb --- /dev/null +++ b/nodejs/test/e2e/agent_scoped_hooks.e2e.test.ts @@ -0,0 +1,69 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +import { readFile, writeFile } from "node:fs/promises"; +import { join } from "node:path"; +import { describe, expect, it } from "vitest"; +import { approveAll } from "../../src/index.js"; +import { createSdkTestContext } from "./harness/sdkTestContext.js"; + +describe("Agent-scoped hooks", async () => { + const { copilotClient: client, workDir } = await createSdkTestContext(); + + it("runs scoped commands additively and follows selection without model requests", async () => { + const script = join(workDir, "scoped-hook.cjs"); + const log = join(workDir, "scoped-hook.log"); + await writeFile( + script, + `const fs = require("node:fs"); +const [log, label] = process.argv.slice(2); +fs.appendFileSync(log, label + "\\n"); +process.stdout.write(JSON.stringify({ handled: true, responseContent: label })); +` + ); + let sessionHookCalls = 0; + const scoped = (name: string) => ({ + name, + prompt: "No model request is necessary for this test.", + hooks: { + userPromptSubmitted: [ + { type: "command", exec: process.execPath, args: [script, log, name] }, + ], + }, + }); + const session = await client.createSession({ + onPermissionRequest: approveAll, + customAgents: [scoped("FIRST_SCOPE"), scoped("SECOND_SCOPE")], + hooks: { + onUserPromptSubmitted: () => { + sessionHookCalls++; + return { + additionalContext: "Session hook ran.", + handled: true, + responseContent: "SESSION_ONLY", + }; + }, + }, + }); + try { + const selected = await session.rpc.agent.select({ name: "FIRST_SCOPE" }); + expect(selected.agent.hooks).toBeDefined(); + expect((await session.sendAndWait({ prompt: "First request" }))?.data.content).toBe( + "FIRST_SCOPE" + ); + await session.rpc.agent.select({ name: "SECOND_SCOPE" }); + expect((await session.sendAndWait({ prompt: "Second request" }))?.data.content).toBe( + "SECOND_SCOPE" + ); + await session.rpc.agent.deselect(); + expect((await session.sendAndWait({ prompt: "Default request" }))?.data.content).toBe( + "SESSION_ONLY" + ); + expect(sessionHookCalls).toBe(3); + expect(await readFile(log, "utf8")).toBe("FIRST_SCOPE\nSECOND_SCOPE\n"); + } finally { + await session.disconnect(); + } + }); +}); diff --git a/nodejs/test/e2e/assisted_autopilot_permission.e2e.test.ts b/nodejs/test/e2e/assisted_autopilot_permission.e2e.test.ts index 9349d0091d..37d6346089 100644 --- a/nodejs/test/e2e/assisted_autopilot_permission.e2e.test.ts +++ b/nodejs/test/e2e/assisted_autopilot_permission.e2e.test.ts @@ -4,15 +4,16 @@ import { execFileSync } from "node:child_process"; import { existsSync } from "node:fs"; -import { mkdir, rm, writeFile } from "node:fs/promises"; +import { mkdir, realpath, rm, writeFile } from "node:fs/promises"; import { createServer } from "node:http"; import { join } from "node:path"; import { text } from "node:stream/consumers"; -import { describe, expect, it, onTestFinished } from "vitest"; +import { describe, expect, it, onTestFailed, onTestFinished } from "vitest"; import { createAttributedPermissionResult, type CopilotSession, type NamedProviderConfig, + type PermissionRequest, type ProviderModelConfig, } from "../../src/index.js"; import { createSdkTestContext } from "./harness/sdkTestContext.js"; @@ -21,14 +22,61 @@ import { getNextEventOfType } from "./harness/sdkTestHelper.js"; const JUDGE_OUTPUT = "ALLOW: Explicitly authorized bounded filename search."; const HUMAN_REVIEW_OUTPUT = "DENY: Ask the registered human permission handler."; const SHELL_TOOL = process.platform === "win32" ? "powershell" : "bash"; +const EARLIER_RESTRICTION_MARKER = "ASSISTED_AUTHORIZATION_RESTRICT"; +const LATER_AUTHORIZATION_MARKER = "ASSISTED_AUTHORIZATION_ALLOW"; +const AUTHORIZATION_FIXTURE = "assisted-authorization-updated"; + +type ContractRecommendation = "approve" | "requireApproval" | "excluded"; +type ContractScope = "root" | "subagent"; + +function contractMarker(recommendation: ContractRecommendation): string { + return recommendation === "requireApproval" ? "REQUIRE" : recommendation.toUpperCase(); +} + +function contractShellCallId(scope: ContractScope, recommendation: ContractRecommendation): string { + const owner = scope === "subagent" ? "child" : "root"; + return `assisted-contract-${owner}-${recommendation}`; +} + +function contractFixtureName(scope: ContractScope, recommendation: ContractRecommendation): string { + return `assisted-contract-${recommendation}-${scope}`; +} describe("Assisted permission handling in Autopilot", async () => { const { copilotClient: client, workDir } = await createSdkTestContext({ useStdio: true }); - it("executes eligible shell and path ALLOWs without calling created or resumed SDK hosts", async () => { + const scenarioGroups = ["routing", "updated authorization", "recovery"] as const; + it.each(scenarioGroups)("Assisted Autopilot permissions (%s)", async (group) => { let agentCalls = 0; const judgeOutputs: string[] = []; + const authorizationJudgeRequests: string[] = []; const providerFailures: Error[] = []; + const progress = { + scenario: "fixture", + phase: "setup", + permissionCallbacks: 0, + permissionRequests: 0, + toolCompletions: 0, + taskCompletions: 0, + lastEventType: "none", + }; + const recordEvent = (type: string) => { + progress.lastEventType = type; + if (type === "permission.requested") progress.permissionRequests++; + if (type === "tool.execution_complete") progress.toolCompletions++; + if (type === "session.task_complete") progress.taskCompletions++; + }; + onTestFailed(() => { + console.error( + `[Assisted fixture progress] ${JSON.stringify({ + ...progress, + agentCalls, + judgeCalls: judgeOutputs.length, + authorizationJudgeCalls: authorizationJudgeRequests.length, + providerFailures: providerFailures.length, + })}` + ); + }); const outsideDir = `${workDir}-outside`; await mkdir(outsideDir, { recursive: true }); await writeFile(join(outsideDir, "approval-probe.txt"), "ASSISTED_READ_PROBE\n"); @@ -42,6 +90,10 @@ describe("Assisted permission handling in Autopilot", async () => { }; const isJudge = body.model === "gpt-6-luna"; const messages = JSON.stringify(body.messages); + const latestUserMessage = [...body.messages] + .reverse() + .find((entry) => entry.role === "user"); + const latestUserText = JSON.stringify(latestUserMessage?.content); let message: | { role: "assistant"; content: string } | { @@ -54,9 +106,27 @@ describe("Assisted permission handling in Autopilot", async () => { }>; }; if (isJudge) { - const output = messages.includes("assisted-human-ask") - ? HUMAN_REVIEW_OUTPUT - : JUDGE_OUTPUT; + const earlierRestrictionIndex = messages.lastIndexOf( + EARLIER_RESTRICTION_MARKER + ); + const laterAuthorizationIndex = messages.lastIndexOf( + LATER_AUTHORIZATION_MARKER + ); + const authorizationRoute = laterAuthorizationIndex !== -1; + if (authorizationRoute) { + authorizationJudgeRequests.push(messages); + } + const contractRequiresApproval = + messages.includes(contractFixtureName("root", "requireApproval")) || + messages.includes(contractFixtureName("subagent", "requireApproval")); + const output = authorizationRoute + ? earlierRestrictionIndex !== -1 && + earlierRestrictionIndex < laterAuthorizationIndex + ? JUDGE_OUTPUT + : HUMAN_REVIEW_OUTPUT + : messages.includes("assisted-human-ask") || contractRequiresApproval + ? HUMAN_REVIEW_OUTPUT + : JUDGE_OUTPUT; judgeOutputs.push(output); message = { role: "assistant", content: output }; } else { @@ -64,90 +134,259 @@ describe("Assisted permission handling in Autopilot", async () => { const toolResultCount = body.messages.filter( (entry) => entry.role === "tool" ).length; - const primeIndex = messages.lastIndexOf("ASSISTED_SESSION_PRIME"); - const shellIndex = messages.lastIndexOf("ASSISTED_SHELL_"); - const pathIndex = messages.lastIndexOf("ASSISTED_PATH_ROUTE"); - const humanIndex = messages.lastIndexOf("ASSISTED_HUMAN_"); - const primeRoute = primeIndex > Math.max(shellIndex, pathIndex, humanIndex); - const humanRoute = humanIndex > Math.max(primeIndex, shellIndex, pathIndex); - const shellRoute = shellIndex > Math.max(primeIndex, pathIndex, humanIndex); - const resumedShellRoute = - messages.lastIndexOf("ASSISTED_SHELL_RESUME_ROUTE") === shellIndex; - const humanApproved = messages.includes("ASSISTED_HUMAN_APPROVE_"); - const humanLifecycle = - messages.includes("ASSISTED_HUMAN_APPROVE_RESUME_ROUTE") || - messages.includes("ASSISTED_HUMAN_DENY_RESUME_ROUTE") - ? "resume" - : "create"; - const finalText = humanRoute - ? humanApproved - ? "human-approved" - : "human-denied" - : shellRoute - ? "shell-approved" - : "approval-probe.txt"; - message = primeRoute - ? { role: "assistant", content: "prime-ready" } - : toolResultCount >= 2 - ? { + const contractChildRoute = latestUserText.includes("ASSISTED_CONTRACT_CHILD_"); + const contractSubagentRoute = latestUserText.includes( + "ASSISTED_CONTRACT_SUBAGENT_" + ); + const contractRootRoute = latestUserText.includes("ASSISTED_CONTRACT_ROOT_"); + const authorizationRestrictionRoute = latestUserText.includes( + EARLIER_RESTRICTION_MARKER + ); + const authorizationAllowRoute = latestUserText.includes( + LATER_AUTHORIZATION_MARKER + ); + if (authorizationRestrictionRoute) { + message = { role: "assistant", content: "restriction-recorded" }; + } else if (authorizationAllowRoute) { + message = + toolResultCount >= 2 + ? { role: "assistant", content: "authorization-updated" } + : toolResultCount === 1 + ? { + role: "assistant", + content: "", + tool_calls: [ + { + id: "assisted-authorization-task-complete", + type: "function", + function: { + name: "task_complete", + arguments: JSON.stringify({ + summary: "authorization-updated", + }), + }, + }, + ], + } + : { + role: "assistant", + content: "", + tool_calls: [ + { + id: "assisted-authorization-shell", + type: "function", + function: { + name: SHELL_TOOL, + arguments: JSON.stringify({ + command: + process.platform === "win32" + ? `New-Item -ItemType Directory -Path ${AUTHORIZATION_FIXTURE}` + : `mkdir ${AUTHORIZATION_FIXTURE}`, + description: + "Create the fixture authorized by the latest instruction", + }), + }, + }, + ], + }; + } else if (contractChildRoute || contractSubagentRoute || contractRootRoute) { + const recommendation: ContractRecommendation = latestUserText.includes( + "_REQUIRE" + ) + ? "requireApproval" + : latestUserText.includes("_EXCLUDED") + ? "excluded" + : "approve"; + const scope: ContractScope = + contractChildRoute || contractSubagentRoute ? "subagent" : "root"; + const finalText = `contract-${scope}-${ + recommendation === "approve" ? "approved" : "blocked" + }`; + if (contractSubagentRoute) { + message = + toolResultCount === 0 + ? { + role: "assistant", + content: "", + tool_calls: [ + { + id: `assisted-contract-task-${recommendation}`, + type: "function", + function: { + name: "task", + arguments: JSON.stringify({ + name: "assisted-contract", + description: + "Exercise an Assisted permission request", + prompt: `ASSISTED_CONTRACT_CHILD_${contractMarker( + recommendation + )}: Run the requested shell command once and report whether it completed.`, + agent_type: "task", + mode: "sync", + }), + }, + }, + ], + } + : { + role: "assistant", + content: "", + tool_calls: [ + { + id: `assisted-contract-task-complete-${recommendation}`, + type: "function", + function: { + name: "task_complete", + arguments: JSON.stringify({ + summary: finalText, + }), + }, + }, + ], + }; + } else if (contractChildRoute && toolResultCount > 0) { + message = { role: "assistant", content: finalText }; + } else if ( + (contractChildRoute || contractRootRoute) && + (toolResultCount === 0 || + (recommendation === "excluded" && toolResultCount === 1)) + ) { + const command = + recommendation === "excluded" + ? process.platform === "win32" + ? "Get-Content approval-probe.txt | & $runner" + : "printf payload | $runner" + : process.platform === "win32" + ? `New-Item -ItemType Directory -Path ${contractFixtureName( + scope, + recommendation + )}` + : `mkdir ${contractFixtureName(scope, recommendation)}`; + message = { role: "assistant", - content: finalText, - } - : toolResultCount === 1 - ? { - role: "assistant", - content: "", - tool_calls: [ - { - id: "assisted-autopilot-task-complete", - type: "function", - function: { - name: "task_complete", - arguments: JSON.stringify({ - summary: finalText, - }), + content: "", + tool_calls: [ + { + id: + toolResultCount === 0 + ? contractShellCallId(scope, recommendation) + : `${contractShellCallId( + scope, + recommendation + )}-retry`, + type: "function", + function: { + name: SHELL_TOOL, + arguments: JSON.stringify({ + command, + description: "Exercise Assisted permission routing", + }), + }, + }, + ], + }; + } else { + message = { + role: "assistant", + content: "", + tool_calls: [ + { + id: `assisted-contract-task-complete-${recommendation}`, + type: "function", + function: { + name: "task_complete", + arguments: JSON.stringify({ summary: finalText }), + }, + }, + ], + }; + } + } else { + const primeIndex = messages.lastIndexOf("ASSISTED_SESSION_PRIME"); + const shellIndex = messages.lastIndexOf("ASSISTED_SHELL_"); + const pathIndex = messages.lastIndexOf("ASSISTED_PATH_ROUTE"); + const humanIndex = messages.lastIndexOf("ASSISTED_HUMAN_"); + const primeRoute = primeIndex > Math.max(shellIndex, pathIndex, humanIndex); + const humanRoute = humanIndex > Math.max(primeIndex, shellIndex, pathIndex); + const shellRoute = shellIndex > Math.max(primeIndex, pathIndex, humanIndex); + const resumedShellRoute = + messages.lastIndexOf("ASSISTED_SHELL_RESUME_ROUTE") === shellIndex; + const humanApproved = messages.includes("ASSISTED_HUMAN_APPROVE_"); + const humanLifecycle = + messages.includes("ASSISTED_HUMAN_APPROVE_RESUME_ROUTE") || + messages.includes("ASSISTED_HUMAN_DENY_RESUME_ROUTE") + ? "resume" + : "create"; + const finalText = humanRoute + ? humanApproved + ? "human-approved" + : "human-denied" + : shellRoute + ? "shell-approved" + : "approval-probe.txt"; + message = primeRoute + ? { role: "assistant", content: "prime-ready" } + : toolResultCount >= 2 + ? { + role: "assistant", + content: finalText, + } + : toolResultCount === 1 + ? { + role: "assistant", + content: "", + tool_calls: [ + { + id: "assisted-autopilot-task-complete", + type: "function", + function: { + name: "task_complete", + arguments: JSON.stringify({ + summary: finalText, + }), + }, + }, + ], + } + : { + role: "assistant", + content: "", + tool_calls: [ + { + id: shellRoute + ? "assisted-autopilot-shell" + : humanRoute + ? "assisted-autopilot-human" + : "assisted-autopilot-glob", + type: "function", + function: + shellRoute || humanRoute + ? { + name: SHELL_TOOL, + arguments: JSON.stringify({ + command: humanRoute + ? process.platform === "win32" + ? `New-Item -ItemType Directory -Path assisted-human-ask-${humanApproved ? "approve" : "deny"}-${humanLifecycle}` + : `mkdir assisted-human-ask-${humanApproved ? "approve" : "deny"}-${humanLifecycle}` + : process.platform === "win32" + ? `New-Item -ItemType Directory -Path assisted-shell-${resumedShellRoute ? "resume" : "create"}` + : `mkdir assisted-shell-${resumedShellRoute ? "resume" : "create"}`, + description: humanRoute + ? "Create the human-reviewed SDK permission fixture" + : "Create the authorized SDK permission fixture", + }), + } + : { + name: "glob", + arguments: JSON.stringify({ + pattern: "*.txt", + path: outsideDir, + }), + }, }, - }, - ], - } - : { - role: "assistant", - content: "", - tool_calls: [ - { - id: shellRoute - ? "assisted-autopilot-shell" - : humanRoute - ? "assisted-autopilot-human" - : "assisted-autopilot-glob", - type: "function", - function: - shellRoute || humanRoute - ? { - name: SHELL_TOOL, - arguments: JSON.stringify({ - command: humanRoute - ? process.platform === "win32" - ? `New-Item -ItemType Directory -Path assisted-human-ask-${humanApproved ? "approve" : "deny"}-${humanLifecycle}` - : `mkdir assisted-human-ask-${humanApproved ? "approve" : "deny"}-${humanLifecycle}` - : process.platform === "win32" - ? `New-Item -ItemType Directory -Path assisted-shell-${resumedShellRoute ? "resume" : "create"}` - : `mkdir assisted-shell-${resumedShellRoute ? "resume" : "create"}`, - description: humanRoute - ? "Create the human-reviewed SDK permission fixture" - : "Create the authorized SDK permission fixture", - }), - } - : { - name: "glob", - arguments: JSON.stringify({ - pattern: "*.txt", - path: outsideDir, - }), - }, - }, - ], - }; + ], + }; + } } const choice = { @@ -231,23 +470,40 @@ describe("Assisted permission handling in Autopilot", async () => { modelId: "gpt-4o", wireModel: id, })); - const scenarios = [ - { route: "shell", lifecycle: "create", decision: "judge" }, - { route: "path", lifecycle: "create", decision: "judge" }, - { route: "shell", lifecycle: "resume", decision: "judge" }, - { route: "path", lifecycle: "resume", decision: "judge" }, - { route: "human", lifecycle: "create", decision: "approve" }, - { route: "human", lifecycle: "create", decision: "deny" }, - { route: "human", lifecycle: "resume", decision: "approve" }, - { route: "human", lifecycle: "resume", decision: "deny" }, - ] as const; + const scenarios = + group === "routing" + ? ([ + { route: "shell", lifecycle: "create", decision: "judge" }, + { route: "path", lifecycle: "create", decision: "judge" }, + { route: "shell", lifecycle: "resume", decision: "judge" }, + { route: "path", lifecycle: "resume", decision: "judge" }, + { route: "human", lifecycle: "create", decision: "approve" }, + { route: "human", lifecycle: "create", decision: "deny" }, + { route: "human", lifecycle: "resume", decision: "approve" }, + { route: "human", lifecycle: "resume", decision: "deny" }, + ] as const) + : []; for (const scenario of scenarios) { + progress.scenario = `routing:${JSON.stringify(scenario)}`; + progress.phase = "create-session"; let permissionCallbacks = 0; + const expectedRecommendation = + scenario.decision === "judge" ? "approve" : "requireApproval"; + const recommendationByToolCallId = new Map(); + const recommendationWaiters = new Map< + string, + (recommendation: string | undefined) => void + >(); const recommendations: string[] = []; const recoveryStatuses: string[] = []; const toolResults: Array<{ success?: boolean; result?: { content?: string } }> = []; const decisionSources: string[] = []; - const taskOutcomes: Array<{ success?: boolean; summary?: string }> = []; + const taskOutcomes: Array<{ + success?: boolean; + summary?: string; + outcome?: string; + reason?: string; + }> = []; const sessionConfig = { model: "local/gpt-5.6-sol", providers, @@ -259,8 +515,27 @@ describe("Assisted permission handling in Autopilot", async () => { AUTO_APPROVAL: true, ASSISTED_PERMISSIONS_V2: true, }, - onPermissionRequest: () => { + onPermissionRequest: async (request: PermissionRequest) => { + progress.permissionCallbacks++; permissionCallbacks++; + if (!request.toolCallId) { + throw new Error( + "Expected the permission request to identify its tool call" + ); + } + const recommendation = recommendationByToolCallId.has(request.toolCallId) + ? recommendationByToolCallId.get(request.toolCallId) + : await new Promise((resolve) => { + recommendationWaiters.set(request.toolCallId!, resolve); + }); + recommendationByToolCallId.delete(request.toolCallId); + if (recommendation !== expectedRecommendation) { + throw new Error( + `Expected Assisted recommendation ${expectedRecommendation}, got ${String( + recommendation + )}` + ); + } if (scenario.decision === "judge") { return createAttributedPermissionResult( { kind: "approve-once" }, @@ -288,15 +563,27 @@ describe("Assisted permission handling in Autopilot", async () => { session = await client.createSession(sessionConfig); if (scenario.lifecycle === "resume") { const sessionId = session.sessionId; + progress.phase = "prime-turn"; await session.sendAndWait({ prompt: "ASSISTED_SESSION_PRIME: Reply with prime-ready without tools.", }); + progress.phase = "configure-before-resume"; + await configureAssistedAutopilot(session, workDir); + progress.phase = "verify-before-resume"; + await expectAssistedAutopilotConfigured(session, workDir); + progress.phase = "disconnect-before-resume"; await session.disconnect(); session = undefined; + progress.phase = "resume-session"; session = await client.resumeSession(sessionId, sessionConfig); + progress.phase = "verify-after-resume"; + await expectAssistedAutopilotConfigured(session, workDir); + } else { + progress.phase = "configure-session"; + await configureAssistedAutopilot(session, workDir); } - await configureAssistedAutopilot(session, workDir); session.on((event) => { + recordEvent(event.type); if (event.type === "permission.requested") { const promptRequest = ( event.data as { @@ -309,6 +596,20 @@ describe("Assisted permission handling in Autopilot", async () => { const recommendation = promptRequest?.assistedApproval?.recommendation ?? promptRequest?.autoApproval?.recommendation; + const toolCallId = ( + event.data as { + permissionRequest?: { toolCallId?: string }; + } + ).permissionRequest?.toolCallId; + if (toolCallId) { + const waiter = recommendationWaiters.get(toolCallId); + if (waiter) { + recommendationWaiters.delete(toolCallId); + waiter(recommendation); + } else { + recommendationByToolCallId.set(toolCallId, recommendation); + } + } if (recommendation) recommendations.push(recommendation); } else if (event.type === "permission.completed") { const source = (event.data as { decisionSource?: string }).decisionSource; @@ -324,6 +625,7 @@ describe("Assisted permission handling in Autopilot", async () => { try { const taskComplete = getNextEventOfType(session, "session.task_complete"); + progress.phase = "send-turn"; await session.send({ prompt: scenario.route === "shell" @@ -332,17 +634,18 @@ describe("Assisted permission handling in Autopilot", async () => { ? `ASSISTED_PATH_ROUTE: Use only glob to find *.txt in ${outsideDir} and report the matching filename.` : `ASSISTED_HUMAN_${scenario.decision.toUpperCase()}_${scenario.lifecycle.toUpperCase()}_ROUTE: Try to create the human-reviewed fixture directory once and report human-${scenario.decision === "approve" ? "approved" : "denied"}.`, }); + progress.phase = "await-task-complete"; await taskComplete; } catch (error) { throw new Error(`${JSON.stringify(scenario)} failed`, { cause: error }); } - const expectedCompletions = scenario.route === "path" ? 2 : 1; + progress.phase = "assert-results"; + const expectedCompletions = + scenario.route === "path" && scenario.lifecycle === "create" ? 2 : 1; expect(permissionCallbacks, JSON.stringify(scenario)).toBe(expectedCompletions); expect(recommendations, JSON.stringify(scenario)).toEqual( - Array(expectedCompletions).fill( - scenario.decision === "judge" ? "approve" : "requireApproval" - ) + Array(expectedCompletions).fill(expectedRecommendation) ); expect(decisionSources, JSON.stringify(scenario)).toEqual( Array(expectedCompletions).fill( @@ -387,18 +690,433 @@ describe("Assisted permission handling in Autopilot", async () => { } } finally { if (session) { + progress.phase = "abort-session"; await session.abort(); + progress.phase = "disconnect-session"; await session.disconnect(); } } } + if (group === "routing") { + progress.phase = "assert-routing-totals"; + expect(providerFailures).toEqual([]); + expect(judgeOutputs).toEqual([ + ...Array(4).fill(JUDGE_OUTPUT), + ...Array(4).fill(HUMAN_REVIEW_OUTPUT), + ]); + expect(agentCalls).toBe(scenarios.length * 2 + 4); + progress.phase = "complete"; + return; + } + + if (group === "updated authorization") { + const authorizationJudgeStart = judgeOutputs.length; + let authorizationPermissionCallbacks = 0; + const authorizationRecommendations: string[] = []; + const authorizationDecisionSources: string[] = []; + const authorizationRecoveryStatuses: string[] = []; + const authorizationToolResults: Array<{ toolCallId?: string; success?: boolean }> = []; + const authorizationTaskOutcomes: Array<{ success?: boolean; summary?: string }> = []; + let resolveAuthorizationRecommendation: (recommendation: string | undefined) => void; + const authorizationRecommendation = new Promise((resolve) => { + resolveAuthorizationRecommendation = resolve; + }); + let authorizationSession: CopilotSession | undefined; + progress.scenario = "updated-authorization"; + progress.phase = "create-session"; + try { + authorizationSession = await client.createSession({ + model: "local/gpt-5.6-sol", + providers, + models, + availableTools: [SHELL_TOOL, "task_complete"], + streaming: false, + skipCustomInstructions: true, + featureFlags: { + AUTO_APPROVAL: true, + ASSISTED_PERMISSIONS_V2: true, + }, + onPermissionRequest: async (request) => { + progress.permissionCallbacks++; + authorizationPermissionCallbacks++; + if (request.toolCallId !== "assisted-authorization-shell") { + throw new Error( + `Expected authorization shell permission, got ${String( + request.toolCallId + )}` + ); + } + const recommendation = await authorizationRecommendation; + if (recommendation !== "approve") { + throw new Error( + `Expected later authorization to reach the judge as approve, got ${String( + recommendation + )}` + ); + } + return createAttributedPermissionResult( + { kind: "approve-once" }, + { + outcome: "auto_approved", + source: "assisted_approval", + surface: "sdk", + responseCapability: "headless", + } + ); + }, + }); + progress.phase = "restriction-turn"; + await authorizationSession.sendAndWait({ + prompt: `${EARLIER_RESTRICTION_MARKER}: Do not create the authorization fixture. Reply restriction-recorded without tools.`, + }); + progress.phase = "configure-session"; + await configureAssistedAutopilot(authorizationSession, workDir); + authorizationSession.on((event) => { + recordEvent(event.type); + if (event.type === "permission.requested") { + const recommendation = ( + event.data as { + promptRequest?: { + assistedApproval?: { recommendation?: string }; + }; + } + ).promptRequest?.assistedApproval?.recommendation; + if (recommendation) { + authorizationRecommendations.push(recommendation); + } + resolveAuthorizationRecommendation(recommendation); + } else if (event.type === "permission.completed") { + const source = (event.data as { decisionSource?: string }).decisionSource; + if (source) authorizationDecisionSources.push(source); + } else if (event.type === "session.permission_recovery") { + authorizationRecoveryStatuses.push( + (event.data as { status: string }).status + ); + } else if (event.type === "tool.execution_complete") { + authorizationToolResults.push({ + toolCallId: event.data.toolCallId, + success: event.data.success, + }); + } else if (event.type === "session.task_complete") { + authorizationTaskOutcomes.push(event.data); + } + }); + + const taskComplete = getNextEventOfType( + authorizationSession, + "session.task_complete" + ); + progress.phase = "send-turn"; + await authorizationSession.send({ + prompt: `${LATER_AUTHORIZATION_MARKER}: The earlier restriction is superseded. Create the authorization fixture now and report authorization-updated.`, + }); + progress.phase = "await-task-complete"; + await taskComplete; + + progress.phase = "assert-results"; + expect(judgeOutputs.slice(authorizationJudgeStart)).toEqual([JUDGE_OUTPUT]); + expect(authorizationJudgeRequests).toHaveLength(1); + const authorizationRequest = authorizationJudgeRequests[0] ?? ""; + expect( + authorizationRequest.indexOf(EARLIER_RESTRICTION_MARKER) + ).toBeGreaterThanOrEqual(0); + expect(authorizationRequest.indexOf(LATER_AUTHORIZATION_MARKER)).toBeGreaterThan( + authorizationRequest.indexOf(EARLIER_RESTRICTION_MARKER) + ); + expect(authorizationPermissionCallbacks).toBe(1); + expect(authorizationRecommendations).toEqual(["approve"]); + expect(authorizationDecisionSources).toEqual(["assisted_approval"]); + expect(authorizationRecoveryStatuses).toEqual([]); + expect(authorizationToolResults).toContainEqual({ + toolCallId: "assisted-authorization-shell", + success: true, + }); + expect(authorizationTaskOutcomes).toEqual([ + expect.objectContaining({ + success: true, + summary: "authorization-updated", + }), + ]); + expect(existsSync(join(workDir, AUTHORIZATION_FIXTURE))).toBe(true); + } finally { + if (authorizationSession) { + progress.phase = "abort-session"; + await authorizationSession.abort(); + progress.phase = "disconnect-session"; + await authorizationSession.disconnect(); + } + } + expect(providerFailures).toEqual([]); + progress.phase = "complete"; + return; + } + + const contractScenarios = [ + { scope: "root", recommendation: "approve", handler: "registered" }, + { scope: "root", recommendation: "requireApproval", handler: "registered" }, + { scope: "root", recommendation: "excluded", handler: "registered" }, + { scope: "subagent", recommendation: "approve", handler: "registered" }, + { scope: "subagent", recommendation: "requireApproval", handler: "registered" }, + { scope: "root", recommendation: "requireApproval", handler: "none" }, + ] as const satisfies readonly { + scope: ContractScope; + recommendation: ContractRecommendation; + handler: "registered" | "none"; + }[]; + const contractJudgeStart = judgeOutputs.length; + for (const scenario of contractScenarios) { + progress.scenario = `recovery:${JSON.stringify(scenario)}`; + progress.phase = "create-session"; + let permissionCallbacks = 0; + const recommendations: string[] = []; + const recoveryStatuses: string[] = []; + const decisionSources: string[] = []; + const sequence: string[] = []; + const subagentIds = new Set(); + const permissionAgentIds: Array = []; + const toolResults: Array<{ + agentId?: string; + toolCallId?: string; + success?: boolean; + error?: { message?: string }; + result?: unknown; + }> = []; + const taskOutcomes: Array<{ success?: boolean; summary?: string }> = []; + const onPermissionRequest = () => { + progress.permissionCallbacks++; + permissionCallbacks++; + sequence.push("host"); + if (scenario.recommendation === "approve") { + return createAttributedPermissionResult( + { kind: "approve-once" }, + { + outcome: "auto_approved", + source: "assisted_approval", + surface: "sdk", + responseCapability: "headless", + } + ); + } + if (scenario.recommendation === "requireApproval") { + return createAttributedPermissionResult( + { + kind: "reject", + feedback: HUMAN_REVIEW_OUTPUT.replace(/^DENY:\s*/, ""), + }, + { + outcome: "autopilot_denied", + source: "assisted_approval", + surface: "sdk", + responseCapability: "headless", + } + ); + } + if (scenario.recommendation === "excluded") { + return createAttributedPermissionResult( + { kind: "user-not-available" }, + { + outcome: "autopilot_denied", + source: "unattended_fallback", + surface: "sdk", + responseCapability: "headless", + } + ); + } + throw new Error("Unexpected Assisted permission recommendation"); + }; + const sessionConfig = { + model: "local/gpt-5.6-sol", + providers, + models, + availableTools: [SHELL_TOOL, "task", "task_complete"], + streaming: false, + skipCustomInstructions: true, + featureFlags: { + AUTO_APPROVAL: true, + ASSISTED_PERMISSIONS_V2: true, + }, + ...(scenario.handler === "registered" ? { onPermissionRequest } : {}), + } as const; + let session: CopilotSession | undefined; + try { + session = await client.createSession(sessionConfig); + progress.phase = "configure-session"; + await configureAssistedAutopilot(session, workDir); + session.on((event) => { + recordEvent(event.type); + if (event.type === "permission.requested") { + const promptRequest = ( + event.data as { + promptRequest?: { + assistedApproval?: { recommendation?: string }; + autoApproval?: { recommendation?: string }; + }; + } + ).promptRequest; + const recommendation = + promptRequest?.assistedApproval?.recommendation ?? + promptRequest?.autoApproval?.recommendation; + if (recommendation) { + recommendations.push(recommendation); + sequence.push(`permission:${recommendation}`); + } + permissionAgentIds.push(event.agentId); + } else if (event.type === "permission.completed") { + const source = (event.data as { decisionSource?: string }).decisionSource; + if (source) decisionSources.push(source); + } else if (event.type === "session.permission_recovery") { + const status = (event.data as { status: string }).status; + recoveryStatuses.push(status); + sequence.push(`recovery:${status}`); + } else if (event.type === "tool.execution_complete") { + toolResults.push({ + agentId: event.agentId, + toolCallId: event.data.toolCallId, + success: event.data.success, + error: event.data.error, + result: event.data.result, + }); + } else if (event.type === "session.task_complete") { + taskOutcomes.push(event.data); + } else if (event.type === "subagent.started") { + subagentIds.add(event.agentId); + } + }); + + const marker = contractMarker(scenario.recommendation); + const prompt = + scenario.scope === "root" + ? `ASSISTED_CONTRACT_ROOT_${marker}: Run the requested shell command once and report the outcome.` + : `ASSISTED_CONTRACT_SUBAGENT_${marker}: Use the task tool once so a task subagent runs the requested shell command.`; + try { + const taskComplete = getNextEventOfType(session, "session.task_complete"); + progress.phase = "send-turn"; + await session.send({ prompt }); + progress.phase = "await-task-complete"; + await taskComplete; + } catch (error) { + throw new Error(`${JSON.stringify(scenario)} failed`, { cause: error }); + } + + progress.phase = "assert-results"; + if (scenario.handler === "none") { + expect(permissionCallbacks, JSON.stringify(scenario)).toBe(0); + expect(recommendations, JSON.stringify(scenario)).toEqual([]); + } else if (scenario.recommendation === "approve") { + expect(permissionCallbacks, JSON.stringify(scenario)).toBe(1); + expect(recommendations, JSON.stringify(scenario)).toEqual(["approve"]); + } else if (scenario.recommendation === "requireApproval") { + expect(permissionCallbacks, JSON.stringify(scenario)).toBe(1); + expect(recommendations, JSON.stringify(scenario)).toEqual(["requireApproval"]); + } else { + expect(permissionCallbacks, JSON.stringify(scenario)).toBeGreaterThanOrEqual(1); + expect(recommendations, JSON.stringify(scenario)).toEqual( + Array(permissionCallbacks).fill("excluded") + ); + } + if (scenario.handler === "none") { + expect(decisionSources, JSON.stringify(scenario)).toEqual([]); + expect(recoveryStatuses, JSON.stringify(scenario)).toEqual(["recovering"]); + } else { + expect(decisionSources, JSON.stringify(scenario)).toContain( + scenario.recommendation === "excluded" + ? "unattended_fallback" + : "assisted_approval" + ); + } + if (scenario.recommendation === "excluded") { + expect(recoveryStatuses.at(-1), JSON.stringify(scenario)).toBe("blocked"); + } else if (scenario.handler === "registered") { + expect(recoveryStatuses, JSON.stringify(scenario)).toEqual([]); + } + const hostIndex = sequence.indexOf("host"); + const firstRecoveryIndex = sequence.findIndex((entry) => + entry.startsWith("recovery:") + ); + if (firstRecoveryIndex !== -1 && scenario.handler === "registered") { + expect( + hostIndex, + JSON.stringify({ scenario, sequence }) + ).toBeGreaterThanOrEqual(0); + expect(hostIndex, JSON.stringify({ scenario, sequence })).toBeLessThan( + firstRecoveryIndex + ); + } + + const shellResult = toolResults.find( + (result) => + result.toolCallId === + contractShellCallId(scenario.scope, scenario.recommendation) + ); + expect(shellResult, JSON.stringify({ scenario, toolResults })).toBeDefined(); + expect(shellResult?.success, JSON.stringify(scenario)).toBe( + scenario.recommendation === "approve" && scenario.handler === "registered" + ); + if ( + scenario.recommendation === "requireApproval" && + scenario.handler === "registered" + ) { + expect(shellResult?.error?.message, JSON.stringify(scenario)).toContain( + "Ask the registered human permission handler." + ); + } else if (scenario.handler === "none") { + expect(shellResult?.error?.message, JSON.stringify(scenario)).toContain( + "Permission could not be granted automatically." + ); + } + if (scenario.scope === "subagent") { + expect(subagentIds.size, JSON.stringify(scenario)).toBe(1); + expect( + subagentIds.has(shellResult?.agentId ?? ""), + JSON.stringify(scenario) + ).toBe(true); + for (const agentId of permissionAgentIds) { + expect(subagentIds.has(agentId ?? ""), JSON.stringify(scenario)).toBe(true); + } + } + + expect( + existsSync( + join(workDir, contractFixtureName(scenario.scope, scenario.recommendation)) + ), + JSON.stringify(scenario) + ).toBe(scenario.recommendation === "approve" && scenario.handler === "registered"); + progress.phase = "read-pending-requests"; + expect( + (await session.rpc.permissions.pendingRequests()).items, + JSON.stringify(scenario) + ).toEqual([]); + expect(taskOutcomes, JSON.stringify(scenario)).toHaveLength(1); + expect(taskOutcomes[0]?.success, JSON.stringify(scenario)).toBe( + scenario.recommendation !== "excluded" && scenario.handler === "registered" + ); + if (scenario.handler === "none") { + expect(taskOutcomes[0], JSON.stringify(scenario)).toMatchObject({ + outcome: "continue", + reason: "Autopilot is still recovering from a required permission.", + }); + } + } finally { + if (session) { + progress.phase = "abort-session"; + await session.abort(); + progress.phase = "disconnect-session"; + await session.disconnect(); + } + } + } + + progress.phase = "assert-recovery-totals"; expect(providerFailures).toEqual([]); - expect(judgeOutputs).toEqual([ - ...Array(4).fill(JUDGE_OUTPUT), - ...Array(4).fill(HUMAN_REVIEW_OUTPUT), + expect(judgeOutputs.slice(contractJudgeStart)).toEqual([ + JUDGE_OUTPUT, + HUMAN_REVIEW_OUTPUT, + JUDGE_OUTPUT, + HUMAN_REVIEW_OUTPUT, + HUMAN_REVIEW_OUTPUT, ]); - expect(agentCalls).toBe(scenarios.length * 2 + 4); + progress.phase = "complete"; }); }); @@ -422,3 +1140,15 @@ async function configureAssistedAutopilot(session: CopilotSession, workDir: stri }); await session.rpc.mode.set({ mode: "autopilot" }); } + +async function expectAssistedAutopilotConfigured( + session: CopilotSession, + workDir: string +): Promise { + expect(await session.rpc.mode.get()).toBe("autopilot"); + expect(await session.rpc.permissions.getMode()).toMatchObject({ mode: "assisted" }); + expect((await session.rpc.permissions.folderTrust.isTrusted({ path: workDir })).trusted).toBe( + true + ); + expect((await session.rpc.permissions.paths.list()).primary).toBe(await realpath(workDir)); +} diff --git a/nodejs/test/e2e/auto_fusion_routing.e2e.test.ts b/nodejs/test/e2e/auto_fusion_routing.e2e.test.ts new file mode 100644 index 0000000000..97bbd2b799 --- /dev/null +++ b/nodejs/test/e2e/auto_fusion_routing.e2e.test.ts @@ -0,0 +1,908 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +import { beforeEach, describe, expect, it, onTestFailed } from "vitest"; +import { approveAll, CopilotRequestHandler, type SessionEvent } from "../../src/index.js"; +import { createSdkTestContext, DEFAULT_GITHUB_TOKEN } from "./harness/sdkTestContext.js"; +import { isByokBackend } from "./harness/testBackend.js"; + +const MODEL = "gpt-5.6-sol"; +const REPLY = "AUTO_FUSION_EXECUTION_DONE"; + +class AutoFusionRequestHandler extends CopilotRequestHandler { + readonly plans: Record[] = []; + readonly auto: Record[] = []; + readonly inference: Record[] = []; + failRoute = false; + disabled = false; + constituentsEnabled = true; + inferenceGate?: { + entered: ReturnType>; + release: ReturnType>; + }; + + constructor( + private readonly planVersion: "1" | "2", + private readonly executionModel = MODEL + ) { + super(); + } + + protected override async sendRequest(request: Request): Promise { + const path = new URL(request.url).pathname; + const models = { + data: [...new Set([MODEL, this.executionModel])].map((id) => ({ + id, + name: id, + model_picker_enabled: this.constituentsEnabled, + policy: { state: this.constituentsEnabled ? "enabled" : "disabled" }, + supported_endpoints: ["/chat/completions"], + capabilities: { + supports: { streaming: true, tool_calls: true, vision: true }, + limits: { + max_prompt_tokens: 128000, + max_output_tokens: 16384, + max_context_window_tokens: 144384, + }, + }, + })), + }; + if (path === "/models") return Response.json(models); + if (path === "/meta") { + return Response.json({ + models, + auto: { + default_tier: "Balance-V2", + tiers: [ + { + id: "Balance-V2", + display_name: "Balance", + description: "", + type: "auto", + status: { enabled: true }, + }, + { + id: "Other-V2", + display_name: "Ordinary Auto", + description: "", + type: "auto", + status: { enabled: !this.disabled, message: "Contact admin" }, + }, + ...["hydrafusion", "hydrafusion-max"].map((id) => ({ + id, + display_name: id, + description: "", + type: "fusion", + status: { enabled: !this.disabled, message: "Contact admin" }, + })), + { + id: "future-fusion", + display_name: "Future", + description: "", + type: "fusion", + status: { enabled: true }, + }, + { + id: "future-type", + display_name: "Unknown", + description: "", + type: "ensemble", + status: { enabled: true }, + }, + ], + }, + }); + } + if (path === "/model/fusion") { + const body = (await request.json()) as Record; + this.plans.push(body); + if (this.failRoute) + return Response.json({ message: "Route unavailable" }, { status: 500 }); + return Response.json({ + fusion_mode: body.fusion_mode, + fusion_pattern: "solo", + plan_version: this.planVersion, + steps: [{ role: "generation", model_id: this.executionModel }], + session: { token: "jwt.fake.auto.fusion.plan", expires_at: 0 }, + }); + } + if (path === "/auto") { + this.auto.push((await request.json()) as Record); + return Response.json({ + selected_model: models.data[0], + session_token: "jwt.fake.auto.fusion.reset", + expires_at: Math.floor(Date.now() / 1000) + 3600, + }); + } + if (path === "/chat/completions") { + const body = (await request.json()) as Record; + this.inference.push(body); + const gate = this.inferenceGate; + if (gate) { + gate.entered.resolve(); + await gate.release.promise; + } + if (body.model !== MODEL && body.model !== this.executionModel) + throw new Error(`Synthetic model escaped to inference: ${body.model}`); + const completion = { + id: "chatcmpl-auto-fusion", + object: "chat.completion", + created: 0, + model: body.model, + choices: [ + { + index: 0, + message: { role: "assistant", content: REPLY }, + finish_reason: "stop", + }, + ], + usage: { prompt_tokens: 11, completion_tokens: 7, total_tokens: 18 }, + }; + if (!body.stream) return Response.json(completion); + return new Response( + `data: ${JSON.stringify({ + ...completion, + object: "chat.completion.chunk", + choices: [ + { + index: 0, + delta: { role: "assistant", content: REPLY }, + finish_reason: "stop", + }, + ], + })}\n\ndata: [DONE]\n\n`, + { headers: { "content-type": "text/event-stream" } } + ); + } + throw new Error(`Unexpected routing request: ${request.method} ${path}`); + } +} + +describe.skipIf(isByokBackend).each([ + { mode: "hydrafusion", version: "1" as const }, + { mode: "hydrafusion", version: "2" as const }, + { mode: "hydrafusion-max", version: "2" as const }, +])("Metadata Auto execution: $mode v$version", async ({ mode, version }) => { + const executionModel = mode === "hydrafusion" ? "claude-sonnet-5" : MODEL; + const handler = new AutoFusionRequestHandler(version, executionModel); + const { + copilotClient: client, + createClient, + workDir, + } = await createSdkTestContext({ + useStdio: true, + copilotClientOptions: { + gitHubToken: DEFAULT_GITHUB_TOKEN, + requestHandler: handler, + env: { + DYNAMIC_AUTO_TIERS: "false", + COPILOT_EXP_COPILOT_CLI_DYNAMIC_AUTO_TIERS: "false", + HYDRAFUSION: "false", + HYDRAFUSION_ROLLOUT: "false", + COPILOT_EXP_COPILOT_CLI_HYDRAFUSION_ROLLOUT: "false", + HYDRAFUSION_PLAN_V2: String(version === "2"), + COPILOT_EXP_COPILOT_CLI_HYDRAFUSION_PLAN_V2: String(version === "2"), + }, + }, + }); + const config = { + model: "auto", + workingDirectory: workDir, + expAssignments: { + Features: [], + Flights: {}, + Configs: [{ Id: "default", Parameters: { copilot_cli_dynamic_auto_tiers: true } }], + AssignmentContext: "metadata-fusion-treatment", + }, + capi: { enableWebSocketResponses: false }, + onPermissionRequest: approveAll, + }; + beforeEach(() => { + handler.plans.length = 0; + handler.auto.length = 0; + handler.inference.length = 0; + handler.failRoute = false; + handler.disabled = false; + handler.constituentsEnabled = true; + handler.inferenceGate = undefined; + }); + + it("routes only the selected preference, commits it, resumes cold and resets to Auto", async () => { + const diagnosticStart = performance.now(); + const phases: { phase: string; elapsedMs: number }[] = []; + const markPhase = (phase: string) => + phases.push({ phase, elapsedMs: Math.round(performance.now() - diagnosticStart) }); + onTestFailed(() => { + process.stderr.write( + `${JSON.stringify({ + test: "metadata Auto create/resume/reset", + mode, + version, + phases, + planCount: handler.plans.length, + autoCount: handler.auto.length, + inferenceCount: handler.inference.length, + })}\n` + ); + }); + markPhase("creating session"); + const session = await client.createSession(config); + markPhase("listing models"); + const catalog = await session.rpc.model.list(); + expect(catalog.auto?.tiers.some((tier) => tier.id === mode)).toBe(true); + expect( + catalog.list.some( + (model) => + typeof model === "object" && + model !== null && + "id" in model && + typeof model.id === "string" && + model.id.startsWith("hydrafusion") + ) + ).toBe(false); + expect(handler.plans).toEqual([]); + await expect(session.setAutoTier("future-fusion")).rejects.toThrow("unsupported execution"); + await expect(session.setAutoTier("future-type")).rejects.toThrow("unsupported execution"); + await expect(session.setAutoTier("HydraFusion")).rejects.toThrow("unavailable"); + const pending = await session.setAutoTier(mode); + markPhase("first turn"); + expect(pending.pendingAutoTier).toBe(mode); + expect((await session.rpc.model.getCurrent()).autoTier).toBeUndefined(); + const events: SessionEvent[] = []; + session.on((event) => events.push(event)); + expect( + ( + await session.sendAndWait({ + prompt: "What is the chemical symbol for gold? Do not use tools.", + }) + )?.data.content + ).toBe(REPLY); + expect(handler.plans).toEqual([ + expect.objectContaining({ + fusion_mode: mode, + ...(version === "2" ? { plan_version: version } : {}), + }), + ]); + expect(handler.plans[0]).not.toHaveProperty("ensemble"); + if (version === "1") expect(handler.plans[0]).not.toHaveProperty("plan_version"); + expect(handler.auto).toEqual([]); + expect(events.some((event) => event.type === "session.fusion_route_failed")).toBe(false); + expect(events).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + type: "session.fusion_completed", + data: expect.objectContaining({ syntheticModel: mode }), + }), + ]) + ); + expect(events).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + type: "session.fusion_resolved", + data: expect.objectContaining({ + syntheticModel: mode, + policy: mode === "hydrafusion" ? "standard" : "max", + primaryModel: executionModel, + }), + }), + ]) + ); + expect(await session.rpc.model.getCurrent()).toMatchObject({ + modelId: "auto", + autoTier: mode, + }); + const sessionId = session.sessionId; + markPhase("cold client shutdown"); + await session.disconnect(); + await client.stop(); + const coldClient = createClient({ requestHandler: handler }); + try { + markPhase("cold resume"); + const resumed = await coldClient.resumeSession(sessionId, config); + markPhase("cold structured-output admission"); + expect(await resumed.rpc.model.getCurrent()).toMatchObject({ + modelId: "auto", + autoTier: mode, + }); + await expect( + resumed.sendAndWait({ + prompt: "Return a structured chemical symbol before the resumed turn.", + responseSchema: { type: "object", properties: { symbol: { type: "string" } } }, + }) + ).rejects.toThrow("responseFormat is not supported"); + expect(handler.plans).toHaveLength(1); + + markPhase("resumed turn"); + expect( + ( + await resumed.sendAndWait({ + prompt: "What is the chemical symbol for silver? Do not use tools.", + }) + )?.data.content + ).toBe(REPLY); + expect(handler.plans).toHaveLength(2); + markPhase("switch concrete then auto"); + await resumed.setModel(MODEL); + expect((await resumed.rpc.model.getCurrent()).autoTier).toBe(mode); + await resumed.setModel("auto"); + expect( + ( + await resumed.sendAndWait({ + prompt: "Name the chemical symbol for iron. Do not use tools.", + }) + )?.data.content + ).toBe(REPLY); + expect(handler.plans).toHaveLength(3); + markPhase("switch ordinary auto"); + await resumed.setModel("auto", { autoTier: "Balance-V2" }); + expect((await resumed.rpc.model.getCurrent()).autoTier).toBe(mode); + expect( + ( + await resumed.sendAndWait({ + prompt: "What is the chemical symbol for copper? Do not use tools.", + }) + )?.data.content + ).toBe(REPLY); + expect(handler.plans).toHaveLength(3); + expect(handler.plans.some((plan) => "ensemble" in plan)).toBe(false); + expect(handler.auto.at(-1)).toMatchObject({ tier: "Balance-V2", ensemble: true }); + expect(await resumed.rpc.model.getCurrent()).toMatchObject({ + modelId: "auto", + autoTier: "Balance-V2", + }); + await resumed.setAutoTier(null); + markPhase("reset turn"); + expect( + ( + await resumed.sendAndWait({ + prompt: "Name the chemical symbol for lead. Do not use tools.", + }) + )?.data.content + ).toBe(REPLY); + expect((await resumed.rpc.model.getCurrent()).autoTier).toBeUndefined(); + expect(handler.plans).toHaveLength(3); + await resumed.disconnect(); + } finally { + markPhase("cold client cleanup"); + await coldClient.stop(); + } + markPhase("complete"); + }); + + it("executes explicit create and atomic Auto selection without rewriting the logical model", async () => { + const created = await client.createSession({ + ...config, + capi: { ...config.capi, autoTier: mode }, + }); + expect( + ( + await created.sendAndWait({ + prompt: "Name the chemical symbol for gold. Do not use tools.", + }) + )?.data.content + ).toBe(REPLY); + expect(await created.rpc.model.getCurrent()).toMatchObject({ + modelId: "auto", + autoTier: mode, + }); + await created.disconnect(); + const switched = await client.createSession({ ...config, model: MODEL }); + await switched.setModel("auto", { autoTier: mode }); + expect((await switched.rpc.model.getCurrent()).pendingAutoTier).toBe(mode); + expect( + ( + await switched.sendAndWait({ + prompt: "Name the chemical symbol for silver. Do not use tools.", + }) + )?.data.content + ).toBe(REPLY); + expect(await switched.rpc.model.getCurrent()).toMatchObject({ + modelId: "auto", + autoTier: mode, + }); + expect(handler.plans).toHaveLength(2); + expect( + handler.plans.every( + (plan) => + plan.fusion_mode === mode && + plan.plan_version === (version === "2" ? version : undefined) + ) + ).toBe(true); + expect(handler.auto).toEqual([]); + await switched.disconnect(); + }); + + it("retains typed Fusion route-failure fallback and commits only the concrete response", async () => { + handler.failRoute = true; + const session = await client.createSession(config); + await session.setAutoTier(mode); + const events: SessionEvent[] = []; + session.on((event) => events.push(event)); + expect( + ( + await session.sendAndWait({ + prompt: "What is the chemical symbol for gold? Do not use tools.", + }) + )?.data.content + ).toBe(REPLY); + expect(events.some((event) => event.type === "session.fusion_route_failed")).toBe(true); + expect(events.filter((event) => event.type === "assistant.message")).toHaveLength(1); + expect(await session.rpc.model.getCurrent()).toMatchObject({ + modelId: "auto", + autoTier: mode, + }); + expect(handler.plans).toHaveLength(1); + expect(handler.auto).toEqual([]); + await session.disconnect(); + }); + + it("applies reasoning-effort constraints consistently to dedicated Fusion setters", async () => { + const session = await client.createSession(config); + await session.rpc.model.setReasoningEffort({ reasoningEffort: "high" }); + await expect(session.setAutoTier(mode)).rejects.toThrow("do not support reasoning effort"); + expect((await session.rpc.model.getCurrent()).reasoningEffort).toBe("high"); + expect((await session.rpc.model.getCurrent()).pendingAutoTier).toBeUndefined(); + await session.rpc.model.setReasoningEffort({ reasoningEffort: "none" }); + await session.setAutoTier(mode); + await expect( + session.rpc.model.setReasoningEffort({ reasoningEffort: "high" }) + ).rejects.toThrow("do not support reasoning effort"); + await session.rpc.model.setReasoningEffort({ reasoningEffort: "none" }); + await session.setAutoTier(null); + await session.rpc.model.setReasoningEffort({ reasoningEffort: "high" }); + expect((await session.rpc.model.getCurrent()).reasoningEffort).toBe("high"); + await session.disconnect(); + }); + + it("retains the staged Fusion preference when the first inference is cancelled", async () => { + const session = await client.createSession({ + ...config, + capi: { ...config.capi, autoTier: "Balance-V2" }, + }); + const events: SessionEvent[] = []; + session.on((event) => events.push(event)); + await session.setAutoTier(mode); + const gate = { + entered: Promise.withResolvers(), + release: Promise.withResolvers(), + }; + handler.inferenceGate = gate; + const turn = session + .sendAndWait({ + prompt: "What is the chemical symbol for gold? Do not use tools.", + }) + .then( + (reply) => ({ reply, error: undefined }), + (error: Error) => ({ reply: undefined, error }) + ); + try { + await gate.entered.promise; + expect((await session.rpc.model.getCurrent()).activatingAutoTier).toBe(mode); + await session.abort(); + } finally { + gate.release.resolve(); + handler.inferenceGate = undefined; + } + await turn; + expect(await session.rpc.model.getCurrent()).toMatchObject({ + modelId: "auto", + autoTier: "Balance-V2", + pendingAutoTier: mode, + }); + expect((await session.rpc.model.getCurrent()).activatingAutoTier).toBeUndefined(); + expect(events.some((event) => event.type === "session.auto_tier_switch_failed")).toBe( + false + ); + expect( + ( + await session.sendAndWait({ + prompt: "What is the chemical symbol for silver? Do not use tools.", + }) + )?.data.content + ).toBe(REPLY); + expect(handler.plans).toHaveLength(2); + expect(handler.auto).toEqual([]); + expect(await session.rpc.model.getCurrent()).toMatchObject({ + modelId: "auto", + autoTier: mode, + }); + await session.disconnect(); + }); + + it.each(["fusion", "auto"] as const)( + "settles an invalidated pending %s preference once and preserves the incumbent", + async (execution) => { + await client.stop(); + const session = await client.createSession({ + ...config, + capi: { ...config.capi, autoTier: "Balance-V2" }, + }); + const events: SessionEvent[] = []; + session.on((event) => events.push(event)); + const requested = execution === "fusion" ? mode : "Other-V2"; + await session.setAutoTier(requested); + expect((await session.rpc.model.getCurrent()).pendingAutoTier).toBe(requested); + handler.disabled = true; + await session.rpc.model.list({ skipCache: true }); + await session + .sendAndWait({ + prompt: "What is the chemical symbol for gold? Do not use tools.", + }) + .then( + (reply) => expect(reply?.data.content).toBe(REPLY), + (error: unknown) => { + expect(error).toBeInstanceOf(Error); + expect((error as Error).message).toContain("Contact admin"); + } + ); + const current = await session.rpc.model.getCurrent(); + expect(current.autoTier).toBe("Balance-V2"); + expect(current.pendingAutoTier).toBeUndefined(); + expect(current.activatingAutoTier).toBeUndefined(); + expect( + events.filter((event) => event.type === "session.auto_tier_switch_failed") + ).toEqual([ + expect.objectContaining({ + data: expect.objectContaining({ + requestedAutoTier: requested, + effectiveAutoTier: "Balance-V2", + reason: "unsupported", + }), + }), + ]); + expect( + ( + await session.sendAndWait({ + prompt: "What is the chemical symbol for silver? Do not use tools.", + }) + )?.data.content + ).toBe(REPLY); + expect(handler.plans).toEqual([]); + expect(handler.auto.every((body) => body.tier === "Balance-V2")).toBe(true); + expect( + events.filter((event) => event.type === "session.auto_tier_switch_failed") + ).toHaveLength(1); + await session.disconnect(); + } + ); + + it("reports setup failure after claiming Fusion and clears only the failed request", async () => { + await client.stop(); + const session = await client.createSession({ + ...config, + capi: { ...config.capi, autoTier: "Balance-V2" }, + }); + const events: SessionEvent[] = []; + const failedTurnIdle = Promise.withResolvers(); + let failureObserved = false; + session.on((event) => { + events.push(event); + if (event.type === "session.error") failureObserved = true; + if (event.type === "session.idle" && failureObserved) failedTurnIdle.resolve(); + }); + await session.setAutoTier(mode); + handler.constituentsEnabled = false; + await session.rpc.model.list({ skipCache: true }); + await expect( + session.sendAndWait({ + prompt: "What is the chemical symbol for gold? Do not use tools.", + }) + ).rejects.toThrow("no executable constituent"); + // sendAndWait rejects on session.error before that failed turn's idle tail settles. + await failedTurnIdle.promise; + // Host-forwarded error/idle can overtake this event's recorded-stream delivery. + await expect + .poll(() => events.filter((event) => event.type === "session.auto_tier_switch_failed")) + .toEqual([ + expect.objectContaining({ + data: expect.objectContaining({ + requestedAutoTier: mode, + effectiveAutoTier: "Balance-V2", + reason: "setup_failed", + }), + }), + ]); + expect(await session.rpc.model.getCurrent()).toMatchObject({ + modelId: "auto", + autoTier: "Balance-V2", + }); + expect((await session.rpc.model.getCurrent()).pendingAutoTier).toBeUndefined(); + expect((await session.rpc.model.getCurrent()).activatingAutoTier).toBeUndefined(); + expect(handler.plans).toEqual([]); + expect(handler.inference).toEqual([]); + handler.constituentsEnabled = true; + await session.rpc.model.list({ skipCache: true }); + expect( + ( + await session.sendAndWait({ + prompt: "What is the chemical symbol for silver? Do not use tools.", + }) + )?.data.content + ).toBe(REPLY); + expect(handler.plans).toEqual([]); + expect( + events.filter((event) => event.type === "session.auto_tier_switch_failed") + ).toHaveLength(1); + await session.disconnect(); + }); + + it("rejects unsupported context and structured output before Fusion activation", async () => { + const session = await client.createSession(config); + await expect( + session.rpc.model.switchTo({ + modelId: "auto", + autoTier: mode, + contextTier: "long", + }) + ).rejects.toThrow("do not support context tiers"); + expect((await session.rpc.model.getCurrent()).autoTier).toBeUndefined(); + await session.setAutoTier(mode); + await expect( + session.sendAndWait({ + prompt: "Return the chemical symbol for gold as structured JSON.", + responseSchema: { + type: "object", + properties: { symbol: { type: "string" } }, + required: ["symbol"], + additionalProperties: false, + }, + }) + ).rejects.toThrow("responseFormat is not supported"); + expect(await session.rpc.model.getCurrent()).toMatchObject({ + modelId: "auto", + pendingAutoTier: mode, + }); + expect((await session.rpc.model.getCurrent()).autoTier).toBeUndefined(); + expect(handler.plans).toEqual([]); + expect(handler.auto).toEqual([]); + expect(handler.inference).toEqual([]); + expect((await session.rpc.queue.pendingItems()).items).toEqual([]); + expect( + (await session.getEvents()).filter( + (event) => event.type === "user.message" || event.type === "session.error" + ) + ).toEqual([]); + await session.disconnect(); + }); +}); + +describe.skipIf(isByokBackend).each(["1", "2"] as const)( + "Manual HydraFusion plan-version control: %s", + async (version) => { + const handler = new AutoFusionRequestHandler(version); + const { copilotClient: client, workDir } = await createSdkTestContext({ + useStdio: true, + copilotClientOptions: { + gitHubToken: DEFAULT_GITHUB_TOKEN, + requestHandler: handler, + env: { + DYNAMIC_AUTO_TIERS: "false", + COPILOT_EXP_COPILOT_CLI_DYNAMIC_AUTO_TIERS: "false", + HYDRAFUSION: "true", + HYDRAFUSION_ROLLOUT: "true", + HYDRAFUSION_PLAN_V2: String(version === "2"), + COPILOT_EXP_COPILOT_CLI_HYDRAFUSION_PLAN_V2: String(version === "2"), + }, + }, + }); + it("retains the manual v1 Max and v2 Standard execution mapping", async () => { + const session = await client.createSession({ + model: "hydrafusion", + enableExperimentalMode: true, + workingDirectory: workDir, + capi: { enableWebSocketResponses: false }, + onPermissionRequest: approveAll, + }); + const events: SessionEvent[] = []; + session.on((event) => events.push(event)); + expect( + ( + await session.sendAndWait({ + prompt: "What is the chemical symbol for gold? Do not use tools.", + }) + )?.data.content + ).toBe(REPLY); + expect(handler.plans).toEqual([ + expect.objectContaining({ + fusion_mode: version === "1" ? "hydrafusion-max" : "hydrafusion", + ...(version === "2" ? { plan_version: "2" } : {}), + }), + ]); + expect(handler.plans[0]).not.toHaveProperty("ensemble"); + expect(events).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + type: "session.fusion_resolved", + data: expect.objectContaining({ + policy: version === "1" ? "max" : "standard", + }), + }), + ]) + ); + expect(events.some((event) => event.type === "session.fusion_route_failed")).toBe( + false + ); + expect((await session.rpc.model.getCurrent()).modelId).toBe("hydrafusion"); + await session.disconnect(); + }); + + if (version === "1") { + it("routes metadata Standard independently of manual Max history and resumes same-policy reuse", async () => { + const planStart = handler.plans.length; + const config = { + model: "hydrafusion", + enableExperimentalMode: true, + workingDirectory: workDir, + capi: { enableWebSocketResponses: false }, + onPermissionRequest: approveAll, + expAssignments: { + Features: [], + Flights: {}, + Configs: [ + { + Id: "default", + Parameters: { + copilot_cli_dynamic_auto_tiers: true, + copilot_cli_hydrafusion_turn_policy: "initial_only", + }, + }, + ], + AssignmentContext: "policy-scoped-fusion-history", + }, + }; + const session = await client.createSession(config); + const events: SessionEvent[] = []; + session.on((event) => events.push(event)); + expect( + ( + await session.sendAndWait({ + prompt: "Name the chemical symbol for gold. Do not use tools.", + }) + )?.data.content + ).toBe(REPLY); + expect(handler.plans.slice(planStart)).toEqual([ + expect.objectContaining({ fusion_mode: "hydrafusion-max" }), + ]); + await session.setModel("auto", { autoTier: "hydrafusion" }); + expect( + ( + await session.sendAndWait({ + prompt: "Name the chemical symbol for silver. Do not use tools.", + }) + )?.data.content + ).toBe(REPLY); + expect(handler.plans.slice(planStart)).toEqual([ + expect.objectContaining({ fusion_mode: "hydrafusion-max" }), + expect.objectContaining({ fusion_mode: "hydrafusion" }), + ]); + expect(handler.plans.some((plan) => "ensemble" in plan)).toBe(false); + expect( + events + .filter((event) => event.type === "session.fusion_resolved") + .map((event) => event.data.policy) + ).toEqual(["max", "standard"]); + expect(await session.rpc.model.getCurrent()).toMatchObject({ + modelId: "auto", + autoTier: "hydrafusion", + }); + const sessionId = session.sessionId; + await session.disconnect(); + await client.stop(); + const resumed = await client.resumeSession(sessionId, { + ...config, + model: "auto", + }); + expect( + ( + await resumed.sendAndWait({ + prompt: "Name the chemical symbol for copper. Do not use tools.", + }) + )?.data.content + ).toBe(REPLY); + expect(handler.plans.slice(planStart)).toHaveLength(2); + expect(await resumed.rpc.model.getCurrent()).toMatchObject({ + modelId: "auto", + autoTier: "hydrafusion", + }); + await resumed.disconnect(); + }); + } + } +); + +describe.skipIf(isByokBackend)("Metadata Fusion rejection before activation", async () => { + const handler = new AutoFusionRequestHandler("1"); + const { copilotClient: client, workDir } = await createSdkTestContext({ + useStdio: true, + copilotClientOptions: { + gitHubToken: DEFAULT_GITHUB_TOKEN, + requestHandler: handler, + env: { + DYNAMIC_AUTO_TIERS: "true", + HYDRAFUSION_PLAN_V2: "false", + COPILOT_EXP_COPILOT_CLI_HYDRAFUSION_PLAN_V2: "false", + }, + }, + }); + + it("rejects Max with actionable plan-v2 guidance without changing incumbent or calling endpoints", async () => { + const config = { + model: "auto", + workingDirectory: workDir, + capi: { autoTier: "Balance-V2" }, + onPermissionRequest: approveAll, + }; + await expect( + client.createSession({ ...config, capi: { autoTier: "hydrafusion-max" } }) + ).rejects.toThrow("HYDRAFUSION_PLAN_V2"); + const session = await client.createSession(config); + await expect(session.setAutoTier("hydrafusion-max")).rejects.toThrow("HYDRAFUSION_PLAN_V2"); + await expect(session.setModel("auto", { autoTier: "hydrafusion-max" })).rejects.toThrow( + "HYDRAFUSION_PLAN_V2" + ); + expect(await session.rpc.model.getCurrent()).toMatchObject({ + modelId: "auto", + autoTier: "Balance-V2", + }); + expect((await session.rpc.model.getCurrent()).pendingAutoTier).toBeUndefined(); + expect(handler.plans).toEqual([]); + expect(handler.auto).toEqual([]); + expect(handler.inference).toEqual([]); + handler.disabled = true; + await session.rpc.model.list({ skipCache: true }); + await expect(session.setAutoTier("hydrafusion")).rejects.toThrow("Contact admin"); + await expect(session.setAutoTier("Other-V2")).rejects.toThrow("Contact admin"); + expect((await session.rpc.model.getCurrent()).autoTier).toBe("Balance-V2"); + await session.disconnect(); + }); + + it("does not settle pending Auto intent when structured-output admission rejects metadata", async () => { + await client.stop(); + handler.disabled = false; + const session = await client.createSession({ + model: "auto", + workingDirectory: workDir, + capi: { autoTier: "Balance-V2", enableWebSocketResponses: false }, + onPermissionRequest: approveAll, + }); + await session.setAutoTier("Other-V2"); + handler.disabled = true; + await session.rpc.model.list({ skipCache: true }); + const before = await session.getEvents(); + await expect( + session.sendAndWait({ + prompt: "Return the chemical symbol for gold as structured JSON.", + responseSchema: { + type: "object", + properties: { symbol: { type: "string" } }, + }, + }) + ).rejects.toThrow("Contact admin"); + expect(await session.rpc.model.getCurrent()).toMatchObject({ + modelId: "auto", + autoTier: "Balance-V2", + pendingAutoTier: "Other-V2", + }); + expect((await session.rpc.model.getCurrent()).activatingAutoTier).toBeUndefined(); + expect((await session.rpc.queue.pendingItems()).items).toEqual([]); + expect(await session.getEvents()).toEqual(before); + expect(handler.plans).toEqual([]); + expect(handler.inference).toEqual([]); + handler.disabled = false; + await session.rpc.model.list({ skipCache: true }); + expect( + ( + await session.sendAndWait({ + prompt: "What is the chemical symbol for silver? Do not use tools.", + }) + )?.data.content + ).toBe(REPLY); + expect(handler.auto.at(-1)).toMatchObject({ tier: "Other-V2" }); + expect(await session.rpc.model.getCurrent()).toMatchObject({ + modelId: "auto", + autoTier: "Other-V2", + }); + await session.disconnect(); + }); +}); diff --git a/nodejs/test/e2e/auto_tier.e2e.test.ts b/nodejs/test/e2e/auto_tier.e2e.test.ts index 45d557a9ea..b2cc17079b 100644 --- a/nodejs/test/e2e/auto_tier.e2e.test.ts +++ b/nodejs/test/e2e/auto_tier.e2e.test.ts @@ -15,7 +15,7 @@ import { isByokBackend } from "./harness/testBackend"; * they assert what the runtime actually recorded rather than what the SDK serialized. */ describe("Auto tier switching", async () => { - const { copilotClient: client, createClient } = await createSdkTestContext(); + const { copilotClient: client, createClient, openAiEndpoint } = await createSdkTestContext(); it("rejects new tier activation while dynamic discovery is disabled", async () => { const session = await client.createSession({ @@ -168,6 +168,15 @@ describe("Auto tier switching", async () => { prompt: "Reply with exactly AUTO_TIER_FAST_COMMITTED.", }); + // Dynamic Auto tiers are disabled in this suite, so routing omits `ensemble`. + const routing = (await openAiEndpoint.getRequests()).filter( + (entry) => entry.url === "/auto" + ); + expect(routing.length).toBeGreaterThan(0); + for (const request of routing) { + expect(JSON.parse(request.body)).not.toHaveProperty("ensemble"); + } + const modelChange = await modelChangePromise; expect(modelChange.data.previousModel).toBe("auto"); expect(modelChange.data.newModel).toBe("auto"); diff --git a/nodejs/test/e2e/browser_shadow_reload.e2e.test.ts b/nodejs/test/e2e/browser_shadow_reload.e2e.test.ts new file mode 100644 index 0000000000..82a005528d --- /dev/null +++ b/nodejs/test/e2e/browser_shadow_reload.e2e.test.ts @@ -0,0 +1,143 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +import { mkdirSync, readFileSync, writeFileSync } from "node:fs"; +import { dirname, join, resolve } from "node:path"; +import { fileURLToPath } from "node:url"; +import { describe, expect, it } from "vitest"; +import { approveAll, RuntimeConnection } from "../../src/index.js"; +import { createSdkTestContext } from "./harness/sdkTestContext.js"; +import { waitForCondition } from "./harness/sdkTestHelper.js"; + +const BROWSER_NAME = "github-copilot-browser"; +const SHADOW_SERVER = "installed-browser-shadow"; +const TEST_MCP_SERVER = resolve( + dirname(fileURLToPath(import.meta.url)), + "../../../test/harness/test-mcp-server.mjs" +); + +describe("Browser shadow reload", async () => { + const { createClient, workDir, env } = await createSdkTestContext({ useStdio: true }); + + it( + "observes another process's install, disable and removal on a warm session", + { timeout: 90_000 }, + async () => { + const bundle = join(workDir, "bundle"); + const shadow = join(workDir, "shadow"); + for (const [directory, server] of [ + [bundle, BROWSER_NAME], + [shadow, SHADOW_SERVER], + ]) { + mkdirSync(join(directory, ".plugin"), { recursive: true }); + writeFileSync( + join(directory, ".plugin", "plugin.json"), + JSON.stringify({ + name: BROWSER_NAME, + version: "0.0.3", + mcpServers: "./.mcp.json", + }) + ); + writeFileSync( + join(directory, ".mcp.json"), + JSON.stringify({ + mcpServers: { + [server]: { + command: process.execPath, + args: [TEST_MCP_SERVER], + tools: ["*"], + }, + }, + }) + ); + } + writeFileSync( + join(env.COPILOT_HOME, "settings.json"), + JSON.stringify({ enabledPlugins: { [BROWSER_NAME]: true }, disableAllHooks: true }) + ); + await using owner = createClient({ + connection: RuntimeConnection.forStdio(), + builtinPluginDirectories: [bundle], + }); + await using writer = createClient({ connection: RuntimeConnection.forStdio() }); + await owner.start(); + await writer.start(); + await writer.rpc.plugins.builtin.set({ paths: [] }); + // Exercise persisted MCP discovery, not a host-owned effective-plugin snapshot. + const session = await owner.createSession({ + onPermissionRequest: approveAll, + enableConfigDiscovery: false, + mcpServers: { + peer: { + type: "local", + command: process.execPath, + args: [TEST_MCP_SERVER], + tools: ["*"], + }, + }, + }); + try { + const connected = async (serverName: string) => { + await waitForCondition( + async () => + (await session.rpc.mcp.list()).servers.some( + (server) => + server.name === serverName && server.status === "connected" + ), + { timeoutMs: 30_000, timeoutMessage: `${serverName} did not connect` } + ).catch(async (error: unknown) => { + const current = (await session.rpc.mcp.list()).servers.map( + ({ name, status }) => ({ name, status }) + ); + const installed = (await writer.rpc.plugins.list()).plugins.filter( + (plugin) => plugin.name === BROWSER_NAME + ); + const discovered = ( + await owner.rpc.mcp.discover({ workingDirectory: workDir }) + ).servers.map(({ name, enabled }) => ({ name, enabled })); + throw new Error( + `${serverName} did not connect: ${JSON.stringify({ current, installed, discovered })}`, + { cause: error } + ); + }); + expect((await session.rpc.mcp.listTools({ serverName })).tools).toEqual( + expect.arrayContaining([expect.objectContaining({ name: "get_env" })]) + ); + }; + const absent = async (serverName: string) => { + expect( + (await session.rpc.mcp.list()).servers.some( + (server) => server.name === serverName + ) + ).toBe(false); + }; + await connected("peer"); + await connected(BROWSER_NAME); + + const installed = await writer.rpc.plugins.install({ source: shadow }); + expect(installed.plugin.name).toBe(BROWSER_NAME); + expect(installed.plugin.enabled).toBe(true); + await session.rpc.mcp.reload(); + await connected(SHADOW_SERVER); + await absent(BROWSER_NAME); + + await writer.rpc.plugins.disable({ names: [BROWSER_NAME] }); + await session.rpc.mcp.reload(); + await absent(SHADOW_SERVER); + await absent(BROWSER_NAME); + + await writer.rpc.plugins.uninstall({ name: BROWSER_NAME }); + expect( + JSON.parse(readFileSync(join(env.COPILOT_HOME, "settings.json"), "utf8")) + .enabledPlugins[BROWSER_NAME] + ).toBe(true); + await session.rpc.mcp.reload(); + await connected(BROWSER_NAME); + await absent(SHADOW_SERVER); + } finally { + await session.disconnect(); + } + } + ); +}); diff --git a/nodejs/test/e2e/commands.e2e.test.ts b/nodejs/test/e2e/commands.e2e.test.ts index 0a4327370e..9cd17759ff 100644 --- a/nodejs/test/e2e/commands.e2e.test.ts +++ b/nodejs/test/e2e/commands.e2e.test.ts @@ -2,7 +2,10 @@ * Copyright (c) Microsoft Corporation. All rights reserved. *--------------------------------------------------------------------------------------------*/ -import { afterAll, describe, expect, it } from "vitest"; +import { randomUUID } from "node:crypto"; +import { writeFile } from "node:fs/promises"; +import { join } from "node:path"; +import { afterAll, describe, expect, it, onTestFinished } from "vitest"; import { CopilotClient, approveAll, RuntimeConnection } from "../../src/index.js"; import type { CommandContext, SessionEvent } from "../../src/index.js"; import { createSdkTestContext } from "./harness/sdkTestContext.js"; @@ -273,6 +276,82 @@ describe("Commands", async () => { }); }); +describe("Usage command after restart", async () => { + // Custom-provider sessions bypass the server's Copilot billing account. + const { createClient, openAiEndpoint, env } = await createSdkTestContext({ + backend: "capi", + }); + + it.each([ + { + status: "partial", + totalNanoAiu: 23_275_500_000, + expected: "Requests: 23.28 AI Credits reported (partial)", + }, + { + status: "unavailable", + totalNanoAiu: 0, + expected: "Requests: unavailable AI Credits", + }, + ])("reports $status checkpoint AI credits", async ({ status, totalNanoAiu, expected }) => { + const token = `usage-command-checkpoint-token-${randomUUID()}`; + await openAiEndpoint.setCopilotUserByToken(token, { + login: "usage-command-user", + copilot_plan: "individual_pro", + token_based_billing: true, + endpoints: { api: openAiEndpoint.url }, + }); + const originalClient = createClient({ + connection: RuntimeConnection.forStdio(), + gitHubToken: token, + }); + onTestFinished(async () => { + expect(await originalClient.stop()).toHaveLength(0); + }); + const original = await originalClient.createSession({ + onPermissionRequest: approveAll, + }); + await original.rpc.name.set({ name: "Usage checkpoint fixture" }); + const events = await original.getEvents(); + const checkpoint: SessionEvent = { + type: "session.usage_checkpoint", + id: randomUUID(), + timestamp: new Date().toISOString(), + parentId: events.at(-1)?.id ?? null, + data: { totalNanoAiu, totalPremiumRequests: 1 }, + }; + await originalClient.rpc.sessions.save({ sessionId: original.sessionId }); + await originalClient.rpc.sessions.close({ sessionId: original.sessionId }); + expect(await originalClient.stop()).toHaveLength(0); + + // Seed durable accounting only after the session writer has stopped. + await writeFile( + join(env.COPILOT_HOME, "session-state", original.sessionId, "events.jsonl"), + `${[...events, checkpoint].map((event) => JSON.stringify(event)).join("\n")}\n` + ); + const resumedClient = createClient({ + connection: RuntimeConnection.forStdio(), + gitHubToken: token, + }); + onTestFinished(async () => { + expect(await resumedClient.stop()).toHaveLength(0); + }); + const resumed = await resumedClient.resumeSession(original.sessionId, { + onPermissionRequest: approveAll, + }); + expect(await resumed.rpc.usage.getMetrics()).toMatchObject({ + totalNanoAiu: checkpoint.data.totalNanoAiu, + totalPremiumRequestCost: checkpoint.data.totalPremiumRequests, + aiCreditsStatus: status, + }); + const result = await resumed.rpc.commands.invoke({ name: "usage" }); + expect(result.kind).toBe("text"); + if (result.kind !== "text") throw new Error(`Unexpected usage result: ${result.kind}`); + expect(result.text).toContain(expected); + expect(result.text).not.toContain("1 AI Units"); + }); +}); + function isCommand(command: { name: string; kind: string }, name: string, kind: string): boolean { return command.name.toLowerCase() === name.toLowerCase() && command.kind === kind; } diff --git a/nodejs/test/e2e/compaction.e2e.test.ts b/nodejs/test/e2e/compaction.e2e.test.ts index 4c8141b5e6..ad395c0bb9 100644 --- a/nodejs/test/e2e/compaction.e2e.test.ts +++ b/nodejs/test/e2e/compaction.e2e.test.ts @@ -2,7 +2,7 @@ * Copyright (c) Microsoft Corporation. All rights reserved. *--------------------------------------------------------------------------------------------*/ -import { describe, expect, it } from "vitest"; +import { describe, expect, it, onTestFailed } from "vitest"; import { existsSync } from "node:fs"; import * as fs from "node:fs/promises"; import { join } from "node:path"; @@ -15,11 +15,37 @@ import { } from "../../src/index.js"; import { createSdkTestContext, isCI } from "./harness/sdkTestContext.js"; import { testBackend } from "./harness/testBackend.js"; +import { withTestCleanup } from "../helpers/withTestCleanup.js"; const compactionTimeoutMs = 60_000; +interface CacheWireBlock { + text?: string; + cache_control?: unknown; + copilot_cache_control?: unknown; + output_config?: { effort?: string }; +} + +interface CacheWireRequest extends CacheWireBlock { + model: string; + max_tokens: number; + stream?: boolean; + system?: CacheWireBlock[]; + tools?: CacheWireBlock[]; + tool_choice?: unknown; + messages: (CacheWireBlock & { role: string; content: string | CacheWireBlock[] })[]; +} + class RefusalBudgetReplayHandler extends CopilotRequestHandler { - readonly requests: { model: string; max_tokens: number }[] = []; + readonly requests: CacheWireRequest[] = []; + readonly requestUrls: string[] = []; + + constructor( + private readonly endpoint: "/v1/messages" | "/chat/completions" = "/v1/messages", + private readonly fusionCritic = false + ) { + super(); + } protected override async sendRequest(request: Request): Promise { const path = new URL(request.url).pathname; @@ -28,6 +54,7 @@ class RefusalBudgetReplayHandler extends CopilotRequestHandler { data: [ ["claude-sonnet-5.5", 250_000, 384_000, 128_000], ["claude-sonnet-5", 112_000, 128_000, 64_000], + ...(this.fusionCritic ? [["claude-opus-5", 112_000, 128_000, 64_000]] : []), ].map(([id, prompt, context, output]) => ({ id, name: id, @@ -35,7 +62,21 @@ class RefusalBudgetReplayHandler extends CopilotRequestHandler { vendor: "Anthropic", version: "1", model_picker_enabled: true, - supported_endpoints: ["/v1/messages"], + policy: { state: "enabled" }, + supported_endpoints: [this.endpoint], + // Synthetic prices exercise TTL eligibility, not measured savings. + billing: { + token_prices: { + batch_size: 1_000_000, + default: { + input_price: 3, + output_price: 15, + cache_read_price: 0.3, + cache_write_price: 1, + cache_write_1h_price: 2, + }, + }, + }, capabilities: { type: "chat", family: id, @@ -50,17 +91,75 @@ class RefusalBudgetReplayHandler extends CopilotRequestHandler { })), }); } + if (this.fusionCritic && path === "/model/fusion") { + return Response.json({ + fusion_mode: "hydrafusion-max", + fusion_pattern: "critique", + plan_version: "1", + steps: [ + { role: "draft", model_id: "claude-opus-5" }, + { role: "critic", model_id: "claude-opus-5" }, + { role: "revision", model_id: "claude-opus-5" }, + ], + session: { token: "jwt.fake.cache-wire-fusion", expires_at: 0 }, + }); + } if (path.endsWith("/models/session")) return Response.json({}); if (path.includes("/policy")) return Response.json({ state: "enabled" }); - expect(path).toBe("/v1/messages"); - const body = (await request.json()) as { - model: string; - max_tokens: number; - stream?: boolean; - }; + expect(path).toBe(this.endpoint); + const body = (await request.json()) as CacheWireRequest; this.requests.push(body); + this.requestUrls.push(request.url); const refuses = body.model === "claude-sonnet-5.5"; - const content = refuses ? "PRIMARY_REFUSAL" : "FALLBACK_BUDGET_OK"; + const content = refuses + ? "PRIMARY_REFUSAL" + : this.fusionCritic && !body.tools?.length + ? JSON.stringify({ assessment: "approve", feedback: "" }) + : "FALLBACK_BUDGET_OK"; + const compaction = + Boolean(body.tools?.length) && + (body.tool_choice === "none" || + (typeof body.tool_choice === "object" && + body.tool_choice !== null && + "type" in body.tool_choice && + body.tool_choice.type === "none")); + // Model a warm tools/system prefix; this is accounting coverage, not a measured hit. + const cacheReadTokens = compaction ? 50 : 0; + if (this.endpoint === "/chat/completions") { + const usage = { + prompt_tokens: 100 + cacheReadTokens, + completion_tokens: 10, + total_tokens: 110 + cacheReadTokens, + prompt_tokens_details: { + cached_tokens: cacheReadTokens, + cache_creation_tokens: 0, + }, + }; + const choice = { + index: 0, + message: { role: "assistant", content }, + finish_reason: "stop", + }; + if (!body.stream) { + return Response.json({ + id: "cache-wire-replay", + object: "chat.completion", + model: body.model, + choices: [choice], + usage, + }); + } + const chunk = { + id: "cache-wire-replay", + object: "chat.completion.chunk", + model: body.model, + choices: [{ index: 0, delta: { content }, finish_reason: "stop" }], + usage, + }; + return new Response(`data: ${JSON.stringify(chunk)}\n\ndata: [DONE]\n\n`, { + headers: { "content-type": "text/event-stream" }, + }); + } const message = { id: "refusal-budget-replay", type: "message", @@ -69,7 +168,12 @@ class RefusalBudgetReplayHandler extends CopilotRequestHandler { content: [{ type: "text", text: content }], stop_reason: refuses ? "refusal" : "end_turn", stop_sequence: null, - usage: { input_tokens: 100, output_tokens: 10 }, + usage: { + input_tokens: 100, + output_tokens: 10, + cache_read_input_tokens: cacheReadTokens, + cache_creation_input_tokens: 0, + }, }; if (!body.stream) return Response.json(message); const events = [ @@ -104,6 +208,421 @@ class RefusalBudgetReplayHandler extends CopilotRequestHandler { } } +function expectNoConversationCacheMarkers(request: CacheWireRequest): void { + expect(request.cache_control).toBeUndefined(); + expect(Boolean(request.copilot_cache_control)).toBe(false); + for (const message of request.messages.filter(({ role }) => role !== "system")) { + expect(Boolean(message.copilot_cache_control)).toBe(false); + if (typeof message.content !== "string") { + for (const block of message.content) { + expect(block.cache_control).toBeUndefined(); + } + } + } +} + +function systemCacheMarkers(request: CacheWireRequest): unknown[] { + return [ + ...(request.system ?? []).map(({ cache_control }) => cache_control), + ...request.messages + .filter(({ role }) => role === "system") + .map(({ copilot_cache_control }) => copilot_cache_control), + ].filter(Boolean); +} + +for (const oneHour of [false, true]) { + describe.each(["CAPI Messages", "CAPI Chat", "BYOK Messages"] as const)( + `Disposable Anthropic prompt caching (one-hour ${oneHour}, %s)`, + async (route) => { + const { createClient } = await createSdkTestContext({ + replayOnly: true, + logLevel: "debug", + }); + + it(`keeps reusable prefixes but not disposable writes on ${route}`, async () => { + const handler = new RefusalBudgetReplayHandler( + route === "CAPI Chat" ? "/chat/completions" : "/v1/messages" + ); + const cacheClient = createClient({ requestHandler: handler }); + let phase = "create session"; + const events: string[] = []; + onTestFailed(() => { + console.error( + `[cache-wire-progress] ${JSON.stringify({ + phase, + events: events.slice(-20), + requests: handler.requests.map((request, index) => ({ + endpoint: new URL(handler.requestUrls[index]!).pathname, + model: request.model, + stream: request.stream, + messages: request.messages.length, + hasTools: Boolean(request.tools?.length), + toolChoice: request.tool_choice, + })), + })}` + ); + }); + try { + const session = await cacheClient.createSession({ + onPermissionRequest: approveAll, + model: "claude-sonnet-5", + contextTier: "default", + featureFlags: { ANTHROPIC_1_HOUR_PROMPT_CACHE: oneHour }, + expAssignments: { + Features: ["copilot_cli_anthropic_1_hour_prompt_cache"], + Flights: { + copilot_cli_anthropic_1_hour_prompt_cache: String(oneHour), + }, + Configs: [ + { + Id: "default", + Parameters: { + copilot_cli_anthropic_1_hour_prompt_cache: oneHour, + }, + }, + ], + AssignmentContext: `cache-wire-one-hour-${oneHour}`, + }, + ...(route === "BYOK Messages" + ? { + provider: { + type: "anthropic" as const, + baseUrl: "https://cache-wire-replay.invalid", + apiKey: "fake-cache-wire-key", + modelId: "claude-sonnet-5", + }, + } + : { providers: [] }), + systemMessage: { + mode: "replace", + content: "Reusable agent policy. ".repeat(4_096), + }, + streaming: false, + infiniteSessions: { enabled: false }, + }); + session.on((event) => events.push(event.type)); + phase = "initial turn"; + expect( + ( + await session.sendAndWait({ + prompt: "Initialize the reusable conversation.", + }) + )?.data.content + ).toBe("FALLBACK_BUDGET_OK"); + const initial = handler.requests.at(-1)!; + expect( + new URL(handler.requestUrls.at(-1)!).hostname === + "cache-wire-replay.invalid" + ).toBe(route === "BYOK Messages"); + const marker = { + type: "ephemeral", + ...(oneHour && route !== "BYOK Messages" ? { ttl: "1h" } : {}), + }; + expect(systemCacheMarkers(initial)).toContainEqual(marker); + expect( + initial.tools?.some((tool) => + Boolean(tool.cache_control ?? tool.copilot_cache_control) + ) + ).toBe(true); + + const beforeSampling = handler.requests.length; + phase = "sampling and concurrent turn"; + const [sampling, concurrentTurn] = await Promise.all([ + session.rpc.mcp.executeSampling({ + requestId: "cache-wire-sampling", + serverName: "cache-wire-server", + mcpRequestId: "cache-wire-mcp", + request: { + systemPrompt: "Disposable caller-owned policy. ".repeat(4_096), + messages: [ + { + role: "user", + content: { + type: "text", + text: "Compute a sampling result.", + }, + }, + ], + maxTokens: 4096, + }, + }), + session.sendAndWait({ + prompt: "Continue while sampling runs independently.", + }), + ]); + expect(concurrentTurn?.data.content).toBe("FALLBACK_BUDGET_OK"); + expect(sampling).toMatchObject({ + action: "success", + result: { content: { type: "text", text: "FALLBACK_BUDGET_OK" } }, + }); + const concurrentRequests = handler.requests.slice(beforeSampling); + expect(concurrentRequests).toHaveLength(2); + const sampled = concurrentRequests.find((request) => !request.tools?.length)!; + const continued = concurrentRequests.find((request) => + Boolean(request.tools?.length) + )!; + expect(systemCacheMarkers(sampled)).toEqual([]); + expectNoConversationCacheMarkers(sampled); + expect(systemCacheMarkers(continued)).toContainEqual(marker); + + phase = "handoff summary"; + expect(await session.rpc.history.summarizeForHandoff()).toMatchObject({ + summary: "FALLBACK_BUDGET_OK", + }); + expect(systemCacheMarkers(handler.requests.at(-1)!)).toEqual([]); + expectNoConversationCacheMarkers(handler.requests.at(-1)!); + + phase = "manual compaction"; + const completed = getNextSessionEvent( + session, + "session.compaction_complete", + "manual cache compaction" + ); + const [compacted, completionEvent] = await Promise.all([ + session.rpc.history.compact({ trigger: "manual" }), + completed, + ]); + expect(compacted).toMatchObject({ + success: true, + summaryContent: "FALLBACK_BUDGET_OK", + }); + expect(completionEvent.data.compactionTokensUsed).toMatchObject({ + inputTokens: 150, + outputTokens: 10, + cacheReadTokens: 50, + cacheWriteTokens: 0, + }); + const compacting = handler.requests.at(-1)!; + expect(systemCacheMarkers(compacting)).toEqual(systemCacheMarkers(initial)); + expect(compacting.tools).toEqual(initial.tools); + expect(compacting.tool_choice).toEqual( + route === "CAPI Chat" ? "none" : { type: "none" } + ); + expectNoConversationCacheMarkers(compacting); + + phase = "post-compaction turn"; + expect( + (await session.sendAndWait({ prompt: "Continue after compaction." }))?.data + .content + ).toBe("FALLBACK_BUDGET_OK"); + expect(systemCacheMarkers(handler.requests.at(-1)!)).toContainEqual(marker); + phase = "disconnect session"; + await session.disconnect(); + } finally { + phase = `stop client (after ${phase})`; + await cacheClient.stop(); + } + }); + } + ); +} + +describe.each(["CAPI Messages", "CAPI Chat", "BYOK Messages"] as const)( + "Disposable Anthropic prompt caching during automatic compaction (%s)", + async (route) => { + const { createClient } = await createSdkTestContext({ replayOnly: true }); + it(`avoids discarded transcript writes during automatic compaction on ${route}`, async () => { + const handler = new RefusalBudgetReplayHandler( + route === "CAPI Chat" ? "/chat/completions" : "/v1/messages" + ); + const cacheClient = createClient({ requestHandler: handler }); + try { + const session = await cacheClient.createSession({ + onPermissionRequest: approveAll, + model: "claude-sonnet-5", + ...(route === "BYOK Messages" + ? { + provider: { + type: "anthropic" as const, + baseUrl: "https://cache-wire-replay.invalid", + apiKey: "fake-cache-wire-key", + modelId: "claude-sonnet-5", + }, + } + : { providers: [] }), + systemMessage: { + mode: "replace", + content: "Reusable automatic policy. ".repeat(600), + }, + streaming: false, + infiniteSessions: { + enabled: true, + backgroundCompactionThreshold: 0.005, + bufferExhaustionThreshold: 0.01, + }, + }); + const completed = getNextSessionEvent( + session, + "session.compaction_complete", + "automatic cache compaction" + ); + const [, completionEvent] = await Promise.all([ + (async () => { + expect( + ( + await session.sendAndWait({ + prompt: "Start the automatic compaction scenario.", + }) + )?.data.content + ).toBe("FALLBACK_BUDGET_OK"); + expect( + ( + await session.sendAndWait({ + prompt: "Continue the automatic compaction scenario.", + }) + )?.data.content + ).toBe("FALLBACK_BUDGET_OK"); + })(), + completed, + ]); + expect(completionEvent.data).toMatchObject({ + success: true, + summaryContent: "FALLBACK_BUDGET_OK", + compactionTokensUsed: { + inputTokens: 150, + outputTokens: 10, + cacheReadTokens: 50, + cacheWriteTokens: 0, + }, + }); + const compactions = handler.requests.filter(({ tool_choice }) => + route === "CAPI Chat" + ? tool_choice === "none" + : typeof tool_choice === "object" && + tool_choice !== null && + "type" in tool_choice && + tool_choice.type === "none" + ); + expect(compactions.length).toBeGreaterThan(0); + for (const request of compactions) { + expect(systemCacheMarkers(request)).not.toEqual([]); + expect(request.tools).toEqual(handler.requests[0].tools); + expectNoConversationCacheMarkers(request); + } + await session.disconnect(); + } finally { + await cacheClient.stop(); + } + }); + } +); + +describe("Disposable Anthropic prompt caching during refusal fallback", async () => { + const { createClient } = await createSdkTestContext({ + replayOnly: true, + copilotClientOptions: { + env: { + ANTHROPIC_REFUSAL_FALLBACK: "true", + COPILOT_EXP_COPILOT_CLI_ANTHROPIC_REFUSAL_FALLBACK: "true", + }, + }, + }); + + it("preserves single-use cache intent across refusal fallback", async () => { + const handler = new RefusalBudgetReplayHandler(); + const cacheClient = createClient({ requestHandler: handler }); + try { + const session = await cacheClient.createSession({ + onPermissionRequest: approveAll, + model: "claude-sonnet-5.5", + providers: [], + infiniteSessions: { enabled: false }, + }); + const sampled = await session.rpc.mcp.executeSampling({ + requestId: "fallback-cache-sampling", + serverName: "cache-wire-server", + mcpRequestId: "fallback-cache-mcp", + request: { + systemPrompt: "Disposable fallback policy. ".repeat(4_096), + messages: [ + { + role: "user", + content: { type: "text", text: "Compute a sampling result." }, + }, + ], + maxTokens: 4096, + }, + }); + expect(sampled).toMatchObject({ + action: "success", + result: { content: { text: "FALLBACK_BUDGET_OK" } }, + }); + expect(handler.requests.map(({ model }) => model)).toEqual([ + "claude-sonnet-5.5", + "claude-sonnet-5", + ]); + for (const request of handler.requests) { + expect(systemCacheMarkers(request)).toEqual([]); + expectNoConversationCacheMarkers(request); + } + await session.disconnect(); + } finally { + await cacheClient.stop(); + } + }); +}); + +describe("Disposable Anthropic prompt caching during Fusion review", async () => { + const { createClient } = await createSdkTestContext({ replayOnly: true }); + it("avoids tool-less critic writes without disabling the solver cache", async () => { + const handler = new RefusalBudgetReplayHandler("/v1/messages", true); + const cacheClient = createClient({ requestHandler: handler }); + try { + const session = await cacheClient.createSession({ + onPermissionRequest: approveAll, + model: "hydrafusion-max", + providers: [], + enableExperimentalMode: true, + featureFlags: { + HYDRAFUSION: true, + HYDRAFUSION_ROLLOUT: true, + HYDRAFUSION_PLAN_V2: true, + }, + expAssignments: { + Features: [], + Flights: {}, + Configs: [], + AssignmentContext: "cache-wire-fusion-review", + }, + infiniteSessions: { enabled: false }, + }); + const events: SessionEvent[] = []; + expect((await session.rpc.model.getCurrent()).modelId).toBe("hydrafusion-max"); + const unsubscribe = session.on((event) => events.push(event)); + try { + expect( + ( + await session.sendAndWait({ + prompt: "Answer the cache policy review scenario without using tools.", + }) + )?.data.content + ).toBe("FALLBACK_BUDGET_OK"); + expect( + events.filter((event) => event.type === "session.fusion_route_failed") + ).toEqual([]); + expect( + events + .filter((event) => event.type === "assistant.fusion_phase_completed") + .map((event) => event.data.phaseKind) + ).toEqual(["draft", "critic"]); + expect(handler.requests).toHaveLength(2); + expect(systemCacheMarkers(handler.requests[0])).not.toEqual([]); + expect(handler.requests[0].tools?.length).toBeGreaterThan(0); + const critic = handler.requests[1]; + expect(critic.model).toBe("claude-opus-5"); + expect(critic.tools ?? []).toEqual([]); + expect(systemCacheMarkers(critic)).toEqual([]); + expectNoConversationCacheMarkers(critic); + } finally { + unsubscribe(); + await session.disconnect(); + } + } finally { + await cacheClient.stop(); + } + }); +}); + class BudgetReplayHandler extends CopilotRequestHandler { readonly requests: unknown[] = []; @@ -144,6 +663,300 @@ class BudgetReplayHandler extends CopilotRequestHandler { } } +interface CompactionEffortRequest { + model: string; + stream?: boolean; + reasoning?: { effort?: string }; + input: Array<{ type?: string; role?: string; reasoning?: { effort?: string } }>; +} + +class CompactionEffortReplayHandler extends CopilotRequestHandler { + readonly requests: CompactionEffortRequest[] = []; + summaryGate?: { + entered: ReturnType>; + release: ReturnType>; + }; + + protected override async sendRequest(request: Request): Promise { + expect(new URL(request.url).pathname).toBe("/responses"); + const body = (await request.json()) as CompactionEffortRequest; + this.requests.push(body); + expect(body.stream).not.toBe(true); + const incompleteSummary = this.requests.length === 2 && this.summaryGate; + if (incompleteSummary) { + incompleteSummary.entered.resolve(); + await incompleteSummary.release.promise; + } + return Response.json({ + id: "compaction-effort-response", + object: "response", + model: body.model, + status: incompleteSummary ? "incomplete" : "completed", + ...(incompleteSummary ? { incomplete_details: { reason: "max_output_tokens" } } : {}), + output: [ + { + id: "compaction-effort-message", + type: "message", + role: "assistant", + status: "completed", + content: [ + { + type: "output_text", + text: incompleteSummary ? "SUMMARY_PART_ONE" : "COMPACTION_EFFORT_OK", + }, + ], + }, + ], + usage: { input_tokens: 100, output_tokens: 10, total_tokens: 110 }, + }); + } +} + +describe("Compaction reasoning effort", async () => { + const { createClient } = await createSdkTestContext({ replayOnly: true }); + + it("reduces only the OpenAI summary request and retains the session effort", async () => { + const handler = new CompactionEffortReplayHandler(); + const effortClient = createClient({ requestHandler: handler }); + try { + await using session = await effortClient.createSession({ + onPermissionRequest: approveAll, + model: "gpt-6-sol", + reasoningEffort: "max", + provider: { + type: "openai", + wireApi: "responses", + baseUrl: "https://compaction-effort.invalid", + apiKey: "fake-byok-credential-for-e2e-tests", + modelId: "gpt-6-sol", + }, + streaming: false, + featureFlags: { COMPACTION_MEDIUM_REASONING_EFFORT: true }, + expAssignments: { + Features: [], + Flights: {}, + Configs: [], + AssignmentContext: "compaction-medium-effort", + }, + infiniteSessions: { enabled: false }, + }); + expect( + (await session.sendAndWait({ prompt: "Initialize the effort scenario." }))?.data + .content + ).toBe("COMPACTION_EFFORT_OK"); + expect(handler.requests).toHaveLength(1); + expect(handler.requests[0].reasoning?.effort).toBe("max"); + + const compacted = await session.rpc.history.compact({ trigger: "manual" }); + expect(compacted).toMatchObject({ + success: true, + summaryContent: "COMPACTION_EFFORT_OK", + }); + expect(handler.requests).toHaveLength(2); + const summaryRequest = handler.requests[1]; + expect(summaryRequest.reasoning?.effort).toBe("max"); + expect( + summaryRequest.input.filter((item) => item.type === "configuration_update") + ).toEqual([{ type: "configuration_update", reasoning: { effort: "medium" } }]); + expect(summaryRequest.input.at(-2)?.reasoning?.effort).toBe("medium"); + expect((await session.rpc.model.getCurrent()).reasoningEffort).toBe("max"); + + expect( + (await session.sendAndWait({ prompt: "Continue after the summary." }))?.data.content + ).toBe("COMPACTION_EFFORT_OK"); + expect(handler.requests).toHaveLength(3); + const nextRequest = handler.requests[2]; + expect(nextRequest.reasoning?.effort).toBe("max"); + expect( + nextRequest.input.filter((item) => item.type === "configuration_update") + ).toEqual([]); + } finally { + await effortClient.stop(); + } + }); + + it("preserves Anthropic compaction effort even when the reduction is enabled", async () => { + const handler = new RefusalBudgetReplayHandler(); + const effortClient = createClient({ requestHandler: handler }); + try { + await using session = await effortClient.createSession({ + onPermissionRequest: approveAll, + model: "claude-opus-5.5", + reasoningEffort: "max", + provider: { + type: "anthropic", + baseUrl: "https://compaction-effort.invalid", + bearerToken: "fake-byok-credential-for-e2e-tests", + modelId: "claude-opus-5.5", + }, + featureFlags: { COMPACTION_MEDIUM_REASONING_EFFORT: true }, + expAssignments: { + Features: [], + Flights: {}, + Configs: [], + AssignmentContext: "compaction-medium-effort", + }, + infiniteSessions: { enabled: false }, + }); + expect( + (await session.sendAndWait({ prompt: "Initialize the effort scenario." }))?.data + .content + ).toBe("FALLBACK_BUDGET_OK"); + expect(handler.requests).toHaveLength(1); + expect(handler.requests[0].output_config?.effort).toBe("max"); + + const compacted = await session.rpc.history.compact({ trigger: "manual" }); + expect(compacted).toMatchObject({ + success: true, + summaryContent: "FALLBACK_BUDGET_OK", + }); + expect(handler.requests).toHaveLength(2); + const summaryRequest = handler.requests[1]; + expect(summaryRequest.output_config?.effort).toBe("max"); + expect( + summaryRequest.messages.flatMap((message) => + message.output_config?.effort ? [message.output_config.effort] : [] + ) + ).toEqual([]); + expect((await session.rpc.model.getCurrent()).reasoningEffort).toBe("max"); + + expect( + (await session.sendAndWait({ prompt: "Continue after the summary." }))?.data.content + ).toBe("FALLBACK_BUDGET_OK"); + expect(handler.requests).toHaveLength(3); + const nextRequest = handler.requests[2]; + expect(nextRequest.output_config?.effort).toBe("max"); + expect(nextRequest.messages.filter((message) => message.output_config)).toEqual([]); + } finally { + await effortClient.stop(); + } + }); + + it("isolates compaction effort from concurrent sampling and selected effort changes", async ({ + expect, + onTestFinished, + }) => { + const handler = new CompactionEffortReplayHandler(); + const gate = { + entered: Promise.withResolvers(), + release: Promise.withResolvers(), + }; + handler.summaryGate = gate; + const effortClient = createClient({ requestHandler: handler }); + const stopClient = async () => { + gate.release.resolve(); + const errors = await effortClient.stop(); + if (errors.length) { + throw new AggregateError(errors, "Failed to stop compaction overlap client"); + } + }; + onTestFinished(stopClient); + await withTestCleanup(async () => { + const session = await effortClient.createSession({ + onPermissionRequest: approveAll, + model: "gpt-6-sol", + reasoningEffort: "max", + provider: { + type: "openai", + wireApi: "responses", + baseUrl: "https://compaction-effort.invalid", + apiKey: "fake-byok-credential-for-e2e-tests", + modelId: "gpt-6-sol", + }, + streaming: false, + featureFlags: { COMPACTION_MEDIUM_REASONING_EFFORT: true }, + expAssignments: { + Features: [], + Flights: {}, + Configs: [], + AssignmentContext: "compaction-overlap", + }, + infiniteSessions: { enabled: false }, + }); + expect( + (await session.sendAndWait({ prompt: "Initialize the overlap scenario." }))?.data + .content + ).toBe("COMPACTION_EFFORT_OK"); + const compaction = session.rpc.history.compact({ trigger: "manual" }); + await withTestCleanup( + async () => { + await Promise.race([ + gate.entered.promise, + compaction.then((result) => { + throw new Error( + `Compaction completed before its summary gate: ${JSON.stringify(result)}` + ); + }), + ]); + expect(handler.requests).toHaveLength(2); + expect(handler.requests[1].input.at(-2)?.reasoning?.effort).toBe("medium"); + expect((await session.rpc.model.getCurrent()).reasoningEffort).toBe("max"); + + expect( + await session.rpc.mcp.executeSampling({ + requestId: "compaction-overlap-sampling", + serverName: "compaction-overlap-server", + mcpRequestId: "compaction-overlap-mcp", + request: { + messages: [ + { + role: "user", + content: { + type: "text", + text: "Compute a sampling result.", + }, + }, + ], + maxTokens: 4096, + }, + }) + ).toMatchObject({ + action: "success", + result: { content: { type: "text", text: "COMPACTION_EFFORT_OK" } }, + }); + expect(handler.requests).toHaveLength(3); + expect(handler.requests[2].reasoning?.effort).toBe("max"); + expect( + handler.requests[2].input.filter( + (item) => item.type === "configuration_update" + ) + ).toEqual([]); + + await session.rpc.model.setReasoningEffort({ reasoningEffort: "high" }); + expect((await session.rpc.model.getCurrent()).reasoningEffort).toBe("high"); + }, + () => gate.release.resolve(), + async () => { + expect(await compaction).toMatchObject({ + success: true, + summaryContent: expect.stringContaining("COMPACTION_EFFORT_OK"), + }); + } + ); + expect(handler.requests).toHaveLength(4); + const continuation = handler.requests[3]; + expect(continuation.reasoning?.effort).toBe("max"); + expect( + continuation.input.filter((item) => item.type === "configuration_update") + ).toEqual([{ type: "configuration_update", reasoning: { effort: "medium" } }]); + expect((await session.rpc.model.getCurrent()).reasoningEffort).toBe("high"); + expect( + (await session.sendAndWait({ prompt: "Continue after the held summary." }))?.data + .content + ).toBe("COMPACTION_EFFORT_OK"); + expect(handler.requests).toHaveLength(5); + // The cache baseline stays pinned; the durable user boundary selects high. + expect(handler.requests[4].reasoning?.effort).toBe("max"); + expect( + handler.requests[4].input.filter((item) => item.type === "configuration_update") + ).toEqual([{ type: "configuration_update", reasoning: { effort: "high" } }]); + expect(handler.requests[4].input.at(-2)?.reasoning?.effort).toBe("high"); + expect((await session.rpc.model.getCurrent()).reasoningEffort).toBe("high"); + await session.disconnect(); + }, stopClient); + }); +}); + describe("Refusal fallback output budget", async () => { const { createClient } = await createSdkTestContext(); @@ -870,6 +1683,10 @@ describe("Compaction", async () => { await session.disconnect(); } }); +}); + +describe("selected-tier model catalog", async () => { + const { createClient } = await createSdkTestContext(); it("should resolve selected-tier defaults and confirm a smaller-tier switch", async () => { const modelId = "gpt-5.4-mini"; class TierBudgetReplayHandler extends BudgetReplayHandler { diff --git a/nodejs/test/e2e/computer_use_oauth.e2e.test.ts b/nodejs/test/e2e/computer_use_oauth.e2e.test.ts new file mode 100644 index 0000000000..76bdd90390 --- /dev/null +++ b/nodejs/test/e2e/computer_use_oauth.e2e.test.ts @@ -0,0 +1,347 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +import { execFileSync } from "node:child_process"; +import { mkdir, mkdtemp, readFile, realpath, rm, writeFile } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; +import { describe, test, type TestContext } from "vitest"; +import { approveAll, CopilotClient, RuntimeConnection } from "../../src/index.js"; +import { startComputerUseOAuthServer } from "../../../test/harness/test-mcp-computer-use-oauth-server.mjs"; +import { createSdkTestContext, DEFAULT_GITHUB_TOKEN } from "./harness/sdkTestContext.js"; + +const TEST_MCP_SERVER = fileURLToPath( + new URL("../../../test/harness/test-mcp-server.mjs", import.meta.url) +); +const SERVER_NAME = "unrelated-oauth"; + +describe("Computer Use OAuth isolation", async () => { + const { env } = await createSdkTestContext(); + + async function createFixture(context: TestContext, preference?: boolean, startup = false) { + // The shared harness clears workDir before this fixture's onTestFinished cleanup. + const directory = await realpath(await mkdtemp(join(tmpdir(), "computer-use-oauth-"))); + const home = join(directory, "home"); + const plugin = join(directory, "computer-use"); + let client: CopilotClient | undefined; + const startingOAuthServer = startComputerUseOAuthServer({ + deferRegistration: !startup, + deferInitialChallenge: startup, + deferAuthorizationMetadata: startup, + preconfiguredClientId: startup ? "app-client" : "", + }); + context.onTestFinished(async () => { + const errors: unknown[] = []; + const [serverStartup] = await Promise.allSettled([startingOAuthServer]); + if (serverStartup.status === "fulfilled") { + serverStartup.value.releaseRegistration(); + serverStartup.value.releaseInitialChallenge(); + serverStartup.value.releaseAuthorizationMetadata(); + } + if (client) { + try { + await readFile(join(plugin, ".plugin", "plugin.json"), "utf8"); + } catch (error) { + errors.push( + new Error( + "Computer Use OAuth fixture was removed before its client stopped", + { + cause: error, + } + ) + ); + } + try { + errors.push(...(await client.stop())); + } catch (error) { + errors.push(error); + } + } + if (serverStartup.status === "fulfilled") { + try { + await serverStartup.value.close(); + } catch (error) { + errors.push(error); + } + } + try { + await rm(directory, { + recursive: true, + force: true, + maxRetries: 10, + retryDelay: 100, + }); + } catch (error) { + errors.push(error); + } + if (errors.length) + throw new AggregateError(errors, "Computer Use OAuth cleanup failed"); + }); + const oauthServer = await startingOAuthServer; + context.signal.throwIfAborted(); + await mkdir(home); + await mkdir(join(plugin, ".plugin"), { recursive: true }); + execFileSync("git", ["init", "--quiet", directory], { windowsHide: true }); + if (preference !== undefined) { + await writeFile( + join(home, "settings.json"), + JSON.stringify({ + enabledPlugins: { "computer-use": preference }, + }) + ); + } + await writeFile( + join(plugin, ".plugin", "plugin.json"), + JSON.stringify({ + name: "computer-use", + version: "1.0.0", + mcpServers: "./.mcp.json", + }) + ); + await writeFile( + join(plugin, ".mcp.json"), + JSON.stringify({ + mcpServers: { + "computer-use": { + command: process.execPath, + args: [TEST_MCP_SERVER, "--server-name", "computer-use"], + cwd: dirname(TEST_MCP_SERVER), + tools: ["*"], + }, + }, + }) + ); + client = new CopilotClient({ + workingDirectory: directory, + env: { + ...env, + COPILOT_HOME: home, + GH_CONFIG_DIR: home, + XDG_CONFIG_HOME: home, + XDG_STATE_HOME: home, + COPILOT_DISABLE_KEYTAR: "1", + }, + gitHubToken: DEFAULT_GITHUB_TOKEN, + connection: RuntimeConnection.forStdio({ path: process.env.COPILOT_CLI_PATH }), + }); + await client.start(); + context.signal.throwIfAborted(); + await client.rpc.plugins.builtin.set({ paths: [plugin] }); + const session = await client.createSession({ + workingDirectory: directory, + onPermissionRequest: approveAll, + onMcpAuthRequest: async () => ({ kind: "cancelled" }), + mcpServers: { + [SERVER_NAME]: { + type: "http", + url: `${oauthServer.url}/mcp`, + tools: ["*"], + ...(startup ? { oauthClientId: "app-client", oauthPublicClient: true } : {}), + }, + }, + }); + return { client, session, oauthServer }; + } + + test.for([ + { preference: "unset", hosted: false }, + { preference: "disabled", hosted: false }, + { preference: "unset", hosted: true }, + { preference: "disabled", hosted: true }, + ] as const)( + "returns the initial OAuth URL across Computer Use toggles ($preference, hosted=$hosted)", + { timeout: 90_000 }, + async ({ preference, hosted }, context) => { + const { expect } = context; + const { client, session, oauthServer } = await createFixture( + context, + preference === "disabled" ? false : undefined + ); + const [initial] = await Promise.all([ + session.rpc.mcp.list(), + session.rpc.plugins.list(), + ]); + expect(initial.servers.map((server) => server.name)).not.toContain("computer-use"); + const login = session.rpc.mcp.oauth + .login({ + serverName: SERVER_NAME, + forceReauth: true, + ...(hosted ? { redirectUri: "https://agent.example.test/oauth/callback" } : {}), + }) + .then( + (result) => ({ result }), + (error: unknown) => ({ error }) + ); + // Registration is after lifetime capture but before the browser URL is returned. + await expect + .poll(() => oauthServer.requests.map((request) => request.path), { + timeout: 60_000, + }) + .toContain("/register"); + for (const enabled of [true, false, true]) { + if (enabled) await client.rpc.plugins.enable({ names: ["computer-use"] }); + else await client.rpc.plugins.disable({ names: ["computer-use"] }); + await expect + .poll( + async () => + (await session.rpc.mcp.list()).servers.find( + (candidate) => candidate.name === "computer-use" + )?.status, + { timeout: 60_000 } + ) + .toBe(enabled ? "connected" : undefined); + } + oauthServer.releaseRegistration(); + const outcome = await login; + if ("error" in outcome) throw outcome.error; + const result = outcome.result; + expect(result.authorizationUrl).toBeDefined(); + const authorization = new URL(result.authorizationUrl!); + expect(authorization.origin).toBe(oauthServer.url); + expect(authorization.searchParams.get("client_id")).toBe("registered-client"); + + await client.rpc.plugins.disable({ names: ["computer-use"] }); + await expect + .poll( + async () => (await session.rpc.mcp.list()).servers.map((server) => server.name), + { timeout: 60_000 } + ) + .not.toContain("computer-use"); + const callback = new URL(authorization.searchParams.get("redirect_uri")!); + callback.searchParams.set("code", "accepted-code"); + callback.searchParams.set("state", authorization.searchParams.get("state")!); + if (hosted) { + expect(result.authorizationId).toBe(authorization.searchParams.get("state")); + expect(callback.origin).toBe("https://agent.example.test"); + await session.rpc.mcp.oauth.complete({ + authorizationId: result.authorizationId!, + callbackUrl: callback.toString(), + }); + } else { + const response = await fetch(callback); + expect(response.ok).toBe(true); + } + await expect + .poll( + async () => + (await session.rpc.mcp.list()).servers.find( + (server) => server.name === SERVER_NAME + )?.status, + { timeout: 60_000 } + ) + .toBe("connected"); + expect( + (await session.rpc.mcp.listTools({ serverName: SERVER_NAME })).tools.map( + (tool) => tool.name + ) + ).toContain("whoami"); + const tokens = oauthServer.requests.filter((request) => request.path === "/token"); + expect(tokens).toHaveLength(1); + expect(new URLSearchParams(tokens[0].body).get("client_id")).toBe("registered-client"); + } + ); + + test.for(["unset", "disabled"] as const)( + "preserves preconfigured-client OAuth overlapping initial discovery (%s preference)", + { timeout: 90_000 }, + async (preference, context) => { + const { expect } = context; + const { client, session, oauthServer } = await createFixture( + context, + preference === "disabled" ? false : undefined, + true + ); + const discovery = session.rpc.mcp.list().then( + (result) => ({ result }), + (error: unknown) => ({ error }) + ); + const login = session.rpc.mcp.oauth.login({ serverName: SERVER_NAME }).then( + (result) => ({ result }), + (error: unknown) => ({ error }) + ); + await expect + .poll(() => oauthServer.requests.filter((request) => request.path === "/mcp"), { + timeout: 60_000, + }) + .not.toHaveLength(0); + await session.rpc.plugins.list(); + oauthServer.releaseInitialChallenge(); + await expect + .poll(() => oauthServer.requests.map((request) => request.path), { + timeout: 60_000, + }) + .toContain("/.well-known/oauth-authorization-server"); + const discovered = await discovery; + if ("error" in discovered) throw discovered.error; + expect(discovered.result.servers.map((server) => server.name)).not.toContain( + "computer-use" + ); + await client.rpc.plugins.enable({ names: ["computer-use"] }); + await expect + .poll( + async () => + (await session.rpc.mcp.list()).servers.find( + (server) => server.name === "computer-use" + )?.status, + { timeout: 60_000 } + ) + .toBe("connected"); + oauthServer.releaseAuthorizationMetadata(); + const outcome = await login; + if ("error" in outcome) throw outcome.error; + const authorization = new URL(outcome.result.authorizationUrl!); + expect(authorization.searchParams.get("client_id")).toBe("app-client"); + const callback = new URL(authorization.searchParams.get("redirect_uri")!); + callback.searchParams.set("code", "accepted-code"); + callback.searchParams.set("state", authorization.searchParams.get("state")!); + expect((await fetch(callback)).ok).toBe(true); + await expect + .poll( + async () => + (await session.rpc.mcp.list()).servers.find( + (server) => server.name === SERVER_NAME + )?.status, + { timeout: 60_000 } + ) + .toBe("connected"); + expect( + (await session.rpc.mcp.listTools({ serverName: SERVER_NAME })).tools.map( + (tool) => tool.name + ) + ).toContain("whoami"); + expect( + oauthServer.requests.filter((request) => request.path === "/register") + ).toHaveLength(0); + const tokens = oauthServer.requests.filter((request) => request.path === "/token"); + expect(tokens).toHaveLength(1); + expect(new URLSearchParams(tokens[0].body).get("client_id")).toBe("app-client"); + } + ); + + test("still cancels initial OAuth when its own server is reloaded", async (context) => { + const { expect } = context; + const { session, oauthServer } = await createFixture(context); + await session.rpc.mcp.list(); + const login = session.rpc.mcp.oauth + .login({ serverName: SERVER_NAME, forceReauth: true }) + .then( + (result) => ({ result }), + (error: unknown) => ({ error }) + ); + await expect + .poll(() => oauthServer.requests.map((request) => request.path), { timeout: 60_000 }) + .toContain("/register"); + await session.rpc.mcp.reload(); + oauthServer.releaseRegistration(); + expect(await login).toEqual({ + error: expect.objectContaining({ + message: expect.stringContaining( + "MCP OAuth was cancelled because its original requester or configuration changed." + ), + }), + }); + expect(oauthServer.requests.filter((request) => request.path === "/token")).toHaveLength(0); + }, 90_000); +}); diff --git a/nodejs/test/e2e/content_exclusion.e2e.test.ts b/nodejs/test/e2e/content_exclusion.e2e.test.ts new file mode 100644 index 0000000000..aea5fc31d4 --- /dev/null +++ b/nodejs/test/e2e/content_exclusion.e2e.test.ts @@ -0,0 +1,47 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +import { describe, expect, it } from "vitest"; +import { validateContentExclusionPolicies } from "../../examples/content-exclusion.js"; +import { createSdkTestContext } from "./harness/sdkTestContext.js"; + +describe("Content-exclusion policy validation", async () => { + const { copilotClient: client } = await createSdkTestContext(); + + it("runs the policy example without a session and preserves validation errors", async () => { + await client.start(); + expect(await client.listSessions()).toEqual([]); + expect(await validateContentExclusionPolicies(client)).toEqual({ + policies: [ + { + rules: [ + { + paths: ["secrets/**"], + source: { name: "Example", type: "Organization" }, + futureRuleField: { enabled: true }, + }, + ], + last_updated_at: "2026-10-05T12:00:00Z", + scope: "all", + futurePolicyField: { nullable: null }, + }, + ], + }); + await expect( + client.rpc.contentExclusion.validatePolicies({ policiesJson: "[{" }) + ).rejects.toMatchObject({ + code: -32602, + message: expect.stringContaining("Invalid JSON: "), + }); + await expect( + client.rpc.contentExclusion.validatePolicies({ + policiesJson: '[{"rules":[],"last_updated_at":true,"scope":"invalid"}]', + }) + ).rejects.toMatchObject({ + code: -32602, + message: expect.stringMatching(/0\.last_updated_at[\s\S]*0\.scope/), + }); + expect(await client.listSessions()).toEqual([]); + }); +}); diff --git a/nodejs/test/e2e/copilot_request_handler.e2e.test.ts b/nodejs/test/e2e/copilot_request_handler.e2e.test.ts index af79d84ae3..6faaf40a32 100644 --- a/nodejs/test/e2e/copilot_request_handler.e2e.test.ts +++ b/nodejs/test/e2e/copilot_request_handler.e2e.test.ts @@ -4,6 +4,8 @@ import { createServer, IncomingMessage, Server as HttpServer, ServerResponse } from "http"; import { AddressInfo } from "net"; +import { writeFile } from "node:fs/promises"; +import { join } from "node:path"; import { afterAll, describe, expect, it } from "vitest"; import { WebSocketServer } from "ws"; import { @@ -12,9 +14,11 @@ import { CopilotWebSocketForwarder, defineTool, type CopilotRequestContext, + type SessionConfig, } from "../../src/index.js"; import { createSdkTestContext } from "./harness/sdkTestContext.js"; import { waitForCondition } from "./harness/sdkTestHelper.js"; +import { mcpTestServerConfig } from "./harness/mcpTestServer.js"; const HTTP_TEXT = "OK from synthetic HTTP upstream."; const WS_TEXT = "OK from synthetic WS upstream."; @@ -36,11 +40,13 @@ async function startFakeUpstream(): Promise<{ wsRequestCount: () => number; wsBodies: () => Array>; httpBodies: () => Array>; + chatBodies: () => Array>; close: () => Promise; }> { let wsRequests = 0; const wsBodies: Array> = []; const httpBodies: Array> = []; + const chatBodies: Array> = []; const httpServer = createServer((req, res) => { const url = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`); @@ -72,6 +78,20 @@ async function startFakeUpstream(): Promise<{ }, }, }, + { + id: "gpt-5.5", + name: "GPT-5.5", + vendor: "OpenAI", + model_picker_enabled: true, + supported_endpoints: ["/chat/completions"], + capabilities: { + type: "chat", + family: "gpt-5.5", + tokenizer: "o200k_base", + limits: { max_context_window_tokens: 200000, max_output_tokens: 8192 }, + supports: { streaming: true, tool_calls: true }, + }, + }, { id: "gpt-6-sol", name: "GPT-6 Sol", @@ -108,12 +128,73 @@ async function startFakeUpstream(): Promise<{ sendJson(res, 200, { state: "enabled" }); return; } + if (url.pathname.endsWith("/chat/completions") && req.method === "POST") { + drainBody(req) + .then((raw) => { + const body = JSON.parse(raw.toString()) as Record; + chatBodies.push(body); + const serialized = JSON.stringify(body.messages); + const toolResult = (body.messages as Array<{ role: string }>).some( + (message) => message.role === "tool" + ); + const call = + serialized.includes("SDK_CHAT_RESTORED") && + !serialized.includes("SDK_CHAT_EXECUTED_RESULT"); + const removedCall = serialized.includes("SDK_CHAT_REMOVED") && !toolResult; + const message = + call || removedCall + ? { + role: "assistant", + content: null, + tool_calls: [ + { + index: 0, + id: "chat_call", + type: "function", + function: { name: "sdk_chat_removed", arguments: "{}" }, + }, + ], + } + : { + role: "assistant", + content: serialized.includes("SDK_CHAT_RESTORED") + ? "SDK_CHAT_RESTORED_OK" + : serialized.includes("SDK_CHAT_REMOVED") + ? "SDK_CHAT_REMOVED_OK" + : "SDK_CHAT_FIRST_OK", + }; + const finishReason = call || removedCall ? "tool_calls" : "stop"; + if (body.stream !== true) { + sendJson(res, 200, { + id: "chat_response", + choices: [{ index: 0, message, finish_reason: finishReason }], + usage: { prompt_tokens: 5, completion_tokens: 7 }, + }); + return; + } + res.writeHead(200, { "content-type": "text/event-stream" }); + res.write( + `data: ${JSON.stringify({ id: "chat_response", choices: [{ index: 0, delta: message, finish_reason: null }] })}\n\n` + ); + res.end( + `data: ${JSON.stringify({ id: "chat_response", choices: [{ index: 0, delta: {}, finish_reason: finishReason }] })}\n\ndata: [DONE]\n\n` + ); + }) + .catch((error: unknown) => { + sendJson(res, 500, { error: String(error) }); + }); + return; + } if (url.pathname.endsWith("/responses") && req.method === "POST") { drainBody(req) .then((raw) => { const body = JSON.parse(raw.toString()) as Record; httpBodies.push(body); - const events = buildResponsesEvents(HTTP_TEXT, "resp_stub_http"); + const events = toolUpdateEvents(body, "resp_stub_http", HTTP_TEXT); + if (events[0]?.type === "error") { + sendJson(res, 422, { error: events[0].error }); + return; + } if (body.stream !== true) { sendJson(res, 200, events.at(-1)!.response); return; @@ -143,69 +224,8 @@ async function startFakeUpstream(): Promise<{ wsRequests++; const body = JSON.parse(raw.toString()) as Record; wsBodies.push(body); - const input = JSON.stringify(body.input); const id = `resp_stub_ws_${wsRequests}`; - const positionedUpdate = - Array.isArray(body.input) && - body.input.some((item: { type?: string }) => item.type === "additional_tools"); - const rejection = { - type: "error", - error: { - code: "bad_request", - type: "websocket_error", - message: "additional_tools rejected", - }, - }; - const restricted = - (body.tool_choice as { type?: string } | undefined)?.type === "allowed_tools"; - let events: Array>; - if (restricted && input.includes("SDK_ALLOWLIST_REJECT_BEFORE")) { - events = [ - { - ...rejection, - error: { - ...rejection.error, - code: "invalid_request_body", - message: "allowed_tools rejected", - }, - }, - ]; - } else if (restricted && input.includes("SDK_ALLOWLIST_REJECT_AFTER")) { - events = [ - ...buildResponsesEvents("already streamed", id).slice(0, 4), - { - ...rejection, - error: { - ...rejection.error, - code: "invalid_request_body", - message: "allowed_tools rejected", - }, - }, - ]; - } else if (input.includes("SDK_REMOVAL_RESTORED")) { - events = buildResponsesEvents("SDK_REMOVAL_RESTORED_OK", id); - } else if (input.includes("SDK_ALLOWLIST_REJECT_BEFORE")) { - events = buildResponsesEvents("SDK_ALLOWLIST_RECOVERED", id); - } else if (positionedUpdate && input.includes("SDK_RESPONSES_REJECT_BEFORE")) { - events = [rejection]; - } else if (positionedUpdate && input.includes("SDK_RESPONSES_REJECT_AFTER")) { - events = [...buildResponsesEvents("already streamed", id).slice(0, 4), rejection]; - } else if (input.includes("SDK_RESPONSES_LEGACY_FOLLOW_UP")) { - events = buildResponsesEvents("SDK_RESPONSES_LEGACY_OK", id); - } else if (input.includes("SDK_RESPONSES_REJECT_BEFORE")) { - events = buildResponsesEvents("SDK_RESPONSES_RECOVERED", id); - } else if ( - input.includes("SDK_RESPONSES_AFTER") && - !input.includes("SDK_ADDED_TOOL_RESULT") - ) { - events = buildFunctionCallEvents(id); - } else { - events = buildResponsesEvents( - input.includes("SDK_ADDED_TOOL_RESULT") ? "SDK_RESPONSES_TOOL_OK" : WS_TEXT, - id - ); - } - for (const event of events) { + for (const event of toolUpdateEvents(body, id, WS_TEXT)) { socket.send(JSON.stringify(event)); } }); @@ -221,6 +241,7 @@ async function startFakeUpstream(): Promise<{ wsRequestCount: () => wsRequests, wsBodies: () => [...wsBodies], httpBodies: () => [...httpBodies], + chatBodies: () => [...chatBodies], async close() { wss.clients.forEach((c) => c.terminate()); await new Promise((resolve) => wss.close(() => resolve())); @@ -229,13 +250,107 @@ async function startFakeUpstream(): Promise<{ }; } -function buildFunctionCallEvents(id: string): Array> { +function toolUpdateEvents( + body: Record, + id: string, + defaultText: string +): Array> { + const input = JSON.stringify(body.input); + const positionedUpdate = + Array.isArray(body.input) && + body.input.some((item: { type?: string }) => item.type === "additional_tools"); + const rejection = { + type: "error", + error: { + code: "bad_request", + type: "websocket_error", + message: "additional_tools rejected", + }, + }; + const restricted = + (body.tool_choice as { type?: string } | undefined)?.type === "allowed_tools"; + if (positionedUpdate && input.includes("SDK_RESPONSES_REJECT_GENERIC")) { + return [{ type: "error", error: { message: "invalid request" } }]; + } + if (restricted && input.includes("SDK_ALLOWLIST_REJECT_BEFORE")) { + return [ + { + ...rejection, + error: { + ...rejection.error, + code: "invalid_request_body", + message: "allowed_tools rejected", + }, + }, + ]; + } + if (restricted && input.includes("SDK_ALLOWLIST_REJECT_AFTER")) { + return [ + ...buildResponsesEvents("already streamed", id).slice(0, 4), + { + ...rejection, + error: { + ...rejection.error, + code: "invalid_request_body", + message: "allowed_tools rejected", + }, + }, + ]; + } + if (input.includes("SDK_REMOVAL_RESTORED")) { + return buildResponsesEvents("SDK_REMOVAL_RESTORED_OK", id); + } + if (input.includes("SDK_REMOVAL_ALL")) { + return buildResponsesEvents(defaultText, id); + } + if (input.includes("SDK_ALLOWLIST_REJECT_BEFORE")) { + return buildResponsesEvents("SDK_ALLOWLIST_RECOVERED", id); + } + if (positionedUpdate && input.includes("SDK_RESPONSES_REJECT_BEFORE")) { + return [rejection]; + } + if (positionedUpdate && input.includes("SDK_RESPONSES_REJECT_AFTER")) { + return [...buildResponsesEvents("already streamed", id).slice(0, 4), rejection]; + } + if (input.includes("SDK_RESPONSES_LEGACY_FOLLOW_UP")) { + return buildResponsesEvents("SDK_RESPONSES_LEGACY_OK", id); + } + if (input.includes("SDK_RESPONSES_REJECT_BEFORE")) { + return buildResponsesEvents("SDK_RESPONSES_RECOVERED", id); + } + if (input.includes("MCP_RESPONSES_REMOVED_AGAIN")) { + return buildResponsesEvents("MCP_RESPONSES_REMOVED_OK", id); + } + if (input.includes("MCP_RESPONSES_AFTER") && !input.includes("MCP_ADDED_TOOL_RESULT")) { + return buildFunctionCallEvents( + id, + "a_server-get_env", + JSON.stringify({ name: "CACHE_TEST_RESULT" }) + ); + } + if (input.includes("MCP_ADDED_TOOL_RESULT")) { + return buildResponsesEvents("MCP_RESPONSES_TOOL_OK", id); + } + if (input.includes("SDK_RESPONSES_AFTER") && !input.includes("SDK_ADDED_TOOL_RESULT")) { + return buildFunctionCallEvents(id); + } + return buildResponsesEvents( + input.includes("SDK_ADDED_TOOL_RESULT") ? "SDK_RESPONSES_TOOL_OK" : defaultText, + id + ); +} + +function buildFunctionCallEvents( + id: string, + name = "sdk_epoch_added", + args = "{}" +): Array> { const call = { type: "function_call", id: "fc_sdk_added", call_id: "call_sdk_added", - name: "sdk_epoch_added", - arguments: "{}", + name, + arguments: args, status: "completed", }; return [ @@ -333,7 +448,8 @@ interface Counters { class TestHandler extends CopilotRequestHandler { constructor( private readonly upstreamUrl: string, - private readonly counters: Counters + private readonly counters: Counters, + private readonly failWebSocket = false ) { super(); } @@ -384,6 +500,9 @@ class TestHandler extends CopilotRequestHandler { protected override async openWebSocket( ctx: CopilotRequestContext ): Promise { + if (this.failWebSocket) { + throw new Error("Synthetic WebSocket handshake refused"); + } ctx.url = this.rewriteWsUrl(ctx.url); return new CountingSocketForwarder(ctx, this.counters); } @@ -417,7 +536,11 @@ describe("CopilotRequestHandler — single subclass handles HTTP + WebSocket", a wsResponseMessages: 0, }; - const { copilotClient: client, env } = await createSdkTestContext({ + const { + copilotClient: client, + env, + workDir, + } = await createSdkTestContext({ copilotClientOptions: { requestHandler: new TestHandler(upstream.url, counters), }, @@ -551,6 +674,470 @@ describe("CopilotRequestHandler — single subclass handles HTTP + WebSocket", a } }, 90_000); + const routes: Array<{ + name: string; + config: Partial; + bodies: () => Array>; + firstText: string; + }> = [ + { + name: "CAPI WebSocket", + config: { capi: { enableWebSocketResponses: true } }, + bodies: upstream.wsBodies, + firstText: WS_TEXT, + }, + { + name: "CAPI HTTP", + config: { capi: { enableWebSocketResponses: false } }, + bodies: upstream.httpBodies, + firstText: HTTP_TEXT, + }, + ...(["http", "websockets"] as const).map((transport) => ({ + name: `BYOK ${transport}`, + config: { + provider: { + type: "openai" as const, + wireApi: "responses" as const, + transport, + baseUrl: upstream.url, + apiKey: "synthetic-provider-key", + modelId: "gpt-6-sol", + wireModel: "gpt-6-sol", + }, + }, + bodies: transport === "http" ? upstream.httpBodies : upstream.wsBodies, + firstText: transport === "http" ? HTTP_TEXT : WS_TEXT, + })), + ]; + for (const route of routes) { + describe(route.name, async () => { + const routeClient = + route.config.capi?.enableWebSocketResponses === false + ? ( + await createSdkTestContext({ + copilotClientOptions: { + requestHandler: new TestHandler(upstream.url, counters), + // This runtime does not yet wire the session-level CAPI transport opt-out. + env: { COPILOT_CLI_DISABLE_WEBSOCKET_RESPONSES: "true" }, + }, + }) + ).copilotClient + : client; + it(`${route.config.provider ? "sends current tools" : "preserves the prefix"} on ${route.name} and executes a tool from a newly enabled MCP server`, async () => { + const config = mcpTestServerConfig("MCP_ADDED_TOOL_RESULT"); + await routeClient.start(); + const beforeRequests = route.bodies().length; + const session = await routeClient.createSession({ + model: "gpt-6-sol", + onPermissionRequest: approveAll, + ...route.config, + toolSearch: { enabled: false }, + mcpServers: { z_server: config, a_server: config }, + disabledMcpServers: ["a_server"], + }); + try { + await session.rpc.tools.initializeAndValidate(); + expect( + (await session.rpc.tools.getCurrentMetadata()).tools.map( + (tool) => tool.name + ) + ).toContain("z_server-get_env"); + expect( + (await session.rpc.tools.getCurrentMetadata()).tools.map( + (tool) => tool.name + ) + ).not.toContain("a_server-get_env"); + const first = await session.sendAndWait({ prompt: "MCP_RESPONSES_FIRST" }); + expect(first?.data.content).toContain(route.firstText); + + await session.rpc.mcp.setEnvValueMode({ mode: "direct" }); + await session.rpc.mcp.enable({ serverName: "a_server" }); + await session.rpc.tools.initializeAndValidate(); + expect( + (await session.rpc.tools.getCurrentMetadata()).tools.map( + (tool) => tool.name + ) + ).toContain("a_server-get_env"); + const second = await session.sendAndWait({ + prompt: "MCP_RESPONSES_AFTER: call a_server-get_env.", + }); + expect(second?.data.content).toContain("MCP_RESPONSES_TOOL_OK"); + + const requests = route.bodies().slice(beforeRequests); + expect(requests).toHaveLength(3); + const [before, update, afterTool] = requests; + expect(before.tools).toEqual( + expect.arrayContaining([ + expect.objectContaining({ name: "z_server-get_env" }), + ]) + ); + expect(JSON.stringify(before.tools)).not.toContain('"name":"a_server-get_env"'); + for (const request of [update, afterTool]) { + if (route.config.provider) { + expect(request.tools).toEqual( + expect.arrayContaining([ + expect.objectContaining({ name: "a_server-get_env" }), + expect.objectContaining({ name: "z_server-get_env" }), + ]) + ); + expect(request.tools).toEqual(update.tools); + } else { + expect(JSON.stringify(request.tools)).toBe( + JSON.stringify(before.tools) + ); + } + expect(request.instructions).toEqual(before.instructions); + } + expect(update.previous_response_id ?? null).toBeNull(); + const input = update.input as Array>; + expect(JSON.stringify(input[0])).toBe( + JSON.stringify((before.input as Array>)[0]) + ); + if (route.config.provider) { + expect(JSON.stringify(input)).not.toContain('"type":"additional_tools"'); + } else { + expect(input).toContainEqual( + expect.objectContaining({ + type: "additional_tools", + tools: expect.arrayContaining([ + expect.objectContaining({ name: "a_server-get_env" }), + ]), + }) + ); + const additionIndex = input.findIndex( + (item) => item.type === "additional_tools" + ); + expect(additionIndex).toBeGreaterThan(0); + expect(JSON.stringify(input.slice(0, additionIndex))).toContain( + "MCP_RESPONSES_AFTER" + ); + } + expect(JSON.stringify(afterTool.input)).toContain("MCP_ADDED_TOOL_RESULT"); + } finally { + await session.disconnect(); + } + }, 90_000); + + it(`removes MCP tools on ${route.name} and executes a restored tool`, async () => { + const toolName = "a_server-get_env"; + const survivor = "z_server-get_env"; + const config = mcpTestServerConfig("MCP_ADDED_TOOL_RESULT"); + await client.start(); + const beforeRequests = route.bodies().length; + const session = await routeClient.createSession({ + model: "gpt-6-sol", + onPermissionRequest: approveAll, + ...route.config, + toolSearch: { enabled: false }, + availableTools: [toolName, survivor], + mcpServers: { a_server: config, z_server: config }, + }); + try { + await session.rpc.mcp.setEnvValueMode({ mode: "direct" }); + await session.rpc.tools.initializeAndValidate(); + expect( + (await session.rpc.tools.getCurrentMetadata()).tools.map( + (tool) => tool.name + ) + ).toEqual(expect.arrayContaining([toolName, survivor])); + expect( + (await session.sendAndWait({ prompt: "MCP_RESPONSES_FIRST" }))?.data.content + ).toContain(route.firstText); + + await session.rpc.mcp.disable({ serverName: "a_server" }); + await session.rpc.tools.initializeAndValidate(); + // Tool metadata includes the previous request; the host reports live availability. + const disabled = await session.rpc.mcp.list(); + expect(disabled.host?.disabledServers).toContain("a_server"); + expect(disabled.host?.disabledServers).not.toContain("z_server"); + expect(disabled.servers).toContainEqual( + expect.objectContaining({ name: "a_server", status: "disabled" }) + ); + expect( + (await session.sendAndWait({ prompt: "MCP_RESPONSES_REMOVED" }))?.data + .content + ).toContain(route.firstText); + + await session.rpc.mcp.enable({ serverName: "a_server" }); + await session.rpc.tools.initializeAndValidate(); + expect( + (await session.rpc.tools.getCurrentMetadata()).tools.map( + (tool) => tool.name + ) + ).toContain(toolName); + expect( + ( + await session.sendAndWait({ + prompt: `MCP_RESPONSES_AFTER: call ${toolName}.`, + }) + )?.data.content + ).toContain("MCP_RESPONSES_TOOL_OK"); + + await session.rpc.mcp.disable({ serverName: "a_server" }); + await session.rpc.tools.initializeAndValidate(); + const disabledAgain = await session.rpc.mcp.list(); + expect(disabledAgain.host?.disabledServers).toContain("a_server"); + expect(disabledAgain.host?.disabledServers).not.toContain("z_server"); + expect(disabledAgain.servers).toContainEqual( + expect.objectContaining({ name: "a_server", status: "disabled" }) + ); + expect( + (await session.sendAndWait({ prompt: "MCP_RESPONSES_REMOVED_AGAIN" }))?.data + .content + ).toContain("MCP_RESPONSES_REMOVED_OK"); + + const requests = route.bodies().slice(beforeRequests); + expect(requests).toHaveLength(5); + const [before, removed, restored, afterTool, removedAgain] = requests; + expect(before.tools).toEqual( + expect.arrayContaining([ + expect.objectContaining({ name: toolName }), + expect.objectContaining({ name: survivor }), + ]) + ); + for (const request of [removed, restored, afterTool, removedAgain]) { + expect(request.instructions).toEqual(before.instructions); + expect(JSON.stringify(request.input)).not.toContain( + '"type":"additional_tools"' + ); + } + const choice = { + type: "allowed_tools", + mode: "auto", + tools: [{ type: "function", name: survivor }], + }; + for (const request of [removed, removedAgain]) { + if (route.config.provider) { + expect(request.tools).toEqual( + (before.tools as Array>).filter( + (tool) => tool.name !== toolName + ) + ); + expect(request.tool_choice).not.toEqual( + expect.objectContaining({ type: "allowed_tools" }) + ); + } else { + expect(JSON.stringify(request.tools)).toBe( + JSON.stringify(before.tools) + ); + expect(request.tool_choice).toEqual(choice); + } + expect(request.previous_response_id ?? null).toBeNull(); + } + for (const request of [restored, afterTool]) { + expect(JSON.stringify(request.tools)).toBe(JSON.stringify(before.tools)); + expect(request.tool_choice).not.toEqual( + expect.objectContaining({ type: "allowed_tools" }) + ); + } + expect(restored.previous_response_id ?? null).toBeNull(); + expect(JSON.stringify(afterTool.input)).toContain("MCP_ADDED_TOOL_RESULT"); + } finally { + await session.disconnect(); + } + }, 90_000); + }); + } + + for (const byok of [false, true]) { + it(`preserves ${byok ? "BYOK" : "CAPI"} Chat Completions tools through removal and restores real execution`, async () => { + const original = { + name: "sdk_chat_original", + description: "Original", + parameters: { type: "object", properties: {} }, + defer: "never", + } as const; + const removable = { ...original, name: "sdk_chat_removed", description: "Removable" }; + let calls = 0; + const start = upstream.chatBodies().length; + const session = await client.createSession({ + model: "gpt-5.5", + ...(byok + ? { + provider: { + type: "openai" as const, + wireApi: "completions" as const, + baseUrl: upstream.url, + apiKey: "synthetic-provider-key", + modelId: "gpt-5.5", + wireModel: "gpt-5.5", + }, + } + : {}), + toolSearch: { enabled: false }, + onPermissionRequest: approveAll, + availableTools: [original.name, removable.name], + tools: [ + defineTool(original.name, { + description: original.description, + handler: () => "SDK_CHAT_ORIGINAL_RESULT", + }), + defineTool(removable.name, { + description: removable.description, + handler: () => { + calls++; + return "SDK_CHAT_EXECUTED_RESULT"; + }, + }), + ], + }); + try { + await session.rpc.tools.initializeAndValidate(); + expect( + (await session.sendAndWait({ prompt: "SDK_CHAT_FIRST" }))?.data.content + ).toContain("SDK_CHAT_FIRST_OK"); + await session.rpc.tools.set({ tools: [original] }); + expect( + (await session.sendAndWait({ prompt: "SDK_CHAT_REMOVED" }))?.data.content + ).toContain("SDK_CHAT_REMOVED_OK"); + expect(calls).toBe(0); + const removedRequests = upstream.chatBodies().slice(start); + expect(removedRequests).toHaveLength(3); + const [before, removed, afterRejectedCall] = removedRequests; + expect(JSON.stringify(removed.tools)).toBe(JSON.stringify(before.tools)); + expect(removed.tool_choice).toEqual({ + type: "allowed_tools", + allowed_tools: { + mode: "auto", + tools: [{ type: "function", function: { name: original.name } }], + }, + }); + expect(JSON.stringify(afterRejectedCall.messages)).toContain("does not exist"); + await session.rpc.tools.set({ tools: [original, removable] }); + expect( + (await session.sendAndWait({ prompt: "SDK_CHAT_RESTORED" }))?.data.content + ).toContain("SDK_CHAT_RESTORED_OK"); + expect(calls).toBe(1); + const requests = upstream.chatBodies().slice(start); + expect(requests).toHaveLength(5); + for (const request of requests.slice(3)) { + expect(JSON.stringify(request.tools)).toBe(JSON.stringify(before.tools)); + expect(request.tool_choice).not.toEqual( + expect.objectContaining({ type: "allowed_tools" }) + ); + } + expect(JSON.stringify(requests[4].messages)).toContain("SDK_CHAT_EXECUTED_RESULT"); + } finally { + await session.disconnect(); + } + }); + } + + describe("WebSocket to HTTP tool-update fallback", async () => { + const { copilotClient: fallbackClient } = await createSdkTestContext({ + copilotClientOptions: { requestHandler: new TestHandler(upstream.url, counters, true) }, + }); + it("recovers an unrecognized HTTP 422 once and keeps later turns on current tools", async () => { + const original = { + name: "sdk_epoch_original", + description: "Original", + parameters: { type: "object", properties: {} }, + defer: "never", + } as const; + const added = { ...original, name: "sdk_epoch_added", description: "Added" }; + const start = upstream.httpBodies().length; + const session = await fallbackClient.createSession({ + model: "gpt-6-sol", + capi: { enableWebSocketResponses: true }, + toolSearch: { enabled: false }, + onPermissionRequest: approveAll, + tools: [ + defineTool(original.name, { handler: () => "original" }), + defineTool(added.name, { handler: () => "added" }), + ], + }); + try { + await session.rpc.tools.initializeAndValidate(); + await session.rpc.tools.set({ tools: [original] }); + expect( + (await session.sendAndWait({ prompt: "SDK_RESPONSES_FIRST" }))?.data.content + ).toContain(HTTP_TEXT); + await session.rpc.tools.set({ tools: [original, added] }); + expect( + (await session.sendAndWait({ prompt: "SDK_RESPONSES_REJECT_GENERIC" }))?.data + .content + ).toContain(HTTP_TEXT); + expect( + (await session.sendAndWait({ prompt: "SDK_RESPONSES_LEGACY_FOLLOW_UP" }))?.data + .content + ).toContain("SDK_RESPONSES_LEGACY_OK"); + const requests = upstream.httpBodies().slice(start); + expect(requests).toHaveLength(4); + const [before, rejected, recovered, later] = requests; + expect(JSON.stringify(rejected.tools)).toBe(JSON.stringify(before.tools)); + expect(JSON.stringify(rejected.input)).toContain('"type":"additional_tools"'); + for (const request of [recovered, later]) { + expect(JSON.stringify(request.input)).not.toContain( + '"type":"additional_tools"' + ); + expect(request.tools).toEqual( + expect.arrayContaining([expect.objectContaining({ name: added.name })]) + ); + } + } finally { + await session.disconnect(); + } + }); + }); + + it("rebases a refreshed MCP definition before executing the restarted server", async () => { + const toolName = "a_server-get_env"; + const descriptionFile = join(workDir, "tool-description.txt"); + await writeFile(descriptionFile, "MCP_ORIGINAL_DESCRIPTION"); + await client.start(); + const beforeRequests = upstream.wsBodies().length; + const session = await client.createSession({ + model: "gpt-6-sol", + onPermissionRequest: approveAll, + capi: { enableWebSocketResponses: true }, + toolSearch: { enabled: false }, + mcpServers: { + a_server: mcpTestServerConfig("MCP_ADDED_TOOL_RESULT", descriptionFile), + }, + }); + try { + await session.rpc.mcp.setEnvValueMode({ mode: "direct" }); + await session.rpc.tools.initializeAndValidate(); + expect( + (await session.rpc.tools.getCurrentMetadata()).tools.find( + (tool) => tool.name === toolName + )?.description + ).toContain("MCP_ORIGINAL_DESCRIPTION"); + expect( + (await session.sendAndWait({ prompt: "MCP_RESPONSES_FIRST" }))?.data.content + ).toContain(WS_TEXT); + + await writeFile(descriptionFile, "MCP_REPLACEMENT_DESCRIPTION"); + await session.rpc.mcp.restartServer({ serverName: "a_server" }); + await session.rpc.tools.initializeAndValidate(); + expect( + (await session.rpc.tools.getCurrentMetadata()).tools.find( + (tool) => tool.name === toolName + )?.description + ).toContain("MCP_REPLACEMENT_DESCRIPTION"); + expect( + (await session.sendAndWait({ prompt: `MCP_RESPONSES_AFTER: call ${toolName}.` })) + ?.data.content + ).toContain("MCP_RESPONSES_TOOL_OK"); + + const requests = upstream.wsBodies().slice(beforeRequests); + expect(requests).toHaveLength(3); + const [before, update, afterTool] = requests; + expect(JSON.stringify(before.tools)).toContain("MCP_ORIGINAL_DESCRIPTION"); + expect(JSON.stringify(update.tools)).toContain("MCP_REPLACEMENT_DESCRIPTION"); + expect(JSON.stringify(update.tools)).not.toContain("MCP_ORIGINAL_DESCRIPTION"); + expect(update.instructions).toEqual(before.instructions); + expect(update.previous_response_id ?? null).toBeNull(); + for (const request of [update, afterTool]) { + expect(JSON.stringify(request.input)).not.toContain('"type":"additional_tools"'); + } + expect(JSON.stringify(afterTool.tools)).toBe(JSON.stringify(update.tools)); + expect(JSON.stringify(afterTool.input)).toContain("MCP_ADDED_TOOL_RESULT"); + } finally { + await session.disconnect(); + } + }, 90_000); + it("keeps the SDK removal prefix while restricting tools across continuations and restoration", async () => { const original = { name: "sdk_epoch_original", @@ -619,12 +1206,12 @@ describe("CopilotRequestHandler — single subclass handles HTTP + WebSocket", a expect( (await session.sendAndWait({ prompt: "SDK_REMOVAL_ALL" }))?.data.content ).toContain(HTTP_TEXT); - // No remaining tools selects the runtime's single-shot HTTP route. + // No remaining tools selects HTTP, which keeps the prefix and forbids all calls. const allRemoved = upstream .httpBodies() .findLast((body) => JSON.stringify(body.input).includes("SDK_REMOVAL_ALL"))!; - expect(allRemoved.tools).toEqual([]); - expect(allRemoved.tool_choice).toBeUndefined(); + expect(JSON.stringify(allRemoved.tools)).toBe(JSON.stringify(before.tools)); + expect(allRemoved.tool_choice).toBe("none"); expect(allRemoved.previous_response_id ?? null).toBeNull(); await session.rpc.tools.set({ tools: [original, added] }); diff --git a/nodejs/test/e2e/customization_directories.e2e.test.ts b/nodejs/test/e2e/customization_directories.e2e.test.ts new file mode 100644 index 0000000000..2a3adefa51 --- /dev/null +++ b/nodejs/test/e2e/customization_directories.e2e.test.ts @@ -0,0 +1,387 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +import { execFileSync } from "node:child_process"; +import fs from "node:fs"; +import path from "node:path"; +import { beforeEach, describe, expect, it } from "vitest"; +import { approveAll, RuntimeConnection } from "../../src/index.js"; +import { createSdkTestContext } from "./harness/sdkTestContext.js"; + +function writeFile(root: string, relative: string[], content: string): string { + const file = path.join(root, ...relative); + fs.mkdirSync(path.dirname(file), { recursive: true }); + fs.writeFileSync(file, content); + return file; +} + +function writeSkill(root: string, directory: string, name: string): void { + writeFile( + root, + [directory, name, "SKILL.md"], + `---\nname: ${name}\ndescription: Discovery directory regression fixture.\n---\n\nUse this skill.\n` + ); +} + +describe("Customization directory discovery", async () => { + const { + copilotClient: client, + env, + workDir: testRoot, + createClient, + } = await createSdkTestContext({ + copilotClientOptions: { env: { COPILOT_ALLOW_ALL: "1" } }, + }); + let workDir: string; + + beforeEach(async () => { + workDir = fs.realpathSync.native(fs.mkdtempSync(path.join(testRoot, "customizations-"))); + execFileSync("git", ["init", "--quiet"], { cwd: workDir, windowsHide: true }); + writeFile( + env.COPILOT_HOME, + ["config.json"], + JSON.stringify({ trustedFolders: [testRoot] }) + ); + await client.start(); + }); + + it("discovers all four repository lists and applies independent local replacements", async () => { + writeSkill(workDir, "vendor-skills", "repository-directory-skill"); + writeFile( + workDir, + ["vendor-agents", "directory.agent.md"], + "---\nname: repository-directory-agent\ndescription: Directory fixture.\n---\n\nRead the repository.\n" + ); + const instruction = writeFile( + workDir, + ["vendor-instructions", "guard.instructions.md"], + "---\napplyTo: '**/*.ts'\n---\n\nRepository directory instruction sentinel.\n" + ); + writeFile( + workDir, + ["vendor-instructions", "AGENTS.md"], + "Do not inject this package hierarchy." + ); + writeFile( + workDir, + ["vendor-hooks", "hooks.json"], + JSON.stringify({ hooks: { sessionStart: [{ command: "echo directory-hook" }] } }) + ); + writeFile( + workDir, + [".github", "copilot", "settings.json"], + JSON.stringify({ + skillDirectories: ["vendor-skills"], + agentDirectories: ["vendor-agents"], + instructionDirectories: ["vendor-instructions"], + hookDirectories: ["vendor-hooks"], + }) + ); + const projectPaths = [workDir]; + const skills = await client.rpc.skills.discover({ projectPaths, excludeHostSkills: true }); + expect( + skills.skills.filter((skill) => skill.name === "repository-directory-skill") + ).toHaveLength(1); + const agents = await client.rpc.agents.discover({ projectPaths, excludeHostAgents: true }); + expect( + agents.agents.filter((agent) => agent.name === "repository-directory-agent") + ).toHaveLength(1); + const instructions = await client.rpc.instructions.discover({ + projectPaths, + excludeHostInstructions: true, + }); + expect( + instructions.sources.filter((source) => source.sourcePath === instruction) + ).toHaveLength(1); + expect( + instructions.sources.some((source) => + source.content.includes("Do not inject this package hierarchy.") + ) + ).toBe(false); + const hooks = await client.rpc.hooks.discover({ projectPaths, excludeHostHooks: true }); + expect(hooks.hooks.some((hook) => hook.source.includes("vendor-hooks"))).toBe(true); + + writeFile( + workDir, + [".github", "copilot", "settings.local.json"], + JSON.stringify({ skillDirectories: [], hookDirectories: [] }) + ); + const clearedSkills = await client.rpc.skills.discover({ + projectPaths, + excludeHostSkills: true, + }); + expect( + clearedSkills.skills.some((skill) => skill.name === "repository-directory-skill") + ).toBe(false); + const retainedAgents = await client.rpc.agents.discover({ + projectPaths, + excludeHostAgents: true, + }); + expect( + retainedAgents.agents.some((agent) => agent.name === "repository-directory-agent") + ).toBe(true); + const clearedHooks = await client.rpc.hooks.discover({ + projectPaths, + excludeHostHooks: true, + }); + expect(clearedHooks.hooks.some((hook) => hook.source.includes("vendor-hooks"))).toBe(false); + }); + + it("anchors SDK relative roots once and reanchors only newly supplied lists", async () => { + const original = path.join(workDir, "original"); + const next = path.join(workDir, "next"); + writeSkill(original, "skills", "original-directory-skill"); + writeSkill(next, "skills", "next-directory-skill"); + const session = await client.createSession({ + onPermissionRequest: approveAll, + workingDirectory: original, + enableConfigDiscovery: false, + skillDirectories: ["skills"], + }); + try { + await session.rpc.skills.ensureLoaded(); + expect( + (await session.rpc.skills.list()).skills.some( + (skill) => skill.name === "original-directory-skill" + ) + ).toBe(true); + expect((await session.rpc.options.update({ workingDirectory: next })).success).toBe( + true + ); + await session.rpc.skills.ensureLoaded(); + const retained = await session.rpc.skills.list(); + expect(retained.skills.some((skill) => skill.name === "original-directory-skill")).toBe( + true + ); + expect(retained.skills.some((skill) => skill.name === "next-directory-skill")).toBe( + false + ); + expect( + (await session.rpc.options.update({ skillDirectories: ["skills"] })).success + ).toBe(true); + await session.rpc.skills.ensureLoaded(); + const changed = await session.rpc.skills.list(); + expect(changed.skills.some((skill) => skill.name === "original-directory-skill")).toBe( + false + ); + expect(changed.skills.some((skill) => skill.name === "next-directory-skill")).toBe( + true + ); + expect((await session.rpc.options.update({ skillDirectories: [] })).success).toBe(true); + await session.rpc.skills.ensureLoaded(); + expect( + (await session.rpc.skills.list()).skills.some( + (skill) => skill.name === "next-directory-skill" + ) + ).toBe(false); + } finally { + await session.disconnect(); + } + }); + + it("anchors newly supplied roots to the persisted workspace on cold resume", async () => { + const original = path.join(workDir, "nested"); + fs.mkdirSync(original); + writeSkill(workDir, path.join("vendor", "alternate-skills"), "cold-resume-directory-skill"); + const first = createClient({ connection: RuntimeConnection.forStdio() }); + const next = createClient({ connection: RuntimeConnection.forStdio() }); + try { + await first.start(); + const session = await first.createSession({ + onPermissionRequest: approveAll, + workingDirectory: original, + enableConfigDiscovery: false, + requestExtensions: false, + }); + await session.rpc.workspaces.ensure({}); + const { sessionId } = await first.rpc.sessions.fork({ sessionId: session.sessionId }); + await session.disconnect(); + await first.stop(); + await next.start(); + const resumed = await next.resumeSession(sessionId, { + onPermissionRequest: approveAll, + enableConfigDiscovery: false, + requestExtensions: false, + skillDirectories: [path.join("..", "vendor", "alternate-skills")], + }); + try { + expect((await resumed.rpc.metadata.snapshot()).workingDirectory).toBe(original); + await resumed.rpc.skills.ensureLoaded(); + const skills = await resumed.rpc.skills.list(); + expect( + skills.skills.some((skill) => skill.name === "cold-resume-directory-skill") + ).toBe(true); + } finally { + await resumed.disconnect(); + } + } finally { + await next.stop(); + await first.stop(); + } + }); + + it("retains explicit file hooks when repository hooks and ambient discovery are disabled", async () => { + const hooks = path.join(workDir, "explicit-hooks"); + writeFile(hooks, ["resource.txt"], "resource-sentinel"); + writeFile( + hooks, + ["probe.cjs"], + "const fs = require('node:fs'); const path = require('node:path');\n" + + "fs.appendFileSync(path.join(process.cwd(), 'hook-observations.jsonl'), JSON.stringify({" + + "directory: process.env.COPILOT_HOOK_DIR, cwd: process.cwd(), " + + "resource: fs.readFileSync(path.join(process.env.COPILOT_HOOK_DIR, 'resource.txt'), 'utf8')" + + "}) + '\\n');\n" + ); + writeFile( + hooks, + ["hooks.json"], + JSON.stringify({ + hooks: { + preToolUse: [ + { + type: "command", + bash: `"${process.execPath}" "$COPILOT_HOOK_DIR/probe.cjs"`, + powershell: `& "${process.execPath}" (Join-Path $env:COPILOT_HOOK_DIR 'probe.cjs')`, + }, + ], + }, + }) + ); + writeFile( + workDir, + [".github", "hooks", "ambient.json"], + JSON.stringify({ + hooks: { preToolUse: [{ command: "echo ambient-hook" }] }, + }) + ); + let callbackInvocations = 0; + const session = await client.createSession({ + onPermissionRequest: approveAll, + workingDirectory: workDir, + enableConfigDiscovery: false, + enableFileHooks: false, + hookDirectories: [hooks], + hooks: { + onPreToolUse: async () => { + callbackInvocations++; + return { permissionDecision: "allow" }; + }, + }, + }); + try { + await client.rpc.sessions.reloadPluginHooks({ + sessionId: session.sessionId, + deferRepoHooks: true, + }); + await session.rpc.tools.initializeAndValidate(); + const result = await session.rpc.tools.execute({ + name: "view", + arguments: { path: path.join(hooks, "resource.txt") }, + toolCallId: "explicit-hook-directory-probe", + }); + expect(JSON.stringify(result)).toContain("resource-sentinel"); + expect(callbackInvocations).toBe(1); + const observations = path.join(workDir, "hook-observations.jsonl"); + const first = fs.readFileSync(observations, "utf8").trim().split("\n"); + expect(first).toHaveLength(1); + expect(JSON.parse(first[0])).toEqual({ + directory: hooks, + cwd: workDir, + resource: "resource-sentinel", + }); + expect((await session.rpc.options.update({ hookDirectories: [] })).success).toBe(true); + await client.rpc.sessions.reloadPluginHooks({ + sessionId: session.sessionId, + deferRepoHooks: true, + }); + await session.rpc.tools.execute({ + name: "view", + arguments: { path: path.join(hooks, "resource.txt") }, + toolCallId: "cleared-hook-directory-probe", + }); + expect(fs.readFileSync(observations, "utf8").trim().split("\n")).toHaveLength(1); + expect(callbackInvocations).toBe(2); + } finally { + await session.disconnect(); + } + }); + + it("retains conventional and configured guard hooks after changing to a repository subdirectory", async () => { + const observations = path.join(workDir, "cwd-hooks.jsonl"); + const subdir = path.join(workDir, "subdir"); + fs.mkdirSync(subdir); + for (const parts of [[".github", "hooks"], ["vendor-hooks"]]) { + const hooks = path.join(workDir, ...parts); + writeFile( + hooks, + ["probe.cjs"], + "require('node:fs').appendFileSync(" + + JSON.stringify(observations) + + ", JSON.stringify({directory: process.env.COPILOT_HOOK_DIR, cwd: process.cwd()}) + '\\n');\n" + ); + writeFile( + hooks, + ["hooks.json"], + JSON.stringify({ + hooks: { + preToolUse: [ + { + type: "command", + bash: `"${process.execPath}" "$COPILOT_HOOK_DIR/probe.cjs"`, + powershell: `& "${process.execPath}" (Join-Path $env:COPILOT_HOOK_DIR 'probe.cjs')`, + }, + ], + }, + }) + ); + } + writeFile( + workDir, + [".github", "copilot", "settings.json"], + JSON.stringify({ hookDirectories: ["vendor-hooks"] }) + ); + const resource = writeFile(workDir, ["resource.txt"], "cwd-hook-resource"); + const session = await client.createSession({ + onPermissionRequest: approveAll, + workingDirectory: workDir, + enableConfigDiscovery: true, + requestExtensions: false, + }); + try { + await session.rpc.tools.initializeAndValidate(); + await session.rpc.tools.execute({ + name: "view", + arguments: { path: resource }, + toolCallId: "guard-before-cwd-change", + }); + expect(fs.readFileSync(observations, "utf8").trim().split("\n")).toHaveLength(2); + expect( + await session.rpc.metadata.setWorkingDirectory({ workingDirectory: subdir }) + ).toMatchObject({ workingDirectory: subdir }); + await session.rpc.tools.execute({ + name: "view", + arguments: { path: resource }, + toolCallId: "guard-after-cwd-change", + }); + const rows = fs + .readFileSync(observations, "utf8") + .trim() + .split("\n") + .map((row) => JSON.parse(row)); + expect(rows).toHaveLength(4); + expect( + rows + .slice(2) + .map((row) => row.directory) + .sort() + ).toEqual( + [path.join(workDir, ".github", "hooks"), path.join(workDir, "vendor-hooks")].sort() + ); + // Hooks without an explicit cwd remain anchored to the consuming project root. + expect(rows.slice(2).map((row) => row.cwd)).toEqual([workDir, workDir]); + } finally { + await session.disconnect(); + } + }); +}); diff --git a/nodejs/test/e2e/disabled_mcp_servers.e2e.test.ts b/nodejs/test/e2e/disabled_mcp_servers.e2e.test.ts index 5a240bbd76..e62de0e5d9 100644 --- a/nodejs/test/e2e/disabled_mcp_servers.e2e.test.ts +++ b/nodejs/test/e2e/disabled_mcp_servers.e2e.test.ts @@ -721,11 +721,26 @@ describe("disabled MCP servers", async () => { onPermissionRequest: approveAll, enableSessionStore: true, pluginDirectories: [pluginDirectory], - disabledMcpServers: ["disabled"], + disabledMcpServers: ["disabled", "github-mcp-server", "computer-use"], }); await waitForMcpStatus(resumedSession, "control", "connected"); await waitForMcpStatus(resumedSession, "disabled", "disabled"); await waitForMarkerCount(controlMarker, 1); + const inventory = await resumedSession.rpc.mcp.listConfigured(); + expect( + inventory.servers + .filter((server) => ["control", "disabled"].includes(server.name)) + .map(({ name, enabled }) => ({ name, enabled })) + .sort((a, b) => a.name.localeCompare(b.name)) + ).toEqual([ + { name: "control", enabled: true }, + { name: "disabled", enabled: false }, + ]); + expect( + inventory.servers.filter( + (server) => server.source === "builtin" && server.enabled + ) + ).toEqual([]); expect(existsSync(disabledMarker)).toBe(false); } finally { await initialClient.stop().catch(() => {}); diff --git a/nodejs/test/e2e/dynamic_auto_tiers.e2e.test.ts b/nodejs/test/e2e/dynamic_auto_tiers.e2e.test.ts index 181f4de3b1..e1c0dc76be 100644 --- a/nodejs/test/e2e/dynamic_auto_tiers.e2e.test.ts +++ b/nodejs/test/e2e/dynamic_auto_tiers.e2e.test.ts @@ -324,7 +324,7 @@ describe.skipIf(isByokBackend)("Dynamic Auto tiers", async () => { expect((await changed).data.autoTier).toBe("premium-v2"); expect((await session.rpc.model.getCurrent()).autoTier).toBe("premium-v2"); const request = (await proxy.getRequests()).find((entry) => entry.url === "/auto"); - expect(JSON.parse(request!.body).tier).toBe("premium-v2"); + expect(JSON.parse(request!.body)).toMatchObject({ tier: "premium-v2", ensemble: true }); const sessionId = session.sessionId; await session.disconnect(); await client.stop(); diff --git a/nodejs/test/e2e/entra_identity_first.e2e.test.ts b/nodejs/test/e2e/entra_identity_first.e2e.test.ts index 2a6e49fff2..7df4d67c50 100644 --- a/nodejs/test/e2e/entra_identity_first.e2e.test.ts +++ b/nodejs/test/e2e/entra_identity_first.e2e.test.ts @@ -5,11 +5,12 @@ import { randomUUID } from "node:crypto"; import { mkdir, readFile, writeFile } from "node:fs/promises"; import { join } from "node:path"; -import { describe, expect, it, onTestFinished } from "vitest"; +import { describe, expect, it, onTestFailed, onTestFinished } from "vitest"; import { approveAll, CopilotClient, RuntimeConnection } from "../../src/index.js"; import { createSdkTestContext } from "./harness/sdkTestContext.js"; import { ConnectProxy } from "../../../test/harness/connectProxy.js"; import { createE2eRequestHandler } from "../../../test/harness/mockHandlers.js"; +import { readDiagnosticTail } from "../../../test/harness/sdk-diagnostics.mjs"; const { env, workDir, openAiEndpoint } = await createSdkTestContext({ useStdio: true, @@ -20,6 +21,19 @@ const M365_SCOPE = "42c8f7bd-c7a8-4ba9-bc1c-91edfb716247/.default"; const LOKI_HOST = "loki.identity-first.example"; const VERIFICATION_UNAVAILABLE = "Microsoft sign-in succeeded, but GitHub account verification is temporarily unavailable. Please try again later. Your active account has not changed."; +type CatalogReceipt = { + resource: "capi_models" | "loki_models" | "loki_quota"; + authenticated: boolean; + receivedAtMs: number; + status?: number; + /** Fixture-selected class, not proof that the runtime decoded the response. */ + responseClass?: "catalog" | "quota" | "mos_disabled" | "access_denied"; + replyAtMs?: number; + finishedAtMs?: number; + closedAtMs?: number; + closedBeforeFinish?: boolean; + requestAborted?: boolean; +}; const scenarios = [ { name: "both", emuGrant: true, staff: true, capi: 200, loki: 200, expected: "choices" }, { name: "emu only", emuGrant: true, staff: true, capi: 200, loki: 403, expected: "entraEmu" }, @@ -198,12 +212,70 @@ describe("Entra identity-first resource discovery", () => { } const grants: URLSearchParams[] = []; const catalogs: string[] = []; + const receipts: CatalogReceipt[] = []; + let omittedReceipts = 0; + let loginOutcome: + | "pending" + | "fulfilled" + | "rejected_no_backend" + | "rejected_verification" + | "rejected_rpc_timeout" + | "rejected_other" = "pending"; + const startedAt = performance.now(); + const elapsedMs = () => Math.round(performance.now() - startedAt); + onTestFailed(async () => { + console.error( + `[Entra discovery fixture] ${JSON.stringify({ loginOutcome, receipts, omittedReceipts })}` + ); + try { + const diagnostics = await readDiagnosticTail(profile); + console.error( + `[SDK diagnostics: last ${diagnostics.length} audited records]\n${diagnostics.join("\n")}` + ); + } catch { + console.error("SDK diagnostics unavailable"); + } + }); let latestRefresh = "identity-refresh"; const fallback = createE2eRequestHandler({ capiProxyUrl: openAiEndpoint.url }); const proxy = new ConnectProxy( async (request, response, host) => { const path = request.url ?? "/"; - const reply = (status: number, body: unknown) => { + const resource = + host === "api.githubcopilot.com" && path === "/models" + ? "capi_models" + : host === LOKI_HOST && path === "/api/models" + ? "loki_models" + : host === LOKI_HOST && path === "/api/quota" + ? "loki_quota" + : undefined; + const receipt: CatalogReceipt | undefined = resource + ? { resource, authenticated: false, receivedAtMs: elapsedMs() } + : undefined; + if (receipt) { + if (receipts.length < 32) receipts.push(receipt); + else omittedReceipts++; + request.once("aborted", () => { + receipt.requestAborted = true; + }); + response.once("finish", () => { + receipt.finishedAtMs = elapsedMs(); + }); + response.once("close", () => { + receipt.closedAtMs = elapsedMs(); + receipt.closedBeforeFinish = !response.writableFinished; + }); + } + const reply = ( + status: number, + body: unknown, + responseClass?: CatalogReceipt["responseClass"] + ) => { + if (receipt) { + receipt.status = status; + receipt.responseClass = responseClass; + receipt.replyAtMs = elapsedMs(); + } response.writeHead(status, { "content-type": "application/json" }); response.end(JSON.stringify(body)); return true; @@ -291,50 +363,69 @@ describe("Entra identity-first resource discovery", () => { if (host === "api.githubcopilot.com" && path === "/models") { expect(request.headers.authorization).toBe(`Bearer ${emuToken}`); catalogs.push("capi"); - return reply(scenario.capi, { - data: - "emptyCapi" in scenario && scenario.emptyCapi - ? [] - : [ - { - id: "gpt-5.2", - name: "Candidate CAPI model", - capabilities: { supports: { tool_calls: true } }, - model_picker_enabled: !( - "hiddenCapi" in scenario && scenario.hiddenCapi - ), - ...("restrictedCapi" in scenario - ? { billing: { restricted_to: ["enterprise"] } } - : {}), - }, - ], - }); + if (receipt) receipt.authenticated = true; + return reply( + scenario.capi, + { + data: + "emptyCapi" in scenario && scenario.emptyCapi + ? [] + : [ + { + id: "gpt-5.2", + name: "Candidate CAPI model", + capabilities: { supports: { tool_calls: true } }, + model_picker_enabled: !( + "hiddenCapi" in scenario && + scenario.hiddenCapi + ), + ...("restrictedCapi" in scenario + ? { + billing: { + restricted_to: ["enterprise"], + }, + } + : {}), + }, + ], + }, + scenario.capi === 200 ? "catalog" : "access_denied" + ); } if (host === LOKI_HOST && (path === "/api/models" || path === "/api/quota")) { expect(request.headers.authorization).toBe("Bearer m365-resource-token"); + if (receipt) receipt.authenticated = true; if ("mosDenied" in scenario && path === `/api/${scenario.mosDenied}`) { if (scenario.mosDenied === "models") catalogs.push("loki"); - return reply(403, { - error: { - message: - "Copilot CLI Entra access is not enabled for this user.", - type: "invalid_request_error", - code: "copilot_cli_entra_access_disabled", + return reply( + 403, + { + error: { + message: + "Copilot CLI Entra access is not enabled for this user.", + type: "invalid_request_error", + code: "copilot_cli_entra_access_disabled", + }, }, - }); + "mos_disabled" + ); } - if (path === "/api/quota") return reply(200, {}); + if (path === "/api/quota") return reply(200, {}, "quota"); catalogs.push("loki"); - return reply(scenario.loki, { - data: [ - { - id: "loki-test", - name: "Candidate M365 model", - capabilities: { supports: { tool_calls: true } }, - model_picker_enabled: true, - }, - ], - }); + return reply( + scenario.loki, + { + data: [ + { + id: "loki-test", + name: "Candidate M365 model", + capabilities: { supports: { tool_calls: true } }, + model_picker_enabled: true, + }, + ], + }, + scenario.loki === 200 ? "catalog" : "access_denied" + ); } return fallback(request, response, host); }, @@ -364,9 +455,11 @@ describe("Entra identity-first resource discovery", () => { connection: RuntimeConnection.forStdio({ path: process.env.COPILOT_CLI_PATH }), workingDirectory: workDir, useLoggedInUser: false, + logLevel: "debug" as const, env: { ...env, COPILOT_HOME: profile, + RUST_LOG: "error,sdk_diagnostics=debug", COPILOT_DISABLE_KEYTAR: "1", GH_TOKEN: "", GITHUB_TOKEN: "", @@ -393,6 +486,20 @@ describe("Entra identity-first resource discovery", () => { configDirectory: profile, onPermissionRequest: approveAll, }); + if (process.platform === "linux") { + await expect(session.rpc.accounts.login.begin({ kind: "entra" })).rejects.toThrow( + "only on Windows and macOS" + ); + expect(grants).toEqual([]); + expect(catalogs).toEqual([]); + expect( + await session.rpc.accounts.get({ query: { kind: "activeAccount" } }) + ).toEqual({ + kind: "activeAccount", + account: null, + }); + return; + } const { flowId } = await session.rpc.accounts.login.begin({ kind: "entra" }); const step = await session.rpc.accounts.login.advance({ flowId }); expect(step.kind).toBe("open-url"); @@ -414,6 +521,21 @@ describe("Entra identity-first resource discovery", () => { } return step; }); + void completing.then( + () => { + loginOutcome = "fulfilled"; + }, + (error: unknown) => { + const message = error instanceof Error ? error.message : ""; + loginOutcome = message.includes("No usable inference backend") + ? "rejected_no_backend" + : message.includes(VERIFICATION_UNAVAILABLE) + ? "rejected_verification" + : /timed out|timeout/i.test(message) + ? "rejected_rpc_timeout" + : "rejected_other"; + } + ); // Attach the failure assertion before the callback can complete the request. const completed = scenario.expected === "failure" diff --git a/nodejs/test/e2e/entra_renewal.e2e.test.ts b/nodejs/test/e2e/entra_renewal.e2e.test.ts index e040d8a905..82f20c6a27 100644 --- a/nodejs/test/e2e/entra_renewal.e2e.test.ts +++ b/nodejs/test/e2e/entra_renewal.e2e.test.ts @@ -197,6 +197,16 @@ it("requires linked sign-in, adopts same-session re-login, and persists silent r ); expect(modelRequests).toEqual([]); expect(grants).toBe(0); + if (process.platform === "linux") { + await expect(session.rpc.accounts.login.begin({ kind: "entra" })).rejects.toThrow( + "only on Windows and macOS" + ); + expect(signIns).toBe(0); + expect(exchanges).toBe(0); + expect(grants).toBe(0); + expect(modelRequests).toEqual([]); + return; + } const signIn = async () => { const { flowId } = await session.rpc.accounts.login.begin({ kind: "entra" }); diff --git a/nodejs/test/e2e/fixtures/hook-identity-recorder.cjs b/nodejs/test/e2e/fixtures/hook-identity-recorder.cjs new file mode 100755 index 0000000000..b9330f80e9 --- /dev/null +++ b/nodejs/test/e2e/fixtures/hook-identity-recorder.cjs @@ -0,0 +1,20 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +const { randomUUID } = require("node:crypto"); +const { writeFileSync } = require("node:fs"); +const { join } = require("node:path"); + +let body = ""; +process.stdin.setEncoding("utf8"); +process.stdin.on("data", (chunk) => { + body += chunk; +}); +process.stdin.on("end", () => { + const hook = process.argv[2]; + writeFileSync( + join(process.argv[3], `${hook}.${randomUUID()}.json`), + JSON.stringify({ hook, input: JSON.parse(body) }) + ); +}); diff --git a/nodejs/test/e2e/harness/inferenceRequest.ts b/nodejs/test/e2e/harness/inferenceRequest.ts new file mode 100644 index 0000000000..d58e8527c0 --- /dev/null +++ b/nodejs/test/e2e/harness/inferenceRequest.ts @@ -0,0 +1,56 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +import { z } from "zod"; + +export async function inferenceModelOptions( + request: Request +): Promise<{ model?: string; reasoning_effort?: string } | undefined> { + const path = new URL(request.url).pathname; + if (!["/chat/completions", "/responses", "/v1/messages", "/messages"].includes(path)) { + return undefined; + } + const options = z + .object({ + model: z.string().optional(), + reasoning_effort: z.string().nullish(), + reasoning: z.object({ effort: z.string().nullish() }).nullish(), + output_config: z.object({ effort: z.string().nullish() }).nullish(), + }) + .parse(await request.clone().json()); + const effort = + path === "/responses" + ? options.reasoning?.effort + : path === "/chat/completions" + ? options.reasoning_effort + : options.output_config?.effort; + return { model: options.model, reasoning_effort: effort ?? undefined }; +} + +export function userMessageText(body: { + messages?: { role?: string; content?: unknown }[]; + input?: string | { role?: string; content?: unknown }[]; +}): string[] { + if (typeof body.input === "string") { + return [body.input]; + } + return (body.messages ?? body.input ?? []).flatMap((message) => { + if (message.role !== "user") { + return []; + } + if (typeof message.content === "string") { + return [message.content]; + } + if (!Array.isArray(message.content)) { + return []; + } + return message.content + .filter( + (part): part is { type: string; text: string } => + (part?.type === "text" || part?.type === "input_text") && + typeof part.text === "string" + ) + .map((part) => part.text); + }); +} diff --git a/nodejs/test/e2e/harness/mcpTestServer.ts b/nodejs/test/e2e/harness/mcpTestServer.ts new file mode 100644 index 0000000000..76e50a5837 --- /dev/null +++ b/nodejs/test/e2e/harness/mcpTestServer.ts @@ -0,0 +1,24 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +import { dirname, resolve } from "node:path"; +import { fileURLToPath } from "node:url"; + +export function mcpTestServerConfig(result: string, descriptionFile?: string) { + const serverPath = resolve( + dirname(fileURLToPath(import.meta.url)), + "../../../../test/harness/test-mcp-server.mjs" + ); + return { + type: "stdio" as const, + command: process.execPath, + args: [ + serverPath, + ...(descriptionFile ? ["--tool-description-file", descriptionFile] : []), + ], + workingDirectory: dirname(serverPath), + env: { CACHE_TEST_RESULT: result }, + tools: ["get_env"], + }; +} diff --git a/nodejs/test/e2e/harness/sdkTestContext.ts b/nodejs/test/e2e/harness/sdkTestContext.ts index 1bbc4ab19e..515177ee44 100644 --- a/nodejs/test/e2e/harness/sdkTestContext.ts +++ b/nodejs/test/e2e/harness/sdkTestContext.ts @@ -3,10 +3,11 @@ *--------------------------------------------------------------------------------------------*/ import fs, { realpathSync } from "fs"; -import { rm } from "fs/promises"; +import { readdir, rm } from "fs/promises"; import os from "os"; import { basename, dirname, join, resolve } from "path"; import { rimraf } from "rimraf"; +import { createInterface } from "readline"; import { fileURLToPath } from "url"; import { afterAll, afterEach, beforeEach, onTestFailed, onTestFinished, TestContext } from "vitest"; import { CopilotClient, CopilotClientOptions, RuntimeConnection } from "../../../src"; @@ -16,6 +17,7 @@ import { formatError, retry } from "./sdkTestHelper"; import { resolvePreparedRuntimePath } from "../../../scripts/prepare-runtime.js"; import { testBackend, withTestBackend } from "./testBackend"; import { readDiagnosticTail } from "../../../../test/harness/sdk-diagnostics.mjs"; +import type { ReplayBackend } from "../../../../test/harness/replayingCapiProxy"; export const isCI = process.env.GITHUB_ACTIONS === "true"; export const DEFAULT_GITHUB_TOKEN = "fake-token-for-e2e-tests"; @@ -65,6 +67,7 @@ export async function createSdkTestContext({ copilotClientOptions, replayOnly = process.env.COPILOT_SDK_E2E_REPLAY_ONLY === "true", modelNames, + backend = testBackend, }: { logLevel?: "error" | "none" | "warning" | "info" | "debug" | "all"; cliPath?: string; @@ -72,6 +75,8 @@ export async function createSdkTestContext({ copilotClientOptions?: CopilotClientOptions; replayOnly?: boolean; modelNames?: Record; + /** Pin a provider-specific contract instead of inheriting the CI backend matrix. */ + backend?: ReplayBackend; } = {}) { const homeDir = realpathSync(fs.mkdtempSync(join(os.tmpdir(), "copilot-test-config-"))); const copilotHomeDir = realpathSync(fs.mkdtempSync(join(os.tmpdir(), "copilot-test-home-"))); @@ -170,11 +175,11 @@ export async function createSdkTestContext({ class TestClient extends CopilotClient { override createSession(config: SessionConfig) { - return super.createSession(withTestBackend(config, testBackend, proxyUrl)); + return super.createSession(withTestBackend(config, backend, proxyUrl)); } override resumeSession(sessionId: string, config: ResumeSessionConfig) { - return super.resumeSession(sessionId, withTestBackend(config, testBackend, proxyUrl)); + return super.resumeSession(sessionId, withTestBackend(config, backend, proxyUrl)); } } @@ -273,6 +278,30 @@ export async function createSdkTestContext({ console.error( `[SDK diagnostics: last ${diagnostics.length} audited records]\n${diagnostics.join("\n")}` ); + const logDir = join(copilotHomeDir, "logs"); + for (const file of await readdir(logDir)) { + if (!file.endsWith(".log")) continue; + const lines = createInterface({ + input: fs.createReadStream(join(logDir, file)), + }); + const mcpClosureDiagnostics: string[] = []; + for await (const line of lines) { + const mcpClosure = line.match( + /^\S+ \[(?:DEBUG|WARN)\] \[rust:mcp::client\] MCP (?:liveness probe (?:found the (?:session expired|transport (?:closed|channel closed))|failed; awaiting corroboration|went unanswered; inconclusive|could not be sent in time; inconclusive|failed inconclusively|failed with an unrecognised transport error)|liveness probes are neither proving life nor consistently failing|session reported closed)/ + ); + if (mcpClosure) { + // Keep only static messages; transport error fields can contain user data. + mcpClosureDiagnostics.push(mcpClosure[0]); + if (mcpClosureDiagnostics.length > 32) + mcpClosureDiagnostics.shift(); + } + } + if (mcpClosureDiagnostics.length > 0) { + console.error( + `[MCP session-closure diagnostics: ${file}; last ${mcpClosureDiagnostics.length} matching lines]\n${mcpClosureDiagnostics.join("\n")}` + ); + } + } } catch { console.error("SDK diagnostics unavailable"); } @@ -309,6 +338,7 @@ export async function createSdkTestContext({ } await openAiEndpoint.updateConfig({ + backend, replayOnly, filePath: getTrafficCapturePath(testContext), workDir, diff --git a/nodejs/test/e2e/harness/webSocketResponsesServer.ts b/nodejs/test/e2e/harness/webSocketResponsesServer.ts index 219100a97f..de4810802c 100644 --- a/nodejs/test/e2e/harness/webSocketResponsesServer.ts +++ b/nodejs/test/e2e/harness/webSocketResponsesServer.ts @@ -14,6 +14,9 @@ export async function startWebSocketResponsesServer() { messages: [] as Array>, messageConnectionIds: [] as number[], responses: [] as WebSocketReply[], + /** Scripted HTTP `/responses` SSE replies; when empty, HTTP requests are forwarded. */ + httpResponses: [] as WebSocketEnvelope[][], + httpResponseRequestPaths: [] as string[], openConnectionIds: new Set(), connectionCount: 0, closedConnectionCount: 0, @@ -51,6 +54,24 @@ export async function startWebSocketResponsesServer() { ); return; } + const scriptedHttpEvents = + req.method === "POST" && + new URL(req.url ?? "/", "http://localhost").pathname.endsWith("/responses") + ? state.httpResponses.shift() + : undefined; + if (scriptedHttpEvents) { + state.httpResponseRequestPaths.push(req.url ?? "/"); + req.resume(); + req.on("error", (error) => errors.push(error)); + req.on("end", () => { + res.writeHead(200, { "content-type": "text/event-stream" }); + for (const event of scriptedHttpEvents) { + res.write(`event: ${String(event.type)}\ndata: ${JSON.stringify(event)}\n\n`); + } + res.end(); + }); + return; + } if (!httpTargetUrl) { res.writeHead(503); res.end("The test CAPI proxy has not been attached."); diff --git a/nodejs/test/e2e/hydrafusion_max.e2e.test.ts b/nodejs/test/e2e/hydrafusion_max.e2e.test.ts index a4a7c52c93..078dbeeaab 100644 --- a/nodejs/test/e2e/hydrafusion_max.e2e.test.ts +++ b/nodejs/test/e2e/hydrafusion_max.e2e.test.ts @@ -3,8 +3,10 @@ *--------------------------------------------------------------------------------------------*/ import { describe, expect, it } from "vitest"; +import { z } from "zod"; import { approveAll, + defineTool, CopilotRequestHandler, RuntimeConnection, type SessionEvent, @@ -19,9 +21,37 @@ import { isByokBackend } from "./harness/testBackend.js"; const MODEL = "gpt-5.6-sol"; const REPLY = "SDK_HYDRAFUSION_MAX_DONE: Au"; +interface ChatToolCall { + id: string; + type: "function"; + function: { name: string; arguments: string }; +} + +interface ChatMessage { + role: string; + content?: unknown; + tool_calls?: ChatToolCall[]; + tool_call_id?: string; +} + +interface ChatRequest { + model?: string; + stream?: boolean; + messages: ChatMessage[]; +} + class FusionRequestHandler extends CopilotRequestHandler { readonly plans: unknown[] = []; - readonly inference: unknown[] = []; + readonly inference: ChatRequest[] = []; + + constructor( + private readonly reply: (request: ChatRequest) => ChatMessage = () => ({ + role: "assistant", + content: REPLY, + }) + ) { + super(); + } protected override async sendRequest(request: Request): Promise { const path = new URL(request.url).pathname; @@ -57,23 +87,19 @@ class FusionRequestHandler extends CopilotRequestHandler { }); } if (path === "/chat/completions") { - const body = (await request.json()) as { model?: string; stream?: boolean }; + const body = (await request.json()) as ChatRequest; this.inference.push(body); if (body.model !== MODEL) { throw new Error(`Expected concrete Fusion constituent, received ${body.model}`); } + const message = this.reply(body); + const finishReason = message.tool_calls ? "tool_calls" : "stop"; const completion = { id: "chatcmpl-sdk-max", object: "chat.completion", created: 0, model: MODEL, - choices: [ - { - index: 0, - message: { role: "assistant", content: REPLY }, - finish_reason: "stop", - }, - ], + choices: [{ index: 0, message, finish_reason: finishReason }], usage: { prompt_tokens: 11, completion_tokens: 7, total_tokens: 18 }, }; if (!body.stream) return Response.json(completion); @@ -84,8 +110,18 @@ class FusionRequestHandler extends CopilotRequestHandler { choices: [ { index: 0, - delta: { role: "assistant", content: REPLY }, - finish_reason: "stop", + delta: { + ...message, + ...(message.tool_calls + ? { + tool_calls: message.tool_calls.map((call, index) => ({ + index, + ...call, + })), + } + : {}), + }, + finish_reason: finishReason, }, ], })}\n\ndata: [DONE]\n\n`, @@ -264,3 +300,191 @@ describe.skipIf(isByokBackend).each([false, true])( } } ); + +const DRAFT_NOTE = "FUSION_DRAFT_NOTE_BEFORE_STOP"; +const RECORD_CALL_ID = "call_fusion_record_progress"; +const RECORD_RESULT = "FUSION_PROGRESS_RECORDED"; +const WAIT_CALL_ID = "call_fusion_wait_for_stop"; +const FOLLOW_UP_PROMPT = "FUSION_FOLLOW_UP_AFTER_STOP: continue where you left off."; +const FOLLOW_UP_REPLY = "FUSION_FOLLOW_UP_DONE"; + +function messageText(content: unknown): string { + if (typeof content === "string") return content; + if (Array.isArray(content)) { + return content + .map((part) => (typeof part?.text === "string" ? (part.text as string) : "")) + .join(""); + } + return ""; +} + +// The draft writes a note and finishes one tool call, then blocks in a second one until the +// user stops the turn. The follow-up turn answers once it sees the user's next prompt. +function interruptedDraftReply(request: ChatRequest): ChatMessage { + if ( + request.messages.some((message) => messageText(message.content).includes(FOLLOW_UP_PROMPT)) + ) { + return { role: "assistant", content: FOLLOW_UP_REPLY }; + } + const recorded = request.messages.some( + (message) => message.role === "tool" && message.tool_call_id === RECORD_CALL_ID + ); + const call = (id: string, name: string, args: object): ChatToolCall => ({ + id, + type: "function", + function: { name, arguments: JSON.stringify(args) }, + }); + return recorded + ? { role: "assistant", content: "", tool_calls: [call(WAIT_CALL_ID, "wait_for_stop", {})] } + : { + role: "assistant", + content: DRAFT_NOTE, + tool_calls: [call(RECORD_CALL_ID, "record_progress", { note: "alpha" })], + }; +} + +async function withTimeout(promise: Promise, ms: number, label: string): Promise { + let timer: ReturnType | undefined; + try { + return await Promise.race([ + promise, + new Promise((_, reject) => { + timer = setTimeout(() => reject(new Error(`Timeout: ${label}`)), ms); + }), + ]); + } finally { + if (timer) clearTimeout(timer); + } +} + +describe.skipIf(isByokBackend)("HydraFusion interrupted turn", async () => { + const handler = new FusionRequestHandler(interruptedDraftReply); + const { copilotClient: client, workDir } = await createSdkTestContext({ + copilotClientOptions: { + gitHubToken: DEFAULT_GITHUB_TOKEN, + requestHandler: handler, + env: { + HYDRAFUSION: "true", + HYDRAFUSION_ROLLOUT: "true", + HYDRAFUSION_PLAN_V2: "true", + COPILOT_EXP_COPILOT_CLI_HYDRAFUSION_PLAN_V2: "true", + HYDRAFUSION_INTERRUPTED_COMMIT: "true", + }, + }, + }); + + it("keeps the stopped draft's work in history and in the next turn", async () => { + let markWaitStarted!: () => void; + const waitStarted = new Promise((resolve) => { + markWaitStarted = resolve; + }); + let releaseWait!: () => void; + const waitReleased = new Promise((resolve) => { + releaseWait = resolve; + }); + const session = await client.createSession({ + model: "hydrafusion-max", + enableExperimentalMode: true, + workingDirectory: workDir, + onPermissionRequest: approveAll, + tools: [ + defineTool("record_progress", { + description: "Records a progress note.", + parameters: z.object({ note: z.string() }), + handler: async () => RECORD_RESULT, + }), + defineTool("wait_for_stop", { + description: "Blocks until released.", + parameters: z.object({}), + handler: async (_args, invocation) => { + markWaitStarted(); + await new Promise((resolve) => { + void waitReleased.then(resolve); + invocation.signal?.addEventListener("abort", () => resolve(), { + once: true, + }); + }); + return "RELEASED_AFTER_STOP"; + }, + }), + ], + }); + let stopRequested = false; + let markStoppedIdle!: () => void; + const stoppedIdle = new Promise((resolve) => { + markStoppedIdle = resolve; + }); + const unsubscribe = session.on((event) => { + if (stopRequested && event.type === "session.idle") markStoppedIdle(); + }); + try { + void session.send({ prompt: "Record progress, then wait for the stop signal." }); + await withTimeout(waitStarted, 60_000, "wait_for_stop start"); + stopRequested = true; + await session.abort(); + releaseWait(); + await withTimeout(stoppedIdle, 60_000, "idle after stop"); + + const history = await session.getEvents(); + const commit = history.find((event) => event.type === "session.fusion_commit_started"); + expect(commit?.data).toMatchObject({ + commitId: expect.stringMatching(/:commit:interrupted:0$/), + }); + const commitId = (commit?.data as { commitId: string }).commitId; + const committed = history.filter( + (event) => + (event.data as { fusion?: { commitId?: string } }).fusion?.commitId === commitId + ); + expect(committed).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + type: "assistant.message", + data: expect.objectContaining({ content: DRAFT_NOTE }), + }), + expect.objectContaining({ + type: "tool.execution_complete", + data: expect.objectContaining({ + toolCallId: RECORD_CALL_ID, + success: true, + }), + }), + ]) + ); + expect(history.some((event) => event.type === "session.fusion_completed")).toBe(false); + + const reply = await session.sendAndWait({ prompt: FOLLOW_UP_PROMPT }); + expect(reply?.data.content).toBe(FOLLOW_UP_REPLY); + const followUp = handler.inference.find((request) => + request.messages.some((message) => + messageText(message.content).includes(FOLLOW_UP_PROMPT) + ) + ); + const messages = followUp?.messages ?? []; + const followUpIndex = messages.findIndex((message) => + messageText(message.content).includes(FOLLOW_UP_PROMPT) + ); + const earlier = messages.slice(0, followUpIndex); + expect( + earlier.some( + (message) => + message.role === "assistant" && + messageText(message.content).includes(DRAFT_NOTE) + ) + ).toBe(true); + expect( + earlier.flatMap((message) => message.tool_calls?.map((call) => call.id) ?? []) + ).toEqual(expect.arrayContaining([RECORD_CALL_ID, WAIT_CALL_ID])); + const results = new Map( + earlier + .filter((message) => message.role === "tool") + .map((message) => [message.tool_call_id, messageText(message.content)]) + ); + expect(results.get(RECORD_CALL_ID)).toContain(RECORD_RESULT); + expect(results.has(WAIT_CALL_ID)).toBe(true); + } finally { + releaseWait(); + unsubscribe(); + await session.disconnect(); + } + }); +}); diff --git a/nodejs/test/e2e/image_edits.e2e.test.ts b/nodejs/test/e2e/image_edits.e2e.test.ts new file mode 100644 index 0000000000..952a61fb0c --- /dev/null +++ b/nodejs/test/e2e/image_edits.e2e.test.ts @@ -0,0 +1,508 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +import { createHash, randomUUID } from "node:crypto"; +import { readFile } from "node:fs/promises"; +import { join } from "node:path"; +import { deflateSync } from "node:zlib"; +import { describe, expect, it } from "vitest"; +import { + approveAll, + CopilotRequestHandler, + type CopilotClient, + type CopilotRequestContext, + type SessionEvent, + type UserPromptTransformedHookInput, +} from "../../src/index.js"; +import { createSdkTestContext } from "./harness/sdkTestContext.js"; + +const MODEL = "gpt-image-2.5-sunburst"; +const FINAL = "IMAGE_EDIT_SDK_FINISHED"; + +function png(red: number, width = 1): string { + function chunk(name: string, data: Buffer): Buffer { + const payload = Buffer.concat([Buffer.from(name), data]); + let crc = 0xffffffff; + for (const byte of payload) { + crc ^= byte; + for (let bit = 0; bit < 8; bit++) { + crc = (crc >>> 1) ^ (crc & 1 ? 0xedb88320 : 0); + } + } + const length = Buffer.alloc(4); + length.writeUInt32BE(data.length); + const checksum = Buffer.alloc(4); + checksum.writeUInt32BE((crc ^ 0xffffffff) >>> 0); + return Buffer.concat([length, payload, checksum]); + } + const header = Buffer.alloc(13); + header.writeUInt32BE(width, 0); + header.writeUInt32BE(1, 4); + header[8] = 8; + header[9] = 2; + const pixels = Buffer.alloc(1 + width * 3); + for (let column = 0; column < width; column++) { + pixels[1 + column * 3] = red; + } + return Buffer.concat([ + Buffer.from("89504e470d0a1a0a", "hex"), + chunk("IHDR", header), + chunk("IDAT", deflateSync(pixels)), + chunk("IEND", Buffer.alloc(0)), + ]).toString("base64"); +} + +function imageId(data: string): string { + return `sha256:${createHash("sha256").update(Buffer.from(data, "base64")).digest("hex")}`; +} + +const SOURCE = png(20); +const EDITED = png(200); + +class ImageHandler extends CopilotRequestHandler { + readonly edits: Array> = []; + readonly generations: Array> = []; + readonly chats: Array> = []; + outputImage = EDITED; + supportedEndpoints = ["/v1/images/generations", "/v1/images/edits"]; + nextArguments: Record | undefined; + private calls = 0; + + protected override async sendRequest( + request: Request, + _context: CopilotRequestContext + ): Promise { + const path = new URL(request.url).pathname; + const body: Record = request.body ? await request.json() : {}; + if (path.endsWith("/models")) { + return Response.json({ + data: [ + { + id: MODEL, + name: "Image test", + capabilities: { type: "image" }, + supported_endpoints: this.supportedEndpoints, + model_picker_enabled: false, + policy: { state: "enabled" }, + }, + ], + }); + } + if (path.endsWith("/models/session") || path.includes("/policy")) { + return Response.json({ state: "enabled" }); + } + if (path === "/v1/images/edits" || path === "/v1/images/generations") { + (path.endsWith("/edits") ? this.edits : this.generations).push(body); + return Response.json({ + created: 1, + data: [{ b64_json: this.outputImage }], + output_format: "png", + }); + } + if (path.endsWith("/chat/completions")) { + this.chats.push(body); + const args = this.nextArguments; + this.nextArguments = undefined; + const toolCalls = args + ? [ + { + id: `image-edit-${++this.calls}`, + type: "function", + function: { name: "image_generation", arguments: JSON.stringify(args) }, + }, + ] + : undefined; + const message = { + role: "assistant", + content: args ? null : FINAL, + tool_calls: toolCalls, + }; + const finish = args ? "tool_calls" : "stop"; + if (body.stream === true) { + const delta = { + ...message, + tool_calls: toolCalls?.map((call, index) => ({ index, ...call })), + }; + return new Response( + [ + `data: ${JSON.stringify({ id: "image-chat", object: "chat.completion.chunk", choices: [{ index: 0, delta, finish_reason: null }] })}\n\n`, + `data: ${JSON.stringify({ id: "image-chat", object: "chat.completion.chunk", choices: [{ index: 0, delta: {}, finish_reason: finish }], usage: { prompt_tokens: 1, completion_tokens: 1, total_tokens: 2 } })}\n\n`, + "data: [DONE]\n\n", + ].join(""), + { headers: { "content-type": "text/event-stream" } } + ); + } + return Response.json({ + id: "image-chat", + object: "chat.completion", + model: "image-edit-chat", + choices: [{ index: 0, message, finish_reason: finish }], + usage: { prompt_tokens: 1, completion_tokens: 1, total_tokens: 2 }, + }); + } + throw new Error(`Unexpected image test request: ${request.method} ${path}`); + } +} + +describe("Native image editing", async () => { + const { createClient, env, openAiEndpoint } = await createSdkTestContext({ + copilotClientOptions: { env: { IMAGE_GENERATION_TOOL: "true" } }, + }); + async function readPersistedAssets(client: CopilotClient, sessionId: string) { + // Binary assets are internal persistence records, not public SDK events. + await client.rpc.sessions.save({ sessionId }); + const log = await readFile( + join(env.COPILOT_HOME, "session-state", sessionId, "events.jsonl"), + "utf8" + ); + return log + .trim() + .split("\n") + .map((line): SessionEvent => JSON.parse(line)) + .filter((event) => event.type === "session.binary_asset"); + } + const options = { + model: "image-edit-chat", + imageGeneration: { enabled: true }, + provider: { + type: "openai" as const, + wireApi: "completions" as const, + baseUrl: "https://image-edit-test.invalid/v1", + apiKey: "synthetic-image-test", + }, + onPermissionRequest: approveAll, + }; + + it("edits an explicit attachment and can reuse the generated result after resume", async () => { + const handler = new ImageHandler(); + const client = createClient({ requestHandler: handler }); + try { + await client.start(); + const session = await client.createSession(options); + const events: SessionEvent[] = []; + session.on((event) => events.push(event)); + handler.nextArguments = { + prompt: "Use this reference", + input_images: [{ image_id: imageId(SOURCE) }], + }; + const reply = await session.sendAndWait({ + prompt: "Use the attached image as a reference for a new image.", + attachments: [{ type: "blob", mimeType: "image/png", data: SOURCE }], + }); + expect(reply?.data.content).toBe(FINAL); + const completion = events.find((event) => event.type === "tool.execution_complete"); + expect(completion?.data.error).toBeUndefined(); + expect(completion?.data.success).toBe(true); + expect(handler.generations).toHaveLength(0); + expect(handler.edits).toEqual([ + { + model: MODEL, + prompt: "Use this reference", + images: [{ image_url: `data:image/png;base64,${SOURCE}` }], + n: 1, + output_format: "png", + stream: false, + }, + ]); + const sessionId = session.sessionId; + await session.disconnect(); + await client.stop(); + await client.start(); + const resumed = await client.resumeSession(sessionId, options); + handler.nextArguments = { + prompt: "Make the result brighter", + input_images: [{ image_id: imageId(EDITED) }], + }; + expect( + (await resumed.sendAndWait({ prompt: "Make the generated image brighter." }))?.data + .content + ).toBe(FINAL); + expect(handler.edits).toHaveLength(2); + expect(handler.edits[1].images).toEqual([ + { image_url: `data:image/png;base64,${EDITED}` }, + ]); + expect(handler.generations).toHaveLength(0); + await resumed.disconnect(); + } finally { + await client.stop(); + } + }); + + it("does not upload an unrelated attachment when the tool selects text-only generation", async () => { + const handler = new ImageHandler(); + const client = createClient({ requestHandler: handler }); + try { + await client.start(); + const session = await client.createSession(options); + handler.nextArguments = { prompt: "A fresh landscape" }; + expect( + ( + await session.sendAndWait({ + prompt: "Ignore the attachment and generate a fresh landscape.", + attachments: [{ type: "blob", mimeType: "image/png", data: SOURCE }], + }) + )?.data.content + ).toBe(FINAL); + expect(handler.edits).toHaveLength(0); + expect(handler.generations).toHaveLength(1); + expect(handler.generations[0]).not.toHaveProperty("images"); + await session.disconnect(); + } finally { + await client.stop(); + } + }); + + it("rejects image references outside the session instead of generating a substitute", async () => { + const handler = new ImageHandler(); + const client = createClient({ requestHandler: handler }); + try { + await client.start(); + const session = await client.createSession(options); + const events: SessionEvent[] = []; + session.on((event) => events.push(event)); + handler.nextArguments = { + prompt: "Edit", + input_images: [{ image_id: imageId(SOURCE) }], + }; + expect( + (await session.sendAndWait({ prompt: "Edit the selected image." }))?.data.content + ).toBe(FINAL); + expect(handler.edits).toHaveLength(0); + expect(handler.generations).toHaveLength(0); + const completion = events.find((event) => event.type === "tool.execution_complete"); + expect(completion?.data.error?.message).toMatch(/^image_generation_input_unavailable:/); + expect(completion?.data.success).toBe(false); + await session.disconnect(); + } finally { + await client.stop(); + } + }); + + it("keeps the transformed hook final for batched image attachments", async () => { + const handler = new ImageHandler(); + const client = createClient({ requestHandler: handler }); + const preceding = "Inspect the first batch image."; + const replacement = "IMAGE_BATCH_HOOK_REPLACEMENT"; + const inputs: UserPromptTransformedHookInput[] = []; + try { + await client.start(); + const session = await client.createSession({ + ...options, + hooks: { + onUserPromptTransformed: async (input) => { + inputs.push(input); + return { modifiedTransformedPrompt: replacement }; + }, + }, + }); + const events: SessionEvent[] = []; + session.on((event) => events.push(event)); + await session.rpc.sendMessages({ + messages: [ + { + prompt: preceding, + attachments: [{ type: "blob", mimeType: "image/png", data: SOURCE }], + }, + { prompt: "Finish the batch." }, + ], + wait: true, + }); + expect(inputs.find((input) => input.prompt === preceding)?.transformedPrompt).toContain( + imageId(SOURCE) + ); + const messages = events.filter((event) => event.type === "user.message"); + expect(messages).toHaveLength(2); + for (const message of messages) { + expect(message.data.transformedContent).toBe(replacement); + } + expect(JSON.stringify(handler.chats.at(-1)?.messages)).not.toContain( + "Image input references" + ); + // Validate the final assistant response arrived (guards against truncated captures). + expect( + events.findLast((event) => event.type === "assistant.message")?.data.content + ).toBe(FINAL); + await session.disconnect(); + } finally { + await client.stop(); + } + }); + + it("emits the preceding image message when its transformed hook throws", async () => { + const handler = new ImageHandler(); + const client = createClient({ requestHandler: handler }); + const preceding = "Inspect the first batch image."; + const hooked: string[] = []; + try { + await client.start(); + const session = await client.createSession({ + ...options, + hooks: { + onUserPromptTransformed: async (input) => { + hooked.push(input.prompt); + if (input.prompt === preceding) { + throw new Error("IMAGE_TRANSFORM_EXPECTED_FAILURE"); + } + }, + }, + }); + const events: SessionEvent[] = []; + session.on((event) => events.push(event)); + await session.rpc.sendMessages({ + messages: [ + { + prompt: preceding, + attachments: [{ type: "blob", mimeType: "image/png", data: SOURCE }], + }, + { prompt: "Finish the batch." }, + ], + wait: true, + }); + expect(hooked).toEqual([preceding, "Finish the batch."]); + const messages = events.filter((event) => event.type === "user.message"); + expect(messages.map((message) => message.data.content)).toEqual(hooked); + expect(messages[0].data.attachments).toContainEqual( + expect.objectContaining({ type: "blob", mimeType: "image/png", data: SOURCE }) + ); + expect( + (await readPersistedAssets(client, session.sessionId)).map( + (asset) => asset.data.assetId + ) + ).toEqual([imageId(SOURCE)]); + // Validate the final assistant response arrived (guards against truncated captures). + expect( + events.findLast((event) => event.type === "assistant.message")?.data.content + ).toBe(FINAL); + await session.disconnect(); + } finally { + await client.stop(); + } + }); + + it("adds a persisted preview when reattaching a generated image", async () => { + const handler = new ImageHandler(); + handler.outputImage = png(200, 2200); + const client = createClient({ requestHandler: handler }); + try { + await client.start(); + const session = await client.createSession(options); + handler.nextArguments = { prompt: "Generate a wide image." }; + expect( + (await session.sendAndWait({ prompt: "Generate a wide image." }))?.data.content + ).toBe(FINAL); + expect(handler.generations).toHaveLength(1); + expect( + ( + await session.sendAndWait({ + prompt: "Inspect the reattached image.", + attachments: [ + { type: "blob", mimeType: "image/png", data: handler.outputImage }, + ], + }) + )?.data.content + ).toBe(FINAL); + const assets = await readPersistedAssets(client, session.sessionId); + const update = assets.findLast( + (event) => event.data.assetId === `image-edit:${imageId(handler.outputImage)}` + ); + expect(update?.data.metadata?.imageEditSource).toEqual({ + data: handler.outputImage, + mimeType: "image/png", + }); + const preview = update?.data.data; + if (typeof preview !== "string") { + throw new Error("The reattached image has no persisted chat preview"); + } + expect(preview).not.toBe(handler.outputImage); + expect(Buffer.from(preview, "base64").length).toBeLessThanOrEqual(3 * 1024 * 1024); + const imageMessage = expect.objectContaining({ + role: "user", + content: expect.arrayContaining([ + expect.objectContaining({ + type: "image_url", + image_url: expect.objectContaining({ + url: `data:image/png;base64,${preview}`, + }), + }), + ]), + }); + expect(handler.chats.at(-1)?.messages).toContainEqual(imageMessage); + + const sessionId = session.sessionId; + await session.disconnect(); + await client.stop(); + await client.start(); + const resumed = await client.resumeSession(sessionId, options); + expect((await resumed.sendAndWait({ prompt: "Inspect it again." }))?.data.content).toBe( + FINAL + ); + expect(handler.chats.at(-1)?.messages).toContainEqual(imageMessage); + handler.nextArguments = { + prompt: "Edit the original wide image.", + input_images: [{ image_id: imageId(handler.outputImage) }], + }; + // Validate the final assistant response arrived (guards against truncated captures). + expect( + (await resumed.sendAndWait({ prompt: "Edit the reattached image." }))?.data.content + ).toBe(FINAL); + expect(handler.edits.at(-1)?.images).toEqual([ + { image_url: `data:image/png;base64,${handler.outputImage}` }, + ]); + await resumed.disconnect(); + } finally { + await client.stop(); + } + }); + + it.each(["generations-only", "filtered-out"] as const)( + "does not retain originals or advertise image inputs when %s", + async (availability) => { + const handler = new ImageHandler(); + if (availability === "generations-only") { + handler.supportedEndpoints = ["/v1/images/generations"]; + } + // In-process clients share the model catalog cache, keyed by endpoint and auth. + const token = `image-input-${randomUUID()}`; + await openAiEndpoint.setCopilotUserByToken(token, { + login: "image-input-test", + copilot_plan: "individual_pro", + endpoints: { api: env.COPILOT_API_URL }, + analytics_tracking_id: token, + }); + const client = createClient({ requestHandler: handler, gitHubToken: token }); + const inputs: UserPromptTransformedHookInput[] = []; + try { + await client.start(); + const session = await client.createSession({ + ...options, + excludedTools: availability === "filtered-out" ? ["image_generation"] : [], + hooks: { + onUserPromptTransformed: async (input) => { + inputs.push(input); + }, + }, + }); + const original = png(20, 2200); + // Validate the final assistant response arrived (guards against truncated captures). + expect( + ( + await session.sendAndWait({ + prompt: "Inspect this attachment.", + attachments: [{ type: "blob", mimeType: "image/png", data: original }], + }) + )?.data.content + ).toBe(FINAL); + expect(inputs).toHaveLength(1); + expect(inputs[0].transformedPrompt).not.toContain("Image input references"); + const assets = await readPersistedAssets(client, session.sessionId); + expect(assets).toHaveLength(1); + expect(assets[0].data.data).not.toBe(original); + expect(assets[0].data.metadata?.imageEditSource).toBeUndefined(); + await session.disconnect(); + } finally { + await client.stop(); + } + } + ); +}); diff --git a/nodejs/test/e2e/image_generation.e2e.test.ts b/nodejs/test/e2e/image_generation.e2e.test.ts new file mode 100644 index 0000000000..c365fcc255 --- /dev/null +++ b/nodejs/test/e2e/image_generation.e2e.test.ts @@ -0,0 +1,261 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +import { describe, expect, it } from "vitest"; +import { + approveAll, + CopilotRequestHandler, + RuntimeConnection, + type CopilotRequestContext, + type CopilotSession, +} from "../../src/index.js"; +import { createSdkTestContext } from "./harness/sdkTestContext.js"; +import { withTestBackend } from "./harness/testBackend.js"; + +const IMAGE_MODEL = "gpt-image-2.5-sunburst"; +const IMAGE_ENDPOINT = "/v1/images/generations"; +const PNG = + "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAABHNCSVQICAgIfAhkiAAAAAFzUkdCAK7OHOkAAAANSURBVAiZY2BgYPgPAAEEAQB9ssjfAAAAAElFTkSuQmCC"; + +class ImageService extends CopilotRequestHandler { + readonly imageRequests: unknown[] = []; + readonly chatToolNames: string[][] = []; + + constructor(private readonly modelAvailable = true) { + super(); + } + + protected override async sendRequest( + request: Request, + context: CopilotRequestContext + ): Promise { + const pathname = new URL(request.url).pathname; + if (pathname.endsWith("/chat/completions") && request.method === "POST") { + const body = (await request.json()) as { + model: string; + stream?: boolean; + tools?: Array<{ function: { name: string } }>; + }; + this.chatToolNames.push(body.tools?.map((tool) => tool.function.name) ?? []); + const message = { role: "assistant", content: "IMAGE_CONSENT_TURN_COMPLETE" }; + const response = { + id: "image-consent-turn", + created: 123, + model: body.model, + usage: { prompt_tokens: 5, completion_tokens: 1, total_tokens: 6 }, + }; + if (!body.stream) { + return Response.json({ + ...response, + object: "chat.completion", + choices: [{ index: 0, message, finish_reason: "stop" }], + }); + } + return new Response( + `data: ${JSON.stringify({ + ...response, + object: "chat.completion.chunk", + choices: [{ index: 0, delta: message, finish_reason: "stop" }], + })}\n\ndata: [DONE]\n\n`, + { headers: { "content-type": "text/event-stream" } } + ); + } + if (pathname === IMAGE_ENDPOINT) { + this.imageRequests.push(await request.json()); + return Response.json({ + created: 123, + data: [{ b64_json: PNG }], + output_format: "png", + }); + } + const response = await super.sendRequest(request, context); + if (pathname !== "/models" || !response.ok) { + return response; + } + const catalog = (await response.json()) as { data: unknown[] }; + if (this.modelAvailable) { + catalog.data.push({ + id: IMAGE_MODEL, + name: "SDK test image model", + capabilities: { type: "image" }, + supported_endpoints: [IMAGE_ENDPOINT], + model_picker_enabled: false, + policy: { state: "enabled" }, + }); + } + return Response.json(catalog); + } +} + +async function imageToolOffered(session: CopilotSession): Promise { + await session.rpc.tools.initializeAndValidate(); + const { tools } = await session.rpc.tools.getCurrentMetadata(); + return tools.some((tool) => tool.name === "image_generation"); +} + +describe("Native image generation consent", async () => { + // Native image inference is CAPI-only, independent of the chat-provider CI matrix. + const { createClient } = await createSdkTestContext({ + replayOnly: true, + backend: "capi", + }); + + it("requires SDK consent and CAPI access regardless of the CLI rollout flag", async () => { + for (const flag of [false, true]) { + for (const enabled of [undefined, false, true]) { + const service = new ImageService(); + await using client = createClient({ requestHandler: service }); + await using session = await client.createSession({ + onPermissionRequest: approveAll, + featureFlags: { IMAGE_GENERATION_TOOL: flag }, + imageGeneration: enabled === undefined ? undefined : { enabled }, + }); + expect(await imageToolOffered(session)).toBe(enabled === true); + expect(service.imageRequests).toEqual([]); + } + } + }); + + it("executes an opted-in CAPI image tool while the rollout flag is off", async () => { + const service = new ImageService(); + await using client = createClient({ requestHandler: service }); + await using session = await client.createSession({ + onPermissionRequest: approveAll, + featureFlags: { IMAGE_GENERATION_TOOL: false }, + imageGeneration: { enabled: true }, + }); + expect(await imageToolOffered(session)).toBe(true); + const result = await session.rpc.tools.execute({ + name: "image_generation", + arguments: { prompt: "A watercolor fox" }, + toolCallId: "sdk-image", + }); + expect(result).toMatchObject({ + resultType: "success", + contents: [{ type: "image", mimeType: "image/png", data: PNG }], + }); + expect(service.imageRequests).toEqual([ + { + model: IMAGE_MODEL, + prompt: "A watercolor fox", + n: 1, + output_format: "png", + stream: false, + }, + ]); + }); + + it("preserves resident consent but applies an explicit opt-out", async () => { + await using client = createClient({ requestHandler: new ImageService() }); + await using session = await client.createSession({ + onPermissionRequest: approveAll, + imageGeneration: { enabled: true }, + featureFlags: { IMAGE_GENERATION_TOOL: false }, + }); + expect(await imageToolOffered(session)).toBe(true); + await using resumed = await client.resumeSession(session.sessionId, { + onPermissionRequest: approveAll, + }); + expect(await imageToolOffered(resumed)).toBe(true); + await using disabled = await client.resumeSession(session.sessionId, { + onPermissionRequest: approveAll, + imageGeneration: { enabled: false }, + featureFlags: { IMAGE_GENERATION_TOOL: true }, + }); + expect(await imageToolOffered(disabled)).toBe(false); + }); + + it("requires renewed consent after a runtime restart", async () => { + // Separate stdio runtimes prove that consent is not restored from disk. + const firstService = new ImageService(); + await using first = createClient({ + requestHandler: firstService, + connection: RuntimeConnection.forStdio(), + }); + const original = await first.createSession({ + model: "claude-sonnet-5", + onPermissionRequest: approveAll, + imageGeneration: { enabled: true }, + }); + expect(await imageToolOffered(original)).toBe(true); + // A session with no user activity is deliberately not persisted. + const response = await original.sendAndWait({ + prompt: "Acknowledge this message without using tools.", + }); + expect(response?.data.content).toBe("IMAGE_CONSENT_TURN_COMPLETE"); + expect(firstService.chatToolNames.some((names) => names.includes("image_generation"))).toBe( + true + ); + const sessionId = original.sessionId; + await first.rpc.sessions.save({ sessionId }); + await original.disconnect(); + await first.stop(); + + const secondService = new ImageService(); + await using second = createClient({ + requestHandler: secondService, + connection: RuntimeConnection.forStdio(), + }); + await using resumed = await second.resumeSession(sessionId, { + onPermissionRequest: approveAll, + featureFlags: { IMAGE_GENERATION_TOOL: true }, + }); + expect(await imageToolOffered(resumed)).toBe(false); + const resumedResponse = await resumed.sendAndWait({ + prompt: "Acknowledge again without using tools.", + }); + expect(resumedResponse?.data.content).toBe("IMAGE_CONSENT_TURN_COMPLETE"); + expect(secondService.chatToolNames.length).toBeGreaterThan(0); + expect( + secondService.chatToolNames.every((names) => !names.includes("image_generation")) + ).toBe(true); + await using optedIn = await second.resumeSession(sessionId, { + onPermissionRequest: approveAll, + imageGeneration: { enabled: true }, + featureFlags: { IMAGE_GENERATION_TOOL: false }, + }); + expect(await imageToolOffered(optedIn)).toBe(true); + }); +}); + +describe("Native image generation without a CAPI image model", async () => { + const { createClient, openAiEndpoint } = await createSdkTestContext({ + replayOnly: true, + backend: "capi", + }); + + it("still requires an image model from CAPI", async () => { + const service = new ImageService(false); + await using client = createClient({ requestHandler: service }); + await using session = await client.createSession({ + onPermissionRequest: approveAll, + imageGeneration: { enabled: true }, + featureFlags: { IMAGE_GENERATION_TOOL: true }, + }); + expect(await imageToolOffered(session)).toBe(false); + expect(service.imageRequests).toEqual([]); + }); + + it.each(["anthropic-messages", "openai-responses", "openai-completions"] as const)( + "does not expose image generation for %s without CAPI image-model access", + async (backend) => { + const service = new ImageService(false); + await using client = createClient({ requestHandler: service }); + await using session = await client.createSession( + withTestBackend( + { + model: "claude-sonnet-5", + onPermissionRequest: approveAll, + imageGeneration: { enabled: true }, + featureFlags: { IMAGE_GENERATION_TOOL: true }, + }, + backend, + openAiEndpoint.url + ) + ); + expect(await imageToolOffered(session)).toBe(false); + expect(service.imageRequests).toEqual([]); + } + ); +}); diff --git a/nodejs/test/e2e/managed_permissions_limit_to.e2e.test.ts b/nodejs/test/e2e/managed_permissions_limit_to.e2e.test.ts index 1d02686699..1ec520429d 100644 --- a/nodejs/test/e2e/managed_permissions_limit_to.e2e.test.ts +++ b/nodejs/test/e2e/managed_permissions_limit_to.e2e.test.ts @@ -18,6 +18,123 @@ function closeServer(server: Server): Promise { describe("Managed permissions limitTo", async () => { const { copilotClient: client, workDir } = await createSdkTestContext(); + it("evaluates retained managed network policy without invoking the permission handler", async () => { + let permissionRequests = 0; + const session = await client.createSession({ + managedSettings: { + permissions: { + limitTo: ["Domain(*.example.com)"], + deny: ["Domain(blocked.example.com)"], + ask: ["Domain(prompt.example.com)"], + allow: ["Domain(allowed.example.com)"], + }, + }, + onPermissionRequest: () => { + permissionRequests++; + return { kind: "approve-once" }; + }, + }); + try { + const urls = [ + "https://allowed.example.com/guide", + "https://prompt.example.com/", + "https://blocked.example.com/", + "https://outside.test/", + "http://allowed.example.com:8080/redirect", + ]; + const snapshot = await session.rpc.managedSettings.get(); + expect(snapshot.permissionsContext).toBeDefined(); + const context = snapshot.permissionsContext!; + const operations = urls.map((url) => ({ kind: "url" as const, url })); + const result = await client.rpc.managedSettings.permissions.evaluate({ + context, + operations, + }); + expect(result).toEqual({ + failClosed: false, + results: operations.map((operation, index) => ({ + operation, + verdict: ["allow", "ask", "deny", "deny", "ask"][index], + })), + }); + expect(permissionRequests).toBe(0); + await expect( + client.rpc.managedSettings.permissions.evaluate({ + context, + operations: [{ kind: "url", url: "file:///secret" }], + }) + ).rejects.toMatchObject({ code: -32602 }); + expect( + await client.rpc.managedSettings.permissions.evaluate({ context, operations: [] }) + ).toEqual({ + failClosed: false, + results: [], + }); + expect(snapshot.settings).not.toHaveProperty("sandbox"); + } finally { + await session.disconnect(); + } + }); + + it("evaluates an in-memory permissions context without creating a session", async () => { + await client.start(); + const operations = [ + { kind: "url" as const, url: "https://api.example.com/guide" }, + { kind: "url" as const, url: "https://outside.test/" }, + ]; + const context = { + permissions: { limitTo: ["Domain(api.example.com)"] }, + failClosed: false, + }; + const result = await client.rpc.managedSettings.permissions.evaluate({ + context, + operations, + }); + expect(result).toEqual({ + failClosed: false, + results: [ + { operation: operations[0], verdict: "unmanaged" }, + { operation: operations[1], verdict: "deny" }, + ], + }); + expect( + await client.rpc.managedSettings.permissions.evaluate({ + context: { ...context, failClosed: true }, + operations, + }) + ).toEqual({ + failClosed: true, + results: operations.map((operation) => ({ operation, verdict: "deny" })), + }); + }); + + it("denies every URL for an empty managed boundary", async () => { + const session = await client.createSession({ + managedSettings: { permissions: { limitTo: [] } }, + }); + try { + const snapshot = await session.rpc.managedSettings.get(); + expect(snapshot.permissionsContext).toBeDefined(); + expect( + await client.rpc.managedSettings.permissions.evaluate({ + context: snapshot.permissionsContext!, + operations: [ + { kind: "url", url: "https://example.com/" }, + { kind: "url", url: "https://api.github.com/" }, + ], + }) + ).toEqual({ + failClosed: false, + results: [ + { operation: { kind: "url", url: "https://example.com/" }, verdict: "deny" }, + { operation: { kind: "url", url: "https://api.github.com/" }, verdict: "deny" }, + ], + }); + } finally { + await session.disconnect(); + } + }); + it.skipIf(process.platform !== "darwin")( "admits only the configured dynamic loopback destination", async () => { diff --git a/nodejs/test/e2e/mcp_and_agents.e2e.test.ts b/nodejs/test/e2e/mcp_and_agents.e2e.test.ts index 414e760ea2..443d5699f0 100644 --- a/nodejs/test/e2e/mcp_and_agents.e2e.test.ts +++ b/nodejs/test/e2e/mcp_and_agents.e2e.test.ts @@ -2,9 +2,11 @@ * Copyright (c) Microsoft Corporation. All rights reserved. *--------------------------------------------------------------------------------------------*/ -import { dirname, resolve } from "path"; +import { createHash } from "node:crypto"; +import { readFile } from "node:fs/promises"; +import { dirname, join, resolve } from "path"; import { fileURLToPath } from "url"; -import { describe, expect, it } from "vitest"; +import { describe, expect, it, onTestFinished } from "vitest"; import { z } from "zod"; import type { CopilotSession, @@ -12,7 +14,7 @@ import type { MCPStdioServerConfig, MCPServerConfig, } from "../../src/index.js"; -import { approveAll, defineTool } from "../../src/index.js"; +import { approveAll, defineTool, RuntimeConnection } from "../../src/index.js"; import { createSdkTestContext } from "./harness/sdkTestContext.js"; const __filename = fileURLToPath(import.meta.url); @@ -57,7 +59,12 @@ async function waitForMcpServerStatus( } describe("MCP Servers and Custom Agents", async () => { - const { copilotClient: client, openAiEndpoint } = await createSdkTestContext(); + const { + copilotClient: client, + openAiEndpoint, + createClient, + workDir, + } = await createSdkTestContext(); describe("MCP Servers", () => { it("should accept MCP server configuration on session create", async () => { @@ -133,6 +140,18 @@ describe("MCP Servers and Custom Agents", async () => { }); it("should pass literal env values to MCP server subprocess", async () => { + const telemetryPath = join(workDir, "mcp-telemetry.jsonl"); + const telemetryClient = createClient({ + connection: RuntimeConnection.forStdio(), + telemetry: { + exporterType: "file", + filePath: telemetryPath, + captureContent: true, + }, + }); + onTestFinished(async () => { + expect(await telemetryClient.stop()).toEqual([]); + }); const mcpServers: Record = { "env-echo": { type: "local", @@ -144,20 +163,50 @@ describe("MCP Servers and Custom Agents", async () => { } as MCPStdioServerConfig, }; - const session = await client.createSession({ + const session = await telemetryClient.createSession({ mcpServers, onPermissionRequest: approveAll, }); - - expect(session.sessionId).toBeDefined(); - await waitForMcpServerStatus(session, "env-echo"); - - const message = await session.sendAndWait({ - prompt: "Use the env-echo/get_env tool to read the TEST_SECRET environment variable. Reply with just the value, nothing else.", - }); - expect(message?.data.content).toContain("hunter2"); - - await session.disconnect(); + try { + expect(session.sessionId).toBeDefined(); + await waitForMcpServerStatus(session, "env-echo"); + + const message = await session.sendAndWait({ + prompt: "Use the env-echo/get_env tool to read the TEST_SECRET environment variable. Reply with just the value, nothing else.", + }); + expect(message?.data.content).toContain("hunter2"); + } finally { + await session.disconnect(); + } + expect(await telemetryClient.stop()).toEqual([]); + + const entries: Array<{ type: string; attributes?: Record }> = ( + await readFile(telemetryPath, "utf8") + ) + .split(/\r?\n/) + .filter((line) => line.trim()) + .map((line) => JSON.parse(line)); + const mcpSpans = entries.filter( + (entry) => + entry.type === "span" && + entry.attributes?.["gen_ai.operation.name"] === "execute_tool" && + typeof entry.attributes[ + "github.copilot.tool.parameters.mcp_server_name_hash" + ] === "string" + ); + expect(mcpSpans.length).toBeGreaterThan(0); + for (const span of mcpSpans) { + expect(span.attributes).toMatchObject({ + "gen_ai.tool.type": "function", + "github.copilot.tool.parameters.mcp_server_name_hash": createHash("sha256") + .update("env-echo") + .digest("hex"), + "github.copilot.tool.parameters.mcp_server_name": "env-echo", + "github.copilot.tool.parameters.mcp_tool_name": "get_env", + "github.copilot.mcp.server.transport": "stdio", + "github.copilot.mcp.server.source": "user", + }); + } }); }); diff --git a/nodejs/test/e2e/mcp_auth_entitlement.e2e.test.ts b/nodejs/test/e2e/mcp_auth_entitlement.e2e.test.ts new file mode 100644 index 0000000000..38f23738ec --- /dev/null +++ b/nodejs/test/e2e/mcp_auth_entitlement.e2e.test.ts @@ -0,0 +1,192 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +import { randomUUID } from "node:crypto"; +import { existsSync, readFileSync, rmSync, writeFileSync } from "node:fs"; +import { dirname, join, resolve } from "node:path"; +import { fileURLToPath } from "node:url"; +import { describe, it } from "vitest"; +import { approveAll } from "../../src/index.js"; +import { createSdkTestContext } from "./harness/sdkTestContext.js"; + +const testServer = resolve( + dirname(fileURLToPath(import.meta.url)), + "../../../test/harness/test-mcp-server.mjs" +); + +describe("MCP account entitlement changes", async () => { + const { copilotClient: client, workDir } = await createSdkTestContext(); + + it("restores policy-filtered servers and revokes them when static credentials change", async ({ + expect, + }) => { + const serverName = "account-entitlement-probe"; + const session = await client.createSession({ + onPermissionRequest: approveAll, + requestExtensions: false, + mcpServers: { + [serverName]: { + type: "local", + command: process.execPath, + args: [testServer], + workingDirectory: dirname(testServer), + tools: ["*"], + }, + }, + }); + try { + for (const enabled of [false, true, false, true]) { + const updated = await session.rpc.gitHubAuth.setCredentials({ + credentials: { + type: "token", + host: "https://github.com", + token: "synthetic-mcp-entitlement-token", + copilotUser: { is_mcp_enabled: enabled }, + }, + }); + expect(updated).toMatchObject({ success: true, copilotUserResolved: true }); + const snapshot = await session.rpc.mcp.list(); + expect(snapshot.host?.mcp3pEnabled).toBe(enabled); + expect(snapshot.host?.filteredServers.includes(serverName)).toBe(!enabled); + expect(await session.rpc.mcp.isServerRunning({ serverName })).toEqual({ + running: enabled, + }); + if (enabled) { + expect( + (await session.rpc.mcp.listTools({ serverName })).tools.map( + (tool) => tool.name + ) + ).toEqual(["get_env"]); + } + } + } finally { + await session.disconnect(); + } + }); + + it.for(["connected", "failed"] as const)( + "starts independent restored servers concurrently before reporting auth success with a %s peer", + { timeout: 120_000 }, + async (peerStatus, { expect, onTestFinished }) => { + const fixtureId = randomUUID(); + const servers = [0, 1, 2].map((index) => ({ + name: `parallel-entitlement-${index}`, + marker: join(workDir, `${fixtureId}-${index}.marker`), + gate: join(workDir, `${fixtureId}-${index}.gate`), + })); + const session = await client.createSession({ + onPermissionRequest: approveAll, + requestExtensions: false, + mcpServers: Object.fromEntries( + servers.map((server, index) => [ + server.name, + { + type: "local", + command: process.execPath, + args: [ + testServer, + "--startup-marker", + server.marker, + "--startup-gate", + server.gate, + ...(index === 1 && peerStatus === "failed" + ? ["--fail-startup"] + : []), + ], + workingDirectory: dirname(testServer), + tools: ["*"], + }, + ]) + ), + }); + const setCredentials = (enabled: boolean) => + session.rpc.gitHubAuth.setCredentials({ + credentials: { + type: "token", + host: "https://github.com", + token: "synthetic-parallel-mcp-entitlement-token", + copilotUser: { is_mcp_enabled: enabled }, + }, + }); + let restoring: ReturnType | undefined; + onTestFinished(async () => { + for (const server of servers) { + writeFileSync(server.gate, "release"); + } + if (restoring) { + await Promise.allSettled([restoring]); + } + await session.disconnect(); + }); + + // Cover deferred configs first, then configs removed from live servers. + for (const starts of [1, 2]) { + expect(await setCredentials(false)).toMatchObject({ success: true }); + const denied = await session.rpc.mcp.list(); + for (const server of servers) { + expect(denied.host?.filteredServers).toContain(server.name); + expect( + await session.rpc.mcp.isServerRunning({ serverName: server.name }) + ).toEqual({ + running: false, + }); + rmSync(server.gate, { force: true }); + } + + let restorationCompleted = false; + restoring = setCredentials(true).then((result) => { + restorationCompleted = true; + return result; + }); + await expect + .poll( + () => + servers.map((server) => + existsSync(server.marker) + ? readFileSync(server.marker, "utf8") + .split("\n") + .filter(Boolean).length + : 0 + ), + { timeout: 30_000 } + ) + .toEqual([starts, starts, starts]); + expect(restorationCompleted).toBe(false); + for (const server of servers) { + writeFileSync(server.gate, "release"); + } + expect(await restoring).toMatchObject({ success: true, copilotUserResolved: true }); + + const restored = await session.rpc.mcp.list(); + expect(restored.host?.mcp3pEnabled).toBe(true); + for (const [index, server] of servers.entries()) { + const connected = index !== 1 || peerStatus === "connected"; + expect(restored.host?.filteredServers).not.toContain(server.name); + expect( + await session.rpc.mcp.isServerRunning({ serverName: server.name }) + ).toEqual({ + running: connected, + }); + if (connected) { + expect( + ( + await session.rpc.mcp.listTools({ serverName: server.name }) + ).tools.map((tool) => tool.name) + ).toEqual(["get_env"]); + } else { + expect( + restored.servers.find((entry) => entry.name === server.name)?.status + ).toBe("failed"); + } + } + } + expect(await setCredentials(false)).toMatchObject({ success: true }); + for (const server of servers) { + expect(await session.rpc.mcp.isServerRunning({ serverName: server.name })).toEqual({ + running: false, + }); + } + } + ); +}); diff --git a/nodejs/test/e2e/mcp_instruction_recovery.e2e.test.ts b/nodejs/test/e2e/mcp_instruction_recovery.e2e.test.ts index 32fa3c6266..502e250452 100644 --- a/nodejs/test/e2e/mcp_instruction_recovery.e2e.test.ts +++ b/nodejs/test/e2e/mcp_instruction_recovery.e2e.test.ts @@ -127,6 +127,15 @@ describe("MCP catalog recovery", async () => { ), "the first stale call must be refused before dispatch" ).toBe(true); + expect( + events.some( + (event) => + event.type === "tool.execution_complete" && + event.data.success && + event.data.result?.content.includes("CATALOG_RECORD") + ), + "the reconnected server must execute the offered tool" + ).toBe(true); expect(changing.calls, "only the recovered call may reach the MCP server").toBe(1); } else if (reconnectInHook) { const starts = events diff --git a/nodejs/test/e2e/mcp_remote_session_closure.e2e.test.ts b/nodejs/test/e2e/mcp_remote_session_closure.e2e.test.ts index 4b6ebf174a..1878998d25 100644 --- a/nodejs/test/e2e/mcp_remote_session_closure.e2e.test.ts +++ b/nodejs/test/e2e/mcp_remote_session_closure.e2e.test.ts @@ -6,7 +6,7 @@ import { spawn } from "node:child_process"; import { dirname, resolve } from "node:path"; import { createInterface } from "node:readline"; import { fileURLToPath } from "node:url"; -import { describe, expect, it, onTestFinished } from "vitest"; +import { describe, expect, it, onTestFailed, onTestFinished } from "vitest"; import type { CopilotSession, MCPServerConfig } from "../../src/index.js"; import { approveAll } from "../../src/index.js"; import { createSdkTestContext } from "./harness/sdkTestContext.js"; @@ -29,10 +29,19 @@ interface McpServerStats { expiredRequestLog: { method: string; elapsedMs: number }[]; protocolVersions: string[]; expiredSessionProbes: number; + recentRequests: Array<{ + elapsedMs: number; + httpMethod: string; + sessionAttached: boolean; + expiredSession: boolean; + rpcMethods: string[]; + status: number | null; + }>; } describe("MCP remote session closure", async () => { const { copilotClient: client } = await createSdkTestContext({ + logLevel: "debug", copilotClientOptions: { env: { COPILOT_MCP_APPS: "true", @@ -46,6 +55,10 @@ describe("MCP remote session closure", async () => { { timeout: 180_000 }, async () => { const remoteServer = await startSessionExpiryMcpServer(); + let lastStats = await remoteServer.stats(); + onTestFailed(() => { + console.error("Last MCP session-expiry observation:", JSON.stringify(lastStats)); + }); const serverName = "remote-server"; const session = await client.createSession({ onPermissionRequest: approveAll, @@ -73,8 +86,8 @@ describe("MCP remote session closure", async () => { await waitForCondition( async () => { - const stats = await remoteServer.stats(); - return stats.expiredSessionProbes >= 1 || stats.initializations >= 2; + lastStats = await remoteServer.stats(); + return lastStats.expiredSessionProbes >= 1 || lastStats.initializations >= 2; }, { timeoutMs: 120_000, @@ -83,7 +96,6 @@ describe("MCP remote session closure", async () => { "remote MCP session expiry was neither probed nor re-initialized", } ); - let lastStats: McpServerStats | undefined; try { await waitForCondition( async () => { @@ -124,20 +136,20 @@ async function waitForMcpServerStatus( serverName: string, expectedStatus: string ): Promise { - let lastStatus = ""; - await waitForCondition( - async () => { - const result = await session.rpc.mcp.list(); - const server = result.servers.find((entry) => entry.name === serverName); - lastStatus = server?.status ?? ""; - return server?.status === expectedStatus; - }, - { - timeoutMs: 120_000, - intervalMs: 200, - timeoutMessage: `${serverName} did not reach ${expectedStatus}; last status was ${lastStatus}`, - } - ); + await expect + .poll( + async () => { + const result = await session.rpc.mcp.list(); + const server = result.servers.find((entry) => entry.name === serverName); + return server?.status ?? ""; + }, + { + timeout: 120_000, + interval: 200, + message: `${serverName} did not reach ${expectedStatus}`, + } + ) + .toBe(expectedStatus); } async function disconnectSession(session: CopilotSession): Promise { @@ -160,6 +172,9 @@ async function startSessionExpiryMcpServer(): Promise<{ const stderr: string[] = []; child.stderr.on("data", (chunk) => stderr.push(String(chunk))); + onTestFailed(() => { + console.error("Session-expiry server stderr:", stderr.join("").slice(-16_384)); + }); const url = await new Promise((resolvePromise, reject) => { const rl = createInterface({ input: child.stdout }); diff --git a/nodejs/test/e2e/mcp_scoped_sign_in.e2e.test.ts b/nodejs/test/e2e/mcp_scoped_sign_in.e2e.test.ts index 4139e17bfe..d067c2bcf2 100644 --- a/nodejs/test/e2e/mcp_scoped_sign_in.e2e.test.ts +++ b/nodejs/test/e2e/mcp_scoped_sign_in.e2e.test.ts @@ -104,6 +104,7 @@ describe("MCP sign-in for one server", async () => { const events: SessionEvent[] = []; const session = await client.createSession({ onPermissionRequest: approveAll, + onEvent: (event) => events.push(event), mcpServers: { alpha: { type: "local", @@ -122,16 +123,42 @@ describe("MCP sign-in for one server", async () => { }, }); onTestFinished(() => disconnectSession(session)); - session.on((event) => events.push(event)); - await waitForMcpServerStatus(session, "alpha", "connected"); await waitForMcpServerStatus(session, "bravo", "needs-auth"); await waitForMcpServerStatus(session, "delta", "needs-auth"); + // Live status can precede delivery of its initial notification. + await waitForCondition( + () => + events.some( + (event) => + event.type === "session.mcp_server_status_changed" && + event.data.serverName === "alpha" && + event.data.status === "connected" + ), + { + timeoutMs: STEP_TIMEOUT_MS, + timeoutMessage: "alpha did not deliver its initial connected status", + } + ); const deltaLogin = await session.rpc.mcp.oauth.login({ serverName: "delta", redirectUri: REDIRECT_URI, }); expect(deltaLogin.authorizationId).toBeDefined(); + // Live status can precede its asynchronous event, so observe startup before measuring peer changes. + await waitForCondition( + () => + events.some( + (event) => + event.type === "session.mcp_server_status_changed" && + event.data.serverName === "alpha" && + event.data.status === "connected" + ), + { + timeoutMs: STEP_TIMEOUT_MS, + timeoutMessage: "alpha's initial connected event did not arrive", + } + ); const baseline = events.length; return { diff --git a/nodejs/test/e2e/memory_tool_overrides.e2e.test.ts b/nodejs/test/e2e/memory_tool_overrides.e2e.test.ts new file mode 100644 index 0000000000..9bec5aad92 --- /dev/null +++ b/nodejs/test/e2e/memory_tool_overrides.e2e.test.ts @@ -0,0 +1,102 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +import { describe, expect, it } from "vitest"; +import { z } from "zod"; +import { defineTool } from "../../src/index.js"; +import type { PermissionRequest, SessionConfig, ToolInvocation } from "../../src/index.js"; +import { createSdkTestContext } from "./harness/sdkTestContext.js"; + +describe("Memory tool overrides", async () => { + const { copilotClient: client } = await createSdkTestContext({ replayOnly: true }); + + it.each(["store_memory", "vote_memory"])( + "routes %s to the host with custom-tool permissions on create and resume", + async (name) => { + const invocations: ToolInvocation[] = []; + const permissions: PermissionRequest[] = []; + let approve = true; + const config = { + memory: { enabled: false }, + onPermissionRequest: (request) => { + permissions.push(request); + return approve ? { kind: "approve-once" } : { kind: "reject" }; + }, + tools: [ + defineTool(name, { + description: "Host-owned memory", + parameters: z.object({ fact: z.string() }), + overridesBuiltInTool: true, + defer: "never", + handler: (_args, invocation) => { + invocations.push(invocation); + return "HOST_MEMORY_HANDLED"; + }, + }), + ], + } satisfies SessionConfig; + let session = await client.createSession(config); + try { + for (const phase of ["create", "resume"]) { + if (phase === "resume") { + const sessionId = session.sessionId; + await session.disconnect(); + session = await client.resumeSession(sessionId, config); + } + await session.rpc.tools.initializeAndValidate(); + const { tools } = await session.rpc.tools.getCurrentMetadata(); + expect(tools.filter((tool) => tool.name === name)).toEqual([ + expect.objectContaining({ + description: "Host-owned memory", + input_schema: expect.objectContaining({ required: ["fact"] }), + }), + ]); + const args = { fact: `Host-owned memory after ${phase}.` }; + const toolCallId = `memory-override-${phase}`; + const result = await session.rpc.tools.execute({ + name, + arguments: args, + toolCallId, + }); + + expect(result, `${name} after ${phase}`).toMatchObject({ + resultType: "success", + textResultForLlm: "HOST_MEMORY_HANDLED", + }); + expect(invocations.at(-1)).toMatchObject({ + toolName: name, + toolCallId, + arguments: args, + }); + expect(permissions.at(-1)).toMatchObject({ + kind: "custom-tool", + toolName: name, + toolCallId, + args, + }); + } + expect(invocations).toHaveLength(2); + expect(permissions).toHaveLength(2); + + approve = false; + const args = { fact: "A rejected memory." }; + const rejected = await session.rpc.tools.execute({ + name, + arguments: args, + toolCallId: "memory-override-rejected", + }); + expect(rejected.resultType).toBe("rejected"); + expect(invocations).toHaveLength(2); + expect(permissions).toHaveLength(3); + expect(permissions.at(-1)).toMatchObject({ + kind: "custom-tool", + toolName: name, + args, + }); + } finally { + await session.disconnect(); + } + } + ); +}); diff --git a/nodejs/test/e2e/monthly_usage.e2e.test.ts b/nodejs/test/e2e/monthly_usage.e2e.test.ts new file mode 100644 index 0000000000..b0704efa33 --- /dev/null +++ b/nodejs/test/e2e/monthly_usage.e2e.test.ts @@ -0,0 +1,229 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +import { createServer } from "node:http"; +import { mkdir, writeFile } from "node:fs/promises"; +import { join } from "node:path"; +import { expect, it, onTestFinished } from "vitest"; +import { approveAll, CopilotClient, RuntimeConnection } from "../../src/index.js"; +import { createSdkTestContext } from "./harness/sdkTestContext.js"; + +const { env, workDir, openAiEndpoint } = await createSdkTestContext({ + useStdio: true, + replayOnly: true, + copilotClientOptions: { useLoggedInUser: false, gitHubToken: undefined }, +}); + +it("preserves monthly Microsoft 365 usage without treating it as session spend", async ({ + signal, +}) => { + const profile = join(workDir, "monthly-usage-profile"); + await mkdir(profile); + let usage: Record = {}; + let quotaStatus = 200; + let invalidQuota = false; + const requests: string[] = []; + const server = createServer((request, response) => { + requests.push(`${request.method} ${request.url}`); + expect(request.headers.authorization).toBe("Bearer monthly-usage-token"); + response.setHeader("Content-Type", "application/json"); + if (request.url === "/models") { + response.end( + JSON.stringify({ + data: [{ id: "gpt-5-mini", name: "GPT-5 mini", model_picker_enabled: true }], + }) + ); + } else if (request.url === "/quota") { + if (quotaStatus !== 200) { + response + .writeHead(quotaStatus) + .end(JSON.stringify({ error: { code: "quota_read_failed" } })); + return; + } + if (invalidQuota) { + response.end("not JSON"); + return; + } + response.end( + JSON.stringify({ + quotaSnapshots: { + enterprise: { + source: "enterprise", + service: "GitHubCopilotApp", + accessState: "allowed", + capacityState: "available", + hasQuota: true, + entitledQuantity: 100, + availableQuantity: 13, + ...usage, + }, + }, + }) + ); + } else { + response.writeHead(404).end(); + } + }); + let client: CopilotClient | undefined; + const listening = new Promise((resolve, reject) => { + server.once("error", reject); + server.listen(0, "127.0.0.1", resolve); + }); + onTestFinished(async () => { + try { + await client?.stop(); + } finally { + await listening; + server.closeAllConnections(); + await new Promise((resolve, reject) => + server.close((error) => (error ? reject(error) : resolve())) + ); + } + }); + await listening; + signal.throwIfAborted(); + const address = server.address(); + if (!address || typeof address === "string") throw new Error("Expected a TCP quota fixture"); + const host = `http://127.0.0.1:${address.port}`; + const baseId = "monthly-usage-base"; + const emuToken = "ghu_monthly_usage_emu"; + const account = { host, login: "monthly-user", kind: "loki", derivedFrom: baseId }; + await openAiEndpoint.setCopilotUserByToken(emuToken, { + login: "monthly_emu", + is_staff: true, + copilot_plan: "business", + endpoints: { api: env.COPILOT_API_URL, telemetry: "https://localhost:1/telemetry" }, + }); + await writeFile(join(profile, "settings.json"), JSON.stringify({ storeTokenPlaintext: true })); + await writeFile( + join(profile, "config.json"), + JSON.stringify({ + loggedInUsers: [ + account, + { + host: "https://github.com", + login: "monthly_emu", + kind: "entraEmu", + derivedFrom: baseId, + }, + ], + lastLoggedInUser: account, + authTokens: { + [`entra:${baseId}:entra`]: { + token: JSON.stringify({ hostOwned: { account_id: baseId } }), + }, + [`loki:${baseId}:loki`]: { + token: "monthly-usage-token", + expiresAt: "2099-01-01T00:00:00Z", + accountType: "entra", + }, + "https://github.com:monthly_emu:entra": { + token: emuToken, + expiresAt: "2099-01-01T00:00:00Z", + accountType: "entra", + }, + }, + }) + ); + client = new CopilotClient({ + workingDirectory: workDir, + useLoggedInUser: true, + connection: RuntimeConnection.forStdio({ path: process.env.COPILOT_CLI_PATH }), + env: { + ...env, + COPILOT_HOME: profile, + COPILOT_DISABLE_KEYTAR: "1", + GH_TOKEN: "", + GITHUB_TOKEN: "", + COPILOT_GITHUB_TOKEN: "", + COPILOT_SDK_AUTH_TOKEN: "", + GITHUB_COPILOT_API_TOKEN: "", + COPILOT_HMAC_KEY: "", + CAPI_HMAC_KEY: "", + }, + }); + await using session = await client.createSession({ + configDirectory: profile, + onPermissionRequest: approveAll, + }); + const before = await session.rpc.usage.getMetrics(); + if (process.platform === "linux") { + expect(await session.rpc.gitHubAuth.getStatus()).toMatchObject({ isAuthenticated: false }); + await expect(session.rpc.quota.refresh()).rejects.toThrow("Not authenticated"); + expect((await session.rpc.quota.get()).providerQuotas).toBeUndefined(); + expect((await session.rpc.usage.getMetrics()).totalNanoAiu).toBe(before.totalNanoAiu); + expect(requests).toEqual([]); + return; + } + const initial = await session.rpc.quota.refresh(); + expect( + initial.providerQuotas?.find((state) => state.provider.kind === "loki")?.monthlyUsage + ).toBeUndefined(); + + for (const consumedQuantity of [0, 125.375]) { + usage = { + usageState: "available", + usageUnit: "ai_credits", + usageScope: "user", + consumedQuantity, + usageQueriedAt: "2026-10-08T12:00:00Z", + usageCycleStart: "2026-10-01T00:00:00Z", + usageResetOn: "2026-11-01T00:00:00Z", + }; + const quota = await session.rpc.quota.refresh(); + const state = quota.providerQuotas?.find((state) => state.provider.kind === "loki"); + expect(state).toMatchObject({ + accessState: "allowed", + capacityState: "available", + availableQuantity: 13, + quantityKind: "advisory_balance", + monthlyUsage: { + state: "available", + unit: "ai_credits", + scope: "user", + consumedQuantity, + queriedAt: "2026-10-08T12:00:00Z", + cycleStart: "2026-10-01T00:00:00Z", + resetOn: "2026-11-01T00:00:00Z", + }, + }); + expect((await session.rpc.quota.get()).providerQuotas).toEqual(quota.providerQuotas); + expect(await session.rpc.quota.takeWarnings()).toEqual([]); + } + for (const usageState of ["no_policy", "unavailable"]) { + usage = { usageState, usageUnit: "ai_credits", usageScope: "user" }; + const quota = await session.rpc.quota.refresh(); + expect( + quota.providerQuotas?.find((state) => state.provider.kind === "loki")?.monthlyUsage + ).toEqual({ + state: usageState, + unit: "ai_credits", + scope: "user", + }); + } + for (const status of [401, 403, 404, 429, 503]) { + quotaStatus = status; + const quota = await session.rpc.quota.refresh(); + const state = quota.providerQuotas?.find((state) => state.provider.kind === "loki"); + expect(state).toMatchObject({ + httpStatus: status, + acquisitionStatus: status === 404 ? "unavailable" : "failed", + }); + expect(state?.monthlyUsage).toBeUndefined(); + } + quotaStatus = 200; + invalidQuota = true; + const invalid = await session.rpc.quota.refresh(); + const invalidState = invalid.providerQuotas?.find((state) => state.provider.kind === "loki"); + expect(invalidState).toMatchObject({ + acquisitionStatus: "failed", + acquisitionError: "invalid_json", + }); + expect(invalidState?.monthlyUsage).toBeUndefined(); + expect((await session.rpc.usage.getMetrics()).totalNanoAiu).toBe(before.totalNanoAiu); + expect(requests.length).toBeGreaterThan(0); + expect(requests.every((request) => request === "GET /models" || request === "GET /quota")).toBe( + true + ); +}); diff --git a/nodejs/test/e2e/multi_provider_registry.e2e.test.ts b/nodejs/test/e2e/multi_provider_registry.e2e.test.ts index cd0eb53169..49b4a3618c 100644 --- a/nodejs/test/e2e/multi_provider_registry.e2e.test.ts +++ b/nodejs/test/e2e/multi_provider_registry.e2e.test.ts @@ -137,7 +137,8 @@ describe("Multi-provider BYOK registry", async () => { async function assertRouting( selectionId: string, expectedWireModel: string, - expectedProviderHeader: string + expectedProviderHeader: string, + expectedOutputCap?: number ): Promise { // Two OpenAI-compatible providers, both pointed at the replay proxy so // their /chat/completions traffic is captured. They are distinguished on @@ -162,7 +163,12 @@ describe("Multi-provider BYOK registry", async () => { }, ]; const models: ProviderModelConfig[] = [ - { id: "sonnet", provider: "alpha", wireModel: "byok-gpt-4o" }, + { + id: "sonnet", + provider: "alpha", + wireModel: "byok-gpt-4o", + maxOutputTokens: 16_384, + }, { id: "haiku", provider: "alpha", wireModel: "byok-gpt-4o-mini" }, { id: "turbo", provider: "delta", wireModel: "byok-gpt-4-turbo" }, ]; @@ -175,7 +181,8 @@ describe("Multi-provider BYOK registry", async () => { }); try { - await session.sendAndWait({ prompt: "What is 5+5?" }); + const response = await session.sendAndWait({ prompt: "What is 5+5?" }); + expect(response?.data.content).toContain("10"); const exchanges = await waitForExchanges(); expect(exchanges.length).toBe(1); const exchange = exchanges[0]; @@ -183,6 +190,9 @@ describe("Multi-provider BYOK registry", async () => { // The wire model sent to the provider is the selected model's // wireModel, not its provider-qualified selection id. expect(exchange.request.model).toBe(expectedWireModel); + expect(exchange.request.max_tokens).toBe(expectedOutputCap); + expect(exchange.request.max_completion_tokens).toBeUndefined(); + expect("max_output_tokens" in exchange.request).toBe(false); // The request carried the owning provider's custom header, proving // the turn was dispatched against the correct provider connection. @@ -200,7 +210,7 @@ describe("Multi-provider BYOK registry", async () => { } it("should route alpha sonnet turn to its provider and wire model", async () => { - await assertRouting("alpha/sonnet", "byok-gpt-4o", "alpha"); + await assertRouting("alpha/sonnet", "byok-gpt-4o", "alpha", 16_384); }); it("should route alpha haiku turn to its provider and wire model", async () => { diff --git a/nodejs/test/e2e/objective_lifecycle.e2e.test.ts b/nodejs/test/e2e/objective_lifecycle.e2e.test.ts new file mode 100644 index 0000000000..7a87209d24 --- /dev/null +++ b/nodejs/test/e2e/objective_lifecycle.e2e.test.ts @@ -0,0 +1,37 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +import { expect, it } from "vitest"; +import { approveAll } from "../../src/index.js"; +import { createSdkTestContext } from "./harness/sdkTestContext.js"; + +const { copilotClient: client, workDir } = await createSdkTestContext(); + +it("reduces goal completion before idle and pauses a goal when leaving autopilot", async () => { + const config = { + onPermissionRequest: approveAll, + workingDirectory: workDir, + remoteSession: "off" as const, + }; + const completed = await client.createSession(config); + const goal = await completed.rpc.commands.invoke({ + name: "goal", + input: "Call task_complete immediately with summary 'The release is ready.' Do not call other tools or perform any work.", + }); + if (goal.kind !== "agent-prompt") throw new Error(`Unexpected goal result: ${goal.kind}`); + await completed.sendAndWait({ prompt: goal.prompt, agentMode: "autopilot" }); + expect((await completed.rpc.autopilotObjective.getState()).state).toMatchObject({ + status: "completed", + turnCount: 1, + }); + + const paused = await client.createSession(config); + await paused.rpc.commands.invoke({ name: "goal", input: "Prepare another release" }); + await paused.rpc.mode.set({ mode: "autopilot" }); + await paused.rpc.mode.set({ mode: "interactive" }); + expect((await paused.rpc.autopilotObjective.getState()).state).toMatchObject({ + status: "paused", + pauseReason: "autopilot_off", + }); +}); diff --git a/nodejs/test/e2e/pending_work_resume.e2e.test.ts b/nodejs/test/e2e/pending_work_resume.e2e.test.ts index b4bf6fa352..ebe0889f76 100644 --- a/nodejs/test/e2e/pending_work_resume.e2e.test.ts +++ b/nodejs/test/e2e/pending_work_resume.e2e.test.ts @@ -150,7 +150,6 @@ describe("Pending work resume", async () => { const server = createClient({ workingDirectory: workDir, env, - gitHubToken: DEFAULT_GITHUB_TOKEN, connection: RuntimeConnection.forTcp({ path: process.env.COPILOT_CLI_PATH, connectionToken: SHARED_TOKEN, @@ -308,6 +307,122 @@ describe("Pending work resume", async () => { } ); + it( + "should continue an approved permission after abandoning an earlier pending permission", + { timeout: TEST_TIMEOUT_MS }, + async () => { + const client = createClient(); + onTestFinished(async () => { + await client.stop(); + }); + const executions: string[] = []; + const config = { + tools: [ + defineTool("resume_permission_tool", { + description: "Transforms a value after permission is granted", + parameters: z.object({ value: z.string() }), + handler: ({ value }) => { + executions.push(value); + return `PERMISSION_RESUMED_${value.toUpperCase()}`; + }, + }), + ], + onPermissionRequest: (): PermissionRequestResult => ({ kind: "no-result" }), + infiniteSessions: { enabled: false }, + }; + const original = await client.createSession(config); + expect((await original.rpc.permissions.setRequired({ required: true })).success).toBe( + true + ); + const abandonedPermission = waitForPermissionRequest(original); + await original.send({ + prompt: "Use resume_permission_tool with value 'alpha', then reply with the result.", + }); + const abandoned = await abandonedPermission; + await original.disconnect(); + + const next = await client.resumeSession(original.sessionId, { + ...config, + continuePendingWork: false, + }); + expect((await next.rpc.permissions.pendingRequests()).items).toEqual([]); + expect( + ( + await next.rpc.permissions.handlePendingPermissionRequest({ + requestId: abandoned.data.requestId, + result: { kind: "approve-once" }, + }) + ).success + ).toBe(false); + expect((await next.rpc.permissions.setRequired({ required: true })).success).toBe(true); + const nextPermission = waitForPermissionRequest(next); + await next.send({ + prompt: "Use resume_permission_tool with value 'beta', then reply with the result.", + }); + const pending = await nextPermission; + await next.disconnect(); + + const resumed = await client.resumeSession(original.sessionId, { + ...config, + continuePendingWork: true, + }); + expect((await resumed.rpc.permissions.setRequired({ required: true })).success).toBe( + true + ); + expect( + (await resumed.rpc.permissions.pendingRequests()).items.map( + (request) => request.requestId + ) + ).toEqual([pending.data.requestId]); + const resumeEvents = (await resumed.getEvents()).filter( + (event) => event.type === "session.resume" + ); + expect( + resumeEvents.map((event) => ({ + continuePendingWork: event.data.continuePendingWork, + sessionWasActive: event.data.sessionWasActive, + })) + ).toEqual([ + { continuePendingWork: false, sessionWasActive: false }, + { continuePendingWork: true, sessionWasActive: false }, + ]); + + const idle = deferred(); + const replies: string[] = []; + const unsubscribe = resumed.on((event) => { + if (event.agentId) return; + if (event.type === "assistant.message") { + replies.push(event.data.content); + } else if (event.type === "session.idle") { + idle.resolve(); + } else if (event.type === "session.error") { + idle.reject(new Error(event.data.message)); + } + }); + onTestFinished(unsubscribe); + expect( + ( + await resumed.rpc.permissions.handlePendingPermissionRequest({ + requestId: pending.data.requestId, + result: { kind: "approve-once" }, + }) + ).success + ).toBe(true); + await waitWithTimeout(idle.promise, PENDING_WORK_TIMEOUT_MS, "approved turn idle"); + expect(replies.join("\n")).toContain("PERMISSION_RESUMED_BETA"); + expect(executions).toEqual(["beta"]); + expect( + (await resumed.getEvents()).filter( + (event) => + event.type === "tool.execution_complete" && + event.data.toolCallId === pending.data.permissionRequest.toolCallId + ) + ).toMatchObject([{ data: { success: true } }]); + expect((await resumed.rpc.permissions.pendingRequests()).items).toEqual([]); + await resumed.disconnect(); + } + ); + it( "should continue pending external tool request after resume", { timeout: TEST_TIMEOUT_MS }, @@ -602,18 +717,30 @@ describe("Pending work resume", async () => { .filter(Boolean) .map((line) => JSON.parse(line) as SessionEvent); }; + // Live SDK callbacks do not prove that both parallel boundaries reached the journal. await waitForCondition( - () => - readPersistedEvents().some( - (event) => - event.type === "tool.execution_complete" && - event.data.toolCallId === toolA.data.toolCallId && - event.data.success && - event.data.result?.content === "PARALLEL_A_ALPHA" - ), + () => { + const persistedEvents = readPersistedEvents(); + return ( + persistedEvents.some( + (event) => + event.type === "tool.execution_complete" && + event.data.toolCallId === toolA.data.toolCallId && + event.data.success && + event.data.result?.content === "PARALLEL_A_ALPHA" + ) && + persistedEvents.some( + (event) => + event.type === "external_tool.requested" && + event.data.requestId === toolB.data.requestId && + event.data.toolCallId === toolB.data.toolCallId + ) + ); + }, { timeoutMs: PENDING_WORK_TIMEOUT_MS, - timeoutMessage: "Tool A's result was not flushed to events.jsonl before B", + timeoutMessage: + "Tool A's result and tool B's pending request were not both flushed to events.jsonl before shutdown", } ); expect( diff --git a/nodejs/test/e2e/plugin_git_auth.e2e.test.ts b/nodejs/test/e2e/plugin_git_auth.e2e.test.ts new file mode 100644 index 0000000000..be3c6f3005 --- /dev/null +++ b/nodejs/test/e2e/plugin_git_auth.e2e.test.ts @@ -0,0 +1,236 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +import { execFileSync, spawn, type ChildProcess } from "node:child_process"; +import { access, mkdir, readFile, readdir, writeFile } from "node:fs/promises"; +import { createServer } from "node:https"; +import { join } from "node:path"; +import { describe, it } from "vitest"; +import { approveAll, CopilotClient, RuntimeConnection } from "../../src/index.js"; +import { createIdentityForHost, generateCA } from "../../../test/harness/certUtils.js"; +import { createSdkTestContext, DEFAULT_GITHUB_TOKEN } from "./harness/sdkTestContext.js"; + +// Rust covers Git retry decisions; this exercises SDK-owned credentials across RPC. +describe("Plugin Git authentication fallback", async () => { + const { workDir, env } = await createSdkTestContext({ + copilotClientOptions: { gitHubToken: DEFAULT_GITHUB_TOKEN }, + }); + + it( + "clones and refreshes a private marketplace with the signed-in token", + { timeout: 90_000 }, + async (ctx) => { + const home = join(workDir, "auth-home"); + const source = join(workDir, "source"); + await mkdir(home); + await mkdir(source); + const configPath = join(home, "gitconfig"); + const tracePath = join(home, "git-trace.jsonl"); + await writeFile(configPath, ""); + const gitEnv = { + ...env, + GIT_CONFIG_GLOBAL: configPath, + GIT_CONFIG_NOSYSTEM: "1", + GIT_CONFIG_COUNT: "0", + GIT_TERMINAL_PROMPT: "0", + GIT_TRACE_REDACT: "0", + GIT_TRACE2_EVENT: tracePath, + GIT_TRACE2_ENV_VARS: "GIT_CONFIG_VALUE_1,GIT_CONFIG_VALUE_2,GIT_CONFIG_VALUE_3", + GIT_TRACE2_CONFIG_PARAMS: "http.*.extraheader", + }; + const git = (...args: string[]) => + execFileSync("git", args, { cwd: source, env: gitEnv, windowsHide: true }); + await access( + join( + git("--exec-path").toString().trim(), + process.platform === "win32" ? "git-http-backend.exe" : "git-http-backend" + ) + ); + git("init", "--quiet"); + git("config", "user.name", "SDK Test"); + git("config", "user.email", "sdk-test@example.com"); + await writeFile( + join(source, "marketplace.json"), + JSON.stringify({ + name: "private-auth-marketplace", + owner: { name: "SDK Test" }, + plugins: [], + }) + ); + git("add", "marketplace.json"); + git("commit", "--quiet", "-m", "fixture"); + git("clone", "--quiet", "--bare", ".", "../repo.git"); + + const ca = generateCA(); + const identity = createIdentityForHost("127.0.0.1", ca); + const caPath = join(home, "ca.pem"); + await writeFile(caPath, ca.certPem); + const authorization = `Basic ${Buffer.from(`x-access-token:${DEFAULT_GITHUB_TOKEN}`).toString("base64")}`; + const observed: Array = []; + const failures: Error[] = []; + const backends = new Set(); + const server = createServer( + { key: identity.keyPem, cert: identity.certPem }, + (request, response) => { + observed.push(request.headers.authorization); + if (request.headers.authorization !== authorization) { + response.writeHead(401, { + "WWW-Authenticate": 'Basic realm="marketplace"', + }); + response.end(); + return; + } + const target = new URL(request.url!, "https://127.0.0.1"); + const backend = spawn("git", ["http-backend"], { + cwd: workDir, + env: { + ...gitEnv, + GIT_PROJECT_ROOT: workDir, + GIT_HTTP_EXPORT_ALL: "1", + PATH_INFO: target.pathname, + QUERY_STRING: target.search.slice(1), + REQUEST_METHOD: request.method, + CONTENT_TYPE: request.headers["content-type"], + REMOTE_USER: "sdk-test", + }, + windowsHide: true, + stdio: ["pipe", "pipe", "pipe"], + }); + backends.add(backend); + const chunks: Buffer[] = []; + const stderr: Buffer[] = []; + backend.stdout!.on("data", (chunk: Buffer) => chunks.push(chunk)); + backend.stderr!.on("data", (chunk: Buffer) => stderr.push(chunk)); + backend.on("error", (error) => { + failures.push(error); + response.destroy(error); + }); + backend.on("close", (code) => { + backends.delete(backend); + if (code !== 0) { + const error = new Error( + `git http-backend exited ${code}: ${Buffer.concat(stderr)}` + ); + failures.push(error); + response.destroy(error); + return; + } + const output = Buffer.concat(chunks); + const boundary = output.indexOf("\r\n\r\n"); + if (boundary < 0) { + const error = new Error("git http-backend returned no CGI headers"); + failures.push(error); + response.destroy(error); + return; + } + for (const header of output + .subarray(0, boundary) + .toString() + .split("\r\n")) { + const colon = header.indexOf(":"); + response.setHeader( + header.slice(0, colon), + header.slice(colon + 1).trim() + ); + } + response.end(output.subarray(boundary + 4)); + }); + request.pipe(backend.stdin!); + } + ); + let client: CopilotClient | undefined; + ctx.onTestFinished(async () => { + try { + await client?.stop(); + } finally { + server.closeAllConnections(); + if (server.listening) { + await new Promise((resolve, reject) => + server.close((error) => (error ? reject(error) : resolve())) + ); + } + for (const backend of backends) { + backend.kill(); + } + } + }); + await new Promise((resolve, reject) => { + server.once("error", reject); + server.listen(0, "127.0.0.1", resolve); + }); + const address = server.address(); + if (address === null || typeof address === "string") { + throw new Error("HTTPS fixture has no TCP address"); + } + const host = `https://127.0.0.1:${address.port}`; + await writeFile( + configPath, + `[http]\nsslCAInfo = ${caPath.replaceAll("\\", "/")}\nproxy =\n` + + // The fixture CA has no revocation service; retain certificate verification. + (process.platform === "win32" + ? "sslBackend = schannel\nschannelUseSSLCAInfo = true\nschannelCheckRevoke = false\n" + : "") + ); + client = new CopilotClient({ + workingDirectory: workDir, + connection: RuntimeConnection.forStdio({ path: process.env.COPILOT_CLI_PATH }), + gitHubToken: DEFAULT_GITHUB_TOKEN, + env: { + ...gitEnv, + GIT_SSL_CAINFO: caPath, + COPILOT_HOME: home, + COPILOT_CACHE_HOME: join(home, "cache"), + COPILOT_GH_HOST: host, + GH_HOST: "", + GH_CONFIG_DIR: home, + }, + logLevel: "error", + }); + await client.start(); + ctx.expect((await client.getAuthStatus()).host).toBe(host); + const remote = `${host}/repo.git`; + const added = await client.rpc.plugins.marketplaces.add({ source: remote }); + ctx.expect(added.name).toBe("private-auth-marketplace"); + const refreshed = await client.rpc.plugins.marketplaces.refresh({ + name: "private-auth-marketplace", + }); + ctx.expect(refreshed.results).toEqual([ + { name: "private-auth-marketplace", success: true }, + ]); + const session = await client.createSession({ onPermissionRequest: approveAll }); + try { + const sessionRefresh = await session.rpc.plugins.marketplaces.refresh({ + name: "private-auth-marketplace", + }); + ctx.expect(sessionRefresh.results).toEqual([ + { name: "private-auth-marketplace", success: true }, + ]); + } finally { + await session.disconnect(); + } + ctx.expect(failures).toEqual([]); + ctx.expect(observed[0]).toBeUndefined(); + ctx.expect(observed).toContain(authorization); + const marketplace = (await client.rpc.plugins.marketplaces.list()).marketplaces.find( + (entry) => entry.name === "private-auth-marketplace" + ); + ctx.expect(marketplace?.source).toBe(`URL: ${remote}`); + const cache = join(home, "cache", "marketplaces"); + const directories = (await readdir(cache, { withFileTypes: true })).filter((entry) => + entry.isDirectory() + ); + ctx.expect(directories).toHaveLength(1); + const persisted = await readFile( + join(cache, directories[0].name, ".git", "config"), + "utf8" + ); + ctx.expect(persisted).toContain(remote); + ctx.expect(persisted).not.toContain(DEFAULT_GITHUB_TOKEN); + ctx.expect(persisted).not.toContain("extraheader"); + const trace = await readFile(tracePath, "utf8"); + ctx.expect(trace).not.toContain(DEFAULT_GITHUB_TOKEN); + ctx.expect(trace).not.toContain(authorization); + } + ); +}); diff --git a/nodejs/test/e2e/rpc_mcp_lifecycle.e2e.test.ts b/nodejs/test/e2e/rpc_mcp_lifecycle.e2e.test.ts index 40f837434d..b47eed190d 100644 --- a/nodejs/test/e2e/rpc_mcp_lifecycle.e2e.test.ts +++ b/nodejs/test/e2e/rpc_mcp_lifecycle.e2e.test.ts @@ -86,6 +86,103 @@ describe("Session-scoped MCP lifecycle RPC", async () => { expect(message.toLowerCase()).not.toContain("unhandled method"); } + async function offeredToolNames(session: CopilotSession): Promise { + await session.rpc.tools.initializeAndValidate(); + const { tools } = await session.rpc.tools.getCurrentMetadata(); + if (tools === null) { + throw new Error("initializeAndValidate did not initialize session tools"); + } + return tools.map((tool) => tool.name); + } + + it("should start an ephemeral server from caller config and reuse its replacement", async () => { + const serverName = missingName("ephemeral"); + const session = await client.createSession({ onPermissionRequest: approveAll }); + try { + const persistedBefore = await client.rpc.mcp.config.list(); + const initialConfig = { + type: "stdio" as const, + command: "node", + args: [TEST_MCP_SERVER, "--tool-name", "ephemeral_initial"], + cwd: TEST_HARNESS_DIR, + excludeTools: ["ephemeral_initial"], + disableToolCache: true, + }; + await session.rpc.mcp.startServer({ serverName, config: initialConfig }); + expect( + (await session.rpc.mcp.listTools({ serverName })).tools.map((tool) => tool.name) + ).toEqual(["ephemeral_initial"]); + expect(await offeredToolNames(session)).not.toContain( + `${serverName}-ephemeral_initial` + ); + expect((await session.rpc.mcp.isServerRunning({ serverName })).running).toBe(true); + + await session.rpc.mcp.restartServer({ + serverName, + config: { + ...initialConfig, + args: [TEST_MCP_SERVER, "--tool-name", "ephemeral_replacement"], + }, + }); + expect(await offeredToolNames(session)).toContain( + `${serverName}-ephemeral_replacement` + ); + await session.rpc.mcp.stopServer({ serverName }); + expect((await session.rpc.mcp.isServerRunning({ serverName })).running).toBe(false); + await session.rpc.mcp.startServer({ serverName }); + await session.rpc.mcp.restartServer({ serverName }); + expect( + (await session.rpc.mcp.listTools({ serverName })).tools.map((tool) => tool.name) + ).toEqual(["ephemeral_replacement"]); + expect(await client.rpc.mcp.config.list()).toEqual(persistedBefore); + } finally { + await session.disconnect(); + } + const nextSession = await client.createSession({ onPermissionRequest: approveAll }); + try { + expect( + (await nextSession.rpc.mcp.listConfigured()).servers.some( + (server) => server.name === serverName + ) + ).toBe(false); + } finally { + await nextSession.disconnect(); + } + }); + + it("should replace the registered config when restarting an installed server", async () => { + const serverName = "rpc-lifecycle-replace-server"; + const session = await createSessionWithMcp(serverName); + try { + await waitForMcpServerStatus(session, serverName); + expect( + (await session.rpc.mcp.listTools({ serverName })).tools.map((tool) => tool.name) + ).toEqual(["get_env"]); + expect(await offeredToolNames(session)).toContain(`${serverName}-get_env`); + await session.rpc.mcp.restartServer({ + serverName, + config: { + type: "stdio", + command: "node", + args: [TEST_MCP_SERVER, "--tool-name", "installed_replacement"], + cwd: TEST_HARNESS_DIR, + tools: ["*"], + excludeTools: ["installed_replacement"], + disableToolCache: true, + }, + }); + expect( + (await session.rpc.mcp.listTools({ serverName })).tools.map((tool) => tool.name) + ).toEqual(["installed_replacement"]); + const offered = await offeredToolNames(session); + expect(offered).not.toContain(`${serverName}-installed_replacement`); + expect(offered).not.toContain(`${serverName}-get_env`); + expect((await session.rpc.mcp.isServerRunning({ serverName })).running).toBe(true); + } finally { + await session.disconnect(); + } + }); + it( "should list tools and report running status for connected server", { timeout: 120_000 }, diff --git a/nodejs/test/e2e/rpc_server.e2e.test.ts b/nodejs/test/e2e/rpc_server.e2e.test.ts index 07d7ffcf51..e72eb5b656 100644 --- a/nodejs/test/e2e/rpc_server.e2e.test.ts +++ b/nodejs/test/e2e/rpc_server.e2e.test.ts @@ -4,6 +4,7 @@ import * as fs from "fs"; import * as path from "path"; +import { execFileSync } from "node:child_process"; import { randomUUID } from "node:crypto"; import { describe, expect, it, onTestFinished } from "vitest"; import { CopilotClient, RuntimeConnection } from "../../src/index.js"; @@ -113,11 +114,19 @@ describe("Server-scoped RPC", async () => { const policy = { model: "auto", autoTier: "balance", + vscode: { temporaryDefaultToLocalHarness: true }, + permissions: { + limitTo: ["Domain(*.example.com)"], + deny: ["Domain(blocked.example.com)"], + ask: ["Domain(prompt.example.com)"], + allow: ["Domain(allowed.example.com)"], + }, }; const concretePolicy = { model: "gpt-5", effortLevel: { overridable: "high" }, contextTier: "long_context", + vscode: { temporaryDefaultToLocalHarness: false }, }; fs.writeFileSync(policyPath, JSON.stringify(policy)); const policyClient = new CopilotClient({ @@ -143,6 +152,10 @@ describe("Server-scoped RPC", async () => { const schema = await api.schema(); expect(schema.runtimeVersion).toEqual(expect.any(String)); expect(schema.schema).toHaveProperty("properties.model"); + expect(schema.schema).toHaveProperty( + "properties.vscode.properties.temporaryDefaultToLocalHarness.type", + "boolean" + ); const validated = await api.validate({ content: policy }); expect(validated.valid).toBe(true); @@ -157,6 +170,7 @@ describe("Server-scoped RPC", async () => { expect(resolved.account).toBeUndefined(); expect(resolved.resolved.deviceManaged).toBe(true); expect(resolved.values?.model).toBe(policy.model); + expect(resolved.resolved.settings).toHaveProperty("vscode", policy.vscode); const device = resolved.layers.find((layer) => layer.source === "device"); expect(device?.settings).toMatchObject(policy); @@ -175,11 +189,51 @@ describe("Server-scoped RPC", async () => { const absent = await api.compose({ layers: [{ source: "device" }] }); expect(absent.resolved.source).toBe("none"); + const permissionsContext = resolved.permissionsContext; + if (!permissionsContext) { + throw new Error("Runtime omitted the managed permission evaluation context"); + } + expect( + await api.permissions.evaluate({ + context: permissionsContext, + operations: [ + { kind: "url", url: "https://allowed.example.com/guide" }, + { kind: "url", url: "https://prompt.example.com/" }, + { kind: "url", url: "https://blocked.example.com/" }, + { kind: "url", url: "https://outside.test/" }, + ], + }) + ).toEqual({ + failClosed: false, + results: [ + { + operation: { kind: "url", url: "https://allowed.example.com/guide" }, + verdict: "allow", + }, + { operation: { kind: "url", url: "https://prompt.example.com/" }, verdict: "ask" }, + { + operation: { kind: "url", url: "https://blocked.example.com/" }, + verdict: "deny", + }, + { operation: { kind: "url", url: "https://outside.test/" }, verdict: "deny" }, + ], + }); + await expect( + api.permissions.evaluate({ + context: permissionsContext, + operations: [{ kind: "url", url: "not-a-url" }], + }) + ).rejects.toMatchObject({ code: -32602 }); + await expect(api.resolve({ selectionId: "missing-account" })).rejects.toThrow(); + const managedSession = await policyClient.createSession({ enableManagedSettings: true, model: "auto", }); onTestFinished(() => managedSession.disconnect()); + await expect(managedSession.rpc.managedSettings.get()).resolves.toMatchObject({ + settings: { vscode: policy.vscode }, + }); await expect .poll(() => managedSession.rpc.model.getCurrent()) .toMatchObject({ modelId: "auto", autoTier: "balance" }); @@ -197,11 +251,7 @@ describe("Server-scoped RPC", async () => { const concretePolicyPath = path.join(workDir, "managed-concrete-settings.json"); fs.writeFileSync( concretePolicyPath, - JSON.stringify({ - model: "gpt-5", - effortLevel: "high", - contextTier: "long_context", - }) + JSON.stringify({ ...concretePolicy, effortLevel: "high" }) ); const concretePolicyClient = new CopilotClient({ workingDirectory: workDir, @@ -233,6 +283,7 @@ describe("Server-scoped RPC", async () => { model: "gpt-5", effortLevel: "high", contextTier: "long_context", + vscode: concretePolicy.vscode, }, }); await expect @@ -251,6 +302,9 @@ describe("Server-scoped RPC", async () => { enforceManagedModelDefaults: false, }); onTestFinished(() => relaxedSession.disconnect()); + await expect(relaxedSession.rpc.managedSettings.get()).resolves.toMatchObject({ + settings: { vscode: concretePolicy.vscode }, + }); await expect( relaxedSession.rpc.model.setReasoningEffort({ reasoningEffort: "low" }) ).resolves.toMatchObject({ reasoningEffort: "low" }); @@ -471,6 +525,119 @@ describe("Server-scoped RPC", async () => { } }, 60_000); + it("should resolve lifecycle-only session prefixes in the configured home", async () => { + const sessionId = randomUUID(); + const home = createUniqueWorkDirectory("prefix-home"); + const sessionDirectory = path.join(home, "session-state", sessionId); + fs.mkdirSync(sessionDirectory, { recursive: true }); + fs.writeFileSync( + path.join(sessionDirectory, "events.jsonl"), + `${JSON.stringify({ type: "session.start", data: { sessionId } })}\n` + ); + const prefixClient = createClientWithEnv({ COPILOT_HOME: home }); + await prefixClient.start(); + + expect( + await prefixClient.rpc.sessions.findByPrefix({ prefix: sessionId.slice(0, 8) }) + ).toEqual({ sessionId }); + const listed = await prefixClient.rpc.sessions.list({}); + expect(listed.sessions.map((session) => session.sessionId)).not.toContain(sessionId); + const defaultHomeClient = createClientWithEnv({}); + await defaultHomeClient.start(); + expect( + await defaultHomeClient.rpc.sessions.findByPrefix({ prefix: sessionId.slice(0, 8) }) + ).toEqual({}); + }); + + it("should omit detached session prefixes", async () => { + const home = createUniqueWorkDirectory("detached-prefix-home"); + const prefixClient = createClientWithEnv({ COPILOT_HOME: home }); + await prefixClient.start(); + + for (const detachedKind of ["marker", "event"] as const) { + const sessionId = randomUUID(); + const sessionDirectory = path.join(home, "session-state", sessionId); + fs.mkdirSync(sessionDirectory, { recursive: true }); + const eventsPath = path.join(sessionDirectory, "events.jsonl"); + fs.writeFileSync( + eventsPath, + `${JSON.stringify({ type: "session.start", data: { sessionId } })}\n` + ); + expect( + await prefixClient.rpc.sessions.findByPrefix({ prefix: sessionId.slice(0, 8) }) + ).toEqual({ sessionId }); + fs.writeFileSync( + eventsPath, + `${JSON.stringify({ + type: "session.start", + data: { + sessionId, + ...(detachedKind === "event" + ? { detachedFromSpawningParentSessionId: randomUUID() } + : {}), + }, + })}\n` + ); + if (detachedKind === "marker") { + fs.writeFileSync(path.join(sessionDirectory, ".detached"), ""); + } + + expect( + await prefixClient.rpc.sessions.findByPrefix({ prefix: sessionId.slice(0, 8) }) + ).toEqual({}); + } + }); + + it("should omit linked session directories for exact and abbreviated prefixes", async () => { + const home = createUniqueWorkDirectory("linked-prefix-home"); + const outside = createUniqueWorkDirectory("linked-prefix-target"); + const transcript = `${JSON.stringify({ type: "session.start", data: {} })}\n`; + fs.writeFileSync(path.join(outside, "events.jsonl"), transcript); + const sessionsHome = path.join(home, "session-state"); + fs.mkdirSync(sessionsHome); + const prefixClient = createClientWithEnv({ COPILOT_HOME: home }); + await prefixClient.start(); + + const sessionId = randomUUID(); + const prefix = sessionId.slice(0, 8); + for (const linkedId of [prefix, sessionId]) { + const link = path.join(sessionsHome, linkedId); + fs.symlinkSync(outside, link, process.platform === "win32" ? "junction" : "dir"); + try { + expect(await prefixClient.rpc.sessions.findByPrefix({ prefix })).toEqual({}); + } finally { + fs.unlinkSync(link); + } + } + expect(fs.readFileSync(path.join(outside, "events.jsonl"), "utf8")).toBe(transcript); + }); + + it("should omit linked event files for exact and abbreviated prefixes", async () => { + const home = createUniqueWorkDirectory("linked-events-home"); + const outside = createUniqueWorkDirectory("linked-events-target"); + const target = path.join(outside, "events.jsonl"); + const transcript = `${JSON.stringify({ type: "session.start", data: {} })}\n`; + fs.writeFileSync(target, transcript); + const prefixClient = createClientWithEnv({ COPILOT_HOME: home }); + await prefixClient.start(); + + const sessionId = randomUUID(); + const prefix = sessionId.slice(0, 8); + for (const linkedId of [prefix, sessionId]) { + const sessionDirectory = path.join(home, "session-state", linkedId); + fs.mkdirSync(sessionDirectory, { recursive: true }); + const link = path.join(sessionDirectory, "events.jsonl"); + fs.symlinkSync(target, link, "file"); + try { + expect(await prefixClient.rpc.sessions.findByPrefix({ prefix })).toEqual({}); + } finally { + fs.unlinkSync(link); + fs.rmdirSync(sessionDirectory); + } + } + expect(fs.readFileSync(target, "utf8")).toBe(transcript); + }); + it("should enrich basic session metadata", async () => { const sessionId = randomUUID(); const workingDirectory = createUniqueWorkDirectory("server-rpc-enrich"); @@ -647,6 +814,37 @@ describe("Server-scoped RPC", async () => { ); }); + it("should discover all 128 custom agents including deeply nested definitions", async () => { + const projectPath = createUniqueWorkDirectory("server-rpc-agents"); + execFileSync("git", ["init", "--quiet"], { + cwd: projectPath, + env, + windowsHide: true, + }); + const agentsDirectory = path.join(projectPath, ".github", "agents"); + const nestedDirectory = path.join(agentsDirectory, ...Array(12).fill("d")); + fs.mkdirSync(nestedDirectory, { recursive: true }); + const expectedNames = Array.from( + { length: 128 }, + (_, index) => `agent-${index.toString().padStart(3, "0")}` + ); + for (const [index, name] of expectedNames.entries()) { + const directory = index === 127 ? nestedDirectory : agentsDirectory; + fs.writeFileSync( + path.join(directory, `${name}.agent.md`), + `---\nname: ${name}\ndescription: Test agent.\n---\nAgent prompt.` + ); + } + + await client.start(); + const result = await client.rpc.agents.discover({ + projectPaths: [projectPath], + excludeHostAgents: true, + }); + + expect(result.agents.map((agent) => agent.name).sort()).toEqual(expectedNames); + }); + it("should discover server mcp and skills", async () => { await client.start(); diff --git a/nodejs/test/e2e/rpc_server_plugins.e2e.test.ts b/nodejs/test/e2e/rpc_server_plugins.e2e.test.ts index 20575a9114..2ce0800f4a 100644 --- a/nodejs/test/e2e/rpc_server_plugins.e2e.test.ts +++ b/nodejs/test/e2e/rpc_server_plugins.e2e.test.ts @@ -3,15 +3,25 @@ *--------------------------------------------------------------------------------------------*/ import { randomUUID } from "node:crypto"; -import { mkdirSync, rmSync, writeFileSync } from "node:fs"; -import { join } from "node:path"; -import { describe, expect, it } from "vitest"; -import { CopilotClient, RuntimeConnection } from "../../src/index.js"; +import { mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs"; +import { join, dirname } from "node:path"; +import { fileURLToPath } from "node:url"; +import { describe, expect, it, onTestFinished } from "vitest"; +import { + approveAll, + CopilotClient, + RuntimeConnection, + type CopilotSession, +} from "../../src/index.js"; import { createSdkTestContext, DEFAULT_GITHUB_TOKEN } from "./harness/sdkTestContext.js"; +import { waitForCondition } from "./harness/sdkTestHelper.js"; const MARKETPLACE_NAME = "csharp-e2e-marketplace"; const PLUGIN_NAME = "csharp-e2e-plugin"; const DIRECT_PLUGIN_NAME = "csharp-e2e-direct"; +const TEST_MCP_SERVER = fileURLToPath( + new URL("../../../test/harness/test-mcp-server.mjs", import.meta.url) +); describe("Server-scoped plugin RPC", async () => { const { env, workDir } = await createSdkTestContext(); @@ -127,6 +137,110 @@ This skill exists so the plugin reports at least one installed skill. writeFileSync(join(pluginDir, "SKILL.md"), skill); } + it( + "keeps unrelated MCP servers running when Computer Use is toggled", + { timeout: 120_000 }, + async ({ expect }) => { + const pluginDir = createUniqueDirectory("computer-use"); + const { client, home } = await createIsolatedStartedClient(); + let session: CopilotSession | undefined; + onTestFinished(async () => { + try { + await session?.disconnect(); + } finally { + await disposeIsolated(client, home, pluginDir); + } + }); + writeFileSync( + join(pluginDir, "plugin.json"), + JSON.stringify({ + name: "computer-use", + version: "1.0.0", + mcpServers: "./.mcp.json", + }) + ); + writeFileSync( + join(pluginDir, ".mcp.json"), + JSON.stringify({ + mcpServers: { + "computer-use": { + command: process.execPath, + args: [TEST_MCP_SERVER, "--server-name", "computer-use"], + cwd: dirname(TEST_MCP_SERVER), + tools: ["*"], + }, + }, + }) + ); + await client.rpc.plugins.builtin.set({ paths: [pluginDir] }); + const serverName = "unrelated-to-computer-use"; + const startupMarker = join(home, "unrelated-mcp-starts.log"); + const mcpSession = await client.createSession({ + onPermissionRequest: approveAll, + mcpServers: { + [serverName]: { + type: "local", + command: process.execPath, + args: [ + TEST_MCP_SERVER, + "--startup-marker", + startupMarker, + "--server-name", + serverName, + ], + workingDirectory: dirname(TEST_MCP_SERVER), + tools: ["*"], + }, + }, + }); + session = mcpSession; + const waitForConnected = (name: string) => + waitForCondition( + async () => + (await mcpSession.rpc.mcp.list()).servers.some( + (server) => server.name === name && server.status === "connected" + ), + { + timeoutMs: 60_000, + intervalMs: 200, + timeoutMessage: `${name} did not connect`, + } + ); + await waitForConnected(serverName); + expect(readFileSync(startupMarker, "utf8")).toBe(`${serverName}\n`); + const toolsBefore = await mcpSession.rpc.mcp.listTools({ serverName }); + expect(toolsBefore.tools.map((tool) => tool.name)).toContain("get_env"); + expect( + (await mcpSession.rpc.mcp.list()).servers.map((server) => server.name) + ).not.toContain("computer-use"); + + for (const enabled of [true, false, true, false]) { + if (enabled) { + await client.rpc.plugins.enable({ names: ["computer-use"] }); + await waitForConnected("computer-use"); + } else { + await client.rpc.plugins.disable({ names: ["computer-use"] }); + await waitForCondition( + async () => + !(await mcpSession.rpc.mcp.list()).servers.some( + (server) => server.name === "computer-use" + ), + { + timeoutMs: 60_000, + intervalMs: 200, + timeoutMessage: "Computer Use MCP server was not removed", + } + ); + } + expect((await mcpSession.rpc.mcp.isServerRunning({ serverName })).running).toBe( + true + ); + expect(await mcpSession.rpc.mcp.listTools({ serverName })).toEqual(toolsBefore); + expect(readFileSync(startupMarker, "utf8")).toBe(`${serverName}\n`); + } + } + ); + it("should install and list plugin from local marketplace", { timeout: 120_000 }, async () => { const marketplaceDir = createLocalMarketplaceFixture(); const { client, home } = await createIsolatedStartedClient(); diff --git a/nodejs/test/e2e/rpc_session_state.e2e.test.ts b/nodejs/test/e2e/rpc_session_state.e2e.test.ts index 76e7ff2261..9efa6dfdc9 100644 --- a/nodejs/test/e2e/rpc_session_state.e2e.test.ts +++ b/nodejs/test/e2e/rpc_session_state.e2e.test.ts @@ -304,6 +304,60 @@ describe("Session-scoped RPC", async () => { } }); + it("should read the reasoning-summaries policy for the current model", async () => { + const session = await client.createSession({ + onPermissionRequest: approveAll, + model: "claude-sonnet-5", + // This policy needs the CAPI catalog; legacy BYOK sessions skip it. + // An explicit provider roster keeps the harness from replacing CAPI. + providers: [], + }); + + try { + // `ready` stays false until the session has resolved its model + // catalog, which only happens once something asks for it. Prime the + // session's model-list cache so the policy reports the real answer + // instead of the not-ready placeholder. + await session.rpc.model.list(); + + const policy = await session.rpc.model.getReasoningSummaries({}); + expect(policy.ready).toBe(true); + expect(typeof policy.experimentSettled).toBe("boolean"); + expect(typeof policy.modelDefaultShowsSummaries).toBe("boolean"); + expect(typeof policy.showReasoningHeaders).toBe("boolean"); + // No session-level reasoning-summary override, so the session + // follows the selected model's default. + expect(policy.sessionShowsSummaries).toBe(policy.modelDefaultShowsSummaries); + + // A persisted setting decides visibility whatever the experiment + // assignment is, and one that restates the model default is + // reported as redundant. + const redundant = await session.rpc.model.getReasoningSummaries({ + persistedShowReasoning: policy.modelDefaultShowsSummaries, + }); + expect(redundant.visibility).toBe(policy.modelDefaultShowsSummaries); + expect(redundant.clearRedundantSetting).toBe(true); + + const opposed = await session.rpc.model.getReasoningSummaries({ + persistedShowReasoning: !policy.modelDefaultShowsSummaries, + }); + expect(opposed.visibility).toBe(!policy.modelDefaultShowsSummaries); + expect(opposed.clearRedundantSetting).toBe(false); + + // A visible view never asks for the "summaries are hidden" hint, + // and a pending experiment assignment never asks for it either. + const visible = await session.rpc.model.getReasoningSummaries({ + summariesVisible: true, + }); + expect(visible.showHiddenHint).toBe(false); + if (!policy.experimentSettled) { + expect(policy.showHiddenHint).toBe(false); + } + } finally { + await session.disconnect(); + } + }); + it("should get and set session metadata", async () => { const session = await client.createSession({ onPermissionRequest: approveAll }); diff --git a/nodejs/test/e2e/rpc_settings.e2e.test.ts b/nodejs/test/e2e/rpc_settings.e2e.test.ts new file mode 100644 index 0000000000..7d0ce82023 --- /dev/null +++ b/nodejs/test/e2e/rpc_settings.e2e.test.ts @@ -0,0 +1,138 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +import { spawn } from "node:child_process"; +import { once } from "node:events"; +import { mkdir, readFile, writeFile } from "node:fs/promises"; +import { join } from "node:path"; +import { describe, it, onTestFinished } from "vitest"; +import { + createMessageConnection, + StreamMessageReader, + StreamMessageWriter, +} from "vscode-jsonrpc/node.js"; +import { createInternalServerRpc, createServerRpc } from "../../src/generated/rpc.js"; +import { createSdkTestContext, getLegacyCliPathForTests } from "./harness/sdkTestContext.js"; +import { stopChildProcess } from "./harness/sdkTestHelper.js"; + +describe("Internal settings RPC through the CLI SDK server", async () => { + const { env, workDir } = await createSdkTestContext(); + + it("preserves settings semantics through the generated SDK methods", async ({ expect }) => { + const home = join(workDir, "settings-home"); + const repoRoot = join(workDir, "repository"); + await mkdir(home, { recursive: true }); + await mkdir(join(repoRoot, ".github", "copilot"), { recursive: true }); + const legacy = { model: "legacy-model", render_markdown: false }; + await writeFile(join(home, "config.json"), JSON.stringify(legacy)); + + // These methods are internal, so use their generated projection over a real CLI transport. + const cliPath = await getLegacyCliPathForTests(); + const args = ["--headless", "--stdio", "--no-auto-update", "--log-level", "error"]; + const child = spawn( + cliPath.endsWith(".js") ? process.execPath : cliPath, + cliPath.endsWith(".js") ? [cliPath, ...args] : args, + { cwd: workDir, env: { ...env, COPILOT_HOME: home }, stdio: "pipe" } + ); + let stderr = ""; + child.stderr.on("data", (chunk) => { + stderr += String(chunk); + }); + const connection = createMessageConnection( + new StreamMessageReader(child.stdout), + new StreamMessageWriter(child.stdin) + ); + onTestFinished(async () => { + connection.dispose(); + await stopChildProcess(child); + }); + await once(child, "spawn"); + connection.listen(); + const rpc = createInternalServerRpc(connection); + expect(await rpc.connect({}), stderr).toMatchObject({ + protocolVersion: expect.any(Number), + }); + + const paths = await rpc.user.settings.getPaths({ repoRoot }); + expect(paths).toEqual({ + userSettings: join(home, "settings.json"), + repoSettings: join(repoRoot, ".github", "copilot", "settings.json"), + repoLocalSettings: join(repoRoot, ".github", "copilot", "settings.local.json"), + }); + expect((await rpc.user.settings.listGovernanceKeys()).repo).toContain("model"); + expect(await rpc.user.settings.normalize({ settings: { render_markdown: true } })).toEqual({ + settings: { renderMarkdown: true }, + }); + const problems = await rpc.user.settings.diagnoseProblems({ + sources: [ + { + kind: "user", + label: "User", + filePath: paths.userSettings, + config: { notASetting: true }, + }, + ], + }); + expect(problems.problems).toEqual([ + expect.objectContaining({ kind: "unknown-key", rawKey: "notASetting" }), + ]); + + await rpc.user.settings.writeAll({ settings: { model: "initial-model", beep: true } }); + expect( + await rpc.user.settings.edit({ + canonicalPath: ["model"], + operation: "set", + value: "edited-model", + }) + ).toEqual({ status: "changed" }); + expect( + await rpc.user.settings.applyTopKey({ + canonicalPath: ["model"], + topKey: "model", + value: null, + }) + ).toMatchObject({ status: "validation-error" }); + expect( + await rpc.user.settings.applyTopKey({ + canonicalPath: ["model"], + topKey: "model", + value: "applied-model", + }) + ).toEqual({ status: "ok" }); + expect(JSON.parse(await readFile(paths.userSettings, "utf8"))).toEqual({ + model: "applied-model", + beep: true, + }); + await rpc.user.settings.writeAll({ settings: { model: "replacement-model" } }); + expect(JSON.parse(await readFile(paths.userSettings, "utf8"))).toEqual({ + model: "replacement-model", + }); + const loaded = await createServerRpc(connection).user.settings.get(); + expect(loaded.settings.model.value).toBe("replacement-model"); + + const repoPath = join(repoRoot, ".github", "copilot", "settings.json"); + await writeFile(repoPath, '{"custom":{"items":[true,null,12.5]}}'); + const target = { repoRoot, scope: "repo" }; + await rpc.repo.settings.writeKey({ ...target, key: "model", value: "repo-model" }); + expect(await rpc.repo.settings.readRaw(target)).toEqual({ + config: { custom: { items: [true, null, 12.5] }, model: "repo-model" }, + invalid: false, + }); + await rpc.repo.settings.writeAll({ ...target, settings: {} }); + expect(await rpc.repo.settings.readRaw(target)).toEqual({ + config: { custom: { items: [true, null, 12.5] } }, + invalid: false, + }); + await writeFile(repoPath, "{ invalid"); + expect(await rpc.repo.settings.readRaw(target)).toEqual({ config: {}, invalid: true }); + await expect( + rpc.repo.settings.writeKey({ + ...target, + key: "model", + value: "must-not-replace-invalid-file", + }) + ).rejects.toThrow(); + expect(await readFile(repoPath, "utf8")).toBe("{ invalid"); + }); +}); diff --git a/nodejs/test/e2e/rpc_shell_and_fleet.e2e.test.ts b/nodejs/test/e2e/rpc_shell_and_fleet.e2e.test.ts index 4f8061c960..51e5855e13 100644 --- a/nodejs/test/e2e/rpc_shell_and_fleet.e2e.test.ts +++ b/nodejs/test/e2e/rpc_shell_and_fleet.e2e.test.ts @@ -14,7 +14,12 @@ import { createSdkTestContext } from "./harness/sdkTestContext.js"; import { waitForCondition, waitForFileText } from "./harness/sdkTestHelper.js"; describe("Shell and fleet RPC", async () => { - const { copilotClient: client, workDir } = await createSdkTestContext(); + const { copilotClient: client, workDir } = await createSdkTestContext({ + logLevel: "debug", + copilotClientOptions: { + env: { RUST_LOG: "error,sdk_diagnostics=debug" }, + }, + }); function createWriteFileCommand(markerPath: string, marker: string): string { if (os.platform() === "win32") { @@ -23,6 +28,12 @@ describe("Shell and fleet RPC", async () => { return `sh -c "printf '%s' '${marker}' > '${markerPath}'"`; } + function createWindowsBatchCommand(command: string): string { + const filename = `shell-command-${randomUUID()}.cmd`; + fs.writeFileSync(path.join(workDir, filename), `@echo off\r\n${command}\r\n`); + return `cmd.exe /d /c ${filename}`; + } + async function waitForMessages( session: CopilotSession, predicate: (events: SessionEvent[]) => boolean, @@ -140,7 +151,9 @@ describe("Shell and fleet RPC", async () => { const timeout = os.platform() === "win32" ? 10_000 : 1_000; const command = os.platform() === "win32" - ? `echo started>"${started}" & ping.exe -n 31 127.0.0.1 >nul & echo completed>"${completed}"` + ? createWindowsBatchCommand( + `echo started>"${started}" & ping.exe -n 31 127.0.0.1 >nul & echo completed>"${completed}"` + ) : `printf started > '${started}'; sleep 30; printf completed > '${completed}'`; try { const result = await session.rpc.shell.exec({ command, cwd: workDir, timeout }); @@ -160,13 +173,120 @@ describe("Shell and fleet RPC", async () => { } ); + it.skipIf(os.platform() === "win32")( + "should release post-root-exit shell pipes and rewind admission at timeout", + async ({ onTestFailed }) => { + const cwd = fs.mkdtempSync(path.join(workDir, "post-exit-shell-")); + let phase = "creating session"; + let rootPid: number | undefined; + let rootExited = false; + let lastRewindState: + | Awaited> + | undefined; + onTestFailed(() => { + console.error("Post-root-exit shell timeout diagnostic:", { + phase, + rootPid, + rootExited, + lastRewindState, + descendantReady: fs.existsSync(path.join(cwd, "descendant.ready")), + terminated: fs.existsSync(path.join(cwd, "terminated")), + }); + }); + const session = await client.createSession({ + enableFileChangeTracking: true, + onPermissionRequest: approveAll, + }); + let processId: string | undefined; + function processExists(pid: number): boolean { + try { + process.kill(pid, 0); + return true; + } catch (error) { + if ((error as NodeJS.ErrnoException).code === "ESRCH") { + return false; + } + throw error; + } + } + try { + phase = "waiting for rewind tracking initialization"; + await waitForCondition( + async () => { + lastRewindState = await session.rpc.history.listRewindPoints(); + return lastRewindState.fileChangeTrackingEnabled; + }, + { timeoutMessage: "Rewind tracking did not initialize" } + ); + phase = "launching post-exit shell"; + // Reader teardown must not SIGPIPE the fixture before its TERM trap records termination. + const result = await session.rpc.shell.exec({ + command: + `sh -c 'trap "" PIPE; trap "printf terminated > terminated; exit 0" TERM; ` + + `printf ready > descendant.ready; sleep 60' & ` + + `echo $$ > root.pending; mv root.pending root.pid; exit 7`, + cwd, + timeout: 10_000, + }); + processId = result.processId; + expect(processId).toBeTruthy(); + phase = "waiting for root reap and descendant readiness"; + await waitForCondition( + () => { + if ( + !fs.existsSync(path.join(cwd, "root.pid")) || + !fs.existsSync(path.join(cwd, "descendant.ready")) + ) { + return false; + } + rootPid = Number(fs.readFileSync(path.join(cwd, "root.pid"), "utf8")); + expect(Number.isInteger(rootPid) && rootPid > 1).toBe(true); + rootExited = !processExists(rootPid); + return rootExited; + }, + { timeoutMessage: "Fixture root did not exit with a ready pipe holder" } + ); + phase = "checking retained rewind lease"; + expect(fs.existsSync(path.join(cwd, "terminated"))).toBe(false); + lastRewindState = await session.rpc.history.listRewindPoints(); + expect(lastRewindState.unavailableReason).toBe("session-busy"); + + // Neither poll sends a kill: the configured timeout must end the + // descendant and release the production sink's operation lease. + phase = "waiting for rewind admission after timeout"; + await waitForCondition( + async () => { + lastRewindState = await session.rpc.history.listRewindPoints(); + return ( + lastRewindState.fileChangeTrackingEnabled && + lastRewindState.unavailableReason === undefined + ); + }, + { timeoutMessage: "Post-exit timeout left rewind admission session-busy" } + ); + phase = "waiting for automatic descendant termination"; + await waitForFileText(path.join(cwd, "terminated"), "terminated"); + phase = "checking registry cleanup"; + expect((await session.rpc.shell.kill({ processId })).killed).toBe(false); + } finally { + try { + if (processId) { + await session.rpc.shell.kill({ processId, signal: "SIGKILL" }); + } + } finally { + await session.disconnect(); + } + } + } + ); + it("should accept a missing shell command and clean up after it exits", async () => { const session = await client.createSession({ onPermissionRequest: approveAll }); const marker = path.join(workDir, `shell-missing-${randomUUID()}.txt`); const missingCommand = `not-a-command-${randomUUID()}`; const command = os.platform() === "win32" - ? `${missingCommand} & echo done>"${marker}" & exit /b 1` + ? createWindowsBatchCommand(`${missingCommand} & echo done>"${marker}" & exit /b 1`) : `${missingCommand}; code=$?; printf done > '${marker}'; exit $code`; try { const result = await session.rpc.shell.exec({ command, cwd: workDir }); @@ -187,32 +307,40 @@ describe("Shell and fleet RPC", async () => { async (outputKind) => { const session = await client.createSession({ onPermissionRequest: approveAll }); const marker = path.join(workDir, `shell-${outputKind}-${randomUUID()}.txt`); + const outputFile = path.join(workDir, `shell-output-${randomUUID()}.txt`); + const started = `${marker}.started`; const phase = `${marker}.phase`; - const recordPhase = (value: string) => - `[IO.File]::WriteAllText('${phase}', '${value}')`; - // Publish only after Set-Content has closed its exclusive Windows writer. + const recordPhase = (value: string) => `echo ${value}>"${phase}"`; + // Run cmd syntax in an owned batch even when the RPC selects PowerShell. const publishMarker = - `${recordPhase("output-written")}; ` + - `Set-Content -LiteralPath '${marker}.pending' -Value done; ` + - `${recordPhase("marker-written")}; ` + - `Move-Item -LiteralPath '${marker}.pending' -Destination '${marker}'; ` + + `${recordPhase("output-written")} & ` + + `echo done>"${marker}.pending" & ` + + `${recordPhase("marker-written")} & ` + + `move /y "${marker}.pending" "${marker}" >nul & ` + recordPhase("marker-published"); const command = outputKind === "stderr" ? os.platform() === "win32" - ? `powershell -NoLogo -NoProfile -Command "${recordPhase("started")}; [Console]::Error.WriteLine('boom'); ${publishMarker}; exit 2"` - : `echo boom 1>&2; printf done > '${marker}'; exit 2` + ? createWindowsBatchCommand( + `${recordPhase("started")} & echo boom 1>&2 & ${publishMarker} & exit /b 2` + ) + : `printf started > '${started}'; echo boom 1>&2; printf done > '${marker}'; exit 2` : os.platform() === "win32" - ? `powershell -NoLogo -NoProfile -Command "${recordPhase("started")}; Write-Host ('x' * 71680); ${publishMarker}"` - : `printf '%71680s' '' | tr ' ' '='; printf done > '${marker}'`; + ? createWindowsBatchCommand( + `${recordPhase("started")} & type "${outputFile}" & ${publishMarker}` + ) + : `printf started > '${started}'; printf '%71680s' '' | tr ' ' '='; printf done > '${marker}'`; try { + if (outputKind === "large-stdout" && os.platform() === "win32") { + fs.writeFileSync(outputFile, "x".repeat(71680)); + } const result = await session.rpc.shell.exec({ command, cwd: workDir }); expect(result.processId).toBeTruthy(); await waitForFileText( marker, "done", 30_000, - os.platform() === "win32" ? [phase, `${marker}.pending`] : [] + os.platform() === "win32" ? [phase, `${marker}.pending`] : [started] ); // The process map is updated after the output stream closes. await new Promise((resolve) => setTimeout(resolve, 2_000)); diff --git a/nodejs/test/e2e/rpc_surface_coverage.e2e.test.ts b/nodejs/test/e2e/rpc_surface_coverage.e2e.test.ts index 9888df8a7b..aea0df1ec6 100644 --- a/nodejs/test/e2e/rpc_surface_coverage.e2e.test.ts +++ b/nodejs/test/e2e/rpc_surface_coverage.e2e.test.ts @@ -327,10 +327,38 @@ describe("Generated RPC surface coverage", () => { ...collectRuntimeFunctions(session.rpc, "session"), ]); - expect(inventory).toHaveLength(404); + expect(inventory).toHaveLength(412); + expect( + inventory.filter((method) => method.wireMethod === "contentExclusion.validatePolicies") + ).toEqual([ + expect.objectContaining({ scope: "server", path: "contentExclusion.validatePolicies" }), + ]); + expect( + inventory.filter( + (method) => method.wireMethod === "session.model.getReasoningSummaries" + ) + ).toEqual([ + expect.objectContaining({ + scope: "session", + path: "model.getReasoningSummaries", + parameterCount: 1, + returnType: "Promise", + }), + ]); expect(inventory.filter((method) => method.wireMethod === "host.listSessions")).toEqual([ expect.objectContaining({ scope: "server", path: "host.listSessions" }), ]); + expect( + inventory.filter( + (method) => method.wireMethod === "managedSettings.permissions.evaluate" + ) + ).toEqual([ + expect.objectContaining({ + scope: "server", + path: "managedSettings.permissions.evaluate", + parameterCount: 1, + }), + ]); expect(inventory.some((method) => method.wireMethod === "user.settings.reload")).toBe( false ); @@ -341,6 +369,15 @@ describe("Generated RPC surface coverage", () => { path: "extensions.reconcile", parameterCount: 0, }); + expect( + inventory.filter((method) => method.wireMethod === "session.plugins.retryManaged") + ).toEqual([ + expect.objectContaining({ + scope: "session", + path: "plugins.retryManaged", + parameterCount: 1, + }), + ]); const sessionProviderMethods = [ "providers.getCatalog", "providers.discover", @@ -467,6 +504,7 @@ describe("Generated RPC surface coverage", () => { expect((await session.rpc.customizations.reload()).outcomes).toEqual([ { subsystem: "instructions", status: "reloaded" }, ]); + await session.rpc.plugins.retryManaged(); await expect( client.rpc.ping({ message: "error", __forceError: true } as never) @@ -496,5 +534,20 @@ describe("Generated RPC surface coverage", () => { { method: wireMethod, params: { sessionId: "rpc-surface-session" } }, ]); } + expect( + captured.filter((request) => request.method === "session.plugins.retryManaged") + ).toEqual([ + { + method: "session.plugins.retryManaged", + params: { + __coveragePath: "session.plugins.retryManaged", + sessionId: "rpc-surface-session", + }, + }, + { + method: "session.plugins.retryManaged", + params: { sessionId: "rpc-surface-session" }, + }, + ]); }); }); diff --git a/nodejs/test/e2e/rpc_tasks_and_handlers.e2e.test.ts b/nodejs/test/e2e/rpc_tasks_and_handlers.e2e.test.ts index 5f926f120c..f389c4c89e 100644 --- a/nodejs/test/e2e/rpc_tasks_and_handlers.e2e.test.ts +++ b/nodejs/test/e2e/rpc_tasks_and_handlers.e2e.test.ts @@ -3,12 +3,17 @@ *--------------------------------------------------------------------------------------------*/ import { describe, expect, it } from "vitest"; -import { z } from "zod"; import { approveAll, CopilotRequestHandler } from "../../src/index.js"; -import type { SessionEvent, CopilotRequestContext, CopilotSession } from "../../src/index.js"; +import type { + SessionEvent, + CopilotRequestContext, + CopilotSession, + SessionConfig, +} from "../../src/index.js"; import { createSdkTestContext } from "./harness/sdkTestContext.js"; -import { waitForCondition } from "./harness/sdkTestHelper.js"; -import { isByokBackend } from "./harness/testBackend"; +import { getNextEventOfType, waitForCondition } from "./harness/sdkTestHelper.js"; +import { isByokBackend, testBackend, withTestBackend } from "./harness/testBackend"; +import { inferenceModelOptions } from "./harness/inferenceRequest"; const capiIt = it.skipIf(isByokBackend); @@ -16,6 +21,7 @@ class ModelRequestRecorder extends CopilotRequestHandler { readonly requests: Array<{ sessionId?: string; agentId?: string; + path: string; body: { model?: string; reasoning_effort?: string }; }> = []; @@ -23,28 +29,13 @@ class ModelRequestRecorder extends CopilotRequestHandler { request: Request, context: CopilotRequestContext ): Promise { - const pathname = new URL(request.url).pathname; - if (["/chat/completions", "/responses", "/v1/messages"].includes(pathname)) { - const body = z - .object({ - model: z.string(), - reasoning_effort: z.string().optional(), - reasoning: z.object({ effort: z.string().optional() }).optional(), - output_config: z.object({ effort: z.string().optional() }).optional(), - }) - .parse(await request.clone().json()); + const body = await inferenceModelOptions(request); + if (body) { this.requests.push({ sessionId: context.sessionId, agentId: context.agentId, - body: { - model: body.model, - reasoning_effort: - pathname === "/responses" - ? body.reasoning?.effort - : pathname === "/v1/messages" - ? body.output_config?.effort - : body.reasoning_effort, - }, + path: new URL(request.url).pathname, + body, }); } return super.sendRequest(request, context); @@ -326,6 +317,95 @@ describe("Session tasks RPC and pending handlers", async () => { } }); + // Each attached-shell test owns its client; replay normalizes process-global shell IDs. + async function withRunningAttachedShell( + run: ( + session: CopilotSession, + shellId: string, + eventTypes: SessionEvent["type"][], + replies: string[] + ) => Promise + ): Promise { + const shellClient = createClient(); + try { + const session = await shellClient.createSession({ onPermissionRequest: approveAll }); + const eventTypes: SessionEvent["type"][] = []; + const replies: string[] = []; + const unsubscribe = session.on((event) => { + eventTypes.push(event.type); + if (event.type === "assistant.message") { + replies.push(event.data.content ?? ""); + } + }); + try { + const assistantIdle = getNextEventOfType(session, "assistant.idle"); + await session.send({ + prompt: "Use your shell tool to run `sleep 600` in async mode without detaching. Do not read its output or wait for it. Then reply with exactly SHELL_STARTED.", + }); + await assistantIdle; + expect(replies.at(-1)).toContain("SHELL_STARTED"); + + const shell = (await session.rpc.tasks.list()).tasks.find( + (task) => task.type === "shell" && task.status === "running" + ); + expect(shell).toMatchObject({ attachmentMode: "attached" }); + await run(session, shell!.id, eventTypes, replies); + } finally { + unsubscribe(); + await session.disconnect(); + } + } finally { + await shellClient.stop(); + } + } + + it( + "should emit session idle after cancelling a running attached shell", + { timeout: 120_000 }, + async () => { + await withRunningAttachedShell(async (session, shellId, eventTypes) => { + // The running attached shell holds the turn's session.idle. + expect(eventTypes).not.toContain("session.idle"); + + expect((await session.rpc.tasks.cancel({ id: shellId })).cancelled).toBe(true); + await waitForCondition(() => eventTypes.includes("session.idle"), { + timeoutMessage: `session.idle never followed cancelling shell ${shellId}`, + }); + }); + } + ); + + it( + "should run a queued message after cancelling a running attached shell", + { timeout: 120_000 }, + async () => { + await withRunningAttachedShell(async (session, shellId, eventTypes, replies) => { + // The running attached shell holds idle, so an enqueued message waits behind it. + await session.send({ prompt: "Reply with exactly QUEUED_DONE.", mode: "enqueue" }); + await waitForCondition( + async () => + (await session.rpc.queue.pendingItems()).items.some((item) => + item.displayText.includes("QUEUED_DONE") + ), + { timeoutMessage: "The enqueued message was not parked behind the shell" } + ); + expect(replies.some((reply) => reply.includes("QUEUED_DONE"))).toBe(false); + expect(eventTypes).not.toContain("session.idle"); + + expect((await session.rpc.tasks.cancel({ id: shellId })).cancelled).toBe(true); + await waitForCondition( + () => replies.some((reply) => reply.includes("QUEUED_DONE")), + { + timeoutMessage: `The queued message never ran after cancelling shell ${shellId}`, + } + ); + await waitForCondition(() => eventTypes.includes("session.idle"), { + timeoutMessage: "session.idle never followed the queued message", + }); + }); + } + ); + it( "should apply a custom agent's composed model and effort when started over RPC", { timeout: 240_000 }, @@ -438,8 +518,46 @@ describe("Session tasks RPC and pending handlers", async () => { const requestRecorder = new ModelRequestRecorder(); const recordingClient = createClient({ requestHandler: requestRecorder }); try { + let selectedModel = "claude-sonnet-5"; + let agentModel = "Claude Sonnet 5"; + let catalog: Pick = {}; + if (isByokBackend) { + const { provider } = withTestBackend( + { model: "claude-sonnet-5" }, + testBackend, + openAiEndpoint.url + ); + if (!provider?.baseUrl) { + throw new Error("The BYOK fixture must supply a provider endpoint"); + } + // CAPI resolves display aliases; BYOK requires a registered selection + // ID. Its catalog maps that alias to the intended wire model. + selectedModel = "sdk-composed/sonnet"; + agentModel = selectedModel; + catalog = { + providers: [ + { + name: "sdk-composed", + type: provider.type, + wireApi: provider.wireApi, + baseUrl: provider.baseUrl, + bearerToken: provider.bearerToken, + }, + ], + models: [ + { + id: "sonnet", + provider: "sdk-composed", + modelId: "claude-sonnet-5", + wireModel: "claude-sonnet-5", + name: "SDK Claude Sonnet 5", + }, + ], + }; + } const session = await recordingClient.createSession({ - model: "claude-sonnet-5", + ...catalog, + model: selectedModel, reasoningEffort: "medium", onPermissionRequest: approveAll, hooks: { @@ -455,8 +573,7 @@ describe("Session tasks RPC and pending handlers", async () => { name: "sdk-display-model-agent", description: "SDK display-name model coverage", prompt: "Reply with SDK_DISPLAY_AGENT_DONE exactly.", - // BYOK requires explicit IDs; CAPI also resolves its catalog's display names. - model: `${isByokBackend ? "claude-sonnet-5" : "Claude Sonnet 5"}:defaultReasoningEffort=high`, + model: `${agentModel}:defaultReasoningEffort=high`, }, ], }); @@ -507,12 +624,19 @@ describe("Session tasks RPC and pending handlers", async () => { reasoningEffort: "high", }); expect(configuration.agentId).toBeTruthy(); - const requests = requestRecorder.requests - .filter((request) => request.agentId === configuration.agentId) - .map((request) => request.body); + const requests = requestRecorder.requests.filter( + (request) => request.agentId === configuration.agentId + ); expect(requests.length).toBeGreaterThan(0); for (const request of requests) { - expect(request).toMatchObject({ + const expectedPaths = + testBackend === "anthropic-messages" + ? ["/v1/messages", "/messages"] + : testBackend === "openai-responses" + ? ["/responses"] + : ["/chat/completions"]; + expect(expectedPaths).toContain(request.path); + expect(request.body).toMatchObject({ model: "claude-sonnet-5", reasoning_effort: "high", }); diff --git a/nodejs/test/e2e/rpc_workspace_checkpoints.e2e.test.ts b/nodejs/test/e2e/rpc_workspace_checkpoints.e2e.test.ts index 83ba34434c..91bd822a71 100644 --- a/nodejs/test/e2e/rpc_workspace_checkpoints.e2e.test.ts +++ b/nodejs/test/e2e/rpc_workspace_checkpoints.e2e.test.ts @@ -62,10 +62,11 @@ describe("Session workspace checkpoint RPC", async () => { ); writeFileSync(join(gitWorkDir, modifiedName), "after"); unlinkSync(join(gitWorkDir, deletedName)); - const gitClient = createClient({ workingDirectory: gitWorkDir }); + const gitClient = createClient(); try { const session = await gitClient.createSession({ onPermissionRequest: approveAll, + workingDirectory: gitWorkDir, }); try { const result = await session.rpc.workspaces.diff({ mode }); diff --git a/nodejs/test/e2e/sandbox_network.e2e.test.ts b/nodejs/test/e2e/sandbox_network.e2e.test.ts index 9e476d4aff..0a78088696 100644 --- a/nodejs/test/e2e/sandbox_network.e2e.test.ts +++ b/nodejs/test/e2e/sandbox_network.e2e.test.ts @@ -5,6 +5,7 @@ import { createServer } from "node:http"; import { describe, it } from "vitest"; import type { CopilotSession } from "../../src/index.js"; +import { getRuntimePlatform } from "../../src/runtimeArtifacts.js"; import { createSdkTestContext } from "./harness/sdkTestContext.js"; const TEST_NAME = @@ -17,6 +18,25 @@ describe("Sandbox network", async () => { }, }); + it("reports the shipped platform's sandbox support and refusal", async ({ expect }) => { + await client.start(); + const support = await client.rpc.sandbox.getHostSupport(); + if (getRuntimePlatform().startsWith("linuxmusl-")) { + expect(support).toEqual({ + supported: false, + reason: "Sandboxed shell execution is not supported on Alpine Linux/musl.", + capabilities: [], + }); + } else if (support.supported) { + expect(support.reason).toBeUndefined(); + expect(support.capabilities).toContainEqual({ name: "shell", supported: true }); + } else { + expect(support.reason).toEqual(expect.any(String)); + expect(support.reason).not.toBe(""); + expect(support.capabilities).toEqual([]); + } + }); + it(TEST_NAME, async ({ expect, onTestFinished, skip }) => { await client.start(); const support = await client.rpc.sandbox.getHostSupport(); diff --git a/nodejs/test/e2e/session_fs.e2e.test.ts b/nodejs/test/e2e/session_fs.e2e.test.ts index 61d15b9b26..963f80aae9 100644 --- a/nodejs/test/e2e/session_fs.e2e.test.ts +++ b/nodejs/test/e2e/session_fs.e2e.test.ts @@ -4,12 +4,13 @@ import { SessionCompactionCompleteEvent } from "@github/copilot/sdk"; import { MemoryProvider, VirtualProvider } from "@platformatic/vfs"; -import { createHash } from "crypto"; +import { createHash, randomUUID } from "crypto"; import { existsSync, mkdtempSync, realpathSync } from "fs"; -import { mkdir, readFile, rm, writeFile } from "fs/promises"; +import { mkdir, readFile, readdir, rm, writeFile } from "fs/promises"; import { createServer } from "http"; import { tmpdir } from "os"; import { join } from "path"; +import { fileURLToPath } from "url"; import { describe, expect, it, onTestFinished, vi } from "vitest"; import { CopilotClient } from "../../src/client.js"; import { getRuntimePlatform } from "../../src/runtimeArtifacts.js"; @@ -51,6 +52,7 @@ describe("Session Fs", async () => { createClient, env, openAiEndpoint, + workDir, } = await createSdkTestContext({ copilotClientOptions: { sessionFs: sessionFsConfig }, }); @@ -179,6 +181,131 @@ describe("Session Fs", async () => { expect(msg2?.data.content).toContain("300"); }); + it("should roll context through native tools.execute from an active SDK handler", async () => { + await openAiEndpoint.updateConfig({ + filePath: fileURLToPath( + new URL( + "../../../test/snapshots/session_fs/should_route_file_operations_through_the_session_fs_provider.yaml", + import.meta.url + ) + ), + workDir, + replayOnly: true, + }); + const original = await client.createSession({ + onPermissionRequest: approveAll, + createSessionFsProvider, + featureFlags: { CONTEXT_MANAGEMENT_TOOLS: false }, + }); + const sessionId = original.sessionId; + try { + expect( + (await original.sendAndWait({ prompt: "What is 100 + 200?" }))?.data.content + ).toContain("300"); + } finally { + await original.disconnect(); + } + + // Resume a valid large user event so a fresh window can reclaim context. + const request: SessionEvent = { + id: randomUUID(), + timestamp: new Date().toISOString(), + parentId: null, + type: "user.message", + data: { + content: + "Preserve SDK rollover state. " + "Rollover fixture context. ".repeat(3_000), + source: "user", + }, + }; + await provider.appendFile( + p(sessionId, `${sessionStatePath}/events.jsonl`), + JSON.stringify(request) + "\n" + ); + + let resumed: CopilotSession; + let innerResult: Awaited> | undefined; + resumed = await client.resumeSession(sessionId, { + onPermissionRequest: approveAll, + createSessionFsProvider, + featureFlags: { CONTEXT_MANAGEMENT_TOOLS: true }, + continuePendingWork: false, + tools: [ + defineTool("sdk_context_rollover", { + description: "Roll context from an active SDK tool handler", + isTerminal: true, + defer: "never", + handler: async () => { + innerResult = await resumed.rpc.tools.execute({ + name: "new_context", + arguments: {}, + toolCallId: "sdk-context-rollover-native", + }); + return JSON.stringify(innerResult); + }, + }), + ], + }); + try { + await resumed.rpc.tools.initializeAndValidate(); + expect((await resumed.rpc.tools.getCurrentMetadata()).tools).toEqual( + expect.arrayContaining([expect.objectContaining({ name: "new_context" })]) + ); + const remaining = await resumed.rpc.tools.execute({ + name: "get_context_remaining", + arguments: {}, + }); + expect(remaining, JSON.stringify(remaining)).toMatchObject({ resultType: "success" }); + expect(JSON.parse(remaining.textResultForLlm)).toMatchObject({ + context_window: { number: 1, previous_window_id: null }, + }); + const checkpoint = await resumed.rpc.tools.execute({ + name: "session_artifacts", + arguments: { + operation: "append", + path: "checkpoint.md", + content: "\nPreserve SDK rollover state.", + }, + }); + expect(checkpoint, JSON.stringify(checkpoint)).toMatchObject({ resultType: "success" }); + + const outerResult = await resumed.rpc.tools.execute({ + name: "sdk_context_rollover", + arguments: {}, + toolCallId: "sdk-context-rollover-handler", + }); + expect(outerResult, JSON.stringify(outerResult)).toMatchObject({ + resultType: "success", + }); + expect(innerResult, JSON.stringify(innerResult)).toMatchObject({ + resultType: "success", + }); + await expect + .poll(async () => + (await resumed.getEvents()).filter( + (event) => event.type === "session.context_cleared" + ) + ) + .toEqual([ + expect.objectContaining({ + data: expect.objectContaining({ + initialMessage: expect.stringContaining("Preserve SDK rollover state."), + }), + }), + ]); + const next = await resumed.rpc.tools.execute({ + name: "get_context_remaining", + arguments: {}, + }); + expect(next, JSON.stringify(next)).toMatchObject({ resultType: "success" }); + expect(JSON.parse(next.textResultForLlm)).toMatchObject({ + context_window: { number: 2, previous_window_id: expect.any(String) }, + }); + } finally { + await resumed.disconnect(); + } + }); + it("should reject setProvider when sessions already exist", async () => { const tcpConnectionToken = "session-fs-test-token"; const client = new CopilotClient({ @@ -307,6 +434,91 @@ describe("Session Fs", async () => { }); }); +describe("Session Fs native plan edits", async () => { + const { createClient, workDir } = await createSdkTestContext(); + + it.each(["interactive", "plan"] as const)( + "keeps native edits and write failures in the provider in %s mode", + async (mode) => { + const statePath = join(workDir, `provider-state-${mode}`); + const planPath = join(statePath, "plan.md"); + const provider = new MemoryProvider(); + const writes: string[] = []; + let rejectWrites = false; + const client = createClient({ + mode: "empty", + sessionFs: { + initialCwd: workDir, + sessionStatePath: statePath, + conventions: process.platform === "win32" ? "windows" : "posix", + }, + }); + onTestFinished(() => client.stop()); + const session = await client.createSession({ + onPermissionRequest: approveAll, + availableTools: ["edit"], + createSessionFsProvider: (session) => { + const handler = createTestSessionFsHandler(session, provider); + return { + ...handler, + async writeFile(path, content) { + if (path === planPath) { + writes.push(content); + if (rejectWrites) { + throw new Error("provider write denied"); + } + } + return handler.writeFile(path, content); + }, + }; + }, + }); + await session.rpc.mode.set({ mode }); + await session.rpc.plan.update({ content: "# Provider plan\n" }); + expect(await session.rpc.plan.read()).toMatchObject({ + path: planPath, + content: "# Provider plan\n", + }); + + // A writable host parent makes an accidental host write succeed. + await mkdir(statePath, { recursive: true }); + expect(await readdir(statePath)).toEqual([]); + const edit = (old_str: string, new_str: string) => + session.rpc.tools.execute({ + name: "edit", + arguments: { path: planPath, old_str, new_str }, + }); + await expect(edit("Provider plan", "Native update")).resolves.toMatchObject({ + resultType: "success", + }); + expect((await session.rpc.plan.read()).content).toBe("# Native update\n"); + expect(writes).toEqual(["# Provider plan\n", "# Native update\n"]); + expect(await readdir(statePath)).toEqual([]); + + await writeFile(planPath, "host-only content\n"); + await expect(edit("Native update", "Second update")).resolves.toMatchObject({ + resultType: "success", + }); + expect((await session.rpc.plan.read()).content).toBe("# Second update\n"); + expect(await readFile(planPath, "utf8")).toBe("host-only content\n"); + + rejectWrites = true; + const failed = await edit("Second update", "Rejected update"); + expect(failed.resultType).toBe("failure"); + expect(failed.textResultForLlm).toContain("provider write denied"); + expect((await session.rpc.plan.read()).content).toBe("# Second update\n"); + expect(writes).toEqual([ + "# Provider plan\n", + "# Native update\n", + "# Second update\n", + "# Rejected update\n", + ]); + expect(await readFile(planPath, "utf8")).toBe("host-only content\n"); + expect(await readdir(statePath)).toEqual(["plan.md"]); + } + ); +}); + describe("Session Fs remembered approvals", async () => { const workingDirectory = "/workspace"; const otherDirectory = "/provider-dir"; diff --git a/nodejs/test/e2e/skill_provider.e2e.test.ts b/nodejs/test/e2e/skill_provider.e2e.test.ts index 82d584809c..e8f2560625 100644 --- a/nodejs/test/e2e/skill_provider.e2e.test.ts +++ b/nodejs/test/e2e/skill_provider.e2e.test.ts @@ -6,10 +6,11 @@ import { execFileSync } from "child_process"; import * as fs from "fs"; import * as path from "path"; import { fileURLToPath } from "url"; -import { describe, expect, it } from "vitest"; +import { describe, expect, it, onTestFinished } from "vitest"; import { approveAll, RuntimeConnection, + type SessionConfig, type SessionEvent, type SkillProvider, type SkillProviderDescriptor, @@ -50,7 +51,12 @@ function skill(name: string, description: string, markdown: string): ProvidedSki } describe("Skill providers", async () => { - const { copilotClient: client, workDir } = await createSdkTestContext(); + const { + copilotClient: client, + createClient, + openAiEndpoint, + workDir, + } = await createSdkTestContext(); it("should load provider skill lazily through skill tool", async () => { // Body-only content: the catalog descriptor supplies all of the metadata. @@ -175,6 +181,83 @@ describe("Skill providers", async () => { } }); + it("should preserve skill prompt bytes across cold resume with reordered discovery", async () => { + const directories = ["custom-b", "custom-a"].map((name) => { + const directory = path.join(workDir, name); + fs.mkdirSync(directory, { recursive: true }); + fs.writeFileSync( + path.join(directory, "SKILL.md"), + `---\nname: ${name}\ndescription: An ordering fixture.\n---\nOrdering fixture.\n` + ); + return directory; + }); + const provided = ["provider-c", "provider-a", "provider-b"].map((name) => + skill(name, "An ordering fixture.", "Ordering fixture.\n") + ); + const config = { + model: "claude-sonnet-5", + onPermissionRequest: approveAll, + availableTools: ["skill"], + enableConfigDiscovery: false, + includedBuiltinSkills: [], + skipCustomInstructions: true, + skipEmbeddingRetrieval: true, + } satisfies SessionConfig; + const first = createClient({ connection: RuntimeConnection.forStdio() }); + onTestFinished(() => first.stop()); + const session = await first.createSession({ + ...config, + skillProvider: new TestSkillProvider(provided), + skillDirectories: directories, + }); + const response = await session.sendAndWait({ + prompt: "Without using tools, reply with exactly SKILL_ORDER_INITIAL_OK.", + }); + // Validate the final assistant response arrived (guards against truncated captures) + expect(response?.data.content).toContain("SKILL_ORDER_INITIAL_OK"); + const beforeRequests = await openAiEndpoint.getRequests(); + const beforeBlocks = beforeRequests.flatMap( + (request) => + request.body.match(/\\n[\s\S]*?<\/available_skills>/g) ?? [] + ); + expect(beforeBlocks.length).toBeGreaterThan(0); + for (const block of beforeBlocks) { + for (const name of [ + ...provided.map((entry) => entry.descriptor.name), + "custom-a", + "custom-b", + ]) { + expect(block).toContain(`${name}`); + } + expect(block.indexOf("provider-c")).toBeLessThan( + block.indexOf("custom-a") + ); + } + await session.disconnect(); + expect(await first.stop()).toHaveLength(0); + + const second = createClient({ connection: RuntimeConnection.forStdio() }); + onTestFinished(() => second.stop()); + const resumed = await second.resumeSession(session.sessionId, { + ...config, + skillProvider: new TestSkillProvider([provided[2], provided[0], provided[1]]), + skillDirectories: [...directories].reverse(), + }); + const resumedResponse = await resumed.sendAndWait({ + prompt: "Without using tools, reply with exactly SKILL_ORDER_RESUMED_OK.", + }); + // Validate the final assistant response arrived (guards against truncated captures) + expect(resumedResponse?.data.content).toContain("SKILL_ORDER_RESUMED_OK"); + const afterBlocks = (await openAiEndpoint.getRequests()) + .slice(beforeRequests.length) + .flatMap( + (request) => + request.body.match(/\\n[\s\S]*?<\/available_skills>/g) ?? [] + ); + expect(afterBlocks).toEqual(beforeBlocks); + await resumed.disconnect(); + }); + it("should report provider read failure without leaking details", async () => { const secret = "PROVIDER_SECRET_7F3A9C"; const provider = new TestSkillProvider([ diff --git a/nodejs/test/e2e/startup_policy.e2e.test.ts b/nodejs/test/e2e/startup_policy.e2e.test.ts new file mode 100644 index 0000000000..330cf3c44b --- /dev/null +++ b/nodejs/test/e2e/startup_policy.e2e.test.ts @@ -0,0 +1,33 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +import { describe, expect, it } from "vitest"; +import { createSdkTestContext } from "./harness/sdkTestContext.js"; + +describe("Sandbox proxy CA policy", async () => { + const { copilotClient: client } = await createSdkTestContext(); + + it("reports relevance without installation or caller config changes", async () => { + await client.start(); + const configured = { sandboxConfig: { enabled: true, auth: { gh: true } } }; + const before = structuredClone(configured); + expect(await client.rpc.sandbox.proxyCa.checkRelevance(configured)).toBe(true); + expect(configured).toEqual(before); + expect( + await client.rpc.sandbox.proxyCa.checkRelevance({ + sandboxConfig: { enabled: false, auth: { gh: true } }, + }) + ).toBe(false); + expect(await client.rpc.sandbox.proxyCa.checkRelevance({})).toBe(false); + }); + + it("reports installation support without an elevation prompt", async () => { + await client.start(); + const supported = await client.rpc.sandbox.proxyCa.checkInstallationSupport(); + expect(typeof supported).toBe("boolean"); + if (process.platform !== "win32") { + expect(supported).toBe(true); + } + }); +}); diff --git a/nodejs/test/e2e/structured_output.e2e.test.ts b/nodejs/test/e2e/structured_output.e2e.test.ts index ae08a9d16b..fbc84ab261 100644 --- a/nodejs/test/e2e/structured_output.e2e.test.ts +++ b/nodejs/test/e2e/structured_output.e2e.test.ts @@ -44,7 +44,7 @@ describe("Structured output", async () => { it("infers_typed_result_after_custom_tool", async () => { let calls = 0; const schema = z.object({ count: z.number().int(), color: z.string() }); - const session = await client.createSession({ + await using session = await client.createSession({ model, provider, onPermissionRequest: approveAll, @@ -89,8 +89,7 @@ describe("Structured output", async () => { it("typed_result_after_terminal_tool_and_steering", async () => { const events: SessionEvent[] = []; let calls = 0; - let session: CopilotSession; - session = await client.createSession({ + await using session: CopilotSession = await client.createSession({ model, provider, onPermissionRequest: approveAll, @@ -146,7 +145,7 @@ describe("Structured output", async () => { let calls = 0; let stops = 0; const replies: AssistantMessageEvent[] = []; - const session = await client.createSession({ + await using session = await client.createSession({ model, provider, onPermissionRequest: approveAll, @@ -204,7 +203,7 @@ describe("Structured output", async () => { it("rejects_unsupported_or_oversized_schemas_before_admission", async () => { for (const model of ["gpt-4.1", "hydrafusion"]) { - const session = await client.createSession({ + await using session = await client.createSession({ model, provider, ...(model === "hydrafusion" @@ -258,7 +257,7 @@ describe("Structured output", async () => { const hookReleased = new Promise((resolve) => { releaseHook = resolve; }); - const session = await client.createSession({ + await using session = await client.createSession({ model, provider, onPermissionRequest: approveAll, @@ -279,14 +278,20 @@ describe("Structured output", async () => { }, }); const replies: AssistantMessageEvent[] = []; - const errors: string[] = []; + const errors: "session.error"[] = []; + let errorCount = 0; + const startedAt = Date.now(); + const progress: { type: SessionEvent["type"]; elapsedMs: number }[] = []; let idle = false; const unsubscribe = session.on((event) => { if (event.agentId) return; + progress.push({ type: event.type, elapsedMs: Date.now() - startedAt }); + if (progress.length > 20) progress.shift(); if (event.type === "assistant.message") { replies.push(event); } else if (event.type === "session.error") { - errors.push(event.data.message); + errorCount++; + if (errors.length === 0) errors.push("session.error"); } else if (event.type === "session.idle") { idle = true; } @@ -320,6 +325,24 @@ describe("Structured output", async () => { expect(replies.some((event) => event.data.toolRequests?.length)).toBe(true); expect(reply.data.toolRequests ?? []).toEqual([]); expect(replies.at(-1)).toBe(reply); + } catch (error) { + console.error( + "Structured-output stop-hook diagnostics:", + JSON.stringify({ + sessionId: session.sessionId, + hookEntered, + idle, + errorCount, + errorCategories: errors, + progress, + replies: replies.slice(-20).map((reply) => ({ + originatingMessageId: reply.data.originatingMessageId, + toolRequestCount: reply.data.toolRequests?.length ?? 0, + contentLength: reply.data.content.length, + })), + }) + ); + throw error; } finally { releaseHook(); unsubscribe(); @@ -330,7 +353,7 @@ describe("Structured output", async () => { let stops = 0; const replies: AssistantMessageEvent[] = []; const schema = z.object({ answer: z.number().int() }); - const session = await client.createSession({ + await using session = await client.createSession({ model, provider, onPermissionRequest: approveAll, @@ -370,10 +393,9 @@ describe("Structured output", async () => { it("typed_wait_returns_late_steering_response", async () => { let stops = 0; let steeringId: string | undefined; - let session: CopilotSession; const replies: AssistantMessageEvent[] = []; const schema = z.object({ answer: z.number().int() }); - session = await client.createSession({ + await using session: CopilotSession = await client.createSession({ model, provider, onPermissionRequest: approveAll, @@ -423,7 +445,7 @@ describe("Structured output", async () => { const toolReleased = new Promise((resolve) => { releaseTool = resolve; }); - const session = await client.createSession({ + await using session = await client.createSession({ model, provider, onPermissionRequest: approveAll, @@ -480,7 +502,7 @@ describe("Structured output", async () => { }); it("sends_explicit_schema_for_message_and_batch", async () => { - const session = await client.createSession({ + await using session = await client.createSession({ model, provider, onPermissionRequest: approveAll, diff --git a/nodejs/test/e2e/subagent_hooks.e2e.test.ts b/nodejs/test/e2e/subagent_hooks.e2e.test.ts index 818560bd7f..84aff60450 100644 --- a/nodejs/test/e2e/subagent_hooks.e2e.test.ts +++ b/nodejs/test/e2e/subagent_hooks.e2e.test.ts @@ -2,7 +2,7 @@ * Copyright (c) Microsoft Corporation. All rights reserved. *--------------------------------------------------------------------------------------------*/ -import { writeFile } from "fs/promises"; +import { copyFile, mkdir, readFile, readdir, writeFile } from "fs/promises"; import { randomUUID } from "node:crypto"; import { join } from "path"; import { describe, expect, it } from "vitest"; @@ -98,7 +98,11 @@ describe("Subagent hooks", async () => { // For snapshot recording (non-CI), use RECORD_GH_TOKEN if available const recordToken = !isCI ? process.env.RECORD_GH_TOKEN : undefined; const requestHandler = new RecordingRequestHandler(); - const { copilotClient: client, workDir } = await createSdkTestContext({ + const { + copilotClient: client, + workDir, + env, + } = await createSdkTestContext({ copilotClientOptions: { ...(recordToken ? { gitHubToken: recordToken } : {}), requestHandler, @@ -109,7 +113,12 @@ describe("Subagent hooks", async () => { it( "should apply subagent lifecycle hook outputs", async () => { - const hookLog: { kind: "pre" | "post"; toolName: string; sessionId: string }[] = []; + const hookLog: { + kind: "pre" | "post"; + toolName: string; + sessionId: string; + input: PreToolUseHookInput | PostToolUseHookInput; + }[] = []; const startInputs: { input: SubagentStartHookInput; invocationSessionId: string }[] = []; const stopInputs: { input: SubagentStopHookInput; invocationSessionId: string }[] = []; @@ -118,6 +127,43 @@ describe("Subagent hooks", async () => { const finalText = "The explore agent successfully read the file. The contents of **subagent-test.txt** are:\n\n```\nHello from subagent test!\n```"; const parentSessionId = randomUUID(); + const capturesDir = join(workDir, "hook-inputs"); + const hooksDir = join(workDir, ".github", "hooks"); + await mkdir(capturesDir); + await mkdir(hooksDir, { recursive: true }); + await mkdir(env.COPILOT_HOME, { recursive: true }); + await writeFile( + join(env.COPILOT_HOME, "config.json"), + JSON.stringify({ trustedFolders: [workDir] }) + ); + await copyFile( + new URL("./fixtures/hook-identity-recorder.cjs", import.meta.url), + join(workDir, "hook-recorder.cjs") + ); + const repositoryHooks: Record = {}; + for (const [native, claude] of [ + ["preToolUse", "PreToolUse"], + ["postToolUse", "PostToolUse"], + ["subagentStart", "SubagentStart"], + ["subagentStop", "SubagentStop"], + ]) { + for (const [event, dialect] of [ + [native, "native"], + [claude, "claude"], + ]) { + repositoryHooks[event] = [ + { + type: "command", + exec: process.execPath, + args: ["hook-recorder.cjs", `${native}.${dialect}`, capturesDir], + }, + ]; + } + } + await writeFile( + join(hooksDir, "identity.json"), + JSON.stringify({ version: 1, hooks: repositoryHooks }) + ); let releaseView!: () => void; const parentWaiting = new Promise((resolve) => { releaseView = resolve; @@ -132,6 +178,7 @@ describe("Subagent hooks", async () => { kind: "pre", toolName: input.toolName, sessionId: input.sessionId, + input, }); return { permissionDecision: "allow" } as PreToolUseHookOutput; }, @@ -140,6 +187,7 @@ describe("Subagent hooks", async () => { kind: "post", toolName: input.toolName, sessionId: input.sessionId, + input, }); // A fast child can inject its result before the parent ever asks for the fixture's waiting reply. if (input.toolName === "view" && input.sessionId !== parentSessionId) @@ -238,6 +286,23 @@ describe("Subagent hooks", async () => { expect(stop!.input.agentDescription).toBe(start!.input.agentDescription); expect(stop!.input.agentId).toBeTruthy(); expect(stop!.input.agentType).toBe("explore"); + expect(start!.input).toMatchObject({ + agentId: stop!.input.agentId, + agentType: "explore", + parentAgentId: session.sessionId, + }); + for (const { input } of [...viewPre, ...viewPost]) { + expect(input).toMatchObject({ + agentId: stop!.input.agentId, + agentType: "explore", + parentAgentId: session.sessionId, + }); + } + for (const { input } of hookLog.filter((hook) => hook.sessionId === parentSessionId)) { + expect(input).not.toHaveProperty("agentId"); + expect(input).not.toHaveProperty("agentType"); + expect(input).not.toHaveProperty("parentAgentId"); + } expect(stop!.input.stopReason).toBe("end_turn"); expect(stop!.input.response).toContain("Hello from subagent test!"); @@ -252,6 +317,103 @@ describe("Subagent hooks", async () => { timeout: SUBAGENT_TEST_TIMEOUT_MS, }) .toBeGreaterThan(0); + const capturedInputs = await Promise.all( + (await readdir(capturesDir)).map(async (name) => { + return JSON.parse(await readFile(join(capturesDir, name), "utf8")) as { + hook: string; + input: Record; + }; + }) + ); + for (const dialect of ["native", "claude"]) { + const [idKey, typeKey, parentKey, toolKey, childTool, parentTool] = + dialect === "native" + ? ["agentId", "agentType", "parentAgentId", "toolName", "view", "task"] + : [ + "agent_id", + "agent_type", + "parent_agent_id", + "tool_name", + "Read", + "Agent", + ]; + for (const event of ["preToolUse", "postToolUse"]) { + const inputs = capturedInputs.filter( + (record) => record.hook === `${event}.${dialect}` + ); + const childInputs = inputs.filter( + (record) => record.input[toolKey] === childTool + ); + const parentInputs = inputs.filter( + (record) => record.input[toolKey] === parentTool + ); + expect( + childInputs, + `${event}/${dialect} must run in the child` + ).not.toHaveLength(0); + expect( + parentInputs, + `${event}/${dialect} must run in the parent` + ).not.toHaveLength(0); + for (const { input } of childInputs) { + expect(input).toMatchObject({ + [idKey]: stop!.input.agentId, + [typeKey]: "explore", + [parentKey]: parentSessionId, + }); + } + for (const { input } of parentInputs) { + expect(input).not.toHaveProperty(idKey); + expect(input).not.toHaveProperty(typeKey); + expect(input).not.toHaveProperty(parentKey); + } + } + for (const event of ["subagentStart", "subagentStop"]) { + const inputs = capturedInputs.filter( + (record) => record.hook === `${event}.${dialect}` + ); + expect(inputs, `${event}/${dialect} must describe the one child`).toHaveLength( + 1 + ); + expect(inputs[0].input).toMatchObject({ + [idKey]: stop!.input.agentId, + [typeKey]: "explore", + [parentKey]: parentSessionId, + }); + expect(inputs[0].input.sessionId ?? inputs[0].input.session_id).toBe( + parentSessionId + ); + } + } + const transcript = await session.getEvents(); + const hookStarts = transcript.filter( + (event) => + event.type === "hook.start" && + ["preToolUse", "postToolUse", "subagentStart", "subagentStop"].includes( + event.data.hookType + ) + ); + expect( + hookStarts.some( + (event) => + event.agentId === stop!.input.agentId && + event.data.hookType === "preToolUse" + ), + "the transcript must attribute child hooks to the same stable identity" + ).toBe(true); + for (const start of hookStarts) { + const end = transcript.find( + (event) => + event.type === "hook.end" && + event.data.hookInvocationId === start.data.hookInvocationId + ); + expect( + end, + `hook ${start.data.hookInvocationId} must have completed` + ).toMatchObject({ + data: { success: true }, + }); + } await session.disconnect(); }, SUBAGENT_TEST_TIMEOUT_MS diff --git a/nodejs/test/e2e/subagent_prompt_resume.e2e.test.ts b/nodejs/test/e2e/subagent_prompt_resume.e2e.test.ts index 2e82470012..62faad1221 100644 --- a/nodejs/test/e2e/subagent_prompt_resume.e2e.test.ts +++ b/nodejs/test/e2e/subagent_prompt_resume.e2e.test.ts @@ -2,7 +2,7 @@ * Copyright (c) Microsoft Corporation. All rights reserved. *--------------------------------------------------------------------------------------------*/ -import { writeFile } from "node:fs/promises"; +import { readFile, writeFile } from "node:fs/promises"; import { dirname, join, resolve } from "node:path"; import { fileURLToPath } from "node:url"; import { describe, expect, it } from "vitest"; @@ -11,19 +11,24 @@ import type { CopilotSession, CustomAgentConfig, MCPStdioServerConfig, + SessionEvent, } from "../../src/index.js"; import { approveAll, CopilotRequestHandler } from "../../src/index.js"; import { createSdkTestContext } from "./harness/sdkTestContext.js"; import { waitForCondition } from "./harness/sdkTestHelper.js"; +import { isByokBackend } from "./harness/testBackend.js"; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); const TEST_MCP_SERVER = resolve(__dirname, "../../../test/harness/test-mcp-server.mjs"); const TEST_HARNESS_DIR = dirname(TEST_MCP_SERVER); +const capiIt = it.skipIf(isByokBackend); interface InferenceRequest { agentId?: string; parentAgentId?: string; + model: string; + reasoningEffort?: string; systemPrompt: string; toolNames: string[]; } @@ -37,9 +42,16 @@ class RecordingRequestHandler extends CopilotRequestHandler { ): Promise { const body = request.body ? await request.text() : ""; if (isInferenceUrl(request.url)) { + const inference = JSON.parse(body) as { + model: string; + reasoning_effort?: string; + reasoning?: { effort?: string }; + }; this.inferenceRequests.push({ agentId: ctx.agentId, parentAgentId: ctx.parentAgentId, + model: inference.model, + reasoningEffort: inference.reasoning_effort ?? inference.reasoning?.effort, systemPrompt: systemPromptFromRequest(request.url, body), toolNames: toolNamesFromRequest(body), }); @@ -122,6 +134,32 @@ function bootstrapResponse(url: string): Response { }, }, }, + { + id: "gpt-5.4-mini", + name: "GPT-5.4 Mini", + object: "model", + vendor: "OpenAI", + version: "1", + preview: false, + model_picker_enabled: true, + supported_endpoints: ["/chat/completions"], + capabilities: { + type: "chat", + family: "gpt-5.4-mini", + 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, + reasoning_effort: ["low", "medium", "high"], + }, + }, + }, ], }); } @@ -211,7 +249,11 @@ describe("Subagent prompt resume", async () => { const { copilotClient: client, env } = await createSdkTestContext({ copilotClientOptions: { requestHandler, - env: { COPILOT_EXP_COPILOT_CLI_SESSION_BASED_SUBAGENTS: "true" }, + env: { + COPILOT_EXP_COPILOT_CLI_SESSION_BASED_SUBAGENTS: "true", + STABILITY_ORDERED_SYSTEM_PROMPT_V2: "false", + COPILOT_EXP_COPILOT_CLI_STABILITY_ORDERED_SYSTEM_PROMPT_V2: "false", + }, }, }); @@ -225,14 +267,22 @@ describe("Subagent prompt resume", async () => { }, { timeoutMessage: `Agent ${agentId} did not become idle.` } ); + const task = (await session.rpc.tasks.list()).tasks.find( + (candidate) => candidate.id === agentId + ); + if (task?.type !== "agent") { + throw new Error(`Agent task ${agentId} is missing or has the wrong type.`); + } + expect(task.latestResponse ?? task.result).toContain("SUBAGENT_TURN_COMPLETE"); } async function runResumeScenario( trigger: (session: CopilotSession) => Promise, createOptions: Parameters[0] = {}, beforeAgentStart: (session: CopilotSession) => Promise = async () => {}, - agentType = "explore" - ): Promise<[InferenceRequest, InferenceRequest]> { + agentType = "explore", + agentModel: string | null = "claude-sonnet-5" + ): Promise<[InferenceRequest, InferenceRequest, string]> { await writeFile(join(env.COPILOT_HOME, "settings.json"), "{}"); const requestBaseline = requestHandler.inferenceRequests.length; const session = await client.createSession({ @@ -248,7 +298,7 @@ describe("Subagent prompt resume", async () => { prompt: "Reply with exactly SUBAGENT_TURN_COMPLETE.", name: "prompt-resume", description: "Exercise an idle subagent follow-up.", - model: "claude-sonnet-5", + ...(agentModel ? { model: agentModel } : {}), }); await waitForIdleAgent(session, agentId); @@ -270,12 +320,13 @@ describe("Subagent prompt resume", async () => { }, { timeoutMessage: "Agent did not complete its follow-up turn." } ); + await waitForIdleAgent(session, agentId); const childRequests = requestHandler.inferenceRequests .slice(requestBaseline) .filter((request) => request.agentId === agentId && request.parentAgentId); expect(childRequests).toHaveLength(2); - return [childRequests[0]!, childRequests[1]!]; + return [childRequests[0]!, childRequests[1]!, session.sessionId]; } finally { const tasks = await session.rpc.tasks.list(); for (const task of tasks.tasks) { @@ -311,12 +362,155 @@ describe("Subagent prompt resume", async () => { } it("preserves the adapted prompt on an ordinary idle-agent wake", async () => { - const [initial, resumed] = await runResumeScenario(async () => {}); + const [initial, resumed, sessionId] = await runResumeScenario(async () => {}); expectAdaptedExplorePrompt(initial.systemPrompt); expect(resumed.systemPrompt).toBe(initial.systemPrompt); + const journal = await readFile( + join(env.COPILOT_HOME, "session-state", sessionId, "events.jsonl"), + "utf8" + ); + const events = journal + .trim() + .split("\n") + .map((line) => JSON.parse(line) as SessionEvent); + const childEvents = events.filter((event) => event.agentId === initial.agentId); + const childPrompts = childEvents.filter((event) => event.type === "system.message"); + expect(childPrompts).toHaveLength(1); + expect( + childEvents + .filter((event) => event.type === "assistant.message") + .map((event) => event.data.content) + ).toEqual(["SUBAGENT_TURN_COMPLETE", "SUBAGENT_TURN_COMPLETE"]); + const ids = new Set(events.map((event) => event.id)); + for (const event of events) { + if (event.parentId) { + expect(ids.has(event.parentId), `Missing parent of ${event.id}`).toBe(true); + } + } + await using reopened = await client.resumeSession(sessionId, { + model: "claude-sonnet-5", + onPermissionRequest: approveAll, + }); + const history = await reopened.getEvents(); + expect(history.find((event) => event.id === childPrompts[0]!.id)).toEqual(childPrompts[0]); + }); + + it("keeps legacy prompt blocks live while persisting only their reconstructible content", async () => { + const content = + "sharedlimits\n\nper-user instructions"; + const systemMessage = { + mode: "replace" as const, + content, + contentBlocks: [ + { + content: "sharedlimits", + isStatic: true, + }, + { content: "per-user instructions" }, + ], + }; + await using session = await client.createSession({ + model: "claude-sonnet-5", + systemMessage, + onPermissionRequest: approveAll, + }); + const livePrompts: SessionEvent[] = []; + const unsubscribe = session.on("system.message", (event) => livePrompts.push(event)); + try { + const response = await session.sendAndWait({ + prompt: "Reply with exactly SUBAGENT_TURN_COMPLETE.", + }); + expect(response?.data.content).toBe("SUBAGENT_TURN_COMPLETE"); + } finally { + unsubscribe(); + } + await session.disconnect(); + const live = livePrompts.find((event) => event.type === "system.message"); + expect(live?.type).toBe("system.message"); + if (live?.type !== "system.message") { + throw new Error("Expected a live system prompt."); + } + expect(live.data.content).toBe(content); + expect(live.data.contentBlocks).toEqual(systemMessage.contentBlocks); + const journal = await readFile( + join(env.COPILOT_HOME, "session-state", session.sessionId, "events.jsonl"), + "utf8" + ); + const persisted = journal + .trim() + .split("\n") + .map((line) => JSON.parse(line) as SessionEvent) + .find((event) => event.id === live.id); + expect(persisted?.type).toBe("system.message"); + if (persisted?.type !== "system.message") { + throw new Error("Expected the persisted system prompt."); + } + expect(persisted.data.content).toBe(content); + expect(persisted.data.contentBlocks).toBeUndefined(); + await using reopened = await client.resumeSession(session.sessionId, { + model: "claude-sonnet-5", + onPermissionRequest: approveAll, + }); + const history = await reopened.getEvents(); + expect(history.find((event) => event.id === live.id)).toEqual(persisted); }); + capiIt.each(["minimal", "max", "xhigh", "none", "high"])( + "reconciles a concrete child's composed %s effort on its first and resumed requests", + async (effort) => { + const [initial, resumed] = await runResumeScenario( + async () => {}, + { + customAgents: [ + { + name: "composed-effort", + prompt: "Reply with exactly SUBAGENT_TURN_COMPLETE.", + model: `gpt-5.4-mini:defaultReasoningEffort=${effort}`, + tools: [], + }, + ], + }, + async () => {}, + "composed-effort", + null + ); + + for (const request of [initial, resumed]) { + expect(request.model).toBe("gpt-5.4-mini"); + expect(request.reasoningEffort).toBe(effort === "high" ? "high" : "medium"); + } + } + ); + + capiIt( + "reconciles an independently declared child effort without changing its model", + async () => { + const [initial, resumed] = await runResumeScenario( + async () => {}, + { + customAgents: [ + { + name: "declared-effort", + prompt: "Reply with exactly SUBAGENT_TURN_COMPLETE.", + model: "gpt-5.4-mini", + reasoningEffort: "max", + tools: [], + }, + ], + }, + async () => {}, + "declared-effort", + null + ); + + for (const request of [initial, resumed]) { + expect(request.model).toBe("gpt-5.4-mini"); + expect(request.reasoningEffort).toBe("medium"); + } + } + ); + it("preserves the adapted prompt after persisted settings rebuild the child context", async () => { const [initial, resumed] = await runResumeScenario(changePersistedSettings); diff --git a/nodejs/test/e2e/telemetry.e2e.test.ts b/nodejs/test/e2e/telemetry.e2e.test.ts index 6d2915744e..439e56f209 100644 --- a/nodejs/test/e2e/telemetry.e2e.test.ts +++ b/nodejs/test/e2e/telemetry.e2e.test.ts @@ -186,6 +186,11 @@ describe("Telemetry export", async () => { `{"value":"${marker}"}` ); expect(getStringAttribute(toolSpan!, "gen_ai.tool.call.result")).toBe(marker); + expect(toolSpan!.attributes).not.toHaveProperty( + "github.copilot.tool.parameters.mcp_server_name_hash" + ); + expect(toolSpan!.attributes).not.toHaveProperty("github.copilot.mcp.server.transport"); + expect(toolSpan!.attributes).not.toHaveProperty("github.copilot.mcp.server.source"); }); it("should export per request subagent chat spans", { timeout: 90_000 }, async () => { @@ -212,7 +217,9 @@ describe("Telemetry export", async () => { const session = await subagentClient.createSession({ onPermissionRequest: approveAll }); sessionId = session.sessionId; try { - expect((await session.rpc.gitHubAuth.getStatus()).isAuthenticated).toBe(true); + const auth = await session.rpc.gitHubAuth.getStatus(); + expect(auth.isAuthenticated).toBe(true); + expect(auth.login).toBe("e2e-test-user"); const response = await session.sendAndWait({ prompt }, 90_000); expect(response?.data.content ?? "").toContain("SUBAGENT_OTEL_DONE"); } finally { diff --git a/nodejs/test/e2e/telemetry_preparation.e2e.test.ts b/nodejs/test/e2e/telemetry_preparation.e2e.test.ts new file mode 100644 index 0000000000..8c8f79c5a1 --- /dev/null +++ b/nodejs/test/e2e/telemetry_preparation.e2e.test.ts @@ -0,0 +1,170 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +import { readFile, writeFile } from "node:fs/promises"; +import { join } from "node:path"; +import { describe, it, onTestFinished } from "vitest"; +import { z } from "zod"; +import { + type CopilotSession, + defineTool, + type PermissionRequestResult, + RuntimeConnection, + type SessionEvent, +} from "../../src/index.js"; +import { + createSdkTestContext, + DEFAULT_GITHUB_TOKEN, + getLegacyCliPathForTests, +} from "./harness/sdkTestContext.js"; + +async function telemetryWarnings(session: CopilotSession) { + return (await session.getEvents()).filter( + (event) => event.type === "session.warning" && event.data.warningType === "telemetry" + ); +} + +describe.each(["native", "legacy"] as const)("Telemetry preparation (%s)", async (host) => { + const policyFile = "managed-telemetry-policy.json"; + const telemetryFile = "session-telemetry.jsonl"; + const harness = await createSdkTestContext({ + copilotClientOptions: { + connection: RuntimeConnection.forStdio(), + env: { + COPILOT_E2E_TEST_HOOKS: "1", + COPILOT_TEST_MANAGED_SETTINGS_FILE_PATH: policyFile, + COPILOT_MANAGED_SETTINGS_CACHE: "false", + }, + }, + }); + + it("keeps create and resume usable after exporter failure", async ({ expect }) => { + await writeFile( + join(harness.workDir, policyFile), + JSON.stringify({ + telemetry: { + enabled: true, + endpoint: "ftp://collector.example", + headers: { "x-routing-key": "managed-value" }, + }, + }) + ); + const options = { + gitHubToken: DEFAULT_GITHUB_TOKEN, + connection: RuntimeConnection.forStdio({ + path: + host === "legacy" + ? await getLegacyCliPathForTests() + : process.env.COPILOT_CLI_PATH, + }), + telemetry: { + exporterType: "file" as const, + filePath: telemetryFile, + sourceName: "ts-sdk-telemetry-e2e", + captureContent: true, + }, + }; + const client = harness.createClient(options); + onTestFinished(() => client.stop()); + const receivedWarnings: SessionEvent[] = []; + const sessionOptions = { + enableManagedSettings: true, + onEvent: (event: SessionEvent) => { + if (event.type === "session.warning" && event.data.warningType === "telemetry") { + receivedWarnings.push(event); + } + }, + onPermissionRequest: (): PermissionRequestResult => ({ kind: "approve-once" }), + tools: [ + defineTool("echo_telemetry_marker", { + description: "Echoes a marker string for telemetry validation.", + parameters: z.object({ value: z.string() }), + handler: ({ value }) => value, + }), + ], + }; + const session = await client.createSession(sessionOptions); + const failedSessionId = session.sessionId; + const initialWarnings = await telemetryWarnings(session); + expect(initialWarnings).toMatchObject([ + { + data: { + warningType: "telemetry", + message: + "OpenTelemetry could not be initialized and is disabled for this session. You can continue using the session.", + }, + }, + ]); + const replay = { + filePath: join( + import.meta.dirname, + "..", + "..", + "..", + "test", + "snapshots", + "telemetry", + "should_export_file_telemetry_for_sdk_interactions.yaml" + ), + workDir: harness.workDir, + }; + const prompt = + "Use the echo_telemetry_marker tool with value 'copilot-sdk-telemetry-e2e', then respond with TELEMETRY_E2E_DONE."; + await harness.openAiEndpoint.updateConfig(replay); + const assistant = await session.sendAndWait({ prompt }, 90_000); + expect(assistant?.data.content).toContain("TELEMETRY_E2E_DONE"); + expect(receivedWarnings.map((event) => event.id)).toEqual( + initialWarnings.map((event) => event.id) + ); + await client.rpc.sessions.save({ sessionId: failedSessionId }); + const resident = await client.resumeSession(failedSessionId, sessionOptions); + expect(resident.sessionId).toBe(failedSessionId); + await expect(resident.rpc.model.getCurrent()).resolves.toHaveProperty("modelId"); + expect(await telemetryWarnings(resident)).toHaveLength(1); + await resident.disconnect(); + await session.disconnect(); + expect(await client.stop()).toEqual([]); + + const resumedClient = harness.createClient(options); + onTestFinished(() => resumedClient.stop()); + const cold = await resumedClient.resumeSession(failedSessionId, sessionOptions); + expect(cold.sessionId).toBe(failedSessionId); + await expect(cold.rpc.model.getCurrent()).resolves.toHaveProperty("modelId"); + expect(await telemetryWarnings(cold)).toHaveLength(1); + await cold.disconnect(); + + await writeFile(join(harness.workDir, policyFile), "{}"); + let healthyClient = resumedClient; + if (host === "legacy") { + // The legacy host retains its startup device policy until the next process. + expect(await resumedClient.stop()).toEqual([]); + healthyClient = harness.createClient(options); + onTestFinished(() => healthyClient.stop()); + } + const healthy = await healthyClient.createSession(sessionOptions); + await harness.openAiEndpoint.updateConfig(replay); + const healthyAssistant = await healthy.sendAndWait({ prompt }, 90_000); + expect(healthyAssistant?.data.content).toContain("TELEMETRY_E2E_DONE"); + expect(await telemetryWarnings(healthy)).toEqual([]); + await healthy.disconnect(); + expect(await healthyClient.stop()).toEqual([]); + + const entries: Array<{ attributes?: Record }> = ( + await readFile(join(harness.workDir, telemetryFile), "utf8") + ) + .split(/\r?\n/) + .filter(Boolean) + .map((line) => JSON.parse(line)); + expect( + entries.some( + (entry) => entry.attributes?.["gen_ai.conversation.id"] === healthy.sessionId + ) + ).toBe(true); + expect( + entries.some( + (entry) => entry.attributes?.["gen_ai.conversation.id"] === failedSessionId + ) + ).toBe(false); + }); +}); diff --git a/nodejs/test/e2e/tools_anthropic_updates.e2e.test.ts b/nodejs/test/e2e/tools_anthropic_updates.e2e.test.ts index 721b432ae1..01ac7f0edd 100644 --- a/nodejs/test/e2e/tools_anthropic_updates.e2e.test.ts +++ b/nodejs/test/e2e/tools_anthropic_updates.e2e.test.ts @@ -2,15 +2,19 @@ * Copyright (c) Microsoft Corporation. All rights reserved. *--------------------------------------------------------------------------------------------*/ +import { writeFile } from "node:fs/promises"; +import { join } from "node:path"; import { describe, expect, it } from "vitest"; import { approveAll, CopilotRequestHandler, defineTool, type CopilotRequestContext, + type SessionConfig, } from "../../src/index.js"; import { createSdkTestContext } from "./harness/sdkTestContext.js"; import { waitForCondition } from "./harness/sdkTestHelper.js"; +import { mcpTestServerConfig } from "./harness/mcpTestServer.js"; interface AnthropicRequest { system: unknown; @@ -18,6 +22,16 @@ interface AnthropicRequest { messages: Array<{ role: string; content: unknown }>; } +interface AnthropicToolBlock { + type?: string; + tool_name?: string; + content?: unknown; + tool?: { + name?: string; + definition?: { name?: string }; + }; +} + function streamResponse(block: Record, stopReason: string): Response { const events: Array<[string, Record]> = [ [ @@ -110,7 +124,11 @@ class AnthropicToolUpdateHandler extends CopilotRequestHandler { readonly requests: Array<{ body: AnthropicRequest; beta: string | null }> = []; constructor( - private readonly rejectPositionedUpdate: false | "before-output" | "after-output" = false + private readonly rejectPositionedUpdate: false | "before-output" | "after-output" = false, + private readonly addedTool: { name: string; input: Record } = { + name: "sdk_epoch_added", + input: {}, + } ) { super(); } @@ -119,7 +137,28 @@ class AnthropicToolUpdateHandler extends CopilotRequestHandler { request: Request, ctx: CopilotRequestContext ): Promise { - if (new URL(request.url).hostname !== "api.anthropic.com") { + const url = new URL(request.url); + if (url.pathname === "/models") { + return Response.json({ + data: [ + { + id: "claude-opus-5.5", + name: "Claude Opus 5.5", + vendor: "Anthropic", + model_picker_enabled: true, + supported_endpoints: ["/v1/messages"], + capabilities: { + type: "chat", + family: "claude-opus-5.5", + tokenizer: "o200k_base", + limits: { max_context_window_tokens: 200000, max_output_tokens: 32000 }, + supports: { streaming: true, tool_calls: true }, + }, + }, + ], + }); + } + if (!url.pathname.endsWith("/messages")) { return super.sendRequest(request, ctx); } const body = (await request.json()) as AnthropicRequest; @@ -133,6 +172,27 @@ class AnthropicToolUpdateHandler extends CopilotRequestHandler { } this.requests.push({ body, beta: request.headers.get("anthropic-beta") }); + if ( + body.messages.some( + (message) => + message.role !== "system" && + Array.isArray(message.content) && + message.content.some((block: { type?: string }) => + ["tool_addition", "tool_removal"].includes(block.type ?? "") + ) + ) + ) { + return Response.json( + { + error: { + type: "invalid_request_error", + message: "tool_addition/tool_removal require role: system", + }, + }, + { status: 400 } + ); + } + if (serialized.includes("SDK_ANTHROPIC_FOLLOW_UP")) { return streamResponse({ type: "text", text: "SDK_ANTHROPIC_LEGACY_OK" }, "end_turn"); } @@ -167,7 +227,7 @@ class AnthropicToolUpdateHandler extends CopilotRequestHandler { ); } return streamResponse( - { type: "tool_use", id: "toolu_sdk_added", name: "sdk_epoch_added", input: {} }, + { type: "tool_use", id: "toolu_sdk_added", ...this.addedTool }, "tool_use" ); } @@ -175,12 +235,643 @@ class AnthropicToolUpdateHandler extends CopilotRequestHandler { } } +class StaleToolReferenceHandler extends AnthropicToolUpdateHandler { + protected override async sendRequest( + request: Request, + ctx: CopilotRequestContext + ): Promise { + const url = new URL(request.url); + if (!url.pathname.endsWith("/messages")) { + return super.sendRequest(request, ctx); + } + const body = (await request.clone().json()) as AnthropicRequest; + const serialized = JSON.stringify(body.messages); + if (!serialized.includes("SDK_STALE_REFERENCE")) { + return super.sendRequest(request, ctx); + } + this.requests.push({ body, beta: request.headers.get("anthropic-beta") }); + + const available = new Set( + Array.isArray(body.tools) + ? body.tools + .map((tool) => (tool as { name?: string }).name) + .filter((name): name is string => typeof name === "string") + : [] + ); + const unresolved: string[] = []; + for (const message of body.messages) { + if (!Array.isArray(message.content)) { + continue; + } + for (const block of message.content as AnthropicToolBlock[]) { + if (block.type === "tool_addition") { + const name = block.tool?.definition?.name; + if (name) { + available.add(name); + } + } else if (block.type === "tool_removal") { + const name = block.tool?.name; + if (name) { + available.delete(name); + } + } else if (block.type === "tool_result" && Array.isArray(block.content)) { + for (const result of block.content as AnthropicToolBlock[]) { + if ( + result.type === "tool_reference" && + typeof result.tool_name === "string" && + !available.has(result.tool_name) + ) { + unresolved.push(result.tool_name); + } + } + } + } + } + if (unresolved.length > 0) { + return Response.json( + { + error: { + type: "invalid_request_error", + message: `Tool reference '${unresolved[0]}' not found in available tools`, + }, + }, + { status: 400 } + ); + } + + if (serialized.includes("SDK_STALE_REFERENCE_AFTER_RESUME")) { + return streamResponse( + { type: "text", text: "SDK_STALE_REFERENCE_AFTER_RESUME_OK" }, + "end_turn" + ); + } + if (serialized.includes("SDK_STALE_REFERENCE_STILL_USABLE")) { + return streamResponse( + { type: "text", text: "SDK_STALE_REFERENCE_STILL_USABLE_OK" }, + "end_turn" + ); + } + if (serialized.includes("SDK_STALE_REFERENCE_AFTER_REMOVAL")) { + return streamResponse( + { type: "text", text: "SDK_STALE_REFERENCE_AFTER_REMOVAL_OK" }, + "end_turn" + ); + } + if ( + body.messages.some( + (message) => + Array.isArray(message.content) && + message.content.some( + (block: AnthropicToolBlock) => block.type === "tool_result" + ) + ) + ) { + return streamResponse( + { type: "text", text: "SDK_STALE_REFERENCE_SEARCH_OK" }, + "end_turn" + ); + } + return streamResponse( + { + type: "tool_use", + id: "toolu_stale_reference_search", + name: "tool_search_tool", + input: { pattern: "a_server-get_env", limit: 1 }, + }, + "tool_use" + ); + } +} + describe("SDK tool updates on direct Anthropic Messages", async () => { const handler = new AnthropicToolUpdateHandler(); const { copilotClient: client } = await createSdkTestContext({ copilotClientOptions: { requestHandler: handler }, }); + describe("MCP tool updates on direct Anthropic Messages", async () => { + const toolName = "a_server-get_env"; + const handler = new AnthropicToolUpdateHandler(false, { + name: toolName, + input: { name: "CACHE_TEST_RESULT" }, + }); + const { copilotClient: client, workDir } = await createSdkTestContext({ + copilotClientOptions: { requestHandler: handler }, + }); + + const routes: Array<{ name: string; config: Partial }> = [ + { + name: "direct BYOK", + config: { + provider: { + type: "anthropic", + baseUrl: "https://api.anthropic.com", + apiKey: "synthetic-provider-key", + modelId: "claude-opus-5.5", + wireModel: "claude-opus-5.5", + }, + }, + }, + { + name: "compatible BYOK proxy", + config: { + provider: { + type: "anthropic", + baseUrl: "https://messages.example.test", + apiKey: "synthetic-provider-key", + modelId: "claude-opus-5.5", + wireModel: "claude-opus-5.5", + }, + }, + }, + { name: "CAPI", config: {} }, + ]; + for (const route of routes) { + it(`keeps the serialized prefix on ${route.name} when enabling an MCP server adds tools before existing ones`, async () => { + const config = mcpTestServerConfig("SDK_ADDED_TOOL_RESULT"); + const beforeRequests = handler.requests.length; + const session = await client.createSession({ + model: "claude-opus-5.5", + toolSearch: { enabled: false }, + onPermissionRequest: approveAll, + ...route.config, + mcpServers: { z_server: config, a_server: config }, + disabledMcpServers: ["a_server"], + }); + try { + await session.rpc.tools.initializeAndValidate(); + expect( + (await session.rpc.tools.getCurrentMetadata()).tools.map( + (tool) => tool.name + ) + ).toContain("z_server-get_env"); + expect( + (await session.rpc.tools.getCurrentMetadata()).tools.map( + (tool) => tool.name + ) + ).not.toContain(toolName); + const first = await session.sendAndWait({ prompt: "SDK_ANTHROPIC_FIRST" }); + expect(first?.data.content).toContain("SDK_ANTHROPIC_FIRST_OK"); + + await session.rpc.mcp.setEnvValueMode({ mode: "direct" }); + await session.rpc.mcp.enable({ serverName: "a_server" }); + await session.rpc.tools.initializeAndValidate(); + expect( + (await session.rpc.tools.getCurrentMetadata()).tools.map( + (tool) => tool.name + ) + ).toContain(toolName); + const second = await session.sendAndWait({ + prompt: `SDK_ANTHROPIC_AFTER: call ${toolName}.`, + }); + expect(second?.data.content).toContain("SDK_ANTHROPIC_TOOL_OK"); + + const requests = handler.requests.slice(beforeRequests); + expect(requests).toHaveLength(3); + const [before, update, afterTool] = requests; + expect(before.body.tools).toEqual( + expect.arrayContaining([ + expect.objectContaining({ name: "z_server-get_env" }), + ]) + ); + expect(JSON.stringify(before.body.tools)).not.toContain(`"name":"${toolName}"`); + for (const request of [update, afterTool]) { + expect(JSON.stringify(request.body.system)).toBe( + JSON.stringify(before.body.system) + ); + expect(JSON.stringify(request.body.tools)).toBe( + JSON.stringify(before.body.tools) + ); + } + expect(update.body.messages).toContainEqual( + expect.objectContaining({ + role: "system", + content: [ + expect.objectContaining({ + type: "tool_addition", + tool: expect.objectContaining({ + type: "tool_definition", + definition: expect.objectContaining({ name: toolName }), + }), + }), + ], + }) + ); + expect(update.beta).toContain("inline-tools-2026-09-15"); + expect(JSON.stringify(afterTool.body.messages)).toContain( + "SDK_ADDED_TOOL_RESULT" + ); + expect( + (await session.rpc.tools.getCurrentMetadata()).tools.map( + (tool) => tool.name + ) + ).toEqual(expect.arrayContaining(["a_server-get_env", "z_server-get_env"])); + } finally { + await session.disconnect(); + } + }); + } + + describe("CAPI Messages compatibility", async () => { + const handler = new AnthropicToolUpdateHandler(); + const { copilotClient: client } = await createSdkTestContext({ + copilotClientOptions: { requestHandler: handler }, + }); + const strictHandler = new StaleToolReferenceHandler(); + const { copilotClient: strictClient, createClient: createStrictClient } = + await createSdkTestContext({ + copilotClientOptions: { + requestHandler: strictHandler, + env: { + COPILOT_EXP_COPILOT_CLI_TOOL_SEARCH_ANTHROPIC: "true", + COPILOT_EXP_COPILOT_CLI_TOOL_SEARCH_BUILTIN_ANTHROPIC: "false", + }, + }, + }); + it("preserves the prefix across CAPI reordering, removal and restoration without a rejected attempt", async () => { + let addedCalls = 0; + const original = { + name: "sdk_epoch_original", + description: "Original SDK tool", + parameters: { type: "object", properties: {} }, + defer: "never", + } as const; + const added = { + ...original, + name: "sdk_epoch_added", + description: "Added SDK tool", + }; + const session = await client.createSession({ + model: "claude-opus-5.5", + toolSearch: { enabled: false }, + onPermissionRequest: approveAll, + availableTools: [original.name, added.name], + tools: [ + defineTool(original.name, { + description: original.description, + handler: () => "original", + }), + defineTool(added.name, { + description: added.description, + handler: () => { + addedCalls++; + return "SDK_ADDED_TOOL_RESULT"; + }, + }), + ], + }); + try { + await session.rpc.tools.initializeAndValidate(); + await session.rpc.tools.set({ tools: [original, added] }); + expect( + (await session.sendAndWait({ prompt: "SDK_ANTHROPIC_FIRST" }))?.data.content + ).toContain("SDK_ANTHROPIC_FIRST_OK"); + await session.rpc.tools.set({ tools: [added, original] }); + expect( + (await session.sendAndWait({ prompt: "SDK_ANTHROPIC_FIRST_REORDERED" })) + ?.data.content + ).toContain("SDK_ANTHROPIC_FIRST_OK"); + await session.rpc.tools.set({ tools: [original] }); + expect( + (await session.sendAndWait({ prompt: "SDK_ANTHROPIC_FIRST_REMOVED" }))?.data + .content + ).toContain("SDK_ANTHROPIC_FIRST_OK"); + await session.rpc.tools.set({ tools: [added, original] }); + expect( + ( + await session.sendAndWait({ + prompt: "SDK_ANTHROPIC_AFTER: call sdk_epoch_added.", + }) + )?.data.content + ).toContain("SDK_ANTHROPIC_TOOL_OK"); + expect(addedCalls).toBe(1); + expect(handler.requests).toHaveLength(5); + const [before, reordered, removed, restored, afterTool] = handler.requests; + expect(JSON.stringify(reordered.body.tools)).toBe( + JSON.stringify(before.body.tools) + ); + expect(removed.body.messages).toContainEqual({ + role: "system", + content: [ + { + type: "tool_removal", + tool: { type: "tool_reference", name: added.name }, + }, + ], + }); + expect(removed.beta).toContain("mid-conversation-tool-changes-2026-07-01"); + expect(restored.body.messages).toContainEqual( + expect.objectContaining({ + role: "system", + content: [ + expect.objectContaining({ + type: "tool_addition", + tool: expect.objectContaining({ + definition: expect.objectContaining({ name: added.name }), + }), + }), + ], + }) + ); + expect(restored.beta).toContain("inline-tools-2026-09-15"); + expect(JSON.stringify(afterTool.body.messages)).toContain( + "SDK_ADDED_TOOL_RESULT" + ); + for (const request of handler.requests) { + expect(JSON.stringify(request.body.system)).toBe( + JSON.stringify(before.body.system) + ); + expect(JSON.stringify(request.body.tools)).toBe( + JSON.stringify(before.body.tools) + ); + } + } finally { + await session.disconnect(); + } + }); + + it("keeps a session usable when an MCP tool referenced by deferred search disappears", async () => { + const config = mcpTestServerConfig("SDK_STALE_REFERENCE_TOOL_RESULT"); + const session = await strictClient.createSession({ + model: "claude-opus-5.5", + toolSearch: { enabled: true, deferThreshold: 0 }, + onPermissionRequest: approveAll, + mcpServers: { a_server: config }, + }); + try { + await session.rpc.mcp.setEnvValueMode({ mode: "direct" }); + await session.rpc.tools.initializeAndValidate(); + + expect( + ( + await session.sendAndWait({ + prompt: "SDK_STALE_REFERENCE_SEARCH", + }) + )?.data.content + ).toContain("SDK_STALE_REFERENCE_SEARCH_OK"); + expect(JSON.stringify(strictHandler.requests.at(-1)?.body.messages)).toContain( + '"type":"tool_reference","tool_name":"a_server-get_env"' + ); + + await session.rpc.mcp.disable({ serverName: "a_server" }); + await session.rpc.tools.initializeAndValidate(); + expect((await session.rpc.mcp.list()).host?.disabledServers).toContain( + "a_server" + ); + expect( + ( + await session.sendAndWait({ + prompt: "SDK_STALE_REFERENCE_AFTER_REMOVAL", + }) + )?.data.content + ).toContain("SDK_STALE_REFERENCE_AFTER_REMOVAL_OK"); + + const removed = strictHandler.requests.at(-1); + const removedMessages = JSON.stringify(removed?.body.messages); + expect(removedMessages).not.toContain( + '"type":"tool_reference","tool_name":"a_server-get_env"' + ); + expect(removedMessages).toContain("Found 1 matching tool(s): a_server-get_env"); + expect( + ( + await session.sendAndWait({ + prompt: "SDK_STALE_REFERENCE_STILL_USABLE", + }) + )?.data.content + ).toContain("SDK_STALE_REFERENCE_STILL_USABLE_OK"); + + const sessionId = session.sessionId; + await session.disconnect(); + await strictClient.stop(); + const coldClient = createStrictClient({ requestHandler: strictHandler }); + try { + const resumed = await coldClient.resumeSession(sessionId, { + model: "claude-opus-5.5", + toolSearch: { enabled: true, deferThreshold: 0 }, + onPermissionRequest: approveAll, + mcpServers: { a_server: config }, + disabledMcpServers: ["a_server"], + }); + try { + const beforeResumeRequests = strictHandler.requests.length; + expect( + ( + await resumed.sendAndWait({ + prompt: "SDK_STALE_REFERENCE_AFTER_RESUME", + }) + )?.data.content + ).toContain("SDK_STALE_REFERENCE_AFTER_RESUME_OK"); + expect(strictHandler.requests).toHaveLength(beforeResumeRequests + 1); + const resumedMessages = JSON.stringify( + strictHandler.requests.at(-1)?.body.messages + ); + expect(resumedMessages).not.toContain( + '"type":"tool_reference","tool_name":"a_server-get_env"' + ); + expect(resumedMessages).toContain( + "Found 1 matching tool(s): a_server-get_env" + ); + } finally { + await resumed.disconnect(); + } + } finally { + await coldClient.stop(); + } + } finally { + await session.disconnect(); + } + }); + }); + + it.each(routes)( + "keeps the prefix across MCP removal and restoration on $name and executes the restored tool", + async (route) => { + const config = mcpTestServerConfig("SDK_ADDED_TOOL_RESULT"); + const beforeRequests = handler.requests.length; + const session = await client.createSession({ + model: "claude-opus-5.5", + toolSearch: { enabled: false }, + onPermissionRequest: approveAll, + availableTools: [toolName, "z_server-get_env"], + ...route.config, + mcpServers: { a_server: config, z_server: config }, + }); + try { + await session.rpc.mcp.setEnvValueMode({ mode: "direct" }); + await session.rpc.tools.initializeAndValidate(); + expect( + (await session.rpc.tools.getCurrentMetadata()).tools.map( + (tool) => tool.name + ) + ).toEqual(expect.arrayContaining([toolName, "z_server-get_env"])); + expect( + (await session.sendAndWait({ prompt: "SDK_ANTHROPIC_FIRST" }))?.data.content + ).toContain("SDK_ANTHROPIC_FIRST_OK"); + + await session.rpc.mcp.disable({ serverName: "a_server" }); + await session.rpc.tools.initializeAndValidate(); + // Tool metadata includes the previous request; the host reports live availability. + const disabled = await session.rpc.mcp.list(); + expect(disabled.host?.disabledServers).toContain("a_server"); + expect(disabled.host?.disabledServers).not.toContain("z_server"); + expect(disabled.servers).toContainEqual( + expect.objectContaining({ name: "a_server", status: "disabled" }) + ); + expect( + (await session.sendAndWait({ prompt: "SDK_ANTHROPIC_FIRST_REMOVED" }))?.data + .content + ).toContain("SDK_ANTHROPIC_FIRST_OK"); + + await session.rpc.mcp.enable({ serverName: "a_server" }); + await session.rpc.tools.initializeAndValidate(); + expect( + (await session.rpc.tools.getCurrentMetadata()).tools.map( + (tool) => tool.name + ) + ).toContain(toolName); + expect( + ( + await session.sendAndWait({ + prompt: `SDK_ANTHROPIC_AFTER: call ${toolName}.`, + }) + )?.data.content + ).toContain("SDK_ANTHROPIC_TOOL_OK"); + + const requests = handler.requests.slice(beforeRequests); + expect(requests).toHaveLength(4); + const [before, removed, restored, afterTool] = requests; + expect(before.body.tools).toEqual( + expect.arrayContaining([expect.objectContaining({ name: toolName })]) + ); + for (const request of [removed, restored, afterTool]) { + expect(JSON.stringify(request.body.system)).toBe( + JSON.stringify(before.body.system) + ); + expect(JSON.stringify(request.body.tools)).toBe( + JSON.stringify(before.body.tools) + ); + } + const blocks = (request: AnthropicRequest) => + request.messages.flatMap((message) => + Array.isArray(message.content) ? message.content : [] + ); + expect(blocks(removed.body)).toContainEqual({ + type: "tool_removal", + tool: { type: "tool_reference", name: toolName }, + }); + expect(removed.beta).toContain("mid-conversation-tool-changes-2026-07-01"); + expect(restored.beta).toContain("inline-tools-2026-09-15"); + expect( + blocks(restored.body) + .filter((block) => + ["tool_removal", "tool_addition"].includes(block.type) + ) + .map((block) => block.type) + ).toEqual(["tool_removal", "tool_addition"]); + expect(blocks(restored.body)).toContainEqual( + expect.objectContaining({ + type: "tool_addition", + tool: expect.objectContaining({ + definition: expect.objectContaining({ name: toolName }), + }), + }) + ); + expect(JSON.stringify(afterTool.body.messages)).toContain( + "SDK_ADDED_TOOL_RESULT" + ); + } finally { + await session.disconnect(); + } + } + ); + + it.each(routes)( + "replaces a refreshed MCP definition inline on $name and executes the restarted server", + async (route) => { + const descriptionFile = join(workDir, `tool-description-${route.name}.txt`); + await writeFile(descriptionFile, "MCP_ORIGINAL_DESCRIPTION"); + const beforeRequests = handler.requests.length; + const session = await client.createSession({ + model: "claude-opus-5.5", + toolSearch: { enabled: false }, + onPermissionRequest: approveAll, + ...route.config, + mcpServers: { + a_server: mcpTestServerConfig("SDK_ADDED_TOOL_RESULT", descriptionFile), + }, + }); + try { + await session.rpc.mcp.setEnvValueMode({ mode: "direct" }); + await session.rpc.tools.initializeAndValidate(); + expect( + (await session.rpc.tools.getCurrentMetadata()).tools.find( + (tool) => tool.name === toolName + )?.description + ).toContain("MCP_ORIGINAL_DESCRIPTION"); + expect( + (await session.sendAndWait({ prompt: "SDK_ANTHROPIC_FIRST" }))?.data.content + ).toContain("SDK_ANTHROPIC_FIRST_OK"); + + await writeFile(descriptionFile, "MCP_REPLACEMENT_DESCRIPTION"); + await session.rpc.mcp.restartServer({ serverName: "a_server" }); + await session.rpc.tools.initializeAndValidate(); + expect( + (await session.rpc.tools.getCurrentMetadata()).tools.find( + (tool) => tool.name === toolName + )?.description + ).toContain("MCP_REPLACEMENT_DESCRIPTION"); + expect( + ( + await session.sendAndWait({ + prompt: `SDK_ANTHROPIC_AFTER: call ${toolName}.`, + }) + )?.data.content + ).toContain("SDK_ANTHROPIC_TOOL_OK"); + + const requests = handler.requests.slice(beforeRequests); + expect(requests).toHaveLength(3); + const [before, update, afterTool] = requests; + expect(before.body.tools).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + name: toolName, + description: expect.stringContaining("MCP_ORIGINAL_DESCRIPTION"), + }), + ]) + ); + for (const request of [update, afterTool]) { + expect(JSON.stringify(request.body.system)).toBe( + JSON.stringify(before.body.system) + ); + expect(JSON.stringify(request.body.tools)).toBe( + JSON.stringify(before.body.tools) + ); + } + const additions = update.body.messages + .flatMap((message) => + Array.isArray(message.content) ? message.content : [] + ) + .filter((block) => block.type === "tool_addition"); + expect(additions).toHaveLength(1); + expect(additions[0]).toMatchObject({ + tool: { + definition: { + name: toolName, + description: expect.stringContaining("MCP_REPLACEMENT_DESCRIPTION"), + }, + }, + }); + expect(update.beta).toContain("inline-tools-2026-09-15"); + expect(JSON.stringify(afterTool.body.messages)).toContain( + "SDK_ADDED_TOOL_RESULT" + ); + } finally { + await session.disconnect(); + } + } + ); + }); + it("keeps the top-level prefix and executes a newly added inline tool", async () => { let addedToolCalls = 0; const session = await client.createSession({ @@ -263,149 +954,169 @@ describe("SDK tool updates on direct Anthropic Messages", async () => { }); }); -describe("SDK tool updates on direct Anthropic Messages", async () => { - const rejectingHandler = new AnthropicToolUpdateHandler("before-output"); - const { copilotClient: retryClient } = await createSdkTestContext({ - copilotClientOptions: { requestHandler: rejectingHandler }, - }); - - it("retries a rejected inline update once with the full current tools", async () => { - const original = { - name: "sdk_epoch_original", - description: "Original SDK tool", - parameters: { type: "object", properties: {} }, - defer: "never", - } as const; - const added = { - name: "sdk_epoch_added", - description: "Added SDK tool", - parameters: { type: "object", properties: {} }, - defer: "never", - } as const; - const session = await retryClient.createSession({ - model: "claude-opus-5.5", - toolSearch: { enabled: false }, - onPermissionRequest: approveAll, - provider: { - type: "anthropic", - baseUrl: "https://api.anthropic.com", - apiKey: "synthetic-provider-key", - modelId: "claude-opus-5.5", - wireModel: "claude-opus-5.5", - }, - tools: [ - defineTool(original.name, { - description: original.description, - handler: () => "original", - }), - defineTool(added.name, { description: added.description, handler: () => "added" }), - ], +describe.each(["BYOK", "CAPI"])( + "SDK tool update recovery on %s Anthropic Messages", + async (route) => { + const rejectingHandler = new AnthropicToolUpdateHandler("before-output"); + const { copilotClient: retryClient } = await createSdkTestContext({ + copilotClientOptions: { requestHandler: rejectingHandler }, }); - try { - await session.rpc.tools.initializeAndValidate(); - await session.rpc.tools.set({ tools: [original] }); - const first = await session.sendAndWait({ prompt: "SDK_ANTHROPIC_FIRST" }); - expect(first?.data.content).toContain("SDK_ANTHROPIC_FIRST_OK"); - await session.rpc.tools.set({ tools: [original, added] }); - const recovered = await session.sendAndWait({ - prompt: "SDK_ANTHROPIC_AFTER: add a tool.", + it("retries a rejected inline update once with the full current tools", async () => { + const original = { + name: "sdk_epoch_original", + description: "Original SDK tool", + parameters: { type: "object", properties: {} }, + defer: "never", + } as const; + const added = { + name: "sdk_epoch_added", + description: "Added SDK tool", + parameters: { type: "object", properties: {} }, + defer: "never", + } as const; + const session = await retryClient.createSession({ + model: "claude-opus-5.5", + toolSearch: { enabled: false }, + onPermissionRequest: approveAll, + provider: + route === "BYOK" + ? { + type: "anthropic", + baseUrl: "https://api.anthropic.com", + apiKey: "synthetic-provider-key", + modelId: "claude-opus-5.5", + wireModel: "claude-opus-5.5", + } + : undefined, + tools: [ + defineTool(original.name, { + description: original.description, + handler: () => "original", + }), + defineTool(added.name, { + description: added.description, + handler: () => "added", + }), + ], }); - expect(recovered?.data.content).toContain("SDK_ANTHROPIC_RECOVERED"); - expect(rejectingHandler.requests).toHaveLength(3); - const [before, rejected, retry] = rejectingHandler.requests; - expect(rejected.body.system).toEqual(before.body.system); - expect(rejected.body.tools).toEqual(before.body.tools); - expect(JSON.stringify(rejected.body.messages)).toContain('"type":"tool_addition"'); - expect(rejected.beta).toContain("inline-tools-2026-09-15"); - expect(retry.body.system).toEqual(before.body.system); - expect((retry.body.tools as Array<{ name: string }>).map((tool) => tool.name)).toEqual( - expect.arrayContaining([original.name, added.name]) - ); - expect(JSON.stringify(retry.body.messages)).not.toContain('"type":"tool_addition"'); - expect(retry.beta).not.toContain("inline-tools-2026-09-15"); - } finally { - await session.disconnect(); - } - }); -}); - -describe("SDK tool updates after a streamed Anthropic rejection", async () => { - const handler = new AnthropicToolUpdateHandler("after-output"); - const { copilotClient: client } = await createSdkTestContext({ - copilotClientOptions: { requestHandler: handler }, - }); + try { + await session.rpc.tools.initializeAndValidate(); + await session.rpc.tools.set({ tools: [original] }); + const first = await session.sendAndWait({ prompt: "SDK_ANTHROPIC_FIRST" }); + expect(first?.data.content).toContain("SDK_ANTHROPIC_FIRST_OK"); - it("disables the rejected epoch without replaying output and uses full tools next turn", async () => { - const original = { - name: "sdk_epoch_original", - description: "Original SDK tool", - parameters: { type: "object", properties: {} }, - defer: "never", - } as const; - const added = { - name: "sdk_epoch_added", - description: "Added SDK tool", - parameters: { type: "object", properties: {} }, - defer: "never", - } as const; - const session = await client.createSession({ - model: "claude-opus-5.5", - toolSearch: { enabled: false }, - onPermissionRequest: approveAll, - provider: { - type: "anthropic", - baseUrl: "https://api.anthropic.com", - apiKey: "synthetic-provider-key", - modelId: "claude-opus-5.5", - wireModel: "claude-opus-5.5", - }, - tools: [ - defineTool(original.name, { - description: original.description, - handler: () => "original", - }), - defineTool(added.name, { description: added.description, handler: () => "added" }), - ], - }); - let failedTurnIdle = false; - const unsubscribe = session.on((event) => { - if (event.type === "session.idle" && !event.agentId) { - failedTurnIdle = true; + await session.rpc.tools.set({ tools: [original, added] }); + const recovered = await session.sendAndWait({ + prompt: "SDK_ANTHROPIC_AFTER: add a tool.", + }); + expect(recovered?.data.content).toContain("SDK_ANTHROPIC_RECOVERED"); + expect(rejectingHandler.requests).toHaveLength(3); + const [before, rejected, retry] = rejectingHandler.requests; + expect(rejected.body.system).toEqual(before.body.system); + expect(rejected.body.tools).toEqual(before.body.tools); + expect(JSON.stringify(rejected.body.messages)).toContain('"type":"tool_addition"'); + expect(rejected.beta).toContain("inline-tools-2026-09-15"); + expect(retry.body.system).toEqual(before.body.system); + expect( + (retry.body.tools as Array<{ name: string }>).map((tool) => tool.name) + ).toEqual(expect.arrayContaining([original.name, added.name])); + expect(JSON.stringify(retry.body.messages)).not.toContain('"type":"tool_addition"'); + expect(retry.beta).not.toContain("inline-tools-2026-09-15"); + } finally { + await session.disconnect(); } }); - try { - await session.rpc.tools.initializeAndValidate(); - await session.rpc.tools.set({ tools: [original] }); - const first = await session.sendAndWait({ prompt: "SDK_ANTHROPIC_FIRST" }); - expect(first?.data.content).toContain("SDK_ANTHROPIC_FIRST_OK"); - failedTurnIdle = false; + } +); - await session.rpc.tools.set({ tools: [original, added] }); - await expect( - session.sendAndWait({ prompt: "SDK_ANTHROPIC_AFTER: add a tool." }) - ).rejects.toThrow(/invalid tool_addition/); - await waitForCondition(() => failedTurnIdle, { - timeoutMessage: "Rejected SDK tool update did not reach session.idle", +describe.each(["BYOK", "CAPI"])( + "SDK tool updates after a streamed %s Anthropic rejection", + async (route) => { + const handler = new AnthropicToolUpdateHandler("after-output"); + const { copilotClient: client } = await createSdkTestContext({ + copilotClientOptions: { requestHandler: handler }, + }); + + it("disables the rejected epoch without replaying output and uses full tools next turn", async () => { + const original = { + name: "sdk_epoch_original", + description: "Original SDK tool", + parameters: { type: "object", properties: {} }, + defer: "never", + } as const; + const added = { + name: "sdk_epoch_added", + description: "Added SDK tool", + parameters: { type: "object", properties: {} }, + defer: "never", + } as const; + const session = await client.createSession({ + model: "claude-opus-5.5", + toolSearch: { enabled: false }, + onPermissionRequest: approveAll, + provider: + route === "BYOK" + ? { + type: "anthropic", + baseUrl: "https://api.anthropic.com", + apiKey: "synthetic-provider-key", + modelId: "claude-opus-5.5", + wireModel: "claude-opus-5.5", + } + : undefined, + tools: [ + defineTool(original.name, { + description: original.description, + handler: () => "original", + }), + defineTool(added.name, { + description: added.description, + handler: () => "added", + }), + ], }); - expect(handler.requests).toHaveLength(2); - const [before, rejected] = handler.requests; - expect(rejected.body.system).toEqual(before.body.system); - expect(rejected.body.tools).toEqual(before.body.tools); - expect(JSON.stringify(rejected.body.messages)).toContain('"type":"tool_addition"'); - - const followUp = await session.sendAndWait({ prompt: "SDK_ANTHROPIC_FOLLOW_UP" }); - expect(followUp?.data.content).toContain("SDK_ANTHROPIC_LEGACY_OK"); - expect(handler.requests).toHaveLength(3); - const request = handler.requests[2]; - expect( - (request.body.tools as Array<{ name: string }>).map((tool) => tool.name) - ).toEqual(expect.arrayContaining([original.name, added.name])); - expect(JSON.stringify(request.body.messages)).not.toContain('"type":"tool_addition"'); - expect(request.beta).not.toContain("inline-tools-2026-09-15"); - } finally { - unsubscribe(); - await session.disconnect(); - } - }); -}); + let failedTurnIdle = false; + const unsubscribe = session.on((event) => { + if (event.type === "session.idle" && !event.agentId) { + failedTurnIdle = true; + } + }); + try { + await session.rpc.tools.initializeAndValidate(); + await session.rpc.tools.set({ tools: [original] }); + const first = await session.sendAndWait({ prompt: "SDK_ANTHROPIC_FIRST" }); + expect(first?.data.content).toContain("SDK_ANTHROPIC_FIRST_OK"); + failedTurnIdle = false; + + await session.rpc.tools.set({ tools: [original, added] }); + await expect( + session.sendAndWait({ prompt: "SDK_ANTHROPIC_AFTER: add a tool." }) + ).rejects.toThrow(/invalid tool_addition/); + await waitForCondition(() => failedTurnIdle, { + timeoutMessage: "Rejected SDK tool update did not reach session.idle", + }); + expect(handler.requests).toHaveLength(2); + const [before, rejected] = handler.requests; + expect(rejected.body.system).toEqual(before.body.system); + expect(rejected.body.tools).toEqual(before.body.tools); + expect(JSON.stringify(rejected.body.messages)).toContain('"type":"tool_addition"'); + + const followUp = await session.sendAndWait({ prompt: "SDK_ANTHROPIC_FOLLOW_UP" }); + expect(followUp?.data.content).toContain("SDK_ANTHROPIC_LEGACY_OK"); + expect(handler.requests).toHaveLength(3); + const request = handler.requests[2]; + expect( + (request.body.tools as Array<{ name: string }>).map((tool) => tool.name) + ).toEqual(expect.arrayContaining([original.name, added.name])); + expect(JSON.stringify(request.body.messages)).not.toContain( + '"type":"tool_addition"' + ); + expect(request.beta).not.toContain("inline-tools-2026-09-15"); + } finally { + unsubscribe(); + await session.disconnect(); + } + }); + } +); diff --git a/nodejs/test/e2e/user_settings_edit.e2e.test.ts b/nodejs/test/e2e/user_settings_edit.e2e.test.ts new file mode 100644 index 0000000000..857a1bfdcf --- /dev/null +++ b/nodejs/test/e2e/user_settings_edit.e2e.test.ts @@ -0,0 +1,63 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +import { mkdirSync, readFileSync, writeFileSync } from "node:fs"; +import { join } from "node:path"; +import { describe, expect, it } from "vitest"; +import { editUserSettings } from "../../examples/internal-user-settings.js"; +import { createSdkTestContext } from "./harness/sdkTestContext.js"; + +describe("Internal user settings edit contract", async () => { + const { copilotClient: client, workDir, env } = await createSdkTestContext(); + + it("runs the edit example and preserves settings-only reads and safe writes", async () => { + await client.start(); + // These host-only methods deliberately stay off the public SDK surface. + const settings = client["internalRpc"].user.settings; + const home = env.COPILOT_HOME; + writeFileSync(join(home, "config.json"), '{"voice":{"enabled":false}}'); + writeFileSync(join(home, "settings.json"), '{"voice":{"selectedModel":"local"}}'); + + expect(await settings.loadForEdit()).toEqual({ + status: "ok", + settings: { voice: { selectedModel: "local" } }, + }); + + const configDir = join(workDir, "example-config"); + mkdirSync(configDir); + const result = await editUserSettings(settings, configDir); + expect(result).toEqual({ value: { enabled: true, selectedModel: null } }); + expect(JSON.parse(readFileSync(join(configDir, "settings.json"), "utf8"))).toEqual({ + voice: result.value, + }); + console.log("Internal settings example:", JSON.stringify(result)); + + await settings.update({ + key: "voice", + value: { enabled: false }, + configDir, + subDir: "profile", + }); + expect(JSON.parse(readFileSync(join(configDir, "settings/profile.json"), "utf8"))).toEqual({ + voice: { enabled: false }, + }); + await expect( + settings.update({ + key: "voice", + value: { enabled: false }, + configDir, + subDir: "../../outside", + }) + ).rejects.toThrow("`subDir` must be a single path component"); + + const file = join(configDir, "settings.json"); + const original = "{ invalid settings"; + writeFileSync(file, original); + await expect( + settings.update({ key: "voice", value: { enabled: false }, configDir }) + ).rejects.toThrow("refusing to overwrite settings.json"); + expect(readFileSync(file, "utf8")).toBe(original); + expect(await settings.validate({ topKey: "voice", delete: true })).toEqual({ ok: true }); + }); +}); diff --git a/nodejs/test/e2e/websocket_fallback_recovery.e2e.test.ts b/nodejs/test/e2e/websocket_fallback_recovery.e2e.test.ts new file mode 100644 index 0000000000..671c7549bd --- /dev/null +++ b/nodejs/test/e2e/websocket_fallback_recovery.e2e.test.ts @@ -0,0 +1,277 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +import http from "node:http"; +import { afterAll, describe, expect, it } from "vitest"; +import { WebSocketServer } from "ws"; +import { approveAll, defineTool, type SessionEvent } from "../../src/index.js"; +import { withTestCleanup } from "../helpers/withTestCleanup.js"; +import { createSdkTestContext, DEFAULT_GITHUB_TOKEN } from "./harness/sdkTestContext.js"; + +const FINAL_TEXT = "WS_FALLBACK_RECOVERY_DONE"; +const TOOL_NAME = "fallback_recovery_step"; + +type Transport = "ws" | "http"; +type ProviderRequest = { transport: Transport; connectionId?: number; body: ProviderBody }; +type ProviderBody = { + input?: Array<{ type?: string; call_id?: string; output?: unknown }>; + previous_response_id?: string; + stream?: boolean; +}; + +/** + * Scripts one agent loop: two tool rounds, then a final answer. The reply depends + * only on how many tool results the request carries, so either transport can + * serve any step and the assertions observe which transport the runtime chose. + */ +function responseEvents(body: ProviderBody, id: string): Array> { + const toolResults = (body.input ?? []).filter((item) => item.type === "function_call_output"); + const created = { + type: "response.created", + response: { id, object: "response", status: "in_progress", output: [] }, + }; + if (toolResults.length < 2) { + const call = { + type: "function_call", + id: `fc-${toolResults.length + 1}`, + call_id: `call-${toolResults.length + 1}`, + name: TOOL_NAME, + arguments: JSON.stringify({ step: toolResults.length + 1 }), + status: "completed", + }; + return [ + created, + { type: "response.output_item.added", output_index: 0, item: call }, + { type: "response.output_item.done", output_index: 0, item: call }, + { + type: "response.completed", + response: { id, object: "response", status: "completed", output: [call] }, + }, + ]; + } + const message = { + id: "msg-final", + type: "message", + role: "assistant", + content: [{ type: "output_text", text: FINAL_TEXT, annotations: [] }], + }; + return [ + created, + { + type: "response.output_item.added", + output_index: 0, + item: { ...message, content: [] }, + }, + { + type: "response.content_part.added", + output_index: 0, + content_index: 0, + part: { type: "output_text", text: "" }, + }, + { + type: "response.output_text.delta", + output_index: 0, + content_index: 0, + delta: FINAL_TEXT, + }, + { type: "response.output_text.done", output_index: 0, content_index: 0, text: FINAL_TEXT }, + { type: "response.output_item.done", output_index: 0, item: message }, + { + type: "response.completed", + response: { id, object: "response", status: "completed", output: [message] }, + }, + ]; +} + +/** An OpenAI-compatible provider whose first WebSocket request fails before any output. */ +async function startProvider() { + const requests: ProviderRequest[] = []; + const errors: unknown[] = []; + let connectionCount = 0; + let wsRequestCount = 0; + const server = http.createServer((req, res) => { + void (async () => { + if (req.method !== "POST" || req.url !== "/v1/responses") { + throw new Error(`Unexpected provider request: ${req.method} ${req.url}`); + } + req.setEncoding("utf8"); + let raw = ""; + for await (const chunk of req) raw += chunk; + const body = JSON.parse(raw) as ProviderBody; + if (body.stream !== true) { + // Session title generation is a one-shot request outside the agent loop. + res.writeHead(200, { "content-type": "application/json" }); + res.end( + JSON.stringify({ + id: "resp-title", + object: "response", + status: "completed", + output: [ + { + id: "msg-title", + type: "message", + role: "assistant", + content: [{ type: "output_text", text: "Title", annotations: [] }], + }, + ], + }) + ); + return; + } + requests.push({ transport: "http", body }); + res.writeHead(200, { "content-type": "text/event-stream" }); + for (const event of responseEvents(body, `resp-http-${requests.length}`)) { + res.write(`data: ${JSON.stringify(event)}\n\n`); + } + res.end(); + })().catch((error: unknown) => { + errors.push(error); + res.destroy(error instanceof Error ? error : new Error(String(error))); + }); + }); + const sockets = new WebSocketServer({ server, path: "/v1/responses" }); + sockets.on("connection", (socket) => { + const connectionId = ++connectionCount; + socket.on("error", (error) => errors.push(error)); + socket.on("message", (raw) => { + const body = JSON.parse(raw.toString()) as ProviderBody; + requests.push({ transport: "ws", connectionId, body }); + const events = + ++wsRequestCount === 1 + ? [ + { + type: "error", + error: { + code: "service_unavailable", + type: "websocket_error", + message: "Controlled transient WebSocket failure", + }, + }, + ] + : responseEvents(body, `resp-ws-${wsRequestCount}`); + for (const event of events) socket.send(JSON.stringify(event)); + }); + }); + await new Promise((resolve, reject) => { + server.once("error", reject); + server.listen(0, "127.0.0.1", resolve); + }); + const address = server.address(); + if (address === null || typeof address === "string") { + throw new Error("Expected a bound TCP address for the provider."); + } + return { + baseUrl: `http://127.0.0.1:${address.port}/v1`, + requests, + connectionCount: () => connectionCount, + async stop() { + for (const socket of sockets.clients) socket.terminate(); + await new Promise((resolve) => sockets.close(() => resolve())); + server.closeAllConnections(); + await new Promise((resolve, reject) => + server.close((error) => (error ? reject(error) : resolve())) + ); + if (errors.length > 0) throw new AggregateError(errors, "Provider fixture failed."); + }, + }; +} + +// The dispatcher's fallback state lives for one run (one user message's agent +// loop), so recovery is only observable across the model calls of one send. +describe.each([ + { recovery: true, transports: ["ws", "http", "http", "ws"] }, + { recovery: false, transports: ["ws", "http", "http", "http"] }, +])("WebSocket fallback recovery=$recovery", async ({ recovery, transports }) => { + const provider = await startProvider(); + // Register first so the SDK context stops its client before this provider. + afterAll(() => provider.stop()); + const flag = String(recovery); + const { copilotClient: client } = await createSdkTestContext({ + useStdio: true, + copilotClientOptions: { + gitHubToken: DEFAULT_GITHUB_TOKEN, + env: { + COPILOT_CLI_DISABLE_WEBSOCKET_RESPONSES: "false", + WEBSOCKET_FALLBACK_RECOVERY: flag, + COPILOT_EXP_COPILOT_CLI_WEBSOCKET_FALLBACK_RECOVERY: flag, + }, + }, + }); + + it(`${recovery ? "returns to" : "stays off"} the socket after a transient failure within one run`, async () => { + const toolCalls: number[] = []; + const session = await client.createSession({ + model: "gpt-5-responses", + streaming: true, + provider: { + type: "openai", + baseUrl: provider.baseUrl, + apiKey: "test-provider-key", + wireApi: "responses", + transport: "websockets", + }, + tools: [ + defineTool<{ step: number }>(TOOL_NAME, { + description: "Records one step of the controlled agent loop", + parameters: { + type: "object", + properties: { step: { type: "number" } }, + required: ["step"], + }, + defer: "never", + handler: ({ step }) => { + toolCalls.push(step); + return `STEP_${step}_RESULT`; + }, + }), + ], + onPermissionRequest: approveAll, + }); + const events: SessionEvent[] = []; + const unsubscribe = session.on((event) => events.push(event)); + await withTestCleanup( + async () => { + const answer = await session.sendAndWait({ prompt: "Run the two steps." }); + // Validate the final assistant response arrived (guards against truncated captures) + expect(answer?.data.content).toBe(FINAL_TEXT); + + const { requests } = provider; + expect(requests.map((request) => request.transport)).toEqual(transports); + // The failed socket request is replayed once over HTTP, then each + // tool runs exactly once whichever transport carried its turn. + expect(toolCalls).toEqual([1, 2]); + expect(events.filter((event) => event.type === "session.error")).toEqual([]); + const deltas = events.flatMap((event) => + event.type === "assistant.message_delta" ? [event.data.deltaContent] : [] + ); + expect(deltas.join("")).toBe(FINAL_TEXT); + + const last = requests.at(-1)!; + const results = (last.body.input ?? []).filter( + (item) => item.type === "function_call_output" + ); + expect(results.map((item) => item.call_id)).toEqual(["call-1", "call-2"]); + expect(JSON.stringify(results)).toContain("STEP_1_RESULT"); + expect(JSON.stringify(results)).toContain("STEP_2_RESULT"); + if (recovery) { + // A fresh socket carries the full history, not a continuation + // of the response it never produced. + expect(last.connectionId).toBe(2); + expect(last.body.previous_response_id).toBeUndefined(); + expect(provider.connectionCount()).toBe(2); + } else { + expect(provider.connectionCount()).toBe(1); + } + }, + unsubscribe, + () => session.disconnect(), + async () => { + const errors = await client.stop(); + if (errors.length > 0) { + throw new AggregateError(errors, "Client cleanup failed"); + } + } + ); + }); +}); diff --git a/nodejs/test/e2e/websocket_http_fallback.e2e.test.ts b/nodejs/test/e2e/websocket_http_fallback.e2e.test.ts new file mode 100644 index 0000000000..d8cd382285 --- /dev/null +++ b/nodejs/test/e2e/websocket_http_fallback.e2e.test.ts @@ -0,0 +1,143 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +import { afterAll, describe, expect, it } from "vitest"; +import { z } from "zod"; +import { approveAll, defineTool, type SessionEvent } from "../../src/index.js"; +import { withTestCleanup } from "../helpers/withTestCleanup.js"; +import { createSdkTestContext, DEFAULT_GITHUB_TOKEN } from "./harness/sdkTestContext.js"; +import { + createCompletedResponse, + startWebSocketResponsesServer, +} from "./harness/webSocketResponsesServer.js"; + +const TOOL_NAME = "ws_fallback_marker"; +const TOOL_RESULT = "WS_FALLBACK_TOOL_RESULT"; +const FINAL = "WS_FALLBACK_FINAL_ANSWER"; +const usage = { + input_tokens: 10, + output_tokens: 5, + total_tokens: 15, + input_tokens_details: { cached_tokens: 0 }, + output_tokens_details: { reasoning_tokens: 0 }, +}; + +describe("WebSocket to HTTP fallback telemetry", async () => { + const upstream = await startWebSocketResponsesServer(); + // Register first so the SDK context stops its client and proxy before this provider. + afterAll(() => upstream.stop()); + const { copilotClient: client, openAiEndpoint } = await createSdkTestContext({ + useStdio: true, + copilotClientOptions: { + gitHubToken: DEFAULT_GITHUB_TOKEN, + env: { + COPILOT_API_URL: upstream.baseUrl, + COPILOT_CLI_DISABLE_WEBSOCKET_RESPONSES: "false", + }, + }, + }); + upstream.forwardHttpTo(openAiEndpoint.url); + + it("describes the WebSocket failure on the downgrading call and keeps it sticky", async () => { + const { state } = upstream; + state.responses.push({ + type: "error", + error: { + code: "service_unavailable", + type: "websocket_error", + message: "controlled WebSocket outage", + }, + }); + state.httpResponses.push( + [ + { + type: "response.completed", + response: { + ...createCompletedResponse("", "resp-http-tool"), + output: [ + { + type: "function_call", + id: "fc-1", + call_id: "call-1", + name: TOOL_NAME, + arguments: JSON.stringify({ input: "ping" }), + status: "completed", + }, + ], + output_text: "", + usage, + }, + }, + ], + [ + { + type: "response.completed", + response: { ...createCompletedResponse(FINAL, "resp-http-final"), usage }, + }, + ] + ); + const session = await client.createSession({ + model: "gpt-5-responses", + onPermissionRequest: approveAll, + tools: [ + defineTool(TOOL_NAME, { + description: "Returns a fixed marker", + parameters: z.object({ input: z.string() }), + handler: () => TOOL_RESULT, + }), + ], + }); + const events: SessionEvent[] = []; + const unsubscribe = session.on((event) => events.push(event)); + await withTestCleanup( + async () => { + const answer = await session.sendAndWait({ prompt: "Use the marker tool." }); + // Validate the final assistant response arrived (guards against truncated captures) + expect(answer?.data.content).toBe(FINAL); + + expect(state.messages).toHaveLength(1); + expect(state.httpResponseRequestPaths).toHaveLength(2); + expect(state.httpResponses).toHaveLength(0); + expect( + events.some( + (event) => + event.type === "tool.execution_complete" && + JSON.stringify(event.data).includes(TOOL_RESULT) + ) + ).toBe(true); + + const usages = events.flatMap((event) => + event.type === "assistant.usage" ? [event.data] : [] + ); + expect(usages).toHaveLength(2); + const descriptors = { + websocketFallbackReason: "api_error", + websocketFallbackErrorKind: "http_status", + websocketFallbackStatusCode: 503, + websocketFallbackReconnectAttempted: false, + }; + expect(usages[0]).toMatchObject({ + ...descriptors, + websocketFallbackStartedThisCall: true, + }); + expect(usages[0].websocketFallbackCloseCode).toBeUndefined(); + expect(usages[1]).toMatchObject({ + ...descriptors, + websocketFallbackStartedThisCall: false, + }); + expect( + usages.every((data) => typeof data.websocketFallbackAfterMs === "number") + ).toBe(true); + }, + unsubscribe, + () => session.disconnect(), + async () => { + const errors = await client.stop(); + if (errors.length > 0) { + throw new AggregateError(errors, "WebSocket fallback client cleanup failed"); + } + } + ); + }); +}); diff --git a/nodejs/test/e2e/workflow.e2e.test.ts b/nodejs/test/e2e/workflow.e2e.test.ts index 349c7b7783..5d2673ea40 100644 --- a/nodejs/test/e2e/workflow.e2e.test.ts +++ b/nodejs/test/e2e/workflow.e2e.test.ts @@ -532,10 +532,15 @@ it("allows a module-level extension watcher to start a workflow while another bo const extensionDir = join(workDir, ".github", "extensions", "workflow-smoke"); await using session = await setupWorkflowExtension(workDir); - const parked = Promise.allSettled([ - session.workflow.run("parked", { notifyOnComplete: false }), - ]); + // Admission must finish before starting the body-entry deadline. + const parked = await session.rpc.workflow.run({ + name: "parked", + args: {}, + options: { notifyOnComplete: false }, + }); + const completion = Promise.allSettled([session.workflow.waitForRun(parked.runId)]); try { + expect(parked.status).toBe("running"); await retry( "wait for the parked workflow to enter its body", async () => { @@ -564,14 +569,14 @@ it("allows a module-level extension watcher to start a workflow while another bo ); } finally { writeFileSync(join(extensionDir, "release"), "release"); - const [outcome] = await parked; + const [outcome] = await completion; if (outcome.status === "rejected") { console.error("Parked workflow failed during cleanup:", outcome.reason); } else if (outcome.value.status !== "completed") { console.error("Parked workflow did not complete during cleanup:", outcome.value); } } - await expect(parked).resolves.toMatchObject([ + await expect(completion).resolves.toMatchObject([ { status: "fulfilled", value: { status: "completed", result: "released" }, diff --git a/nodejs/test/e2e/workiq_discovery.e2e.test.ts b/nodejs/test/e2e/workiq_discovery.e2e.test.ts index 249d982770..e17eda6809 100644 --- a/nodejs/test/e2e/workiq_discovery.e2e.test.ts +++ b/nodejs/test/e2e/workiq_discovery.e2e.test.ts @@ -15,7 +15,7 @@ import { createE2eRequestHandler } from "../../../test/harness/mockHandlers.js"; describe("account-derived WorkIQ", async () => { const { env, workDir, openAiEndpoint } = await createSdkTestContext(); - it("discovers disabled WorkIQ without requesting authentication or persisting a server", async () => { + it("discovers WorkIQ only on supported platforms without requesting authentication or persisting a server", async () => { const profile = join(workDir, "workiq-profile"); await mkdir(profile, { recursive: true }); await writeFile( @@ -53,22 +53,29 @@ describe("account-derived WorkIQ", async () => { ]) ); const discovered = await session.rpc.mcp.list(); - expect(discovered.servers).toEqual( - expect.arrayContaining([ - expect.objectContaining({ - name: "WorkIQ", - source: "account", - status: "disabled", - url: "https://workiq.svc.cloud.microsoft/mcp", - }), - ]) - ); + const expected = + process.platform === "linux" + ? [] + : [ + expect.objectContaining({ + name: "WorkIQ", + source: "account", + status: "disabled", + url: "https://workiq.svc.cloud.microsoft/mcp", + }), + ]; + expect(discovered.servers.filter((server) => server.name === "WorkIQ")).toEqual(expected); expect(discovered).not.toHaveProperty("accountServerConfigs"); - await session.rpc.mcp.reload(); + // Gating off WorkIQ can leave this fixture with no MCP configuration to reload. + if (discovered.servers.length === 0) { + await expect(session.rpc.mcp.reload()).rejects.toThrow( + "MCP config reload not available" + ); + } else { + await session.rpc.mcp.reload(); + } const reloaded = await session.rpc.mcp.list(); - expect(reloaded.servers.find((server) => server.name === "WorkIQ")?.status).toBe( - "disabled" - ); + expect(reloaded.servers.filter((server) => server.name === "WorkIQ")).toEqual(expected); expect(authenticationRequests).toBe(0); await expect(readFile(join(profile, "mcp-config.json"))).rejects.toMatchObject({ code: "ENOENT", @@ -142,7 +149,7 @@ describe("account-derived WorkIQ", async () => { expect(result).not.toHaveProperty("accountServerConfigs"); }); - it.each(["silent", "browser"] as const)( + it.skipIf(process.platform === "linux").each(["silent", "browser"] as const)( "authenticates through %s, persists renewal, and disconnects on parent logout", async (authentication) => { const profile = join(workDir, `workiq-${authentication}-profile`); @@ -371,7 +378,10 @@ describe("account-derived WorkIQ", async () => { { timeoutMessage: "WorkIQ did not connect after enablement" } ); const initialGrantCount = authentication === "browser" ? 2 : 1; - expect(grants).toHaveLength(initialGrantCount); + expect( + grants.map((grant) => Object.fromEntries(grant)), + JSON.stringify({ requests, browserScopes }) + ).toHaveLength(initialGrantCount); expect(grants[0].get("client_id")).toBe("ba081686-5d24-4bc6-a0d6-d034ecffed87"); expect(grants[0].get("refresh_token")).toBe("parent-refresh"); expect(grants[0].get("scope")).toContain(scope); diff --git a/nodejs/test/extension.test.ts b/nodejs/test/extension.test.ts index 9bb6b11ae9..132f5a8fc7 100644 --- a/nodejs/test/extension.test.ts +++ b/nodejs/test/extension.test.ts @@ -1,6 +1,6 @@ import { afterEach, describe, expect, it, vi } from "vitest"; import { CopilotClient } from "../src/client.js"; -import { approveAll } from "../src/index.js"; +import { approveAll, type CopilotSession } from "../src/index.js"; import { createCanvas, defineWorkflow, joinSession } from "../src/extension.js"; import { defaultJoinSessionPermissionHandler } from "../src/types.js"; @@ -111,6 +111,22 @@ describe("joinSession", () => { }); }); + it.each([true, false])( + "strips image generation consent (%s) from extension joins", + async (enabled) => { + process.env.SESSION_ID = "session-123"; + const resumeForExtension = vi + .spyOn(CopilotClient.prototype, "resumeSessionForExtension") + .mockResolvedValue({} as CopilotSession); + + const config = { tools: [], imageGeneration: { enabled } }; + await joinSession(config); + + const [, forwarded] = resumeForExtension.mock.calls[0]!; + expect(forwarded).not.toHaveProperty("imageGeneration"); + } + ); + it("exports the canvas helper from the extension surface", () => { const canvas = createCanvas({ id: "counter", diff --git a/nodejs/test/ffiRuntimeHost.test.ts b/nodejs/test/ffiRuntimeHost.test.ts index b5badfb27c..f7e19686fd 100644 --- a/nodejs/test/ffiRuntimeHost.test.ts +++ b/nodejs/test/ffiRuntimeHost.test.ts @@ -109,6 +109,18 @@ describe("FfiRuntimeHost callback cleanup", () => { vi.useRealTimers(); }); + it("forwards the CLI login sync integration without a CLI entrypoint", async () => { + const host = FfiRuntimeHost.create("runtime.node", undefined, undefined, [ + "--cli-login-sync-integration-id=github/github-app", + ]); + await host.start(); + + const argv = JSON.parse(ffi.hostStart.async.mock.calls[0][0].toString()) as string[]; + expect(argv).toEqual(["--cli-login-sync-integration-id=github/github-app"]); + + await host.dispose(); + }); + it("finishes disposal after a false close while retaining resources for the detached retry", async () => { ffi.connectionClose.async.mockImplementationOnce((_id, callback) => callback(null, false)); const host = FfiRuntimeHost.create("runtime.node", undefined, undefined, []); diff --git a/nodejs/test/hook-identity.test.ts b/nodejs/test/hook-identity.test.ts new file mode 100644 index 0000000000..ecf510e8d2 --- /dev/null +++ b/nodejs/test/hook-identity.test.ts @@ -0,0 +1,67 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. + +import { describe, expect, it } from "vitest"; +import type { MessageConnection } from "vscode-jsonrpc/node.js"; +import { CopilotSession } from "../src/session.js"; +import type { BaseHookInput, SessionHooks } from "../src/types.js"; + +const hooks = [ + ["preToolUse", "onPreToolUse"], + ["preMcpToolCall", "onPreMcpToolCall"], + ["postToolUse", "onPostToolUse"], + ["postToolUseFailure", "onPostToolUseFailure"], + ["userPromptSubmitted", "onUserPromptSubmitted"], + ["userPromptTransformed", "onUserPromptTransformed"], + ["sessionStart", "onSessionStart"], + ["sessionEnd", "onSessionEnd"], + ["errorOccurred", "onErrorOccurred"], + ["agentStop", "onAgentStop"], + ["subagentStart", "onSubagentStart"], + ["subagentStop", "onSubagentStop"], +] as const; + +describe("typed hook identity", () => { + for (const [hook, handler] of hooks) { + for (const enriched of [true, false]) { + it(`${hook} preserves ${enriched ? "child identity" : "old root input"}`, async () => { + const session = new CopilotSession("callback-owner", {} as MessageConnection); + const captured: BaseHookInput[] = []; + session.registerHooks({ + [handler]: (input: BaseHookInput, invocation: { sessionId: string }) => { + expect(invocation.sessionId).toBe("callback-owner"); + captured.push(input); + }, + } as SessionHooks); + const identity = { + agentId: "child-actor", + agentType: "explore", + parentAgentId: "parent-actor", + }; + await session._handleHooksInvoke(hook, { + sessionId: "reported-session", + timestamp: 1, + cwd: "work", + ...(enriched ? identity : {}), + }); + expect(captured).toHaveLength(1); + const input = captured[0]; + if (input === undefined) { + throw new Error("Hook callback was not dispatched"); + } + expect(input.sessionId).toBe("reported-session"); + expect(input.timestamp).toEqual(new Date(1)); + expect([input.agentId, input.agentType, input.parentAgentId]).toEqual( + enriched + ? ["child-actor", "explore", "parent-actor"] + : [undefined, undefined, undefined] + ); + const oldRoot: BaseHookInput = { + sessionId: "root", + timestamp: new Date(1), + workingDirectory: "work", + }; + expect(oldRoot.agentId).toBeUndefined(); + }); + } + } +}); diff --git a/nodejs/test/image-generation.test.ts b/nodejs/test/image-generation.test.ts new file mode 100644 index 0000000000..93e5a4d71b --- /dev/null +++ b/nodejs/test/image-generation.test.ts @@ -0,0 +1,78 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. + +import { once } from "node:events"; +import { createServer } from "node:net"; +import { describe, expect, it, onTestFinished } from "vitest"; +import { + createMessageConnection, + StreamMessageReader, + StreamMessageWriter, + type MessageConnection, +} from "vscode-jsonrpc/node.js"; +import { + approveAll, + CopilotClient, + RuntimeConnection, + type ImageGenerationConfig, +} from "../src/index.js"; +import { getSdkProtocolVersion } from "../src/sdkProtocolVersion.js"; + +describe("image generation request serialization", () => { + it.each([undefined, {}, { enabled: true }, { enabled: false }] satisfies ( + | ImageGenerationConfig + | undefined + )[])("forwards %j on create and resume", async (imageGeneration) => { + const requests = new Map>(); + const connections: MessageConnection[] = []; + const server = createServer((socket) => { + const connection = createMessageConnection( + new StreamMessageReader(socket), + new StreamMessageWriter(socket) + ); + connections.push(connection); + connection.onRequest((method, params) => { + if (method === "connect") { + return { ok: true, protocolVersion: getSdkProtocolVersion(), version: "test" }; + } + if (method === "session.create" || method === "session.resume") { + const payload = params as Record; + requests.set(method, payload); + return { sessionId: payload.sessionId }; + } + return {}; + }); + connection.listen(); + }); + onTestFinished(async () => { + for (const connection of connections) connection.dispose(); + await new Promise((resolve, reject) => + server.close((error) => (error ? reject(error) : resolve())) + ); + }); + server.listen(0, "127.0.0.1"); + await once(server, "listening"); + const address = server.address(); + if (!address || typeof address === "string") throw new Error("Missing TCP address"); + const client = new CopilotClient({ + connection: RuntimeConnection.forUri(`127.0.0.1:${address.port}`), + }); + onTestFinished(() => client.forceStop()); + const session = await client.createSession({ + onPermissionRequest: approveAll, + imageGeneration, + }); + await client.resumeSession(session.sessionId, { + onPermissionRequest: approveAll, + imageGeneration, + }); + for (const method of ["session.create", "session.resume"]) { + const payload = requests.get(method); + expect(payload).toBeDefined(); + if (imageGeneration === undefined) { + expect(payload).not.toHaveProperty("imageGeneration"); + } else { + expect(payload?.imageGeneration).toEqual(imageGeneration); + } + } + }); +}); diff --git a/nodejs/test/inferenceRequest.test.ts b/nodejs/test/inferenceRequest.test.ts new file mode 100644 index 0000000000..43183eabf1 --- /dev/null +++ b/nodejs/test/inferenceRequest.test.ts @@ -0,0 +1,113 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +import { describe, expect, it } from "vitest"; +import { inferenceModelOptions, userMessageText } from "./e2e/harness/inferenceRequest"; + +describe("inference request model options", () => { + it.each([ + ["/chat/completions", { reasoning_effort: "high" }], + ["/responses", { reasoning: { effort: "high" } }], + ["/v1/messages", { output_config: { effort: "high" } }], + ["/messages", { output_config: { effort: "high" } }], + ])("reads model and effort from %s without consuming the request", async (path, effort) => { + const body = { model: "custom-wire-model", ...effort }; + const request = new Request(`https://provider.test${path}?api-version=test`, { + method: "POST", + body: JSON.stringify(body), + }); + expect(await inferenceModelOptions(request)).toEqual({ + model: "custom-wire-model", + reasoning_effort: "high", + }); + expect(await request.json()).toEqual(body); + }); + + it.each(["/models", "/user"])("ignores a non-inference request: %s", async (path) => { + expect( + await inferenceModelOptions(new Request(`https://provider.test${path}`)) + ).toBeUndefined(); + }); + + it("retains absent optional model options", async () => { + const request = new Request("https://provider.test/responses", { + method: "POST", + body: "{}", + }); + expect(await inferenceModelOptions(request)).toEqual({ + model: undefined, + reasoning_effort: undefined, + }); + }); + + it("retains arbitrary model IDs and effort strings", async () => { + const request = new Request("https://provider.test/chat/completions", { + method: "POST", + body: JSON.stringify({ model: "User Model Name", reasoning_effort: "custom-effort" }), + }); + expect(await inferenceModelOptions(request)).toEqual({ + model: "User Model Name", + reasoning_effort: "custom-effort", + }); + }); + + it("treats null optional effort fields as absent", async () => { + const request = new Request("https://provider.test/responses", { + method: "POST", + body: JSON.stringify({ model: "literal", reasoning: null }), + }); + expect(await inferenceModelOptions(request)).toEqual({ + model: "literal", + reasoning_effort: undefined, + }); + }); + + it("rejects malformed inference model fields", async () => { + const request = new Request("https://provider.test/responses", { + method: "POST", + body: JSON.stringify({ model: 123 }), + }); + await expect(inferenceModelOptions(request)).rejects.toThrow(); + }); +}); + +describe("inference request user text", () => { + const context = "Hook context\n\nRead the requested file."; + + it.each(["text", "input_text"])("reads %s content without assistant or tool text", (type) => { + const messages = [ + { role: "system", content: "Not user text" }, + { role: "user", content: [{ type, text: context }] }, + { role: "assistant", content: context }, + { type: "function_call_output", output: context }, + ]; + const body = type === "input_text" ? { input: messages } : { messages }; + expect(userMessageText(body)).toEqual([context]); + }); + + it("reads string input used by Responses", () => { + expect(userMessageText({ input: context })).toEqual([context]); + }); + + it("reads string user messages used by Chat Completions", () => { + expect(userMessageText({ messages: [{ role: "user", content: context }] })).toEqual([ + context, + ]); + }); + + it.each([{}, { input: [] }, { messages: [] }])("handles empty requests: %j", (body) => { + expect(userMessageText(body)).toEqual([]); + }); + + it("ignores non-text and malformed content parts", () => { + expect( + userMessageText({ + input: [ + { role: "user", content: [null, {}, { type: "input_image", text: context }] }, + { role: "user", content: null }, + ], + }) + ).toEqual([]); + }); +}); diff --git a/nodejs/test/java-codegen.test.ts b/nodejs/test/java-codegen.test.ts index d44fb039a5..28db7fe54a 100644 --- a/nodejs/test/java-codegen.test.ts +++ b/nodejs/test/java-codegen.test.ts @@ -1,6 +1,11 @@ +import type { JSONSchema7 } from "json-schema"; import { describe, expect, it } from "vitest"; -import { renderRpcTypes, renderRpcWrappers } from "../../java/scripts/codegen/java.ts"; +import { + generateRpcClass, + renderRpcTypes, + renderRpcWrappers, +} from "../../java/scripts/codegen/java.ts"; function rpcSource(files: Map, name: string): string { const source = files.get( @@ -10,6 +15,126 @@ function rpcSource(files: Map, name: string): string { return source!; } +describe("java RPC primitive results", () => { + it.each([ + ["boolean", "Boolean"], + ["integer", "Long"], + ["number", "Double"], + ["string", "String"], + ] as const)("preserves inline and referenced %s results", async (type, javaType) => { + const schema = { + server: { + sample: { + inline: { + rpcMethod: "sample.inline", + params: null, + result: { type }, + }, + referenced: { + rpcMethod: "sample.referenced", + visibility: "internal", + params: null, + result: { $ref: "#/definitions/ScalarResult" }, + }, + empty: { rpcMethod: "sample.empty", params: null, result: null }, + }, + }, + definitions: { ScalarResult: { type } }, + } satisfies Parameters[0]; + const code = rpcSource(await renderRpcWrappers(schema), "ServerSampleApi"); + + expect(code).toContain(`CompletableFuture<${javaType}> inline()`); + expect(code).toContain(`"sample.inline", java.util.Map.of(), ${javaType}.class`); + expect(code).toContain(`CompletableFuture<${javaType}> referenced()`); + expect(code).toContain(`"sample.referenced", java.util.Map.of(), ${javaType}.class`); + expect(code).toContain("CompletableFuture empty()"); + expect(code).toContain('"sample.empty", java.util.Map.of(), Void.class'); + }); +}); + +it("preserves boolean-map RPC results without widening them to JsonNode", async () => { + const files = await renderRpcWrappers({ + server: { + sample: { + flags: { + rpcMethod: "sample.flags", + params: null, + result: { type: "object", additionalProperties: { type: "boolean" } }, + }, + document: { + rpcMethod: "sample.document", + params: null, + result: { type: "object", additionalProperties: true }, + }, + }, + }, + }); + const source = rpcSource(files, "ServerSampleApi"); + expect(source).toContain("import java.util.Map;"); + expect(source).toContain("CompletableFuture> flags()"); + expect(source).toContain("constructMapType(Map.class, String.class, Boolean.class)"); + expect(source).toContain("CompletableFuture document()"); +}); + +describe("Java required opaque JSON parameters", () => { + it.each([ + ["user", "applyTopKey"], + ["repo", "writeKey"], + ])("preserves optional value presence in %s.settings.%s", async (namespace, action) => { + const rpcMethod = `${namespace}.settings.${action}`; + const files = await renderRpcWrappers({ + server: { + [namespace]: { + settings: { + [action]: { + rpcMethod, + params: { + type: "object", + properties: { + value: { "x-opaque-json": true } as JSONSchema7, + delete: { type: "boolean" }, + }, + }, + result: null, + }, + }, + }, + }, + }); + const source = rpcSource( + files, + `Server${namespace === "user" ? "User" : "Repo"}SettingsApi` + ); + expect(source).toContain(`return caller.invoke("${rpcMethod}", params, Void.class);`); + expect(source).not.toContain('putNull("value")'); + }); + + it("preserves a required JSON null without emitting absent optional fields", () => { + const opaque = { "x-opaque-json": true } as JSONSchema7; + const { code } = generateRpcClass( + "SettingsUpdateParams", + { + type: "object", + required: ["value"], + properties: { + value: opaque, + optionalValue: opaque, + }, + }, + new Map(), + "com.github.copilot.generated.rpc", + "internal", + true + ); + expect(code).toContain( + '@JsonInclude(JsonInclude.Include.ALWAYS)\n @JsonProperty("value") Object value' + ); + expect(code).not.toContain( + '@JsonInclude(JsonInclude.Include.ALWAYS)\n @JsonProperty("optionalValue")' + ); + }); +}); + describe("java RPC codegen visibility", () => { const schema = { server: { diff --git a/nodejs/test/python-codegen.test.ts b/nodejs/test/python-codegen.test.ts index e87cefb5c9..627f8ca52e 100644 --- a/nodejs/test/python-codegen.test.ts +++ b/nodejs/test/python-codegen.test.ts @@ -67,6 +67,19 @@ describe("Python RPC projection compatibility", () => { expect(code).toContain(`return list(await self._client.request("${method}"`); }); + it("keeps the managed permission discriminator independent of MCP URL kinds", () => { + expect(code).toContain("class ManagedPermissionOperationKind(Enum):"); + const operation = code + .split("class ManagedPermissionOperation:")[1] + ?.split("\n\n@dataclass")[0]; + expect(operation).toContain("kind: ManagedPermissionOperationKind"); + expect(operation).toContain('kind = ManagedPermissionOperationKind(obj.get("kind"))'); + expect(operation).toContain( + 'result["kind"] = to_enum(ManagedPermissionOperationKind, self.kind)' + ); + expect(operation).not.toContain("MCPServerCardURLKind"); + }); + it("preserves the existing Workflow checkpoint result API", () => { const result = "SessionWorkflowPauseAtCheckpointResult"; expect(code).toContain(`class ${result}:`); @@ -75,11 +88,6 @@ describe("Python RPC projection compatibility", () => { expect(code).toContain(`"${result}",`); }); - it("does not restore the retired Factory checkpoint aliases", () => { - expect(code).not.toContain("SessionFactoryPauseAtCheckpointResult"); - expect(code).not.toContain('"session.factory.pauseAtCheckpoint"'); - }); - it("keeps named connection-scoped host callback results", () => { expect(code).toContain( "async def materialize_session(self, params: HostSessionCreateCallback) -> HostSessionCreateResult:" diff --git a/nodejs/test/reasoning-summaries-types.test.ts b/nodejs/test/reasoning-summaries-types.test.ts new file mode 100644 index 0000000000..556b6765b4 --- /dev/null +++ b/nodejs/test/reasoning-summaries-types.test.ts @@ -0,0 +1,19 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +import { expectTypeOf, it } from "vitest"; +import type { + CopilotSession, + ModelGetReasoningSummariesRequest, + ModelGetReasoningSummariesResult, +} from "../src/index.js"; + +it("exports reasoning-summary request and result types from the package root", () => { + expectTypeOf().toEqualTypeOf< + Parameters[0] + >(); + expectTypeOf().toEqualTypeOf< + Awaited> + >(); +}); diff --git a/nodejs/test/rust-codegen.test.ts b/nodejs/test/rust-codegen.test.ts index 5f34d804b1..ce3ee6b426 100644 --- a/nodejs/test/rust-codegen.test.ts +++ b/nodejs/test/rust-codegen.test.ts @@ -17,6 +17,45 @@ import { import { legacyRequestSchema } from "./legacy-parameters-fixture.ts"; describe("Rust API type codegen", () => { + it("reuses the named managed operation and kind in request and result fields", () => { + const code = generateApiTypesCode({ + definitions: { + ManagedPermissionOperation: { + anyOf: [ + { + type: "object", + properties: { + kind: { type: "string", const: "url" }, + url: { type: "string" }, + }, + required: ["kind", "url"], + }, + ], + }, + ManagedPermissionsEvaluateRequest: { + type: "object", + properties: { + operations: { + type: "array", + items: { $ref: "#/definitions/ManagedPermissionOperation" }, + }, + }, + required: ["operations"], + }, + ManagedPermissionEvaluation: { + type: "object", + properties: { operation: { $ref: "#/definitions/ManagedPermissionOperation" } }, + required: ["operation"], + }, + }, + } as ApiSchema); + expect(code).toContain("pub operations: Vec,"); + expect(code).toContain("pub operation: ManagedPermissionOperation,"); + expect(code).toContain("pub kind: ManagedPermissionOperationKind,"); + expect(code).not.toContain("ManagedPermissionsEvaluateRequestOperationsItem"); + expect(code).not.toContain("ManagedPermissionEvaluationOperation"); + }); + it("keeps every noncanonical open identifier distinct from the default sentinel", () => { const code = generateApiTypesCode( postProcessSchema({ @@ -782,6 +821,42 @@ pub enum ${name} { }); describe("Rust x-legacy-parameters", () => { + it.each(["server", "session"] as const)( + "serializes unrelated %s requests and options by value", + (scope) => { + const { schema } = legacyRequestSchema(1, scope); + const rpc = generateRpcCode(schema); + expect(rpc.match(/serde_json::to_value\(params\)\?/g)).toHaveLength(2); + expect(rpc).not.toContain("serde_json::to_value(¶ms)"); + } + ); + + it("preserves optional settings value presence without inserting null", () => { + const rpc = generateRpcCode({ + server: { + user: { + settings: { + applyTopKey: { + rpcMethod: "user.settings.applyTopKey", + params: { + type: "object", + properties: { + topKey: { type: "string" }, + value: { "x-opaque-json": true } as JSONSchema7, + delete: { type: "boolean" }, + }, + required: ["topKey"], + }, + result: { type: "object" }, + }, + }, + }, + }, + }); + expect(rpc).toContain("let wire_params = serde_json::to_value(params)?;"); + expect(rpc).not.toContain('_obj.insert("value"'); + }); + const render = (additions: 0 | 1 | 2, scope: "server" | "session" = "server") => { const { schema } = legacyRequestSchema(additions, scope); const types = generateApiTypesCode(schema); diff --git a/nodejs/test/shared-codegen.test.ts b/nodejs/test/shared-codegen.test.ts index a4698ac549..ca229ca01c 100644 --- a/nodejs/test/shared-codegen.test.ts +++ b/nodejs/test/shared-codegen.test.ts @@ -7,6 +7,7 @@ import { fileURLToPath } from "node:url"; import { describe, expect, it, onTestFinished } from "vitest"; import { + addManagedApprovalRequiredToPermissionRequests, collectDefinitionCollections, collectExperimentalOnlyRpcReferencedDefinitionNames, collectReachableDefinitionNames, @@ -53,6 +54,38 @@ describe("codegen entrypoints", () => { }); describe("shared schema definition codegen utilities", () => { + it("adds managed approval metadata to workflow permission requests", () => { + const schema = { + definitions: { + PermissionRequestWorkflow: { + type: "object", + properties: { + kind: { const: "workflow" }, + }, + }, + PermissionRequestUnrelated: { + type: "object", + properties: { + kind: { const: "unrelated" }, + }, + }, + }, + } satisfies JSONSchema7; + + const result = addManagedApprovalRequiredToPermissionRequests(schema); + + expect(result.definitions?.PermissionRequestWorkflow).toMatchObject({ + properties: { + managedApprovalRequired: { + type: ["boolean", "null"], + }, + }, + }); + expect(result.definitions?.PermissionRequestUnrelated).not.toHaveProperty( + "properties.managedApprovalRequired" + ); + }); + it("shares open tier definitions across raw RPC and processed event schemas", () => { const tier = { type: "string", diff --git a/nodejs/test/toolSet.test.ts b/nodejs/test/toolSet.test.ts index b77b797072..8d3f9fa67b 100644 --- a/nodejs/test/toolSet.test.ts +++ b/nodejs/test/toolSet.test.ts @@ -498,6 +498,27 @@ describe("Empty-mode safe defaults", () => { expect(payload.enableSkills).toBe(true); }); + it("passes the create-time session name through on create", async () => { + const { client, spy } = await setupClient(); + await client.createSession({ + onPermissionRequest: approveAll, + availableTools: new ToolSet().addBuiltIn(BuiltInTools.Isolated), + name: "named at create", + }); + const payload = createPayload(spy); + expect(payload.name).toBe("named at create"); + }); + + it("omits the session name on create when it is not set", async () => { + const { client, spy } = await setupClient(); + await client.createSession({ + onPermissionRequest: approveAll, + availableTools: new ToolSet().addBuiltIn(BuiltInTools.Isolated), + }); + const payload = createPayload(spy); + expect(payload.name).toBeUndefined(); + }); + it("passes organizationCustomInstructions through on create", async () => { const { client, spy } = await setupClient(); await client.createSession({ @@ -544,6 +565,38 @@ describe("Empty-mode safe defaults", () => { expect(resumePayload.enableSkills).toBe(false); }); + it("passes enableGitHubCacheStore through on create and resume and omits it when unset", async () => { + const { client, spy } = await setupClient("copilot-cli"); + const session = await client.createSession({ + onPermissionRequest: approveAll, + enableGitHubCacheStore: true, + }); + expect(createPayload(spy).enableGitHubCacheStore).toBe(true); + await client.resumeSession(session.sessionId, { + onPermissionRequest: approveAll, + enableGitHubCacheStore: false, + }); + const resumePayload = spy.mock.calls.find(([m]) => m === "session.resume")![1] as any; + expect(resumePayload.enableGitHubCacheStore).toBe(false); + + spy.mockClear(); + const unset = await client.createSession({ onPermissionRequest: approveAll }); + await client.resumeSession(unset.sessionId, { onPermissionRequest: approveAll }); + for (const method of ["session.create", "session.resume"]) { + const payload = spy.mock.calls.find(([m]) => m === method)![1] as any; + expect(payload.enableGitHubCacheStore).toBeUndefined(); + } + }); + + it("does not default enableGitHubCacheStore in empty mode", async () => { + const { client, spy } = await setupClient(); + await client.createSession({ + onPermissionRequest: approveAll, + availableTools: new ToolSet().addBuiltIn(BuiltInTools.Isolated), + }); + expect(createPayload(spy).enableGitHubCacheStore).toBeUndefined(); + }); + it("forwards the four flags in copilot-cli mode when the app sets them", async () => { const { client, spy } = await setupClient("copilot-cli"); await client.createSession({ diff --git a/nodejs/tsconfig.test.json b/nodejs/tsconfig.test.json index 6f8ef72510..f6f04f3548 100644 --- a/nodejs/tsconfig.test.json +++ b/nodejs/tsconfig.test.json @@ -13,6 +13,7 @@ "test/ffiRuntimeHost.test.ts", "test/session-event-types.test.ts", "test/session-config-types.test.ts", + "test/reasoning-summaries-types.test.ts", "test/sandbox-config.test.ts", "test/message-source.test.ts", "test/legacy-request-compatibility.test.ts" diff --git a/nodejs/vitest.config.ts b/nodejs/vitest.config.ts index 54c1d1681b..e672e9799f 100644 --- a/nodejs/vitest.config.ts +++ b/nodejs/vitest.config.ts @@ -3,57 +3,6 @@ import { isByokBackend } from "./test/e2e/harness/testBackend"; 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/mcp_instruction_recovery.e2e.test.ts", - "**/test/e2e/memory_retrieval.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", -]; - // These suites own their provider or CAPI auth/request-handler setup. They run // in the ordinary CAPI job, not as repetitions of the matrix-selected backend. const selfConfiguredBackendE2E = [ @@ -69,6 +18,8 @@ const selfConfiguredBackendE2E = [ "**/test/e2e/per_session_auth.e2e.test.ts", "**/test/e2e/provider_endpoint.e2e.test.ts", "**/test/e2e/subagent_prompt_resume.e2e.test.ts", + "**/test/e2e/tools_anthropic_updates.e2e.test.ts", + "**/test/e2e/websocket_http_fallback.e2e.test.ts", "**/test/e2e/websocket_terminals.e2e.test.ts", ]; @@ -89,7 +40,6 @@ export default defineConfig({ "**/dist/**", "**/*.d.ts", "**/basic-test.ts", // Old manual test - ...(isInProcessTransport ? inProcessBlockedE2E : []), ...(isByokBackend ? selfConfiguredBackendE2E : []), ], }, diff --git a/python/README.md b/python/README.md index 91f433dc9c..6c63928da1 100644 --- a/python/README.md +++ b/python/README.md @@ -441,10 +441,12 @@ 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. +- `enable_github_cache_store` (bool): Requests the read-only GitHub cache store (`github-cache-store` capability) on create or resume. The runtime registers the `github_sql` tool only when its `GITHUB_CACHE_STORE` feature flag is also on. Defaults to disabled in every mode. - `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 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. +- `interactive_user` (bool): Declares that a person types this session's messages, as in an editor chat panel. Enables the runtime's user-message analysis telemetry for user-authored messages; automated messages stay excluded. Defaults to `False`. Ignored for cloud sessions. Re-supply it when cold-resuming a session; it is ignored when resuming a session that is already active. - `hooks` (SessionHooks): Hook handlers for session lifecycle events. See [Session Hooks](#session-hooks) section. ```python diff --git a/python/copilot/__init__.py b/python/copilot/__init__.py index 9e9a9fa8bf..ae3aa7e7ac 100644 --- a/python/copilot/__init__.py +++ b/python/copilot/__init__.py @@ -157,6 +157,7 @@ ExitPlanModeRequest, ExitPlanModeResult, GitHubMcpToolConfig, + ImageGenerationConfig, InfiniteSessionConfig, InputOptions, LargeToolOutputConfig, @@ -464,6 +465,7 @@ "ToolResult", "ToolResultType", "ToolSearchConfig", + "ImageGenerationConfig", "ToolSet", "UriRuntimeConnection", "UserInputHandler", diff --git a/python/copilot/client.py b/python/copilot/client.py index 2d0230023c..8113c400e5 100644 --- a/python/copilot/client.py +++ b/python/copilot/client.py @@ -114,6 +114,7 @@ ElicitationHandler, ExitPlanModeHandler, GitHubMcpToolConfig, + ImageGenerationConfig, InfiniteSessionConfig, LargeToolOutputConfig, McpAuthHandler, @@ -1751,6 +1752,9 @@ def __init__( self._ffi_host: FfiRuntimeHost | None = None self._inprocess_runtime_path: str | None = None self._inprocess_cli_entrypoint: str | None = None + self._cli_login_sync_integration_id = ( + os.environ.get("GITHUB_COPILOT_INTEGRATION_ID", "").strip() or None + ) if isinstance(connection, UriRuntimeConnection): if connection.connection_token is not None and len(connection.connection_token) == 0: @@ -2351,6 +2355,7 @@ async def create_session( on_permission_request: _PermissionHandlerFn | None = None, model: str | None = None, session_id: str | None = None, + name: str | None = None, client_name: str | None = None, reasoning_effort: ReasoningEffort | None = None, reasoning_summary: ReasoningSummary | None = None, @@ -2359,6 +2364,7 @@ async def create_session( tools: list[Tool] | None = None, system_message: SystemMessageConfig | None = None, tool_search: ToolSearchConfig | None = None, + image_generation: ImageGenerationConfig | None = None, available_tools: list[str] | ToolSet | None = None, excluded_tools: list[str] | ToolSet | None = None, on_user_input_request: UserInputHandler | None = None, @@ -2399,10 +2405,13 @@ async def create_session( enable_file_hooks: bool | None = None, enable_host_git_operations: bool | None = None, enable_session_store: bool | None = None, + enable_github_cache_store: bool | None = None, enable_skills: bool | None = None, included_builtin_skills: list[str] | None = None, skill_directories: list[str] | None = None, skill_provider: SkillProvider | None = None, + agent_directories: list[str] | None = None, + hook_directories: list[str] | None = None, plugin_directories: list[str] | None = None, instruction_directories: list[str] | None = None, disabled_skills: list[str] | None = None, @@ -2433,6 +2442,7 @@ async def create_session( exp_assignments: CopilotExpAssignmentResponse | None = None, enable_managed_settings: bool | None = None, enforce_managed_model_defaults: bool | None = None, + interactive_user: bool | None = None, github_mcp_tool_config: GitHubMcpToolConfig | None = None, managed_settings: ManagedSettings | None = None, ) -> CopilotSession: @@ -2449,6 +2459,12 @@ async def create_session( for the consumer to resolve via the pending permission RPC. model: The model to use for the session (e.g. ``"gpt-4"``). session_id: Optional session ID. If not provided, a UUID is generated. + name: Names the session at creation, as ``session.name.set`` would, and + is validated the same way: non-empty, no leading or trailing whitespace, + at most 100 UTF-16 units, no control characters, and no double quotes. + A name also makes the session persist from the start, so it can be listed + and resumed without ever running a turn. Local sessions only: cloud + creation rejects it. client_name: Optional client name for identification. reasoning_effort: Reasoning effort level for the model. reasoning_summary: Reasoning summary mode for supported models. @@ -2549,18 +2565,36 @@ async def create_session( organization_custom_instructions: Organization-level custom instructions. enable_on_demand_instruction_discovery: Enables on-demand instruction file discovery. - enable_file_hooks: Enables file-based hooks from ``.github/hooks/``. + enable_file_hooks: Enables repository-origin file hooks from ``.github/hooks/`` + and trusted repository-configured roots, not explicit hook_directories + or callback hooks. enable_host_git_operations: Enables git operations on the host filesystem. enable_session_store: Enables the cross-session store. + enable_github_cache_store: Requests the read-only GitHub cache store + (``github-cache-store`` capability). The runtime registers the + ``github_sql`` tool only when its ``GITHUB_CACHE_STORE`` feature flag + is also on. Defaults to disabled. enable_skills: Enables skill loading. - skill_directories: Directories to search for skills. + skill_directories: Explicit discovery-only skill roots. No general filesystem + grant. Available without enable_config_discovery, subject to skill/managed + disables. Relative paths resolve once when supplied against the request's + effective working_directory; retained roots do not move on cwd changes. + None and omission are equivalent; [] clears only the explicit SDK roots. skill_provider: **Experimental.** Session-scoped provider for SDK-owned skills. Providers are not persisted; re-supply the provider when resuming the session. Synchronous providers run on the event loop, so keep them fast or make them async. Not supported for cloud sessions. - instruction_directories: Additional directories to search for custom - instruction files. + agent_directories: Explicit discovery-only agent roots, separate from + custom_agents. Same path and clearing rules as skill_directories. Available + without enable_config_discovery, subject to source/managed disables. + hook_directories: Explicit discovery-only file-hook roots, separate from callback + hooks. Same path and clearing rules as skill_directories. Available without + enable_config_discovery or enable_file_hooks (repository-origin hooks only); + hook and managed disables still apply. + instruction_directories: Explicit discovery-only instruction roots using existing + SDK formats. Same path and clearing rules as skill_directories. Available + without enable_config_discovery, subject to instruction/managed disables. 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 @@ -2568,6 +2602,8 @@ async def create_session( already running. This does not change global MCP settings. infinite_sessions: Infinite session configuration. memory: Session memory configuration. + image_generation: **Experimental.** Image generation opt-in. Not persisted; re-supply + after runtime restart. See :class:`ImageGenerationConfig`. cloud: Creates a remote session in the cloud instead of a local session. Optionally associates repository metadata with the cloud session. @@ -2616,6 +2652,10 @@ async def create_session( controls are enforced for this session. Conflicting model changes are rejected, while overridable managed defaults remain mutable. Sent on the wire as ``enforceManagedModelDefaults``. + interactive_user: Declares that a human drives this session's + conversation, enabling user-message analysis telemetry for + user-authored messages. Automated messages remain excluded. + Defaults to False for SDK sessions. Ignored for cloud sessions. 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 @@ -2715,6 +2755,8 @@ async def create_session( if tool_search is not None: payload["toolSearch"] = _tool_search_to_wire(tool_search) + if image_generation is not None: + payload["imageGeneration"] = dict(image_generation) if available_tools is not None: payload["availableTools"] = available_tools @@ -2777,6 +2819,9 @@ async def create_session( if enforce_managed_model_defaults is not None: payload["enforceManagedModelDefaults"] = enforce_managed_model_defaults + if interactive_user is not None: + payload["interactiveUser"] = interactive_user + # Host-injected managed settings (permissions-only contract) if managed_settings is not None: payload["managedSettings"] = _managed_settings_to_dict(managed_settings) @@ -2885,14 +2930,20 @@ async def create_session( payload["enableHostGitOperations"] = enable_host_git_operations if enable_session_store is not None: payload["enableSessionStore"] = enable_session_store + if enable_github_cache_store is not None: + payload["enableGitHubCacheStore"] = enable_github_cache_store if enable_skills is not None: payload["enableSkills"] = enable_skills if skill_provider is not None: payload["hasSkillProvider"] = True # Add skill directories configuration if provided - if skill_directories: + if skill_directories is not None: payload["skillDirectories"] = skill_directories + if agent_directories is not None: + payload["agentDirectories"] = agent_directories + if hook_directories is not None: + payload["hookDirectories"] = hook_directories # Add plugin directories configuration if provided if plugin_directories: @@ -2960,6 +3011,8 @@ async def create_session( ) if local_session_id is not None: payload["sessionId"] = local_session_id + if name is not None: + payload["name"] = name github_token_provider_registration_id = self._register_github_token_provider( github_token_provider, local_session_id ) @@ -3239,6 +3292,7 @@ async def resume_session( tools: list[Tool] | None = None, system_message: SystemMessageConfig | None = None, tool_search: ToolSearchConfig | None = None, + image_generation: ImageGenerationConfig | None = None, available_tools: list[str] | ToolSet | None = None, excluded_tools: list[str] | ToolSet | None = None, on_user_input_request: UserInputHandler | None = None, @@ -3278,10 +3332,13 @@ async def resume_session( enable_file_hooks: bool | None = None, enable_host_git_operations: bool | None = None, enable_session_store: bool | None = None, + enable_github_cache_store: bool | None = None, enable_skills: bool | None = None, included_builtin_skills: list[str] | None = None, skill_directories: list[str] | None = None, skill_provider: SkillProvider | None = None, + agent_directories: list[str] | None = None, + hook_directories: list[str] | None = None, plugin_directories: list[str] | None = None, instruction_directories: list[str] | None = None, disabled_skills: list[str] | None = None, @@ -3315,6 +3372,7 @@ async def resume_session( exp_assignments: CopilotExpAssignmentResponse | None = None, enable_managed_settings: bool | None = None, enforce_managed_model_defaults: bool | None = None, + interactive_user: bool | None = None, github_mcp_tool_config: GitHubMcpToolConfig | None = None, managed_settings: ManagedSettings | None = None, ) -> CopilotSession: @@ -3426,17 +3484,35 @@ async def resume_session( organization_custom_instructions: Organization-level custom instructions. enable_on_demand_instruction_discovery: Enables on-demand instruction file discovery. - enable_file_hooks: Enables file-based hooks from ``.github/hooks/``. + enable_file_hooks: Enables repository-origin file hooks from ``.github/hooks/`` + and trusted repository-configured roots, not explicit hook_directories + or callback hooks. enable_host_git_operations: Enables git operations on the host filesystem. enable_session_store: Enables the cross-session store. + enable_github_cache_store: Requests the read-only GitHub cache store + (``github-cache-store`` capability). The runtime registers the + ``github_sql`` tool only when its ``GITHUB_CACHE_STORE`` feature flag + is also on. Defaults to disabled. enable_skills: Enables skill loading. - skill_directories: Directories to search for skills. + skill_directories: Explicit discovery-only skill roots. No general filesystem + grant. Available without enable_config_discovery, subject to skill/managed + disables. Relative paths resolve once when supplied against the request's + effective working_directory; retained roots do not move on cwd changes. + None and omission are equivalent; [] clears only the explicit SDK roots. skill_provider: **Experimental.** Session-scoped provider for SDK-owned skills. Providers are not persisted; callers must re-supply the provider on resume. Synchronous providers run on the event loop, so keep them fast or make them async. - instruction_directories: Additional directories to search for custom - instruction files. + agent_directories: Explicit discovery-only agent roots, separate from + custom_agents. Same path and clearing rules as skill_directories. Available + without enable_config_discovery, subject to source/managed disables. + hook_directories: Explicit discovery-only file-hook roots, separate from callback + hooks. Same path and clearing rules as skill_directories. Available without + enable_config_discovery or enable_file_hooks (repository-origin hooks only); + hook and managed disables still apply. + instruction_directories: Explicit discovery-only instruction roots using existing + SDK formats. Same path and clearing rules as skill_directories. Available + without enable_config_discovery, subject to instruction/managed disables. 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 @@ -3444,6 +3520,8 @@ async def resume_session( already running. This does not change global MCP settings. infinite_sessions: Infinite session configuration. memory: Session memory configuration. + image_generation: **Experimental.** Image generation opt-in. Omission preserves a + resident session's setting; re-supply after runtime restart. on_event: Callback for session events. enable_mcp_apps: **Experimental.** Opt into MCP Apps (SEP-1865) UI passthrough on resume. This parameter is part of an experimental @@ -3499,6 +3577,10 @@ async def resume_session( controls are enforced after resume. Conflicting model changes are rejected, while overridable managed defaults remain mutable. Sent on the wire as ``enforceManagedModelDefaults``. + interactive_user: Re-supplies that a human drives this session so a + session loaded from storage keeps the declaration. It is not + persisted and is ignored if the session is already active in the + runtime process. 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 @@ -3589,6 +3671,8 @@ async def resume_session( payload["systemMessage"] = wire_system_message if tool_search is not None: payload["toolSearch"] = _tool_search_to_wire(tool_search) + if image_generation is not None: + payload["imageGeneration"] = dict(image_generation) if available_tools is not None: payload["availableTools"] = available_tools if excluded_tools is not None: @@ -3678,6 +3762,9 @@ async def resume_session( if enforce_managed_model_defaults is not None: payload["enforceManagedModelDefaults"] = enforce_managed_model_defaults + if interactive_user is not None: + payload["interactiveUser"] = interactive_user + # Host-injected managed settings (permissions-only contract) if managed_settings is not None: payload["managedSettings"] = _managed_settings_to_dict(managed_settings) @@ -3702,6 +3789,8 @@ async def resume_session( payload["enableHostGitOperations"] = enable_host_git_operations if enable_session_store is not None: payload["enableSessionStore"] = enable_session_store + if enable_github_cache_store is not None: + payload["enableGitHubCacheStore"] = enable_github_cache_store if enable_skills is not None: payload["enableSkills"] = enable_skills if skill_provider is not None: @@ -3743,8 +3832,12 @@ async def resume_session( if agent: payload["agent"] = agent - if skill_directories: + if skill_directories is not None: payload["skillDirectories"] = skill_directories + if agent_directories is not None: + payload["agentDirectories"] = agent_directories + if hook_directories is not None: + payload["hookDirectories"] = hook_directories if plugin_directories: payload["pluginDirectories"] = plugin_directories if instruction_directories is not None: @@ -4549,6 +4642,8 @@ def _convert_custom_agent_to_wire_format( The custom agent configuration in camelCase wire format. """ wire_agent: dict[str, Any] = {"name": agent.get("name"), "prompt": agent.get("prompt")} + if "hooks" in agent: + wire_agent["hooks"] = agent["hooks"] if "display_name" in agent: wire_agent["displayName"] = agent["display_name"] if "description" in agent: @@ -4800,6 +4895,8 @@ async def _start_inprocess_ffi(self) -> None: args.extend(["--session-idle-timeout", str(opts.session_idle_timeout_seconds)]) if opts.enable_remote_sessions: args.append("--remote") + if self._cli_login_sync_integration_id is not None: + args.append(f"--cli-login-sync-integration-id={self._cli_login_sync_integration_id}") environment: dict[str, str] = {} if opts.github_token: diff --git a/python/copilot/generated/rpc.py b/python/copilot/generated/rpc.py index c4010c7315..bc4a4e2ddb 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, AgentModelPolicy, Attachment, AutoTier, ContextTier, EmbeddedBlobResourceContents, EmbeddedTextResourceContents, IndexedSearchState, ManagedSettingsResolvedSource, McpOauthHttpResponse, McpOauthWWWAuthenticateParams, McpServerMetadata, McpServerSource, McpServerStatus, ModelChangeSource, ModelProviderKind, ModelProviderRef, PermissionDecisionSource, PermissionMode, PermissionPromptRequest, PermissionRule, ProviderQuotaState, ReasoningSummary, RemediationAction, SessionEvent, SessionLimitsConfig, SessionMode, ShutdownType, SkillSource, TaskBlocker, TaskCompletionOutcome, UsageGetMetricsResult, UserToolSessionApproval, Verbosity, _load_Attachment, _load_PermissionPromptRequest, _load_UserToolSessionApproval +from .session_events import AbortReason, AgentModelPolicy, Attachment, AutoTier, ContextTier, EmbeddedBlobResourceContents, EmbeddedTextResourceContents, IndexedSearchState, ManagedPermissionsContext, ManagedSettingsResolvedSource, McpOauthHttpResponse, McpOauthWWWAuthenticateParams, McpServerMetadata, McpServerSource, McpServerStatus, ModelChangeSource, ModelProviderKind, ModelProviderRef, PermissionDecisionSource, PermissionMode, PermissionPromptRequest, PermissionRule, ProviderQuotaState, ReasoningSummary, RemediationAction, SessionEvent, SessionLimitsConfig, SessionMode, ShutdownType, SkillSource, TaskBlocker, TaskCompletionOutcome, UsageGetMetricsResult, UserToolSessionApproval, Verbosity, _load_Attachment, _load_PermissionPromptRequest, _load_UserToolSessionApproval if TYPE_CHECKING: from .._jsonrpc import JsonRpcClient @@ -22,6 +22,7 @@ T = TypeVar("T") EnumT = TypeVar("EnumT", bound=Enum) +UNSET_SENTINEL = object() def from_str(x: Any) -> str: @@ -351,6 +352,31 @@ def to_dict(self) -> dict: class AccountAuthInfoType(Enum): ACCOUNT = "account" +# 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 _AccountDescribeAuthInfoResult: + """The wording the runtime uses for an authentication identity.""" + + description: str + """One-line description of the identity, suitable after a verb such as "Switched to".""" + + message: str + """Sign-in banner announcing the authenticated user.""" + + @staticmethod + def from_dict(obj: Any) -> '_AccountDescribeAuthInfoResult': + assert isinstance(obj, dict) + description = from_str(obj.get("description")) + message = from_str(obj.get("message")) + return _AccountDescribeAuthInfoResult(description, message) + + def to_dict(self) -> dict: + result: dict = {} + result["description"] = from_str(self.description) + 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 AccountGetCurrentAuthResult: @@ -377,6 +403,38 @@ def to_dict(self) -> dict: result["authInfo"] = from_union([lambda x: (x).to_dict(), from_none], self.auth_info) 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 _AccountGetGistSupportResult: + """Whether the identity can create gists, and why not when it cannot.""" + + supported: bool + """True when the identity can create gists.""" + + kind: str | None = None + """Machine-readable reason category when unsupported.""" + + reason: str | None = None + """Human-readable explanation to show where the gist action would have been.""" + + @staticmethod + def from_dict(obj: Any) -> '_AccountGetGistSupportResult': + assert isinstance(obj, dict) + supported = from_bool(obj.get("supported")) + kind = from_union([from_str, from_none], obj.get("kind")) + reason = from_union([from_str, from_none], obj.get("reason")) + return _AccountGetGistSupportResult(supported, kind, reason) + + def to_dict(self) -> dict: + result: dict = {} + result["supported"] = from_bool(self.supported) + if self.kind is not None: + result["kind"] = from_union([from_str, from_none], self.kind) + 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 AccountGetQuotaRequest: @@ -460,6 +518,49 @@ def to_dict(self) -> dict: result["resetDate"] = from_union([from_str, from_none], self.reset_date) 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 _AccountGetTokenEnvVarResult: + """The environment variable supplying a classic personal access token, when one is set.""" + + name: str | None = None + """Variable name, for example `GH_TOKEN`. Absent when no classic token is present in the + environment. + """ + + @staticmethod + def from_dict(obj: Any) -> '_AccountGetTokenEnvVarResult': + assert isinstance(obj, dict) + name = from_union([from_str, from_none], obj.get("name")) + return _AccountGetTokenEnvVarResult(name) + + def to_dict(self) -> dict: + result: dict = {} + if self.name is not None: + result["name"] = from_union([from_str, from_none], self.name) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +# Internal: this type is an internal SDK API and is not part of the public surface. +@dataclass +class _AccountGetWebURLResult: + """An absolute URL on the identity's GitHub host.""" + + url: str + """Absolute URL for the requested path.""" + + @staticmethod + def from_dict(obj: Any) -> '_AccountGetWebURLResult': + assert isinstance(obj, dict) + url = from_str(obj.get("url")) + return _AccountGetWebURLResult(url) + + def to_dict(self) -> dict: + result: dict = {} + result["url"] = from_str(self.url) + return result + # Experimental: this type is part of an experimental API and may change or be removed. class AccountKind(Enum): """The provider kind stamped on a signed-in account. @@ -808,6 +909,12 @@ def to_dict(self) -> dict: class AgentsDiscoverRequest: """Optional project paths to include in agent discovery.""" + agent_directories: list[str] | None = field(default=None, kw_only=True) + """Explicit discovery-only agent roots, separate from customAgents and filesystem grants. + Available without ambient discovery, subject to managed/source disables. Relative paths + resolve against the request's effective working directory when supplied. Null and + omission are equivalent; an empty array contributes no explicit roots. + """ exclude_host_agents: bool | None = None """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. @@ -820,12 +927,15 @@ class AgentsDiscoverRequest: @staticmethod def from_dict(obj: Any) -> 'AgentsDiscoverRequest': assert isinstance(obj, dict) + agent_directories = from_union([lambda x: from_list(from_str, x), from_none], obj.get("agentDirectories")) exclude_host_agents = from_union([from_bool, from_none], obj.get("excludeHostAgents")) project_paths = from_union([lambda x: from_list(from_str, x), from_none], obj.get("projectPaths")) - return AgentsDiscoverRequest(exclude_host_agents, project_paths) + return AgentsDiscoverRequest(exclude_host_agents, project_paths, agent_directories=agent_directories) def to_dict(self) -> dict: result: dict = {} + if self.agent_directories is not None: + result["agentDirectories"] = from_union([lambda x: from_list(from_str, x), from_none], self.agent_directories) if self.exclude_host_agents is not None: result["excludeHostAgents"] = from_union([from_bool, from_none], self.exclude_host_agents) if self.project_paths is not None: @@ -1026,6 +1136,12 @@ def to_dict(self) -> dict: class AgentsGetDiscoveryPathsRequest: """Optional project paths to include when enumerating agent discovery directories.""" + agent_directories: list[str] | None = field(default=None, kw_only=True) + """Explicit discovery-only agent roots to enumerate, including configured missing + directories. Relative paths resolve against the request's effective working directory + when supplied. These are not filesystem grants or preferred creation targets. Null and + omission are equivalent. + """ exclude_host_agents: bool | None = None """When true, omit the host's user-level agent directory, leaving only project directories. For multitenant deployments (mirrors `discover`'s `excludeHostAgents`). @@ -1038,12 +1154,15 @@ class AgentsGetDiscoveryPathsRequest: @staticmethod def from_dict(obj: Any) -> 'AgentsGetDiscoveryPathsRequest': assert isinstance(obj, dict) + agent_directories = from_union([lambda x: from_list(from_str, x), from_none], obj.get("agentDirectories")) exclude_host_agents = from_union([from_bool, from_none], obj.get("excludeHostAgents")) project_paths = from_union([lambda x: from_list(from_str, x), from_none], obj.get("projectPaths")) - return AgentsGetDiscoveryPathsRequest(exclude_host_agents, project_paths) + return AgentsGetDiscoveryPathsRequest(exclude_host_agents, project_paths, agent_directories=agent_directories) def to_dict(self) -> dict: result: dict = {} + if self.agent_directories is not None: + result["agentDirectories"] = from_union([lambda x: from_list(from_str, x), from_none], self.agent_directories) if self.exclude_host_agents is not None: result["excludeHostAgents"] = from_union([from_bool, from_none], self.exclude_host_agents) if self.project_paths is not None: @@ -1585,6 +1704,17 @@ class CardDigestAlgorithm(Enum): SHA256_RFC8785 = "sha256-rfc8785" +class CatalogAgentPluginInstallSourceKind(Enum): + """Discriminator for a materialisation install source + + Discriminator for a marketplace install source + + Discriminator for a repository install source + """ + MARKETPLACE = "marketplace" + MATERIALIZATION = "materialization" + REPOSITORY = "repository" + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class CatalogPluginRepositorySource: @@ -1675,6 +1805,24 @@ class CatalogTrustTier(Enum): T1 = "T1" T2 = "T2" +# Experimental: this type is part of an experimental API and may change or be removed. +class CatalogAgentPluginMarketplaceSourceKind(Enum): + """Discriminator for a marketplace install source""" + + MARKETPLACE = "marketplace" + +# Experimental: this type is part of an experimental API and may change or be removed. +class CatalogAgentPluginMaterializationSourceKind(Enum): + """Discriminator for a materialisation install source""" + + MATERIALIZATION = "materialization" + +# Experimental: this type is part of an experimental API and may change or be removed. +class CatalogAgentPluginRepositorySourceKind(Enum): + """Discriminator for a repository install source""" + + REPOSITORY = "repository" + # Experimental: this type is part of an experimental API and may change or be removed. class CatalogAISkillInstallability(Enum): """Whether this AI skill candidate can be planned for verified installation in the selected @@ -1722,23 +1870,6 @@ class CatalogAuthenticationRequiredReason(Enum): CREDENTIAL_REJECTED = "credential-rejected" NO_CREDENTIAL = "no-credential" -class CatalogInstallability(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 - - Whether this AI skill candidate can be planned for verified installation in the selected - session. - - Typed installability state for an AI skill candidate - """ - FEATURE_DISABLED = "feature-disabled" - INSTALLABLE = "installable" - MATERIALISATION_UNAVAILABLE = "materialisation-unavailable" - NOT_INSTALLABLE_KIND = "not-installable-kind" - NOT_INSTALLABLE_POLICY = "not-installable-policy" - POLICY_FORBIDS = "policy-forbids" - # 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""" @@ -1757,7 +1888,9 @@ class CatalogCapability(Enum): grant means the runtime understands the feature's contract, not that the deployment has enabled the operation; typed unavailable results report availability separately. """ + AGENT_PLUGIN_CONFIRMED_INSTALLATION = "agent-plugin-confirmed-installation" AGENT_PLUGIN_DISCOVERY = "agent-plugin-discovery" + AGENT_PLUGIN_FORMATS = "agent-plugin-formats" AI_SKILL_DISCOVERY = "ai-skill-discovery" CATALOG_SEARCH_CREDENTIAL_REQUIRED = "catalog-search-credential-required" CATALOG_SEARCH_PAGINATION = "catalog-search-pagination" @@ -1770,6 +1903,7 @@ class CatalogCapability(Enum): MCP_SERVER_CARD = "mcp-server-card" MULTIPLE_TRANSPORT_CHOICE = "multiple-transport-choice" SKILL_CONFIRMED_INSTALLATION = "skill-confirmed-installation" + SKILL_REFERENCE_INSTALLATION = "skill-reference-installation" TRUST_SNAPSHOT = "trust-snapshot" class CatalogContractViolationErrorKind(Enum): @@ -2869,6 +3003,49 @@ def to_dict(self) -> dict: result["path"] = from_str(self.path) return result +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class ContentExclusionValidatePoliciesRequest: + """Candidate content-exclusion policies to validate, as the raw JSON text a host received. + The text is supplied unparsed so that a syntax error and a schema violation are both + reported by the runtime's own validator rather than by the caller's JSON parser. + """ + policies_json: str + """Unparsed JSON text expected to hold an array of content-exclusion policies.""" + + @staticmethod + def from_dict(obj: Any) -> 'ContentExclusionValidatePoliciesRequest': + assert isinstance(obj, dict) + policies_json = from_str(obj.get("policiesJson")) + return ContentExclusionValidatePoliciesRequest(policies_json) + + def to_dict(self) -> dict: + result: dict = {} + result["policiesJson"] = from_str(self.policies_json) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class ContentExclusionValidatePoliciesResult: + """The accepted policies, parsed and normalized. A rejected input is reported as an error + rather than in this result. + """ + policies: list[Any] + """Validated policy objects in input order. Raw JSON preserves every field returned by the + runtime's policy parser. + """ + + @staticmethod + def from_dict(obj: Any) -> 'ContentExclusionValidatePoliciesResult': + assert isinstance(obj, dict) + policies = from_list(lambda x: x, obj.get("policies")) + return ContentExclusionValidatePoliciesResult(policies) + + def to_dict(self) -> dict: + result: dict = {} + result["policies"] = from_list(lambda x: x, self.policies) + return result + # Experimental: this type is part of an experimental API and may change or be removed. class ContentFilterMode(Enum): """Controls how MCP tool result content is filtered: none leaves content unchanged, markdown @@ -4142,6 +4319,180 @@ class ExternalToolTextResultForLlmContentTerminalType(Enum): class KindEnum(Enum): TEXT = "text" +# 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 _FeatureFlagAvailability(Enum): + """The population a feature flag is declared for, before overrides. + + The population the flag is declared for before any environment or user-settings override + is applied. + """ + EXPERIMENTAL = "experimental" + OFF = "off" + ON = "on" + STAFF = "staff" + STAFF_AND_EXPERIMENTAL = "staff-and-experimental" + STAFF_GITHUB = "staff-github" + STAFF_MICROSOFT = "staff-microsoft" + STAFF_OR_EXPERIMENTAL = "staff-or-experimental" + TEAM = "team" + +# 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 _FeatureFlagsResolveRequest: + """Caller population and overrides for static feature-flag resolution, independent of + experiment assignments and active runtime state. + """ + is_experimental: bool + """Whether the caller is in an experimental population.""" + + is_staff: bool + """Whether the caller is GitHub or Microsoft staff.""" + + is_team: bool + """Whether the caller belongs to the team population used by feature availability.""" + + base_flags: dict[str, bool] | None = None + """Resolve against this already-computed flag set rather than each flag's declared + availability. Keyed by flag name. + """ + config: Any = None + """User settings to apply, including `enabledFeatureFlags`, legacy `feature_flags.enabled`, + and supported convenience settings. + """ + environment: dict[str, str] | None = None + """Environment for direct feature-flag overrides and `COPILOT_CLI_ENABLED_FEATURE_FLAGS`. + Does not apply `COPILOT_EXP_*` experiment assignments. Defaults to this runtime process's + own environment. + """ + flag_overrides: dict[str, bool] | None = None + """Flags forced to a value after every other layer, keyed by flag name.""" + + is_staff_github: bool | None = None + """GitHub-staff membership. Omit when it was never recorded: a legacy staff account then + counts as both staff populations and keeps every staff feature. + """ + is_staff_microsoft: bool | None = None + """Microsoft-staff membership. Omitted the same way as `isStaffGitHub`.""" + + @staticmethod + def from_dict(obj: Any) -> '_FeatureFlagsResolveRequest': + assert isinstance(obj, dict) + is_experimental = from_bool(obj.get("isExperimental")) + is_staff = from_bool(obj.get("isStaff")) + is_team = from_bool(obj.get("isTeam")) + base_flags = from_union([lambda x: from_dict(from_bool, x), from_none], obj.get("baseFlags")) + config = obj.get("config") + environment = from_union([lambda x: from_dict(from_str, x), from_none], obj.get("environment")) + flag_overrides = from_union([lambda x: from_dict(from_bool, x), from_none], obj.get("flagOverrides")) + is_staff_github = from_union([from_bool, from_none], obj.get("isStaffGithub")) + is_staff_microsoft = from_union([from_bool, from_none], obj.get("isStaffMicrosoft")) + return _FeatureFlagsResolveRequest(is_experimental, is_staff, is_team, base_flags, config, environment, flag_overrides, is_staff_github, is_staff_microsoft) + + def to_dict(self) -> dict: + result: dict = {} + result["isExperimental"] = from_bool(self.is_experimental) + result["isStaff"] = from_bool(self.is_staff) + result["isTeam"] = from_bool(self.is_team) + if self.base_flags is not None: + result["baseFlags"] = from_union([lambda x: from_dict(from_bool, x), from_none], self.base_flags) + if self.config is not None: + result["config"] = self.config + if self.environment is not None: + result["environment"] = from_union([lambda x: from_dict(from_str, x), from_none], self.environment) + if self.flag_overrides is not None: + result["flagOverrides"] = from_union([lambda x: from_dict(from_bool, x), from_none], self.flag_overrides) + if self.is_staff_github is not None: + result["isStaffGithub"] = from_union([from_bool, from_none], self.is_staff_github) + if self.is_staff_microsoft is not None: + result["isStaffMicrosoft"] = from_union([from_bool, from_none], self.is_staff_microsoft) + 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 _FeatureFlagsResolveStartupGatesRequest: + """Caller population and startup inputs to resolve named online-experiment gates for. + Carries no account: the runtime resolves the assignment for the authentication it already + holds. + """ + flags: list[str] + """The feature flags to resolve. A name this runtime does not declare is reported as an + error rather than as a disabled flag. + """ + is_experimental: bool + """Whether the caller is in an experimental population.""" + + is_staff: bool + """Whether the caller is GitHub or Microsoft staff.""" + + is_team: bool + """Whether the caller belongs to a team plan.""" + + refresh_without_session: bool + """Whether the runtime may refresh the cached assignment over the network without an active + session. The refresh never blocks this call: the answer comes from the cache either way, + so a refresh only affects a later launch. + """ + streamer_mode: bool + """Whether streamer mode is on, which withholds the populations an experiment would + otherwise segment on. + """ + config: Any = None + """User settings document whose `featureFlags` block is layered on top of the resolved set.""" + + environment: dict[str, str] | None = None + """Environment to read flag overrides from. Defaults to this runtime process's own + environment. + """ + first_launch_at_millis: float | None = None + """When this installation first launched, in milliseconds since the Unix epoch. Experiments + that segment on installation age read it. Omit when the first launch was never recorded. + """ + is_staff_github: bool | None = None + """GitHub-staff membership. Omit when it was never recorded: a legacy staff account then + counts as both staff populations and keeps every staff feature. + """ + is_staff_microsoft: bool | None = None + """Microsoft-staff membership. Omitted the same way as `isStaffGitHub`.""" + + @staticmethod + def from_dict(obj: Any) -> '_FeatureFlagsResolveStartupGatesRequest': + assert isinstance(obj, dict) + flags = from_list(from_str, obj.get("flags")) + is_experimental = from_bool(obj.get("isExperimental")) + is_staff = from_bool(obj.get("isStaff")) + is_team = from_bool(obj.get("isTeam")) + refresh_without_session = from_bool(obj.get("refreshWithoutSession")) + streamer_mode = from_bool(obj.get("streamerMode")) + config = obj.get("config") + environment = from_union([lambda x: from_dict(from_str, x), from_none], obj.get("environment")) + first_launch_at_millis = from_union([from_float, from_none], obj.get("firstLaunchAtMillis")) + is_staff_github = from_union([from_bool, from_none], obj.get("isStaffGithub")) + is_staff_microsoft = from_union([from_bool, from_none], obj.get("isStaffMicrosoft")) + return _FeatureFlagsResolveStartupGatesRequest(flags, is_experimental, is_staff, is_team, refresh_without_session, streamer_mode, config, environment, first_launch_at_millis, is_staff_github, is_staff_microsoft) + + def to_dict(self) -> dict: + result: dict = {} + result["flags"] = from_list(from_str, self.flags) + result["isExperimental"] = from_bool(self.is_experimental) + result["isStaff"] = from_bool(self.is_staff) + result["isTeam"] = from_bool(self.is_team) + result["refreshWithoutSession"] = from_bool(self.refresh_without_session) + result["streamerMode"] = from_bool(self.streamer_mode) + if self.config is not None: + result["config"] = self.config + if self.environment is not None: + result["environment"] = from_union([lambda x: from_dict(from_str, x), from_none], self.environment) + if self.first_launch_at_millis is not None: + result["firstLaunchAtMillis"] = from_union([to_float, from_none], self.first_launch_at_millis) + if self.is_staff_github is not None: + result["isStaffGithub"] = from_union([from_bool, from_none], self.is_staff_github) + if self.is_staff_microsoft is not None: + result["isStaffMicrosoft"] = from_union([from_bool, from_none], self.is_staff_microsoft) + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class FleetStartRequest: @@ -4262,6 +4613,110 @@ def to_dict(self) -> dict: result["trusted"] = from_bool(self.trusted) 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 _FolderTrustIsTrustedParams: + """A folder to check against a persisted trusted-folders list, and the configuration + directory holding that list. + """ + path: str + """Folder to check. A folder counts as trusted when it is itself listed, when an ancestor is + listed, or when its git root is listed. + """ + config_dir: str | None = None + """Configuration directory to read the trusted-folders list from. Omit it to read the + default location; supply it when the host has redirected its configuration, or the check + answers from a list the host is not using. + """ + + @staticmethod + def from_dict(obj: Any) -> '_FolderTrustIsTrustedParams': + assert isinstance(obj, dict) + path = from_str(obj.get("path")) + config_dir = from_union([from_str, from_none], obj.get("configDir")) + return _FolderTrustIsTrustedParams(path, config_dir) + + def to_dict(self) -> dict: + result: dict = {} + result["path"] = from_str(self.path) + 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. +# Internal: this type is an internal SDK API and is not part of the public surface. +@dataclass +class _ForceLoginOrgsDecision: + """The verdict of the `forceLoginOrgs` enterprise sign-in pin for one credential. A managed + device may pin Copilot to an approved set of GitHub organizations so a developer cannot + sign in with a personal — or another enterprise's — account and escape every control that + keys off the signed-in identity. A host must refuse to proceed when `allowed` is false; + the remaining fields explain the refusal rather than qualifying it. + + The enterprise sign-in verdict for the credential this resolution ran against, evaluated + over the effective policy so a server-delivered pin is honored alongside a device one. + Computed on every resolution, including one that was not retained, so a host that + declines to apply policy still cannot proceed past a pin. + """ + allowed: bool + """Whether the client may proceed. True whenever no pin is in force, so an unmanaged host is + never blocked. + """ + enforced: bool + """Whether a pin applies at all. Distinguishes "allowed because unmanaged" from "allowed + because the account is approved", which a diagnostics surface needs and which also + decides whether a bring-your-own-key provider may serve inference. + """ + policy_diagnostics: list[str] + """Error-level detail from managed-policy discovery — which file failed to load, which field + failed validation. Carried on the decision rather than left to the log because a refusal + can exit the process before a log sink exists, which would discard the only actionable + diagnostic a misconfigured managed device produces. + """ + required_orgs: list[str] + """The approved organization logins, as configured. Enterprise configuration rather than + user data, so it is safe to name in a message or a log line. + """ + denial: str | None = None + """A stable kebab-case reason code when refused: `org-not-approved`, + `unverifiable-credential`, `policy-undetermined`, `policy-invalid`, `not-signed-in`, or + `membership-unknown`. Omitted when allowed. Treat an unrecognized code as a refusal + rather than mapping it to an allow: a code this host has not been taught about still + denied the credential. + """ + matched_org: str | None = None + """The approved organization the account matched, when allowed under a pin.""" + + message: str | None = None + """A developer-facing explanation of the refusal. Omitted when allowed.""" + + @staticmethod + def from_dict(obj: Any) -> '_ForceLoginOrgsDecision': + assert isinstance(obj, dict) + allowed = from_bool(obj.get("allowed")) + enforced = from_bool(obj.get("enforced")) + policy_diagnostics = from_list(from_str, obj.get("policyDiagnostics")) + required_orgs = from_list(from_str, obj.get("requiredOrgs")) + denial = from_union([from_str, from_none], obj.get("denial")) + matched_org = from_union([from_str, from_none], obj.get("matchedOrg")) + message = from_union([from_str, from_none], obj.get("message")) + return _ForceLoginOrgsDecision(allowed, enforced, policy_diagnostics, required_orgs, denial, matched_org, message) + + def to_dict(self) -> dict: + result: dict = {} + result["allowed"] = from_bool(self.allowed) + result["enforced"] = from_bool(self.enforced) + result["policyDiagnostics"] = from_list(from_str, self.policy_diagnostics) + result["requiredOrgs"] = from_list(from_str, self.required_orgs) + if self.denial is not None: + result["denial"] = from_union([from_str, from_none], self.denial) + if self.matched_org is not None: + result["matchedOrg"] = from_union([from_str, from_none], self.matched_org) + if self.message is not None: + result["message"] = from_union([from_str, from_none], self.message) + return result + class GhCLIAuthInfoType(Enum): GH_CLI = "gh-cli" @@ -4412,7 +4867,7 @@ def to_dict(self) -> dict: # 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 GitHubOwnersRequestIDResult: +class _GitHubOwnersRequestIDResult: """A freshly registered request id. Registering it before the listing starts is what lets a cancel that races the request still find the owner listing slot. The id serves one listing only. Long-abandoned unused ids can be released by later allocations. @@ -4421,10 +4876,10 @@ class GitHubOwnersRequestIDResult: """Request id to pass to `gitHubOwners.list` and, to abandon it, `gitHubOwners.cancel`.""" @staticmethod - def from_dict(obj: Any) -> 'GitHubOwnersRequestIDResult': + def from_dict(obj: Any) -> '_GitHubOwnersRequestIDResult': assert isinstance(obj, dict) request_id = from_int(obj.get("requestId")) - return GitHubOwnersRequestIDResult(request_id) + return _GitHubOwnersRequestIDResult(request_id) def to_dict(self) -> dict: result: dict = {} @@ -5085,6 +5540,13 @@ class HooksDiscoverRequest: still contribute to each remaining row's effective enabled state. This filters sources rather than simulating a host with no settings. """ + hook_directories: list[str] | None = field(default=None, kw_only=True) + """Explicit discovery-only file-hook roots, separate from callback hooks and filesystem + grants. Available without ambient discovery or repository file-hook enablement, subject + to managed/hook disables. Relative paths resolve against the request's effective working + directory when supplied. Null and omission are equivalent; an empty array contributes no + explicit roots. + """ 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 @@ -5095,13 +5557,16 @@ class HooksDiscoverRequest: def from_dict(obj: Any) -> 'HooksDiscoverRequest': assert isinstance(obj, dict) exclude_host_hooks = from_union([from_bool, from_none], obj.get("excludeHostHooks")) + hook_directories = from_union([lambda x: from_list(from_str, x), from_none], obj.get("hookDirectories")) project_paths = from_union([lambda x: from_list(from_str, x), from_none], obj.get("projectPaths")) - return HooksDiscoverRequest(exclude_host_hooks, project_paths) + return HooksDiscoverRequest(exclude_host_hooks, project_paths, hook_directories=hook_directories) 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.hook_directories is not None: + result["hookDirectories"] = from_union([lambda x: from_list(from_str, x), from_none], self.hook_directories) 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 @@ -5995,6 +6460,63 @@ class InstructionSourceType(Enum): REPO = "repo" VSCODE = "vscode" +# 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 _InstructionsDiscoverRepoSourcesRequest: + """Explicit discovery arguments for one location, supplied by the caller rather than derived + from a session. + """ + location: str + """Repository location to discover. An empty string selects no repository scope; cwdPath can + still supply working-directory sources. + """ + additional_dirs: list[str] | None = None + """Extra directories to scan alongside instruction directories configured in the runtime + environment. + """ + config_dir: str | None = None + """Copilot configuration directory for user-level sources. An omitted or empty value uses + the server's configuration home. + """ + cwd_path: str | None = None + """Working directory whose instruction sources should also be included. Omission adds no + separate working-directory scope. + """ + enable_child_instructions: bool | None = None + """When true, also discover instruction files nested below the location.""" + + is_cli: bool | None = None + """When true, include the sources a command-line host reads. Hosts that are not the CLI omit + it. + """ + + @staticmethod + def from_dict(obj: Any) -> '_InstructionsDiscoverRepoSourcesRequest': + assert isinstance(obj, dict) + location = from_str(obj.get("location")) + additional_dirs = from_union([lambda x: from_list(from_str, x), from_none], obj.get("additionalDirs")) + config_dir = from_union([from_str, from_none], obj.get("configDir")) + cwd_path = from_union([from_str, from_none], obj.get("cwdPath")) + enable_child_instructions = from_union([from_bool, from_none], obj.get("enableChildInstructions")) + is_cli = from_union([from_bool, from_none], obj.get("isCli")) + return _InstructionsDiscoverRepoSourcesRequest(location, additional_dirs, config_dir, cwd_path, enable_child_instructions, is_cli) + + def to_dict(self) -> dict: + result: dict = {} + result["location"] = from_str(self.location) + if self.additional_dirs is not None: + result["additionalDirs"] = from_union([lambda x: from_list(from_str, x), from_none], self.additional_dirs) + if self.config_dir is not None: + result["configDir"] = from_union([from_str, from_none], self.config_dir) + if self.cwd_path is not None: + result["cwdPath"] = from_union([from_str, from_none], self.cwd_path) + if self.enable_child_instructions is not None: + result["enableChildInstructions"] = from_union([from_bool, from_none], self.enable_child_instructions) + if self.is_cli is not None: + result["isCli"] = from_union([from_bool, from_none], self.is_cli) + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class InstructionsDiscoverRequest: @@ -6004,6 +6526,12 @@ 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. """ + instruction_directories: list[str] | None = field(default=None, kw_only=True) + """Explicit discovery-only instruction roots using existing SDK instruction formats, not + filesystem grants. Available without ambient discovery, subject to instruction/managed + disables. Relative paths resolve against the request's effective working directory when + supplied. Null and omission are equivalent; an empty array contributes no explicit roots. + """ project_paths: list[str] | None = None """Optional list of project directory paths to scan for repository/working-directory instruction sources. When omitted or empty, only user-level and plugin instruction @@ -6014,13 +6542,16 @@ class InstructionsDiscoverRequest: def from_dict(obj: Any) -> 'InstructionsDiscoverRequest': assert isinstance(obj, dict) exclude_host_instructions = from_union([from_bool, from_none], obj.get("excludeHostInstructions")) + instruction_directories = from_union([lambda x: from_list(from_str, x), from_none], obj.get("instructionDirectories")) project_paths = from_union([lambda x: from_list(from_str, x), from_none], obj.get("projectPaths")) - return InstructionsDiscoverRequest(exclude_host_instructions, project_paths) + return InstructionsDiscoverRequest(exclude_host_instructions, project_paths, instruction_directories=instruction_directories) def to_dict(self) -> dict: result: dict = {} if self.exclude_host_instructions is not None: result["excludeHostInstructions"] = from_union([from_bool, from_none], self.exclude_host_instructions) + if self.instruction_directories is not None: + result["instructionDirectories"] = from_union([lambda x: from_list(from_str, x), from_none], self.instruction_directories) 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 @@ -6034,6 +6565,12 @@ class InstructionsGetDiscoveryPathsRequest: """When true, omit the host's user-level instruction targets, leaving only repository targets. For multitenant deployments (mirrors `discover`'s `excludeHostInstructions`). """ + instruction_directories: list[str] | None = field(default=None, kw_only=True) + """Explicit discovery-only instruction roots to enumerate, including configured missing + directories. Relative paths resolve against the request's effective working directory + when supplied. These are not filesystem grants or preferred creation targets. Null and + omission are equivalent. + """ project_paths: list[str] | None = None """Optional list of project directory paths. When omitted or empty, only the user-level targets are returned. @@ -6043,17 +6580,40 @@ class InstructionsGetDiscoveryPathsRequest: def from_dict(obj: Any) -> 'InstructionsGetDiscoveryPathsRequest': assert isinstance(obj, dict) exclude_host_instructions = from_union([from_bool, from_none], obj.get("excludeHostInstructions")) + instruction_directories = from_union([lambda x: from_list(from_str, x), from_none], obj.get("instructionDirectories")) project_paths = from_union([lambda x: from_list(from_str, x), from_none], obj.get("projectPaths")) - return InstructionsGetDiscoveryPathsRequest(exclude_host_instructions, project_paths) + return InstructionsGetDiscoveryPathsRequest(exclude_host_instructions, project_paths, instruction_directories=instruction_directories) def to_dict(self) -> dict: result: dict = {} if self.exclude_host_instructions is not None: result["excludeHostInstructions"] = from_union([from_bool, from_none], self.exclude_host_instructions) + if self.instruction_directories is not None: + result["instructionDirectories"] = from_union([lambda x: from_list(from_str, x), from_none], self.instruction_directories) 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 +# 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 _InstructionsInitPromptResult: + """The prompt the init command runs.""" + + prompt: str + """Prompt text authoring repository instructions.""" + + @staticmethod + def from_dict(obj: Any) -> '_InstructionsInitPromptResult': + assert isinstance(obj, dict) + prompt = from_str(obj.get("prompt")) + return _InstructionsInitPromptResult(prompt) + + def to_dict(self) -> dict: + result: dict = {} + result["prompt"] = from_str(self.prompt) + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class InterruptMainTurnRequest: @@ -6376,6 +6936,26 @@ class HostType(Enum): ADO = "ado" GITHUB = "github" +# 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 _LocationPermissionsCleanupResult: + """Which stored location permissions were dropped because their directory is gone.""" + + removed_location_keys: list[str] + """Location keys whose grants were removed. Empty when every stored location still exists.""" + + @staticmethod + def from_dict(obj: Any) -> '_LocationPermissionsCleanupResult': + assert isinstance(obj, dict) + removed_location_keys = from_list(from_str, obj.get("removedLocationKeys")) + return _LocationPermissionsCleanupResult(removed_location_keys) + + def to_dict(self) -> dict: + result: dict = {} + result["removedLocationKeys"] = from_list(from_str, self.removed_location_keys) + return result + # Experimental: this type is part of an experimental API and may change or be removed. class SessionLogLevel(Enum): """Log severity level. Determines how the message is displayed in the timeline. Defaults to @@ -6483,6 +7063,33 @@ def to_dict(self) -> dict: 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. +class ManagedPermissionVerdict(Enum): + """Managed-policy decision only; never bypasses user permissions or other security + controls. + + Managed-policy verdict only: deny blocks the URL, ask requires approval, allow approves + under managed policy, and unmanaged leaves the normal consumer permission flow in effect. + No verdict bypasses other security controls. + """ + ALLOW = "allow" + ASK = "ask" + DENY = "deny" + UNMANAGED = "unmanaged" + +# Experimental: this type is part of an experimental API and may change or be removed. +class ManagedPluginRetryStatus(Enum): + """What the retry did for this plugin. + + What retrying did for one plugin required by managed settings. + """ + ALREADY_PRESENT = "already_present" + DEFERRED = "deferred" + FAILED = "failed" + INSTALLED = "installed" + NOT_REQUIRED = "not_required" + UPDATED = "updated" + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class ManagedSettingMeta: @@ -6526,6 +7133,86 @@ def to_dict(self) -> dict: result["requested"] = from_union([from_str, from_none], self.requested) 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 _ManagedSettingsApplyControlsParams: + """A user settings document and the effective managed policy whose controlled entries should + be stamped onto it. + """ + settings: Any + """The user settings document to stamp. Entries the managed layer does not name are returned + exactly as supplied. + """ + managed: Any = None + """The effective managed policy, as composed by `managedSettings.resolveEffectiveValue`. + Omit it on an unmanaged host: the settings then come back unchanged. + """ + + @staticmethod + def from_dict(obj: Any) -> '_ManagedSettingsApplyControlsParams': + assert isinstance(obj, dict) + settings = obj.get("settings") + managed = obj.get("managed") + return _ManagedSettingsApplyControlsParams(settings, managed) + + def to_dict(self) -> dict: + result: dict = {} + result["settings"] = self.settings + if self.managed is not None: + result["managed"] = self.managed + 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 _ManagedSettingsApplyControlsResult: + """The user settings with the managed layer's controlled entries stamped on.""" + + settings_json: Any + """The resulting settings document.""" + + @staticmethod + def from_dict(obj: Any) -> '_ManagedSettingsApplyControlsResult': + assert isinstance(obj, dict) + settings_json = obj.get("settingsJson") + return _ManagedSettingsApplyControlsResult(settings_json) + + def to_dict(self) -> dict: + result: dict = {} + result["settingsJson"] = self.settings_json + 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 _ManagedSettingsBypassDisableSource(Enum): + """Which layer disabled bypass-permissions mode. The layer matters because only a + server-delivered disable is pinned to an account and can later be lifted by signing in + elsewhere. + + Which layer currently disables bypass mode. Omit it when nothing does. + + Which layer disables bypass mode after this resolution. Omitted when none does, which is + what a lifted disable looks like. + """ + MANAGED = "managed" + MDM = "mdm" + USER = "user" + +# 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 _ManagedSettingsBypassPolicyResolution(Enum): + """What the managed-policy resolution said about bypass-permissions mode. `unknown` is a + third answer rather than a missing one, and the caller must send it explicitly when it + has no verdict. + + What the resolution said about bypass mode. Send `unknown` rather than omitting the field + when there is no verdict. + """ + ALLOWED = "allowed" + DISABLED = "disabled" + UNKNOWN = "unknown" + # Experimental: this type is part of an experimental API and may change or be removed. class ManagedSettingsChannel(Enum): """A channel accepted by managedSettings.compose. @@ -6590,16 +7277,18 @@ class ManagedSettingsResolvedData: shape as `session.managedSettings.get`, excluding session-local injection. """ bypass_permissions_disabled: bool - """Whether enterprise policy disables bypass-permissions ("yolo") mode for this session. - Deny-wins across layers, and forced on when `failClosed` is true. + """Whether an explicit enterprise policy restriction disables bypass-permissions ("yolo") + mode for this session. Deny-wins across layers; an unresolved policy does not force this + on by itself. """ device_managed: bool """Whether an actual device MDM/plist/registry/file managed-settings layer was present""" fail_closed: 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. + """Whether managed policy could not be determined (e.g. a failed server fetch) and + unresolved-policy safeguards remain active. This does not by itself disable + bypass-permissions; `bypassPermissionsDisabled` reports only an explicit policy + restriction. """ managed_keys: list[str] """The setting keys under enterprise management in the effective managed settings (e.g. @@ -6620,6 +7309,11 @@ class ManagedSettingsResolvedData: """Whether at least two managed sources supplied permission allowlists, so enforcement intersects them and the flattened settings payload omits `permissions.allow`. """ + permissions_context: ManagedPermissionsContext | None = None + """Reusable retained managed permission policy for managedSettings.permissions.evaluate, + including source composition and session-local injection. Provided by live session + snapshots; absent in older events. Refresh the context when managed policy changes. + """ policy_helper_managed: bool | None = None """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. @@ -6646,10 +7340,11 @@ def from_dict(obj: Any) -> 'ManagedSettingsResolvedData': source = ManagedSettingsResolvedSource(obj.get("source")) client_managed = from_union([from_bool, from_none], obj.get("clientManaged")) permissions_allow_intersected = from_union([from_bool, from_none], obj.get("permissionsAllowIntersected")) + permissions_context = from_union([ManagedPermissionsContext.from_dict, from_none], obj.get("permissionsContext")) policy_helper_managed = from_union([from_bool, from_none], obj.get("policyHelperManaged")) sandbox_enabled_by_undetermined_policy = from_union([from_bool, from_none], obj.get("sandboxEnabledByUndeterminedPolicy")) settings = obj.get("settings") - return ManagedSettingsResolvedData(bypass_permissions_disabled, device_managed, fail_closed, managed_keys, server_managed, source, client_managed, permissions_allow_intersected, policy_helper_managed, sandbox_enabled_by_undetermined_policy, settings) + return ManagedSettingsResolvedData(bypass_permissions_disabled, device_managed, fail_closed, managed_keys, server_managed, source, client_managed, permissions_allow_intersected, permissions_context, policy_helper_managed, sandbox_enabled_by_undetermined_policy, settings) def to_dict(self) -> dict: result: dict = {} @@ -6663,6 +7358,8 @@ def to_dict(self) -> dict: result["clientManaged"] = from_union([from_bool, from_none], self.client_managed) if self.permissions_allow_intersected is not None: result["permissionsAllowIntersected"] = from_union([from_bool, from_none], self.permissions_allow_intersected) + if self.permissions_context is not None: + result["permissionsContext"] = from_union([lambda x: to_class(ManagedPermissionsContext, x), from_none], self.permissions_context) if self.policy_helper_managed is not None: result["policyHelperManaged"] = from_union([from_bool, from_none], self.policy_helper_managed) if self.sandbox_enabled_by_undetermined_policy is not None: @@ -6721,6 +7418,186 @@ 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. +# Internal: this type is an internal SDK API and is not part of the public surface. +class _ManagedSettingsPolicyHelperReason(Enum): + """The policy-helper invocation reason. Defaults to sessionStart. + + The operation that requires a policy-helper response. + """ + PLUGIN_ACTIVATION = "pluginActivation" + SESSION_START = "sessionStart" + +# 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 _ManagedSettingsServerPolicyCacheMode(Enum): + """How this ingestion may use the on-disk server-policy cache. Defaults to the runtime's own + policy when omitted. + + How an ingestion may use the on-disk server-policy cache. + + How this resolution may use the on-disk server-policy cache. Defaults to the runtime's + own policy when omitted. Rejected when combined with `bypassCacheRead`, because the two + express the same decision and accepting both would leave the caller unsure which one the + runtime honored. + """ + CACHE_ONLY = "cacheOnly" + DISABLED = "disabled" + FALLBACK_ONLY = "fallbackOnly" + PREFER_FRESH = "preferFresh" + +# 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 _ManagedSettingsLogLevel(Enum): + """Severity of this diagnostic. + + Severity of a diagnostic emitted while ingesting managed settings. + """ + DEBUG = "debug" + ERROR = "error" + INFO = "info" + WARNING = "warning" + +# 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 _ManagedSettingsPolicyHelperAuthority(Enum): + """Which channel registered the policy helper that produced the helper layer. + + Which channel registered the policy helper that produced a helper layer. + """ + DEVICE = "device" + SERVER = "server" + +# 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 _ManagedSettingsServerPolicyResolution(Enum): + """How the server layer was resolved. + + How the server layer of an ingestion was resolved. + + How the current runtime resolved the server layer. + """ + CACHE_MISS = "cacheMiss" + FAIL_CLOSED = "failClosed" + FAIL_OPEN = "failOpen" + FALLBACK_CACHE = "fallbackCache" + FRESH_CACHE = "freshCache" + LIVE = "live" + SKIPPED_NO_TOKEN = "skippedNoToken" + +# 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 _ManagedSettingsLoadRemoteControlResult: + """The managed remote-control policy in force for an account, if any. An absent policy means + the account is unmanaged for this capability, which a host reads as allowed — so absence + here is a positive statement that no policy applies, never a stand-in for a resolution + that failed. A resolution that cannot be completed is rejected instead. + """ + remote_control: Any = None + """The account's `remoteControl` policy document. Absent when no managed policy governs + remote control for this account. Reported as an explicit disabled policy, rather than as + absence, when the service refused to deliver a policy this client is too old to enforce. + """ + + @staticmethod + def from_dict(obj: Any) -> '_ManagedSettingsLoadRemoteControlResult': + assert isinstance(obj, dict) + remote_control = obj.get("remoteControl") + return _ManagedSettingsLoadRemoteControlResult(remote_control) + + def to_dict(self) -> dict: + result: dict = {} + if self.remote_control is not None: + result["remoteControl"] = self.remote_control + 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 _ManagedSettingsParseClientValueParams: + """The client-supplied managed settings to validate and normalize. Omitting them is valid + and yields no policy. + """ + settings: Any = None + """The managed settings a client is supplying for a session it is about to open. Omit them + when the client is imposing no policy of its own. + """ + + @staticmethod + def from_dict(obj: Any) -> '_ManagedSettingsParseClientValueParams': + assert isinstance(obj, dict) + settings = obj.get("settings") + return _ManagedSettingsParseClientValueParams(settings) + + def to_dict(self) -> dict: + result: dict = {} + 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. +# Internal: this type is an internal SDK API and is not part of the public surface. +@dataclass +class _ManagedSettingsParseClientValueResult: + """The accepted managed settings, reduced to the keys a client is allowed to set.""" + + settings_json: Any = None + """The normalized settings. Omitted when the client supplied none. An accepted document that + sets nothing comes back as an empty object, which is distinct from omission. + """ + + @staticmethod + def from_dict(obj: Any) -> '_ManagedSettingsParseClientValueResult': + assert isinstance(obj, dict) + settings_json = obj.get("settingsJson") + return _ManagedSettingsParseClientValueResult(settings_json) + + def to_dict(self) -> dict: + result: dict = {} + 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. +# Internal: this type is an internal SDK API and is not part of the public surface. +@dataclass +class _ManagedSettingsReadDeviceResult: + """The device-managed settings layer, with the sandbox floor and the load outcome a host + needs to compose it into an effective policy. + """ + load_failed: bool + """Whether discovery or validation failed. A host must treat this as "policy unknown" rather + than "no policy": absent settings with this set false mean the device genuinely has none. + """ + sandbox_floor: Any = None + """Sandbox floor read from the discovered document before validation, so a document that is + otherwise rejected can still raise the floor it declared. + """ + settings: Any = None + """Validated, canonical device-managed settings. Omitted when no device policy was + discovered, when the discovered document failed validation, or when it validated to an + empty object — an empty document carries no policy, and reporting it as present would + outrank layers that do. + """ + + @staticmethod + def from_dict(obj: Any) -> '_ManagedSettingsReadDeviceResult': + assert isinstance(obj, dict) + load_failed = from_bool(obj.get("loadFailed")) + sandbox_floor = obj.get("sandboxFloor") + settings = obj.get("settings") + return _ManagedSettingsReadDeviceResult(load_failed, sandbox_floor, settings) + + def to_dict(self) -> dict: + result: dict = {} + result["loadFailed"] = from_bool(self.load_failed) + if self.sandbox_floor is not None: + result["sandboxFloor"] = self.sandbox_floor + 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 ManagedSettingsReadResult: @@ -6749,6 +7626,105 @@ def to_dict(self) -> dict: result["settingsJson"] = self.settings_json 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 _ManagedSettingsResolveDeviceForceLoginOrgsGateParams: + """The credential and environment to evaluate the device-channel sign-in pin against.""" + + environment: Any + """The host's environment variables. Read for the variables that would make an identity's + membership snapshot forgeable, so a credential whose provenance cannot be trusted is + refused rather than accepted. + """ + auth_info: Any = None + """The authenticated identity to judge. Omitted when signed out, which is the case the + device channel exists to serve: it can refuse a first sign-in before any credential + exists. + """ + + @staticmethod + def from_dict(obj: Any) -> '_ManagedSettingsResolveDeviceForceLoginOrgsGateParams': + assert isinstance(obj, dict) + environment = obj.get("environment") + auth_info = obj.get("authInfo") + return _ManagedSettingsResolveDeviceForceLoginOrgsGateParams(environment, auth_info) + + def to_dict(self) -> dict: + result: dict = {} + result["environment"] = self.environment + if self.auth_info is not None: + result["authInfo"] = self.auth_info + 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 _ManagedSettingsResolveEffectiveValueParams: + """The managed-settings layers to compose into an effective policy. Every layer is optional; + omitting all of them yields no effective policy. + """ + device_sandbox_floor: Any = None + """The device sandbox floor, folded into the device layer under `sandbox` before composition.""" + + mdm: Any = None + """The device layer: native MDM and managed-file settings describing the machine rather than + the account. + """ + policy_helper: Any = None + """The policy-helper layer, an enforced channel sourced from a platform policy helper + process. + """ + server: Any = None + """The account's server-delivered org policy, as returned by a managed-settings fetch. + Omitted before sign-in or when the fetch has not landed. + """ + + @staticmethod + def from_dict(obj: Any) -> '_ManagedSettingsResolveEffectiveValueParams': + assert isinstance(obj, dict) + device_sandbox_floor = obj.get("deviceSandboxFloor") + mdm = obj.get("mdm") + policy_helper = obj.get("policyHelper") + server = obj.get("server") + return _ManagedSettingsResolveEffectiveValueParams(device_sandbox_floor, mdm, policy_helper, server) + + def to_dict(self) -> dict: + result: dict = {} + if self.device_sandbox_floor is not None: + result["deviceSandboxFloor"] = self.device_sandbox_floor + if self.mdm is not None: + result["mdm"] = self.mdm + if self.policy_helper is not None: + result["policyHelper"] = self.policy_helper + if self.server is not None: + result["server"] = self.server + 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 _ManagedSettingsResolveEffectiveValueResult: + """The effective managed policy composed from the supplied layers.""" + + settings_json: Any = None + """The composed effective managed settings. Omitted when no layer sets anything, so an + unmanaged host locks nothing. Carries `sandboxAdvisoryFloorDeclined: true` when an + advisory server force-on was dropped because this host cannot run the sandbox. + """ + + @staticmethod + def from_dict(obj: Any) -> '_ManagedSettingsResolveEffectiveValueResult': + assert isinstance(obj, dict) + settings_json = obj.get("settingsJson") + return _ManagedSettingsResolveEffectiveValueResult(settings_json) + + def to_dict(self) -> dict: + result: dict = {} + 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 ManagedSettingsResolveRequest: @@ -6790,6 +7766,119 @@ 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. +# Internal: this type is an internal SDK API and is not part of the public surface. +@dataclass +class _ManagedSettingsRetainPolicyParams: + """The managed layers to retain as this process's startup policy, with the outcome of + fetching each one. + """ + device_load_failed: bool + """Whether the device load failed. Report it honestly, for the same reason as + `serverFetchFailed`. + """ + device_sandbox_undetermined: bool + """Whether the device sandbox intent is unreadable independently of its other settings.""" + + device_source_found: bool + """Whether a device policy source demonstrably exists. This, not a read error, is the + evidence a device is managed, so a fail-closed policy gates on it. + """ + managed_policy_present: bool + """Whether any managed policy source exists, independently of fetch failures.""" + + permission_policy_undetermined: bool + """Whether permission policy is undetermined. Sandbox-only uncertainty does not imply this + flag. + """ + preserve_server_state: bool + """Preserve the retained server layer during a device-only resolution.""" + + sandbox_enabled_by_undetermined_policy: bool + """Whether uncertainty alone enabled the shared sandbox floor.""" + + sandbox_floor_undetermined: bool + """Whether the shared sandbox policy is undetermined. Diagnostic errors alone do not imply + this flag. + """ + server_fetch_failed: bool + """Whether the server fetch failed. Report it honestly: true keeps the already-retained + server layer rather than replacing it with an absent one. + """ + server_forced_refresh_failure: bool + """Whether a required fresh server-policy confirmation failed.""" + + server_source_found: bool + """Whether the server policy source exists, even when its response is unavailable.""" + + device_response: Any = None + """The device/MDM policy layer.""" + + device_sandbox_floor: Any = None + """Sandbox floor declared by the device layer, carried separately so a document that failed + validation can still raise the floor. + """ + policy_helper_response: Any = None + """The policy helper's response: the third managed layer that a process owning its startup + policy resolves. It is retained with the other two so that every ambient read in this + process, including the mutation guards and the governed marketplace allowlist that the + `plugins.*` handlers derive, sees the whole policy the process fetched. A session-owning + process omits it, because it resolves a helper per session against that session's working + directory. + """ + server_response: Any = None + """The resolved server policy. A known source can lack a response after a forced-refresh + failure. + """ + shared_sandbox_floor: Any = None + """Shared server/device sandbox floor, excluding session-local helper policy.""" + + @staticmethod + def from_dict(obj: Any) -> '_ManagedSettingsRetainPolicyParams': + assert isinstance(obj, dict) + device_load_failed = from_bool(obj.get("deviceLoadFailed")) + device_sandbox_undetermined = from_bool(obj.get("deviceSandboxUndetermined")) + device_source_found = from_bool(obj.get("deviceSourceFound")) + managed_policy_present = from_bool(obj.get("managedPolicyPresent")) + permission_policy_undetermined = from_bool(obj.get("permissionPolicyUndetermined")) + preserve_server_state = from_bool(obj.get("preserveServerState")) + sandbox_enabled_by_undetermined_policy = from_bool(obj.get("sandboxEnabledByUndeterminedPolicy")) + sandbox_floor_undetermined = from_bool(obj.get("sandboxFloorUndetermined")) + server_fetch_failed = from_bool(obj.get("serverFetchFailed")) + server_forced_refresh_failure = from_bool(obj.get("serverForcedRefreshFailure")) + server_source_found = from_bool(obj.get("serverSourceFound")) + device_response = obj.get("deviceResponse") + device_sandbox_floor = obj.get("deviceSandboxFloor") + policy_helper_response = obj.get("policyHelperResponse") + server_response = obj.get("serverResponse") + shared_sandbox_floor = obj.get("sharedSandboxFloor") + return _ManagedSettingsRetainPolicyParams(device_load_failed, device_sandbox_undetermined, device_source_found, managed_policy_present, permission_policy_undetermined, preserve_server_state, sandbox_enabled_by_undetermined_policy, sandbox_floor_undetermined, server_fetch_failed, server_forced_refresh_failure, server_source_found, device_response, device_sandbox_floor, policy_helper_response, server_response, shared_sandbox_floor) + + def to_dict(self) -> dict: + result: dict = {} + result["deviceLoadFailed"] = from_bool(self.device_load_failed) + result["deviceSandboxUndetermined"] = from_bool(self.device_sandbox_undetermined) + result["deviceSourceFound"] = from_bool(self.device_source_found) + result["managedPolicyPresent"] = from_bool(self.managed_policy_present) + result["permissionPolicyUndetermined"] = from_bool(self.permission_policy_undetermined) + result["preserveServerState"] = from_bool(self.preserve_server_state) + result["sandboxEnabledByUndeterminedPolicy"] = from_bool(self.sandbox_enabled_by_undetermined_policy) + result["sandboxFloorUndetermined"] = from_bool(self.sandbox_floor_undetermined) + result["serverFetchFailed"] = from_bool(self.server_fetch_failed) + result["serverForcedRefreshFailure"] = from_bool(self.server_forced_refresh_failure) + result["serverSourceFound"] = from_bool(self.server_source_found) + if self.device_response is not None: + result["deviceResponse"] = self.device_response + if self.device_sandbox_floor is not None: + result["deviceSandboxFloor"] = self.device_sandbox_floor + if self.policy_helper_response is not None: + result["policyHelperResponse"] = self.policy_helper_response + if self.server_response is not None: + result["serverResponse"] = self.server_response + if self.shared_sandbox_floor is not None: + result["sharedSandboxFloor"] = self.shared_sandbox_floor + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class ManagedSettingsValidateRequest: @@ -7295,7 +8384,7 @@ def to_dict(self) -> dict: # 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 MCPConfigureGitHubRequest: +class _MCPConfigureGitHubRequest: """Credential-free authentication identity used to configure GitHub MCP.""" auth_info: Any @@ -7304,10 +8393,10 @@ class MCPConfigureGitHubRequest: """ @staticmethod - def from_dict(obj: Any) -> 'MCPConfigureGitHubRequest': + def from_dict(obj: Any) -> '_MCPConfigureGitHubRequest': assert isinstance(obj, dict) auth_info = obj.get("authInfo") - return MCPConfigureGitHubRequest(auth_info) + return _MCPConfigureGitHubRequest(auth_info) def to_dict(self) -> dict: result: dict = {} @@ -7717,6 +8806,28 @@ def to_dict(self) -> dict: result["running"] = from_bool(self.running) 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 _MCPListBuiltinServerNamesResult: + """Names of the MCP servers the runtime supplies itself.""" + + names: list[str] + """Every built-in server name. A host disabling the built-ins wholesale suppresses exactly + this set. + """ + + @staticmethod + def from_dict(obj: Any) -> '_MCPListBuiltinServerNamesResult': + assert isinstance(obj, dict) + names = from_list(from_str, obj.get("names")) + return _MCPListBuiltinServerNamesResult(names) + + def to_dict(self) -> dict: + result: dict = {} + result["names"] = from_list(from_str, self.names) + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class MCPListToolsRequest: @@ -8282,6 +9393,94 @@ def to_dict(self) -> dict: result["cursor"] = from_union([from_str, from_none], self.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 _MCPRegistryCancelRequest: + """Registry search to abandon.""" + + request_id: int + """Request ID from `mcp.registry.allocateRequestId` that the search uses.""" + + @staticmethod + def from_dict(obj: Any) -> '_MCPRegistryCancelRequest': + assert isinstance(obj, dict) + request_id = from_int(obj.get("requestId")) + return _MCPRegistryCancelRequest(request_id) + + def to_dict(self) -> dict: + result: dict = {} + result["requestId"] = from_int(self.request_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 _MCPRegistryCancelResult: + """Whether the cancel reached a live search.""" + + canceled: bool + """True when the cancel stopped a running search. False for unknown or reclaimed IDs, + completed or canceled searches, and unused reservations. The cancel releases an unused + reservation. + """ + + @staticmethod + def from_dict(obj: Any) -> '_MCPRegistryCancelResult': + assert isinstance(obj, dict) + canceled = from_bool(obj.get("canceled")) + return _MCPRegistryCancelResult(canceled) + + def to_dict(self) -> dict: + result: dict = {} + result["canceled"] = from_bool(self.canceled) + 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 _MCPRegistryRequestIDResult: + """Request id naming a cancellable registry search.""" + + request_id: int + """Request ID for `mcp.registry.search` and `mcp.registry.cancel`. It serves one search. + Allocation can reclaim unused IDs at the 1,024-ID reservation limit. + """ + + @staticmethod + def from_dict(obj: Any) -> '_MCPRegistryRequestIDResult': + assert isinstance(obj, dict) + request_id = from_int(obj.get("requestId")) + return _MCPRegistryRequestIDResult(request_id) + + def to_dict(self) -> dict: + result: dict = {} + result["requestId"] = from_int(self.request_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 _MCPRegistrySearchResult: + """Servers selected from the registry response.""" + + servers: Any + """The server objects, carried opaquely. The runtime follows pages, keeps the newest entry + per server name, cuts the list to `limit`, and sorts an empty-query result by GitHub + stars. Each server object remains unchanged because the registry owns that shape. + """ + + @staticmethod + def from_dict(obj: Any) -> '_MCPRegistrySearchResult': + assert isinstance(obj, dict) + servers = obj.get("servers") + return _MCPRegistrySearchResult(servers) + + def to_dict(self) -> dict: + result: dict = {} + result["servers"] = self.servers + return result + class MCPServerConfigType(Enum): """Local transport type. Defaults to stdio when omitted. @@ -8300,7 +9499,7 @@ class MCPServerConfigType(Enum): # Experimental: this type is part of an experimental API and may change or be removed. # Internal: this type is an internal SDK API and is not part of the public surface. @dataclass -class MCPReloadWithConfigRequest: +class _MCPReloadWithConfigRequest: """Opaque MCP reload configuration.""" config: Any @@ -8309,10 +9508,10 @@ class MCPReloadWithConfigRequest: """ @staticmethod - def from_dict(obj: Any) -> 'MCPReloadWithConfigRequest': + def from_dict(obj: Any) -> '_MCPReloadWithConfigRequest': assert isinstance(obj, dict) config = obj.get("config") - return MCPReloadWithConfigRequest(config) + return _MCPReloadWithConfigRequest(config) def to_dict(self) -> dict: result: dict = {} @@ -8434,7 +9633,7 @@ class MCPServerConfigHTTPType(Enum): # 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): +class _MCPServerConfigMemoryType(Enum): """In-process MCP transport type.""" MEMORY = "memory" @@ -8466,6 +9665,38 @@ class MCPSetEnvValueModeDetails(Enum): DIRECT = "direct" INDIRECT = "indirect" +# Experimental: this type is part of an experimental API and may change or be removed. +class OptionsUpdateToolFilterPrecedence(Enum): + """Controls how availableTools (allowlist) and excludedTools (denylist) combine when both + are set. + """ + AVAILABLE = "available" + EXCLUDED = "excluded" + +# 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 _MCPShouldExcludeGitHubToolsResult: + """Whether the gh-replaceable GitHub MCP tools may be clipped for the session described by + the request. + """ + exclude_gh_replaceable_tools: bool + """True only when both halves hold: the session's filters still reach the platform shell + tool, and the host actually has the `gh` those tools would be replaced by. Feed it + straight back as the `excludeGhReplaceableTools` build option. + """ + + @staticmethod + def from_dict(obj: Any) -> '_MCPShouldExcludeGitHubToolsResult': + assert isinstance(obj, dict) + exclude_gh_replaceable_tools = from_bool(obj.get("excludeGhReplaceableTools")) + return _MCPShouldExcludeGitHubToolsResult(exclude_gh_replaceable_tools) + + def to_dict(self) -> dict: + result: dict = {} + result["excludeGhReplaceableTools"] = from_bool(self.exclude_gh_replaceable_tools) + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class MCPStopServerRequest: @@ -8516,17 +9747,17 @@ def to_dict(self) -> dict: # 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 MCPUnregisterExternalClientRequest: +class _MCPUnregisterExternalClientRequest: """Server name identifying the external client to remove.""" server_name: str """Server name of the external client to unregister""" @staticmethod - def from_dict(obj: Any) -> 'MCPUnregisterExternalClientRequest': + def from_dict(obj: Any) -> '_MCPUnregisterExternalClientRequest': assert isinstance(obj, dict) server_name = from_str(obj.get("serverName")) - return MCPUnregisterExternalClientRequest(server_name) + return _MCPUnregisterExternalClientRequest(server_name) def to_dict(self) -> dict: result: dict = {} @@ -9323,6 +10554,92 @@ def to_dict(self) -> dict: result["cleared"] = from_bool(self.cleared) return result +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class ModelGetReasoningSummariesResult: + """Reasoning-summaries presentation policy for the session's current model.""" + + clear_redundant_setting: bool + """Whether the supplied `persistedShowReasoning` setting only restates the model default. + This reports a redundancy, not a standing instruction to delete the setting: it stays + true for as long as the setting and the model default agree, including for a user who + later chose that value deliberately. It exists for a one-time migration of a legacy + setting written before the model default made it redundant, so a host that acts on it + owns the cleanup and must gate it behind its own persisted `done` marker, delete only a + setting whose value still matches the one it read, and never repeat the deletion on a + later launch. Under an off-by-default reasoning-summaries experiment an explicit + `showReasoning: true` opt-in also reads as redundant on a summary-producing model, so an + ungated host would keep deleting that opt-in and hide summaries again on every launch. + """ + experiment_settled: bool + """Whether the session's reasoning-summaries experiment assignment has settled. False means + this read does not wait for a pending assignment and reports the experiment-derived + fields conservatively: `showHiddenHint` is false and `visibility` falls back to + `persistedShowReasoning`, or is omitted when none was supplied. A host that wants the + assigned answer re-reads until this is true. + """ + model_default_shows_summaries: bool + """Whether the selected model shows reasoning summaries by default.""" + + ready: bool + """False until the session has a model catalog and a selected model. This read never + resolves the catalog itself, and the runtime emits no readiness notification, so + repeating this call alone leaves `ready` false indefinitely on a session that has neither + listed models nor run a turn. `session.model.list` is the prerequisite and the signal: it + resolves and caches the catalog for the session, so a successful return means the next + read is ready. The exception is a session that resolves models without a Copilot catalog, + that is a BYOK registry or an alternate provider: `session.model.list` returns an empty + list without caching a catalog, so `ready` stays false and this policy is unavailable + there. While false, every model-policy field is false and `visibility` is omitted; + `experimentSettled` is independent of readiness and still reports the assignment state. + """ + session_shows_summaries: bool + """Whether turns on this session produce reasoning summaries, honouring an explicit session + reasoning-summary override. + """ + show_hidden_hint: bool + """Whether to show the hint that reasoning summaries are hidden. Always false while + `experimentSettled` is false. + """ + show_reasoning_headers: bool + """Whether the selected model emits reasoning headers.""" + + visibility: bool | None = None + """Default reasoning visibility, and only meaningful once `ready` is true: false when the + session produces no summaries, else the persisted setting, else the experiment's default. + While `ready` is true it is omitted only when all three are unavailable, that is when the + session produces summaries, no `persistedShowReasoning` was supplied, and + `experimentSettled` is false. While `ready` is false there is no model policy to report + and this field is always omitted, so an absent value alone does not imply a pending + experiment. + """ + + @staticmethod + def from_dict(obj: Any) -> 'ModelGetReasoningSummariesResult': + assert isinstance(obj, dict) + clear_redundant_setting = from_bool(obj.get("clearRedundantSetting")) + experiment_settled = from_bool(obj.get("experimentSettled")) + model_default_shows_summaries = from_bool(obj.get("modelDefaultShowsSummaries")) + ready = from_bool(obj.get("ready")) + session_shows_summaries = from_bool(obj.get("sessionShowsSummaries")) + show_hidden_hint = from_bool(obj.get("showHiddenHint")) + show_reasoning_headers = from_bool(obj.get("showReasoningHeaders")) + visibility = from_union([from_bool, from_none], obj.get("visibility")) + return ModelGetReasoningSummariesResult(clear_redundant_setting, experiment_settled, model_default_shows_summaries, ready, session_shows_summaries, show_hidden_hint, show_reasoning_headers, visibility) + + def to_dict(self) -> dict: + result: dict = {} + result["clearRedundantSetting"] = from_bool(self.clear_redundant_setting) + result["experimentSettled"] = from_bool(self.experiment_settled) + result["modelDefaultShowsSummaries"] = from_bool(self.model_default_shows_summaries) + result["ready"] = from_bool(self.ready) + result["sessionShowsSummaries"] = from_bool(self.session_shows_summaries) + result["showHiddenHint"] = from_bool(self.show_hidden_hint) + result["showReasoningHeaders"] = from_bool(self.show_reasoning_headers) + if self.visibility is not None: + result["visibility"] = from_union([from_bool, from_none], self.visibility) + return result + # Experimental: this type is part of an experimental API and may change or be removed. class ModelProviderAutomaticDiscoveryMode(Enum): """Whether automatic discovery is allowed, limited to configured providers, or @@ -9892,14 +11209,6 @@ class OptionsUpdateContextTier(Enum): DEFAULT = "default" LONG_CONTEXT = "long_context" -# Experimental: this type is part of an experimental API and may change or be removed. -class OptionsUpdateToolFilterPrecedence(Enum): - """Controls how availableTools (allowlist) and excludedTools (denylist) combine when both - are set. - """ - AVAILABLE = "available" - EXCLUDED = "excluded" - # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class PendingPermissionRequest: @@ -10427,6 +11736,62 @@ 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. +# Internal: this type is an internal SDK API and is not part of the public surface. +@dataclass +class _PermissionsEvaluateManagedPolicyParams: + """The verbatim managed `permissions` slice to interpret.""" + + permissions: Any = None + """The `permissions` object from a managed settings layer, for example + `{"disableBypassPermissionsMode":"disable"}`. Omitted or null means no managed policy + applies. + """ + + @staticmethod + def from_dict(obj: Any) -> '_PermissionsEvaluateManagedPolicyParams': + assert isinstance(obj, dict) + permissions = obj.get("permissions") + return _PermissionsEvaluateManagedPolicyParams(permissions) + + def to_dict(self) -> dict: + result: dict = {} + if self.permissions is not None: + result["permissions"] = self.permissions + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class PermissionsEvaluateManagedPolicyResult: + """The policy questions a host asks before granting elevated permissions.""" + + bypass_permissions_disabled: bool + """True when managed policy forbids bypass-permissions mode, so allow-all style flags must + not take effect. + """ + permission_rules_active: bool + """True when the managed layer supplies permission rules that the session must enforce.""" + + policy_undetermined: bool + """True when a managed policy exists but this build could not determine its meaning. Hosts + fail closed on it rather than assuming permission was granted. + """ + + @staticmethod + def from_dict(obj: Any) -> 'PermissionsEvaluateManagedPolicyResult': + assert isinstance(obj, dict) + bypass_permissions_disabled = from_bool(obj.get("bypassPermissionsDisabled")) + permission_rules_active = from_bool(obj.get("permissionRulesActive")) + policy_undetermined = from_bool(obj.get("policyUndetermined")) + return PermissionsEvaluateManagedPolicyResult(bypass_permissions_disabled, permission_rules_active, policy_undetermined) + + def to_dict(self) -> dict: + result: dict = {} + result["bypassPermissionsDisabled"] = from_bool(self.bypass_permissions_disabled) + result["permissionRulesActive"] = from_bool(self.permission_rules_active) + result["policyUndetermined"] = from_bool(self.policy_undetermined) + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class PermissionsFolderTrustAddTrustedResult: @@ -10755,6 +12120,66 @@ 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 PersistenceWriteRawJSONLockedRequest: + """A complete JSON snapshot to write under the runtime's per-path file lock.""" + + data_json: str + """The serialized JSON document to write. It replaces the file's contents entirely; nothing + is merged. + """ + path: str + """Absolute path of the file to replace. Parent directories are created when missing.""" + + file_header: str | None = None + """Comment lines to emit above the document, for a format that tolerates them. Omit for + strict JSON. + """ + + @staticmethod + def from_dict(obj: Any) -> 'PersistenceWriteRawJSONLockedRequest': + assert isinstance(obj, dict) + data_json = from_str(obj.get("dataJson")) + path = from_str(obj.get("path")) + file_header = from_union([from_none, from_str], obj.get("fileHeader")) + return PersistenceWriteRawJSONLockedRequest(data_json, path, file_header) + + def to_dict(self) -> dict: + result: dict = {} + result["dataJson"] = from_str(self.data_json) + result["path"] = from_str(self.path) + if self.file_header is not None: + result["fileHeader"] = from_union([from_none, from_str], self.file_header) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class PersistenceWriteRawJSONLockedResult: + """The outcome of a locked snapshot write.""" + + success: bool + """Whether the file now holds the requested contents.""" + + error: str | None = None + """Why the write failed. Absent when `success` is true. A lock the runtime could not acquire + in time reports here rather than raising. + """ + + @staticmethod + def from_dict(obj: Any) -> 'PersistenceWriteRawJSONLockedResult': + assert isinstance(obj, dict) + success = from_bool(obj.get("success")) + error = from_union([from_none, from_str], obj.get("error")) + return PersistenceWriteRawJSONLockedResult(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_none, from_str], self.error) + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class PingRequest: @@ -12467,9 +13892,182 @@ 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. +# Internal: this type is an internal SDK API and is not part of the public surface. +class _SettingsEditOperation(Enum): + """Set a scalar, add or remove a string list item, or remove the setting. The handler + rejects a missing or incompatible value. + + One atomic settings mutation. + """ + ADD_ITEM = "add-item" + REMOVE = "remove" + REMOVE_ITEM = "remove-item" + SET = "set" + +# 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 _RepoSettingsEditScope(Enum): + """Shared or local repository settings file. + + Which repository settings file to edit. + """ + LOCAL = "local" + REPO = "repo" + +# 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 _RepoSettingsReadRawRequest: + """Which repository settings file to read.""" + + repo_root: str + """Absolute path of the repository root.""" + + scope: str + """Use repo for shared settings or local for settings.local.json. Other scopes are rejected.""" + + @staticmethod + def from_dict(obj: Any) -> '_RepoSettingsReadRawRequest': + assert isinstance(obj, dict) + repo_root = from_str(obj.get("repoRoot")) + scope = from_str(obj.get("scope")) + return _RepoSettingsReadRawRequest(repo_root, scope) + + def to_dict(self) -> dict: + result: dict = {} + result["repoRoot"] = from_str(self.repo_root) + result["scope"] = from_str(self.scope) + 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 _RepoSettingsReadRawResult: + """Parsed repository settings without normalization or layering.""" + + config: Any + """Stored JSON values after the existing JSONC parser and prototype-safety checks. Empty + when the file is missing, blank, or invalid. + """ + invalid: bool + """True when the file exists but did not parse, so `config` is empty for that reason rather + than because nothing is set. + """ + + @staticmethod + def from_dict(obj: Any) -> '_RepoSettingsReadRawResult': + assert isinstance(obj, dict) + config = obj.get("config") + invalid = from_bool(obj.get("invalid")) + return _RepoSettingsReadRawResult(config, invalid) + + def to_dict(self) -> dict: + result: dict = {} + result["config"] = self.config + result["invalid"] = from_bool(self.invalid) + 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 _RepoSettingsWriteAllRequest: + """The complete set of repository-overridable settings to store.""" + + repo_root: str + """Absolute path of the repository root.""" + + scope: str + """Use repo for shared settings or local for settings.local.json. Other scopes are rejected.""" + + settings: Any + """Object of allowed settings to store. Missing allowlisted keys are removed. Other input + keys are ignored, while unknown stored keys are preserved. + """ + + @staticmethod + def from_dict(obj: Any) -> '_RepoSettingsWriteAllRequest': + assert isinstance(obj, dict) + repo_root = from_str(obj.get("repoRoot")) + scope = from_str(obj.get("scope")) + settings = obj.get("settings") + return _RepoSettingsWriteAllRequest(repo_root, scope, settings) + + def to_dict(self) -> dict: + result: dict = {} + result["repoRoot"] = from_str(self.repo_root) + result["scope"] = from_str(self.scope) + result["settings"] = self.settings + 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 _RepoSettingsWriteKeyRequest: + """One key to write or remove in a repository settings file.""" + + key: str + """Canonical top-level key permitted by the repository settings allowlist.""" + + repo_root: str + """Absolute path of the repository root.""" + + scope: str + """Use repo for shared settings or local for settings.local.json. Other scopes are rejected.""" + + delete: bool | None = None + """True to remove the key instead of writing it.""" + + value: Any = UNSET_SENTINEL + """Value to validate and write. Required unless delete is true; a null value is validated, + not treated as deletion. + """ + + @staticmethod + def from_dict(obj: Any) -> '_RepoSettingsWriteKeyRequest': + assert isinstance(obj, dict) + key = from_str(obj.get("key")) + repo_root = from_str(obj.get("repoRoot")) + scope = from_str(obj.get("scope")) + delete = from_union([from_bool, from_none], obj.get("delete")) + value = obj["value"] if "value" in obj else UNSET_SENTINEL + return _RepoSettingsWriteKeyRequest(key, repo_root, scope, delete, value) + + def to_dict(self) -> dict: + result: dict = {} + result["key"] = from_str(self.key) + result["repoRoot"] = from_str(self.repo_root) + result["scope"] = from_str(self.scope) + if self.delete is not None: + result["delete"] = from_union([from_bool, from_none], self.delete) + if self.value is not UNSET_SENTINEL: + result["value"] = self.value + return result + class ResponseFormatType(Enum): JSON_SCHEMA = "json_schema" +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class SandboxApplyDefaultsRequest: + """The stored sandbox settings to resolve against the defaults.""" + + stored: Any + """The sandbox settings as stored in the settings file. An empty object resolves to the + defaults alone. + """ + + @staticmethod + def from_dict(obj: Any) -> 'SandboxApplyDefaultsRequest': + assert isinstance(obj, dict) + stored = obj.get("stored") + return SandboxApplyDefaultsRequest(stored) + + def to_dict(self) -> dict: + result: dict = {} + result["stored"] = self.stored + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class SandboxConfigAuth: @@ -12679,6 +14277,34 @@ class SandboxConfigSource(Enum): 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 SandboxCredentialSuggestion: + """A possible secret-bearing environment variable and optional locally suggested HTTPS + injection hosts. This is a draft for user review, not an active grant. + """ + name: str + """Environment variable name. The secret value is never returned.""" + + suggested_inject_hosts: list[str] + """HTTPS hostnames suggested by a local known-provider mapping. An empty list requires the + user to supply injection hosts before adding a masking entry. These suggestions do not + allow network access. + """ + + @staticmethod + def from_dict(obj: Any) -> 'SandboxCredentialSuggestion': + assert isinstance(obj, dict) + name = from_str(obj.get("name")) + suggested_inject_hosts = from_list(from_str, obj.get("suggestedInjectHosts")) + return SandboxCredentialSuggestion(name, suggested_inject_hosts) + + def to_dict(self) -> dict: + result: dict = {} + result["name"] = from_str(self.name) + result["suggestedInjectHosts"] = from_list(from_str, self.suggested_inject_hosts) + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class SandboxDisableForSessionResult: @@ -12703,6 +14329,102 @@ 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 SandboxDocsURLResult: + """The documentation URL a host cites when it declines or downgrades a sandbox setting.""" + + url: str + """Documentation URL describing the sandbox and the settings that control it.""" + + @staticmethod + def from_dict(obj: Any) -> 'SandboxDocsURLResult': + assert isinstance(obj, dict) + url = from_str(obj.get("url")) + return SandboxDocsURLResult(url) + + def to_dict(self) -> dict: + result: dict = {} + result["url"] = from_str(self.url) + 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 _SandboxEffectiveForOptionalParams: + """A sandbox config and the per-feature routing flag to resolve it for.""" + + routing_flag: str + """The routing flag naming the feature being launched, such as `sandboxMcpServers` or + `sandboxLspServers`. + """ + sandbox: Any = None + """The session's resolved `sandbox` slice. Omitted when the host has none, which yields a + disabled sandbox rather than an error. + """ + + @staticmethod + def from_dict(obj: Any) -> '_SandboxEffectiveForOptionalParams': + assert isinstance(obj, dict) + routing_flag = from_str(obj.get("routingFlag")) + sandbox = obj.get("sandbox") + return _SandboxEffectiveForOptionalParams(routing_flag, sandbox) + + def to_dict(self) -> dict: + result: dict = {} + result["routingFlag"] = from_str(self.routing_flag) + if self.sandbox is not None: + result["sandbox"] = self.sandbox + 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 _SandboxEffectiveForOptionalResult: + """The sandbox a named feature actually launches under.""" + + sandbox_json: Any + """The effective sandbox config for the requested feature, or a disabled sandbox when the + feature is routed out of it. + """ + + @staticmethod + def from_dict(obj: Any) -> '_SandboxEffectiveForOptionalResult': + assert isinstance(obj, dict) + sandbox_json = obj.get("sandboxJson") + return _SandboxEffectiveForOptionalResult(sandbox_json) + + def to_dict(self) -> dict: + result: dict = {} + result["sandboxJson"] = self.sandbox_json + 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 _SandboxEnforcementCommandAllowedParams: + """One slash command, as typed, to test against the enforcement-block allowlist.""" + + args: list[str] + """The command's arguments, in order. Pass an empty array for a bare command: several + entries in the allowlist admit the bare form and refuse every argument. + """ + command_name: str + """The command name as the user typed it, with or without its leading slash.""" + + @staticmethod + def from_dict(obj: Any) -> '_SandboxEnforcementCommandAllowedParams': + assert isinstance(obj, dict) + args = from_list(from_str, obj.get("args")) + command_name = from_str(obj.get("commandName")) + return _SandboxEnforcementCommandAllowedParams(args, command_name) + + def to_dict(self) -> dict: + result: dict = {} + result["args"] = from_list(from_str, self.args) + result["commandName"] = from_str(self.command_name) + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class SandboxEnforcementStatus: @@ -12733,6 +14455,54 @@ 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. +# Internal: this type is an internal SDK API and is not part of the public surface. +@dataclass +class _SandboxPickerCandidate: + """One file-picker suggestion offered up for the sandbox filesystem check.""" + + is_directory: bool + """Whether the suggestion is a directory. A directory the policy does not itself allow may + still be needed as a waypoint to an allowed subtree. + """ + path: str + """Absolute path the suggestion would attach.""" + + @staticmethod + def from_dict(obj: Any) -> '_SandboxPickerCandidate': + assert isinstance(obj, dict) + is_directory = from_bool(obj.get("isDirectory")) + path = from_str(obj.get("path")) + return _SandboxPickerCandidate(is_directory, path) + + def to_dict(self) -> dict: + result: dict = {} + result["isDirectory"] = from_bool(self.is_directory) + result["path"] = from_str(self.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 _SandboxFilterPickerCandidatesResult: + """Per-candidate allow decisions, positionally aligned with the request.""" + + allowed: list[bool] + """One decision per candidate, in the same order. Always the same length as the request's + `candidates`. + """ + + @staticmethod + def from_dict(obj: Any) -> '_SandboxFilterPickerCandidatesResult': + assert isinstance(obj, dict) + allowed = from_list(from_bool, obj.get("allowed")) + return _SandboxFilterPickerCandidatesResult(allowed) + + def to_dict(self) -> dict: + result: dict = {} + result["allowed"] = from_list(from_bool, self.allowed) + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class SandboxGrantPathForRequestResult: @@ -12802,6 +14572,58 @@ 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. +# Internal: this type is an internal SDK API and is not part of the public surface. +@dataclass +class _SandboxPreparePickerPolicyParams: + """The working directory and sandbox settings to build a file-picker policy from.""" + + cwd: str + """The session's working directory. Git discovery and grant canonicalization are resolved + against it. + """ + sandbox: Any = None + """The session's resolved `sandbox` slice. Omitted, or disabled, yields no policy and every + candidate is kept. + """ + + @staticmethod + def from_dict(obj: Any) -> '_SandboxPreparePickerPolicyParams': + assert isinstance(obj, dict) + cwd = from_str(obj.get("cwd")) + sandbox = obj.get("sandbox") + return _SandboxPreparePickerPolicyParams(cwd, sandbox) + + def to_dict(self) -> dict: + result: dict = {} + result["cwd"] = from_str(self.cwd) + if self.sandbox is not None: + result["sandbox"] = self.sandbox + 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 _SandboxPreparePickerPolicyResult: + """The prepared file-picker policy, or none when the sandbox is disabled.""" + + sandbox_json: Any = None + """The prepared policy to pass back to `sandbox.filterPickerCandidates`. Absent when the + sandbox is disabled or absent, in which case filtering keeps every candidate. + """ + + @staticmethod + def from_dict(obj: Any) -> '_SandboxPreparePickerPolicyResult': + assert isinstance(obj, dict) + sandbox_json = obj.get("sandboxJson") + return _SandboxPreparePickerPolicyResult(sandbox_json) + + def to_dict(self) -> dict: + result: dict = {} + if self.sandbox_json is not None: + result["sandboxJson"] = self.sandbox_json + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class SandboxProxyCACreateResult: @@ -12832,6 +14654,148 @@ class SandboxProxyCAState(Enum): NOT_INSTALLED = "notInstalled" UNSUPPORTED = "unsupported" +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class SandboxProxySecretGetResult: + """The stored sandbox proxy password, if one is present.""" + + value: str | None = None + """The stored password. Absent when no entry exists in either the keychain or the on-disk + fallback store. + """ + + @staticmethod + def from_dict(obj: Any) -> 'SandboxProxySecretGetResult': + assert isinstance(obj, dict) + value = from_union([from_str, from_none], obj.get("value")) + return SandboxProxySecretGetResult(value) + + def to_dict(self) -> dict: + result: dict = {} + if self.value is not None: + result["value"] = from_union([from_str, from_none], self.value) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class SandboxProxySecretLocation: + """Where this runtime keeps the sandbox proxy password.""" + + config_dir: str + """Absolute path of the credential store holding the sandbox proxy password. The proxy + password belongs to no MCP server, so it is kept in its own directory rather than in the + MCP secret store. + """ + secret_id: str + """Identifier of the proxy password entry within that store. Derived from the configuration + directory, so two Copilot homes on one machine never share an entry. Use it to recognize + a `${secret:…}` reference in stored settings as this runtime's own proxy password. + """ + + @staticmethod + def from_dict(obj: Any) -> 'SandboxProxySecretLocation': + assert isinstance(obj, dict) + config_dir = from_str(obj.get("configDir")) + secret_id = from_str(obj.get("secretId")) + return SandboxProxySecretLocation(config_dir, secret_id) + + def to_dict(self) -> dict: + result: dict = {} + result["configDir"] = from_str(self.config_dir) + result["secretId"] = from_str(self.secret_id) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class SandboxProxySecretSaveRequest: + """The sandbox proxy password to store.""" + + value: str + """The password to store for the sandbox proxy.""" + + @staticmethod + def from_dict(obj: Any) -> 'SandboxProxySecretSaveRequest': + assert isinstance(obj, dict) + value = from_str(obj.get("value")) + return SandboxProxySecretSaveRequest(value) + + def to_dict(self) -> dict: + result: dict = {} + 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 SandboxProxySecretSaveResult: + """Where the sandbox proxy password was stored.""" + + stored_in_keychain: bool + """Whether the OS keychain accepted the value. False means it was written to the on-disk + fallback store instead, which is still readable by `sandbox.proxySecret.get`. + """ + + @staticmethod + def from_dict(obj: Any) -> 'SandboxProxySecretSaveResult': + assert isinstance(obj, dict) + stored_in_keychain = from_bool(obj.get("storedInKeychain")) + return SandboxProxySecretSaveResult(stored_in_keychain) + + def to_dict(self) -> dict: + result: dict = {} + result["storedInKeychain"] = from_bool(self.stored_in_keychain) + 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 _SandboxResolveFlooredConfigParams: + """The user's sandbox settings and the managed sandbox floor to raise them to.""" + + managed_sandbox: Any = None + """The effective managed `sandbox` slice acting as a floor. Omitted when no administrator + policy applies, in which case the user's own settings stand unchanged. + """ + user_sandbox: Any = None + """The user's own `sandbox` settings slice. Omitted when the user has configured none, which + still yields the managed floor rather than a disabled sandbox. + """ + + @staticmethod + def from_dict(obj: Any) -> '_SandboxResolveFlooredConfigParams': + assert isinstance(obj, dict) + managed_sandbox = obj.get("managedSandbox") + user_sandbox = obj.get("userSandbox") + return _SandboxResolveFlooredConfigParams(managed_sandbox, user_sandbox) + + def to_dict(self) -> dict: + result: dict = {} + if self.managed_sandbox is not None: + result["managedSandbox"] = self.managed_sandbox + if self.user_sandbox is not None: + result["userSandbox"] = self.user_sandbox + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class SandboxResolveFlooredConfigResult: + """The user's sandbox settings raised to the managed floor.""" + + sandbox_json: Any + """The floored sandbox config, normalized through the declared sandbox shape so only + declared fields in a fixed order are present. + """ + + @staticmethod + def from_dict(obj: Any) -> 'SandboxResolveFlooredConfigResult': + assert isinstance(obj, dict) + sandbox_json = obj.get("sandboxJson") + return SandboxResolveFlooredConfigResult(sandbox_json) + + def to_dict(self) -> dict: + result: dict = {} + result["sandboxJson"] = self.sandbox_json + return result + # Experimental: this type is part of an experimental API and may change or be removed. class SandboxSessionChange(Enum): """A session-scoped sandbox transition applied while handling a slash command @@ -12843,6 +14807,63 @@ class SandboxSessionChange(Enum): DISABLED = "disabled" RESTORED = "restored" +# 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 _SandboxUnsupportedStartupNoticeParams: + """The managed sandbox policy in force, which selects which remedy the notice offers.""" + + enabled_by_undetermined_policy: bool | None = None + """True when the sandbox stayed on only because the managed policy could not be determined. + Distinguishes a policy that positively enforces the sandbox from one that merely failed + to resolve, which is the difference between telling the user to contact an administrator + and telling them to wait. Defaults to false. + """ + managed_sandbox: Any = None + """The managed `sandbox` slice, when a policy supplies one. The notice treats the sandbox as + enforced only when this both enables it and sets `failIfUnavailable`, because a managed + sandbox that tolerates being unavailable still leaves the user able to turn it off. + """ + + @staticmethod + def from_dict(obj: Any) -> '_SandboxUnsupportedStartupNoticeParams': + assert isinstance(obj, dict) + enabled_by_undetermined_policy = from_union([from_bool, from_none], obj.get("enabledByUndeterminedPolicy")) + managed_sandbox = obj.get("managedSandbox") + return _SandboxUnsupportedStartupNoticeParams(enabled_by_undetermined_policy, managed_sandbox) + + def to_dict(self) -> dict: + result: dict = {} + if self.enabled_by_undetermined_policy is not None: + result["enabledByUndeterminedPolicy"] = from_union([from_bool, from_none], self.enabled_by_undetermined_policy) + if self.managed_sandbox is not None: + result["managedSandbox"] = self.managed_sandbox + 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 _SandboxUnsupportedStartupNoticeResult: + """The startup warning, absent when the machine can run a sandbox.""" + + notice: str | None = None + """The user-facing warning, carrying the platform remedy and the recovery the policy in + force actually allows. Absent when this machine supports a sandbox, so a host can call + unconditionally and print only what it gets. + """ + + @staticmethod + def from_dict(obj: Any) -> '_SandboxUnsupportedStartupNoticeResult': + assert isinstance(obj, dict) + notice = from_union([from_str, from_none], obj.get("notice")) + return _SandboxUnsupportedStartupNoticeResult(notice) + + def to_dict(self) -> dict: + result: dict = {} + if self.notice is not None: + result["notice"] = from_union([from_str, from_none], self.notice) + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class ScheduleAddAtRequest: @@ -13521,6 +15542,7 @@ class SessionCapability(Enum): CANVAS_RENDERER = "canvas-renderer" CLI_DOCUMENTATION = "cli-documentation" ELICITATION = "elicitation" + GITHUB_CACHE_STORE = "github-cache-store" INTERACTIVE_MODE = "interactive-mode" MCP_APPS = "mcp-apps" MEMORY = "memory" @@ -14664,6 +16686,26 @@ def to_dict(self) -> dict: result["reloadMcp"] = from_union([from_bool, from_none], self.reload_mcp) return result +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class SessionPluginsRetryManagedRequest: + plugins: list[str] | None = None + """"plugin@marketplace" specs to retry. Omit to retry every plugin the session's managed + settings require. + """ + + @staticmethod + def from_dict(obj: Any) -> 'SessionPluginsRetryManagedRequest': + assert isinstance(obj, dict) + plugins = from_union([lambda x: from_list(from_str, x), from_none], obj.get("plugins")) + return SessionPluginsRetryManagedRequest(plugins) + + def to_dict(self) -> dict: + result: dict = {} + if self.plugins is not None: + result["plugins"] = from_union([lambda x: from_list(from_str, x), from_none], self.plugins) + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class SessionPruneResult: @@ -16552,7 +18594,11 @@ class SkillsDiscoverRequest: """Optional list of project directory paths to scan for project-scoped skills""" skill_directories: list[str] | None = None - """Optional list of additional skill directory paths to include""" + """Explicit discovery-only skill roots, not filesystem grants. Available without ambient + discovery, subject to skill/managed disables. Relative paths resolve against the + request's effective working directory when supplied. Null and omission are equivalent; an + empty array contributes no explicit roots. + """ @staticmethod def from_dict(obj: Any) -> 'SkillsDiscoverRequest': @@ -16610,6 +18656,12 @@ class SkillsGetDiscoveryPathsRequest: """Optional list of project directory paths. When omitted or empty, only personal and custom directories are returned. """ + skill_directories: list[str] | None = field(default=None, kw_only=True) + """Explicit discovery-only skill roots to enumerate, including configured missing + directories. Relative paths resolve against the request's effective working directory + when supplied. These are not filesystem grants or preferred creation targets. Null and + omission are equivalent. + """ @staticmethod def from_dict(obj: Any) -> 'SkillsGetDiscoveryPathsRequest': @@ -16617,7 +18669,8 @@ def from_dict(obj: Any) -> 'SkillsGetDiscoveryPathsRequest': exclude_host_skills = from_union([from_bool, from_none], obj.get("excludeHostSkills")) ignored_skills_locations = from_union([lambda x: from_list(from_str, x), from_none], obj.get("ignoredSkillsLocations")) project_paths = from_union([lambda x: from_list(from_str, x), from_none], obj.get("projectPaths")) - return SkillsGetDiscoveryPathsRequest(exclude_host_skills, ignored_skills_locations, project_paths) + skill_directories = from_union([lambda x: from_list(from_str, x), from_none], obj.get("skillDirectories")) + return SkillsGetDiscoveryPathsRequest(exclude_host_skills, ignored_skills_locations, project_paths, skill_directories=skill_directories) def to_dict(self) -> dict: result: dict = {} @@ -16627,6 +18680,8 @@ def to_dict(self) -> dict: result["ignoredSkillsLocations"] = from_union([lambda x: from_list(from_str, x), from_none], self.ignored_skills_locations) if self.project_paths is not None: result["projectPaths"] = from_union([lambda x: from_list(from_str, x), from_none], self.project_paths) + if self.skill_directories is not None: + result["skillDirectories"] = from_union([lambda x: from_list(from_str, x), from_none], self.skill_directories) return result # Experimental: this type is part of an experimental API and may change or be removed. @@ -16756,6 +18811,28 @@ class SlashCommandSetPlanModelResultKind(Enum): class SlashCommandShowDialogResultKind(Enum): SHOW_DIALOG = "show-dialog" +# Experimental: this type is part of an experimental API and may change or be removed. +class StaffLogLevelMigrationStatus(Enum): + """What the one-time staff log-level migration did. + + What the migration did. Only `refreshed` can have changed the persisted settings. + """ + CURRENT = "current" + INELIGIBLE = "ineligible" + REFRESHED = "refreshed" + +# Experimental: this type is part of an experimental API and may change or be removed. +class StaffUpdateChannelMigrationStatus(Enum): + """What the one-time staff update-channel migration did. + + What the migration did. Only `migrated` means the persisted settings changed. + """ + COMPLETED = "completed" + CURRENT = "current" + EXCLUDED_EXPERIMENTAL = "excluded-experimental" + INELIGIBLE = "ineligible" + MIGRATED = "migrated" + # Experimental: this type is part of an experimental API and may change or be removed. class SubagentSettingsEntryContextTier(Enum): """Context tier override for matching subagents""" @@ -17932,6 +20009,828 @@ def to_dict(self) -> dict: result["value"] = self.value return result +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class UserSettingsApplyTopKeyRequest: + """A single top-level setting subtree to validate and write.""" + + canonical_path: list[str] + """Canonical path of the setting the caller is editing. Currently informational only.""" + + top_key: str + """Top-level setting name to write.""" + + complete_staff_update_channel_migration: bool | None = None + """Records that the user chose an update channel explicitly, so the one-time staff channel + migration stops reapplying. Honored only for the `autoUpdatesChannel` key. + """ + delete: bool | None = None + """True to remove the top-level key instead of writing it.""" + + value: Any = UNSET_SENTINEL + """Value to write for the whole top-level key. Omitted when `delete` is true. Explicit null + is preserved and validated as null. + """ + + @staticmethod + def from_dict(obj: Any) -> 'UserSettingsApplyTopKeyRequest': + assert isinstance(obj, dict) + canonical_path = from_list(from_str, obj.get("canonicalPath")) + top_key = from_str(obj.get("topKey")) + complete_staff_update_channel_migration = from_union([from_bool, from_none], obj.get("completeStaffUpdateChannelMigration")) + delete = from_union([from_bool, from_none], obj.get("delete")) + value = obj["value"] if "value" in obj else UNSET_SENTINEL + return UserSettingsApplyTopKeyRequest(canonical_path, top_key, complete_staff_update_channel_migration, delete, value) + + def to_dict(self) -> dict: + result: dict = {} + result["canonicalPath"] = from_list(from_str, self.canonical_path) + result["topKey"] = from_str(self.top_key) + if self.complete_staff_update_channel_migration is not None: + result["completeStaffUpdateChannelMigration"] = from_union([from_bool, from_none], self.complete_staff_update_channel_migration) + if self.delete is not None: + result["delete"] = from_union([from_bool, from_none], self.delete) + if self.value is not UNSET_SENTINEL: + result["value"] = self.value + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class UserSettingsValidationIssue: + """One problem found while validating a setting value.""" + + message: str + """Human-readable description of the problem.""" + + path: list[Any] + """Where in the settings tree the problem sits, as object keys and array indices. The first + element is the top-level key; a whole-value problem is reported as that single key. + """ + + @staticmethod + def from_dict(obj: Any) -> 'UserSettingsValidationIssue': + assert isinstance(obj, dict) + message = from_str(obj.get("message")) + path = from_list(lambda x: x, obj.get("path")) + return UserSettingsValidationIssue(message, path) + + def to_dict(self) -> dict: + result: dict = {} + result["message"] = from_str(self.message) + result["path"] = from_list(lambda x: x, self.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 _UserSettingsBuildRuntimeRequest: + """The overrides and host context to resolve effective runtime settings from.""" + + environment: dict[str, str] + """The environment to read settings-bearing variables from. Passed explicitly rather than + read from the server process so a host can resolve settings for an environment it is + about to create rather than the one it runs in. + """ + platform: str + """The platform to apply platform-specific defaults for, in the same spelling the host's own + runtime reports. + """ + working_directory: str + """The directory the settings are being resolved for. This selects the repository-scoped + document, so a host resolving settings for a directory other than its own process working + directory must pass that directory here. + """ + overrides: dict[str, Any] | None = None + """Settings that take precedence over every document and environment layer, such as the + values a host derived from its own command line. Keyed by setting name, with each value + carried verbatim, so a host may pass keys this runtime does not recognize. Absent when + the host contributes no overrides of its own. + """ + + @staticmethod + def from_dict(obj: Any) -> '_UserSettingsBuildRuntimeRequest': + assert isinstance(obj, dict) + environment = from_dict(from_str, obj.get("environment")) + platform = from_str(obj.get("platform")) + working_directory = from_str(obj.get("workingDirectory")) + overrides = from_union([lambda x: from_dict(lambda x: x, x), from_none], obj.get("overrides")) + return _UserSettingsBuildRuntimeRequest(environment, platform, working_directory, overrides) + + def to_dict(self) -> dict: + result: dict = {} + result["environment"] = from_dict(from_str, self.environment) + result["platform"] = from_str(self.platform) + result["workingDirectory"] = from_str(self.working_directory) + if self.overrides is not None: + result["overrides"] = from_union([lambda x: from_dict(lambda x: x, x), from_none], self.overrides) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class UserSettingsGetPathsRequest: + """Selects which settings file paths to resolve.""" + + repo_root: str | None = None + """Absolute path of a repository root. Supply it to also resolve that repository's two + settings file paths; omit it to resolve only the user path. + """ + + @staticmethod + def from_dict(obj: Any) -> 'UserSettingsGetPathsRequest': + assert isinstance(obj, dict) + repo_root = from_union([from_str, from_none], obj.get("repoRoot")) + return UserSettingsGetPathsRequest(repo_root) + + def to_dict(self) -> dict: + result: dict = {} + if self.repo_root is not None: + result["repoRoot"] = from_union([from_str, from_none], self.repo_root) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class UserSettingsGetPathsResult: + """On-disk locations of the settings files this runtime reads. A path is reported whether or + not the file exists. + """ + user_settings: str + """The user settings file: `settings.json` under the resolved Copilot home. This is the file + `user.settings.get`, `user.settings.load`, and `user.settings.set` read and write. + """ + repo_local_settings: str | None = None + """The repository's local, untracked settings file, or null when no repository root was + supplied. + """ + repo_settings: str | None = None + """The repository's checked-in settings file, or null when no repository root was supplied.""" + + @staticmethod + def from_dict(obj: Any) -> 'UserSettingsGetPathsResult': + assert isinstance(obj, dict) + user_settings = from_str(obj.get("userSettings")) + repo_local_settings = from_union([from_none, from_str], obj.get("repoLocalSettings")) + repo_settings = from_union([from_none, from_str], obj.get("repoSettings")) + return UserSettingsGetPathsResult(user_settings, repo_local_settings, repo_settings) + + def to_dict(self) -> dict: + result: dict = {} + result["userSettings"] = from_str(self.user_settings) + if self.repo_local_settings is not None: + result["repoLocalSettings"] = from_union([from_none, from_str], self.repo_local_settings) + if self.repo_settings is not None: + result["repoSettings"] = from_union([from_none, from_str], self.repo_settings) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class UserSettingsGovernanceKeysResult: + """Top-level user-setting keys grouped by who may govern them.""" + + claude_repo: list[str] + """Top-level keys a repository may override through a Claude-compatible repository config.""" + + managed: list[str] + """Top-level keys an enterprise policy may fix through managed settings.""" + + repo: list[str] + """Top-level keys a repository may override through `.github/copilot/settings.json` or + `.github/copilot/settings.local.json`. + """ + + @staticmethod + def from_dict(obj: Any) -> 'UserSettingsGovernanceKeysResult': + assert isinstance(obj, dict) + claude_repo = from_list(from_str, obj.get("claudeRepo")) + managed = from_list(from_str, obj.get("managed")) + repo = from_list(from_str, obj.get("repo")) + return UserSettingsGovernanceKeysResult(claude_repo, managed, repo) + + def to_dict(self) -> dict: + result: dict = {} + result["claudeRepo"] = from_list(from_str, self.claude_repo) + result["managed"] = from_list(from_str, self.managed) + result["repo"] = from_list(from_str, self.repo) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class UserSettingsHostContext: + """The filesystem and environment facts a user-settings file is resolved from. Passed + explicitly rather than read from the runtime's own process, so a host whose configuration + directory differs from its process environment resolves the file it means. + + The configuration directory, home directory and environment to resolve the settings and + global-state documents from. A host that scopes a session to its own Copilot home must + pass that directory here, or the migration is evaluated against the server's own home + instead. + + The configuration directory, home directory and environment to resolve the settings and + global-state documents from. A host that scopes a session to its own Copilot home must + pass that directory here, or the reset is evaluated against the server's own home + instead. + + The configuration directory, home directory and environment to resolve the settings + document from. A host that scopes a session to its own Copilot home must pass that + directory here, or the server's own home is inspected instead. + """ + environment: dict[str, Any] + """The host's environment variables. `COPILOT_HOME` is read from here, as are the variables + a settings hook may expand. + """ + home_directory: str + """The user's home directory, used when neither a configuration directory nor `COPILOT_HOME` + is set. + """ + config_dir: str | None = None + """The Copilot configuration directory, when the host set one. Takes precedence over + `COPILOT_HOME` and the home directory. + """ + + @staticmethod + def from_dict(obj: Any) -> 'UserSettingsHostContext': + assert isinstance(obj, dict) + 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 UserSettingsHostContext(environment, home_directory, config_dir) + + def to_dict(self) -> dict: + result: dict = {} + 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. +# Internal: this type is an internal SDK API and is not part of the public surface. +@dataclass +class _UserSettingsLoadAndMergeRepoRequest: + """A repository to read settings from, and the user layer they are merged over.""" + + repo_root: str + """Absolute path of the repository root whose settings to read. When this is the user's home + directory, the Claude-compatible configuration is skipped, so a home-directory session + does not inherit a personal config as if it were repository policy. + """ + user_settings: dict[str, Any] + """The user's own settings, keyed by setting name. The repository layer is merged over this; + a repository may only add to the collection settings it is allowed to influence, so a + user setting it does not name survives unchanged. + """ + + @staticmethod + def from_dict(obj: Any) -> '_UserSettingsLoadAndMergeRepoRequest': + assert isinstance(obj, dict) + repo_root = from_str(obj.get("repoRoot")) + user_settings = from_dict(lambda x: x, obj.get("userSettings")) + return _UserSettingsLoadAndMergeRepoRequest(repo_root, user_settings) + + def to_dict(self) -> dict: + result: dict = {} + result["repoRoot"] = from_str(self.repo_root) + result["userSettings"] = from_dict(lambda x: x, self.user_settings) + 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 _UserSettingsLoadAndMergeRepoResult: + """The settings a consumer should act on inside a repository, together with any diagnostic + raised while reading the repository layer. + """ + settings: dict[str, Any] + """The merged settings document, keyed by setting name. Equals the supplied user layer when + the repository declares no settings. + """ + warning: str | None = None + """Why part of the repository document was ignored, as a single human-readable string + joining every diagnostic with `; `. Null when it loaded cleanly. The settings that did + load are still merged, so a consumer should surface this and continue rather than treat + it as a failure. + """ + + @staticmethod + def from_dict(obj: Any) -> '_UserSettingsLoadAndMergeRepoResult': + assert isinstance(obj, dict) + settings = from_dict(lambda x: x, obj.get("settings")) + warning = from_union([from_none, from_str], obj.get("warning")) + return _UserSettingsLoadAndMergeRepoResult(settings, warning) + + def to_dict(self) -> dict: + result: dict = {} + result["settings"] = from_dict(lambda x: x, self.settings) + if self.warning is not None: + result["warning"] = from_union([from_none, from_str], self.warning) + 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 _UserSettingsLoadForConfigDirRequest: + """Selects the configuration directory whose user settings to read.""" + + config_dir: str | None = None + """Copilot configuration directory to read the settings document from, taking precedence + over the server's own `COPILOT_HOME` and default home. Omit it, or pass an empty string, + to read the directory the server resolved for itself. + """ + + @staticmethod + def from_dict(obj: Any) -> '_UserSettingsLoadForConfigDirRequest': + assert isinstance(obj, dict) + config_dir = from_union([from_str, from_none], obj.get("configDir")) + return _UserSettingsLoadForConfigDirRequest(config_dir) + + def to_dict(self) -> dict: + result: dict = {} + 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 UserSettingsLoadForEditResult: + """Contents of settings.json as stored, for a caller about to rewrite it.""" + + status: str + """`ok` when the file parsed, `missing` when there is no file yet, `invalid` when the file + exists but will not parse. + """ + settings: Any = None + """Parsed contents, present only when `status` is `ok`.""" + + @staticmethod + def from_dict(obj: Any) -> 'UserSettingsLoadForEditResult': + assert isinstance(obj, dict) + status = from_str(obj.get("status")) + settings = obj.get("settings") + return UserSettingsLoadForEditResult(status, settings) + + def to_dict(self) -> dict: + result: dict = {} + result["status"] = from_str(self.status) + 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 UserSettingsLoadRepoRequest: + """Selects the repository whose checked-in settings to read.""" + + repo_root: str + """Absolute path of the repository root whose settings to read. When this is the user's home + directory, the Claude-compatible configuration is skipped, so a home-directory session + does not inherit a personal config as if it were repository policy. + """ + + @staticmethod + def from_dict(obj: Any) -> 'UserSettingsLoadRepoRequest': + assert isinstance(obj, dict) + repo_root = from_str(obj.get("repoRoot")) + return UserSettingsLoadRepoRequest(repo_root) + + def to_dict(self) -> dict: + result: dict = {} + result["repoRoot"] = from_str(self.repo_root) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class UserSettingsLoadRepoResult: + """A repository's settings as stored, together with any diagnostic raised while reading them.""" + + settings: dict[str, Any] | None = None + """The repository's settings document, keyed by setting name, with each value carried + verbatim. Null when the repository declares no settings. + """ + warning: str | None = None + """Why part of the document was ignored, as a single human-readable string joining every + diagnostic with `; `. Null when the document loaded cleanly. The settings that did load + are still reported, so a consumer should surface this and continue rather than treat it + as a failure. + """ + + @staticmethod + def from_dict(obj: Any) -> 'UserSettingsLoadRepoResult': + assert isinstance(obj, dict) + settings = from_union([lambda x: from_dict(lambda x: x, x), from_none], obj.get("settings")) + warning = from_union([from_none, from_str], obj.get("warning")) + return UserSettingsLoadRepoResult(settings, warning) + + def to_dict(self) -> dict: + result: dict = {} + if self.settings is not None: + result["settings"] = from_union([lambda x: from_dict(lambda x: x, x), from_none], self.settings) + if self.warning is not None: + result["warning"] = from_union([from_none, from_str], self.warning) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class UserSettingsLoadWithWarningResult: + """The user's stored settings, together with any diagnostic raised while reading them.""" + + settings: dict[str, Any] + """The stored user settings document, keyed by setting name, with each value carried + verbatim. Empty when the file could not be read at all. + """ + warning: str | None = None + """Why part of the document was ignored, phrased for display to the user. Null when the + document loaded cleanly. The settings that did load are still reported, so a consumer may + surface this and continue rather than treat it as a failure. + """ + + @staticmethod + def from_dict(obj: Any) -> 'UserSettingsLoadWithWarningResult': + assert isinstance(obj, dict) + settings = from_dict(lambda x: x, obj.get("settings")) + warning = from_union([from_none, from_str], obj.get("warning")) + return UserSettingsLoadWithWarningResult(settings, warning) + + def to_dict(self) -> dict: + result: dict = {} + result["settings"] = from_dict(lambda x: x, self.settings) + if self.warning is not None: + result["warning"] = from_union([from_none, from_str], self.warning) + 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 _UserSettingsMergeManagedAndUserParams: + """The managed (enterprise policy) layer and the user layer to fold together. The field + names carry the layer identity: nothing about a settings object identifies which layer + produced it, so a generic base/override pair would let a caller transpose them and + silently resolve the governance keys in the user's favour. + """ + managed: Any = None + """The managed layer. Its governance keys win, and the per-entry `enabledPlugins` and + `extraKnownMarketplaces` control maps are stamped back from it after the union. Omitted + when the host is unmanaged. + """ + user: Any = None + """The user's own settings layer. Wins on every key the managed layer does not govern. + Omitted when the user has no settings yet. + """ + + @staticmethod + def from_dict(obj: Any) -> '_UserSettingsMergeManagedAndUserParams': + assert isinstance(obj, dict) + managed = obj.get("managed") + user = obj.get("user") + return _UserSettingsMergeManagedAndUserParams(managed, user) + + def to_dict(self) -> dict: + result: dict = {} + if self.managed is not None: + result["managed"] = self.managed + if self.user is not None: + result["user"] = self.user + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class UserSettingsMergeManagedAndUserResult: + """The user layer folded over the managed layer.""" + + settings_json: Any + """The merged settings. An empty object when neither layer was supplied, so an unmanaged + host with no user settings governs nothing. + """ + + @staticmethod + def from_dict(obj: Any) -> 'UserSettingsMergeManagedAndUserResult': + assert isinstance(obj, dict) + settings_json = obj.get("settingsJson") + return UserSettingsMergeManagedAndUserResult(settings_json) + + def to_dict(self) -> dict: + result: dict = {} + result["settingsJson"] = self.settings_json + 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 _UserSettingsMergeRepoValueParams: + """The user layer and the repository layer to fold together.""" + + repo_settings: Any + """The repository's checked-in settings layer.""" + + user_settings: Any + """The user's settings, already resolved against any managed layer.""" + + @staticmethod + def from_dict(obj: Any) -> '_UserSettingsMergeRepoValueParams': + assert isinstance(obj, dict) + repo_settings = obj.get("repoSettings") + user_settings = obj.get("userSettings") + return _UserSettingsMergeRepoValueParams(repo_settings, user_settings) + + def to_dict(self) -> dict: + result: dict = {} + result["repoSettings"] = self.repo_settings + result["userSettings"] = self.user_settings + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class UserSettingsMergeRepoValueResult: + """The repository layer folded into the user layer.""" + + settings_json: Any + """The merged settings, with the per-entry managed control maps re-stamped from the + runtime's ambient managed policy. + """ + + @staticmethod + def from_dict(obj: Any) -> 'UserSettingsMergeRepoValueResult': + assert isinstance(obj, dict) + settings_json = obj.get("settingsJson") + return UserSettingsMergeRepoValueResult(settings_json) + + def to_dict(self) -> dict: + result: dict = {} + result["settingsJson"] = self.settings_json + 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 _UserSettingsMigrateObsoleteOnDiskRequest: + """Selects the configuration directory whose settings document to clean.""" + + config_dir: str | None = None + """The configuration directory holding the settings document to clean. A host that scopes a + session to its own Copilot home passes that directory here; omit it to clean the document + under the home the server resolved for itself. The home directory and environment are + deliberately not caller-supplied: they decide only the settings path root and the two + hook-parsing flags, and those describe the process doing the reading rather than the + caller. + """ + + @staticmethod + def from_dict(obj: Any) -> '_UserSettingsMigrateObsoleteOnDiskRequest': + assert isinstance(obj, dict) + config_dir = from_union([from_str, from_none], obj.get("configDir")) + return _UserSettingsMigrateObsoleteOnDiskRequest(config_dir) + + def to_dict(self) -> dict: + result: dict = {} + 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. +# Internal: this type is an internal SDK API and is not part of the public surface. +class _UserSettingsMigrateObsoleteOnDiskStatus(Enum): + """What the cleanup did. Only `changed` rewrote the document. The cleaned settings are not + returned here: the document is written before this replies, so `user.settings.load` reads + it authoritatively. + + What the one-time obsolete-settings cleanup did to the document. + """ + CHANGED = "changed" + INVALID = "invalid" + MISSING = "missing" + UNCHANGED = "unchanged" + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class UserSettingsNormalizeRequest: + """Settings object whose key names should be canonicalized.""" + + settings: Any + """Free-form settings object, possibly using legacy or snake_case key names.""" + + @staticmethod + def from_dict(obj: Any) -> 'UserSettingsNormalizeRequest': + assert isinstance(obj, dict) + settings = obj.get("settings") + return UserSettingsNormalizeRequest(settings) + + def to_dict(self) -> dict: + result: dict = {} + result["settings"] = self.settings + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class UserSettingsNormalizeResult: + """Settings object with canonical key names.""" + + settings: Any + """The same settings with every legacy or snake_case key renamed to its canonical spelling.""" + + @staticmethod + def from_dict(obj: Any) -> 'UserSettingsNormalizeResult': + assert isinstance(obj, dict) + settings = obj.get("settings") + return UserSettingsNormalizeResult(settings) + + def to_dict(self) -> dict: + result: dict = {} + result["settings"] = self.settings + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class UserSettingsPathResult: + """The on-disk location of the user's settings document.""" + + path: str + """Absolute path to the settings file this runtime reads and writes. Normally + `settings.json` under the Copilot configuration directory; an extensionless legacy + `settings` file is reported instead when it exists and `settings.json` does not. + """ + + @staticmethod + def from_dict(obj: Any) -> 'UserSettingsPathResult': + assert isinstance(obj, dict) + path = from_str(obj.get("path")) + return UserSettingsPathResult(path) + + def to_dict(self) -> dict: + result: dict = {} + result["path"] = from_str(self.path) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class UserSettingsProblemLocation: + """The settings layer the problem was found in. + + The settings layer a problem was found in. + """ + file_path: str + """Absolute path of the file backing this layer.""" + + kind: str + """Which layer this is: `user`, `repo`, or `local`.""" + + label: str + """The caller-supplied display label for the layer.""" + + @staticmethod + def from_dict(obj: Any) -> 'UserSettingsProblemLocation': + assert isinstance(obj, dict) + file_path = from_str(obj.get("filePath")) + kind = from_str(obj.get("kind")) + label = from_str(obj.get("label")) + return UserSettingsProblemLocation(file_path, kind, label) + + def to_dict(self) -> dict: + result: dict = {} + result["filePath"] = from_str(self.file_path) + result["kind"] = from_str(self.kind) + result["label"] = from_str(self.label) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class UserSettingsProblemSource: + """One already-loaded settings layer to diagnose.""" + + file_path: str + """Absolute path of the file backing this layer.""" + + kind: str + """Which layer this is: `user`, `repo`, or `local`.""" + + label: str + """Caller-chosen display label for the layer, echoed back on each problem so a caller can + group by layer without re-deriving its own naming. + """ + config: dict[str, Any] | None = None + """The layer's settings exactly as loaded, unnormalized. Empty when the file is missing or + failed to parse. + """ + invalid: bool | None = None + """True when the file itself failed to parse, so its keys could not be read at all.""" + + @staticmethod + def from_dict(obj: Any) -> 'UserSettingsProblemSource': + assert isinstance(obj, dict) + file_path = from_str(obj.get("filePath")) + kind = from_str(obj.get("kind")) + label = from_str(obj.get("label")) + config = from_union([lambda x: from_dict(lambda x: x, x), from_none], obj.get("config")) + invalid = from_union([from_bool, from_none], obj.get("invalid")) + return UserSettingsProblemSource(file_path, kind, label, config, invalid) + + def to_dict(self) -> dict: + result: dict = {} + result["filePath"] = from_str(self.file_path) + result["kind"] = from_str(self.kind) + result["label"] = from_str(self.label) + if self.config is not None: + result["config"] = from_union([lambda x: from_dict(lambda x: x, x), from_none], self.config) + if self.invalid is not None: + result["invalid"] = from_union([from_bool, from_none], self.invalid) + 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 _UserSettingsResetStaffModelSelectionOnceResult: + """The outcome of the one-time staff model-selection reset.""" + + cutoff: str + """The cutoff this reset is gated on, as an ISO-8601 timestamp. A recorded timestamp at or + after it means the reset is already done. + """ + did_reset: bool + """Whether the persisted model selection was cleared. Only `true` means the settings + document changed and the host should adopt the returned settings. + """ + reset_at: str | None = None + """The timestamp the host should now record, as an ISO-8601 timestamp. Carries the caller's + own value back unchanged when nothing was reset. + """ + settings: dict[str, Any] | None = None + """The settings document as it stands after the reset, keyed by setting name. Present only + when `didReset` is true. + """ + + @staticmethod + def from_dict(obj: Any) -> '_UserSettingsResetStaffModelSelectionOnceResult': + assert isinstance(obj, dict) + cutoff = from_str(obj.get("cutoff")) + did_reset = from_bool(obj.get("didReset")) + reset_at = from_union([from_str, from_none], obj.get("resetAt")) + settings = from_union([lambda x: from_dict(lambda x: x, x), from_none], obj.get("settings")) + return _UserSettingsResetStaffModelSelectionOnceResult(cutoff, did_reset, reset_at, settings) + + def to_dict(self) -> dict: + result: dict = {} + result["cutoff"] = from_str(self.cutoff) + result["didReset"] = from_bool(self.did_reset) + if self.reset_at is not None: + result["resetAt"] = from_union([from_str, from_none], self.reset_at) + if self.settings is not None: + result["settings"] = from_union([lambda x: from_dict(lambda x: x, x), from_none], self.settings) + 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 _UserSettingsSetHookDisabledRequest: + """One hook to disable or re-enable.""" + + disabled: bool + """True adds the key to `disabledHooks`, false removes it.""" + + disable_key: str + """Durable content-hash key of the hook, as `hooks.loadResources` reports it.""" + + settings_path: str + """Path of the settings.json to rewrite.""" + + @staticmethod + def from_dict(obj: Any) -> '_UserSettingsSetHookDisabledRequest': + assert isinstance(obj, dict) + disabled = from_bool(obj.get("disabled")) + disable_key = from_str(obj.get("disableKey")) + settings_path = from_str(obj.get("settingsPath")) + return _UserSettingsSetHookDisabledRequest(disabled, disable_key, settings_path) + + def to_dict(self) -> dict: + result: dict = {} + result["disabled"] = from_bool(self.disabled) + result["disableKey"] = from_str(self.disable_key) + result["settingsPath"] = from_str(self.settings_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 _UserSettingsSetHookDisabledResult: + """Outcome of rewriting the `disabledHooks` setting.""" + + status: str + """`ok` when the setting was written, `invalid` when settings.json could not be parsed and + was left alone, `error` when the write itself failed. + """ + error_message: str | None = None + """Why the write failed. Absent when the status is `ok`.""" + + @staticmethod + def from_dict(obj: Any) -> '_UserSettingsSetHookDisabledResult': + assert isinstance(obj, dict) + status = from_str(obj.get("status")) + error_message = from_union([from_str, from_none], obj.get("errorMessage")) + return _UserSettingsSetHookDisabledResult(status, error_message) + + def to_dict(self) -> dict: + result: dict = {} + result["status"] = from_str(self.status) + if self.error_message is not None: + result["errorMessage"] = from_union([from_str, from_none], self.error_message) + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class UserSettingsSetRequest: @@ -17952,6 +20851,150 @@ def to_dict(self) -> dict: result["settings"] = self.settings 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 _UserSettingsUnknownKeysWarningResult: + """The unrecognized top-level keys found in the user settings document, already phrased for + display. + """ + warning: str | None = None + """The warning to show the user, naming the unrecognized keys and the file they were found + in. Absent when every top-level key is recognized, and also when the document is missing + or could not be read, because an unknown-key list taken from a document the runtime could + not load would name keys that may not be there. + """ + + @staticmethod + def from_dict(obj: Any) -> '_UserSettingsUnknownKeysWarningResult': + assert isinstance(obj, dict) + warning = from_union([from_str, from_none], obj.get("warning")) + return _UserSettingsUnknownKeysWarningResult(warning) + + def to_dict(self) -> dict: + result: dict = {} + 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. +# Internal: this type is an internal SDK API and is not part of the public surface. +@dataclass +class _UserSettingsUpdateRequest: + """Names the user setting to merge into, and the partial object to merge.""" + + key: str + """Top-level user-setting key to merge into, such as `extensions`.""" + + value: Any + """Value to store for `key`. When both the stored value and this value are objects, their + fields are shallow-merged and fields this object does not name are preserved. Otherwise + this value replaces the stored value. + """ + config_dir: str | None = None + """Copilot configuration directory to write the settings document in, taking precedence over + the server's own `COPILOT_HOME` and default home. Omit it, or pass an empty string, to + use the directory the server resolved for itself. + """ + sub_dir: str | None = None + """Single-component name for the settings document at `/settings/.json`; + path separators, `.` and `..` are rejected. Omit it, or pass an empty string, to use the + main settings document. + """ + + @staticmethod + def from_dict(obj: Any) -> '_UserSettingsUpdateRequest': + assert isinstance(obj, dict) + key = from_str(obj.get("key")) + value = obj.get("value") + config_dir = from_union([from_str, from_none], obj.get("configDir")) + sub_dir = from_union([from_str, from_none], obj.get("subDir")) + return _UserSettingsUpdateRequest(key, value, config_dir, sub_dir) + + def to_dict(self) -> dict: + result: dict = {} + result["key"] = from_str(self.key) + result["value"] = self.value + if self.config_dir is not None: + result["configDir"] = from_union([from_str, from_none], self.config_dir) + if self.sub_dir is not None: + result["subDir"] = from_union([from_str, from_none], self.sub_dir) + 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 _UserSettingsUpdateResult: + """The stored value of the user setting after the merge.""" + + value: Any + """The key's stored value after the write: the merged object when both the stored value and + the supplied value are objects, otherwise the supplied value. + """ + + @staticmethod + def from_dict(obj: Any) -> '_UserSettingsUpdateResult': + assert isinstance(obj, dict) + value = obj.get("value") + return _UserSettingsUpdateResult(value) + + def to_dict(self) -> dict: + result: dict = {} + result["value"] = self.value + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class UserSettingsValidateRequest: + """A single top-level setting subtree to validate.""" + + top_key: str + """Top-level setting name to validate.""" + + delete: bool | None = None + """True to validate a removal of this key. The current settings file is not read.""" + + value: Any = None + """Value to validate. Omitted when `delete` is true.""" + + @staticmethod + def from_dict(obj: Any) -> 'UserSettingsValidateRequest': + assert isinstance(obj, dict) + top_key = from_str(obj.get("topKey")) + delete = from_union([from_bool, from_none], obj.get("delete")) + value = obj.get("value") + return UserSettingsValidateRequest(top_key, delete, value) + + def to_dict(self) -> dict: + result: dict = {} + result["topKey"] = from_str(self.top_key) + if self.delete is not None: + result["delete"] = from_union([from_bool, from_none], self.delete) + if self.value is not None: + result["value"] = self.value + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class UserSettingsWriteAllRequest: + """The whole content to store in the user settings file.""" + + settings: Any + """The settings object to store. It replaces the file's entire content, so a key absent here + is removed. + """ + + @staticmethod + def from_dict(obj: Any) -> 'UserSettingsWriteAllRequest': + assert isinstance(obj, dict) + settings = obj.get("settings") + return UserSettingsWriteAllRequest(settings) + + def to_dict(self) -> dict: + result: dict = {} + result["settings"] = self.settings + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class WorkflowAbortRequest: @@ -19081,6 +22124,16 @@ def to_dict(self) -> dict: result["operation"] = from_str(self.operation) return result +# Experimental: this type is part of an experimental API and may change or be removed. +class ConnectorDiscoveryAvailability(Enum): + "Availability." + # Enabled. + ENABLED = "enabled" + # Disabled. + DISABLED = "disabled" + # Unavailable. + UNAVAILABLE = "unavailable" + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class AuthIdentityMetadata: @@ -19202,6 +22255,10 @@ class AccountStatus: selection_id: str """Opaque selection id used to switch to, or log out, this account.""" + auth_source: str | None = None + """Human-readable credential source used to authenticate this account, such as `gh` or + `GITHUB_TOKEN`. + """ derived_from: str | None = None """Opaque id of the account this one was derived from (e.g. an EMU account's base Entra identity); absent for a root account. Matches the base identity account's selectionId, @@ -19216,8 +22273,9 @@ def from_dict(obj: Any) -> 'AccountStatus': kind = AccountKind(obj.get("kind")) login = from_str(obj.get("login")) selection_id = from_str(obj.get("selectionId")) + auth_source = from_union([from_str, from_none], obj.get("authSource")) derived_from = from_union([from_str, from_none], obj.get("derivedFrom")) - return AccountStatus(active, host, kind, login, selection_id, derived_from) + return AccountStatus(active, host, kind, login, selection_id, auth_source, derived_from) def to_dict(self) -> dict: result: dict = {} @@ -19226,6 +22284,8 @@ def to_dict(self) -> dict: result["kind"] = to_enum(AccountKind, self.kind) result["login"] = from_str(self.login) result["selectionId"] = from_str(self.selection_id) + if self.auth_source is not None: + result["authSource"] = from_union([from_str, from_none], self.auth_source) if self.derived_from is not None: result["derivedFrom"] = from_union([from_str, from_none], self.derived_from) return result @@ -19246,6 +22306,10 @@ class ItemElement: active: bool | None = None """Whether this is the active account.""" + auth_source: str | None = None + """Human-readable credential source used to authenticate this account, such as `gh` or + `GITHUB_TOKEN`. + """ derived_from: str | None = None """Opaque id of the account this one was derived from (e.g. an EMU account's base Entra identity); absent for a root account. Matches the base identity account's selectionId, @@ -19271,19 +22335,22 @@ def from_dict(obj: Any) -> 'ItemElement': assert isinstance(obj, dict) kind = AccountKind(obj.get("kind")) active = from_union([from_bool, from_none], obj.get("active")) + auth_source = from_union([from_str, from_none], obj.get("authSource")) derived_from = from_union([from_str, from_none], obj.get("derivedFrom")) host = from_union([from_str, from_none], obj.get("host")) login = from_union([from_str, from_none], obj.get("login")) selection_id = from_union([from_str, from_none], obj.get("selectionId")) available = from_union([from_bool, from_none], obj.get("available")) label = from_union([from_str, from_none], obj.get("label")) - return ItemElement(kind, active, derived_from, host, login, selection_id, available, label) + return ItemElement(kind, active, auth_source, derived_from, host, login, selection_id, available, label) def to_dict(self) -> dict: result: dict = {} result["kind"] = to_enum(AccountKind, self.kind) if self.active is not None: result["active"] = from_union([from_bool, from_none], self.active) + if self.auth_source is not None: + result["authSource"] = from_union([from_str, from_none], self.auth_source) if self.derived_from is not None: result["derivedFrom"] = from_union([from_str, from_none], self.derived_from) if self.host is not None: @@ -19974,6 +23041,86 @@ def to_dict(self) -> dict: result["source"] = to_enum(CatalogTrustSource, self.source) return result +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class CatalogAgentPluginMarketplaceSource: + """A plugin marketplace named by verified catalogue install data, installed as + `pluginName@name`. + """ + kind: ClassVar[str] = "marketplace" + """Discriminator: the plugin installs from a marketplace.""" + + name: str + """Marketplace name.""" + + repository: str + """Canonical lowercase GitHub owner/repository hosting the marketplace.""" + + @staticmethod + def from_dict(obj: Any) -> 'CatalogAgentPluginMarketplaceSource': + assert isinstance(obj, dict) + name = from_str(obj.get("name")) + repository = from_str(obj.get("repository")) + return CatalogAgentPluginMarketplaceSource(name, repository) + + def to_dict(self) -> dict: + result: dict = {} + result["kind"] = self.kind + result["name"] = from_str(self.name) + result["repository"] = from_str(self.repository) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class CatalogAgentPluginMaterializationSource: + """Agent Finder's verified materialisation of the plugin. Present only when the candidate + carries a complete materialisation reference; the reference itself stays runtime-private. + """ + kind: ClassVar[str] = "materialization" + """Discriminator: the plugin installs from Agent Finder's materialisation.""" + + @staticmethod + def from_dict(obj: Any) -> 'CatalogAgentPluginMaterializationSource': + assert isinstance(obj, dict) + return CatalogAgentPluginMaterializationSource() + + 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 CatalogAgentPluginRepositorySource: + """A GitHub repository holding the plugin, installed directly as `owner/repo[:path]` at the + plan's commit, without a marketplace. + """ + kind: ClassVar[str] = "repository" + """Discriminator: the plugin installs directly from a repository.""" + + repository: str + """Canonical lowercase GitHub owner/repository holding the plugin.""" + + path: str | None = None + """Relative POSIX path of the plugin directory in the repository. Omitted when the plugin is + at the repository root. + """ + + @staticmethod + def from_dict(obj: Any) -> 'CatalogAgentPluginRepositorySource': + assert isinstance(obj, dict) + repository = from_str(obj.get("repository")) + path = from_union([from_str, from_none], obj.get("path")) + return CatalogAgentPluginRepositorySource(repository, path) + + def to_dict(self) -> dict: + result: dict = {} + result["kind"] = self.kind + result["repository"] = from_str(self.repository) + if self.path is not None: + result["path"] = from_union([from_str, from_none], self.path) + return result + @dataclass class SourceClass: """Where the card came from: exactly one of a URL or embedded data, encoded as a tagged @@ -22006,6 +25153,32 @@ def to_dict(self) -> dict: result["callbackUrl"] = from_str(self.callback_url) return result +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class ModelGetReasoningSummariesRequest: + """Host presentation state the reasoning-summaries policy depends on.""" + + persisted_show_reasoning: bool | None = None + """The host's persisted `showReasoning` user setting, omitted when unset.""" + + summaries_visible: bool | None = None + """Whether the host is currently showing reasoning summaries. Defaults to false.""" + + @staticmethod + def from_dict(obj: Any) -> 'ModelGetReasoningSummariesRequest': + assert isinstance(obj, dict) + persisted_show_reasoning = from_union([from_bool, from_none], obj.get("persistedShowReasoning")) + summaries_visible = from_union([from_bool, from_none], obj.get("summariesVisible")) + return ModelGetReasoningSummariesRequest(persisted_show_reasoning, summaries_visible) + + def to_dict(self) -> dict: + result: dict = {} + if self.persisted_show_reasoning is not None: + result["persistedShowReasoning"] = from_union([from_bool, from_none], self.persisted_show_reasoning) + if self.summaries_visible is not None: + result["summariesVisible"] = from_union([from_bool, from_none], self.summaries_visible) + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class ModelSetAllowedModelsRequest: @@ -23273,6 +26446,60 @@ 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. +# Internal: this type is an internal SDK API and is not part of the public surface. +@dataclass +class _FeatureFlagMetadata: + """The declared shape of one feature flag, independent of any caller population.""" + + availability: _FeatureFlagAvailability + """The population the flag is declared for before any environment or user-settings override + is applied. + """ + capi_sanity: bool + """Whether the flag participates in the CAPI sanity suite.""" + + name: str + """The flag's name, matching the key it is listed under.""" + + experimental_description: str | None = None + """Text shown to a user choosing whether to turn the flag on in experimental mode. Absent + when the flag is not user-facing. + """ + exp_flag: str | None = None + """The online-experiment (ExP) flag this maps to, when the flag is assigned by an experiment + rather than declared statically. + """ + exp_flag_env_var: str | None = None + """Environment variable that overrides this flag's experiment assignment locally in the + experiment service, not in `featureFlags.resolve`. Present exactly when `expFlag` is, and + derived from it, so a caller reads a local override without reproducing the naming rule. + """ + + @staticmethod + def from_dict(obj: Any) -> '_FeatureFlagMetadata': + assert isinstance(obj, dict) + availability = _FeatureFlagAvailability(obj.get("availability")) + capi_sanity = from_bool(obj.get("capiSanity")) + name = from_str(obj.get("name")) + experimental_description = from_union([from_str, from_none], obj.get("experimentalDescription")) + exp_flag = from_union([from_str, from_none], obj.get("expFlag")) + exp_flag_env_var = from_union([from_str, from_none], obj.get("expFlagEnvVar")) + return _FeatureFlagMetadata(availability, capi_sanity, name, experimental_description, exp_flag, exp_flag_env_var) + + def to_dict(self) -> dict: + result: dict = {} + result["availability"] = to_enum(_FeatureFlagAvailability, self.availability) + result["capiSanity"] = from_bool(self.capi_sanity) + result["name"] = from_str(self.name) + if self.experimental_description is not None: + result["experimentalDescription"] = from_union([from_str, from_none], self.experimental_description) + if self.exp_flag is not None: + result["expFlag"] = from_union([from_str, from_none], self.exp_flag) + if self.exp_flag_env_var is not None: + result["expFlagEnvVar"] = from_union([from_str, from_none], self.exp_flag_env_var) + 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 @@ -24908,6 +28135,91 @@ 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. +# Internal: this type is an internal SDK API and is not part of the public surface. +@dataclass +class _ManagedSettingsResolveBypassDisableTransitionResult: + """The bypass-permissions state a host should hold after the resolution, and what it must do + to get there. + """ + suppress_runtime_allow_all: bool + """Whether a bypass-permissions session already running must be turned off. True only when + this resolution newly disables bypass mode, so a host that is already compliant is not + interrupted. + """ + managed_identity: str | None = None + """The account the returned `managed` source is pinned to. Store it alongside `source`; the + next call needs it to decide whether an account switch lifts the disable. + """ + set_disabled_state: bool | None = None + """The value to store in the host's own bypass-disabled flag. Omitted means leave it exactly + as it is, which is not the same as being told to store `false`. + """ + source: _ManagedSettingsBypassDisableSource | None = None + """Which layer disables bypass mode after this resolution. Omitted when none does, which is + what a lifted disable looks like. + """ + + @staticmethod + def from_dict(obj: Any) -> '_ManagedSettingsResolveBypassDisableTransitionResult': + assert isinstance(obj, dict) + suppress_runtime_allow_all = from_bool(obj.get("suppressRuntimeAllowAll")) + managed_identity = from_union([from_str, from_none], obj.get("managedIdentity")) + set_disabled_state = from_union([from_bool, from_none], obj.get("setDisabledState")) + source = from_union([_ManagedSettingsBypassDisableSource, from_none], obj.get("source")) + return _ManagedSettingsResolveBypassDisableTransitionResult(suppress_runtime_allow_all, managed_identity, set_disabled_state, source) + + def to_dict(self) -> dict: + result: dict = {} + result["suppressRuntimeAllowAll"] = from_bool(self.suppress_runtime_allow_all) + if self.managed_identity is not None: + result["managedIdentity"] = from_union([from_str, from_none], self.managed_identity) + if self.set_disabled_state is not None: + result["setDisabledState"] = from_union([from_bool, from_none], self.set_disabled_state) + if self.source is not None: + result["source"] = from_union([lambda x: to_enum(_ManagedSettingsBypassDisableSource, 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 _ManagedSettingsResolveBypassDisableTransitionParams: + """The bypass-permissions state a host holds today, the account now signed in, and what the + managed-policy resolution said. + """ + active_identity: str + """The account signed in now. A `managed` disable pinned to a different account is lifted.""" + + policy: _ManagedSettingsBypassPolicyResolution + """What the resolution said about bypass mode. Send `unknown` rather than omitting the field + when there is no verdict. + """ + current_managed_identity: str | None = None + """The account a `managed` disable is pinned to. Omit it when the current source is not + `managed`; it is ignored for the other sources. + """ + current_source: _ManagedSettingsBypassDisableSource | None = None + """Which layer currently disables bypass mode. Omit it when nothing does.""" + + @staticmethod + def from_dict(obj: Any) -> '_ManagedSettingsResolveBypassDisableTransitionParams': + assert isinstance(obj, dict) + active_identity = from_str(obj.get("activeIdentity")) + policy = _ManagedSettingsBypassPolicyResolution(obj.get("policy")) + current_managed_identity = from_union([from_str, from_none], obj.get("currentManagedIdentity")) + current_source = from_union([_ManagedSettingsBypassDisableSource, from_none], obj.get("currentSource")) + return _ManagedSettingsResolveBypassDisableTransitionParams(active_identity, policy, current_managed_identity, current_source) + + def to_dict(self) -> dict: + result: dict = {} + result["activeIdentity"] = from_str(self.active_identity) + result["policy"] = to_enum(_ManagedSettingsBypassPolicyResolution, self.policy) + if self.current_managed_identity is not None: + result["currentManagedIdentity"] = from_union([from_str, from_none], self.current_managed_identity) + if self.current_source is not None: + result["currentSource"] = from_union([lambda x: to_enum(_ManagedSettingsBypassDisableSource, x), from_none], self.current_source) + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class ManagedSettingsComposeLayer: @@ -25009,6 +28321,500 @@ def to_dict(self) -> dict: result["model"] = from_union([lambda x: to_class(ManagedSettingMeta, x), from_none], self.model) 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 _ManagedSettingsLoadRemoteControlParams: + """Inputs describing the host and account whose managed remote-control policy should be + resolved. Mirrors `managedSettings.ingest`, and deliberately carries no retention + decision: reading this gate never becomes this runtime process's startup policy. + """ + environment: Any + """The host's environment variables, read for cache location and policy overrides.""" + + home_directory: str + """The host's home directory, used to resolve cache and managed-file locations.""" + + platform: str + """The host's platform, in Node's `process.platform` spelling, used to locate + platform-specific cache and policy paths. + """ + user_agent: str + """User agent to send with the policy fetch.""" + + auth_info: Any = None + """The authenticated identity to resolve policy for. Omitted before sign-in, in which case + no server layer is fetched. + """ + client_name: str | None = None + """Name of the embedding client, reported to the managed-settings service as `client_id` so + it can refuse to deliver a policy this client is too old to enforce. That refusal is + exactly what turns into an explicit `disabled` below, so a host that omits this field + forfeits the protection. + """ + device_response: Any = None + """A device layer supplied by the host instead of discovered from the machine. Pair it with + `hasDeviceResponse` so an explicit absence is distinguishable from an omitted override. + """ + has_device_response: bool | None = None + """Whether `deviceResponse` is an override at all. True with a null `deviceResponse` asserts + the device layer is empty; false discovers it from the machine. + """ + policy_helper_enabled: bool | None = None + """Whether a configured platform policy helper may be run to produce an enforced helper + layer. + """ + policy_helper_reason: _ManagedSettingsPolicyHelperReason | None = None + """The policy-helper invocation reason. Defaults to sessionStart.""" + + self_fetch: bool | None = None + """Whether to run ingestion. Omit or set false to return an empty result without discovering + or fetching policy. + """ + + @staticmethod + def from_dict(obj: Any) -> '_ManagedSettingsLoadRemoteControlParams': + assert isinstance(obj, dict) + environment = obj.get("environment") + home_directory = from_str(obj.get("homeDirectory")) + platform = from_str(obj.get("platform")) + user_agent = from_str(obj.get("userAgent")) + auth_info = obj.get("authInfo") + client_name = from_union([from_str, from_none], obj.get("clientName")) + device_response = obj.get("deviceResponse") + has_device_response = from_union([from_bool, from_none], obj.get("hasDeviceResponse")) + policy_helper_enabled = from_union([from_bool, from_none], obj.get("policyHelperEnabled")) + policy_helper_reason = from_union([_ManagedSettingsPolicyHelperReason, from_none], obj.get("policyHelperReason")) + self_fetch = from_union([from_bool, from_none], obj.get("selfFetch")) + return _ManagedSettingsLoadRemoteControlParams(environment, home_directory, platform, user_agent, auth_info, client_name, device_response, has_device_response, policy_helper_enabled, policy_helper_reason, self_fetch) + + def to_dict(self) -> dict: + result: dict = {} + result["environment"] = self.environment + result["homeDirectory"] = from_str(self.home_directory) + result["platform"] = from_str(self.platform) + result["userAgent"] = from_str(self.user_agent) + if self.auth_info is not None: + result["authInfo"] = self.auth_info + if self.client_name is not None: + result["clientName"] = from_union([from_str, from_none], self.client_name) + if self.device_response is not None: + result["deviceResponse"] = self.device_response + if self.has_device_response is not None: + result["hasDeviceResponse"] = from_union([from_bool, from_none], self.has_device_response) + if self.policy_helper_enabled is not None: + result["policyHelperEnabled"] = from_union([from_bool, from_none], self.policy_helper_enabled) + if self.policy_helper_reason is not None: + result["policyHelperReason"] = from_union([lambda x: to_enum(_ManagedSettingsPolicyHelperReason, x), from_none], self.policy_helper_reason) + if self.self_fetch is not None: + result["selfFetch"] = from_union([from_bool, from_none], self.self_fetch) + 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 _ManagedSettingsIngestParams: + """Inputs describing the host and account an ingestion should resolve managed policy for.""" + + environment: Any + """The host's environment variables, read for cache location and policy overrides. Supplied + by the host rather than read from the runtime process so a command that selected its own + configuration home is honored. + """ + home_directory: str + """The host's home directory, used to resolve cache and managed-file locations.""" + + platform: str + """The host's platform, in Node's `process.platform` spelling, used to locate + platform-specific cache and policy paths. + """ + user_agent: str + """User agent to send with the policy fetch.""" + + auth_info: Any = None + """The authenticated identity to resolve policy for. Omitted before sign-in, in which case + no server layer is fetched. + """ + bypass_cache_read: bool | None = None + """Whether to ignore a cached server policy when reading, forcing the resolution to come + from a live fetch. + """ + client_name: str | None = None + """Name of the embedding client, reported to the managed-settings service as `client_id` so + it can refuse to deliver a policy this client is too old to enforce. Omit it and the + runtime reports a generic identity; the runtime version sent alongside is the gate that + actually matters. + """ + device_response: Any = None + """A device layer supplied by the host instead of discovered from the machine. Pair it with + `hasDeviceResponse` so an explicit absence is distinguishable from an omitted override. + """ + has_device_response: bool | None = None + """Whether `deviceResponse` is an override at all. True with a null `deviceResponse` asserts + the device layer is empty; false discovers it from the machine. + """ + policy_helper_enabled: bool | None = None + """Whether a configured platform policy helper may be run to produce an enforced helper + layer. + """ + policy_helper_reason: _ManagedSettingsPolicyHelperReason | None = None + """The policy-helper invocation reason. Defaults to sessionStart.""" + + policy_helper_working_directory: str | None = None + """Directory the policy helper runs in when this ingestion is not owned by a registered + session. A host resolving policy before its session exists must pass this, or a + directory-sensitive helper judges the wrong repository. + """ + self_fetch: bool | None = None + """Whether to run ingestion. Omit or set false to return an empty result without discovering + or fetching policy. + """ + server_policy_cache_mode: _ManagedSettingsServerPolicyCacheMode | None = None + """How this ingestion may use the on-disk server-policy cache. Defaults to the runtime's own + policy when omitted. + """ + session_id: str | None = None + """The session whose credential store and working directory the policy helper should use. + Omit it when no session exists yet and the process-wide store applies. + """ + + @staticmethod + def from_dict(obj: Any) -> '_ManagedSettingsIngestParams': + assert isinstance(obj, dict) + environment = obj.get("environment") + home_directory = from_str(obj.get("homeDirectory")) + platform = from_str(obj.get("platform")) + user_agent = from_str(obj.get("userAgent")) + auth_info = obj.get("authInfo") + bypass_cache_read = from_union([from_bool, from_none], obj.get("bypassCacheRead")) + client_name = from_union([from_str, from_none], obj.get("clientName")) + device_response = obj.get("deviceResponse") + has_device_response = from_union([from_bool, from_none], obj.get("hasDeviceResponse")) + policy_helper_enabled = from_union([from_bool, from_none], obj.get("policyHelperEnabled")) + policy_helper_reason = from_union([_ManagedSettingsPolicyHelperReason, from_none], obj.get("policyHelperReason")) + policy_helper_working_directory = from_union([from_str, from_none], obj.get("policyHelperWorkingDirectory")) + self_fetch = from_union([from_bool, from_none], obj.get("selfFetch")) + server_policy_cache_mode = from_union([_ManagedSettingsServerPolicyCacheMode, from_none], obj.get("serverPolicyCacheMode")) + session_id = from_union([from_str, from_none], obj.get("sessionId")) + return _ManagedSettingsIngestParams(environment, home_directory, platform, user_agent, auth_info, bypass_cache_read, client_name, device_response, has_device_response, policy_helper_enabled, policy_helper_reason, policy_helper_working_directory, self_fetch, server_policy_cache_mode, session_id) + + def to_dict(self) -> dict: + result: dict = {} + result["environment"] = self.environment + result["homeDirectory"] = from_str(self.home_directory) + result["platform"] = from_str(self.platform) + result["userAgent"] = from_str(self.user_agent) + if self.auth_info is not None: + result["authInfo"] = self.auth_info + if self.bypass_cache_read is not None: + result["bypassCacheRead"] = from_union([from_bool, from_none], self.bypass_cache_read) + if self.client_name is not None: + result["clientName"] = from_union([from_str, from_none], self.client_name) + if self.device_response is not None: + result["deviceResponse"] = self.device_response + if self.has_device_response is not None: + result["hasDeviceResponse"] = from_union([from_bool, from_none], self.has_device_response) + if self.policy_helper_enabled is not None: + result["policyHelperEnabled"] = from_union([from_bool, from_none], self.policy_helper_enabled) + if self.policy_helper_reason is not None: + result["policyHelperReason"] = from_union([lambda x: to_enum(_ManagedSettingsPolicyHelperReason, x), from_none], self.policy_helper_reason) + if self.policy_helper_working_directory is not None: + result["policyHelperWorkingDirectory"] = from_union([from_str, from_none], self.policy_helper_working_directory) + if self.self_fetch is not None: + result["selfFetch"] = from_union([from_bool, from_none], self.self_fetch) + if self.server_policy_cache_mode is not None: + result["serverPolicyCacheMode"] = from_union([lambda x: to_enum(_ManagedSettingsServerPolicyCacheMode, x), from_none], self.server_policy_cache_mode) + 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. +# Internal: this type is an internal SDK API and is not part of the public surface. +@dataclass +class _ManagedSettingsResolveStartupPolicyParams: + """Inputs describing the host and account a startup policy resolution should run for. + Mirrors `managedSettings.ingest`, and adds the decision of whether the resolved policy is + retained for this runtime process. + """ + environment: Any + """The host's environment variables, read for cache location, policy overrides, and the + variables that would make a membership snapshot forgeable. + """ + home_directory: str + """The host's home directory, used to resolve cache and managed-file locations.""" + + platform: str + """The host's platform, in Node's `process.platform` spelling, used to locate + platform-specific cache and policy paths. + """ + retain_and_apply: bool + """Whether to retain the resolved policy as this runtime process's startup policy, so later + work in the process enforces it. Set false to resolve without side effects, which is what + a protocol host wants before session creation: its sessions apply the complete policy + through the normal lifecycle, and retaining here would let a readiness credential affect + unrelated sessions. The returned verdict is identical either way — this decides only + whether the process keeps it. + """ + user_agent: str + """User agent to send with the policy fetch.""" + + auth_info: Any = None + """The authenticated identity to resolve policy for. Omitted before sign-in, in which case + no server layer is fetched and the sign-in pin is evaluated against no credential. + """ + bypass_cache_read: bool | None = None + """Whether to ignore a cached server policy when reading, forcing the resolution to come + from a live fetch. + """ + client_name: str | None = None + """Name of the embedding client, reported to the managed-settings service as `client_id` so + it can refuse to deliver a policy this client is too old to enforce. + """ + device_response: Any = None + """A device layer supplied by the host instead of discovered from the machine. Pair it with + `hasDeviceResponse` so an explicit absence is distinguishable from an omitted override. + """ + has_device_response: bool | None = None + """Whether `deviceResponse` is an override at all. True with a null `deviceResponse` asserts + the device layer is empty; false discovers it from the machine. + """ + policy_helper_enabled: bool | None = None + """Whether a configured platform policy helper may be run to produce an enforced helper + layer. + """ + policy_helper_reason: _ManagedSettingsPolicyHelperReason | None = None + """The policy-helper invocation reason. Defaults to sessionStart.""" + + policy_helper_working_directory: str | None = None + """Directory the policy helper runs in when this resolution is not owned by a registered + session. A host resolving policy before its session exists must pass this, or a + directory-sensitive helper judges the wrong repository. + """ + self_fetch: bool | None = None + """Whether to run ingestion. Omit or set false to return an empty result without discovering + or fetching policy. + """ + server_policy_cache_mode: _ManagedSettingsServerPolicyCacheMode | None = None + """How this resolution may use the on-disk server-policy cache. Defaults to the runtime's + own policy when omitted. Rejected when combined with `bypassCacheRead`, because the two + express the same decision and accepting both would leave the caller unsure which one the + runtime honored. + """ + session_id: str | None = None + """The session whose credential store, working directory and client kind the resolution + should use. Omit it when no session exists yet and the process-wide store applies. A + resolution whose session names a credential other than `authInfo` is returned but not + retained, so a stale sign-in cannot apply policy for an account that has already been + switched away from. + """ + + @staticmethod + def from_dict(obj: Any) -> '_ManagedSettingsResolveStartupPolicyParams': + assert isinstance(obj, dict) + environment = obj.get("environment") + home_directory = from_str(obj.get("homeDirectory")) + platform = from_str(obj.get("platform")) + retain_and_apply = from_bool(obj.get("retainAndApply")) + user_agent = from_str(obj.get("userAgent")) + auth_info = obj.get("authInfo") + bypass_cache_read = from_union([from_bool, from_none], obj.get("bypassCacheRead")) + client_name = from_union([from_str, from_none], obj.get("clientName")) + device_response = obj.get("deviceResponse") + has_device_response = from_union([from_bool, from_none], obj.get("hasDeviceResponse")) + policy_helper_enabled = from_union([from_bool, from_none], obj.get("policyHelperEnabled")) + policy_helper_reason = from_union([_ManagedSettingsPolicyHelperReason, from_none], obj.get("policyHelperReason")) + policy_helper_working_directory = from_union([from_str, from_none], obj.get("policyHelperWorkingDirectory")) + self_fetch = from_union([from_bool, from_none], obj.get("selfFetch")) + server_policy_cache_mode = from_union([_ManagedSettingsServerPolicyCacheMode, from_none], obj.get("serverPolicyCacheMode")) + session_id = from_union([from_str, from_none], obj.get("sessionId")) + return _ManagedSettingsResolveStartupPolicyParams(environment, home_directory, platform, retain_and_apply, user_agent, auth_info, bypass_cache_read, client_name, device_response, has_device_response, policy_helper_enabled, policy_helper_reason, policy_helper_working_directory, self_fetch, server_policy_cache_mode, session_id) + + def to_dict(self) -> dict: + result: dict = {} + result["environment"] = self.environment + result["homeDirectory"] = from_str(self.home_directory) + result["platform"] = from_str(self.platform) + result["retainAndApply"] = from_bool(self.retain_and_apply) + result["userAgent"] = from_str(self.user_agent) + if self.auth_info is not None: + result["authInfo"] = self.auth_info + if self.bypass_cache_read is not None: + result["bypassCacheRead"] = from_union([from_bool, from_none], self.bypass_cache_read) + if self.client_name is not None: + result["clientName"] = from_union([from_str, from_none], self.client_name) + if self.device_response is not None: + result["deviceResponse"] = self.device_response + if self.has_device_response is not None: + result["hasDeviceResponse"] = from_union([from_bool, from_none], self.has_device_response) + if self.policy_helper_enabled is not None: + result["policyHelperEnabled"] = from_union([from_bool, from_none], self.policy_helper_enabled) + if self.policy_helper_reason is not None: + result["policyHelperReason"] = from_union([lambda x: to_enum(_ManagedSettingsPolicyHelperReason, x), from_none], self.policy_helper_reason) + if self.policy_helper_working_directory is not None: + result["policyHelperWorkingDirectory"] = from_union([from_str, from_none], self.policy_helper_working_directory) + if self.self_fetch is not None: + result["selfFetch"] = from_union([from_bool, from_none], self.self_fetch) + if self.server_policy_cache_mode is not None: + result["serverPolicyCacheMode"] = from_union([lambda x: to_enum(_ManagedSettingsServerPolicyCacheMode, x), from_none], self.server_policy_cache_mode) + 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. +# Internal: this type is an internal SDK API and is not part of the public surface. +@dataclass +class _ManagedSettingsLogMessage: + """One diagnostic emitted while ingesting managed settings. Returned rather than logged by + the runtime so the host decides where ingestion diagnostics go, which matters because an + ingestion runs before a session exists to carry a log sink. + """ + level: _ManagedSettingsLogLevel + """Severity of this diagnostic.""" + + message: str + """Human-readable diagnostic text.""" + + @staticmethod + def from_dict(obj: Any) -> '_ManagedSettingsLogMessage': + assert isinstance(obj, dict) + level = _ManagedSettingsLogLevel(obj.get("level")) + message = from_str(obj.get("message")) + return _ManagedSettingsLogMessage(level, message) + + def to_dict(self) -> dict: + result: dict = {} + result["level"] = to_enum(_ManagedSettingsLogLevel, self.level) + result["message"] = from_str(self.message) + 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 _ManagedSettingsResolveStartupPolicyResult: + """The startup policy a resolution composed, the per-layer outcome flags a host needs to + decide how much to trust it, and the enterprise sign-in verdict for the credential it ran + against. The flags are reported rather than folded into the settings because a host must + distinguish an unmanaged device from a managed device whose policy could not be + confirmed, and apply its fail-closed gates in the second case. + """ + device_load_failed: bool + """The device layer could not be read, so it is unconfirmed rather than known-absent.""" + + force_login_orgs: _ForceLoginOrgsDecision + """The enterprise sign-in verdict for the credential this resolution ran against, evaluated + over the effective policy so a server-delivered pin is honored alongside a device one. + Computed on every resolution, including one that was not retained, so a host that + declines to apply policy still cannot proceed past a pin. + """ + managed_policy_present: bool + """A managed policy is known to apply to this host, from a discovered source or a resolved + document. + """ + permission_policy_undetermined: bool + """The permissions layer specifically could not be confirmed, so permission gates apply + their fail-closed posture. + """ + policy_helper_load_failed: bool + """The policy helper was configured but could not be run, or its output could not be read.""" + + policy_undetermined: bool + """Some layer of the policy could not be confirmed, so its absence must not be read as + permission. Folds in a failed server fetch as well as an unreadable device layer. + """ + sandbox_enabled_by_undetermined_policy: bool + """The sandbox was enabled because policy could not be confirmed rather than because an + administrator asked for it. Lets a host explain a sandbox the user did not configure. + """ + server_fetch_failed: bool + """The server fetch failed and no cached entry stood in for it, so the server layer is + unconfirmed and fail-closed gates apply. + """ + server_resolution: _ManagedSettingsServerPolicyResolution + """How the current runtime resolved the server layer.""" + + telemetry: Any + """Telemetry describing which layers answered and how, for the host to report alongside its + own startup events. + """ + warnings: list[str] + """Warnings raised while resolving, in the order they occurred.""" + + client_update_requested: str | None = None + """Advisory minimum client version from a successful server response.""" + + client_update_required: str | None = None + """The server refused to deliver a policy because this client is too old to enforce it, + carrying the user-facing sentence describing the refusal. No retry will help; the remedy + is a client update. + """ + device_response: Any = None + """The device layer discovered from the machine, when one exists.""" + + device_sandbox_floor: Any = None + """The sandbox floor contributed by the device layer.""" + + policy_helper_response: Any = None + """The layer produced by the platform policy helper, when one ran.""" + + sandbox_floor: Any = None + """The effective sandbox floor composed across every layer, which a host must apply rather + than its own default. + """ + server_response: Any = None + """The account's server-delivered policy, when one was resolved.""" + + @staticmethod + def from_dict(obj: Any) -> '_ManagedSettingsResolveStartupPolicyResult': + assert isinstance(obj, dict) + device_load_failed = from_bool(obj.get("deviceLoadFailed")) + force_login_orgs = _ForceLoginOrgsDecision.from_dict(obj.get("forceLoginOrgs")) + managed_policy_present = from_bool(obj.get("managedPolicyPresent")) + permission_policy_undetermined = from_bool(obj.get("permissionPolicyUndetermined")) + policy_helper_load_failed = from_bool(obj.get("policyHelperLoadFailed")) + policy_undetermined = from_bool(obj.get("policyUndetermined")) + sandbox_enabled_by_undetermined_policy = from_bool(obj.get("sandboxEnabledByUndeterminedPolicy")) + server_fetch_failed = from_bool(obj.get("serverFetchFailed")) + server_resolution = _ManagedSettingsServerPolicyResolution(obj.get("serverResolution")) + telemetry = obj.get("telemetry") + warnings = from_list(from_str, obj.get("warnings")) + client_update_requested = from_union([from_str, from_none], obj.get("clientUpdateRequested")) + client_update_required = from_union([from_str, from_none], obj.get("clientUpdateRequired")) + device_response = obj.get("deviceResponse") + device_sandbox_floor = obj.get("deviceSandboxFloor") + policy_helper_response = obj.get("policyHelperResponse") + sandbox_floor = obj.get("sandboxFloor") + server_response = obj.get("serverResponse") + return _ManagedSettingsResolveStartupPolicyResult(device_load_failed, force_login_orgs, managed_policy_present, permission_policy_undetermined, policy_helper_load_failed, policy_undetermined, sandbox_enabled_by_undetermined_policy, server_fetch_failed, server_resolution, telemetry, warnings, client_update_requested, client_update_required, device_response, device_sandbox_floor, policy_helper_response, sandbox_floor, server_response) + + def to_dict(self) -> dict: + result: dict = {} + result["deviceLoadFailed"] = from_bool(self.device_load_failed) + result["forceLoginOrgs"] = to_class(_ForceLoginOrgsDecision, self.force_login_orgs) + result["managedPolicyPresent"] = from_bool(self.managed_policy_present) + result["permissionPolicyUndetermined"] = from_bool(self.permission_policy_undetermined) + result["policyHelperLoadFailed"] = from_bool(self.policy_helper_load_failed) + result["policyUndetermined"] = from_bool(self.policy_undetermined) + result["sandboxEnabledByUndeterminedPolicy"] = from_bool(self.sandbox_enabled_by_undetermined_policy) + result["serverFetchFailed"] = from_bool(self.server_fetch_failed) + result["serverResolution"] = to_enum(_ManagedSettingsServerPolicyResolution, self.server_resolution) + result["telemetry"] = self.telemetry + result["warnings"] = from_list(from_str, self.warnings) + if self.client_update_requested is not None: + result["clientUpdateRequested"] = from_union([from_str, from_none], self.client_update_requested) + if self.client_update_required is not None: + result["clientUpdateRequired"] = from_union([from_str, from_none], self.client_update_required) + if self.device_response is not None: + result["deviceResponse"] = self.device_response + if self.device_sandbox_floor is not None: + result["deviceSandboxFloor"] = self.device_sandbox_floor + if self.policy_helper_response is not None: + result["policyHelperResponse"] = self.policy_helper_response + if self.sandbox_floor is not None: + result["sandboxFloor"] = self.sandbox_floor + if self.server_response is not None: + result["serverResponse"] = self.server_response + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class MarketplaceListResult: @@ -25957,6 +29763,45 @@ def to_dict(self) -> dict: result["mode"] = to_enum(MCPSetEnvValueModeDetails, self.mode) 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 _MCPShouldExcludeGitHubToolsRequest: + """The session tool filters that decide whether the session still has a shell to run `gh` + with. + """ + available_tools: list[str] | None = None + """The session's tool allowlist, when it set one. Omitted means the session constrains + nothing this way. + """ + excluded_tools: list[str] | None = None + """The session's tool denylist, when it set one. Omitted means the session constrains + nothing this way. + """ + tool_filter_precedence: OptionsUpdateToolFilterPrecedence | None = None + """How the allowlist and denylist combine when both are set. Omitted means the default every + session gets, so a caller that never chose a precedence is answered as its sessions + behave. + """ + + @staticmethod + def from_dict(obj: Any) -> '_MCPShouldExcludeGitHubToolsRequest': + assert isinstance(obj, dict) + available_tools = from_union([lambda x: from_list(from_str, x), from_none], obj.get("availableTools")) + excluded_tools = from_union([lambda x: from_list(from_str, x), from_none], obj.get("excludedTools")) + tool_filter_precedence = from_union([from_none, OptionsUpdateToolFilterPrecedence], obj.get("toolFilterPrecedence")) + return _MCPShouldExcludeGitHubToolsRequest(available_tools, excluded_tools, tool_filter_precedence) + + def to_dict(self) -> dict: + result: dict = {} + if self.available_tools is not None: + result["availableTools"] = from_union([lambda x: from_list(from_str, x), from_none], self.available_tools) + if self.excluded_tools is not None: + result["excludedTools"] = from_union([lambda x: from_list(from_str, x), from_none], self.excluded_tools) + if self.tool_filter_precedence is not None: + result["toolFilterPrecedence"] = from_union([from_none, lambda x: to_enum(OptionsUpdateToolFilterPrecedence, x)], self.tool_filter_precedence) + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class ConnectorDiscoveryCatalogEntry: @@ -27931,6 +31776,31 @@ class CatalogAgentPluginCompatibilityTag(Enum): CANVAS_ONLY = "canvas-only" GITHUB_COPILOT = "github-copilot" +# Experimental: this type is part of an experimental API and may change or be removed. +class CatalogAgentPluginComponent(Enum): + """A kind of component an Agent Plugin contributes""" + + AGENT = "agent" + CANVAS = "canvas" + COMMAND = "command" + EXTENSION = "extension" + HOOK = "hook" + LSP_SERVER = "lsp-server" + MCP_SERVER = "mcp-server" + SKILL = "skill" + +# Experimental: this type is part of an experimental API and may change or be removed. +class CatalogAgentPluginInstallability(Enum): + """Whether this plugin can be installed in the selected session. Present only when the + caller requires the agent-plugin-confirmed-installation capability. + + Typed installability state for an Agent Plugin candidate + """ + FEATURE_DISABLED = "feature-disabled" + INSTALLABLE = "installable" + INSTALL_DATA_UNAVAILABLE = "install-data-unavailable" + NOT_INSTALLABLE_KIND = "not-installable-kind" + # Experimental: this type is part of an experimental API and may change or be removed. class CatalogAgentPluginCandidateKind(Enum): """Discriminator for an Agent Plugin candidate""" @@ -27939,12 +31809,39 @@ class CatalogAgentPluginCandidateKind(Enum): # Experimental: this type is part of an experimental API and may change or be removed. class CatalogAgentPluginMediaType(Enum): - """Canonical Agent Plugin media type. + """Media type naming the plugin's format. - Canonical Agent Plugin media type + Media type naming the plugin format of an Agent Plugin candidate. Values other than + `application/vnd.github.copilot-plugin` appear only when the caller requires the + agent-plugin-formats capability. """ + APPLICATION_AGENT_PLUGIN_JSON = "application/agent-plugin+json" + APPLICATION_VND_ANTHROPIC_CLAUDE_PLUGIN_JSON = "application/vnd.anthropic.claude-plugin+json" APPLICATION_VND_GITHUB_COPILOT_PLUGIN = "application/vnd.github.copilot-plugin" +class CatalogInstallability(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 + + Whether this AI skill candidate can be planned for verified installation in the selected + session. + + Typed installability state for an AI skill candidate + + Whether this plugin can be installed in the selected session. Present only when the + caller requires the agent-plugin-confirmed-installation capability. + + Typed installability state for an Agent Plugin candidate + """ + FEATURE_DISABLED = "feature-disabled" + INSTALLABLE = "installable" + INSTALL_DATA_UNAVAILABLE = "install-data-unavailable" + MATERIALISATION_UNAVAILABLE = "materialisation-unavailable" + NOT_INSTALLABLE_KIND = "not-installable-kind" + NOT_INSTALLABLE_POLICY = "not-installable-policy" + POLICY_FORBIDS = "policy-forbids" + # Experimental: this type is part of an experimental API and may change or be removed. class CatalogCandidateKind(Enum): """Discriminator for an MCP server candidate @@ -27973,17 +31870,21 @@ class CatalogMediaType(Enum): Canonical AI skill media type - Canonical Agent Plugin media type. + Media type naming the plugin's format. - Canonical Agent Plugin media type + Media type naming the plugin format of an Agent Plugin candidate. Values other than + `application/vnd.github.copilot-plugin` appear only when the caller requires the + agent-plugin-formats capability. Media type the card was interpreted as, when it declared one this runtime recognises. Media type a catalog card is interpreted as """ + APPLICATION_AGENT_PLUGIN_JSON = "application/agent-plugin+json" APPLICATION_AI_SKILL = "application/ai-skill" APPLICATION_MCP_SERVER_CARD_JSON = "application/mcp-server-card+json" APPLICATION_MCP_SERVER_JSON = "application/mcp-server+json" + APPLICATION_VND_ANTHROPIC_CLAUDE_PLUGIN_JSON = "application/vnd.anthropic.claude-plugin+json" APPLICATION_VND_GITHUB_COPILOT_PLUGIN = "application/vnd.github.copilot-plugin" # Experimental: this type is part of an experimental API and may change or be removed. @@ -28105,6 +32006,36 @@ def to_dict(self) -> dict: 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 ManagedPluginRetryEntry: + """Retry outcome for one plugin required by managed settings.""" + + spec: str + """Plugin spec in "plugin@marketplace" form.""" + + status: ManagedPluginRetryStatus + """What the retry did for this plugin.""" + + error: str | None = None + """Why the plugin was not prepared. Present when `status` is `failed` or `deferred`.""" + + @staticmethod + def from_dict(obj: Any) -> 'ManagedPluginRetryEntry': + assert isinstance(obj, dict) + spec = from_str(obj.get("spec")) + status = ManagedPluginRetryStatus(obj.get("status")) + error = from_union([from_str, from_none], obj.get("error")) + return ManagedPluginRetryEntry(spec, status, error) + + def to_dict(self) -> dict: + result: dict = {} + result["spec"] = from_str(self.spec) + result["status"] = to_enum(ManagedPluginRetryStatus, self.status) + 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 MarketplacePluginInfo: @@ -29752,6 +33683,91 @@ def to_dict(self) -> dict: result["mode"] = from_union([lambda x: to_enum(RemoteSessionMode, x), from_none], self.mode) return result +# Experimental: this type is part of an experimental API and may change or be removed. +# Internal: this type is an internal SDK API and is not part of the public surface. +@dataclass +class _UserSettingsEditRequest: + """One atomic edit of a canonical setting in settings.json.""" + + canonical_path: list[str] + """Nonempty canonical path segments of the setting to edit, starting with its top-level key.""" + + operation: _SettingsEditOperation + """Set a scalar, add or remove a string list item, or remove the setting. The handler + rejects a missing or incompatible value. + """ + config_dir: str | None = None + """Configuration directory to edit. Omitted or empty resolves the server's own home, as in + `user.settings.loadForConfigDir`. + """ + value: Any = UNSET_SENTINEL + """Required boolean, string or number for set, required string for add-item and remove-item, + and forbidden for remove. The runtime rejects all other JSON values. + """ + + @staticmethod + def from_dict(obj: Any) -> '_UserSettingsEditRequest': + assert isinstance(obj, dict) + canonical_path = from_list(from_str, obj.get("canonicalPath")) + operation = _SettingsEditOperation(obj.get("operation")) + config_dir = from_union([from_str, from_none], obj.get("configDir")) + value = obj["value"] if "value" in obj else UNSET_SENTINEL + return _UserSettingsEditRequest(canonical_path, operation, config_dir, value) + + def to_dict(self) -> dict: + result: dict = {} + result["canonicalPath"] = from_list(from_str, self.canonical_path) + result["operation"] = to_enum(_SettingsEditOperation, self.operation) + if self.config_dir is not None: + result["configDir"] = from_union([from_str, from_none], self.config_dir) + if self.value is not UNSET_SENTINEL: + result["value"] = self.value + 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 _RepoSettingsEditRequest: + """One atomic edit of an allowed top-level repository setting.""" + + key: str + """One canonical top-level repository setting that the allowlist permits.""" + + operation: _SettingsEditOperation + """Set a scalar, add or remove a string list item, or remove the setting. The handler + rejects a missing or incompatible value. + """ + repo_root: str + """Absolute path of the repository root.""" + + scope: _RepoSettingsEditScope + """Shared or local repository settings file.""" + + value: Any = None + """Required boolean, string or number for set, required string for add-item and remove-item, + and forbidden for remove. The runtime rejects all other JSON values. + """ + + @staticmethod + def from_dict(obj: Any) -> '_RepoSettingsEditRequest': + assert isinstance(obj, dict) + key = from_str(obj.get("key")) + operation = _SettingsEditOperation(obj.get("operation")) + repo_root = from_str(obj.get("repoRoot")) + scope = _RepoSettingsEditScope(obj.get("scope")) + value = obj.get("value") + return _RepoSettingsEditRequest(key, operation, repo_root, scope, value) + + def to_dict(self) -> dict: + result: dict = {} + result["key"] = from_str(self.key) + result["operation"] = to_enum(_SettingsEditOperation, self.operation) + result["repoRoot"] = from_str(self.repo_root) + result["scope"] = to_enum(_RepoSettingsEditScope, self.scope) + if self.value is not None: + result["value"] = self.value + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class ResponseFormat: @@ -29894,6 +33910,59 @@ def to_dict(self) -> dict: result["proxy"] = from_union([lambda x: to_class(SandboxConfigUserPolicyNetworkProxy, x), from_none], self.proxy) return result +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class SandboxCredentialSuggestionsResult: + """Possible unconfigured secrets in the sandbox shell environment, sorted by variable name, + with no secret values. + """ + suggestions: list[SandboxCredentialSuggestion] + """Candidates for user review. Empty when sandboxing is disabled or no unconfigured + candidates are present. + """ + + @staticmethod + def from_dict(obj: Any) -> 'SandboxCredentialSuggestionsResult': + assert isinstance(obj, dict) + suggestions = from_list(SandboxCredentialSuggestion.from_dict, obj.get("suggestions")) + return SandboxCredentialSuggestionsResult(suggestions) + + def to_dict(self) -> dict: + result: dict = {} + result["suggestions"] = from_list(lambda x: to_class(SandboxCredentialSuggestion, x), self.suggestions) + 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 _SandboxFilterPickerCandidatesParams: + """A batch of file-picker candidates to check against a prepared policy.""" + + candidates: list[_SandboxPickerCandidate] + """The candidates to check, in the order the answers are returned.""" + + cwd: str + """The session's working directory, matching the one the policy was prepared from.""" + + sandbox: Any = None + """The policy returned by `sandbox.preparePickerPolicy`. Omitted keeps every candidate.""" + + @staticmethod + def from_dict(obj: Any) -> '_SandboxFilterPickerCandidatesParams': + assert isinstance(obj, dict) + candidates = from_list(_SandboxPickerCandidate.from_dict, obj.get("candidates")) + cwd = from_str(obj.get("cwd")) + sandbox = obj.get("sandbox") + return _SandboxFilterPickerCandidatesParams(candidates, cwd, sandbox) + + def to_dict(self) -> dict: + result: dict = {} + result["candidates"] = from_list(lambda x: to_class(_SandboxPickerCandidate, x), self.candidates) + result["cwd"] = from_str(self.cwd) + if self.sandbox is not None: + result["sandbox"] = self.sandbox + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class SandboxHostSupport: @@ -31118,6 +35187,9 @@ class AgentInfo: disable_model_invocation: bool | None = None """Whether model-driven invocation is disabled for this agent.""" + hooks: dict[str, list[Any]] | None = None + """Declarative hook actions keyed by event name, executed only while this agent is active.""" + mcp_servers: dict[str, Any] | None = None """MCP server configurations attached to this agent, keyed by server name. Server config shape mirrors the MCP `mcpServers` schema. @@ -31167,6 +35239,7 @@ def from_dict(obj: Any) -> 'AgentInfo': id = from_str(obj.get("id")) name = from_str(obj.get("name")) disable_model_invocation = from_union([from_bool, from_none], obj.get("disableModelInvocation")) + hooks = from_union([lambda x: from_dict(lambda x: from_list(lambda x: x, x), x), from_none], obj.get("hooks")) 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")) @@ -31178,7 +35251,7 @@ def from_dict(obj: Any) -> 'AgentInfo': 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, disable_model_invocation, mcp_servers, model, model_policy, models, path, prompt, reasoning_effort, skills, source, tools, user_invocable) + return AgentInfo(description, display_name, id, name, disable_model_invocation, hooks, mcp_servers, model, model_policy, models, path, prompt, reasoning_effort, skills, source, tools, user_invocable) def to_dict(self) -> dict: result: dict = {} @@ -31188,6 +35261,8 @@ def to_dict(self) -> dict: result["name"] = from_str(self.name) if self.disable_model_invocation is not None: result["disableModelInvocation"] = from_union([from_bool, from_none], self.disable_model_invocation) + if self.hooks is not None: + result["hooks"] = from_union([lambda x: from_dict(lambda x: from_list(lambda x: x, x), x), from_none], self.hooks) if self.mcp_servers is not None: result["mcpServers"] = from_union([lambda x: from_dict(lambda x: x, x), from_none], self.mcp_servers) if self.model is not None: @@ -31233,6 +35308,9 @@ class CatalogClientContract: Required authenticated bound catalogue and Skill installation capabilities. Required authenticated bound Skill installation capability. + + Required authenticated bound catalogue, skill-confirmed-installation and + skill-reference-installation capabilities. """ protocol_version: int """SDK protocol version the caller was generated against. A caller below the runtime's @@ -31259,6 +35337,53 @@ def to_dict(self) -> dict: result["requiredCapabilities"] = from_list(from_str, self.required_capabilities) return result +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class CatalogMaterializationReference: + """An Agent Finder materialisation reference pinning one exact resource revision. + + It carries no origin or URL: the runtime always builds the descriptor URL on its fixed + Agent + Finder authority. It carries no display data either; every reviewed field comes from the + verified descriptor and Skill files. + + Materialisation reference to verify and plan. + """ + bundle_digest: str + """Expected `sha256:` digest of the descriptor's bundle manifest.""" + + descriptor_digest: str + """Expected `sha256:` digest of the canonical descriptor.""" + + resource_id: str + """Agent Finder resource identifier, a positive decimal integer.""" + + revision_id: str + """Agent Finder materialisation revision identifier, a positive decimal integer. This is not + a Git commit. + """ + schema_version: str + """Materialisation descriptor schema version, currently `1.0.0`.""" + + @staticmethod + def from_dict(obj: Any) -> 'CatalogMaterializationReference': + assert isinstance(obj, dict) + bundle_digest = from_str(obj.get("bundleDigest")) + descriptor_digest = from_str(obj.get("descriptorDigest")) + resource_id = from_str(obj.get("resourceId")) + revision_id = from_str(obj.get("revisionId")) + schema_version = from_str(obj.get("schemaVersion")) + return CatalogMaterializationReference(bundle_digest, descriptor_digest, resource_id, revision_id, schema_version) + + def to_dict(self) -> dict: + result: dict = {} + result["bundleDigest"] = from_str(self.bundle_digest) + result["descriptorDigest"] = from_str(self.descriptor_digest) + result["resourceId"] = from_str(self.resource_id) + result["revisionId"] = from_str(self.revision_id) + result["schemaVersion"] = from_str(self.schema_version) + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class SkillInstallationFileReview: @@ -31421,12 +35546,16 @@ class SkillInstallationFailureReason(Enum): CONFIRMATION_INVALID = "confirmation-invalid" CONFIRMATION_UNAVAILABLE = "confirmation-unavailable" DESCRIPTOR_INVALID = "descriptor-invalid" + DESCRIPTOR_NOT_FOUND = "descriptor-not-found" DESCRIPTOR_UNAVAILABLE = "descriptor-unavailable" + DESCRIPTOR_WITHDRAWN = "descriptor-withdrawn" + DIGEST_MISMATCH = "digest-mismatch" ENTRYPOINT_UNAVAILABLE = "entrypoint-unavailable" EXPIRED = "expired" FEATURE_DISABLED = "feature-disabled" FOREIGN_RUNTIME = "foreign-runtime" INVALID_CANDIDATE = "invalid-candidate" + INVALID_REFERENCE = "invalid-reference" INVALID_REQUEST = "invalid-request" INVALID_SKILL = "invalid-skill" LIFECYCLE_UNAVAILABLE = "lifecycle-unavailable" @@ -31442,6 +35571,7 @@ class SkillInstallationFailureReason(Enum): REVIEW_TOO_LARGE = "review-too-large" SEARCH_MISMATCH = "search-mismatch" SOURCE_CHANGED = "source-changed" + UNSUPPORTED_RESOURCE = "unsupported-resource" WRITE_FAILED = "write-failed" WRONG_KIND = "wrong-kind" @@ -31787,6 +35917,69 @@ 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. +# Internal: this type is an internal SDK API and is not part of the public surface. +@dataclass +class _UserSettingsMigrateStaffLogLevelOnceResult: + """The outcome of the one-time staff log-level migration.""" + + status: StaffLogLevelMigrationStatus + """What the migration did. Only `refreshed` can have changed the persisted settings.""" + + completed_at: str | None = None + """When the migration was recorded as done, as an ISO-8601 timestamp. Absent only for + `ineligible`, which records nothing. + """ + settings: dict[str, Any] | None = None + """The settings document as it stands after the migration, keyed by setting name. Present + only for `refreshed`, and only when the document could be re-read cleanly. + """ + + @staticmethod + def from_dict(obj: Any) -> '_UserSettingsMigrateStaffLogLevelOnceResult': + assert isinstance(obj, dict) + status = StaffLogLevelMigrationStatus(obj.get("status")) + completed_at = from_union([from_str, from_none], obj.get("completedAt")) + settings = from_union([lambda x: from_dict(lambda x: x, x), from_none], obj.get("settings")) + return _UserSettingsMigrateStaffLogLevelOnceResult(status, completed_at, settings) + + def to_dict(self) -> dict: + result: dict = {} + result["status"] = to_enum(StaffLogLevelMigrationStatus, self.status) + if self.completed_at is not None: + result["completedAt"] = from_union([from_str, from_none], self.completed_at) + if self.settings is not None: + result["settings"] = from_union([lambda x: from_dict(lambda x: x, x), from_none], self.settings) + 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 _UserSettingsMigrateStaffUpdateChannelOnceResult: + """The outcome of the one-time staff update-channel migration.""" + + status: StaffUpdateChannelMigrationStatus + """What the migration did. Only `migrated` means the persisted settings changed.""" + + completed_at: str | None = None + """When the migration was recorded as done, as an ISO-8601 timestamp. Absent only for + `ineligible`, which records nothing. + """ + + @staticmethod + def from_dict(obj: Any) -> '_UserSettingsMigrateStaffUpdateChannelOnceResult': + assert isinstance(obj, dict) + status = StaffUpdateChannelMigrationStatus(obj.get("status")) + completed_at = from_union([from_str, from_none], obj.get("completedAt")) + return _UserSettingsMigrateStaffUpdateChannelOnceResult(status, completed_at) + + def to_dict(self) -> dict: + result: dict = {} + result["status"] = to_enum(StaffUpdateChannelMigrationStatus, self.status) + if self.completed_at is not None: + result["completedAt"] = from_union([from_str, from_none], self.completed_at) + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class TaskAgentProgress: @@ -32712,6 +36905,251 @@ def to_dict(self) -> dict: result["settings"] = from_dict(lambda x: to_class(UserSettingMetadata, x), self.settings) return result +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class UserSettingsApplyTopKeyResult: + """Outcome of validating and writing one top-level setting subtree.""" + + status: str + """`ok` when the value was written, `validation-error` when it was rejected before any + write, `write-error` when validation passed but the write failed. + """ + error: str | None = None + """Why the write failed, present only when `status` is `write-error`.""" + + issues: list[UserSettingsValidationIssue] | None = None + """Problems found, present only when `status` is `validation-error`.""" + + @staticmethod + def from_dict(obj: Any) -> 'UserSettingsApplyTopKeyResult': + assert isinstance(obj, dict) + status = from_str(obj.get("status")) + error = from_union([from_none, from_str], obj.get("error")) + issues = from_union([lambda x: from_list(UserSettingsValidationIssue.from_dict, x), from_none], obj.get("issues")) + return UserSettingsApplyTopKeyResult(status, error, issues) + + def to_dict(self) -> dict: + result: dict = {} + result["status"] = from_str(self.status) + if self.error is not None: + result["error"] = from_union([from_none, from_str], self.error) + if self.issues is not None: + result["issues"] = from_union([lambda x: from_list(lambda x: to_class(UserSettingsValidationIssue, x), x), from_none], self.issues) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class UserSettingsValidateResult: + """Outcome of validating one top-level setting subtree.""" + + ok: bool + """True when the subtree is valid.""" + + issues: list[UserSettingsValidationIssue] | None = None + """Problems found, empty when `ok` is true.""" + + @staticmethod + def from_dict(obj: Any) -> 'UserSettingsValidateResult': + assert isinstance(obj, dict) + ok = from_bool(obj.get("ok")) + issues = from_union([lambda x: from_list(UserSettingsValidationIssue.from_dict, x), from_none], obj.get("issues")) + return UserSettingsValidateResult(ok, issues) + + def to_dict(self) -> dict: + result: dict = {} + result["ok"] = from_bool(self.ok) + if self.issues is not None: + result["issues"] = from_union([lambda x: from_list(lambda x: to_class(UserSettingsValidationIssue, x), x), from_none], self.issues) + 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 _UserSettingsMigrateStaffLogLevelOnceRequest: + """Selects the configuration directory to run the one-time staff log-level migration + against, and supplies the caller's record of whether it has already run. + """ + context: UserSettingsHostContext + """The configuration directory, home directory and environment to resolve the settings and + global-state documents from. A host that scopes a session to its own Copilot home must + pass that directory here, or the migration is evaluated against the server's own home + instead. + """ + cached_completed_at: str | None = None + """When the caller last recorded this migration, as an ISO-8601 timestamp. Supplying it lets + the runtime answer `current` without re-running the evaluation. + """ + + @staticmethod + def from_dict(obj: Any) -> '_UserSettingsMigrateStaffLogLevelOnceRequest': + assert isinstance(obj, dict) + context = UserSettingsHostContext.from_dict(obj.get("context")) + cached_completed_at = from_union([from_str, from_none], obj.get("cachedCompletedAt")) + return _UserSettingsMigrateStaffLogLevelOnceRequest(context, cached_completed_at) + + def to_dict(self) -> dict: + result: dict = {} + result["context"] = to_class(UserSettingsHostContext, self.context) + if self.cached_completed_at is not None: + result["cachedCompletedAt"] = from_union([from_str, from_none], self.cached_completed_at) + 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 _UserSettingsResetStaffModelSelectionOnceRequest: + """Selects the configuration directory to run the one-time staff model-selection reset + against, and supplies the caller's record of whether it has already run. + """ + context: UserSettingsHostContext + """The configuration directory, home directory and environment to resolve the settings and + global-state documents from. A host that scopes a session to its own Copilot home must + pass that directory here, or the reset is evaluated against the server's own home instead. + """ + staff: bool | None = None + """Whether the signed-in account is a staff account. The reset applies only to staff, so + anything other than `true` returns `didReset: false` without touching the settings + document. + """ + staff_model_reset_at: str | None = None + """When the caller last recorded this reset, as an ISO-8601 timestamp. A timestamp at or + after the cutoff means the reset already ran and is returned unchanged. + """ + + @staticmethod + def from_dict(obj: Any) -> '_UserSettingsResetStaffModelSelectionOnceRequest': + assert isinstance(obj, dict) + context = UserSettingsHostContext.from_dict(obj.get("context")) + staff = from_union([from_bool, from_none], obj.get("staff")) + staff_model_reset_at = from_union([from_str, from_none], obj.get("staffModelResetAt")) + return _UserSettingsResetStaffModelSelectionOnceRequest(context, staff, staff_model_reset_at) + + def to_dict(self) -> dict: + result: dict = {} + result["context"] = to_class(UserSettingsHostContext, self.context) + if self.staff is not None: + result["staff"] = from_union([from_bool, from_none], self.staff) + if self.staff_model_reset_at is not None: + result["staffModelResetAt"] = from_union([from_str, from_none], self.staff_model_reset_at) + 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 _UserSettingsUnknownKeysWarningRequest: + """Selects the configuration directory whose user settings document should be inspected for + unrecognized top-level keys. + """ + context: UserSettingsHostContext + """The configuration directory, home directory and environment to resolve the settings + document from. A host that scopes a session to its own Copilot home must pass that + directory here, or the server's own home is inspected instead. + """ + + @staticmethod + def from_dict(obj: Any) -> '_UserSettingsUnknownKeysWarningRequest': + assert isinstance(obj, dict) + context = UserSettingsHostContext.from_dict(obj.get("context")) + return _UserSettingsUnknownKeysWarningRequest(context) + + def to_dict(self) -> dict: + result: dict = {} + result["context"] = to_class(UserSettingsHostContext, self.context) + 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 _UserSettingsMigrateObsoleteOnDiskResult: + """The outcome of the one-time obsolete-settings cleanup.""" + + status: _UserSettingsMigrateObsoleteOnDiskStatus + """What the cleanup did. Only `changed` rewrote the document. The cleaned settings are not + returned here: the document is written before this replies, so `user.settings.load` reads + it authoritatively. + """ + + @staticmethod + def from_dict(obj: Any) -> '_UserSettingsMigrateObsoleteOnDiskResult': + assert isinstance(obj, dict) + status = _UserSettingsMigrateObsoleteOnDiskStatus(obj.get("status")) + return _UserSettingsMigrateObsoleteOnDiskResult(status) + + def to_dict(self) -> dict: + result: dict = {} + result["status"] = to_enum(_UserSettingsMigrateObsoleteOnDiskStatus, self.status) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class UserSettingsProblem: + """One setting that will not take effect, and why.""" + + category: str + """Coarse grouping for the problem, for a caller that presents problems in sections.""" + + explanation: str + """Why the setting will not take effect. Never contains the setting's value, which may be a + token or another secret. + """ + id: str + """Stable identifier for this problem within the reported set, so a caller can track a + selection across refreshes. + """ + kind: str + """What is wrong in machine-readable form, for a caller that wants to branch rather than + display. + """ + source: UserSettingsProblemLocation + """The settings layer the problem was found in.""" + + raw_key: str | None = None + """The offending key as written in the file, when the problem is attributable to a single + key. Absent when the whole file is at fault. + """ + + @staticmethod + def from_dict(obj: Any) -> 'UserSettingsProblem': + assert isinstance(obj, dict) + category = from_str(obj.get("category")) + explanation = from_str(obj.get("explanation")) + id = from_str(obj.get("id")) + kind = from_str(obj.get("kind")) + source = UserSettingsProblemLocation.from_dict(obj.get("source")) + raw_key = from_union([from_str, from_none], obj.get("rawKey")) + return UserSettingsProblem(category, explanation, id, kind, source, raw_key) + + def to_dict(self) -> dict: + result: dict = {} + result["category"] = from_str(self.category) + result["explanation"] = from_str(self.explanation) + result["id"] = from_str(self.id) + result["kind"] = from_str(self.kind) + result["source"] = to_class(UserSettingsProblemLocation, self.source) + if self.raw_key is not None: + result["rawKey"] = from_union([from_str, from_none], self.raw_key) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class UserSettingsProblemsRequest: + """The settings layers to diagnose.""" + + sources: list[UserSettingsProblemSource] | None = None + """Layers to diagnose, in the precedence order the caller applies them.""" + + @staticmethod + def from_dict(obj: Any) -> 'UserSettingsProblemsRequest': + assert isinstance(obj, dict) + sources = from_union([lambda x: from_list(UserSettingsProblemSource.from_dict, x), from_none], obj.get("sources")) + return UserSettingsProblemsRequest(sources) + + def to_dict(self) -> dict: + result: dict = {} + if self.sources is not None: + result["sources"] = from_union([lambda x: from_list(lambda x: to_class(UserSettingsProblemSource, x), x), from_none], self.sources) + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class WorkflowAgentRequest: @@ -33170,6 +37608,40 @@ def to_dict(self) -> dict: result["saved"] = from_union([lambda x: to_class(Saved, x), from_none], self.saved) return result +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class ConnectorDiscoveryCapabilities: + """Feature availability.""" + + api_version: int + """API version.""" + + availability: ConnectorDiscoveryAvailability + """Availability.""" + + conditional_cache: bool + """Whether results are cached.""" + + opaque_account_selection: bool + """Whether accounts are selected by opaque ID.""" + + @staticmethod + def from_dict(obj: Any) -> 'ConnectorDiscoveryCapabilities': + assert isinstance(obj, dict) + api_version = from_int(obj.get("apiVersion")) + availability = ConnectorDiscoveryAvailability(obj.get("availability")) + conditional_cache = from_bool(obj.get("conditionalCache")) + opaque_account_selection = from_bool(obj.get("opaqueAccountSelection")) + return ConnectorDiscoveryCapabilities(api_version, availability, conditional_cache, opaque_account_selection) + + def to_dict(self) -> dict: + result: dict = {} + result["apiVersion"] = from_int(self.api_version) + result["availability"] = to_enum(ConnectorDiscoveryAvailability, self.availability) + result["conditionalCache"] = from_bool(self.conditional_cache) + result["opaqueAccountSelection"] = from_bool(self.opaque_account_selection) + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class ConnectorSessionAccount: @@ -35385,6 +39857,26 @@ def to_dict(self) -> dict: 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. +# Internal: this type is an internal SDK API and is not part of the public surface. +@dataclass +class _FeatureFlagsMetadataResult: + """Every feature flag this runtime declares, with the shape each one was declared with.""" + + flags: dict[str, _FeatureFlagMetadata] + """Declared feature flags keyed by flag name.""" + + @staticmethod + def from_dict(obj: Any) -> '_FeatureFlagsMetadataResult': + assert isinstance(obj, dict) + flags = from_dict(_FeatureFlagMetadata.from_dict, obj.get("flags")) + return _FeatureFlagsMetadataResult(flags) + + def to_dict(self) -> dict: + result: dict = {} + result["flags"] = from_dict(lambda x: to_class(_FeatureFlagMetadata, x), self.flags) + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class MCPOauthHandlePendingRequest: @@ -36406,6 +40898,12 @@ class ManagedSettingsResolveResult: meta: ManagedSettingsMeta | None = None """Per-key lock state and provenance for the entries in `values`, using the same key names.""" + permissions_context: ManagedPermissionsContext | None = None + """Reusable source-composed permission policy for managedSettings.permissions.evaluate. + Unlike resolved.settings.permissions, this retains every source's allowlist and + default-prompt semantics. Refresh through resolve when account/device policy changes. + Absent on runtimes that do not support permission evaluation. + """ values: ManagedSettingsValues | None = None """Typed effective values of managed settings, keyed like the managed-settings schema and already resolved across channels, with the `{ "overridable": ... }` wrapper removed. @@ -36421,8 +40919,9 @@ def from_dict(obj: Any) -> 'ManagedSettingsResolveResult': resolved = ManagedSettingsResolvedData.from_dict(obj.get("resolved")) account = from_union([from_str, from_none], obj.get("account")) meta = from_union([ManagedSettingsMeta.from_dict, from_none], obj.get("meta")) + permissions_context = from_union([ManagedPermissionsContext.from_dict, from_none], obj.get("permissionsContext")) values = from_union([ManagedSettingsValues.from_dict, from_none], obj.get("values")) - return ManagedSettingsResolveResult(diagnostics, layers, resolved, account, meta, values) + return ManagedSettingsResolveResult(diagnostics, layers, resolved, account, meta, permissions_context, values) def to_dict(self) -> dict: result: dict = {} @@ -36433,10 +40932,191 @@ def to_dict(self) -> dict: result["account"] = from_union([from_str, from_none], self.account) if self.meta is not None: result["meta"] = from_union([lambda x: to_class(ManagedSettingsMeta, x), from_none], self.meta) + if self.permissions_context is not None: + result["permissionsContext"] = from_union([lambda x: to_class(ManagedPermissionsContext, x), from_none], self.permissions_context) if self.values is not None: result["values"] = from_union([lambda x: to_class(ManagedSettingsValues, x), from_none], self.values) 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 _ManagedSettingsIngestResult: + """The managed policy layers an ingestion resolved, together with the per-layer outcome + flags a host needs to decide how much to trust the result. The flags are reported rather + than folded into the settings because a host must be able to distinguish an unmanaged + device from a managed device whose policy could not be confirmed, and apply its + fail-closed gates in the second case. + """ + device_load_failed: bool + """The device layer could not be read, so it is unconfirmed rather than known-absent.""" + + device_sandbox_undetermined: bool + """Whether the device layer's sandbox intent could not be determined, so a consumer must not + read its absence as permission to run unsandboxed. + """ + device_source_found: bool + """A device-managed policy is known to exist.""" + + log_messages: list[_ManagedSettingsLogMessage] + """Diagnostics emitted while resolving the layers, in the order they occurred.""" + + policy_helper_configured: bool + """A policy helper is configured for this host.""" + + policy_helper_fail_closed: bool + """The policy helper's failure must be treated as fail-closed rather than ignored.""" + + policy_helper_load_failed: bool + """The policy helper was configured but could not be run or its output could not be read.""" + + server_cache_fallback: bool + """A live fetch failed but a recent cached entry was served in its place, so + `serverFetchFailed` stays false. Lets a consumer distinguish a clean fetch from a + cache-backed fallback. + """ + server_fetch_failed: bool + """The server fetch failed and no cached entry stood in for it, so the server layer is + unconfirmed and fail-closed gates apply. + """ + server_forced_refresh_failure: bool + """The server fetch failed while a policy demanded a fresh confirmation, which is the strict + posture an enterprise opted into rather than an ordinary transient failure. + """ + server_resolution: _ManagedSettingsServerPolicyResolution + """How the server layer was resolved.""" + + server_retryable_failure: bool + """The server fetch failed in a way a later retry could succeed at, as opposed to a refusal + that no retry will change. + """ + server_source_found: bool + """A server-managed policy is known to exist even though `serverResponse` is intentionally + unset. Consumers should OR this with the presence of `serverResponse` when deciding + whether the device is managed, so a fresh start during an outage still applies the + fail-closed gates instead of treating the device as unmanaged. + """ + server_token_unavailable: bool + """No usable token was available, so no server fetch was attempted.""" + + device_response: Any = None + """The device layer discovered from the machine, when one exists.""" + + device_sandbox_floor: Any = None + """The sandbox floor contributed by the device layer.""" + + permissions: Any = None + """The effective managed permissions layer, when the resolved policy carries one.""" + + policy_helper_authority: _ManagedSettingsPolicyHelperAuthority | None = None + """Which channel registered the policy helper that produced the helper layer.""" + + policy_helper_response: Any = None + """The layer produced by the platform policy helper, when one ran.""" + + server_client_update_requested: str | None = None + """Minimum client version requested by a successful response. Advisory only; the delivered + policy remains active. + """ + server_client_update_required: str | None = None + """The server refused to deliver a policy because this client is too old to enforce it, + carrying the user-facing sentence describing the refusal. No retry will help; the remedy + is a client update. Gates that exist only because of managed policy should deny on this + rather than falling back to their unmanaged default. + """ + server_fetch_error: str | None = None + """The error that made the server fetch fail, when one did.""" + + server_permissions_floor: Any = None + """The synthesized fail-closed permissions layer applied in place of a policy a refused + client may not enforce. + """ + server_response: Any = None + """The account's server-delivered policy, when one was resolved.""" + + server_sandbox_floor: Any = None + """The sandbox object from a cached server policy that a forced refresh declined to serve in + full. Folded back into the effective sandbox layer so an outage during a forced refresh + still synthesizes the fail-closed sandbox floor from the administrator's known intent. + """ + settings: Any = None + """The effective managed settings composed from every resolved layer. Omitted when no layer + set anything, so an unmanaged host locks nothing. + """ + + @staticmethod + def from_dict(obj: Any) -> '_ManagedSettingsIngestResult': + assert isinstance(obj, dict) + device_load_failed = from_bool(obj.get("deviceLoadFailed")) + device_sandbox_undetermined = from_bool(obj.get("deviceSandboxUndetermined")) + device_source_found = from_bool(obj.get("deviceSourceFound")) + log_messages = from_list(_ManagedSettingsLogMessage.from_dict, obj.get("logMessages")) + policy_helper_configured = from_bool(obj.get("policyHelperConfigured")) + policy_helper_fail_closed = from_bool(obj.get("policyHelperFailClosed")) + policy_helper_load_failed = from_bool(obj.get("policyHelperLoadFailed")) + server_cache_fallback = from_bool(obj.get("serverCacheFallback")) + server_fetch_failed = from_bool(obj.get("serverFetchFailed")) + server_forced_refresh_failure = from_bool(obj.get("serverForcedRefreshFailure")) + server_resolution = _ManagedSettingsServerPolicyResolution(obj.get("serverResolution")) + server_retryable_failure = from_bool(obj.get("serverRetryableFailure")) + server_source_found = from_bool(obj.get("serverSourceFound")) + server_token_unavailable = from_bool(obj.get("serverTokenUnavailable")) + device_response = obj.get("deviceResponse") + device_sandbox_floor = obj.get("deviceSandboxFloor") + permissions = obj.get("permissions") + policy_helper_authority = from_union([_ManagedSettingsPolicyHelperAuthority, from_none], obj.get("policyHelperAuthority")) + policy_helper_response = obj.get("policyHelperResponse") + server_client_update_requested = from_union([from_str, from_none], obj.get("serverClientUpdateRequested")) + server_client_update_required = from_union([from_str, from_none], obj.get("serverClientUpdateRequired")) + server_fetch_error = from_union([from_str, from_none], obj.get("serverFetchError")) + server_permissions_floor = obj.get("serverPermissionsFloor") + server_response = obj.get("serverResponse") + server_sandbox_floor = obj.get("serverSandboxFloor") + settings = obj.get("settings") + return _ManagedSettingsIngestResult(device_load_failed, device_sandbox_undetermined, device_source_found, log_messages, policy_helper_configured, policy_helper_fail_closed, policy_helper_load_failed, server_cache_fallback, server_fetch_failed, server_forced_refresh_failure, server_resolution, server_retryable_failure, server_source_found, server_token_unavailable, device_response, device_sandbox_floor, permissions, policy_helper_authority, policy_helper_response, server_client_update_requested, server_client_update_required, server_fetch_error, server_permissions_floor, server_response, server_sandbox_floor, settings) + + def to_dict(self) -> dict: + result: dict = {} + result["deviceLoadFailed"] = from_bool(self.device_load_failed) + result["deviceSandboxUndetermined"] = from_bool(self.device_sandbox_undetermined) + result["deviceSourceFound"] = from_bool(self.device_source_found) + result["logMessages"] = from_list(lambda x: to_class(_ManagedSettingsLogMessage, x), self.log_messages) + result["policyHelperConfigured"] = from_bool(self.policy_helper_configured) + result["policyHelperFailClosed"] = from_bool(self.policy_helper_fail_closed) + result["policyHelperLoadFailed"] = from_bool(self.policy_helper_load_failed) + result["serverCacheFallback"] = from_bool(self.server_cache_fallback) + result["serverFetchFailed"] = from_bool(self.server_fetch_failed) + result["serverForcedRefreshFailure"] = from_bool(self.server_forced_refresh_failure) + result["serverResolution"] = to_enum(_ManagedSettingsServerPolicyResolution, self.server_resolution) + result["serverRetryableFailure"] = from_bool(self.server_retryable_failure) + result["serverSourceFound"] = from_bool(self.server_source_found) + result["serverTokenUnavailable"] = from_bool(self.server_token_unavailable) + if self.device_response is not None: + result["deviceResponse"] = self.device_response + if self.device_sandbox_floor is not None: + result["deviceSandboxFloor"] = self.device_sandbox_floor + if self.permissions is not None: + result["permissions"] = self.permissions + if self.policy_helper_authority is not None: + result["policyHelperAuthority"] = from_union([lambda x: to_enum(_ManagedSettingsPolicyHelperAuthority, x), from_none], self.policy_helper_authority) + if self.policy_helper_response is not None: + result["policyHelperResponse"] = self.policy_helper_response + if self.server_client_update_requested is not None: + result["serverClientUpdateRequested"] = from_union([from_str, from_none], self.server_client_update_requested) + if self.server_client_update_required is not None: + result["serverClientUpdateRequired"] = from_union([from_str, from_none], self.server_client_update_required) + if self.server_fetch_error is not None: + result["serverFetchError"] = from_union([from_str, from_none], self.server_fetch_error) + if self.server_permissions_floor is not None: + result["serverPermissionsFloor"] = self.server_permissions_floor + if self.server_response is not None: + result["serverResponse"] = self.server_response + if self.server_sandbox_floor is not None: + result["serverSandboxFloor"] = self.server_sandbox_floor + 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 MCPAppsHostContext: @@ -37196,6 +41876,78 @@ 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 CatalogAgentPluginInstallPlan: + """Reviewable install plan built only from verified catalogue install data. Present exactly + when installability is `installable`; the runtime never derives it from the repository + source, display text, or identity. + + What installing an Agent Plugin would do, for the user to review and confirm. + Provisional: the catalogue install data it is built from is not final. + """ + canvas_ids: list[str] + """Canvas IDs the plugin contributes. Non-empty exactly when components include `canvas`.""" + + components: list[CatalogAgentPluginComponent] + """Components the plugin contributes, in canonical order.""" + + connector_dependencies: list[str] + """Connectors the plugin depends on.""" + + extension_ids: list[str] + """Extension IDs the plugin contributes. Non-empty exactly when components include + `extension`. + """ + mcp_dependencies: list[str] + """MCP servers the plugin depends on but does not bundle.""" + + plugin_name: str + """The plugin's name, as named in its marketplace or manifest.""" + + sources: list[CatalogAgentPluginInstallSource] + """Every valid install source, in preference order: materialization, then marketplace, then + repository. Each kind appears at most once. + """ + commit: str | None = None + """Exact full commit ID to install, in lowercase hex. Always present when the plan has a + repository source; a plan with a marketplace source carries at least one of version and + commit. + """ + version: str | None = None + """Exact plugin version to install. A plan with a marketplace source carries at least one of + version and commit. + """ + + @staticmethod + def from_dict(obj: Any) -> 'CatalogAgentPluginInstallPlan': + assert isinstance(obj, dict) + canvas_ids = from_list(from_str, obj.get("canvasIds")) + components = from_list(CatalogAgentPluginComponent, obj.get("components")) + connector_dependencies = from_list(from_str, obj.get("connectorDependencies")) + extension_ids = from_list(from_str, obj.get("extensionIds")) + mcp_dependencies = from_list(from_str, obj.get("mcpDependencies")) + plugin_name = from_str(obj.get("pluginName")) + sources = from_list(_load_CatalogAgentPluginInstallSource, obj.get("sources")) + commit = from_union([from_str, from_none], obj.get("commit")) + version = from_union([from_str, from_none], obj.get("version")) + return CatalogAgentPluginInstallPlan(canvas_ids, components, connector_dependencies, extension_ids, mcp_dependencies, plugin_name, sources, commit, version) + + def to_dict(self) -> dict: + result: dict = {} + result["canvasIds"] = from_list(from_str, self.canvas_ids) + result["components"] = from_list(lambda x: to_enum(CatalogAgentPluginComponent, x), self.components) + result["connectorDependencies"] = from_list(from_str, self.connector_dependencies) + result["extensionIds"] = from_list(from_str, self.extension_ids) + result["mcpDependencies"] = from_list(from_str, self.mcp_dependencies) + result["pluginName"] = from_str(self.plugin_name) + result["sources"] = from_list(lambda x: (x).to_dict(), self.sources) + if self.commit is not None: + result["commit"] = from_union([from_str, from_none], self.commit) + 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 CatalogAgentPluginCandidateProvenance: @@ -37210,7 +41962,7 @@ class CatalogAgentPluginCandidateProvenance: credentials. Inert untrusted data. """ media_type: CatalogAgentPluginMediaType - """Canonical Agent Plugin media type.""" + """Media type naming the plugin's format.""" observed_at: str """ISO 8601 timestamp at which the runtime observed the catalog reference. This is not a @@ -37293,7 +42045,7 @@ class CatalogCandidateProvenance: Media type advertised for the referenced AI skill card - Canonical Agent Plugin media type. + Media type naming the plugin's format. """ observed_at: str """ISO 8601 timestamp at which the runtime observed the catalog reference. This is not a @@ -37457,6 +42209,27 @@ def to_dict(self) -> dict: result["plugins"] = from_list(lambda x: to_class(InstalledPluginInfo, x), self.plugins) return result +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class SessionPluginsRetryManagedResult: + """Per-plugin outcomes of retrying the plugins required by the session's managed settings.""" + + plugins: list[ManagedPluginRetryEntry] + """One entry per requested plugin, in request order. When no plugins were requested, one + entry per plugin the managed settings require. + """ + + @staticmethod + def from_dict(obj: Any) -> 'SessionPluginsRetryManagedResult': + assert isinstance(obj, dict) + plugins = from_list(ManagedPluginRetryEntry.from_dict, obj.get("plugins")) + return SessionPluginsRetryManagedResult(plugins) + + def to_dict(self) -> dict: + result: dict = {} + result["plugins"] = from_list(lambda x: to_class(ManagedPluginRetryEntry, x), self.plugins) + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class MarketplaceBrowseResult: @@ -39077,6 +43850,37 @@ def to_dict(self) -> dict: result["policySessionId"] = from_str(self.policy_session_id) return result +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class SkillPlanInstallFromReferenceRequest: + """Side-effect-free planning of one Skill from a materialisation reference rather than a + search candidate. + """ + contract: CatalogClientContract + """Required authenticated bound catalogue, skill-confirmed-installation and + skill-reference-installation capabilities. + """ + policy_session_id: str + """Existing local session attached to this connection.""" + + reference: CatalogMaterializationReference + """Materialisation reference to verify and plan.""" + + @staticmethod + def from_dict(obj: Any) -> 'SkillPlanInstallFromReferenceRequest': + assert isinstance(obj, dict) + contract = CatalogClientContract.from_dict(obj.get("contract")) + policy_session_id = from_str(obj.get("policySessionId")) + reference = CatalogMaterializationReference.from_dict(obj.get("reference")) + return SkillPlanInstallFromReferenceRequest(contract, policy_session_id, reference) + + def to_dict(self) -> dict: + result: dict = {} + result["contract"] = to_class(CatalogClientContract, self.contract) + result["policySessionId"] = from_str(self.policy_session_id) + result["reference"] = to_class(CatalogMaterializationReference, self.reference) + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class SkillInstallationSummary: @@ -39661,6 +44465,26 @@ def to_dict(self) -> dict: result["response"] = to_class(UIExitPlanModeResponse, self.response) return result +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class UserSettingsProblemsResult: + """The settings that will not take effect across the given layers.""" + + problems: list[UserSettingsProblem] | None = None + """Each setting that will not take effect, and why. Empty when every layer is healthy.""" + + @staticmethod + def from_dict(obj: Any) -> 'UserSettingsProblemsResult': + assert isinstance(obj, dict) + problems = from_union([lambda x: from_list(UserSettingsProblem.from_dict, x), from_none], obj.get("problems")) + return UserSettingsProblemsResult(problems) + + def to_dict(self) -> dict: + result: dict = {} + if self.problems is not None: + result["problems"] = from_union([lambda x: from_list(lambda x: to_class(UserSettingsProblem, x), x), from_none], self.problems) + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class WorkflowLogRequest: @@ -41118,6 +45942,30 @@ 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 ConnectorDiscoveryAccountList: + """Eligible accounts.""" + + accounts: list[ConnectorDiscoveryAccount] + """Eligible accounts.""" + + availability: ConnectorDiscoveryAvailability + """Availability.""" + + @staticmethod + def from_dict(obj: Any) -> 'ConnectorDiscoveryAccountList': + assert isinstance(obj, dict) + accounts = from_list(ConnectorDiscoveryAccount.from_dict, obj.get("accounts")) + availability = ConnectorDiscoveryAvailability(obj.get("availability")) + return ConnectorDiscoveryAccountList(accounts, availability) + + def to_dict(self) -> dict: + result: dict = {} + result["accounts"] = from_list(lambda x: to_class(ConnectorDiscoveryAccount, x), self.accounts) + result["availability"] = to_enum(ConnectorDiscoveryAvailability, self.availability) + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class EnvironmentsGetResult: @@ -41358,9 +46206,6 @@ class _GlobalStateLoadResult: recorded nothing yet, so a reader must treat an absent field as `not yet`, never as a negative answer. Stored credentials are deliberately absent from this shape. """ - app_install_nudge_responded: bool | None = None - """Whether the user has answered the prompt suggesting they install the desktop app.""" - app_tip_shown: bool | None = None """Whether the app tip has been shown.""" @@ -41423,7 +46268,6 @@ class _GlobalStateLoadResult: @staticmethod def from_dict(obj: Any) -> '_GlobalStateLoadResult': assert isinstance(obj, dict) - app_install_nudge_responded = from_union([from_bool, from_none], obj.get("appInstallNudgeResponded")) app_tip_shown = from_union([from_bool, from_none], obj.get("appTipShown")) asked_setup_terminals = from_union([lambda x: from_list(from_str, x), from_none], obj.get("askedSetupTerminals")) auto_feedback_last_prompted_at = from_union([from_str, from_none], obj.get("autoFeedbackLastPromptedAt")) @@ -41443,12 +46287,10 @@ def from_dict(obj: Any) -> '_GlobalStateLoadResult': staff_update_channel_migration_at = from_union([from_str, from_none], obj.get("staffUpdateChannelMigrationAt")) suppress_init_folders = from_union([lambda x: from_list(from_str, x), from_none], obj.get("suppressInitFolders")) trusted_folders = from_union([lambda x: from_list(from_str, x), from_none], obj.get("trustedFolders")) - return _GlobalStateLoadResult(app_install_nudge_responded, app_tip_shown, asked_setup_terminals, auto_feedback_last_prompted_at, first_launch_at, installed_plugins, last_logged_in_user, logged_in_users, reasoning_summaries_cleanup_done, recent_model_ids, sandbox_credential_proxy_ca_declined, sandbox_onboarding_shown, staff, staff_github, staff_log_level_migration_at, staff_microsoft, staff_model_reset_at, staff_update_channel_migration_at, suppress_init_folders, trusted_folders) + return _GlobalStateLoadResult(app_tip_shown, asked_setup_terminals, auto_feedback_last_prompted_at, first_launch_at, installed_plugins, last_logged_in_user, logged_in_users, reasoning_summaries_cleanup_done, recent_model_ids, sandbox_credential_proxy_ca_declined, sandbox_onboarding_shown, staff, staff_github, staff_log_level_migration_at, staff_microsoft, staff_model_reset_at, staff_update_channel_migration_at, suppress_init_folders, trusted_folders) def to_dict(self) -> dict: result: dict = {} - if self.app_install_nudge_responded is not None: - result["appInstallNudgeResponded"] = from_union([from_bool, from_none], self.app_install_nudge_responded) if self.app_tip_shown is not None: result["appTipShown"] = from_union([from_bool, from_none], self.app_tip_shown) if self.asked_setup_terminals is not None: @@ -42396,7 +47238,7 @@ class CatalogAgentPluginCandidate: """Discriminator: this candidate describes an Agent Plugin.""" media_type: CatalogAgentPluginMediaType - """Canonical Agent Plugin media type.""" + """Media type naming the plugin's format.""" provenance: CatalogAgentPluginCandidateProvenance """Where the Agent Plugin catalog reference was observed, without its descriptor, URL, raw @@ -42409,6 +47251,15 @@ class CatalogAgentPluginCandidate: description: str | None = None """Description taken verbatim from the card. Inert untrusted text.""" + installability: CatalogAgentPluginInstallability | None = None + """Whether this plugin can be installed in the selected session. Present only when the + caller requires the agent-plugin-confirmed-installation capability. + """ + install_plan: CatalogAgentPluginInstallPlan | None = None + """Reviewable install plan built only from verified catalogue install data. Present exactly + when installability is `installable`; the runtime never derives it from the repository + source, display text, or identity. + """ publisher: str | None = None """Publisher taken verbatim from the card. Inert untrusted text.""" @@ -42433,10 +47284,12 @@ def from_dict(obj: Any) -> 'CatalogAgentPluginCandidate': provenance = CatalogAgentPluginCandidateProvenance.from_dict(obj.get("provenance")) source = CatalogPluginRepositorySource.from_dict(obj.get("source")) description = from_union([from_str, from_none], obj.get("description")) + installability = from_union([CatalogAgentPluginInstallability, from_none], obj.get("installability")) + install_plan = from_union([CatalogAgentPluginInstallPlan.from_dict, from_none], obj.get("installPlan")) publisher = from_union([from_str, from_none], obj.get("publisher")) trust = from_union([_load_CatalogTrustSnapshot, from_none], obj.get("trust")) version = from_union([from_str, from_none], obj.get("version")) - return CatalogAgentPluginCandidate(compatibility_tags, display_name, identity, kind, media_type, provenance, source, description, publisher, trust, version) + return CatalogAgentPluginCandidate(compatibility_tags, display_name, identity, kind, media_type, provenance, source, description, installability, install_plan, publisher, trust, version) def to_dict(self) -> dict: result: dict = {} @@ -42449,6 +47302,10 @@ def to_dict(self) -> dict: result["source"] = to_class(CatalogPluginRepositorySource, self.source) if self.description is not None: result["description"] = from_union([from_str, from_none], self.description) + if self.installability is not None: + result["installability"] = from_union([lambda x: to_enum(CatalogAgentPluginInstallability, x), from_none], self.installability) + if self.install_plan is not None: + result["installPlan"] = from_union([lambda x: to_class(CatalogAgentPluginInstallPlan, x), from_none], self.install_plan) if self.publisher is not None: result["publisher"] = from_union([from_str, from_none], self.publisher) if self.trust is not None: @@ -42492,7 +47349,7 @@ class CatalogCandidate: Media type of the underlying AI skill card - Canonical Agent Plugin media type. + Media type naming the plugin's format. """ provenance: CatalogCandidateProvenance """Where the catalog reference was observed, without the card itself or any content digest. @@ -42523,6 +47380,9 @@ class CatalogCandidate: Whether this AI skill candidate can be planned for verified installation in the selected session. + + Whether this plugin can be installed in the selected session. Present only when the + caller requires the agent-plugin-confirmed-installation capability. """ publisher: str | None = None """Publisher taken verbatim from the card. Inert untrusted text.""" @@ -42545,6 +47405,11 @@ class CatalogCandidate: """Validated, normalised catalogue resource URN. This identity comes only from the catalog identifier and is never inferred from display text or installed-plugin state. """ + install_plan: CatalogAgentPluginInstallPlan | None = None + """Reviewable install plan built only from verified catalogue install data. Present exactly + when installability is `installable`; the runtime never derives it from the repository + source, display text, or identity. + """ version: str | None = None """Optional version declared by the catalog source. Omitted rather than guessed when the source supplies no version. @@ -42566,8 +47431,9 @@ def from_dict(obj: Any) -> 'CatalogCandidate': trust = from_union([_load_CatalogTrustSnapshot, from_none], obj.get("trust")) compatibility_tags = from_union([lambda x: from_list(CatalogAgentPluginCompatibilityTag, x), from_none], obj.get("compatibilityTags")) identity = from_union([from_str, from_none], obj.get("identity")) + install_plan = from_union([CatalogAgentPluginInstallPlan.from_dict, from_none], obj.get("installPlan")) version = from_union([from_str, from_none], obj.get("version")) - return CatalogCandidate(display_name, kind, media_type, provenance, source, description, handle, handle_expires_at, installability, publisher, trust, compatibility_tags, identity, version) + return CatalogCandidate(display_name, kind, media_type, provenance, source, description, handle, handle_expires_at, installability, publisher, trust, compatibility_tags, identity, install_plan, version) def to_dict(self) -> dict: result: dict = {} @@ -42592,6 +47458,8 @@ def to_dict(self) -> dict: result["compatibilityTags"] = from_union([lambda x: from_list(lambda x: to_enum(CatalogAgentPluginCompatibilityTag, x), x), from_none], self.compatibility_tags) if self.identity is not None: result["identity"] = from_union([from_str, from_none], self.identity) + if self.install_plan is not None: + result["installPlan"] = from_union([lambda x: to_class(CatalogAgentPluginInstallPlan, x), from_none], self.install_plan) if self.version is not None: result["version"] = from_union([from_str, from_none], self.version) return result @@ -43879,6 +48747,13 @@ class SessionUpdateOptionsParams: agent_context: str | None = None """Runtime context discriminator (e.g., `cli`, `actions`).""" + agent_directories: list[str] | None = None + """Explicit discovery-only agent roots, separate from customAgents and filesystem grants. + Available without enableConfigDiscovery, subject to managed/source disables. Relative SDK + paths resolve once when supplied against the request's effective workingDirectory; + retained roots do not move on cwd changes. Null and omission are equivalent; [] clears + only the explicit SDK contribution. + """ allow_all_mcp_server_instructions: bool | None = None """Whether to include instructions from every MCP server in the system prompt instead of only allowlisted servers. @@ -43919,8 +48794,9 @@ class SessionUpdateOptionsParams: """Skill IDs that should be excluded from this session.""" enable_file_hooks: bool | None = None - """Whether to enable loading of `.github/hooks/` filesystem hooks. Separate from the SDK - callback hook mechanism. + """Whether to enable repository-origin file hooks from `.github/hooks/` and trusted + repository-configured roots. Does not control explicit hookDirectories or SDK callback + hooks. """ enable_host_git_operations: bool | None = None """Whether to enable host git operations (context resolution, child repo scanning, git info @@ -43941,9 +48817,10 @@ class SessionUpdateOptionsParams: """Whether to enable cross-session store writes and reads.""" enable_skills: bool | None = None - """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. + """Whether skill loading is enabled. Explicit false disables every source, including + explicit SDK roots and a bound SDK provider; changing the value invalidates the loaded + skill snapshot. When omitted, creation falls back to enableConfigDiscovery unless + explicit SDK skill roots or an SDK skill provider are supplied. """ enable_streaming: bool | None = None """Whether to stream model responses.""" @@ -43970,6 +48847,14 @@ class SessionUpdateOptionsParams: feature_flags: dict[str, bool] | None = None """Map of feature-flag IDs to their boolean enabled state.""" + hook_directories: list[str] | None = None + """Explicit discovery-only file-hook roots, separate from callback hooks and filesystem + grants. Available without enableConfigDiscovery or enableFileHooks, which controls + repository-origin hooks only; hook/managed disables still apply. Relative SDK paths + resolve once when supplied against the request's effective workingDirectory; retained + roots do not move on cwd changes. Null and omission are equivalent; [] clears only the + explicit SDK contribution. + """ ignored_skills_locations: list[str] | None = None """Skill scan directories and descendants excluded from discovery. Supports `~`-relative paths. @@ -43988,6 +48873,13 @@ class SessionUpdateOptionsParams: """Full set of installed plugins for the session. Replaces the existing list; the runtime invalidates the skills cache only when the list materially changes. """ + instruction_directories: list[str] | None = None + """Explicit discovery-only instruction roots using existing SDK instruction formats, not + filesystem grants. Available without enableConfigDiscovery, subject to + instruction/managed disables. Relative SDK paths resolve once when supplied against the + request's effective workingDirectory; retained roots do not move on cwd changes. Null and + omission are equivalent; [] clears only the explicit SDK contribution. + """ integration_id: str | None = None """Stable integration identifier used for analytics and rate-limit attribution.""" @@ -44061,8 +48953,12 @@ class SessionUpdateOptionsParams: """PowerShell process flags applied to built-in and user-requested shell commands.""" skill_directories: list[str] | None = None - """Additional directories to search for skills.""" - + """Explicit discovery-only skill roots, not filesystem grants. Available without + enableConfigDiscovery, subject to skill/managed disables. Relative SDK paths resolve once + when supplied against the request's effective workingDirectory; retained roots do not + move on cwd changes. Null and omission are equivalent; [] clears only the explicit SDK + contribution. + """ skip_custom_instructions: bool | None = None """Whether to skip loading custom instruction sources.""" @@ -44092,6 +48988,7 @@ def from_dict(obj: Any) -> 'SessionUpdateOptionsParams': assert isinstance(obj, dict) additional_content_exclusion_policies = from_union([lambda x: from_list(OptionsUpdateAdditionalContentExclusionPolicy.from_dict, x), from_none], obj.get("additionalContentExclusionPolicies")) agent_context = from_union([from_str, from_none], obj.get("agentContext")) + agent_directories = from_union([lambda x: from_list(from_str, x), from_none], obj.get("agentDirectories")) allow_all_mcp_server_instructions = from_union([from_bool, from_none], obj.get("allowAllMcpServerInstructions")) ask_user_disabled = from_union([from_bool, from_none], obj.get("askUserDisabled")) available_tools = from_union([lambda x: from_list(from_str, x), from_none], obj.get("availableTools")) @@ -44118,10 +49015,12 @@ def from_dict(obj: Any) -> 'SessionUpdateOptionsParams': excluded_builtin_agents = from_union([lambda x: from_list(from_str, x), from_none], obj.get("excludedBuiltinAgents")) excluded_tools = from_union([lambda x: from_list(from_str, x), from_none], obj.get("excludedTools")) feature_flags = from_union([lambda x: from_dict(from_bool, x), from_none], obj.get("featureFlags")) + hook_directories = from_union([lambda x: from_list(from_str, x), from_none], obj.get("hookDirectories")) ignored_skills_locations = from_union([lambda x: from_list(from_str, x), from_none], obj.get("ignoredSkillsLocations")) 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")) + instruction_directories = from_union([lambda x: from_list(from_str, x), from_none], obj.get("instructionDirectories")) 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")) @@ -44146,11 +49045,11 @@ def from_dict(obj: Any) -> 'SessionUpdateOptionsParams': skip_custom_instructions = from_union([from_bool, from_none], obj.get("skipCustomInstructions")) skip_embedding_retrieval = from_union([from_bool, from_none], obj.get("skipEmbeddingRetrieval")) suppress_custom_agent_prompt = from_union([from_bool, from_none], obj.get("suppressCustomAgentPrompt")) - tool_filter_precedence = from_union([OptionsUpdateToolFilterPrecedence, from_none], obj.get("toolFilterPrecedence")) + tool_filter_precedence = from_union([from_none, OptionsUpdateToolFilterPrecedence], obj.get("toolFilterPrecedence")) 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, ignored_skills_locations, 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) + return SessionUpdateOptionsParams(additional_content_exclusion_policies, agent_context, agent_directories, 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, hook_directories, ignored_skills_locations, included_builtin_agents, included_builtin_skills, installed_plugins, instruction_directories, 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 = {} @@ -44158,6 +49057,8 @@ def to_dict(self) -> dict: result["additionalContentExclusionPolicies"] = from_union([lambda x: from_list(lambda x: to_class(OptionsUpdateAdditionalContentExclusionPolicy, x), x), from_none], self.additional_content_exclusion_policies) if self.agent_context is not None: result["agentContext"] = from_union([from_str, from_none], self.agent_context) + if self.agent_directories is not None: + result["agentDirectories"] = from_union([lambda x: from_list(from_str, x), from_none], self.agent_directories) if self.allow_all_mcp_server_instructions is not None: result["allowAllMcpServerInstructions"] = from_union([from_bool, from_none], self.allow_all_mcp_server_instructions) if self.ask_user_disabled is not None: @@ -44210,6 +49111,8 @@ def to_dict(self) -> dict: result["excludedTools"] = from_union([lambda x: from_list(from_str, x), from_none], self.excluded_tools) 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.hook_directories is not None: + result["hookDirectories"] = from_union([lambda x: from_list(from_str, x), from_none], self.hook_directories) if self.ignored_skills_locations is not None: result["ignoredSkillsLocations"] = from_union([lambda x: from_list(from_str, x), from_none], self.ignored_skills_locations) if self.included_builtin_agents is not None: @@ -44218,6 +49121,8 @@ def to_dict(self) -> dict: 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.instruction_directories is not None: + result["instructionDirectories"] = from_union([lambda x: from_list(from_str, x), from_none], self.instruction_directories) if self.integration_id is not None: result["integrationId"] = from_union([from_str, from_none], self.integration_id) if self.is_experimental_mode is not None: @@ -44267,7 +49172,7 @@ def to_dict(self) -> dict: if self.suppress_custom_agent_prompt is not None: result["suppressCustomAgentPrompt"] = from_union([from_bool, from_none], self.suppress_custom_agent_prompt) if self.tool_filter_precedence is not None: - result["toolFilterPrecedence"] = from_union([lambda x: to_enum(OptionsUpdateToolFilterPrecedence, x), from_none], self.tool_filter_precedence) + result["toolFilterPrecedence"] = from_union([from_none, lambda x: to_enum(OptionsUpdateToolFilterPrecedence, x)], self.tool_filter_precedence) if self.trajectory_file is not None: result["trajectoryFile"] = from_union([from_str, from_none], self.trajectory_file) if self.verbosity is not None: @@ -45161,6 +50066,134 @@ def to_dict(self) -> dict: result["token_based_billing"] = from_union([from_bool, from_none], self.token_based_billing) return result +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class AuthIdentity: + """Identity to describe. + + Credential-free authentication identity safe to expose to hosts and user interfaces. + + Identity to evaluate. + + Identity whose host the path resolves against. Omit to resolve against the public + github.com host. + + Credential-free identity metadata for the active account, including resolved Copilot user + information when available. + """ + 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. +# Internal: this type is an internal SDK API and is not part of the public surface. +@dataclass +class _AccountDescribeAuthInfoRequest: + """The authentication identity whose shared wording should be rendered.""" + + identity: AuthIdentity + """Identity to describe.""" + + @staticmethod + def from_dict(obj: Any) -> '_AccountDescribeAuthInfoRequest': + assert isinstance(obj, dict) + identity = AuthIdentity.from_dict(obj.get("identity")) + return _AccountDescribeAuthInfoRequest(identity) + + def to_dict(self) -> dict: + result: dict = {} + result["identity"] = to_class(AuthIdentity, self.identity) + 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 _AccountGetGistSupportRequest: + """The authentication identity whose gist support should be reported.""" + + identity: AuthIdentity + """Identity to evaluate.""" + + @staticmethod + def from_dict(obj: Any) -> '_AccountGetGistSupportRequest': + assert isinstance(obj, dict) + identity = AuthIdentity.from_dict(obj.get("identity")) + return _AccountGetGistSupportRequest(identity) + + def to_dict(self) -> dict: + result: dict = {} + result["identity"] = to_class(AuthIdentity, self.identity) + 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 _AccountGetWebURLRequest: + """A path to resolve against the GitHub host an identity is signed in to.""" + + path: str + """Host-relative path, without a leading slash, for example `settings/copilot/features`.""" + + identity: AuthIdentity | None = None + """Identity whose host the path resolves against. Omit to resolve against the public + github.com host. + """ + + @staticmethod + def from_dict(obj: Any) -> '_AccountGetWebURLRequest': + assert isinstance(obj, dict) + path = from_str(obj.get("path")) + identity = from_union([AuthIdentity.from_dict, from_none], obj.get("identity")) + return _AccountGetWebURLRequest(path, identity) + + def to_dict(self) -> dict: + result: dict = {} + result["path"] = from_str(self.path) + if self.identity is not None: + result["identity"] = from_union([lambda x: to_class(AuthIdentity, x), from_none], self.identity) + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class AgentRegistryLiveTargetEntry: @@ -45419,59 +50452,6 @@ 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. - - Credential-free identity metadata for the active account, including resolved Copilot user - information when available. - """ - 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 AuthReadValue: @@ -45577,64 +50557,6 @@ def to_dict(self) -> dict: 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 ConnectorDiscoveryAccountList: - """Eligible accounts.""" - - accounts: list[ConnectorDiscoveryAccount] - """Eligible accounts.""" - - availability: ConnectorDiscoveryAvailability - """Availability.""" - - @staticmethod - def from_dict(obj: Any) -> 'ConnectorDiscoveryAccountList': - assert isinstance(obj, dict) - accounts = from_list(ConnectorDiscoveryAccount.from_dict, obj.get("accounts")) - availability = ConnectorDiscoveryAvailability(obj.get("availability")) - return ConnectorDiscoveryAccountList(accounts, availability) - - def to_dict(self) -> dict: - result: dict = {} - result["accounts"] = from_list(lambda x: to_class(ConnectorDiscoveryAccount, x), self.accounts) - result["availability"] = to_enum(ConnectorDiscoveryAvailability, self.availability) - return result - -# Experimental: this type is part of an experimental API and may change or be removed. -@dataclass -class ConnectorDiscoveryCapabilities: - """Feature availability.""" - - api_version: int - """API version.""" - - availability: ConnectorDiscoveryAvailability - """Availability.""" - - conditional_cache: bool - """Whether results are cached.""" - - opaque_account_selection: bool - """Whether accounts are selected by opaque ID.""" - - @staticmethod - def from_dict(obj: Any) -> 'ConnectorDiscoveryCapabilities': - assert isinstance(obj, dict) - api_version = from_int(obj.get("apiVersion")) - availability = ConnectorDiscoveryAvailability(obj.get("availability")) - conditional_cache = from_bool(obj.get("conditionalCache")) - opaque_account_selection = from_bool(obj.get("opaqueAccountSelection")) - return ConnectorDiscoveryCapabilities(api_version, availability, conditional_cache, opaque_account_selection) - - def to_dict(self) -> dict: - result: dict = {} - result["apiVersion"] = from_int(self.api_version) - result["availability"] = to_enum(ConnectorDiscoveryAvailability, self.availability) - result["conditionalCache"] = from_bool(self.conditional_cache) - result["opaqueAccountSelection"] = from_bool(self.opaque_account_selection) - return result - # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class CopilotAPITokenAuthInfo: @@ -46211,6 +51133,99 @@ 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 ManagedPermissionOperation: + """Original input operation associated with this verdict.""" + + kind: ManagedPermissionOperationKind + url: str + """Absolute HTTP(S) URL with a host. Invalid URLs reject the entire evaluation batch.""" + + @staticmethod + def from_dict(obj: Any) -> 'ManagedPermissionOperation': + assert isinstance(obj, dict) + kind = ManagedPermissionOperationKind(obj.get("kind")) + url = from_str(obj.get("url")) + return ManagedPermissionOperation(kind, url) + + def to_dict(self) -> dict: + result: dict = {} + result["kind"] = to_enum(ManagedPermissionOperationKind, 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 ManagedPermissionEvaluation: + operation: ManagedPermissionOperation + """Original input operation associated with this verdict.""" + + verdict: ManagedPermissionVerdict + """Managed-policy decision only; never bypasses user permissions or other security controls.""" + + @staticmethod + def from_dict(obj: Any) -> 'ManagedPermissionEvaluation': + assert isinstance(obj, dict) + operation = ManagedPermissionOperation.from_dict(obj.get("operation")) + verdict = ManagedPermissionVerdict(obj.get("verdict")) + return ManagedPermissionEvaluation(operation, verdict) + + def to_dict(self) -> dict: + result: dict = {} + result["operation"] = to_class(ManagedPermissionOperation, self.operation) + result["verdict"] = to_enum(ManagedPermissionVerdict, self.verdict) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class ManagedPermissionsEvaluateRequest: + """Managed policy context and ordered operations for pure, sessionless evaluation.""" + + context: ManagedPermissionsContext + """Trusted, reusable managed permission policy snapshot; no policy is discovered during + evaluation. + """ + operations: list[ManagedPermissionOperation] + """Operations to evaluate, preserving input order and duplicates. An empty batch is valid.""" + + @staticmethod + def from_dict(obj: Any) -> 'ManagedPermissionsEvaluateRequest': + assert isinstance(obj, dict) + context = ManagedPermissionsContext.from_dict(obj.get("context")) + operations = from_list(ManagedPermissionOperation.from_dict, obj.get("operations")) + return ManagedPermissionsEvaluateRequest(context, operations) + + def to_dict(self) -> dict: + result: dict = {} + result["context"] = to_class(ManagedPermissionsContext, self.context) + result["operations"] = from_list(lambda x: to_class(ManagedPermissionOperation, x), self.operations) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class ManagedPermissionsEvaluateResult: + """Ordered managed permission verdicts and the supplied policy's fail-closed posture.""" + + fail_closed: bool + """Managed permission policy could not be determined; every operation is denied.""" + + results: list[ManagedPermissionEvaluation] + """One verdict per input operation, in the same order, including duplicates.""" + + @staticmethod + def from_dict(obj: Any) -> 'ManagedPermissionsEvaluateResult': + assert isinstance(obj, dict) + fail_closed = from_bool(obj.get("failClosed")) + results = from_list(ManagedPermissionEvaluation.from_dict, obj.get("results")) + return ManagedPermissionsEvaluateResult(fail_closed, results) + + def to_dict(self) -> dict: + result: dict = {} + result["failClosed"] = from_bool(self.fail_closed) + result["results"] = from_list(lambda x: to_class(ManagedPermissionEvaluation, x), self.results) + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class ManagedSettingsSchemaResult: @@ -46354,7 +51369,7 @@ def to_dict(self) -> dict: # 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 MCPRegisterExternalClientRequest: +class _MCPRegisterExternalClientRequest: """Registration parameters for an external MCP client.""" client: Any @@ -46374,13 +51389,13 @@ class MCPRegisterExternalClientRequest: """ @staticmethod - def from_dict(obj: Any) -> 'MCPRegisterExternalClientRequest': + def from_dict(obj: Any) -> '_MCPRegisterExternalClientRequest': assert isinstance(obj, dict) client = obj.get("client") config = obj.get("config") server_name = from_str(obj.get("serverName")) transport = obj.get("transport") - return MCPRegisterExternalClientRequest(client, config, server_name, transport) + return _MCPRegisterExternalClientRequest(client, config, server_name, transport) def to_dict(self) -> dict: result: dict = {} @@ -46390,6 +51405,57 @@ 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. +# Internal: this type is an internal SDK API and is not part of the public surface. +@dataclass +class _MCPRegistrySearchRequest: + """Registry search terms, the credential to search under, and the request id that makes the + search cancellable. + """ + auth_info: Any + """The credential the search runs under, carried opaquely. Hosts usually send + credential-free `AuthIdentity`; a `token` identity can be resolved only when it embeds a + token, while `env` and `gh-cli` identities can use a token embedded in the request first. + """ + limit: int + """Maximum number of servers to return.""" + + request_id: int + """Request ID from `mcp.registry.allocateRequestId`. The search refuses unknown, reclaimed, + or canceled IDs and IDs that another search or request already uses. + """ + query: str | None = None + """Free-text query. Omitted or empty asks the registry for its top servers rather than + searching. A value that is not a string is refused. + """ + repository: str | None = None + """Repository used for the policy lookup, as `owner/name`. The policy selects the registry + URL and whether the user token goes to the registry. The registry receives this + repository only when the policy entry lists it as required context. A value that is not a + string is refused. + """ + + @staticmethod + def from_dict(obj: Any) -> '_MCPRegistrySearchRequest': + assert isinstance(obj, dict) + auth_info = obj.get("authInfo") + limit = from_int(obj.get("limit")) + request_id = from_int(obj.get("requestId")) + query = from_union([from_str, from_none], obj.get("query")) + repository = from_union([from_str, from_none], obj.get("repository")) + return _MCPRegistrySearchRequest(auth_info, limit, request_id, query, repository) + + def to_dict(self) -> dict: + result: dict = {} + result["authInfo"] = self.auth_info + result["limit"] = from_int(self.limit) + result["requestId"] = from_int(self.request_id) + if self.query is not None: + result["query"] = from_union([from_str, from_none], self.query) + if self.repository is not None: + result["repository"] = from_union([from_str, from_none], self.repository) + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class MCPServerConfig: @@ -46639,7 +51705,7 @@ def to_dict(self) -> dict: # 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: +class _MCPReloadConfig: """In-process MCP reload configuration.""" mcp_servers: dict[str, MCPServerConfig] @@ -46659,7 +51725,7 @@ class MCPReloadConfig: use_cached_tool_snapshots: bool | None = None @staticmethod - def from_dict(obj: Any) -> 'MCPReloadConfig': + 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")) @@ -46674,7 +51740,7 @@ def from_dict(obj: Any) -> 'MCPReloadConfig': 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) + 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 = {} @@ -46943,13 +52009,13 @@ def to_dict(self) -> dict: # 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: +class _MCPServerConfigMemory: """In-process MCP server configuration used by embedded SDK clients.""" server_instance: Any """In-process MCP server instance. This value cannot cross a JSON-RPC boundary.""" - type: MCPServerConfigMemoryType + type: _MCPServerConfigMemoryType config_warnings: list[str] | None = None """Configuration warnings recorded while loading the server.""" @@ -47013,10 +52079,10 @@ class MCPServerConfigMemory: """Tools to include. Defaults to all tools if not specified.""" @staticmethod - def from_dict(obj: Any) -> 'MCPServerConfigMemory': + def from_dict(obj: Any) -> '_MCPServerConfigMemory': assert isinstance(obj, dict) server_instance = obj.get("serverInstance") - type = MCPServerConfigMemoryType(obj.get("type")) + 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")) @@ -47037,12 +52103,12 @@ def from_dict(obj: Any) -> 'MCPServerConfigMemory': 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, slow_connection_threshold_ms, source, source_path, source_plugin, source_plugin_spec, source_plugin_version, timeout, 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, slow_connection_threshold_ms, 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) + 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: @@ -48054,6 +53120,13 @@ class SessionOpenOptions: agent_context: str | None = None """Runtime context discriminator for agent filtering.""" + agent_directories: list[str] | None = None + """Explicit discovery-only agent roots, separate from customAgents and filesystem grants. + Available without enableConfigDiscovery, subject to managed/source disables. Relative SDK + paths resolve once when supplied against the request's effective workingDirectory; + retained roots do not move on cwd changes. Null and omission are equivalent; [] clears + only the explicit SDK contribution. + """ allow_all_mcp_server_instructions: bool | None = None """Whether to include instructions from every MCP server in the system prompt instead of only allowlisted servers. @@ -48154,8 +53227,8 @@ class SessionOpenOptions: """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. + """Whether skill loading is enabled. When omitted, explicit SDK skill roots or an SDK skill + provider enable skills independently of enableConfigDiscovery. """ enable_streaming: bool | None = None """Whether model responses stream as delta events.""" @@ -48200,6 +53273,14 @@ class SessionOpenOptions: sessions.open flows reject it because they cannot safely pre-register the callback handler. """ + hook_directories: list[str] | None = None + """Explicit discovery-only file-hook roots, separate from callback hooks and filesystem + grants. Available without enableConfigDiscovery or enableFileHooks, which controls + repository-origin hooks only; hook/managed disables still apply. Relative SDK paths + resolve once when supplied against the request's effective workingDirectory; retained + roots do not move on cwd changes. Null and omission are equivalent; [] clears only the + explicit SDK contribution. + """ ignored_skills_locations: list[str] | None = None """Skill scan directories and descendants excluded from discovery. Supports `~`-relative paths. @@ -48217,9 +53298,23 @@ class SessionOpenOptions: installed_plugins: list[InstalledPlugin] | None = None """Installed plugins visible to the session.""" + instruction_directories: list[str] | None = None + """Explicit discovery-only instruction roots using existing SDK instruction formats, not + filesystem grants. Available without enableConfigDiscovery, subject to + instruction/managed disables. Relative SDK paths resolve once when supplied against the + request's effective workingDirectory; retained roots do not move on cwd changes. Null and + omission are equivalent; [] clears only the explicit SDK contribution. + """ integration_id: str | None = None """Stable integration identifier for analytics.""" + interactive_user: bool | None = None + """Declares that a human drives this session's conversation, enabling user-message analysis + (sentiment, task completion criteria, and task analytics) for user-authored messages. + Automated messages remain excluded. Defaults to false for SDK sessions. Applied when the + session is created or loaded from storage; ignored when resuming a session that is + already active, and for cloud sessions. + """ is_experimental_mode: bool | None = None """Whether experimental behavior is enabled.""" @@ -48317,8 +53412,12 @@ class SessionOpenOptions: """PowerShell process flags applied to built-in and user-requested shell commands.""" skill_directories: list[str] | None = None - """Additional directories to search for skills.""" - + """Explicit discovery-only skill roots, not filesystem grants. Available without + enableConfigDiscovery, subject to skill/managed disables. Relative SDK paths resolve once + when supplied against the request's effective workingDirectory; retained roots do not + move on cwd changes. Null and omission are equivalent; [] clears only the explicit SDK + contribution. + """ skip_custom_instructions: bool | None = None """Whether to skip custom instruction sources.""" @@ -48340,6 +53439,7 @@ def from_dict(obj: Any) -> 'SessionOpenOptions': additional_content_exclusion_policies = from_union([lambda x: from_list(SessionOpenOptionsAdditionalContentExclusionPolicy.from_dict, x), from_none], obj.get("additionalContentExclusionPolicies")) additional_directories = from_union([lambda x: from_list(from_str, x), from_none], obj.get("additionalDirectories")) agent_context = from_union([from_str, from_none], obj.get("agentContext")) + agent_directories = from_union([lambda x: from_list(from_str, x), from_none], obj.get("agentDirectories")) allow_all_mcp_server_instructions = from_union([from_bool, from_none], obj.get("allowAllMcpServerInstructions")) ask_user_disabled = from_union([from_bool, from_none], obj.get("askUserDisabled")) auth_client_id_metadata_url = from_union([from_str, from_none], obj.get("authClientIdMetadataUrl")) @@ -48375,11 +53475,14 @@ def from_dict(obj: Any) -> 'SessionOpenOptions': 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")) + hook_directories = from_union([lambda x: from_list(from_str, x), from_none], obj.get("hookDirectories")) ignored_skills_locations = from_union([lambda x: from_list(from_str, x), from_none], obj.get("ignoredSkillsLocations")) 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")) + instruction_directories = from_union([lambda x: from_list(from_str, x), from_none], obj.get("instructionDirectories")) integration_id = from_union([from_str, from_none], obj.get("integrationId")) + interactive_user = from_union([from_bool, from_none], obj.get("interactiveUser")) 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")) @@ -48414,7 +53517,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_client_id_metadata_url, auth_info, auto_tier_is_explicit, 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, enforce_managed_model_defaults, env_value_mode, events_log_directory, events_log_includes_subagents, excluded_builtin_agents, excluded_tools, exp_assignments, feature_flags, has_skill_provider, ignored_skills_locations, included_builtin_agents, included_builtin_skills, installed_plugins, integration_id, is_experimental_mode, log_interactive_shells, lsp_client_name, managed_mcp_servers, managed_settings, max_inline_binary_bytes, memory, model, model_capabilities_overrides, models, name, provider, providers, reasoning_effort, reasoning_summary, refresh_custom_instructions, 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) + return SessionOpenOptions(additional_content_exclusion_policies, additional_directories, agent_context, agent_directories, allow_all_mcp_server_instructions, ask_user_disabled, auth_client_id_metadata_url, auth_info, auto_tier_is_explicit, 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, enforce_managed_model_defaults, env_value_mode, events_log_directory, events_log_includes_subagents, excluded_builtin_agents, excluded_tools, exp_assignments, feature_flags, has_skill_provider, hook_directories, ignored_skills_locations, included_builtin_agents, included_builtin_skills, installed_plugins, instruction_directories, integration_id, interactive_user, is_experimental_mode, log_interactive_shells, lsp_client_name, managed_mcp_servers, managed_settings, max_inline_binary_bytes, memory, model, model_capabilities_overrides, models, name, provider, providers, reasoning_effort, reasoning_summary, refresh_custom_instructions, 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 = {} @@ -48424,6 +53527,8 @@ def to_dict(self) -> dict: result["additionalDirectories"] = from_union([lambda x: from_list(from_str, x), from_none], self.additional_directories) if self.agent_context is not None: result["agentContext"] = from_union([from_str, from_none], self.agent_context) + if self.agent_directories is not None: + result["agentDirectories"] = from_union([lambda x: from_list(from_str, x), from_none], self.agent_directories) if self.allow_all_mcp_server_instructions is not None: result["allowAllMcpServerInstructions"] = from_union([from_bool, from_none], self.allow_all_mcp_server_instructions) if self.ask_user_disabled is not None: @@ -48494,6 +53599,8 @@ def to_dict(self) -> dict: 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.hook_directories is not None: + result["hookDirectories"] = from_union([lambda x: from_list(from_str, x), from_none], self.hook_directories) if self.ignored_skills_locations is not None: result["ignoredSkillsLocations"] = from_union([lambda x: from_list(from_str, x), from_none], self.ignored_skills_locations) if self.included_builtin_agents is not None: @@ -48502,8 +53609,12 @@ def to_dict(self) -> dict: 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.instruction_directories is not None: + result["instructionDirectories"] = from_union([lambda x: from_list(from_str, x), from_none], self.instruction_directories) if self.integration_id is not None: result["integrationId"] = from_union([from_str, from_none], self.integration_id) + if self.interactive_user is not None: + result["interactiveUser"] = from_union([from_bool, from_none], self.interactive_user) if self.is_experimental_mode is not None: result["isExperimentalMode"] = from_union([from_bool, from_none], self.is_experimental_mode) if self.log_interactive_shells is not None: @@ -49017,6 +54128,40 @@ 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. +# Internal: this type is an internal SDK API and is not part of the public surface. +class _SettingsEditStatus(Enum): + """Changed, unchanged or invalid existing file. Rejected input, validation failures and I/O + errors remain RPC errors. + + Whether one atomic settings edit changed a file. + """ + CHANGED = "changed" + INVALID = "invalid" + UNCHANGED = "unchanged" + +# 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 _SettingsEditResult: + """Outcome of an atomic user or repository settings edit.""" + + status: _SettingsEditStatus + """Changed, unchanged or invalid existing file. Rejected input, validation failures and I/O + errors remain RPC errors. + """ + + @staticmethod + def from_dict(obj: Any) -> '_SettingsEditResult': + assert isinstance(obj, dict) + status = _SettingsEditStatus(obj.get("status")) + return _SettingsEditResult(status) + + def to_dict(self) -> dict: + result: dict = {} + result["status"] = to_enum(_SettingsEditStatus, self.status) + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class ShellExecRequest: @@ -49674,6 +54819,12 @@ def to_dict(self) -> dict: result["terminal"] = from_union([lambda x: to_class(WorkflowRunTerminal, x), from_none], self.terminal) return result +def rpc_from_dict(s: Any) -> RPC: + return RPC.from_dict(s) + +def rpc_to_dict(x: RPC) -> Any: + return to_class(RPC, x) + @dataclass class RPC: abort_request: AbortRequest @@ -49681,10 +54832,17 @@ class RPC: accepted_enqueue_command_result: AcceptedEnqueueCommandResult account_all_users: AccountAllUsers account_auth_info: AccountAuthInfo + account_describe_auth_info_request: _AccountDescribeAuthInfoRequest + account_describe_auth_info_result: _AccountDescribeAuthInfoResult account_get_all_users_result: list[AccountAllUsers] account_get_current_auth_result: AccountGetCurrentAuthResult + account_get_gist_support_request: _AccountGetGistSupportRequest + account_get_gist_support_result: _AccountGetGistSupportResult account_get_quota_request: AccountGetQuotaRequest account_get_quota_result: AccountGetQuotaResult + account_get_token_env_var_result: _AccountGetTokenEnvVarResult + account_get_web_url_request: _AccountGetWebURLRequest + account_get_web_url_result: _AccountGetWebURLResult account_kind: AccountKind account_login_request: AccountLoginRequest account_login_result: AccountLoginResult @@ -49800,7 +54958,17 @@ class RPC: catalog_agent_plugin_candidate_kind: CatalogAgentPluginCandidateKind catalog_agent_plugin_candidate_provenance: CatalogAgentPluginCandidateProvenance catalog_agent_plugin_compatibility_tag: CatalogAgentPluginCompatibilityTag + catalog_agent_plugin_component: CatalogAgentPluginComponent + catalog_agent_plugin_installability: CatalogAgentPluginInstallability + catalog_agent_plugin_install_plan: CatalogAgentPluginInstallPlan + catalog_agent_plugin_install_source: CatalogAgentPluginInstallSource + catalog_agent_plugin_marketplace_source: CatalogAgentPluginMarketplaceSource + catalog_agent_plugin_marketplace_source_kind: CatalogAgentPluginMarketplaceSourceKind + catalog_agent_plugin_materialization_source: CatalogAgentPluginMaterializationSource + catalog_agent_plugin_materialization_source_kind: CatalogAgentPluginMaterializationSourceKind catalog_agent_plugin_media_type: CatalogAgentPluginMediaType + catalog_agent_plugin_repository_source: CatalogAgentPluginRepositorySource + catalog_agent_plugin_repository_source_kind: CatalogAgentPluginRepositorySourceKind catalog_ai_skill_candidate: CatalogAISkillCandidate catalog_ai_skill_candidate_kind: CatalogAISkillCandidateKind catalog_ai_skill_candidate_provenance: CatalogAISkillCandidateProvenance @@ -49825,6 +54993,7 @@ class RPC: catalog_invalid_request_field: CatalogInvalidRequestField catalog_malformed_card_error: CatalogMalformedCardError catalog_malformed_card_reason: CatalogMalformedCardReason + catalog_materialization_reference: CatalogMaterializationReference catalog_mcp_server_candidate: CatalogMCPServerCandidate catalog_mcp_server_candidate_kind: CatalogMCPServerCandidateKind catalog_mcp_server_candidate_provenance: CatalogMCPServerCandidateProvenance @@ -49940,6 +55109,8 @@ class RPC: content_exclusion_check_paths_request: ContentExclusionCheckPathsRequest content_exclusion_check_paths_result: ContentExclusionCheckPathsResult content_exclusion_path_check: ContentExclusionPathCheck + content_exclusion_validate_policies_request: ContentExclusionValidatePoliciesRequest + content_exclusion_validate_policies_result: ContentExclusionValidatePoliciesResult content_filter_mode: ContentFilterMode context_heaviest_message: ContextHeaviestMessage copilot_api_token_auth_info: CopilotAPITokenAuthInfo @@ -50035,12 +55206,21 @@ class RPC: external_tool_text_result_for_llm_content_shell_exit: ExternalToolTextResultForLlmContentShellExit external_tool_text_result_for_llm_content_terminal: ExternalToolTextResultForLlmContentTerminal external_tool_text_result_for_llm_content_text: ExternalToolTextResultForLlmContentText + feature_flag_availability: _FeatureFlagAvailability + feature_flag_metadata: _FeatureFlagMetadata + feature_flags_metadata_result: _FeatureFlagsMetadataResult + feature_flags_resolve_request: _FeatureFlagsResolveRequest + feature_flags_resolve_result: dict[str, bool] + feature_flags_resolve_startup_gates_request: _FeatureFlagsResolveStartupGatesRequest + feature_flags_resolve_startup_gates_result: dict[str, bool] filter_mapping: dict[str, ContentFilterMode] | ContentFilterMode fleet_start_request: FleetStartRequest fleet_start_result: FleetStartResult folder_trust_add_params: FolderTrustAddParams folder_trust_check_params: FolderTrustCheckParams folder_trust_check_result: FolderTrustCheckResult + folder_trust_is_trusted_params: _FolderTrustIsTrustedParams + force_login_orgs_decision: _ForceLoginOrgsDecision gh_cli_auth_info: GhCLIAuthInfo git_current_branch_remote_result: _GitCurrentBranchRemoteResult git_cwd_request: _GitCwdRequest @@ -50050,7 +55230,7 @@ class RPC: git_hub_owners_cancel_result: _GitHubOwnersCancelResult git_hub_owners_list_request: _GitHubOwnersListRequest git_hub_owners_list_result: _GitHubOwnersListResult - git_hub_owners_request_id_result: GitHubOwnersRequestIDResult + git_hub_owners_request_id_result: _GitHubOwnersRequestIDResult git_hub_repository_at_path_request: _GitHubRepositoryAtPathRequest git_hub_repository_at_path_result: _GitHubRepositoryAtPathResult git_hub_repository_identity: GitHubRepositoryIdentity @@ -50136,9 +55316,11 @@ class RPC: instruction_discovery_path_kind: DebugCollectLogsEntryKind instruction_discovery_path_list: InstructionDiscoveryPathList instruction_discovery_path_location: InstructionLocation + instructions_discover_repo_sources_request: _InstructionsDiscoverRepoSourcesRequest instructions_discover_request: InstructionsDiscoverRequest instructions_get_discovery_paths_request: InstructionsGetDiscoveryPathsRequest instructions_get_sources_result: InstructionsGetSourcesResult + instructions_init_prompt_result: _InstructionsInitPromptResult instruction_source: InstructionSource instruction_source_location: InstructionLocation instruction_source_type: InstructionSourceType @@ -50158,26 +55340,60 @@ class RPC: llm_inference_http_response_start_result: LlmInferenceHTTPResponseStartResult llm_inference_set_provider_result: LlmInferenceSetProviderResult local_session_metadata_value: LocalSessionMetadataValue + location_permissions_cleanup_result: _LocationPermissionsCleanupResult logged_in_user: _LoggedInUser login_provider_kind: LoginProviderKind log_request: LogRequest log_result: LogResult lsp_initialize_request: LspInitializeRequest managed_mcp_server_config: ManagedMCPServerConfig + managed_permission_evaluation: ManagedPermissionEvaluation + managed_permission_operation: ManagedPermissionOperation + managed_permission_operation_kind: ManagedPermissionOperationKind + managed_permissions_evaluate_request: ManagedPermissionsEvaluateRequest + managed_permissions_evaluate_result: ManagedPermissionsEvaluateResult + managed_permission_verdict: ManagedPermissionVerdict + managed_plugin_retry_entry: ManagedPluginRetryEntry + managed_plugin_retry_status: ManagedPluginRetryStatus managed_setting_meta: ManagedSettingMeta + managed_settings_apply_controls_params: _ManagedSettingsApplyControlsParams + managed_settings_apply_controls_result: _ManagedSettingsApplyControlsResult + managed_settings_bypass_disable_source: _ManagedSettingsBypassDisableSource + managed_settings_bypass_policy_resolution: _ManagedSettingsBypassPolicyResolution managed_settings_channel: ManagedSettingsChannel managed_settings_compose_layer: ManagedSettingsComposeLayer managed_settings_compose_request: ManagedSettingsComposeRequest managed_settings_compose_result: ManagedSettingsComposeResult managed_settings_diagnostic: ManagedSettingsDiagnostic managed_settings_diagnostic_severity: ManagedSettingsDiagnosticSeverity + managed_settings_ingest_params: _ManagedSettingsIngestParams + managed_settings_ingest_result: _ManagedSettingsIngestResult managed_settings_layer: ManagedSettingsLayer + managed_settings_load_remote_control_params: _ManagedSettingsLoadRemoteControlParams + managed_settings_load_remote_control_result: _ManagedSettingsLoadRemoteControlResult + managed_settings_log_level: _ManagedSettingsLogLevel + managed_settings_log_message: _ManagedSettingsLogMessage managed_settings_meta: ManagedSettingsMeta + managed_settings_parse_client_value_params: _ManagedSettingsParseClientValueParams + managed_settings_parse_client_value_result: _ManagedSettingsParseClientValueResult + managed_settings_policy_helper_authority: _ManagedSettingsPolicyHelperAuthority + managed_settings_policy_helper_reason: _ManagedSettingsPolicyHelperReason + managed_settings_read_device_result: _ManagedSettingsReadDeviceResult managed_settings_read_result: ManagedSettingsReadResult + managed_settings_resolve_bypass_disable_transition_params: _ManagedSettingsResolveBypassDisableTransitionParams + managed_settings_resolve_bypass_disable_transition_result: _ManagedSettingsResolveBypassDisableTransitionResult managed_settings_resolved_data: ManagedSettingsResolvedData + managed_settings_resolve_device_force_login_orgs_gate_params: _ManagedSettingsResolveDeviceForceLoginOrgsGateParams + managed_settings_resolve_effective_value_params: _ManagedSettingsResolveEffectiveValueParams + managed_settings_resolve_effective_value_result: _ManagedSettingsResolveEffectiveValueResult managed_settings_resolve_request: ManagedSettingsResolveRequest managed_settings_resolve_result: ManagedSettingsResolveResult + managed_settings_resolve_startup_policy_params: _ManagedSettingsResolveStartupPolicyParams + managed_settings_resolve_startup_policy_result: _ManagedSettingsResolveStartupPolicyResult + managed_settings_retain_policy_params: _ManagedSettingsRetainPolicyParams managed_settings_schema_result: ManagedSettingsSchemaResult + managed_settings_server_policy_cache_mode: _ManagedSettingsServerPolicyCacheMode + managed_settings_server_policy_resolution: _ManagedSettingsServerPolicyResolution managed_settings_validate_request: ManagedSettingsValidateRequest managed_settings_validate_result: ManagedSettingsValidateResult managed_settings_values: ManagedSettingsValues @@ -50225,7 +55441,7 @@ class RPC: mcp_configured_server: MCPConfiguredServer mcp_configured_server_list: MCPConfiguredServerList mcp_configured_server_state: MCPConfiguredServerState - mcp_configure_git_hub_request: MCPConfigureGitHubRequest + mcp_configure_git_hub_request: _MCPConfigureGitHubRequest mcp_configure_git_hub_result: MCPConfigureGitHubResult mcp_diagnostic_details: MCPDiagnosticDetails mcp_diagnostic_direction: MCPDiagnosticDirection @@ -50263,6 +55479,7 @@ class RPC: mcp_install_plan: MCPInstallPlan mcp_is_server_running_request: MCPIsServerRunningRequest mcp_is_server_running_result: MCPIsServerRunningResult + mcp_list_builtin_server_names_result: _MCPListBuiltinServerNamesResult mcp_list_tools_request: MCPListToolsRequest mcp_list_tools_result: MCPListToolsResult mcp_oauth_authentication_state_changed_request: MCPOauthAuthenticationStateChangedRequest @@ -50329,9 +55546,14 @@ class RPC: mcp_prompts_get_result: MCPPromptsGetResult mcp_prompts_list_request: MCPPromptsListRequest mcp_prompts_list_result: MCPPromptsListResult - mcp_register_external_client_request: MCPRegisterExternalClientRequest - mcp_reload_config: MCPReloadConfig - mcp_reload_with_config_request: MCPReloadWithConfigRequest + mcp_register_external_client_request: _MCPRegisterExternalClientRequest + mcp_registry_cancel_request: _MCPRegistryCancelRequest + mcp_registry_cancel_result: _MCPRegistryCancelResult + mcp_registry_request_id_result: _MCPRegistryRequestIDResult + mcp_registry_search_request: _MCPRegistrySearchRequest + mcp_registry_search_result: _MCPRegistrySearchResult + mcp_reload_config: _MCPReloadConfig + mcp_reload_with_config_request: _MCPReloadWithConfigRequest mcp_remove_git_hub_result: MCPRemoveGitHubResult mcp_resource: MCPResource mcp_resource_annotations: MCPResourceAnnotations @@ -50364,8 +55586,8 @@ class RPC: 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_memory: _MCPServerConfigMemory + mcp_server_config_memory_type: _MCPServerConfigMemoryType mcp_server_config_stdio: MCPServerConfigStdio mcp_server_config_stdio_type: MCPServerConfigStdioType mcp_server_failure_info: MCPServerFailureInfo @@ -50375,6 +55597,8 @@ class RPC: mcp_set_env_value_mode_details: MCPSetEnvValueModeDetails mcp_set_env_value_mode_params: MCPSetEnvValueModeParams mcp_set_env_value_mode_result: MCPSetEnvValueModeResult + mcp_should_exclude_git_hub_tools_request: _MCPShouldExcludeGitHubToolsRequest + mcp_should_exclude_git_hub_tools_result: _MCPShouldExcludeGitHubToolsResult mcp_source_file: MCPSourceFile mcp_source_plugin: MCPSourcePlugin mcp_source_ref: MCPSourceRef @@ -50386,7 +55610,7 @@ class RPC: mcp_tool_ui: MCPToolUI mcp_tool_ui_visibility: MCPToolUIVisibility mcp_uninstall_plan: MCPUninstallPlan - mcp_unregister_external_client_request: MCPUnregisterExternalClientRequest + mcp_unregister_external_client_request: _MCPUnregisterExternalClientRequest memory_configuration: MemoryConfiguration metadata_context_attribution_result: MetadataContextAttributionResult metadata_context_heaviest_messages_request: MetadataContextHeaviestMessagesRequest @@ -50422,6 +55646,8 @@ class RPC: model_capabilities_supports: ModelCapabilitiesSupports model_clear_startup_seed_request: _ModelClearStartupSeedRequest model_clear_startup_seed_result: _ModelClearStartupSeedResult + model_get_reasoning_summaries_request: ModelGetReasoningSummariesRequest + model_get_reasoning_summaries_result: ModelGetReasoningSummariesResult model_list: ModelList model_list_request: Any model_message: ModelMessage @@ -50557,6 +55783,8 @@ class RPC: permissions_configure_additional_content_exclusion_policy_scope: AdditionalContentExclusionPolicyScope permissions_configure_params: PermissionsConfigureParams permissions_configure_result: PermissionsConfigureResult + permissions_evaluate_managed_policy_params: _PermissionsEvaluateManagedPolicyParams + permissions_evaluate_managed_policy_result: PermissionsEvaluateManagedPolicyResult permissions_folder_trust_add_trusted_result: PermissionsFolderTrustAddTrustedResult permissions_get_mode_request: PermissionsGetModeRequest permissions_get_mode_result: PermissionsGetModeResult @@ -50593,6 +55821,8 @@ class RPC: permissions_urls_set_unrestricted_mode_result: PermissionsUrlsSetUnrestrictedModeResult permission_urls_config: PermissionUrlsConfig permission_urls_set_unrestricted_mode_params: PermissionUrlsSetUnrestrictedModeParams + persistence_write_raw_json_locked_request: PersistenceWriteRawJSONLockedRequest + persistence_write_raw_json_locked_result: PersistenceWriteRawJSONLockedResult ping_request: PingRequest ping_result: PingResult plan_read_result: PlanReadResult @@ -50737,7 +55967,17 @@ class RPC: remote_session_metadata_value: RemoteSessionMetadataValue remote_session_mode: RemoteSessionMode remote_session_repository: RemoteSessionRepository + repo_settings_edit_request: _RepoSettingsEditRequest + repo_settings_edit_scope: _RepoSettingsEditScope + repo_settings_read_raw_request: _RepoSettingsReadRawRequest + repo_settings_read_raw_result: _RepoSettingsReadRawResult + repo_settings_write_all_request: _RepoSettingsWriteAllRequest + repo_settings_write_key_request: _RepoSettingsWriteKeyRequest response_format: ResponseFormat + sandbox_apply_defaults_request: SandboxApplyDefaultsRequest + sandbox_check_enforcement_command_result: bool + sandbox_check_platform_support_result: bool + sandbox_check_windows_host_loopback_support_result: bool sandbox_config: SandboxConfig sandbox_config_auth: SandboxConfigAuth sandbox_config_source: SandboxConfigSource @@ -50749,20 +55989,44 @@ class RPC: sandbox_config_user_policy_network_proxy: SandboxConfigUserPolicyNetworkProxy sandbox_config_user_policy_seatbelt: SandboxConfigUserPolicySeatbelt sandbox_credentials_config: SandboxCredentialsConfig + sandbox_credential_suggestion: SandboxCredentialSuggestion + sandbox_credential_suggestions_result: SandboxCredentialSuggestionsResult sandbox_disable_for_session_request: SandboxDisableForSessionRequest sandbox_disable_for_session_result: SandboxDisableForSessionResult + sandbox_docs_url_result: SandboxDocsURLResult + sandbox_effective_for_optional_params: _SandboxEffectiveForOptionalParams + sandbox_effective_for_optional_result: _SandboxEffectiveForOptionalResult + sandbox_enforcement_command_allowed_params: _SandboxEnforcementCommandAllowedParams sandbox_enforcement_status: SandboxEnforcementStatus + sandbox_filter_picker_candidates_params: _SandboxFilterPickerCandidatesParams + sandbox_filter_picker_candidates_result: _SandboxFilterPickerCandidatesResult + sandbox_get_unsupported_message_result: str + sandbox_get_unsupported_setting_disabled_notice_result: str sandbox_grant_path_for_request_request: SandboxGrantPathForRequestRequest sandbox_grant_path_for_request_result: SandboxGrantPathForRequestResult sandbox_host_capability: SandboxHostCapability sandbox_host_capability_name: str sandbox_host_support: SandboxHostSupport sandbox_masked_env_var: SandboxMaskedEnvVar + sandbox_picker_candidate: _SandboxPickerCandidate + sandbox_prepare_picker_policy_params: _SandboxPreparePickerPolicyParams + sandbox_prepare_picker_policy_result: _SandboxPreparePickerPolicyResult + sandbox_proxy_ca_check_installation_support_result: bool + sandbox_proxy_ca_check_relevance_result: bool sandbox_proxy_ca_create_result: SandboxProxyCACreateResult sandbox_proxy_ca_request: SandboxProxyCARequest sandbox_proxy_ca_state: SandboxProxyCAState sandbox_proxy_ca_status: SandboxProxyCAStatus + sandbox_proxy_secret_get_result: SandboxProxySecretGetResult + sandbox_proxy_secret_location: SandboxProxySecretLocation + sandbox_proxy_secret_save_request: SandboxProxySecretSaveRequest + sandbox_proxy_secret_save_result: SandboxProxySecretSaveResult + sandbox_resolve_floored_config_params: _SandboxResolveFlooredConfigParams + sandbox_resolve_floored_config_result: SandboxResolveFlooredConfigResult sandbox_session_change: SandboxSessionChange + sandbox_settings_apply_defaults_result: dict[str, Any] + sandbox_unsupported_startup_notice_params: _SandboxUnsupportedStartupNoticeParams + sandbox_unsupported_startup_notice_result: _SandboxUnsupportedStartupNoticeResult schedule_add_at_request: ScheduleAddAtRequest schedule_add_cron_request: ScheduleAddCronRequest schedule_add_request: ScheduleAddRequest @@ -50893,6 +56157,8 @@ class RPC: session_plugins_install_request: SessionPluginsInstallRequest session_plugins_marketplaces_refresh_request: SessionPluginsMarketplacesRefreshRequest session_plugins_reload_request: SessionPluginsReloadRequest + session_plugins_retry_managed_request: SessionPluginsRetryManagedRequest + session_plugins_retry_managed_result: SessionPluginsRetryManagedResult session_provider_get_endpoint_request: SessionProviderGetEndpointRequest session_prune_result: SessionPruneResult session_quota_delegate_warning: SessionQuotaDelegateWarning @@ -50989,6 +56255,9 @@ class RPC: session_working_directory_context_with_client: SessionWorkingDirectoryContextWithClient settable_auth_info: SettableAuthInfo settable_token_auth_info: SettableTokenAuthInfo + settings_edit_operation: _SettingsEditOperation + settings_edit_result: _SettingsEditResult + settings_edit_status: _SettingsEditStatus shell_cancel_user_requested_request: ShellCancelUserRequestedRequest shell_credentials: ShellCredentials shell_exec_request: ShellExecRequest @@ -51026,6 +56295,7 @@ class RPC: skill_installation_summary: SkillInstallationSummary skill_install_plan: SkillInstallPlan skill_list: SkillList + skill_plan_install_from_reference_request: SkillPlanInstallFromReferenceRequest skill_plan_install_request: SkillPlanInstallRequest skill_plan_uninstall_request: SkillPlanUninstallRequest skill_provider_descriptor: SkillProviderDescriptor @@ -51061,6 +56331,8 @@ class RPC: slash_command_show_dialog_result: SlashCommandShowDialogResult slash_command_text_result: SlashCommandTextResult slash_command_timeline_entry: SlashCommandTimelineEntry + staff_log_level_migration_status: StaffLogLevelMigrationStatus + staff_update_channel_migration_status: StaffUpdateChannelMigrationStatus subagent_settings_entry: SubagentSettingsEntry subagent_settings_entry_context_tier: SubagentSettingsEntryContextTier system_message_block: SystemMessageBlock @@ -51175,8 +56447,55 @@ class RPC: user_auth_info: UserAuthInfo user_requested_shell_command_result: UserRequestedShellCommandResult user_setting_metadata: UserSettingMetadata + user_settings_apply_top_key_request: UserSettingsApplyTopKeyRequest + user_settings_apply_top_key_result: UserSettingsApplyTopKeyResult + user_settings_build_runtime_request: _UserSettingsBuildRuntimeRequest + user_settings_build_runtime_result: dict[str, Any] + user_settings_edit_request: _UserSettingsEditRequest + user_settings_get_paths_request: UserSettingsGetPathsRequest + user_settings_get_paths_result: UserSettingsGetPathsResult user_settings_get_result: UserSettingsGetResult + user_settings_governance_keys_result: UserSettingsGovernanceKeysResult + user_settings_host_context: UserSettingsHostContext + user_settings_load_and_merge_repo_request: _UserSettingsLoadAndMergeRepoRequest + user_settings_load_and_merge_repo_result: _UserSettingsLoadAndMergeRepoResult + user_settings_load_for_config_dir_request: _UserSettingsLoadForConfigDirRequest + user_settings_load_for_config_dir_result: dict[str, Any] + user_settings_load_for_edit_result: UserSettingsLoadForEditResult + user_settings_load_repo_request: UserSettingsLoadRepoRequest + user_settings_load_repo_result: UserSettingsLoadRepoResult + user_settings_load_with_warning_result: UserSettingsLoadWithWarningResult + user_settings_merge_managed_and_user_params: _UserSettingsMergeManagedAndUserParams + user_settings_merge_managed_and_user_result: UserSettingsMergeManagedAndUserResult + user_settings_merge_repo_value_params: _UserSettingsMergeRepoValueParams + user_settings_merge_repo_value_result: UserSettingsMergeRepoValueResult + user_settings_migrate_obsolete_on_disk_request: _UserSettingsMigrateObsoleteOnDiskRequest + user_settings_migrate_obsolete_on_disk_result: _UserSettingsMigrateObsoleteOnDiskResult + user_settings_migrate_obsolete_on_disk_status: _UserSettingsMigrateObsoleteOnDiskStatus + user_settings_migrate_staff_log_level_once_request: _UserSettingsMigrateStaffLogLevelOnceRequest + user_settings_migrate_staff_log_level_once_result: _UserSettingsMigrateStaffLogLevelOnceResult + user_settings_migrate_staff_update_channel_once_result: _UserSettingsMigrateStaffUpdateChannelOnceResult + user_settings_normalize_request: UserSettingsNormalizeRequest + user_settings_normalize_result: UserSettingsNormalizeResult + user_settings_path_result: UserSettingsPathResult + user_settings_problem: UserSettingsProblem + user_settings_problem_location: UserSettingsProblemLocation + user_settings_problem_source: UserSettingsProblemSource + user_settings_problems_request: UserSettingsProblemsRequest + user_settings_problems_result: UserSettingsProblemsResult + user_settings_reset_staff_model_selection_once_request: _UserSettingsResetStaffModelSelectionOnceRequest + user_settings_reset_staff_model_selection_once_result: _UserSettingsResetStaffModelSelectionOnceResult + user_settings_set_hook_disabled_request: _UserSettingsSetHookDisabledRequest + user_settings_set_hook_disabled_result: _UserSettingsSetHookDisabledResult user_settings_set_request: UserSettingsSetRequest + user_settings_unknown_keys_warning_request: _UserSettingsUnknownKeysWarningRequest + user_settings_unknown_keys_warning_result: _UserSettingsUnknownKeysWarningResult + user_settings_update_request: _UserSettingsUpdateRequest + user_settings_update_result: _UserSettingsUpdateResult + user_settings_validate_request: UserSettingsValidateRequest + user_settings_validate_result: UserSettingsValidateResult + user_settings_validation_issue: UserSettingsValidationIssue + user_settings_write_all_request: UserSettingsWriteAllRequest visibility_get_result: VisibilityGetResult visibility_set_request: VisibilitySetRequest visibility_set_result: VisibilitySetResult @@ -51277,10 +56596,17 @@ def from_dict(obj: Any) -> 'RPC': accepted_enqueue_command_result = AcceptedEnqueueCommandResult.from_dict(obj.get("AcceptedEnqueueCommandResult")) account_all_users = AccountAllUsers.from_dict(obj.get("AccountAllUsers")) account_auth_info = AccountAuthInfo.from_dict(obj.get("AccountAuthInfo")) + account_describe_auth_info_request = _AccountDescribeAuthInfoRequest.from_dict(obj.get("AccountDescribeAuthInfoRequest")) + account_describe_auth_info_result = _AccountDescribeAuthInfoResult.from_dict(obj.get("AccountDescribeAuthInfoResult")) account_get_all_users_result = from_list(AccountAllUsers.from_dict, obj.get("AccountGetAllUsersResult")) account_get_current_auth_result = AccountGetCurrentAuthResult.from_dict(obj.get("AccountGetCurrentAuthResult")) + account_get_gist_support_request = _AccountGetGistSupportRequest.from_dict(obj.get("AccountGetGistSupportRequest")) + account_get_gist_support_result = _AccountGetGistSupportResult.from_dict(obj.get("AccountGetGistSupportResult")) account_get_quota_request = AccountGetQuotaRequest.from_dict(obj.get("AccountGetQuotaRequest")) account_get_quota_result = AccountGetQuotaResult.from_dict(obj.get("AccountGetQuotaResult")) + account_get_token_env_var_result = _AccountGetTokenEnvVarResult.from_dict(obj.get("AccountGetTokenEnvVarResult")) + account_get_web_url_request = _AccountGetWebURLRequest.from_dict(obj.get("AccountGetWebUrlRequest")) + account_get_web_url_result = _AccountGetWebURLResult.from_dict(obj.get("AccountGetWebUrlResult")) account_kind = AccountKind(obj.get("AccountKind")) account_login_request = AccountLoginRequest.from_dict(obj.get("AccountLoginRequest")) account_login_result = AccountLoginResult.from_dict(obj.get("AccountLoginResult")) @@ -51396,7 +56722,17 @@ def from_dict(obj: Any) -> 'RPC': catalog_agent_plugin_candidate_kind = CatalogAgentPluginCandidateKind(obj.get("CatalogAgentPluginCandidateKind")) catalog_agent_plugin_candidate_provenance = CatalogAgentPluginCandidateProvenance.from_dict(obj.get("CatalogAgentPluginCandidateProvenance")) catalog_agent_plugin_compatibility_tag = CatalogAgentPluginCompatibilityTag(obj.get("CatalogAgentPluginCompatibilityTag")) + catalog_agent_plugin_component = CatalogAgentPluginComponent(obj.get("CatalogAgentPluginComponent")) + catalog_agent_plugin_installability = CatalogAgentPluginInstallability(obj.get("CatalogAgentPluginInstallability")) + catalog_agent_plugin_install_plan = CatalogAgentPluginInstallPlan.from_dict(obj.get("CatalogAgentPluginInstallPlan")) + catalog_agent_plugin_install_source = _load_CatalogAgentPluginInstallSource(obj.get("CatalogAgentPluginInstallSource")) + catalog_agent_plugin_marketplace_source = CatalogAgentPluginMarketplaceSource.from_dict(obj.get("CatalogAgentPluginMarketplaceSource")) + catalog_agent_plugin_marketplace_source_kind = CatalogAgentPluginMarketplaceSourceKind(obj.get("CatalogAgentPluginMarketplaceSourceKind")) + catalog_agent_plugin_materialization_source = CatalogAgentPluginMaterializationSource.from_dict(obj.get("CatalogAgentPluginMaterializationSource")) + catalog_agent_plugin_materialization_source_kind = CatalogAgentPluginMaterializationSourceKind(obj.get("CatalogAgentPluginMaterializationSourceKind")) catalog_agent_plugin_media_type = CatalogAgentPluginMediaType(obj.get("CatalogAgentPluginMediaType")) + catalog_agent_plugin_repository_source = CatalogAgentPluginRepositorySource.from_dict(obj.get("CatalogAgentPluginRepositorySource")) + catalog_agent_plugin_repository_source_kind = CatalogAgentPluginRepositorySourceKind(obj.get("CatalogAgentPluginRepositorySourceKind")) catalog_ai_skill_candidate = CatalogAISkillCandidate.from_dict(obj.get("CatalogAiSkillCandidate")) catalog_ai_skill_candidate_kind = CatalogAISkillCandidateKind(obj.get("CatalogAiSkillCandidateKind")) catalog_ai_skill_candidate_provenance = CatalogAISkillCandidateProvenance.from_dict(obj.get("CatalogAiSkillCandidateProvenance")) @@ -51421,6 +56757,7 @@ def from_dict(obj: Any) -> 'RPC': 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_materialization_reference = CatalogMaterializationReference.from_dict(obj.get("CatalogMaterializationReference")) catalog_mcp_server_candidate = CatalogMCPServerCandidate.from_dict(obj.get("CatalogMcpServerCandidate")) catalog_mcp_server_candidate_kind = CatalogMCPServerCandidateKind(obj.get("CatalogMcpServerCandidateKind")) catalog_mcp_server_candidate_provenance = CatalogMCPServerCandidateProvenance.from_dict(obj.get("CatalogMcpServerCandidateProvenance")) @@ -51536,6 +56873,8 @@ def from_dict(obj: Any) -> 'RPC': content_exclusion_check_paths_request = ContentExclusionCheckPathsRequest.from_dict(obj.get("ContentExclusionCheckPathsRequest")) content_exclusion_check_paths_result = ContentExclusionCheckPathsResult.from_dict(obj.get("ContentExclusionCheckPathsResult")) content_exclusion_path_check = ContentExclusionPathCheck.from_dict(obj.get("ContentExclusionPathCheck")) + content_exclusion_validate_policies_request = ContentExclusionValidatePoliciesRequest.from_dict(obj.get("ContentExclusionValidatePoliciesRequest")) + content_exclusion_validate_policies_result = ContentExclusionValidatePoliciesResult.from_dict(obj.get("ContentExclusionValidatePoliciesResult")) content_filter_mode = ContentFilterMode(obj.get("ContentFilterMode")) context_heaviest_message = ContextHeaviestMessage.from_dict(obj.get("ContextHeaviestMessage")) copilot_api_token_auth_info = CopilotAPITokenAuthInfo.from_dict(obj.get("CopilotApiTokenAuthInfo")) @@ -51631,12 +56970,21 @@ def from_dict(obj: Any) -> 'RPC': external_tool_text_result_for_llm_content_shell_exit = ExternalToolTextResultForLlmContentShellExit.from_dict(obj.get("ExternalToolTextResultForLlmContentShellExit")) external_tool_text_result_for_llm_content_terminal = ExternalToolTextResultForLlmContentTerminal.from_dict(obj.get("ExternalToolTextResultForLlmContentTerminal")) external_tool_text_result_for_llm_content_text = ExternalToolTextResultForLlmContentText.from_dict(obj.get("ExternalToolTextResultForLlmContentText")) + feature_flag_availability = _FeatureFlagAvailability(obj.get("FeatureFlagAvailability")) + feature_flag_metadata = _FeatureFlagMetadata.from_dict(obj.get("FeatureFlagMetadata")) + feature_flags_metadata_result = _FeatureFlagsMetadataResult.from_dict(obj.get("FeatureFlagsMetadataResult")) + feature_flags_resolve_request = _FeatureFlagsResolveRequest.from_dict(obj.get("FeatureFlagsResolveRequest")) + feature_flags_resolve_result = from_dict(from_bool, obj.get("FeatureFlagsResolveResult")) + feature_flags_resolve_startup_gates_request = _FeatureFlagsResolveStartupGatesRequest.from_dict(obj.get("FeatureFlagsResolveStartupGatesRequest")) + feature_flags_resolve_startup_gates_result = from_dict(from_bool, obj.get("FeatureFlagsResolveStartupGatesResult")) 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")) folder_trust_add_params = FolderTrustAddParams.from_dict(obj.get("FolderTrustAddParams")) folder_trust_check_params = FolderTrustCheckParams.from_dict(obj.get("FolderTrustCheckParams")) folder_trust_check_result = FolderTrustCheckResult.from_dict(obj.get("FolderTrustCheckResult")) + folder_trust_is_trusted_params = _FolderTrustIsTrustedParams.from_dict(obj.get("FolderTrustIsTrustedParams")) + force_login_orgs_decision = _ForceLoginOrgsDecision.from_dict(obj.get("ForceLoginOrgsDecision")) gh_cli_auth_info = GhCLIAuthInfo.from_dict(obj.get("GhCliAuthInfo")) git_current_branch_remote_result = _GitCurrentBranchRemoteResult.from_dict(obj.get("GitCurrentBranchRemoteResult")) git_cwd_request = _GitCwdRequest.from_dict(obj.get("GitCwdRequest")) @@ -51646,7 +56994,7 @@ def from_dict(obj: Any) -> 'RPC': git_hub_owners_cancel_result = _GitHubOwnersCancelResult.from_dict(obj.get("GitHubOwnersCancelResult")) git_hub_owners_list_request = _GitHubOwnersListRequest.from_dict(obj.get("GitHubOwnersListRequest")) git_hub_owners_list_result = _GitHubOwnersListResult.from_dict(obj.get("GitHubOwnersListResult")) - git_hub_owners_request_id_result = GitHubOwnersRequestIDResult.from_dict(obj.get("GitHubOwnersRequestIdResult")) + git_hub_owners_request_id_result = _GitHubOwnersRequestIDResult.from_dict(obj.get("GitHubOwnersRequestIdResult")) git_hub_repository_at_path_request = _GitHubRepositoryAtPathRequest.from_dict(obj.get("GitHubRepositoryAtPathRequest")) git_hub_repository_at_path_result = _GitHubRepositoryAtPathResult.from_dict(obj.get("GitHubRepositoryAtPathResult")) git_hub_repository_identity = GitHubRepositoryIdentity.from_dict(obj.get("GitHubRepositoryIdentity")) @@ -51732,9 +57080,11 @@ def from_dict(obj: Any) -> 'RPC': instruction_discovery_path_kind = DebugCollectLogsEntryKind(obj.get("InstructionDiscoveryPathKind")) instruction_discovery_path_list = InstructionDiscoveryPathList.from_dict(obj.get("InstructionDiscoveryPathList")) instruction_discovery_path_location = InstructionLocation(obj.get("InstructionDiscoveryPathLocation")) + instructions_discover_repo_sources_request = _InstructionsDiscoverRepoSourcesRequest.from_dict(obj.get("InstructionsDiscoverRepoSourcesRequest")) instructions_discover_request = InstructionsDiscoverRequest.from_dict(obj.get("InstructionsDiscoverRequest")) instructions_get_discovery_paths_request = InstructionsGetDiscoveryPathsRequest.from_dict(obj.get("InstructionsGetDiscoveryPathsRequest")) instructions_get_sources_result = InstructionsGetSourcesResult.from_dict(obj.get("InstructionsGetSourcesResult")) + instructions_init_prompt_result = _InstructionsInitPromptResult.from_dict(obj.get("InstructionsInitPromptResult")) instruction_source = InstructionSource.from_dict(obj.get("InstructionSource")) instruction_source_location = InstructionLocation(obj.get("InstructionSourceLocation")) instruction_source_type = InstructionSourceType(obj.get("InstructionSourceType")) @@ -51754,26 +57104,60 @@ def from_dict(obj: Any) -> 'RPC': llm_inference_http_response_start_result = LlmInferenceHTTPResponseStartResult.from_dict(obj.get("LlmInferenceHttpResponseStartResult")) llm_inference_set_provider_result = LlmInferenceSetProviderResult.from_dict(obj.get("LlmInferenceSetProviderResult")) local_session_metadata_value = LocalSessionMetadataValue.from_dict(obj.get("LocalSessionMetadataValue")) + location_permissions_cleanup_result = _LocationPermissionsCleanupResult.from_dict(obj.get("LocationPermissionsCleanupResult")) logged_in_user = _LoggedInUser.from_dict(obj.get("LoggedInUser")) login_provider_kind = LoginProviderKind(obj.get("LoginProviderKind")) 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_mcp_server_config = ManagedMCPServerConfig.from_dict(obj.get("ManagedMcpServerConfig")) + managed_permission_evaluation = ManagedPermissionEvaluation.from_dict(obj.get("ManagedPermissionEvaluation")) + managed_permission_operation = ManagedPermissionOperation.from_dict(obj.get("ManagedPermissionOperation")) + managed_permission_operation_kind = ManagedPermissionOperationKind(obj.get("ManagedPermissionOperationKind")) + managed_permissions_evaluate_request = ManagedPermissionsEvaluateRequest.from_dict(obj.get("ManagedPermissionsEvaluateRequest")) + managed_permissions_evaluate_result = ManagedPermissionsEvaluateResult.from_dict(obj.get("ManagedPermissionsEvaluateResult")) + managed_permission_verdict = ManagedPermissionVerdict(obj.get("ManagedPermissionVerdict")) + managed_plugin_retry_entry = ManagedPluginRetryEntry.from_dict(obj.get("ManagedPluginRetryEntry")) + managed_plugin_retry_status = ManagedPluginRetryStatus(obj.get("ManagedPluginRetryStatus")) managed_setting_meta = ManagedSettingMeta.from_dict(obj.get("ManagedSettingMeta")) + managed_settings_apply_controls_params = _ManagedSettingsApplyControlsParams.from_dict(obj.get("ManagedSettingsApplyControlsParams")) + managed_settings_apply_controls_result = _ManagedSettingsApplyControlsResult.from_dict(obj.get("ManagedSettingsApplyControlsResult")) + managed_settings_bypass_disable_source = _ManagedSettingsBypassDisableSource(obj.get("ManagedSettingsBypassDisableSource")) + managed_settings_bypass_policy_resolution = _ManagedSettingsBypassPolicyResolution(obj.get("ManagedSettingsBypassPolicyResolution")) managed_settings_channel = ManagedSettingsChannel(obj.get("ManagedSettingsChannel")) managed_settings_compose_layer = ManagedSettingsComposeLayer.from_dict(obj.get("ManagedSettingsComposeLayer")) managed_settings_compose_request = ManagedSettingsComposeRequest.from_dict(obj.get("ManagedSettingsComposeRequest")) managed_settings_compose_result = ManagedSettingsComposeResult.from_dict(obj.get("ManagedSettingsComposeResult")) managed_settings_diagnostic = ManagedSettingsDiagnostic.from_dict(obj.get("ManagedSettingsDiagnostic")) managed_settings_diagnostic_severity = ManagedSettingsDiagnosticSeverity(obj.get("ManagedSettingsDiagnosticSeverity")) + managed_settings_ingest_params = _ManagedSettingsIngestParams.from_dict(obj.get("ManagedSettingsIngestParams")) + managed_settings_ingest_result = _ManagedSettingsIngestResult.from_dict(obj.get("ManagedSettingsIngestResult")) managed_settings_layer = ManagedSettingsLayer.from_dict(obj.get("ManagedSettingsLayer")) + managed_settings_load_remote_control_params = _ManagedSettingsLoadRemoteControlParams.from_dict(obj.get("ManagedSettingsLoadRemoteControlParams")) + managed_settings_load_remote_control_result = _ManagedSettingsLoadRemoteControlResult.from_dict(obj.get("ManagedSettingsLoadRemoteControlResult")) + managed_settings_log_level = _ManagedSettingsLogLevel(obj.get("ManagedSettingsLogLevel")) + managed_settings_log_message = _ManagedSettingsLogMessage.from_dict(obj.get("ManagedSettingsLogMessage")) managed_settings_meta = ManagedSettingsMeta.from_dict(obj.get("ManagedSettingsMeta")) + managed_settings_parse_client_value_params = _ManagedSettingsParseClientValueParams.from_dict(obj.get("ManagedSettingsParseClientValueParams")) + managed_settings_parse_client_value_result = _ManagedSettingsParseClientValueResult.from_dict(obj.get("ManagedSettingsParseClientValueResult")) + managed_settings_policy_helper_authority = _ManagedSettingsPolicyHelperAuthority(obj.get("ManagedSettingsPolicyHelperAuthority")) + managed_settings_policy_helper_reason = _ManagedSettingsPolicyHelperReason(obj.get("ManagedSettingsPolicyHelperReason")) + managed_settings_read_device_result = _ManagedSettingsReadDeviceResult.from_dict(obj.get("ManagedSettingsReadDeviceResult")) managed_settings_read_result = ManagedSettingsReadResult.from_dict(obj.get("ManagedSettingsReadResult")) + managed_settings_resolve_bypass_disable_transition_params = _ManagedSettingsResolveBypassDisableTransitionParams.from_dict(obj.get("ManagedSettingsResolveBypassDisableTransitionParams")) + managed_settings_resolve_bypass_disable_transition_result = _ManagedSettingsResolveBypassDisableTransitionResult.from_dict(obj.get("ManagedSettingsResolveBypassDisableTransitionResult")) managed_settings_resolved_data = ManagedSettingsResolvedData.from_dict(obj.get("ManagedSettingsResolvedData")) + managed_settings_resolve_device_force_login_orgs_gate_params = _ManagedSettingsResolveDeviceForceLoginOrgsGateParams.from_dict(obj.get("ManagedSettingsResolveDeviceForceLoginOrgsGateParams")) + managed_settings_resolve_effective_value_params = _ManagedSettingsResolveEffectiveValueParams.from_dict(obj.get("ManagedSettingsResolveEffectiveValueParams")) + managed_settings_resolve_effective_value_result = _ManagedSettingsResolveEffectiveValueResult.from_dict(obj.get("ManagedSettingsResolveEffectiveValueResult")) managed_settings_resolve_request = ManagedSettingsResolveRequest.from_dict(obj.get("ManagedSettingsResolveRequest")) managed_settings_resolve_result = ManagedSettingsResolveResult.from_dict(obj.get("ManagedSettingsResolveResult")) + managed_settings_resolve_startup_policy_params = _ManagedSettingsResolveStartupPolicyParams.from_dict(obj.get("ManagedSettingsResolveStartupPolicyParams")) + managed_settings_resolve_startup_policy_result = _ManagedSettingsResolveStartupPolicyResult.from_dict(obj.get("ManagedSettingsResolveStartupPolicyResult")) + managed_settings_retain_policy_params = _ManagedSettingsRetainPolicyParams.from_dict(obj.get("ManagedSettingsRetainPolicyParams")) managed_settings_schema_result = ManagedSettingsSchemaResult.from_dict(obj.get("ManagedSettingsSchemaResult")) + managed_settings_server_policy_cache_mode = _ManagedSettingsServerPolicyCacheMode(obj.get("ManagedSettingsServerPolicyCacheMode")) + managed_settings_server_policy_resolution = _ManagedSettingsServerPolicyResolution(obj.get("ManagedSettingsServerPolicyResolution")) managed_settings_validate_request = ManagedSettingsValidateRequest.from_dict(obj.get("ManagedSettingsValidateRequest")) managed_settings_validate_result = ManagedSettingsValidateResult.from_dict(obj.get("ManagedSettingsValidateResult")) managed_settings_values = ManagedSettingsValues.from_dict(obj.get("ManagedSettingsValues")) @@ -51821,7 +57205,7 @@ def from_dict(obj: Any) -> 'RPC': mcp_configured_server = MCPConfiguredServer.from_dict(obj.get("McpConfiguredServer")) mcp_configured_server_list = MCPConfiguredServerList.from_dict(obj.get("McpConfiguredServerList")) mcp_configured_server_state = MCPConfiguredServerState.from_dict(obj.get("McpConfiguredServerState")) - mcp_configure_git_hub_request = MCPConfigureGitHubRequest.from_dict(obj.get("McpConfigureGitHubRequest")) + mcp_configure_git_hub_request = _MCPConfigureGitHubRequest.from_dict(obj.get("McpConfigureGitHubRequest")) mcp_configure_git_hub_result = MCPConfigureGitHubResult.from_dict(obj.get("McpConfigureGitHubResult")) mcp_diagnostic_details = MCPDiagnosticDetails.from_dict(obj.get("McpDiagnosticDetails")) mcp_diagnostic_direction = MCPDiagnosticDirection(obj.get("McpDiagnosticDirection")) @@ -51859,6 +57243,7 @@ def from_dict(obj: Any) -> 'RPC': 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_builtin_server_names_result = _MCPListBuiltinServerNamesResult.from_dict(obj.get("McpListBuiltinServerNamesResult")) mcp_list_tools_request = MCPListToolsRequest.from_dict(obj.get("McpListToolsRequest")) mcp_list_tools_result = MCPListToolsResult.from_dict(obj.get("McpListToolsResult")) mcp_oauth_authentication_state_changed_request = MCPOauthAuthenticationStateChangedRequest.from_dict(obj.get("McpOauthAuthenticationStateChangedRequest")) @@ -51925,9 +57310,14 @@ def from_dict(obj: Any) -> 'RPC': mcp_prompts_get_result = MCPPromptsGetResult.from_dict(obj.get("McpPromptsGetResult")) mcp_prompts_list_request = MCPPromptsListRequest.from_dict(obj.get("McpPromptsListRequest")) mcp_prompts_list_result = MCPPromptsListResult.from_dict(obj.get("McpPromptsListResult")) - 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_register_external_client_request = _MCPRegisterExternalClientRequest.from_dict(obj.get("McpRegisterExternalClientRequest")) + mcp_registry_cancel_request = _MCPRegistryCancelRequest.from_dict(obj.get("McpRegistryCancelRequest")) + mcp_registry_cancel_result = _MCPRegistryCancelResult.from_dict(obj.get("McpRegistryCancelResult")) + mcp_registry_request_id_result = _MCPRegistryRequestIDResult.from_dict(obj.get("McpRegistryRequestIdResult")) + mcp_registry_search_request = _MCPRegistrySearchRequest.from_dict(obj.get("McpRegistrySearchRequest")) + mcp_registry_search_result = _MCPRegistrySearchResult.from_dict(obj.get("McpRegistrySearchResult")) + 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")) mcp_resource_annotations = MCPResourceAnnotations.from_dict(obj.get("McpResourceAnnotations")) @@ -51960,8 +57350,8 @@ def from_dict(obj: Any) -> 'RPC': 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_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")) @@ -51971,6 +57361,8 @@ def from_dict(obj: Any) -> 'RPC': mcp_set_env_value_mode_details = MCPSetEnvValueModeDetails(obj.get("McpSetEnvValueModeDetails")) mcp_set_env_value_mode_params = MCPSetEnvValueModeParams.from_dict(obj.get("McpSetEnvValueModeParams")) mcp_set_env_value_mode_result = MCPSetEnvValueModeResult.from_dict(obj.get("McpSetEnvValueModeResult")) + mcp_should_exclude_git_hub_tools_request = _MCPShouldExcludeGitHubToolsRequest.from_dict(obj.get("McpShouldExcludeGitHubToolsRequest")) + mcp_should_exclude_git_hub_tools_result = _MCPShouldExcludeGitHubToolsResult.from_dict(obj.get("McpShouldExcludeGitHubToolsResult")) mcp_source_file = MCPSourceFile.from_dict(obj.get("McpSourceFile")) mcp_source_plugin = MCPSourcePlugin.from_dict(obj.get("McpSourcePlugin")) mcp_source_ref = MCPSourceRef.from_dict(obj.get("McpSourceRef")) @@ -51982,7 +57374,7 @@ def from_dict(obj: Any) -> 'RPC': mcp_tool_ui = MCPToolUI.from_dict(obj.get("McpToolUi")) mcp_tool_ui_visibility = MCPToolUIVisibility(obj.get("McpToolUiVisibility")) mcp_uninstall_plan = MCPUninstallPlan.from_dict(obj.get("McpUninstallPlan")) - mcp_unregister_external_client_request = MCPUnregisterExternalClientRequest.from_dict(obj.get("McpUnregisterExternalClientRequest")) + mcp_unregister_external_client_request = _MCPUnregisterExternalClientRequest.from_dict(obj.get("McpUnregisterExternalClientRequest")) memory_configuration = MemoryConfiguration.from_dict(obj.get("MemoryConfiguration")) metadata_context_attribution_result = MetadataContextAttributionResult.from_dict(obj.get("MetadataContextAttributionResult")) metadata_context_heaviest_messages_request = MetadataContextHeaviestMessagesRequest.from_dict(obj.get("MetadataContextHeaviestMessagesRequest")) @@ -52018,6 +57410,8 @@ def from_dict(obj: Any) -> 'RPC': model_capabilities_supports = ModelCapabilitiesSupports.from_dict(obj.get("ModelCapabilitiesSupports")) model_clear_startup_seed_request = _ModelClearStartupSeedRequest.from_dict(obj.get("ModelClearStartupSeedRequest")) model_clear_startup_seed_result = _ModelClearStartupSeedResult.from_dict(obj.get("ModelClearStartupSeedResult")) + model_get_reasoning_summaries_request = ModelGetReasoningSummariesRequest.from_dict(obj.get("ModelGetReasoningSummariesRequest")) + model_get_reasoning_summaries_result = ModelGetReasoningSummariesResult.from_dict(obj.get("ModelGetReasoningSummariesResult")) model_list = ModelList.from_dict(obj.get("ModelList")) model_list_request = obj.get("ModelListRequest") model_message = ModelMessage.from_dict(obj.get("ModelMessage")) @@ -52153,6 +57547,8 @@ def from_dict(obj: Any) -> 'RPC': permissions_configure_additional_content_exclusion_policy_scope = AdditionalContentExclusionPolicyScope(obj.get("PermissionsConfigureAdditionalContentExclusionPolicyScope")) permissions_configure_params = PermissionsConfigureParams.from_dict(obj.get("PermissionsConfigureParams")) permissions_configure_result = PermissionsConfigureResult.from_dict(obj.get("PermissionsConfigureResult")) + permissions_evaluate_managed_policy_params = _PermissionsEvaluateManagedPolicyParams.from_dict(obj.get("PermissionsEvaluateManagedPolicyParams")) + permissions_evaluate_managed_policy_result = PermissionsEvaluateManagedPolicyResult.from_dict(obj.get("PermissionsEvaluateManagedPolicyResult")) permissions_folder_trust_add_trusted_result = PermissionsFolderTrustAddTrustedResult.from_dict(obj.get("PermissionsFolderTrustAddTrustedResult")) permissions_get_mode_request = PermissionsGetModeRequest.from_dict(obj.get("PermissionsGetModeRequest")) permissions_get_mode_result = PermissionsGetModeResult.from_dict(obj.get("PermissionsGetModeResult")) @@ -52189,6 +57585,8 @@ def from_dict(obj: Any) -> 'RPC': permissions_urls_set_unrestricted_mode_result = PermissionsUrlsSetUnrestrictedModeResult.from_dict(obj.get("PermissionsUrlsSetUnrestrictedModeResult")) permission_urls_config = PermissionUrlsConfig.from_dict(obj.get("PermissionUrlsConfig")) permission_urls_set_unrestricted_mode_params = PermissionUrlsSetUnrestrictedModeParams.from_dict(obj.get("PermissionUrlsSetUnrestrictedModeParams")) + persistence_write_raw_json_locked_request = PersistenceWriteRawJSONLockedRequest.from_dict(obj.get("PersistenceWriteRawJsonLockedRequest")) + persistence_write_raw_json_locked_result = PersistenceWriteRawJSONLockedResult.from_dict(obj.get("PersistenceWriteRawJsonLockedResult")) ping_request = PingRequest.from_dict(obj.get("PingRequest")) ping_result = PingResult.from_dict(obj.get("PingResult")) plan_read_result = PlanReadResult.from_dict(obj.get("PlanReadResult")) @@ -52333,7 +57731,17 @@ def from_dict(obj: Any) -> 'RPC': remote_session_metadata_value = RemoteSessionMetadataValue.from_dict(obj.get("RemoteSessionMetadataValue")) remote_session_mode = RemoteSessionMode(obj.get("RemoteSessionMode")) remote_session_repository = RemoteSessionRepository.from_dict(obj.get("RemoteSessionRepository")) + repo_settings_edit_request = _RepoSettingsEditRequest.from_dict(obj.get("RepoSettingsEditRequest")) + repo_settings_edit_scope = _RepoSettingsEditScope(obj.get("RepoSettingsEditScope")) + repo_settings_read_raw_request = _RepoSettingsReadRawRequest.from_dict(obj.get("RepoSettingsReadRawRequest")) + repo_settings_read_raw_result = _RepoSettingsReadRawResult.from_dict(obj.get("RepoSettingsReadRawResult")) + repo_settings_write_all_request = _RepoSettingsWriteAllRequest.from_dict(obj.get("RepoSettingsWriteAllRequest")) + repo_settings_write_key_request = _RepoSettingsWriteKeyRequest.from_dict(obj.get("RepoSettingsWriteKeyRequest")) response_format = ResponseFormat.from_dict(obj.get("ResponseFormat")) + sandbox_apply_defaults_request = SandboxApplyDefaultsRequest.from_dict(obj.get("SandboxApplyDefaultsRequest")) + sandbox_check_enforcement_command_result = from_bool(obj.get("SandboxCheckEnforcementCommandResult")) + sandbox_check_platform_support_result = from_bool(obj.get("SandboxCheckPlatformSupportResult")) + sandbox_check_windows_host_loopback_support_result = from_bool(obj.get("SandboxCheckWindowsHostLoopbackSupportResult")) sandbox_config = SandboxConfig.from_dict(obj.get("SandboxConfig")) sandbox_config_auth = SandboxConfigAuth.from_dict(obj.get("SandboxConfigAuth")) sandbox_config_source = SandboxConfigSource(obj.get("SandboxConfigSource")) @@ -52345,20 +57753,44 @@ def from_dict(obj: Any) -> 'RPC': sandbox_config_user_policy_network_proxy = SandboxConfigUserPolicyNetworkProxy.from_dict(obj.get("SandboxConfigUserPolicyNetworkProxy")) sandbox_config_user_policy_seatbelt = SandboxConfigUserPolicySeatbelt.from_dict(obj.get("SandboxConfigUserPolicySeatbelt")) sandbox_credentials_config = SandboxCredentialsConfig.from_dict(obj.get("SandboxCredentialsConfig")) + sandbox_credential_suggestion = SandboxCredentialSuggestion.from_dict(obj.get("SandboxCredentialSuggestion")) + sandbox_credential_suggestions_result = SandboxCredentialSuggestionsResult.from_dict(obj.get("SandboxCredentialSuggestionsResult")) sandbox_disable_for_session_request = SandboxDisableForSessionRequest.from_dict(obj.get("SandboxDisableForSessionRequest")) sandbox_disable_for_session_result = SandboxDisableForSessionResult.from_dict(obj.get("SandboxDisableForSessionResult")) + sandbox_docs_url_result = SandboxDocsURLResult.from_dict(obj.get("SandboxDocsUrlResult")) + sandbox_effective_for_optional_params = _SandboxEffectiveForOptionalParams.from_dict(obj.get("SandboxEffectiveForOptionalParams")) + sandbox_effective_for_optional_result = _SandboxEffectiveForOptionalResult.from_dict(obj.get("SandboxEffectiveForOptionalResult")) + sandbox_enforcement_command_allowed_params = _SandboxEnforcementCommandAllowedParams.from_dict(obj.get("SandboxEnforcementCommandAllowedParams")) sandbox_enforcement_status = SandboxEnforcementStatus.from_dict(obj.get("SandboxEnforcementStatus")) + sandbox_filter_picker_candidates_params = _SandboxFilterPickerCandidatesParams.from_dict(obj.get("SandboxFilterPickerCandidatesParams")) + sandbox_filter_picker_candidates_result = _SandboxFilterPickerCandidatesResult.from_dict(obj.get("SandboxFilterPickerCandidatesResult")) + sandbox_get_unsupported_message_result = from_str(obj.get("SandboxGetUnsupportedMessageResult")) + sandbox_get_unsupported_setting_disabled_notice_result = from_str(obj.get("SandboxGetUnsupportedSettingDisabledNoticeResult")) sandbox_grant_path_for_request_request = SandboxGrantPathForRequestRequest.from_dict(obj.get("SandboxGrantPathForRequestRequest")) sandbox_grant_path_for_request_result = SandboxGrantPathForRequestResult.from_dict(obj.get("SandboxGrantPathForRequestResult")) sandbox_host_capability = SandboxHostCapability.from_dict(obj.get("SandboxHostCapability")) sandbox_host_capability_name = from_str(obj.get("SandboxHostCapabilityName")) sandbox_host_support = SandboxHostSupport.from_dict(obj.get("SandboxHostSupport")) sandbox_masked_env_var = SandboxMaskedEnvVar.from_dict(obj.get("SandboxMaskedEnvVar")) + sandbox_picker_candidate = _SandboxPickerCandidate.from_dict(obj.get("SandboxPickerCandidate")) + sandbox_prepare_picker_policy_params = _SandboxPreparePickerPolicyParams.from_dict(obj.get("SandboxPreparePickerPolicyParams")) + sandbox_prepare_picker_policy_result = _SandboxPreparePickerPolicyResult.from_dict(obj.get("SandboxPreparePickerPolicyResult")) + sandbox_proxy_ca_check_installation_support_result = from_bool(obj.get("SandboxProxyCaCheckInstallationSupportResult")) + sandbox_proxy_ca_check_relevance_result = from_bool(obj.get("SandboxProxyCaCheckRelevanceResult")) sandbox_proxy_ca_create_result = SandboxProxyCACreateResult.from_dict(obj.get("SandboxProxyCaCreateResult")) sandbox_proxy_ca_request = SandboxProxyCARequest.from_dict(obj.get("SandboxProxyCaRequest")) sandbox_proxy_ca_state = SandboxProxyCAState(obj.get("SandboxProxyCaState")) sandbox_proxy_ca_status = SandboxProxyCAStatus.from_dict(obj.get("SandboxProxyCaStatus")) + sandbox_proxy_secret_get_result = SandboxProxySecretGetResult.from_dict(obj.get("SandboxProxySecretGetResult")) + sandbox_proxy_secret_location = SandboxProxySecretLocation.from_dict(obj.get("SandboxProxySecretLocation")) + sandbox_proxy_secret_save_request = SandboxProxySecretSaveRequest.from_dict(obj.get("SandboxProxySecretSaveRequest")) + sandbox_proxy_secret_save_result = SandboxProxySecretSaveResult.from_dict(obj.get("SandboxProxySecretSaveResult")) + sandbox_resolve_floored_config_params = _SandboxResolveFlooredConfigParams.from_dict(obj.get("SandboxResolveFlooredConfigParams")) + sandbox_resolve_floored_config_result = SandboxResolveFlooredConfigResult.from_dict(obj.get("SandboxResolveFlooredConfigResult")) sandbox_session_change = SandboxSessionChange(obj.get("SandboxSessionChange")) + sandbox_settings_apply_defaults_result = from_dict(lambda x: x, obj.get("SandboxSettingsApplyDefaultsResult")) + sandbox_unsupported_startup_notice_params = _SandboxUnsupportedStartupNoticeParams.from_dict(obj.get("SandboxUnsupportedStartupNoticeParams")) + sandbox_unsupported_startup_notice_result = _SandboxUnsupportedStartupNoticeResult.from_dict(obj.get("SandboxUnsupportedStartupNoticeResult")) 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")) @@ -52489,6 +57921,8 @@ def from_dict(obj: Any) -> 'RPC': session_plugins_install_request = SessionPluginsInstallRequest.from_dict(obj.get("SessionPluginsInstallRequest")) session_plugins_marketplaces_refresh_request = SessionPluginsMarketplacesRefreshRequest.from_dict(obj.get("SessionPluginsMarketplacesRefreshRequest")) session_plugins_reload_request = SessionPluginsReloadRequest.from_dict(obj.get("SessionPluginsReloadRequest")) + session_plugins_retry_managed_request = SessionPluginsRetryManagedRequest.from_dict(obj.get("SessionPluginsRetryManagedRequest")) + session_plugins_retry_managed_result = SessionPluginsRetryManagedResult.from_dict(obj.get("SessionPluginsRetryManagedResult")) session_provider_get_endpoint_request = SessionProviderGetEndpointRequest.from_dict(obj.get("SessionProviderGetEndpointRequest")) session_prune_result = SessionPruneResult.from_dict(obj.get("SessionPruneResult")) session_quota_delegate_warning = SessionQuotaDelegateWarning.from_dict(obj.get("SessionQuotaDelegateWarning")) @@ -52585,6 +58019,9 @@ def from_dict(obj: Any) -> 'RPC': session_working_directory_context_with_client = SessionWorkingDirectoryContextWithClient.from_dict(obj.get("SessionWorkingDirectoryContextWithClient")) settable_auth_info = _load_SettableAuthInfo(obj.get("SettableAuthInfo")) settable_token_auth_info = SettableTokenAuthInfo.from_dict(obj.get("SettableTokenAuthInfo")) + settings_edit_operation = _SettingsEditOperation(obj.get("SettingsEditOperation")) + settings_edit_result = _SettingsEditResult.from_dict(obj.get("SettingsEditResult")) + settings_edit_status = _SettingsEditStatus(obj.get("SettingsEditStatus")) 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")) @@ -52622,6 +58059,7 @@ def from_dict(obj: Any) -> 'RPC': skill_installation_summary = SkillInstallationSummary.from_dict(obj.get("SkillInstallationSummary")) skill_install_plan = SkillInstallPlan.from_dict(obj.get("SkillInstallPlan")) skill_list = SkillList.from_dict(obj.get("SkillList")) + skill_plan_install_from_reference_request = SkillPlanInstallFromReferenceRequest.from_dict(obj.get("SkillPlanInstallFromReferenceRequest")) skill_plan_install_request = SkillPlanInstallRequest.from_dict(obj.get("SkillPlanInstallRequest")) skill_plan_uninstall_request = SkillPlanUninstallRequest.from_dict(obj.get("SkillPlanUninstallRequest")) skill_provider_descriptor = SkillProviderDescriptor.from_dict(obj.get("SkillProviderDescriptor")) @@ -52657,6 +58095,8 @@ def from_dict(obj: Any) -> 'RPC': 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")) + staff_log_level_migration_status = StaffLogLevelMigrationStatus(obj.get("StaffLogLevelMigrationStatus")) + staff_update_channel_migration_status = StaffUpdateChannelMigrationStatus(obj.get("StaffUpdateChannelMigrationStatus")) subagent_settings_entry = SubagentSettingsEntry.from_dict(obj.get("SubagentSettingsEntry")) subagent_settings_entry_context_tier = SubagentSettingsEntryContextTier(obj.get("SubagentSettingsEntryContextTier")) system_message_block = SystemMessageBlock.from_dict(obj.get("SystemMessageBlock")) @@ -52771,8 +58211,55 @@ def from_dict(obj: Any) -> 'RPC': user_auth_info = UserAuthInfo.from_dict(obj.get("UserAuthInfo")) user_requested_shell_command_result = UserRequestedShellCommandResult.from_dict(obj.get("UserRequestedShellCommandResult")) user_setting_metadata = UserSettingMetadata.from_dict(obj.get("UserSettingMetadata")) + user_settings_apply_top_key_request = UserSettingsApplyTopKeyRequest.from_dict(obj.get("UserSettingsApplyTopKeyRequest")) + user_settings_apply_top_key_result = UserSettingsApplyTopKeyResult.from_dict(obj.get("UserSettingsApplyTopKeyResult")) + user_settings_build_runtime_request = _UserSettingsBuildRuntimeRequest.from_dict(obj.get("UserSettingsBuildRuntimeRequest")) + user_settings_build_runtime_result = from_dict(lambda x: x, obj.get("UserSettingsBuildRuntimeResult")) + user_settings_edit_request = _UserSettingsEditRequest.from_dict(obj.get("UserSettingsEditRequest")) + user_settings_get_paths_request = UserSettingsGetPathsRequest.from_dict(obj.get("UserSettingsGetPathsRequest")) + user_settings_get_paths_result = UserSettingsGetPathsResult.from_dict(obj.get("UserSettingsGetPathsResult")) user_settings_get_result = UserSettingsGetResult.from_dict(obj.get("UserSettingsGetResult")) + user_settings_governance_keys_result = UserSettingsGovernanceKeysResult.from_dict(obj.get("UserSettingsGovernanceKeysResult")) + user_settings_host_context = UserSettingsHostContext.from_dict(obj.get("UserSettingsHostContext")) + user_settings_load_and_merge_repo_request = _UserSettingsLoadAndMergeRepoRequest.from_dict(obj.get("UserSettingsLoadAndMergeRepoRequest")) + user_settings_load_and_merge_repo_result = _UserSettingsLoadAndMergeRepoResult.from_dict(obj.get("UserSettingsLoadAndMergeRepoResult")) + user_settings_load_for_config_dir_request = _UserSettingsLoadForConfigDirRequest.from_dict(obj.get("UserSettingsLoadForConfigDirRequest")) + user_settings_load_for_config_dir_result = from_dict(lambda x: x, obj.get("UserSettingsLoadForConfigDirResult")) + user_settings_load_for_edit_result = UserSettingsLoadForEditResult.from_dict(obj.get("UserSettingsLoadForEditResult")) + user_settings_load_repo_request = UserSettingsLoadRepoRequest.from_dict(obj.get("UserSettingsLoadRepoRequest")) + user_settings_load_repo_result = UserSettingsLoadRepoResult.from_dict(obj.get("UserSettingsLoadRepoResult")) + user_settings_load_with_warning_result = UserSettingsLoadWithWarningResult.from_dict(obj.get("UserSettingsLoadWithWarningResult")) + user_settings_merge_managed_and_user_params = _UserSettingsMergeManagedAndUserParams.from_dict(obj.get("UserSettingsMergeManagedAndUserParams")) + user_settings_merge_managed_and_user_result = UserSettingsMergeManagedAndUserResult.from_dict(obj.get("UserSettingsMergeManagedAndUserResult")) + user_settings_merge_repo_value_params = _UserSettingsMergeRepoValueParams.from_dict(obj.get("UserSettingsMergeRepoValueParams")) + user_settings_merge_repo_value_result = UserSettingsMergeRepoValueResult.from_dict(obj.get("UserSettingsMergeRepoValueResult")) + user_settings_migrate_obsolete_on_disk_request = _UserSettingsMigrateObsoleteOnDiskRequest.from_dict(obj.get("UserSettingsMigrateObsoleteOnDiskRequest")) + user_settings_migrate_obsolete_on_disk_result = _UserSettingsMigrateObsoleteOnDiskResult.from_dict(obj.get("UserSettingsMigrateObsoleteOnDiskResult")) + user_settings_migrate_obsolete_on_disk_status = _UserSettingsMigrateObsoleteOnDiskStatus(obj.get("UserSettingsMigrateObsoleteOnDiskStatus")) + user_settings_migrate_staff_log_level_once_request = _UserSettingsMigrateStaffLogLevelOnceRequest.from_dict(obj.get("UserSettingsMigrateStaffLogLevelOnceRequest")) + user_settings_migrate_staff_log_level_once_result = _UserSettingsMigrateStaffLogLevelOnceResult.from_dict(obj.get("UserSettingsMigrateStaffLogLevelOnceResult")) + user_settings_migrate_staff_update_channel_once_result = _UserSettingsMigrateStaffUpdateChannelOnceResult.from_dict(obj.get("UserSettingsMigrateStaffUpdateChannelOnceResult")) + user_settings_normalize_request = UserSettingsNormalizeRequest.from_dict(obj.get("UserSettingsNormalizeRequest")) + user_settings_normalize_result = UserSettingsNormalizeResult.from_dict(obj.get("UserSettingsNormalizeResult")) + user_settings_path_result = UserSettingsPathResult.from_dict(obj.get("UserSettingsPathResult")) + user_settings_problem = UserSettingsProblem.from_dict(obj.get("UserSettingsProblem")) + user_settings_problem_location = UserSettingsProblemLocation.from_dict(obj.get("UserSettingsProblemLocation")) + user_settings_problem_source = UserSettingsProblemSource.from_dict(obj.get("UserSettingsProblemSource")) + user_settings_problems_request = UserSettingsProblemsRequest.from_dict(obj.get("UserSettingsProblemsRequest")) + user_settings_problems_result = UserSettingsProblemsResult.from_dict(obj.get("UserSettingsProblemsResult")) + user_settings_reset_staff_model_selection_once_request = _UserSettingsResetStaffModelSelectionOnceRequest.from_dict(obj.get("UserSettingsResetStaffModelSelectionOnceRequest")) + user_settings_reset_staff_model_selection_once_result = _UserSettingsResetStaffModelSelectionOnceResult.from_dict(obj.get("UserSettingsResetStaffModelSelectionOnceResult")) + user_settings_set_hook_disabled_request = _UserSettingsSetHookDisabledRequest.from_dict(obj.get("UserSettingsSetHookDisabledRequest")) + user_settings_set_hook_disabled_result = _UserSettingsSetHookDisabledResult.from_dict(obj.get("UserSettingsSetHookDisabledResult")) user_settings_set_request = UserSettingsSetRequest.from_dict(obj.get("UserSettingsSetRequest")) + user_settings_unknown_keys_warning_request = _UserSettingsUnknownKeysWarningRequest.from_dict(obj.get("UserSettingsUnknownKeysWarningRequest")) + user_settings_unknown_keys_warning_result = _UserSettingsUnknownKeysWarningResult.from_dict(obj.get("UserSettingsUnknownKeysWarningResult")) + user_settings_update_request = _UserSettingsUpdateRequest.from_dict(obj.get("UserSettingsUpdateRequest")) + user_settings_update_result = _UserSettingsUpdateResult.from_dict(obj.get("UserSettingsUpdateResult")) + user_settings_validate_request = UserSettingsValidateRequest.from_dict(obj.get("UserSettingsValidateRequest")) + user_settings_validate_result = UserSettingsValidateResult.from_dict(obj.get("UserSettingsValidateResult")) + user_settings_validation_issue = UserSettingsValidationIssue.from_dict(obj.get("UserSettingsValidationIssue")) + user_settings_write_all_request = UserSettingsWriteAllRequest.from_dict(obj.get("UserSettingsWriteAllRequest")) visibility_get_result = VisibilityGetResult.from_dict(obj.get("VisibilityGetResult")) visibility_set_request = VisibilitySetRequest.from_dict(obj.get("VisibilitySetRequest")) visibility_set_result = VisibilitySetResult.from_dict(obj.get("VisibilitySetResult")) @@ -52864,7 +58351,7 @@ def from_dict(obj: Any) -> 'RPC': 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, accepted_enqueue_command_result, account_all_users, account_auth_info, account_get_all_users_result, account_get_current_auth_result, account_get_quota_request, account_get_quota_result, account_kind, account_login_request, account_login_result, account_logout_request, account_logout_result, account_quota_snapshot, accounts_enumerate_request, accounts_get_request, accounts_set_request, account_status, 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_custom_agent_initial_model_decision_params, agents_custom_agent_initial_model_decision_result, agents_discover_request, agent_select_request, agent_select_result, agent_set_prompt_request, agents_get_available_builtins_request, agents_get_available_builtins_result, agents_get_builtin_definition_request, agents_get_builtin_definition_result, agents_get_builtin_listing_definition_request, agents_get_builtin_listing_definition_result, agents_get_builtins_result, agents_get_discovery_paths_request, api_key_auth_info, auth_enumerate_query, auth_enumerate_value, auth_identity, auth_identity_metadata, auth_info, auth_info_type, auth_login_account, auth_login_advance_request, auth_login_begin_request, auth_login_begun, auth_login_cancel_request, auth_login_result_dto, auth_login_result_status, auth_login_step, auth_read_query, auth_read_value, auth_status_dto, auth_validation_error, auth_validation_errors, auth_write, auth_write_result, autopilot_objective_credit_limit, autopilot_objective_get_state_result, autopilot_objective_state, autopilot_objective_status, auto_tier_descriptor, auto_tier_metadata, auto_tier_status, builtin_agent_summary, 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_agent_plugin_candidate, catalog_agent_plugin_candidate_kind, catalog_agent_plugin_candidate_provenance, catalog_agent_plugin_compatibility_tag, catalog_agent_plugin_media_type, catalog_ai_skill_candidate, catalog_ai_skill_candidate_kind, catalog_ai_skill_candidate_provenance, catalog_ai_skill_installability, catalog_ai_skill_media_type, 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_kind, 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_plugin_repository_source, catalog_policy_rejected_error, catalog_resource_identity, catalog_resource_version, catalog_search_page, catalog_search_pagination, catalog_search_request, catalog_search_result, catalog_search_succeeded, catalog_search_total_count_relation, catalog_selection_cancelled, catalog_selection_decision, catalog_selection_declined, catalog_selection_foreign, catalog_selection_invalid, catalog_selection_replayed, catalog_selection_request, catalog_selection_result, catalog_selection_selected, catalog_selection_stale, catalog_selection_timed_out, catalog_selection_wrong_kind, catalog_trust_eligibility, catalog_trust_provenance, catalog_trust_snapshot, catalog_trust_snapshot_absent, catalog_trust_snapshot_absent_status, catalog_trust_snapshot_current, catalog_trust_snapshot_current_status, catalog_trust_snapshot_downgraded, catalog_trust_snapshot_downgraded_status, catalog_trust_snapshot_malformed, catalog_trust_snapshot_malformed_status, catalog_trust_snapshot_revoked, catalog_trust_snapshot_revoked_status, catalog_trust_snapshot_schema_version, catalog_trust_snapshot_stale, catalog_trust_snapshot_stale_status, catalog_trust_snapshot_unsupported, catalog_trust_snapshot_unsupported_status, catalog_trust_source, catalog_trust_tier, 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, client_metadata, client_task_cancel_reason, client_task_cancel_request, client_task_cancel_result, 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, connector_account_request, connector_authorization_requirement, connector_authorization_scope, connector_availability, connector_capabilities, connector_catalog_entry, connector_catalog_result, connector_catalog_status, connector_connect_request, connector_connect_result, connector_continue_request, connector_disconnect_result, connector_discovery_account, connector_discovery_account_list, connector_discovery_account_request, connector_discovery_auth_info, connector_discovery_availability, connector_discovery_capabilities, connector_discovery_catalog_entry, connector_discovery_catalog_result, connector_mcp_status, connector_reconcile_request, connector_runtime_status, connector_session_account, connector_status, 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, customization_reload_outcome, customization_reload_status, customization_reload_subsystem, customizations_reload_result, 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, diagnostic_cursor_status, diagnostic_entry, diagnostic_log_level, diagnostics_configuration, diagnostics_configure_request, diagnostic_severity, diagnostic_source, diagnostic_sources_configuration, diagnostics_read_request, diagnostics_read_result, 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, discovered_model, discovered_model_list, enqueue_command_params, enqueue_command_result, env_auth_info, environment_capabilities, environment_kind, environments_delete_request, environments_delete_result, environments_get_request, environments_get_result, environments_list_request, environments_list_result, 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, 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_current_branch_remote_result, git_cwd_request, git_hub_environment, git_hub_owner_option, git_hub_owners_cancel_request, git_hub_owners_cancel_result, git_hub_owners_list_request, git_hub_owners_list_result, git_hub_owners_request_id_result, git_hub_repository_at_path_request, git_hub_repository_at_path_result, git_hub_repository_identity, 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, git_remote_repository, git_repos_from_remotes_request, git_repos_from_remotes_result, global_state_load_for_config_dir_request, global_state_load_result, global_state_write_key_request, 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, host_configuration, host_dispose_request, host_empty_result, host_environment_credentials, host_exited_notification, host_exit_reason, host_git_hub_environment_options, host_list_sessions_request, host_list_sessions_result, host_local_server_configuration, host_local_server_options, host_publish_session_request, host_publish_session_result, host_ready_request, host_register_session_request, host_session_create_callback, host_session_create_request, host_session_create_result, host_session_released_notification, host_session_release_request, host_session_summary, host_start_request, host_start_result, installation_catalogue_identity, installation_confirmation_request, installation_confirmation_response, installation_decision, installation_review, 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, json_schema_response_format, 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, logged_in_user, login_provider_kind, log_request, log_result, lsp_initialize_request, managed_mcp_server_config, managed_setting_meta, managed_settings_channel, managed_settings_compose_layer, managed_settings_compose_request, managed_settings_compose_result, managed_settings_diagnostic, managed_settings_diagnostic_severity, managed_settings_layer, managed_settings_meta, managed_settings_read_result, managed_settings_resolved_data, managed_settings_resolve_request, managed_settings_resolve_result, managed_settings_schema_result, managed_settings_validate_request, managed_settings_validate_result, managed_settings_values, 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_apply_install_request, mcp_apply_uninstall_request, 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_configured_server, mcp_configured_server_list, mcp_configured_server_state, mcp_configure_git_hub_request, mcp_configure_git_hub_result, mcp_diagnostic_details, mcp_diagnostic_direction, mcp_diagnostic_kind, mcp_diagnostic_source_configuration, 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_installation_failure_reason, mcp_installation_input, mcp_installation_management_outcome, mcp_installation_management_result, mcp_installation_operation_request, mcp_installation_operation_status, mcp_installation_outcome, mcp_installation_remote_configuration, mcp_installation_result, mcp_installation_review, mcp_installation_secret, mcp_installation_secret_storage, mcp_installations_request, mcp_installation_state, mcp_installation_summary, 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_cancel_login_request, mcp_oauth_cancel_login_result, mcp_oauth_complete_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_prepare_login_request, mcp_oauth_prepare_login_result, mcp_oauth_probe_needs_auth_reason, mcp_oauth_probe_request, mcp_oauth_probe_result, mcp_oauth_respond_request, mcp_oauth_respond_result, mcp_owned_oauth_login_status, 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_uninstall_request, mcp_plan_value_category, mcp_prepared_install, mcp_prepare_install_request, mcp_prompt, mcp_prompt_argument, mcp_prompt_icon, mcp_prompt_message, mcp_prompt_role, mcp_prompts_get_request, mcp_prompts_get_result, mcp_prompts_list_request, mcp_prompts_list_result, 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_server_ownership, mcp_set_env_value_mode_details, mcp_set_env_value_mode_params, mcp_set_env_value_mode_result, mcp_source_file, mcp_source_plugin, mcp_source_ref, 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_uninstall_plan, 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, metadata_update_client_metadata_request, model, model_apply_startup_overlay_request, model_artifact_details, 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_clear_startup_seed_request, model_clear_startup_seed_result, 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_provider_adapter_catalog, model_provider_adapter_descriptor, model_provider_adapter_operation_descriptor, model_provider_attribution, model_provider_automatic_discovery_mode, model_provider_automatic_discovery_policy, model_provider_configuration_disposition, model_provider_configuration_plan, model_provider_descriptor, model_provider_discover_request, model_provider_discover_result, model_provider_discovery_network_scope, model_provider_get_status_request, model_provider_instance, model_provider_instance_reference, model_provider_models_list_request, model_provider_operation_outcome, model_provider_operation_outcome_code, model_provider_prepare_configuration_request, model_provider_provenance, model_provider_provenance_source, model_provider_status, model_provider_warning, model_set_allowed_models_request, model_set_allowed_models_result, model_set_reasoning_effort_request, model_set_reasoning_effort_result, models_list_request, model_switch_auto_tier_request, model_switch_auto_tier_result, model_switch_auto_tier_status, 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_external_tool_request, pending_external_tool_request_list, 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_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_workflow, 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_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_workflow, permission_decision_approve_for_session_approval_write, permission_decision_approve_once, permission_decision_approve_permanently, permission_decision_approve_read_only_for_session, 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_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_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_workflow, 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_install_staging_mode, 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_append_mode, protocol_customize_mode, protocol_external_tool_defer, protocol_external_tool_definition, protocol_marker_section_override, protocol_replace_mode, protocol_section_override, protocol_static_section_action, protocol_static_section_override, protocol_system_message_append_config, protocol_system_message_config, protocol_system_message_customize_config, protocol_system_message_replace_config, provider_add_request, provider_add_result, provider_config, provider_config_azure, provider_config_model_provider, provider_config_transport, provider_config_type, provider_config_wire_api, provider_descriptor, provider_endpoint, provider_endpoint_transport, provider_endpoint_type, provider_endpoint_wire_api, provider_get_endpoint_request, provider_model_config, provider_session_token, provider_sync_request, provider_sync_result, provider_token_acquire_request, provider_token_acquire_result, provider_withdraw_request, provider_withdraw_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_append_steering_request, 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, queue_withdraw_message_request, queue_withdraw_message_result, quota_take_warnings_result, quota_warning_projection, register_event_interest_params, register_event_interest_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, response_format, 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_credentials_config, sandbox_disable_for_session_request, sandbox_disable_for_session_result, sandbox_enforcement_status, sandbox_grant_path_for_request_request, sandbox_grant_path_for_request_result, sandbox_host_capability, sandbox_host_capability_name, sandbox_host_support, sandbox_masked_env_var, sandbox_proxy_ca_create_result, sandbox_proxy_ca_request, sandbox_proxy_ca_state, sandbox_proxy_ca_status, sandbox_session_change, 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_connected_ide_info, 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_bytes_request, session_fs_read_file_bytes_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_bytes_request, 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_mcp_oauth_cancel_login_request, session_mcp_oauth_cancel_login_result, session_mcp_oauth_prepare_login_request, session_mcp_oauth_prepare_login_result, session_mcp_set_connected_ide_info_params, session_metadata_snapshot, session_mode, session_model_clear_startup_seed_request, session_model_clear_startup_seed_result, 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_disable_request, session_plugins_enable_request, session_plugins_install_request, session_plugins_marketplaces_refresh_request, session_plugins_reload_request, session_provider_get_endpoint_request, session_prune_result, session_quota_delegate_warning, session_quota_get_result, session_quota_plan_tier, session_quota_projection, session_quota_refresh_result, session_quota_snapshot, session_quota_take_warnings_result, sessions_bulk_delete_request, sessions_check_in_use_request, sessions_check_in_use_result, sessions_client_metadata_entry, sessions_close_request, sessions_close_result, sessions_create_workspace_request, sessions_create_workspace_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_client_metadata_request, sessions_get_client_metadata_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_load_workspace_request, sessions_load_workspace_result, 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_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, sessions_update_workspace_fields_request, sessions_update_workspace_fields_result, session_telemetry_engagement, session_update_options_params, session_update_options_result, session_usage_set_code_changes_request, session_visibility_status, session_workflow_pause_at_checkpoint_result, session_working_directory_context, session_working_directory_context_host_type, session_working_directory_context_with_client, 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_apply_install_request, skill_apply_uninstall_request, skill_discovery_path, skill_discovery_path_list, skill_discovery_scope, skill_installation_failure_reason, skill_installation_file_review, skill_installation_location, skill_installation_management_outcome, skill_installation_management_result, skill_installation_operation_request, skill_installation_operation_status, skill_installation_outcome, skill_installation_ownership_state, skill_installation_result, skill_installation_review, skill_installation_scope, skill_installation_session_state, skill_installation_source, skill_installations_request, skill_installation_summary, skill_install_plan, skill_list, skill_plan_install_request, skill_plan_uninstall_request, 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, skill_set_enabled_request, skills_get_discovery_paths_request, skills_get_invoked_result, skills_invoked_skill, skills_load_diagnostics, skill_uninstall_plan, 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, system_message_block, task_agent_info, task_agent_progress, task_client_active_status, task_client_execution_mode, task_client_info, task_client_owner, task_client_owner_kind, task_client_owner_presence, task_client_progress, task_client_status, task_client_type, task_client_update, task_complete_data, task_completion_decision, task_execution_mode, task_info, task_kind, 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_register_request, tasks_register_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_update_request, tasks_update_result, 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_pending_requests_request, 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, unsupported_enqueue_command_result, update_subagent_settings_request, usage_get_metrics_result, usage_set_code_changes_request, user_auth_info, user_requested_shell_command_result, user_setting_metadata, user_settings_get_result, user_settings_set_request, visibility_get_result, visibility_set_request, visibility_set_result, workflow_abort_request, workflow_ack_result, workflow_agent_options, workflow_agent_request, workflow_agent_result, workflow_agent_summary, workflow_cancel_request, workflow_current_phase, workflow_declared_limits, workflow_durable_operation, workflow_execute_request, workflow_execute_result, workflow_get_run_progress_request, workflow_get_run_request, workflow_journal_get_request, workflow_journal_get_result, workflow_journal_put_request, workflow_list_runs_request, workflow_list_runs_result, workflow_log_line, workflow_log_line_kind, workflow_log_request, workflow_pause_checkpoint_action, workflow_pause_checkpoint_request, workflow_pause_checkpoint_result, workflow_pause_info, workflow_pause_request, workflow_phase_observation, workflow_phase_status, workflow_progress_line, workflow_progress_page, workflow_resume_request, workflow_resume_result, workflow_run_consumed, workflow_run_detail, workflow_run_failure, workflow_run_failure_kind, workflow_run_limits, workflow_run_options, workflow_run_request, workflow_run_result, workflow_run_status, workflow_run_summary, workflow_run_terminal, workflow_tool_resume_request, workflow_tool_run_options, workflow_tool_run_request, workspace_diff_contents, 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_directory_request, 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_remove_path_request, workspaces_rename_path_request, workspaces_save_large_paste_request, workspaces_save_large_paste_result, workspaces_stat_file_request, workspaces_stat_file_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, connector_session_account_result, session_auth_info_result, session_context_attribution, session_context_info, subagent_settings, task_progress, workspace_summary) + return RPC(abort_request, abort_result, accepted_enqueue_command_result, account_all_users, account_auth_info, account_describe_auth_info_request, account_describe_auth_info_result, account_get_all_users_result, account_get_current_auth_result, account_get_gist_support_request, account_get_gist_support_result, account_get_quota_request, account_get_quota_result, account_get_token_env_var_result, account_get_web_url_request, account_get_web_url_result, account_kind, account_login_request, account_login_result, account_logout_request, account_logout_result, account_quota_snapshot, accounts_enumerate_request, accounts_get_request, accounts_set_request, account_status, 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_custom_agent_initial_model_decision_params, agents_custom_agent_initial_model_decision_result, agents_discover_request, agent_select_request, agent_select_result, agent_set_prompt_request, agents_get_available_builtins_request, agents_get_available_builtins_result, agents_get_builtin_definition_request, agents_get_builtin_definition_result, agents_get_builtin_listing_definition_request, agents_get_builtin_listing_definition_result, agents_get_builtins_result, agents_get_discovery_paths_request, api_key_auth_info, auth_enumerate_query, auth_enumerate_value, auth_identity, auth_identity_metadata, auth_info, auth_info_type, auth_login_account, auth_login_advance_request, auth_login_begin_request, auth_login_begun, auth_login_cancel_request, auth_login_result_dto, auth_login_result_status, auth_login_step, auth_read_query, auth_read_value, auth_status_dto, auth_validation_error, auth_validation_errors, auth_write, auth_write_result, autopilot_objective_credit_limit, autopilot_objective_get_state_result, autopilot_objective_state, autopilot_objective_status, auto_tier_descriptor, auto_tier_metadata, auto_tier_status, builtin_agent_summary, 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_agent_plugin_candidate, catalog_agent_plugin_candidate_kind, catalog_agent_plugin_candidate_provenance, catalog_agent_plugin_compatibility_tag, catalog_agent_plugin_component, catalog_agent_plugin_installability, catalog_agent_plugin_install_plan, catalog_agent_plugin_install_source, catalog_agent_plugin_marketplace_source, catalog_agent_plugin_marketplace_source_kind, catalog_agent_plugin_materialization_source, catalog_agent_plugin_materialization_source_kind, catalog_agent_plugin_media_type, catalog_agent_plugin_repository_source, catalog_agent_plugin_repository_source_kind, catalog_ai_skill_candidate, catalog_ai_skill_candidate_kind, catalog_ai_skill_candidate_provenance, catalog_ai_skill_installability, catalog_ai_skill_media_type, 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_materialization_reference, catalog_mcp_server_candidate, catalog_mcp_server_candidate_kind, 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_plugin_repository_source, catalog_policy_rejected_error, catalog_resource_identity, catalog_resource_version, catalog_search_page, catalog_search_pagination, catalog_search_request, catalog_search_result, catalog_search_succeeded, catalog_search_total_count_relation, catalog_selection_cancelled, catalog_selection_decision, catalog_selection_declined, catalog_selection_foreign, catalog_selection_invalid, catalog_selection_replayed, catalog_selection_request, catalog_selection_result, catalog_selection_selected, catalog_selection_stale, catalog_selection_timed_out, catalog_selection_wrong_kind, catalog_trust_eligibility, catalog_trust_provenance, catalog_trust_snapshot, catalog_trust_snapshot_absent, catalog_trust_snapshot_absent_status, catalog_trust_snapshot_current, catalog_trust_snapshot_current_status, catalog_trust_snapshot_downgraded, catalog_trust_snapshot_downgraded_status, catalog_trust_snapshot_malformed, catalog_trust_snapshot_malformed_status, catalog_trust_snapshot_revoked, catalog_trust_snapshot_revoked_status, catalog_trust_snapshot_schema_version, catalog_trust_snapshot_stale, catalog_trust_snapshot_stale_status, catalog_trust_snapshot_unsupported, catalog_trust_snapshot_unsupported_status, catalog_trust_source, catalog_trust_tier, 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, client_metadata, client_task_cancel_reason, client_task_cancel_request, client_task_cancel_result, 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, connector_account_request, connector_authorization_requirement, connector_authorization_scope, connector_availability, connector_capabilities, connector_catalog_entry, connector_catalog_result, connector_catalog_status, connector_connect_request, connector_connect_result, connector_continue_request, connector_disconnect_result, connector_discovery_account, connector_discovery_account_list, connector_discovery_account_request, connector_discovery_auth_info, connector_discovery_availability, connector_discovery_capabilities, connector_discovery_catalog_entry, connector_discovery_catalog_result, connector_mcp_status, connector_reconcile_request, connector_runtime_status, connector_session_account, connector_status, connect_remote_session_params, connect_request, connect_result, content_exclusion_check_paths_request, content_exclusion_check_paths_result, content_exclusion_path_check, content_exclusion_validate_policies_request, content_exclusion_validate_policies_result, content_filter_mode, context_heaviest_message, copilot_api_token_auth_info, copilot_user_response, copilot_user_response_endpoints, copilot_user_response_quota_snapshots, copilot_user_response_quota_snapshots_chat, copilot_user_response_quota_snapshots_completions, copilot_user_response_quota_snapshots_premium_interactions, current_model, current_tool_metadata, customization_reload_outcome, customization_reload_status, customization_reload_subsystem, customizations_reload_result, 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, diagnostic_cursor_status, diagnostic_entry, diagnostic_log_level, diagnostics_configuration, diagnostics_configure_request, diagnostic_severity, diagnostic_source, diagnostic_sources_configuration, diagnostics_read_request, diagnostics_read_result, 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, discovered_model, discovered_model_list, enqueue_command_params, enqueue_command_result, env_auth_info, environment_capabilities, environment_kind, environments_delete_request, environments_delete_result, environments_get_request, environments_get_result, environments_list_request, environments_list_result, 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, feature_flag_availability, feature_flag_metadata, feature_flags_metadata_result, feature_flags_resolve_request, feature_flags_resolve_result, feature_flags_resolve_startup_gates_request, feature_flags_resolve_startup_gates_result, filter_mapping, fleet_start_request, fleet_start_result, folder_trust_add_params, folder_trust_check_params, folder_trust_check_result, folder_trust_is_trusted_params, force_login_orgs_decision, gh_cli_auth_info, git_current_branch_remote_result, git_cwd_request, git_hub_environment, git_hub_owner_option, git_hub_owners_cancel_request, git_hub_owners_cancel_result, git_hub_owners_list_request, git_hub_owners_list_result, git_hub_owners_request_id_result, git_hub_repository_at_path_request, git_hub_repository_at_path_result, git_hub_repository_identity, 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, git_remote_repository, git_repos_from_remotes_request, git_repos_from_remotes_result, global_state_load_for_config_dir_request, global_state_load_result, global_state_write_key_request, 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, host_configuration, host_dispose_request, host_empty_result, host_environment_credentials, host_exited_notification, host_exit_reason, host_git_hub_environment_options, host_list_sessions_request, host_list_sessions_result, host_local_server_configuration, host_local_server_options, host_publish_session_request, host_publish_session_result, host_ready_request, host_register_session_request, host_session_create_callback, host_session_create_request, host_session_create_result, host_session_released_notification, host_session_release_request, host_session_summary, host_start_request, host_start_result, installation_catalogue_identity, installation_confirmation_request, installation_confirmation_response, installation_decision, installation_review, 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_repo_sources_request, instructions_discover_request, instructions_get_discovery_paths_request, instructions_get_sources_result, instructions_init_prompt_result, instruction_source, instruction_source_location, instruction_source_type, interrupt_main_turn_request, interrupt_main_turn_result, json_schema_response_format, 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, location_permissions_cleanup_result, logged_in_user, login_provider_kind, log_request, log_result, lsp_initialize_request, managed_mcp_server_config, managed_permission_evaluation, managed_permission_operation, managed_permission_operation_kind, managed_permissions_evaluate_request, managed_permissions_evaluate_result, managed_permission_verdict, managed_plugin_retry_entry, managed_plugin_retry_status, managed_setting_meta, managed_settings_apply_controls_params, managed_settings_apply_controls_result, managed_settings_bypass_disable_source, managed_settings_bypass_policy_resolution, managed_settings_channel, managed_settings_compose_layer, managed_settings_compose_request, managed_settings_compose_result, managed_settings_diagnostic, managed_settings_diagnostic_severity, managed_settings_ingest_params, managed_settings_ingest_result, managed_settings_layer, managed_settings_load_remote_control_params, managed_settings_load_remote_control_result, managed_settings_log_level, managed_settings_log_message, managed_settings_meta, managed_settings_parse_client_value_params, managed_settings_parse_client_value_result, managed_settings_policy_helper_authority, managed_settings_policy_helper_reason, managed_settings_read_device_result, managed_settings_read_result, managed_settings_resolve_bypass_disable_transition_params, managed_settings_resolve_bypass_disable_transition_result, managed_settings_resolved_data, managed_settings_resolve_device_force_login_orgs_gate_params, managed_settings_resolve_effective_value_params, managed_settings_resolve_effective_value_result, managed_settings_resolve_request, managed_settings_resolve_result, managed_settings_resolve_startup_policy_params, managed_settings_resolve_startup_policy_result, managed_settings_retain_policy_params, managed_settings_schema_result, managed_settings_server_policy_cache_mode, managed_settings_server_policy_resolution, managed_settings_validate_request, managed_settings_validate_result, managed_settings_values, 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_apply_install_request, mcp_apply_uninstall_request, 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_configured_server, mcp_configured_server_list, mcp_configured_server_state, mcp_configure_git_hub_request, mcp_configure_git_hub_result, mcp_diagnostic_details, mcp_diagnostic_direction, mcp_diagnostic_kind, mcp_diagnostic_source_configuration, 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_installation_failure_reason, mcp_installation_input, mcp_installation_management_outcome, mcp_installation_management_result, mcp_installation_operation_request, mcp_installation_operation_status, mcp_installation_outcome, mcp_installation_remote_configuration, mcp_installation_result, mcp_installation_review, mcp_installation_secret, mcp_installation_secret_storage, mcp_installations_request, mcp_installation_state, mcp_installation_summary, mcp_install_plan, mcp_is_server_running_request, mcp_is_server_running_result, mcp_list_builtin_server_names_result, mcp_list_tools_request, mcp_list_tools_result, mcp_oauth_authentication_state_changed_request, mcp_oauth_cancel_login_request, mcp_oauth_cancel_login_result, mcp_oauth_complete_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_prepare_login_request, mcp_oauth_prepare_login_result, mcp_oauth_probe_needs_auth_reason, mcp_oauth_probe_request, mcp_oauth_probe_result, mcp_oauth_respond_request, mcp_oauth_respond_result, mcp_owned_oauth_login_status, 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_uninstall_request, mcp_plan_value_category, mcp_prepared_install, mcp_prepare_install_request, mcp_prompt, mcp_prompt_argument, mcp_prompt_icon, mcp_prompt_message, mcp_prompt_role, mcp_prompts_get_request, mcp_prompts_get_result, mcp_prompts_list_request, mcp_prompts_list_result, mcp_register_external_client_request, mcp_registry_cancel_request, mcp_registry_cancel_result, mcp_registry_request_id_result, mcp_registry_search_request, mcp_registry_search_result, 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_server_ownership, mcp_set_env_value_mode_details, mcp_set_env_value_mode_params, mcp_set_env_value_mode_result, mcp_should_exclude_git_hub_tools_request, mcp_should_exclude_git_hub_tools_result, mcp_source_file, mcp_source_plugin, mcp_source_ref, 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_uninstall_plan, 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, metadata_update_client_metadata_request, model, model_apply_startup_overlay_request, model_artifact_details, 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_clear_startup_seed_request, model_clear_startup_seed_result, model_get_reasoning_summaries_request, model_get_reasoning_summaries_result, 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_provider_adapter_catalog, model_provider_adapter_descriptor, model_provider_adapter_operation_descriptor, model_provider_attribution, model_provider_automatic_discovery_mode, model_provider_automatic_discovery_policy, model_provider_configuration_disposition, model_provider_configuration_plan, model_provider_descriptor, model_provider_discover_request, model_provider_discover_result, model_provider_discovery_network_scope, model_provider_get_status_request, model_provider_instance, model_provider_instance_reference, model_provider_models_list_request, model_provider_operation_outcome, model_provider_operation_outcome_code, model_provider_prepare_configuration_request, model_provider_provenance, model_provider_provenance_source, model_provider_status, model_provider_warning, model_set_allowed_models_request, model_set_allowed_models_result, model_set_reasoning_effort_request, model_set_reasoning_effort_result, models_list_request, model_switch_auto_tier_request, model_switch_auto_tier_result, model_switch_auto_tier_status, 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_external_tool_request, pending_external_tool_request_list, 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_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_workflow, 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_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_workflow, permission_decision_approve_for_session_approval_write, permission_decision_approve_once, permission_decision_approve_permanently, permission_decision_approve_read_only_for_session, 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_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_evaluate_managed_policy_params, permissions_evaluate_managed_policy_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_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_workflow, 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, persistence_write_raw_json_locked_request, persistence_write_raw_json_locked_result, 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_install_staging_mode, 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_append_mode, protocol_customize_mode, protocol_external_tool_defer, protocol_external_tool_definition, protocol_marker_section_override, protocol_replace_mode, protocol_section_override, protocol_static_section_action, protocol_static_section_override, protocol_system_message_append_config, protocol_system_message_config, protocol_system_message_customize_config, protocol_system_message_replace_config, provider_add_request, provider_add_result, provider_config, provider_config_azure, provider_config_model_provider, provider_config_transport, provider_config_type, provider_config_wire_api, provider_descriptor, provider_endpoint, provider_endpoint_transport, provider_endpoint_type, provider_endpoint_wire_api, provider_get_endpoint_request, provider_model_config, provider_session_token, provider_sync_request, provider_sync_result, provider_token_acquire_request, provider_token_acquire_result, provider_withdraw_request, provider_withdraw_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_append_steering_request, 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, queue_withdraw_message_request, queue_withdraw_message_result, quota_take_warnings_result, quota_warning_projection, register_event_interest_params, register_event_interest_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, repo_settings_edit_request, repo_settings_edit_scope, repo_settings_read_raw_request, repo_settings_read_raw_result, repo_settings_write_all_request, repo_settings_write_key_request, response_format, sandbox_apply_defaults_request, sandbox_check_enforcement_command_result, sandbox_check_platform_support_result, sandbox_check_windows_host_loopback_support_result, 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_credentials_config, sandbox_credential_suggestion, sandbox_credential_suggestions_result, sandbox_disable_for_session_request, sandbox_disable_for_session_result, sandbox_docs_url_result, sandbox_effective_for_optional_params, sandbox_effective_for_optional_result, sandbox_enforcement_command_allowed_params, sandbox_enforcement_status, sandbox_filter_picker_candidates_params, sandbox_filter_picker_candidates_result, sandbox_get_unsupported_message_result, sandbox_get_unsupported_setting_disabled_notice_result, sandbox_grant_path_for_request_request, sandbox_grant_path_for_request_result, sandbox_host_capability, sandbox_host_capability_name, sandbox_host_support, sandbox_masked_env_var, sandbox_picker_candidate, sandbox_prepare_picker_policy_params, sandbox_prepare_picker_policy_result, sandbox_proxy_ca_check_installation_support_result, sandbox_proxy_ca_check_relevance_result, sandbox_proxy_ca_create_result, sandbox_proxy_ca_request, sandbox_proxy_ca_state, sandbox_proxy_ca_status, sandbox_proxy_secret_get_result, sandbox_proxy_secret_location, sandbox_proxy_secret_save_request, sandbox_proxy_secret_save_result, sandbox_resolve_floored_config_params, sandbox_resolve_floored_config_result, sandbox_session_change, sandbox_settings_apply_defaults_result, sandbox_unsupported_startup_notice_params, sandbox_unsupported_startup_notice_result, 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_connected_ide_info, 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_bytes_request, session_fs_read_file_bytes_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_bytes_request, 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_mcp_oauth_cancel_login_request, session_mcp_oauth_cancel_login_result, session_mcp_oauth_prepare_login_request, session_mcp_oauth_prepare_login_result, session_mcp_set_connected_ide_info_params, session_metadata_snapshot, session_mode, session_model_clear_startup_seed_request, session_model_clear_startup_seed_result, 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_disable_request, session_plugins_enable_request, session_plugins_install_request, session_plugins_marketplaces_refresh_request, session_plugins_reload_request, session_plugins_retry_managed_request, session_plugins_retry_managed_result, session_provider_get_endpoint_request, session_prune_result, session_quota_delegate_warning, session_quota_get_result, session_quota_plan_tier, session_quota_projection, session_quota_refresh_result, session_quota_snapshot, session_quota_take_warnings_result, sessions_bulk_delete_request, sessions_check_in_use_request, sessions_check_in_use_result, sessions_client_metadata_entry, sessions_close_request, sessions_close_result, sessions_create_workspace_request, sessions_create_workspace_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_client_metadata_request, sessions_get_client_metadata_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_load_workspace_request, sessions_load_workspace_result, 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_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, sessions_update_workspace_fields_request, sessions_update_workspace_fields_result, session_telemetry_engagement, session_update_options_params, session_update_options_result, session_usage_set_code_changes_request, session_visibility_status, session_workflow_pause_at_checkpoint_result, session_working_directory_context, session_working_directory_context_host_type, session_working_directory_context_with_client, settable_auth_info, settable_token_auth_info, settings_edit_operation, settings_edit_result, settings_edit_status, 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_apply_install_request, skill_apply_uninstall_request, skill_discovery_path, skill_discovery_path_list, skill_discovery_scope, skill_installation_failure_reason, skill_installation_file_review, skill_installation_location, skill_installation_management_outcome, skill_installation_management_result, skill_installation_operation_request, skill_installation_operation_status, skill_installation_outcome, skill_installation_ownership_state, skill_installation_result, skill_installation_review, skill_installation_scope, skill_installation_session_state, skill_installation_source, skill_installations_request, skill_installation_summary, skill_install_plan, skill_list, skill_plan_install_from_reference_request, skill_plan_install_request, skill_plan_uninstall_request, 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, skill_set_enabled_request, skills_get_discovery_paths_request, skills_get_invoked_result, skills_invoked_skill, skills_load_diagnostics, skill_uninstall_plan, 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, staff_log_level_migration_status, staff_update_channel_migration_status, subagent_settings_entry, subagent_settings_entry_context_tier, system_message_block, task_agent_info, task_agent_progress, task_client_active_status, task_client_execution_mode, task_client_info, task_client_owner, task_client_owner_kind, task_client_owner_presence, task_client_progress, task_client_status, task_client_type, task_client_update, task_complete_data, task_completion_decision, task_execution_mode, task_info, task_kind, 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_register_request, tasks_register_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_update_request, tasks_update_result, 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_pending_requests_request, 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, unsupported_enqueue_command_result, update_subagent_settings_request, usage_get_metrics_result, usage_set_code_changes_request, user_auth_info, user_requested_shell_command_result, user_setting_metadata, user_settings_apply_top_key_request, user_settings_apply_top_key_result, user_settings_build_runtime_request, user_settings_build_runtime_result, user_settings_edit_request, user_settings_get_paths_request, user_settings_get_paths_result, user_settings_get_result, user_settings_governance_keys_result, user_settings_host_context, user_settings_load_and_merge_repo_request, user_settings_load_and_merge_repo_result, user_settings_load_for_config_dir_request, user_settings_load_for_config_dir_result, user_settings_load_for_edit_result, user_settings_load_repo_request, user_settings_load_repo_result, user_settings_load_with_warning_result, user_settings_merge_managed_and_user_params, user_settings_merge_managed_and_user_result, user_settings_merge_repo_value_params, user_settings_merge_repo_value_result, user_settings_migrate_obsolete_on_disk_request, user_settings_migrate_obsolete_on_disk_result, user_settings_migrate_obsolete_on_disk_status, user_settings_migrate_staff_log_level_once_request, user_settings_migrate_staff_log_level_once_result, user_settings_migrate_staff_update_channel_once_result, user_settings_normalize_request, user_settings_normalize_result, user_settings_path_result, user_settings_problem, user_settings_problem_location, user_settings_problem_source, user_settings_problems_request, user_settings_problems_result, user_settings_reset_staff_model_selection_once_request, user_settings_reset_staff_model_selection_once_result, user_settings_set_hook_disabled_request, user_settings_set_hook_disabled_result, user_settings_set_request, user_settings_unknown_keys_warning_request, user_settings_unknown_keys_warning_result, user_settings_update_request, user_settings_update_result, user_settings_validate_request, user_settings_validate_result, user_settings_validation_issue, user_settings_write_all_request, visibility_get_result, visibility_set_request, visibility_set_result, workflow_abort_request, workflow_ack_result, workflow_agent_options, workflow_agent_request, workflow_agent_result, workflow_agent_summary, workflow_cancel_request, workflow_current_phase, workflow_declared_limits, workflow_durable_operation, workflow_execute_request, workflow_execute_result, workflow_get_run_progress_request, workflow_get_run_request, workflow_journal_get_request, workflow_journal_get_result, workflow_journal_put_request, workflow_list_runs_request, workflow_list_runs_result, workflow_log_line, workflow_log_line_kind, workflow_log_request, workflow_pause_checkpoint_action, workflow_pause_checkpoint_request, workflow_pause_checkpoint_result, workflow_pause_info, workflow_pause_request, workflow_phase_observation, workflow_phase_status, workflow_progress_line, workflow_progress_page, workflow_resume_request, workflow_resume_result, workflow_run_consumed, workflow_run_detail, workflow_run_failure, workflow_run_failure_kind, workflow_run_limits, workflow_run_options, workflow_run_request, workflow_run_result, workflow_run_status, workflow_run_summary, workflow_run_terminal, workflow_tool_resume_request, workflow_tool_run_options, workflow_tool_run_request, workspace_diff_contents, 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_directory_request, 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_remove_path_request, workspaces_rename_path_request, workspaces_save_large_paste_request, workspaces_save_large_paste_result, workspaces_stat_file_request, workspaces_stat_file_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, connector_session_account_result, session_auth_info_result, session_context_attribution, session_context_info, subagent_settings, task_progress, workspace_summary) def to_dict(self) -> dict: result: dict = {} @@ -52873,10 +58360,17 @@ def to_dict(self) -> dict: result["AcceptedEnqueueCommandResult"] = to_class(AcceptedEnqueueCommandResult, self.accepted_enqueue_command_result) result["AccountAllUsers"] = to_class(AccountAllUsers, self.account_all_users) result["AccountAuthInfo"] = to_class(AccountAuthInfo, self.account_auth_info) + result["AccountDescribeAuthInfoRequest"] = to_class(_AccountDescribeAuthInfoRequest, self.account_describe_auth_info_request) + result["AccountDescribeAuthInfoResult"] = to_class(_AccountDescribeAuthInfoResult, self.account_describe_auth_info_result) result["AccountGetAllUsersResult"] = from_list(lambda x: to_class(AccountAllUsers, x), self.account_get_all_users_result) result["AccountGetCurrentAuthResult"] = to_class(AccountGetCurrentAuthResult, self.account_get_current_auth_result) + result["AccountGetGistSupportRequest"] = to_class(_AccountGetGistSupportRequest, self.account_get_gist_support_request) + result["AccountGetGistSupportResult"] = to_class(_AccountGetGistSupportResult, self.account_get_gist_support_result) result["AccountGetQuotaRequest"] = to_class(AccountGetQuotaRequest, self.account_get_quota_request) result["AccountGetQuotaResult"] = to_class(AccountGetQuotaResult, self.account_get_quota_result) + result["AccountGetTokenEnvVarResult"] = to_class(_AccountGetTokenEnvVarResult, self.account_get_token_env_var_result) + result["AccountGetWebUrlRequest"] = to_class(_AccountGetWebURLRequest, self.account_get_web_url_request) + result["AccountGetWebUrlResult"] = to_class(_AccountGetWebURLResult, self.account_get_web_url_result) result["AccountKind"] = to_enum(AccountKind, self.account_kind) result["AccountLoginRequest"] = to_class(AccountLoginRequest, self.account_login_request) result["AccountLoginResult"] = to_class(AccountLoginResult, self.account_login_result) @@ -52992,7 +58486,17 @@ def to_dict(self) -> dict: result["CatalogAgentPluginCandidateKind"] = to_enum(CatalogAgentPluginCandidateKind, self.catalog_agent_plugin_candidate_kind) result["CatalogAgentPluginCandidateProvenance"] = to_class(CatalogAgentPluginCandidateProvenance, self.catalog_agent_plugin_candidate_provenance) result["CatalogAgentPluginCompatibilityTag"] = to_enum(CatalogAgentPluginCompatibilityTag, self.catalog_agent_plugin_compatibility_tag) + result["CatalogAgentPluginComponent"] = to_enum(CatalogAgentPluginComponent, self.catalog_agent_plugin_component) + result["CatalogAgentPluginInstallability"] = to_enum(CatalogAgentPluginInstallability, self.catalog_agent_plugin_installability) + result["CatalogAgentPluginInstallPlan"] = to_class(CatalogAgentPluginInstallPlan, self.catalog_agent_plugin_install_plan) + result["CatalogAgentPluginInstallSource"] = (self.catalog_agent_plugin_install_source).to_dict() + result["CatalogAgentPluginMarketplaceSource"] = to_class(CatalogAgentPluginMarketplaceSource, self.catalog_agent_plugin_marketplace_source) + result["CatalogAgentPluginMarketplaceSourceKind"] = to_enum(CatalogAgentPluginMarketplaceSourceKind, self.catalog_agent_plugin_marketplace_source_kind) + result["CatalogAgentPluginMaterializationSource"] = to_class(CatalogAgentPluginMaterializationSource, self.catalog_agent_plugin_materialization_source) + result["CatalogAgentPluginMaterializationSourceKind"] = to_enum(CatalogAgentPluginMaterializationSourceKind, self.catalog_agent_plugin_materialization_source_kind) result["CatalogAgentPluginMediaType"] = to_enum(CatalogAgentPluginMediaType, self.catalog_agent_plugin_media_type) + result["CatalogAgentPluginRepositorySource"] = to_class(CatalogAgentPluginRepositorySource, self.catalog_agent_plugin_repository_source) + result["CatalogAgentPluginRepositorySourceKind"] = to_enum(CatalogAgentPluginRepositorySourceKind, self.catalog_agent_plugin_repository_source_kind) result["CatalogAiSkillCandidate"] = to_class(CatalogAISkillCandidate, self.catalog_ai_skill_candidate) result["CatalogAiSkillCandidateKind"] = to_enum(CatalogAISkillCandidateKind, self.catalog_ai_skill_candidate_kind) result["CatalogAiSkillCandidateProvenance"] = to_class(CatalogAISkillCandidateProvenance, self.catalog_ai_skill_candidate_provenance) @@ -53017,6 +58521,7 @@ def to_dict(self) -> dict: 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["CatalogMaterializationReference"] = to_class(CatalogMaterializationReference, self.catalog_materialization_reference) result["CatalogMcpServerCandidate"] = to_class(CatalogMCPServerCandidate, self.catalog_mcp_server_candidate) result["CatalogMcpServerCandidateKind"] = to_enum(CatalogMCPServerCandidateKind, self.catalog_mcp_server_candidate_kind) result["CatalogMcpServerCandidateProvenance"] = to_class(CatalogMCPServerCandidateProvenance, self.catalog_mcp_server_candidate_provenance) @@ -53132,6 +58637,8 @@ def to_dict(self) -> dict: result["ContentExclusionCheckPathsRequest"] = to_class(ContentExclusionCheckPathsRequest, self.content_exclusion_check_paths_request) result["ContentExclusionCheckPathsResult"] = to_class(ContentExclusionCheckPathsResult, self.content_exclusion_check_paths_result) result["ContentExclusionPathCheck"] = to_class(ContentExclusionPathCheck, self.content_exclusion_path_check) + result["ContentExclusionValidatePoliciesRequest"] = to_class(ContentExclusionValidatePoliciesRequest, self.content_exclusion_validate_policies_request) + result["ContentExclusionValidatePoliciesResult"] = to_class(ContentExclusionValidatePoliciesResult, self.content_exclusion_validate_policies_result) result["ContentFilterMode"] = to_enum(ContentFilterMode, self.content_filter_mode) result["ContextHeaviestMessage"] = to_class(ContextHeaviestMessage, self.context_heaviest_message) result["CopilotApiTokenAuthInfo"] = to_class(CopilotAPITokenAuthInfo, self.copilot_api_token_auth_info) @@ -53227,12 +58734,21 @@ def to_dict(self) -> dict: result["ExternalToolTextResultForLlmContentShellExit"] = to_class(ExternalToolTextResultForLlmContentShellExit, self.external_tool_text_result_for_llm_content_shell_exit) result["ExternalToolTextResultForLlmContentTerminal"] = to_class(ExternalToolTextResultForLlmContentTerminal, self.external_tool_text_result_for_llm_content_terminal) result["ExternalToolTextResultForLlmContentText"] = to_class(ExternalToolTextResultForLlmContentText, self.external_tool_text_result_for_llm_content_text) + result["FeatureFlagAvailability"] = to_enum(_FeatureFlagAvailability, self.feature_flag_availability) + result["FeatureFlagMetadata"] = to_class(_FeatureFlagMetadata, self.feature_flag_metadata) + result["FeatureFlagsMetadataResult"] = to_class(_FeatureFlagsMetadataResult, self.feature_flags_metadata_result) + result["FeatureFlagsResolveRequest"] = to_class(_FeatureFlagsResolveRequest, self.feature_flags_resolve_request) + result["FeatureFlagsResolveResult"] = from_dict(from_bool, self.feature_flags_resolve_result) + result["FeatureFlagsResolveStartupGatesRequest"] = to_class(_FeatureFlagsResolveStartupGatesRequest, self.feature_flags_resolve_startup_gates_request) + result["FeatureFlagsResolveStartupGatesResult"] = from_dict(from_bool, self.feature_flags_resolve_startup_gates_result) 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) result["FolderTrustAddParams"] = to_class(FolderTrustAddParams, self.folder_trust_add_params) result["FolderTrustCheckParams"] = to_class(FolderTrustCheckParams, self.folder_trust_check_params) result["FolderTrustCheckResult"] = to_class(FolderTrustCheckResult, self.folder_trust_check_result) + result["FolderTrustIsTrustedParams"] = to_class(_FolderTrustIsTrustedParams, self.folder_trust_is_trusted_params) + result["ForceLoginOrgsDecision"] = to_class(_ForceLoginOrgsDecision, self.force_login_orgs_decision) result["GhCliAuthInfo"] = to_class(GhCLIAuthInfo, self.gh_cli_auth_info) result["GitCurrentBranchRemoteResult"] = to_class(_GitCurrentBranchRemoteResult, self.git_current_branch_remote_result) result["GitCwdRequest"] = to_class(_GitCwdRequest, self.git_cwd_request) @@ -53242,7 +58758,7 @@ def to_dict(self) -> dict: result["GitHubOwnersCancelResult"] = to_class(_GitHubOwnersCancelResult, self.git_hub_owners_cancel_result) result["GitHubOwnersListRequest"] = to_class(_GitHubOwnersListRequest, self.git_hub_owners_list_request) result["GitHubOwnersListResult"] = to_class(_GitHubOwnersListResult, self.git_hub_owners_list_result) - result["GitHubOwnersRequestIdResult"] = to_class(GitHubOwnersRequestIDResult, self.git_hub_owners_request_id_result) + result["GitHubOwnersRequestIdResult"] = to_class(_GitHubOwnersRequestIDResult, self.git_hub_owners_request_id_result) result["GitHubRepositoryAtPathRequest"] = to_class(_GitHubRepositoryAtPathRequest, self.git_hub_repository_at_path_request) result["GitHubRepositoryAtPathResult"] = to_class(_GitHubRepositoryAtPathResult, self.git_hub_repository_at_path_result) result["GitHubRepositoryIdentity"] = to_class(GitHubRepositoryIdentity, self.git_hub_repository_identity) @@ -53328,9 +58844,11 @@ def to_dict(self) -> dict: result["InstructionDiscoveryPathKind"] = to_enum(DebugCollectLogsEntryKind, self.instruction_discovery_path_kind) result["InstructionDiscoveryPathList"] = to_class(InstructionDiscoveryPathList, self.instruction_discovery_path_list) result["InstructionDiscoveryPathLocation"] = to_enum(InstructionLocation, self.instruction_discovery_path_location) + result["InstructionsDiscoverRepoSourcesRequest"] = to_class(_InstructionsDiscoverRepoSourcesRequest, self.instructions_discover_repo_sources_request) result["InstructionsDiscoverRequest"] = to_class(InstructionsDiscoverRequest, self.instructions_discover_request) result["InstructionsGetDiscoveryPathsRequest"] = to_class(InstructionsGetDiscoveryPathsRequest, self.instructions_get_discovery_paths_request) result["InstructionsGetSourcesResult"] = to_class(InstructionsGetSourcesResult, self.instructions_get_sources_result) + result["InstructionsInitPromptResult"] = to_class(_InstructionsInitPromptResult, self.instructions_init_prompt_result) result["InstructionSource"] = to_class(InstructionSource, self.instruction_source) result["InstructionSourceLocation"] = to_enum(InstructionLocation, self.instruction_source_location) result["InstructionSourceType"] = to_enum(InstructionSourceType, self.instruction_source_type) @@ -53350,26 +58868,60 @@ def to_dict(self) -> dict: result["LlmInferenceHttpResponseStartResult"] = to_class(LlmInferenceHTTPResponseStartResult, self.llm_inference_http_response_start_result) result["LlmInferenceSetProviderResult"] = to_class(LlmInferenceSetProviderResult, self.llm_inference_set_provider_result) result["LocalSessionMetadataValue"] = to_class(LocalSessionMetadataValue, self.local_session_metadata_value) + result["LocationPermissionsCleanupResult"] = to_class(_LocationPermissionsCleanupResult, self.location_permissions_cleanup_result) result["LoggedInUser"] = to_class(_LoggedInUser, self.logged_in_user) result["LoginProviderKind"] = to_enum(LoginProviderKind, self.login_provider_kind) 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["ManagedMcpServerConfig"] = to_class(ManagedMCPServerConfig, self.managed_mcp_server_config) + result["ManagedPermissionEvaluation"] = to_class(ManagedPermissionEvaluation, self.managed_permission_evaluation) + result["ManagedPermissionOperation"] = to_class(ManagedPermissionOperation, self.managed_permission_operation) + result["ManagedPermissionOperationKind"] = to_enum(ManagedPermissionOperationKind, self.managed_permission_operation_kind) + result["ManagedPermissionsEvaluateRequest"] = to_class(ManagedPermissionsEvaluateRequest, self.managed_permissions_evaluate_request) + result["ManagedPermissionsEvaluateResult"] = to_class(ManagedPermissionsEvaluateResult, self.managed_permissions_evaluate_result) + result["ManagedPermissionVerdict"] = to_enum(ManagedPermissionVerdict, self.managed_permission_verdict) + result["ManagedPluginRetryEntry"] = to_class(ManagedPluginRetryEntry, self.managed_plugin_retry_entry) + result["ManagedPluginRetryStatus"] = to_enum(ManagedPluginRetryStatus, self.managed_plugin_retry_status) result["ManagedSettingMeta"] = to_class(ManagedSettingMeta, self.managed_setting_meta) + result["ManagedSettingsApplyControlsParams"] = to_class(_ManagedSettingsApplyControlsParams, self.managed_settings_apply_controls_params) + result["ManagedSettingsApplyControlsResult"] = to_class(_ManagedSettingsApplyControlsResult, self.managed_settings_apply_controls_result) + result["ManagedSettingsBypassDisableSource"] = to_enum(_ManagedSettingsBypassDisableSource, self.managed_settings_bypass_disable_source) + result["ManagedSettingsBypassPolicyResolution"] = to_enum(_ManagedSettingsBypassPolicyResolution, self.managed_settings_bypass_policy_resolution) result["ManagedSettingsChannel"] = to_enum(ManagedSettingsChannel, self.managed_settings_channel) result["ManagedSettingsComposeLayer"] = to_class(ManagedSettingsComposeLayer, self.managed_settings_compose_layer) result["ManagedSettingsComposeRequest"] = to_class(ManagedSettingsComposeRequest, self.managed_settings_compose_request) result["ManagedSettingsComposeResult"] = to_class(ManagedSettingsComposeResult, self.managed_settings_compose_result) result["ManagedSettingsDiagnostic"] = to_class(ManagedSettingsDiagnostic, self.managed_settings_diagnostic) result["ManagedSettingsDiagnosticSeverity"] = to_enum(ManagedSettingsDiagnosticSeverity, self.managed_settings_diagnostic_severity) + result["ManagedSettingsIngestParams"] = to_class(_ManagedSettingsIngestParams, self.managed_settings_ingest_params) + result["ManagedSettingsIngestResult"] = to_class(_ManagedSettingsIngestResult, self.managed_settings_ingest_result) result["ManagedSettingsLayer"] = to_class(ManagedSettingsLayer, self.managed_settings_layer) + result["ManagedSettingsLoadRemoteControlParams"] = to_class(_ManagedSettingsLoadRemoteControlParams, self.managed_settings_load_remote_control_params) + result["ManagedSettingsLoadRemoteControlResult"] = to_class(_ManagedSettingsLoadRemoteControlResult, self.managed_settings_load_remote_control_result) + result["ManagedSettingsLogLevel"] = to_enum(_ManagedSettingsLogLevel, self.managed_settings_log_level) + result["ManagedSettingsLogMessage"] = to_class(_ManagedSettingsLogMessage, self.managed_settings_log_message) result["ManagedSettingsMeta"] = to_class(ManagedSettingsMeta, self.managed_settings_meta) + result["ManagedSettingsParseClientValueParams"] = to_class(_ManagedSettingsParseClientValueParams, self.managed_settings_parse_client_value_params) + result["ManagedSettingsParseClientValueResult"] = to_class(_ManagedSettingsParseClientValueResult, self.managed_settings_parse_client_value_result) + result["ManagedSettingsPolicyHelperAuthority"] = to_enum(_ManagedSettingsPolicyHelperAuthority, self.managed_settings_policy_helper_authority) + result["ManagedSettingsPolicyHelperReason"] = to_enum(_ManagedSettingsPolicyHelperReason, self.managed_settings_policy_helper_reason) + result["ManagedSettingsReadDeviceResult"] = to_class(_ManagedSettingsReadDeviceResult, self.managed_settings_read_device_result) result["ManagedSettingsReadResult"] = to_class(ManagedSettingsReadResult, self.managed_settings_read_result) + result["ManagedSettingsResolveBypassDisableTransitionParams"] = to_class(_ManagedSettingsResolveBypassDisableTransitionParams, self.managed_settings_resolve_bypass_disable_transition_params) + result["ManagedSettingsResolveBypassDisableTransitionResult"] = to_class(_ManagedSettingsResolveBypassDisableTransitionResult, self.managed_settings_resolve_bypass_disable_transition_result) result["ManagedSettingsResolvedData"] = to_class(ManagedSettingsResolvedData, self.managed_settings_resolved_data) + result["ManagedSettingsResolveDeviceForceLoginOrgsGateParams"] = to_class(_ManagedSettingsResolveDeviceForceLoginOrgsGateParams, self.managed_settings_resolve_device_force_login_orgs_gate_params) + result["ManagedSettingsResolveEffectiveValueParams"] = to_class(_ManagedSettingsResolveEffectiveValueParams, self.managed_settings_resolve_effective_value_params) + result["ManagedSettingsResolveEffectiveValueResult"] = to_class(_ManagedSettingsResolveEffectiveValueResult, self.managed_settings_resolve_effective_value_result) result["ManagedSettingsResolveRequest"] = to_class(ManagedSettingsResolveRequest, self.managed_settings_resolve_request) result["ManagedSettingsResolveResult"] = to_class(ManagedSettingsResolveResult, self.managed_settings_resolve_result) + result["ManagedSettingsResolveStartupPolicyParams"] = to_class(_ManagedSettingsResolveStartupPolicyParams, self.managed_settings_resolve_startup_policy_params) + result["ManagedSettingsResolveStartupPolicyResult"] = to_class(_ManagedSettingsResolveStartupPolicyResult, self.managed_settings_resolve_startup_policy_result) + result["ManagedSettingsRetainPolicyParams"] = to_class(_ManagedSettingsRetainPolicyParams, self.managed_settings_retain_policy_params) result["ManagedSettingsSchemaResult"] = to_class(ManagedSettingsSchemaResult, self.managed_settings_schema_result) + result["ManagedSettingsServerPolicyCacheMode"] = to_enum(_ManagedSettingsServerPolicyCacheMode, self.managed_settings_server_policy_cache_mode) + result["ManagedSettingsServerPolicyResolution"] = to_enum(_ManagedSettingsServerPolicyResolution, self.managed_settings_server_policy_resolution) result["ManagedSettingsValidateRequest"] = to_class(ManagedSettingsValidateRequest, self.managed_settings_validate_request) result["ManagedSettingsValidateResult"] = to_class(ManagedSettingsValidateResult, self.managed_settings_validate_result) result["ManagedSettingsValues"] = to_class(ManagedSettingsValues, self.managed_settings_values) @@ -53417,7 +58969,7 @@ def to_dict(self) -> dict: result["McpConfiguredServer"] = to_class(MCPConfiguredServer, self.mcp_configured_server) result["McpConfiguredServerList"] = to_class(MCPConfiguredServerList, self.mcp_configured_server_list) result["McpConfiguredServerState"] = to_class(MCPConfiguredServerState, self.mcp_configured_server_state) - result["McpConfigureGitHubRequest"] = to_class(MCPConfigureGitHubRequest, self.mcp_configure_git_hub_request) + result["McpConfigureGitHubRequest"] = to_class(_MCPConfigureGitHubRequest, self.mcp_configure_git_hub_request) result["McpConfigureGitHubResult"] = to_class(MCPConfigureGitHubResult, self.mcp_configure_git_hub_result) result["McpDiagnosticDetails"] = to_class(MCPDiagnosticDetails, self.mcp_diagnostic_details) result["McpDiagnosticDirection"] = to_enum(MCPDiagnosticDirection, self.mcp_diagnostic_direction) @@ -53455,6 +59007,7 @@ def to_dict(self) -> dict: 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["McpListBuiltinServerNamesResult"] = to_class(_MCPListBuiltinServerNamesResult, self.mcp_list_builtin_server_names_result) result["McpListToolsRequest"] = to_class(MCPListToolsRequest, self.mcp_list_tools_request) result["McpListToolsResult"] = to_class(MCPListToolsResult, self.mcp_list_tools_result) result["McpOauthAuthenticationStateChangedRequest"] = to_class(MCPOauthAuthenticationStateChangedRequest, self.mcp_oauth_authentication_state_changed_request) @@ -53521,9 +59074,14 @@ def to_dict(self) -> dict: result["McpPromptsGetResult"] = to_class(MCPPromptsGetResult, self.mcp_prompts_get_result) result["McpPromptsListRequest"] = to_class(MCPPromptsListRequest, self.mcp_prompts_list_request) result["McpPromptsListResult"] = to_class(MCPPromptsListResult, self.mcp_prompts_list_result) - 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["McpRegisterExternalClientRequest"] = to_class(_MCPRegisterExternalClientRequest, self.mcp_register_external_client_request) + result["McpRegistryCancelRequest"] = to_class(_MCPRegistryCancelRequest, self.mcp_registry_cancel_request) + result["McpRegistryCancelResult"] = to_class(_MCPRegistryCancelResult, self.mcp_registry_cancel_result) + result["McpRegistryRequestIdResult"] = to_class(_MCPRegistryRequestIDResult, self.mcp_registry_request_id_result) + result["McpRegistrySearchRequest"] = to_class(_MCPRegistrySearchRequest, self.mcp_registry_search_request) + result["McpRegistrySearchResult"] = to_class(_MCPRegistrySearchResult, self.mcp_registry_search_result) + 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) result["McpResourceAnnotations"] = to_class(MCPResourceAnnotations, self.mcp_resource_annotations) @@ -53556,8 +59114,8 @@ def to_dict(self) -> dict: 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["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) @@ -53567,6 +59125,8 @@ def to_dict(self) -> dict: result["McpSetEnvValueModeDetails"] = to_enum(MCPSetEnvValueModeDetails, self.mcp_set_env_value_mode_details) result["McpSetEnvValueModeParams"] = to_class(MCPSetEnvValueModeParams, self.mcp_set_env_value_mode_params) result["McpSetEnvValueModeResult"] = to_class(MCPSetEnvValueModeResult, self.mcp_set_env_value_mode_result) + result["McpShouldExcludeGitHubToolsRequest"] = to_class(_MCPShouldExcludeGitHubToolsRequest, self.mcp_should_exclude_git_hub_tools_request) + result["McpShouldExcludeGitHubToolsResult"] = to_class(_MCPShouldExcludeGitHubToolsResult, self.mcp_should_exclude_git_hub_tools_result) result["McpSourceFile"] = to_class(MCPSourceFile, self.mcp_source_file) result["McpSourcePlugin"] = to_class(MCPSourcePlugin, self.mcp_source_plugin) result["McpSourceRef"] = to_class(MCPSourceRef, self.mcp_source_ref) @@ -53578,7 +59138,7 @@ def to_dict(self) -> dict: result["McpToolUi"] = to_class(MCPToolUI, self.mcp_tool_ui) result["McpToolUiVisibility"] = to_enum(MCPToolUIVisibility, self.mcp_tool_ui_visibility) result["McpUninstallPlan"] = to_class(MCPUninstallPlan, self.mcp_uninstall_plan) - result["McpUnregisterExternalClientRequest"] = to_class(MCPUnregisterExternalClientRequest, self.mcp_unregister_external_client_request) + result["McpUnregisterExternalClientRequest"] = to_class(_MCPUnregisterExternalClientRequest, self.mcp_unregister_external_client_request) result["MemoryConfiguration"] = to_class(MemoryConfiguration, self.memory_configuration) result["MetadataContextAttributionResult"] = to_class(MetadataContextAttributionResult, self.metadata_context_attribution_result) result["MetadataContextHeaviestMessagesRequest"] = to_class(MetadataContextHeaviestMessagesRequest, self.metadata_context_heaviest_messages_request) @@ -53614,6 +59174,8 @@ def to_dict(self) -> dict: result["ModelCapabilitiesSupports"] = to_class(ModelCapabilitiesSupports, self.model_capabilities_supports) result["ModelClearStartupSeedRequest"] = to_class(_ModelClearStartupSeedRequest, self.model_clear_startup_seed_request) result["ModelClearStartupSeedResult"] = to_class(_ModelClearStartupSeedResult, self.model_clear_startup_seed_result) + result["ModelGetReasoningSummariesRequest"] = to_class(ModelGetReasoningSummariesRequest, self.model_get_reasoning_summaries_request) + result["ModelGetReasoningSummariesResult"] = to_class(ModelGetReasoningSummariesResult, self.model_get_reasoning_summaries_result) result["ModelList"] = to_class(ModelList, self.model_list) result["ModelListRequest"] = self.model_list_request result["ModelMessage"] = to_class(ModelMessage, self.model_message) @@ -53749,6 +59311,8 @@ def to_dict(self) -> dict: result["PermissionsConfigureAdditionalContentExclusionPolicyScope"] = to_enum(AdditionalContentExclusionPolicyScope, self.permissions_configure_additional_content_exclusion_policy_scope) result["PermissionsConfigureParams"] = to_class(PermissionsConfigureParams, self.permissions_configure_params) result["PermissionsConfigureResult"] = to_class(PermissionsConfigureResult, self.permissions_configure_result) + result["PermissionsEvaluateManagedPolicyParams"] = to_class(_PermissionsEvaluateManagedPolicyParams, self.permissions_evaluate_managed_policy_params) + result["PermissionsEvaluateManagedPolicyResult"] = to_class(PermissionsEvaluateManagedPolicyResult, self.permissions_evaluate_managed_policy_result) result["PermissionsFolderTrustAddTrustedResult"] = to_class(PermissionsFolderTrustAddTrustedResult, self.permissions_folder_trust_add_trusted_result) result["PermissionsGetModeRequest"] = to_class(PermissionsGetModeRequest, self.permissions_get_mode_request) result["PermissionsGetModeResult"] = to_class(PermissionsGetModeResult, self.permissions_get_mode_result) @@ -53785,6 +59349,8 @@ def to_dict(self) -> dict: result["PermissionsUrlsSetUnrestrictedModeResult"] = to_class(PermissionsUrlsSetUnrestrictedModeResult, self.permissions_urls_set_unrestricted_mode_result) result["PermissionUrlsConfig"] = to_class(PermissionUrlsConfig, self.permission_urls_config) result["PermissionUrlsSetUnrestrictedModeParams"] = to_class(PermissionUrlsSetUnrestrictedModeParams, self.permission_urls_set_unrestricted_mode_params) + result["PersistenceWriteRawJsonLockedRequest"] = to_class(PersistenceWriteRawJSONLockedRequest, self.persistence_write_raw_json_locked_request) + result["PersistenceWriteRawJsonLockedResult"] = to_class(PersistenceWriteRawJSONLockedResult, self.persistence_write_raw_json_locked_result) result["PingRequest"] = to_class(PingRequest, self.ping_request) result["PingResult"] = to_class(PingResult, self.ping_result) result["PlanReadResult"] = to_class(PlanReadResult, self.plan_read_result) @@ -53929,7 +59495,17 @@ def to_dict(self) -> dict: result["RemoteSessionMetadataValue"] = to_class(RemoteSessionMetadataValue, self.remote_session_metadata_value) result["RemoteSessionMode"] = to_enum(RemoteSessionMode, self.remote_session_mode) result["RemoteSessionRepository"] = to_class(RemoteSessionRepository, self.remote_session_repository) + result["RepoSettingsEditRequest"] = to_class(_RepoSettingsEditRequest, self.repo_settings_edit_request) + result["RepoSettingsEditScope"] = to_enum(_RepoSettingsEditScope, self.repo_settings_edit_scope) + result["RepoSettingsReadRawRequest"] = to_class(_RepoSettingsReadRawRequest, self.repo_settings_read_raw_request) + result["RepoSettingsReadRawResult"] = to_class(_RepoSettingsReadRawResult, self.repo_settings_read_raw_result) + result["RepoSettingsWriteAllRequest"] = to_class(_RepoSettingsWriteAllRequest, self.repo_settings_write_all_request) + result["RepoSettingsWriteKeyRequest"] = to_class(_RepoSettingsWriteKeyRequest, self.repo_settings_write_key_request) result["ResponseFormat"] = to_class(ResponseFormat, self.response_format) + result["SandboxApplyDefaultsRequest"] = to_class(SandboxApplyDefaultsRequest, self.sandbox_apply_defaults_request) + result["SandboxCheckEnforcementCommandResult"] = from_bool(self.sandbox_check_enforcement_command_result) + result["SandboxCheckPlatformSupportResult"] = from_bool(self.sandbox_check_platform_support_result) + result["SandboxCheckWindowsHostLoopbackSupportResult"] = from_bool(self.sandbox_check_windows_host_loopback_support_result) 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) @@ -53941,20 +59517,44 @@ def to_dict(self) -> dict: result["SandboxConfigUserPolicyNetworkProxy"] = to_class(SandboxConfigUserPolicyNetworkProxy, self.sandbox_config_user_policy_network_proxy) result["SandboxConfigUserPolicySeatbelt"] = to_class(SandboxConfigUserPolicySeatbelt, self.sandbox_config_user_policy_seatbelt) result["SandboxCredentialsConfig"] = to_class(SandboxCredentialsConfig, self.sandbox_credentials_config) + result["SandboxCredentialSuggestion"] = to_class(SandboxCredentialSuggestion, self.sandbox_credential_suggestion) + result["SandboxCredentialSuggestionsResult"] = to_class(SandboxCredentialSuggestionsResult, self.sandbox_credential_suggestions_result) result["SandboxDisableForSessionRequest"] = to_class(SandboxDisableForSessionRequest, self.sandbox_disable_for_session_request) result["SandboxDisableForSessionResult"] = to_class(SandboxDisableForSessionResult, self.sandbox_disable_for_session_result) + result["SandboxDocsUrlResult"] = to_class(SandboxDocsURLResult, self.sandbox_docs_url_result) + result["SandboxEffectiveForOptionalParams"] = to_class(_SandboxEffectiveForOptionalParams, self.sandbox_effective_for_optional_params) + result["SandboxEffectiveForOptionalResult"] = to_class(_SandboxEffectiveForOptionalResult, self.sandbox_effective_for_optional_result) + result["SandboxEnforcementCommandAllowedParams"] = to_class(_SandboxEnforcementCommandAllowedParams, self.sandbox_enforcement_command_allowed_params) result["SandboxEnforcementStatus"] = to_class(SandboxEnforcementStatus, self.sandbox_enforcement_status) + result["SandboxFilterPickerCandidatesParams"] = to_class(_SandboxFilterPickerCandidatesParams, self.sandbox_filter_picker_candidates_params) + result["SandboxFilterPickerCandidatesResult"] = to_class(_SandboxFilterPickerCandidatesResult, self.sandbox_filter_picker_candidates_result) + result["SandboxGetUnsupportedMessageResult"] = from_str(self.sandbox_get_unsupported_message_result) + result["SandboxGetUnsupportedSettingDisabledNoticeResult"] = from_str(self.sandbox_get_unsupported_setting_disabled_notice_result) result["SandboxGrantPathForRequestRequest"] = to_class(SandboxGrantPathForRequestRequest, self.sandbox_grant_path_for_request_request) result["SandboxGrantPathForRequestResult"] = to_class(SandboxGrantPathForRequestResult, self.sandbox_grant_path_for_request_result) result["SandboxHostCapability"] = to_class(SandboxHostCapability, self.sandbox_host_capability) result["SandboxHostCapabilityName"] = from_str(self.sandbox_host_capability_name) result["SandboxHostSupport"] = to_class(SandboxHostSupport, self.sandbox_host_support) result["SandboxMaskedEnvVar"] = to_class(SandboxMaskedEnvVar, self.sandbox_masked_env_var) + result["SandboxPickerCandidate"] = to_class(_SandboxPickerCandidate, self.sandbox_picker_candidate) + result["SandboxPreparePickerPolicyParams"] = to_class(_SandboxPreparePickerPolicyParams, self.sandbox_prepare_picker_policy_params) + result["SandboxPreparePickerPolicyResult"] = to_class(_SandboxPreparePickerPolicyResult, self.sandbox_prepare_picker_policy_result) + result["SandboxProxyCaCheckInstallationSupportResult"] = from_bool(self.sandbox_proxy_ca_check_installation_support_result) + result["SandboxProxyCaCheckRelevanceResult"] = from_bool(self.sandbox_proxy_ca_check_relevance_result) result["SandboxProxyCaCreateResult"] = to_class(SandboxProxyCACreateResult, self.sandbox_proxy_ca_create_result) result["SandboxProxyCaRequest"] = to_class(SandboxProxyCARequest, self.sandbox_proxy_ca_request) result["SandboxProxyCaState"] = to_enum(SandboxProxyCAState, self.sandbox_proxy_ca_state) result["SandboxProxyCaStatus"] = to_class(SandboxProxyCAStatus, self.sandbox_proxy_ca_status) + result["SandboxProxySecretGetResult"] = to_class(SandboxProxySecretGetResult, self.sandbox_proxy_secret_get_result) + result["SandboxProxySecretLocation"] = to_class(SandboxProxySecretLocation, self.sandbox_proxy_secret_location) + result["SandboxProxySecretSaveRequest"] = to_class(SandboxProxySecretSaveRequest, self.sandbox_proxy_secret_save_request) + result["SandboxProxySecretSaveResult"] = to_class(SandboxProxySecretSaveResult, self.sandbox_proxy_secret_save_result) + result["SandboxResolveFlooredConfigParams"] = to_class(_SandboxResolveFlooredConfigParams, self.sandbox_resolve_floored_config_params) + result["SandboxResolveFlooredConfigResult"] = to_class(SandboxResolveFlooredConfigResult, self.sandbox_resolve_floored_config_result) result["SandboxSessionChange"] = to_enum(SandboxSessionChange, self.sandbox_session_change) + result["SandboxSettingsApplyDefaultsResult"] = from_dict(lambda x: x, self.sandbox_settings_apply_defaults_result) + result["SandboxUnsupportedStartupNoticeParams"] = to_class(_SandboxUnsupportedStartupNoticeParams, self.sandbox_unsupported_startup_notice_params) + result["SandboxUnsupportedStartupNoticeResult"] = to_class(_SandboxUnsupportedStartupNoticeResult, self.sandbox_unsupported_startup_notice_result) 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) @@ -54085,6 +59685,8 @@ def to_dict(self) -> dict: result["SessionPluginsInstallRequest"] = to_class(SessionPluginsInstallRequest, self.session_plugins_install_request) result["SessionPluginsMarketplacesRefreshRequest"] = to_class(SessionPluginsMarketplacesRefreshRequest, self.session_plugins_marketplaces_refresh_request) result["SessionPluginsReloadRequest"] = to_class(SessionPluginsReloadRequest, self.session_plugins_reload_request) + result["SessionPluginsRetryManagedRequest"] = to_class(SessionPluginsRetryManagedRequest, self.session_plugins_retry_managed_request) + result["SessionPluginsRetryManagedResult"] = to_class(SessionPluginsRetryManagedResult, self.session_plugins_retry_managed_result) result["SessionProviderGetEndpointRequest"] = to_class(SessionProviderGetEndpointRequest, self.session_provider_get_endpoint_request) result["SessionPruneResult"] = to_class(SessionPruneResult, self.session_prune_result) result["SessionQuotaDelegateWarning"] = to_class(SessionQuotaDelegateWarning, self.session_quota_delegate_warning) @@ -54181,6 +59783,9 @@ def to_dict(self) -> dict: result["SessionWorkingDirectoryContextWithClient"] = to_class(SessionWorkingDirectoryContextWithClient, self.session_working_directory_context_with_client) result["SettableAuthInfo"] = (self.settable_auth_info).to_dict() result["SettableTokenAuthInfo"] = to_class(SettableTokenAuthInfo, self.settable_token_auth_info) + result["SettingsEditOperation"] = to_enum(_SettingsEditOperation, self.settings_edit_operation) + result["SettingsEditResult"] = to_class(_SettingsEditResult, self.settings_edit_result) + result["SettingsEditStatus"] = to_enum(_SettingsEditStatus, self.settings_edit_status) 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) @@ -54218,6 +59823,7 @@ def to_dict(self) -> dict: result["SkillInstallationSummary"] = to_class(SkillInstallationSummary, self.skill_installation_summary) result["SkillInstallPlan"] = to_class(SkillInstallPlan, self.skill_install_plan) result["SkillList"] = to_class(SkillList, self.skill_list) + result["SkillPlanInstallFromReferenceRequest"] = to_class(SkillPlanInstallFromReferenceRequest, self.skill_plan_install_from_reference_request) result["SkillPlanInstallRequest"] = to_class(SkillPlanInstallRequest, self.skill_plan_install_request) result["SkillPlanUninstallRequest"] = to_class(SkillPlanUninstallRequest, self.skill_plan_uninstall_request) result["SkillProviderDescriptor"] = to_class(SkillProviderDescriptor, self.skill_provider_descriptor) @@ -54253,6 +59859,8 @@ def to_dict(self) -> dict: 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["StaffLogLevelMigrationStatus"] = to_enum(StaffLogLevelMigrationStatus, self.staff_log_level_migration_status) + result["StaffUpdateChannelMigrationStatus"] = to_enum(StaffUpdateChannelMigrationStatus, self.staff_update_channel_migration_status) result["SubagentSettingsEntry"] = to_class(SubagentSettingsEntry, self.subagent_settings_entry) result["SubagentSettingsEntryContextTier"] = to_enum(SubagentSettingsEntryContextTier, self.subagent_settings_entry_context_tier) result["SystemMessageBlock"] = to_class(SystemMessageBlock, self.system_message_block) @@ -54367,8 +59975,55 @@ def to_dict(self) -> dict: result["UserAuthInfo"] = to_class(UserAuthInfo, self.user_auth_info) result["UserRequestedShellCommandResult"] = to_class(UserRequestedShellCommandResult, self.user_requested_shell_command_result) result["UserSettingMetadata"] = to_class(UserSettingMetadata, self.user_setting_metadata) + result["UserSettingsApplyTopKeyRequest"] = to_class(UserSettingsApplyTopKeyRequest, self.user_settings_apply_top_key_request) + result["UserSettingsApplyTopKeyResult"] = to_class(UserSettingsApplyTopKeyResult, self.user_settings_apply_top_key_result) + result["UserSettingsBuildRuntimeRequest"] = to_class(_UserSettingsBuildRuntimeRequest, self.user_settings_build_runtime_request) + result["UserSettingsBuildRuntimeResult"] = from_dict(lambda x: x, self.user_settings_build_runtime_result) + result["UserSettingsEditRequest"] = to_class(_UserSettingsEditRequest, self.user_settings_edit_request) + result["UserSettingsGetPathsRequest"] = to_class(UserSettingsGetPathsRequest, self.user_settings_get_paths_request) + result["UserSettingsGetPathsResult"] = to_class(UserSettingsGetPathsResult, self.user_settings_get_paths_result) result["UserSettingsGetResult"] = to_class(UserSettingsGetResult, self.user_settings_get_result) + result["UserSettingsGovernanceKeysResult"] = to_class(UserSettingsGovernanceKeysResult, self.user_settings_governance_keys_result) + result["UserSettingsHostContext"] = to_class(UserSettingsHostContext, self.user_settings_host_context) + result["UserSettingsLoadAndMergeRepoRequest"] = to_class(_UserSettingsLoadAndMergeRepoRequest, self.user_settings_load_and_merge_repo_request) + result["UserSettingsLoadAndMergeRepoResult"] = to_class(_UserSettingsLoadAndMergeRepoResult, self.user_settings_load_and_merge_repo_result) + result["UserSettingsLoadForConfigDirRequest"] = to_class(_UserSettingsLoadForConfigDirRequest, self.user_settings_load_for_config_dir_request) + result["UserSettingsLoadForConfigDirResult"] = from_dict(lambda x: x, self.user_settings_load_for_config_dir_result) + result["UserSettingsLoadForEditResult"] = to_class(UserSettingsLoadForEditResult, self.user_settings_load_for_edit_result) + result["UserSettingsLoadRepoRequest"] = to_class(UserSettingsLoadRepoRequest, self.user_settings_load_repo_request) + result["UserSettingsLoadRepoResult"] = to_class(UserSettingsLoadRepoResult, self.user_settings_load_repo_result) + result["UserSettingsLoadWithWarningResult"] = to_class(UserSettingsLoadWithWarningResult, self.user_settings_load_with_warning_result) + result["UserSettingsMergeManagedAndUserParams"] = to_class(_UserSettingsMergeManagedAndUserParams, self.user_settings_merge_managed_and_user_params) + result["UserSettingsMergeManagedAndUserResult"] = to_class(UserSettingsMergeManagedAndUserResult, self.user_settings_merge_managed_and_user_result) + result["UserSettingsMergeRepoValueParams"] = to_class(_UserSettingsMergeRepoValueParams, self.user_settings_merge_repo_value_params) + result["UserSettingsMergeRepoValueResult"] = to_class(UserSettingsMergeRepoValueResult, self.user_settings_merge_repo_value_result) + result["UserSettingsMigrateObsoleteOnDiskRequest"] = to_class(_UserSettingsMigrateObsoleteOnDiskRequest, self.user_settings_migrate_obsolete_on_disk_request) + result["UserSettingsMigrateObsoleteOnDiskResult"] = to_class(_UserSettingsMigrateObsoleteOnDiskResult, self.user_settings_migrate_obsolete_on_disk_result) + result["UserSettingsMigrateObsoleteOnDiskStatus"] = to_enum(_UserSettingsMigrateObsoleteOnDiskStatus, self.user_settings_migrate_obsolete_on_disk_status) + result["UserSettingsMigrateStaffLogLevelOnceRequest"] = to_class(_UserSettingsMigrateStaffLogLevelOnceRequest, self.user_settings_migrate_staff_log_level_once_request) + result["UserSettingsMigrateStaffLogLevelOnceResult"] = to_class(_UserSettingsMigrateStaffLogLevelOnceResult, self.user_settings_migrate_staff_log_level_once_result) + result["UserSettingsMigrateStaffUpdateChannelOnceResult"] = to_class(_UserSettingsMigrateStaffUpdateChannelOnceResult, self.user_settings_migrate_staff_update_channel_once_result) + result["UserSettingsNormalizeRequest"] = to_class(UserSettingsNormalizeRequest, self.user_settings_normalize_request) + result["UserSettingsNormalizeResult"] = to_class(UserSettingsNormalizeResult, self.user_settings_normalize_result) + result["UserSettingsPathResult"] = to_class(UserSettingsPathResult, self.user_settings_path_result) + result["UserSettingsProblem"] = to_class(UserSettingsProblem, self.user_settings_problem) + result["UserSettingsProblemLocation"] = to_class(UserSettingsProblemLocation, self.user_settings_problem_location) + result["UserSettingsProblemSource"] = to_class(UserSettingsProblemSource, self.user_settings_problem_source) + result["UserSettingsProblemsRequest"] = to_class(UserSettingsProblemsRequest, self.user_settings_problems_request) + result["UserSettingsProblemsResult"] = to_class(UserSettingsProblemsResult, self.user_settings_problems_result) + result["UserSettingsResetStaffModelSelectionOnceRequest"] = to_class(_UserSettingsResetStaffModelSelectionOnceRequest, self.user_settings_reset_staff_model_selection_once_request) + result["UserSettingsResetStaffModelSelectionOnceResult"] = to_class(_UserSettingsResetStaffModelSelectionOnceResult, self.user_settings_reset_staff_model_selection_once_result) + result["UserSettingsSetHookDisabledRequest"] = to_class(_UserSettingsSetHookDisabledRequest, self.user_settings_set_hook_disabled_request) + result["UserSettingsSetHookDisabledResult"] = to_class(_UserSettingsSetHookDisabledResult, self.user_settings_set_hook_disabled_result) result["UserSettingsSetRequest"] = to_class(UserSettingsSetRequest, self.user_settings_set_request) + result["UserSettingsUnknownKeysWarningRequest"] = to_class(_UserSettingsUnknownKeysWarningRequest, self.user_settings_unknown_keys_warning_request) + result["UserSettingsUnknownKeysWarningResult"] = to_class(_UserSettingsUnknownKeysWarningResult, self.user_settings_unknown_keys_warning_result) + result["UserSettingsUpdateRequest"] = to_class(_UserSettingsUpdateRequest, self.user_settings_update_request) + result["UserSettingsUpdateResult"] = to_class(_UserSettingsUpdateResult, self.user_settings_update_result) + result["UserSettingsValidateRequest"] = to_class(UserSettingsValidateRequest, self.user_settings_validate_request) + result["UserSettingsValidateResult"] = to_class(UserSettingsValidateResult, self.user_settings_validate_result) + result["UserSettingsValidationIssue"] = to_class(UserSettingsValidationIssue, self.user_settings_validation_issue) + result["UserSettingsWriteAllRequest"] = to_class(UserSettingsWriteAllRequest, self.user_settings_write_all_request) result["VisibilityGetResult"] = to_class(VisibilityGetResult, self.visibility_get_result) result["VisibilitySetRequest"] = to_class(VisibilitySetRequest, self.visibility_set_request) result["VisibilitySetResult"] = to_class(VisibilitySetResult, self.visibility_set_result) @@ -54462,21 +60117,10 @@ def to_dict(self) -> dict: result["WorkspaceSummary"] = from_union([lambda x: to_class(WorkspaceSummary, x), from_none], self.workspace_summary) return result -def rpc_from_dict(s: Any) -> RPC: - return RPC.from_dict(s) - -def rpc_to_dict(x: RPC) -> Any: - return to_class(RPC, x) - # Experimental: this type is part of an experimental API and may change or be removed. -class ConnectorDiscoveryAvailability(Enum): - "Availability." - # Enabled. - ENABLED = "enabled" - # Disabled. - DISABLED = "disabled" - # Unavailable. - UNAVAILABLE = "unavailable" +class ManagedPermissionOperationKind(Enum): + "Operation to evaluate. Currently only url is supported." + URL = "url" # Outcome of an agentRegistry.spawn call. AgentRegistrySpawnResult = AgentRegistrySpawnSpawned | AgentRegistrySpawnError | AgentRegistrySpawnRegistryTimeout | AgentRegistrySpawnValidationError @@ -54509,6 +60153,18 @@ def _load_AuthInfo(obj: Any) -> "AuthInfo": case "api-key": return APIKeyAuthInfo.from_dict(obj) case _: raise ValueError(f"Unknown AuthInfo type: {kind!r}") +# One place an Agent Plugin can install from: Agent Finder's materialisation, a marketplace, or a GitHub repository. Each variant carries only its own fields. +CatalogAgentPluginInstallSource = CatalogAgentPluginMaterializationSource | CatalogAgentPluginMarketplaceSource | CatalogAgentPluginRepositorySource + +def _load_CatalogAgentPluginInstallSource(obj: Any) -> "CatalogAgentPluginInstallSource": + assert isinstance(obj, dict) + kind = obj.get("kind") + match kind: + case "materialization": return CatalogAgentPluginMaterializationSource.from_dict(obj) + case "marketplace": return CatalogAgentPluginMarketplaceSource.from_dict(obj) + case "repository": return CatalogAgentPluginRepositorySource.from_dict(obj) + case _: raise ValueError(f"Unknown CatalogAgentPluginInstallSource kind: {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 @@ -54894,6 +60550,8 @@ def _load_TaskInfo(obj: Any) -> "TaskInfo": EventsCursorStatus = CursorStatus ExternalToolResult = ExternalToolTextResultForLlm ExternalToolTextResultForLlmContentResourceLinkIconTheme = Theme +FeatureFlagsResolveResult = dict +FeatureFlagsResolveStartupGatesResult = dict FilterMapping = dict HistoryCompactRequest = Any InstructionDiscoveryPathKind = DebugCollectLogsEntryKind @@ -54939,7 +60597,15 @@ def _load_TaskInfo(obj: Any) -> "TaskInfo": ProviderGetEndpointRequest = Any QuotaTakeWarningsResult = list RemoteSessionMetadataTaskType = TaskType +SandboxCheckEnforcementCommandResult = bool +SandboxCheckPlatformSupportResult = bool +SandboxCheckWindowsHostLoopbackSupportResult = bool +SandboxGetUnsupportedMessageResult = str +SandboxGetUnsupportedSettingDisabledNoticeResult = str SandboxHostCapabilityName = str +SandboxProxyCaCheckInstallationSupportResult = bool +SandboxProxyCaCheckRelevanceResult = bool +SandboxSettingsApplyDefaultsResult = dict SessionAuthInfoResult = AuthIdentity | None SessionCancelAllBackgroundAgentsResult = int SessionContextHostType = HostType @@ -54959,6 +60625,8 @@ def _load_TaskInfo(obj: Any) -> "TaskInfo": TaskInfoExecutionMode = TaskExecutionMode TaskInfoStatus = TaskStatus ToolResult = ToolResultExpanded +UserSettingsBuildRuntimeResult = dict +UserSettingsLoadForConfigDirResult = dict WorkspaceSummaryHostType = HostType WorkspacesWorkspaceDetailsHostType = HostType @@ -54990,6 +60658,123 @@ def _patch_model_capabilities(data: dict) -> dict: return data +# Experimental: this API group is experimental and may change or be removed. +class ServerSandboxProxyCaApi: + def __init__(self, client: "JsonRpcClient"): + self._client = client + + async def check_installation_support(self, *, timeout: float | None = None) -> bool: + "Reports whether this runtime process can offer installing the sandbox credential-proxy certificate authority into OS trust.\n\nReturns:\n True when this process can offer installing the sandbox credential-proxy certificate authority into OS trust." + return bool(await self._client.request("sandbox.proxyCa.checkInstallationSupport", {}, **_timeout_kwargs(timeout))) + + async def check_relevance(self, params: SandboxProxyCARequest, *, timeout: float | None = None) -> bool: + "Reports whether this sandbox configuration uses the credential proxy and can need certificate-authority setup.\n\nArgs:\n params: Identifies the credential hosts that the persistent certificate authority of the sandbox credential proxy must cover. The runtime always adds the hosts from the saved user settings.\n\nReturns:\n True when the sandbox configuration uses the credential proxy and can need certificate-authority setup." + params_dict = {k: v for k, v in params.to_dict().items() if v is not None} + return bool(await self._client.request("sandbox.proxyCa.checkRelevance", params_dict, **_timeout_kwargs(timeout))) + + async def get_status(self, params: SandboxProxyCARequest, *, timeout: float | None = None) -> SandboxProxyCAStatus: + "Reports whether the persistent certificate authority of the sandbox credential proxy exists, whether OS trust includes it, and whether it must be rotated. Changes nothing.\n\nArgs:\n params: Identifies the credential hosts that the persistent certificate authority of the sandbox credential proxy must cover. The runtime always adds the hosts from the saved user settings.\n\nReturns:\n Status of the persistent certificate authority of the sandbox credential proxy." + params_dict = {k: v for k, v in params.to_dict().items() if v is not None} + return SandboxProxyCAStatus.from_dict(await self._client.request("sandbox.proxyCa.getStatus", params_dict, **_timeout_kwargs(timeout))) + + async def create(self, params: SandboxProxyCARequest, *, timeout: float | None = None) -> SandboxProxyCACreateResult: + "Creates the persistent certificate authority of the sandbox credential proxy if none is stored, without changing OS trust, and returns the path of its public certificate. Keeps an existing certificate authority, even one that must be rotated. Fails where OS trust is unsupported. Trust it with sandbox.proxyCa.trust: the CLI trusts only the hosts in the saved user settings, so it refuses a certificate authority that also covers hosts from sandboxConfig.\n\nArgs:\n params: Identifies the credential hosts that the persistent certificate authority of the sandbox credential proxy must cover. The runtime always adds the hosts from the saved user settings.\n\nReturns:\n Result of creating the persistent certificate authority of the sandbox credential proxy." + params_dict = {k: v for k, v in params.to_dict().items() if v is not None} + return SandboxProxyCACreateResult.from_dict(await self._client.request("sandbox.proxyCa.create", params_dict, **_timeout_kwargs(timeout))) + + async def rotate(self, params: SandboxProxyCARequest, *, timeout: float | None = None) -> SandboxProxyCAStatus: + "Replaces the persistent certificate authority of the sandbox credential proxy with a new one for the current credential hosts. If OS trust included the old one, removes it and trusts the new one, which can show an OS authentication prompt. Running sandboxed tools keep the old certificate authority until they restart.\n\nArgs:\n params: Identifies the credential hosts that the persistent certificate authority of the sandbox credential proxy must cover. The runtime always adds the hosts from the saved user settings.\n\nReturns:\n Status of the persistent certificate authority of the sandbox credential proxy." + params_dict = {k: v for k, v in params.to_dict().items() if v is not None} + return SandboxProxyCAStatus.from_dict(await self._client.request("sandbox.proxyCa.rotate", params_dict, **_timeout_kwargs(timeout))) + + async def trust(self, params: SandboxProxyCARequest, *, timeout: float | None = None) -> SandboxProxyCAStatus: + "Adds the persistent certificate authority of the sandbox credential proxy to OS trust, so sandboxed clients that read only OS trust accept the proxy. Call create first. Refuses a certificate authority that is not constrained to the current credential hosts. Can show an OS authentication prompt.\n\nArgs:\n params: Identifies the credential hosts that the persistent certificate authority of the sandbox credential proxy must cover. The runtime always adds the hosts from the saved user settings.\n\nReturns:\n Status of the persistent certificate authority of the sandbox credential proxy." + params_dict = {k: v for k, v in params.to_dict().items() if v is not None} + return SandboxProxyCAStatus.from_dict(await self._client.request("sandbox.proxyCa.trust", params_dict, **_timeout_kwargs(timeout))) + + async def remove(self, *, timeout: float | None = None) -> SandboxProxyCAStatus: + "Removes the persistent certificate authority of the sandbox credential proxy from OS trust. Keeps the stored certificate authority. Can show an OS authentication prompt. Sandboxed clients that read only OS trust then reject the proxy; clients that read the per-process certificate bundle continue to work.\n\nReturns:\n Status of the persistent certificate authority of the sandbox credential proxy." + return SandboxProxyCAStatus.from_dict(await self._client.request("sandbox.proxyCa.remove", {}, **_timeout_kwargs(timeout))) + + +# Experimental: this API group is experimental and may change or be removed. +class ServerSandboxApi: + def __init__(self, client: "JsonRpcClient"): + self._client = client + self.proxy_ca = ServerSandboxProxyCaApi(client) + + async def get_host_support(self, *, timeout: float | None = None) -> SandboxHostSupport: + "Reports whether the host running this runtime can run the command sandbox, without starting a session or spawning a sandboxed command.\n\nReturns:\n Whether the host running this runtime can run the command sandbox. The runtime checks `supported` once per process. A capability answer can change while the process runs, for example after the user installs a missing package." + return SandboxHostSupport.from_dict(await self._client.request("sandbox.getHostSupport", {}, **_timeout_kwargs(timeout))) + + +# Experimental: this API group is experimental and may change or be removed. +class ServerUserSettingsApi: + def __init__(self, client: "JsonRpcClient"): + self._client = client + + async def get(self, *, timeout: float | None = None) -> UserSettingsGetResult: + "Lists every known user setting from settings.json, each with its effective value, its default, and whether it is at the default — so settings the user has never set still appear with their default value. Does not include repository- or enterprise-managed overrides that the runtime layers on top at session time.\n\nReturns:\n Per-key metadata for every known user setting in settings.json, including settings left at their default. Excludes repository- and enterprise-managed overrides." + return UserSettingsGetResult.from_dict(await self._client.request("user.settings.get", {}, **_timeout_kwargs(timeout))) + + async def set(self, params: UserSettingsSetRequest, *, timeout: float | None = None) -> None: + "Writes one or more user settings to settings.json, replacing each provided top-level key. A key whose value is null is removed.\n\nArgs:\n params: 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." + params_dict = {k: v for k, v in params.to_dict().items() if v is not None} + await self._client.request("user.settings.set", params_dict, **_timeout_kwargs(timeout)) + + +# Experimental: this API group is experimental and may change or be removed. +class ServerUserApi: + def __init__(self, client: "JsonRpcClient"): + self._client = client + self.settings = ServerUserSettingsApi(client) + + +# Experimental: this API group is experimental and may change or be removed. +class ServerManagedSettingsPermissionsApi: + def __init__(self, client: "JsonRpcClient"): + self._client = client + + async def evaluate(self, params: ManagedPermissionsEvaluateRequest, *, timeout: float | None = None) -> ManagedPermissionsEvaluateResult: + "Pure, sessionless evaluation of operations against a supplied permissionsContext. Performs no policy discovery, I/O, prompting, navigation, permission grants, or sandbox changes. Obtain the context from managedSettings.resolve or session.managedSettings.get, or reuse an in-memory context. Results preserve input order and duplicates. Consumers must block deny, obtain approval for ask, and use their normal permission flow for unmanaged. Unknown policy returns failClosed with every operation denied. This is not a substitute for user permissions or other security controls.\n\nArgs:\n params: Managed policy context and ordered operations for pure, sessionless evaluation.\n\nReturns:\n Ordered managed permission verdicts and the supplied policy's fail-closed posture." + params_dict = {k: v for k, v in params.to_dict().items() if v is not None} + return ManagedPermissionsEvaluateResult.from_dict(await self._client.request("managedSettings.permissions.evaluate", params_dict, **_timeout_kwargs(timeout))) + + +# Experimental: this API group is experimental and may change or be removed. +class ServerManagedSettingsApi: + def __init__(self, client: "JsonRpcClient"): + self._client = client + self.permissions = ServerManagedSettingsPermissionsApi(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. `managedSettings.resolve` returns the same device settings together with the account's server policy.\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))) + + async def clear_cache(self, *, timeout: float | None = None) -> None: + "Force-refreshes enterprise managed settings for every account: wipes the persistent server-policy cache (the whole `/managed-settings` directory) and drops this runtime process's in-memory retained server policy. It does not itself fetch policy — the effect is that the next time a session resolves managed settings for an account, that resolution re-fetches the account's org policy from the network instead of serving a cached response. Note that `managedSettings.read` returns only device/MDM settings and never triggers the account server-policy fetch, so a host implementing \"sync account policy\" should call `managedSettings.resolve` or start a fresh session resolution rather than treat a subsequent `managedSettings.read` as the refreshed org policy. Mirrors the invalidation a sign-out performs, broadened from the one signing-out account to all of them; device/MDM layers describe the machine, not the account, and are left untouched. Rejects if the on-disk cache cannot be removed." + await self._client.request("managedSettings.clearCache", {}, **_timeout_kwargs(timeout)) + + async def resolve(self, params: ManagedSettingsResolveRequest, *, timeout: float | None = None) -> ManagedSettingsResolveResult: + "Resolves the effective enterprise managed settings without a session, from the device channel and, when an account is available, the account's server policy through the same per-account cache sessions use. A cached server policy less than an hour old is used without a fetch; otherwise the policy is fetched, and when the fetch fails a cached policy up to 24 hours old is used instead, unless `forceRemoteSettingsRefresh` requires a live fetch. With no account requested or signed in, it reports device policy only; signing out removes the account's cached policy. It can fetch server policy over the network when the cache is stale, so call it off latency-critical paths such as startup rather than before listing models. The policy helper is not run. `layers` lists each channel's document before merging, and `values` and `meta` carry typed effective values and their lock state for the keys typed so far.\n\nArgs:\n params: Optional opaque account selection or GitHub token whose managed settings are resolved.\n\nReturns:\n Effective enterprise managed settings for an account, resolved without a session." + params_dict = {k: v for k, v in params.to_dict().items() if v is not None} + return ManagedSettingsResolveResult.from_dict(await self._client.request("managedSettings.resolve", params_dict, **_timeout_kwargs(timeout))) + + async def schema(self, *, timeout: float | None = None) -> ManagedSettingsSchemaResult: + "Returns the managed-settings authoring JSON schema with descriptive `x-composition` annotations aligned with the shared settings-engine vocabulary. These annotations are not a complete runtime composition contract: model, effortLevel, and contextTier remain coupled. Use `managedSettings.compose` for the runtime's effective result. Performs no I/O.\n\nReturns:\n The authoring JSON schema for managed settings recognized by this runtime." + return ManagedSettingsSchemaResult.from_dict(await self._client.request("managedSettings.schema", {}, **_timeout_kwargs(timeout))) + + async def validate(self, params: ManagedSettingsValidateRequest, *, timeout: float | None = None) -> ManagedSettingsValidateResult: + "Validates a candidate managed-settings document the way the runtime validates delivered policy, without applying it. Reports errors that would reject the document, warnings for content the runtime ignores, and the canonical document it would apply. Document text nested more than 64 levels deep is rejected. Performs no I/O.\n\nArgs:\n params: A candidate managed-settings document to validate without applying it.\n\nReturns:\n Result of validating a managed-settings document." + params_dict = {k: v for k, v in params.to_dict().items() if v is not None} + return ManagedSettingsValidateResult.from_dict(await self._client.request("managedSettings.validate", params_dict, **_timeout_kwargs(timeout))) + + async def compose(self, params: ManagedSettingsComposeRequest, *, timeout: float | None = None) -> ManagedSettingsComposeResult: + "Merges candidate managed-settings documents for the device, server, and policy-helper channels into the effective settings the runtime would enforce on this host, using the same precedence and composition rules as live resolution, without applying them. Like live resolution, a server's advisory sandbox force-enable is declined on a host that cannot run the sandbox. Does not fetch policy or read policy files, but may perform blocking OS or subprocess probes for sandbox support. Preview documents are limited to 1 MiB and 64 levels of nesting.\n\nArgs:\n params: Candidate managed-settings documents to merge without applying them.\n\nReturns:\n The effective managed settings the runtime would enforce for the given documents, in the same shape `managedSettings.resolve` returns." + params_dict = {k: v for k, v in params.to_dict().items() if v is not None} + return ManagedSettingsComposeResult.from_dict(await self._client.request("managedSettings.compose", params_dict, **_timeout_kwargs(timeout))) + + # Experimental: this API group is experimental and may change or be removed. class ServerEnvironmentsApi: def __init__(self, client: "JsonRpcClient"): @@ -55064,44 +60849,14 @@ async def get_built_in_catalog(self, *, timeout: float | None = None) -> BuiltIn # Experimental: this API group is experimental and may change or be removed. -class ServerSandboxProxyCaApi: +class ServerContentExclusionApi: def __init__(self, client: "JsonRpcClient"): self._client = client - async def get_status(self, params: SandboxProxyCARequest, *, timeout: float | None = None) -> SandboxProxyCAStatus: - "Reports whether the persistent certificate authority of the sandbox credential proxy exists, whether OS trust includes it, and whether it must be rotated. Changes nothing.\n\nArgs:\n params: Identifies the credential hosts that the persistent certificate authority of the sandbox credential proxy must cover. The runtime always adds the hosts from the saved user settings.\n\nReturns:\n Status of the persistent certificate authority of the sandbox credential proxy." - params_dict = {k: v for k, v in params.to_dict().items() if v is not None} - return SandboxProxyCAStatus.from_dict(await self._client.request("sandbox.proxyCa.getStatus", params_dict, **_timeout_kwargs(timeout))) - - async def create(self, params: SandboxProxyCARequest, *, timeout: float | None = None) -> SandboxProxyCACreateResult: - "Creates the persistent certificate authority of the sandbox credential proxy if none is stored, without changing OS trust, and returns the path of its public certificate. Keeps an existing certificate authority, even one that must be rotated. Fails where OS trust is unsupported. Trust it with sandbox.proxyCa.trust: the CLI trusts only the hosts in the saved user settings, so it refuses a certificate authority that also covers hosts from sandboxConfig.\n\nArgs:\n params: Identifies the credential hosts that the persistent certificate authority of the sandbox credential proxy must cover. The runtime always adds the hosts from the saved user settings.\n\nReturns:\n Result of creating the persistent certificate authority of the sandbox credential proxy." + async def validate_policies(self, params: ContentExclusionValidatePoliciesRequest, *, timeout: float | None = None) -> ContentExclusionValidatePoliciesResult: + "Validates candidate content-exclusion policies and returns them normalized, without starting a session. This runs the same parser the runtime's own policy enforcement uses, so a host cannot accept a policy set the runtime would later reject, nor reject one it would have honored. Call it when policies arrive from outside the runtime — a command-line flag, a configuration file, a host's own settings — and must be checked before a session exists to apply them to. A malformed document fails with a message prefixed `Invalid JSON: `; a well-formed document that violates the policy schema fails with the offending field paths.\n\nArgs:\n params: Candidate content-exclusion policies to validate, as the raw JSON text a host received. The text is supplied unparsed so that a syntax error and a schema violation are both reported by the runtime's own validator rather than by the caller's JSON parser.\n\nReturns:\n The accepted policies, parsed and normalized. A rejected input is reported as an error rather than in this result." params_dict = {k: v for k, v in params.to_dict().items() if v is not None} - return SandboxProxyCACreateResult.from_dict(await self._client.request("sandbox.proxyCa.create", params_dict, **_timeout_kwargs(timeout))) - - async def rotate(self, params: SandboxProxyCARequest, *, timeout: float | None = None) -> SandboxProxyCAStatus: - "Replaces the persistent certificate authority of the sandbox credential proxy with a new one for the current credential hosts. If OS trust included the old one, removes it and trusts the new one, which can show an OS authentication prompt. Running sandboxed tools keep the old certificate authority until they restart.\n\nArgs:\n params: Identifies the credential hosts that the persistent certificate authority of the sandbox credential proxy must cover. The runtime always adds the hosts from the saved user settings.\n\nReturns:\n Status of the persistent certificate authority of the sandbox credential proxy." - params_dict = {k: v for k, v in params.to_dict().items() if v is not None} - return SandboxProxyCAStatus.from_dict(await self._client.request("sandbox.proxyCa.rotate", params_dict, **_timeout_kwargs(timeout))) - - async def trust(self, params: SandboxProxyCARequest, *, timeout: float | None = None) -> SandboxProxyCAStatus: - "Adds the persistent certificate authority of the sandbox credential proxy to OS trust, so sandboxed clients that read only OS trust accept the proxy. Call create first. Refuses a certificate authority that is not constrained to the current credential hosts. Can show an OS authentication prompt.\n\nArgs:\n params: Identifies the credential hosts that the persistent certificate authority of the sandbox credential proxy must cover. The runtime always adds the hosts from the saved user settings.\n\nReturns:\n Status of the persistent certificate authority of the sandbox credential proxy." - params_dict = {k: v for k, v in params.to_dict().items() if v is not None} - return SandboxProxyCAStatus.from_dict(await self._client.request("sandbox.proxyCa.trust", params_dict, **_timeout_kwargs(timeout))) - - async def remove(self, *, timeout: float | None = None) -> SandboxProxyCAStatus: - "Removes the persistent certificate authority of the sandbox credential proxy from OS trust. Keeps the stored certificate authority. Can show an OS authentication prompt. Sandboxed clients that read only OS trust then reject the proxy; clients that read the per-process certificate bundle continue to work.\n\nReturns:\n Status of the persistent certificate authority of the sandbox credential proxy." - return SandboxProxyCAStatus.from_dict(await self._client.request("sandbox.proxyCa.remove", {}, **_timeout_kwargs(timeout))) - - -# Experimental: this API group is experimental and may change or be removed. -class ServerSandboxApi: - def __init__(self, client: "JsonRpcClient"): - self._client = client - self.proxy_ca = ServerSandboxProxyCaApi(client) - - async def get_host_support(self, *, timeout: float | None = None) -> SandboxHostSupport: - "Reports whether the host running this runtime can run the command sandbox, without starting a session or spawning a sandboxed command.\n\nReturns:\n Whether the host running this runtime can run the command sandbox. The runtime checks `supported` once per process. A capability answer can change while the process runs, for example after the user installs a missing package." - return SandboxHostSupport.from_dict(await self._client.request("sandbox.getHostSupport", {}, **_timeout_kwargs(timeout))) + return ContentExclusionValidatePoliciesResult.from_dict(await self._client.request("contentExclusion.validatePolicies", params_dict, **_timeout_kwargs(timeout))) # Experimental: this API group is experimental and may change or be removed. @@ -55278,6 +61033,22 @@ 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 ServerInstructionsApi: + def __init__(self, client: "JsonRpcClient"): + self._client = client + + async def discover(self, params: InstructionsDiscoverRequest, *, timeout: float | None = None) -> ServerInstructionSourceList: + "Discovers instruction sources across user, repository, and plugin sources.\n\nArgs:\n params: Optional project paths to include in instruction discovery.\n\nReturns:\n Instruction sources discovered across user, repository, and plugin sources." + params_dict = {k: v for k, v in params.to_dict().items() if v is not None} + return ServerInstructionSourceList.from_dict(await self._client.request("instructions.discover", params_dict, **_timeout_kwargs(timeout))) + + async def get_discovery_paths(self, params: InstructionsGetDiscoveryPathsRequest, *, timeout: float | None = None) -> InstructionDiscoveryPathList: + "Returns the canonical files and directories where a client may create custom instructions that the runtime will recognize, including ones that do not exist yet. Repository targets become active once created.\n\nArgs:\n params: Optional project paths to include when enumerating instruction discovery targets.\n\nReturns:\n Canonical files and directories where custom instructions can be created so the runtime will recognize them." + params_dict = {k: v for k, v in params.to_dict().items() if v is not None} + return InstructionDiscoveryPathList.from_dict(await self._client.request("instructions.getDiscoveryPaths", params_dict, **_timeout_kwargs(timeout))) + + # Experimental: this API group is experimental and may change or be removed. class ServerSkillsInstallationsApi: def __init__(self, client: "JsonRpcClient"): @@ -55337,6 +61108,11 @@ async def plan_install(self, params: SkillPlanInstallRequest, *, timeout: float params_dict = {k: v for k, v in params.to_dict().items() if v is not None} return SkillInstallationManagementResult.from_dict(await self._client.request("skills.planInstall", params_dict, **_timeout_kwargs(timeout))) + async def plan_install_from_reference(self, params: SkillPlanInstallFromReferenceRequest, *, timeout: float | None = None) -> SkillInstallationManagementResult: + "Plans installation of the Skill pinned by an Agent Finder materialisation reference without writing files. The runtime retrieves the descriptor only from its fixed Agent Finder authority and applies the same verification as skills.planInstall; the review shows only verified data. Apply the returned plan with skills.applyInstall.\n\nArgs:\n params: Side-effect-free planning of one Skill from a materialisation reference rather than a search candidate.\n\nReturns:\n Skill installation management result with the honoured contract, or a typed refusal." + params_dict = {k: v for k, v in params.to_dict().items() if v is not None} + return SkillInstallationManagementResult.from_dict(await self._client.request("skills.planInstallFromReference", params_dict, **_timeout_kwargs(timeout))) + async def apply_install(self, params: SkillApplyInstallRequest, *, timeout: float | None = None) -> SkillInstallationResult: "Consumes one verified Skill installation plan, requests explicit human consent through installations.confirm on the original connection, then revalidates and installs the Skill disabled.\n\nArgs:\n params: Applies exactly one retained verified Skill installation plan.\n\nReturns:\n Skill installation result with the honoured contract, or a typed request/negotiation refusal." params_dict = {k: v for k, v in params.to_dict().items() if v is not None} @@ -55477,22 +61253,6 @@ async def get_discovery_paths(self, params: AgentsGetDiscoveryPathsRequest, *, t return AgentDiscoveryPathList.from_dict(await self._client.request("agents.getDiscoveryPaths", params_dict, **_timeout_kwargs(timeout))) -# Experimental: this API group is experimental and may change or be removed. -class ServerInstructionsApi: - def __init__(self, client: "JsonRpcClient"): - self._client = client - - async def discover(self, params: InstructionsDiscoverRequest, *, timeout: float | None = None) -> ServerInstructionSourceList: - "Discovers instruction sources across user, repository, and plugin sources.\n\nArgs:\n params: Optional project paths to include in instruction discovery.\n\nReturns:\n Instruction sources discovered across user, repository, and plugin sources." - params_dict = {k: v for k, v in params.to_dict().items() if v is not None} - return ServerInstructionSourceList.from_dict(await self._client.request("instructions.discover", params_dict, **_timeout_kwargs(timeout))) - - async def get_discovery_paths(self, params: InstructionsGetDiscoveryPathsRequest, *, timeout: float | None = None) -> InstructionDiscoveryPathList: - "Returns the canonical files and directories where a client may create custom instructions that the runtime will recognize, including ones that do not exist yet. Repository targets become active once created.\n\nArgs:\n params: Optional project paths to include when enumerating instruction discovery targets.\n\nReturns:\n Canonical files and directories where custom instructions can be created so the runtime will recognize them." - params_dict = {k: v for k, v in params.to_dict().items() if v is not None} - return InstructionDiscoveryPathList.from_dict(await self._client.request("instructions.getDiscoveryPaths", params_dict, **_timeout_kwargs(timeout))) - - # Experimental: this API group is experimental and may change or be removed. class ServerCommandsApi: def __init__(self, client: "JsonRpcClient"): @@ -55503,61 +61263,6 @@ async def list(self, *, timeout: float | None = None) -> CommandList: return CommandList.from_dict(await self._client.request("commands.list", {}, **_timeout_kwargs(timeout))) -# Experimental: this API group is experimental and may change or be removed. -class ServerUserSettingsApi: - def __init__(self, client: "JsonRpcClient"): - self._client = client - - async def get(self, *, timeout: float | None = None) -> UserSettingsGetResult: - "Lists every known user setting from settings.json, each with its effective value, its default, and whether it is at the default — so settings the user has never set still appear with their default value. Does not include repository- or enterprise-managed overrides that the runtime layers on top at session time.\n\nReturns:\n Per-key metadata for every known user setting in settings.json, including settings left at their default. Excludes repository- and enterprise-managed overrides." - return UserSettingsGetResult.from_dict(await self._client.request("user.settings.get", {}, **_timeout_kwargs(timeout))) - - async def set(self, params: UserSettingsSetRequest, *, timeout: float | None = None) -> None: - "Writes one or more user settings to settings.json, replacing each provided top-level key. A key whose value is null is removed.\n\nArgs:\n params: 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." - params_dict = {k: v for k, v in params.to_dict().items() if v is not None} - await self._client.request("user.settings.set", params_dict, **_timeout_kwargs(timeout)) - - -# Experimental: this API group is experimental and may change or be removed. -class ServerUserApi: - def __init__(self, client: "JsonRpcClient"): - self._client = client - 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. `managedSettings.resolve` returns the same device settings together with the account's server policy.\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))) - - async def clear_cache(self, *, timeout: float | None = None) -> None: - "Force-refreshes enterprise managed settings for every account: wipes the persistent server-policy cache (the whole `/managed-settings` directory) and drops this runtime process's in-memory retained server policy. It does not itself fetch policy — the effect is that the next time a session resolves managed settings for an account, that resolution re-fetches the account's org policy from the network instead of serving a cached response. Note that `managedSettings.read` returns only device/MDM settings and never triggers the account server-policy fetch, so a host implementing \"sync account policy\" should call `managedSettings.resolve` or start a fresh session resolution rather than treat a subsequent `managedSettings.read` as the refreshed org policy. Mirrors the invalidation a sign-out performs, broadened from the one signing-out account to all of them; device/MDM layers describe the machine, not the account, and are left untouched. Rejects if the on-disk cache cannot be removed." - await self._client.request("managedSettings.clearCache", {}, **_timeout_kwargs(timeout)) - - async def resolve(self, params: ManagedSettingsResolveRequest, *, timeout: float | None = None) -> ManagedSettingsResolveResult: - "Resolves the effective enterprise managed settings without a session, from the device channel and, when an account is available, the account's server policy through the same per-account cache sessions use. A cached server policy less than an hour old is used without a fetch; otherwise the policy is fetched, and when the fetch fails a cached policy up to 24 hours old is used instead, unless `forceRemoteSettingsRefresh` requires a live fetch. With no account requested or signed in, it reports device policy only; signing out removes the account's cached policy. It can fetch server policy over the network when the cache is stale, so call it off latency-critical paths such as startup rather than before listing models. The policy helper is not run. `layers` lists each channel's document before merging, and `values` and `meta` carry typed effective values and their lock state for the keys typed so far.\n\nArgs:\n params: Optional opaque account selection or GitHub token whose managed settings are resolved.\n\nReturns:\n Effective enterprise managed settings for an account, resolved without a session." - params_dict = {k: v for k, v in params.to_dict().items() if v is not None} - return ManagedSettingsResolveResult.from_dict(await self._client.request("managedSettings.resolve", params_dict, **_timeout_kwargs(timeout))) - - async def schema(self, *, timeout: float | None = None) -> ManagedSettingsSchemaResult: - "Returns the managed-settings authoring JSON schema with descriptive `x-composition` annotations aligned with the shared settings-engine vocabulary. These annotations are not a complete runtime composition contract: model, effortLevel, and contextTier remain coupled. Use `managedSettings.compose` for the runtime's effective result. Performs no I/O.\n\nReturns:\n The authoring JSON schema for managed settings recognized by this runtime." - return ManagedSettingsSchemaResult.from_dict(await self._client.request("managedSettings.schema", {}, **_timeout_kwargs(timeout))) - - async def validate(self, params: ManagedSettingsValidateRequest, *, timeout: float | None = None) -> ManagedSettingsValidateResult: - "Validates a candidate managed-settings document the way the runtime validates delivered policy, without applying it. Reports errors that would reject the document, warnings for content the runtime ignores, and the canonical document it would apply. Document text nested more than 64 levels deep is rejected. Performs no I/O.\n\nArgs:\n params: A candidate managed-settings document to validate without applying it.\n\nReturns:\n Result of validating a managed-settings document." - params_dict = {k: v for k, v in params.to_dict().items() if v is not None} - return ManagedSettingsValidateResult.from_dict(await self._client.request("managedSettings.validate", params_dict, **_timeout_kwargs(timeout))) - - async def compose(self, params: ManagedSettingsComposeRequest, *, timeout: float | None = None) -> ManagedSettingsComposeResult: - "Merges candidate managed-settings documents for the device, server, and policy-helper channels into the effective settings the runtime would enforce on this host, using the same precedence and composition rules as live resolution, without applying them. Like live resolution, a server's advisory sandbox force-enable is declined on a host that cannot run the sandbox. Does not fetch policy or read policy files, but may perform blocking OS or subprocess probes for sandbox support. Preview documents are limited to 1 MiB and 64 levels of nesting.\n\nArgs:\n params: Candidate managed-settings documents to merge without applying them.\n\nReturns:\n The effective managed settings the runtime would enforce for the given documents, in the same shape `managedSettings.resolve` returns." - params_dict = {k: v for k, v in params.to_dict().items() if v is not None} - return ManagedSettingsComposeResult.from_dict(await self._client.request("managedSettings.compose", params_dict, **_timeout_kwargs(timeout))) - - # Experimental: this API group is experimental and may change or be removed. class ServerRuntimeApi: def __init__(self, client: "JsonRpcClient"): @@ -55767,24 +61472,25 @@ class ServerRpc: """Typed server-scoped RPC methods.""" def __init__(self, client: "JsonRpcClient"): self._client = client + self.sandbox = ServerSandboxApi(client) + self.user = ServerUserApi(client) + self.managed_settings = ServerManagedSettingsApi(client) self.environments = ServerEnvironmentsApi(client) self.host = ServerHostApi(client) self.hooks = ServerHooksApi(client) self.models = ServerModelsApi(client) - self.sandbox = ServerSandboxApi(client) + self.content_exclusion = ServerContentExclusionApi(client) self.tools = ServerToolsApi(client) self.account = ServerAccountApi(client) self.secrets = ServerSecretsApi(client) self.mcp = ServerMcpApi(client) self.extensions = ServerExtensionsApi(client) + self.instructions = ServerInstructionsApi(client) self.skills = ServerSkillsApi(client) self.catalog = ServerCatalogApi(client) self.plugins = ServerPluginsApi(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) @@ -55802,6 +61508,380 @@ async def register_extension_launch_provider(self, *, timeout: float | None = No await self._client.request("registerExtensionLaunchProvider", {}, **_timeout_kwargs(timeout)) +# Experimental: this API group is experimental and may change or be removed. +class _InternalServerSandboxProxySecretApi: + def __init__(self, client: "JsonRpcClient"): + self._client = client + + async def _get_location(self, *, timeout: float | None = None) -> SandboxProxySecretLocation: + "Reports where this runtime keeps the sandbox proxy password: the credential store directory and the entry identifier within it. Both are derived from the configuration directory this runtime was started with, so a host does not resolve them itself. Read this to recognize a `${secret:…}` value in stored sandbox settings as this runtime's own proxy password rather than a reference the user wrote by hand. Marked internal because it exists to carry a CLI call site off the napi boundary onto the SDK contract.\n\nReturns:\n Where this runtime keeps the sandbox proxy password.\n\n:meta private:\n\nInternal SDK API; not part of the public surface." + return SandboxProxySecretLocation.from_dict(await self._client.request("sandbox.proxySecret.getLocation", {}, **_timeout_kwargs(timeout))) + + async def _get(self, *, timeout: float | None = None) -> SandboxProxySecretGetResult: + "Reads the stored sandbox proxy password from the credential store `sandbox.proxySecret.getLocation` names, preferring the OS keychain and falling back to the on-disk store. Reports an absent value rather than failing when no entry exists. Marked internal because it exists to carry a CLI call site off the napi boundary onto the SDK contract.\n\nReturns:\n The stored sandbox proxy password, if one is present.\n\n:meta private:\n\nInternal SDK API; not part of the public surface." + return SandboxProxySecretGetResult.from_dict(await self._client.request("sandbox.proxySecret.get", {}, **_timeout_kwargs(timeout))) + + async def _save(self, params: SandboxProxySecretSaveRequest, *, timeout: float | None = None) -> SandboxProxySecretSaveResult: + "Stores the sandbox proxy password in the credential store `sandbox.proxySecret.getLocation` names, replacing any existing entry. Reports whether the OS keychain accepted it or it fell back to the on-disk store. Marked internal because it exists to carry a CLI call site off the napi boundary onto the SDK contract.\n\nArgs:\n params: The sandbox proxy password to store.\n\nReturns:\n Where the sandbox proxy password was stored.\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 SandboxProxySecretSaveResult.from_dict(await self._client.request("sandbox.proxySecret.save", params_dict, **_timeout_kwargs(timeout))) + + async def _delete(self, *, timeout: float | None = None) -> None: + "Removes the stored sandbox proxy password from the credential store `sandbox.proxySecret.getLocation` names. Succeeds when no entry exists, so it is safe to call while clearing a proxy password whose origin is unknown. Marked internal because it exists to carry a CLI call site off the napi boundary onto the SDK contract.\n\n:meta private:\n\nInternal SDK API; not part of the public surface." + await self._client.request("sandbox.proxySecret.delete", {}, **_timeout_kwargs(timeout)) + + +# Experimental: this API group is experimental and may change or be removed. +class _InternalServerSandboxSettingsApi: + def __init__(self, client: "JsonRpcClient"): + self._client = client + + async def _apply_defaults(self, params: SandboxApplyDefaultsRequest, *, timeout: float | None = None) -> dict: + "Resolves a stored sandbox settings object into the effective one, filling every value the user left unset with its default. Read this to render or reason about the sandbox configuration a session would actually run under, rather than the sparse document the user wrote.\n\nArgs:\n params: The stored sandbox settings to resolve against the defaults.\n\nReturns:\n The effective sandbox settings, with every unset value resolved to its default.\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 dict(await self._client.request("sandbox.settings.applyDefaults", params_dict, **_timeout_kwargs(timeout))) + + +# Experimental: this API group is experimental and may change or be removed. +class _InternalServerSandboxApi: + def __init__(self, client: "JsonRpcClient"): + self._client = client + self.proxy_secret = _InternalServerSandboxProxySecretApi(client) + self.settings = _InternalServerSandboxSettingsApi(client) + + async def _resolve_floored_config(self, params: _SandboxResolveFlooredConfigParams, *, timeout: float | None = None) -> SandboxResolveFlooredConfigResult: + "Raises a user's sandbox settings to an administrator's managed sandbox floor and returns the result a session should actually run under. The floor can only tighten: an enterprise force-on is reflected even when the user's own `enabled` is false, and a user cannot lower a setting the floor raised. The result is normalized through the declared sandbox shape, so a key reorder or an undeclared passthrough key is not mistaken for a policy change by callers that diff two results. A host must call this rather than merge the two layers itself, because reimplementing the direction of the fold is what turns an administrator's floor into a suggestion.\n\nArgs:\n params: The user's sandbox settings and the managed sandbox floor to raise them to.\n\nReturns:\n The user's sandbox settings raised to the managed floor.\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 SandboxResolveFlooredConfigResult.from_dict(await self._client.request("sandbox.resolveFlooredConfig", params_dict, **_timeout_kwargs(timeout))) + + async def _prepare_picker_policy(self, params: _SandboxPreparePickerPolicyParams, *, timeout: float | None = None) -> _SandboxPreparePickerPolicyResult: + "Builds the effective filesystem policy one `@`-file picker checks its suggestions against, so Git discovery and grant canonicalization run once for the picker's lifetime instead of on every debounced keystroke. Returns no policy when the sandbox is absent or disabled, which the paired filter reads as \"keep every candidate\". A host must pair this with `sandbox.filterPickerCandidates` rather than resolve a grant list itself, because the picker's grants are narrower than a file tool's — the same path a built-in tool may read can be one the picker must not offer.\n\nArgs:\n params: The working directory and sandbox settings to build a file-picker policy from.\n\nReturns:\n The prepared file-picker policy, or none when the sandbox is disabled.\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 _SandboxPreparePickerPolicyResult.from_dict(await self._client.request("sandbox.preparePickerPolicy", params_dict, **_timeout_kwargs(timeout))) + + async def _filter_picker_candidates(self, params: _SandboxFilterPickerCandidatesParams, *, timeout: float | None = None) -> _SandboxFilterPickerCandidatesResult: + "Checks a batch of `@`-file picker suggestions against a policy prepared by `sandbox.preparePickerPolicy` and returns one allow decision per candidate, positionally aligned with the request. Batched deliberately: each check canonicalizes a path component-by-component through the real filesystem, so one call amortizes that over a single hop rather than dozens while the user types. A directory the policy does not itself allow can still be kept as a waypoint to an allowed subtree. An absent policy keeps every candidate.\n\nArgs:\n params: A batch of file-picker candidates to check against a prepared policy.\n\nReturns:\n Per-candidate allow decisions, positionally aligned with the request.\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 _SandboxFilterPickerCandidatesResult.from_dict(await self._client.request("sandbox.filterPickerCandidates", params_dict, **_timeout_kwargs(timeout))) + + async def _resolve_for_optional(self, params: _SandboxEffectiveForOptionalParams, *, timeout: float | None = None) -> _SandboxEffectiveForOptionalResult: + "The sandbox a single named feature actually launches under, which is not the same as the session's sandbox. A feature is routed out of the sandbox by its own flag — `sandboxMcpServers`, `sandboxLspServers` — so an enabled session can still start one server confined and another free. Returns a disabled sandbox rather than an error when the host has no sandbox slice at all, so an absent config and an opted-out feature read alike at the launch site. A host must ask per feature rather than reuse the session config, because reading only `enabled` confines a server the user opted out of, or reports as sandboxed one that is not.\n\nArgs:\n params: A sandbox config and the per-feature routing flag to resolve it for.\n\nReturns:\n The sandbox a named feature actually launches under.\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 _SandboxEffectiveForOptionalResult.from_dict(await self._client.request("sandbox.resolveForOptional", params_dict, **_timeout_kwargs(timeout))) + + async def _check_platform_support(self, *, timeout: float | None = None) -> bool: + "Reports whether this machine can run an OS-level command sandbox at all. A host must ask before it offers sandboxing, because the answer is a property of the machine rather than of any setting: on Linux it depends on bubblewrap being installed, and on Windows on the OS build supporting BaseContainer. Probing it requires spawning a process the first time, so the answer is cached for the life of the runtime. A host that assumes support and enables the sandbox anyway produces a session in which every shell command fails.\n\nReturns:\n True when an OS-level sandbox can run on this host.\n\n:meta private:\n\nInternal SDK API; not part of the public surface." + return bool(await self._client.request("sandbox.checkPlatformSupport", {}, **_timeout_kwargs(timeout))) + + async def _check_windows_host_loopback_support(self, *, timeout: float | None = None) -> bool: + "Reports whether this Windows host's BaseContainer implementation can reach the host's loopback interface directly, which is what a local-network policy needs to avoid routing through the egress proxy. Like `sandbox.checkPlatformSupport`, the answer is a property of the machine's OS build rather than of any setting, and resolving it the first time spawns a platform probe. A host that offers the direct local-network option on a machine that lacks the capability produces a session whose sandboxed commands cannot reach local services. Always false off Windows.\n\nReturns:\n True when the sandbox on this host can reach host loopback directly.\n\n:meta private:\n\nInternal SDK API; not part of the public surface." + return bool(await self._client.request("sandbox.checkWindowsHostLoopbackSupport", {}, **_timeout_kwargs(timeout))) + + async def _get_unsupported_message(self, *, timeout: float | None = None) -> str: + "The explanation a host shows when it refuses an explicit request to turn the sandbox on for a machine that cannot run one. Carries the platform-specific remedy — install bubblewrap, update Windows — and the documentation link, so a host that got `false` from `sandbox.checkPlatformSupport` does not have to compose its own reason and cannot describe the same machine differently from the runtime's own spawn path.\n\nReturns:\n The user-facing refusal, including the platform remedy and the documentation link.\n\n:meta private:\n\nInternal SDK API; not part of the public surface." + return str(await self._client.request("sandbox.getUnsupportedMessage", {}, **_timeout_kwargs(timeout))) + + async def _get_unsupported_setting_disabled_notice(self, *, timeout: float | None = None) -> str: + "The notice a host shows when a stored `sandbox` setting is being ignored for this session because the machine cannot enforce it. Distinct from `sandbox.getUnsupportedMessage`, which refuses an explicit request: this one reports that a setting the user already saved is inert, states that commands will run unsandboxed, and confirms the stored setting itself is left alone. A host that stays silent here leaves the user believing a sandbox is active.\n\nReturns:\n The user-facing notice that a saved sandbox setting is inert for this session.\n\n:meta private:\n\nInternal SDK API; not part of the public surface." + return str(await self._client.request("sandbox.getUnsupportedSettingDisabledNotice", {}, **_timeout_kwargs(timeout))) + + async def _get_unsupported_startup_notice(self, params: _SandboxUnsupportedStartupNoticeParams, *, timeout: float | None = None) -> _SandboxUnsupportedStartupNoticeResult: + "The warning a host prints at startup when a session asked for a sandbox on a machine that cannot run one. The third of the unsupported notices, and the only one that depends on the policy in force: it names the remedy the reader can actually act on, which is the stored setting for an unmanaged host, an administrator for a managed one, and waiting for the policy to resolve when the sandbox stayed on only because the policy could not be determined. Returns no notice when the machine does support a sandbox, so a host can call it unconditionally on the startup path. A host that composes its own warning tells a managed user to change a setting their policy will overwrite.\n\nArgs:\n params: The managed sandbox policy in force, which selects which remedy the notice offers.\n\nReturns:\n The startup warning, absent when the machine can run a sandbox.\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 _SandboxUnsupportedStartupNoticeResult.from_dict(await self._client.request("sandbox.getUnsupportedStartupNotice", params_dict, **_timeout_kwargs(timeout))) + + async def _check_enforcement_command(self, params: _SandboxEnforcementCommandAllowedParams, *, timeout: float | None = None) -> bool: + "Whether one slash command stays reachable while sandbox enforcement is unavailable and the session is otherwise blocked. The permitted set is deliberately narrow — commands that only report state, and the few that let the user repair or leave the situation — and it is keyed on the arguments as well as the name, so `/sandbox status` is admitted where `/sandbox enable` is not. A host must ask rather than keep its own list: a command this returns false for is one the runtime would refuse anyway, and a divergent list either strands the user with no way out or admits a command that acts on an unenforced session.\n\nArgs:\n params: One slash command, as typed, to test against the enforcement-block allowlist.\n\nReturns:\n True when the command stays available while enforcement is blocked.\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 bool(await self._client.request("sandbox.checkEnforcementCommand", params_dict, **_timeout_kwargs(timeout))) + + async def _get_docs_url(self, *, timeout: float | None = None) -> SandboxDocsURLResult: + "Returns the documentation URL a host should point a user at when it declines or downgrades a sandbox setting. The runtime owns the address so every surface cites the same page and a host does not hard-code a link that later moves.\n\nReturns:\n The documentation URL a host cites when it declines or downgrades a sandbox setting.\n\n:meta private:\n\nInternal SDK API; not part of the public surface." + return SandboxDocsURLResult.from_dict(await self._client.request("sandbox.getDocsUrl", {}, **_timeout_kwargs(timeout))) + + +# Experimental: this API group is experimental and may change or be removed. +class _InternalServerPersistenceApi: + def __init__(self, client: "JsonRpcClient"): + self._client = client + + async def _write_raw_json_locked(self, params: PersistenceWriteRawJSONLockedRequest, *, timeout: float | None = None) -> PersistenceWriteRawJSONLockedResult: + "Atomically replaces a JSON file's entire contents while holding the runtime's per-path lock. The lock lives in the runtime process, so a host that writes these files itself takes no part in it and can tear a concurrent runtime write; route the write here instead. Unlike a merging write this never reads or parses the existing file, so a previously corrupted snapshot is replaced wholesale rather than failing every later write. Use it only for callers that always persist a complete snapshot.\n\nArgs:\n params: A complete JSON snapshot to write under the runtime's per-path file lock.\n\nReturns:\n The outcome of a locked snapshot write.\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 PersistenceWriteRawJSONLockedResult.from_dict(await self._client.request("persistence.writeRawJsonLocked", params_dict, **_timeout_kwargs(timeout))) + + +# Experimental: this API group is experimental and may change or be removed. +class _InternalServerUserSettingsApi: + def __init__(self, client: "JsonRpcClient"): + self._client = client + + async def _set_hook_disabled(self, params: _UserSettingsSetHookDisabledRequest, *, timeout: float | None = None) -> _UserSettingsSetHookDisabledResult: + "Adds or removes one hook key in the user `disabledHooks` setting, rewriting settings.json in place and leaving every other setting untouched. Reports a typed outcome rather than failing the call, so a host can tell an unparseable settings file apart from a write that could not complete.\n\nArgs:\n params: One hook to disable or re-enable.\n\nReturns:\n Outcome of rewriting the `disabledHooks` setting.\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 _UserSettingsSetHookDisabledResult.from_dict(await self._client.request("user.settings.setHookDisabled", params_dict, **_timeout_kwargs(timeout))) + + async def _load_for_config_dir(self, params: _UserSettingsLoadForConfigDirRequest, *, timeout: float | None = None) -> dict: + "Reads the user's settings document exactly as `user.settings.load` does, but from a caller-supplied configuration directory instead of the one the server resolved for itself. Use this when a consumer scopes a session to its own Copilot home — the SDK's per-session `configDir` override — so the settings read match the directory that session actually uses. An absent or empty `configDir` resolves the server's own home, making this identical to `user.settings.load`. Hook-validation relaxations are deliberately not caller-supplied: they continue to come from the server's environment, so a consumer cannot widen the host's hook policy through this method.\n\nArgs:\n params: Selects the configuration directory whose user settings to read.\n\nReturns:\n The stored user settings document, keyed by setting name.\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 dict(await self._client.request("user.settings.loadForConfigDir", params_dict, **_timeout_kwargs(timeout))) + + async def _load_repo(self, params: UserSettingsLoadRepoRequest, *, timeout: float | None = None) -> UserSettingsLoadRepoResult: + "Reads a repository's checked-in settings document as stored, normalized, together with any diagnostic raised while reading it. This is the repository half of the layering `user.settings.get` reports: `user.settings.load` reads the user's own document, and this reads the one a repository checks in to govern the keys `user.settings.governanceKeys` lists as repo-governable. A document that fails to parse, or that sets a key a repository may not govern, is reported through `warning` while the settings that did load are still returned, so a consumer surfaces the diagnostic and continues rather than failing the session. When `repoRoot` is the user's home directory the Claude-compatible configuration is skipped, so a home-directory session does not inherit a personal config as repository policy.\n\nArgs:\n params: Selects the repository whose checked-in settings to read.\n\nReturns:\n A repository's settings as stored, together with any diagnostic raised while reading them.\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 UserSettingsLoadRepoResult.from_dict(await self._client.request("user.settings.loadRepo", params_dict, **_timeout_kwargs(timeout))) + + async def _load_and_merge_repo(self, params: _UserSettingsLoadAndMergeRepoRequest, *, timeout: float | None = None) -> _UserSettingsLoadAndMergeRepoResult: + "Reads a repository's settings and merges them over a supplied user layer in one step, returning the settings a consumer should act on inside that repository. Use this rather than reading the two layers separately: the merge is per setting and collection-aware, so a repository adds to the collections it is allowed to influence without replacing the user's own entries, and a whole-document overlay would get that wrong. Any diagnostic raised while reading the repository layer is returned rather than printed, so the consumer decides how to surface it; the settings that did load are merged regardless.\n\nArgs:\n params: A repository to read settings from, and the user layer they are merged over.\n\nReturns:\n The settings a consumer should act on inside a repository, together with any diagnostic raised while reading the repository layer.\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 _UserSettingsLoadAndMergeRepoResult.from_dict(await self._client.request("user.settings.loadAndMergeRepo", params_dict, **_timeout_kwargs(timeout))) + + async def _get_path(self, *, timeout: float | None = None) -> UserSettingsPathResult: + "Reports the on-disk path of the settings document `user.settings.load` and `user.settings.set` read and write. Resolved the same way those methods resolve it, so a host that shows or opens the settings file names the file the runtime actually uses, including the extensionless legacy `settings` file when that is what is present.\n\nReturns:\n The on-disk location of the user's settings document.\n\n:meta private:\n\nInternal SDK API; not part of the public surface." + return UserSettingsPathResult.from_dict(await self._client.request("user.settings.getPath", {}, **_timeout_kwargs(timeout))) + + async def _load_with_warning(self, *, timeout: float | None = None) -> UserSettingsLoadWithWarningResult: + "Reads the user's stored settings together with the diagnostic raised while reading them. This is `user.settings.load` plus the reason part of the document was ignored: that one answers the document alone, so a caller cannot tell a clean read from a salvaged one. Read this when the caller is about to write the file back, where accepting a partial parse would discard every value the loader could not read. The settings that did load are still reported alongside the warning, so a consumer may surface it and continue instead of failing.\n\nReturns:\n The user's stored settings, together with any diagnostic raised while reading them.\n\n:meta private:\n\nInternal SDK API; not part of the public surface." + return UserSettingsLoadWithWarningResult.from_dict(await self._client.request("user.settings.loadWithWarning", {}, **_timeout_kwargs(timeout))) + + async def _merge_managed_and_user(self, params: _UserSettingsMergeManagedAndUserParams, *, timeout: float | None = None) -> UserSettingsMergeManagedAndUserResult: + "Folds a user settings layer over a managed (enterprise policy) layer and returns the merged settings. The managed layer's governance keys win, `autoTier` resolves through its overridable form, and the two per-entry control maps (`enabledPlugins`, `extraKnownMarketplaces`) union across the layers and are then stamped back from the managed layer, so each entry an administrator declares wins while the user keeps control of the rest. The parameters are named for their layers rather than being a generic base/override pair: nothing about a settings object identifies which layer produced it, so the runtime cannot detect a transposed call, and transposing one resolves the governance keys in the user's favour instead of the administrator's. Supplying only one layer returns it unchanged, and supplying neither returns an empty object. Rejects a managed `autoTier` that is neither a user-configurable tier nor a single-key overridable form, rather than silently dropping it.\n\nArgs:\n params: The managed (enterprise policy) layer and the user layer to fold together. The field names carry the layer identity: nothing about a settings object identifies which layer produced it, so a generic base/override pair would let a caller transpose them and silently resolve the governance keys in the user's favour.\n\nReturns:\n The user layer folded over the managed layer.\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 UserSettingsMergeManagedAndUserResult.from_dict(await self._client.request("user.settings.mergeManagedAndUser", params_dict, **_timeout_kwargs(timeout))) + + async def _merge_repo_value(self, params: _UserSettingsMergeRepoValueParams, *, timeout: float | None = None) -> UserSettingsMergeRepoValueResult: + "Folds a repository's settings layer into the user layer. Each repo-overridable key carries a directional policy chosen so a repository can only add restrictions and never relax the user's own. The two per-entry managed control maps are the exception those directional rules cannot express — a repository may legitimately enable plugins and declare marketplaces — so they are re-stamped afterwards from the runtime's ambient managed policy. That makes the result depend on the runtime process's managed state and not on the arguments alone: a host must call this rather than reimplement the fold, or a repository could relax a restriction the administrator imposed. Rejects when either layer is not a JSON object.\n\nArgs:\n params: The user layer and the repository layer to fold together.\n\nReturns:\n The repository layer folded into the user layer.\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 UserSettingsMergeRepoValueResult.from_dict(await self._client.request("user.settings.mergeRepoValue", params_dict, **_timeout_kwargs(timeout))) + + async def _migrate_staff_update_channel_once(self, params: UserSettingsHostContext, *, timeout: float | None = None) -> _UserSettingsMigrateStaffUpdateChannelOnceResult: + "Runs the one-time migration that moves a staff account's update channel to stable, and records that it ran. Idempotent and self-gating: it takes a lock over the global-state file, re-reads the completion stamp inside it, and returns `current` rather than migrating twice, so two hosts racing at startup cannot both migrate. Only a `migrated` status means the persisted settings changed and the caller should reload them. An account on the experimental channel is deliberately left alone and reported as `excluded-experimental`.\n\nArgs:\n params: The filesystem and environment facts a user-settings file is resolved from. Passed explicitly rather than read from the runtime's own process, so a host whose configuration directory differs from its process environment resolves the file it means.\n\nReturns:\n The outcome of the one-time staff update-channel migration.\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 _UserSettingsMigrateStaffUpdateChannelOnceResult.from_dict(await self._client.request("user.settings.migrateStaffUpdateChannelOnce", params_dict, **_timeout_kwargs(timeout))) + + async def _reset_staff_model_selection_once(self, params: _UserSettingsResetStaffModelSelectionOnceRequest, *, timeout: float | None = None) -> _UserSettingsResetStaffModelSelectionOnceResult: + "Runs the one-time reset that clears a staff account's pinned model selection, and reports the timestamp the host should record. Idempotent and self-gating: the reset is gated on a fixed cutoff, so a caller that passes back the timestamp it recorded gets `didReset: false` rather than a second reset. It applies to staff accounts only; any other account is reported as not reset without the settings document being opened. Only `didReset: true` means the persisted settings changed, and only then are the updated settings returned for the host to adopt.\n\nArgs:\n params: Selects the configuration directory to run the one-time staff model-selection reset against, and supplies the caller's record of whether it has already run.\n\nReturns:\n The outcome of the one-time staff model-selection reset.\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 _UserSettingsResetStaffModelSelectionOnceResult.from_dict(await self._client.request("user.settings.resetStaffModelSelectionOnce", params_dict, **_timeout_kwargs(timeout))) + + async def _get_unknown_keys_warning(self, params: _UserSettingsUnknownKeysWarningRequest, *, timeout: float | None = None) -> _UserSettingsUnknownKeysWarningResult: + "Reports the top-level keys in the user settings document that the runtime does not recognize, phrased for display to the user. This is a separate question from the warning `user.settings.loadWithWarning` returns: that one describes parts of the document the loader had to ignore to load the rest, whereas an unknown key parses cleanly and is simply not a setting, so a host that surfaces only the load warning silently accepts a misspelled key that will never take effect. Nothing is reported when the document is absent, unreadable, or failed to load, because an unknown-key list derived from a document the runtime could not read would be misleading rather than helpful. Inspection never modifies the document.\n\nArgs:\n params: Selects the configuration directory whose user settings document should be inspected for unrecognized top-level keys.\n\nReturns:\n The unrecognized top-level keys found in the user settings document, already phrased for display.\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 _UserSettingsUnknownKeysWarningResult.from_dict(await self._client.request("user.settings.getUnknownKeysWarning", params_dict, **_timeout_kwargs(timeout))) + + async def _build_runtime(self, params: _UserSettingsBuildRuntimeRequest, *, timeout: float | None = None) -> dict: + "Resolves the effective runtime settings for a host that is about to start a session, layering the given overrides over the settings documents, the environment and the platform defaults. A host cannot assemble this itself: the precedence between a user document, a repository document, the managed layers and the environment is the runtime's own rule, and a host that re-implements it drifts from the server it is configuring. The working directory, environment and platform are taken as arguments rather than read from the server process, so a host resolving settings for a directory other than its own gets that directory's answer. Nothing is written; this only computes.\n\nArgs:\n params: The overrides and host context to resolve effective runtime settings from.\n\nReturns:\n The effective runtime settings, keyed by setting name. Keys this runtime does not recognize are preserved rather than dropped, so a caller reading a document written by a newer runtime keeps every value.\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 dict(await self._client.request("user.settings.buildRuntime", params_dict, **_timeout_kwargs(timeout))) + + async def _migrate_staff_log_level_once(self, params: _UserSettingsMigrateStaffLogLevelOnceRequest, *, timeout: float | None = None) -> _UserSettingsMigrateStaffLogLevelOnceResult: + "Runs the one-time migration that moves a staff account's log level to the current default, and records that it ran. Idempotent and self-gating: it takes a lock over the global-state file and re-reads the completion stamp inside it, so two hosts racing at startup cannot both migrate. Only `refreshed` can have changed the persisted settings, and the settings it returns are the document re-read after the write, so a host should adopt them rather than reloading separately and racing the next writer.\n\nArgs:\n params: Selects the configuration directory to run the one-time staff log-level migration against, and supplies the caller's record of whether it has already run.\n\nReturns:\n The outcome of the one-time staff log-level migration.\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 _UserSettingsMigrateStaffLogLevelOnceResult.from_dict(await self._client.request("user.settings.migrateStaffLogLevelOnce", params_dict, **_timeout_kwargs(timeout))) + + async def _migrate_obsolete_on_disk(self, params: _UserSettingsMigrateObsoleteOnDiskRequest, *, timeout: float | None = None) -> _UserSettingsMigrateObsoleteOnDiskResult: + "Runs the one-time cleanup that removes obsolete CLI-owned settings and legacy rubber-duck keys from the user's settings document, and reports what it did. Idempotent and safe to call on every startup: with nothing obsolete left it reports `unchanged` and writes nothing. The cleanup preserves every key it does not recognize, so a setting written by a newer host is never dropped by an older one, and a document that fails to parse is reported `invalid` and left untouched rather than rewritten from a partial read. The cleaned document is not returned: it is written before this replies, so `user.settings.load` reads it authoritatively. A host that surfaces unrecognized keys through `user.settings.unknownKeys` wants this first, because the keys this retires would otherwise be shown to the user as if they were misspelled settings.\n\nArgs:\n params: Selects the configuration directory whose settings document to clean.\n\nReturns:\n The outcome of the one-time obsolete-settings cleanup.\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 _UserSettingsMigrateObsoleteOnDiskResult.from_dict(await self._client.request("user.settings.migrateObsoleteOnDisk", params_dict, **_timeout_kwargs(timeout))) + + async def _get_paths(self, params: UserSettingsGetPathsRequest, *, timeout: float | None = None) -> UserSettingsGetPathsResult: + "Resolves the on-disk locations of the settings files this runtime reads, so a consumer can show or open them. The user path is the same file `user.settings.get`, `user.settings.load`, and `user.settings.set` operate on. Supply `repoRoot` to also resolve that repository's checked-in and local settings paths. A path is reported whether or not the file exists.\n\nArgs:\n params: Selects which settings file paths to resolve.\n\nReturns:\n On-disk locations of the settings files this runtime reads. A path is reported whether or not the file exists.\n\n:meta private:\n\nInternal SDK API; not part of the public surface." + params_dict = {k: v for k, v in params.to_dict().items() if v is not None} + return UserSettingsGetPathsResult.from_dict(await self._client.request("user.settings.getPaths", params_dict, **_timeout_kwargs(timeout))) + + async def _list_governance_keys(self, *, timeout: float | None = None) -> UserSettingsGovernanceKeysResult: + "Lists the top-level user-setting keys by who is allowed to govern them: `repo` keys a repository may override through `.github/copilot/settings.json`, `claudeRepo` keys a repository may override through a Claude-compatible config, and `managed` keys an enterprise policy may fix. The lists are static for a given runtime build and fail closed: a key absent from a list cannot be governed that way.\n\nReturns:\n Top-level user-setting keys grouped by who may govern them.\n\n:meta private:\n\nInternal SDK API; not part of the public surface." + return UserSettingsGovernanceKeysResult.from_dict(await self._client.request("user.settings.listGovernanceKeys", {}, **_timeout_kwargs(timeout))) + + async def _edit(self, params: _UserSettingsEditRequest, *, timeout: float | None = None) -> _SettingsEditResult: + "Atomically edits one canonical user setting inside its settings-file lock. Reads, canonicalizes touched aliases, applies the edit, validates the resulting top-level setting and writes before releasing the lock. An explicit autoUpdatesChannel set completes the staff migration in the same transaction. Only a malformed existing file returns `invalid`; refused keys, invalid values and I/O errors reject the call.\n\nArgs:\n params: One atomic edit of a canonical setting in settings.json.\n\nReturns:\n Outcome of an atomic user or repository settings edit.\n\n:meta private:\n\nInternal SDK API; not part of the public surface." + params_dict = params.to_dict() + return _SettingsEditResult.from_dict(await self._client.request("user.settings.edit", params_dict, **_timeout_kwargs(timeout))) + + async def _normalize(self, params: UserSettingsNormalizeRequest, *, timeout: float | None = None) -> UserSettingsNormalizeResult: + "Rewrites legacy and snake_case setting names in a settings object to their canonical spelling. A caller editing settings.json must normalize before writing: a legacy key left in place survives the write, is re-normalized on the next load, and silently shadows the canonical value the write just stored.\n\nArgs:\n params: Settings object whose key names should be canonicalized.\n\nReturns:\n Settings object with canonical key names.\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 UserSettingsNormalizeResult.from_dict(await self._client.request("user.settings.normalize", params_dict, **_timeout_kwargs(timeout))) + + async def _diagnose_problems(self, params: UserSettingsProblemsRequest, *, timeout: float | None = None) -> UserSettingsProblemsResult: + "Diagnoses a set of already-loaded settings layers, reporting each key that will not take effect and why. Settings load tolerantly so that one bad key does not stop a session from starting; this method surfaces what was dropped so a caller can report it instead of silently ignoring the user's intent. Takes the layers as given rather than reading them, so a caller can diagnose exactly the content it is showing, including unsaved edits. Never echoes a setting's value, because a value may be a token or another secret. Marked internal because it exists to carry a CLI call site off the native addon, not to serve a consumer-facing need; it becomes public once an SDK consumer needs to inspect settings health itself.\n\nArgs:\n params: The settings layers to diagnose.\n\nReturns:\n The settings that will not take effect across the given layers.\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 UserSettingsProblemsResult.from_dict(await self._client.request("user.settings.diagnoseProblems", params_dict, **_timeout_kwargs(timeout))) + + async def _apply_top_key(self, params: UserSettingsApplyTopKeyRequest, *, timeout: float | None = None) -> UserSettingsApplyTopKeyResult: + "Validates one top-level setting subtree and writes just that key to settings.json as a single step. Doing both here closes the race a caller cannot close itself: between a separate preflight read and the write, the file may change. Rewriting only the touched key keeps legacy siblings out of settings.json. Answers a status rather than failing, so a validation problem and a write problem stay distinguishable.\n\nArgs:\n params: A single top-level setting subtree to validate and write.\n\nReturns:\n Outcome of validating and writing one top-level setting subtree.\n\n:meta private:\n\nInternal SDK API; not part of the public surface." + params_dict = params.to_dict() + return UserSettingsApplyTopKeyResult.from_dict(await self._client.request("user.settings.applyTopKey", params_dict, **_timeout_kwargs(timeout))) + + async def _write_all(self, params: UserSettingsWriteAllRequest, *, timeout: float | None = None) -> None: + "Replaces the user settings file's entire content in one write. A key absent from `settings` is removed, which is what distinguishes this from `user.settings.set`: that one merges, so it can never clear a key the caller has dropped. For a caller saving the result of a raw edit, a merge would silently resurrect every key the user deleted. Records the same one-time mutation marks a per-key write records, so a setting the user has now chosen explicitly stops being migrated for them.\n\nArgs:\n params: The whole content to store in the user settings file.\n\n:meta private:\n\nInternal SDK API; not part of the public surface." + params_dict = {k: v for k, v in params.to_dict().items() if v is not None} + await self._client.request("user.settings.writeAll", params_dict, **_timeout_kwargs(timeout)) + + async def _update(self, params: _UserSettingsUpdateRequest, *, timeout: float | None = None) -> _UserSettingsUpdateResult: + "Merges a partial object into one top-level user setting, re-reading the stored value inside this runtime process's settings.json write lock so that concurrent writers in this process to different sub-fields of the same key do not clobber each other. Unlike `user.settings.set`, which replaces a key's whole value with what the caller last read, this keeps sub-fields the caller did not name when both the stored value and supplied value are objects; otherwise the supplied value replaces the stored value. Use it whenever two independent surfaces own different sub-fields of one setting. Returns the key's stored value after the write, in `value`.\n\nArgs:\n params: Names the user setting to merge into, and the partial object to merge.\n\nReturns:\n The stored value of the user setting after the merge.\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 _UserSettingsUpdateResult.from_dict(await self._client.request("user.settings.update", params_dict, **_timeout_kwargs(timeout))) + + async def _load_for_edit(self, *, timeout: float | None = None) -> UserSettingsLoadForEditResult: + "Reads settings.json as stored for a caller that is about to edit it. Unlike `user.settings.get`, this returns file values rather than per-key metadata and defaults. Reports `missing` for an absent file and `invalid` when the existing file will not parse. Retired config.json and extensionless files are not read.\n\nReturns:\n Contents of settings.json as stored, for a caller about to rewrite it.\n\n:meta private:\n\nInternal SDK API; not part of the public surface." + return UserSettingsLoadForEditResult.from_dict(await self._client.request("user.settings.loadForEdit", {}, **_timeout_kwargs(timeout))) + + async def _validate(self, params: UserSettingsValidateRequest, *, timeout: float | None = None) -> UserSettingsValidateResult: + "Validates one top-level setting subtree without writing it, reporting each problem with the path it applies to. Scoped to a single top-level key on purpose: validating the whole file would reject an edit whenever an unrelated pre-existing field is malformed, locking the user out of the very tool that could fix it.\n\nArgs:\n params: A single top-level setting subtree to validate.\n\nReturns:\n Outcome of validating one top-level setting subtree.\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 UserSettingsValidateResult.from_dict(await self._client.request("user.settings.validate", params_dict, **_timeout_kwargs(timeout))) + + +# Experimental: this API group is experimental and may change or be removed. +class _InternalServerUserApi: + def __init__(self, client: "JsonRpcClient"): + self._client = client + self.settings = _InternalServerUserSettingsApi(client) + + +# Experimental: this API group is experimental and may change or be removed. +class _InternalServerRepoSettingsApi: + def __init__(self, client: "JsonRpcClient"): + self._client = client + + async def _edit(self, params: _RepoSettingsEditRequest, *, timeout: float | None = None) -> _SettingsEditResult: + "Atomically edits one allowed top-level repository setting. Reads the raw file, preserves unrelated keys, applies one scalar or list edit, validates the result and writes under one file lock, so concurrent sibling or list edits are not lost. A malformed or unsafe existing file returns `invalid` without a write; rejected keys, invalid values and I/O errors reject the call.\n\nArgs:\n params: One atomic edit of an allowed top-level repository setting.\n\nReturns:\n Outcome of an atomic user or repository settings edit.\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 _SettingsEditResult.from_dict(await self._client.request("repo.settings.edit", params_dict, **_timeout_kwargs(timeout))) + + async def _read_raw(self, params: _RepoSettingsReadRawRequest, *, timeout: float | None = None) -> _RepoSettingsReadRawResult: + "Reads one repository settings file without settings normalization or layering. The existing JSONC parser accepts comments and trailing commas and applies its prototype-safety rules. Missing or blank files return an empty valid config. Invalid files return an empty config with invalid=true; other read failures reject.\n\nArgs:\n params: Which repository settings file to read.\n\nReturns:\n Parsed repository settings without normalization or layering.\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 _RepoSettingsReadRawResult.from_dict(await self._client.request("repo.settings.readRaw", params_dict, **_timeout_kwargs(timeout))) + + async def _write_key(self, params: _RepoSettingsWriteKeyRequest, *, timeout: float | None = None) -> None: + "Writes or removes one allowed top-level repository setting through the existing validator and file lock. Preserves unrelated stored keys. Invalid keys, values, existing documents, and I/O errors reject without replacing the file. Deletion requires delete=true; otherwise value must be present.\n\nArgs:\n params: One key to write or remove in a repository settings file.\n\n:meta private:\n\nInternal SDK API; not part of the public surface." + params_dict = params.to_dict() + await self._client.request("repo.settings.writeKey", params_dict, **_timeout_kwargs(timeout)) + + async def _write_all(self, params: _RepoSettingsWriteAllRequest, *, timeout: float | None = None) -> None: + "Replaces the repository-overridable settings in one locked write. Allowed keys present in settings are validated and written; allowed keys absent from settings are removed. Other input keys are ignored, and unknown keys already stored in the file remain unchanged. Invalid values or an invalid existing file reject without a partial write.\n\nArgs:\n params: The complete set of repository-overridable settings to store.\n\n:meta private:\n\nInternal SDK API; not part of the public surface." + params_dict = {k: v for k, v in params.to_dict().items() if v is not None} + await self._client.request("repo.settings.writeAll", params_dict, **_timeout_kwargs(timeout)) + + +# Experimental: this API group is experimental and may change or be removed. +class _InternalServerRepoApi: + def __init__(self, client: "JsonRpcClient"): + self._client = client + self.settings = _InternalServerRepoSettingsApi(client) + + +# Experimental: this API group is experimental and may change or be removed. +class _InternalServerFeatureFlagsApi: + def __init__(self, client: "JsonRpcClient"): + self._client = client + + async def _resolve_startup_gates(self, params: _FeatureFlagsResolveStartupGatesRequest, *, timeout: float | None = None) -> dict: + "Resolves named gates against this installation's cached online-experiment assignment, for the account the runtime is already authenticated as. Unlike `featureFlags.resolve`, which layers static availability with the populations supplied, this reads the experiment assignment the runtime cached for its own current authentication, so a launch can honor a rollout before any session exists. The account is never taken from the request: naming one would read another account's cached assignments. The cached assignment answers immediately and any network refresh happens behind it, so this call does not wait on the network. Not available to an extension, which cannot carry the user's consent to read their assignment.\n\nArgs:\n params: Caller population and startup inputs to resolve named online-experiment gates for. Carries no account: the runtime resolves the assignment for the authentication it already holds.\n\nReturns:\n The resolved gates, keyed by flag name.\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 dict(await self._client.request("featureFlags.resolveStartupGates", params_dict, **_timeout_kwargs(timeout))) + + async def _list_metadata(self, *, timeout: float | None = None) -> _FeatureFlagsMetadataResult: + "Lists every feature flag this runtime declares, with the population each one is declared for, its experimental description, and the online-experiment flag it maps to. This is the static catalog: it describes how each flag is defined, not whether it is on for anyone. Use `featureFlags.resolve` for static values with caller overrides, not active runtime values or experiment assignments.\n\nReturns:\n Every feature flag this runtime declares, with the shape each one was declared with.\n\n:meta private:\n\nInternal SDK API; not part of the public surface." + return _FeatureFlagsMetadataResult.from_dict(await self._client.request("featureFlags.listMetadata", {}, **_timeout_kwargs(timeout))) + + async def _resolve(self, params: _FeatureFlagsResolveRequest, *, timeout: float | None = None) -> dict: + "Computes static feature flags from the supplied population and overrides without changing runtime state or verifying audience membership. Starts from declared availability, then applies direct feature-flag environment values, user settings, and explicit `flagOverrides`. It does not apply `COPILOT_EXP_*` experiment assignments, authenticated startup gates, or active runtime state. Settings include `enabledFeatureFlags`, the legacy `feature_flags.enabled` list, and supported convenience settings. Supply `baseFlags` to use an existing flag set instead of declared availability. Omit `isStaffGitHub` or `isStaffMicrosoft` when membership was never recorded: a legacy staff account then counts as both populations.\n\nArgs:\n params: Caller population and overrides for static feature-flag resolution, independent of experiment assignments and active runtime state.\n\nReturns:\n The resolved feature flags, keyed by flag name.\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 dict(await self._client.request("featureFlags.resolve", params_dict, **_timeout_kwargs(timeout))) + + +# Experimental: this API group is experimental and may change or be removed. +class _InternalServerPermissionsLocationsApi: + def __init__(self, client: "JsonRpcClient"): + self._client = client + + async def _cleanup_orphaned(self, *, timeout: float | None = None) -> _LocationPermissionsCleanupResult: + "Drops stored per-location permission grants whose directory no longer exists, and reports which location keys were removed. Runs against the permission store under the runtime's own Copilot home, the same store `session.permissions.locations.resolve` reads. Marked internal because it exists to carry a CLI call site off the napi boundary onto the SDK contract; it is migration plumbing, not a surface consumers are meant to depend on. It can become public once an SDK consumer needs to prune its own permission store.\n\nReturns:\n Which stored location permissions were dropped because their directory is gone.\n\n:meta private:\n\nInternal SDK API; not part of the public surface." + return _LocationPermissionsCleanupResult.from_dict(await self._client.request("permissions.locations.cleanupOrphaned", {}, **_timeout_kwargs(timeout))) + + +# Experimental: this API group is experimental and may change or be removed. +class _InternalServerPermissionsFolderTrustApi: + def __init__(self, client: "JsonRpcClient"): + self._client = client + + async def _check_trust(self, params: _FolderTrustIsTrustedParams, *, timeout: float | None = None) -> FolderTrustCheckResult: + "Reports whether a folder is trusted, reading the persisted trusted-folders list directly rather than through a session. Use this from a host that has no session yet, such as a one-shot command deciding whether to honor a checkout's own settings. It differs from the session-scoped check in two ways that matter: it takes the configuration directory to read the list from, instead of inheriting the session's, and it answers only from the persisted list, so trust a user granted for the lifetime of one session is not visible here. A host that has a session should prefer the session-scoped check, which also covers session-only trust.\n\nArgs:\n params: A folder to check against a persisted trusted-folders list, and the configuration directory holding that list.\n\nReturns:\n Folder trust check result.\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 FolderTrustCheckResult.from_dict(await self._client.request("permissions.folderTrust.checkTrust", params_dict, **_timeout_kwargs(timeout))) + + +# Experimental: this API group is experimental and may change or be removed. +class _InternalServerPermissionsApi: + def __init__(self, client: "JsonRpcClient"): + self._client = client + self.locations = _InternalServerPermissionsLocationsApi(client) + self.folder_trust = _InternalServerPermissionsFolderTrustApi(client) + + async def _evaluate_managed_policy(self, params: _PermissionsEvaluateManagedPolicyParams, *, timeout: float | None = None) -> PermissionsEvaluateManagedPolicyResult: + "Interprets a verbatim managed `permissions` slice into the three policy questions a host asks before granting elevated permissions: whether bypass-permissions mode is forbidden, whether a policy exists that this build could not determine, and whether the layer supplies permission rules the session must enforce. An absent or empty slice means no managed policy applies. The runtime and its permission service share one policy type, so a new managed permission setting is interpreted in exactly one place; a host that reimplements the reading would silently diverge the first time that set grows. Hosts must fail closed on an undetermined policy rather than treating it as permission granted.\n\nArgs:\n params: The verbatim managed `permissions` slice to interpret.\n\nReturns:\n The policy questions a host asks before granting elevated permissions.\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 PermissionsEvaluateManagedPolicyResult.from_dict(await self._client.request("permissions.evaluateManagedPolicy", params_dict, **_timeout_kwargs(timeout))) + + +# Experimental: this API group is experimental and may change or be removed. +class _InternalServerManagedSettingsApi: + def __init__(self, client: "JsonRpcClient"): + self._client = client + + async def _read_device(self, *, timeout: float | None = None) -> _ManagedSettingsReadDeviceResult: + "Discovers device-managed settings the way the runtime's own device layer does, and reports the sandbox floor and the load outcome alongside the validated document. This differs from `managedSettings.read` in three ways a policy-enforcing host depends on: a document that validates to an empty object is reported as absent rather than as an empty policy, a discovery error does not suppress a document that still validates, and a failure is reported as a flag rather than only as error text. Hosts that compose the device layer into an effective policy use this, because `managedSettings.read` cannot distinguish \"no device policy\" from \"device policy could not be read\" and would silently hand the user's own layer the win.\n\nReturns:\n The device-managed settings layer, with the sandbox floor and the load outcome a host needs to compose it into an effective policy.\n\n:meta private:\n\nInternal SDK API; not part of the public surface." + return _ManagedSettingsReadDeviceResult.from_dict(await self._client.request("managedSettings.readDevice", {}, **_timeout_kwargs(timeout))) + + async def _retain_policy(self, params: _ManagedSettingsRetainPolicyParams, *, timeout: float | None = None) -> None: + "Retains the supplied managed layers as this runtime process's startup policy and re-applies the result to every live session. Hosts that own their own short-lived process policy use this, so a guarded mutation is checked against the policy the host actually fetched. Retaining is a whole-process replacement rather than a merge, so a host that already resolved a good policy must not retain a partial one over it. The failure flags are honest inputs rather than assumptions: reporting a fetch or load that failed keeps the layers already retained, and the interim fail-closed state, instead of clearing them.\n\nArgs:\n params: The managed layers to retain as this process's startup policy, with the outcome of fetching each one.\n\n:meta private:\n\nInternal SDK API; not part of the public surface." + params_dict = {k: v for k, v in params.to_dict().items() if v is not None} + await self._client.request("managedSettings.retainPolicy", params_dict, **_timeout_kwargs(timeout)) + + async def _apply_controls(self, params: _ManagedSettingsApplyControlsParams, *, timeout: float | None = None) -> _ManagedSettingsApplyControlsResult: + "Stamps an effective managed policy's individually-controlled entries onto a user settings document, returning the settings a host should actually act on. The managed layer wins on every plugin spec and marketplace name it names, and leaves everything it does not name as the user wrote it. Hosts use this rather than merging the two documents themselves, because the merge is per entry rather than per document and a whole-document overlay would either discard the user's unmanaged entries or let a user entry outrank a managed one. Omitting the managed layer returns the settings unchanged, which is what an unmanaged host wants. Rejects when either document is present but is not a JSON object rather than treating it as absent, since silently dropping a malformed policy hands the user's layer the win on exactly the entries the policy exists to control.\n\nArgs:\n params: A user settings document and the effective managed policy whose controlled entries should be stamped onto it.\n\nReturns:\n The user settings with the managed layer's controlled entries stamped on.\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 _ManagedSettingsApplyControlsResult.from_dict(await self._client.request("managedSettings.applyControls", params_dict, **_timeout_kwargs(timeout))) + + async def _parse_client_value(self, params: _ManagedSettingsParseClientValueParams, *, timeout: float | None = None) -> _ManagedSettingsParseClientValueResult: + "Validates the managed settings a client is supplying for a session it is about to open, and returns them reduced to the keys a client is allowed to set. A client-supplied policy is not the same thing as an administrator's: it may only narrow permissions, so this accepts `permissions` and rejects every other key outright rather than ignoring it. Rejecting is the point — a host that silently dropped an unsupported key would report a policy as applied while running without it. Hosts call this before opening the session rather than letting the open fail, so a malformed policy is reported against the request that carried it. The runtime applies the same rule again when the session starts, so this cannot be used to smuggle a wider document past it. An accepted document that sets nothing comes back as an empty object, which is distinct from supplying nothing at all.\n\nArgs:\n params: The client-supplied managed settings to validate and normalize. Omitting them is valid and yields no policy.\n\nReturns:\n The accepted managed settings, reduced to the keys a client is allowed to set.\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 _ManagedSettingsParseClientValueResult.from_dict(await self._client.request("managedSettings.parseClientValue", params_dict, **_timeout_kwargs(timeout))) + + async def _resolve_effective_value(self, params: _ManagedSettingsResolveEffectiveValueParams, *, timeout: float | None = None) -> _ManagedSettingsResolveEffectiveValueResult: + "Composes the supplied server, device/MDM, device sandbox floor and policy-helper layers into the single effective managed policy a host should enforce, resolving each key across the layers by the runtime's own precedence rules. Hosts that assemble managed layers themselves use this so their precedence matches the runtime's exactly instead of reimplementing it. The composition is not a pure function of its inputs: an advisory sandbox force-on that arrived over the server channel is declined when this host cannot run a sandbox, so the result reflects the runtime host's capabilities, and the returned settings carry `sandboxAdvisoryFloorDeclined: true` when that happened. Enforced channels (device/MDM and policy helper) are never declined, and a server force-on carrying `failIfUnavailable` is preserved so the session fails loudly instead of quietly running unsandboxed. Rejects when a supplied layer is present but is not a JSON object, rather than treating it as unmanaged, because silently dropping a malformed policy would hand the user's own layer the win on exactly the keys the policy exists to control.\n\nArgs:\n params: The managed-settings layers to compose into an effective policy. Every layer is optional; omitting all of them yields no effective policy.\n\nReturns:\n The effective managed policy composed from the supplied layers.\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 _ManagedSettingsResolveEffectiveValueResult.from_dict(await self._client.request("managedSettings.resolveEffectiveValue", params_dict, **_timeout_kwargs(timeout))) + + async def _resolve_bypass_disable_transition(self, params: _ManagedSettingsResolveBypassDisableTransitionParams, *, timeout: float | None = None) -> _ManagedSettingsResolveBypassDisableTransitionResult: + "Decides what a host must do to its bypass-permissions state after a managed-policy resolution lands, given the state already in force and the account now signed in. A host holds two pieces of state — which layer disabled bypass mode, and for a server-delivered disable, the account it was pinned to — and cannot decide the next step from the new policy alone. Three rules make that so, and each one is a control rather than a preference. A device/MDM disable outranks the account, so a later server answer never lifts it. An unknown policy, which is what a failed fetch or a refusal this build cannot enforce yields, preserves whatever is in force instead of reverting to permissive. And a server disable is lifted only when the account it was pinned to is no longer the signed-in one, which is what makes signing in to an unrestricted account recover without a restart while a reconnect on the same restricted account does not. Hosts call this rather than reimplementing it because getting the pinning backwards either strands a disable on an account that never carried one or drops one that still applies. `setDisabledState` is omitted when the host must leave its stored flag untouched, which is distinct from being told to set it to false.\n\nArgs:\n params: The bypass-permissions state a host holds today, the account now signed in, and what the managed-policy resolution said.\n\nReturns:\n The bypass-permissions state a host should hold after the resolution, and what it must do to get there.\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 _ManagedSettingsResolveBypassDisableTransitionResult.from_dict(await self._client.request("managedSettings.resolveBypassDisableTransition", params_dict, **_timeout_kwargs(timeout))) + + async def _ingest(self, params: _ManagedSettingsIngestParams, *, timeout: float | None = None) -> _ManagedSettingsIngestResult: + "Resolves managed (enterprise policy) settings for a host from every layer at once: the account's server-delivered policy, the device/MDM layer, and a platform policy helper. Hosts use this instead of fetching and composing the layers themselves, because the per-layer precedence and the fail-closed rules that apply when a layer cannot be confirmed are the runtime's own and must match the policy a session will later enforce. It does not retain or apply anything to process-global policy state, so a host may call it before any session exists — which is the point, since plugin activation and one-shot commands resolve policy before a session is available to carry it. The returned per-layer outcome flags are load-bearing rather than diagnostic: a host must distinguish an unmanaged device from a managed device whose policy could not be confirmed, and apply its fail-closed gates in the second case. Reports its diagnostics in `logMessages` rather than logging them, so a host that runs before a log sink exists still sees them.\n\nArgs:\n params: Inputs describing the host and account an ingestion should resolve managed policy for.\n\nReturns:\n The managed policy layers an ingestion resolved, together with the per-layer outcome flags a host needs to decide how much to trust the result. The flags are reported rather than folded into the settings because a host must be able to distinguish an unmanaged device from a managed device whose policy could not be confirmed, and apply its fail-closed gates in the second case.\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 _ManagedSettingsIngestResult.from_dict(await self._client.request("managedSettings.ingest", params_dict, **_timeout_kwargs(timeout))) + + async def _resolve_startup_policy(self, params: _ManagedSettingsResolveStartupPolicyParams, *, timeout: float | None = None) -> _ManagedSettingsResolveStartupPolicyResult: + "Resolves the managed (enterprise policy) settings a host must enforce from startup, and judges the account against the enterprise sign-in pin in the same pass. This composes the layers the way `managedSettings.ingest` does and then answers the two questions a host cannot answer for itself: which fail-closed floors apply when a layer could not be confirmed, and whether this credential is allowed to sign in at all. The sign-in verdict is the reason a host must not assemble this itself from `managedSettings.ingest` — a pinned organization is an access control, and a host that composed the layers but skipped the pin would let a developer sign in with a personal or another enterprise's account and escape every control that keys off the signed-in identity. The verdict is computed on every resolution, including one that is not retained, so declining to apply policy never widens access. Set `retainAndApply` to keep the resolved policy as this runtime process's startup policy; leave it false to resolve without side effects.\n\nArgs:\n params: Inputs describing the host and account a startup policy resolution should run for. Mirrors `managedSettings.ingest`, and adds the decision of whether the resolved policy is retained for this runtime process.\n\nReturns:\n The startup policy a resolution composed, the per-layer outcome flags a host needs to decide how much to trust it, and the enterprise sign-in verdict for the credential it ran against. The flags are reported rather than folded into the settings because a host must distinguish an unmanaged device from a managed device whose policy could not be confirmed, and apply its fail-closed gates in the second case.\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 _ManagedSettingsResolveStartupPolicyResult.from_dict(await self._client.request("managedSettings.resolveStartupPolicy", params_dict, **_timeout_kwargs(timeout))) + + async def _load_remote_control(self, params: _ManagedSettingsLoadRemoteControlParams, *, timeout: float | None = None) -> _ManagedSettingsLoadRemoteControlResult: + "Resolves the managed `remoteControl` policy for an account on its own, for a host deciding whether a remote-control session may start. Composes the same layers as `managedSettings.ingest` and returns only that one key, so a host does not have to know which layer won. An absent policy means the account is unmanaged for this capability and the host may proceed; that default is safe only while absence really does mean \"no policy\", so a service refusal that says an account policy exists which this client is too old to enforce resolves to an explicit disabled policy instead of to absence. A resolution that cannot be completed is rejected rather than answered as absence, for the same reason.\n\nArgs:\n params: Inputs describing the host and account whose managed remote-control policy should be resolved. Mirrors `managedSettings.ingest`, and deliberately carries no retention decision: reading this gate never becomes this runtime process's startup policy.\n\nReturns:\n The managed remote-control policy in force for an account, if any. An absent policy means the account is unmanaged for this capability, which a host reads as allowed — so absence here is a positive statement that no policy applies, never a stand-in for a resolution that failed. A resolution that cannot be completed is rejected instead.\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 _ManagedSettingsLoadRemoteControlResult.from_dict(await self._client.request("managedSettings.loadRemoteControl", params_dict, **_timeout_kwargs(timeout))) + + async def _resolve_device_force_login_orgs_gate(self, params: _ManagedSettingsResolveDeviceForceLoginOrgsGateParams, *, timeout: float | None = None) -> _ForceLoginOrgsDecision: + "Evaluates the device-channel enterprise sign-in pin (MDM plist, registry, or managed-settings file) against a credential, without resolving any server-delivered policy. This is the gate an entry point runs before it branches, and the only one that can refuse a developer's first sign-in: it is local, needs no credential, and a server-delivered pin only ever reaches accounts already inside the organization. It fails closed by construction — a discovery that cannot determine whether a pin exists refuses rather than allows, because a control that fails open under a triggerable condition is not a control. Hosts that also want the server channel should call `managedSettings.resolveStartupPolicy`, which folds both channels into one verdict.\n\nArgs:\n params: The credential and environment to evaluate the device-channel sign-in pin against.\n\nReturns:\n The verdict of the `forceLoginOrgs` enterprise sign-in pin for one credential. A managed device may pin Copilot to an approved set of GitHub organizations so a developer cannot sign in with a personal — or another enterprise's — account and escape every control that keys off the signed-in identity. A host must refuse to proceed when `allowed` is false; the remaining fields explain the refusal rather than qualifying it.\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 _ForceLoginOrgsDecision.from_dict(await self._client.request("managedSettings.resolveDeviceForceLoginOrgsGate", params_dict, **_timeout_kwargs(timeout))) + + # Experimental: this API group is experimental and may change or be removed. class _InternalServerHostApi: def __init__(self, client: "JsonRpcClient"): @@ -55831,6 +61911,86 @@ async def _ready(self, params: HostReadyRequest, *, timeout: float | None = None return HostEmptyResult.from_dict(await self._client.request("host.ready", params_dict, **_timeout_kwargs(timeout))) +# Experimental: this API group is experimental and may change or be removed. +class _InternalServerAccountApi: + def __init__(self, client: "JsonRpcClient"): + self._client = client + + async def _describe_auth_info(self, params: _AccountDescribeAuthInfoRequest, *, timeout: float | None = None) -> _AccountDescribeAuthInfoResult: + "Renders the sign-in banner and the one-line account description the runtime uses for an authentication identity. The runtime words these in several places for its own callers, so a host that announces a sign-in or an account switch should render them from here rather than composing its own, which would let the two spellings drift apart.\n\nArgs:\n params: The authentication identity whose shared wording should be rendered.\n\nReturns:\n The wording the runtime uses for an authentication identity.\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 _AccountDescribeAuthInfoResult.from_dict(await self._client.request("account.describeAuthInfo", params_dict, **_timeout_kwargs(timeout))) + + async def _get_gist_support(self, params: _AccountGetGistSupportRequest, *, timeout: float | None = None) -> _AccountGetGistSupportResult: + "Reports whether the supplied identity can create gists. Gists are unavailable on GitHub Enterprise Cloud with data residency, and to Enterprise Managed Users on github.com, so a host that offers gist actions should hide or disable them when this reports no support.\n\nArgs:\n params: The authentication identity whose gist support should be reported.\n\nReturns:\n Whether the identity can create gists, and why not when it cannot.\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 _AccountGetGistSupportResult.from_dict(await self._client.request("account.getGistSupport", params_dict, **_timeout_kwargs(timeout))) + + async def _get_web_url(self, params: _AccountGetWebURLRequest, *, timeout: float | None = None) -> _AccountGetWebURLResult: + "Resolves a path on the identity's GitHub host into an absolute URL, so a host can link to settings or documentation on the enterprise instance the user is signed in to rather than to github.com. Falls back to the public host when no identity is supplied.\n\nArgs:\n params: A path to resolve against the GitHub host an identity is signed in to.\n\nReturns:\n An absolute URL on the identity's GitHub host.\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 _AccountGetWebURLResult.from_dict(await self._client.request("account.getWebUrl", params_dict, **_timeout_kwargs(timeout))) + + async def _get_token_env_var(self, *, timeout: float | None = None) -> _AccountGetTokenEnvVarResult: + "Names the environment variable supplying a classic personal access token, if one is set. A classic token cannot be upgraded in place, so a host that finds one should tell the user which variable to unset rather than starting an interactive sign-in that the variable would override.\n\nReturns:\n The environment variable supplying a classic personal access token, when one is set.\n\n:meta private:\n\nInternal SDK API; not part of the public surface." + return _AccountGetTokenEnvVarResult.from_dict(await self._client.request("account.getTokenEnvVar", {}, **_timeout_kwargs(timeout))) + + +# Experimental: this API group is experimental and may change or be removed. +class _InternalServerMcpRegistryApi: + def __init__(self, client: "JsonRpcClient"): + self._client = client + + async def _allocate_request_id(self, *, timeout: float | None = None) -> _MCPRegistryRequestIDResult: + "Allocates an ID for one cancellable MCP registry search. The ID exists before the search starts, so callers can cancel before it starts. The networking stack supplies the cancellation namespace. The runtime retains at most 1,024 unused IDs. At capacity, another allocation can reclaim an unused ID. Active searches retain their IDs until they finish.\n\nReturns:\n Request id naming a cancellable registry search.\n\n:meta private:\n\nInternal SDK API; not part of the public surface." + return _MCPRegistryRequestIDResult.from_dict(await self._client.request("mcp.registry.allocateRequestId", {}, **_timeout_kwargs(timeout))) + + async def _search(self, params: _MCPRegistrySearchRequest, *, timeout: float | None = None) -> _MCPRegistrySearchResult: + "Searches the MCP registry the supplied credential may read, resolving the registry endpoint from policy first. Hosts usually send credential-free `AuthIdentity`; a `token` identity can be resolved only when it embeds a token, while `env` and `gh-cli` identities can use a token embedded in the request first. The runtime follows registry pages, keeps the newest entry per server name, cuts the list to `limit`, and sorts an empty-query result by GitHub stars. Each server object is carried opaquely.\n\nArgs:\n params: Registry search terms, the credential to search under, and the request id that makes the search cancellable.\n\nReturns:\n Servers selected from the registry response.\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 _MCPRegistrySearchResult.from_dict(await self._client.request("mcp.registry.search", params_dict, **_timeout_kwargs(timeout))) + + async def _cancel(self, params: _MCPRegistryCancelRequest, *, timeout: float | None = None) -> _MCPRegistryCancelResult: + "Abandons the registry search that uses the given request ID. It acts only on IDs from `mcp.registry.allocateRequestId`, so it never cancels another component's request. Answers `canceled: true` when it stops a running search. Answers `canceled: false` for unknown or reclaimed IDs, completed or canceled searches, and unused reservations. It releases an unused reservation, so a later search with that ID is refused.\n\nArgs:\n params: Registry search to abandon.\n\nReturns:\n Whether the cancel reached a live search.\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 _MCPRegistryCancelResult.from_dict(await self._client.request("mcp.registry.cancel", params_dict, **_timeout_kwargs(timeout))) + + +# Experimental: this API group is experimental and may change or be removed. +class _InternalServerMcpApi: + def __init__(self, client: "JsonRpcClient"): + self._client = client + self.registry = _InternalServerMcpRegistryApi(client) + + async def _list_builtin_server_names(self, *, timeout: float | None = None) -> _MCPListBuiltinServerNamesResult: + "Lists the MCP servers the runtime supplies itself, so a host that disables the built-ins wholesale suppresses exactly the set the runtime would otherwise add. The names are fixed for a build and depend on no configuration, session or account.\n\nReturns:\n Names of the MCP servers the runtime supplies itself.\n\n:meta private:\n\nInternal SDK API; not part of the public surface." + return _MCPListBuiltinServerNamesResult.from_dict(await self._client.request("mcp.listBuiltinServerNames", {}, **_timeout_kwargs(timeout))) + + async def _should_exclude_git_hub_tools(self, params: _MCPShouldExcludeGitHubToolsRequest, *, timeout: float | None = None) -> _MCPShouldExcludeGitHubToolsResult: + "Reports whether the gh-replaceable GitHub MCP tools may be clipped for a session, which a host feeds back as the `excludeGhReplaceableTools` build option. The tools are redundant only when the session can reach a shell *and* the host has the `gh` that would replace them, so both halves are decided here. The shell half runs the platform shell tool through the runtime's own tool-filter matcher, so entry forms like `builtin:bash` and `builtin:*` behave exactly as they do when a session builds its tool catalog. The host half probes for `gh`, and is skipped entirely when the filters already rule the shell out. Host presence alone is not enough: a session restricted to, say, `view` would otherwise lose the built-in issue-read fallback while having no shell to replace it with. The answer describes the host the runtime runs on, so it is never forwarded to a remote engine.\n\nArgs:\n params: The session tool filters that decide whether the session still has a shell to run `gh` with.\n\nReturns:\n Whether the gh-replaceable GitHub MCP tools may be clipped for the session described by the request.\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 _MCPShouldExcludeGitHubToolsResult.from_dict(await self._client.request("mcp.shouldExcludeGitHubTools", params_dict, **_timeout_kwargs(timeout))) + + +# Experimental: this API group is experimental and may change or be removed. +class _InternalServerInstructionsApi: + def __init__(self, client: "JsonRpcClient"): + self._client = client + + async def _get_init_prompt(self, *, timeout: float | None = None) -> _InstructionsInitPromptResult: + "Returns the prompt the `init` command runs to author repository instructions. A host offering its own initialize action sends this text so the instructions it produces match the ones the CLI produces. The text is fixed for a build and depends on no configuration, session or repository.\n\nReturns:\n The prompt the init command runs.\n\n:meta private:\n\nInternal SDK API; not part of the public surface." + return _InstructionsInitPromptResult.from_dict(await self._client.request("instructions.getInitPrompt", {}, **_timeout_kwargs(timeout))) + + async def _discover_repo_sources(self, params: _InstructionsDiscoverRepoSourcesRequest, *, timeout: float | None = None) -> ServerInstructionSourceList: + "Discovers the instruction sources for one caller-named location and returns them in the order the runtime applies them. `discover` answers for a set of project paths and derives the rest of its scope itself; this one instead takes every argument from the caller — the extra directories to scan and whether to descend into nested instruction files — and honors them as given. It also includes instruction directories from trusted repository settings, matching native repository discovery. Use it when the caller knows its own scope and no session exists to derive that scope from, such as a host reporting how many instruction files a directory contributes before any session starts. Marked internal because it exists to carry a CLI call site off the napi boundary onto the SDK contract; it is migration plumbing, not a surface consumers are meant to depend on.\n\nArgs:\n params: Explicit discovery arguments for one location, supplied by the caller rather than derived from a session.\n\nReturns:\n Instruction sources discovered across user, repository, and plugin sources.\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 ServerInstructionSourceList.from_dict(await self._client.request("instructions.discoverRepoSources", params_dict, **_timeout_kwargs(timeout))) + + async def _clear_cache(self, *, timeout: float | None = None) -> None: + "Drops the runtime's in-memory cache of discovered custom instructions so the next discovery reads them from disk again. A host calls this after it changes instruction files, or after it trusts a folder whose instructions an earlier discovery skipped, so that a session started afterwards sees the current files instead of a cached earlier answer. The call performs no I/O of its own and removes nothing from disk; it only invalidates, so the cost lands on the next discovery. Returns once the cache is clear, which is what lets a caller order a session start after it. Marked internal because it exists to carry a CLI call site off the napi boundary onto the SDK contract; it is migration plumbing, not a surface consumers are meant to depend on.\n\n:meta private:\n\nInternal SDK API; not part of the public surface." + await self._client.request("instructions.clearCache", {}, **_timeout_kwargs(timeout)) + + # Experimental: this API group is experimental and may change or be removed. class _InternalServerAgentsApi: def __init__(self, client: "JsonRpcClient"): @@ -55876,7 +62036,7 @@ async def _load_for_config_dir(self, params: _GlobalStateLoadForConfigDirRequest return _GlobalStateLoadResult.from_dict(await self._client.request("globalState.loadForConfigDir", params_dict, **_timeout_kwargs(timeout))) async def _write_key(self, params: _GlobalStateWriteKeyRequest, *, timeout: float | None = None) -> None: - "Records one top-level key in the host's machine-wide state, the counterpart to `globalState.load`. A host calls this to remember that it has shown an onboarding step, asked a one-off question, or completed a migration, so the next run can skip it. Only the named key is replaced and the rest of the document is preserved, which lets two writers record different flags without overwriting each other; passing no value removes the key instead. Only the keys a host records itself are writable: `appInstallNudgeResponded`, `appTipShown`, `askedSetupTerminals`, `autoFeedbackLastPromptedAt`, `firstLaunchAt`, `recentModelIds`, `sandboxCredentialProxyCaDeclined` and `sandboxOnboardingShown`. Every other key is refused, including `installedPlugins`, the stored credentials, `trustedFolders`, the staff flags and the signed-in accounts. Plugin enablement must use the plugin APIs, which apply repository and managed-policy checks.\n\nArgs:\n params: A single top-level key to record in the host's machine-wide state. The write replaces only that key and leaves the rest of the document untouched, so two writers recording different one-off flags do not overwrite each other. The stored credential keys cannot be written through this method.\n\n:meta private:\n\nInternal SDK API; not part of the public surface." + "Records one top-level key in the host's machine-wide state, the counterpart to `globalState.load`. A host calls this to remember that it has shown an onboarding step, asked a one-off question, or completed a migration, so the next run can skip it. Only the named key is replaced and the rest of the document is preserved, which lets two writers record different flags without overwriting each other; passing no value removes the key instead. Only the keys a host records itself are writable: `appTipShown`, `askedSetupTerminals`, `autoFeedbackLastPromptedAt`, `firstLaunchAt`, `recentModelIds`, `sandboxCredentialProxyCaDeclined` and `sandboxOnboardingShown`. Every other key is refused, including `installedPlugins`, the stored credentials, `trustedFolders`, the staff flags and the signed-in accounts. Plugin enablement must use the plugin APIs, which apply repository and managed-policy checks.\n\nArgs:\n params: A single top-level key to record in the host's machine-wide state. The write replaces only that key and leaves the rest of the document untouched, so two writers recording different one-off flags do not overwrite each other. The stored credential keys cannot be written through this method.\n\n:meta private:\n\nInternal SDK API; not part of the public surface." params_dict = {k: v for k, v in params.to_dict().items() if v is not None} await self._client.request("globalState.writeKey", params_dict, **_timeout_kwargs(timeout)) @@ -55897,9 +62057,9 @@ class _InternalServerGitHubOwnersApi: def __init__(self, client: "JsonRpcClient"): self._client = client - async def _next_request_id(self, *, timeout: float | None = None) -> GitHubOwnersRequestIDResult: + async def _next_request_id(self, *, timeout: float | None = None) -> _GitHubOwnersRequestIDResult: "Registers a cancellable owner listing and returns its request id. Separate from `gitHubOwners.list` so the id exists before the listing starts: a caller that abandons the listing the moment it begins would otherwise have nothing to name in `gitHubOwners.cancel`. The id serves one listing only. Long-abandoned unused ids can be released by later allocations.\n\nReturns:\n A freshly registered request id. Registering it before the listing starts is what lets a cancel that races the request still find the owner listing slot. The id serves one listing only. Long-abandoned unused ids can be released by later allocations.\n\n:meta private:\n\nInternal SDK API; not part of the public surface." - return GitHubOwnersRequestIDResult.from_dict(await self._client.request("gitHubOwners.nextRequestId", {}, **_timeout_kwargs(timeout))) + return _GitHubOwnersRequestIDResult.from_dict(await self._client.request("gitHubOwners.nextRequestId", {}, **_timeout_kwargs(timeout))) async def _list(self, params: _GitHubOwnersListRequest, *, timeout: float | None = None) -> _GitHubOwnersListResult: "Lists the logins the authenticated user may act as — their own account first, then the organizations they belong to — by asking the GitHub API under the supplied credential. No credential travels in the request: `authInfo` selects one the runtime already holds, and the runtime resolves the token and the GitHub host from it. A failure the caller should render arrives as `message`; one it should raise arrives as `throwError`.\n\nArgs:\n params: Credential to list owners under, and the request id that makes the listing cancellable.\n\nReturns:\n Outcome of an owner listing. Exactly one of `owners` and `message` is present, except that `throwError` reports a failure the caller is expected to raise rather than render.\n\n:meta private:\n\nInternal SDK API; not part of the public surface." @@ -55993,7 +62153,17 @@ class _InternalServerRpc: """Internal SDK server-scoped RPC methods. Not part of the public API.""" def __init__(self, client: "JsonRpcClient"): self._client = client + self.sandbox = _InternalServerSandboxApi(client) + self.persistence = _InternalServerPersistenceApi(client) + self.user = _InternalServerUserApi(client) + self.repo = _InternalServerRepoApi(client) + self.feature_flags = _InternalServerFeatureFlagsApi(client) + self.permissions = _InternalServerPermissionsApi(client) + self.managed_settings = _InternalServerManagedSettingsApi(client) self.host = _InternalServerHostApi(client) + self.account = _InternalServerAccountApi(client) + self.mcp = _InternalServerMcpApi(client) + self.instructions = _InternalServerInstructionsApi(client) self.agents = _InternalServerAgentsApi(client) self.global_state = _InternalServerGlobalStateApi(client) self.git_hub_repository = _InternalServerGitHubRepositoryApi(client) @@ -56060,6 +62230,10 @@ async def get_enforcement_status(self, *, timeout: float | None = None) -> Sandb "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))) + async def get_credential_suggestions(self, *, timeout: float | None = None) -> SandboxCredentialSuggestionsResult: + "Lists possible secret-bearing environment variables available to new sandboxed shells that are not already covered by credential masking. Uses local name rules and known-provider host suggestions, never returns secret values, and changes no settings or network permissions. Returns no suggestions while the session is not sandboxed.\n\nReturns:\n Possible unconfigured secrets in the sandbox shell environment, sorted by variable name, with no secret values." + return SandboxCredentialSuggestionsResult.from_dict(await self._client.request("session.sandbox.getCredentialSuggestions", {"sessionId": self._session_id}, **_timeout_kwargs(timeout))) + async def disable_for_session(self, params: SandboxDisableForSessionRequest, *, timeout: float | None = None) -> SandboxDisableForSessionResult: "Disables sandboxing for the remainder of the current session and approves the referenced pending sandbox-bypass permission request. The request is rejected unless the exact request is still pending and the effective sandbox policy permits bypass.\n\nArgs:\n params: Request to disable sandboxing for the current session while resolving an active sandbox-bypass permission prompt.\n\nReturns:\n Result of attempting to disable sandboxing for the current session." params_dict: dict[str, Any] = {k: v for k, v in params.to_dict().items() if v is not None} @@ -56316,6 +62490,12 @@ async def set_reasoning_effort(self, params: ModelSetReasoningEffortRequest, *, params_dict["sessionId"] = self._session_id return ModelSetReasoningEffortResult.from_dict(await self._client.request("session.model.setReasoningEffort", params_dict, **_timeout_kwargs(timeout))) + async def get_reasoning_summaries(self, params: ModelGetReasoningSummariesRequest, *, timeout: float | None = None) -> ModelGetReasoningSummariesResult: + "Computes whether the session's current model shows reasoning summaries and how a host should present them: the default Ctrl+T visibility, reasoning headers, the hidden-summaries hint, and whether a persisted `showReasoning` setting is redundant. The runtime folds in the session's model catalog, selection and reasoning-summaries experiment assignment; the host supplies only its own persisted setting and current visibility. This method reads the session's resolved model catalog rather than resolving it, so call `session.model.list` first on a session that has not listed models or run a turn; until then the result is `ready: false`. A session that resolves models without a Copilot catalog, that is a BYOK registry or an alternate provider, never caches one and stays `ready: false`.\n\nArgs:\n params: Host presentation state the reasoning-summaries policy depends on.\n\nReturns:\n Reasoning-summaries presentation policy for the session's current model." + 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 ModelGetReasoningSummariesResult.from_dict(await self._client.request("session.model.getReasoningSummaries", params_dict, **_timeout_kwargs(timeout))) + async def list(self, params: SessionModelListRequest | None = None, *, timeout: float | None = None) -> SessionModelList: "Lists models available to this session using its own auth and integration context. Connected hosts (CLI TUI, GitHub App) should call this through the session client so remote sessions return the remote CLI's available models rather than the caller's.\n\nArgs:\n params: Optional listing options.\n\nReturns:\n The list of models available to this session." params_dict: dict[str, Any] = {k: v for k, v in params.to_dict().items() if v is not None} if params is not None else {} @@ -57140,6 +63320,12 @@ async def reload(self, params: SessionPluginsReloadRequest | None = None, *, tim params_dict["sessionId"] = self._session_id await self._client.request("session.plugins.reload", params_dict, **_timeout_kwargs(timeout)) + async def retry_managed(self, params: SessionPluginsRetryManagedRequest | None = None, *, timeout: float | None = None) -> SessionPluginsRetryManagedResult: + "Installs or updates the plugins the session's managed settings require now, without waiting for the next automatic attempt after a failure, then reloads plugins. Reports progress and failures with the same `session.info` and `session.warning` events as automatic preparation. Fails when the runtime does not prepare managed plugins for the session.\n\nArgs:\n params: Plugins required by the session's managed settings to install or update again.\n\nReturns:\n Per-plugin outcomes of retrying the plugins required by the session's managed settings." + params_dict: dict[str, Any] = {k: v for k, v in params.to_dict().items() if v is not None} if params is not None else {} + params_dict["sessionId"] = self._session_id + return SessionPluginsRetryManagedResult.from_dict(await self._client.request("session.plugins.retryManaged", params_dict, **_timeout_kwargs(timeout))) + # Experimental: this API group is experimental and may change or be removed. class ProviderApi: @@ -58163,25 +64349,25 @@ async def _set_connected_ide_info(self, params: SessionMCPSetConnectedIDEInfoPar params_dict["sessionId"] = self._session_id await self._client.request("session.mcp.setConnectedIdeInfo", params_dict, **_timeout_kwargs(timeout)) - async def _reload_with_config(self, params: MCPReloadWithConfigRequest, *, timeout: float | None = None) -> MCPStartServersResult: + async def _reload_with_config(self, params: _MCPReloadWithConfigRequest, *, timeout: float | None = None) -> MCPStartServersResult: "Reloads MCP server connections for the session with an explicit host-provided configuration.\n\nArgs:\n params: Opaque MCP reload configuration.\n\nReturns:\n MCP server startup filtering result.\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 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: + 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: 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))) - async def _register_external_client(self, params: MCPRegisterExternalClientRequest, *, timeout: float | None = None) -> None: + async def _register_external_client(self, params: _MCPRegisterExternalClientRequest, *, timeout: float | None = None) -> None: "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.\n\nArgs:\n params: Registration parameters for an external MCP client.\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.mcp.registerExternalClient", params_dict, **_timeout_kwargs(timeout)) - async def _unregister_external_client(self, params: MCPUnregisterExternalClientRequest, *, timeout: float | None = None) -> None: + async def _unregister_external_client(self, params: _MCPUnregisterExternalClientRequest, *, timeout: float | None = None) -> None: "Unregisters a previously registered external MCP client by server name. Marked internal as the paired companion of `registerExternalClient`: only in-process callers that registered a client this way can meaningfully unregister it. Disappears alongside `registerExternalClient`: once external clients are described to the runtime as config rather than handed in as instances, lifecycle (including deregistration) is owned entirely by the runtime.\n\nArgs:\n params: Server name identifying the external client to remove.\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 @@ -58947,7 +65133,18 @@ async def handle_installations_confirm(params: dict) -> dict | None: "CatalogAgentPluginCandidateKind", "CatalogAgentPluginCandidateProvenance", "CatalogAgentPluginCompatibilityTag", + "CatalogAgentPluginComponent", + "CatalogAgentPluginInstallPlan", + "CatalogAgentPluginInstallSource", + "CatalogAgentPluginInstallSourceKind", + "CatalogAgentPluginInstallability", + "CatalogAgentPluginMarketplaceSource", + "CatalogAgentPluginMarketplaceSourceKind", + "CatalogAgentPluginMaterializationSource", + "CatalogAgentPluginMaterializationSourceKind", "CatalogAgentPluginMediaType", + "CatalogAgentPluginRepositorySource", + "CatalogAgentPluginRepositorySourceKind", "CatalogAuthenticationRequiredError", "CatalogAuthenticationRequiredErrorKind", "CatalogAuthenticationRequiredReason", @@ -58981,6 +65178,7 @@ async def handle_installations_confirm(params: dict) -> dict | None: "CatalogMalformedCardError", "CatalogMalformedCardErrorKind", "CatalogMalformedCardReason", + "CatalogMaterializationReference", "CatalogMcpServerInstallability", "CatalogMediaType", "CatalogNegotiatedContract", @@ -59121,6 +65319,8 @@ async def handle_installations_confirm(params: dict) -> dict | None: "ContentExclusionCheckPathsRequest", "ContentExclusionCheckPathsResult", "ContentExclusionPathCheck", + "ContentExclusionValidatePoliciesRequest", + "ContentExclusionValidatePoliciesResult", "ContentFilterMode", "ContextHeaviestMessage", "CopilotAPITokenAuthInfo", @@ -59235,6 +65435,8 @@ async def handle_installations_confirm(params: dict) -> dict | None: "ExternalToolTextResultForLlmContentTerminalType", "ExternalToolTextResultForLlmContentText", "ExternalToolTextResultForLlmContentType", + "FeatureFlagsResolveResult", + "FeatureFlagsResolveStartupGatesResult", "FilterMapping", "FleetApi", "FleetStartRequest", @@ -59247,7 +65449,6 @@ async def handle_installations_confirm(params: dict) -> dict | None: "GhCLIAuthInfoType", "GitHubAuthApi", "GitHubEnvironment", - "GitHubOwnersRequestIDResult", "GitHubRepositoryIdentity", "GitHubTelemetryClientInfo", "GitHubTelemetryEvent", @@ -59395,7 +65596,6 @@ async def handle_installations_confirm(params: dict) -> dict | None: "MCPConfigList", "MCPConfigRemoveRequest", "MCPConfigUpdateRequest", - "MCPConfigureGitHubRequest", "MCPConfigureGitHubResult", "MCPConfiguredServer", "MCPConfiguredServerList", @@ -59512,9 +65712,6 @@ async def handle_installations_confirm(params: dict) -> dict | None: "MCPPromptsGetResult", "MCPPromptsListRequest", "MCPPromptsListResult", - "MCPRegisterExternalClientRequest", - "MCPReloadConfig", - "MCPReloadWithConfigRequest", "MCPRemoveGitHubResult", "MCPResource", "MCPResourceAnnotations", @@ -59545,8 +65742,6 @@ async def handle_installations_confirm(params: dict) -> dict | None: "MCPServerConfigDeferTools", "MCPServerConfigHTTP", "MCPServerConfigHTTPType", - "MCPServerConfigMemory", - "MCPServerConfigMemoryType", "MCPServerConfigStdio", "MCPServerConfigStdioType", "MCPServerConfigType", @@ -59568,8 +65763,15 @@ async def handle_installations_confirm(params: dict) -> dict | None: "MCPToolUIVisibility", "MCPTools", "MCPUninstallPlan", - "MCPUnregisterExternalClientRequest", "ManagedMCPServerConfig", + "ManagedPermissionEvaluation", + "ManagedPermissionOperation", + "ManagedPermissionOperationKind", + "ManagedPermissionVerdict", + "ManagedPermissionsEvaluateRequest", + "ManagedPermissionsEvaluateResult", + "ManagedPluginRetryEntry", + "ManagedPluginRetryStatus", "ManagedSettingMeta", "ManagedSettingsApi", "ManagedSettingsChannel", @@ -59662,6 +65864,8 @@ async def handle_installations_confirm(params: dict) -> dict | None: "ModelCapabilitiesOverrideLimitsVision", "ModelCapabilitiesOverrideSupports", "ModelCapabilitiesSupports", + "ModelGetReasoningSummariesRequest", + "ModelGetReasoningSummariesResult", "ModelList", "ModelListRequest", "ModelMessage", @@ -59829,6 +66033,7 @@ async def handle_installations_confirm(params: dict) -> dict | None: "PermissionsConfigureAdditionalContentExclusionPolicyScope", "PermissionsConfigureParams", "PermissionsConfigureResult", + "PermissionsEvaluateManagedPolicyResult", "PermissionsFolderTrustAddTrustedResult", "PermissionsFolderTrustApi", "PermissionsGetModeRequest", @@ -59867,6 +66072,8 @@ async def handle_installations_confirm(params: dict) -> dict | None: "PermissionsSetRequiredResult", "PermissionsUrlsApi", "PermissionsUrlsSetUnrestrictedModeResult", + "PersistenceWriteRawJSONLockedRequest", + "PersistenceWriteRawJSONLockedResult", "Phase", "PingRequest", "PingResult", @@ -60054,6 +66261,10 @@ async def handle_installations_confirm(params: dict) -> dict | None: "ResponseFormatType", "ReviewClass", "SandboxApi", + "SandboxApplyDefaultsRequest", + "SandboxCheckEnforcementCommandResult", + "SandboxCheckPlatformSupportResult", + "SandboxCheckWindowsHostLoopbackSupportResult", "SandboxConfig", "SandboxConfigAuth", "SandboxConfigSource", @@ -60064,10 +66275,15 @@ async def handle_installations_confirm(params: dict) -> dict | None: "SandboxConfigUserPolicyNetwork", "SandboxConfigUserPolicyNetworkProxy", "SandboxConfigUserPolicySeatbelt", + "SandboxCredentialSuggestion", + "SandboxCredentialSuggestionsResult", "SandboxCredentialsConfig", "SandboxDisableForSessionRequest", "SandboxDisableForSessionResult", + "SandboxDocsURLResult", "SandboxEnforcementStatus", + "SandboxGetUnsupportedMessageResult", + "SandboxGetUnsupportedSettingDisabledNoticeResult", "SandboxGrantPathForRequestRequest", "SandboxGrantPathForRequestResult", "SandboxHostCapability", @@ -60078,7 +66294,15 @@ async def handle_installations_confirm(params: dict) -> dict | None: "SandboxProxyCARequest", "SandboxProxyCAState", "SandboxProxyCAStatus", + "SandboxProxyCaCheckInstallationSupportResult", + "SandboxProxyCaCheckRelevanceResult", + "SandboxProxySecretGetResult", + "SandboxProxySecretLocation", + "SandboxProxySecretSaveRequest", + "SandboxProxySecretSaveResult", + "SandboxResolveFlooredConfigResult", "SandboxSessionChange", + "SandboxSettingsApplyDefaultsResult", "Saved", "ScheduleAddAtRequest", "ScheduleAddCronRequest", @@ -60110,6 +66334,7 @@ async def handle_installations_confirm(params: dict) -> dict | None: "ServerCatalogApi", "ServerCommandsApi", "ServerConnectorsApi", + "ServerContentExclusionApi", "ServerEnvironmentsApi", "ServerExtensionsApi", "ServerHooksApi", @@ -60118,6 +66343,7 @@ async def handle_installations_confirm(params: dict) -> dict | None: "ServerInstructionsApi", "ServerLlmInferenceApi", "ServerManagedSettingsApi", + "ServerManagedSettingsPermissionsApi", "ServerMcpApi", "ServerMcpConfigApi", "ServerMcpInstallationsApi", @@ -60249,6 +66475,8 @@ async def handle_installations_confirm(params: dict) -> dict | None: "SessionPluginsInstallRequest", "SessionPluginsMarketplacesRefreshRequest", "SessionPluginsReloadRequest", + "SessionPluginsRetryManagedRequest", + "SessionPluginsRetryManagedResult", "SessionProviderGetEndpointRequest", "SessionPruneResult", "SessionQuotaDelegateWarning", @@ -60394,6 +66622,7 @@ async def handle_installations_confirm(params: dict) -> dict | None: "SkillInstallationSummary", "SkillInstallationsRequest", "SkillList", + "SkillPlanInstallFromReferenceRequest", "SkillPlanInstallRequest", "SkillPlanUninstallRequest", "SkillProviderDescriptor", @@ -60437,6 +66666,8 @@ async def handle_installations_confirm(params: dict) -> dict | None: "SlashCommandTextResult", "SlashCommandTimelineEntry", "SourceClass", + "StaffLogLevelMigrationStatus", + "StaffUpdateChannelMigrationStatus", "SubagentSettings", "SubagentSettingsEntry", "SubagentSettingsEntryContextTier", @@ -60561,6 +66792,7 @@ async def handle_installations_confirm(params: dict) -> dict | None: "UIUnregisterDirectAutoModeSwitchHandlerRequest", "UIUnregisterDirectAutoModeSwitchHandlerResult", "UIUserInputResponse", + "UNSET_SENTINEL", "UiApi", "UnsupportedEnqueueCommandResult", "UpdateSubagentSettingsRequest", @@ -60568,8 +66800,34 @@ async def handle_installations_confirm(params: dict) -> dict | None: "UserAuthInfo", "UserRequestedShellCommandResult", "UserSettingMetadata", + "UserSettingsApplyTopKeyRequest", + "UserSettingsApplyTopKeyResult", + "UserSettingsBuildRuntimeResult", + "UserSettingsGetPathsRequest", + "UserSettingsGetPathsResult", "UserSettingsGetResult", + "UserSettingsGovernanceKeysResult", + "UserSettingsHostContext", + "UserSettingsLoadForConfigDirResult", + "UserSettingsLoadForEditResult", + "UserSettingsLoadRepoRequest", + "UserSettingsLoadRepoResult", + "UserSettingsLoadWithWarningResult", + "UserSettingsMergeManagedAndUserResult", + "UserSettingsMergeRepoValueResult", + "UserSettingsNormalizeRequest", + "UserSettingsNormalizeResult", + "UserSettingsPathResult", + "UserSettingsProblem", + "UserSettingsProblemLocation", + "UserSettingsProblemSource", + "UserSettingsProblemsRequest", + "UserSettingsProblemsResult", "UserSettingsSetRequest", + "UserSettingsValidateRequest", + "UserSettingsValidateResult", + "UserSettingsValidationIssue", + "UserSettingsWriteAllRequest", "VisibilityApi", "VisibilityGetResult", "VisibilitySetRequest", diff --git a/python/copilot/generated/session_events.py b/python/copilot/generated/session_events.py index 8de79749b4..b205ef3232 100644 --- a/python/copilot/generated/session_events.py +++ b/python/copilot/generated/session_events.py @@ -1788,6 +1788,54 @@ def to_dict(self) -> dict: return result +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class ProviderMonthlyUsage: + "An authoritative monthly usage reading for the observation's provider, account, and service." + scope: ProviderMonthlyUsageScope + state: ProviderMonthlyUsageState + unit: ProviderQuotaUnit + consumed_quantity: float | None = None + cycle_start: str | None = None + queried_at: str | None = None + reset_on: str | None = None + + @staticmethod + def from_dict(obj: Any) -> "ProviderMonthlyUsage": + assert isinstance(obj, dict) + scope = parse_enum(ProviderMonthlyUsageScope, obj.get("scope")) + state = parse_enum(ProviderMonthlyUsageState, obj.get("state")) + unit = parse_enum(ProviderQuotaUnit, obj.get("unit")) + consumed_quantity = from_union([from_none, from_float], obj.get("consumedQuantity")) + cycle_start = from_union([from_none, from_str], obj.get("cycleStart")) + queried_at = from_union([from_none, from_str], obj.get("queriedAt")) + reset_on = from_union([from_none, from_str], obj.get("resetOn")) + return ProviderMonthlyUsage( + scope=scope, + state=state, + unit=unit, + consumed_quantity=consumed_quantity, + cycle_start=cycle_start, + queried_at=queried_at, + reset_on=reset_on, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["scope"] = to_enum(ProviderMonthlyUsageScope, self.scope) + result["state"] = to_enum(ProviderMonthlyUsageState, self.state) + result["unit"] = to_enum(ProviderQuotaUnit, self.unit) + if self.consumed_quantity is not None: + result["consumedQuantity"] = from_union([from_none, to_float], self.consumed_quantity) + if self.cycle_start is not None: + result["cycleStart"] = from_union([from_none, from_str], self.cycle_start) + if self.queried_at is not None: + result["queriedAt"] = from_union([from_none, from_str], self.queried_at) + if self.reset_on is not None: + result["resetOn"] = from_union([from_none, from_str], self.reset_on) + return result + + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class ProviderQuotaState: @@ -1805,6 +1853,7 @@ class ProviderQuotaState: entitled_quantity: int | None = None has_quota: bool | None = None http_status: int | None = None + monthly_usage: ProviderMonthlyUsage | None = None observation_kind: ProviderQuotaObservationKind | None = None observed_at: str | None = None quantity_kind: ProviderQuotaQuantityKind | None = None @@ -1828,6 +1877,7 @@ def from_dict(obj: Any) -> "ProviderQuotaState": entitled_quantity = from_union([from_none, from_int], obj.get("entitledQuantity")) has_quota = from_union([from_none, from_bool], obj.get("hasQuota")) http_status = from_union([from_none, from_int], obj.get("httpStatus")) + monthly_usage = from_union([from_none, ProviderMonthlyUsage.from_dict], obj.get("monthlyUsage")) observation_kind = from_union([from_none, lambda x: parse_enum(ProviderQuotaObservationKind, x)], obj.get("observationKind")) observed_at = from_union([from_none, from_str], obj.get("observedAt")) quantity_kind = from_union([from_none, lambda x: parse_enum(ProviderQuotaQuantityKind, x)], obj.get("quantityKind")) @@ -1848,6 +1898,7 @@ def from_dict(obj: Any) -> "ProviderQuotaState": entitled_quantity=entitled_quantity, has_quota=has_quota, http_status=http_status, + monthly_usage=monthly_usage, observation_kind=observation_kind, observed_at=observed_at, quantity_kind=quantity_kind, @@ -1879,6 +1930,8 @@ def to_dict(self) -> dict: result["hasQuota"] = from_union([from_none, from_bool], self.has_quota) if self.http_status is not None: result["httpStatus"] = from_union([from_none, to_int], self.http_status) + if self.monthly_usage is not None: + result["monthlyUsage"] = from_union([from_none, lambda x: to_class(ProviderMonthlyUsage, x)], self.monthly_usage) if self.observation_kind is not None: result["observationKind"] = from_union([from_none, lambda x: to_enum(ProviderQuotaObservationKind, x)], self.observation_kind) if self.observed_at is not None: @@ -2636,6 +2689,7 @@ class SessionManagedSettingsResolvedData: source: ManagedSettingsResolvedSource client_managed: bool | None = None permissions_allow_intersected: bool | None = None + permissions_context: ManagedPermissionsContext | None = None policy_helper_managed: bool | None = None sandbox_enabled_by_undetermined_policy: bool | None = None settings: Any = None @@ -2651,6 +2705,7 @@ def from_dict(obj: Any) -> "SessionManagedSettingsResolvedData": 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")) + permissions_context = from_union([from_none, ManagedPermissionsContext.from_dict], obj.get("permissionsContext")) 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") @@ -2663,6 +2718,7 @@ def from_dict(obj: Any) -> "SessionManagedSettingsResolvedData": source=source, client_managed=client_managed, permissions_allow_intersected=permissions_allow_intersected, + permissions_context=permissions_context, policy_helper_managed=policy_helper_managed, sandbox_enabled_by_undetermined_policy=sandbox_enabled_by_undetermined_policy, settings=settings, @@ -2680,6 +2736,8 @@ def to_dict(self) -> dict: 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.permissions_context is not None: + result["permissionsContext"] = from_union([from_none, lambda x: to_class(ManagedPermissionsContext, x)], self.permissions_context) 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: @@ -3708,6 +3766,8 @@ class AssistantUsageData: reasoning_tokens: int | None = None rejected_prediction_tokens: int | None = None request_body_bytes: int | None = None + request_body_encoding: ModelCallRequestBodyEncoding | None = None + request_body_wire_bytes: int | None = None rte: bool | None = None service_request_id: str | None = None # Internal: this field is an internal SDK API and is not part of the public surface. @@ -3721,7 +3781,12 @@ class AssistantUsageData: _tool_token_count: int | None = None transport: AssistantUsageTransport | None = None websocket_fallback_after: timedelta | None = None + websocket_fallback_close_code: int | None = None + websocket_fallback_error_kind: ModelCallWebSocketFallbackErrorKind | None = None websocket_fallback_reason: ModelCallWebSocketFallbackReason | None = None + websocket_fallback_reconnect_attempted: bool | None = None + websocket_fallback_started_this_call: bool | None = None + websocket_fallback_status_code: int | None = None @staticmethod def from_dict(obj: Any) -> "AssistantUsageData": @@ -3768,6 +3833,8 @@ def from_dict(obj: Any) -> "AssistantUsageData": reasoning_tokens = from_union([from_none, from_int], obj.get("reasoningTokens")) rejected_prediction_tokens = from_union([from_none, from_int], obj.get("rejectedPredictionTokens")) request_body_bytes = from_union([from_none, from_int], obj.get("requestBodyBytes")) + request_body_encoding = from_union([from_none, lambda x: parse_enum(ModelCallRequestBodyEncoding, x)], obj.get("requestBodyEncoding")) + request_body_wire_bytes = from_union([from_none, from_int], obj.get("requestBodyWireBytes")) rte = from_union([from_none, from_bool], obj.get("rte")) service_request_id = from_union([from_none, from_str], obj.get("serviceRequestId")) _thinking_dropped_blocks = from_union([from_none, from_int], obj.get("thinkingDroppedBlocks")) @@ -3777,7 +3844,12 @@ def from_dict(obj: Any) -> "AssistantUsageData": _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")) websocket_fallback_after = from_union([from_none, from_timedelta], obj.get("websocketFallbackAfterMs")) + websocket_fallback_close_code = from_union([from_none, from_int], obj.get("websocketFallbackCloseCode")) + websocket_fallback_error_kind = from_union([from_none, lambda x: parse_enum(ModelCallWebSocketFallbackErrorKind, x)], obj.get("websocketFallbackErrorKind")) websocket_fallback_reason = from_union([from_none, lambda x: parse_enum(ModelCallWebSocketFallbackReason, x)], obj.get("websocketFallbackReason")) + websocket_fallback_reconnect_attempted = from_union([from_none, from_bool], obj.get("websocketFallbackReconnectAttempted")) + websocket_fallback_started_this_call = from_union([from_none, from_bool], obj.get("websocketFallbackStartedThisCall")) + websocket_fallback_status_code = from_union([from_none, from_int], obj.get("websocketFallbackStatusCode")) return AssistantUsageData( model=model, accepted_prediction_tokens=accepted_prediction_tokens, @@ -3821,6 +3893,8 @@ def from_dict(obj: Any) -> "AssistantUsageData": reasoning_tokens=reasoning_tokens, rejected_prediction_tokens=rejected_prediction_tokens, request_body_bytes=request_body_bytes, + request_body_encoding=request_body_encoding, + request_body_wire_bytes=request_body_wire_bytes, rte=rte, service_request_id=service_request_id, _thinking_dropped_blocks=_thinking_dropped_blocks, @@ -3830,7 +3904,12 @@ def from_dict(obj: Any) -> "AssistantUsageData": _tool_token_count=_tool_token_count, transport=transport, websocket_fallback_after=websocket_fallback_after, + websocket_fallback_close_code=websocket_fallback_close_code, + websocket_fallback_error_kind=websocket_fallback_error_kind, websocket_fallback_reason=websocket_fallback_reason, + websocket_fallback_reconnect_attempted=websocket_fallback_reconnect_attempted, + websocket_fallback_started_this_call=websocket_fallback_started_this_call, + websocket_fallback_status_code=websocket_fallback_status_code, ) def to_dict(self) -> dict: @@ -3918,6 +3997,10 @@ def to_dict(self) -> dict: result["rejectedPredictionTokens"] = from_union([from_none, to_int], self.rejected_prediction_tokens) if self.request_body_bytes is not None: result["requestBodyBytes"] = from_union([from_none, to_int], self.request_body_bytes) + if self.request_body_encoding is not None: + result["requestBodyEncoding"] = from_union([from_none, lambda x: to_enum(ModelCallRequestBodyEncoding, x)], self.request_body_encoding) + if self.request_body_wire_bytes is not None: + result["requestBodyWireBytes"] = from_union([from_none, to_int], self.request_body_wire_bytes) if self.rte is not None: result["rte"] = from_union([from_none, from_bool], self.rte) if self.service_request_id is not None: @@ -3936,8 +4019,18 @@ def to_dict(self) -> dict: result["transport"] = from_union([from_none, lambda x: to_enum(AssistantUsageTransport, x)], self.transport) if self.websocket_fallback_after is not None: result["websocketFallbackAfterMs"] = from_union([from_none, to_timedelta_int], self.websocket_fallback_after) + if self.websocket_fallback_close_code is not None: + result["websocketFallbackCloseCode"] = from_union([from_none, to_int], self.websocket_fallback_close_code) + if self.websocket_fallback_error_kind is not None: + result["websocketFallbackErrorKind"] = from_union([from_none, lambda x: to_enum(ModelCallWebSocketFallbackErrorKind, x)], self.websocket_fallback_error_kind) if self.websocket_fallback_reason is not None: result["websocketFallbackReason"] = from_union([from_none, lambda x: to_enum(ModelCallWebSocketFallbackReason, x)], self.websocket_fallback_reason) + if self.websocket_fallback_reconnect_attempted is not None: + result["websocketFallbackReconnectAttempted"] = from_union([from_none, from_bool], self.websocket_fallback_reconnect_attempted) + if self.websocket_fallback_started_this_call is not None: + result["websocketFallbackStartedThisCall"] = from_union([from_none, from_bool], self.websocket_fallback_started_this_call) + if self.websocket_fallback_status_code is not None: + result["websocketFallbackStatusCode"] = from_union([from_none, to_int], self.websocket_fallback_status_code) return result @@ -4928,6 +5021,7 @@ class CompactionCompleteCompactionTokensUsed: model_display_name: str | None = None output_tokens: int | None = None provider: ModelProviderRef | None = None + request_count: int | None = None @staticmethod def from_dict(obj: Any) -> "CompactionCompleteCompactionTokensUsed": @@ -4942,6 +5036,7 @@ def from_dict(obj: Any) -> "CompactionCompleteCompactionTokensUsed": model_display_name = from_union([from_none, from_str], obj.get("modelDisplayName")) output_tokens = from_union([from_none, from_int], obj.get("outputTokens")) provider = from_union([from_none, ModelProviderRef.from_dict], obj.get("provider")) + request_count = from_union([from_none, from_int], obj.get("requestCount")) return CompactionCompleteCompactionTokensUsed( ai_credits_status=ai_credits_status, cache_read_tokens=cache_read_tokens, @@ -4953,6 +5048,7 @@ def from_dict(obj: Any) -> "CompactionCompleteCompactionTokensUsed": model_display_name=model_display_name, output_tokens=output_tokens, provider=provider, + request_count=request_count, ) def to_dict(self) -> dict: @@ -4977,6 +5073,8 @@ def to_dict(self) -> dict: result["outputTokens"] = from_union([from_none, to_int], self.output_tokens) if self.provider is not None: result["provider"] = from_union([from_none, lambda x: to_class(ModelProviderRef, x)], self.provider) + if self.request_count is not None: + result["requestCount"] = from_union([from_none, to_int], self.request_count) return result @@ -5885,6 +5983,30 @@ def to_dict(self) -> dict: return result +@dataclass +class ManagedPermissionsContext: + "Reusable managed permission evaluation context. Treat permissions as runtime-owned policy data and retain it verbatim; it includes source-aware composition metadata. This is a snapshot, not a capability or approval token; only use contexts obtained from trusted policy sources." + fail_closed: bool + permissions: Any = None + + @staticmethod + def from_dict(obj: Any) -> "ManagedPermissionsContext": + assert isinstance(obj, dict) + fail_closed = from_bool(obj.get("failClosed")) + permissions = obj.get("permissions") + return ManagedPermissionsContext( + fail_closed=fail_closed, + permissions=permissions, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["failClosed"] = from_bool(self.fail_closed) + if self.permissions is not None: + result["permissions"] = self.permissions + return result + + @dataclass class McpAppToolCallCompleteData: "MCP App view called a tool on a connected MCP server (SEP-1865)" @@ -6388,6 +6510,8 @@ class ModelCallFailureData: _quota_snapshots: dict[str, _AssistantUsageQuotaSnapshot] | None = None reasoning_effort: str | None = None request_body_bytes: int | None = None + request_body_encoding: ModelCallRequestBodyEncoding | None = None + request_body_wire_bytes: int | None = None request_fingerprint: ModelCallFailureRequestFingerprint | None = None retry_attempt: int | None = None rte: bool | None = None @@ -6395,7 +6519,12 @@ class ModelCallFailureData: status_code: int | None = None transport: ModelCallFailureTransport | None = None websocket_fallback_after: timedelta | None = None + websocket_fallback_close_code: int | None = None + websocket_fallback_error_kind: ModelCallWebSocketFallbackErrorKind | None = None websocket_fallback_reason: ModelCallWebSocketFallbackReason | None = None + websocket_fallback_reconnect_attempted: bool | None = None + websocket_fallback_started_this_call: bool | None = None + websocket_fallback_status_code: int | None = None @staticmethod def from_dict(obj: Any) -> "ModelCallFailureData": @@ -6424,6 +6553,8 @@ def from_dict(obj: Any) -> "ModelCallFailureData": _quota_snapshots = from_union([from_none, lambda x: from_dict(_AssistantUsageQuotaSnapshot.from_dict, x)], obj.get("quotaSnapshots")) reasoning_effort = from_union([from_none, from_str], obj.get("reasoningEffort")) request_body_bytes = from_union([from_none, from_int], obj.get("requestBodyBytes")) + request_body_encoding = from_union([from_none, lambda x: parse_enum(ModelCallRequestBodyEncoding, x)], obj.get("requestBodyEncoding")) + request_body_wire_bytes = from_union([from_none, from_int], obj.get("requestBodyWireBytes")) request_fingerprint = from_union([from_none, ModelCallFailureRequestFingerprint.from_dict], obj.get("requestFingerprint")) retry_attempt = from_union([from_none, from_int], obj.get("retryAttempt")) rte = from_union([from_none, from_bool], obj.get("rte")) @@ -6431,7 +6562,12 @@ def from_dict(obj: Any) -> "ModelCallFailureData": status_code = from_union([from_none, from_int], obj.get("statusCode")) transport = from_union([from_none, lambda x: parse_enum(ModelCallFailureTransport, x)], obj.get("transport")) websocket_fallback_after = from_union([from_none, from_timedelta], obj.get("websocketFallbackAfterMs")) + websocket_fallback_close_code = from_union([from_none, from_int], obj.get("websocketFallbackCloseCode")) + websocket_fallback_error_kind = from_union([from_none, lambda x: parse_enum(ModelCallWebSocketFallbackErrorKind, x)], obj.get("websocketFallbackErrorKind")) websocket_fallback_reason = from_union([from_none, lambda x: parse_enum(ModelCallWebSocketFallbackReason, x)], obj.get("websocketFallbackReason")) + websocket_fallback_reconnect_attempted = from_union([from_none, from_bool], obj.get("websocketFallbackReconnectAttempted")) + websocket_fallback_started_this_call = from_union([from_none, from_bool], obj.get("websocketFallbackStartedThisCall")) + websocket_fallback_status_code = from_union([from_none, from_int], obj.get("websocketFallbackStatusCode")) return ModelCallFailureData( source=source, api_call_id=api_call_id, @@ -6457,6 +6593,8 @@ def from_dict(obj: Any) -> "ModelCallFailureData": _quota_snapshots=_quota_snapshots, reasoning_effort=reasoning_effort, request_body_bytes=request_body_bytes, + request_body_encoding=request_body_encoding, + request_body_wire_bytes=request_body_wire_bytes, request_fingerprint=request_fingerprint, retry_attempt=retry_attempt, rte=rte, @@ -6464,7 +6602,12 @@ def from_dict(obj: Any) -> "ModelCallFailureData": status_code=status_code, transport=transport, websocket_fallback_after=websocket_fallback_after, + websocket_fallback_close_code=websocket_fallback_close_code, + websocket_fallback_error_kind=websocket_fallback_error_kind, websocket_fallback_reason=websocket_fallback_reason, + websocket_fallback_reconnect_attempted=websocket_fallback_reconnect_attempted, + websocket_fallback_started_this_call=websocket_fallback_started_this_call, + websocket_fallback_status_code=websocket_fallback_status_code, ) def to_dict(self) -> dict: @@ -6516,6 +6659,10 @@ def to_dict(self) -> dict: result["reasoningEffort"] = from_union([from_none, from_str], self.reasoning_effort) if self.request_body_bytes is not None: result["requestBodyBytes"] = from_union([from_none, to_int], self.request_body_bytes) + if self.request_body_encoding is not None: + result["requestBodyEncoding"] = from_union([from_none, lambda x: to_enum(ModelCallRequestBodyEncoding, x)], self.request_body_encoding) + if self.request_body_wire_bytes is not None: + result["requestBodyWireBytes"] = from_union([from_none, to_int], self.request_body_wire_bytes) if self.request_fingerprint is not None: result["requestFingerprint"] = from_union([from_none, lambda x: to_class(ModelCallFailureRequestFingerprint, x)], self.request_fingerprint) if self.retry_attempt is not None: @@ -6530,8 +6677,18 @@ def to_dict(self) -> dict: result["transport"] = from_union([from_none, lambda x: to_enum(ModelCallFailureTransport, x)], self.transport) if self.websocket_fallback_after is not None: result["websocketFallbackAfterMs"] = from_union([from_none, to_timedelta_int], self.websocket_fallback_after) + if self.websocket_fallback_close_code is not None: + result["websocketFallbackCloseCode"] = from_union([from_none, to_int], self.websocket_fallback_close_code) + if self.websocket_fallback_error_kind is not None: + result["websocketFallbackErrorKind"] = from_union([from_none, lambda x: to_enum(ModelCallWebSocketFallbackErrorKind, x)], self.websocket_fallback_error_kind) if self.websocket_fallback_reason is not None: result["websocketFallbackReason"] = from_union([from_none, lambda x: to_enum(ModelCallWebSocketFallbackReason, x)], self.websocket_fallback_reason) + if self.websocket_fallback_reconnect_attempted is not None: + result["websocketFallbackReconnectAttempted"] = from_union([from_none, from_bool], self.websocket_fallback_reconnect_attempted) + if self.websocket_fallback_started_this_call is not None: + result["websocketFallbackStartedThisCall"] = from_union([from_none, from_bool], self.websocket_fallback_started_this_call) + if self.websocket_fallback_status_code is not None: + result["websocketFallbackStatusCode"] = from_union([from_none, to_int], self.websocket_fallback_status_code) return result @@ -7773,6 +7930,8 @@ class PermissionRecoveryData: on_blocked: PermissionRecoveryOnBlocked reason: PermissionRecoveryReason status: PermissionRecoveryStatus + owner_agent_id: str | None = None + owner_session_id: str | None = None @staticmethod def from_dict(obj: Any) -> "PermissionRecoveryData": @@ -7783,6 +7942,8 @@ def from_dict(obj: Any) -> "PermissionRecoveryData": on_blocked = parse_enum(PermissionRecoveryOnBlocked, obj.get("onBlocked")) reason = parse_enum(PermissionRecoveryReason, obj.get("reason")) status = parse_enum(PermissionRecoveryStatus, obj.get("status")) + owner_agent_id = from_union([from_none, from_str], obj.get("ownerAgentId")) + owner_session_id = from_union([from_none, from_str], obj.get("ownerSessionId")) return PermissionRecoveryData( attempts=attempts, episode_id=episode_id, @@ -7790,6 +7951,8 @@ def from_dict(obj: Any) -> "PermissionRecoveryData": on_blocked=on_blocked, reason=reason, status=status, + owner_agent_id=owner_agent_id, + owner_session_id=owner_session_id, ) def to_dict(self) -> dict: @@ -7800,6 +7963,10 @@ def to_dict(self) -> dict: result["onBlocked"] = to_enum(PermissionRecoveryOnBlocked, self.on_blocked) result["reason"] = to_enum(PermissionRecoveryReason, self.reason) result["status"] = to_enum(PermissionRecoveryStatus, self.status) + if self.owner_agent_id is not None: + result["ownerAgentId"] = from_union([from_none, from_str], self.owner_agent_id) + if self.owner_session_id is not None: + result["ownerSessionId"] = from_union([from_none, from_str], self.owner_session_id) return result @@ -8563,6 +8730,8 @@ class PermissionRequestedData: permission_request: PermissionRequest request_id: str agent_mode: SessionMode | None = None + owner_agent_id: str | None = None + owner_session_id: str | None = None permission_mode: PermissionMode | None = None prompt_request: PermissionPromptRequest | None = None recovery_episode_id: str | None = None @@ -8575,6 +8744,8 @@ def from_dict(obj: Any) -> "PermissionRequestedData": 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")) + owner_agent_id = from_union([from_none, from_str], obj.get("ownerAgentId")) + owner_session_id = from_union([from_none, from_str], obj.get("ownerSessionId")) permission_mode = from_union([from_none, lambda x: parse_enum(PermissionMode, x)], obj.get("permissionMode")) prompt_request = from_union([from_none, _load_PermissionPromptRequest], obj.get("promptRequest")) recovery_episode_id = from_union([from_none, from_str], obj.get("recoveryEpisodeId")) @@ -8584,6 +8755,8 @@ def from_dict(obj: Any) -> "PermissionRequestedData": permission_request=permission_request, request_id=request_id, agent_mode=agent_mode, + owner_agent_id=owner_agent_id, + owner_session_id=owner_session_id, permission_mode=permission_mode, prompt_request=prompt_request, recovery_episode_id=recovery_episode_id, @@ -8597,6 +8770,10 @@ def to_dict(self) -> 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.owner_agent_id is not None: + result["ownerAgentId"] = from_union([from_none, from_str], self.owner_agent_id) + if self.owner_session_id is not None: + result["ownerSessionId"] = from_union([from_none, from_str], self.owner_session_id) if self.permission_mode is not None: result["permissionMode"] = from_union([from_none, lambda x: to_enum(PermissionMode, x)], self.permission_mode) if self.prompt_request is not None: @@ -9307,8 +9484,6 @@ class SessionCompactionCompleteData: "Conversation compaction results including success status, metrics, and optional error details" success: bool # Internal: this field is an internal SDK API and is not part of the public surface. - _active_factory_summary: str | None = None - # Internal: this field is an internal SDK API and is not part of the public surface. _active_workflow_summary: str | None = None behavior_model_id: str | None = None checkpoint_number: int | None = None @@ -9336,7 +9511,6 @@ class SessionCompactionCompleteData: def from_dict(obj: Any) -> "SessionCompactionCompleteData": assert isinstance(obj, dict) success = from_bool(obj.get("success")) - _active_factory_summary = from_union([from_none, from_str], obj.get("activeFactorySummary")) _active_workflow_summary = from_union([from_none, from_str], obj.get("activeWorkflowSummary")) behavior_model_id = from_union([from_none, from_str], obj.get("behaviorModelId")) checkpoint_number = from_union([from_none, from_int], obj.get("checkpointNumber")) @@ -9361,7 +9535,6 @@ def from_dict(obj: Any) -> "SessionCompactionCompleteData": trigger = from_union([from_none, lambda x: parse_enum(CompactionTrigger, x)], obj.get("trigger")) return SessionCompactionCompleteData( success=success, - _active_factory_summary=_active_factory_summary, _active_workflow_summary=_active_workflow_summary, behavior_model_id=behavior_model_id, checkpoint_number=checkpoint_number, @@ -9389,8 +9562,6 @@ def from_dict(obj: Any) -> "SessionCompactionCompleteData": def to_dict(self) -> dict: result: dict = {} result["success"] = from_bool(self.success) - if self._active_factory_summary is not None: - result["activeFactorySummary"] = from_union([from_none, from_str], self._active_factory_summary) if self._active_workflow_summary is not None: result["activeWorkflowSummary"] = from_union([from_none, from_str], self._active_workflow_summary) if self.behavior_model_id is not None: @@ -9643,6 +9814,8 @@ class SessionErrorData: eligible_for_auto_switch: bool | None = None error_code: str | None = None provider_call_id: str | None = None + provider_error_param: str | None = None + provider_error_type: str | None = None remediation: RemediationAction | None = None service_request_id: str | None = None stack: str | None = None @@ -9657,6 +9830,8 @@ def from_dict(obj: Any) -> "SessionErrorData": eligible_for_auto_switch = from_union([from_none, from_bool], obj.get("eligibleForAutoSwitch")) error_code = from_union([from_none, from_str], obj.get("errorCode")) provider_call_id = from_union([from_none, from_str], obj.get("providerCallId")) + provider_error_param = from_union([from_none, from_str], obj.get("providerErrorParam")) + provider_error_type = from_union([from_none, from_str], obj.get("providerErrorType")) remediation = from_union([from_none, lambda x: parse_enum(RemediationAction, x)], obj.get("remediation")) service_request_id = from_union([from_none, from_str], obj.get("serviceRequestId")) stack = from_union([from_none, from_str], obj.get("stack")) @@ -9668,6 +9843,8 @@ def from_dict(obj: Any) -> "SessionErrorData": eligible_for_auto_switch=eligible_for_auto_switch, error_code=error_code, provider_call_id=provider_call_id, + provider_error_param=provider_error_param, + provider_error_type=provider_error_type, remediation=remediation, service_request_id=service_request_id, stack=stack, @@ -9685,6 +9862,10 @@ def to_dict(self) -> dict: result["errorCode"] = from_union([from_none, from_str], self.error_code) if self.provider_call_id is not None: result["providerCallId"] = from_union([from_none, from_str], self.provider_call_id) + if self.provider_error_param is not None: + result["providerErrorParam"] = from_union([from_none, from_str], self.provider_error_param) + if self.provider_error_type is not None: + result["providerErrorType"] = from_union([from_none, from_str], self.provider_error_type) if self.remediation is not None: result["remediation"] = from_union([from_none, lambda x: to_enum(RemediationAction, x)], self.remediation) if self.service_request_id is not None: @@ -10338,6 +10519,8 @@ class SessionPermissionRecoveryData: on_blocked: PermissionRecoveryOnBlocked reason: PermissionRecoveryReason status: PermissionRecoveryStatus + owner_agent_id: str | None = None + owner_session_id: str | None = None @staticmethod def from_dict(obj: Any) -> "SessionPermissionRecoveryData": @@ -10348,6 +10531,8 @@ def from_dict(obj: Any) -> "SessionPermissionRecoveryData": on_blocked = parse_enum(PermissionRecoveryOnBlocked, obj.get("onBlocked")) reason = parse_enum(PermissionRecoveryReason, obj.get("reason")) status = parse_enum(PermissionRecoveryStatus, obj.get("status")) + owner_agent_id = from_union([from_none, from_str], obj.get("ownerAgentId")) + owner_session_id = from_union([from_none, from_str], obj.get("ownerSessionId")) return SessionPermissionRecoveryData( attempts=attempts, episode_id=episode_id, @@ -10355,6 +10540,8 @@ def from_dict(obj: Any) -> "SessionPermissionRecoveryData": on_blocked=on_blocked, reason=reason, status=status, + owner_agent_id=owner_agent_id, + owner_session_id=owner_session_id, ) def to_dict(self) -> dict: @@ -10365,6 +10552,10 @@ def to_dict(self) -> dict: result["onBlocked"] = to_enum(PermissionRecoveryOnBlocked, self.on_blocked) result["reason"] = to_enum(PermissionRecoveryReason, self.reason) result["status"] = to_enum(PermissionRecoveryStatus, self.status) + if self.owner_agent_id is not None: + result["ownerAgentId"] = from_union([from_none, from_str], self.owner_agent_id) + if self.owner_session_id is not None: + result["ownerSessionId"] = from_union([from_none, from_str], self.owner_session_id) return result @@ -11933,7 +12124,6 @@ class SubagentStartedData: tool_call_id: str agent_type: str | None = None execution_mode: str | None = None - factory_run_id: str | None = None model: str | None = None model_selection_source: SubagentModelSelectionSource | None = None parent_id: str | None = None @@ -11950,7 +12140,6 @@ def from_dict(obj: Any) -> "SubagentStartedData": 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")) model_selection_source = from_union([from_none, lambda x: parse_enum(SubagentModelSelectionSource, x)], obj.get("modelSelectionSource")) parent_id = from_union([from_none, from_str], obj.get("parentId")) @@ -11964,7 +12153,6 @@ def from_dict(obj: Any) -> "SubagentStartedData": tool_call_id=tool_call_id, agent_type=agent_type, execution_mode=execution_mode, - factory_run_id=factory_run_id, model=model, model_selection_source=model_selection_source, parent_id=parent_id, @@ -11983,8 +12171,6 @@ def to_dict(self) -> dict: 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.model_selection_source is not None: @@ -15221,6 +15407,44 @@ class ModelCallFinishedOutcome(Enum): REJECTED = "rejected" +class ModelCallRequestBodyEncoding(Enum): + "Content-Encoding applied to the request body sent on the wire" + # The request body was sent uncompressed (including every WebSocket request). + IDENTITY = "identity" + # The request body was sent gzip-compressed. + GZIP = "gzip" + # The request body was sent zstd-compressed. + ZSTD = "zstd" + + +class ModelCallWebSocketFallbackErrorKind(Enum): + "Normalized cause of the WebSocket failure that triggered the HTTP fallback" + # DNS resolution of the WebSocket host failed. + DNS = "dns" + # The TLS handshake or a TLS record failed. + TLS = "tls" + # A WebSocket connect or read timed out. + TIMEOUT = "timeout" + # The peer refused the TCP connection. + CONNECTION_REFUSED = "connection_refused" + # The connection was reset, aborted, or ended mid-stream without a close. + CONNECTION_RESET = "connection_reset" + # The peer closed the WebSocket. + CLOSED_BY_PEER = "closed_by_peer" + # The runtime closed the WebSocket itself. + CLOSED_LOCALLY = "closed_locally" + # The socket was already closed before it was used. + NOT_CONNECTED = "not_connected" + # The upgrade or request was rejected with an HTTP status. + HTTP_STATUS = "http_status" + # A WebSocket protocol violation or an undecodable frame. + PROTOCOL = "protocol" + # The WebSocket request could not be built. + CONFIGURATION = "configuration" + # A failure that maps onto none of the other kinds. + OTHER = "other" + + class ModelCallWebSocketFallbackReason(Enum): "Why a WebSocket-capable model call was carried by the HTTP fallback" # The WebSocket connection could not be established. @@ -15547,6 +15771,26 @@ class PlanChangedOperation(Enum): DELETE = "delete" +class ProviderMonthlyUsageScope(Enum): + "Identity scope of a monthly usage reading." + # Consumption of the authenticated user within the parent observation's service and returned cycle. + USER = "user" + # The service did not establish a recognized usage scope. + UNKNOWN = "unknown" + + +class ProviderMonthlyUsageState(Enum): + "Availability of a service's monthly consumption reading." + # The service reported a usable consumption reading, including an explicit zero. + AVAILABLE = "available" + # The service found no usage policy; no consumption amount is known. + NO_POLICY = "no_policy" + # The read failed or its data could not be used; consumption is unknown. + UNAVAILABLE = "unavailable" + # The service reported an unrecognized usage state. + UNKNOWN = "unknown" + + class ProviderQuotaAccessState(Enum): "Access policy reported by the quota service." # Access is allowed. @@ -15660,6 +15904,8 @@ class RemediationAction(Enum): REVIEW_SANDBOX_POLICY = "review_sandbox_policy" # Permit outbound network access in the sandbox policy. ALLOW_SANDBOX_OUTBOUND = "allow_sandbox_outbound" + # Discard conversation history back through the blocked request before continuing. + REWIND_CONVERSATION = "rewind_conversation" class SandboxAttestation(Enum): @@ -16474,6 +16720,7 @@ def session_event_to_dict(x: SessionEvent) -> Any: "IndexedSearchIncrementalPhase", "IndexedSearchOutcome", "IndexedSearchState", + "ManagedPermissionsContext", "ManagedPluginProgressPhase", "ManagedSettingsEnforcedAction", "ManagedSettingsEnforcedEscalation", @@ -16511,7 +16758,9 @@ def session_event_to_dict(x: SessionEvent) -> Any: "ModelCallFinalResultData", "ModelCallFinishedData", "ModelCallFinishedOutcome", + "ModelCallRequestBodyEncoding", "ModelCallStartData", + "ModelCallWebSocketFallbackErrorKind", "ModelCallWebSocketFallbackReason", "ModelChangeSource", "ModelDeselectedReason", @@ -16601,6 +16850,9 @@ def session_event_to_dict(x: SessionEvent) -> Any: "PersistedBinaryResult", "PlanChangedOperation", "PromptCacheBreakData", + "ProviderMonthlyUsage", + "ProviderMonthlyUsageScope", + "ProviderMonthlyUsageState", "ProviderQuotaAccessState", "ProviderQuotaAcquisitionStatus", "ProviderQuotaBudgetMetadata", diff --git a/python/copilot/session.py b/python/copilot/session.py index 9c8e0cf55f..5b6bffb07d 100644 --- a/python/copilot/session.py +++ b/python/copilot/session.py @@ -11,6 +11,7 @@ import asyncio import functools import inspect +import json import logging import os import pathlib @@ -256,6 +257,22 @@ def _tool_handlers_for(tools: list[Tool] | None) -> dict[str, ToolHandler]: } +def _hook_error_text(error: Any) -> Any: + """Decode the ``error`` of an errorOccurred or sessionEnd hook input. + + The CLI sends an error object (``{"name", "message", "stack"}`` or + ``{"message"}``) where the hook input types declare a string: its message + becomes the error, and an object without a string message becomes its + compact JSON. Other values pass through. + """ + if isinstance(error, dict): + message = error.get("message") + if isinstance(message, str): + return message + return json.dumps(error, separators=(",", ":"), ensure_ascii=False) + return error + + def _consume_task_exception(task: asyncio.Task[Any]) -> None: if not task.cancelled(): task.exception() @@ -994,6 +1011,10 @@ class PreToolUseHookInput(TypedDict): toolName: str toolArgs: Any + agentId: NotRequired[str] + agentType: NotRequired[str] + parentAgentId: NotRequired[str] + class PreToolUseHookOutput(TypedDict, total=False): """Output for pre-tool-use hook""" @@ -1023,6 +1044,10 @@ class PreMcpToolCallHookInput(TypedDict): toolCallId: NotRequired[str] _meta: NotRequired[dict[str, Any]] + agentId: NotRequired[str] + agentType: NotRequired[str] + parentAgentId: NotRequired[str] + class PreMcpToolCallHookOutput(TypedDict, total=False): """Output for pre-MCP-tool-call hook. @@ -1052,6 +1077,10 @@ class PostToolUseHookInput(TypedDict): toolArgs: Any toolResult: Any + agentId: NotRequired[str] + agentType: NotRequired[str] + parentAgentId: NotRequired[str] + class PostToolUseHookOutput(TypedDict, total=False): """Output for post-tool-use hook""" @@ -1082,6 +1111,10 @@ class PostToolUseFailureHookInput(TypedDict): toolArgs: Any error: str + agentId: NotRequired[str] + agentType: NotRequired[str] + parentAgentId: NotRequired[str] + class PostToolUseFailureHookOutput(TypedDict, total=False): """Output for post-tool-use-failure hook. @@ -1107,6 +1140,10 @@ class UserPromptSubmittedHookInput(TypedDict): workingDirectory: str prompt: str + agentId: NotRequired[str] + agentType: NotRequired[str] + parentAgentId: NotRequired[str] + class UserPromptSubmittedHookOutput(TypedDict, total=False): """Output for user-prompt-submitted hook""" @@ -1131,6 +1168,10 @@ class UserPromptTransformedHookInput(TypedDict): prompt: str transformedPrompt: str + agentId: NotRequired[str] + agentType: NotRequired[str] + parentAgentId: NotRequired[str] + class UserPromptTransformedHookOutput(TypedDict, total=False): """Output for the user-prompt-transformed hook.""" @@ -1153,6 +1194,10 @@ class SessionStartHookInput(TypedDict): source: Literal["startup", "resume", "new"] initialPrompt: NotRequired[str] + agentId: NotRequired[str] + agentType: NotRequired[str] + parentAgentId: NotRequired[str] + class SessionStartHookOutput(TypedDict, total=False): """Output for session-start hook""" @@ -1177,6 +1222,10 @@ class SessionEndHookInput(TypedDict): finalMessage: NotRequired[str] error: NotRequired[str] + agentId: NotRequired[str] + agentType: NotRequired[str] + parentAgentId: NotRequired[str] + class SessionEndHookOutput(TypedDict, total=False): """Output for session-end hook""" @@ -1202,6 +1251,10 @@ class ErrorOccurredHookInput(TypedDict): errorContext: Literal["model_call", "tool_execution", "system", "user_input"] recoverable: bool + agentId: NotRequired[str] + agentType: NotRequired[str] + parentAgentId: NotRequired[str] + class ErrorOccurredHookOutput(TypedDict, total=False): """Output for error-occurred hook""" @@ -1228,6 +1281,10 @@ class AgentStopHookInput(TypedDict): transcriptPath: NotRequired[str] stopHookActive: NotRequired[bool] + agentId: NotRequired[str] + agentType: NotRequired[str] + parentAgentId: NotRequired[str] + class AgentStopHookOutput(TypedDict, total=False): """Output for the agent-stop hook.""" @@ -1253,6 +1310,10 @@ class SubagentStartHookInput(TypedDict): agentDisplayName: NotRequired[str] agentDescription: NotRequired[str] + agentId: NotRequired[str] + agentType: NotRequired[str] + parentAgentId: NotRequired[str] + class SubagentStartHookOutput(TypedDict, total=False): """Context prepended to the subagent's initial prompt.""" @@ -1281,6 +1342,8 @@ class SubagentStopHookInput(TypedDict): agentDisplayName: NotRequired[str] agentDescription: NotRequired[str] + parentAgentId: NotRequired[str] + class _SubagentStopBlockHookOutput(TypedDict): decision: Literal["block"] @@ -1373,6 +1436,7 @@ class GitHubMcpToolConfig(TypedDict, total=False): class CustomAgentConfig(TypedDict, total=False): """Configuration for a custom agent.""" + hooks: NotRequired[dict[str, list[dict[str, Any]]]] name: str # Unique name of the custom agent display_name: NotRequired[str] # Display name for UI purposes description: NotRequired[str] # Description of what the agent does @@ -1448,6 +1512,20 @@ class LargeToolOutputConfig(TypedDict, total=False): output_directory: str +class ImageGenerationConfig(TypedDict, total=False): + """**Experimental.** Opt in to an authorized Copilot image model. + + This API may change or be removed in a future release. + + Omission disables on create/cold resume and preserves resident state on + resume. Not persisted: re-supply after runtime restart. Policy, permissions, + offline mode, and tool filters still apply. BYOK is not supported. + """ + + enabled: bool + """True opts in; False disables image generation.""" + + class ToolSearchConfig(TypedDict, total=False): """ Override for the runtime's built-in tool-search behavior. @@ -3560,6 +3638,10 @@ async def _handle_hooks_invoke(self, hook_type: str, input_data: Any) -> Any: timestamp = transformed.get("timestamp") if isinstance(timestamp, (int, float)): transformed["timestamp"] = datetime.fromtimestamp(timestamp / 1000, tz=UTC) + # The CLI sends errorOccurred's and sessionEnd's ``error`` as an + # error object; the hook input types declare a string. + if hook_type in ("errorOccurred", "sessionEnd") and "error" in transformed: + transformed["error"] = _hook_error_text(transformed["error"]) # Each per-hook-type TypedDict is structurally compatible with the # normalized dict; cast to ``Any`` so ty doesn't try to narrow the # specific TypedDict variant from the runtime ``dict``. diff --git a/python/e2e/inference_request.py b/python/e2e/inference_request.py new file mode 100644 index 0000000000..280e457187 --- /dev/null +++ b/python/e2e/inference_request.py @@ -0,0 +1,27 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. + + +def user_message_text(body: dict) -> list[str]: + if isinstance(body.get("input"), str): + return [body["input"]] + messages = body.get("messages") + if messages is None: + messages = body.get("input", []) + texts: list[str] = [] + if not isinstance(messages, list): + return texts + for message in messages: + if not isinstance(message, dict) or message.get("role") != "user": + continue + content = message.get("content") + if isinstance(content, str): + texts.append(content) + elif isinstance(content, list): + texts.extend( + part["text"] + for part in content + if isinstance(part, dict) + and part.get("type") in ("text", "input_text") + and isinstance(part.get("text"), str) + ) + return texts diff --git a/python/e2e/test_client_options_e2e.py b/python/e2e/test_client_options_e2e.py index 1267bf6d57..701ddddd42 100644 --- a/python/e2e/test_client_options_e2e.py +++ b/python/e2e/test_client_options_e2e.py @@ -499,6 +499,7 @@ async def test_should_forward_advanced_session_options_in_create_wire_request( try: await client.start() session = await client.create_session( + name="python named at create", client_name="advanced-create-client", model="claude-sonnet-5", reasoning_effort="medium", @@ -585,6 +586,7 @@ async def test_should_forward_advanced_session_options_in_create_wire_request( ) try: params = _get_captured_request(capture_path, "session.create") + assert params["name"] == "python named at create" assert params["clientName"] == "advanced-create-client" assert params["model"] == "claude-sonnet-5" assert params["reasoningEffort"] == "medium" diff --git a/python/e2e/test_subagent_hooks_e2e.py b/python/e2e/test_subagent_hooks_e2e.py index e06168f0ce..6f0e245b75 100644 --- a/python/e2e/test_subagent_hooks_e2e.py +++ b/python/e2e/test_subagent_hooks_e2e.py @@ -26,6 +26,7 @@ from copilot.client import CopilotClient, RuntimeConnection from copilot.session import PermissionHandler +from .inference_request import user_message_text from .testharness import E2ETestContext from .testharness.helper import write_file @@ -55,21 +56,9 @@ async def send_request( if _is_inference_url(str(request.url)): body = json.loads(request.content) if ctx.parent_agent_id: - for message in body.get("messages", []): - if message["role"] != "user": - continue - content = message.get("content") - parts = [content] if isinstance(content, str) else content - if isinstance(parts, list): - for part in parts: - if isinstance(part, str): - text = part - elif isinstance(part, dict): - text = part.get("text") - else: - continue - if isinstance(text, str) and f"{CHILD_CONTEXT}\n\n" in text: - self.child_prompts.append(text) + for text in user_message_text(body): + if f"{CHILD_CONTEXT}\n\n" in text: + self.child_prompts.append(text) elif STOP_RESPONSE_PREFIX in json.dumps(body): self.modified_parent_request.set() return await super().send_request(request, ctx) diff --git a/python/test_client.py b/python/test_client.py index 5badf26ee6..8180de01b5 100644 --- a/python/test_client.py +++ b/python/test_client.py @@ -16,6 +16,7 @@ import pytest +import copilot.client as client_module from copilot import ( CanvasProviderIdentity, CapiSessionOptions, @@ -87,6 +88,29 @@ def test_inprocess_connection_has_no_child_process_options(): assert not hasattr(connection, "args") +@pytest.mark.asyncio +async def test_inprocess_forwards_integration_id_to_native_host( + monkeypatch: pytest.MonkeyPatch, tmp_path +): + monkeypatch.setenv("GITHUB_COPILOT_INTEGRATION_ID", " microsoft/vscode ") + monkeypatch.setattr( + "copilot._cli_download.ensure_runtime_wrapper", + lambda: str(tmp_path / "copilot-runtime"), + ) + host = Mock() + host.process = Mock() + host.start_blocking = Mock() + create_host = Mock(return_value=host) + monkeypatch.setattr(client_module.FfiRuntimeHost, "create", create_host) + client = CopilotClient(connection=RuntimeConnection.for_inprocess()) + + await client._start_inprocess_ffi() + + assert ( + "--cli-login-sync-integration-id=microsoft/vscode" in create_host.call_args.kwargs["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)) @@ -1319,6 +1343,7 @@ async def mock_request(method, params, **kwargs): on_permission_request=PermissionHandler.approve_all, enable_managed_settings=True, enforce_managed_model_defaults=True, + interactive_user=True, managed_settings=ManagedSettings( permissions=ManagedSettingsPermissions( disable_bypass_permissions_mode=DisableBypassPermissionsModes.ALLOW_AUTO_ONLY, @@ -1334,6 +1359,7 @@ async def mock_request(method, params, **kwargs): session.session_id, on_permission_request=PermissionHandler.approve_all, enforce_managed_model_defaults=True, + interactive_user=True, managed_settings=ManagedSettings( permissions=ManagedSettingsPermissions( disable_bypass_permissions_mode="future-fail-closed-mode", @@ -1347,6 +1373,8 @@ async def mock_request(method, params, **kwargs): assert captured["session.create"]["enableManagedSettings"] is True assert captured["session.create"]["enforceManagedModelDefaults"] is True assert captured["session.resume"]["enforceManagedModelDefaults"] is True + assert captured["session.create"]["interactiveUser"] is True + assert captured["session.resume"]["interactiveUser"] is True assert captured["session.create"]["managedSettings"] == { "permissions": { "disableBypassPermissionsMode": "allow-auto-only", @@ -1380,6 +1408,39 @@ async def mock_request(method, params, **kwargs): finally: await client.force_stop() + @pytest.mark.asyncio + async def test_create_and_resume_session_forward_explicit_interactive_user_false(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, + interactive_user=False, + ) + await client.resume_session( + session.session_id, + on_permission_request=PermissionHandler.approve_all, + interactive_user=False, + ) + + assert captured["session.create"]["interactiveUser"] is False + assert captured["session.resume"]["interactiveUser"] is False + 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: @@ -1417,6 +1478,8 @@ async def mock_request(method, params, **kwargs): assert captured["session.create"]["isExperimentalMode"] is False assert captured["session.resume"]["isExperimentalMode"] is False + assert "interactiveUser" not in captured["session.create"] + assert "interactiveUser" not in captured["session.resume"] finally: await client.force_stop() @@ -1442,6 +1505,7 @@ async def mock_request(method, params, **kwargs): ) assert "managedSettings" not in captured["session.create"] + assert "interactiveUser" not in captured["session.create"] finally: await client.force_stop() @@ -1914,6 +1978,49 @@ async def mock_request(method, params, **kwargs): finally: await client.force_stop() + @pytest.mark.asyncio + async def test_create_and_resume_session_forward_github_cache_store(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_github_cache_store=True, + ) + await client.resume_session( + session.session_id, + on_permission_request=PermissionHandler.approve_all, + enable_github_cache_store=False, + ) + assert captured["session.create"]["enableGitHubCacheStore"] is True + assert captured["session.resume"]["enableGitHubCacheStore"] is False + + unset = await client.create_session( + on_permission_request=PermissionHandler.approve_all, + ) + await client.resume_session( + unset.session_id, + on_permission_request=PermissionHandler.approve_all, + ) + assert "enableGitHubCacheStore" not in captured["session.create"] + assert "enableGitHubCacheStore" not in captured["session.resume"] + finally: + await client.force_stop() + @pytest.mark.asyncio async def test_create_and_resume_session_forward_exp_assignments(self): client = CopilotClient(connection=RuntimeConnection.for_stdio(path=CLI_PATH)) @@ -3817,6 +3924,63 @@ def on_failure(input_data, invocation): assert result == {"additionalContext": "sync-ok"} +class TestHookErrorObjectNormalization: + """The CLI's error object reaches errorOccurred and sessionEnd handlers as a string.""" + + @pytest.mark.asyncio + @pytest.mark.parametrize( + ("error", "expected"), + [ + ( + {"name": "Error", "message": "model timeout", "stack": "Error: model timeout"}, + "model timeout", + ), + ("boom", "boom"), + (None, None), + ({"code": 7}, '{"code":7}'), + ], + ) + @pytest.mark.parametrize("hook_type", ["errorOccurred", "sessionEnd"]) + async def test_decodes_the_error(self, hook_type, error, expected): + from copilot.session import CopilotSession, SessionHooks + + captured: dict = {} + + def handler(input_data, invocation): + captured["input"] = input_data + + session = CopilotSession.__new__(CopilotSession) + CopilotSession.__init__(session, "sess-123", client=None) + session._hooks = SessionHooks( # type: ignore[typeddict-item] + on_error_occurred=handler, on_session_end=handler + ) + + await session._handle_hooks_invoke( + hook_type, + {"sessionId": "sess-x", "timestamp": 1700000000, "cwd": "/work", "error": error}, + ) + assert captured["input"]["error"] == expected + + @pytest.mark.asyncio + async def test_leaves_an_absent_session_end_error_absent(self): + from copilot.session import CopilotSession, SessionHooks + + captured: dict = {} + + def handler(input_data, invocation): + captured["input"] = input_data + + session = CopilotSession.__new__(CopilotSession) + CopilotSession.__init__(session, "sess-123", client=None) + session._hooks = SessionHooks(on_session_end=handler) # type: ignore[typeddict-item] + + await session._handle_hooks_invoke( + "sessionEnd", + {"sessionId": "sess-x", "timestamp": 1700000000, "cwd": "/work", "reason": "complete"}, + ) + assert "error" not in captured["input"] + + class TestAgentStopHookDispatch: """Unit tests for the agentStop handler dispatch.""" diff --git a/python/test_codegen_type_names.py b/python/test_codegen_type_names.py index 5242f1c786..c3a00cc044 100644 --- a/python/test_codegen_type_names.py +++ b/python/test_codegen_type_names.py @@ -27,3 +27,17 @@ def test_permission_approval_union_loaders_deserialize_expected_variants(): assert isinstance(session, rpc.PermissionDecisionApproveForSessionApprovalCommands) assert isinstance(location, rpc.PermissionDecisionApproveForLocationApprovalRead) + + +def test_catalog_candidate_provenance_serialises_every_catalog_media_type(): + assert not hasattr(rpc, "MediaType") + for media_type in rpc.CatalogMediaType: + provenance = rpc.CatalogCandidateProvenance( + authority="agentfinder.github.com", + media_type=media_type, + observed_at="2026-10-13T00:00:00.000Z", + ) + serialised = provenance.to_dict() + + assert serialised["mediaType"] == media_type.value + assert rpc.CatalogCandidateProvenance.from_dict(serialised) == provenance diff --git a/python/test_customization_directories.py b/python/test_customization_directories.py new file mode 100644 index 0000000000..254025a34f --- /dev/null +++ b/python/test_customization_directories.py @@ -0,0 +1,71 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. + +from unittest.mock import AsyncMock + +import pytest + +from copilot import CopilotClient, RuntimeConnection +from copilot._jsonrpc import JsonRpcClient + + +@pytest.mark.asyncio +@pytest.mark.parametrize("roots", [None, [], ["relative-root", "other-root"]]) +async def test_customization_roots_preserved_on_create_and_resume(monkeypatch, roots): + client = CopilotClient(connection=RuntimeConnection.for_uri("localhost:1234")) + rpc = AsyncMock(spec=JsonRpcClient) + requests = {} + + async def request(method, params, **kwargs): + requests[method] = params + if method in ("session.create", "session.resume"): + response = {"sessionId": params["sessionId"]} + if callback := kwargs.get("on_response_inline"): + callback(response) + return response + return {} + + rpc.request.side_effect = request + monkeypatch.setattr(client, "_client", rpc) + skills = None if roots is None else [f"{root}-skills" for root in roots] + agents = None if roots is None else [f"{root}-agents" for root in roots] + instructions = None if roots is None else [f"{root}-instructions" for root in roots] + hooks = None if roots is None else [f"{root}-hooks" for root in roots] + try: + session = await client.create_session( + working_directory="request-cwd", + enable_config_discovery=False, + enable_file_hooks=False, + skill_directories=skills, + agent_directories=agents, + instruction_directories=instructions, + hook_directories=hooks, + ) + await client.resume_session( + session.session_id, + working_directory="resume-cwd", + enable_config_discovery=False, + enable_file_hooks=False, + skill_directories=skills, + agent_directories=agents, + instruction_directories=instructions, + hook_directories=hooks, + ) + for method in ("session.create", "session.resume"): + payload = requests[method] + for key, expected in ( + ("skillDirectories", skills), + ("agentDirectories", agents), + ("instructionDirectories", instructions), + ("hookDirectories", hooks), + ): + if expected is None: + assert key not in payload + else: + assert payload[key] == expected + assert payload["enableConfigDiscovery"] is False + assert payload["enableFileHooks"] is False + assert "additionalDirectories" not in payload + assert "customAgents" not in payload + assert "hooks" not in payload + finally: + await client.force_stop() diff --git a/python/test_image_generation.py b/python/test_image_generation.py new file mode 100644 index 0000000000..7323d869da --- /dev/null +++ b/python/test_image_generation.py @@ -0,0 +1,47 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. + +"""Image generation request serialization without starting a runtime.""" + +import json +from unittest.mock import AsyncMock, Mock + +import pytest + +from copilot import CopilotClient, ImageGenerationConfig, PermissionHandler, RuntimeConnection + + +@pytest.mark.asyncio +@pytest.mark.parametrize("option", [None, {}, {"enabled": True}, {"enabled": False}]) +async def test_image_generation_create_and_resume_wire(option: ImageGenerationConfig | None): + client = CopilotClient(connection=RuntimeConnection.for_stdio(path="unused-cli")) + client._state = "connected" + transport = Mock() + captured = {} + + async def request(method, params, **kwargs): + captured[method] = json.loads(json.dumps(params)) + response = {"sessionId": params["sessionId"]} + if callback := kwargs.get("on_response_inline"): + callback(response) + return response + + transport.request = AsyncMock(side_effect=request) + transport.stop = AsyncMock() + client._client = transport + try: + session = await client.create_session( + on_permission_request=PermissionHandler.approve_all, + image_generation=option, + ) + await client.resume_session( + session.session_id, + on_permission_request=PermissionHandler.approve_all, + image_generation=option, + ) + for method in ("session.create", "session.resume"): + if option is None: + assert "imageGeneration" not in captured[method], captured[method] + else: + assert captured[method]["imageGeneration"] == option + finally: + await client.force_stop() diff --git a/python/test_subagent_hooks.py b/python/test_subagent_hooks.py index 71db1c6c00..5db29ecf30 100644 --- a/python/test_subagent_hooks.py +++ b/python/test_subagent_hooks.py @@ -1,6 +1,7 @@ # Copyright (c) Microsoft Corporation. All rights reserved. from datetime import UTC, datetime +from typing import NotRequired, get_origin, get_type_hints import pytest @@ -11,9 +12,52 @@ SubagentStopHookInput, SubagentStopHookOutput, ) +from copilot import session as session_types from copilot.session import CopilotSession +@pytest.mark.asyncio +@pytest.mark.parametrize( + ("hook", "handler", "input_type"), + [ + ("preToolUse", "on_pre_tool_use", "PreToolUseHookInput"), + ("preMcpToolCall", "on_pre_mcp_tool_call", "PreMcpToolCallHookInput"), + ("postToolUse", "on_post_tool_use", "PostToolUseHookInput"), + ("postToolUseFailure", "on_post_tool_use_failure", "PostToolUseFailureHookInput"), + ("userPromptSubmitted", "on_user_prompt_submitted", "UserPromptSubmittedHookInput"), + ("userPromptTransformed", "on_user_prompt_transformed", "UserPromptTransformedHookInput"), + ("sessionStart", "on_session_start", "SessionStartHookInput"), + ("sessionEnd", "on_session_end", "SessionEndHookInput"), + ("errorOccurred", "on_error_occurred", "ErrorOccurredHookInput"), + ("agentStop", "on_agent_stop", "AgentStopHookInput"), + ("subagentStart", "on_subagent_start", "SubagentStartHookInput"), + ("subagentStop", "on_subagent_stop", "SubagentStopHookInput"), + ], +) +@pytest.mark.parametrize("enriched", [True, False]) +async def test_typed_hook_identity_retention_and_optional_declarations( + hook, handler, input_type, enriched +): + captured = [] + session = CopilotSession("callback-owner", None) + session._register_hooks({handler: lambda data, invocation: captured.append((data, invocation))}) + payload = {"sessionId": "reported-session", "timestamp": 1, "cwd": "work"} + identity = {"agentId": "child-actor", "agentType": "explore", "parentAgentId": "parent-actor"} + if enriched: + payload.update(identity) + await session._handle_hooks_invoke(hook, payload) + assert len(captured) == 1 + data, invocation = captured[0] + assert invocation == {"session_id": "callback-owner"} + assert data["sessionId"] == "reported-session" + declaration = getattr(session_types, input_type) + for key, value in identity.items(): + assert data.get(key) == (value if enriched else None) + assert key in declaration.__annotations__ + if not (hook == "subagentStop" and key == "agentType"): + assert get_origin(get_type_hints(declaration, include_extras=True)[key]) is NotRequired + + @pytest.mark.asyncio async def test_subagent_start_dispatches_normalized_input_and_context(): captured: list[tuple[SubagentStartHookInput, dict[str, str]]] = [] diff --git a/python/test_subagent_request_observer.py b/python/test_subagent_request_observer.py new file mode 100644 index 0000000000..26d99ad9e4 --- /dev/null +++ b/python/test_subagent_request_observer.py @@ -0,0 +1,38 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. + +import pytest + +from e2e.inference_request import user_message_text + + +@pytest.mark.parametrize( + ("body", "expected"), + [ + ({"messages": [{"role": "user", "content": "chat"}]}, ["chat"]), + ({"input": "responses"}, ["responses"]), + ({"input": [{"role": "user", "content": "responses"}]}, ["responses"]), + ( + { + "input": [ + {"role": "assistant", "content": "ignored"}, + {"type": "function_call_output", "output": "ignored"}, + { + "role": "user", + "content": [ + {"type": "text", "text": "chat"}, + {"type": "input_text", "text": "responses"}, + {"type": "input_image", "text": "ignored"}, + {"type": "input_text", "text": 42}, + None, + "ignored", + ], + }, + ] + }, + ["chat", "responses"], + ), + ({}, []), + ], +) +def test_user_message_text(body, expected): + assert user_message_text(body) == expected diff --git a/rust/Cargo.lock b/rust/Cargo.lock index 74d6d98972..a5ef1e65ca 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -1702,6 +1702,7 @@ version = "1.0.151" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" dependencies = [ + "indexmap", "itoa", "memchr", "serde", diff --git a/rust/Cargo.toml b/rust/Cargo.toml index b82e322b02..741735b517 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -55,7 +55,7 @@ async-trait = "0.1" indexmap = { version = "2", features = ["serde"] } schemars = { version = "1", optional = true } serde = { version = "1", features = ["derive"] } -serde_json = "1" +serde_json = { version = "1", features = ["preserve_order"] } tokio = { version = "1", features = ["io-util", "sync", "rt", "process", "net", "time", "macros"] } tokio-stream = { version = "0.1", features = ["sync"] } tokio-util = { version = "0.7", default-features = false } @@ -81,8 +81,10 @@ tokio-tungstenite = { version = "0.28.0", default-features = false, features = [ zip = { version = "7.2.0", default-features = false, features = ["deflate"], optional = true } windows-sys = { version = "0.61", default-features = false, features = [ "Win32_Foundation", - "Win32_System_Diagnostics_ToolHelp", + "Win32_System_Diagnostics_Debug", + "Win32_System_Diagnostics_ProcessSnapshotting", "Win32_System_JobObjects", + "Win32_System_Kernel", "Win32_System_Threading", ] } diff --git a/rust/README.md b/rust/README.md index 87484defec..48915f2dc6 100644 --- a/rust/README.md +++ b/rust/README.md @@ -124,6 +124,12 @@ Transport-specific phases are optional. For example, `port_wait_ms` is present only for TCP and `process_spawn_ms` is absent for external and in-process transports. +On Windows, SDK-spawned CLI processes start suspended and are assigned to a +private kill-on-close Job Object before their initial thread resumes. Thread +lookup captures only that child's process snapshot, rather than enumerating +machine-wide threads. The Job owns the CLI and its descendants until client +shutdown or final-client drop, including abrupt termination of the SDK host. + **`ClientOptions`:** | Field | Type | Description | @@ -722,6 +728,12 @@ Use `with_ask_user_variant(AskUserVariant::Elicitation)` together with tool. The default remains `AskUserVariant::Legacy`. Re-supply the option and handler through `ResumeSessionConfig` on a cold resume. +Use `with_interactive_user(true)` to declare that a person types the session's +messages, as in an editor chat panel. This enables the runtime's user-message +analysis telemetry for user-authored messages; automated messages stay +excluded. The default is off, and cloud sessions ignore it. Re-supply it through +`ResumeSessionConfig` on a cold resume. + For rotating per-session GitHub credentials, install a `GitHubTokenProvider` instead of setting `github_token`: @@ -846,6 +858,29 @@ let session = client **Hook events:** `PreToolUse`, `PostToolUse`, `PostToolUseFailure`, `UserPromptSubmitted`, `UserPromptTransformed`, `SessionStart`, `SessionEnd`, `ErrorOccurred`, `AgentStop`, `SubagentStart`, and `SubagentStop`. Each carries typed input/output structs. `PostToolUse` only fires on success; override `on_post_tool_use_failure` to observe failed tool calls. `on_subagent_start` can inject `additional_context` into the child agent's initial prompt; `on_subagent_stop` can return a `decision` of `"block"` with a `reason` to continue the child or a `modified_response` to replace its final answer. Return `HookOutput::None` for events you don't handle. +To inspect runtime-provided actor metadata, override `SessionHooks::on_hook_with_actor`. +Its typed `HookActor` exposes optional `agent_id()`, `agent_type()`, and `parent_agent_id()` getters. +Root hooks and older runtimes can omit these fields; existing `SubagentStopInput` actor fields remain unchanged. +The default implementation delegates to `on_hook`, which retains the existing per-hook dispatch. +Existing implementations, input struct literals, and exact destructuring patterns therefore need no migration. +The enriched wire fields are opt-in metadata, not new fields on the legacy Rust input structs. + +```rust,no_run +# use async_trait::async_trait; +# use github_copilot_sdk::hooks::{HookActor, HookEvent, HookOutput, SessionHooks}; +struct ActorAwareHooks; + +#[async_trait] +impl SessionHooks for ActorAwareHooks { + async fn on_hook_with_actor(&self, event: HookEvent, actor: HookActor) -> HookOutput { + if let Some(id) = actor.agent_id() { + eprintln!("Hook actor: {id}"); + } + self.on_hook(event).await + } +} +``` + ### System Message Transforms Transforms customize system message sections during session creation. The SDK injects `action: "transform"` entries for each section ID your transform handles. @@ -1061,7 +1096,13 @@ let config = SessionConfig::default() .with_elicitation_handler(Arc::new(MyElicitation)); ``` -The handler receives a message, optional JSON Schema for form fields, and an optional mode. Known modes include `Form` and `Url`, but the mode may be absent or an unknown future value. +The handler receives a message, optional JSON Schema for form fields, and an optional mode. The schema retains the wire object's field order and metadata, including keys not modeled by the generated event type. Known modes include `Form` and `Url`, but the mode may be absent or an unknown future value. + +Wire-schema preservation is a Rust SDK guarantee, not a guarantee shared by +every SDK. For applications targeting multiple SDK languages, constrain form +schemas to the [portable elicitation schema contract](../README.md#portable-elicitation-schemas): +the modeled `type`, `properties`, and optional `required` fields, without +depending on additional top-level metadata or property insertion order. ### User Input Requests @@ -1206,6 +1247,8 @@ The CLI emits `session.compaction_start` / `session.compaction_complete` events `enable_session_store` on `SessionConfig` enables the cross-session store for search and retrieval across sessions. When unset in the default client mode, the runtime default applies (enabled). In `Empty` mode, defaults to disabled. +`enable_github_cache_store` on `SessionConfig` and `ResumeSessionConfig` (builder `with_enable_github_cache_store`) requests the read-only GitHub cache store (`github-cache-store` capability) on create or resume. The runtime registers the `github_sql` tool only when its `GITHUB_CACHE_STORE` feature flag is also on. Defaults to disabled in every mode. + ### Memory Configure the runtime memory feature for a session: diff --git a/rust/bazel/crates/BUILD.serde_json-1.0.151.bazel b/rust/bazel/crates/BUILD.serde_json-1.0.151.bazel index e02e3ef934..1a110fe834 100644 --- a/rust/bazel/crates/BUILD.serde_json-1.0.151.bazel +++ b/rust/bazel/crates/BUILD.serde_json-1.0.151.bazel @@ -41,6 +41,8 @@ rust_library( crate_features = [ "alloc", "default", + "indexmap", + "preserve_order", "std", ], crate_root = "src/lib.rs", @@ -70,6 +72,7 @@ rust_library( version = "1.0.151", deps = [ ":build_script_build", + "@sdk_vendor__indexmap-2.14.2//:indexmap", "@sdk_vendor__itoa-1.0.18//:itoa", "@sdk_vendor__memchr-2.8.3//:memchr", "@sdk_vendor__serde_core-1.0.229//:serde_core", @@ -102,6 +105,8 @@ cargo_build_script( crate_features = [ "alloc", "default", + "indexmap", + "preserve_order", "std", ], crate_name = "build_script_build", diff --git a/rust/bazel/crates/BUILD.windows-sys-0.61.2.bazel b/rust/bazel/crates/BUILD.windows-sys-0.61.2.bazel index f964882760..87b0fb6806 100644 --- a/rust/bazel/crates/BUILD.windows-sys-0.61.2.bazel +++ b/rust/bazel/crates/BUILD.windows-sys-0.61.2.bazel @@ -54,9 +54,11 @@ rust_library( "Win32_Storage_FileSystem", "Win32_System", "Win32_System_Diagnostics", - "Win32_System_Diagnostics_ToolHelp", + "Win32_System_Diagnostics_Debug", + "Win32_System_Diagnostics_ProcessSnapshotting", "Win32_System_IO", "Win32_System_JobObjects", + "Win32_System_Kernel", "Win32_System_LibraryLoader", "Win32_System_Memory", "Win32_System_Pipes", diff --git a/rust/src/ahp_host.rs b/rust/src/ahp_host.rs index 7ae2f1ca4e..eb87b09ced 100644 --- a/rust/src/ahp_host.rs +++ b/rust/src/ahp_host.rs @@ -925,6 +925,7 @@ macro_rules! host_settings { "featureFlags" => feature_flags, "enableManagedSettings" => enable_managed_settings, "enableSessionStore" => enable_session_store, + "enableGitHubCacheStore" => enable_github_cache_store, "availableTools" => available_tools, "excludedTools" => excluded_tools, "allowedModels" => allowed_models, @@ -936,6 +937,8 @@ macro_rules! host_settings { "memory" => memory, "pluginDirectories" => plugin_directories, "skillDirectories" => skill_directories, + "agentDirectories" => agent_directories, + "hookDirectories" => hook_directories, "instructionDirectories" => instruction_directories, "enableMcpApps" => enable_mcp_apps, "model" => model, diff --git a/rust/src/ahp_host/factory_tests.rs b/rust/src/ahp_host/factory_tests.rs index bb328c7acd..a008da5097 100644 --- a/rust/src/ahp_host/factory_tests.rs +++ b/rust/src/ahp_host/factory_tests.rs @@ -624,17 +624,20 @@ fn host_settings_round_trip_without_callbacks_or_losing_constraints() { "streaming": true, "gitHubToken": "test", "mcpOAuthTokenStorage": "in-memory", "enableConfigDiscovery": false, "featureFlags": {"flag": true}, "enableManagedSettings": false, "enableSessionStore": true, + "enableGitHubCacheStore": true, "availableTools": ["read_file"], "excludedTools": ["shell"], "allowedModels": ["model"], "systemMessage": {"mode":"append","content":"app"}, "skipCustomInstructions": true, "customAgentsLocalOnly": true, "coauthorEnabled": false, "manageScheduleEnabled": false, "memory": {"enabled":false}, "pluginDirectories": ["/plugins"], "skillDirectories": ["/skills"], "instructionDirectories": ["/instructions"], + "agentDirectories": ["relative-agents"], "hookDirectories": [], "enableMcpApps": true, "model":"model", "reasoningEffort":"high", "contextTier":"long_context" }); let config = config_from_host(&serde_json::from_value(value.clone()).unwrap()).unwrap(); assert!(config.permission_handler.is_none()); assert_eq!(config.config_directory, Some(PathBuf::from("/config"))); assert_eq!(config.enable_experimental_mode, Some(true)); + assert_eq!(config.enable_github_cache_store, Some(true)); assert_eq!( config.infinite_sessions.as_ref().unwrap().enabled, Some(false) @@ -647,6 +650,7 @@ fn host_settings_round_trip_without_callbacks_or_losing_constraints() { assert!(resume.permission_handler.is_none()); assert_eq!(resume.config_directory, Some(PathBuf::from("/config"))); assert_eq!(resume.enable_experimental_mode, Some(true)); + assert_eq!(resume.enable_github_cache_store, Some(true)); assert_eq!( resume.infinite_sessions.as_ref().unwrap().enabled, Some(false) diff --git a/rust/src/copilot_request_handler.rs b/rust/src/copilot_request_handler.rs index 381acac0d2..c0dec20a97 100644 --- a/rust/src/copilot_request_handler.rs +++ b/rust/src/copilot_request_handler.rs @@ -718,7 +718,13 @@ impl CopilotRequestExchange { let mut buf = Vec::new(); let mut rx = self.body_rx.lock().await; while let Some(frame) = rx.recv().await { - buf.extend_from_slice(&frame); + // The first frame becomes the buffer, so a body sent as one chunk (the runtime sends + // every HTTP request body so) reaches the handler without a copy. + if buf.is_empty() { + buf = frame; + } else { + buf.extend_from_slice(&frame); + } } buf } @@ -1114,7 +1120,8 @@ impl CopilotRequestDispatcher { async fn handle_start(self: &Arc, request: JsonRpcRequest) { let id = request.id; - let Some(params) = parse_params::(&request) else { + let Some(params) = parse_params::(request.params) + else { self.send_error(id, "invalid llmInference.httpRequestStart params") .await; return; @@ -1140,7 +1147,8 @@ impl CopilotRequestDispatcher { async fn handle_chunk(&self, request: JsonRpcRequest) { let id = request.id; - let Some(params) = parse_params::(&request) else { + let Some(params) = parse_params::(request.params) + else { self.send_error(id, "invalid llmInference.httpRequestChunk params") .await; return; @@ -1149,7 +1157,7 @@ impl CopilotRequestDispatcher { // May arrive before the matching start frame; get-or-create so the body // is buffered, never lost. let exchange = self.get_or_create_exchange(params.request_id.to_string()); - apply_chunk(&exchange, ¶ms); + apply_chunk(&exchange, params); self.ack(id).await; } @@ -1193,7 +1201,7 @@ impl CopilotRequestDispatcher { /// Apply one body chunk to a pending request: route data into the body stream, /// or terminate it on `end` / `cancel`. -fn apply_chunk(exchange: &CopilotRequestExchange, params: &LlmInferenceHttpRequestChunkRequest) { +fn apply_chunk(exchange: &CopilotRequestExchange, params: LlmInferenceHttpRequestChunkRequest) { if params.cancel == Some(true) { exchange.push_cancel(); return; @@ -1209,7 +1217,7 @@ fn apply_chunk(exchange: &CopilotRequestExchange, params: &LlmInferenceHttpReque } } } else { - params.data.clone().into_bytes() + params.data.into_bytes() }; exchange.push_chunk(decoded); } @@ -1219,11 +1227,10 @@ fn apply_chunk(exchange: &CopilotRequestExchange, params: &LlmInferenceHttpReque } } -fn parse_params(request: &JsonRpcRequest) -> Option { - request - .params - .as_ref() - .and_then(|p| serde_json::from_value(p.clone()).ok()) +/// Deserialize a request's params by value, so their strings (the request body among them) move +/// into `T`: deserializing from a borrow deep-clones the whole `Value` first. +fn parse_params(params: Option) -> Option { + params.and_then(|p| serde_json::from_value(p).ok()) } /// Convert a wire header map into an [`http::HeaderMap`], skipping any entry the diff --git a/rust/src/ffi.rs b/rust/src/ffi.rs index df0269b0b8..3d7bbf4401 100644 --- a/rust/src/ffi.rs +++ b/rust/src/ffi.rs @@ -245,6 +245,7 @@ impl AsyncWrite for FfiWriter { pub(crate) struct FfiHost { library_path: PathBuf, cli_entrypoint: Option, + integration_id: Option, environment: Vec<(String, String)>, args: Vec, host_start: HostStartFn, @@ -263,6 +264,7 @@ impl FfiHost { pub(crate) fn create( runtime_entrypoint: &Path, cli_entrypoint: Option<&Path>, + integration_id: Option<&str>, environment: Vec<(String, String)>, args: Vec, ) -> Result { @@ -307,6 +309,7 @@ impl FfiHost { Ok(Self { library_path, cli_entrypoint, + integration_id: integration_id.map(str::to_owned), environment, args, host_start, @@ -330,7 +333,11 @@ impl FfiHost { } fn start_blocking(self) -> Result<(FfiReader, FfiWriter, Arc), Error> { - let argv = build_argv_json(self.cli_entrypoint.as_deref(), &self.args); + let argv = build_argv_json( + self.cli_entrypoint.as_deref(), + self.integration_id.as_deref(), + &self.args, + ); let env = build_env_json(&self.environment); let (env_ptr, env_len) = match &env { @@ -561,7 +568,11 @@ fn path_for_child_process(path: PathBuf) -> PathBuf { path } -fn build_argv_json(entrypoint: Option<&Path>, extra_args: &[String]) -> Vec { +fn build_argv_json( + entrypoint: Option<&Path>, + integration_id: Option<&str>, + extra_args: &[String], +) -> Vec { let mut argv = Vec::new(); if let Some(entrypoint) = entrypoint { let entrypoint_str = entrypoint.to_string_lossy().into_owned(); @@ -578,6 +589,9 @@ fn build_argv_json(entrypoint: Option<&Path>, extra_args: &[String]) -> Vec "--no-auto-update".to_string(), ]); } + if let Some(integration_id) = integration_id { + argv.push(format!("--cli-login-sync-integration-id={integration_id}")); + } argv.extend_from_slice(extra_args); serde_json::to_vec(&argv).expect("argv serializes") } diff --git a/rust/src/ffi/tests.rs b/rust/src/ffi/tests.rs index 5662dc4e5b..5fbd0929fc 100644 --- a/rust/src/ffi/tests.rs +++ b/rust/src/ffi/tests.rs @@ -36,6 +36,7 @@ unsafe extern "C" fn test_connection_close(_connection_id: u32) -> bool { #[test] fn argv_without_entrypoint_contains_only_client_options() { let argv: Vec = serde_json::from_slice(&build_argv_json( + None, None, &["--log-level".into(), "debug".into()], )) @@ -46,12 +47,22 @@ fn argv_without_entrypoint_contains_only_client_options() { #[test] fn explicit_javascript_entrypoint_uses_node() { - let argv: Vec = - serde_json::from_slice(&build_argv_json(Some(Path::new("index.js")), &[])).unwrap(); + let argv: Vec = serde_json::from_slice(&build_argv_json( + Some(Path::new("index.js")), + Some("microsoft/vscode"), + &[], + )) + .unwrap(); assert_eq!( argv, - ["node", "index.js", "--embedded-host", "--no-auto-update"] + [ + "node", + "index.js", + "--embedded-host", + "--no-auto-update", + "--cli-login-sync-integration-id=microsoft/vscode", + ] ); } diff --git a/rust/src/generated/api_types.rs b/rust/src/generated/api_types.rs index 31cc1b82d7..82ace5597b 100644 --- a/rust/src/generated/api_types.rs +++ b/rust/src/generated/api_types.rs @@ -11,17 +11,182 @@ use serde::{Deserialize, Serialize}; pub use super::session_events::{ AbortReason, AgentModelPolicy, AutoTier, ContextTier, IndexedSearchState, - ManagedSettingsResolvedSource, McpOauthHttpResponse, McpOauthWWWAuthenticateParams, - McpServerMetadata, McpServerSource, McpServerStatus, ModelChangeSource, ModelProviderKind, - ModelProviderRef, OmittedBinaryOmittedReason, PermissionDecisionSource, PermissionMode, - PermissionPromptRequest, PermissionRule, ProviderQuotaState, ReasoningSummary, - RemediationAction, SessionLimitsConfig, SessionMode, ShutdownType, SkillSource, TaskBlocker, - TaskCompletionOutcome, UsageGetMetricsResult, UserToolSessionApproval, Verbosity, + ManagedPermissionsContext, ManagedSettingsResolvedSource, McpOauthHttpResponse, + McpOauthWWWAuthenticateParams, McpServerMetadata, McpServerSource, McpServerStatus, + ModelChangeSource, ModelProviderKind, ModelProviderRef, OmittedBinaryOmittedReason, + PermissionDecisionSource, PermissionMode, PermissionPromptRequest, PermissionRule, + ProviderQuotaState, ReasoningSummary, RemediationAction, SessionLimitsConfig, SessionMode, + ShutdownType, SkillSource, TaskBlocker, TaskCompletionOutcome, UsageGetMetricsResult, + UserToolSessionApproval, Verbosity, }; use crate::types::{RequestId, SessionEvent, SessionId}; /// JSON-RPC method name constants. pub mod rpc_methods { + /// `sandbox.proxyCa.checkInstallationSupport` + pub const SANDBOX_PROXYCA_CHECKINSTALLATIONSUPPORT: &str = + "sandbox.proxyCa.checkInstallationSupport"; + /// `sandbox.proxyCa.checkRelevance` + pub const SANDBOX_PROXYCA_CHECKRELEVANCE: &str = "sandbox.proxyCa.checkRelevance"; + /// `sandbox.proxyCa.getStatus` + pub const SANDBOX_PROXYCA_GETSTATUS: &str = "sandbox.proxyCa.getStatus"; + /// `sandbox.proxyCa.create` + pub const SANDBOX_PROXYCA_CREATE: &str = "sandbox.proxyCa.create"; + /// `sandbox.proxyCa.rotate` + pub const SANDBOX_PROXYCA_ROTATE: &str = "sandbox.proxyCa.rotate"; + /// `sandbox.proxyCa.trust` + pub const SANDBOX_PROXYCA_TRUST: &str = "sandbox.proxyCa.trust"; + /// `sandbox.proxyCa.remove` + pub const SANDBOX_PROXYCA_REMOVE: &str = "sandbox.proxyCa.remove"; + /// `sandbox.proxySecret.getLocation` + pub const SANDBOX_PROXYSECRET_GETLOCATION: &str = "sandbox.proxySecret.getLocation"; + /// `sandbox.proxySecret.get` + pub const SANDBOX_PROXYSECRET_GET: &str = "sandbox.proxySecret.get"; + /// `sandbox.proxySecret.save` + pub const SANDBOX_PROXYSECRET_SAVE: &str = "sandbox.proxySecret.save"; + /// `sandbox.proxySecret.delete` + pub const SANDBOX_PROXYSECRET_DELETE: &str = "sandbox.proxySecret.delete"; + /// `sandbox.settings.applyDefaults` + pub const SANDBOX_SETTINGS_APPLYDEFAULTS: &str = "sandbox.settings.applyDefaults"; + /// `sandbox.resolveFlooredConfig` + pub const SANDBOX_RESOLVEFLOOREDCONFIG: &str = "sandbox.resolveFlooredConfig"; + /// `sandbox.preparePickerPolicy` + pub const SANDBOX_PREPAREPICKERPOLICY: &str = "sandbox.preparePickerPolicy"; + /// `sandbox.filterPickerCandidates` + pub const SANDBOX_FILTERPICKERCANDIDATES: &str = "sandbox.filterPickerCandidates"; + /// `sandbox.resolveForOptional` + pub const SANDBOX_RESOLVEFOROPTIONAL: &str = "sandbox.resolveForOptional"; + /// `sandbox.checkPlatformSupport` + pub const SANDBOX_CHECKPLATFORMSUPPORT: &str = "sandbox.checkPlatformSupport"; + /// `sandbox.checkWindowsHostLoopbackSupport` + pub const SANDBOX_CHECKWINDOWSHOSTLOOPBACKSUPPORT: &str = + "sandbox.checkWindowsHostLoopbackSupport"; + /// `sandbox.getUnsupportedMessage` + pub const SANDBOX_GETUNSUPPORTEDMESSAGE: &str = "sandbox.getUnsupportedMessage"; + /// `sandbox.getUnsupportedSettingDisabledNotice` + pub const SANDBOX_GETUNSUPPORTEDSETTINGDISABLEDNOTICE: &str = + "sandbox.getUnsupportedSettingDisabledNotice"; + /// `sandbox.getUnsupportedStartupNotice` + pub const SANDBOX_GETUNSUPPORTEDSTARTUPNOTICE: &str = "sandbox.getUnsupportedStartupNotice"; + /// `sandbox.checkEnforcementCommand` + pub const SANDBOX_CHECKENFORCEMENTCOMMAND: &str = "sandbox.checkEnforcementCommand"; + /// `sandbox.getDocsUrl` + pub const SANDBOX_GETDOCSURL: &str = "sandbox.getDocsUrl"; + /// `sandbox.getHostSupport` + pub const SANDBOX_GETHOSTSUPPORT: &str = "sandbox.getHostSupport"; + /// `persistence.writeRawJsonLocked` + pub const PERSISTENCE_WRITERAWJSONLOCKED: &str = "persistence.writeRawJsonLocked"; + /// `user.settings.setHookDisabled` + pub const USER_SETTINGS_SETHOOKDISABLED: &str = "user.settings.setHookDisabled"; + /// `user.settings.loadForConfigDir` + pub const USER_SETTINGS_LOADFORCONFIGDIR: &str = "user.settings.loadForConfigDir"; + /// `user.settings.loadRepo` + pub const USER_SETTINGS_LOADREPO: &str = "user.settings.loadRepo"; + /// `user.settings.loadAndMergeRepo` + pub const USER_SETTINGS_LOADANDMERGEREPO: &str = "user.settings.loadAndMergeRepo"; + /// `user.settings.getPath` + pub const USER_SETTINGS_GETPATH: &str = "user.settings.getPath"; + /// `user.settings.loadWithWarning` + pub const USER_SETTINGS_LOADWITHWARNING: &str = "user.settings.loadWithWarning"; + /// `user.settings.mergeManagedAndUser` + pub const USER_SETTINGS_MERGEMANAGEDANDUSER: &str = "user.settings.mergeManagedAndUser"; + /// `user.settings.mergeRepoValue` + pub const USER_SETTINGS_MERGEREPOVALUE: &str = "user.settings.mergeRepoValue"; + /// `user.settings.migrateStaffUpdateChannelOnce` + pub const USER_SETTINGS_MIGRATESTAFFUPDATECHANNELONCE: &str = + "user.settings.migrateStaffUpdateChannelOnce"; + /// `user.settings.resetStaffModelSelectionOnce` + pub const USER_SETTINGS_RESETSTAFFMODELSELECTIONONCE: &str = + "user.settings.resetStaffModelSelectionOnce"; + /// `user.settings.getUnknownKeysWarning` + pub const USER_SETTINGS_GETUNKNOWNKEYSWARNING: &str = "user.settings.getUnknownKeysWarning"; + /// `user.settings.buildRuntime` + pub const USER_SETTINGS_BUILDRUNTIME: &str = "user.settings.buildRuntime"; + /// `user.settings.migrateStaffLogLevelOnce` + pub const USER_SETTINGS_MIGRATESTAFFLOGLEVELONCE: &str = + "user.settings.migrateStaffLogLevelOnce"; + /// `user.settings.migrateObsoleteOnDisk` + pub const USER_SETTINGS_MIGRATEOBSOLETEONDISK: &str = "user.settings.migrateObsoleteOnDisk"; + /// `user.settings.get` + pub const USER_SETTINGS_GET: &str = "user.settings.get"; + /// `user.settings.getPaths` + pub const USER_SETTINGS_GETPATHS: &str = "user.settings.getPaths"; + /// `user.settings.set` + pub const USER_SETTINGS_SET: &str = "user.settings.set"; + /// `user.settings.listGovernanceKeys` + pub const USER_SETTINGS_LISTGOVERNANCEKEYS: &str = "user.settings.listGovernanceKeys"; + /// `user.settings.edit` + pub const USER_SETTINGS_EDIT: &str = "user.settings.edit"; + /// `user.settings.normalize` + pub const USER_SETTINGS_NORMALIZE: &str = "user.settings.normalize"; + /// `user.settings.diagnoseProblems` + pub const USER_SETTINGS_DIAGNOSEPROBLEMS: &str = "user.settings.diagnoseProblems"; + /// `user.settings.applyTopKey` + pub const USER_SETTINGS_APPLYTOPKEY: &str = "user.settings.applyTopKey"; + /// `user.settings.writeAll` + pub const USER_SETTINGS_WRITEALL: &str = "user.settings.writeAll"; + /// `user.settings.update` + pub const USER_SETTINGS_UPDATE: &str = "user.settings.update"; + /// `user.settings.loadForEdit` + pub const USER_SETTINGS_LOADFOREDIT: &str = "user.settings.loadForEdit"; + /// `user.settings.validate` + pub const USER_SETTINGS_VALIDATE: &str = "user.settings.validate"; + /// `repo.settings.edit` + pub const REPO_SETTINGS_EDIT: &str = "repo.settings.edit"; + /// `repo.settings.readRaw` + pub const REPO_SETTINGS_READRAW: &str = "repo.settings.readRaw"; + /// `repo.settings.writeKey` + pub const REPO_SETTINGS_WRITEKEY: &str = "repo.settings.writeKey"; + /// `repo.settings.writeAll` + pub const REPO_SETTINGS_WRITEALL: &str = "repo.settings.writeAll"; + /// `featureFlags.resolveStartupGates` + pub const FEATUREFLAGS_RESOLVESTARTUPGATES: &str = "featureFlags.resolveStartupGates"; + /// `featureFlags.listMetadata` + pub const FEATUREFLAGS_LISTMETADATA: &str = "featureFlags.listMetadata"; + /// `featureFlags.resolve` + pub const FEATUREFLAGS_RESOLVE: &str = "featureFlags.resolve"; + /// `permissions.locations.cleanupOrphaned` + pub const PERMISSIONS_LOCATIONS_CLEANUPORPHANED: &str = "permissions.locations.cleanupOrphaned"; + /// `permissions.folderTrust.checkTrust` + pub const PERMISSIONS_FOLDERTRUST_CHECKTRUST: &str = "permissions.folderTrust.checkTrust"; + /// `permissions.evaluateManagedPolicy` + pub const PERMISSIONS_EVALUATEMANAGEDPOLICY: &str = "permissions.evaluateManagedPolicy"; + /// `managedSettings.readDevice` + pub const MANAGEDSETTINGS_READDEVICE: &str = "managedSettings.readDevice"; + /// `managedSettings.retainPolicy` + pub const MANAGEDSETTINGS_RETAINPOLICY: &str = "managedSettings.retainPolicy"; + /// `managedSettings.applyControls` + pub const MANAGEDSETTINGS_APPLYCONTROLS: &str = "managedSettings.applyControls"; + /// `managedSettings.parseClientValue` + pub const MANAGEDSETTINGS_PARSECLIENTVALUE: &str = "managedSettings.parseClientValue"; + /// `managedSettings.resolveEffectiveValue` + pub const MANAGEDSETTINGS_RESOLVEEFFECTIVEVALUE: &str = "managedSettings.resolveEffectiveValue"; + /// `managedSettings.resolveBypassDisableTransition` + pub const MANAGEDSETTINGS_RESOLVEBYPASSDISABLETRANSITION: &str = + "managedSettings.resolveBypassDisableTransition"; + /// `managedSettings.ingest` + pub const MANAGEDSETTINGS_INGEST: &str = "managedSettings.ingest"; + /// `managedSettings.resolveStartupPolicy` + pub const MANAGEDSETTINGS_RESOLVESTARTUPPOLICY: &str = "managedSettings.resolveStartupPolicy"; + /// `managedSettings.loadRemoteControl` + pub const MANAGEDSETTINGS_LOADREMOTECONTROL: &str = "managedSettings.loadRemoteControl"; + /// `managedSettings.resolveDeviceForceLoginOrgsGate` + pub const MANAGEDSETTINGS_RESOLVEDEVICEFORCELOGINORGSGATE: &str = + "managedSettings.resolveDeviceForceLoginOrgsGate"; + /// `managedSettings.read` + pub const MANAGEDSETTINGS_READ: &str = "managedSettings.read"; + /// `managedSettings.clearCache` + pub const MANAGEDSETTINGS_CLEARCACHE: &str = "managedSettings.clearCache"; + /// `managedSettings.resolve` + pub const MANAGEDSETTINGS_RESOLVE: &str = "managedSettings.resolve"; + /// `managedSettings.permissions.evaluate` + pub const MANAGEDSETTINGS_PERMISSIONS_EVALUATE: &str = "managedSettings.permissions.evaluate"; + /// `managedSettings.schema` + pub const MANAGEDSETTINGS_SCHEMA: &str = "managedSettings.schema"; + /// `managedSettings.validate` + pub const MANAGEDSETTINGS_VALIDATE: &str = "managedSettings.validate"; + /// `managedSettings.compose` + pub const MANAGEDSETTINGS_COMPOSE: &str = "managedSettings.compose"; /// `environments.list` pub const ENVIRONMENTS_LIST: &str = "environments.list"; /// `environments.get` @@ -56,18 +221,8 @@ pub mod rpc_methods { pub const MODELS_LIST: &str = "models.list"; /// `models.getBuiltInCatalog` pub const MODELS_GETBUILTINCATALOG: &str = "models.getBuiltInCatalog"; - /// `sandbox.getHostSupport` - pub const SANDBOX_GETHOSTSUPPORT: &str = "sandbox.getHostSupport"; - /// `sandbox.proxyCa.getStatus` - pub const SANDBOX_PROXYCA_GETSTATUS: &str = "sandbox.proxyCa.getStatus"; - /// `sandbox.proxyCa.create` - pub const SANDBOX_PROXYCA_CREATE: &str = "sandbox.proxyCa.create"; - /// `sandbox.proxyCa.rotate` - pub const SANDBOX_PROXYCA_ROTATE: &str = "sandbox.proxyCa.rotate"; - /// `sandbox.proxyCa.trust` - pub const SANDBOX_PROXYCA_TRUST: &str = "sandbox.proxyCa.trust"; - /// `sandbox.proxyCa.remove` - pub const SANDBOX_PROXYCA_REMOVE: &str = "sandbox.proxyCa.remove"; + /// `contentExclusion.validatePolicies` + pub const CONTENTEXCLUSION_VALIDATEPOLICIES: &str = "contentExclusion.validatePolicies"; /// `tools.list` pub const TOOLS_LIST: &str = "tools.list"; /// `account.getQuota` @@ -80,6 +235,14 @@ pub mod rpc_methods { pub const ACCOUNT_LOGIN: &str = "account.login"; /// `account.logout` pub const ACCOUNT_LOGOUT: &str = "account.logout"; + /// `account.describeAuthInfo` + pub const ACCOUNT_DESCRIBEAUTHINFO: &str = "account.describeAuthInfo"; + /// `account.getGistSupport` + pub const ACCOUNT_GETGISTSUPPORT: &str = "account.getGistSupport"; + /// `account.getWebUrl` + pub const ACCOUNT_GETWEBURL: &str = "account.getWebUrl"; + /// `account.getTokenEnvVar` + pub const ACCOUNT_GETTOKENENVVAR: &str = "account.getTokenEnvVar"; /// `secrets.addFilterValues` pub const SECRETS_ADDFILTERVALUES: &str = "secrets.addFilterValues"; /// `mcp.config.list` @@ -96,8 +259,18 @@ pub mod rpc_methods { pub const MCP_CONFIG_DISABLE: &str = "mcp.config.disable"; /// `mcp.config.reload` pub const MCP_CONFIG_RELOAD: &str = "mcp.config.reload"; + /// `mcp.registry.allocateRequestId` + pub const MCP_REGISTRY_ALLOCATEREQUESTID: &str = "mcp.registry.allocateRequestId"; + /// `mcp.registry.search` + pub const MCP_REGISTRY_SEARCH: &str = "mcp.registry.search"; + /// `mcp.registry.cancel` + pub const MCP_REGISTRY_CANCEL: &str = "mcp.registry.cancel"; /// `mcp.discover` pub const MCP_DISCOVER: &str = "mcp.discover"; + /// `mcp.listBuiltinServerNames` + pub const MCP_LISTBUILTINSERVERNAMES: &str = "mcp.listBuiltinServerNames"; + /// `mcp.shouldExcludeGitHubTools` + pub const MCP_SHOULDEXCLUDEGITHUBTOOLS: &str = "mcp.shouldExcludeGitHubTools"; /// `mcp.planInstall` pub const MCP_PLANINSTALL: &str = "mcp.planInstall"; /// `mcp.prepareInstall` @@ -122,8 +295,20 @@ pub mod rpc_methods { pub const EXTENSIONS_ENABLE: &str = "extensions.enable"; /// `extensions.disable` pub const EXTENSIONS_DISABLE: &str = "extensions.disable"; + /// `instructions.getInitPrompt` + pub const INSTRUCTIONS_GETINITPROMPT: &str = "instructions.getInitPrompt"; + /// `instructions.discover` + pub const INSTRUCTIONS_DISCOVER: &str = "instructions.discover"; + /// `instructions.discoverRepoSources` + pub const INSTRUCTIONS_DISCOVERREPOSOURCES: &str = "instructions.discoverRepoSources"; + /// `instructions.getDiscoveryPaths` + pub const INSTRUCTIONS_GETDISCOVERYPATHS: &str = "instructions.getDiscoveryPaths"; + /// `instructions.clearCache` + pub const INSTRUCTIONS_CLEARCACHE: &str = "instructions.clearCache"; /// `skills.planInstall` pub const SKILLS_PLANINSTALL: &str = "skills.planInstall"; + /// `skills.planInstallFromReference` + pub const SKILLS_PLANINSTALLFROMREFERENCE: &str = "skills.planInstallFromReference"; /// `skills.applyInstall` pub const SKILLS_APPLYINSTALL: &str = "skills.applyInstall"; /// `skills.installations.list` @@ -195,10 +380,6 @@ pub mod rpc_methods { /// `agents.customAgentInitialModelDecision` pub const AGENTS_CUSTOMAGENTINITIALMODELDECISION: &str = "agents.customAgentInitialModelDecision"; - /// `instructions.discover` - pub const INSTRUCTIONS_DISCOVER: &str = "instructions.discover"; - /// `instructions.getDiscoveryPaths` - pub const INSTRUCTIONS_GETDISCOVERYPATHS: &str = "instructions.getDiscoveryPaths"; /// `globalState.load` pub const GLOBALSTATE_LOAD: &str = "globalState.load"; /// `globalState.loadForConfigDir` @@ -207,10 +388,6 @@ pub mod rpc_methods { pub const GLOBALSTATE_WRITEKEY: &str = "globalState.writeKey"; /// `commands.list` pub const COMMANDS_LIST: &str = "commands.list"; - /// `user.settings.get` - pub const USER_SETTINGS_GET: &str = "user.settings.get"; - /// `user.settings.set` - pub const USER_SETTINGS_SET: &str = "user.settings.set"; /// `gitHubRepository.atPath` pub const GITHUBREPOSITORY_ATPATH: &str = "gitHubRepository.atPath"; /// `gitHubOwners.nextRequestId` @@ -225,18 +402,6 @@ pub mod rpc_methods { pub const GIT_WORKINGDIRECTORYCONTEXT: &str = "git.workingDirectoryContext"; /// `git.reposFromRemotes` pub const GIT_REPOSFROMREMOTES: &str = "git.reposFromRemotes"; - /// `managedSettings.read` - pub const MANAGEDSETTINGS_READ: &str = "managedSettings.read"; - /// `managedSettings.clearCache` - pub const MANAGEDSETTINGS_CLEARCACHE: &str = "managedSettings.clearCache"; - /// `managedSettings.resolve` - pub const MANAGEDSETTINGS_RESOLVE: &str = "managedSettings.resolve"; - /// `managedSettings.schema` - pub const MANAGEDSETTINGS_SCHEMA: &str = "managedSettings.schema"; - /// `managedSettings.validate` - pub const MANAGEDSETTINGS_VALIDATE: &str = "managedSettings.validate"; - /// `managedSettings.compose` - pub const MANAGEDSETTINGS_COMPOSE: &str = "managedSettings.compose"; /// `runtime.shutdown` pub const RUNTIME_SHUTDOWN: &str = "runtime.shutdown"; /// `sessionFs.setProvider` @@ -346,6 +511,9 @@ pub mod rpc_methods { pub const SESSION_SENDMESSAGES: &str = "session.sendMessages"; /// `session.sandbox.getEnforcementStatus` pub const SESSION_SANDBOX_GETENFORCEMENTSTATUS: &str = "session.sandbox.getEnforcementStatus"; + /// `session.sandbox.getCredentialSuggestions` + pub const SESSION_SANDBOX_GETCREDENTIALSUGGESTIONS: &str = + "session.sandbox.getCredentialSuggestions"; /// `session.sandbox.disableForSession` pub const SESSION_SANDBOX_DISABLEFORSESSION: &str = "session.sandbox.disableForSession"; /// `session.sandbox.grantPathForRequest` @@ -453,6 +621,8 @@ pub mod rpc_methods { pub const SESSION_MODEL_SETALLOWEDMODELS: &str = "session.model.setAllowedModels"; /// `session.model.setReasoningEffort` pub const SESSION_MODEL_SETREASONINGEFFORT: &str = "session.model.setReasoningEffort"; + /// `session.model.getReasoningSummaries` + pub const SESSION_MODEL_GETREASONINGSUMMARIES: &str = "session.model.getReasoningSummaries"; /// `session.model.list` pub const SESSION_MODEL_LIST: &str = "session.model.list"; /// `session.mode.get` @@ -724,6 +894,8 @@ pub mod rpc_methods { pub const SESSION_PLUGINS_MARKETPLACES_REFRESH: &str = "session.plugins.marketplaces.refresh"; /// `session.plugins.reload` pub const SESSION_PLUGINS_RELOAD: &str = "session.plugins.reload"; + /// `session.plugins.retryManaged` + pub const SESSION_PLUGINS_RETRYMANAGED: &str = "session.plugins.retryManaged"; /// `session.provider.getEndpoint` pub const SESSION_PROVIDER_GETENDPOINT: &str = "session.provider.getEndpoint"; /// `session.provider.add` @@ -1759,6 +1931,67 @@ pub struct AccountAllUsers { pub token: Option, } +/// Credential-free authentication identity safe to expose to hosts and user interfaces. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +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 authentication identity whose shared wording should be rendered. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol 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 AccountDescribeAuthInfoRequest { + /// Identity to describe. + pub identity: AuthIdentity, +} + +/// The wording the runtime uses for an authentication identity. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol 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 AccountDescribeAuthInfoResult { + /// One-line description of the identity, suitable after a verb such as "Switched to". + pub description: String, + /// Sign-in banner announcing the authenticated user. + pub message: String, +} + /// Current authentication state /// ///
@@ -1778,6 +2011,42 @@ pub struct AccountGetCurrentAuthResult { pub auth_info: Option, } +/// The authentication identity whose gist support should be reported. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol 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 AccountGetGistSupportRequest { + /// Identity to evaluate. + pub identity: AuthIdentity, +} + +/// Whether the identity can create gists, and why not when it cannot. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol 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 AccountGetGistSupportResult { + /// Machine-readable reason category when unsupported. + #[serde(skip_serializing_if = "Option::is_none")] + pub kind: Option, + /// Human-readable explanation to show where the gist action would have been. + #[serde(skip_serializing_if = "Option::is_none")] + pub reason: Option, + /// True when the identity can create gists. + pub supported: bool, +} + /// Optional opaque account selection or compatibility GitHub token used to look up quota. /// ///
@@ -1842,6 +2111,55 @@ pub struct AccountGetQuotaResult { pub quota_snapshots: HashMap, } +/// The environment variable supplying a classic personal access token, when one is 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(crate) struct AccountGetTokenEnvVarResult { + /// Variable name, for example `GH_TOKEN`. Absent when no classic token is present in the environment. + #[serde(skip_serializing_if = "Option::is_none")] + pub name: Option, +} + +/// A path to resolve against the GitHub host an identity is signed in 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(crate) struct AccountGetWebUrlRequest { + /// Identity whose host the path resolves against. Omit to resolve against the public github.com host. + #[serde(skip_serializing_if = "Option::is_none")] + pub identity: Option, + /// Host-relative path, without a leading slash, for example `settings/copilot/features`. + pub path: String, +} + +/// An absolute URL on the identity's GitHub host. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol 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 AccountGetWebUrlResult { + /// Absolute URL for the requested path. + pub url: String, +} + /// Credentials to validate and store. Omit login to resolve the authenticated user from the token. /// ///
@@ -2039,6 +2357,9 @@ pub struct AccountsSetRequest { pub struct AccountStatus { /// Whether this is the active account. pub active: bool, + /// Human-readable credential source used to authenticate this account, such as `gh` or `GITHUB_TOKEN`. + #[serde(skip_serializing_if = "Option::is_none")] + pub auth_source: Option, /// Opaque id of the account this one was derived from (e.g. an EMU account's base Entra identity); absent for a root account. Matches the base identity account's selectionId, forming the derivation edge. #[serde(skip_serializing_if = "Option::is_none")] pub derived_from: Option, @@ -2107,6 +2428,9 @@ pub struct AgentInfo { pub disable_model_invocation: Option, /// Human-readable display name pub display_name: String, + /// Declarative hook actions keyed by event name, executed only while this agent is active. + #[serde(skip_serializing_if = "Option::is_none")] + pub hooks: Option>>, /// 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. @@ -2477,6 +2801,63 @@ pub struct AgentsDiscoverRequest { pub project_paths: Option>, } +/// Extensible [`AgentsDiscoverRequest`], including inputs added after it was published. +/// +/// Required inputs are [`AgentsDiscoverOptions::new`] arguments; optional inputs have fluent setters. +/// Input-only: it serialises to the flat wire request and is not deserialisable. +/// +///
+/// +/// **Experimental.** This 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)] +#[serde(rename_all = "camelCase")] +pub struct AgentsDiscoverOptions { + #[serde(flatten)] + legacy: AgentsDiscoverRequest, + #[serde(skip_serializing_if = "Option::is_none")] + agent_directories: Option>, +} + +impl AgentsDiscoverOptions { + /// Creates options with the required inputs. + pub fn new() -> Self { + Self { + legacy: AgentsDiscoverRequest { + project_paths: None, + exclude_host_agents: None, + }, + agent_directories: None, + } + } + + /// 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 fn project_paths(mut self, value: Vec) -> Self { + self.legacy.project_paths = Some(value); + self + } + + /// 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. + pub fn exclude_host_agents(mut self, value: bool) -> Self { + self.legacy.exclude_host_agents = Some(value); + self + } + + /// Explicit discovery-only agent roots, separate from customAgents and filesystem grants. Available without ambient discovery, subject to managed/source disables. Relative paths resolve against the request's effective working directory when supplied. Null and omission are equivalent; an empty array contributes no explicit roots. + pub fn agent_directories(mut self, value: Vec) -> Self { + self.agent_directories = Some(value); + self + } +} + +impl Default for AgentsDiscoverOptions { + fn default() -> Self { + Self::new() + } +} + /// Name of the custom agent to select for subsequent turns. /// ///
@@ -2677,6 +3058,63 @@ pub struct AgentsGetDiscoveryPathsRequest { pub project_paths: Option>, } +/// Extensible [`AgentsGetDiscoveryPathsRequest`], including inputs added after it was published. +/// +/// Required inputs are [`AgentsGetDiscoveryPathsOptions::new`] arguments; optional inputs have fluent setters. +/// Input-only: it serialises to the flat wire request and is not deserialisable. +/// +///
+/// +/// **Experimental.** This 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)] +#[serde(rename_all = "camelCase")] +pub struct AgentsGetDiscoveryPathsOptions { + #[serde(flatten)] + legacy: AgentsGetDiscoveryPathsRequest, + #[serde(skip_serializing_if = "Option::is_none")] + agent_directories: Option>, +} + +impl AgentsGetDiscoveryPathsOptions { + /// Creates options with the required inputs. + pub fn new() -> Self { + Self { + legacy: AgentsGetDiscoveryPathsRequest { + project_paths: None, + exclude_host_agents: None, + }, + agent_directories: None, + } + } + + /// Optional list of project directory paths. When omitted or empty, only the user-level directory is returned. + pub fn project_paths(mut self, value: Vec) -> Self { + self.legacy.project_paths = Some(value); + self + } + + /// When true, omit the host's user-level agent directory, leaving only project directories. For multitenant deployments (mirrors `discover`'s `excludeHostAgents`). + pub fn exclude_host_agents(mut self, value: bool) -> Self { + self.legacy.exclude_host_agents = Some(value); + self + } + + /// Explicit discovery-only agent roots to enumerate, including configured missing directories. Relative paths resolve against the request's effective working directory when supplied. These are not filesystem grants or preferred creation targets. Null and omission are equivalent. + pub fn agent_directories(mut self, value: Vec) -> Self { + self.agent_directories = Some(value); + self + } +} + +impl Default for AgentsGetDiscoveryPathsOptions { + fn default() -> Self { + Self::new() + } +} + /// Blob attachment with inline base64-encoded data /// ///
@@ -3215,35 +3653,6 @@ pub struct AuthEnumerateValueProviders { pub kind: AuthEnumerateValueProvidersKind, } -/// Credential-free authentication identity safe to expose to hosts and user interfaces. -/// -///
-/// -/// **Experimental.** This type is part of an experimental wire-protocol surface -/// and may change or be removed in future SDK or CLI releases. -/// -///
-#[derive(Debug, Clone, Default, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -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, -} - /// Credential-free identity metadata. /// ///
@@ -4284,6 +4693,150 @@ impl CardDigest { } } +/// Agent Finder's verified materialisation of the plugin. Present only when the candidate carries a complete materialisation reference; the reference itself stays runtime-private. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CatalogAgentPluginMaterializationSource { + /// Discriminator: the plugin installs from Agent Finder's materialisation. + #[serde(deserialize_with = "CatalogAgentPluginMaterializationSource::deserialize_kind")] + pub kind: CatalogAgentPluginMaterializationSourceKind, +} + +impl CatalogAgentPluginMaterializationSource { + fn deserialize_kind<'de, D>( + deserializer: D, + ) -> Result + where + D: serde::Deserializer<'de>, + { + let value = String::deserialize(deserializer)?; + if value != "materialization" { + return Err(serde::de::Error::unknown_variant( + &value, + &["materialization"], + )); + } + ::deserialize( + serde::de::value::StringDeserializer::::new(value), + ) + } +} + +/// A plugin marketplace named by verified catalogue install data, installed as `pluginName@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 struct CatalogAgentPluginMarketplaceSource { + /// Discriminator: the plugin installs from a marketplace. + #[serde(deserialize_with = "CatalogAgentPluginMarketplaceSource::deserialize_kind")] + pub kind: CatalogAgentPluginMarketplaceSourceKind, + /// Marketplace name. + pub name: String, + /// Canonical lowercase GitHub owner/repository hosting the marketplace. + pub repository: String, +} + +impl CatalogAgentPluginMarketplaceSource { + fn deserialize_kind<'de, D>( + deserializer: D, + ) -> Result + where + D: serde::Deserializer<'de>, + { + let value = String::deserialize(deserializer)?; + if value != "marketplace" { + return Err(serde::de::Error::unknown_variant(&value, &["marketplace"])); + } + ::deserialize( + serde::de::value::StringDeserializer::::new(value), + ) + } +} + +/// A GitHub repository holding the plugin, installed directly as `owner/repo[:path]` at the plan's commit, without a marketplace. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CatalogAgentPluginRepositorySource { + /// Discriminator: the plugin installs directly from a repository. + #[serde(deserialize_with = "CatalogAgentPluginRepositorySource::deserialize_kind")] + pub kind: CatalogAgentPluginRepositorySourceKind, + /// Relative POSIX path of the plugin directory in the repository. Omitted when the plugin is at the repository root. + #[serde(skip_serializing_if = "Option::is_none")] + pub path: Option, + /// Canonical lowercase GitHub owner/repository holding the plugin. + pub repository: String, +} + +impl CatalogAgentPluginRepositorySource { + fn deserialize_kind<'de, D>( + deserializer: D, + ) -> Result + where + D: serde::Deserializer<'de>, + { + let value = String::deserialize(deserializer)?; + if value != "repository" { + return Err(serde::de::Error::unknown_variant(&value, &["repository"])); + } + ::deserialize( + serde::de::value::StringDeserializer::::new(value), + ) + } +} + +/// What installing an Agent Plugin would do, for the user to review and confirm. Provisional: the catalogue install data it is built from is not final. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CatalogAgentPluginInstallPlan { + /// Canvas IDs the plugin contributes. Non-empty exactly when components include `canvas`. + pub canvas_ids: Vec, + /// Exact full commit ID to install, in lowercase hex. Always present when the plan has a repository source; a plan with a marketplace source carries at least one of version and commit. + #[serde(skip_serializing_if = "Option::is_none")] + pub commit: Option, + /// Components the plugin contributes, in canonical order. + pub components: Vec, + /// Connectors the plugin depends on. + pub connector_dependencies: Vec, + /// Extension IDs the plugin contributes. Non-empty exactly when components include `extension`. + pub extension_ids: Vec, + /// MCP servers the plugin depends on but does not bundle. + pub mcp_dependencies: Vec, + /// The plugin's name, as named in its marketplace or manifest. + pub plugin_name: String, + /// Every valid install source, in preference order: materialization, then marketplace, then repository. Each kind appears at most once. + pub sources: Vec, + /// Exact plugin version to install. A plan with a marketplace source carries at least one of version and commit. + #[serde(skip_serializing_if = "Option::is_none")] + pub version: Option, +} + /// Where and when an Agent Plugin catalog reference was observed. Discovery provenance deliberately carries no descriptor URL, raw data, candidate handle, or content digest. /// ///
@@ -4297,7 +4850,7 @@ impl CardDigest { pub struct CatalogAgentPluginCandidateProvenance { /// Host of the catalog authority that advertised the reference, without path, query, or credentials. Inert untrusted data. pub authority: String, - /// Canonical Agent Plugin media type. + /// Media type naming the plugin's format. pub media_type: CatalogAgentPluginMediaType, /// ISO 8601 timestamp at which the runtime observed the catalog reference. This is not a retrieval or validation timestamp. pub observed_at: String, @@ -4340,10 +4893,16 @@ pub struct CatalogAgentPluginCandidate { pub display_name: String, /// Validated, normalised catalogue resource URN. This identity comes only from the catalog identifier and is never inferred from display text or installed-plugin state. pub identity: String, + /// Whether this plugin can be installed in the selected session. Present only when the caller requires the agent-plugin-confirmed-installation capability. + #[serde(skip_serializing_if = "Option::is_none")] + pub installability: Option, + /// Reviewable install plan built only from verified catalogue install data. Present exactly when installability is `installable`; the runtime never derives it from the repository source, display text, or identity. + #[serde(skip_serializing_if = "Option::is_none")] + pub install_plan: Option, /// Discriminator: this candidate describes an Agent Plugin. #[serde(deserialize_with = "CatalogAgentPluginCandidate::deserialize_kind")] pub kind: CatalogAgentPluginCandidateKind, - /// Canonical Agent Plugin media type. + /// Media type naming the plugin's format. pub media_type: CatalogAgentPluginMediaType, /// Where the Agent Plugin catalog reference was observed, without its descriptor, URL, raw data, or content digest. pub provenance: CatalogAgentPluginCandidateProvenance, @@ -4708,6 +5267,33 @@ pub struct CatalogMalformedCardError { pub reason: CatalogMalformedCardReason, } +/// An Agent Finder materialisation reference pinning one exact resource revision. +/// +/// It carries no origin or URL: the runtime always builds the descriptor URL on its fixed Agent +/// Finder authority. It carries no display data either; every reviewed field comes from the +/// verified descriptor and Skill files. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CatalogMaterializationReference { + /// Expected `sha256:` digest of the descriptor's bundle manifest. + pub bundle_digest: String, + /// Expected `sha256:` digest of the canonical descriptor. + pub descriptor_digest: String, + /// Agent Finder resource identifier, a positive decimal integer. + pub resource_id: String, + /// Agent Finder materialisation revision identifier, a positive decimal integer. This is not a Git commit. + pub revision_id: String, + /// Materialisation descriptor schema version, currently `1.0.0`. + pub schema_version: String, +} + /// The protocol version and capability set the runtime actually honoured for a successful catalog operation. /// ///
@@ -6578,6 +7164,36 @@ pub struct ContentExclusionCheckPathsResult { pub checks: Vec, } +/// Candidate content-exclusion policies to validate, as the raw JSON text a host received. The text is supplied unparsed so that a syntax error and a schema violation are both reported by the runtime's own validator rather than by the caller's JSON parser. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ContentExclusionValidatePoliciesRequest { + /// Unparsed JSON text expected to hold an array of content-exclusion policies. + pub policies_json: String, +} + +/// The accepted policies, parsed and normalized. A rejected input is reported as an error rather than in this result. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ContentExclusionValidatePoliciesResult { + /// Validated policy objects in input order. Raw JSON preserves every field returned by the runtime's policy parser. + pub policies: Vec, +} + /// A single large message currently in context. /// ///
@@ -8200,6 +8816,128 @@ pub struct ExternalToolTextResultForLlmContentText { pub r#type: ExternalToolTextResultForLlmContentTextType, } +/// The declared shape of one feature flag, independent of any caller population. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol 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 FeatureFlagMetadata { + /// The population the flag is declared for before any environment or user-settings override is applied. + #[doc(hidden)] + pub(crate) availability: FeatureFlagAvailability, + /// Whether the flag participates in the CAPI sanity suite. + pub capi_sanity: bool, + /// Text shown to a user choosing whether to turn the flag on in experimental mode. Absent when the flag is not user-facing. + #[serde(skip_serializing_if = "Option::is_none")] + pub experimental_description: Option, + /// The online-experiment (ExP) flag this maps to, when the flag is assigned by an experiment rather than declared statically. + #[serde(skip_serializing_if = "Option::is_none")] + pub exp_flag: Option, + /// Environment variable that overrides this flag's experiment assignment locally in the experiment service, not in `featureFlags.resolve`. Present exactly when `expFlag` is, and derived from it, so a caller reads a local override without reproducing the naming rule. + #[serde(skip_serializing_if = "Option::is_none")] + pub exp_flag_env_var: Option, + /// The flag's name, matching the key it is listed under. + pub name: String, +} + +/// Every feature flag this runtime declares, with the shape each one was declared with. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol 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 FeatureFlagsMetadataResult { + /// Declared feature flags keyed by flag name. + #[doc(hidden)] + pub(crate) flags: HashMap, +} + +/// Caller population and overrides for static feature-flag resolution, independent of experiment assignments and active runtime 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(crate) struct FeatureFlagsResolveRequest { + /// Resolve against this already-computed flag set rather than each flag's declared availability. Keyed by flag name. + #[serde(skip_serializing_if = "Option::is_none")] + pub base_flags: Option>, + /// User settings to apply, including `enabledFeatureFlags`, legacy `feature_flags.enabled`, and supported convenience settings. + #[serde(skip_serializing_if = "Option::is_none")] + pub config: Option, + /// Environment for direct feature-flag overrides and `COPILOT_CLI_ENABLED_FEATURE_FLAGS`. Does not apply `COPILOT_EXP_*` experiment assignments. Defaults to this runtime process's own environment. + #[serde(skip_serializing_if = "Option::is_none")] + pub environment: Option>, + /// Flags forced to a value after every other layer, keyed by flag name. + #[serde(skip_serializing_if = "Option::is_none")] + pub flag_overrides: Option>, + /// Whether the caller is in an experimental population. + pub is_experimental: bool, + /// Whether the caller is GitHub or Microsoft staff. + pub is_staff: bool, + /// GitHub-staff membership. Omit when it was never recorded: a legacy staff account then counts as both staff populations and keeps every staff feature. + #[serde(skip_serializing_if = "Option::is_none")] + pub is_staff_github: Option, + /// Microsoft-staff membership. Omitted the same way as `isStaffGitHub`. + #[serde(skip_serializing_if = "Option::is_none")] + pub is_staff_microsoft: Option, + /// Whether the caller belongs to the team population used by feature availability. + pub is_team: bool, +} + +/// Caller population and startup inputs to resolve named online-experiment gates for. Carries no account: the runtime resolves the assignment for the authentication it already holds. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol 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 FeatureFlagsResolveStartupGatesRequest { + /// User settings document whose `featureFlags` block is layered on top of the resolved set. + #[serde(skip_serializing_if = "Option::is_none")] + pub config: Option, + /// Environment to read flag overrides from. Defaults to this runtime process's own environment. + #[serde(skip_serializing_if = "Option::is_none")] + pub environment: Option>, + /// When this installation first launched, in milliseconds since the Unix epoch. Experiments that segment on installation age read it. Omit when the first launch was never recorded. + #[serde(skip_serializing_if = "Option::is_none")] + pub first_launch_at_millis: Option, + /// The feature flags to resolve. A name this runtime does not declare is reported as an error rather than as a disabled flag. + pub flags: Vec, + /// Whether the caller is in an experimental population. + pub is_experimental: bool, + /// Whether the caller is GitHub or Microsoft staff. + pub is_staff: bool, + /// GitHub-staff membership. Omit when it was never recorded: a legacy staff account then counts as both staff populations and keeps every staff feature. + #[serde(skip_serializing_if = "Option::is_none")] + pub is_staff_github: Option, + /// Microsoft-staff membership. Omitted the same way as `isStaffGitHub`. + #[serde(skip_serializing_if = "Option::is_none")] + pub is_staff_microsoft: Option, + /// Whether the caller belongs to a team plan. + pub is_team: bool, + /// Whether the runtime may refresh the cached assignment over the network without an active session. The refresh never blocks this call: the answer comes from the cache either way, so a refresh only affects a later launch. + pub refresh_without_session: bool, + /// Whether streamer mode is on, which withholds the populations an experiment would otherwise segment on. + pub streamer_mode: bool, +} + /// Parameters for starting fleet orchestration: an optional user prompt combined with the fleet instructions, plus the send options forwarded to the resulting turn. /// ///
@@ -8286,6 +9024,54 @@ pub struct FolderTrustCheckResult { pub trusted: bool, } +/// A folder to check against a persisted trusted-folders list, and the configuration directory holding that 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(crate) struct FolderTrustIsTrustedParams { + /// Configuration directory to read the trusted-folders list from. Omit it to read the default location; supply it when the host has redirected its configuration, or the check answers from a list the host is not using. + #[serde(skip_serializing_if = "Option::is_none")] + pub config_dir: Option, + /// Folder to check. A folder counts as trusted when it is itself listed, when an ancestor is listed, or when its git root is listed. + pub path: String, +} + +/// The verdict of the `forceLoginOrgs` enterprise sign-in pin for one credential. A managed device may pin Copilot to an approved set of GitHub organizations so a developer cannot sign in with a personal — or another enterprise's — account and escape every control that keys off the signed-in identity. A host must refuse to proceed when `allowed` is false; the remaining fields explain the refusal rather than qualifying 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 ForceLoginOrgsDecision { + /// Whether the client may proceed. True whenever no pin is in force, so an unmanaged host is never blocked. + pub allowed: bool, + /// A stable kebab-case reason code when refused: `org-not-approved`, `unverifiable-credential`, `policy-undetermined`, `policy-invalid`, `not-signed-in`, or `membership-unknown`. Omitted when allowed. Treat an unrecognized code as a refusal rather than mapping it to an allow: a code this host has not been taught about still denied the credential. + #[serde(skip_serializing_if = "Option::is_none")] + pub denial: Option, + /// Whether a pin applies at all. Distinguishes "allowed because unmanaged" from "allowed because the account is approved", which a diagnostics surface needs and which also decides whether a bring-your-own-key provider may serve inference. + pub enforced: bool, + /// The approved organization the account matched, when allowed under a pin. + #[serde(skip_serializing_if = "Option::is_none")] + pub matched_org: Option, + /// A developer-facing explanation of the refusal. Omitted when allowed. + #[serde(skip_serializing_if = "Option::is_none")] + pub message: Option, + /// Error-level detail from managed-policy discovery — which file failed to load, which field failed validation. Carried on the decision rather than left to the log because a refusal can exit the process before a log sink exists, which would discard the only actionable diagnostic a misconfigured managed device produces. + pub policy_diagnostics: Vec, + /// The approved organization logins, as configured. Enterprise configuration rather than user data, so it is safe to name in a message or a log line. + pub required_orgs: Vec, +} + /// The remote the checked-out branch tracks. /// ///
@@ -8768,9 +9554,6 @@ pub(crate) struct LoggedInUser { #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub(crate) struct GlobalStateLoadResult { - /// Whether the user has answered the prompt suggesting they install the desktop app. - #[serde(skip_serializing_if = "Option::is_none")] - pub app_install_nudge_responded: Option, /// Whether the app tip has been shown. #[serde(skip_serializing_if = "Option::is_none")] pub app_tip_shown: Option, @@ -9279,6 +10062,63 @@ pub struct HooksDiscoverRequest { pub project_paths: Option>, } +/// Extensible [`HooksDiscoverRequest`], including inputs added after it was published. +/// +/// Required inputs are [`HooksDiscoverOptions::new`] arguments; optional inputs have fluent setters. +/// Input-only: it serialises to the flat wire request and is not deserialisable. +/// +///
+/// +/// **Experimental.** This 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)] +#[serde(rename_all = "camelCase")] +pub struct HooksDiscoverOptions { + #[serde(flatten)] + legacy: HooksDiscoverRequest, + #[serde(skip_serializing_if = "Option::is_none")] + hook_directories: Option>, +} + +impl HooksDiscoverOptions { + /// Creates options with the required inputs. + pub fn new() -> Self { + Self { + legacy: HooksDiscoverRequest { + project_paths: None, + exclude_host_hooks: None, + }, + hook_directories: 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. + pub fn project_paths(mut self, value: Vec) -> Self { + self.legacy.project_paths = Some(value); + self + } + + /// 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. + pub fn exclude_host_hooks(mut self, value: bool) -> Self { + self.legacy.exclude_host_hooks = Some(value); + self + } + + /// Explicit discovery-only file-hook roots, separate from callback hooks and filesystem grants. Available without ambient discovery or repository file-hook enablement, subject to managed/hook disables. Relative paths resolve against the request's effective working directory when supplied. Null and omission are equivalent; an empty array contributes no explicit roots. + pub fn hook_directories(mut self, value: Vec) -> Self { + self.hook_directories = Some(value); + self + } +} + +impl Default for HooksDiscoverOptions { + fn default() -> Self { + Self::new() + } +} + /// 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. /// ///
@@ -10627,6 +11467,36 @@ pub struct InstructionDiscoveryPathList { pub paths: Vec, } +/// Explicit discovery arguments for one location, supplied by the caller rather than derived from 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(crate) struct InstructionsDiscoverRepoSourcesRequest { + /// Extra directories to scan alongside instruction directories configured in the runtime environment. + #[serde(skip_serializing_if = "Option::is_none")] + pub additional_dirs: Option>, + /// Copilot configuration directory for user-level sources. An omitted or empty value uses the server's configuration home. + #[serde(skip_serializing_if = "Option::is_none")] + pub config_dir: Option, + /// Working directory whose instruction sources should also be included. Omission adds no separate working-directory scope. + #[serde(skip_serializing_if = "Option::is_none")] + pub cwd_path: Option, + /// When true, also discover instruction files nested below the location. + #[serde(skip_serializing_if = "Option::is_none")] + pub enable_child_instructions: Option, + /// When true, include the sources a command-line host reads. Hosts that are not the CLI omit it. + #[serde(skip_serializing_if = "Option::is_none")] + pub is_cli: Option, + /// Repository location to discover. An empty string selects no repository scope; cwdPath can still supply working-directory sources. + pub location: String, +} + /// Optional project paths to include in instruction discovery. /// ///
@@ -10646,6 +11516,63 @@ pub struct InstructionsDiscoverRequest { pub project_paths: Option>, } +/// Extensible [`InstructionsDiscoverRequest`], including inputs added after it was published. +/// +/// Required inputs are [`InstructionsDiscoverOptions::new`] arguments; optional inputs have fluent setters. +/// Input-only: it serialises to the flat wire request and is not deserialisable. +/// +///
+/// +/// **Experimental.** This 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)] +#[serde(rename_all = "camelCase")] +pub struct InstructionsDiscoverOptions { + #[serde(flatten)] + legacy: InstructionsDiscoverRequest, + #[serde(skip_serializing_if = "Option::is_none")] + instruction_directories: Option>, +} + +impl InstructionsDiscoverOptions { + /// Creates options with the required inputs. + pub fn new() -> Self { + Self { + legacy: InstructionsDiscoverRequest { + project_paths: None, + exclude_host_instructions: None, + }, + instruction_directories: None, + } + } + + /// 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). + pub fn project_paths(mut self, value: Vec) -> Self { + self.legacy.project_paths = Some(value); + self + } + + /// 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. + pub fn exclude_host_instructions(mut self, value: bool) -> Self { + self.legacy.exclude_host_instructions = Some(value); + self + } + + /// Explicit discovery-only instruction roots using existing SDK instruction formats, not filesystem grants. Available without ambient discovery, subject to instruction/managed disables. Relative paths resolve against the request's effective working directory when supplied. Null and omission are equivalent; an empty array contributes no explicit roots. + pub fn instruction_directories(mut self, value: Vec) -> Self { + self.instruction_directories = Some(value); + self + } +} + +impl Default for InstructionsDiscoverOptions { + fn default() -> Self { + Self::new() + } +} + /// Optional project paths to include when enumerating instruction discovery targets. /// ///
@@ -10665,6 +11592,63 @@ pub struct InstructionsGetDiscoveryPathsRequest { pub project_paths: Option>, } +/// Extensible [`InstructionsGetDiscoveryPathsRequest`], including inputs added after it was published. +/// +/// Required inputs are [`InstructionsGetDiscoveryPathsOptions::new`] arguments; optional inputs have fluent setters. +/// Input-only: it serialises to the flat wire request and is not deserialisable. +/// +///
+/// +/// **Experimental.** This 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)] +#[serde(rename_all = "camelCase")] +pub struct InstructionsGetDiscoveryPathsOptions { + #[serde(flatten)] + legacy: InstructionsGetDiscoveryPathsRequest, + #[serde(skip_serializing_if = "Option::is_none")] + instruction_directories: Option>, +} + +impl InstructionsGetDiscoveryPathsOptions { + /// Creates options with the required inputs. + pub fn new() -> Self { + Self { + legacy: InstructionsGetDiscoveryPathsRequest { + project_paths: None, + exclude_host_instructions: None, + }, + instruction_directories: None, + } + } + + /// Optional list of project directory paths. When omitted or empty, only the user-level targets are returned. + pub fn project_paths(mut self, value: Vec) -> Self { + self.legacy.project_paths = Some(value); + self + } + + /// When true, omit the host's user-level instruction targets, leaving only repository targets. For multitenant deployments (mirrors `discover`'s `excludeHostInstructions`). + pub fn exclude_host_instructions(mut self, value: bool) -> Self { + self.legacy.exclude_host_instructions = Some(value); + self + } + + /// Explicit discovery-only instruction roots to enumerate, including configured missing directories. Relative paths resolve against the request's effective working directory when supplied. These are not filesystem grants or preferred creation targets. Null and omission are equivalent. + pub fn instruction_directories(mut self, value: Vec) -> Self { + self.instruction_directories = Some(value); + self + } +} + +impl Default for InstructionsGetDiscoveryPathsOptions { + fn default() -> Self { + Self::new() + } +} + /// Loaded instruction source for a session, including path, content, category, location, applicability, and optional description. /// ///
@@ -10717,6 +11701,21 @@ pub struct InstructionsGetSourcesResult { pub sources: Vec, } +/// The prompt the init command runs. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol 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 InstructionsInitPromptResult { + /// Prompt text authoring repository instructions. + pub prompt: String, +} + /// Parameters for interrupting the main agent turn. /// ///
@@ -11015,6 +12014,21 @@ pub struct LocalSessionMetadataValue { pub summary: Option, } +/// Which stored location permissions were dropped because their directory is gone. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol 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 LocationPermissionsCleanupResult { + /// Location keys whose grants were removed. Empty when every stored location still exists. + pub removed_location_keys: Vec, +} + /// Message text, optional severity level, persistence flag, optional follow-up URL, and optional tip. /// ///
@@ -11108,6 +12122,92 @@ pub struct ManagedMcpServerConfig { pub url: 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 ManagedPermissionOperation { + /// Operation to evaluate. Currently only url is supported. + pub kind: ManagedPermissionOperationKind, + /// Absolute HTTP(S) URL with a host. Invalid URLs reject the entire evaluation batch. + pub url: 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 ManagedPermissionEvaluation { + /// Original input operation associated with this verdict. + pub operation: ManagedPermissionOperation, + /// Managed-policy decision only; never bypasses user permissions or other security controls. + pub verdict: ManagedPermissionVerdict, +} + +/// Managed policy context and ordered operations for pure, sessionless evaluation. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ManagedPermissionsEvaluateRequest { + /// Trusted, reusable managed permission policy snapshot; no policy is discovered during evaluation. + pub context: ManagedPermissionsContext, + /// Operations to evaluate, preserving input order and duplicates. An empty batch is valid. + pub operations: Vec, +} + +/// Ordered managed permission verdicts and the supplied policy's fail-closed posture. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ManagedPermissionsEvaluateResult { + /// Managed permission policy could not be determined; every operation is denied. + pub fail_closed: bool, + /// One verdict per input operation, in the same order, including duplicates. + pub results: Vec, +} + +/// Retry outcome for one plugin required by managed settings. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ManagedPluginRetryEntry { + /// Why the plugin was not prepared. Present when `status` is `failed` or `deferred`. + #[serde(skip_serializing_if = "Option::is_none")] + pub error: Option, + /// Plugin spec in "plugin@marketplace" form. + pub spec: String, + /// What the retry did for this plugin. + pub status: ManagedPluginRetryStatus, +} + /// Lock state and provenance of one managed setting. /// ///
@@ -11128,6 +12228,39 @@ pub struct ManagedSettingMeta { pub source: String, } +/// A user settings document and the effective managed policy whose controlled entries should be stamped onto 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 ManagedSettingsApplyControlsParams { + /// The effective managed policy, as composed by `managedSettings.resolveEffectiveValue`. Omit it on an unmanaged host: the settings then come back unchanged. + #[serde(skip_serializing_if = "Option::is_none")] + pub managed: Option, + /// The user settings document to stamp. Entries the managed layer does not name are returned exactly as supplied. + pub settings: serde_json::Value, +} + +/// The user settings with the managed layer's controlled entries stamped on. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol 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 ManagedSettingsApplyControlsResult { + /// The resulting settings document. + pub settings_json: serde_json::Value, +} + /// One candidate channel; absent settings represents a channel that delivered no document. /// ///
@@ -11234,20 +12367,23 @@ pub struct ManagedSettingsMeta { #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct ManagedSettingsResolvedData { - /// Whether enterprise policy disables bypass-permissions ("yolo") mode for this session. Deny-wins across layers, and forced on when `failClosed` is true. + /// Whether an explicit enterprise policy restriction disables bypass-permissions ("yolo") mode for this session. Deny-wins across layers; an unresolved policy does not force this on by itself. pub bypass_permissions_disabled: bool, /// 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. + /// Whether managed policy could not be determined (e.g. a failed server fetch) and unresolved-policy safeguards remain active. This does not by itself disable bypass-permissions; `bypassPermissionsDisabled` reports only an explicit policy restriction. pub fail_closed: bool, /// The setting keys under enterprise management in the effective managed settings (e.g. `model`, `enabledPlugins`, `permissions`). Empty when no managed settings are in force. pub managed_keys: Vec, /// Whether at least two managed sources supplied permission allowlists, so enforcement intersects them and the flattened settings payload omits `permissions.allow`. #[serde(skip_serializing_if = "Option::is_none")] pub permissions_allow_intersected: Option, + /// Reusable retained managed permission policy for managedSettings.permissions.evaluate, including source composition and session-local injection. Provided by live session snapshots; absent in older events. Refresh the context when managed policy changes. + #[serde(skip_serializing_if = "Option::is_none")] + pub permissions_context: 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, @@ -11313,6 +12449,272 @@ pub struct ManagedSettingsComposeResult { pub values: Option, } +/// Inputs describing the host and account an ingestion should resolve managed policy 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(crate) struct ManagedSettingsIngestParams { + /// The authenticated identity to resolve policy for. Omitted before sign-in, in which case no server layer is fetched. + #[serde(skip_serializing_if = "Option::is_none")] + pub auth_info: Option, + /// Whether to ignore a cached server policy when reading, forcing the resolution to come from a live fetch. + #[serde(skip_serializing_if = "Option::is_none")] + pub bypass_cache_read: Option, + /// Name of the embedding client, reported to the managed-settings service as `client_id` so it can refuse to deliver a policy this client is too old to enforce. Omit it and the runtime reports a generic identity; the runtime version sent alongside is the gate that actually matters. + #[serde(skip_serializing_if = "Option::is_none")] + pub client_name: Option, + /// A device layer supplied by the host instead of discovered from the machine. Pair it with `hasDeviceResponse` so an explicit absence is distinguishable from an omitted override. + #[serde(skip_serializing_if = "Option::is_none")] + pub device_response: Option, + /// The host's environment variables, read for cache location and policy overrides. Supplied by the host rather than read from the runtime process so a command that selected its own configuration home is honored. + pub environment: serde_json::Value, + /// Whether `deviceResponse` is an override at all. True with a null `deviceResponse` asserts the device layer is empty; false discovers it from the machine. + #[serde(skip_serializing_if = "Option::is_none")] + pub has_device_response: Option, + /// The host's home directory, used to resolve cache and managed-file locations. + pub home_directory: String, + /// The host's platform, in Node's `process.platform` spelling, used to locate platform-specific cache and policy paths. + pub platform: String, + /// Whether a configured platform policy helper may be run to produce an enforced helper layer. + #[serde(skip_serializing_if = "Option::is_none")] + pub policy_helper_enabled: Option, + /// The policy-helper invocation reason. Defaults to sessionStart. + #[doc(hidden)] + #[serde(skip_serializing_if = "Option::is_none")] + pub(crate) policy_helper_reason: Option, + /// Directory the policy helper runs in when this ingestion is not owned by a registered session. A host resolving policy before its session exists must pass this, or a directory-sensitive helper judges the wrong repository. + #[serde(skip_serializing_if = "Option::is_none")] + pub policy_helper_working_directory: Option, + /// Whether to run ingestion. Omit or set false to return an empty result without discovering or fetching policy. + #[serde(skip_serializing_if = "Option::is_none")] + pub self_fetch: Option, + /// How this ingestion may use the on-disk server-policy cache. Defaults to the runtime's own policy when omitted. + #[doc(hidden)] + #[serde(skip_serializing_if = "Option::is_none")] + pub(crate) server_policy_cache_mode: Option, + /// The session whose credential store and working directory the policy helper should use. Omit it when no session exists yet and the process-wide store applies. + #[serde(skip_serializing_if = "Option::is_none")] + pub session_id: Option, + /// User agent to send with the policy fetch. + pub user_agent: String, +} + +/// One diagnostic emitted while ingesting managed settings. Returned rather than logged by the runtime so the host decides where ingestion diagnostics go, which matters because an ingestion runs before a session exists to carry a log sink. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol 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 ManagedSettingsLogMessage { + /// Severity of this diagnostic. + #[doc(hidden)] + pub(crate) level: ManagedSettingsLogLevel, + /// Human-readable diagnostic text. + pub message: String, +} + +/// The managed policy layers an ingestion resolved, together with the per-layer outcome flags a host needs to decide how much to trust the result. The flags are reported rather than folded into the settings because a host must be able to distinguish an unmanaged device from a managed device whose policy could not be confirmed, and apply its fail-closed gates in the second 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, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub(crate) struct ManagedSettingsIngestResult { + /// The device layer could not be read, so it is unconfirmed rather than known-absent. + pub device_load_failed: bool, + /// The device layer discovered from the machine, when one exists. + #[serde(skip_serializing_if = "Option::is_none")] + pub device_response: Option, + /// The sandbox floor contributed by the device layer. + #[serde(skip_serializing_if = "Option::is_none")] + pub device_sandbox_floor: Option, + /// Whether the device layer's sandbox intent could not be determined, so a consumer must not read its absence as permission to run unsandboxed. + pub device_sandbox_undetermined: bool, + /// A device-managed policy is known to exist. + pub device_source_found: bool, + /// Diagnostics emitted while resolving the layers, in the order they occurred. + #[doc(hidden)] + pub(crate) log_messages: Vec, + /// The effective managed permissions layer, when the resolved policy carries one. + #[serde(skip_serializing_if = "Option::is_none")] + pub permissions: Option, + /// Which channel registered the policy helper that produced the helper layer. + #[doc(hidden)] + #[serde(skip_serializing_if = "Option::is_none")] + pub(crate) policy_helper_authority: Option, + /// A policy helper is configured for this host. + pub policy_helper_configured: bool, + /// The policy helper's failure must be treated as fail-closed rather than ignored. + pub policy_helper_fail_closed: bool, + /// The policy helper was configured but could not be run or its output could not be read. + pub policy_helper_load_failed: bool, + /// The layer produced by the platform policy helper, when one ran. + #[serde(skip_serializing_if = "Option::is_none")] + pub policy_helper_response: Option, + /// A live fetch failed but a recent cached entry was served in its place, so `serverFetchFailed` stays false. Lets a consumer distinguish a clean fetch from a cache-backed fallback. + pub server_cache_fallback: bool, + /// Minimum client version requested by a successful response. Advisory only; the delivered policy remains active. + #[serde(skip_serializing_if = "Option::is_none")] + pub server_client_update_requested: Option, + /// The server refused to deliver a policy because this client is too old to enforce it, carrying the user-facing sentence describing the refusal. No retry will help; the remedy is a client update. Gates that exist only because of managed policy should deny on this rather than falling back to their unmanaged default. + #[serde(skip_serializing_if = "Option::is_none")] + pub server_client_update_required: Option, + /// The error that made the server fetch fail, when one did. + #[serde(skip_serializing_if = "Option::is_none")] + pub server_fetch_error: Option, + /// The server fetch failed and no cached entry stood in for it, so the server layer is unconfirmed and fail-closed gates apply. + pub server_fetch_failed: bool, + /// The server fetch failed while a policy demanded a fresh confirmation, which is the strict posture an enterprise opted into rather than an ordinary transient failure. + pub server_forced_refresh_failure: bool, + /// The synthesized fail-closed permissions layer applied in place of a policy a refused client may not enforce. + #[serde(skip_serializing_if = "Option::is_none")] + pub server_permissions_floor: Option, + /// How the server layer was resolved. + #[doc(hidden)] + pub(crate) server_resolution: ManagedSettingsServerPolicyResolution, + /// The account's server-delivered policy, when one was resolved. + #[serde(skip_serializing_if = "Option::is_none")] + pub server_response: Option, + /// The server fetch failed in a way a later retry could succeed at, as opposed to a refusal that no retry will change. + pub server_retryable_failure: bool, + /// The sandbox object from a cached server policy that a forced refresh declined to serve in full. Folded back into the effective sandbox layer so an outage during a forced refresh still synthesizes the fail-closed sandbox floor from the administrator's known intent. + #[serde(skip_serializing_if = "Option::is_none")] + pub server_sandbox_floor: Option, + /// A server-managed policy is known to exist even though `serverResponse` is intentionally unset. Consumers should OR this with the presence of `serverResponse` when deciding whether the device is managed, so a fresh start during an outage still applies the fail-closed gates instead of treating the device as unmanaged. + pub server_source_found: bool, + /// No usable token was available, so no server fetch was attempted. + pub server_token_unavailable: bool, + /// The effective managed settings composed from every resolved layer. Omitted when no layer set anything, so an unmanaged host locks nothing. + #[serde(skip_serializing_if = "Option::is_none")] + pub settings: Option, +} + +/// Inputs describing the host and account whose managed remote-control policy should be resolved. Mirrors `managedSettings.ingest`, and deliberately carries no retention decision: reading this gate never becomes this runtime process's startup 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(crate) struct ManagedSettingsLoadRemoteControlParams { + /// The authenticated identity to resolve policy for. Omitted before sign-in, in which case no server layer is fetched. + #[serde(skip_serializing_if = "Option::is_none")] + pub auth_info: Option, + /// Name of the embedding client, reported to the managed-settings service as `client_id` so it can refuse to deliver a policy this client is too old to enforce. That refusal is exactly what turns into an explicit `disabled` below, so a host that omits this field forfeits the protection. + #[serde(skip_serializing_if = "Option::is_none")] + pub client_name: Option, + /// A device layer supplied by the host instead of discovered from the machine. Pair it with `hasDeviceResponse` so an explicit absence is distinguishable from an omitted override. + #[serde(skip_serializing_if = "Option::is_none")] + pub device_response: Option, + /// The host's environment variables, read for cache location and policy overrides. + pub environment: serde_json::Value, + /// Whether `deviceResponse` is an override at all. True with a null `deviceResponse` asserts the device layer is empty; false discovers it from the machine. + #[serde(skip_serializing_if = "Option::is_none")] + pub has_device_response: Option, + /// The host's home directory, used to resolve cache and managed-file locations. + pub home_directory: String, + /// The host's platform, in Node's `process.platform` spelling, used to locate platform-specific cache and policy paths. + pub platform: String, + /// Whether a configured platform policy helper may be run to produce an enforced helper layer. + #[serde(skip_serializing_if = "Option::is_none")] + pub policy_helper_enabled: Option, + /// The policy-helper invocation reason. Defaults to sessionStart. + #[doc(hidden)] + #[serde(skip_serializing_if = "Option::is_none")] + pub(crate) policy_helper_reason: Option, + /// Whether to run ingestion. Omit or set false to return an empty result without discovering or fetching policy. + #[serde(skip_serializing_if = "Option::is_none")] + pub self_fetch: Option, + /// User agent to send with the policy fetch. + pub user_agent: String, +} + +/// The managed remote-control policy in force for an account, if any. An absent policy means the account is unmanaged for this capability, which a host reads as allowed — so absence here is a positive statement that no policy applies, never a stand-in for a resolution that failed. A resolution that cannot be completed is rejected instead. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol 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 ManagedSettingsLoadRemoteControlResult { + /// The account's `remoteControl` policy document. Absent when no managed policy governs remote control for this account. Reported as an explicit disabled policy, rather than as absence, when the service refused to deliver a policy this client is too old to enforce. + #[serde(skip_serializing_if = "Option::is_none")] + pub remote_control: Option, +} + +/// The client-supplied managed settings to validate and normalize. Omitting them is valid and yields no 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(crate) struct ManagedSettingsParseClientValueParams { + /// The managed settings a client is supplying for a session it is about to open. Omit them when the client is imposing no policy of its own. + #[serde(skip_serializing_if = "Option::is_none")] + pub settings: Option, +} + +/// The accepted managed settings, reduced to the keys a client is allowed to 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(crate) struct ManagedSettingsParseClientValueResult { + /// The normalized settings. Omitted when the client supplied none. An accepted document that sets nothing comes back as an empty object, which is distinct from omission. + #[serde(skip_serializing_if = "Option::is_none")] + pub settings_json: Option, +} + +/// The device-managed settings layer, with the sandbox floor and the load outcome a host needs to compose it into an effective 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(crate) struct ManagedSettingsReadDeviceResult { + /// Whether discovery or validation failed. A host must treat this as "policy unknown" rather than "no policy": absent settings with this set false mean the device genuinely has none. + pub load_failed: bool, + /// Sandbox floor read from the discovered document before validation, so a document that is otherwise rejected can still raise the floor it declared. + #[serde(skip_serializing_if = "Option::is_none")] + pub sandbox_floor: Option, + /// Validated, canonical device-managed settings. Omitted when no device policy was discovered, when the discovered document failed validation, or when it validated to an empty object — an empty document carries no policy, and reporting it as present would outrank layers that do. + #[serde(skip_serializing_if = "Option::is_none")] + pub settings: Option, +} + /// Validated device-managed settings discovered before a session exists. /// ///
@@ -11332,6 +12734,115 @@ pub struct ManagedSettingsReadResult { pub settings_json: Option, } +/// The bypass-permissions state a host holds today, the account now signed in, and what the managed-policy resolution said. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol 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 ManagedSettingsResolveBypassDisableTransitionParams { + /// The account signed in now. A `managed` disable pinned to a different account is lifted. + pub active_identity: String, + /// The account a `managed` disable is pinned to. Omit it when the current source is not `managed`; it is ignored for the other sources. + #[serde(skip_serializing_if = "Option::is_none")] + pub current_managed_identity: Option, + /// Which layer currently disables bypass mode. Omit it when nothing does. + #[doc(hidden)] + #[serde(skip_serializing_if = "Option::is_none")] + pub(crate) current_source: Option, + /// What the resolution said about bypass mode. Send `unknown` rather than omitting the field when there is no verdict. + #[doc(hidden)] + pub(crate) policy: ManagedSettingsBypassPolicyResolution, +} + +/// The bypass-permissions state a host should hold after the resolution, and what it must do to get there. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol 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 ManagedSettingsResolveBypassDisableTransitionResult { + /// The account the returned `managed` source is pinned to. Store it alongside `source`; the next call needs it to decide whether an account switch lifts the disable. + #[serde(skip_serializing_if = "Option::is_none")] + pub managed_identity: Option, + /// The value to store in the host's own bypass-disabled flag. Omitted means leave it exactly as it is, which is not the same as being told to store `false`. + #[serde(skip_serializing_if = "Option::is_none")] + pub set_disabled_state: Option, + /// Which layer disables bypass mode after this resolution. Omitted when none does, which is what a lifted disable looks like. + #[doc(hidden)] + #[serde(skip_serializing_if = "Option::is_none")] + pub(crate) source: Option, + /// Whether a bypass-permissions session already running must be turned off. True only when this resolution newly disables bypass mode, so a host that is already compliant is not interrupted. + pub suppress_runtime_allow_all: bool, +} + +/// The credential and environment to evaluate the device-channel sign-in pin against. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol 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 ManagedSettingsResolveDeviceForceLoginOrgsGateParams { + /// The authenticated identity to judge. Omitted when signed out, which is the case the device channel exists to serve: it can refuse a first sign-in before any credential exists. + #[serde(skip_serializing_if = "Option::is_none")] + pub auth_info: Option, + /// The host's environment variables. Read for the variables that would make an identity's membership snapshot forgeable, so a credential whose provenance cannot be trusted is refused rather than accepted. + pub environment: serde_json::Value, +} + +/// The managed-settings layers to compose into an effective policy. Every layer is optional; omitting all of them yields no effective 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(crate) struct ManagedSettingsResolveEffectiveValueParams { + /// The device sandbox floor, folded into the device layer under `sandbox` before composition. + #[serde(skip_serializing_if = "Option::is_none")] + pub device_sandbox_floor: Option, + /// The device layer: native MDM and managed-file settings describing the machine rather than the account. + #[serde(skip_serializing_if = "Option::is_none")] + pub mdm: Option, + /// The policy-helper layer, an enforced channel sourced from a platform policy helper process. + #[serde(skip_serializing_if = "Option::is_none")] + pub policy_helper: Option, + /// The account's server-delivered org policy, as returned by a managed-settings fetch. Omitted before sign-in or when the fetch has not landed. + #[serde(skip_serializing_if = "Option::is_none")] + pub server: Option, +} + +/// The effective managed policy composed from the supplied layers. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol 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 ManagedSettingsResolveEffectiveValueResult { + /// The composed effective managed settings. Omitted when no layer sets anything, so an unmanaged host locks nothing. Carries `sandboxAdvisoryFloorDeclined: true` when an advisory server force-on was dropped because this host cannot run the sandbox. + #[serde(skip_serializing_if = "Option::is_none")] + pub settings_json: Option, +} + /// Optional opaque account selection or GitHub token whose managed settings are resolved. /// ///
@@ -11378,6 +12889,9 @@ pub struct ManagedSettingsResolveResult { /// Per-key lock state and provenance for the entries in `values`, using the same key names. #[serde(skip_serializing_if = "Option::is_none")] pub meta: Option, + /// Reusable source-composed permission policy for managedSettings.permissions.evaluate. Unlike resolved.settings.permissions, this retains every source's allowlist and default-prompt semantics. Refresh through resolve when account/device policy changes. Absent on runtimes that do not support permission evaluation. + #[serde(skip_serializing_if = "Option::is_none")] + pub permissions_context: Option, /// Effective managed settings from the device and account (server) channels, in the same shape as `session.managedSettings.get`, excluding session-local injection. pub resolved: ManagedSettingsResolvedData, /// Typed effective values of managed settings, keyed like the managed-settings schema and already resolved across channels, with the `{ "overridable": ... }` wrapper removed. Present when policy sets at least one typed key. Keys not typed here are available in `resolved.settings`. @@ -11385,6 +12899,172 @@ pub struct ManagedSettingsResolveResult { pub values: Option, } +/// Inputs describing the host and account a startup policy resolution should run for. Mirrors `managedSettings.ingest`, and adds the decision of whether the resolved policy is retained for this runtime 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(crate) struct ManagedSettingsResolveStartupPolicyParams { + /// The authenticated identity to resolve policy for. Omitted before sign-in, in which case no server layer is fetched and the sign-in pin is evaluated against no credential. + #[serde(skip_serializing_if = "Option::is_none")] + pub auth_info: Option, + /// Whether to ignore a cached server policy when reading, forcing the resolution to come from a live fetch. + #[serde(skip_serializing_if = "Option::is_none")] + pub bypass_cache_read: Option, + /// Name of the embedding client, reported to the managed-settings service as `client_id` so it can refuse to deliver a policy this client is too old to enforce. + #[serde(skip_serializing_if = "Option::is_none")] + pub client_name: Option, + /// A device layer supplied by the host instead of discovered from the machine. Pair it with `hasDeviceResponse` so an explicit absence is distinguishable from an omitted override. + #[serde(skip_serializing_if = "Option::is_none")] + pub device_response: Option, + /// The host's environment variables, read for cache location, policy overrides, and the variables that would make a membership snapshot forgeable. + pub environment: serde_json::Value, + /// Whether `deviceResponse` is an override at all. True with a null `deviceResponse` asserts the device layer is empty; false discovers it from the machine. + #[serde(skip_serializing_if = "Option::is_none")] + pub has_device_response: Option, + /// The host's home directory, used to resolve cache and managed-file locations. + pub home_directory: String, + /// The host's platform, in Node's `process.platform` spelling, used to locate platform-specific cache and policy paths. + pub platform: String, + /// Whether a configured platform policy helper may be run to produce an enforced helper layer. + #[serde(skip_serializing_if = "Option::is_none")] + pub policy_helper_enabled: Option, + /// The policy-helper invocation reason. Defaults to sessionStart. + #[doc(hidden)] + #[serde(skip_serializing_if = "Option::is_none")] + pub(crate) policy_helper_reason: Option, + /// Directory the policy helper runs in when this resolution is not owned by a registered session. A host resolving policy before its session exists must pass this, or a directory-sensitive helper judges the wrong repository. + #[serde(skip_serializing_if = "Option::is_none")] + pub policy_helper_working_directory: Option, + /// Whether to retain the resolved policy as this runtime process's startup policy, so later work in the process enforces it. Set false to resolve without side effects, which is what a protocol host wants before session creation: its sessions apply the complete policy through the normal lifecycle, and retaining here would let a readiness credential affect unrelated sessions. The returned verdict is identical either way — this decides only whether the process keeps it. + pub retain_and_apply: bool, + /// Whether to run ingestion. Omit or set false to return an empty result without discovering or fetching policy. + #[serde(skip_serializing_if = "Option::is_none")] + pub self_fetch: Option, + /// How this resolution may use the on-disk server-policy cache. Defaults to the runtime's own policy when omitted. Rejected when combined with `bypassCacheRead`, because the two express the same decision and accepting both would leave the caller unsure which one the runtime honored. + #[doc(hidden)] + #[serde(skip_serializing_if = "Option::is_none")] + pub(crate) server_policy_cache_mode: Option, + /// The session whose credential store, working directory and client kind the resolution should use. Omit it when no session exists yet and the process-wide store applies. A resolution whose session names a credential other than `authInfo` is returned but not retained, so a stale sign-in cannot apply policy for an account that has already been switched away from. + #[serde(skip_serializing_if = "Option::is_none")] + pub session_id: Option, + /// User agent to send with the policy fetch. + pub user_agent: String, +} + +/// The startup policy a resolution composed, the per-layer outcome flags a host needs to decide how much to trust it, and the enterprise sign-in verdict for the credential it ran against. The flags are reported rather than folded into the settings because a host must distinguish an unmanaged device from a managed device whose policy could not be confirmed, and apply its fail-closed gates in the second 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, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub(crate) struct ManagedSettingsResolveStartupPolicyResult { + /// Advisory minimum client version from a successful server response. + #[serde(skip_serializing_if = "Option::is_none")] + pub client_update_requested: Option, + /// The server refused to deliver a policy because this client is too old to enforce it, carrying the user-facing sentence describing the refusal. No retry will help; the remedy is a client update. + #[serde(skip_serializing_if = "Option::is_none")] + pub client_update_required: Option, + /// The device layer could not be read, so it is unconfirmed rather than known-absent. + pub device_load_failed: bool, + /// The device layer discovered from the machine, when one exists. + #[serde(skip_serializing_if = "Option::is_none")] + pub device_response: Option, + /// The sandbox floor contributed by the device layer. + #[serde(skip_serializing_if = "Option::is_none")] + pub device_sandbox_floor: Option, + /// The enterprise sign-in verdict for the credential this resolution ran against, evaluated over the effective policy so a server-delivered pin is honored alongside a device one. Computed on every resolution, including one that was not retained, so a host that declines to apply policy still cannot proceed past a pin. + #[doc(hidden)] + pub(crate) force_login_orgs: ForceLoginOrgsDecision, + /// A managed policy is known to apply to this host, from a discovered source or a resolved document. + pub managed_policy_present: bool, + /// The permissions layer specifically could not be confirmed, so permission gates apply their fail-closed posture. + pub permission_policy_undetermined: bool, + /// The policy helper was configured but could not be run, or its output could not be read. + pub policy_helper_load_failed: bool, + /// The layer produced by the platform policy helper, when one ran. + #[serde(skip_serializing_if = "Option::is_none")] + pub policy_helper_response: Option, + /// Some layer of the policy could not be confirmed, so its absence must not be read as permission. Folds in a failed server fetch as well as an unreadable device layer. + pub policy_undetermined: bool, + /// The sandbox was enabled because policy could not be confirmed rather than because an administrator asked for it. Lets a host explain a sandbox the user did not configure. + pub sandbox_enabled_by_undetermined_policy: bool, + /// The effective sandbox floor composed across every layer, which a host must apply rather than its own default. + #[serde(skip_serializing_if = "Option::is_none")] + pub sandbox_floor: Option, + /// The server fetch failed and no cached entry stood in for it, so the server layer is unconfirmed and fail-closed gates apply. + pub server_fetch_failed: bool, + /// How the current runtime resolved the server layer. + #[doc(hidden)] + pub(crate) server_resolution: ManagedSettingsServerPolicyResolution, + /// The account's server-delivered policy, when one was resolved. + #[serde(skip_serializing_if = "Option::is_none")] + pub server_response: Option, + /// Telemetry describing which layers answered and how, for the host to report alongside its own startup events. + pub telemetry: serde_json::Value, + /// Warnings raised while resolving, in the order they occurred. + pub warnings: Vec, +} + +/// The managed layers to retain as this process's startup policy, with the outcome of fetching each 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(crate) struct ManagedSettingsRetainPolicyParams { + /// Whether the device load failed. Report it honestly, for the same reason as `serverFetchFailed`. + pub device_load_failed: bool, + /// The device/MDM policy layer. + #[serde(skip_serializing_if = "Option::is_none")] + pub device_response: Option, + /// Sandbox floor declared by the device layer, carried separately so a document that failed validation can still raise the floor. + #[serde(skip_serializing_if = "Option::is_none")] + pub device_sandbox_floor: Option, + /// Whether the device sandbox intent is unreadable independently of its other settings. + pub device_sandbox_undetermined: bool, + /// Whether a device policy source demonstrably exists. This, not a read error, is the evidence a device is managed, so a fail-closed policy gates on it. + pub device_source_found: bool, + /// Whether any managed policy source exists, independently of fetch failures. + pub managed_policy_present: bool, + /// Whether permission policy is undetermined. Sandbox-only uncertainty does not imply this flag. + pub permission_policy_undetermined: bool, + /// The policy helper's response: the third managed layer that a process owning its startup policy resolves. It is retained with the other two so that every ambient read in this process, including the mutation guards and the governed marketplace allowlist that the `plugins.*` handlers derive, sees the whole policy the process fetched. A session-owning process omits it, because it resolves a helper per session against that session's working directory. + #[serde(skip_serializing_if = "Option::is_none")] + pub policy_helper_response: Option, + /// Preserve the retained server layer during a device-only resolution. + pub preserve_server_state: bool, + /// Whether uncertainty alone enabled the shared sandbox floor. + pub sandbox_enabled_by_undetermined_policy: bool, + /// Whether the shared sandbox policy is undetermined. Diagnostic errors alone do not imply this flag. + pub sandbox_floor_undetermined: bool, + /// Whether the server fetch failed. Report it honestly: true keeps the already-retained server layer rather than replacing it with an absent one. + pub server_fetch_failed: bool, + /// Whether a required fresh server-policy confirmation failed. + pub server_forced_refresh_failure: bool, + /// The resolved server policy. A known source can lack a response after a forced-refresh failure. + #[serde(skip_serializing_if = "Option::is_none")] + pub server_response: Option, + /// Whether the server policy source exists, even when its response is unavailable. + pub server_source_found: bool, + /// Shared server/device sandbox floor, excluding session-local helper policy. + #[serde(skip_serializing_if = "Option::is_none")] + pub shared_sandbox_floor: Option, +} + /// The authoring JSON schema for managed settings recognized by this runtime. /// ///
@@ -12902,6 +14582,21 @@ pub struct McpIsServerRunningResult { pub running: bool, } +/// Names of the MCP servers the runtime supplies itself. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol 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 McpListBuiltinServerNamesResult { + /// Every built-in server name. A host disabling the built-ins wholesale suppresses exactly this set. + pub names: Vec, +} + /// Server name whose tool list should be returned. /// ///
@@ -13926,6 +15621,91 @@ pub(crate) struct McpRegisterExternalClientRequest { pub(crate) transport: serde_json::Value, } +/// Registry search to abandon. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol 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 McpRegistryCancelRequest { + /// Request ID from `mcp.registry.allocateRequestId` that the search uses. + pub request_id: i64, +} + +/// Whether the cancel reached a live search. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol 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 McpRegistryCancelResult { + /// True when the cancel stopped a running search. False for unknown or reclaimed IDs, completed or canceled searches, and unused reservations. The cancel releases an unused reservation. + pub canceled: bool, +} + +/// Request id naming a cancellable registry search. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol 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 McpRegistryRequestIdResult { + /// Request ID for `mcp.registry.search` and `mcp.registry.cancel`. It serves one search. Allocation can reclaim unused IDs at the 1,024-ID reservation limit. + pub request_id: i64, +} + +/// Registry search terms, the credential to search under, and the request id that makes the search cancellable. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol 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 McpRegistrySearchRequest { + /// The credential the search runs under, carried opaquely. Hosts usually send credential-free `AuthIdentity`; a `token` identity can be resolved only when it embeds a token, while `env` and `gh-cli` identities can use a token embedded in the request first. + pub auth_info: serde_json::Value, + /// Maximum number of servers to return. + pub limit: i64, + /// Free-text query. Omitted or empty asks the registry for its top servers rather than searching. A value that is not a string is refused. + #[serde(skip_serializing_if = "Option::is_none")] + pub query: Option, + /// Repository used for the policy lookup, as `owner/name`. The policy selects the registry URL and whether the user token goes to the registry. The registry receives this repository only when the policy entry lists it as required context. A value that is not a string is refused. + #[serde(skip_serializing_if = "Option::is_none")] + pub repository: Option, + /// Request ID from `mcp.registry.allocateRequestId`. The search refuses unknown, reclaimed, or canceled IDs and IDs that another search or request already uses. + pub request_id: i64, +} + +/// Servers selected from the registry 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(crate) struct McpRegistrySearchResult { + /// The server objects, carried opaquely. The runtime follows pages, keeps the newest entry per server name, cuts the list to `limit`, and sorts an empty-query result by GitHub stars. Each server object remains unchanged because the registry owns that shape. + pub servers: serde_json::Value, +} + /// In-process MCP reload configuration. /// ///
@@ -14746,6 +16526,43 @@ pub struct McpSetEnvValueModeResult { pub mode: McpSetEnvValueModeDetails, } +/// The session tool filters that decide whether the session still has a shell to run `gh` with. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol 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 McpShouldExcludeGitHubToolsRequest { + /// The session's tool allowlist, when it set one. Omitted means the session constrains nothing this way. + #[serde(skip_serializing_if = "Option::is_none")] + pub available_tools: Option>, + /// The session's tool denylist, when it set one. Omitted means the session constrains nothing this way. + #[serde(skip_serializing_if = "Option::is_none")] + pub excluded_tools: Option>, + /// How the allowlist and denylist combine when both are set. Omitted means the default every session gets, so a caller that never chose a precedence is answered as its sessions behave. + #[serde(skip_serializing_if = "Option::is_none")] + pub tool_filter_precedence: Option, +} + +/// Whether the gh-replaceable GitHub MCP tools may be clipped for the session described by 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, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub(crate) struct McpShouldExcludeGitHubToolsResult { + /// True only when both halves hold: the session's filters still reach the platform shell tool, and the host actually has the `gh` those tools would be replaced by. Feed it straight back as the `excludeGhReplaceableTools` build option. + pub exclude_gh_replaceable_tools: bool, +} + /// 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. /// ///
@@ -15758,6 +17575,55 @@ pub(crate) struct ModelClearStartupSeedResult { pub cleared: bool, } +/// Host presentation state the reasoning-summaries policy depends on. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ModelGetReasoningSummariesRequest { + /// The host's persisted `showReasoning` user setting, omitted when unset. + #[serde(skip_serializing_if = "Option::is_none")] + pub persisted_show_reasoning: Option, + /// Whether the host is currently showing reasoning summaries. Defaults to false. + #[serde(skip_serializing_if = "Option::is_none")] + pub summaries_visible: Option, +} + +/// Reasoning-summaries presentation policy for the session's current 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 ModelGetReasoningSummariesResult { + /// Whether the supplied `persistedShowReasoning` setting only restates the model default. This reports a redundancy, not a standing instruction to delete the setting: it stays true for as long as the setting and the model default agree, including for a user who later chose that value deliberately. It exists for a one-time migration of a legacy setting written before the model default made it redundant, so a host that acts on it owns the cleanup and must gate it behind its own persisted `done` marker, delete only a setting whose value still matches the one it read, and never repeat the deletion on a later launch. Under an off-by-default reasoning-summaries experiment an explicit `showReasoning: true` opt-in also reads as redundant on a summary-producing model, so an ungated host would keep deleting that opt-in and hide summaries again on every launch. + pub clear_redundant_setting: bool, + /// Whether the session's reasoning-summaries experiment assignment has settled. False means this read does not wait for a pending assignment and reports the experiment-derived fields conservatively: `showHiddenHint` is false and `visibility` falls back to `persistedShowReasoning`, or is omitted when none was supplied. A host that wants the assigned answer re-reads until this is true. + pub experiment_settled: bool, + /// Whether the selected model shows reasoning summaries by default. + pub model_default_shows_summaries: bool, + /// False until the session has a model catalog and a selected model. This read never resolves the catalog itself, and the runtime emits no readiness notification, so repeating this call alone leaves `ready` false indefinitely on a session that has neither listed models nor run a turn. `session.model.list` is the prerequisite and the signal: it resolves and caches the catalog for the session, so a successful return means the next read is ready. The exception is a session that resolves models without a Copilot catalog, that is a BYOK registry or an alternate provider: `session.model.list` returns an empty list without caching a catalog, so `ready` stays false and this policy is unavailable there. While false, every model-policy field is false and `visibility` is omitted; `experimentSettled` is independent of readiness and still reports the assignment state. + pub ready: bool, + /// Whether turns on this session produce reasoning summaries, honouring an explicit session reasoning-summary override. + pub session_shows_summaries: bool, + /// Whether to show the hint that reasoning summaries are hidden. Always false while `experimentSettled` is false. + pub show_hidden_hint: bool, + /// Whether the selected model emits reasoning headers. + pub show_reasoning_headers: bool, + /// Default reasoning visibility, and only meaningful once `ready` is true: false when the session produces no summaries, else the persisted setting, else the experiment's default. While `ready` is true it is omitted only when all three are unavailable, that is when the session produces summaries, no `persistedShowReasoning` was supplied, and `experimentSettled` is false. While `ready` is false there is no model policy to report and this field is always omitted, so an absent value alone does not imply a pending experiment. + #[serde(skip_serializing_if = "Option::is_none")] + pub visibility: Option, +} + /// List of Copilot models available to the resolved user, including capabilities and billing metadata. /// ///
@@ -18213,6 +20079,41 @@ pub struct PermissionsConfigureResult { pub success: bool, } +/// The verbatim managed `permissions` slice to interpret. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol 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 PermissionsEvaluateManagedPolicyParams { + /// The `permissions` object from a managed settings layer, for example `{"disableBypassPermissionsMode":"disable"}`. Omitted or null means no managed policy applies. + #[serde(skip_serializing_if = "Option::is_none")] + pub permissions: Option, +} + +/// The policy questions a host asks before granting elevated permissions. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct PermissionsEvaluateManagedPolicyResult { + /// True when managed policy forbids bypass-permissions mode, so allow-all style flags must not take effect. + pub bypass_permissions_disabled: bool, + /// True when the managed layer supplies permission rules that the session must enforce. + pub permission_rules_active: bool, + /// True when a managed policy exists but this build could not determine its meaning. Hosts fail closed on it rather than assuming permission was granted. + pub policy_undetermined: bool, +} + /// Indicates whether the operation succeeded. /// ///
@@ -18540,6 +20441,44 @@ pub struct PermissionUrlsSetUnrestrictedModeParams { pub enabled: bool, } +/// A complete JSON snapshot to write under the runtime's per-path file lock. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct PersistenceWriteRawJsonLockedRequest { + /// The serialized JSON document to write. It replaces the file's contents entirely; nothing is merged. + pub data_json: String, + /// Comment lines to emit above the document, for a format that tolerates them. Omit for strict JSON. + #[serde(skip_serializing_if = "Option::is_none")] + pub file_header: Option, + /// Absolute path of the file to replace. Parent directories are created when missing. + pub path: String, +} + +/// The outcome of a locked snapshot write. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct PersistenceWriteRawJsonLockedResult { + /// Why the write failed. Absent when `success` is true. A lock the runtime could not acquire in time reports here rather than raising. + #[serde(skip_serializing_if = "Option::is_none")] + pub error: Option, + /// Whether the file now holds the requested contents. + pub success: bool, +} + /// Optional message to echo back to the caller. /// ///
@@ -20865,6 +22804,110 @@ pub struct RemoteSessionRepository { pub owner: String, } +/// One atomic edit of an allowed top-level repository setting. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol 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 RepoSettingsEditRequest { + /// One canonical top-level repository setting that the allowlist permits. + pub key: String, + /// Set a scalar, add or remove a string list item, or remove the setting. The handler rejects a missing or incompatible value. + #[doc(hidden)] + pub(crate) operation: SettingsEditOperation, + /// Absolute path of the repository root. + pub repo_root: String, + /// Shared or local repository settings file. + #[doc(hidden)] + pub(crate) scope: RepoSettingsEditScope, + /// Required boolean, string or number for set, required string for add-item and remove-item, and forbidden for remove. The runtime rejects all other JSON values. + #[serde(skip_serializing_if = "Option::is_none")] + pub value: Option, +} + +/// Which repository settings 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(crate) struct RepoSettingsReadRawRequest { + /// Absolute path of the repository root. + pub repo_root: String, + /// Use repo for shared settings or local for settings.local.json. Other scopes are rejected. + pub scope: String, +} + +/// Parsed repository settings without normalization or layering. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol 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 RepoSettingsReadRawResult { + /// Stored JSON values after the existing JSONC parser and prototype-safety checks. Empty when the file is missing, blank, or invalid. + pub config: serde_json::Value, + /// True when the file exists but did not parse, so `config` is empty for that reason rather than because nothing is set. + pub invalid: bool, +} + +/// The complete set of repository-overridable settings to store. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol 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 RepoSettingsWriteAllRequest { + /// Absolute path of the repository root. + pub repo_root: String, + /// Use repo for shared settings or local for settings.local.json. Other scopes are rejected. + pub scope: String, + /// Object of allowed settings to store. Missing allowlisted keys are removed. Other input keys are ignored, while unknown stored keys are preserved. + pub settings: serde_json::Value, +} + +/// One key to write or remove in a repository settings 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(crate) struct RepoSettingsWriteKeyRequest { + /// True to remove the key instead of writing it. + #[serde(skip_serializing_if = "Option::is_none")] + pub delete: Option, + /// Canonical top-level key permitted by the repository settings allowlist. + pub key: String, + /// Absolute path of the repository root. + pub repo_root: String, + /// Use repo for shared settings or local for settings.local.json. Other scopes are rejected. + pub scope: String, + /// Value to validate and write. Required unless delete is true; a null value is validated, not treated as deletion. + #[serde(skip_serializing_if = "Option::is_none")] + pub value: Option, +} + /// Provider-native structured output format. JSON Schema is forwarded without rewriting or validating the schema or the generated output. /// ///
@@ -20882,6 +22925,21 @@ pub struct ResponseFormat { pub r#type: ResponseFormatType, } +/// The stored sandbox settings to resolve against the defaults. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SandboxApplyDefaultsRequest { + /// The sandbox settings as stored in the settings file. An empty object resolves to the defaults alone. + pub stored: serde_json::Value, +} + /// 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. /// ///
@@ -21125,6 +23183,38 @@ pub struct SandboxConfig { pub user_policy: Option, } +/// A possible secret-bearing environment variable and optional locally suggested HTTPS injection hosts. This is a draft for user review, not an active grant. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SandboxCredentialSuggestion { + /// Environment variable name. The secret value is never returned. + pub name: String, + /// HTTPS hostnames suggested by a local known-provider mapping. An empty list requires the user to supply injection hosts before adding a masking entry. These suggestions do not allow network access. + pub suggested_inject_hosts: Vec, +} + +/// Possible unconfigured secrets in the sandbox shell environment, sorted by variable name, with no secret values. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SandboxCredentialSuggestionsResult { + /// Candidates for user review. Empty when sandboxing is disabled or no unconfigured candidates are present. + pub suggestions: Vec, +} + /// Request to disable sandboxing for the current session while resolving an active sandbox-bypass permission prompt. /// ///
@@ -21160,6 +23250,71 @@ pub struct SandboxDisableForSessionResult { pub success: bool, } +/// The documentation URL a host cites when it declines or downgrades a sandbox setting. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SandboxDocsUrlResult { + /// Documentation URL describing the sandbox and the settings that control it. + pub url: String, +} + +/// A sandbox config and the per-feature routing flag to resolve it 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(crate) struct SandboxEffectiveForOptionalParams { + /// The routing flag naming the feature being launched, such as `sandboxMcpServers` or `sandboxLspServers`. + pub routing_flag: String, + /// The session's resolved `sandbox` slice. Omitted when the host has none, which yields a disabled sandbox rather than an error. + #[serde(skip_serializing_if = "Option::is_none")] + pub sandbox: Option, +} + +/// The sandbox a named feature actually launches under. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol 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 SandboxEffectiveForOptionalResult { + /// The effective sandbox config for the requested feature, or a disabled sandbox when the feature is routed out of it. + pub sandbox_json: serde_json::Value, +} + +/// One slash command, as typed, to test against the enforcement-block allowlist. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol 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 SandboxEnforcementCommandAllowedParams { + /// The command's arguments, in order. Pass an empty array for a bare command: several entries in the allowlist admit the bare form and refuse every argument. + pub args: Vec, + /// The command name as the user typed it, with or without its leading slash. + pub command_name: String, +} + /// Managed sandbox enforcement state for a session. /// ///
@@ -21180,6 +23335,59 @@ pub struct SandboxEnforcementStatus { pub required: bool, } +/// One file-picker suggestion offered up for the sandbox filesystem check. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol 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 SandboxPickerCandidate { + /// Whether the suggestion is a directory. A directory the policy does not itself allow may still be needed as a waypoint to an allowed subtree. + pub is_directory: bool, + /// Absolute path the suggestion would attach. + pub path: String, +} + +/// A batch of file-picker candidates to check against a prepared 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(crate) struct SandboxFilterPickerCandidatesParams { + /// The candidates to check, in the order the answers are returned. + #[doc(hidden)] + pub(crate) candidates: Vec, + /// The session's working directory, matching the one the policy was prepared from. + pub cwd: String, + /// The policy returned by `sandbox.preparePickerPolicy`. Omitted keeps every candidate. + #[serde(skip_serializing_if = "Option::is_none")] + pub sandbox: Option, +} + +/// Per-candidate allow decisions, positionally aligned with 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, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub(crate) struct SandboxFilterPickerCandidatesResult { + /// One decision per candidate, in the same order. Always the same length as the request's `candidates`. + pub allowed: Vec, +} + /// Request to accept the sandbox path grant offered on an active sandbox escalation permission prompt. /// ///
@@ -21253,6 +23461,40 @@ pub struct SandboxHostSupport { pub supported: bool, } +/// The working directory and sandbox settings to build a file-picker policy from. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol 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 SandboxPreparePickerPolicyParams { + /// The session's working directory. Git discovery and grant canonicalization are resolved against it. + pub cwd: String, + /// The session's resolved `sandbox` slice. Omitted, or disabled, yields no policy and every candidate is kept. + #[serde(skip_serializing_if = "Option::is_none")] + pub sandbox: Option, +} + +/// The prepared file-picker policy, or none when the sandbox is disabled. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol 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 SandboxPreparePickerPolicyResult { + /// The prepared policy to pass back to `sandbox.filterPickerCandidates`. Absent when the sandbox is disabled or absent, in which case filtering keeps every candidate. + #[serde(skip_serializing_if = "Option::is_none")] + pub sandbox_json: Option, +} + /// Result of creating the persistent certificate authority of the sandbox credential proxy. /// ///
@@ -21304,6 +23546,138 @@ pub struct SandboxProxyCaStatus { pub state: SandboxProxyCaState, } +/// The stored sandbox proxy password, if one is present. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SandboxProxySecretGetResult { + /// The stored password. Absent when no entry exists in either the keychain or the on-disk fallback store. + #[serde(skip_serializing_if = "Option::is_none")] + pub value: Option, +} + +/// Where this runtime keeps the sandbox proxy password. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SandboxProxySecretLocation { + /// Absolute path of the credential store holding the sandbox proxy password. The proxy password belongs to no MCP server, so it is kept in its own directory rather than in the MCP secret store. + pub config_dir: String, + /// Identifier of the proxy password entry within that store. Derived from the configuration directory, so two Copilot homes on one machine never share an entry. Use it to recognize a `${secret:…}` reference in stored settings as this runtime's own proxy password. + pub secret_id: String, +} + +/// The sandbox proxy password to store. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SandboxProxySecretSaveRequest { + /// The password to store for the sandbox proxy. + pub value: String, +} + +/// Where the sandbox proxy password was stored. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SandboxProxySecretSaveResult { + /// Whether the OS keychain accepted the value. False means it was written to the on-disk fallback store instead, which is still readable by `sandbox.proxySecret.get`. + pub stored_in_keychain: bool, +} + +/// The user's sandbox settings and the managed sandbox floor to raise them 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(crate) struct SandboxResolveFlooredConfigParams { + /// The effective managed `sandbox` slice acting as a floor. Omitted when no administrator policy applies, in which case the user's own settings stand unchanged. + #[serde(skip_serializing_if = "Option::is_none")] + pub managed_sandbox: Option, + /// The user's own `sandbox` settings slice. Omitted when the user has configured none, which still yields the managed floor rather than a disabled sandbox. + #[serde(skip_serializing_if = "Option::is_none")] + pub user_sandbox: Option, +} + +/// The user's sandbox settings raised to the managed floor. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SandboxResolveFlooredConfigResult { + /// The floored sandbox config, normalized through the declared sandbox shape so only declared fields in a fixed order are present. + pub sandbox_json: serde_json::Value, +} + +/// The managed sandbox policy in force, which selects which remedy the notice offers. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol 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 SandboxUnsupportedStartupNoticeParams { + /// True when the sandbox stayed on only because the managed policy could not be determined. Distinguishes a policy that positively enforces the sandbox from one that merely failed to resolve, which is the difference between telling the user to contact an administrator and telling them to wait. Defaults to false. + #[serde(skip_serializing_if = "Option::is_none")] + pub enabled_by_undetermined_policy: Option, + /// The managed `sandbox` slice, when a policy supplies one. The notice treats the sandbox as enforced only when this both enables it and sets `failIfUnavailable`, because a managed sandbox that tolerates being unavailable still leaves the user able to turn it off. + #[serde(skip_serializing_if = "Option::is_none")] + pub managed_sandbox: Option, +} + +/// The startup warning, absent when the machine can run a sandbox. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol 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 SandboxUnsupportedStartupNoticeResult { + /// The user-facing warning, carrying the platform remedy and the recovery the policy in force actually allows. Absent when this machine supports a sandbox, so a host can call unconditionally and print only what it gets. + #[serde(skip_serializing_if = "Option::is_none")] + pub notice: Option, +} + /// Register an absolute-time scheduled prompt. /// ///
@@ -23344,6 +25718,9 @@ pub struct SessionOpenOptions { /// Runtime context discriminator for agent filtering. #[serde(skip_serializing_if = "Option::is_none")] pub agent_context: Option, + /// Explicit discovery-only agent roots, separate from customAgents and filesystem grants. Available without enableConfigDiscovery, subject to managed/source disables. Relative SDK paths resolve once when supplied against the request's effective workingDirectory; retained roots do not move on cwd changes. Null and omission are equivalent; [] clears only the explicit SDK contribution. + #[serde(skip_serializing_if = "Option::is_none")] + pub agent_directories: 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, @@ -23424,7 +25801,7 @@ pub struct SessionOpenOptions { /// 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. + /// Whether skill loading is enabled. When omitted, explicit SDK skill roots or an SDK skill provider enable skills independently of enableConfigDiscovery. #[serde(skip_serializing_if = "Option::is_none")] pub enable_skills: Option, /// Whether model responses stream as delta events. @@ -23466,6 +25843,9 @@ pub struct SessionOpenOptions { #[doc(hidden)] #[serde(skip_serializing_if = "Option::is_none")] pub(crate) has_skill_provider: Option, + /// Explicit discovery-only file-hook roots, separate from callback hooks and filesystem grants. Available without enableConfigDiscovery or enableFileHooks, which controls repository-origin hooks only; hook/managed disables still apply. Relative SDK paths resolve once when supplied against the request's effective workingDirectory; retained roots do not move on cwd changes. Null and omission are equivalent; [] clears only the explicit SDK contribution. + #[serde(skip_serializing_if = "Option::is_none")] + pub hook_directories: Option>, /// Skill scan directories and descendants excluded from discovery. Supports `~`-relative paths. #[serde(skip_serializing_if = "Option::is_none")] pub ignored_skills_locations: Option>, @@ -23478,9 +25858,15 @@ pub struct SessionOpenOptions { /// Installed plugins visible to the session. #[serde(skip_serializing_if = "Option::is_none")] pub installed_plugins: Option>, + /// Explicit discovery-only instruction roots using existing SDK instruction formats, not filesystem grants. Available without enableConfigDiscovery, subject to instruction/managed disables. Relative SDK paths resolve once when supplied against the request's effective workingDirectory; retained roots do not move on cwd changes. Null and omission are equivalent; [] clears only the explicit SDK contribution. + #[serde(skip_serializing_if = "Option::is_none")] + pub instruction_directories: Option>, /// Stable integration identifier for analytics. #[serde(skip_serializing_if = "Option::is_none")] pub integration_id: Option, + /// Declares that a human drives this session's conversation, enabling user-message analysis (sentiment, task completion criteria, and task analytics) for user-authored messages. Automated messages remain excluded. Defaults to false for SDK sessions. Applied when the session is created or loaded from storage; ignored when resuming a session that is already active, and for cloud sessions. + #[serde(skip_serializing_if = "Option::is_none")] + pub interactive_user: Option, /// Whether experimental behavior is enabled. #[serde(skip_serializing_if = "Option::is_none")] pub is_experimental_mode: Option, @@ -23595,7 +25981,7 @@ pub struct SessionOpenOptions { /// 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. + /// Explicit discovery-only skill roots, not filesystem grants. Available without enableConfigDiscovery, subject to skill/managed disables. Relative SDK paths resolve once when supplied against the request's effective workingDirectory; retained roots do not move on cwd changes. Null and omission are equivalent; [] clears only the explicit SDK contribution. #[serde(skip_serializing_if = "Option::is_none")] pub skill_directories: Option>, /// Whether to skip custom instruction sources. @@ -23884,6 +26270,37 @@ pub struct SessionPluginsInstallRequest { pub source: String, } +/// Plugins required by the session's managed settings to install or update again. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionPluginsRetryManagedRequest { + /// "plugin@marketplace" specs to retry. Omit to retry every plugin the session's managed settings require. + #[serde(skip_serializing_if = "Option::is_none")] + pub plugins: Option>, +} + +/// Per-plugin outcomes of retrying the plugins required by the session's managed settings. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionPluginsRetryManagedResult { + /// One entry per requested plugin, in request order. When no plugins were requested, one entry per plugin the managed settings require. + pub plugins: Vec, +} + /// Outcome of the prune operation: deleted IDs, dry-run candidates, skipped IDs, total bytes freed, and the dry-run flag. /// ///
@@ -25186,6 +27603,9 @@ pub struct SessionUpdateOptionsParams { /// Runtime context discriminator (e.g., `cli`, `actions`). #[serde(skip_serializing_if = "Option::is_none")] pub agent_context: Option, + /// Explicit discovery-only agent roots, separate from customAgents and filesystem grants. Available without enableConfigDiscovery, subject to managed/source disables. Relative SDK paths resolve once when supplied against the request's effective workingDirectory; retained roots do not move on cwd changes. Null and omission are equivalent; [] clears only the explicit SDK contribution. + #[serde(skip_serializing_if = "Option::is_none")] + pub agent_directories: 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, @@ -25222,7 +27642,7 @@ pub struct SessionUpdateOptionsParams { /// 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. + /// Whether to enable repository-origin file hooks from `.github/hooks/` and trusted repository-configured roots. Does not control explicit hookDirectories or SDK callback hooks. #[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). @@ -25240,7 +27660,7 @@ pub struct SessionUpdateOptionsParams { /// 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. + /// Whether skill loading is enabled. Explicit false disables every source, including explicit SDK roots and a bound SDK provider; changing the value invalidates the loaded skill snapshot. When omitted, creation falls back to enableConfigDiscovery unless explicit SDK skill roots or an SDK skill provider are supplied. #[serde(skip_serializing_if = "Option::is_none")] pub enable_skills: Option, /// Whether to stream model responses. @@ -25264,6 +27684,9 @@ pub struct SessionUpdateOptionsParams { /// Map of feature-flag IDs to their boolean enabled state. #[serde(skip_serializing_if = "Option::is_none")] pub feature_flags: Option>, + /// Explicit discovery-only file-hook roots, separate from callback hooks and filesystem grants. Available without enableConfigDiscovery or enableFileHooks, which controls repository-origin hooks only; hook/managed disables still apply. Relative SDK paths resolve once when supplied against the request's effective workingDirectory; retained roots do not move on cwd changes. Null and omission are equivalent; [] clears only the explicit SDK contribution. + #[serde(skip_serializing_if = "Option::is_none")] + pub hook_directories: Option>, /// Skill scan directories and descendants excluded from discovery. Supports `~`-relative paths. #[serde(skip_serializing_if = "Option::is_none")] pub ignored_skills_locations: Option>, @@ -25276,6 +27699,9 @@ pub struct SessionUpdateOptionsParams { /// 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>, + /// Explicit discovery-only instruction roots using existing SDK instruction formats, not filesystem grants. Available without enableConfigDiscovery, subject to instruction/managed disables. Relative SDK paths resolve once when supplied against the request's effective workingDirectory; retained roots do not move on cwd changes. Null and omission are equivalent; [] clears only the explicit SDK contribution. + #[serde(skip_serializing_if = "Option::is_none")] + pub instruction_directories: Option>, /// Stable integration identifier used for analytics and rate-limit attribution. #[serde(skip_serializing_if = "Option::is_none")] pub integration_id: Option, @@ -25345,7 +27771,7 @@ pub struct SessionUpdateOptionsParams { /// 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. + /// Explicit discovery-only skill roots, not filesystem grants. Available without enableConfigDiscovery, subject to skill/managed disables. Relative SDK paths resolve once when supplied against the request's effective workingDirectory; retained roots do not move on cwd changes. Null and omission are equivalent; [] clears only the explicit SDK contribution. #[serde(skip_serializing_if = "Option::is_none")] pub skill_directories: Option>, /// Whether to skip loading custom instruction sources. @@ -25389,6 +27815,22 @@ pub struct SessionUpdateOptionsResult { pub success: bool, } +/// Outcome of an atomic user or repository settings edit. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol 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 SettingsEditResult { + /// Changed, unchanged or invalid existing file. Rejected input, validation failures and I/O errors remain RPC errors. + #[doc(hidden)] + pub(crate) status: SettingsEditStatus, +} + /// User-requested shell execution cancellation handle. /// ///
@@ -25975,6 +28417,25 @@ pub struct SkillList { pub skills: Vec, } +/// Side-effect-free planning of one Skill from a materialisation reference rather than a search candidate. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SkillPlanInstallFromReferenceRequest { + /// Required authenticated bound catalogue, skill-confirmed-installation and skill-reference-installation capabilities. + pub contract: CatalogClientContract, + /// Existing local session attached to this connection. + pub policy_session_id: String, + /// Materialisation reference to verify and plan. + pub reference: CatalogMaterializationReference, +} + /// Side-effect-free planning of one verified Agent Finder Skill candidate. /// ///
@@ -26153,7 +28614,7 @@ pub struct SkillsDiscoverRequest { /// Optional list of project directory paths to scan for project-scoped skills #[serde(skip_serializing_if = "Option::is_none")] pub project_paths: Option>, - /// Optional list of additional skill directory paths to include + /// Explicit discovery-only skill roots, not filesystem grants. Available without ambient discovery, subject to skill/managed disables. Relative paths resolve against the request's effective working directory when supplied. Null and omission are equivalent; an empty array contributes no explicit roots. #[serde(skip_serializing_if = "Option::is_none")] pub skill_directories: Option>, } @@ -26216,6 +28677,70 @@ pub struct SkillsGetDiscoveryPathsRequest { pub project_paths: Option>, } +/// Extensible [`SkillsGetDiscoveryPathsRequest`], including inputs added after it was published. +/// +/// Required inputs are [`SkillsGetDiscoveryPathsOptions::new`] arguments; optional inputs have fluent setters. +/// Input-only: it serialises to the flat wire request and is not deserialisable. +/// +///
+/// +/// **Experimental.** This 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)] +#[serde(rename_all = "camelCase")] +pub struct SkillsGetDiscoveryPathsOptions { + #[serde(flatten)] + legacy: SkillsGetDiscoveryPathsRequest, + #[serde(skip_serializing_if = "Option::is_none")] + skill_directories: Option>, +} + +impl SkillsGetDiscoveryPathsOptions { + /// Creates options with the required inputs. + pub fn new() -> Self { + Self { + legacy: SkillsGetDiscoveryPathsRequest { + project_paths: None, + ignored_skills_locations: None, + exclude_host_skills: None, + }, + skill_directories: None, + } + } + + /// Optional list of project directory paths. When omitted or empty, only personal and custom directories are returned. + pub fn project_paths(mut self, value: Vec) -> Self { + self.legacy.project_paths = Some(value); + self + } + + /// Optional skill scan paths to exclude from discovery. + pub fn ignored_skills_locations(mut self, value: Vec) -> Self { + self.legacy.ignored_skills_locations = Some(value); + self + } + + /// When true, omit the host's personal and custom skill directories, leaving only project directories. For multitenant deployments. + pub fn exclude_host_skills(mut self, value: bool) -> Self { + self.legacy.exclude_host_skills = Some(value); + self + } + + /// Explicit discovery-only skill roots to enumerate, including configured missing directories. Relative paths resolve against the request's effective working directory when supplied. These are not filesystem grants or preferred creation targets. Null and omission are equivalent. + pub fn skill_directories(mut self, value: Vec) -> Self { + self.skill_directories = Some(value); + self + } +} + +impl Default for SkillsGetDiscoveryPathsOptions { + fn default() -> Self { + Self::new() + } +} + /// Skill invocation record with name, path, content, allowed tools, and turn number. /// ///
@@ -28494,6 +31019,153 @@ pub struct UserSettingMetadata { pub value: serde_json::Value, } +/// A single top-level setting subtree to validate and write. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct UserSettingsApplyTopKeyRequest { + /// Canonical path of the setting the caller is editing. Currently informational only. + pub canonical_path: Vec, + /// Records that the user chose an update channel explicitly, so the one-time staff channel migration stops reapplying. Honored only for the `autoUpdatesChannel` key. + #[serde(skip_serializing_if = "Option::is_none")] + pub complete_staff_update_channel_migration: Option, + /// True to remove the top-level key instead of writing it. + #[serde(skip_serializing_if = "Option::is_none")] + pub delete: Option, + /// Top-level setting name to write. + pub top_key: String, + /// Value to write for the whole top-level key. Omitted when `delete` is true. Explicit null is preserved and validated as null. + #[serde(skip_serializing_if = "Option::is_none")] + pub value: Option, +} + +/// One problem found while validating a setting 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 UserSettingsValidationIssue { + /// Human-readable description of the problem. + pub message: String, + /// Where in the settings tree the problem sits, as object keys and array indices. The first element is the top-level key; a whole-value problem is reported as that single key. + pub path: Vec, +} + +/// Outcome of validating and writing one top-level setting subtree. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct UserSettingsApplyTopKeyResult { + /// Why the write failed, present only when `status` is `write-error`. + #[serde(skip_serializing_if = "Option::is_none")] + pub error: Option, + /// Problems found, present only when `status` is `validation-error`. + #[serde(skip_serializing_if = "Option::is_none")] + pub issues: Option>, + /// `ok` when the value was written, `validation-error` when it was rejected before any write, `write-error` when validation passed but the write failed. + pub status: String, +} + +/// The overrides and host context to resolve effective runtime settings from. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol 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 UserSettingsBuildRuntimeRequest { + /// The environment to read settings-bearing variables from. Passed explicitly rather than read from the server process so a host can resolve settings for an environment it is about to create rather than the one it runs in. + pub environment: HashMap, + /// Settings that take precedence over every document and environment layer, such as the values a host derived from its own command line. Keyed by setting name, with each value carried verbatim, so a host may pass keys this runtime does not recognize. Absent when the host contributes no overrides of its own. + #[serde(skip_serializing_if = "Option::is_none")] + pub overrides: Option>, + /// The platform to apply platform-specific defaults for, in the same spelling the host's own runtime reports. + pub platform: String, + /// The directory the settings are being resolved for. This selects the repository-scoped document, so a host resolving settings for a directory other than its own process working directory must pass that directory here. + pub working_directory: String, +} + +/// One atomic edit of a canonical setting in settings.json. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol 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 UserSettingsEditRequest { + /// Nonempty canonical path segments of the setting to edit, starting with its top-level key. + pub canonical_path: Vec, + /// Configuration directory to edit. Omitted or empty resolves the server's own home, as in `user.settings.loadForConfigDir`. + #[serde(skip_serializing_if = "Option::is_none")] + pub config_dir: Option, + /// Set a scalar, add or remove a string list item, or remove the setting. The handler rejects a missing or incompatible value. + #[doc(hidden)] + pub(crate) operation: SettingsEditOperation, + /// Required boolean, string or number for set, required string for add-item and remove-item, and forbidden for remove. The runtime rejects all other JSON values. + #[serde(skip_serializing_if = "Option::is_none")] + pub value: Option, +} + +/// Selects which settings file paths to resolve. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct UserSettingsGetPathsRequest { + /// Absolute path of a repository root. Supply it to also resolve that repository's two settings file paths; omit it to resolve only the user path. + #[serde(skip_serializing_if = "Option::is_none")] + pub repo_root: Option, +} + +/// On-disk locations of the settings files this runtime reads. A path is reported whether or not the file 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 UserSettingsGetPathsResult { + /// The repository's local, untracked settings file, or null when no repository root was supplied. + #[serde(skip_serializing_if = "Option::is_none")] + pub repo_local_settings: Option, + /// The repository's checked-in settings file, or null when no repository root was supplied. + #[serde(skip_serializing_if = "Option::is_none")] + pub repo_settings: Option, + /// The user settings file: `settings.json` under the resolved Copilot home. This is the file `user.settings.get`, `user.settings.load`, and `user.settings.set` read and write. + pub user_settings: String, +} + /// Per-key metadata for every known user setting in settings.json, including settings left at their default. Excludes repository- and enterprise-managed overrides. /// ///
@@ -28509,6 +31181,549 @@ pub struct UserSettingsGetResult { pub settings: HashMap, } +/// Top-level user-setting keys grouped by who may govern 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 UserSettingsGovernanceKeysResult { + /// Top-level keys a repository may override through a Claude-compatible repository config. + pub claude_repo: Vec, + /// Top-level keys an enterprise policy may fix through managed settings. + pub managed: Vec, + /// Top-level keys a repository may override through `.github/copilot/settings.json` or `.github/copilot/settings.local.json`. + pub repo: Vec, +} + +/// The filesystem and environment facts a user-settings file is resolved from. Passed explicitly rather than read from the runtime's own process, so a host whose configuration directory differs from its process environment resolves the file it means. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct UserSettingsHostContext { + /// The Copilot configuration directory, when the host set one. Takes precedence over `COPILOT_HOME` and the home directory. + #[serde(skip_serializing_if = "Option::is_none")] + pub config_dir: Option, + /// The host's environment variables. `COPILOT_HOME` is read from here, as are the variables a settings hook may expand. + pub environment: serde_json::Value, + /// The user's home directory, used when neither a configuration directory nor `COPILOT_HOME` is set. + pub home_directory: String, +} + +/// A repository to read settings from, and the user layer they are merged over. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol 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 UserSettingsLoadAndMergeRepoRequest { + /// Absolute path of the repository root whose settings to read. When this is the user's home directory, the Claude-compatible configuration is skipped, so a home-directory session does not inherit a personal config as if it were repository policy. + pub repo_root: String, + /// The user's own settings, keyed by setting name. The repository layer is merged over this; a repository may only add to the collection settings it is allowed to influence, so a user setting it does not name survives unchanged. + pub user_settings: HashMap, +} + +/// The settings a consumer should act on inside a repository, together with any diagnostic raised while reading the repository layer. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol 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 UserSettingsLoadAndMergeRepoResult { + /// The merged settings document, keyed by setting name. Equals the supplied user layer when the repository declares no settings. + pub settings: HashMap, + /// Why part of the repository document was ignored, as a single human-readable string joining every diagnostic with `; `. Null when it loaded cleanly. The settings that did load are still merged, so a consumer should surface this and continue rather than treat it as a failure. + #[serde(skip_serializing_if = "Option::is_none")] + pub warning: Option, +} + +/// Selects the configuration directory whose user settings 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(crate) struct UserSettingsLoadForConfigDirRequest { + /// Copilot configuration directory to read the settings document from, taking precedence over the server's own `COPILOT_HOME` and default home. Omit it, or pass an empty string, to read the directory the server resolved for itself. + #[serde(skip_serializing_if = "Option::is_none")] + pub config_dir: Option, +} + +/// Contents of settings.json as stored, for a caller about to rewrite 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 UserSettingsLoadForEditResult { + /// Parsed contents, present only when `status` is `ok`. + #[serde(skip_serializing_if = "Option::is_none")] + pub settings: Option, + /// `ok` when the file parsed, `missing` when there is no file yet, `invalid` when the file exists but will not parse. + pub status: String, +} + +/// Selects the repository whose checked-in settings 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 UserSettingsLoadRepoRequest { + /// Absolute path of the repository root whose settings to read. When this is the user's home directory, the Claude-compatible configuration is skipped, so a home-directory session does not inherit a personal config as if it were repository policy. + pub repo_root: String, +} + +/// A repository's settings as stored, together with any diagnostic raised while reading 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 UserSettingsLoadRepoResult { + /// The repository's settings document, keyed by setting name, with each value carried verbatim. Null when the repository declares no settings. + #[serde(skip_serializing_if = "Option::is_none")] + pub settings: Option>, + /// Why part of the document was ignored, as a single human-readable string joining every diagnostic with `; `. Null when the document loaded cleanly. The settings that did load are still reported, so a consumer should surface this and continue rather than treat it as a failure. + #[serde(skip_serializing_if = "Option::is_none")] + pub warning: Option, +} + +/// The user's stored settings, together with any diagnostic raised while reading 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 UserSettingsLoadWithWarningResult { + /// The stored user settings document, keyed by setting name, with each value carried verbatim. Empty when the file could not be read at all. + pub settings: HashMap, + /// Why part of the document was ignored, phrased for display to the user. Null when the document loaded cleanly. The settings that did load are still reported, so a consumer may surface this and continue rather than treat it as a failure. + #[serde(skip_serializing_if = "Option::is_none")] + pub warning: Option, +} + +/// The managed (enterprise policy) layer and the user layer to fold together. The field names carry the layer identity: nothing about a settings object identifies which layer produced it, so a generic base/override pair would let a caller transpose them and silently resolve the governance keys in the user's favour. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol 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 UserSettingsMergeManagedAndUserParams { + /// The managed layer. Its governance keys win, and the per-entry `enabledPlugins` and `extraKnownMarketplaces` control maps are stamped back from it after the union. Omitted when the host is unmanaged. + #[serde(skip_serializing_if = "Option::is_none")] + pub managed: Option, + /// The user's own settings layer. Wins on every key the managed layer does not govern. Omitted when the user has no settings yet. + #[serde(skip_serializing_if = "Option::is_none")] + pub user: Option, +} + +/// The user layer folded over the managed layer. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct UserSettingsMergeManagedAndUserResult { + /// The merged settings. An empty object when neither layer was supplied, so an unmanaged host with no user settings governs nothing. + pub settings_json: serde_json::Value, +} + +/// The user layer and the repository layer to fold together. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol 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 UserSettingsMergeRepoValueParams { + /// The repository's checked-in settings layer. + pub repo_settings: serde_json::Value, + /// The user's settings, already resolved against any managed layer. + pub user_settings: serde_json::Value, +} + +/// The repository layer folded into the user layer. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct UserSettingsMergeRepoValueResult { + /// The merged settings, with the per-entry managed control maps re-stamped from the runtime's ambient managed policy. + pub settings_json: serde_json::Value, +} + +/// Selects the configuration directory whose settings document to clean. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol 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 UserSettingsMigrateObsoleteOnDiskRequest { + /// The configuration directory holding the settings document to clean. A host that scopes a session to its own Copilot home passes that directory here; omit it to clean the document under the home the server resolved for itself. The home directory and environment are deliberately not caller-supplied: they decide only the settings path root and the two hook-parsing flags, and those describe the process doing the reading rather than the caller. + #[serde(skip_serializing_if = "Option::is_none")] + pub config_dir: Option, +} + +/// The outcome of the one-time obsolete-settings cleanup. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol 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 UserSettingsMigrateObsoleteOnDiskResult { + /// What the cleanup did. Only `changed` rewrote the document. The cleaned settings are not returned here: the document is written before this replies, so `user.settings.load` reads it authoritatively. + #[doc(hidden)] + pub(crate) status: UserSettingsMigrateObsoleteOnDiskStatus, +} + +/// Selects the configuration directory to run the one-time staff log-level migration against, and supplies the caller's record of whether it has already run. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub(crate) struct UserSettingsMigrateStaffLogLevelOnceRequest { + /// When the caller last recorded this migration, as an ISO-8601 timestamp. Supplying it lets the runtime answer `current` without re-running the evaluation. + #[serde(skip_serializing_if = "Option::is_none")] + pub cached_completed_at: Option, + /// The configuration directory, home directory and environment to resolve the settings and global-state documents from. A host that scopes a session to its own Copilot home must pass that directory here, or the migration is evaluated against the server's own home instead. + pub context: UserSettingsHostContext, +} + +/// The outcome of the one-time staff log-level migration. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol 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 UserSettingsMigrateStaffLogLevelOnceResult { + /// When the migration was recorded as done, as an ISO-8601 timestamp. Absent only for `ineligible`, which records nothing. + #[serde(skip_serializing_if = "Option::is_none")] + pub completed_at: Option, + /// The settings document as it stands after the migration, keyed by setting name. Present only for `refreshed`, and only when the document could be re-read cleanly. + #[serde(skip_serializing_if = "Option::is_none")] + pub settings: Option, + /// What the migration did. Only `refreshed` can have changed the persisted settings. + pub status: StaffLogLevelMigrationStatus, +} + +/// The outcome of the one-time staff update-channel migration. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol 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 UserSettingsMigrateStaffUpdateChannelOnceResult { + /// When the migration was recorded as done, as an ISO-8601 timestamp. Absent only for `ineligible`, which records nothing. + #[serde(skip_serializing_if = "Option::is_none")] + pub completed_at: Option, + /// What the migration did. Only `migrated` means the persisted settings changed. + pub status: StaffUpdateChannelMigrationStatus, +} + +/// Settings object whose key names should be canonicalized. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct UserSettingsNormalizeRequest { + /// Free-form settings object, possibly using legacy or snake_case key names. + pub settings: serde_json::Value, +} + +/// Settings object with canonical key names. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct UserSettingsNormalizeResult { + /// The same settings with every legacy or snake_case key renamed to its canonical spelling. + pub settings: serde_json::Value, +} + +/// The on-disk location of the user's settings document. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct UserSettingsPathResult { + /// Absolute path to the settings file this runtime reads and writes. Normally `settings.json` under the Copilot configuration directory; an extensionless legacy `settings` file is reported instead when it exists and `settings.json` does not. + pub path: String, +} + +/// The settings layer a problem was found in. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct UserSettingsProblemLocation { + /// Absolute path of the file backing this layer. + pub file_path: String, + /// Which layer this is: `user`, `repo`, or `local`. + pub kind: String, + /// The caller-supplied display label for the layer. + pub label: String, +} + +/// One setting that will not take effect, and why. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct UserSettingsProblem { + /// Coarse grouping for the problem, for a caller that presents problems in sections. + pub category: String, + /// Why the setting will not take effect. Never contains the setting's value, which may be a token or another secret. + pub explanation: String, + /// Stable identifier for this problem within the reported set, so a caller can track a selection across refreshes. + pub id: String, + /// What is wrong in machine-readable form, for a caller that wants to branch rather than display. + pub kind: String, + /// The offending key as written in the file, when the problem is attributable to a single key. Absent when the whole file is at fault. + #[serde(skip_serializing_if = "Option::is_none")] + pub raw_key: Option, + /// The settings layer the problem was found in. + pub source: UserSettingsProblemLocation, +} + +/// One already-loaded settings layer to diagnose. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct UserSettingsProblemSource { + /// The layer's settings exactly as loaded, unnormalized. Empty when the file is missing or failed to parse. + #[serde(skip_serializing_if = "Option::is_none")] + pub config: Option>, + /// Absolute path of the file backing this layer. + pub file_path: String, + /// True when the file itself failed to parse, so its keys could not be read at all. + #[serde(skip_serializing_if = "Option::is_none")] + pub invalid: Option, + /// Which layer this is: `user`, `repo`, or `local`. + pub kind: String, + /// Caller-chosen display label for the layer, echoed back on each problem so a caller can group by layer without re-deriving its own naming. + pub label: String, +} + +/// The settings layers to diagnose. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct UserSettingsProblemsRequest { + /// Layers to diagnose, in the precedence order the caller applies them. + #[serde(skip_serializing_if = "Option::is_none")] + pub sources: Option>, +} + +/// The settings that will not take effect across the given layers. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct UserSettingsProblemsResult { + /// Each setting that will not take effect, and why. Empty when every layer is healthy. + #[serde(skip_serializing_if = "Option::is_none")] + pub problems: Option>, +} + +/// Selects the configuration directory to run the one-time staff model-selection reset against, and supplies the caller's record of whether it has already run. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub(crate) struct UserSettingsResetStaffModelSelectionOnceRequest { + /// The configuration directory, home directory and environment to resolve the settings and global-state documents from. A host that scopes a session to its own Copilot home must pass that directory here, or the reset is evaluated against the server's own home instead. + pub context: UserSettingsHostContext, + /// Whether the signed-in account is a staff account. The reset applies only to staff, so anything other than `true` returns `didReset: false` without touching the settings document. + #[serde(skip_serializing_if = "Option::is_none")] + pub staff: Option, + /// When the caller last recorded this reset, as an ISO-8601 timestamp. A timestamp at or after the cutoff means the reset already ran and is returned unchanged. + #[serde(skip_serializing_if = "Option::is_none")] + pub staff_model_reset_at: Option, +} + +/// The outcome of the one-time staff model-selection reset. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol 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 UserSettingsResetStaffModelSelectionOnceResult { + /// The cutoff this reset is gated on, as an ISO-8601 timestamp. A recorded timestamp at or after it means the reset is already done. + pub cutoff: String, + /// Whether the persisted model selection was cleared. Only `true` means the settings document changed and the host should adopt the returned settings. + pub did_reset: bool, + /// The timestamp the host should now record, as an ISO-8601 timestamp. Carries the caller's own value back unchanged when nothing was reset. + #[serde(skip_serializing_if = "Option::is_none")] + pub reset_at: Option, + /// The settings document as it stands after the reset, keyed by setting name. Present only when `didReset` is true. + #[serde(skip_serializing_if = "Option::is_none")] + pub settings: Option, +} + +/// One hook to disable or re-enable. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol 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 UserSettingsSetHookDisabledRequest { + /// True adds the key to `disabledHooks`, false removes it. + pub disabled: bool, + /// Durable content-hash key of the hook, as `hooks.loadResources` reports it. + pub disable_key: String, + /// Path of the settings.json to rewrite. + pub settings_path: String, +} + +/// Outcome of rewriting the `disabledHooks` setting. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol 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 UserSettingsSetHookDisabledResult { + /// Why the write failed. Absent when the status is `ok`. + #[serde(skip_serializing_if = "Option::is_none")] + pub error_message: Option, + /// `ok` when the setting was written, `invalid` when settings.json could not be parsed and was left alone, `error` when the write itself failed. + pub status: String, +} + /// 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. /// ///
@@ -28524,6 +31739,129 @@ pub struct UserSettingsSetRequest { pub settings: serde_json::Value, } +/// Selects the configuration directory whose user settings document should be inspected for unrecognized top-level keys. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol 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 UserSettingsUnknownKeysWarningRequest { + /// The configuration directory, home directory and environment to resolve the settings document from. A host that scopes a session to its own Copilot home must pass that directory here, or the server's own home is inspected instead. + pub context: UserSettingsHostContext, +} + +/// The unrecognized top-level keys found in the user settings document, already phrased for display. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol 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 UserSettingsUnknownKeysWarningResult { + /// The warning to show the user, naming the unrecognized keys and the file they were found in. Absent when every top-level key is recognized, and also when the document is missing or could not be read, because an unknown-key list taken from a document the runtime could not load would name keys that may not be there. + #[serde(skip_serializing_if = "Option::is_none")] + pub warning: Option, +} + +/// Names the user setting to merge into, and the partial object to merge. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol 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 UserSettingsUpdateRequest { + /// Copilot configuration directory to write the settings document in, taking precedence over the server's own `COPILOT_HOME` and default home. Omit it, or pass an empty string, to use the directory the server resolved for itself. + #[serde(skip_serializing_if = "Option::is_none")] + pub config_dir: Option, + /// Top-level user-setting key to merge into, such as `extensions`. + pub key: String, + /// Single-component name for the settings document at `/settings/.json`; path separators, `.` and `..` are rejected. Omit it, or pass an empty string, to use the main settings document. + #[serde(skip_serializing_if = "Option::is_none")] + pub sub_dir: Option, + /// Value to store for `key`. When both the stored value and this value are objects, their fields are shallow-merged and fields this object does not name are preserved. Otherwise this value replaces the stored value. + pub value: serde_json::Value, +} + +/// The stored value of the user setting after the merge. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol 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 UserSettingsUpdateResult { + /// The key's stored value after the write: the merged object when both the stored value and the supplied value are objects, otherwise the supplied value. + pub value: serde_json::Value, +} + +/// A single top-level setting subtree to validate. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct UserSettingsValidateRequest { + /// True to validate a removal of this key. The current settings file is not read. + #[serde(skip_serializing_if = "Option::is_none")] + pub delete: Option, + /// Top-level setting name to validate. + pub top_key: String, + /// Value to validate. Omitted when `delete` is true. + #[serde(skip_serializing_if = "Option::is_none")] + pub value: Option, +} + +/// Outcome of validating one top-level setting subtree. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct UserSettingsValidateResult { + /// Problems found, empty when `ok` is true. + #[serde(skip_serializing_if = "Option::is_none")] + pub issues: Option>, + /// True when the subtree is valid. + pub ok: bool, +} + +/// The whole content to store in the user settings 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 UserSettingsWriteAllRequest { + /// The settings object to store. It replaces the file's entire content, so a key absent here is removed. + pub settings: serde_json::Value, +} + /// Current sharing status and shareable GitHub URL for a session. /// ///
@@ -30198,6 +33536,360 @@ pub struct WorkspacesWriteAutopilotObjectiveResult { pub operation: String, } +/// Status of the persistent certificate authority of the sandbox credential proxy. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SandboxProxyCaGetStatusResult { + /// Whether this process can add the certificate authority to OS trust without credentials from a different user. False where OS trust is unsupported, and on Windows when the process cannot elevate itself to write the machine trust store. When false, do not offer to set up the certificate authority. + pub can_install: bool, + /// Human-readable reason for the state. On `installed` or `notInstalled`, present only when the certificate authority must be rotated, and then says why. + #[serde(skip_serializing_if = "Option::is_none")] + pub detail: Option, + /// The state of the certificate authority. + pub state: SandboxProxyCaState, +} + +/// Status of the persistent certificate authority of the sandbox credential proxy. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SandboxProxyCaRotateResult { + /// Whether this process can add the certificate authority to OS trust without credentials from a different user. False where OS trust is unsupported, and on Windows when the process cannot elevate itself to write the machine trust store. When false, do not offer to set up the certificate authority. + pub can_install: bool, + /// Human-readable reason for the state. On `installed` or `notInstalled`, present only when the certificate authority must be rotated, and then says why. + #[serde(skip_serializing_if = "Option::is_none")] + pub detail: Option, + /// The state of the certificate authority. + pub state: SandboxProxyCaState, +} + +/// Status of the persistent certificate authority of the sandbox credential proxy. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SandboxProxyCaTrustResult { + /// Whether this process can add the certificate authority to OS trust without credentials from a different user. False where OS trust is unsupported, and on Windows when the process cannot elevate itself to write the machine trust store. When false, do not offer to set up the certificate authority. + pub can_install: bool, + /// Human-readable reason for the state. On `installed` or `notInstalled`, present only when the certificate authority must be rotated, and then says why. + #[serde(skip_serializing_if = "Option::is_none")] + pub detail: Option, + /// The state of the certificate authority. + pub state: SandboxProxyCaState, +} + +/// Status of the persistent certificate authority of the sandbox credential proxy. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SandboxProxyCaRemoveResult { + /// Whether this process can add the certificate authority to OS trust without credentials from a different user. False where OS trust is unsupported, and on Windows when the process cannot elevate itself to write the machine trust store. When false, do not offer to set up the certificate authority. + pub can_install: bool, + /// Human-readable reason for the state. On `installed` or `notInstalled`, present only when the certificate authority must be rotated, and then says why. + #[serde(skip_serializing_if = "Option::is_none")] + pub detail: Option, + /// The state of the certificate authority. + pub state: SandboxProxyCaState, +} + +/// Where this runtime keeps the sandbox proxy password. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SandboxProxySecretGetLocationResult { + /// Absolute path of the credential store holding the sandbox proxy password. The proxy password belongs to no MCP server, so it is kept in its own directory rather than in the MCP secret store. + pub config_dir: String, + /// Identifier of the proxy password entry within that store. Derived from the configuration directory, so two Copilot homes on one machine never share an entry. Use it to recognize a `${secret:…}` reference in stored settings as this runtime's own proxy password. + pub secret_id: String, +} + +/// The sandbox a named feature actually launches under. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol 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 SandboxResolveForOptionalResult { + /// The effective sandbox config for the requested feature, or a disabled sandbox when the feature is routed out of it. + pub sandbox_json: serde_json::Value, +} + +/// The startup warning, absent when the machine can run a sandbox. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol 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 SandboxGetUnsupportedStartupNoticeResult { + /// The user-facing warning, carrying the platform remedy and the recovery the policy in force actually allows. Absent when this machine supports a sandbox, so a host can call unconditionally and print only what it gets. + #[serde(skip_serializing_if = "Option::is_none")] + pub notice: Option, +} + +/// The documentation URL a host cites when it declines or downgrades a sandbox setting. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SandboxGetDocsUrlResult { + /// Documentation URL describing the sandbox and the settings that control it. + pub url: String, +} + +/// Whether the host running this runtime can run the command sandbox. The runtime checks `supported` once per process. A capability answer can change while the process runs, for example after the user installs a missing package. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SandboxGetHostSupportResult { + /// Sandbox policy features whose availability varies between hosts that can run the backend. Empty when `supported` is false, because no feature can run without a backend. Later runtimes can add entries; ignore an entry whose `name` you do not recognize. + pub capabilities: Vec, + /// Human-readable reason the sandbox cannot run on this host. Present only when `supported` is false. + #[serde(skip_serializing_if = "Option::is_none")] + pub reason: Option, + /// Whether a process-containment backend is usable on this host: Seatbelt on macOS, Bubblewrap on Linux, or ProcessContainer on Windows. + pub supported: bool, +} + +/// The on-disk location of the user's settings document. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct UserSettingsGetPathResult { + /// Absolute path to the settings file this runtime reads and writes. Normally `settings.json` under the Copilot configuration directory; an extensionless legacy `settings` file is reported instead when it exists and `settings.json` does not. + pub path: String, +} + +/// The unrecognized top-level keys found in the user settings document, already phrased for display. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol 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 UserSettingsGetUnknownKeysWarningResult { + /// The warning to show the user, naming the unrecognized keys and the file they were found in. Absent when every top-level key is recognized, and also when the document is missing or could not be read, because an unknown-key list taken from a document the runtime could not load would name keys that may not be there. + #[serde(skip_serializing_if = "Option::is_none")] + pub warning: Option, +} + +/// Top-level user-setting keys grouped by who may govern 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 UserSettingsListGovernanceKeysResult { + /// Top-level keys a repository may override through a Claude-compatible repository config. + pub claude_repo: Vec, + /// Top-level keys an enterprise policy may fix through managed settings. + pub managed: Vec, + /// Top-level keys a repository may override through `.github/copilot/settings.json` or `.github/copilot/settings.local.json`. + pub repo: Vec, +} + +/// Outcome of an atomic user or repository settings edit. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol 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 UserSettingsEditResult { + /// Changed, unchanged or invalid existing file. Rejected input, validation failures and I/O errors remain RPC errors. + #[doc(hidden)] + pub(crate) status: SettingsEditStatus, +} + +/// The settings that will not take effect across the given layers. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct UserSettingsDiagnoseProblemsResult { + /// Each setting that will not take effect, and why. Empty when every layer is healthy. + #[serde(skip_serializing_if = "Option::is_none")] + pub problems: Option>, +} + +/// Outcome of an atomic user or repository settings edit. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol 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 RepoSettingsEditResult { + /// Changed, unchanged or invalid existing file. Rejected input, validation failures and I/O errors remain RPC errors. + #[doc(hidden)] + pub(crate) status: SettingsEditStatus, +} + +/// Every feature flag this runtime declares, with the shape each one was declared with. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol 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 FeatureFlagsListMetadataResult { + /// Declared feature flags keyed by flag name. + #[doc(hidden)] + pub(crate) flags: HashMap, +} + +/// Which stored location permissions were dropped because their directory is gone. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol 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 PermissionsLocationsCleanupOrphanedResult { + /// Location keys whose grants were removed. Empty when every stored location still exists. + pub removed_location_keys: Vec, +} + +/// Folder trust check result. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct PermissionsFolderTrustCheckTrustResult { + /// Whether the folder is trusted + pub trusted: bool, +} + +/// The verdict of the `forceLoginOrgs` enterprise sign-in pin for one credential. A managed device may pin Copilot to an approved set of GitHub organizations so a developer cannot sign in with a personal — or another enterprise's — account and escape every control that keys off the signed-in identity. A host must refuse to proceed when `allowed` is false; the remaining fields explain the refusal rather than qualifying 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 ManagedSettingsResolveDeviceForceLoginOrgsGateResult { + /// Whether the client may proceed. True whenever no pin is in force, so an unmanaged host is never blocked. + pub allowed: bool, + /// A stable kebab-case reason code when refused: `org-not-approved`, `unverifiable-credential`, `policy-undetermined`, `policy-invalid`, `not-signed-in`, or `membership-unknown`. Omitted when allowed. Treat an unrecognized code as a refusal rather than mapping it to an allow: a code this host has not been taught about still denied the credential. + #[serde(skip_serializing_if = "Option::is_none")] + pub denial: Option, + /// Whether a pin applies at all. Distinguishes "allowed because unmanaged" from "allowed because the account is approved", which a diagnostics surface needs and which also decides whether a bring-your-own-key provider may serve inference. + pub enforced: bool, + /// The approved organization the account matched, when allowed under a pin. + #[serde(skip_serializing_if = "Option::is_none")] + pub matched_org: Option, + /// A developer-facing explanation of the refusal. Omitted when allowed. + #[serde(skip_serializing_if = "Option::is_none")] + pub message: Option, + /// Error-level detail from managed-policy discovery — which file failed to load, which field failed validation. Carried on the decision rather than left to the log because a refusal can exit the process before a log sink exists, which would discard the only actionable diagnostic a misconfigured managed device produces. + pub policy_diagnostics: Vec, + /// The approved organization logins, as configured. Enterprise configuration rather than user data, so it is safe to name in a message or a log line. + pub required_orgs: Vec, +} + +/// Ordered managed permission verdicts and the supplied policy's fail-closed posture. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ManagedSettingsPermissionsEvaluateResult { + /// Managed permission policy could not be determined; every operation is denied. + pub fail_closed: bool, + /// One verdict per input operation, in the same order, including duplicates. + pub results: Vec, +} + /// The resident session the application has materialized on its own connection. /// ///
@@ -30323,7 +34015,7 @@ pub struct ModelsGetBuiltInCatalogResult { pub models: Vec, } -/// Whether the host running this runtime can run the command sandbox. The runtime checks `supported` once per process. A capability answer can change while the process runs, for example after the user installs a missing package. +/// Built-in tools available for the requested model, with their parameters and instructions. /// ///
/// @@ -30333,17 +34025,12 @@ pub struct ModelsGetBuiltInCatalogResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SandboxGetHostSupportResult { - /// Sandbox policy features whose availability varies between hosts that can run the backend. Empty when `supported` is false, because no feature can run without a backend. Later runtimes can add entries; ignore an entry whose `name` you do not recognize. - pub capabilities: Vec, - /// Human-readable reason the sandbox cannot run on this host. Present only when `supported` is false. - #[serde(skip_serializing_if = "Option::is_none")] - pub reason: Option, - /// Whether a process-containment backend is usable on this host: Seatbelt on macOS, Bubblewrap on Linux, or ProcessContainer on Windows. - pub supported: bool, +pub struct ToolsListResult { + /// List of available built-in tools with metadata + pub tools: Vec, } -/// Status of the persistent certificate authority of the sandbox credential proxy. +/// User-configured MCP servers, keyed by server name. /// ///
/// @@ -30353,17 +34040,12 @@ pub struct SandboxGetHostSupportResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SandboxProxyCaGetStatusResult { - /// Whether this process can add the certificate authority to OS trust without credentials from a different user. False where OS trust is unsupported, and on Windows when the process cannot elevate itself to write the machine trust store. When false, do not offer to set up the certificate authority. - pub can_install: bool, - /// Human-readable reason for the state. On `installed` or `notInstalled`, present only when the certificate authority must be rotated, and then says why. - #[serde(skip_serializing_if = "Option::is_none")] - pub detail: Option, - /// The state of the certificate authority. - pub state: SandboxProxyCaState, +pub struct McpConfigListResult { + /// All MCP servers from user config, keyed by name + pub servers: HashMap, } -/// Status of the persistent certificate authority of the sandbox credential proxy. +/// Request id naming a cancellable registry search. /// ///
/// @@ -30373,17 +34055,12 @@ pub struct SandboxProxyCaGetStatusResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SandboxProxyCaRotateResult { - /// Whether this process can add the certificate authority to OS trust without credentials from a different user. False where OS trust is unsupported, and on Windows when the process cannot elevate itself to write the machine trust store. When false, do not offer to set up the certificate authority. - pub can_install: bool, - /// Human-readable reason for the state. On `installed` or `notInstalled`, present only when the certificate authority must be rotated, and then says why. - #[serde(skip_serializing_if = "Option::is_none")] - pub detail: Option, - /// The state of the certificate authority. - pub state: SandboxProxyCaState, +pub(crate) struct McpRegistryAllocateRequestIdResult { + /// Request ID for `mcp.registry.search` and `mcp.registry.cancel`. It serves one search. Allocation can reclaim unused IDs at the 1,024-ID reservation limit. + pub request_id: i64, } -/// Status of the persistent certificate authority of the sandbox credential proxy. +/// Extensions discovered from persisted Copilot home state and their effective loading mode. Launch-scoped additional plugins are not included. /// ///
/// @@ -30393,17 +34070,14 @@ pub struct SandboxProxyCaRotateResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SandboxProxyCaTrustResult { - /// Whether this process can add the certificate authority to OS trust without credentials from a different user. False where OS trust is unsupported, and on Windows when the process cannot elevate itself to write the machine trust store. When false, do not offer to set up the certificate authority. - pub can_install: bool, - /// Human-readable reason for the state. On `installed` or `notInstalled`, present only when the certificate authority must be rotated, and then says why. - #[serde(skip_serializing_if = "Option::is_none")] - pub detail: Option, - /// The state of the certificate authority. - pub state: SandboxProxyCaState, +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, } -/// Status of the persistent certificate authority of the sandbox credential proxy. +/// The prompt the init command runs. /// ///
/// @@ -30413,17 +34087,12 @@ pub struct SandboxProxyCaTrustResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SandboxProxyCaRemoveResult { - /// Whether this process can add the certificate authority to OS trust without credentials from a different user. False where OS trust is unsupported, and on Windows when the process cannot elevate itself to write the machine trust store. When false, do not offer to set up the certificate authority. - pub can_install: bool, - /// Human-readable reason for the state. On `installed` or `notInstalled`, present only when the certificate authority must be rotated, and then says why. - #[serde(skip_serializing_if = "Option::is_none")] - pub detail: Option, - /// The state of the certificate authority. - pub state: SandboxProxyCaState, +pub(crate) struct InstructionsGetInitPromptResult { + /// Prompt text authoring repository instructions. + pub prompt: String, } -/// Built-in tools available for the requested model, with their parameters and instructions. +/// Instruction sources discovered across user, repository, and plugin sources. /// ///
/// @@ -30433,12 +34102,12 @@ pub struct SandboxProxyCaRemoveResult { ///
#[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 InstructionsDiscoverResult { + /// All discovered instruction sources + pub sources: Vec, } -/// User-configured MCP servers, keyed by server name. +/// Instruction sources discovered across user, repository, and plugin sources. /// ///
/// @@ -30448,12 +34117,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 InstructionsDiscoverRepoSourcesResult { + /// All discovered instruction sources + pub sources: Vec, } -/// Extensions discovered from persisted Copilot home state and their effective loading mode. Launch-scoped additional plugins are not included. +/// Canonical files and directories where custom instructions can be created so the runtime will recognize them. /// ///
/// @@ -30463,11 +34132,9 @@ 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 InstructionsGetDiscoveryPathsResult { + /// Canonical instruction create/discovery files and directories, in priority order + pub paths: Vec, } /// Skills discovered across global and project sources. @@ -30688,36 +34355,6 @@ pub struct AgentsGetDiscoveryPathsResult { pub paths: 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 InstructionsDiscoverResult { - /// All discovered instruction sources - pub sources: Vec, -} - -/// 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, -} - /// The host's machine-wide state. Every field is optional because a fresh install has recorded nothing yet, so a reader must treat an absent field as `not yet`, never as a negative answer. Stored credentials are deliberately absent from this shape. /// ///
@@ -30729,9 +34366,6 @@ pub struct InstructionsGetDiscoveryPathsResult { #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub(crate) struct GlobalStateLoadForConfigDirResult { - /// Whether the user has answered the prompt suggesting they install the desktop app. - #[serde(skip_serializing_if = "Option::is_none")] - pub app_install_nudge_responded: Option, /// Whether the app tip has been shown. #[serde(skip_serializing_if = "Option::is_none")] pub app_tip_shown: Option, @@ -31397,6 +35031,36 @@ pub struct SessionSandboxGetEnforcementStatusResult { pub required: bool, } +/// 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 SessionSandboxGetCredentialSuggestionsParams { + /// Target session identifier + pub session_id: SessionId, +} + +/// Possible unconfigured secrets in the sandbox shell environment, sorted by variable name, with no secret values. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionSandboxGetCredentialSuggestionsResult { + /// Candidates for user review. Empty when sandboxing is disabled or no unconfigured candidates are present. + pub suggestions: Vec, +} + /// Result of attempting to disable sandboxing for the current session. /// ///
@@ -32429,6 +36093,36 @@ pub struct SessionModelSetReasoningEffortResult { pub reasoning_effort: String, } +/// Reasoning-summaries presentation policy for the session's current 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 SessionModelGetReasoningSummariesResult { + /// Whether the supplied `persistedShowReasoning` setting only restates the model default. This reports a redundancy, not a standing instruction to delete the setting: it stays true for as long as the setting and the model default agree, including for a user who later chose that value deliberately. It exists for a one-time migration of a legacy setting written before the model default made it redundant, so a host that acts on it owns the cleanup and must gate it behind its own persisted `done` marker, delete only a setting whose value still matches the one it read, and never repeat the deletion on a later launch. Under an off-by-default reasoning-summaries experiment an explicit `showReasoning: true` opt-in also reads as redundant on a summary-producing model, so an ungated host would keep deleting that opt-in and hide summaries again on every launch. + pub clear_redundant_setting: bool, + /// Whether the session's reasoning-summaries experiment assignment has settled. False means this read does not wait for a pending assignment and reports the experiment-derived fields conservatively: `showHiddenHint` is false and `visibility` falls back to `persistedShowReasoning`, or is omitted when none was supplied. A host that wants the assigned answer re-reads until this is true. + pub experiment_settled: bool, + /// Whether the selected model shows reasoning summaries by default. + pub model_default_shows_summaries: bool, + /// False until the session has a model catalog and a selected model. This read never resolves the catalog itself, and the runtime emits no readiness notification, so repeating this call alone leaves `ready` false indefinitely on a session that has neither listed models nor run a turn. `session.model.list` is the prerequisite and the signal: it resolves and caches the catalog for the session, so a successful return means the next read is ready. The exception is a session that resolves models without a Copilot catalog, that is a BYOK registry or an alternate provider: `session.model.list` returns an empty list without caching a catalog, so `ready` stays false and this policy is unavailable there. While false, every model-policy field is false and `visibility` is omitted; `experimentSettled` is independent of readiness and still reports the assignment state. + pub ready: bool, + /// Whether turns on this session produce reasoning summaries, honouring an explicit session reasoning-summary override. + pub session_shows_summaries: bool, + /// Whether to show the hint that reasoning summaries are hidden. Always false while `experimentSettled` is false. + pub show_hidden_hint: bool, + /// Whether the selected model emits reasoning headers. + pub show_reasoning_headers: bool, + /// Default reasoning visibility, and only meaningful once `ready` is true: false when the session produces no summaries, else the persisted setting, else the experiment's default. While `ready` is true it is omitted only when all three are unavailable, that is when the session produces summaries, no `persistedShowReasoning` was supplied, and `experimentSettled` is false. While `ready` is false there is no model policy to report and this field is always omitted, so an absent value alone does not imply a pending experiment. + #[serde(skip_serializing_if = "Option::is_none")] + pub visibility: Option, +} + /// The list of models available to this session. /// ///
@@ -34846,20 +38540,23 @@ pub struct SessionManagedSettingsGetParams { #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct SessionManagedSettingsGetResult { - /// Whether enterprise policy disables bypass-permissions ("yolo") mode for this session. Deny-wins across layers, and forced on when `failClosed` is true. + /// Whether an explicit enterprise policy restriction disables bypass-permissions ("yolo") mode for this session. Deny-wins across layers; an unresolved policy does not force this on by itself. pub bypass_permissions_disabled: bool, /// 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. + /// Whether managed policy could not be determined (e.g. a failed server fetch) and unresolved-policy safeguards remain active. This does not by itself disable bypass-permissions; `bypassPermissionsDisabled` reports only an explicit policy restriction. pub fail_closed: bool, /// The setting keys under enterprise management in the effective managed settings (e.g. `model`, `enabledPlugins`, `permissions`). Empty when no managed settings are in force. pub managed_keys: Vec, /// Whether at least two managed sources supplied permission allowlists, so enforcement intersects them and the flattened settings payload omits `permissions.allow`. #[serde(skip_serializing_if = "Option::is_none")] pub permissions_allow_intersected: Option, + /// Reusable retained managed permission policy for managedSettings.permissions.evaluate, including source composition and session-local injection. Provided by live session snapshots; absent in older events. Refresh the context when managed policy changes. + #[serde(skip_serializing_if = "Option::is_none")] + pub permissions_context: 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, @@ -37947,6 +41644,126 @@ pub type SessionsGetClientMetadataResult = Vec; ///
pub type UIElicitationResponseContent = HashMap; +/// True when this process can offer installing the sandbox credential-proxy certificate authority into OS trust. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+pub type SandboxProxyCaCheckInstallationSupportResult = bool; + +/// True when the sandbox configuration uses the credential proxy and can need certificate-authority setup. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+pub type SandboxProxyCaCheckRelevanceResult = bool; + +/// The effective sandbox settings, with every unset value resolved to its default. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+pub type SandboxSettingsApplyDefaultsResult = HashMap; + +/// True when an OS-level sandbox can run on this host. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+pub type SandboxCheckPlatformSupportResult = bool; + +/// True when the sandbox on this host can reach host loopback directly. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+pub type SandboxCheckWindowsHostLoopbackSupportResult = bool; + +/// The user-facing refusal, including the platform remedy and the documentation link. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+pub type SandboxGetUnsupportedMessageResult = String; + +/// The user-facing notice that a saved sandbox setting is inert 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. +/// +///
+pub type SandboxGetUnsupportedSettingDisabledNoticeResult = String; + +/// True when the command stays available while enforcement is blocked. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+pub type SandboxCheckEnforcementCommandResult = bool; + +/// The stored user settings document, keyed by setting name. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+pub type UserSettingsLoadForConfigDirResult = HashMap; + +/// The effective runtime settings, keyed by setting name. Keys this runtime does not recognize are preserved rather than dropped, so a caller reading a document written by a newer runtime keeps every value. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+pub type UserSettingsBuildRuntimeResult = HashMap; + +/// The resolved gates, keyed by flag name. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+pub type FeatureFlagsResolveStartupGatesResult = HashMap; + +/// The resolved feature flags, keyed by flag name. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+pub type FeatureFlagsResolveResult = HashMap; + /// List of all authenticated users /// ///
@@ -38172,6 +41989,49 @@ pub enum AuthInfo { ApiKey(ApiKeyAuthInfo), } +/// Authentication 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, 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 from a selected provider-owned account, without a GitHub credential. + #[serde(rename = "account")] + Account, + /// 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, +} + /// The provider kind stamped on a signed-in account. /// ///
@@ -38855,49 +42715,6 @@ pub enum AuthEnumerateValue { Providers(AuthEnumerateValueProviders), } -/// Authentication 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, 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 from a selected provider-owned account, without a GitHub credential. - #[serde(rename = "account")] - Account, - /// 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, -} - /// Login flow step variant discriminator. #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] pub enum AuthLoginStepOpenUrlKind { @@ -39140,6 +42957,147 @@ pub enum CatalogAgentPluginCompatibilityTag { Unknown, } +/// Typed installability state for an Agent Plugin candidate +/// +///
+/// +/// **Experimental.** This type 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 CatalogAgentPluginInstallability { + /// The candidate carries complete, verified install data and the selected session may install it. Its install plan is present. + #[serde(rename = "installable")] + Installable, + /// The catalogue supplied no complete, verified install data, so no install target is offered. + #[serde(rename = "install-data-unavailable")] + InstallDataUnavailable, + /// Plugin installation is understood but disabled for the selected session. + #[serde(rename = "feature-disabled")] + FeatureDisabled, + /// Reserved for plugin formats this runtime cannot install. Every format surfaced today installs through a marketplace. + #[serde(rename = "not-installable-kind")] + NotInstallableKind, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, +} + +/// A kind of component an Agent Plugin contributes +/// +///
+/// +/// **Experimental.** This type 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 CatalogAgentPluginComponent { + /// Custom agents. + #[serde(rename = "agent")] + Agent, + /// Slash commands. + #[serde(rename = "command")] + Command, + /// Skills. + #[serde(rename = "skill")] + Skill, + /// Hooks. + #[serde(rename = "hook")] + Hook, + /// Bundled MCP servers. + #[serde(rename = "mcp-server")] + McpServer, + /// Bundled LSP servers. + #[serde(rename = "lsp-server")] + LspServer, + /// Extensions. + #[serde(rename = "extension")] + Extension, + /// Canvases. + #[serde(rename = "canvas")] + Canvas, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, +} + +/// Discriminator for a materialisation install source +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum CatalogAgentPluginMaterializationSourceKind { + /// Install from Agent Finder's verified materialisation. + #[serde(rename = "materialization")] + Materialization, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, +} + +/// Discriminator for a marketplace install source +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum CatalogAgentPluginMarketplaceSourceKind { + /// Install from a plugin marketplace. + #[serde(rename = "marketplace")] + Marketplace, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, +} + +/// Discriminator for a repository install source +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum CatalogAgentPluginRepositorySourceKind { + /// Install directly from a GitHub repository. + #[serde(rename = "repository")] + Repository, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, +} + +/// One place an Agent Plugin can install from: Agent Finder's materialisation, a marketplace, or a GitHub repository. Each variant carries only its own 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, Serialize, Deserialize)] +#[serde(untagged)] +pub enum CatalogAgentPluginInstallSource { + Materialization(CatalogAgentPluginMaterializationSource), + Marketplace(CatalogAgentPluginMarketplaceSource), + Repository(CatalogAgentPluginRepositorySource), +} + /// Discriminator for an Agent Plugin candidate /// ///
@@ -39159,7 +43117,7 @@ pub enum CatalogAgentPluginCandidateKind { Unknown, } -/// Canonical Agent Plugin media type +/// Media type naming the plugin format of an Agent Plugin candidate. Values other than `application/vnd.github.copilot-plugin` appear only when the caller requires the agent-plugin-formats capability. /// ///
/// @@ -39172,6 +43130,12 @@ pub enum CatalogAgentPluginMediaType { /// A GitHub Copilot Agent Plugin descriptor. #[serde(rename = "application/vnd.github.copilot-plugin")] ApplicationVndGitHubCopilotPlugin, + /// A Claude plugin descriptor (`.claude-plugin/plugin.json` layout). + #[serde(rename = "application/vnd.anthropic.claude-plugin+json")] + ApplicationVndAnthropicClaudePluginJson, + /// A portable Agent Plugin descriptor. + #[serde(rename = "application/agent-plugin+json")] + ApplicationAgentPluginJson, /// Unknown variant for forward compatibility. #[default] #[serde(other)] @@ -39437,6 +43401,9 @@ pub enum CatalogCapability { /// Understands opt-in `application/vnd.github.copilot-plugin` candidates and their typed identity, version, source, and compatibility fields. #[serde(rename = "agent-plugin-discovery")] AgentPluginDiscovery, + /// Extends agent-plugin-discovery to Claude plugin (`application/vnd.anthropic.claude-plugin+json`) and portable Agent Plugin (`application/agent-plugin+json`) candidates, each reporting its format as its media type. Requires agent-plugin-discovery; without it, plugin searches return only Copilot plugins. + #[serde(rename = "agent-plugin-formats")] + AgentPluginFormats, /// 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, @@ -39467,6 +43434,12 @@ pub enum CatalogCapability { /// Understands verified Agent Finder Skill install, uninstall, recovery and installation-scoped enablement APIs. Advertised only by runtimes with the Skill installation engine linked; acquisition may still be refused as feature-disabled per selected session. #[serde(rename = "skill-confirmed-installation")] SkillConfirmedInstallation, + /// Understands `installability` and the reviewable `installPlan` on Agent Plugin candidates. Requires agent-plugin-discovery; without it, plugin candidates are unchanged. A plugin is installable only with complete, verified catalogue install data, and may still be reported as feature-disabled per selected session. + #[serde(rename = "agent-plugin-confirmed-installation")] + AgentPluginConfirmedInstallation, + /// Extends skill-confirmed-installation to planning from an Agent Finder materialisation reference through skills.planInstallFromReference, and to its distinct reference failure reasons. Requires skill-confirmed-installation. Advertised only by runtimes with the Skill installation engine linked. + #[serde(rename = "skill-reference-installation")] + SkillReferenceInstallation, /// Unknown variant for forward compatibility. #[default] #[serde(other)] @@ -39666,6 +43639,12 @@ pub enum CatalogMediaType { /// An inert Agent Plugin descriptor. #[serde(rename = "application/vnd.github.copilot-plugin")] ApplicationVndGitHubCopilotPlugin, + /// An inert Claude plugin descriptor, surfaced only under the agent-plugin-formats capability. + #[serde(rename = "application/vnd.anthropic.claude-plugin+json")] + ApplicationVndAnthropicClaudePluginJson, + /// An inert portable Agent Plugin descriptor, surfaced only under the agent-plugin-formats capability. + #[serde(rename = "application/agent-plugin+json")] + ApplicationAgentPluginJson, /// Unknown variant for forward compatibility. #[default] #[serde(other)] @@ -41634,6 +45613,49 @@ pub enum ExternalToolTextResultForLlmContentTextType { Text, } +/// The population a feature flag is declared for, before overrides. +/// +///
+/// +/// **Experimental.** This type 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 FeatureFlagAvailability { + /// Enabled for everyone, before any override. + #[serde(rename = "on")] + On, + /// Enabled only for accounts on the owning team. + #[serde(rename = "team")] + Team, + /// Enabled only for staff accounts that have also opted into experimental features. + #[serde(rename = "staff-and-experimental")] + StaffAndExperimental, + /// Enabled for staff accounts and for accounts that opted into experimental features. + #[serde(rename = "staff-or-experimental")] + StaffOrExperimental, + /// Enabled for any staff account, GitHub or Microsoft. + #[serde(rename = "staff")] + Staff, + /// Enabled for GitHub staff accounts only. + #[serde(rename = "staff-github")] + StaffGitHub, + /// Enabled for Microsoft staff accounts only. + #[serde(rename = "staff-microsoft")] + StaffMicrosoft, + /// Enabled only for accounts that opted into experimental features. + #[serde(rename = "experimental")] + Experimental, + /// Disabled for everyone, before any override. + #[serde(rename = "off")] + Off, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, +} + /// Why the runtime is requesting a GitHub credential. #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] pub enum GitHubTokenAcquireReason { @@ -42544,6 +46566,126 @@ pub enum SessionLogLevel { Unknown, } +/// Operation to evaluate. Currently only url is supported. +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum ManagedPermissionOperationKind { + #[serde(rename = "url")] + #[default] + Url, +} + +/// Managed-policy verdict only: deny blocks the URL, ask requires approval, allow approves under managed policy, and unmanaged leaves the normal consumer permission flow in effect. No verdict bypasses other security controls. +/// +///
+/// +/// **Experimental.** This type 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 ManagedPermissionVerdict { + /// Managed policy prohibits the operation; do not offer approval. + #[serde(rename = "deny")] + Deny, + /// Managed policy requires the consumer to obtain approval. + #[serde(rename = "ask")] + Ask, + /// Managed policy allows the operation, subject to other security controls. + #[serde(rename = "allow")] + Allow, + /// Managed policy does not decide the operation; use the consumer's normal permission flow. + #[serde(rename = "unmanaged")] + Unmanaged, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, +} + +/// What retrying did for one plugin required by managed settings. +/// +///
+/// +/// **Experimental.** This type 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 ManagedPluginRetryStatus { + /// The plugin was missing and is now installed. + #[serde(rename = "installed")] + Installed, + /// An update was available and is now installed. + #[serde(rename = "updated")] + Updated, + /// The plugin was already installed and current, so nothing was done. + #[serde(rename = "already_present")] + AlreadyPresent, + /// Checking, installing, or updating the plugin failed. See `error`. + #[serde(rename = "failed")] + Failed, + /// The plugin was not prepared because another session was changing plugins or the managed settings changed during the retry. It is prepared again before a later message. + #[serde(rename = "deferred")] + Deferred, + /// The session's managed settings do not require this plugin, so nothing was done. + #[serde(rename = "not_required")] + NotRequired, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, +} + +/// Which layer disabled bypass-permissions mode. The layer matters because only a server-delivered disable is pinned to an account and can later be lifted by signing in elsewhere. +/// +///
+/// +/// **Experimental.** This type 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 ManagedSettingsBypassDisableSource { + /// The user turned bypass mode off themselves. + #[serde(rename = "user")] + User, + /// The account's server-delivered org policy disabled it. Pinned to the account named by `currentManagedIdentity`. + #[serde(rename = "managed")] + Managed, + /// The device/MDM layer disabled it. Outranks the account, so no server answer lifts it. + #[serde(rename = "mdm")] + Mdm, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, +} + +/// What the managed-policy resolution said about bypass-permissions mode. `unknown` is a third answer rather than a missing one, and the caller must send it explicitly when it has no verdict. +/// +///
+/// +/// **Experimental.** This type 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 ManagedSettingsBypassPolicyResolution { + /// The policy resolved and does not disable bypass mode. + #[serde(rename = "allowed")] + Allowed, + /// The policy resolved and disables bypass mode. + #[serde(rename = "disabled")] + Disabled, + /// No verdict: the fetch failed, or the service declared a policy this build may not enforce. Whatever is in force is preserved, because reverting to permissive on an unreadable policy would widen access. + #[serde(rename = "unknown")] + UnknownValue, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, +} + /// A channel accepted by managedSettings.compose. /// ///
@@ -42591,6 +46733,143 @@ pub enum ManagedSettingsDiagnosticSeverity { Unknown, } +/// The operation that requires a policy-helper 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 ManagedSettingsPolicyHelperReason { + /// Resolve policy for session startup. + #[serde(rename = "sessionStart")] + SessionStart, + /// Resolve policy before plugin activation. + #[serde(rename = "pluginActivation")] + PluginActivation, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, +} + +/// How an ingestion may use the on-disk server-policy cache. +/// +///
+/// +/// **Experimental.** This type 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 ManagedSettingsServerPolicyCacheMode { + /// Serve a recent cache entry before a live fetch. + #[serde(rename = "preferFresh")] + PreferFresh, + /// Read cached policy without a network request. + #[serde(rename = "cacheOnly")] + CacheOnly, + /// Serve a cached entry only as a fallback after a failed live fetch. + #[serde(rename = "fallbackOnly")] + FallbackOnly, + /// Ignore the cache entirely and rely on the live fetch alone. + #[serde(rename = "disabled")] + Disabled, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, +} + +/// Severity of a diagnostic emitted while ingesting managed settings. +/// +///
+/// +/// **Experimental.** This type 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 ManagedSettingsLogLevel { + /// Detail useful only when diagnosing an ingestion. + #[serde(rename = "debug")] + Debug, + /// Ordinary progress worth surfacing under a verbose flag. + #[serde(rename = "info")] + Info, + /// A condition the operator should see but which did not stop the ingestion. + #[serde(rename = "warning")] + Warning, + /// A failure that changed the outcome of the ingestion. + #[serde(rename = "error")] + Error, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, +} + +/// Which channel registered the policy helper that produced a helper layer. +/// +///
+/// +/// **Experimental.** This type 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 ManagedSettingsPolicyHelperAuthority { + /// The helper was registered by the device (MDM or managed file) layer. + #[serde(rename = "device")] + Device, + /// The helper was registered by the account's server-delivered policy. + #[serde(rename = "server")] + Server, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, +} + +/// How the server layer of an ingestion was resolved. +/// +///
+/// +/// **Experimental.** This type 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 ManagedSettingsServerPolicyResolution { + /// A live fetch succeeded. + #[serde(rename = "live")] + Live, + /// A recent cached entry was served without a live fetch. + #[serde(rename = "freshCache")] + FreshCache, + /// No cached policy or confirmed no-policy result exists. + #[serde(rename = "cacheMiss")] + CacheMiss, + /// A live fetch failed and a cached entry was served in its place. + #[serde(rename = "fallbackCache")] + FallbackCache, + /// No policy could be resolved and the host proceeds unmanaged. + #[serde(rename = "failOpen")] + FailOpen, + /// No policy could be resolved and the host applies the fail-closed gates. + #[serde(rename = "failClosed")] + FailClosed, + /// No fetch was attempted because no usable token was available. + #[serde(rename = "skippedNoToken")] + SkippedNoToken, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, +} + /// Allowed values for the `McpAppsHostContextDetailsAvailableDisplayMode` enumeration. /// ///
@@ -43715,6 +47994,28 @@ pub enum McpSetEnvValueModeDetails { Unknown, } +/// Controls how availableTools (allowlist) and excludedTools (denylist) combine when both are 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, PartialEq, Eq, Serialize, Deserialize)] +pub enum OptionsUpdateToolFilterPrecedence { + /// If availableTools is set, it is the only constraint that applies (excludedTools is ignored). Preserves CLI / pre-existing client behavior. Default. + #[serde(rename = "available")] + Available, + /// 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. + #[serde(rename = "excluded")] + Excluded, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, +} + /// Hosting platform type of the repository /// ///
@@ -44228,28 +48529,6 @@ pub enum OptionsUpdateReasoningSummary { Unknown, } -/// Controls how availableTools (allowlist) and excludedTools (denylist) combine when both are 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, PartialEq, Eq, Serialize, Deserialize)] -pub enum OptionsUpdateToolFilterPrecedence { - /// If availableTools is set, it is the only constraint that applies (excludedTools is ignored). Preserves CLI / pre-existing client behavior. Default. - #[serde(rename = "available")] - Available, - /// 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. - #[serde(rename = "excluded")] - Excluded, - /// Unknown variant for forward compatibility. - #[default] - #[serde(other)] - Unknown, -} - /// Approve this single request only #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] pub enum PermissionDecisionApproveOnceKind { @@ -45389,6 +49668,56 @@ pub enum RemoteSessionMetadataTaskType { Unknown, } +/// One atomic settings mutation. +/// +///
+/// +/// **Experimental.** This type 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 SettingsEditOperation { + /// Set one scalar value. + #[serde(rename = "set")] + Set, + /// Add one string to a list, unless present. + #[serde(rename = "add-item")] + AddItem, + /// Remove one string from a list, unless absent. + #[serde(rename = "remove-item")] + RemoveItem, + /// Remove the setting without supplying a value. + #[serde(rename = "remove")] + Remove, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, +} + +/// Which repository settings file to edit. +/// +///
+/// +/// **Experimental.** This type 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 RepoSettingsEditScope { + /// The shared repository settings file. + #[serde(rename = "repo")] + Repo, + /// The local repository settings file. + #[serde(rename = "local")] + Local, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, +} + /// Output format discriminator. Currently only json_schema is supported. #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] pub enum ResponseFormatType { @@ -45537,6 +49866,9 @@ pub enum SessionCapability { /// Cross-session history tools and session-store SQL prompt/tool metadata. #[serde(rename = "session-store")] SessionStore, + /// Read-only GitHub cache SQL tool (github_sql). + #[serde(rename = "github-cache-store")] + GitHubCacheStore, /// MCP Apps UI passthrough. #[serde(rename = "mcp-apps")] McpApps, @@ -46324,6 +50656,31 @@ pub enum SessionVisibilityStatus { Unknown, } +/// Whether one atomic settings edit changed a 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, PartialEq, Eq, Serialize, Deserialize)] +pub enum SettingsEditStatus { + /// The edit changed the setting and was stored. + #[serde(rename = "changed")] + Changed, + /// The setting already had the requested state. + #[serde(rename = "unchanged")] + Unchanged, + /// The existing settings file could not be edited safely; nothing was written. + #[serde(rename = "invalid")] + Invalid, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, +} + /// Signal to send (default: SIGTERM) /// ///
@@ -46474,6 +50831,21 @@ pub enum SkillInstallationFailureReason { /// A storage operation failed; inspect durable state before retrying. #[serde(rename = "write-failed")] WriteFailed, + /// The materialisation reference is malformed or uses an unsupported schema version. Returned only to callers that require skill-reference-installation. + #[serde(rename = "invalid-reference")] + InvalidReference, + /// Agent Finder has no materialisation for the referenced resource revision (HTTP 404). Returned only to callers that require skill-reference-installation. + #[serde(rename = "descriptor-not-found")] + DescriptorNotFound, + /// Agent Finder withdrew the referenced resource revision (HTTP 410). Returned only to callers that require skill-reference-installation. + #[serde(rename = "descriptor-withdrawn")] + DescriptorWithdrawn, + /// The retrieved descriptor does not match the referenced descriptor or bundle digest. Returned only to callers that require skill-reference-installation. + #[serde(rename = "digest-mismatch")] + DigestMismatch, + /// The referenced resource is not a Skill. Returned only to callers that require skill-reference-installation. + #[serde(rename = "unsupported-resource")] + UnsupportedResource, /// Unknown variant for forward compatibility. #[default] #[serde(other)] @@ -46852,6 +51224,62 @@ pub enum SlashCommandInvocationResult { SetPlanModel(SlashCommandSetPlanModelResult), } +/// What the one-time staff log-level migration did. +/// +///
+/// +/// **Experimental.** This type 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 StaffLogLevelMigrationStatus { + /// The account is not a staff account, so the migration does not apply and nothing was recorded. + #[serde(rename = "ineligible")] + Ineligible, + /// The migration already ran; the stored completion timestamp is returned unchanged. + #[serde(rename = "current")] + Current, + /// The migration ran now. The persisted settings may have changed, so the host should adopt the returned settings when they are present. + #[serde(rename = "refreshed")] + Refreshed, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, +} + +/// What the one-time staff update-channel migration did. +/// +///
+/// +/// **Experimental.** This type 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 StaffUpdateChannelMigrationStatus { + /// The account is not a staff account, so the migration does not apply and nothing was recorded. + #[serde(rename = "ineligible")] + Ineligible, + /// The migration already ran within the cutoff window; the stored completion timestamp is returned unchanged. + #[serde(rename = "current")] + Current, + /// The persisted update channel was changed to stable. Only this outcome requires the host to reload its configuration. + #[serde(rename = "migrated")] + Migrated, + /// The persisted channel was already stable, so nothing changed, but the migration is now recorded as done. + #[serde(rename = "completed")] + Completed, + /// The account is on the experimental channel, which the migration deliberately leaves alone, and the migration is recorded as done. + #[serde(rename = "excluded-experimental")] + ExcludedExperimental, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, +} + /// Context tier override for matching subagents /// ///
@@ -47435,6 +51863,34 @@ pub enum UISessionLimitsExhaustedResponseAction { Unknown, } +/// What the one-time obsolete-settings cleanup did to the document. +/// +///
+/// +/// **Experimental.** This type 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 UserSettingsMigrateObsoleteOnDiskStatus { + /// No settings document exists, so there was nothing to clean. + #[serde(rename = "missing")] + Missing, + /// The document could not be parsed, so it was left untouched rather than rewritten from a partial read. + #[serde(rename = "invalid")] + Invalid, + /// The document parsed and held nothing obsolete, so nothing was written. + #[serde(rename = "unchanged")] + Unchanged, + /// Obsolete keys were removed and the document was rewritten. + #[serde(rename = "changed")] + Changed, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, +} + /// Execution-critical workflow storage operation. /// ///
diff --git a/rust/src/generated/rpc.rs b/rust/src/generated/rpc.rs index 2eb9168bab..b3babe4378 100644 --- a/rust/src/generated/rpc.rs +++ b/rust/src/generated/rpc.rs @@ -64,6 +64,13 @@ impl<'a> ClientRpc<'a> { } } + /// `contentExclusion.*` sub-namespace. + pub fn content_exclusion(&self) -> ClientRpcContentExclusion<'a> { + ClientRpcContentExclusion { + client: self.client, + } + } + /// `environments.*` sub-namespace. pub fn environments(&self) -> ClientRpcEnvironments<'a> { ClientRpcEnvironments { @@ -78,6 +85,13 @@ impl<'a> ClientRpc<'a> { } } + /// `featureFlags.*` sub-namespace. + pub fn feature_flags(&self) -> ClientRpcFeatureFlags<'a> { + ClientRpcFeatureFlags { + client: self.client, + } + } + /// `git.*` sub-namespace. pub fn git(&self) -> ClientRpcGit<'a> { ClientRpcGit { @@ -155,6 +169,20 @@ impl<'a> ClientRpc<'a> { } } + /// `permissions.*` sub-namespace. + pub fn permissions(&self) -> ClientRpcPermissions<'a> { + ClientRpcPermissions { + client: self.client, + } + } + + /// `persistence.*` sub-namespace. + pub fn persistence(&self) -> ClientRpcPersistence<'a> { + ClientRpcPersistence { + client: self.client, + } + } + /// `plugins.*` sub-namespace. pub fn plugins(&self) -> ClientRpcPlugins<'a> { ClientRpcPlugins { @@ -162,6 +190,13 @@ impl<'a> ClientRpc<'a> { } } + /// `repo.*` sub-namespace. + pub fn repo(&self) -> ClientRpcRepo<'a> { + ClientRpcRepo { + client: self.client, + } + } + /// `runtime.*` sub-namespace. pub fn runtime(&self) -> ClientRpcRuntime<'a> { ClientRpcRuntime { @@ -463,6 +498,123 @@ impl<'a> ClientRpcAccount<'a> { .await?; Ok(serde_json::from_value(_value)?) } + + /// Renders the sign-in banner and the one-line account description the runtime uses for an authentication identity. The runtime words these in several places for its own callers, so a host that announces a sign-in or an account switch should render them from here rather than composing its own, which would let the two spellings drift apart. + /// + /// Wire method: `account.describeAuthInfo`. + /// + /// # Parameters + /// + /// * `params` - The authentication identity whose shared wording should be rendered. + /// + /// # Returns + /// + /// The wording the runtime uses for an authentication identity. + /// + ///
+ /// + /// **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 describe_auth_info( + &self, + params: AccountDescribeAuthInfoRequest, + ) -> Result { + let wire_params = serde_json::to_value(params)?; + let _value = self + .client + .call(rpc_methods::ACCOUNT_DESCRIBEAUTHINFO, Some(wire_params)) + .await?; + Ok(serde_json::from_value(_value)?) + } + + /// Reports whether the supplied identity can create gists. Gists are unavailable on GitHub Enterprise Cloud with data residency, and to Enterprise Managed Users on github.com, so a host that offers gist actions should hide or disable them when this reports no support. + /// + /// Wire method: `account.getGistSupport`. + /// + /// # Parameters + /// + /// * `params` - The authentication identity whose gist support should be reported. + /// + /// # Returns + /// + /// Whether the identity can create gists, and why not when it cannot. + /// + ///
+ /// + /// **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_gist_support( + &self, + params: AccountGetGistSupportRequest, + ) -> Result { + let wire_params = serde_json::to_value(params)?; + let _value = self + .client + .call(rpc_methods::ACCOUNT_GETGISTSUPPORT, Some(wire_params)) + .await?; + Ok(serde_json::from_value(_value)?) + } + + /// Resolves a path on the identity's GitHub host into an absolute URL, so a host can link to settings or documentation on the enterprise instance the user is signed in to rather than to github.com. Falls back to the public host when no identity is supplied. + /// + /// Wire method: `account.getWebUrl`. + /// + /// # Parameters + /// + /// * `params` - A path to resolve against the GitHub host an identity is signed in to. + /// + /// # Returns + /// + /// An absolute URL on the identity's GitHub 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_web_url( + &self, + params: AccountGetWebUrlRequest, + ) -> Result { + let wire_params = serde_json::to_value(params)?; + let _value = self + .client + .call(rpc_methods::ACCOUNT_GETWEBURL, Some(wire_params)) + .await?; + Ok(serde_json::from_value(_value)?) + } + + /// Names the environment variable supplying a classic personal access token, if one is set. A classic token cannot be upgraded in place, so a host that finds one should tell the user which variable to unset rather than starting an interactive sign-in that the variable would override. + /// + /// Wire method: `account.getTokenEnvVar`. + /// + /// # Returns + /// + /// The environment variable supplying a classic personal access token, when one is set. + /// + ///
+ /// + /// **Experimental.** This API is part of an experimental wire-protocol surface + /// and may change or be removed in future SDK or CLI releases. Pin both the + /// SDK and CLI versions if your code depends on it. + /// + ///
+ pub(crate) async fn get_token_env_var(&self) -> Result { + let wire_params = serde_json::json!({}); + let _value = self + .client + .call(rpc_methods::ACCOUNT_GETTOKENENVVAR, Some(wire_params)) + .await?; + Ok(serde_json::from_value(_value)?) + } } /// `agentRegistry.*` RPCs. @@ -539,6 +691,39 @@ impl<'a> ClientRpcAgents<'a> { Ok(serde_json::from_value(_value)?) } + /// Discovers custom agents across user, project, plugin, and remote sources. + /// + /// Wire method: `agents.discover`. + /// + /// # Parameters + /// + /// * `params` - Optional project paths to include in agent discovery. + /// + /// # Returns + /// + /// Agents discovered across user, project, plugin, and remote 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. + /// + ///
+ /// + /// Accepts [`AgentsDiscoverOptions`], including inputs added after [`AgentsDiscoverRequest`]. + pub async fn discover_with_options( + &self, + params: AgentsDiscoverOptions, + ) -> Result { + let wire_params = serde_json::to_value(params)?; + let _value = self + .client + .call(rpc_methods::AGENTS_DISCOVER, Some(wire_params)) + .await?; + Ok(serde_json::from_value(_value)?) + } + /// Returns the canonical directories where a client may create custom agents that the runtime will recognize, including ones that do not exist yet. Project directories become active once created. /// /// Wire method: `agents.getDiscoveryPaths`. @@ -570,6 +755,39 @@ impl<'a> ClientRpcAgents<'a> { Ok(serde_json::from_value(_value)?) } + /// Returns the canonical directories where a client may create custom agents that the runtime will recognize, including ones that do not exist yet. Project directories become active once created. + /// + /// Wire method: `agents.getDiscoveryPaths`. + /// + /// # Parameters + /// + /// * `params` - Optional project paths to include when enumerating agent discovery directories. + /// + /// # Returns + /// + /// Canonical locations where custom agents can be created so the runtime will recognize them. + /// + ///
+ /// + /// **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. + /// + ///
+ /// + /// Accepts [`AgentsGetDiscoveryPathsOptions`], including inputs added after [`AgentsGetDiscoveryPathsRequest`]. + pub async fn get_discovery_paths_with_options( + &self, + params: AgentsGetDiscoveryPathsOptions, + ) -> Result { + let wire_params = serde_json::to_value(params)?; + let _value = self + .client + .call(rpc_methods::AGENTS_GETDISCOVERYPATHS, Some(wire_params)) + .await?; + Ok(serde_json::from_value(_value)?) + } + /// Lists the agents this runtime ships, by name. A consumer separating shipped agents from ones the user or a plugin authored should compare against these names rather than against `AgentInfo.source`: an authored agent may carry the `builtin` source while not being one of these, and the runtime treats the two as separate questions. `disableableNames` is the subset a user may turn off, which a client needs to decide whether to offer a toggle. `yamlBasedNames` is the subset backed by a shipped YAML definition, which a client needs before asking the runtime to load one. /// /// Wire method: `agents.getBuiltins`. @@ -975,6 +1193,48 @@ impl<'a> ClientRpcConnectors<'a> { } } +/// `contentExclusion.*` RPCs. +#[derive(Clone, Copy)] +pub struct ClientRpcContentExclusion<'a> { + pub(crate) client: &'a Client, +} + +impl<'a> ClientRpcContentExclusion<'a> { + /// Validates candidate content-exclusion policies and returns them normalized, without starting a session. This runs the same parser the runtime's own policy enforcement uses, so a host cannot accept a policy set the runtime would later reject, nor reject one it would have honored. Call it when policies arrive from outside the runtime — a command-line flag, a configuration file, a host's own settings — and must be checked before a session exists to apply them to. A malformed document fails with a message prefixed `Invalid JSON: `; a well-formed document that violates the policy schema fails with the offending field paths. + /// + /// Wire method: `contentExclusion.validatePolicies`. + /// + /// # Parameters + /// + /// * `params` - Candidate content-exclusion policies to validate, as the raw JSON text a host received. The text is supplied unparsed so that a syntax error and a schema violation are both reported by the runtime's own validator rather than by the caller's JSON parser. + /// + /// # Returns + /// + /// The accepted policies, parsed and normalized. A rejected input is reported as an error rather than in this result. + /// + ///
+ /// + /// **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 validate_policies( + &self, + params: ContentExclusionValidatePoliciesRequest, + ) -> Result { + let wire_params = serde_json::to_value(params)?; + let _value = self + .client + .call( + rpc_methods::CONTENTEXCLUSION_VALIDATEPOLICIES, + Some(wire_params), + ) + .await?; + Ok(serde_json::from_value(_value)?) + } +} + /// `environments.*` RPCs. #[derive(Clone, Copy)] pub struct ClientRpcEnvironments<'a> { @@ -1156,24 +1416,24 @@ impl<'a> ClientRpcExtensions<'a> { } } -/// `git.*` RPCs. +/// `featureFlags.*` RPCs. #[derive(Clone, Copy)] -pub struct ClientRpcGit<'a> { +pub struct ClientRpcFeatureFlags<'a> { pub(crate) client: &'a Client, } -impl<'a> ClientRpcGit<'a> { - /// Reads the remote that the branch checked out in a working tree tracks, as `branch..remote` configures it. Reports `origin` rather than failing whenever there is no tracking configuration to read — on a detached HEAD, on a branch with no upstream, or when git itself fails — because a caller asking which remote to talk to needs an answer it can act on, not an error. Marked internal because it exists to carry a CLI call site off the napi boundary onto the SDK contract; it is migration plumbing, not a surface consumers are meant to depend on. +impl<'a> ClientRpcFeatureFlags<'a> { + /// Resolves named gates against this installation's cached online-experiment assignment, for the account the runtime is already authenticated as. Unlike `featureFlags.resolve`, which layers static availability with the populations supplied, this reads the experiment assignment the runtime cached for its own current authentication, so a launch can honor a rollout before any session exists. The account is never taken from the request: naming one would read another account's cached assignments. The cached assignment answers immediately and any network refresh happens behind it, so this call does not wait on the network. Not available to an extension, which cannot carry the user's consent to read their assignment. /// - /// Wire method: `git.currentBranchRemote`. + /// Wire method: `featureFlags.resolveStartupGates`. /// /// # Parameters /// - /// * `params` - Working-tree path a git query applies to. + /// * `params` - Caller population and startup inputs to resolve named online-experiment gates for. Carries no account: the runtime resolves the assignment for the authentication it already holds. /// /// # Returns /// - /// The remote the checked-out branch tracks. + /// The resolved gates, keyed by flag name. /// ///
/// @@ -1182,29 +1442,28 @@ impl<'a> ClientRpcGit<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub(crate) async fn current_branch_remote( + pub(crate) async fn resolve_startup_gates( &self, - params: GitCwdRequest, - ) -> Result { + params: FeatureFlagsResolveStartupGatesRequest, + ) -> Result { let wire_params = serde_json::to_value(params)?; let _value = self .client - .call(rpc_methods::GIT_CURRENTBRANCHREMOTE, Some(wire_params)) + .call( + rpc_methods::FEATUREFLAGS_RESOLVESTARTUPGATES, + Some(wire_params), + ) .await?; Ok(serde_json::from_value(_value)?) } - /// Collects the repository context of a working directory in one call: working tree root, repository identifier and host, current branch, and the HEAD and base commits. Every repository field is omitted when the path is not inside a git working tree, and the requested path is echoed back as `cwd`. The answer is the same `SessionWorkingDirectoryContext` that `session.metadata.recordContextChange` accepts, so a caller polling for a context change can forward the result unchanged. Marked internal because it exists to carry a CLI call site off the napi boundary onto the SDK contract; it is migration plumbing, not a surface consumers are meant to depend on. It can become public once an SDK consumer needs to derive session context from a directory itself. - /// - /// Wire method: `git.workingDirectoryContext`. - /// - /// # Parameters + /// Lists every feature flag this runtime declares, with the population each one is declared for, its experimental description, and the online-experiment flag it maps to. This is the static catalog: it describes how each flag is defined, not whether it is on for anyone. Use `featureFlags.resolve` for static values with caller overrides, not active runtime values or experiment assignments. /// - /// * `params` - Working-tree path a git query applies to. + /// Wire method: `featureFlags.listMetadata`. /// /// # Returns /// - /// Updated working directory and git context. Emitted as the new payload of `session.context_changed`. + /// Every feature flag this runtime declares, with the shape each one was declared with. /// ///
/// @@ -1213,29 +1472,26 @@ impl<'a> ClientRpcGit<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub(crate) async fn working_directory_context( - &self, - params: GitCwdRequest, - ) -> Result { - let wire_params = serde_json::to_value(params)?; + pub(crate) async fn list_metadata(&self) -> Result { + let wire_params = serde_json::json!({}); let _value = self .client - .call(rpc_methods::GIT_WORKINGDIRECTORYCONTEXT, Some(wire_params)) + .call(rpc_methods::FEATUREFLAGS_LISTMETADATA, Some(wire_params)) .await?; Ok(serde_json::from_value(_value)?) } - /// Lists the GitHub repositories a working tree's remotes point at, one entry per distinct repository, so a caller can resolve a base and head repository without parsing remote URLs itself. When several remotes name the same repository, only the first is listed, and the entry keeps that remote name. Remotes pointing at no GitHub host are left out, so an empty list means the tree reaches GitHub through no remote. Failing to read the remotes is reported as an error rather than as an empty list, because the two mean different things to a caller. Marked internal because it exists to carry a CLI call site off the napi boundary onto the SDK contract; it is migration plumbing, not a surface consumers are meant to depend on. + /// Computes static feature flags from the supplied population and overrides without changing runtime state or verifying audience membership. Starts from declared availability, then applies direct feature-flag environment values, user settings, and explicit `flagOverrides`. It does not apply `COPILOT_EXP_*` experiment assignments, authenticated startup gates, or active runtime state. Settings include `enabledFeatureFlags`, the legacy `feature_flags.enabled` list, and supported convenience settings. Supply `baseFlags` to use an existing flag set instead of declared availability. Omit `isStaffGitHub` or `isStaffMicrosoft` when membership was never recorded: a legacy staff account then counts as both populations. /// - /// Wire method: `git.reposFromRemotes`. + /// Wire method: `featureFlags.resolve`. /// /// # Parameters /// - /// * `params` - Git working tree whose GitHub remotes should be listed. + /// * `params` - Caller population and overrides for static feature-flag resolution, independent of experiment assignments and active runtime state. /// /// # Returns /// - /// The GitHub repositories a working tree's remotes point at. + /// The resolved feature flags, keyed by flag name. /// ///
/// @@ -1244,33 +1500,37 @@ impl<'a> ClientRpcGit<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub(crate) async fn repos_from_remotes( + pub(crate) async fn resolve( &self, - params: GitReposFromRemotesRequest, - ) -> Result { + params: FeatureFlagsResolveRequest, + ) -> Result { let wire_params = serde_json::to_value(params)?; let _value = self .client - .call(rpc_methods::GIT_REPOSFROMREMOTES, Some(wire_params)) + .call(rpc_methods::FEATUREFLAGS_RESOLVE, Some(wire_params)) .await?; Ok(serde_json::from_value(_value)?) } } -/// `gitHubOwners.*` RPCs. +/// `git.*` RPCs. #[derive(Clone, Copy)] -pub struct ClientRpcGitHubOwners<'a> { +pub struct ClientRpcGit<'a> { pub(crate) client: &'a Client, } -impl<'a> ClientRpcGitHubOwners<'a> { - /// Registers a cancellable owner listing and returns its request id. Separate from `gitHubOwners.list` so the id exists before the listing starts: a caller that abandons the listing the moment it begins would otherwise have nothing to name in `gitHubOwners.cancel`. The id serves one listing only. Long-abandoned unused ids can be released by later allocations. +impl<'a> ClientRpcGit<'a> { + /// Reads the remote that the branch checked out in a working tree tracks, as `branch..remote` configures it. Reports `origin` rather than failing whenever there is no tracking configuration to read — on a detached HEAD, on a branch with no upstream, or when git itself fails — because a caller asking which remote to talk to needs an answer it can act on, not an error. Marked internal because it exists to carry a CLI call site off the napi boundary onto the SDK contract; it is migration plumbing, not a surface consumers are meant to depend on. /// - /// Wire method: `gitHubOwners.nextRequestId`. + /// Wire method: `git.currentBranchRemote`. + /// + /// # Parameters + /// + /// * `params` - Working-tree path a git query applies to. /// /// # Returns /// - /// A freshly registered request id. Registering it before the listing starts is what lets a cancel that races the request still find the owner listing slot. The id serves one listing only. Long-abandoned unused ids can be released by later allocations. + /// The remote the checked-out branch tracks. /// ///
/// @@ -1279,12 +1539,109 @@ impl<'a> ClientRpcGitHubOwners<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub(crate) async fn next_request_id(&self) -> Result { - let wire_params = serde_json::json!({}); - let _value = self - .client - .call(rpc_methods::GITHUBOWNERS_NEXTREQUESTID, Some(wire_params)) - .await?; + pub(crate) async fn current_branch_remote( + &self, + params: GitCwdRequest, + ) -> Result { + let wire_params = serde_json::to_value(params)?; + let _value = self + .client + .call(rpc_methods::GIT_CURRENTBRANCHREMOTE, Some(wire_params)) + .await?; + Ok(serde_json::from_value(_value)?) + } + + /// Collects the repository context of a working directory in one call: working tree root, repository identifier and host, current branch, and the HEAD and base commits. Every repository field is omitted when the path is not inside a git working tree, and the requested path is echoed back as `cwd`. The answer is the same `SessionWorkingDirectoryContext` that `session.metadata.recordContextChange` accepts, so a caller polling for a context change can forward the result unchanged. Marked internal because it exists to carry a CLI call site off the napi boundary onto the SDK contract; it is migration plumbing, not a surface consumers are meant to depend on. It can become public once an SDK consumer needs to derive session context from a directory itself. + /// + /// Wire method: `git.workingDirectoryContext`. + /// + /// # Parameters + /// + /// * `params` - Working-tree path a git query applies to. + /// + /// # Returns + /// + /// Updated working directory and git context. Emitted as the new payload of `session.context_changed`. + /// + ///
+ /// + /// **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 working_directory_context( + &self, + params: GitCwdRequest, + ) -> Result { + let wire_params = serde_json::to_value(params)?; + let _value = self + .client + .call(rpc_methods::GIT_WORKINGDIRECTORYCONTEXT, Some(wire_params)) + .await?; + Ok(serde_json::from_value(_value)?) + } + + /// Lists the GitHub repositories a working tree's remotes point at, one entry per distinct repository, so a caller can resolve a base and head repository without parsing remote URLs itself. When several remotes name the same repository, only the first is listed, and the entry keeps that remote name. Remotes pointing at no GitHub host are left out, so an empty list means the tree reaches GitHub through no remote. Failing to read the remotes is reported as an error rather than as an empty list, because the two mean different things to a caller. Marked internal because it exists to carry a CLI call site off the napi boundary onto the SDK contract; it is migration plumbing, not a surface consumers are meant to depend on. + /// + /// Wire method: `git.reposFromRemotes`. + /// + /// # Parameters + /// + /// * `params` - Git working tree whose GitHub remotes should be listed. + /// + /// # Returns + /// + /// The GitHub repositories a working tree's remotes point at. + /// + ///
+ /// + /// **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 repos_from_remotes( + &self, + params: GitReposFromRemotesRequest, + ) -> Result { + let wire_params = serde_json::to_value(params)?; + let _value = self + .client + .call(rpc_methods::GIT_REPOSFROMREMOTES, Some(wire_params)) + .await?; + Ok(serde_json::from_value(_value)?) + } +} + +/// `gitHubOwners.*` RPCs. +#[derive(Clone, Copy)] +pub struct ClientRpcGitHubOwners<'a> { + pub(crate) client: &'a Client, +} + +impl<'a> ClientRpcGitHubOwners<'a> { + /// Registers a cancellable owner listing and returns its request id. Separate from `gitHubOwners.list` so the id exists before the listing starts: a caller that abandons the listing the moment it begins would otherwise have nothing to name in `gitHubOwners.cancel`. The id serves one listing only. Long-abandoned unused ids can be released by later allocations. + /// + /// Wire method: `gitHubOwners.nextRequestId`. + /// + /// # Returns + /// + /// A freshly registered request id. Registering it before the listing starts is what lets a cancel that races the request still find the owner listing slot. The id serves one listing only. Long-abandoned unused ids can be released by later allocations. + /// + ///
+ /// + /// **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 next_request_id(&self) -> Result { + let wire_params = serde_json::json!({}); + let _value = self + .client + .call(rpc_methods::GITHUBOWNERS_NEXTREQUESTID, Some(wire_params)) + .await?; Ok(serde_json::from_value(_value)?) } @@ -1452,7 +1809,7 @@ impl<'a> ClientRpcGlobalState<'a> { Ok(serde_json::from_value(_value)?) } - /// Records one top-level key in the host's machine-wide state, the counterpart to `globalState.load`. A host calls this to remember that it has shown an onboarding step, asked a one-off question, or completed a migration, so the next run can skip it. Only the named key is replaced and the rest of the document is preserved, which lets two writers record different flags without overwriting each other; passing no value removes the key instead. Only the keys a host records itself are writable: `appInstallNudgeResponded`, `appTipShown`, `askedSetupTerminals`, `autoFeedbackLastPromptedAt`, `firstLaunchAt`, `recentModelIds`, `sandboxCredentialProxyCaDeclined` and `sandboxOnboardingShown`. Every other key is refused, including `installedPlugins`, the stored credentials, `trustedFolders`, the staff flags and the signed-in accounts. Plugin enablement must use the plugin APIs, which apply repository and managed-policy checks. + /// Records one top-level key in the host's machine-wide state, the counterpart to `globalState.load`. A host calls this to remember that it has shown an onboarding step, asked a one-off question, or completed a migration, so the next run can skip it. Only the named key is replaced and the rest of the document is preserved, which lets two writers record different flags without overwriting each other; passing no value removes the key instead. Only the keys a host records itself are writable: `appTipShown`, `askedSetupTerminals`, `autoFeedbackLastPromptedAt`, `firstLaunchAt`, `recentModelIds`, `sandboxCredentialProxyCaDeclined` and `sandboxOnboardingShown`. Every other key is refused, including `installedPlugins`, the stored credentials, `trustedFolders`, the staff flags and the signed-in accounts. Plugin enablement must use the plugin APIs, which apply repository and managed-policy checks. /// /// Wire method: `globalState.writeKey`. /// @@ -1514,6 +1871,39 @@ impl<'a> ClientRpcHooks<'a> { .await?; Ok(serde_json::from_value(_value)?) } + + /// 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. + /// + ///
+ /// + /// Accepts [`HooksDiscoverOptions`], including inputs added after [`HooksDiscoverRequest`]. + pub async fn discover_with_options( + &self, + params: HooksDiscoverOptions, + ) -> 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)?) + } } /// `host.*` RPCs. @@ -1825,17 +2215,13 @@ pub struct ClientRpcInstructions<'a> { } impl<'a> ClientRpcInstructions<'a> { - /// Discovers instruction sources across user, repository, and plugin sources. - /// - /// Wire method: `instructions.discover`. + /// Returns the prompt the `init` command runs to author repository instructions. A host offering its own initialize action sends this text so the instructions it produces match the ones the CLI produces. The text is fixed for a build and depends on no configuration, session or repository. /// - /// # Parameters - /// - /// * `params` - Optional project paths to include in instruction discovery. + /// Wire method: `instructions.getInitPrompt`. /// /// # Returns /// - /// Instruction sources discovered across user, repository, and plugin sources. + /// The prompt the init command runs. /// ///
/// @@ -1844,29 +2230,26 @@ impl<'a> ClientRpcInstructions<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub async fn discover( - &self, - params: InstructionsDiscoverRequest, - ) -> Result { - let wire_params = serde_json::to_value(params)?; + pub(crate) async fn get_init_prompt(&self) -> Result { + let wire_params = serde_json::json!({}); let _value = self .client - .call(rpc_methods::INSTRUCTIONS_DISCOVER, Some(wire_params)) + .call(rpc_methods::INSTRUCTIONS_GETINITPROMPT, Some(wire_params)) .await?; Ok(serde_json::from_value(_value)?) } - /// Returns the canonical files and directories where a client may create custom instructions that the runtime will recognize, including ones that do not exist yet. Repository targets become active once created. + /// Discovers instruction sources across user, repository, and plugin sources. /// - /// Wire method: `instructions.getDiscoveryPaths`. + /// Wire method: `instructions.discover`. /// /// # Parameters /// - /// * `params` - Optional project paths to include when enumerating instruction discovery targets. + /// * `params` - Optional project paths to include in instruction discovery. /// /// # Returns /// - /// Canonical files and directories where custom instructions can be created so the runtime will recognize them. + /// Instruction sources discovered across user, repository, and plugin sources. /// ///
/// @@ -1875,36 +2258,29 @@ impl<'a> ClientRpcInstructions<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub async fn get_discovery_paths( + pub async fn discover( &self, - params: InstructionsGetDiscoveryPathsRequest, - ) -> Result { + params: InstructionsDiscoverRequest, + ) -> Result { let wire_params = serde_json::to_value(params)?; let _value = self .client - .call( - rpc_methods::INSTRUCTIONS_GETDISCOVERYPATHS, - Some(wire_params), - ) + .call(rpc_methods::INSTRUCTIONS_DISCOVER, Some(wire_params)) .await?; Ok(serde_json::from_value(_value)?) } -} - -/// `llmInference.*` RPCs. -#[derive(Clone, Copy)] -pub struct ClientRpcLlmInference<'a> { - pub(crate) client: &'a Client, -} -impl<'a> ClientRpcLlmInference<'a> { - /// Registers an SDK client as the LLM inference callback provider. + /// Discovers instruction sources across user, repository, and plugin sources. /// - /// Wire method: `llmInference.setProvider`. + /// Wire method: `instructions.discover`. + /// + /// # Parameters + /// + /// * `params` - Optional project paths to include in instruction discovery. /// /// # Returns /// - /// Indicates whether the calling client was registered as the LLM inference provider. + /// Instruction sources discovered across user, repository, and plugin sources. /// ///
/// @@ -1913,26 +2289,31 @@ impl<'a> ClientRpcLlmInference<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub async fn set_provider(&self) -> Result { - let wire_params = serde_json::json!({}); + /// + /// Accepts [`InstructionsDiscoverOptions`], including inputs added after [`InstructionsDiscoverRequest`]. + pub async fn discover_with_options( + &self, + params: InstructionsDiscoverOptions, + ) -> Result { + let wire_params = serde_json::to_value(params)?; let _value = self .client - .call(rpc_methods::LLMINFERENCE_SETPROVIDER, Some(wire_params)) + .call(rpc_methods::INSTRUCTIONS_DISCOVER, Some(wire_params)) .await?; Ok(serde_json::from_value(_value)?) } - /// 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. + /// Discovers the instruction sources for one caller-named location and returns them in the order the runtime applies them. `discover` answers for a set of project paths and derives the rest of its scope itself; this one instead takes every argument from the caller — the extra directories to scan and whether to descend into nested instruction files — and honors them as given. It also includes instruction directories from trusted repository settings, matching native repository discovery. Use it when the caller knows its own scope and no session exists to derive that scope from, such as a host reporting how many instruction files a directory contributes before any session starts. Marked internal because it exists to carry a CLI call site off the napi boundary onto the SDK contract; it is migration plumbing, not a surface consumers are meant to depend on. /// - /// Wire method: `llmInference.httpResponseStart`. + /// Wire method: `instructions.discoverRepoSources`. /// /// # Parameters /// - /// * `params` - Response head. + /// * `params` - Explicit discovery arguments for one location, supplied by the caller rather than derived from a session. /// /// # Returns /// - /// Whether the start frame was accepted. + /// Instruction sources discovered across user, repository, and plugin sources. /// ///
/// @@ -1941,32 +2322,32 @@ impl<'a> ClientRpcLlmInference<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub async fn http_response_start( + pub(crate) async fn discover_repo_sources( &self, - params: LlmInferenceHttpResponseStartRequest, - ) -> Result { + params: InstructionsDiscoverRepoSourcesRequest, + ) -> Result { let wire_params = serde_json::to_value(params)?; let _value = self .client .call( - rpc_methods::LLMINFERENCE_HTTPRESPONSESTART, + rpc_methods::INSTRUCTIONS_DISCOVERREPOSOURCES, Some(wire_params), ) .await?; Ok(serde_json::from_value(_value)?) } - /// Delivers a body byte range (or a terminal transport error) for an in-flight response, correlated by requestId. Set `end` true on the last chunk. When `error` is set the response terminates with a transport-level failure and the runtime raises an APIConnectionError. + /// Returns the canonical files and directories where a client may create custom instructions that the runtime will recognize, including ones that do not exist yet. Repository targets become active once created. /// - /// Wire method: `llmInference.httpResponseChunk`. + /// Wire method: `instructions.getDiscoveryPaths`. /// /// # Parameters /// - /// * `params` - A response body chunk or terminal error. + /// * `params` - Optional project paths to include when enumerating instruction discovery targets. /// /// # Returns /// - /// Whether the chunk was accepted. + /// Canonical files and directories where custom instructions can be created so the runtime will recognize them. /// ///
/// @@ -1975,36 +2356,32 @@ impl<'a> ClientRpcLlmInference<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub async fn http_response_chunk( + pub async fn get_discovery_paths( &self, - params: LlmInferenceHttpResponseChunkRequest, - ) -> Result { + params: InstructionsGetDiscoveryPathsRequest, + ) -> Result { let wire_params = serde_json::to_value(params)?; let _value = self .client .call( - rpc_methods::LLMINFERENCE_HTTPRESPONSECHUNK, + rpc_methods::INSTRUCTIONS_GETDISCOVERYPATHS, Some(wire_params), ) .await?; Ok(serde_json::from_value(_value)?) } -} - -/// `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. `managedSettings.resolve` returns the same device settings together with the account's server policy. + /// Returns the canonical files and directories where a client may create custom instructions that the runtime will recognize, including ones that do not exist yet. Repository targets become active once created. /// - /// Wire method: `managedSettings.read`. + /// Wire method: `instructions.getDiscoveryPaths`. + /// + /// # Parameters + /// + /// * `params` - Optional project paths to include when enumerating instruction discovery targets. /// /// # Returns /// - /// Validated device-managed settings discovered before a session exists. + /// Canonical files and directories where custom instructions can be created so the runtime will recognize them. /// ///
/// @@ -2013,18 +2390,26 @@ impl<'a> ClientRpcManagedSettings<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub async fn read(&self) -> Result { - let wire_params = serde_json::json!({}); + /// + /// Accepts [`InstructionsGetDiscoveryPathsOptions`], including inputs added after [`InstructionsGetDiscoveryPathsRequest`]. + pub async fn get_discovery_paths_with_options( + &self, + params: InstructionsGetDiscoveryPathsOptions, + ) -> Result { + let wire_params = serde_json::to_value(params)?; let _value = self .client - .call(rpc_methods::MANAGEDSETTINGS_READ, Some(wire_params)) + .call( + rpc_methods::INSTRUCTIONS_GETDISCOVERYPATHS, + Some(wire_params), + ) .await?; Ok(serde_json::from_value(_value)?) } - /// Force-refreshes enterprise managed settings for every account: wipes the persistent server-policy cache (the whole `/managed-settings` directory) and drops this runtime process's in-memory retained server policy. It does not itself fetch policy — the effect is that the next time a session resolves managed settings for an account, that resolution re-fetches the account's org policy from the network instead of serving a cached response. Note that `managedSettings.read` returns only device/MDM settings and never triggers the account server-policy fetch, so a host implementing "sync account policy" should call `managedSettings.resolve` or start a fresh session resolution rather than treat a subsequent `managedSettings.read` as the refreshed org policy. Mirrors the invalidation a sign-out performs, broadened from the one signing-out account to all of them; device/MDM layers describe the machine, not the account, and are left untouched. Rejects if the on-disk cache cannot be removed. + /// Drops the runtime's in-memory cache of discovered custom instructions so the next discovery reads them from disk again. A host calls this after it changes instruction files, or after it trusts a folder whose instructions an earlier discovery skipped, so that a session started afterwards sees the current files instead of a cached earlier answer. The call performs no I/O of its own and removes nothing from disk; it only invalidates, so the cost lands on the next discovery. Returns once the cache is clear, which is what lets a caller order a session start after it. Marked internal because it exists to carry a CLI call site off the napi boundary onto the SDK contract; it is migration plumbing, not a surface consumers are meant to depend on. /// - /// Wire method: `managedSettings.clearCache`. + /// Wire method: `instructions.clearCache`. /// ///
/// @@ -2033,22 +2418,30 @@ impl<'a> ClientRpcManagedSettings<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub async fn clear_cache(&self) -> Result<(), Error> { + pub(crate) async fn clear_cache(&self) -> Result<(), Error> { let wire_params = serde_json::json!({}); let _value = self .client - .call(rpc_methods::MANAGEDSETTINGS_CLEARCACHE, Some(wire_params)) + .call(rpc_methods::INSTRUCTIONS_CLEARCACHE, Some(wire_params)) .await?; Ok(()) } +} - /// Resolves the effective enterprise managed settings without a session, from the device channel and, when an account is available, the account's server policy through the same per-account cache sessions use. A cached server policy less than an hour old is used without a fetch; otherwise the policy is fetched, and when the fetch fails a cached policy up to 24 hours old is used instead, unless `forceRemoteSettingsRefresh` requires a live fetch. With no account requested or signed in, it reports device policy only; signing out removes the account's cached policy. It can fetch server policy over the network when the cache is stale, so call it off latency-critical paths such as startup rather than before listing models. The policy helper is not run. `layers` lists each channel's document before merging, and `values` and `meta` carry typed effective values and their lock state for the keys typed so far. - /// - /// Wire method: `managedSettings.resolve`. - /// +/// `llmInference.*` RPCs. +#[derive(Clone, Copy)] +pub struct ClientRpcLlmInference<'a> { + pub(crate) client: &'a Client, +} + +impl<'a> ClientRpcLlmInference<'a> { + /// Registers an SDK client as the LLM inference callback provider. + /// + /// Wire method: `llmInference.setProvider`. + /// /// # Returns /// - /// Effective enterprise managed settings for an account, resolved without a session. + /// Indicates whether the calling client was registered as the LLM inference provider. /// ///
/// @@ -2057,26 +2450,26 @@ impl<'a> ClientRpcManagedSettings<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub async fn resolve(&self) -> Result { + pub async fn set_provider(&self) -> Result { let wire_params = serde_json::json!({}); let _value = self .client - .call(rpc_methods::MANAGEDSETTINGS_RESOLVE, Some(wire_params)) + .call(rpc_methods::LLMINFERENCE_SETPROVIDER, Some(wire_params)) .await?; Ok(serde_json::from_value(_value)?) } - /// Resolves the effective enterprise managed settings without a session, from the device channel and, when an account is available, the account's server policy through the same per-account cache sessions use. A cached server policy less than an hour old is used without a fetch; otherwise the policy is fetched, and when the fetch fails a cached policy up to 24 hours old is used instead, unless `forceRemoteSettingsRefresh` requires a live fetch. With no account requested or signed in, it reports device policy only; signing out removes the account's cached policy. It can fetch server policy over the network when the cache is stale, so call it off latency-critical paths such as startup rather than before listing models. The policy helper is not run. `layers` lists each channel's document before merging, and `values` and `meta` carry typed effective values and their lock state for the keys typed so far. + /// 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. /// - /// Wire method: `managedSettings.resolve`. + /// Wire method: `llmInference.httpResponseStart`. /// /// # Parameters /// - /// * `params` - Optional opaque account selection or GitHub token whose managed settings are resolved. + /// * `params` - Response head. /// /// # Returns /// - /// Effective enterprise managed settings for an account, resolved without a session. + /// Whether the start frame was accepted. /// ///
/// @@ -2085,25 +2478,32 @@ impl<'a> ClientRpcManagedSettings<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub async fn resolve_with_params( + pub async fn http_response_start( &self, - params: ManagedSettingsResolveRequest, - ) -> Result { + params: LlmInferenceHttpResponseStartRequest, + ) -> Result { let wire_params = serde_json::to_value(params)?; let _value = self .client - .call(rpc_methods::MANAGEDSETTINGS_RESOLVE, Some(wire_params)) + .call( + rpc_methods::LLMINFERENCE_HTTPRESPONSESTART, + Some(wire_params), + ) .await?; Ok(serde_json::from_value(_value)?) } - /// Returns the managed-settings authoring JSON schema with descriptive `x-composition` annotations aligned with the shared settings-engine vocabulary. These annotations are not a complete runtime composition contract: model, effortLevel, and contextTier remain coupled. Use `managedSettings.compose` for the runtime's effective result. Performs no I/O. + /// Delivers a body byte range (or a terminal transport error) for an in-flight response, correlated by requestId. Set `end` true on the last chunk. When `error` is set the response terminates with a transport-level failure and the runtime raises an APIConnectionError. /// - /// Wire method: `managedSettings.schema`. + /// Wire method: `llmInference.httpResponseChunk`. + /// + /// # Parameters + /// + /// * `params` - A response body chunk or terminal error. /// /// # Returns /// - /// The authoring JSON schema for managed settings recognized by this runtime. + /// Whether the chunk was accepted. /// ///
/// @@ -2112,26 +2512,43 @@ impl<'a> ClientRpcManagedSettings<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub async fn schema(&self) -> Result { - let wire_params = serde_json::json!({}); + pub async fn http_response_chunk( + &self, + params: LlmInferenceHttpResponseChunkRequest, + ) -> Result { + let wire_params = serde_json::to_value(params)?; let _value = self .client - .call(rpc_methods::MANAGEDSETTINGS_SCHEMA, Some(wire_params)) + .call( + rpc_methods::LLMINFERENCE_HTTPRESPONSECHUNK, + Some(wire_params), + ) .await?; Ok(serde_json::from_value(_value)?) } +} - /// Validates a candidate managed-settings document the way the runtime validates delivered policy, without applying it. Reports errors that would reject the document, warnings for content the runtime ignores, and the canonical document it would apply. Document text nested more than 64 levels deep is rejected. Performs no I/O. - /// - /// Wire method: `managedSettings.validate`. - /// - /// # Parameters +/// `managedSettings.*` RPCs. +#[derive(Clone, Copy)] +pub struct ClientRpcManagedSettings<'a> { + pub(crate) client: &'a Client, +} + +impl<'a> ClientRpcManagedSettings<'a> { + /// `managedSettings.permissions.*` sub-namespace. + pub fn permissions(&self) -> ClientRpcManagedSettingsPermissions<'a> { + ClientRpcManagedSettingsPermissions { + client: self.client, + } + } + + /// Discovers device-managed settings the way the runtime's own device layer does, and reports the sandbox floor and the load outcome alongside the validated document. This differs from `managedSettings.read` in three ways a policy-enforcing host depends on: a document that validates to an empty object is reported as absent rather than as an empty policy, a discovery error does not suppress a document that still validates, and a failure is reported as a flag rather than only as error text. Hosts that compose the device layer into an effective policy use this, because `managedSettings.read` cannot distinguish "no device policy" from "device policy could not be read" and would silently hand the user's own layer the win. /// - /// * `params` - A candidate managed-settings document to validate without applying it. + /// Wire method: `managedSettings.readDevice`. /// /// # Returns /// - /// Result of validating a managed-settings document. + /// The device-managed settings layer, with the sandbox floor and the load outcome a host needs to compose it into an effective policy. /// ///
/// @@ -2140,29 +2557,22 @@ impl<'a> ClientRpcManagedSettings<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub async fn validate( - &self, - params: ManagedSettingsValidateRequest, - ) -> Result { - let wire_params = serde_json::to_value(params)?; + pub(crate) async fn read_device(&self) -> Result { + let wire_params = serde_json::json!({}); let _value = self .client - .call(rpc_methods::MANAGEDSETTINGS_VALIDATE, Some(wire_params)) + .call(rpc_methods::MANAGEDSETTINGS_READDEVICE, Some(wire_params)) .await?; Ok(serde_json::from_value(_value)?) } - /// Merges candidate managed-settings documents for the device, server, and policy-helper channels into the effective settings the runtime would enforce on this host, using the same precedence and composition rules as live resolution, without applying them. Like live resolution, a server's advisory sandbox force-enable is declined on a host that cannot run the sandbox. Does not fetch policy or read policy files, but may perform blocking OS or subprocess probes for sandbox support. Preview documents are limited to 1 MiB and 64 levels of nesting. + /// Retains the supplied managed layers as this runtime process's startup policy and re-applies the result to every live session. Hosts that own their own short-lived process policy use this, so a guarded mutation is checked against the policy the host actually fetched. Retaining is a whole-process replacement rather than a merge, so a host that already resolved a good policy must not retain a partial one over it. The failure flags are honest inputs rather than assumptions: reporting a fetch or load that failed keeps the layers already retained, and the interim fail-closed state, instead of clearing them. /// - /// Wire method: `managedSettings.compose`. + /// Wire method: `managedSettings.retainPolicy`. /// /// # Parameters /// - /// * `params` - Candidate managed-settings documents to merge without applying them. - /// - /// # Returns - /// - /// The effective managed settings the runtime would enforce for the given documents, in the same shape `managedSettings.resolve` returns. + /// * `params` - The managed layers to retain as this process's startup policy, with the outcome of fetching each one. /// ///
/// @@ -2171,51 +2581,29 @@ impl<'a> ClientRpcManagedSettings<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub async fn compose( + pub(crate) async fn retain_policy( &self, - params: ManagedSettingsComposeRequest, - ) -> Result { + params: ManagedSettingsRetainPolicyParams, + ) -> Result<(), Error> { let wire_params = serde_json::to_value(params)?; let _value = self .client - .call(rpc_methods::MANAGEDSETTINGS_COMPOSE, Some(wire_params)) + .call(rpc_methods::MANAGEDSETTINGS_RETAINPOLICY, Some(wire_params)) .await?; - Ok(serde_json::from_value(_value)?) - } -} - -/// `mcp.*` RPCs. -#[derive(Clone, Copy)] -pub struct ClientRpcMcp<'a> { - pub(crate) client: &'a Client, -} - -impl<'a> ClientRpcMcp<'a> { - /// `mcp.config.*` sub-namespace. - pub fn config(&self) -> ClientRpcMcpConfig<'a> { - ClientRpcMcpConfig { - client: self.client, - } - } - - /// `mcp.installations.*` sub-namespace. - pub fn installations(&self) -> ClientRpcMcpInstallations<'a> { - ClientRpcMcpInstallations { - client: self.client, - } + Ok(()) } - /// Discovers MCP servers from user, workspace, plugin, and builtin sources. + /// Stamps an effective managed policy's individually-controlled entries onto a user settings document, returning the settings a host should actually act on. The managed layer wins on every plugin spec and marketplace name it names, and leaves everything it does not name as the user wrote it. Hosts use this rather than merging the two documents themselves, because the merge is per entry rather than per document and a whole-document overlay would either discard the user's unmanaged entries or let a user entry outrank a managed one. Omitting the managed layer returns the settings unchanged, which is what an unmanaged host wants. Rejects when either document is present but is not a JSON object rather than treating it as absent, since silently dropping a malformed policy hands the user's layer the win on exactly the entries the policy exists to control. /// - /// Wire method: `mcp.discover`. + /// Wire method: `managedSettings.applyControls`. /// /// # Parameters /// - /// * `params` - Optional working directory used as context for MCP server discovery. + /// * `params` - A user settings document and the effective managed policy whose controlled entries should be stamped onto it. /// /// # Returns /// - /// MCP servers discovered from user, workspace, plugin, and built-in sources. + /// The user settings with the managed layer's controlled entries stamped on. /// ///
/// @@ -2224,26 +2612,32 @@ impl<'a> ClientRpcMcp<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub async fn discover(&self, params: McpDiscoverRequest) -> Result { + pub(crate) async fn apply_controls( + &self, + params: ManagedSettingsApplyControlsParams, + ) -> Result { let wire_params = serde_json::to_value(params)?; let _value = self .client - .call(rpc_methods::MCP_DISCOVER, Some(wire_params)) + .call( + rpc_methods::MANAGEDSETTINGS_APPLYCONTROLS, + Some(wire_params), + ) .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. + /// Validates the managed settings a client is supplying for a session it is about to open, and returns them reduced to the keys a client is allowed to set. A client-supplied policy is not the same thing as an administrator's: it may only narrow permissions, so this accepts `permissions` and rejects every other key outright rather than ignoring it. Rejecting is the point — a host that silently dropped an unsupported key would report a policy as applied while running without it. Hosts call this before opening the session rather than letting the open fail, so a malformed policy is reported against the request that carried it. The runtime applies the same rule again when the session starts, so this cannot be used to smuggle a wider document past it. An accepted document that sets nothing comes back as an empty object, which is distinct from supplying nothing at all. /// - /// Wire method: `mcp.planInstall`. + /// Wire method: `managedSettings.parseClientValue`. /// /// # 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. + /// * `params` - The client-supplied managed settings to validate and normalize. Omitting them is valid and yields no policy. /// /// # Returns /// - /// Outcome of an mcp.planInstall call: either a normalised plan, or one typed refusal. Nothing is written in either case. + /// The accepted managed settings, reduced to the keys a client is allowed to set. /// ///
/// @@ -2252,29 +2646,32 @@ impl<'a> ClientRpcMcp<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub async fn plan_install( + pub(crate) async fn parse_client_value( &self, - params: McpPlanInstallRequest, - ) -> Result { + params: ManagedSettingsParseClientValueParams, + ) -> Result { let wire_params = serde_json::to_value(params)?; let _value = self .client - .call(rpc_methods::MCP_PLANINSTALL, Some(wire_params)) + .call( + rpc_methods::MANAGEDSETTINGS_PARSECLIENTVALUE, + Some(wire_params), + ) .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. + /// Composes the supplied server, device/MDM, device sandbox floor and policy-helper layers into the single effective managed policy a host should enforce, resolving each key across the layers by the runtime's own precedence rules. Hosts that assemble managed layers themselves use this so their precedence matches the runtime's exactly instead of reimplementing it. The composition is not a pure function of its inputs: an advisory sandbox force-on that arrived over the server channel is declined when this host cannot run a sandbox, so the result reflects the runtime host's capabilities, and the returned settings carry `sandboxAdvisoryFloorDeclined: true` when that happened. Enforced channels (device/MDM and policy helper) are never declined, and a server force-on carrying `failIfUnavailable` is preserved so the session fails loudly instead of quietly running unsandboxed. Rejects when a supplied layer is present but is not a JSON object, rather than treating it as unmanaged, because silently dropping a malformed policy would hand the user's own layer the win on exactly the keys the policy exists to control. /// - /// Wire method: `mcp.planInstall`. + /// Wire method: `managedSettings.resolveEffectiveValue`. /// /// # 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. + /// * `params` - The managed-settings layers to compose into an effective policy. Every layer is optional; omitting all of them yields no effective policy. /// /// # Returns /// - /// Outcome of an mcp.planInstall call: either a normalised plan, or one typed refusal. Nothing is written in either case. + /// The effective managed policy composed from the supplied layers. /// ///
/// @@ -2283,31 +2680,32 @@ impl<'a> ClientRpcMcp<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- /// - /// Accepts [`McpPlanInstallOptions`], including inputs added after [`McpPlanInstallRequest`]. - pub async fn plan_install_with_options( + pub(crate) async fn resolve_effective_value( &self, - params: McpPlanInstallOptions, - ) -> Result { + params: ManagedSettingsResolveEffectiveValueParams, + ) -> Result { let wire_params = serde_json::to_value(params)?; let _value = self .client - .call(rpc_methods::MCP_PLANINSTALL, Some(wire_params)) + .call( + rpc_methods::MANAGEDSETTINGS_RESOLVEEFFECTIVEVALUE, + Some(wire_params), + ) .await?; Ok(serde_json::from_value(_value)?) } - /// Consumes a bound catalogue plan and retains one exact fully resolved personal remote MCP operation requiring no supplied values or configured secrets. Returns its runtime operation ID and original expiry before any confirmation, activation, writer initialisation or installation effect. Register the original connection, operation and selected-session binding before calling applyInstall. Missing lower owned admission is unavailable, never a raw-config fallback. + /// Decides what a host must do to its bypass-permissions state after a managed-policy resolution lands, given the state already in force and the account now signed in. A host holds two pieces of state — which layer disabled bypass mode, and for a server-delivered disable, the account it was pinned to — and cannot decide the next step from the new policy alone. Three rules make that so, and each one is a control rather than a preference. A device/MDM disable outranks the account, so a later server answer never lifts it. An unknown policy, which is what a failed fetch or a refusal this build cannot enforce yields, preserves whatever is in force instead of reverting to permissive. And a server disable is lifted only when the account it was pinned to is no longer the signed-in one, which is what makes signing in to an unrestricted account recover without a restart while a reconnect on the same restricted account does not. Hosts call this rather than reimplementing it because getting the pinning backwards either strands a disable on an account that never carried one or drops one that still applies. `setDisabledState` is omitted when the host must leave its stored flag untouched, which is distinct from being told to set it to false. /// - /// Wire method: `mcp.prepareInstall`. + /// Wire method: `managedSettings.resolveBypassDisableTransition`. /// /// # Parameters /// - /// * `params` - Side-effect-free preparation of one original bound remote MCP choice. + /// * `params` - The bypass-permissions state a host holds today, the account now signed in, and what the managed-policy resolution said. /// /// # Returns /// - /// Management result with contract receipt, or a typed request/negotiation refusal. + /// The bypass-permissions state a host should hold after the resolution, and what it must do to get there. /// ///
/// @@ -2316,29 +2714,32 @@ impl<'a> ClientRpcMcp<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub async fn prepare_install( + pub(crate) async fn resolve_bypass_disable_transition( &self, - params: McpPrepareInstallRequest, - ) -> Result { + params: ManagedSettingsResolveBypassDisableTransitionParams, + ) -> Result { let wire_params = serde_json::to_value(params)?; let _value = self .client - .call(rpc_methods::MCP_PREPAREINSTALL, Some(wire_params)) + .call( + rpc_methods::MANAGEDSETTINGS_RESOLVEBYPASSDISABLETRANSITION, + Some(wire_params), + ) .await?; Ok(serde_json::from_value(_value)?) } - /// Consumes a retained prepared MCP operation once, revalidates its original authority, requests explicit human consent through installations.confirm on the original connection, then revalidates source and applies the sealed transaction. An uncertain result requires original-operation inspection or recovery, never replay. + /// Resolves managed (enterprise policy) settings for a host from every layer at once: the account's server-delivered policy, the device/MDM layer, and a platform policy helper. Hosts use this instead of fetching and composing the layers themselves, because the per-layer precedence and the fail-closed rules that apply when a layer cannot be confirmed are the runtime's own and must match the policy a session will later enforce. It does not retain or apply anything to process-global policy state, so a host may call it before any session exists — which is the point, since plugin activation and one-shot commands resolve policy before a session is available to carry it. The returned per-layer outcome flags are load-bearing rather than diagnostic: a host must distinguish an unmanaged device from a managed device whose policy could not be confirmed, and apply its fail-closed gates in the second case. Reports its diagnostics in `logMessages` rather than logging them, so a host that runs before a log sink exists still sees them. /// - /// Wire method: `mcp.applyInstall`. + /// Wire method: `managedSettings.ingest`. /// /// # Parameters /// - /// * `params` - Applies exactly one previously prepared operation on its original connection. + /// * `params` - Inputs describing the host and account an ingestion should resolve managed policy for. /// /// # Returns /// - /// An installation result together with the exact honoured contract, or a negotiation refusal. + /// The managed policy layers an ingestion resolved, together with the per-layer outcome flags a host needs to decide how much to trust the result. The flags are reported rather than folded into the settings because a host must be able to distinguish an unmanaged device from a managed device whose policy could not be confirmed, and apply its fail-closed gates in the second case. /// ///
/// @@ -2347,29 +2748,29 @@ impl<'a> ClientRpcMcp<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub async fn apply_install( + pub(crate) async fn ingest( &self, - params: McpApplyInstallRequest, - ) -> Result { + params: ManagedSettingsIngestParams, + ) -> Result { let wire_params = serde_json::to_value(params)?; let _value = self .client - .call(rpc_methods::MCP_APPLYINSTALL, Some(wire_params)) + .call(rpc_methods::MANAGEDSETTINGS_INGEST, Some(wire_params)) .await?; Ok(serde_json::from_value(_value)?) } - /// Prepares a read-only removal plan for an exact owned receipt under the selected existing session. Returns the original operation ID before confirmation; neither planning nor abandonment changes configuration or shared OAuth credentials. + /// Resolves the managed (enterprise policy) settings a host must enforce from startup, and judges the account against the enterprise sign-in pin in the same pass. This composes the layers the way `managedSettings.ingest` does and then answers the two questions a host cannot answer for itself: which fail-closed floors apply when a layer could not be confirmed, and whether this credential is allowed to sign in at all. The sign-in verdict is the reason a host must not assemble this itself from `managedSettings.ingest` — a pinned organization is an access control, and a host that composed the layers but skipped the pin would let a developer sign in with a personal or another enterprise's account and escape every control that keys off the signed-in identity. The verdict is computed on every resolution, including one that is not retained, so declining to apply policy never widens access. Set `retainAndApply` to keep the resolved policy as this runtime process's startup policy; leave it false to resolve without side effects. /// - /// Wire method: `mcp.planUninstall`. + /// Wire method: `managedSettings.resolveStartupPolicy`. /// /// # Parameters /// - /// * `params` - Read-only preparation of one owned removal under fresh selected-session authority. + /// * `params` - Inputs describing the host and account a startup policy resolution should run for. Mirrors `managedSettings.ingest`, and adds the decision of whether the resolved policy is retained for this runtime process. /// /// # Returns /// - /// Management result with contract receipt, or a typed request/negotiation refusal. + /// The startup policy a resolution composed, the per-layer outcome flags a host needs to decide how much to trust it, and the enterprise sign-in verdict for the credential it ran against. The flags are reported rather than folded into the settings because a host must distinguish an unmanaged device from a managed device whose policy could not be confirmed, and apply its fail-closed gates in the second case. /// ///
/// @@ -2378,29 +2779,32 @@ impl<'a> ClientRpcMcp<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub async fn plan_uninstall( + pub(crate) async fn resolve_startup_policy( &self, - params: McpPlanUninstallRequest, - ) -> Result { + params: ManagedSettingsResolveStartupPolicyParams, + ) -> Result { let wire_params = serde_json::to_value(params)?; let _value = self .client - .call(rpc_methods::MCP_PLANUNINSTALL, Some(wire_params)) + .call( + rpc_methods::MANAGEDSETTINGS_RESOLVESTARTUPPOLICY, + Some(wire_params), + ) .await?; Ok(serde_json::from_value(_value)?) } - /// Consumes the original owned-removal plan once and requests fresh exact human confirmation on its original connection. Drift is refused; unrelated manual configuration and shared OAuth credentials are preserved. + /// Resolves the managed `remoteControl` policy for an account on its own, for a host deciding whether a remote-control session may start. Composes the same layers as `managedSettings.ingest` and returns only that one key, so a host does not have to know which layer won. An absent policy means the account is unmanaged for this capability and the host may proceed; that default is safe only while absence really does mean "no policy", so a service refusal that says an account policy exists which this client is too old to enforce resolves to an explicit disabled policy instead of to absence. A resolution that cannot be completed is rejected rather than answered as absence, for the same reason. /// - /// Wire method: `mcp.applyUninstall`. + /// Wire method: `managedSettings.loadRemoteControl`. /// /// # Parameters /// - /// * `params` - One-use application of the exact retained removal plan. + /// * `params` - Inputs describing the host and account whose managed remote-control policy should be resolved. Mirrors `managedSettings.ingest`, and deliberately carries no retention decision: reading this gate never becomes this runtime process's startup policy. /// /// # Returns /// - /// An installation result together with the exact honoured contract, or a negotiation refusal. + /// The managed remote-control policy in force for an account, if any. An absent policy means the account is unmanaged for this capability, which a host reads as allowed — so absence here is a positive statement that no policy applies, never a stand-in for a resolution that failed. A resolution that cannot be completed is rejected instead. /// ///
/// @@ -2409,33 +2813,32 @@ impl<'a> ClientRpcMcp<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub async fn apply_uninstall( + pub(crate) async fn load_remote_control( &self, - params: McpApplyUninstallRequest, - ) -> Result { + params: ManagedSettingsLoadRemoteControlParams, + ) -> Result { let wire_params = serde_json::to_value(params)?; let _value = self .client - .call(rpc_methods::MCP_APPLYUNINSTALL, Some(wire_params)) + .call( + rpc_methods::MANAGEDSETTINGS_LOADREMOTECONTROL, + Some(wire_params), + ) .await?; Ok(serde_json::from_value(_value)?) } -} - -/// `mcp.config.*` RPCs. -#[derive(Clone, Copy)] -pub struct ClientRpcMcpConfig<'a> { - pub(crate) client: &'a Client, -} -impl<'a> ClientRpcMcpConfig<'a> { - /// Lists MCP servers from user configuration. + /// Evaluates the device-channel enterprise sign-in pin (MDM plist, registry, or managed-settings file) against a credential, without resolving any server-delivered policy. This is the gate an entry point runs before it branches, and the only one that can refuse a developer's first sign-in: it is local, needs no credential, and a server-delivered pin only ever reaches accounts already inside the organization. It fails closed by construction — a discovery that cannot determine whether a pin exists refuses rather than allows, because a control that fails open under a triggerable condition is not a control. Hosts that also want the server channel should call `managedSettings.resolveStartupPolicy`, which folds both channels into one verdict. /// - /// Wire method: `mcp.config.list`. + /// Wire method: `managedSettings.resolveDeviceForceLoginOrgsGate`. + /// + /// # Parameters + /// + /// * `params` - The credential and environment to evaluate the device-channel sign-in pin against. /// /// # Returns /// - /// User-configured MCP servers, keyed by server name. + /// The verdict of the `forceLoginOrgs` enterprise sign-in pin for one credential. A managed device may pin Copilot to an approved set of GitHub organizations so a developer cannot sign in with a personal — or another enterprise's — account and escape every control that keys off the signed-in identity. A host must refuse to proceed when `allowed` is false; the remaining fields explain the refusal rather than qualifying it. /// ///
/// @@ -2444,22 +2847,28 @@ impl<'a> ClientRpcMcpConfig<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub async fn list(&self) -> Result { - let wire_params = serde_json::json!({}); + pub(crate) async fn resolve_device_force_login_orgs_gate( + &self, + params: ManagedSettingsResolveDeviceForceLoginOrgsGateParams, + ) -> Result { + let wire_params = serde_json::to_value(params)?; let _value = self .client - .call(rpc_methods::MCP_CONFIG_LIST, Some(wire_params)) + .call( + rpc_methods::MANAGEDSETTINGS_RESOLVEDEVICEFORCELOGINORGSGATE, + Some(wire_params), + ) .await?; Ok(serde_json::from_value(_value)?) } - /// Adds an MCP server to user configuration. + /// 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. `managedSettings.resolve` returns the same device settings together with the account's server policy. /// - /// Wire method: `mcp.config.add`. + /// Wire method: `managedSettings.read`. /// - /// # Parameters + /// # Returns /// - /// * `params` - MCP server name and configuration to add to user configuration. + /// Validated device-managed settings discovered before a session exists. /// ///
/// @@ -2468,22 +2877,18 @@ impl<'a> ClientRpcMcpConfig<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub async fn add(&self, params: McpConfigAddRequest) -> Result<(), Error> { - let wire_params = serde_json::to_value(params)?; + pub async fn read(&self) -> Result { + let wire_params = serde_json::json!({}); let _value = self .client - .call(rpc_methods::MCP_CONFIG_ADD, Some(wire_params)) + .call(rpc_methods::MANAGEDSETTINGS_READ, Some(wire_params)) .await?; - Ok(()) + Ok(serde_json::from_value(_value)?) } - /// Updates an MCP server in user configuration. - /// - /// Wire method: `mcp.config.update`. - /// - /// # Parameters + /// Force-refreshes enterprise managed settings for every account: wipes the persistent server-policy cache (the whole `/managed-settings` directory) and drops this runtime process's in-memory retained server policy. It does not itself fetch policy — the effect is that the next time a session resolves managed settings for an account, that resolution re-fetches the account's org policy from the network instead of serving a cached response. Note that `managedSettings.read` returns only device/MDM settings and never triggers the account server-policy fetch, so a host implementing "sync account policy" should call `managedSettings.resolve` or start a fresh session resolution rather than treat a subsequent `managedSettings.read` as the refreshed org policy. Mirrors the invalidation a sign-out performs, broadened from the one signing-out account to all of them; device/MDM layers describe the machine, not the account, and are left untouched. Rejects if the on-disk cache cannot be removed. /// - /// * `params` - MCP server name and replacement configuration to write to user configuration. + /// Wire method: `managedSettings.clearCache`. /// ///
/// @@ -2492,22 +2897,22 @@ impl<'a> ClientRpcMcpConfig<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub async fn update(&self, params: McpConfigUpdateRequest) -> Result<(), Error> { - let wire_params = serde_json::to_value(params)?; + pub async fn clear_cache(&self) -> Result<(), Error> { + let wire_params = serde_json::json!({}); let _value = self .client - .call(rpc_methods::MCP_CONFIG_UPDATE, Some(wire_params)) + .call(rpc_methods::MANAGEDSETTINGS_CLEARCACHE, Some(wire_params)) .await?; Ok(()) } - /// Removes an MCP server from user configuration. + /// Resolves the effective enterprise managed settings without a session, from the device channel and, when an account is available, the account's server policy through the same per-account cache sessions use. A cached server policy less than an hour old is used without a fetch; otherwise the policy is fetched, and when the fetch fails a cached policy up to 24 hours old is used instead, unless `forceRemoteSettingsRefresh` requires a live fetch. With no account requested or signed in, it reports device policy only; signing out removes the account's cached policy. It can fetch server policy over the network when the cache is stale, so call it off latency-critical paths such as startup rather than before listing models. The policy helper is not run. `layers` lists each channel's document before merging, and `values` and `meta` carry typed effective values and their lock state for the keys typed so far. /// - /// Wire method: `mcp.config.remove`. + /// Wire method: `managedSettings.resolve`. /// - /// # Parameters + /// # Returns /// - /// * `params` - MCP server name to remove from user configuration. + /// Effective enterprise managed settings for an account, resolved without a session. /// ///
/// @@ -2516,22 +2921,2208 @@ impl<'a> ClientRpcMcpConfig<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub async fn remove(&self, params: McpConfigRemoveRequest) -> Result<(), Error> { + pub async fn resolve(&self) -> Result { + let wire_params = serde_json::json!({}); + let _value = self + .client + .call(rpc_methods::MANAGEDSETTINGS_RESOLVE, Some(wire_params)) + .await?; + Ok(serde_json::from_value(_value)?) + } + + /// Resolves the effective enterprise managed settings without a session, from the device channel and, when an account is available, the account's server policy through the same per-account cache sessions use. A cached server policy less than an hour old is used without a fetch; otherwise the policy is fetched, and when the fetch fails a cached policy up to 24 hours old is used instead, unless `forceRemoteSettingsRefresh` requires a live fetch. With no account requested or signed in, it reports device policy only; signing out removes the account's cached policy. It can fetch server policy over the network when the cache is stale, so call it off latency-critical paths such as startup rather than before listing models. The policy helper is not run. `layers` lists each channel's document before merging, and `values` and `meta` carry typed effective values and their lock state for the keys typed so far. + /// + /// Wire method: `managedSettings.resolve`. + /// + /// # Parameters + /// + /// * `params` - Optional opaque account selection or GitHub token whose managed settings are resolved. + /// + /// # Returns + /// + /// Effective enterprise managed settings for an account, resolved without 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 resolve_with_params( + &self, + params: ManagedSettingsResolveRequest, + ) -> Result { + let wire_params = serde_json::to_value(params)?; + let _value = self + .client + .call(rpc_methods::MANAGEDSETTINGS_RESOLVE, Some(wire_params)) + .await?; + Ok(serde_json::from_value(_value)?) + } + + /// Returns the managed-settings authoring JSON schema with descriptive `x-composition` annotations aligned with the shared settings-engine vocabulary. These annotations are not a complete runtime composition contract: model, effortLevel, and contextTier remain coupled. Use `managedSettings.compose` for the runtime's effective result. Performs no I/O. + /// + /// Wire method: `managedSettings.schema`. + /// + /// # Returns + /// + /// The authoring JSON schema for managed settings recognized by this runtime. + /// + ///
+ /// + /// **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 schema(&self) -> Result { + let wire_params = serde_json::json!({}); + let _value = self + .client + .call(rpc_methods::MANAGEDSETTINGS_SCHEMA, Some(wire_params)) + .await?; + Ok(serde_json::from_value(_value)?) + } + + /// Validates a candidate managed-settings document the way the runtime validates delivered policy, without applying it. Reports errors that would reject the document, warnings for content the runtime ignores, and the canonical document it would apply. Document text nested more than 64 levels deep is rejected. Performs no I/O. + /// + /// Wire method: `managedSettings.validate`. + /// + /// # Parameters + /// + /// * `params` - A candidate managed-settings document to validate without applying it. + /// + /// # Returns + /// + /// Result of validating a managed-settings document. + /// + ///
+ /// + /// **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 validate( + &self, + params: ManagedSettingsValidateRequest, + ) -> Result { + let wire_params = serde_json::to_value(params)?; + let _value = self + .client + .call(rpc_methods::MANAGEDSETTINGS_VALIDATE, Some(wire_params)) + .await?; + Ok(serde_json::from_value(_value)?) + } + + /// Merges candidate managed-settings documents for the device, server, and policy-helper channels into the effective settings the runtime would enforce on this host, using the same precedence and composition rules as live resolution, without applying them. Like live resolution, a server's advisory sandbox force-enable is declined on a host that cannot run the sandbox. Does not fetch policy or read policy files, but may perform blocking OS or subprocess probes for sandbox support. Preview documents are limited to 1 MiB and 64 levels of nesting. + /// + /// Wire method: `managedSettings.compose`. + /// + /// # Parameters + /// + /// * `params` - Candidate managed-settings documents to merge without applying them. + /// + /// # Returns + /// + /// The effective managed settings the runtime would enforce for the given documents, in the same shape `managedSettings.resolve` returns. + /// + ///
+ /// + /// **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 compose( + &self, + params: ManagedSettingsComposeRequest, + ) -> Result { + let wire_params = serde_json::to_value(params)?; + let _value = self + .client + .call(rpc_methods::MANAGEDSETTINGS_COMPOSE, Some(wire_params)) + .await?; + Ok(serde_json::from_value(_value)?) + } +} + +/// `managedSettings.permissions.*` RPCs. +#[derive(Clone, Copy)] +pub struct ClientRpcManagedSettingsPermissions<'a> { + pub(crate) client: &'a Client, +} + +impl<'a> ClientRpcManagedSettingsPermissions<'a> { + /// Pure, sessionless evaluation of operations against a supplied permissionsContext. Performs no policy discovery, I/O, prompting, navigation, permission grants, or sandbox changes. Obtain the context from managedSettings.resolve or session.managedSettings.get, or reuse an in-memory context. Results preserve input order and duplicates. Consumers must block deny, obtain approval for ask, and use their normal permission flow for unmanaged. Unknown policy returns failClosed with every operation denied. This is not a substitute for user permissions or other security controls. + /// + /// Wire method: `managedSettings.permissions.evaluate`. + /// + /// # Parameters + /// + /// * `params` - Managed policy context and ordered operations for pure, sessionless evaluation. + /// + /// # Returns + /// + /// Ordered managed permission verdicts and the supplied policy's fail-closed posture. + /// + ///
+ /// + /// **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 evaluate( + &self, + params: ManagedPermissionsEvaluateRequest, + ) -> Result { + let wire_params = serde_json::to_value(params)?; + let _value = self + .client + .call( + rpc_methods::MANAGEDSETTINGS_PERMISSIONS_EVALUATE, + Some(wire_params), + ) + .await?; + Ok(serde_json::from_value(_value)?) + } +} + +/// `mcp.*` RPCs. +#[derive(Clone, Copy)] +pub struct ClientRpcMcp<'a> { + pub(crate) client: &'a Client, +} + +impl<'a> ClientRpcMcp<'a> { + /// `mcp.config.*` sub-namespace. + pub fn config(&self) -> ClientRpcMcpConfig<'a> { + ClientRpcMcpConfig { + client: self.client, + } + } + + /// `mcp.installations.*` sub-namespace. + pub fn installations(&self) -> ClientRpcMcpInstallations<'a> { + ClientRpcMcpInstallations { + client: self.client, + } + } + + /// `mcp.registry.*` sub-namespace. + pub fn registry(&self) -> ClientRpcMcpRegistry<'a> { + ClientRpcMcpRegistry { + client: self.client, + } + } + + /// Discovers MCP servers from user, workspace, plugin, and builtin sources. + /// + /// Wire method: `mcp.discover`. + /// + /// # Parameters + /// + /// * `params` - Optional working directory used as context for MCP server discovery. + /// + /// # Returns + /// + /// MCP servers discovered from user, workspace, plugin, and built-in 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: McpDiscoverRequest) -> Result { + let wire_params = serde_json::to_value(params)?; + let _value = self + .client + .call(rpc_methods::MCP_DISCOVER, Some(wire_params)) + .await?; + Ok(serde_json::from_value(_value)?) + } + + /// Lists the MCP servers the runtime supplies itself, so a host that disables the built-ins wholesale suppresses exactly the set the runtime would otherwise add. The names are fixed for a build and depend on no configuration, session or account. + /// + /// Wire method: `mcp.listBuiltinServerNames`. + /// + /// # Returns + /// + /// Names of the MCP servers the runtime supplies itself. + /// + ///
+ /// + /// **Experimental.** This API is part of an experimental wire-protocol surface + /// and may change or be removed in future SDK or CLI releases. Pin both the + /// SDK and CLI versions if your code depends on it. + /// + ///
+ pub(crate) async fn list_builtin_server_names( + &self, + ) -> Result { + let wire_params = serde_json::json!({}); + let _value = self + .client + .call(rpc_methods::MCP_LISTBUILTINSERVERNAMES, Some(wire_params)) + .await?; + Ok(serde_json::from_value(_value)?) + } + + /// Reports whether the gh-replaceable GitHub MCP tools may be clipped for a session, which a host feeds back as the `excludeGhReplaceableTools` build option. The tools are redundant only when the session can reach a shell *and* the host has the `gh` that would replace them, so both halves are decided here. The shell half runs the platform shell tool through the runtime's own tool-filter matcher, so entry forms like `builtin:bash` and `builtin:*` behave exactly as they do when a session builds its tool catalog. The host half probes for `gh`, and is skipped entirely when the filters already rule the shell out. Host presence alone is not enough: a session restricted to, say, `view` would otherwise lose the built-in issue-read fallback while having no shell to replace it with. The answer describes the host the runtime runs on, so it is never forwarded to a remote engine. + /// + /// Wire method: `mcp.shouldExcludeGitHubTools`. + /// + /// # Parameters + /// + /// * `params` - The session tool filters that decide whether the session still has a shell to run `gh` with. + /// + /// # Returns + /// + /// Whether the gh-replaceable GitHub MCP tools may be clipped for the session described by the request. + /// + ///
+ /// + /// **Experimental.** This API is part of an experimental wire-protocol surface + /// and may change or be removed in future SDK or CLI releases. Pin both the + /// SDK and CLI versions if your code depends on it. + /// + ///
+ pub(crate) async fn should_exclude_git_hub_tools( + &self, + params: McpShouldExcludeGitHubToolsRequest, + ) -> Result { + let wire_params = serde_json::to_value(params)?; + let _value = self + .client + .call(rpc_methods::MCP_SHOULDEXCLUDEGITHUBTOOLS, Some(wire_params)) + .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)?) + } + + /// 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. + /// + ///
+ /// + /// Accepts [`McpPlanInstallOptions`], including inputs added after [`McpPlanInstallRequest`]. + pub async fn plan_install_with_options( + &self, + params: McpPlanInstallOptions, + ) -> 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)?) + } + + /// Consumes a bound catalogue plan and retains one exact fully resolved personal remote MCP operation requiring no supplied values or configured secrets. Returns its runtime operation ID and original expiry before any confirmation, activation, writer initialisation or installation effect. Register the original connection, operation and selected-session binding before calling applyInstall. Missing lower owned admission is unavailable, never a raw-config fallback. + /// + /// Wire method: `mcp.prepareInstall`. + /// + /// # Parameters + /// + /// * `params` - Side-effect-free preparation of one original bound remote MCP choice. + /// + /// # Returns + /// + /// Management result with contract receipt, or a typed request/negotiation refusal. + /// + ///
+ /// + /// **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 prepare_install( + &self, + params: McpPrepareInstallRequest, + ) -> Result { + let wire_params = serde_json::to_value(params)?; + let _value = self + .client + .call(rpc_methods::MCP_PREPAREINSTALL, Some(wire_params)) + .await?; + Ok(serde_json::from_value(_value)?) + } + + /// Consumes a retained prepared MCP operation once, revalidates its original authority, requests explicit human consent through installations.confirm on the original connection, then revalidates source and applies the sealed transaction. An uncertain result requires original-operation inspection or recovery, never replay. + /// + /// Wire method: `mcp.applyInstall`. + /// + /// # Parameters + /// + /// * `params` - Applies exactly one previously prepared operation on its original connection. + /// + /// # Returns + /// + /// An installation result together with the exact honoured contract, or a negotiation refusal. + /// + ///
+ /// + /// **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 apply_install( + &self, + params: McpApplyInstallRequest, + ) -> Result { + let wire_params = serde_json::to_value(params)?; + let _value = self + .client + .call(rpc_methods::MCP_APPLYINSTALL, Some(wire_params)) + .await?; + Ok(serde_json::from_value(_value)?) + } + + /// Prepares a read-only removal plan for an exact owned receipt under the selected existing session. Returns the original operation ID before confirmation; neither planning nor abandonment changes configuration or shared OAuth credentials. + /// + /// Wire method: `mcp.planUninstall`. + /// + /// # Parameters + /// + /// * `params` - Read-only preparation of one owned removal under fresh selected-session authority. + /// + /// # Returns + /// + /// Management result with contract receipt, or a typed request/negotiation refusal. + /// + ///
+ /// + /// **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_uninstall( + &self, + params: McpPlanUninstallRequest, + ) -> Result { + let wire_params = serde_json::to_value(params)?; + let _value = self + .client + .call(rpc_methods::MCP_PLANUNINSTALL, Some(wire_params)) + .await?; + Ok(serde_json::from_value(_value)?) + } + + /// Consumes the original owned-removal plan once and requests fresh exact human confirmation on its original connection. Drift is refused; unrelated manual configuration and shared OAuth credentials are preserved. + /// + /// Wire method: `mcp.applyUninstall`. + /// + /// # Parameters + /// + /// * `params` - One-use application of the exact retained removal plan. + /// + /// # Returns + /// + /// An installation result together with the exact honoured contract, or a negotiation refusal. + /// + ///
+ /// + /// **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 apply_uninstall( + &self, + params: McpApplyUninstallRequest, + ) -> Result { + let wire_params = serde_json::to_value(params)?; + let _value = self + .client + .call(rpc_methods::MCP_APPLYUNINSTALL, Some(wire_params)) + .await?; + Ok(serde_json::from_value(_value)?) + } +} + +/// `mcp.config.*` RPCs. +#[derive(Clone, Copy)] +pub struct ClientRpcMcpConfig<'a> { + pub(crate) client: &'a Client, +} + +impl<'a> ClientRpcMcpConfig<'a> { + /// Lists MCP servers from user configuration. + /// + /// Wire method: `mcp.config.list`. + /// + /// # Returns + /// + /// User-configured MCP servers, keyed by server name. + /// + ///
+ /// + /// **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!({}); + let _value = self + .client + .call(rpc_methods::MCP_CONFIG_LIST, Some(wire_params)) + .await?; + Ok(serde_json::from_value(_value)?) + } + + /// Adds an MCP server to user configuration. + /// + /// Wire method: `mcp.config.add`. + /// + /// # Parameters + /// + /// * `params` - MCP server name and configuration to add to user configuration. + /// + ///
+ /// + /// **Experimental.** This API is part of an experimental wire-protocol surface + /// and may change or be removed in future SDK or CLI releases. Pin both the + /// SDK and CLI versions if your code depends on it. + /// + ///
+ pub async fn add(&self, params: McpConfigAddRequest) -> Result<(), Error> { + let wire_params = serde_json::to_value(params)?; + let _value = self + .client + .call(rpc_methods::MCP_CONFIG_ADD, Some(wire_params)) + .await?; + Ok(()) + } + + /// Updates an MCP server in user configuration. + /// + /// Wire method: `mcp.config.update`. + /// + /// # Parameters + /// + /// * `params` - MCP server name and replacement configuration to write to user configuration. + /// + ///
+ /// + /// **Experimental.** This API is part of an experimental wire-protocol surface + /// and may change or be removed in future SDK or CLI releases. Pin both the + /// SDK and CLI versions if your code depends on it. + /// + ///
+ pub async fn update(&self, params: McpConfigUpdateRequest) -> Result<(), Error> { + let wire_params = serde_json::to_value(params)?; + let _value = self + .client + .call(rpc_methods::MCP_CONFIG_UPDATE, Some(wire_params)) + .await?; + Ok(()) + } + + /// Removes an MCP server from user configuration. + /// + /// Wire method: `mcp.config.remove`. + /// + /// # Parameters + /// + /// * `params` - MCP server name to remove from user configuration. + /// + ///
+ /// + /// **Experimental.** This API is part of an experimental wire-protocol surface + /// and may change or be removed in future SDK or CLI releases. Pin both the + /// SDK and CLI versions if your code depends on it. + /// + ///
+ pub async fn remove(&self, params: McpConfigRemoveRequest) -> Result<(), Error> { + let wire_params = serde_json::to_value(params)?; + let _value = self + .client + .call(rpc_methods::MCP_CONFIG_REMOVE, Some(wire_params)) + .await?; + Ok(()) + } + + /// Enables MCP servers in user configuration for new sessions. + /// + /// Wire method: `mcp.config.enable`. + /// + /// # Parameters + /// + /// * `params` - MCP server names to enable for new sessions. + /// + ///
+ /// + /// **Experimental.** This API is part of an experimental wire-protocol surface + /// and may change or be removed in future SDK or CLI releases. Pin both the + /// SDK and CLI versions if your code depends on it. + /// + ///
+ pub async fn enable(&self, params: McpConfigEnableRequest) -> Result<(), Error> { + let wire_params = serde_json::to_value(params)?; + let _value = self + .client + .call(rpc_methods::MCP_CONFIG_ENABLE, Some(wire_params)) + .await?; + Ok(()) + } + + /// Disables MCP servers in user configuration for new sessions. + /// + /// Wire method: `mcp.config.disable`. + /// + /// # Parameters + /// + /// * `params` - MCP server names to disable for new sessions. + /// + ///
+ /// + /// **Experimental.** This API is part of an experimental wire-protocol surface + /// and may change or be removed in future SDK or CLI releases. Pin both the + /// SDK and CLI versions if your code depends on it. + /// + ///
+ pub async fn disable(&self, params: McpConfigDisableRequest) -> Result<(), Error> { + let wire_params = serde_json::to_value(params)?; + let _value = self + .client + .call(rpc_methods::MCP_CONFIG_DISABLE, Some(wire_params)) + .await?; + Ok(()) + } + + /// Drops this runtime process's in-memory MCP server-definition cache so the next MCP config read observes disk. + /// + /// Wire method: `mcp.config.reload`. + /// + ///
+ /// + /// **Experimental.** This API is part of an experimental wire-protocol surface + /// and may change or be removed in future SDK or CLI releases. Pin both the + /// SDK and CLI versions if your code depends on it. + /// + ///
+ pub async fn reload(&self) -> Result<(), Error> { + let wire_params = serde_json::json!({}); + let _value = self + .client + .call(rpc_methods::MCP_CONFIG_RELOAD, Some(wire_params)) + .await?; + Ok(()) + } +} + +/// `mcp.installations.*` RPCs. +#[derive(Clone, Copy)] +pub struct ClientRpcMcpInstallations<'a> { + pub(crate) client: &'a Client, +} + +impl<'a> ClientRpcMcpInstallations<'a> { + /// Reads receipt-owned MCP inventory for the selected account and host without activating servers or reconstructing missing ownership. Configuration ownership does not prove session-specific usability. + /// + /// Wire method: `mcp.installations.list`. + /// + /// # Parameters + /// + /// * `params` - New-work inventory or recovery request under an explicitly selected existing session. + /// + /// # Returns + /// + /// Management result with contract receipt, or a typed request/negotiation refusal. + /// + ///
+ /// + /// **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, + params: McpInstallationsRequest, + ) -> Result { + let wire_params = serde_json::to_value(params)?; + let _value = self + .client + .call(rpc_methods::MCP_INSTALLATIONS_LIST, Some(wire_params)) + .await?; + Ok(serde_json::from_value(_value)?) + } + + /// Reconciles already-confirmed durable MCP transactions, then inspects owned inventory. Does not replay apply or reconstruct deleted ownership metadata; unresolved or unsafe evidence remains an explicit refusal. + /// + /// Wire method: `mcp.installations.recover`. + /// + /// # Parameters + /// + /// * `params` - New-work inventory or recovery request under an explicitly selected existing session. + /// + /// # Returns + /// + /// Management result with contract receipt, or a typed request/negotiation refusal. + /// + ///
+ /// + /// **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 recover( + &self, + params: McpInstallationsRequest, + ) -> Result { + let wire_params = serde_json::to_value(params)?; + let _value = self + .client + .call(rpc_methods::MCP_INSTALLATIONS_RECOVER, Some(wire_params)) + .await?; + Ok(serde_json::from_value(_value)?) + } + + /// Inspects a known operation only on its original connection. Remains available after account or selected-session loss; does not acquire new authority or rebind an operation. + /// + /// Wire method: `mcp.installations.status`. + /// + /// # Parameters + /// + /// * `params` - Existing-operation control. A new session selector is deliberately not accepted. + /// + /// # Returns + /// + /// Management result with contract receipt, or a typed request/negotiation refusal. + /// + ///
+ /// + /// **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 status( + &self, + params: McpInstallationOperationRequest, + ) -> Result { + let wire_params = serde_json::to_value(params)?; + let _value = self + .client + .call(rpc_methods::MCP_INSTALLATIONS_STATUS, Some(wire_params)) + .await?; + Ok(serde_json::from_value(_value)?) + } + + /// Requests cancellation of a known operation on its original connection, including before apply or confirmation. Already-started effects retain their transaction lease and report an honest terminal or recovery outcome. + /// + /// Wire method: `mcp.installations.cancel`. + /// + /// # Parameters + /// + /// * `params` - Existing-operation control. A new session selector is deliberately not accepted. + /// + /// # Returns + /// + /// Management result with contract receipt, or a typed request/negotiation refusal. + /// + ///
+ /// + /// **Experimental.** This API is part of an experimental wire-protocol surface + /// and may change or be removed in future SDK or CLI releases. Pin both the + /// SDK and CLI versions if your code depends on it. + /// + ///
+ pub async fn cancel( + &self, + params: McpInstallationOperationRequest, + ) -> Result { + let wire_params = serde_json::to_value(params)?; + let _value = self + .client + .call(rpc_methods::MCP_INSTALLATIONS_CANCEL, Some(wire_params)) + .await?; + Ok(serde_json::from_value(_value)?) + } +} + +/// `mcp.registry.*` RPCs. +#[derive(Clone, Copy)] +pub struct ClientRpcMcpRegistry<'a> { + pub(crate) client: &'a Client, +} + +impl<'a> ClientRpcMcpRegistry<'a> { + /// Allocates an ID for one cancellable MCP registry search. The ID exists before the search starts, so callers can cancel before it starts. The networking stack supplies the cancellation namespace. The runtime retains at most 1,024 unused IDs. At capacity, another allocation can reclaim an unused ID. Active searches retain their IDs until they finish. + /// + /// Wire method: `mcp.registry.allocateRequestId`. + /// + /// # Returns + /// + /// Request id naming a cancellable registry search. + /// + ///
+ /// + /// **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 allocate_request_id(&self) -> Result { + let wire_params = serde_json::json!({}); + let _value = self + .client + .call( + rpc_methods::MCP_REGISTRY_ALLOCATEREQUESTID, + Some(wire_params), + ) + .await?; + Ok(serde_json::from_value(_value)?) + } + + /// Searches the MCP registry the supplied credential may read, resolving the registry endpoint from policy first. Hosts usually send credential-free `AuthIdentity`; a `token` identity can be resolved only when it embeds a token, while `env` and `gh-cli` identities can use a token embedded in the request first. The runtime follows registry pages, keeps the newest entry per server name, cuts the list to `limit`, and sorts an empty-query result by GitHub stars. Each server object is carried opaquely. + /// + /// Wire method: `mcp.registry.search`. + /// + /// # Parameters + /// + /// * `params` - Registry search terms, the credential to search under, and the request id that makes the search cancellable. + /// + /// # Returns + /// + /// Servers selected from the registry response. + /// + ///
+ /// + /// **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 search( + &self, + params: McpRegistrySearchRequest, + ) -> Result { + let wire_params = serde_json::to_value(params)?; + let _value = self + .client + .call(rpc_methods::MCP_REGISTRY_SEARCH, Some(wire_params)) + .await?; + Ok(serde_json::from_value(_value)?) + } + + /// Abandons the registry search that uses the given request ID. It acts only on IDs from `mcp.registry.allocateRequestId`, so it never cancels another component's request. Answers `canceled: true` when it stops a running search. Answers `canceled: false` for unknown or reclaimed IDs, completed or canceled searches, and unused reservations. It releases an unused reservation, so a later search with that ID is refused. + /// + /// Wire method: `mcp.registry.cancel`. + /// + /// # Parameters + /// + /// * `params` - Registry search to abandon. + /// + /// # Returns + /// + /// Whether the cancel reached a live search. + /// + ///
+ /// + /// **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 cancel( + &self, + params: McpRegistryCancelRequest, + ) -> Result { + let wire_params = serde_json::to_value(params)?; + let _value = self + .client + .call(rpc_methods::MCP_REGISTRY_CANCEL, Some(wire_params)) + .await?; + Ok(serde_json::from_value(_value)?) + } +} + +/// `models.*` RPCs. +#[derive(Clone, Copy)] +pub struct ClientRpcModels<'a> { + pub(crate) client: &'a Client, +} + +impl<'a> ClientRpcModels<'a> { + /// Lists Copilot models available to the authenticated user. + /// + /// Wire method: `models.list`. + /// + /// # Returns + /// + /// List of Copilot models available to the resolved user, including capabilities and billing metadata. + /// + ///
+ /// + /// **Experimental.** This API is part of an experimental wire-protocol surface + /// and may change or be removed in future SDK or CLI releases. Pin both the + /// SDK and CLI versions if your code depends on it. + /// + ///
+ pub async fn list(&self) -> Result { + let wire_params = serde_json::json!({}); + let _value = self + .client + .call(rpc_methods::MODELS_LIST, Some(wire_params)) + .await?; + Ok(serde_json::from_value(_value)?) + } + + /// Lists Copilot models available to the authenticated user. + /// + /// Wire method: `models.list`. + /// + /// # Parameters + /// + /// * `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. + /// + ///
+ /// + /// **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: ModelsListRequest) -> Result { + let wire_params = serde_json::to_value(params)?; + let _value = self + .client + .call(rpc_methods::MODELS_LIST, Some(wire_params)) + .await?; + Ok(serde_json::from_value(_value)?) + } + + /// Returns the running runtime's complete catalog of well-known built-in model IDs without authentication or network access. + /// + /// Wire method: `models.getBuiltInCatalog`. + /// + /// # Returns + /// + /// The running runtime's complete catalog of well-known built-in model IDs, including supported models and additional IDs with built-in metadata. + /// + ///
+ /// + /// **Experimental.** This API is part of an experimental wire-protocol surface + /// and may change or be removed in future SDK or CLI releases. Pin both the + /// SDK and CLI versions if your code depends on it. + /// + ///
+ pub async fn get_built_in_catalog(&self) -> Result { + let wire_params = serde_json::json!({}); + let _value = self + .client + .call(rpc_methods::MODELS_GETBUILTINCATALOG, Some(wire_params)) + .await?; + Ok(serde_json::from_value(_value)?) + } +} + +/// `permissions.*` RPCs. +#[derive(Clone, Copy)] +pub struct ClientRpcPermissions<'a> { + pub(crate) client: &'a Client, +} + +impl<'a> ClientRpcPermissions<'a> { + /// `permissions.folderTrust.*` sub-namespace. + pub fn folder_trust(&self) -> ClientRpcPermissionsFolderTrust<'a> { + ClientRpcPermissionsFolderTrust { + client: self.client, + } + } + + /// `permissions.locations.*` sub-namespace. + pub fn locations(&self) -> ClientRpcPermissionsLocations<'a> { + ClientRpcPermissionsLocations { + client: self.client, + } + } + + /// Interprets a verbatim managed `permissions` slice into the three policy questions a host asks before granting elevated permissions: whether bypass-permissions mode is forbidden, whether a policy exists that this build could not determine, and whether the layer supplies permission rules the session must enforce. An absent or empty slice means no managed policy applies. The runtime and its permission service share one policy type, so a new managed permission setting is interpreted in exactly one place; a host that reimplements the reading would silently diverge the first time that set grows. Hosts must fail closed on an undetermined policy rather than treating it as permission granted. + /// + /// Wire method: `permissions.evaluateManagedPolicy`. + /// + /// # Parameters + /// + /// * `params` - The verbatim managed `permissions` slice to interpret. + /// + /// # Returns + /// + /// The policy questions a host asks before granting elevated permissions. + /// + ///
+ /// + /// **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 evaluate_managed_policy( + &self, + params: PermissionsEvaluateManagedPolicyParams, + ) -> Result { + let wire_params = serde_json::to_value(params)?; + let _value = self + .client + .call( + rpc_methods::PERMISSIONS_EVALUATEMANAGEDPOLICY, + Some(wire_params), + ) + .await?; + Ok(serde_json::from_value(_value)?) + } +} + +/// `permissions.folderTrust.*` RPCs. +#[derive(Clone, Copy)] +pub struct ClientRpcPermissionsFolderTrust<'a> { + pub(crate) client: &'a Client, +} + +impl<'a> ClientRpcPermissionsFolderTrust<'a> { + /// Reports whether a folder is trusted, reading the persisted trusted-folders list directly rather than through a session. Use this from a host that has no session yet, such as a one-shot command deciding whether to honor a checkout's own settings. It differs from the session-scoped check in two ways that matter: it takes the configuration directory to read the list from, instead of inheriting the session's, and it answers only from the persisted list, so trust a user granted for the lifetime of one session is not visible here. A host that has a session should prefer the session-scoped check, which also covers session-only trust. + /// + /// Wire method: `permissions.folderTrust.checkTrust`. + /// + /// # Parameters + /// + /// * `params` - A folder to check against a persisted trusted-folders list, and the configuration directory holding that list. + /// + /// # Returns + /// + /// Folder trust check result. + /// + ///
+ /// + /// **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 check_trust( + &self, + params: FolderTrustIsTrustedParams, + ) -> Result { + let wire_params = serde_json::to_value(params)?; + let _value = self + .client + .call( + rpc_methods::PERMISSIONS_FOLDERTRUST_CHECKTRUST, + Some(wire_params), + ) + .await?; + Ok(serde_json::from_value(_value)?) + } +} + +/// `permissions.locations.*` RPCs. +#[derive(Clone, Copy)] +pub struct ClientRpcPermissionsLocations<'a> { + pub(crate) client: &'a Client, +} + +impl<'a> ClientRpcPermissionsLocations<'a> { + /// Drops stored per-location permission grants whose directory no longer exists, and reports which location keys were removed. Runs against the permission store under the runtime's own Copilot home, the same store `session.permissions.locations.resolve` reads. Marked internal because it exists to carry a CLI call site off the napi boundary onto the SDK contract; it is migration plumbing, not a surface consumers are meant to depend on. It can become public once an SDK consumer needs to prune its own permission store. + /// + /// Wire method: `permissions.locations.cleanupOrphaned`. + /// + /// # Returns + /// + /// Which stored location permissions were dropped because their directory is gone. + /// + ///
+ /// + /// **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 cleanup_orphaned(&self) -> Result { + let wire_params = serde_json::json!({}); + let _value = self + .client + .call( + rpc_methods::PERMISSIONS_LOCATIONS_CLEANUPORPHANED, + Some(wire_params), + ) + .await?; + Ok(serde_json::from_value(_value)?) + } +} + +/// `persistence.*` RPCs. +#[derive(Clone, Copy)] +pub struct ClientRpcPersistence<'a> { + pub(crate) client: &'a Client, +} + +impl<'a> ClientRpcPersistence<'a> { + /// Atomically replaces a JSON file's entire contents while holding the runtime's per-path lock. The lock lives in the runtime process, so a host that writes these files itself takes no part in it and can tear a concurrent runtime write; route the write here instead. Unlike a merging write this never reads or parses the existing file, so a previously corrupted snapshot is replaced wholesale rather than failing every later write. Use it only for callers that always persist a complete snapshot. + /// + /// Wire method: `persistence.writeRawJsonLocked`. + /// + /// # Parameters + /// + /// * `params` - A complete JSON snapshot to write under the runtime's per-path file lock. + /// + /// # Returns + /// + /// The outcome of a locked snapshot write. + /// + ///
+ /// + /// **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 write_raw_json_locked( + &self, + params: PersistenceWriteRawJsonLockedRequest, + ) -> Result { + let wire_params = serde_json::to_value(params)?; + let _value = self + .client + .call( + rpc_methods::PERSISTENCE_WRITERAWJSONLOCKED, + Some(wire_params), + ) + .await?; + Ok(serde_json::from_value(_value)?) + } +} + +/// `plugins.*` RPCs. +#[derive(Clone, Copy)] +pub struct ClientRpcPlugins<'a> { + pub(crate) client: &'a Client, +} + +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 { + client: self.client, + } + } + + /// Lists plugins installed in user/global state. + /// + /// Wire method: `plugins.list`. + /// + /// # Returns + /// + /// Plugins installed in user/global state. + /// + ///
+ /// + /// **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!({}); + let _value = self + .client + .call(rpc_methods::PLUGINS_LIST, Some(wire_params)) + .await?; + Ok(serde_json::from_value(_value)?) + } + + /// Installs a plugin from a marketplace, GitHub repo, URL, or local path. + /// + /// Wire method: `plugins.install`. + /// + /// # Parameters + /// + /// * `params` - Plugin source and optional working directory for relative-path resolution. + /// + /// # Returns + /// + /// Result of installing a plugin. + /// + ///
+ /// + /// **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 install( + &self, + params: PluginsInstallRequest, + ) -> Result { + let wire_params = serde_json::to_value(params)?; + let _value = self + .client + .call(rpc_methods::PLUGINS_INSTALL, Some(wire_params)) + .await?; + Ok(serde_json::from_value(_value)?) + } + + /// Uninstalls an installed plugin. + /// + /// Wire method: `plugins.uninstall`. + /// + /// # Parameters + /// + /// * `params` - Name (or spec) of the plugin to uninstall. + /// + ///
+ /// + /// **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 uninstall(&self, params: PluginsUninstallRequest) -> Result<(), Error> { + let wire_params = serde_json::to_value(params)?; + let _value = self + .client + .call(rpc_methods::PLUGINS_UNINSTALL, Some(wire_params)) + .await?; + Ok(()) + } + + /// Updates an installed plugin to its latest published version. + /// + /// Wire method: `plugins.update`. + /// + /// # Parameters + /// + /// * `params` - Name (or spec) of the plugin to update. + /// + /// # Returns + /// + /// Result of updating a single plugin. + /// + ///
+ /// + /// **Experimental.** This API is part of an experimental wire-protocol surface + /// and may change or be removed in future SDK or CLI releases. Pin both the + /// SDK and CLI versions if your code depends on it. + /// + ///
+ pub async fn update(&self, params: PluginsUpdateRequest) -> Result { + let wire_params = serde_json::to_value(params)?; + let _value = self + .client + .call(rpc_methods::PLUGINS_UPDATE, Some(wire_params)) + .await?; + Ok(serde_json::from_value(_value)?) + } + + /// Updates every installed plugin to its latest published version. + /// + /// Wire method: `plugins.updateAll`. + /// + /// # Returns + /// + /// Result of updating all installed plugins. + /// + ///
+ /// + /// **Experimental.** This API is part of an experimental wire-protocol surface + /// and may change or be removed in future SDK or CLI releases. Pin both the + /// SDK and CLI versions if your code depends on it. + /// + ///
+ pub async fn update_all(&self) -> Result { + let wire_params = serde_json::json!({}); + let _value = self + .client + .call(rpc_methods::PLUGINS_UPDATEALL, Some(wire_params)) + .await?; + Ok(serde_json::from_value(_value)?) + } + + /// Enables installed plugins for new sessions. + /// + /// Wire method: `plugins.enable`. + /// + /// # Parameters + /// + /// * `params` - Plugin names (or specs) to enable, plus the optional working directory the repository-controlled guard is evaluated against. + /// + ///
+ /// + /// **Experimental.** This API is part of an experimental wire-protocol surface + /// and may change or be removed in future SDK or CLI releases. Pin both the + /// SDK and CLI versions if your code depends on it. + /// + ///
+ pub async fn enable(&self, params: PluginsEnableRequest) -> Result<(), Error> { + let wire_params = serde_json::to_value(params)?; + let _value = self + .client + .call(rpc_methods::PLUGINS_ENABLE, Some(wire_params)) + .await?; + Ok(()) + } + + /// Disables installed plugins for new sessions. + /// + /// Wire method: `plugins.disable`. + /// + /// # Parameters + /// + /// * `params` - Plugin names (or specs) to disable, plus the optional working directory the repository-controlled guard is evaluated against. + /// + ///
+ /// + /// **Experimental.** This API is part of an experimental wire-protocol surface + /// and may change or be removed in future SDK or CLI releases. Pin both the + /// SDK and CLI versions if your code depends on it. + /// + ///
+ pub async fn disable(&self, params: PluginsDisableRequest) -> Result<(), Error> { + let wire_params = serde_json::to_value(params)?; + let _value = self + .client + .call(rpc_methods::PLUGINS_DISABLE, Some(wire_params)) + .await?; + Ok(()) + } +} + +/// `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> { + pub(crate) client: &'a Client, +} + +impl<'a> ClientRpcPluginsMarketplaces<'a> { + /// Lists all registered marketplaces (defaults + user-added). + /// + /// Wire method: `plugins.marketplaces.list`. + /// + /// # Returns + /// + /// All registered marketplaces, including built-in defaults. + /// + ///
+ /// + /// **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!({}); + let _value = self + .client + .call(rpc_methods::PLUGINS_MARKETPLACES_LIST, Some(wire_params)) + .await?; + Ok(serde_json::from_value(_value)?) + } + + /// Registers a new marketplace from a source (owner/repo, URL, or local path). + /// + /// Wire method: `plugins.marketplaces.add`. + /// + /// # Parameters + /// + /// * `params` - Marketplace source and optional working directory for relative-path resolution. + /// + /// # Returns + /// + /// Result of registering a new marketplace. + /// + ///
+ /// + /// **Experimental.** This API is part of an experimental wire-protocol surface + /// and may change or be removed in future SDK or CLI releases. Pin both the + /// SDK and CLI versions if your code depends on it. + /// + ///
+ pub async fn add( + &self, + params: PluginsMarketplacesAddRequest, + ) -> Result { + let wire_params = serde_json::to_value(params)?; + let _value = self + .client + .call(rpc_methods::PLUGINS_MARKETPLACES_ADD, Some(wire_params)) + .await?; + Ok(serde_json::from_value(_value)?) + } + + /// Removes a previously-registered marketplace. When the marketplace has dependent plugins and `force` is not set, the marketplace is left intact and the result lists the dependents so the caller can decide whether to retry with `force=true`. + /// + /// Wire method: `plugins.marketplaces.remove`. + /// + /// # Parameters + /// + /// * `params` - Name of the marketplace to remove and an optional force flag. + /// + /// # Returns + /// + /// Outcome of the remove attempt, including dependent-plugin info when applicable. + /// + ///
+ /// + /// **Experimental.** This API is part of an experimental wire-protocol surface + /// and may change or be removed in future SDK or CLI releases. Pin both the + /// SDK and CLI versions if your code depends on it. + /// + ///
+ pub async fn remove( + &self, + params: PluginsMarketplacesRemoveRequest, + ) -> Result { + let wire_params = serde_json::to_value(params)?; + let _value = self + .client + .call(rpc_methods::PLUGINS_MARKETPLACES_REMOVE, Some(wire_params)) + .await?; + Ok(serde_json::from_value(_value)?) + } + + /// Lists plugins advertised by a registered marketplace. + /// + /// Wire method: `plugins.marketplaces.browse`. + /// + /// # Parameters + /// + /// * `params` - Name of the marketplace whose plugin catalog to fetch. + /// + /// # Returns + /// + /// Plugins advertised by the marketplace. + /// + ///
+ /// + /// **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 browse( + &self, + params: PluginsMarketplacesBrowseRequest, + ) -> Result { + let wire_params = serde_json::to_value(params)?; + let _value = self + .client + .call(rpc_methods::PLUGINS_MARKETPLACES_BROWSE, Some(wire_params)) + .await?; + Ok(serde_json::from_value(_value)?) + } + + /// Re-fetches one or all registered marketplace catalogs. + /// + /// Wire method: `plugins.marketplaces.refresh`. + /// + /// # Returns + /// + /// Result of refreshing one or more marketplace catalogs. + /// + ///
+ /// + /// **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 refresh(&self) -> Result { + let wire_params = serde_json::json!({}); + let _value = self + .client + .call(rpc_methods::PLUGINS_MARKETPLACES_REFRESH, Some(wire_params)) + .await?; + Ok(serde_json::from_value(_value)?) + } + + /// Re-fetches one or all registered marketplace catalogs. + /// + /// Wire method: `plugins.marketplaces.refresh`. + /// + /// # Parameters + /// + /// * `params` - Optional marketplace name; omit to refresh all. + /// + /// # Returns + /// + /// Result of refreshing one or more marketplace catalogs. + /// + ///
+ /// + /// **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 refresh_with_params( + &self, + params: PluginsMarketplacesRefreshRequest, + ) -> Result { + let wire_params = serde_json::to_value(params)?; + let _value = self + .client + .call(rpc_methods::PLUGINS_MARKETPLACES_REFRESH, Some(wire_params)) + .await?; + Ok(serde_json::from_value(_value)?) + } +} + +/// `repo.*` RPCs. +#[derive(Clone, Copy)] +pub struct ClientRpcRepo<'a> { + pub(crate) client: &'a Client, +} + +impl<'a> ClientRpcRepo<'a> { + /// `repo.settings.*` sub-namespace. + pub fn settings(&self) -> ClientRpcRepoSettings<'a> { + ClientRpcRepoSettings { + client: self.client, + } + } +} + +/// `repo.settings.*` RPCs. +#[derive(Clone, Copy)] +pub struct ClientRpcRepoSettings<'a> { + pub(crate) client: &'a Client, +} + +impl<'a> ClientRpcRepoSettings<'a> { + /// Atomically edits one allowed top-level repository setting. Reads the raw file, preserves unrelated keys, applies one scalar or list edit, validates the result and writes under one file lock, so concurrent sibling or list edits are not lost. A malformed or unsafe existing file returns `invalid` without a write; rejected keys, invalid values and I/O errors reject the call. + /// + /// Wire method: `repo.settings.edit`. + /// + /// # Parameters + /// + /// * `params` - One atomic edit of an allowed top-level repository setting. + /// + /// # Returns + /// + /// Outcome of an atomic user or repository settings edit. + /// + ///
+ /// + /// **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 edit( + &self, + params: RepoSettingsEditRequest, + ) -> Result { + let wire_params = serde_json::to_value(params)?; + let _value = self + .client + .call(rpc_methods::REPO_SETTINGS_EDIT, Some(wire_params)) + .await?; + Ok(serde_json::from_value(_value)?) + } + + /// Reads one repository settings file without settings normalization or layering. The existing JSONC parser accepts comments and trailing commas and applies its prototype-safety rules. Missing or blank files return an empty valid config. Invalid files return an empty config with invalid=true; other read failures reject. + /// + /// Wire method: `repo.settings.readRaw`. + /// + /// # Parameters + /// + /// * `params` - Which repository settings file to read. + /// + /// # Returns + /// + /// Parsed repository settings without normalization or layering. + /// + ///
+ /// + /// **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 read_raw( + &self, + params: RepoSettingsReadRawRequest, + ) -> Result { + let wire_params = serde_json::to_value(params)?; + let _value = self + .client + .call(rpc_methods::REPO_SETTINGS_READRAW, Some(wire_params)) + .await?; + Ok(serde_json::from_value(_value)?) + } + + /// Writes or removes one allowed top-level repository setting through the existing validator and file lock. Preserves unrelated stored keys. Invalid keys, values, existing documents, and I/O errors reject without replacing the file. Deletion requires delete=true; otherwise value must be present. + /// + /// Wire method: `repo.settings.writeKey`. + /// + /// # Parameters + /// + /// * `params` - One key to write or remove in a repository settings file. + /// + ///
+ /// + /// **Experimental.** This API is part of an experimental wire-protocol surface + /// and may change or be removed in future SDK or CLI releases. Pin both the + /// SDK and CLI versions if your code depends on it. + /// + ///
+ pub(crate) async fn write_key(&self, params: RepoSettingsWriteKeyRequest) -> Result<(), Error> { + let wire_params = serde_json::to_value(params)?; + let _value = self + .client + .call(rpc_methods::REPO_SETTINGS_WRITEKEY, Some(wire_params)) + .await?; + Ok(()) + } + + /// Replaces the repository-overridable settings in one locked write. Allowed keys present in settings are validated and written; allowed keys absent from settings are removed. Other input keys are ignored, and unknown keys already stored in the file remain unchanged. Invalid values or an invalid existing file reject without a partial write. + /// + /// Wire method: `repo.settings.writeAll`. + /// + /// # Parameters + /// + /// * `params` - The complete set of repository-overridable settings to store. + /// + ///
+ /// + /// **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 write_all(&self, params: RepoSettingsWriteAllRequest) -> Result<(), Error> { + let wire_params = serde_json::to_value(params)?; + let _value = self + .client + .call(rpc_methods::REPO_SETTINGS_WRITEALL, Some(wire_params)) + .await?; + Ok(()) + } +} + +/// `runtime.*` RPCs. +#[derive(Clone, Copy)] +pub struct ClientRpcRuntime<'a> { + pub(crate) client: &'a Client, +} + +impl<'a> ClientRpcRuntime<'a> { + /// Gracefully shuts down an SDK-owned runtime. The response is sent only after cleanup completes; callers may then terminate the owned runtime process. + /// + /// Wire method: `runtime.shutdown`. + /// + ///
+ /// + /// **Experimental.** This API is part of an experimental wire-protocol surface + /// and may change or be removed in future SDK or CLI releases. Pin both the + /// SDK and CLI versions if your code depends on it. + /// + ///
+ pub async fn shutdown(&self) -> Result<(), Error> { + let wire_params = serde_json::json!({}); + let _value = self + .client + .call(rpc_methods::RUNTIME_SHUTDOWN, Some(wire_params)) + .await?; + Ok(()) + } +} + +/// `sandbox.*` RPCs. +#[derive(Clone, Copy)] +pub struct ClientRpcSandbox<'a> { + pub(crate) client: &'a Client, +} + +impl<'a> ClientRpcSandbox<'a> { + /// `sandbox.proxyCa.*` sub-namespace. + pub fn proxy_ca(&self) -> ClientRpcSandboxProxyCa<'a> { + ClientRpcSandboxProxyCa { + client: self.client, + } + } + + /// `sandbox.proxySecret.*` sub-namespace. + pub fn proxy_secret(&self) -> ClientRpcSandboxProxySecret<'a> { + ClientRpcSandboxProxySecret { + client: self.client, + } + } + + /// `sandbox.settings.*` sub-namespace. + pub fn settings(&self) -> ClientRpcSandboxSettings<'a> { + ClientRpcSandboxSettings { + client: self.client, + } + } + + /// Raises a user's sandbox settings to an administrator's managed sandbox floor and returns the result a session should actually run under. The floor can only tighten: an enterprise force-on is reflected even when the user's own `enabled` is false, and a user cannot lower a setting the floor raised. The result is normalized through the declared sandbox shape, so a key reorder or an undeclared passthrough key is not mistaken for a policy change by callers that diff two results. A host must call this rather than merge the two layers itself, because reimplementing the direction of the fold is what turns an administrator's floor into a suggestion. + /// + /// Wire method: `sandbox.resolveFlooredConfig`. + /// + /// # Parameters + /// + /// * `params` - The user's sandbox settings and the managed sandbox floor to raise them to. + /// + /// # Returns + /// + /// The user's sandbox settings raised to the managed floor. + /// + ///
+ /// + /// **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 resolve_floored_config( + &self, + params: SandboxResolveFlooredConfigParams, + ) -> Result { + let wire_params = serde_json::to_value(params)?; + let _value = self + .client + .call(rpc_methods::SANDBOX_RESOLVEFLOOREDCONFIG, Some(wire_params)) + .await?; + Ok(serde_json::from_value(_value)?) + } + + /// Builds the effective filesystem policy one `@`-file picker checks its suggestions against, so Git discovery and grant canonicalization run once for the picker's lifetime instead of on every debounced keystroke. Returns no policy when the sandbox is absent or disabled, which the paired filter reads as "keep every candidate". A host must pair this with `sandbox.filterPickerCandidates` rather than resolve a grant list itself, because the picker's grants are narrower than a file tool's — the same path a built-in tool may read can be one the picker must not offer. + /// + /// Wire method: `sandbox.preparePickerPolicy`. + /// + /// # Parameters + /// + /// * `params` - The working directory and sandbox settings to build a file-picker policy from. + /// + /// # Returns + /// + /// The prepared file-picker policy, or none when the sandbox is disabled. + /// + ///
+ /// + /// **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 prepare_picker_policy( + &self, + params: SandboxPreparePickerPolicyParams, + ) -> Result { + let wire_params = serde_json::to_value(params)?; + let _value = self + .client + .call(rpc_methods::SANDBOX_PREPAREPICKERPOLICY, Some(wire_params)) + .await?; + Ok(serde_json::from_value(_value)?) + } + + /// Checks a batch of `@`-file picker suggestions against a policy prepared by `sandbox.preparePickerPolicy` and returns one allow decision per candidate, positionally aligned with the request. Batched deliberately: each check canonicalizes a path component-by-component through the real filesystem, so one call amortizes that over a single hop rather than dozens while the user types. A directory the policy does not itself allow can still be kept as a waypoint to an allowed subtree. An absent policy keeps every candidate. + /// + /// Wire method: `sandbox.filterPickerCandidates`. + /// + /// # Parameters + /// + /// * `params` - A batch of file-picker candidates to check against a prepared policy. + /// + /// # Returns + /// + /// Per-candidate allow decisions, positionally aligned with the request. + /// + ///
+ /// + /// **Experimental.** This API is part of an experimental wire-protocol surface + /// and may change or be removed in future SDK or CLI releases. Pin both the + /// SDK and CLI versions if your code depends on it. + /// + ///
+ pub(crate) async fn filter_picker_candidates( + &self, + params: SandboxFilterPickerCandidatesParams, + ) -> Result { + let wire_params = serde_json::to_value(params)?; + let _value = self + .client + .call( + rpc_methods::SANDBOX_FILTERPICKERCANDIDATES, + Some(wire_params), + ) + .await?; + Ok(serde_json::from_value(_value)?) + } + + /// The sandbox a single named feature actually launches under, which is not the same as the session's sandbox. A feature is routed out of the sandbox by its own flag — `sandboxMcpServers`, `sandboxLspServers` — so an enabled session can still start one server confined and another free. Returns a disabled sandbox rather than an error when the host has no sandbox slice at all, so an absent config and an opted-out feature read alike at the launch site. A host must ask per feature rather than reuse the session config, because reading only `enabled` confines a server the user opted out of, or reports as sandboxed one that is not. + /// + /// Wire method: `sandbox.resolveForOptional`. + /// + /// # Parameters + /// + /// * `params` - A sandbox config and the per-feature routing flag to resolve it for. + /// + /// # Returns + /// + /// The sandbox a named feature actually launches under. + /// + ///
+ /// + /// **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 resolve_for_optional( + &self, + params: SandboxEffectiveForOptionalParams, + ) -> Result { + let wire_params = serde_json::to_value(params)?; + let _value = self + .client + .call(rpc_methods::SANDBOX_RESOLVEFOROPTIONAL, Some(wire_params)) + .await?; + Ok(serde_json::from_value(_value)?) + } + + /// Reports whether this machine can run an OS-level command sandbox at all. A host must ask before it offers sandboxing, because the answer is a property of the machine rather than of any setting: on Linux it depends on bubblewrap being installed, and on Windows on the OS build supporting BaseContainer. Probing it requires spawning a process the first time, so the answer is cached for the life of the runtime. A host that assumes support and enables the sandbox anyway produces a session in which every shell command fails. + /// + /// Wire method: `sandbox.checkPlatformSupport`. + /// + /// # Returns + /// + /// True when an OS-level sandbox can run on this 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 check_platform_support( + &self, + ) -> Result { + let wire_params = serde_json::json!({}); + let _value = self + .client + .call(rpc_methods::SANDBOX_CHECKPLATFORMSUPPORT, Some(wire_params)) + .await?; + Ok(serde_json::from_value(_value)?) + } + + /// Reports whether this Windows host's BaseContainer implementation can reach the host's loopback interface directly, which is what a local-network policy needs to avoid routing through the egress proxy. Like `sandbox.checkPlatformSupport`, the answer is a property of the machine's OS build rather than of any setting, and resolving it the first time spawns a platform probe. A host that offers the direct local-network option on a machine that lacks the capability produces a session whose sandboxed commands cannot reach local services. Always false off Windows. + /// + /// Wire method: `sandbox.checkWindowsHostLoopbackSupport`. + /// + /// # Returns + /// + /// True when the sandbox on this host can reach host loopback directly. + /// + ///
+ /// + /// **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 check_windows_host_loopback_support( + &self, + ) -> Result { + let wire_params = serde_json::json!({}); + let _value = self + .client + .call( + rpc_methods::SANDBOX_CHECKWINDOWSHOSTLOOPBACKSUPPORT, + Some(wire_params), + ) + .await?; + Ok(serde_json::from_value(_value)?) + } + + /// The explanation a host shows when it refuses an explicit request to turn the sandbox on for a machine that cannot run one. Carries the platform-specific remedy — install bubblewrap, update Windows — and the documentation link, so a host that got `false` from `sandbox.checkPlatformSupport` does not have to compose its own reason and cannot describe the same machine differently from the runtime's own spawn path. + /// + /// Wire method: `sandbox.getUnsupportedMessage`. + /// + /// # Returns + /// + /// The user-facing refusal, including the platform remedy and the documentation link. + /// + ///
+ /// + /// **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_unsupported_message( + &self, + ) -> Result { + let wire_params = serde_json::json!({}); + let _value = self + .client + .call( + rpc_methods::SANDBOX_GETUNSUPPORTEDMESSAGE, + Some(wire_params), + ) + .await?; + Ok(serde_json::from_value(_value)?) + } + + /// The notice a host shows when a stored `sandbox` setting is being ignored for this session because the machine cannot enforce it. Distinct from `sandbox.getUnsupportedMessage`, which refuses an explicit request: this one reports that a setting the user already saved is inert, states that commands will run unsandboxed, and confirms the stored setting itself is left alone. A host that stays silent here leaves the user believing a sandbox is active. + /// + /// Wire method: `sandbox.getUnsupportedSettingDisabledNotice`. + /// + /// # Returns + /// + /// The user-facing notice that a saved sandbox setting is inert for this 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(crate) async fn get_unsupported_setting_disabled_notice( + &self, + ) -> Result { + let wire_params = serde_json::json!({}); + let _value = self + .client + .call( + rpc_methods::SANDBOX_GETUNSUPPORTEDSETTINGDISABLEDNOTICE, + Some(wire_params), + ) + .await?; + Ok(serde_json::from_value(_value)?) + } + + /// The warning a host prints at startup when a session asked for a sandbox on a machine that cannot run one. The third of the unsupported notices, and the only one that depends on the policy in force: it names the remedy the reader can actually act on, which is the stored setting for an unmanaged host, an administrator for a managed one, and waiting for the policy to resolve when the sandbox stayed on only because the policy could not be determined. Returns no notice when the machine does support a sandbox, so a host can call it unconditionally on the startup path. A host that composes its own warning tells a managed user to change a setting their policy will overwrite. + /// + /// Wire method: `sandbox.getUnsupportedStartupNotice`. + /// + /// # Parameters + /// + /// * `params` - The managed sandbox policy in force, which selects which remedy the notice offers. + /// + /// # Returns + /// + /// The startup warning, absent when the machine can run a sandbox. + /// + ///
+ /// + /// **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_unsupported_startup_notice( + &self, + params: SandboxUnsupportedStartupNoticeParams, + ) -> Result { + let wire_params = serde_json::to_value(params)?; + let _value = self + .client + .call( + rpc_methods::SANDBOX_GETUNSUPPORTEDSTARTUPNOTICE, + Some(wire_params), + ) + .await?; + Ok(serde_json::from_value(_value)?) + } + + /// Whether one slash command stays reachable while sandbox enforcement is unavailable and the session is otherwise blocked. The permitted set is deliberately narrow — commands that only report state, and the few that let the user repair or leave the situation — and it is keyed on the arguments as well as the name, so `/sandbox status` is admitted where `/sandbox enable` is not. A host must ask rather than keep its own list: a command this returns false for is one the runtime would refuse anyway, and a divergent list either strands the user with no way out or admits a command that acts on an unenforced session. + /// + /// Wire method: `sandbox.checkEnforcementCommand`. + /// + /// # Parameters + /// + /// * `params` - One slash command, as typed, to test against the enforcement-block allowlist. + /// + /// # Returns + /// + /// True when the command stays available while enforcement is blocked. + /// + ///
+ /// + /// **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 check_enforcement_command( + &self, + params: SandboxEnforcementCommandAllowedParams, + ) -> Result { + let wire_params = serde_json::to_value(params)?; + let _value = self + .client + .call( + rpc_methods::SANDBOX_CHECKENFORCEMENTCOMMAND, + Some(wire_params), + ) + .await?; + Ok(serde_json::from_value(_value)?) + } + + /// Returns the documentation URL a host should point a user at when it declines or downgrades a sandbox setting. The runtime owns the address so every surface cites the same page and a host does not hard-code a link that later moves. + /// + /// Wire method: `sandbox.getDocsUrl`. + /// + /// # Returns + /// + /// The documentation URL a host cites when it declines or downgrades a sandbox setting. + /// + ///
+ /// + /// **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_docs_url(&self) -> Result { + let wire_params = serde_json::json!({}); + let _value = self + .client + .call(rpc_methods::SANDBOX_GETDOCSURL, Some(wire_params)) + .await?; + Ok(serde_json::from_value(_value)?) + } + + /// Reports whether the host running this runtime can run the command sandbox, without starting a session or spawning a sandboxed command. + /// + /// Wire method: `sandbox.getHostSupport`. + /// + /// # Returns + /// + /// Whether the host running this runtime can run the command sandbox. The runtime checks `supported` once per process. A capability answer can change while the process runs, for example after the user installs a missing package. + /// + ///
+ /// + /// **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_host_support(&self) -> Result { + let wire_params = serde_json::json!({}); + let _value = self + .client + .call(rpc_methods::SANDBOX_GETHOSTSUPPORT, Some(wire_params)) + .await?; + Ok(serde_json::from_value(_value)?) + } +} + +/// `sandbox.proxyCa.*` RPCs. +#[derive(Clone, Copy)] +pub struct ClientRpcSandboxProxyCa<'a> { + pub(crate) client: &'a Client, +} + +impl<'a> ClientRpcSandboxProxyCa<'a> { + /// Reports whether this runtime process can offer installing the sandbox credential-proxy certificate authority into OS trust. + /// + /// Wire method: `sandbox.proxyCa.checkInstallationSupport`. + /// + /// # Returns + /// + /// True when this process can offer installing the sandbox credential-proxy certificate authority into OS trust. + /// + ///
+ /// + /// **Experimental.** This API is part of an experimental wire-protocol surface + /// and may change or be removed in future SDK or CLI releases. Pin both the + /// SDK and CLI versions if your code depends on it. + /// + ///
+ pub async fn check_installation_support( + &self, + ) -> Result { + let wire_params = serde_json::json!({}); + let _value = self + .client + .call( + rpc_methods::SANDBOX_PROXYCA_CHECKINSTALLATIONSUPPORT, + Some(wire_params), + ) + .await?; + Ok(serde_json::from_value(_value)?) + } + + /// Reports whether this sandbox configuration uses the credential proxy and can need certificate-authority setup. + /// + /// Wire method: `sandbox.proxyCa.checkRelevance`. + /// + /// # Parameters + /// + /// * `params` - Identifies the credential hosts that the persistent certificate authority of the sandbox credential proxy must cover. The runtime always adds the hosts from the saved user settings. + /// + /// # Returns + /// + /// True when the sandbox configuration uses the credential proxy and can need certificate-authority setup. + /// + ///
+ /// + /// **Experimental.** This API is part of an experimental wire-protocol surface + /// and may change or be removed in future SDK or CLI releases. Pin both the + /// SDK and CLI versions if your code depends on it. + /// + ///
+ pub async fn check_relevance( + &self, + params: SandboxProxyCaRequest, + ) -> Result { + let wire_params = serde_json::to_value(params)?; + let _value = self + .client + .call( + rpc_methods::SANDBOX_PROXYCA_CHECKRELEVANCE, + Some(wire_params), + ) + .await?; + Ok(serde_json::from_value(_value)?) + } + + /// Reports whether the persistent certificate authority of the sandbox credential proxy exists, whether OS trust includes it, and whether it must be rotated. Changes nothing. + /// + /// Wire method: `sandbox.proxyCa.getStatus`. + /// + /// # Parameters + /// + /// * `params` - Identifies the credential hosts that the persistent certificate authority of the sandbox credential proxy must cover. The runtime always adds the hosts from the saved user settings. + /// + /// # Returns + /// + /// Status of the persistent certificate authority of the sandbox credential proxy. + /// + ///
+ /// + /// **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, + params: SandboxProxyCaRequest, + ) -> Result { let wire_params = serde_json::to_value(params)?; let _value = self .client - .call(rpc_methods::MCP_CONFIG_REMOVE, Some(wire_params)) + .call(rpc_methods::SANDBOX_PROXYCA_GETSTATUS, Some(wire_params)) .await?; - Ok(()) + Ok(serde_json::from_value(_value)?) } - /// Enables MCP servers in user configuration for new sessions. + /// Creates the persistent certificate authority of the sandbox credential proxy if none is stored, without changing OS trust, and returns the path of its public certificate. Keeps an existing certificate authority, even one that must be rotated. Fails where OS trust is unsupported. Trust it with sandbox.proxyCa.trust: the CLI trusts only the hosts in the saved user settings, so it refuses a certificate authority that also covers hosts from sandboxConfig. /// - /// Wire method: `mcp.config.enable`. + /// Wire method: `sandbox.proxyCa.create`. /// /// # Parameters /// - /// * `params` - MCP server names to enable for new sessions. + /// * `params` - Identifies the credential hosts that the persistent certificate authority of the sandbox credential proxy must cover. The runtime always adds the hosts from the saved user settings. + /// + /// # Returns + /// + /// Result of creating the persistent certificate authority of the sandbox credential proxy. /// ///
/// @@ -2540,22 +5131,29 @@ impl<'a> ClientRpcMcpConfig<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub async fn enable(&self, params: McpConfigEnableRequest) -> Result<(), Error> { + pub async fn create( + &self, + params: SandboxProxyCaRequest, + ) -> Result { let wire_params = serde_json::to_value(params)?; let _value = self .client - .call(rpc_methods::MCP_CONFIG_ENABLE, Some(wire_params)) + .call(rpc_methods::SANDBOX_PROXYCA_CREATE, Some(wire_params)) .await?; - Ok(()) + Ok(serde_json::from_value(_value)?) } - /// Disables MCP servers in user configuration for new sessions. + /// Replaces the persistent certificate authority of the sandbox credential proxy with a new one for the current credential hosts. If OS trust included the old one, removes it and trusts the new one, which can show an OS authentication prompt. Running sandboxed tools keep the old certificate authority until they restart. /// - /// Wire method: `mcp.config.disable`. + /// Wire method: `sandbox.proxyCa.rotate`. /// /// # Parameters /// - /// * `params` - MCP server names to disable for new sessions. + /// * `params` - Identifies the credential hosts that the persistent certificate authority of the sandbox credential proxy must cover. The runtime always adds the hosts from the saved user settings. + /// + /// # Returns + /// + /// Status of the persistent certificate authority of the sandbox credential proxy. /// ///
/// @@ -2564,18 +5162,29 @@ impl<'a> ClientRpcMcpConfig<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub async fn disable(&self, params: McpConfigDisableRequest) -> Result<(), Error> { + pub async fn rotate( + &self, + params: SandboxProxyCaRequest, + ) -> Result { let wire_params = serde_json::to_value(params)?; let _value = self .client - .call(rpc_methods::MCP_CONFIG_DISABLE, Some(wire_params)) + .call(rpc_methods::SANDBOX_PROXYCA_ROTATE, Some(wire_params)) .await?; - Ok(()) + Ok(serde_json::from_value(_value)?) } - /// Drops this runtime process's in-memory MCP server-definition cache so the next MCP config read observes disk. + /// Adds the persistent certificate authority of the sandbox credential proxy to OS trust, so sandboxed clients that read only OS trust accept the proxy. Call create first. Refuses a certificate authority that is not constrained to the current credential hosts. Can show an OS authentication prompt. /// - /// Wire method: `mcp.config.reload`. + /// Wire method: `sandbox.proxyCa.trust`. + /// + /// # Parameters + /// + /// * `params` - Identifies the credential hosts that the persistent certificate authority of the sandbox credential proxy must cover. The runtime always adds the hosts from the saved user settings. + /// + /// # Returns + /// + /// Status of the persistent certificate authority of the sandbox credential proxy. /// ///
/// @@ -2584,34 +5193,112 @@ impl<'a> ClientRpcMcpConfig<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub async fn reload(&self) -> Result<(), Error> { + pub async fn trust( + &self, + params: SandboxProxyCaRequest, + ) -> Result { + let wire_params = serde_json::to_value(params)?; + let _value = self + .client + .call(rpc_methods::SANDBOX_PROXYCA_TRUST, Some(wire_params)) + .await?; + Ok(serde_json::from_value(_value)?) + } + + /// Removes the persistent certificate authority of the sandbox credential proxy from OS trust. Keeps the stored certificate authority. Can show an OS authentication prompt. Sandboxed clients that read only OS trust then reject the proxy; clients that read the per-process certificate bundle continue to work. + /// + /// Wire method: `sandbox.proxyCa.remove`. + /// + /// # Returns + /// + /// Status of the persistent certificate authority of the sandbox credential proxy. + /// + ///
+ /// + /// **Experimental.** This API is part of an experimental wire-protocol surface + /// and may change or be removed in future SDK or CLI releases. Pin both the + /// SDK and CLI versions if your code depends on it. + /// + ///
+ pub async fn remove(&self) -> Result { let wire_params = serde_json::json!({}); let _value = self .client - .call(rpc_methods::MCP_CONFIG_RELOAD, Some(wire_params)) + .call(rpc_methods::SANDBOX_PROXYCA_REMOVE, Some(wire_params)) .await?; - Ok(()) + Ok(serde_json::from_value(_value)?) } } -/// `mcp.installations.*` RPCs. +/// `sandbox.proxySecret.*` RPCs. #[derive(Clone, Copy)] -pub struct ClientRpcMcpInstallations<'a> { +pub struct ClientRpcSandboxProxySecret<'a> { pub(crate) client: &'a Client, } -impl<'a> ClientRpcMcpInstallations<'a> { - /// Reads receipt-owned MCP inventory for the selected account and host without activating servers or reconstructing missing ownership. Configuration ownership does not prove session-specific usability. +impl<'a> ClientRpcSandboxProxySecret<'a> { + /// Reports where this runtime keeps the sandbox proxy password: the credential store directory and the entry identifier within it. Both are derived from the configuration directory this runtime was started with, so a host does not resolve them itself. Read this to recognize a `${secret:…}` value in stored sandbox settings as this runtime's own proxy password rather than a reference the user wrote by hand. Marked internal because it exists to carry a CLI call site off the napi boundary onto the SDK contract. /// - /// Wire method: `mcp.installations.list`. + /// Wire method: `sandbox.proxySecret.getLocation`. + /// + /// # Returns + /// + /// Where this runtime keeps the sandbox proxy password. + /// + ///
+ /// + /// **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_location(&self) -> Result { + let wire_params = serde_json::json!({}); + let _value = self + .client + .call( + rpc_methods::SANDBOX_PROXYSECRET_GETLOCATION, + Some(wire_params), + ) + .await?; + Ok(serde_json::from_value(_value)?) + } + + /// Reads the stored sandbox proxy password from the credential store `sandbox.proxySecret.getLocation` names, preferring the OS keychain and falling back to the on-disk store. Reports an absent value rather than failing when no entry exists. Marked internal because it exists to carry a CLI call site off the napi boundary onto the SDK contract. + /// + /// Wire method: `sandbox.proxySecret.get`. + /// + /// # Returns + /// + /// The stored sandbox proxy password, if one is present. + /// + ///
+ /// + /// **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(&self) -> Result { + let wire_params = serde_json::json!({}); + let _value = self + .client + .call(rpc_methods::SANDBOX_PROXYSECRET_GET, Some(wire_params)) + .await?; + Ok(serde_json::from_value(_value)?) + } + + /// Stores the sandbox proxy password in the credential store `sandbox.proxySecret.getLocation` names, replacing any existing entry. Reports whether the OS keychain accepted it or it fell back to the on-disk store. Marked internal because it exists to carry a CLI call site off the napi boundary onto the SDK contract. + /// + /// Wire method: `sandbox.proxySecret.save`. /// /// # Parameters /// - /// * `params` - New-work inventory or recovery request under an explicitly selected existing session. + /// * `params` - The sandbox proxy password to store. /// /// # Returns /// - /// Management result with contract receipt, or a typed request/negotiation refusal. + /// Where the sandbox proxy password was stored. /// ///
/// @@ -2620,29 +5307,57 @@ impl<'a> ClientRpcMcpInstallations<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub async fn list( + pub(crate) async fn save( &self, - params: McpInstallationsRequest, - ) -> Result { + params: SandboxProxySecretSaveRequest, + ) -> Result { let wire_params = serde_json::to_value(params)?; let _value = self .client - .call(rpc_methods::MCP_INSTALLATIONS_LIST, Some(wire_params)) + .call(rpc_methods::SANDBOX_PROXYSECRET_SAVE, Some(wire_params)) .await?; Ok(serde_json::from_value(_value)?) } - /// Reconciles already-confirmed durable MCP transactions, then inspects owned inventory. Does not replay apply or reconstruct deleted ownership metadata; unresolved or unsafe evidence remains an explicit refusal. + /// Removes the stored sandbox proxy password from the credential store `sandbox.proxySecret.getLocation` names. Succeeds when no entry exists, so it is safe to call while clearing a proxy password whose origin is unknown. Marked internal because it exists to carry a CLI call site off the napi boundary onto the SDK contract. /// - /// Wire method: `mcp.installations.recover`. + /// Wire method: `sandbox.proxySecret.delete`. + /// + ///
+ /// + /// **Experimental.** This API is part of an experimental wire-protocol surface + /// and may change or be removed in future SDK or CLI releases. Pin both the + /// SDK and CLI versions if your code depends on it. + /// + ///
+ pub(crate) async fn delete(&self) -> Result<(), Error> { + let wire_params = serde_json::json!({}); + let _value = self + .client + .call(rpc_methods::SANDBOX_PROXYSECRET_DELETE, Some(wire_params)) + .await?; + Ok(()) + } +} + +/// `sandbox.settings.*` RPCs. +#[derive(Clone, Copy)] +pub struct ClientRpcSandboxSettings<'a> { + pub(crate) client: &'a Client, +} + +impl<'a> ClientRpcSandboxSettings<'a> { + /// Resolves a stored sandbox settings object into the effective one, filling every value the user left unset with its default. Read this to render or reason about the sandbox configuration a session would actually run under, rather than the sparse document the user wrote. + /// + /// Wire method: `sandbox.settings.applyDefaults`. /// /// # Parameters /// - /// * `params` - New-work inventory or recovery request under an explicitly selected existing session. + /// * `params` - The stored sandbox settings to resolve against the defaults. /// /// # Returns /// - /// Management result with contract receipt, or a typed request/negotiation refusal. + /// The effective sandbox settings, with every unset value resolved to its default. /// ///
/// @@ -2651,29 +5366,40 @@ impl<'a> ClientRpcMcpInstallations<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub async fn recover( + pub(crate) async fn apply_defaults( &self, - params: McpInstallationsRequest, - ) -> Result { + params: SandboxApplyDefaultsRequest, + ) -> Result { let wire_params = serde_json::to_value(params)?; let _value = self .client - .call(rpc_methods::MCP_INSTALLATIONS_RECOVER, Some(wire_params)) + .call( + rpc_methods::SANDBOX_SETTINGS_APPLYDEFAULTS, + Some(wire_params), + ) .await?; Ok(serde_json::from_value(_value)?) } +} - /// Inspects a known operation only on its original connection. Remains available after account or selected-session loss; does not acquire new authority or rebind an operation. +/// `secrets.*` RPCs. +#[derive(Clone, Copy)] +pub struct ClientRpcSecrets<'a> { + pub(crate) client: &'a Client, +} + +impl<'a> ClientRpcSecrets<'a> { + /// Registers secret values for redaction in session logs and exports. The SDK calls this to inject dynamically generated secret values (e.g., OIDC tokens). /// - /// Wire method: `mcp.installations.status`. + /// Wire method: `secrets.addFilterValues`. /// /// # Parameters /// - /// * `params` - Existing-operation control. A new session selector is deliberately not accepted. + /// * `params` - Secret values to add to the redaction filter. /// /// # Returns /// - /// Management result with contract receipt, or a typed request/negotiation refusal. + /// Confirmation that the secret values were registered. /// ///
/// @@ -2682,29 +5408,37 @@ impl<'a> ClientRpcMcpInstallations<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub async fn status( + pub async fn add_filter_values( &self, - params: McpInstallationOperationRequest, - ) -> Result { + params: SecretsAddFilterValuesRequest, + ) -> Result { let wire_params = serde_json::to_value(params)?; let _value = self .client - .call(rpc_methods::MCP_INSTALLATIONS_STATUS, Some(wire_params)) + .call(rpc_methods::SECRETS_ADDFILTERVALUES, Some(wire_params)) .await?; Ok(serde_json::from_value(_value)?) } +} - /// Requests cancellation of a known operation on its original connection, including before apply or confirmation. Already-started effects retain their transaction lease and report an honest terminal or recovery outcome. +/// `sessionFs.*` RPCs. +#[derive(Clone, Copy)] +pub struct ClientRpcSessionFs<'a> { + pub(crate) client: &'a Client, +} + +impl<'a> ClientRpcSessionFs<'a> { + /// Registers an SDK client as the session filesystem provider. /// - /// Wire method: `mcp.installations.cancel`. + /// Wire method: `sessionFs.setProvider`. /// /// # Parameters /// - /// * `params` - Existing-operation control. A new session selector is deliberately not accepted. + /// * `params` - Initial working directory, session-state path layout, and path conventions used to register the calling SDK client as the session filesystem provider. A registered provider is authoritative for path interpretation and filesystem facts used by workspace permission validation. Paths are interpreted lexically; home-relative paths (`~` and `~/...`) and Windows drive-relative paths such as `C:foo` are unsupported. Until provider-side canonicalization is supported, providers must not expose symlinks inside allowed roots that escape those roots. /// /// # Returns /// - /// Management result with contract receipt, or a typed request/negotiation refusal. + /// Indicates whether the calling client was registered as the session filesystem provider. /// ///
/// @@ -2713,33 +5447,61 @@ impl<'a> ClientRpcMcpInstallations<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub async fn cancel( + pub async fn set_provider( &self, - params: McpInstallationOperationRequest, - ) -> Result { + params: SessionFsSetProviderRequest, + ) -> Result { let wire_params = serde_json::to_value(params)?; let _value = self .client - .call(rpc_methods::MCP_INSTALLATIONS_CANCEL, Some(wire_params)) + .call(rpc_methods::SESSIONFS_SETPROVIDER, Some(wire_params)) .await?; Ok(serde_json::from_value(_value)?) } } -/// `models.*` RPCs. +/// `sessions.*` RPCs. #[derive(Clone, Copy)] -pub struct ClientRpcModels<'a> { +pub struct ClientRpcSessions<'a> { pub(crate) client: &'a Client, } -impl<'a> ClientRpcModels<'a> { - /// Lists Copilot models available to the authenticated user. +impl<'a> ClientRpcSessions<'a> { + /// Creates or resumes a local session and returns the opened session ID. + /// + /// Wire method: `sessions.open`. + /// + /// # Returns + /// + /// Result of opening 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 open(&self) -> Result { + let wire_params = serde_json::json!({}); + let _value = self + .client + .call(rpc_methods::SESSIONS_OPEN, Some(wire_params)) + .await?; + Ok(serde_json::from_value(_value)?) + } + + /// Creates a new session by forking persisted history from an existing session. /// - /// Wire method: `models.list`. + /// Wire method: `sessions.fork`. + /// + /// # Parameters + /// + /// * `params` - Source session identifier to fork from, optional event-ID boundary, and optional friendly name for the new session. /// /// # Returns /// - /// List of Copilot models available to the resolved user, including capabilities and billing metadata. + /// Identifier and optional friendly name assigned to the newly forked session. /// ///
/// @@ -2748,26 +5510,26 @@ impl<'a> ClientRpcModels<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub async fn list(&self) -> Result { - let wire_params = serde_json::json!({}); + pub async fn fork(&self, params: SessionsForkRequest) -> Result { + let wire_params = serde_json::to_value(params)?; let _value = self .client - .call(rpc_methods::MODELS_LIST, Some(wire_params)) + .call(rpc_methods::SESSIONS_FORK, Some(wire_params)) .await?; Ok(serde_json::from_value(_value)?) } - /// Lists Copilot models available to the authenticated user. + /// Connects to an existing remote session and exposes it as an SDK session. /// - /// Wire method: `models.list`. + /// Wire method: `sessions.connect`. /// /// # Parameters /// - /// * `params` - Optional opaque account selection or compatibility GitHub token used to list models. + /// * `params` - Remote session connection parameters. /// /// # Returns /// - /// List of Copilot models available to the resolved user, including capabilities and billing metadata. + /// Remote session connection result. /// ///
/// @@ -2776,22 +5538,25 @@ impl<'a> ClientRpcModels<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub async fn list_with_params(&self, params: ModelsListRequest) -> Result { + pub async fn connect( + &self, + params: ConnectRemoteSessionParams, + ) -> Result { let wire_params = serde_json::to_value(params)?; let _value = self .client - .call(rpc_methods::MODELS_LIST, Some(wire_params)) + .call(rpc_methods::SESSIONS_CONNECT, Some(wire_params)) .await?; Ok(serde_json::from_value(_value)?) } - /// Returns the running runtime's complete catalog of well-known built-in model IDs without authentication or network access. + /// Lists sessions, optionally filtered by source and working-directory context. Returned entries are discriminated by `isRemote`: local entries carry only the lightweight `LocalSessionMetadataValue` shape; remote entries carry the full `RemoteSessionMetadataValue` shape (repository, PR number, taskType, etc.). /// - /// Wire method: `models.getBuiltInCatalog`. + /// Wire method: `sessions.list`. /// /// # Returns /// - /// The running runtime's complete catalog of well-known built-in model IDs, including supported models and additional IDs with built-in metadata. + /// Sessions matching the filter, ordered most-recently-modified first. /// ///
/// @@ -2800,44 +5565,26 @@ impl<'a> ClientRpcModels<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub async fn get_built_in_catalog(&self) -> Result { + pub async fn list(&self) -> Result { let wire_params = serde_json::json!({}); let _value = self .client - .call(rpc_methods::MODELS_GETBUILTINCATALOG, Some(wire_params)) + .call(rpc_methods::SESSIONS_LIST, Some(wire_params)) .await?; Ok(serde_json::from_value(_value)?) } -} - -/// `plugins.*` RPCs. -#[derive(Clone, Copy)] -pub struct ClientRpcPlugins<'a> { - pub(crate) client: &'a Client, -} - -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 { - client: self.client, - } - } - - /// Lists plugins installed in user/global state. + /// Lists sessions, optionally filtered by source and working-directory context. Returned entries are discriminated by `isRemote`: local entries carry only the lightweight `LocalSessionMetadataValue` shape; remote entries carry the full `RemoteSessionMetadataValue` shape (repository, PR number, taskType, etc.). /// - /// Wire method: `plugins.list`. + /// Wire method: `sessions.list`. + /// + /// # Parameters + /// + /// * `params` - Optional source filter, metadata-load limit, and context filter applied to the returned sessions. /// /// # Returns /// - /// Plugins installed in user/global state. + /// Sessions matching the filter, ordered most-recently-modified first. /// ///
/// @@ -2846,26 +5593,29 @@ impl<'a> ClientRpcPlugins<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub async fn list(&self) -> Result { - let wire_params = serde_json::json!({}); + pub async fn list_with_params( + &self, + params: SessionsListRequest, + ) -> Result { + let wire_params = serde_json::to_value(params)?; let _value = self .client - .call(rpc_methods::PLUGINS_LIST, Some(wire_params)) + .call(rpc_methods::SESSIONS_LIST, Some(wire_params)) .await?; Ok(serde_json::from_value(_value)?) } - /// Installs a plugin from a marketplace, GitHub repo, URL, or local path. + /// Reads lightweight persisted metadata for one local session without opening it. /// - /// Wire method: `plugins.install`. + /// Wire method: `sessions.getMetadata`. /// /// # Parameters /// - /// * `params` - Plugin source and optional working directory for relative-path resolution. + /// * `params` - Session ID whose persisted metadata should be read. /// /// # Returns /// - /// Result of installing a plugin. + /// Persisted local session metadata when the session exists. /// ///
/// @@ -2874,25 +5624,29 @@ impl<'a> ClientRpcPlugins<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub async fn install( + pub(crate) async fn get_metadata( &self, - params: PluginsInstallRequest, - ) -> Result { + params: SessionsGetMetadataRequest, + ) -> Result { let wire_params = serde_json::to_value(params)?; let _value = self .client - .call(rpc_methods::PLUGINS_INSTALL, Some(wire_params)) + .call(rpc_methods::SESSIONS_GETMETADATA, Some(wire_params)) .await?; Ok(serde_json::from_value(_value)?) } - /// Uninstalls an installed plugin. + /// Reads client-owned metadata for multiple persisted local sessions without opening them. Results preserve request order and report missing, corrupt, unsupported, or temporarily unavailable sessions independently. /// - /// Wire method: `plugins.uninstall`. + /// Wire method: `sessions.getClientMetadata`. /// /// # Parameters /// - /// * `params` - Name (or spec) of the plugin to uninstall. + /// * `params` - Bounded batch request for client-owned metadata from persisted local sessions. + /// + /// # Returns + /// + /// Ordered client metadata outcomes for the requested local sessions. /// ///
/// @@ -2901,26 +5655,29 @@ impl<'a> ClientRpcPlugins<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub async fn uninstall(&self, params: PluginsUninstallRequest) -> Result<(), Error> { + pub async fn get_client_metadata( + &self, + params: SessionsGetClientMetadataRequest, + ) -> Result { let wire_params = serde_json::to_value(params)?; let _value = self .client - .call(rpc_methods::PLUGINS_UNINSTALL, Some(wire_params)) + .call(rpc_methods::SESSIONS_GETCLIENTMETADATA, Some(wire_params)) .await?; - Ok(()) + Ok(serde_json::from_value(_value)?) } - /// Updates an installed plugin to its latest published version. + /// Reads a page of durable events directly from a local session's persisted journal without creating, resuming, or activating the session. The first read pins the currently opened journal generation and its byte-length boundary; opaque cursor continuations remain on that generation across runtime-owned compaction, truncation, and rewrite operations, which replace the live path atomically, and events appended after the boundary are excluded. For cold hydration, await the first successful page before activation and establish lossless live-event buffering before resume; merge subsequent live events by ID, preserving persisted order and letting live payloads win. Continuations are process-local, single-use capabilities bound to the originating session and storage context and must be paged sequentially; concurrent or repeated use of the same cursor expires that duplicate read rather than reading the generation twice. A complete snapshot has cursorStatus 'ok' and hasMore false. Snapshots expire after five idle minutes, with at most eight retained per process and idle-only eviction under pressure; completion and cancelled-worker exit release their handles. No transcript copy is created, but retained handles may keep replaced files' disk blocks alive until release. Pages have a soft 1 MiB serialized event-array budget including resolved binary assets; one oversized event is returned alone to guarantee progress. Working memory also includes a record/lookahead and asset resolution; resolving the first binary reference may scan the full pinned generation to build a bounded offset index. If the snapshot expires, is evicted, is cancelled before a continuation is established, or becomes unreadable after an observable unsupported in-place shortening, the continuation returns cursorStatus 'expired' with an empty terminal page and never falls back to a different generation. A missing or initially unreadable journal is an RPC error. Persisted history excludes ephemeral events and may omit payloads that are reconstructed only for an active session; use the active session event stream for post-resume live events. /// - /// Wire method: `plugins.update`. + /// Wire method: `sessions.readPersistedEvents`. /// /// # Parameters /// - /// * `params` - Name (or spec) of the plugin to update. + /// * `params` - Pagination options for reading an inactive or active local session's persisted event journal. /// /// # Returns /// - /// Result of updating a single plugin. + /// Batch of session events returned by a read, with cursor and continuation metadata. /// ///
/// @@ -2929,22 +5686,29 @@ impl<'a> ClientRpcPlugins<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub async fn update(&self, params: PluginsUpdateRequest) -> Result { + 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::PLUGINS_UPDATE, Some(wire_params)) + .call(rpc_methods::SESSIONS_READPERSISTEDEVENTS, Some(wire_params)) .await?; Ok(serde_json::from_value(_value)?) } - /// Updates every installed plugin to its latest published version. + /// Lists recent local session IDs that contain user-visible history, omitting housekeeping-only sessions. /// - /// Wire method: `plugins.updateAll`. + /// Wire method: `sessions.listNonEmptySessionIds`. + /// + /// # Parameters + /// + /// * `params` - Limit for non-empty local session IDs. /// /// # Returns /// - /// Result of updating all installed plugins. + /// Recent local session IDs that contain user-visible history. /// ///
/// @@ -2953,22 +5717,32 @@ impl<'a> ClientRpcPlugins<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub async fn update_all(&self) -> Result { - let wire_params = serde_json::json!({}); + pub(crate) async fn list_non_empty_session_ids( + &self, + params: SessionsListNonEmptySessionIdsRequest, + ) -> Result { + let wire_params = serde_json::to_value(params)?; let _value = self .client - .call(rpc_methods::PLUGINS_UPDATEALL, Some(wire_params)) + .call( + rpc_methods::SESSIONS_LISTNONEMPTYSESSIONIDS, + Some(wire_params), + ) .await?; Ok(serde_json::from_value(_value)?) } - /// Enables installed plugins for new sessions. + /// Finds the local session bound to a GitHub task ID, if any. /// - /// Wire method: `plugins.enable`. + /// Wire method: `sessions.findByTaskId`. /// /// # Parameters /// - /// * `params` - Plugin names (or specs) to enable, plus the optional working directory the repository-controlled guard is evaluated against. + /// * `params` - GitHub task ID to look up. + /// + /// # Returns + /// + /// ID of the local session bound to the given GitHub task, or omitted when none. /// ///
/// @@ -2977,22 +5751,29 @@ impl<'a> ClientRpcPlugins<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub async fn enable(&self, params: PluginsEnableRequest) -> Result<(), Error> { + pub async fn find_by_task_id( + &self, + params: SessionsFindByTaskIDRequest, + ) -> Result { let wire_params = serde_json::to_value(params)?; let _value = self .client - .call(rpc_methods::PLUGINS_ENABLE, Some(wire_params)) + .call(rpc_methods::SESSIONS_FINDBYTASKID, Some(wire_params)) .await?; - Ok(()) + Ok(serde_json::from_value(_value)?) } - /// Disables installed plugins for new sessions. + /// Resolves a UUID prefix to a unique session ID, if exactly one session matches. /// - /// Wire method: `plugins.disable`. + /// Wire method: `sessions.findByPrefix`. /// /// # Parameters /// - /// * `params` - Plugin names (or specs) to disable, plus the optional working directory the repository-controlled guard is evaluated against. + /// * `params` - UUID prefix to resolve to a unique session ID. + /// + /// # Returns + /// + /// Session ID matching the prefix, omitted when no unique match exists. /// ///
/// @@ -3001,30 +5782,29 @@ impl<'a> ClientRpcPlugins<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub async fn disable(&self, params: PluginsDisableRequest) -> Result<(), Error> { + pub async fn find_by_prefix( + &self, + params: SessionsFindByPrefixRequest, + ) -> Result { let wire_params = serde_json::to_value(params)?; let _value = self .client - .call(rpc_methods::PLUGINS_DISABLE, Some(wire_params)) + .call(rpc_methods::SESSIONS_FINDBYPREFIX, Some(wire_params)) .await?; - Ok(()) + Ok(serde_json::from_value(_value)?) } -} - -/// `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. + /// Returns the most-relevant prior session for a given working-directory context. /// - /// Wire method: `plugins.builtin.set`. + /// Wire method: `sessions.getLastForContext`. /// /// # Parameters /// - /// * `params` - Trusted built-in plugin directories to use for this runtime process. + /// * `params` - Optional working-directory context used to score session relevance. + /// + /// # Returns + /// + /// Most-relevant session ID for the supplied context, or omitted when no sessions exist. /// ///
/// @@ -3033,30 +5813,29 @@ impl<'a> ClientRpcPluginsBuiltin<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub async fn set(&self, params: PluginsBuiltinSetRequest) -> Result<(), Error> { + pub async fn get_last_for_context( + &self, + params: SessionsGetLastForContextRequest, + ) -> Result { let wire_params = serde_json::to_value(params)?; let _value = self .client - .call(rpc_methods::PLUGINS_BUILTIN_SET, Some(wire_params)) + .call(rpc_methods::SESSIONS_GETLASTFORCONTEXT, Some(wire_params)) .await?; - Ok(()) + Ok(serde_json::from_value(_value)?) } -} - -/// `plugins.marketplaces.*` RPCs. -#[derive(Clone, Copy)] -pub struct ClientRpcPluginsMarketplaces<'a> { - pub(crate) client: &'a Client, -} -impl<'a> ClientRpcPluginsMarketplaces<'a> { - /// Lists all registered marketplaces (defaults + user-added). + /// Computes the absolute path to a session's persisted events.jsonl file. Internal: filesystem paths are only meaningful in-process (CLI and runtime share a filesystem). Currently used by the CLI's contribution-graph feature to read historical events directly. Remote SDK consumers must not depend on this; a proper event-query API would replace it if the contribution graph ever needed to work over the wire. /// - /// Wire method: `plugins.marketplaces.list`. + /// Wire method: `sessions.getEventFilePath`. + /// + /// # Parameters + /// + /// * `params` - Session ID whose event-log file path to compute. /// /// # Returns /// - /// All registered marketplaces, including built-in defaults. + /// Absolute path to the session's events.jsonl file on disk. /// ///
/// @@ -3065,26 +5844,25 @@ impl<'a> ClientRpcPluginsMarketplaces<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub async fn list(&self) -> Result { - let wire_params = serde_json::json!({}); + pub(crate) async fn get_event_file_path( + &self, + params: SessionsGetEventFilePathRequest, + ) -> Result { + let wire_params = serde_json::to_value(params)?; let _value = self .client - .call(rpc_methods::PLUGINS_MARKETPLACES_LIST, Some(wire_params)) + .call(rpc_methods::SESSIONS_GETEVENTFILEPATH, Some(wire_params)) .await?; Ok(serde_json::from_value(_value)?) } - /// Registers a new marketplace from a source (owner/repo, URL, or local path). - /// - /// Wire method: `plugins.marketplaces.add`. - /// - /// # Parameters + /// Returns the on-disk byte size of each session's workspace directory. /// - /// * `params` - Marketplace source and optional working directory for relative-path resolution. + /// Wire method: `sessions.getSizes`. /// /// # Returns /// - /// Result of registering a new marketplace. + /// Map of sessionId -> on-disk size in bytes for each session's workspace directory. /// ///
/// @@ -3093,29 +5871,26 @@ impl<'a> ClientRpcPluginsMarketplaces<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub async fn add( - &self, - params: PluginsMarketplacesAddRequest, - ) -> Result { - let wire_params = serde_json::to_value(params)?; + pub async fn get_sizes(&self) -> Result { + let wire_params = serde_json::json!({}); let _value = self .client - .call(rpc_methods::PLUGINS_MARKETPLACES_ADD, Some(wire_params)) + .call(rpc_methods::SESSIONS_GETSIZES, Some(wire_params)) .await?; Ok(serde_json::from_value(_value)?) } - /// Removes a previously-registered marketplace. When the marketplace has dependent plugins and `force` is not set, the marketplace is left intact and the result lists the dependents so the caller can decide whether to retry with `force=true`. + /// Returns the subset of the supplied session IDs that are currently held by another running process. /// - /// Wire method: `plugins.marketplaces.remove`. + /// Wire method: `sessions.checkInUse`. /// /// # Parameters /// - /// * `params` - Name of the marketplace to remove and an optional force flag. + /// * `params` - Session IDs to test for live in-use locks. /// /// # Returns /// - /// Outcome of the remove attempt, including dependent-plugin info when applicable. + /// Session IDs from the input set that are currently in use by another process. /// ///
/// @@ -3124,29 +5899,29 @@ impl<'a> ClientRpcPluginsMarketplaces<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub async fn remove( + pub async fn check_in_use( &self, - params: PluginsMarketplacesRemoveRequest, - ) -> Result { + params: SessionsCheckInUseRequest, + ) -> Result { let wire_params = serde_json::to_value(params)?; let _value = self .client - .call(rpc_methods::PLUGINS_MARKETPLACES_REMOVE, Some(wire_params)) + .call(rpc_methods::SESSIONS_CHECKINUSE, Some(wire_params)) .await?; Ok(serde_json::from_value(_value)?) } - /// Lists plugins advertised by a registered marketplace. + /// Returns a session's persisted remote-steerable flag, if any has been recorded. Internal: this is CLI-specific book-keeping used by `--continue` / `--resume` to inherit the prior session's remote-steerable preference. SDK consumers that want similar behavior should manage their own persistence around start/stop calls rather than relying on this runtime-side flag. /// - /// Wire method: `plugins.marketplaces.browse`. + /// Wire method: `sessions.getPersistedRemoteSteerable`. /// /// # Parameters /// - /// * `params` - Name of the marketplace whose plugin catalog to fetch. + /// * `params` - Session ID to look up the persisted remote-steerable flag for. /// /// # Returns /// - /// Plugins advertised by the marketplace. + /// The session's persisted remote-steerable flag, or omitted when no value has been persisted. /// ///
/// @@ -3155,25 +5930,32 @@ impl<'a> ClientRpcPluginsMarketplaces<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub async fn browse( + pub(crate) async fn get_persisted_remote_steerable( &self, - params: PluginsMarketplacesBrowseRequest, - ) -> Result { + params: SessionsGetPersistedRemoteSteerableRequest, + ) -> Result { let wire_params = serde_json::to_value(params)?; let _value = self .client - .call(rpc_methods::PLUGINS_MARKETPLACES_BROWSE, Some(wire_params)) + .call( + rpc_methods::SESSIONS_GETPERSISTEDREMOTESTEERABLE, + Some(wire_params), + ) .await?; Ok(serde_json::from_value(_value)?) } - /// Re-fetches one or all registered marketplace catalogs. + /// Closes a session: emits shutdown, flushes pending events, releases the in-use lock, and disposes the active session. /// - /// Wire method: `plugins.marketplaces.refresh`. + /// Wire method: `sessions.close`. + /// + /// # Parameters + /// + /// * `params` - Session ID to close. /// /// # Returns /// - /// Result of refreshing one or more marketplace catalogs. + /// 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. /// ///
/// @@ -3182,26 +5964,26 @@ impl<'a> ClientRpcPluginsMarketplaces<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub async fn refresh(&self) -> Result { - let wire_params = serde_json::json!({}); + pub async fn close(&self, params: SessionsCloseRequest) -> Result { + let wire_params = serde_json::to_value(params)?; let _value = self .client - .call(rpc_methods::PLUGINS_MARKETPLACES_REFRESH, Some(wire_params)) + .call(rpc_methods::SESSIONS_CLOSE, Some(wire_params)) .await?; Ok(serde_json::from_value(_value)?) } - /// Re-fetches one or all registered marketplace catalogs. + /// Closes, deactivates, and deletes a set of sessions, returning the bytes freed per session. /// - /// Wire method: `plugins.marketplaces.refresh`. + /// Wire method: `sessions.bulkDelete`. /// /// # Parameters /// - /// * `params` - Optional marketplace name; omit to refresh all. + /// * `params` - Session IDs to close, deactivate, and delete from disk. /// /// # Returns /// - /// Result of refreshing one or more marketplace catalogs. + /// Map of sessionId -> bytes freed by removing the session's workspace directory. /// ///
/// @@ -3210,68 +5992,25 @@ impl<'a> ClientRpcPluginsMarketplaces<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub async fn refresh_with_params( + pub async fn bulk_delete( &self, - params: PluginsMarketplacesRefreshRequest, - ) -> Result { + params: SessionsBulkDeleteRequest, + ) -> Result { let wire_params = serde_json::to_value(params)?; let _value = self .client - .call(rpc_methods::PLUGINS_MARKETPLACES_REFRESH, Some(wire_params)) + .call(rpc_methods::SESSIONS_BULKDELETE, Some(wire_params)) .await?; Ok(serde_json::from_value(_value)?) } -} - -/// `runtime.*` RPCs. -#[derive(Clone, Copy)] -pub struct ClientRpcRuntime<'a> { - pub(crate) client: &'a Client, -} - -impl<'a> ClientRpcRuntime<'a> { - /// Gracefully shuts down an SDK-owned runtime. The response is sent only after cleanup completes; callers may then terminate the owned runtime process. - /// - /// Wire method: `runtime.shutdown`. - /// - ///
- /// - /// **Experimental.** This API is part of an experimental wire-protocol surface - /// and may change or be removed in future SDK or CLI releases. Pin both the - /// SDK and CLI versions if your code depends on it. - /// - ///
- pub async fn shutdown(&self) -> Result<(), Error> { - let wire_params = serde_json::json!({}); - let _value = self - .client - .call(rpc_methods::RUNTIME_SHUTDOWN, Some(wire_params)) - .await?; - Ok(()) - } -} - -/// `sandbox.*` RPCs. -#[derive(Clone, Copy)] -pub struct ClientRpcSandbox<'a> { - pub(crate) client: &'a Client, -} - -impl<'a> ClientRpcSandbox<'a> { - /// `sandbox.proxyCa.*` sub-namespace. - pub fn proxy_ca(&self) -> ClientRpcSandboxProxyCa<'a> { - ClientRpcSandboxProxyCa { - client: self.client, - } - } - /// Reports whether the host running this runtime can run the command sandbox, without starting a session or spawning a sandboxed command. + /// Deletes one local session from disk after running the same lifecycle hooks as the session manager. /// - /// Wire method: `sandbox.getHostSupport`. + /// Wire method: `sessions.delete`. /// - /// # Returns + /// # Parameters /// - /// Whether the host running this runtime can run the command sandbox. The runtime checks `supported` once per process. A capability answer can change while the process runs, for example after the user installs a missing package. + /// * `params` - Session ID to delete from disk. /// ///
/// @@ -3280,34 +6019,26 @@ impl<'a> ClientRpcSandbox<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub async fn get_host_support(&self) -> Result { - let wire_params = serde_json::json!({}); + pub(crate) async fn delete(&self, params: SessionsDeleteRequest) -> Result<(), Error> { + let wire_params = serde_json::to_value(params)?; let _value = self .client - .call(rpc_methods::SANDBOX_GETHOSTSUPPORT, Some(wire_params)) + .call(rpc_methods::SESSIONS_DELETE, Some(wire_params)) .await?; - Ok(serde_json::from_value(_value)?) + Ok(()) } -} -/// `sandbox.proxyCa.*` RPCs. -#[derive(Clone, Copy)] -pub struct ClientRpcSandboxProxyCa<'a> { - pub(crate) client: &'a Client, -} - -impl<'a> ClientRpcSandboxProxyCa<'a> { - /// Reports whether the persistent certificate authority of the sandbox credential proxy exists, whether OS trust includes it, and whether it must be rotated. Changes nothing. + /// Deletes sessions older than the given threshold, with optional dry-run and exclusion list. /// - /// Wire method: `sandbox.proxyCa.getStatus`. + /// Wire method: `sessions.pruneOld`. /// /// # Parameters /// - /// * `params` - Identifies the credential hosts that the persistent certificate authority of the sandbox credential proxy must cover. The runtime always adds the hosts from the saved user settings. + /// * `params` - Age threshold and optional flags controlling which old sessions are pruned (or simulated when dryRun is true). /// /// # Returns /// - /// Status of the persistent certificate authority of the sandbox credential proxy. + /// Outcome of the prune operation: deleted IDs, dry-run candidates, skipped IDs, total bytes freed, and the dry-run flag. /// ///
/// @@ -3316,29 +6047,29 @@ impl<'a> ClientRpcSandboxProxyCa<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub async fn get_status( + pub async fn prune_old( &self, - params: SandboxProxyCaRequest, - ) -> Result { + params: SessionsPruneOldRequest, + ) -> Result { let wire_params = serde_json::to_value(params)?; let _value = self .client - .call(rpc_methods::SANDBOX_PROXYCA_GETSTATUS, Some(wire_params)) + .call(rpc_methods::SESSIONS_PRUNEOLD, Some(wire_params)) .await?; Ok(serde_json::from_value(_value)?) } - /// Creates the persistent certificate authority of the sandbox credential proxy if none is stored, without changing OS trust, and returns the path of its public certificate. Keeps an existing certificate authority, even one that must be rotated. Fails where OS trust is unsupported. Trust it with sandbox.proxyCa.trust: the CLI trusts only the hosts in the saved user settings, so it refuses a certificate authority that also covers hosts from sandboxConfig. + /// Flushes a session's pending events to disk. /// - /// Wire method: `sandbox.proxyCa.create`. + /// Wire method: `sessions.save`. /// /// # Parameters /// - /// * `params` - Identifies the credential hosts that the persistent certificate authority of the sandbox credential proxy must cover. The runtime always adds the hosts from the saved user settings. + /// * `params` - Session ID whose pending events should be flushed to disk. /// /// # Returns /// - /// Result of creating the persistent certificate authority of the sandbox credential proxy. + /// Flush a session's pending events to disk. No-op when no writer exists for the session (e.g., already closed). /// ///
/// @@ -3347,29 +6078,26 @@ impl<'a> ClientRpcSandboxProxyCa<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub async fn create( - &self, - params: SandboxProxyCaRequest, - ) -> Result { + pub async fn save(&self, params: SessionsSaveRequest) -> Result { let wire_params = serde_json::to_value(params)?; let _value = self .client - .call(rpc_methods::SANDBOX_PROXYCA_CREATE, Some(wire_params)) + .call(rpc_methods::SESSIONS_SAVE, Some(wire_params)) .await?; Ok(serde_json::from_value(_value)?) } - /// Replaces the persistent certificate authority of the sandbox credential proxy with a new one for the current credential hosts. If OS trust included the old one, removes it and trusts the new one, which can show an OS authentication prompt. Running sandboxed tools keep the old certificate authority until they restart. + /// Releases the in-use lock held by this process for a session. /// - /// Wire method: `sandbox.proxyCa.rotate`. + /// Wire method: `sessions.releaseLock`. /// /// # Parameters /// - /// * `params` - Identifies the credential hosts that the persistent certificate authority of the sandbox credential proxy must cover. The runtime always adds the hosts from the saved user settings. + /// * `params` - Session ID whose in-use lock should be released. /// /// # Returns /// - /// Status of the persistent certificate authority of the sandbox credential proxy. + /// 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. /// ///
/// @@ -3378,29 +6106,29 @@ impl<'a> ClientRpcSandboxProxyCa<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub async fn rotate( + pub async fn release_lock( &self, - params: SandboxProxyCaRequest, - ) -> Result { + params: SessionsReleaseLockRequest, + ) -> Result { let wire_params = serde_json::to_value(params)?; let _value = self .client - .call(rpc_methods::SANDBOX_PROXYCA_ROTATE, Some(wire_params)) + .call(rpc_methods::SESSIONS_RELEASELOCK, Some(wire_params)) .await?; Ok(serde_json::from_value(_value)?) } - /// Adds the persistent certificate authority of the sandbox credential proxy to OS trust, so sandboxed clients that read only OS trust accept the proxy. Call create first. Refuses a certificate authority that is not constrained to the current credential hosts. Can show an OS authentication prompt. + /// Backfills missing summary and context fields on the supplied session metadata records. /// - /// Wire method: `sandbox.proxyCa.trust`. + /// Wire method: `sessions.enrichMetadata`. /// /// # Parameters /// - /// * `params` - Identifies the credential hosts that the persistent certificate authority of the sandbox credential proxy must cover. The runtime always adds the hosts from the saved user settings. + /// * `params` - Session metadata records to enrich with summary and context information. /// /// # Returns /// - /// Status of the persistent certificate authority of the sandbox credential proxy. + /// The enriched metadata records, with summary and context fields backfilled where available. Sessions confirmed empty and unnamed are omitted. /// ///
/// @@ -3409,25 +6137,29 @@ impl<'a> ClientRpcSandboxProxyCa<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub async fn trust( + pub async fn enrich_metadata( &self, - params: SandboxProxyCaRequest, - ) -> Result { + params: SessionsEnrichMetadataRequest, + ) -> Result { let wire_params = serde_json::to_value(params)?; let _value = self .client - .call(rpc_methods::SANDBOX_PROXYCA_TRUST, Some(wire_params)) + .call(rpc_methods::SESSIONS_ENRICHMETADATA, Some(wire_params)) .await?; Ok(serde_json::from_value(_value)?) } - /// Removes the persistent certificate authority of the sandbox credential proxy from OS trust. Keeps the stored certificate authority. Can show an OS authentication prompt. Sandboxed clients that read only OS trust then reject the proxy; clients that read the per-process certificate bundle continue to work. + /// Creates the workspace record for a session that has not been opened yet. A host that hands a session off to another application — writing the record and then launching that application against the session ID — needs the record on disk before any session exists to carry it, which the session-scoped workspace methods cannot do. Replaces any existing record and resets the checkpoint index. When writing to the local filesystem, a stored `fork_count` survives on disk. Returns the record it built, so a surviving stored `fork_count` can differ from the answer. /// - /// Wire method: `sandbox.proxyCa.remove`. + /// Wire method: `sessions.createWorkspace`. + /// + /// # Parameters + /// + /// * `params` - Identity, state location and starting context for a workspace record. /// /// # Returns /// - /// Status of the persistent certificate authority of the sandbox credential proxy. + /// The workspace record that was written. /// ///
/// @@ -3436,34 +6168,29 @@ impl<'a> ClientRpcSandboxProxyCa<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub async fn remove(&self) -> Result { - let wire_params = serde_json::json!({}); + pub(crate) async fn create_workspace( + &self, + params: SessionsCreateWorkspaceRequest, + ) -> Result { + let wire_params = serde_json::to_value(params)?; let _value = self .client - .call(rpc_methods::SANDBOX_PROXYCA_REMOVE, Some(wire_params)) + .call(rpc_methods::SESSIONS_CREATEWORKSPACE, Some(wire_params)) .await?; Ok(serde_json::from_value(_value)?) } -} - -/// `secrets.*` RPCs. -#[derive(Clone, Copy)] -pub struct ClientRpcSecrets<'a> { - pub(crate) client: &'a Client, -} -impl<'a> ClientRpcSecrets<'a> { - /// Registers secret values for redaction in session logs and exports. The SDK calls this to inject dynamically generated secret values (e.g., OIDC tokens). + /// Reads a session's workspace record straight from disk, without opening the session. Resuming by session ID has to know where the session lives before it can connect, so the lookup cannot come from the session-scoped workspace methods, which resolve their location from a live session's context. Returns no record when the file is absent. /// - /// Wire method: `secrets.addFilterValues`. + /// Wire method: `sessions.loadWorkspace`. /// /// # Parameters /// - /// * `params` - Secret values to add to the redaction filter. + /// * `params` - Where the session's state lives, as a root directory and the session ID under it. /// /// # Returns /// - /// Confirmation that the secret values were registered. + /// The workspace record on disk, omitted when the session has none. /// ///
/// @@ -3472,37 +6199,29 @@ impl<'a> ClientRpcSecrets<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub async fn add_filter_values( + pub(crate) async fn load_workspace( &self, - params: SecretsAddFilterValuesRequest, - ) -> Result { + params: SessionsLoadWorkspaceRequest, + ) -> Result { let wire_params = serde_json::to_value(params)?; let _value = self .client - .call(rpc_methods::SECRETS_ADDFILTERVALUES, Some(wire_params)) + .call(rpc_methods::SESSIONS_LOADWORKSPACE, Some(wire_params)) .await?; Ok(serde_json::from_value(_value)?) } -} - -/// `sessionFs.*` RPCs. -#[derive(Clone, Copy)] -pub struct ClientRpcSessionFs<'a> { - pub(crate) client: &'a Client, -} -impl<'a> ClientRpcSessionFs<'a> { - /// Registers an SDK client as the session filesystem provider. + /// Merges fields into a session's workspace record on disk, creating the record when it is absent. The counterpart to `sessions.loadWorkspace`, for the same before-the-session-exists case. It preserves stored workspace-schema fields the request does not supply, does not preserve stored keys outside the workspace schema, and never replaces a stored `fork_count`. /// - /// Wire method: `sessionFs.setProvider`. + /// Wire method: `sessions.updateWorkspaceFields`. /// /// # Parameters /// - /// * `params` - Initial working directory, session-state path layout, and path conventions used to register the calling SDK client as the session filesystem provider. A registered provider is authoritative for path interpretation and filesystem facts used by workspace permission validation. Paths are interpreted lexically; home-relative paths (`~` and `~/...`) and Windows drive-relative paths such as `C:foo` are unsupported. Until provider-side canonicalization is supported, providers must not expose symlinks inside allowed roots that escape those roots. + /// * `params` - Where the session's state lives, plus workspace-schema fields to merge into its workspace record. Stored keys outside the schema are not preserved, and a stored `fork_count` is never replaced. /// /// # Returns /// - /// Indicates whether the calling client was registered as the session filesystem provider. + /// The merge completed. The record carries the supplied workspace-schema fields, but a stored `fork_count` stays. /// ///
/// @@ -3511,33 +6230,32 @@ impl<'a> ClientRpcSessionFs<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub async fn set_provider( + pub(crate) async fn update_workspace_fields( &self, - params: SessionFsSetProviderRequest, - ) -> Result { + params: SessionsUpdateWorkspaceFieldsRequest, + ) -> Result { let wire_params = serde_json::to_value(params)?; let _value = self .client - .call(rpc_methods::SESSIONFS_SETPROVIDER, Some(wire_params)) + .call( + rpc_methods::SESSIONS_UPDATEWORKSPACEFIELDS, + Some(wire_params), + ) .await?; Ok(serde_json::from_value(_value)?) } -} - -/// `sessions.*` RPCs. -#[derive(Clone, Copy)] -pub struct ClientRpcSessions<'a> { - pub(crate) client: &'a Client, -} -impl<'a> ClientRpcSessions<'a> { - /// Creates or resumes a local session and returns the opened session ID. + /// Reloads user, plugin, and (optionally) repo hooks on the active session. /// - /// Wire method: `sessions.open`. + /// Wire method: `sessions.reloadPluginHooks`. + /// + /// # Parameters + /// + /// * `params` - Active session ID and an optional flag for deferring repo-level hooks until folder trust. /// /// # Returns /// - /// Result of opening a session. + /// Reload all hooks (user, plugin, optionally repo) and apply them to the active session. Call after installing or removing plugins so their hooks take effect immediately. No-op when no active session matches the given sessionId. /// ///
/// @@ -3546,26 +6264,29 @@ impl<'a> ClientRpcSessions<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub async fn open(&self) -> Result { - let wire_params = serde_json::json!({}); + pub async fn reload_plugin_hooks( + &self, + params: SessionsReloadPluginHooksRequest, + ) -> Result { + let wire_params = serde_json::to_value(params)?; let _value = self .client - .call(rpc_methods::SESSIONS_OPEN, Some(wire_params)) + .call(rpc_methods::SESSIONS_RELOADPLUGINHOOKS, Some(wire_params)) .await?; Ok(serde_json::from_value(_value)?) } - /// Creates a new session by forking persisted history from an existing session. + /// Loads previously-deferred repo-level hooks on the active session, returning queued startup prompts. /// - /// Wire method: `sessions.fork`. + /// Wire method: `sessions.loadDeferredRepoHooks`. /// /// # Parameters /// - /// * `params` - Source session identifier to fork from, optional event-ID boundary, and optional friendly name for the new session. + /// * `params` - Active session ID whose deferred repo-level hooks should be loaded. /// /// # Returns /// - /// Identifier and optional friendly name assigned to the newly forked session. + /// Queued repo-level startup prompts and the total hook command count after loading. /// ///
/// @@ -3574,26 +6295,32 @@ impl<'a> ClientRpcSessions<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub async fn fork(&self, params: SessionsForkRequest) -> Result { + pub async fn load_deferred_repo_hooks( + &self, + params: SessionsLoadDeferredRepoHooksRequest, + ) -> Result { let wire_params = serde_json::to_value(params)?; let _value = self .client - .call(rpc_methods::SESSIONS_FORK, Some(wire_params)) + .call( + rpc_methods::SESSIONS_LOADDEFERREDREPOHOOKS, + Some(wire_params), + ) .await?; Ok(serde_json::from_value(_value)?) } - /// Connects to an existing remote session and exposes it as an SDK session. + /// Replaces the manager-wide additional plugins registered with the session manager. /// - /// Wire method: `sessions.connect`. + /// Wire method: `sessions.setAdditionalPlugins`. /// /// # Parameters /// - /// * `params` - Remote session connection parameters. + /// * `params` - Manager-wide additional plugins to register; replaces any previously-configured set. /// /// # Returns /// - /// Remote session connection result. + /// 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. /// ///
/// @@ -3602,25 +6329,32 @@ impl<'a> ClientRpcSessions<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub async fn connect( + pub async fn set_additional_plugins( &self, - params: ConnectRemoteSessionParams, - ) -> Result { + params: SessionsSetAdditionalPluginsRequest, + ) -> Result { let wire_params = serde_json::to_value(params)?; let _value = self .client - .call(rpc_methods::SESSIONS_CONNECT, Some(wire_params)) + .call( + rpc_methods::SESSIONS_SETADDITIONALPLUGINS, + Some(wire_params), + ) .await?; Ok(serde_json::from_value(_value)?) } - /// Lists sessions, optionally filtered by source and working-directory context. Returned entries are discriminated by `isRemote`: local entries carry only the lightweight `LocalSessionMetadataValue` shape; remote entries carry the full `RemoteSessionMetadataValue` shape (repository, PR number, taskType, etc.). + /// Gets the dynamic-context board entry count associated with a session, when available. Internal: this exists solely so CLI telemetry events (`rem_spawn_gate`, `rem_consolidation_complete`) can pair START / END board counts around the detached rem-agent spawn. "Dynamic context board" is a runtime-internal concept that is not part of the public SDK contract; the long-term plan is to relocate the telemetry emission into the runtime so this method can be deleted entirely. /// - /// Wire method: `sessions.list`. + /// Wire method: `sessions.getBoardEntryCount`. + /// + /// # Parameters + /// + /// * `params` - Session ID whose board entry count should be returned. /// /// # Returns /// - /// Sessions matching the filter, ordered most-recently-modified first. + /// Dynamic-context board entry count, when available. /// ///
/// @@ -3629,26 +6363,29 @@ impl<'a> ClientRpcSessions<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub async fn list(&self) -> Result { - let wire_params = serde_json::json!({}); + pub(crate) async fn get_board_entry_count( + &self, + params: SessionsGetBoardEntryCountRequest, + ) -> Result { + let wire_params = serde_json::to_value(params)?; let _value = self .client - .call(rpc_methods::SESSIONS_LIST, Some(wire_params)) + .call(rpc_methods::SESSIONS_GETBOARDENTRYCOUNT, Some(wire_params)) .await?; Ok(serde_json::from_value(_value)?) } - /// Lists sessions, optionally filtered by source and working-directory context. Returned entries are discriminated by `isRemote`: local entries carry only the lightweight `LocalSessionMetadataValue` shape; remote entries carry the full `RemoteSessionMetadataValue` shape (repository, PR number, taskType, etc.). + /// Attaches the runtime-managed remote-control singleton to a session, awaiting initial setup. If remote control is already attached to a different session, the singleton is transferred (preserving the underlying Mission Control connection). Returns the final status. /// - /// Wire method: `sessions.list`. + /// Wire method: `sessions.startRemoteControl`. /// /// # Parameters /// - /// * `params` - Optional source filter, metadata-load limit, and context filter applied to the returned sessions. + /// * `params` - Parameters for attaching the remote-control singleton to a session. /// /// # Returns /// - /// Sessions matching the filter, ordered most-recently-modified first. + /// Wrapper for the singleton's current status. /// ///
/// @@ -3657,29 +6394,29 @@ impl<'a> ClientRpcSessions<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub async fn list_with_params( + pub async fn start_remote_control( &self, - params: SessionsListRequest, - ) -> Result { + params: SessionsStartRemoteControlRequest, + ) -> Result { let wire_params = serde_json::to_value(params)?; let _value = self .client - .call(rpc_methods::SESSIONS_LIST, Some(wire_params)) + .call(rpc_methods::SESSIONS_STARTREMOTECONTROL, Some(wire_params)) .await?; Ok(serde_json::from_value(_value)?) } - /// Reads lightweight persisted metadata for one local session without opening it. + /// Atomically rebinds the remote-control singleton to a different session, preserving the underlying Mission Control connection. When `expectedFromSessionId` is provided and does not match the singleton's current `attachedSessionId`, the transfer is rejected with `transferred: false` and the current status is returned unchanged. /// - /// Wire method: `sessions.getMetadata`. + /// Wire method: `sessions.transferRemoteControl`. /// /// # Parameters /// - /// * `params` - Session ID whose persisted metadata should be read. + /// * `params` - Parameters for atomically rebinding the remote-control singleton. /// /// # Returns /// - /// Persisted local session metadata when the session exists. + /// Outcome of a transferRemoteControl call. /// ///
/// @@ -3688,29 +6425,32 @@ impl<'a> ClientRpcSessions<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub(crate) async fn get_metadata( + pub async fn transfer_remote_control( &self, - params: SessionsGetMetadataRequest, - ) -> Result { + params: SessionsTransferRemoteControlRequest, + ) -> Result { let wire_params = serde_json::to_value(params)?; let _value = self .client - .call(rpc_methods::SESSIONS_GETMETADATA, Some(wire_params)) + .call( + rpc_methods::SESSIONS_TRANSFERREMOTECONTROL, + Some(wire_params), + ) .await?; Ok(serde_json::from_value(_value)?) } - /// Reads client-owned metadata for multiple persisted local sessions without opening them. Results preserve request order and report missing, corrupt, unsupported, or temporarily unavailable sessions independently. + /// Patches the steering state of the active remote-control singleton. When remote control is off, this is a no-op and the off status is returned. Today only `enabled: true` is actionable on the underlying exporter; passing `false` is reserved for future use. /// - /// Wire method: `sessions.getClientMetadata`. + /// Wire method: `sessions.setRemoteControlSteering`. /// /// # Parameters /// - /// * `params` - Bounded batch request for client-owned metadata from persisted local sessions. + /// * `params` - Patch for the singleton's steering state. /// /// # Returns /// - /// Ordered client metadata outcomes for the requested local sessions. + /// Wrapper for the singleton's current status. /// ///
/// @@ -3719,29 +6459,28 @@ impl<'a> ClientRpcSessions<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub async fn get_client_metadata( + pub async fn set_remote_control_steering( &self, - params: SessionsGetClientMetadataRequest, - ) -> Result { + params: SessionsSetRemoteControlSteeringRequest, + ) -> Result { let wire_params = serde_json::to_value(params)?; let _value = self .client - .call(rpc_methods::SESSIONS_GETCLIENTMETADATA, Some(wire_params)) + .call( + rpc_methods::SESSIONS_SETREMOTECONTROLSTEERING, + Some(wire_params), + ) .await?; 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 first read pins the currently opened journal generation and its byte-length boundary; opaque cursor continuations remain on that generation across runtime-owned compaction, truncation, and rewrite operations, which replace the live path atomically, and events appended after the boundary are excluded. For cold hydration, await the first successful page before activation and establish lossless live-event buffering before resume; merge subsequent live events by ID, preserving persisted order and letting live payloads win. Continuations are process-local, single-use capabilities bound to the originating session and storage context and must be paged sequentially; concurrent or repeated use of the same cursor expires that duplicate read rather than reading the generation twice. A complete snapshot has cursorStatus 'ok' and hasMore false. Snapshots expire after five idle minutes, with at most eight retained per process and idle-only eviction under pressure; completion and cancelled-worker exit release their handles. No transcript copy is created, but retained handles may keep replaced files' disk blocks alive until release. Pages have a soft 1 MiB serialized event-array budget including resolved binary assets; one oversized event is returned alone to guarantee progress. Working memory also includes a record/lookahead and asset resolution; resolving the first binary reference may scan the full pinned generation to build a bounded offset index. If the snapshot expires, is evicted, is cancelled before a continuation is established, or becomes unreadable after an observable unsupported in-place shortening, the continuation returns cursorStatus 'expired' with an empty terminal page and never falls back to a different generation. A missing or initially unreadable journal is an RPC error. Persisted history excludes ephemeral events and may omit payloads that are reconstructed only for an active session; use the active session event stream for post-resume live events. - /// - /// Wire method: `sessions.readPersistedEvents`. - /// - /// # Parameters + /// Stops the remote-control singleton. When `expectedSessionId` is provided and does not match the singleton's current `attachedSessionId`, the stop is rejected with `stopped: false` and the current status is returned unchanged (unless `force` is set, in which case the singleton is unconditionally torn down). /// - /// * `params` - Pagination options for reading an inactive or active local session's persisted event journal. + /// Wire method: `sessions.stopRemoteControl`. /// /// # Returns /// - /// Batch of session events returned by a read, with cursor and continuation metadata. + /// Outcome of a stopRemoteControl call. /// ///
/// @@ -3750,29 +6489,26 @@ impl<'a> ClientRpcSessions<'a> { /// 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)?; + pub async fn stop_remote_control(&self) -> Result { + let wire_params = serde_json::json!({}); let _value = self .client - .call(rpc_methods::SESSIONS_READPERSISTEDEVENTS, Some(wire_params)) + .call(rpc_methods::SESSIONS_STOPREMOTECONTROL, Some(wire_params)) .await?; Ok(serde_json::from_value(_value)?) } - /// Lists recent local session IDs that contain user-visible history, omitting housekeeping-only sessions. + /// Stops the remote-control singleton. When `expectedSessionId` is provided and does not match the singleton's current `attachedSessionId`, the stop is rejected with `stopped: false` and the current status is returned unchanged (unless `force` is set, in which case the singleton is unconditionally torn down). /// - /// Wire method: `sessions.listNonEmptySessionIds`. + /// Wire method: `sessions.stopRemoteControl`. /// /// # Parameters /// - /// * `params` - Limit for non-empty local session IDs. + /// * `params` - Parameters for stopping the remote-control singleton. /// /// # Returns /// - /// Recent local session IDs that contain user-visible history. + /// Outcome of a stopRemoteControl call. /// ///
/// @@ -3781,32 +6517,25 @@ impl<'a> ClientRpcSessions<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub(crate) async fn list_non_empty_session_ids( + pub async fn stop_remote_control_with_params( &self, - params: SessionsListNonEmptySessionIdsRequest, - ) -> Result { + params: SessionsStopRemoteControlRequest, + ) -> Result { let wire_params = serde_json::to_value(params)?; let _value = self .client - .call( - rpc_methods::SESSIONS_LISTNONEMPTYSESSIONIDS, - Some(wire_params), - ) + .call(rpc_methods::SESSIONS_STOPREMOTECONTROL, Some(wire_params)) .await?; Ok(serde_json::from_value(_value)?) } - /// Finds the local session bound to a GitHub task ID, if any. - /// - /// Wire method: `sessions.findByTaskId`. - /// - /// # Parameters + /// Returns the current state of the remote-control singleton, including the attached session id and frontend URL when active. /// - /// * `params` - GitHub task ID to look up. + /// Wire method: `sessions.getRemoteControlStatus`. /// /// # Returns /// - /// ID of the local session bound to the given GitHub task, or omitted when none. + /// Wrapper for the singleton's current status. /// ///
/// @@ -3815,29 +6544,25 @@ impl<'a> ClientRpcSessions<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub async fn find_by_task_id( - &self, - params: SessionsFindByTaskIDRequest, - ) -> Result { - let wire_params = serde_json::to_value(params)?; + pub async fn get_remote_control_status(&self) -> Result { + let wire_params = serde_json::json!({}); let _value = self .client - .call(rpc_methods::SESSIONS_FINDBYTASKID, Some(wire_params)) + .call( + rpc_methods::SESSIONS_GETREMOTECONTROLSTATUS, + Some(wire_params), + ) .await?; Ok(serde_json::from_value(_value)?) } - /// Resolves a UUID prefix to a unique session ID, if exactly one session matches. + /// Attaches (or detaches) an in-process ExtensionController delegate for the given session in a local host adapter. Pass `controller: undefined` to detach. Internal because the controller cannot cross the JSON-RPC boundary; the runtime manages its own session extension service. /// - /// Wire method: `sessions.findByPrefix`. + /// Wire method: `sessions.configureSessionExtensions`. /// /// # Parameters /// - /// * `params` - UUID prefix to resolve to a unique session ID. - /// - /// # Returns - /// - /// Session ID matching the prefix, omitted when no unique match exists. + /// * `params` - Params to attach or detach an in-process ExtensionController delegate. /// ///
/// @@ -3846,29 +6571,54 @@ impl<'a> ClientRpcSessions<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub async fn find_by_prefix( + pub(crate) async fn configure_session_extensions( &self, - params: SessionsFindByPrefixRequest, - ) -> Result { + params: ConfigureSessionExtensionsParams, + ) -> Result<(), Error> { let wire_params = serde_json::to_value(params)?; let _value = self .client - .call(rpc_methods::SESSIONS_FINDBYPREFIX, Some(wire_params)) + .call( + rpc_methods::SESSIONS_CONFIGURESESSIONEXTENSIONS, + Some(wire_params), + ) .await?; - Ok(serde_json::from_value(_value)?) + Ok(()) + } +} + +/// `skills.*` RPCs. +#[derive(Clone, Copy)] +pub struct ClientRpcSkills<'a> { + pub(crate) client: &'a Client, +} + +impl<'a> ClientRpcSkills<'a> { + /// `skills.config.*` sub-namespace. + pub fn config(&self) -> ClientRpcSkillsConfig<'a> { + ClientRpcSkillsConfig { + client: self.client, + } + } + + /// `skills.installations.*` sub-namespace. + pub fn installations(&self) -> ClientRpcSkillsInstallations<'a> { + ClientRpcSkillsInstallations { + client: self.client, + } } - /// Returns the most-relevant prior session for a given working-directory context. + /// Plans installation of a verified Agent Finder Skill candidate without writing files. The returned review is safe to present to a user and installing always leaves the Skill disabled until separately enabled. /// - /// Wire method: `sessions.getLastForContext`. + /// Wire method: `skills.planInstall`. /// /// # Parameters /// - /// * `params` - Optional working-directory context used to score session relevance. + /// * `params` - Side-effect-free planning of one verified Agent Finder Skill candidate. /// /// # Returns /// - /// Most-relevant session ID for the supplied context, or omitted when no sessions exist. + /// Skill installation management result with the honoured contract, or a typed refusal. /// ///
/// @@ -3877,29 +6627,29 @@ impl<'a> ClientRpcSessions<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub async fn get_last_for_context( + pub async fn plan_install( &self, - params: SessionsGetLastForContextRequest, - ) -> Result { + params: SkillPlanInstallRequest, + ) -> Result { let wire_params = serde_json::to_value(params)?; let _value = self .client - .call(rpc_methods::SESSIONS_GETLASTFORCONTEXT, Some(wire_params)) + .call(rpc_methods::SKILLS_PLANINSTALL, Some(wire_params)) .await?; Ok(serde_json::from_value(_value)?) } - /// Computes the absolute path to a session's persisted events.jsonl file. Internal: filesystem paths are only meaningful in-process (CLI and runtime share a filesystem). Currently used by the CLI's contribution-graph feature to read historical events directly. Remote SDK consumers must not depend on this; a proper event-query API would replace it if the contribution graph ever needed to work over the wire. + /// Plans installation of the Skill pinned by an Agent Finder materialisation reference without writing files. The runtime retrieves the descriptor only from its fixed Agent Finder authority and applies the same verification as skills.planInstall; the review shows only verified data. Apply the returned plan with skills.applyInstall. /// - /// Wire method: `sessions.getEventFilePath`. + /// Wire method: `skills.planInstallFromReference`. /// /// # Parameters /// - /// * `params` - Session ID whose event-log file path to compute. + /// * `params` - Side-effect-free planning of one Skill from a materialisation reference rather than a search candidate. /// /// # Returns /// - /// Absolute path to the session's events.jsonl file on disk. + /// Skill installation management result with the honoured contract, or a typed refusal. /// ///
/// @@ -3908,25 +6658,32 @@ impl<'a> ClientRpcSessions<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub(crate) async fn get_event_file_path( + pub async fn plan_install_from_reference( &self, - params: SessionsGetEventFilePathRequest, - ) -> Result { + params: SkillPlanInstallFromReferenceRequest, + ) -> Result { let wire_params = serde_json::to_value(params)?; let _value = self .client - .call(rpc_methods::SESSIONS_GETEVENTFILEPATH, Some(wire_params)) + .call( + rpc_methods::SKILLS_PLANINSTALLFROMREFERENCE, + Some(wire_params), + ) .await?; Ok(serde_json::from_value(_value)?) } - /// Returns the on-disk byte size of each session's workspace directory. + /// Consumes one verified Skill installation plan, requests explicit human consent through installations.confirm on the original connection, then revalidates and installs the Skill disabled. /// - /// Wire method: `sessions.getSizes`. + /// Wire method: `skills.applyInstall`. + /// + /// # Parameters + /// + /// * `params` - Applies exactly one retained verified Skill installation plan. /// /// # Returns /// - /// Map of sessionId -> on-disk size in bytes for each session's workspace directory. + /// Skill installation result with the honoured contract, or a typed request/negotiation refusal. /// ///
/// @@ -3935,26 +6692,29 @@ impl<'a> ClientRpcSessions<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub async fn get_sizes(&self) -> Result { - let wire_params = serde_json::json!({}); + pub async fn apply_install( + &self, + params: SkillApplyInstallRequest, + ) -> Result { + let wire_params = serde_json::to_value(params)?; let _value = self .client - .call(rpc_methods::SESSIONS_GETSIZES, Some(wire_params)) + .call(rpc_methods::SKILLS_APPLYINSTALL, Some(wire_params)) .await?; Ok(serde_json::from_value(_value)?) } - /// Returns the subset of the supplied session IDs that are currently held by another running process. + /// Prepares a read-only removal plan for an owned verified Agent Finder Skill installation. Uninstall planning is never gated by the Skill-install feature flag. /// - /// Wire method: `sessions.checkInUse`. + /// Wire method: `skills.planUninstall`. /// /// # Parameters /// - /// * `params` - Session IDs to test for live in-use locks. + /// * `params` - Read-only preparation of one owned Skill removal under fresh selected-session authority. /// /// # Returns /// - /// Session IDs from the input set that are currently in use by another process. + /// Skill installation management result with the honoured contract, or a typed refusal. /// ///
/// @@ -3963,29 +6723,29 @@ impl<'a> ClientRpcSessions<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub async fn check_in_use( + pub async fn plan_uninstall( &self, - params: SessionsCheckInUseRequest, - ) -> Result { + params: SkillPlanUninstallRequest, + ) -> Result { let wire_params = serde_json::to_value(params)?; let _value = self .client - .call(rpc_methods::SESSIONS_CHECKINUSE, Some(wire_params)) + .call(rpc_methods::SKILLS_PLANUNINSTALL, Some(wire_params)) .await?; Ok(serde_json::from_value(_value)?) } - /// Returns a session's persisted remote-steerable flag, if any has been recorded. Internal: this is CLI-specific book-keeping used by `--continue` / `--resume` to inherit the prior session's remote-steerable preference. SDK consumers that want similar behavior should manage their own persistence around start/stop calls rather than relying on this runtime-side flag. + /// Consumes an owned Skill removal plan, requests explicit human consent through installations.confirm, refuses drift, and removes the exact owned files through quarantine. /// - /// Wire method: `sessions.getPersistedRemoteSteerable`. + /// Wire method: `skills.applyUninstall`. /// /// # Parameters /// - /// * `params` - Session ID to look up the persisted remote-steerable flag for. + /// * `params` - One-use application of the exact retained Skill removal plan. /// /// # Returns /// - /// The session's persisted remote-steerable flag, or omitted when no value has been persisted. + /// Skill installation result with the honoured contract, or a typed request/negotiation refusal. /// ///
/// @@ -3994,32 +6754,29 @@ impl<'a> ClientRpcSessions<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub(crate) async fn get_persisted_remote_steerable( + pub async fn apply_uninstall( &self, - params: SessionsGetPersistedRemoteSteerableRequest, - ) -> Result { + params: SkillApplyUninstallRequest, + ) -> Result { let wire_params = serde_json::to_value(params)?; let _value = self .client - .call( - rpc_methods::SESSIONS_GETPERSISTEDREMOTESTEERABLE, - Some(wire_params), - ) + .call(rpc_methods::SKILLS_APPLYUNINSTALL, Some(wire_params)) .await?; Ok(serde_json::from_value(_value)?) } - /// Closes a session: emits shutdown, flushes pending events, releases the in-use lock, and disposes the active session. + /// Discovers skills across global and project sources. /// - /// Wire method: `sessions.close`. + /// Wire method: `skills.discover`. /// /// # Parameters /// - /// * `params` - Session ID to close. + /// * `params` - Optional project paths and additional skill directories to include in discovery. /// /// # Returns /// - /// 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. + /// Skills discovered across global and project sources. /// ///
/// @@ -4028,26 +6785,26 @@ impl<'a> ClientRpcSessions<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub async fn close(&self, params: SessionsCloseRequest) -> Result { + pub async fn discover(&self, params: SkillsDiscoverRequest) -> Result { let wire_params = serde_json::to_value(params)?; let _value = self .client - .call(rpc_methods::SESSIONS_CLOSE, Some(wire_params)) + .call(rpc_methods::SKILLS_DISCOVER, Some(wire_params)) .await?; Ok(serde_json::from_value(_value)?) } - /// Closes, deactivates, and deletes a set of sessions, returning the bytes freed per session. + /// Returns the canonical directories where a client may create skills that the runtime will recognize, including ones that do not exist yet. Project directories become active once created. /// - /// Wire method: `sessions.bulkDelete`. + /// Wire method: `skills.getDiscoveryPaths`. /// /// # Parameters /// - /// * `params` - Session IDs to close, deactivate, and delete from disk. + /// * `params` - Optional project paths to enumerate. /// /// # Returns /// - /// Map of sessionId -> bytes freed by removing the session's workspace directory. + /// Canonical locations where skills can be created so the runtime will recognize them. /// ///
/// @@ -4056,25 +6813,29 @@ impl<'a> ClientRpcSessions<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub async fn bulk_delete( + pub async fn get_discovery_paths( &self, - params: SessionsBulkDeleteRequest, - ) -> Result { + params: SkillsGetDiscoveryPathsRequest, + ) -> Result { let wire_params = serde_json::to_value(params)?; let _value = self .client - .call(rpc_methods::SESSIONS_BULKDELETE, Some(wire_params)) + .call(rpc_methods::SKILLS_GETDISCOVERYPATHS, Some(wire_params)) .await?; Ok(serde_json::from_value(_value)?) } - /// Deletes one local session from disk after running the same lifecycle hooks as the session manager. + /// Returns the canonical directories where a client may create skills that the runtime will recognize, including ones that do not exist yet. Project directories become active once created. /// - /// Wire method: `sessions.delete`. + /// Wire method: `skills.getDiscoveryPaths`. /// /// # Parameters /// - /// * `params` - Session ID to delete from disk. + /// * `params` - Optional project paths to enumerate. + /// + /// # Returns + /// + /// Canonical locations where skills can be created so the runtime will recognize them. /// ///
/// @@ -4083,26 +6844,35 @@ impl<'a> ClientRpcSessions<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub(crate) async fn delete(&self, params: SessionsDeleteRequest) -> Result<(), Error> { + /// + /// Accepts [`SkillsGetDiscoveryPathsOptions`], including inputs added after [`SkillsGetDiscoveryPathsRequest`]. + pub async fn get_discovery_paths_with_options( + &self, + params: SkillsGetDiscoveryPathsOptions, + ) -> Result { let wire_params = serde_json::to_value(params)?; let _value = self .client - .call(rpc_methods::SESSIONS_DELETE, Some(wire_params)) + .call(rpc_methods::SKILLS_GETDISCOVERYPATHS, Some(wire_params)) .await?; - Ok(()) + Ok(serde_json::from_value(_value)?) } +} - /// Deletes sessions older than the given threshold, with optional dry-run and exclusion list. +/// `skills.config.*` RPCs. +#[derive(Clone, Copy)] +pub struct ClientRpcSkillsConfig<'a> { + pub(crate) client: &'a Client, +} + +impl<'a> ClientRpcSkillsConfig<'a> { + /// Replaces the global list of disabled skills. /// - /// Wire method: `sessions.pruneOld`. + /// Wire method: `skills.config.setDisabledSkills`. /// /// # Parameters /// - /// * `params` - Age threshold and optional flags controlling which old sessions are pruned (or simulated when dryRun is true). - /// - /// # Returns - /// - /// Outcome of the prune operation: deleted IDs, dry-run candidates, skipped IDs, total bytes freed, and the dry-run flag. + /// * `params` - Skill names to mark as disabled in global configuration, replacing any previous list. /// ///
/// @@ -4111,29 +6881,28 @@ impl<'a> ClientRpcSessions<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub async fn prune_old( + pub async fn set_disabled_skills( &self, - params: SessionsPruneOldRequest, - ) -> Result { + params: SkillsConfigSetDisabledSkillsRequest, + ) -> Result<(), Error> { let wire_params = serde_json::to_value(params)?; let _value = self .client - .call(rpc_methods::SESSIONS_PRUNEOLD, Some(wire_params)) + .call( + rpc_methods::SKILLS_CONFIG_SETDISABLEDSKILLS, + Some(wire_params), + ) .await?; - Ok(serde_json::from_value(_value)?) + Ok(()) } - /// Flushes a session's pending events to disk. + /// Atomically adds or removes one skill from the disabled list. /// - /// Wire method: `sessions.save`. + /// Wire method: `skills.config.setSkillDisabled`. /// /// # Parameters /// - /// * `params` - Session ID whose pending events should be flushed to disk. - /// - /// # Returns - /// - /// Flush a session's pending events to disk. No-op when no writer exists for the session (e.g., already closed). + /// * `params` - Adds or removes a single skill from the global disabled list, leaving every other entry untouched. /// ///
/// @@ -4142,26 +6911,40 @@ impl<'a> ClientRpcSessions<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub async fn save(&self, params: SessionsSaveRequest) -> Result { + 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::SESSIONS_SAVE, Some(wire_params)) + .call( + rpc_methods::SKILLS_CONFIG_SETSKILLDISABLED, + Some(wire_params), + ) .await?; - Ok(serde_json::from_value(_value)?) + Ok(()) } +} - /// Releases the in-use lock held by this process for a session. +/// `skills.installations.*` RPCs. +#[derive(Clone, Copy)] +pub struct ClientRpcSkillsInstallations<'a> { + pub(crate) client: &'a Client, +} + +impl<'a> ClientRpcSkillsInstallations<'a> { + /// Lists owned verified Agent Finder Skill installations for the selected existing session. Listing is never gated by the Skill-install feature flag. /// - /// Wire method: `sessions.releaseLock`. + /// Wire method: `skills.installations.list`. /// /// # Parameters /// - /// * `params` - Session ID whose in-use lock should be released. + /// * `params` - Inventory request under an explicitly selected existing session. /// /// # Returns /// - /// 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. + /// Skill installation management result with the honoured contract, or a typed refusal. /// ///
/// @@ -4170,29 +6953,29 @@ impl<'a> ClientRpcSessions<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub async fn release_lock( + pub async fn list( &self, - params: SessionsReleaseLockRequest, - ) -> Result { + params: SkillInstallationsRequest, + ) -> Result { let wire_params = serde_json::to_value(params)?; let _value = self .client - .call(rpc_methods::SESSIONS_RELEASELOCK, Some(wire_params)) + .call(rpc_methods::SKILLS_INSTALLATIONS_LIST, Some(wire_params)) .await?; Ok(serde_json::from_value(_value)?) } - /// Backfills missing summary and context fields on the supplied session metadata records. + /// Reconciles interrupted owned Skill installation work for the selected existing session, then inspects owned inventory. Recovery is never gated by the Skill-install feature flag. /// - /// Wire method: `sessions.enrichMetadata`. + /// Wire method: `skills.installations.recover`. /// /// # Parameters /// - /// * `params` - Session metadata records to enrich with summary and context information. + /// * `params` - Inventory request under an explicitly selected existing session. /// /// # Returns /// - /// The enriched metadata records, with summary and context fields backfilled where available. Sessions confirmed empty and unnamed are omitted. + /// Skill installation management result with the honoured contract, or a typed refusal. /// ///
/// @@ -4201,29 +6984,29 @@ impl<'a> ClientRpcSessions<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub async fn enrich_metadata( + pub async fn recover( &self, - params: SessionsEnrichMetadataRequest, - ) -> Result { + params: SkillInstallationsRequest, + ) -> Result { let wire_params = serde_json::to_value(params)?; let _value = self .client - .call(rpc_methods::SESSIONS_ENRICHMETADATA, Some(wire_params)) + .call(rpc_methods::SKILLS_INSTALLATIONS_RECOVER, Some(wire_params)) .await?; Ok(serde_json::from_value(_value)?) } - /// Creates the workspace record for a session that has not been opened yet. A host that hands a session off to another application — writing the record and then launching that application against the session ID — needs the record on disk before any session exists to carry it, which the session-scoped workspace methods cannot do. Replaces any existing record and resets the checkpoint index. When writing to the local filesystem, a stored `fork_count` survives on disk. Returns the record it built, so a surviving stored `fork_count` can differ from the answer. + /// Inspects a known Skill installation operation on its original runtime connection. Status is never gated by the Skill-install feature flag. /// - /// Wire method: `sessions.createWorkspace`. + /// Wire method: `skills.installations.status`. /// /// # Parameters /// - /// * `params` - Identity, state location and starting context for a workspace record. + /// * `params` - Existing-operation control. A new session selector is deliberately not accepted. /// /// # Returns /// - /// The workspace record that was written. + /// Skill installation management result with the honoured contract, or a typed refusal. /// ///
/// @@ -4232,29 +7015,29 @@ impl<'a> ClientRpcSessions<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub(crate) async fn create_workspace( + pub async fn status( &self, - params: SessionsCreateWorkspaceRequest, - ) -> Result { + params: SkillInstallationOperationRequest, + ) -> Result { let wire_params = serde_json::to_value(params)?; let _value = self .client - .call(rpc_methods::SESSIONS_CREATEWORKSPACE, Some(wire_params)) + .call(rpc_methods::SKILLS_INSTALLATIONS_STATUS, Some(wire_params)) .await?; Ok(serde_json::from_value(_value)?) } - /// Reads a session's workspace record straight from disk, without opening the session. Resuming by session ID has to know where the session lives before it can connect, so the lookup cannot come from the session-scoped workspace methods, which resolve their location from a live session's context. Returns no record when the file is absent. + /// Requests cancellation of a known Skill installation operation before commit. Already-started durable work requires recovery instead of silent replay. /// - /// Wire method: `sessions.loadWorkspace`. + /// Wire method: `skills.installations.cancel`. /// /// # Parameters /// - /// * `params` - Where the session's state lives, as a root directory and the session ID under it. + /// * `params` - Existing-operation control. A new session selector is deliberately not accepted. /// /// # Returns /// - /// The workspace record on disk, omitted when the session has none. + /// Skill installation management result with the honoured contract, or a typed refusal. /// ///
/// @@ -4263,29 +7046,29 @@ impl<'a> ClientRpcSessions<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub(crate) async fn load_workspace( + pub async fn cancel( &self, - params: SessionsLoadWorkspaceRequest, - ) -> Result { + params: SkillInstallationOperationRequest, + ) -> Result { let wire_params = serde_json::to_value(params)?; let _value = self .client - .call(rpc_methods::SESSIONS_LOADWORKSPACE, Some(wire_params)) + .call(rpc_methods::SKILLS_INSTALLATIONS_CANCEL, Some(wire_params)) .await?; Ok(serde_json::from_value(_value)?) } - /// Merges fields into a session's workspace record on disk, creating the record when it is absent. The counterpart to `sessions.loadWorkspace`, for the same before-the-session-exists case. It preserves stored workspace-schema fields the request does not supply, does not preserve stored keys outside the workspace schema, and never replaces a stored `fork_count`. + /// Atomically persists enablement for one owned Agent Finder Skill and reconciles the selected bound session. Enablement is installation-scoped by receipt identity and is never gated by the Skill-install feature flag. /// - /// Wire method: `sessions.updateWorkspaceFields`. + /// Wire method: `skills.installations.setEnabled`. /// /// # Parameters /// - /// * `params` - Where the session's state lives, plus workspace-schema fields to merge into its workspace record. Stored keys outside the schema are not preserved, and a stored `fork_count` is never replaced. + /// * `params` - Persisted enablement update for one owned Skill installation. /// /// # Returns /// - /// The merge completed. The record carries the supplied workspace-schema fields, but a stored `fork_count` stays. + /// Skill installation management result with the honoured contract, or a typed refusal. /// ///
/// @@ -4294,32 +7077,40 @@ impl<'a> ClientRpcSessions<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub(crate) async fn update_workspace_fields( + pub async fn set_enabled( &self, - params: SessionsUpdateWorkspaceFieldsRequest, - ) -> Result { + params: SkillSetEnabledRequest, + ) -> Result { let wire_params = serde_json::to_value(params)?; let _value = self .client .call( - rpc_methods::SESSIONS_UPDATEWORKSPACEFIELDS, + rpc_methods::SKILLS_INSTALLATIONS_SETENABLED, Some(wire_params), ) .await?; Ok(serde_json::from_value(_value)?) } +} - /// Reloads user, plugin, and (optionally) repo hooks on the active session. +/// `tools.*` RPCs. +#[derive(Clone, Copy)] +pub struct ClientRpcTools<'a> { + pub(crate) client: &'a Client, +} + +impl<'a> ClientRpcTools<'a> { + /// Lists built-in tools available for a model. /// - /// Wire method: `sessions.reloadPluginHooks`. + /// Wire method: `tools.list`. /// /// # Parameters /// - /// * `params` - Active session ID and an optional flag for deferring repo-level hooks until folder trust. + /// * `params` - Optional model identifier whose tool overrides should be applied to the listing. /// /// # Returns /// - /// 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. + /// Built-in tools available for the requested model, with their parameters and instructions. /// ///
/// @@ -4328,29 +7119,49 @@ impl<'a> ClientRpcSessions<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub async fn reload_plugin_hooks( - &self, - params: SessionsReloadPluginHooksRequest, - ) -> Result { + pub async fn list(&self, params: ToolsListRequest) -> Result { let wire_params = serde_json::to_value(params)?; let _value = self .client - .call(rpc_methods::SESSIONS_RELOADPLUGINHOOKS, Some(wire_params)) + .call(rpc_methods::TOOLS_LIST, Some(wire_params)) .await?; Ok(serde_json::from_value(_value)?) } +} - /// Loads previously-deferred repo-level hooks on the active session, returning queued startup prompts. +/// `user.*` RPCs. +#[derive(Clone, Copy)] +pub struct ClientRpcUser<'a> { + pub(crate) client: &'a Client, +} + +impl<'a> ClientRpcUser<'a> { + /// `user.settings.*` sub-namespace. + pub fn settings(&self) -> ClientRpcUserSettings<'a> { + ClientRpcUserSettings { + client: self.client, + } + } +} + +/// `user.settings.*` RPCs. +#[derive(Clone, Copy)] +pub struct ClientRpcUserSettings<'a> { + pub(crate) client: &'a Client, +} + +impl<'a> ClientRpcUserSettings<'a> { + /// Adds or removes one hook key in the user `disabledHooks` setting, rewriting settings.json in place and leaving every other setting untouched. Reports a typed outcome rather than failing the call, so a host can tell an unparseable settings file apart from a write that could not complete. /// - /// Wire method: `sessions.loadDeferredRepoHooks`. + /// Wire method: `user.settings.setHookDisabled`. /// /// # Parameters /// - /// * `params` - Active session ID whose deferred repo-level hooks should be loaded. + /// * `params` - One hook to disable or re-enable. /// /// # Returns /// - /// Queued repo-level startup prompts and the total hook command count after loading. + /// Outcome of rewriting the `disabledHooks` setting. /// ///
/// @@ -4359,32 +7170,32 @@ impl<'a> ClientRpcSessions<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub async fn load_deferred_repo_hooks( + pub(crate) async fn set_hook_disabled( &self, - params: SessionsLoadDeferredRepoHooksRequest, - ) -> Result { + params: UserSettingsSetHookDisabledRequest, + ) -> Result { let wire_params = serde_json::to_value(params)?; let _value = self .client .call( - rpc_methods::SESSIONS_LOADDEFERREDREPOHOOKS, + rpc_methods::USER_SETTINGS_SETHOOKDISABLED, Some(wire_params), ) .await?; Ok(serde_json::from_value(_value)?) } - /// Replaces the manager-wide additional plugins registered with the session manager. + /// Reads the user's settings document exactly as `user.settings.load` does, but from a caller-supplied configuration directory instead of the one the server resolved for itself. Use this when a consumer scopes a session to its own Copilot home — the SDK's per-session `configDir` override — so the settings read match the directory that session actually uses. An absent or empty `configDir` resolves the server's own home, making this identical to `user.settings.load`. Hook-validation relaxations are deliberately not caller-supplied: they continue to come from the server's environment, so a consumer cannot widen the host's hook policy through this method. /// - /// Wire method: `sessions.setAdditionalPlugins`. + /// Wire method: `user.settings.loadForConfigDir`. /// /// # Parameters /// - /// * `params` - Manager-wide additional plugins to register; replaces any previously-configured set. + /// * `params` - Selects the configuration directory whose user settings to read. /// /// # Returns /// - /// 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. + /// The stored user settings document, keyed by setting name. /// ///
/// @@ -4393,32 +7204,32 @@ impl<'a> ClientRpcSessions<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub async fn set_additional_plugins( + pub(crate) async fn load_for_config_dir( &self, - params: SessionsSetAdditionalPluginsRequest, - ) -> Result { + params: UserSettingsLoadForConfigDirRequest, + ) -> Result { let wire_params = serde_json::to_value(params)?; let _value = self .client .call( - rpc_methods::SESSIONS_SETADDITIONALPLUGINS, + rpc_methods::USER_SETTINGS_LOADFORCONFIGDIR, Some(wire_params), ) .await?; Ok(serde_json::from_value(_value)?) } - /// Gets the dynamic-context board entry count associated with a session, when available. Internal: this exists solely so CLI telemetry events (`rem_spawn_gate`, `rem_consolidation_complete`) can pair START / END board counts around the detached rem-agent spawn. "Dynamic context board" is a runtime-internal concept that is not part of the public SDK contract; the long-term plan is to relocate the telemetry emission into the runtime so this method can be deleted entirely. + /// Reads a repository's checked-in settings document as stored, normalized, together with any diagnostic raised while reading it. This is the repository half of the layering `user.settings.get` reports: `user.settings.load` reads the user's own document, and this reads the one a repository checks in to govern the keys `user.settings.governanceKeys` lists as repo-governable. A document that fails to parse, or that sets a key a repository may not govern, is reported through `warning` while the settings that did load are still returned, so a consumer surfaces the diagnostic and continues rather than failing the session. When `repoRoot` is the user's home directory the Claude-compatible configuration is skipped, so a home-directory session does not inherit a personal config as repository policy. /// - /// Wire method: `sessions.getBoardEntryCount`. + /// Wire method: `user.settings.loadRepo`. /// /// # Parameters /// - /// * `params` - Session ID whose board entry count should be returned. + /// * `params` - Selects the repository whose checked-in settings to read. /// /// # Returns /// - /// Dynamic-context board entry count, when available. + /// A repository's settings as stored, together with any diagnostic raised while reading them. /// ///
/// @@ -4427,29 +7238,29 @@ impl<'a> ClientRpcSessions<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub(crate) async fn get_board_entry_count( + pub(crate) async fn load_repo( &self, - params: SessionsGetBoardEntryCountRequest, - ) -> Result { + params: UserSettingsLoadRepoRequest, + ) -> Result { let wire_params = serde_json::to_value(params)?; let _value = self .client - .call(rpc_methods::SESSIONS_GETBOARDENTRYCOUNT, Some(wire_params)) + .call(rpc_methods::USER_SETTINGS_LOADREPO, Some(wire_params)) .await?; Ok(serde_json::from_value(_value)?) } - /// Attaches the runtime-managed remote-control singleton to a session, awaiting initial setup. If remote control is already attached to a different session, the singleton is transferred (preserving the underlying Mission Control connection). Returns the final status. + /// Reads a repository's settings and merges them over a supplied user layer in one step, returning the settings a consumer should act on inside that repository. Use this rather than reading the two layers separately: the merge is per setting and collection-aware, so a repository adds to the collections it is allowed to influence without replacing the user's own entries, and a whole-document overlay would get that wrong. Any diagnostic raised while reading the repository layer is returned rather than printed, so the consumer decides how to surface it; the settings that did load are merged regardless. /// - /// Wire method: `sessions.startRemoteControl`. + /// Wire method: `user.settings.loadAndMergeRepo`. /// /// # Parameters /// - /// * `params` - Parameters for attaching the remote-control singleton to a session. + /// * `params` - A repository to read settings from, and the user layer they are merged over. /// /// # Returns /// - /// Wrapper for the singleton's current status. + /// The settings a consumer should act on inside a repository, together with any diagnostic raised while reading the repository layer. /// ///
/// @@ -4458,29 +7269,28 @@ impl<'a> ClientRpcSessions<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub async fn start_remote_control( + pub(crate) async fn load_and_merge_repo( &self, - params: SessionsStartRemoteControlRequest, - ) -> Result { + params: UserSettingsLoadAndMergeRepoRequest, + ) -> Result { let wire_params = serde_json::to_value(params)?; let _value = self .client - .call(rpc_methods::SESSIONS_STARTREMOTECONTROL, Some(wire_params)) + .call( + rpc_methods::USER_SETTINGS_LOADANDMERGEREPO, + Some(wire_params), + ) .await?; Ok(serde_json::from_value(_value)?) } - /// Atomically rebinds the remote-control singleton to a different session, preserving the underlying Mission Control connection. When `expectedFromSessionId` is provided and does not match the singleton's current `attachedSessionId`, the transfer is rejected with `transferred: false` and the current status is returned unchanged. - /// - /// Wire method: `sessions.transferRemoteControl`. - /// - /// # Parameters + /// Reports the on-disk path of the settings document `user.settings.load` and `user.settings.set` read and write. Resolved the same way those methods resolve it, so a host that shows or opens the settings file names the file the runtime actually uses, including the extensionless legacy `settings` file when that is what is present. /// - /// * `params` - Parameters for atomically rebinding the remote-control singleton. + /// Wire method: `user.settings.getPath`. /// /// # Returns /// - /// Outcome of a transferRemoteControl call. + /// The on-disk location of the user's settings document. /// ///
/// @@ -4489,32 +7299,22 @@ impl<'a> ClientRpcSessions<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub async fn transfer_remote_control( - &self, - params: SessionsTransferRemoteControlRequest, - ) -> Result { - let wire_params = serde_json::to_value(params)?; + pub(crate) async fn get_path(&self) -> Result { + let wire_params = serde_json::json!({}); let _value = self .client - .call( - rpc_methods::SESSIONS_TRANSFERREMOTECONTROL, - Some(wire_params), - ) + .call(rpc_methods::USER_SETTINGS_GETPATH, Some(wire_params)) .await?; Ok(serde_json::from_value(_value)?) } - /// Patches the steering state of the active remote-control singleton. When remote control is off, this is a no-op and the off status is returned. Today only `enabled: true` is actionable on the underlying exporter; passing `false` is reserved for future use. + /// Reads the user's stored settings together with the diagnostic raised while reading them. This is `user.settings.load` plus the reason part of the document was ignored: that one answers the document alone, so a caller cannot tell a clean read from a salvaged one. Read this when the caller is about to write the file back, where accepting a partial parse would discard every value the loader could not read. The settings that did load are still reported alongside the warning, so a consumer may surface it and continue instead of failing. /// - /// Wire method: `sessions.setRemoteControlSteering`. - /// - /// # Parameters - /// - /// * `params` - Patch for the singleton's steering state. + /// Wire method: `user.settings.loadWithWarning`. /// /// # Returns /// - /// Wrapper for the singleton's current status. + /// The user's stored settings, together with any diagnostic raised while reading them. /// ///
/// @@ -4523,28 +7323,31 @@ impl<'a> ClientRpcSessions<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub async fn set_remote_control_steering( + pub(crate) async fn load_with_warning( &self, - params: SessionsSetRemoteControlSteeringRequest, - ) -> Result { - let wire_params = serde_json::to_value(params)?; + ) -> Result { + let wire_params = serde_json::json!({}); let _value = self .client .call( - rpc_methods::SESSIONS_SETREMOTECONTROLSTEERING, + rpc_methods::USER_SETTINGS_LOADWITHWARNING, Some(wire_params), ) .await?; Ok(serde_json::from_value(_value)?) } - /// Stops the remote-control singleton. When `expectedSessionId` is provided and does not match the singleton's current `attachedSessionId`, the stop is rejected with `stopped: false` and the current status is returned unchanged (unless `force` is set, in which case the singleton is unconditionally torn down). + /// Folds a user settings layer over a managed (enterprise policy) layer and returns the merged settings. The managed layer's governance keys win, `autoTier` resolves through its overridable form, and the two per-entry control maps (`enabledPlugins`, `extraKnownMarketplaces`) union across the layers and are then stamped back from the managed layer, so each entry an administrator declares wins while the user keeps control of the rest. The parameters are named for their layers rather than being a generic base/override pair: nothing about a settings object identifies which layer produced it, so the runtime cannot detect a transposed call, and transposing one resolves the governance keys in the user's favour instead of the administrator's. Supplying only one layer returns it unchanged, and supplying neither returns an empty object. Rejects a managed `autoTier` that is neither a user-configurable tier nor a single-key overridable form, rather than silently dropping it. /// - /// Wire method: `sessions.stopRemoteControl`. + /// Wire method: `user.settings.mergeManagedAndUser`. + /// + /// # Parameters + /// + /// * `params` - The managed (enterprise policy) layer and the user layer to fold together. The field names carry the layer identity: nothing about a settings object identifies which layer produced it, so a generic base/override pair would let a caller transpose them and silently resolve the governance keys in the user's favour. /// /// # Returns /// - /// Outcome of a stopRemoteControl call. + /// The user layer folded over the managed layer. /// ///
/// @@ -4553,26 +7356,32 @@ impl<'a> ClientRpcSessions<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub async fn stop_remote_control(&self) -> Result { - let wire_params = serde_json::json!({}); + pub(crate) async fn merge_managed_and_user( + &self, + params: UserSettingsMergeManagedAndUserParams, + ) -> Result { + let wire_params = serde_json::to_value(params)?; let _value = self .client - .call(rpc_methods::SESSIONS_STOPREMOTECONTROL, Some(wire_params)) + .call( + rpc_methods::USER_SETTINGS_MERGEMANAGEDANDUSER, + Some(wire_params), + ) .await?; Ok(serde_json::from_value(_value)?) } - /// Stops the remote-control singleton. When `expectedSessionId` is provided and does not match the singleton's current `attachedSessionId`, the stop is rejected with `stopped: false` and the current status is returned unchanged (unless `force` is set, in which case the singleton is unconditionally torn down). + /// Folds a repository's settings layer into the user layer. Each repo-overridable key carries a directional policy chosen so a repository can only add restrictions and never relax the user's own. The two per-entry managed control maps are the exception those directional rules cannot express — a repository may legitimately enable plugins and declare marketplaces — so they are re-stamped afterwards from the runtime's ambient managed policy. That makes the result depend on the runtime process's managed state and not on the arguments alone: a host must call this rather than reimplement the fold, or a repository could relax a restriction the administrator imposed. Rejects when either layer is not a JSON object. /// - /// Wire method: `sessions.stopRemoteControl`. + /// Wire method: `user.settings.mergeRepoValue`. /// /// # Parameters /// - /// * `params` - Parameters for stopping the remote-control singleton. + /// * `params` - The user layer and the repository layer to fold together. /// /// # Returns /// - /// Outcome of a stopRemoteControl call. + /// The repository layer folded into the user layer. /// ///
/// @@ -4581,25 +7390,29 @@ impl<'a> ClientRpcSessions<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub async fn stop_remote_control_with_params( + pub(crate) async fn merge_repo_value( &self, - params: SessionsStopRemoteControlRequest, - ) -> Result { + params: UserSettingsMergeRepoValueParams, + ) -> Result { let wire_params = serde_json::to_value(params)?; let _value = self .client - .call(rpc_methods::SESSIONS_STOPREMOTECONTROL, Some(wire_params)) + .call(rpc_methods::USER_SETTINGS_MERGEREPOVALUE, Some(wire_params)) .await?; Ok(serde_json::from_value(_value)?) } - /// Returns the current state of the remote-control singleton, including the attached session id and frontend URL when active. + /// Runs the one-time migration that moves a staff account's update channel to stable, and records that it ran. Idempotent and self-gating: it takes a lock over the global-state file, re-reads the completion stamp inside it, and returns `current` rather than migrating twice, so two hosts racing at startup cannot both migrate. Only a `migrated` status means the persisted settings changed and the caller should reload them. An account on the experimental channel is deliberately left alone and reported as `excluded-experimental`. /// - /// Wire method: `sessions.getRemoteControlStatus`. + /// Wire method: `user.settings.migrateStaffUpdateChannelOnce`. + /// + /// # Parameters + /// + /// * `params` - The filesystem and environment facts a user-settings file is resolved from. Passed explicitly rather than read from the runtime's own process, so a host whose configuration directory differs from its process environment resolves the file it means. /// /// # Returns /// - /// Wrapper for the singleton's current status. + /// The outcome of the one-time staff update-channel migration. /// ///
/// @@ -4608,25 +7421,32 @@ impl<'a> ClientRpcSessions<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub async fn get_remote_control_status(&self) -> Result { - let wire_params = serde_json::json!({}); + pub(crate) async fn migrate_staff_update_channel_once( + &self, + params: UserSettingsHostContext, + ) -> Result { + let wire_params = serde_json::to_value(params)?; let _value = self .client .call( - rpc_methods::SESSIONS_GETREMOTECONTROLSTATUS, + rpc_methods::USER_SETTINGS_MIGRATESTAFFUPDATECHANNELONCE, Some(wire_params), ) .await?; Ok(serde_json::from_value(_value)?) } - /// Attaches (or detaches) an in-process ExtensionController delegate for the given session in a local host adapter. Pass `controller: undefined` to detach. Internal because the controller cannot cross the JSON-RPC boundary; the runtime manages its own session extension service. + /// Runs the one-time reset that clears a staff account's pinned model selection, and reports the timestamp the host should record. Idempotent and self-gating: the reset is gated on a fixed cutoff, so a caller that passes back the timestamp it recorded gets `didReset: false` rather than a second reset. It applies to staff accounts only; any other account is reported as not reset without the settings document being opened. Only `didReset: true` means the persisted settings changed, and only then are the updated settings returned for the host to adopt. /// - /// Wire method: `sessions.configureSessionExtensions`. + /// Wire method: `user.settings.resetStaffModelSelectionOnce`. /// /// # Parameters /// - /// * `params` - Params to attach or detach an in-process ExtensionController delegate. + /// * `params` - Selects the configuration directory to run the one-time staff model-selection reset against, and supplies the caller's record of whether it has already run. + /// + /// # Returns + /// + /// The outcome of the one-time staff model-selection reset. /// ///
/// @@ -4635,54 +7455,32 @@ impl<'a> ClientRpcSessions<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub(crate) async fn configure_session_extensions( + pub(crate) async fn reset_staff_model_selection_once( &self, - params: ConfigureSessionExtensionsParams, - ) -> Result<(), Error> { + params: UserSettingsResetStaffModelSelectionOnceRequest, + ) -> Result { let wire_params = serde_json::to_value(params)?; let _value = self .client .call( - rpc_methods::SESSIONS_CONFIGURESESSIONEXTENSIONS, + rpc_methods::USER_SETTINGS_RESETSTAFFMODELSELECTIONONCE, Some(wire_params), ) .await?; - Ok(()) - } -} - -/// `skills.*` RPCs. -#[derive(Clone, Copy)] -pub struct ClientRpcSkills<'a> { - pub(crate) client: &'a Client, -} - -impl<'a> ClientRpcSkills<'a> { - /// `skills.config.*` sub-namespace. - pub fn config(&self) -> ClientRpcSkillsConfig<'a> { - ClientRpcSkillsConfig { - client: self.client, - } - } - - /// `skills.installations.*` sub-namespace. - pub fn installations(&self) -> ClientRpcSkillsInstallations<'a> { - ClientRpcSkillsInstallations { - client: self.client, - } + Ok(serde_json::from_value(_value)?) } - /// Plans installation of a verified Agent Finder Skill candidate without writing files. The returned review is safe to present to a user and installing always leaves the Skill disabled until separately enabled. + /// Reports the top-level keys in the user settings document that the runtime does not recognize, phrased for display to the user. This is a separate question from the warning `user.settings.loadWithWarning` returns: that one describes parts of the document the loader had to ignore to load the rest, whereas an unknown key parses cleanly and is simply not a setting, so a host that surfaces only the load warning silently accepts a misspelled key that will never take effect. Nothing is reported when the document is absent, unreadable, or failed to load, because an unknown-key list derived from a document the runtime could not read would be misleading rather than helpful. Inspection never modifies the document. /// - /// Wire method: `skills.planInstall`. + /// Wire method: `user.settings.getUnknownKeysWarning`. /// /// # Parameters /// - /// * `params` - Side-effect-free planning of one verified Agent Finder Skill candidate. + /// * `params` - Selects the configuration directory whose user settings document should be inspected for unrecognized top-level keys. /// /// # Returns /// - /// Skill installation management result with the honoured contract, or a typed refusal. + /// The unrecognized top-level keys found in the user settings document, already phrased for display. /// ///
/// @@ -4691,29 +7489,32 @@ impl<'a> ClientRpcSkills<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub async fn plan_install( + pub(crate) async fn get_unknown_keys_warning( &self, - params: SkillPlanInstallRequest, - ) -> Result { + params: UserSettingsUnknownKeysWarningRequest, + ) -> Result { let wire_params = serde_json::to_value(params)?; let _value = self .client - .call(rpc_methods::SKILLS_PLANINSTALL, Some(wire_params)) + .call( + rpc_methods::USER_SETTINGS_GETUNKNOWNKEYSWARNING, + Some(wire_params), + ) .await?; Ok(serde_json::from_value(_value)?) } - /// Consumes one verified Skill installation plan, requests explicit human consent through installations.confirm on the original connection, then revalidates and installs the Skill disabled. + /// Resolves the effective runtime settings for a host that is about to start a session, layering the given overrides over the settings documents, the environment and the platform defaults. A host cannot assemble this itself: the precedence between a user document, a repository document, the managed layers and the environment is the runtime's own rule, and a host that re-implements it drifts from the server it is configuring. The working directory, environment and platform are taken as arguments rather than read from the server process, so a host resolving settings for a directory other than its own gets that directory's answer. Nothing is written; this only computes. /// - /// Wire method: `skills.applyInstall`. + /// Wire method: `user.settings.buildRuntime`. /// /// # Parameters /// - /// * `params` - Applies exactly one retained verified Skill installation plan. + /// * `params` - The overrides and host context to resolve effective runtime settings from. /// /// # Returns /// - /// Skill installation result with the honoured contract, or a typed request/negotiation refusal. + /// The effective runtime settings, keyed by setting name. Keys this runtime does not recognize are preserved rather than dropped, so a caller reading a document written by a newer runtime keeps every value. /// ///
/// @@ -4722,29 +7523,29 @@ impl<'a> ClientRpcSkills<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub async fn apply_install( + pub(crate) async fn build_runtime( &self, - params: SkillApplyInstallRequest, - ) -> Result { + params: UserSettingsBuildRuntimeRequest, + ) -> Result { let wire_params = serde_json::to_value(params)?; let _value = self .client - .call(rpc_methods::SKILLS_APPLYINSTALL, Some(wire_params)) + .call(rpc_methods::USER_SETTINGS_BUILDRUNTIME, Some(wire_params)) .await?; Ok(serde_json::from_value(_value)?) } - /// Prepares a read-only removal plan for an owned verified Agent Finder Skill installation. Uninstall planning is never gated by the Skill-install feature flag. + /// Runs the one-time migration that moves a staff account's log level to the current default, and records that it ran. Idempotent and self-gating: it takes a lock over the global-state file and re-reads the completion stamp inside it, so two hosts racing at startup cannot both migrate. Only `refreshed` can have changed the persisted settings, and the settings it returns are the document re-read after the write, so a host should adopt them rather than reloading separately and racing the next writer. /// - /// Wire method: `skills.planUninstall`. + /// Wire method: `user.settings.migrateStaffLogLevelOnce`. /// /// # Parameters /// - /// * `params` - Read-only preparation of one owned Skill removal under fresh selected-session authority. + /// * `params` - Selects the configuration directory to run the one-time staff log-level migration against, and supplies the caller's record of whether it has already run. /// /// # Returns /// - /// Skill installation management result with the honoured contract, or a typed refusal. + /// The outcome of the one-time staff log-level migration. /// ///
/// @@ -4753,29 +7554,32 @@ impl<'a> ClientRpcSkills<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub async fn plan_uninstall( + pub(crate) async fn migrate_staff_log_level_once( &self, - params: SkillPlanUninstallRequest, - ) -> Result { + params: UserSettingsMigrateStaffLogLevelOnceRequest, + ) -> Result { let wire_params = serde_json::to_value(params)?; let _value = self .client - .call(rpc_methods::SKILLS_PLANUNINSTALL, Some(wire_params)) + .call( + rpc_methods::USER_SETTINGS_MIGRATESTAFFLOGLEVELONCE, + Some(wire_params), + ) .await?; Ok(serde_json::from_value(_value)?) } - /// Consumes an owned Skill removal plan, requests explicit human consent through installations.confirm, refuses drift, and removes the exact owned files through quarantine. + /// Runs the one-time cleanup that removes obsolete CLI-owned settings and legacy rubber-duck keys from the user's settings document, and reports what it did. Idempotent and safe to call on every startup: with nothing obsolete left it reports `unchanged` and writes nothing. The cleanup preserves every key it does not recognize, so a setting written by a newer host is never dropped by an older one, and a document that fails to parse is reported `invalid` and left untouched rather than rewritten from a partial read. The cleaned document is not returned: it is written before this replies, so `user.settings.load` reads it authoritatively. A host that surfaces unrecognized keys through `user.settings.unknownKeys` wants this first, because the keys this retires would otherwise be shown to the user as if they were misspelled settings. /// - /// Wire method: `skills.applyUninstall`. + /// Wire method: `user.settings.migrateObsoleteOnDisk`. /// /// # Parameters /// - /// * `params` - One-use application of the exact retained Skill removal plan. + /// * `params` - Selects the configuration directory whose settings document to clean. /// /// # Returns /// - /// Skill installation result with the honoured contract, or a typed request/negotiation refusal. + /// The outcome of the one-time obsolete-settings cleanup. /// ///
/// @@ -4784,29 +7588,28 @@ impl<'a> ClientRpcSkills<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub async fn apply_uninstall( + pub(crate) async fn migrate_obsolete_on_disk( &self, - params: SkillApplyUninstallRequest, - ) -> Result { + params: UserSettingsMigrateObsoleteOnDiskRequest, + ) -> Result { let wire_params = serde_json::to_value(params)?; let _value = self .client - .call(rpc_methods::SKILLS_APPLYUNINSTALL, Some(wire_params)) + .call( + rpc_methods::USER_SETTINGS_MIGRATEOBSOLETEONDISK, + Some(wire_params), + ) .await?; Ok(serde_json::from_value(_value)?) } - /// Discovers skills across global and project sources. - /// - /// Wire method: `skills.discover`. - /// - /// # Parameters + /// Lists every known user setting from settings.json, each with its effective value, its default, and whether it is at the default — so settings the user has never set still appear with their default value. Does not include repository- or enterprise-managed overrides that the runtime layers on top at session time. /// - /// * `params` - Optional project paths and additional skill directories to include in discovery. + /// Wire method: `user.settings.get`. /// /// # Returns /// - /// Skills discovered across global and project sources. + /// Per-key metadata for every known user setting in settings.json, including settings left at their default. Excludes repository- and enterprise-managed overrides. /// ///
/// @@ -4815,26 +7618,26 @@ impl<'a> ClientRpcSkills<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub async fn discover(&self, params: SkillsDiscoverRequest) -> Result { - let wire_params = serde_json::to_value(params)?; + pub async fn get(&self) -> Result { + let wire_params = serde_json::json!({}); let _value = self .client - .call(rpc_methods::SKILLS_DISCOVER, Some(wire_params)) + .call(rpc_methods::USER_SETTINGS_GET, Some(wire_params)) .await?; Ok(serde_json::from_value(_value)?) } - /// Returns the canonical directories where a client may create skills that the runtime will recognize, including ones that do not exist yet. Project directories become active once created. + /// Resolves the on-disk locations of the settings files this runtime reads, so a consumer can show or open them. The user path is the same file `user.settings.get`, `user.settings.load`, and `user.settings.set` operate on. Supply `repoRoot` to also resolve that repository's checked-in and local settings paths. A path is reported whether or not the file exists. /// - /// Wire method: `skills.getDiscoveryPaths`. + /// Wire method: `user.settings.getPaths`. /// /// # Parameters /// - /// * `params` - Optional project paths to enumerate. + /// * `params` - Selects which settings file paths to resolve. /// /// # Returns /// - /// Canonical locations where skills can be created so the runtime will recognize them. + /// On-disk locations of the settings files this runtime reads. A path is reported whether or not the file exists. /// ///
/// @@ -4843,33 +7646,25 @@ impl<'a> ClientRpcSkills<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub async fn get_discovery_paths( + pub(crate) async fn get_paths( &self, - params: SkillsGetDiscoveryPathsRequest, - ) -> Result { + params: UserSettingsGetPathsRequest, + ) -> Result { let wire_params = serde_json::to_value(params)?; let _value = self .client - .call(rpc_methods::SKILLS_GETDISCOVERYPATHS, Some(wire_params)) + .call(rpc_methods::USER_SETTINGS_GETPATHS, Some(wire_params)) .await?; Ok(serde_json::from_value(_value)?) } -} - -/// `skills.config.*` RPCs. -#[derive(Clone, Copy)] -pub struct ClientRpcSkillsConfig<'a> { - pub(crate) client: &'a Client, -} -impl<'a> ClientRpcSkillsConfig<'a> { - /// Replaces the global list of disabled skills. + /// Writes one or more user settings to settings.json, replacing each provided top-level key. A key whose value is null is removed. /// - /// Wire method: `skills.config.setDisabledSkills`. + /// Wire method: `user.settings.set`. /// /// # Parameters /// - /// * `params` - Skill names to mark as disabled in global configuration, replacing any previous list. + /// * `params` - 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. /// ///
/// @@ -4878,28 +7673,22 @@ impl<'a> ClientRpcSkillsConfig<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub async fn set_disabled_skills( - &self, - params: SkillsConfigSetDisabledSkillsRequest, - ) -> Result<(), Error> { + pub async fn set(&self, params: UserSettingsSetRequest) -> Result<(), Error> { let wire_params = serde_json::to_value(params)?; let _value = self .client - .call( - rpc_methods::SKILLS_CONFIG_SETDISABLEDSKILLS, - Some(wire_params), - ) + .call(rpc_methods::USER_SETTINGS_SET, Some(wire_params)) .await?; Ok(()) } - /// Atomically adds or removes one skill from the disabled list. + /// Lists the top-level user-setting keys by who is allowed to govern them: `repo` keys a repository may override through `.github/copilot/settings.json`, `claudeRepo` keys a repository may override through a Claude-compatible config, and `managed` keys an enterprise policy may fix. The lists are static for a given runtime build and fail closed: a key absent from a list cannot be governed that way. /// - /// Wire method: `skills.config.setSkillDisabled`. + /// Wire method: `user.settings.listGovernanceKeys`. /// - /// # Parameters + /// # Returns /// - /// * `params` - Adds or removes a single skill from the global disabled list, leaving every other entry untouched. + /// Top-level user-setting keys grouped by who may govern them. /// ///
/// @@ -4908,40 +7697,31 @@ impl<'a> ClientRpcSkillsConfig<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub async fn set_skill_disabled( + pub(crate) async fn list_governance_keys( &self, - params: SkillsConfigSetSkillDisabledRequest, - ) -> Result<(), Error> { - let wire_params = serde_json::to_value(params)?; + ) -> Result { + let wire_params = serde_json::json!({}); let _value = self .client .call( - rpc_methods::SKILLS_CONFIG_SETSKILLDISABLED, + rpc_methods::USER_SETTINGS_LISTGOVERNANCEKEYS, Some(wire_params), ) .await?; - Ok(()) + Ok(serde_json::from_value(_value)?) } -} - -/// `skills.installations.*` RPCs. -#[derive(Clone, Copy)] -pub struct ClientRpcSkillsInstallations<'a> { - pub(crate) client: &'a Client, -} -impl<'a> ClientRpcSkillsInstallations<'a> { - /// Lists owned verified Agent Finder Skill installations for the selected existing session. Listing is never gated by the Skill-install feature flag. + /// Atomically edits one canonical user setting inside its settings-file lock. Reads, canonicalizes touched aliases, applies the edit, validates the resulting top-level setting and writes before releasing the lock. An explicit autoUpdatesChannel set completes the staff migration in the same transaction. Only a malformed existing file returns `invalid`; refused keys, invalid values and I/O errors reject the call. /// - /// Wire method: `skills.installations.list`. + /// Wire method: `user.settings.edit`. /// /// # Parameters /// - /// * `params` - Inventory request under an explicitly selected existing session. + /// * `params` - One atomic edit of a canonical setting in settings.json. /// /// # Returns /// - /// Skill installation management result with the honoured contract, or a typed refusal. + /// Outcome of an atomic user or repository settings edit. /// ///
/// @@ -4950,29 +7730,29 @@ impl<'a> ClientRpcSkillsInstallations<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub async fn list( + pub(crate) async fn edit( &self, - params: SkillInstallationsRequest, - ) -> Result { + params: UserSettingsEditRequest, + ) -> Result { let wire_params = serde_json::to_value(params)?; let _value = self .client - .call(rpc_methods::SKILLS_INSTALLATIONS_LIST, Some(wire_params)) + .call(rpc_methods::USER_SETTINGS_EDIT, Some(wire_params)) .await?; Ok(serde_json::from_value(_value)?) } - /// Reconciles interrupted owned Skill installation work for the selected existing session, then inspects owned inventory. Recovery is never gated by the Skill-install feature flag. + /// Rewrites legacy and snake_case setting names in a settings object to their canonical spelling. A caller editing settings.json must normalize before writing: a legacy key left in place survives the write, is re-normalized on the next load, and silently shadows the canonical value the write just stored. /// - /// Wire method: `skills.installations.recover`. + /// Wire method: `user.settings.normalize`. /// /// # Parameters /// - /// * `params` - Inventory request under an explicitly selected existing session. + /// * `params` - Settings object whose key names should be canonicalized. /// /// # Returns /// - /// Skill installation management result with the honoured contract, or a typed refusal. + /// Settings object with canonical key names. /// ///
/// @@ -4981,29 +7761,29 @@ impl<'a> ClientRpcSkillsInstallations<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub async fn recover( + pub(crate) async fn normalize( &self, - params: SkillInstallationsRequest, - ) -> Result { + params: UserSettingsNormalizeRequest, + ) -> Result { let wire_params = serde_json::to_value(params)?; let _value = self .client - .call(rpc_methods::SKILLS_INSTALLATIONS_RECOVER, Some(wire_params)) + .call(rpc_methods::USER_SETTINGS_NORMALIZE, Some(wire_params)) .await?; Ok(serde_json::from_value(_value)?) } - /// Inspects a known Skill installation operation on its original runtime connection. Status is never gated by the Skill-install feature flag. + /// Diagnoses a set of already-loaded settings layers, reporting each key that will not take effect and why. Settings load tolerantly so that one bad key does not stop a session from starting; this method surfaces what was dropped so a caller can report it instead of silently ignoring the user's intent. Takes the layers as given rather than reading them, so a caller can diagnose exactly the content it is showing, including unsaved edits. Never echoes a setting's value, because a value may be a token or another secret. Marked internal because it exists to carry a CLI call site off the native addon, not to serve a consumer-facing need; it becomes public once an SDK consumer needs to inspect settings health itself. /// - /// Wire method: `skills.installations.status`. + /// Wire method: `user.settings.diagnoseProblems`. /// /// # Parameters /// - /// * `params` - Existing-operation control. A new session selector is deliberately not accepted. + /// * `params` - The settings layers to diagnose. /// /// # Returns /// - /// Skill installation management result with the honoured contract, or a typed refusal. + /// The settings that will not take effect across the given layers. /// ///
/// @@ -5012,29 +7792,32 @@ impl<'a> ClientRpcSkillsInstallations<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub async fn status( + pub(crate) async fn diagnose_problems( &self, - params: SkillInstallationOperationRequest, - ) -> Result { + params: UserSettingsProblemsRequest, + ) -> Result { let wire_params = serde_json::to_value(params)?; let _value = self .client - .call(rpc_methods::SKILLS_INSTALLATIONS_STATUS, Some(wire_params)) + .call( + rpc_methods::USER_SETTINGS_DIAGNOSEPROBLEMS, + Some(wire_params), + ) .await?; Ok(serde_json::from_value(_value)?) } - /// Requests cancellation of a known Skill installation operation before commit. Already-started durable work requires recovery instead of silent replay. + /// Validates one top-level setting subtree and writes just that key to settings.json as a single step. Doing both here closes the race a caller cannot close itself: between a separate preflight read and the write, the file may change. Rewriting only the touched key keeps legacy siblings out of settings.json. Answers a status rather than failing, so a validation problem and a write problem stay distinguishable. /// - /// Wire method: `skills.installations.cancel`. + /// Wire method: `user.settings.applyTopKey`. /// /// # Parameters /// - /// * `params` - Existing-operation control. A new session selector is deliberately not accepted. + /// * `params` - A single top-level setting subtree to validate and write. /// /// # Returns /// - /// Skill installation management result with the honoured contract, or a typed refusal. + /// Outcome of validating and writing one top-level setting subtree. /// ///
/// @@ -5043,29 +7826,25 @@ impl<'a> ClientRpcSkillsInstallations<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub async fn cancel( + pub(crate) async fn apply_top_key( &self, - params: SkillInstallationOperationRequest, - ) -> Result { + params: UserSettingsApplyTopKeyRequest, + ) -> Result { let wire_params = serde_json::to_value(params)?; let _value = self .client - .call(rpc_methods::SKILLS_INSTALLATIONS_CANCEL, Some(wire_params)) + .call(rpc_methods::USER_SETTINGS_APPLYTOPKEY, Some(wire_params)) .await?; Ok(serde_json::from_value(_value)?) } - /// Atomically persists enablement for one owned Agent Finder Skill and reconciles the selected bound session. Enablement is installation-scoped by receipt identity and is never gated by the Skill-install feature flag. + /// Replaces the user settings file's entire content in one write. A key absent from `settings` is removed, which is what distinguishes this from `user.settings.set`: that one merges, so it can never clear a key the caller has dropped. For a caller saving the result of a raw edit, a merge would silently resurrect every key the user deleted. Records the same one-time mutation marks a per-key write records, so a setting the user has now chosen explicitly stops being migrated for them. /// - /// Wire method: `skills.installations.setEnabled`. + /// Wire method: `user.settings.writeAll`. /// /// # Parameters /// - /// * `params` - Persisted enablement update for one owned Skill installation. - /// - /// # Returns - /// - /// Skill installation management result with the honoured contract, or a typed refusal. + /// * `params` - The whole content to store in the user settings file. /// ///
/// @@ -5074,40 +7853,26 @@ impl<'a> ClientRpcSkillsInstallations<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub async fn set_enabled( - &self, - params: SkillSetEnabledRequest, - ) -> Result { + pub(crate) async fn write_all(&self, params: UserSettingsWriteAllRequest) -> Result<(), Error> { let wire_params = serde_json::to_value(params)?; let _value = self .client - .call( - rpc_methods::SKILLS_INSTALLATIONS_SETENABLED, - Some(wire_params), - ) + .call(rpc_methods::USER_SETTINGS_WRITEALL, Some(wire_params)) .await?; - Ok(serde_json::from_value(_value)?) + Ok(()) } -} - -/// `tools.*` RPCs. -#[derive(Clone, Copy)] -pub struct ClientRpcTools<'a> { - pub(crate) client: &'a Client, -} -impl<'a> ClientRpcTools<'a> { - /// Lists built-in tools available for a model. + /// Merges a partial object into one top-level user setting, re-reading the stored value inside this runtime process's settings.json write lock so that concurrent writers in this process to different sub-fields of the same key do not clobber each other. Unlike `user.settings.set`, which replaces a key's whole value with what the caller last read, this keeps sub-fields the caller did not name when both the stored value and supplied value are objects; otherwise the supplied value replaces the stored value. Use it whenever two independent surfaces own different sub-fields of one setting. Returns the key's stored value after the write, in `value`. /// - /// Wire method: `tools.list`. + /// Wire method: `user.settings.update`. /// /// # Parameters /// - /// * `params` - Optional model identifier whose tool overrides should be applied to the listing. + /// * `params` - Names the user setting to merge into, and the partial object to merge. /// /// # Returns /// - /// Built-in tools available for the requested model, with their parameters and instructions. + /// The stored value of the user setting after the merge. /// ///
/// @@ -5116,45 +7881,25 @@ impl<'a> ClientRpcTools<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub async fn list(&self, params: ToolsListRequest) -> Result { + pub(crate) async fn update( + &self, + params: UserSettingsUpdateRequest, + ) -> Result { let wire_params = serde_json::to_value(params)?; let _value = self .client - .call(rpc_methods::TOOLS_LIST, Some(wire_params)) + .call(rpc_methods::USER_SETTINGS_UPDATE, Some(wire_params)) .await?; Ok(serde_json::from_value(_value)?) } -} - -/// `user.*` RPCs. -#[derive(Clone, Copy)] -pub struct ClientRpcUser<'a> { - pub(crate) client: &'a Client, -} - -impl<'a> ClientRpcUser<'a> { - /// `user.settings.*` sub-namespace. - pub fn settings(&self) -> ClientRpcUserSettings<'a> { - ClientRpcUserSettings { - client: self.client, - } - } -} -/// `user.settings.*` RPCs. -#[derive(Clone, Copy)] -pub struct ClientRpcUserSettings<'a> { - pub(crate) client: &'a Client, -} - -impl<'a> ClientRpcUserSettings<'a> { - /// Lists every known user setting from settings.json, each with its effective value, its default, and whether it is at the default — so settings the user has never set still appear with their default value. Does not include repository- or enterprise-managed overrides that the runtime layers on top at session time. + /// Reads settings.json as stored for a caller that is about to edit it. Unlike `user.settings.get`, this returns file values rather than per-key metadata and defaults. Reports `missing` for an absent file and `invalid` when the existing file will not parse. Retired config.json and extensionless files are not read. /// - /// Wire method: `user.settings.get`. + /// Wire method: `user.settings.loadForEdit`. /// /// # Returns /// - /// Per-key metadata for every known user setting in settings.json, including settings left at their default. Excludes repository- and enterprise-managed overrides. + /// Contents of settings.json as stored, for a caller about to rewrite it. /// ///
/// @@ -5163,22 +7908,26 @@ impl<'a> ClientRpcUserSettings<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub async fn get(&self) -> Result { + pub(crate) async fn load_for_edit(&self) -> Result { let wire_params = serde_json::json!({}); let _value = self .client - .call(rpc_methods::USER_SETTINGS_GET, Some(wire_params)) + .call(rpc_methods::USER_SETTINGS_LOADFOREDIT, Some(wire_params)) .await?; Ok(serde_json::from_value(_value)?) } - /// Writes one or more user settings to settings.json, replacing each provided top-level key. A key whose value is null is removed. + /// Validates one top-level setting subtree without writing it, reporting each problem with the path it applies to. Scoped to a single top-level key on purpose: validating the whole file would reject an edit whenever an unrelated pre-existing field is malformed, locking the user out of the very tool that could fix it. /// - /// Wire method: `user.settings.set`. + /// Wire method: `user.settings.validate`. /// /// # Parameters /// - /// * `params` - 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. + /// * `params` - A single top-level setting subtree to validate. + /// + /// # Returns + /// + /// Outcome of validating one top-level setting subtree. /// ///
/// @@ -5187,13 +7936,16 @@ impl<'a> ClientRpcUserSettings<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub async fn set(&self, params: UserSettingsSetRequest) -> Result<(), Error> { + pub(crate) async fn validate( + &self, + params: UserSettingsValidateRequest, + ) -> Result { let wire_params = serde_json::to_value(params)?; let _value = self .client - .call(rpc_methods::USER_SETTINGS_SET, Some(wire_params)) + .call(rpc_methods::USER_SETTINGS_VALIDATE, Some(wire_params)) .await?; - Ok(()) + Ok(serde_json::from_value(_value)?) } } @@ -10824,6 +13576,42 @@ impl<'a> SessionRpcModel<'a> { Ok(serde_json::from_value(_value)?) } + /// Computes whether the session's current model shows reasoning summaries and how a host should present them: the default Ctrl+T visibility, reasoning headers, the hidden-summaries hint, and whether a persisted `showReasoning` setting is redundant. The runtime folds in the session's model catalog, selection and reasoning-summaries experiment assignment; the host supplies only its own persisted setting and current visibility. This method reads the session's resolved model catalog rather than resolving it, so call `session.model.list` first on a session that has not listed models or run a turn; until then the result is `ready: false`. A session that resolves models without a Copilot catalog, that is a BYOK registry or an alternate provider, never caches one and stays `ready: false`. + /// + /// Wire method: `session.model.getReasoningSummaries`. + /// + /// # Parameters + /// + /// * `params` - Host presentation state the reasoning-summaries policy depends on. + /// + /// # Returns + /// + /// Reasoning-summaries presentation policy for the session's current model. + /// + ///
+ /// + /// **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_reasoning_summaries( + &self, + params: ModelGetReasoningSummariesRequest, + ) -> 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_GETREASONINGSUMMARIES, + Some(wire_params), + ) + .await?; + Ok(serde_json::from_value(_value)?) + } + /// Lists models available to this session using its own auth and integration context. Connected hosts (CLI TUI, GitHub App) should call this through the session client so remote sessions return the remote CLI's available models rather than the caller's. /// /// Wire method: `session.model.list`. @@ -12168,6 +14956,64 @@ impl<'a> SessionRpcPlugins<'a> { .await?; Ok(()) } + + /// Installs or updates the plugins the session's managed settings require now, without waiting for the next automatic attempt after a failure, then reloads plugins. Reports progress and failures with the same `session.info` and `session.warning` events as automatic preparation. Fails when the runtime does not prepare managed plugins for the session. + /// + /// Wire method: `session.plugins.retryManaged`. + /// + /// # Returns + /// + /// Per-plugin outcomes of retrying the plugins required by the session's managed settings. + /// + ///
+ /// + /// **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 retry_managed(&self) -> Result { + let wire_params = serde_json::json!({ "sessionId": self.session.id() }); + let _value = self + .session + .client() + .call(rpc_methods::SESSION_PLUGINS_RETRYMANAGED, Some(wire_params)) + .await?; + Ok(serde_json::from_value(_value)?) + } + + /// Installs or updates the plugins the session's managed settings require now, without waiting for the next automatic attempt after a failure, then reloads plugins. Reports progress and failures with the same `session.info` and `session.warning` events as automatic preparation. Fails when the runtime does not prepare managed plugins for the session. + /// + /// Wire method: `session.plugins.retryManaged`. + /// + /// # Parameters + /// + /// * `params` - Plugins required by the session's managed settings to install or update again. + /// + /// # Returns + /// + /// Per-plugin outcomes of retrying the plugins required by the session's managed settings. + /// + ///
+ /// + /// **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 retry_managed_with_params( + &self, + params: SessionPluginsRetryManagedRequest, + ) -> Result { + let mut wire_params = serde_json::to_value(params)?; + wire_params["sessionId"] = serde_json::Value::String(self.session.id().to_string()); + let _value = self + .session + .client() + .call(rpc_methods::SESSION_PLUGINS_RETRYMANAGED, Some(wire_params)) + .await?; + Ok(serde_json::from_value(_value)?) + } } /// `session.plugins.marketplaces.*` RPCs. @@ -13549,6 +16395,36 @@ impl<'a> SessionRpcSandbox<'a> { Ok(serde_json::from_value(_value)?) } + /// Lists possible secret-bearing environment variables available to new sandboxed shells that are not already covered by credential masking. Uses local name rules and known-provider host suggestions, never returns secret values, and changes no settings or network permissions. Returns no suggestions while the session is not sandboxed. + /// + /// Wire method: `session.sandbox.getCredentialSuggestions`. + /// + /// # Returns + /// + /// Possible unconfigured secrets in the sandbox shell environment, sorted by variable name, with no secret values. + /// + ///
+ /// + /// **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_credential_suggestions( + &self, + ) -> Result { + let wire_params = serde_json::json!({ "sessionId": self.session.id() }); + let _value = self + .session + .client() + .call( + rpc_methods::SESSION_SANDBOX_GETCREDENTIALSUGGESTIONS, + Some(wire_params), + ) + .await?; + Ok(serde_json::from_value(_value)?) + } + /// Disables sandboxing for the remainder of the current session and approves the referenced pending sandbox-bypass permission request. The request is rejected unless the exact request is still pending and the effective sandbox policy permits bypass. /// /// Wire method: `session.sandbox.disableForSession`. diff --git a/rust/src/generated/session_events.rs b/rust/src/generated/session_events.rs index 4872f9ab0d..9770b046d2 100644 --- a/rust/src/generated/session_events.rs +++ b/rust/src/generated/session_events.rs @@ -1356,13 +1356,19 @@ pub struct SessionErrorData { /// Fine-grained error code from the upstream provider, when available. For `errorType: "rate_limit"`, this is one of the `RateLimitErrorCode` values (e.g., `"user_weekly_rate_limited"`, `"user_global_rate_limited"`, `"rate_limited"`, `"user_model_rate_limited"`, `"integration_rate_limited"`). For `errorType: "quota"`, this is the CAPI quota error code (e.g., `"quota_exceeded"`, `"session_quota_exceeded"`, `"billing_not_configured"`). #[serde(skip_serializing_if = "Option::is_none")] pub error_code: Option, - /// Category of error (e.g., "authentication", "authorization", "quota", "rate_limit", "context_limit", "query") + /// Category of error (e.g., "authentication", "authorization", "policy", "quota", "rate_limit", "context_limit", "query") pub error_type: String, /// Human-readable error message pub message: String, /// GitHub request tracing ID (x-github-request-id header) for correlating with server-side logs #[serde(skip_serializing_if = "Option::is_none")] pub provider_call_id: Option, + /// Sanitized provider-defined error parameter preserved from a structured upstream error envelope, when available. + #[serde(skip_serializing_if = "Option::is_none")] + pub provider_error_param: Option, + /// Provider-defined error type preserved from a structured upstream error envelope, when available (for example, `invalid_request_error`). + #[serde(skip_serializing_if = "Option::is_none")] + pub provider_error_type: Option, /// What the user must do to recover, when the runtime knows of an action. The `message` never names a client affordance, so a client that offers one — a slash command, a settings pane, a link — renders it from this value. #[serde(skip_serializing_if = "Option::is_none")] pub remediation: Option, @@ -2273,6 +2279,37 @@ pub struct ProviderQuotaBudgetMetadata { pub usage_allowed_when_exhausted: bool, } +/// An authoritative monthly usage reading for the observation's provider, account, and service. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ProviderMonthlyUsage { + /// Nonnegative finite consumption reported by the service, preserving zero and fractions. Present only when state is available; never computed from tokens or balance differences. + #[serde(skip_serializing_if = "Option::is_none")] + pub consumed_quantity: Option, + /// Start of the service-reported monthly billing cycle, in UTC RFC 3339. + #[serde(skip_serializing_if = "Option::is_none")] + pub cycle_start: Option, + /// Service read time in UTC RFC 3339, not a ledger reconciliation watermark or confirmation of the latest inference charge. + #[serde(skip_serializing_if = "Option::is_none")] + pub queried_at: Option, + /// End of the service-reported monthly billing cycle, in UTC RFC 3339. + #[serde(skip_serializing_if = "Option::is_none")] + pub reset_on: Option, + /// Identity scope of the reading within its provider and service. + pub scope: ProviderMonthlyUsageScope, + /// Availability of monthly consumption. Missing or unavailable usage must never be interpreted as zero. + pub state: ProviderMonthlyUsageState, + /// Unit of consumedQuantity; independent of the parent observation's balance unit. + pub unit: ProviderQuotaUnit, +} + /// An account quota reading. Absence of a quantity is unknown, never zero. /// ///
@@ -2312,6 +2349,9 @@ pub struct ProviderQuotaState { /// HTTP status from acquisition, when available. #[serde(skip_serializing_if = "Option::is_none")] pub http_status: Option, + /// Service-reported monthly consumption, independent of quota balances and per-call or session cost. Omitted when the service does not report monthly usage. + #[serde(skip_serializing_if = "Option::is_none")] + pub monthly_usage: Option, /// Whether this is a GET account reading or a pre-response admission observation. Observations are never merged across kinds. #[serde(skip_serializing_if = "Option::is_none")] pub observation_kind: Option, @@ -2349,7 +2389,7 @@ pub struct ProviderQuotaState { #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct SessionQuotaObservationData { - /// The admitted provider's state observation. Admission observations never contain quantities or reset/percentage semantics. + /// The admitted provider's state observation. Admission balances and percentages remain absent; separate monthly usage may report its own amount and cycle. pub observation: ProviderQuotaState, } @@ -2516,6 +2556,9 @@ pub struct CompactionCompleteCompactionTokensUsed { /// Provider captured for the compaction call; absent when attribution is unknown. #[serde(skip_serializing_if = "Option::is_none")] pub provider: Option, + /// Number of model requests represented by this aggregated compaction usage + #[serde(skip_serializing_if = "Option::is_none")] + pub request_count: Option, } /// Original request-level and effective conversation reasoning effort for a provider history boundary; the historical type name is retained for compatibility @@ -2534,10 +2577,6 @@ pub struct ResponsesReasoning { #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct SessionCompactionCompleteData { - /// Legacy active-workflow reminder retained for replay compatibility - #[doc(hidden)] - #[serde(skip_serializing_if = "Option::is_none")] - pub(crate) active_factory_summary: Option, /// Authoritative active-workflow reminder appended to the compacted context #[doc(hidden)] #[serde(skip_serializing_if = "Option::is_none")] @@ -2643,6 +2682,12 @@ pub struct PermissionRecoveryData { pub max_attempts: i64, /// Policy selected from the current client's response capability; mode or client changes may update it during recovery pub on_blocked: PermissionRecoveryOnBlocked, + /// Subagent identifier that owns this recovery episode when the request was delegated through a parent permission service + #[serde(skip_serializing_if = "Option::is_none")] + pub owner_agent_id: Option, + /// Root or child execution-session identifier that owns this recovery episode; omitted for legacy snapshots + #[serde(skip_serializing_if = "Option::is_none")] + pub owner_session_id: Option, /// Controlled reason for the latest episode transition pub reason: PermissionRecoveryReason, /// Current lifecycle state of the recovery episode @@ -3076,6 +3121,12 @@ pub struct SessionPermissionRecoveryData { pub max_attempts: i64, /// Policy selected from the current client's response capability; mode or client changes may update it during recovery pub on_blocked: PermissionRecoveryOnBlocked, + /// Subagent identifier that owns this recovery episode when the request was delegated through a parent permission service + #[serde(skip_serializing_if = "Option::is_none")] + pub owner_agent_id: Option, + /// Root or child execution-session identifier that owns this recovery episode; omitted for legacy snapshots + #[serde(skip_serializing_if = "Option::is_none")] + pub owner_session_id: Option, /// Controlled reason for the latest episode transition pub reason: PermissionRecoveryReason, /// Current lifecycle state of the recovery episode @@ -4096,9 +4147,15 @@ pub struct AssistantUsageData { /// Number of rejected speculative prediction tokens #[serde(skip_serializing_if = "Option::is_none")] pub rejected_prediction_tokens: Option, - /// Serialized (uncompressed) byte length of the request body. A content-free size signal. + /// Serialized (uncompressed) byte length of the request body, before any Content-Encoding. A content-free size signal. #[serde(skip_serializing_if = "Option::is_none")] pub request_body_bytes: Option, + /// Content-Encoding applied to the request body of the attempt that produced this outcome; identity when uncompressed + #[serde(skip_serializing_if = "Option::is_none")] + pub request_body_encoding: Option, + /// Byte length of the request body actually sent on the wire for the attempt that produced this outcome, after any Content-Encoding. Equals requestBodyBytes when the body was not compressed. + #[serde(skip_serializing_if = "Option::is_none")] + pub request_body_wire_bytes: 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, @@ -4130,9 +4187,24 @@ pub struct AssistantUsageData { /// Milliseconds spent on the WebSocket attempt before falling back to HTTP #[serde(skip_serializing_if = "Option::is_none")] pub websocket_fallback_after_ms: Option, + /// WebSocket close code received before the HTTP fallback + #[serde(skip_serializing_if = "Option::is_none")] + pub websocket_fallback_close_code: Option, + /// Normalized cause of the WebSocket failure that triggered the HTTP fallback; absent when no fallback occurred or the cause is unknown + #[serde(skip_serializing_if = "Option::is_none")] + pub websocket_fallback_error_kind: Option, /// Why the call was carried by the HTTP fallback of a WebSocket-capable dispatcher; absent when no fallback occurred #[serde(skip_serializing_if = "Option::is_none")] pub websocket_fallback_reason: Option, + /// Whether a WebSocket reconnect was attempted before falling back to HTTP + #[serde(skip_serializing_if = "Option::is_none")] + pub websocket_fallback_reconnect_attempted: Option, + /// Whether the HTTP fallback started during this model call, rather than being inherited from an earlier call + #[serde(skip_serializing_if = "Option::is_none")] + pub websocket_fallback_started_this_call: Option, + /// HTTP status of the WebSocket upgrade rejection or WebSocket API error that triggered the HTTP fallback + #[serde(skip_serializing_if = "Option::is_none")] + pub websocket_fallback_status_code: Option, } /// Session event "prompt_cache_break". A detected loss of a previously cached prompt prefix @@ -4333,9 +4405,15 @@ pub struct ModelCallFailureData { /// Reasoning effort level used for the failed model call, if applicable #[serde(skip_serializing_if = "Option::is_none")] pub reasoning_effort: Option, - /// Serialized (uncompressed) byte length of the failed request body. A content-free size signal. + /// Serialized (uncompressed) byte length of the failed request body, before any Content-Encoding. A content-free size signal. #[serde(skip_serializing_if = "Option::is_none")] pub request_body_bytes: Option, + /// Content-Encoding applied to the failed request body; identity when uncompressed + #[serde(skip_serializing_if = "Option::is_none")] + pub request_body_encoding: Option, + /// Byte length of the failed request body actually sent on the wire, after any Content-Encoding. Equals requestBodyBytes when the body was not compressed. + #[serde(skip_serializing_if = "Option::is_none")] + pub request_body_wire_bytes: Option, /// 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, @@ -4359,9 +4437,24 @@ pub struct ModelCallFailureData { /// Milliseconds spent on the WebSocket attempt before falling back to HTTP #[serde(skip_serializing_if = "Option::is_none")] pub websocket_fallback_after_ms: Option, + /// WebSocket close code received before the HTTP fallback + #[serde(skip_serializing_if = "Option::is_none")] + pub websocket_fallback_close_code: Option, + /// Normalized cause of the WebSocket failure that triggered the HTTP fallback; absent when no fallback occurred or the cause is unknown + #[serde(skip_serializing_if = "Option::is_none")] + pub websocket_fallback_error_kind: Option, /// Why the failed call was carried by the HTTP fallback of a WebSocket-capable dispatcher; absent when no fallback occurred #[serde(skip_serializing_if = "Option::is_none")] pub websocket_fallback_reason: Option, + /// Whether a WebSocket reconnect was attempted before falling back to HTTP + #[serde(skip_serializing_if = "Option::is_none")] + pub websocket_fallback_reconnect_attempted: Option, + /// Whether the HTTP fallback started during this model call, rather than being inherited from an earlier call + #[serde(skip_serializing_if = "Option::is_none")] + pub websocket_fallback_started_this_call: Option, + /// HTTP status of the WebSocket upgrade rejection or WebSocket API error that triggered the HTTP fallback + #[serde(skip_serializing_if = "Option::is_none")] + pub websocket_fallback_status_code: Option, } /// Session event "model.call_final_result". Internal telemetry result for one logical model operation after all orchestrator-owned retries settle @@ -5605,9 +5698,6 @@ pub struct SubagentStartedData { /// Whether the sub-agent runs synchronously or in the background. #[serde(skip_serializing_if = "Option::is_none")] pub execution_mode: Option, - /// Legacy root id of the workflow run that spawned this sub-agent. New consumers should use workflowRunId. - #[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, @@ -6953,6 +7043,12 @@ 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, + /// Delegated agent that owns permission recovery when the owner session multiplexes remote agents. Absent otherwise. + #[serde(skip_serializing_if = "Option::is_none")] + pub owner_agent_id: Option, + /// Execution session that owns permission recovery for this request. Absent on historical events. + #[serde(skip_serializing_if = "Option::is_none")] + pub owner_session_id: Option, /// Permission mode captured when evaluation began. Absent on historical events. #[serde(skip_serializing_if = "Option::is_none")] pub permission_mode: Option, @@ -8002,6 +8098,17 @@ pub struct SessionManagedPluginProgressData { pub plugin_specs: Vec, } +/// Reusable managed permission evaluation context. Treat permissions as runtime-owned policy data and retain it verbatim; it includes source-aware composition metadata. This is a snapshot, not a capability or approval token; only use contexts obtained from trusted policy sources. +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ManagedPermissionsContext { + /// Policy could not be determined; evaluation must deny every operation. + pub fail_closed: bool, + /// Runtime-owned composed permissions object. Absent when no permission policy is configured. + #[serde(skip_serializing_if = "Option::is_none")] + pub permissions: Option, +} + /// Session event "session.managed_settings_resolved". Effective enterprise managed settings applied to the session and their contributing channels. Emitted whenever managed policy is applied or reapplied, including session start, resume, and account switch. This ephemeral live snapshot is delivered to subscribers but not persisted to the session event log; initial resolution occurs before session.start. /// ///
@@ -8013,20 +8120,23 @@ pub struct SessionManagedPluginProgressData { #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] 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. + /// Whether an explicit enterprise policy restriction disables bypass-permissions ("yolo") mode for this session. Deny-wins across layers; an unresolved policy does not force this on by itself. pub bypass_permissions_disabled: bool, /// 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. + /// Whether managed policy could not be determined (e.g. a failed server fetch) and unresolved-policy safeguards remain active. This does not by itself disable bypass-permissions; `bypassPermissionsDisabled` reports only an explicit policy restriction. pub fail_closed: bool, /// The setting keys under enterprise management in the effective managed settings (e.g. `model`, `enabledPlugins`, `permissions`). Empty when no managed settings are in force. pub managed_keys: Vec, /// Whether at least two managed sources supplied permission allowlists, so enforcement intersects them and the flattened settings payload omits `permissions.allow`. #[serde(skip_serializing_if = "Option::is_none")] pub permissions_allow_intersected: Option, + /// Reusable retained managed permission policy for managedSettings.permissions.evaluate, including source composition and session-local injection. Provided by live session snapshots; absent in older events. Refresh the context when managed policy changes. + #[serde(skip_serializing_if = "Option::is_none")] + pub permissions_context: 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, @@ -8887,6 +8997,9 @@ pub enum RemediationAction { /// Permit outbound network access in the sandbox policy. #[serde(rename = "allow_sandbox_outbound")] AllowSandboxOutbound, + /// Discard conversation history back through the blocked request before continuing. + #[serde(rename = "rewind_conversation")] + RewindConversation, /// Unknown variant for forward compatibility. #[default] #[serde(other)] @@ -9452,33 +9565,36 @@ pub enum ProviderQuotaCapacityState { Unknown, } -/// Timing and purpose of a provider quota observation. +/// Identity scope of a monthly usage reading. #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -pub enum ProviderQuotaObservationKind { - /// An explicit account read; consumption may still be subject to service aggregation delay. - #[serde(rename = "account_snapshot")] - AccountSnapshot, - /// A verdict attached before an inference response. May be cached; never a post-charge balance. - #[serde(rename = "admission_state")] - AdmissionState, +pub enum ProviderMonthlyUsageScope { + /// Consumption of the authenticated user within the parent observation's service and returned cycle. + #[serde(rename = "user")] + User, + /// The service did not establish a recognized usage scope. + #[serde(rename = "unknown")] + UnknownValue, /// Unknown variant for forward compatibility. #[default] #[serde(other)] Unknown, } -/// Interpretation permitted for independently reported quantities. +/// Availability of a service's monthly consumption reading. #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -pub enum ProviderQuotaQuantityKind { - /// Quantities form an authoritative available-of-entitled budget suitable for percentage presentation. - #[serde(rename = "authoritative_budget")] - AuthoritativeBudget, - /// Raw advisory quantities; do not infer permission, usage percentage, reset cadence, or post-charge balance. - #[serde(rename = "advisory_balance")] - AdvisoryBalance, - /// No quantity measurement, as with admission-only response headers. - #[serde(rename = "none")] - None, +pub enum ProviderMonthlyUsageState { + /// The service reported a usable consumption reading, including an explicit zero. + #[serde(rename = "available")] + Available, + /// The service found no usage policy; no consumption amount is known. + #[serde(rename = "no_policy")] + NoPolicy, + /// The read failed or its data could not be used; consumption is unknown. + #[serde(rename = "unavailable")] + Unavailable, + /// The service reported an unrecognized usage state. + #[serde(rename = "unknown")] + UnknownValue, /// Unknown variant for forward compatibility. #[default] #[serde(other)] @@ -9506,6 +9622,39 @@ pub enum ProviderQuotaUnit { Unknown, } +/// Timing and purpose of a provider quota observation. +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum ProviderQuotaObservationKind { + /// An explicit account read; consumption may still be subject to service aggregation delay. + #[serde(rename = "account_snapshot")] + AccountSnapshot, + /// A verdict attached before an inference response. May be cached; never a post-charge balance. + #[serde(rename = "admission_state")] + AdmissionState, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, +} + +/// Interpretation permitted for independently reported quantities. +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum ProviderQuotaQuantityKind { + /// Quantities form an authoritative available-of-entitled budget suitable for percentage presentation. + #[serde(rename = "authoritative_budget")] + AuthoritativeBudget, + /// Raw advisory quantities; do not infer permission, usage percentage, reset cadence, or post-charge balance. + #[serde(rename = "advisory_balance")] + AdvisoryBalance, + /// No quantity measurement, as with admission-only response headers. + #[serde(rename = "none")] + None, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, +} + /// What initiated a conversation compaction #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] pub enum CompactionTrigger { @@ -10108,6 +10257,24 @@ pub enum AssistantUsageApiEndpoint { Unknown, } +/// Content-Encoding applied to the request body sent on the wire +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum ModelCallRequestBodyEncoding { + /// The request body was sent uncompressed (including every WebSocket request). + #[serde(rename = "identity")] + Identity, + /// The request body was sent gzip-compressed. + #[serde(rename = "gzip")] + Gzip, + /// The request body was sent zstd-compressed. + #[serde(rename = "zstd")] + Zstd, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, +} + /// Transport used for a successful model call #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] pub enum AssistantUsageTransport { @@ -10123,6 +10290,51 @@ pub enum AssistantUsageTransport { Unknown, } +/// Normalized cause of the WebSocket failure that triggered the HTTP fallback +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum ModelCallWebSocketFallbackErrorKind { + /// DNS resolution of the WebSocket host failed. + #[serde(rename = "dns")] + Dns, + /// The TLS handshake or a TLS record failed. + #[serde(rename = "tls")] + Tls, + /// A WebSocket connect or read timed out. + #[serde(rename = "timeout")] + Timeout, + /// The peer refused the TCP connection. + #[serde(rename = "connection_refused")] + ConnectionRefused, + /// The connection was reset, aborted, or ended mid-stream without a close. + #[serde(rename = "connection_reset")] + ConnectionReset, + /// The peer closed the WebSocket. + #[serde(rename = "closed_by_peer")] + ClosedByPeer, + /// The runtime closed the WebSocket itself. + #[serde(rename = "closed_locally")] + ClosedLocally, + /// The socket was already closed before it was used. + #[serde(rename = "not_connected")] + NotConnected, + /// The upgrade or request was rejected with an HTTP status. + #[serde(rename = "http_status")] + HttpStatus, + /// A WebSocket protocol violation or an undecodable frame. + #[serde(rename = "protocol")] + Protocol, + /// The WebSocket request could not be built. + #[serde(rename = "configuration")] + Configuration, + /// A failure that maps onto none of the other kinds. + #[serde(rename = "other")] + Other, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, +} + /// Why a WebSocket-capable model call was carried by the HTTP fallback #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] pub enum ModelCallWebSocketFallbackReason { diff --git a/rust/src/hooks.rs b/rust/src/hooks.rs index 3b2af37cb1..37a0fc0423 100644 --- a/rust/src/hooks.rs +++ b/rust/src/hooks.rs @@ -21,6 +21,38 @@ pub struct HookContext { pub session_id: SessionId, } +/// Runtime-provided actor metadata for a hook invocation. +/// +/// Available through [`SessionHooks::on_hook_with_actor`] without changing +/// legacy hook input structs or their construction patterns. +#[derive(Debug, Clone, Default, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct HookActor { + #[serde(default)] + agent_id: Option, + #[serde(default)] + agent_type: Option, + #[serde(default)] + parent_agent_id: Option, +} + +impl HookActor { + /// Subagent actor ID, absent for root hooks and older runtimes. + pub fn agent_id(&self) -> Option<&str> { + self.agent_id.as_deref() + } + + /// Subagent type, when supplied by the runtime. + pub fn agent_type(&self) -> Option<&str> { + self.agent_type.as_deref() + } + + /// Immediate parent actor ID, when supplied by the runtime. + pub fn parent_agent_id(&self) -> Option<&str> { + self.parent_agent_id.as_deref() + } +} + /// Input for the `preToolUse` hook — received before a tool executes. #[derive(Debug, Clone, Deserialize)] #[serde(rename_all = "camelCase")] @@ -271,8 +303,9 @@ pub struct SessionEndInput { /// The last assistant message. #[serde(default)] pub final_message: Option, - /// Error message, if the session ended due to an error. - #[serde(default)] + /// Error message, if the session ended due to an error. The CLI sends an + /// error object here (`{"message"}`); see `HookErrorWire`. + #[serde(default, deserialize_with = "optional_hook_error_text")] pub error: Option, } @@ -302,7 +335,9 @@ pub struct ErrorOccurredInput { /// Working directory. #[serde(rename = "cwd")] pub working_directory: PathBuf, - /// The error message. + /// The error message. The CLI sends an error object here + /// (`{"name", "message", "stack"}`); see `HookErrorWire`. + #[serde(deserialize_with = "hook_error_text")] pub error: String, /// Context where the error occurred: `"model_call"`, `"tool_execution"`, `"system"`, `"user_input"`. pub error_context: String, @@ -310,6 +345,46 @@ pub struct ErrorOccurredInput { pub recoverable: bool, } +/// The two wire shapes of a hook input's `error` field. +/// +/// The CLI sends the `error` of the `errorOccurred` and `sessionEnd` inputs as +/// an error object (`{"name", "message", "stack"}`), while the public fields +/// stay strings. A string is the text, an object's string `message` is the +/// text, and an object without one keeps its compact JSON form. +#[derive(Deserialize)] +#[serde(untagged)] +enum HookErrorWire { + Text(String), + Object(serde_json::Map), +} + +impl HookErrorWire { + fn into_text(self) -> String { + match self { + Self::Text(text) => text, + Self::Object(mut object) => match object.get_mut("message") { + Some(Value::String(message)) => std::mem::take(message), + _ => Value::Object(object).to_string(), + }, + } + } +} + +fn hook_error_text<'de, D>(deserializer: D) -> Result +where + D: serde::Deserializer<'de>, +{ + HookErrorWire::deserialize(deserializer).map(HookErrorWire::into_text) +} + +fn optional_hook_error_text<'de, D>(deserializer: D) -> Result, D::Error> +where + D: serde::Deserializer<'de>, +{ + Option::::deserialize(deserializer) + .map(|wire| wire.map(HookErrorWire::into_text)) +} + /// Output for the `errorOccurred` hook. #[derive(Debug, Clone, Default, Serialize)] #[serde(rename_all = "camelCase")] @@ -612,6 +687,14 @@ impl HookOutput { /// sets automatically). #[async_trait] pub trait SessionHooks: Send + Sync + 'static { + /// Receives typed hook input and runtime-provided actor metadata. + /// + /// The default delegates to [`Self::on_hook`], preserving existing + /// whole-event and per-hook implementations. + async fn on_hook_with_actor(&self, event: HookEvent, _actor: HookActor) -> HookOutput { + self.on_hook(event).await + } + /// Top-level dispatch. The default implementation fans out to the /// per-hook methods below; override this only if you want a single /// matching point across all hook types. @@ -818,6 +901,7 @@ pub(crate) async fn dispatch_hook( let ctx = HookContext { session_id: session_id.clone(), }; + let actor_input = raw_input.clone(); let event = match hook_type { "preToolUse" => { @@ -879,7 +963,8 @@ pub(crate) async fn dispatch_hook( }; let dispatch_start = Instant::now(); - let output = hooks.on_hook(event).await; + let actor = serde_json::from_value(actor_input)?; + let output = hooks.on_hook_with_actor(event, actor).await; tracing::debug!( elapsed_ms = dispatch_start.elapsed().as_millis(), session_id = %session_id, diff --git a/rust/src/hooks/tests.rs b/rust/src/hooks/tests.rs index e8a8179c41..6294c70bcd 100644 --- a/rust/src/hooks/tests.rs +++ b/rust/src/hooks/tests.rs @@ -8,6 +8,234 @@ use super::*; struct TestHooks; +#[test] +fn legacy_inputs_preserve_exact_literals_and_destructuring() { + macro_rules! legacy_input { + ($input:ident { $($field:ident: $value:expr),* $(,)? }) => { + let input = $input { + session_id: "root".to_owned(), + timestamp: 1.0, + working_directory: PathBuf::from("work"), + $($field: $value,)* + }; + let $input { session_id, timestamp, working_directory, $($field,)* } = input; + assert_eq!(session_id, "root"); + assert_eq!(timestamp, 1.0); + assert_eq!(working_directory, PathBuf::from("work")); + let _ = ($($field,)*); + }; + } + legacy_input!(PreToolUseInput { + tool_name: "probe".to_owned(), + tool_args: serde_json::json!({}) + }); + legacy_input!(PreMcpToolCallInput { + server_name: "probe".to_owned(), + tool_name: "probe".to_owned(), + arguments: serde_json::json!({}), + tool_call_id: None, + meta: None + }); + legacy_input!(PostToolUseInput { + tool_name: "probe".to_owned(), + tool_args: serde_json::json!({}), + tool_result: serde_json::json!({}) + }); + legacy_input!(PostToolUseFailureInput { + tool_name: "probe".to_owned(), + tool_args: serde_json::json!({}), + error: "probe".to_owned() + }); + legacy_input!(UserPromptSubmittedInput { + prompt: "probe".to_owned() + }); + legacy_input!(UserPromptTransformedInput { + prompt: "probe".to_owned(), + transformed_prompt: "probe".to_owned() + }); + legacy_input!(SessionStartInput { + source: "new".to_owned(), + initial_prompt: None + }); + legacy_input!(SessionEndInput { + reason: "complete".to_owned(), + final_message: None, + error: None + }); + legacy_input!(ErrorOccurredInput { + error: "probe".to_owned(), + error_context: "model_call".to_owned(), + recoverable: false + }); + legacy_input!(AgentStopInput { + stop_reason: None, + transcript_path: None, + stop_hook_active: None + }); + legacy_input!(SubagentStartInput { + transcript_path: PathBuf::from("parent.jsonl"), + agent_name: "explore".to_owned(), + agent_display_name: None, + agent_description: None + }); + legacy_input!(SubagentStopInput { + transcript_path: PathBuf::from("parent.jsonl"), + agent_name: "explore".to_owned(), + agent_type: "explore".to_owned(), + agent_id: None, + agent_display_name: None, + agent_description: None, + stop_reason: "end_turn".to_owned(), + response: "complete".to_owned() + }); +} + +#[tokio::test] +async fn typed_dispatch_retains_optional_child_identity_and_old_root_inputs() { + struct Capture(std::sync::Mutex>); + #[async_trait] + impl SessionHooks for Capture { + async fn on_hook_with_actor(&self, event: HookEvent, actor: HookActor) -> HookOutput { + *self.0.lock().unwrap() = Some((format!("{event:?}"), actor)); + HookOutput::None + } + } + let hooks = Capture(std::sync::Mutex::new(None)); + for hook in HOOK_IDENTITY_TYPES { + for enriched in [true, false] { + dispatch_hook( + &hooks, + &SessionId::new("callback-owner"), + hook, + hook_identity_input(hook, enriched), + ) + .await + .unwrap(); + let (actual, actor) = hooks.0.lock().unwrap().take().unwrap(); + assert!( + actual.contains("reported-session") && actual.contains("callback-owner"), + "{hook}: {actual}" + ); + assert_eq!( + actor.agent_id(), + enriched.then_some("child-actor"), + "{hook}" + ); + assert_eq!( + actor.agent_type(), + (enriched || hook == "subagentStop").then_some("explore"), + "{hook}" + ); + assert_eq!( + actor.parent_agent_id(), + enriched.then_some("parent-actor"), + "{hook}" + ); + } + } +} + +const HOOK_IDENTITY_TYPES: [&str; 12] = [ + "preToolUse", + "preMcpToolCall", + "postToolUse", + "postToolUseFailure", + "userPromptSubmitted", + "userPromptTransformed", + "sessionStart", + "sessionEnd", + "errorOccurred", + "agentStop", + "subagentStart", + "subagentStop", +]; + +fn hook_identity_input(hook: &str, enriched: bool) -> Value { + let mut input = serde_json::json!({ + "sessionId": "reported-session", "timestamp": 1, "cwd": "work", + "toolName": "probe", "toolArgs": {}, "toolResult": {}, "error": "probe", + "serverName": "probe", "arguments": {}, "prompt": "probe", + "transformedPrompt": "probe", "source": "new", "reason": "complete", + "errorContext": "tool_execution", "recoverable": false, + "transcriptPath": "parent.jsonl", "agentName": "explore", + "agentType": "explore", "stopReason": "end_turn", "response": "complete" + }); + if enriched { + input["agentId"] = "child-actor".into(); + input["parentAgentId"] = "parent-actor".into(); + } else if hook != "subagentStop" { + input.as_object_mut().unwrap().remove("agentType"); + } + input +} + +#[tokio::test] +async fn default_actor_dispatch_preserves_legacy_handlers_for_all_hook_inputs() { + struct Legacy(std::sync::Mutex>); + #[async_trait] + impl SessionHooks for Legacy { + async fn on_hook(&self, event: HookEvent) -> HookOutput { + self.0.lock().unwrap().push(format!("{event:?}")); + HookOutput::None + } + } + let legacy = Legacy(std::sync::Mutex::new(Vec::new())); + for hook in HOOK_IDENTITY_TYPES { + for enriched in [true, false] { + let result = dispatch_hook( + &legacy, + &SessionId::new("callback-owner"), + hook, + hook_identity_input(hook, enriched), + ) + .await + .unwrap(); + assert_eq!(result, serde_json::json!({"output": {}}), "{hook}"); + } + } + let events = legacy.0.lock().unwrap(); + assert_eq!(events.len(), 24); + for event in events.iter() { + assert!( + event.contains("reported-session") && event.contains("callback-owner"), + "{event}" + ); + } +} + +#[tokio::test] +async fn actor_metadata_does_not_use_tool_arguments_or_null_fields() { + struct Actorless; + #[async_trait] + impl SessionHooks for Actorless { + async fn on_hook_with_actor(&self, _event: HookEvent, actor: HookActor) -> HookOutput { + assert_eq!(actor.agent_id(), None); + assert_eq!(actor.agent_type(), None); + assert_eq!(actor.parent_agent_id(), None); + HookOutput::None + } + } + for null_fields in [false, true] { + let mut input = hook_identity_input("preToolUse", false); + input["toolArgs"] = serde_json::json!({ + "agentId": "untrusted", "agentType": "untrusted", "parentAgentId": "untrusted" + }); + if null_fields { + for key in ["agentId", "agentType", "parentAgentId"] { + input[key] = Value::Null; + } + } + dispatch_hook( + &Actorless, + &SessionId::new("callback-owner"), + "preToolUse", + input, + ) + .await + .unwrap(); + } +} + #[async_trait] impl SessionHooks for TestHooks { async fn on_hook(&self, event: HookEvent) -> HookOutput { @@ -483,6 +711,73 @@ async fn dispatch_error_occurred() { assert_eq!(result["output"]["retryCount"], 3); } +/// The object-shaped `error` the CLI sends reaches the hook as its `message`. +#[tokio::test] +async fn dispatch_error_occurred_with_the_clis_error_object() { + struct Recorder(std::sync::Mutex>); + #[async_trait] + impl SessionHooks for Recorder { + async fn on_error_occurred( + &self, + input: ErrorOccurredInput, + _ctx: HookContext, + ) -> Option { + *self.0.lock().unwrap() = Some(input); + None + } + } + + let hooks = Recorder(std::sync::Mutex::new(None)); + let input = serde_json::json!({ + "sessionId": "sess-1", + "error": {"name": "Error", "message": "model timeout", "stack": "Error: model timeout"}, + "errorContext": "model_call", + "recoverable": true, + "timestamp": 1790464212617_u64, + "cwd": "/tmp" + }); + let result = dispatch_hook(&hooks, &SessionId::new("sess-1"), "errorOccurred", input) + .await + .unwrap(); + assert_eq!(result["output"], serde_json::json!({})); + let seen = hooks.0.lock().unwrap().take().expect("hook delivered"); + assert_eq!(seen.error, "model timeout"); + assert_eq!(seen.error_context, "model_call"); +} + +#[test] +fn session_end_error_decodes_from_object_string_null_and_absent() { + let base = serde_json::json!({ + "reason": "error", + "sessionId": "sess-1", + "timestamp": 1790464235720_u64, + "cwd": "/tmp" + }); + let with = |error: Option| { + let mut value = base.clone(); + if let Some(error) = error { + value["error"] = error; + } + serde_json::from_value::(value) + .unwrap() + .error + }; + assert_eq!( + with(Some(serde_json::json!({"message": "retries exhausted"}))).as_deref(), + Some("retries exhausted") + ); + assert_eq!( + with(Some(Value::String("boom".to_string()))).as_deref(), + Some("boom") + ); + assert_eq!(with(Some(Value::Null)), None); + assert_eq!(with(None), None); + assert_eq!( + with(Some(serde_json::json!({"code": 7}))).as_deref(), + Some(r#"{"code":7}"#) + ); +} + #[tokio::test] async fn dispatch_agent_stop_block() { struct AgentStopHooks; diff --git a/rust/src/jsonrpc.rs b/rust/src/jsonrpc.rs index 2f69da5ba9..246ff6bdf2 100644 --- a/rust/src/jsonrpc.rs +++ b/rust/src/jsonrpc.rs @@ -268,15 +268,58 @@ fn repair_lone_surrogates(body: &[u8]) -> Option> { /// One framed JSON-RPC message handed to the writer actor. /// -/// `frame` is the fully serialized bytes (header + body); the caller pays -/// the serde cost synchronously before enqueueing so the actor never sees a -/// `Result` from JSON encoding. `ack` resolves once the bytes have been +/// `frame` is the fully serialized frame (header + body, written as +/// [`EncodedFrame::as_bytes`]); the caller pays the serde cost synchronously +/// before enqueueing so the actor never sees a `Result` from JSON encoding. +/// When present, `ack` resolves once the bytes have been /// fully written and flushed (or the underlying I/O reports an error). If /// the caller drops the `oneshot::Receiver`, the actor still completes the /// frame — caller cancellation cannot desync the wire. struct WriteCommand { - frame: Vec, - ack: oneshot::Sender>, + frame: EncodedFrame, + ack: Option>>, +} + +/// Room reserved in front of a frame's body for its header: the header's name, +/// the 20 digits of the longest `usize`, and the blank line. +const FRAME_PREFIX: usize = CONTENT_LENGTH_HEADER.len() + 20 + 4; + +/// One JSON-RPC frame, header and body, in a single buffer: the header is +/// written into room reserved in front of the body and ends right where the +/// body starts. +pub(crate) struct EncodedFrame { + buf: Vec, + start: usize, +} + +impl EncodedFrame { + /// The frame as it goes on the wire: the header, then the body. + pub(crate) fn as_bytes(&self) -> &[u8] { + &self.buf[self.start..] + } +} + +/// Encodes one JSON-RPC frame, header and body, into a single buffer. +/// +/// The body is serialized once, behind room reserved for the header, and the +/// header is then written into that room. A large message, such as a +/// `sessionFs.readFile` reply carrying a long session's `events.jsonl`, is held +/// once, escaped, instead of as a serialized body plus a framed copy of it. The +/// buffer grows by doubling, as `serde_json::to_vec`'s does, so it can be up to +/// about twice the frame's size. +pub(crate) fn encode_frame(message: &T) -> Result { + // `serde_json::to_vec` starts at 128 bytes; the body gets the same room here. + let mut buf = Vec::with_capacity(FRAME_PREFIX + 128); + buf.extend_from_slice(&[0; FRAME_PREFIX]); + serde_json::to_writer(&mut buf, message)?; + let digits = (buf.len() - FRAME_PREFIX).to_string(); + let start = FRAME_PREFIX - CONTENT_LENGTH_HEADER.len() - digits.len() - 4; + let (name, rest) = buf[start..FRAME_PREFIX].split_at_mut(CONTENT_LENGTH_HEADER.len()); + let (length, blank_line) = rest.split_at_mut(digits.len()); + name.copy_from_slice(CONTENT_LENGTH_HEADER.as_bytes()); + length.copy_from_slice(digits.as_bytes()); + blank_line.copy_from_slice(b"\r\n\r\n"); + Ok(EncodedFrame { buf, start }) } /// Inbound requests that honor `$/cancelRequest`. @@ -412,9 +455,12 @@ impl JsonRpcClient { host_notifications: Option>, ) -> Self { let (write_tx, write_rx) = mpsc::unbounded_channel::(); + let connection_closed = CancellationToken::new(); let writer_span = tracing::error_span!("jsonrpc_write_loop"); - let write_task = tokio::spawn(Self::write_loop(writer, write_rx).instrument(writer_span)); + let write_task = tokio::spawn( + Self::write_loop(writer, write_rx, connection_closed.clone()).instrument(writer_span), + ); let client = Self { request_id: AtomicU64::new(1), @@ -423,7 +469,7 @@ impl JsonRpcClient { notification_tx, request_tx, request_handlers: Arc::new(RwLock::new(HashMap::new())), - connection_closed: CancellationToken::new(), + connection_closed, cancellable_requests: Arc::new(CancellableRequests::default()), read_task: Mutex::new(None), write_task: Mutex::new(Some(write_task)), @@ -518,19 +564,30 @@ impl JsonRpcClient { async fn write_loop( mut writer: impl AsyncWrite + Unpin + Send + 'static, mut rx: mpsc::UnboundedReceiver, + connection_closed: CancellationToken, ) { while let Some(WriteCommand { frame, ack }) = rx.recv().await { let result = async { - writer.write_all(&frame).await?; + writer.write_all(frame.as_bytes()).await?; writer.flush().await?; Ok::<_, std::io::Error>(()) } .await; - // Caller may have dropped the ack receiver (e.g. their // `await` was cancelled); that's fine — we still completed // the write, which was the whole point. - let _ = ack.send(result); + match ack { + Some(ack) => { + let _ = ack.send(result); + } + None => { + if let Err(error) = result { + error!(%error, "router rejection write failed; closing connection"); + connection_closed.cancel(); + break; + } + } + } } } @@ -550,7 +607,12 @@ impl JsonRpcClient { let (connection_closed, cancellable_requests) = connection; loop { - match Self::read_message(&mut reader).await { + let message = tokio::select! { + biased; + _ = connection_closed.cancelled() => break, + message = Self::read_message(&mut reader) => message, + }; + match message { Ok(Some(message)) => match message { JsonRpcMessage::Response(mut response) => { let id = response.id; @@ -904,27 +966,33 @@ impl JsonRpcClient { Self::write_message(&self.write_tx, message).await } + /// Commit a router rejection to the existing writer actor without retaining + /// a delivery waiter. The actor reports write failure and closes the connection. + pub(crate) fn enqueue_response(&self, response: &JsonRpcResponse) -> Result<(), Error> { + Self::enqueue_message(&self.write_tx, response, None) + } + async fn write_message( write_tx: &mpsc::UnboundedSender, message: &T, ) -> Result<(), Error> { - let body = serde_json::to_vec(message)?; - let mut frame = Vec::with_capacity(CONTENT_LENGTH_HEADER.len() + 16 + body.len() + 4); - frame.extend_from_slice(CONTENT_LENGTH_HEADER.as_bytes()); - frame.extend_from_slice(body.len().to_string().as_bytes()); - frame.extend_from_slice(b"\r\n\r\n"); - frame.extend_from_slice(&body); + let frame = encode_frame(message)?; + Self::send_frame(write_tx, frame).await + } - let (ack_tx, ack_rx) = oneshot::channel(); - write_tx - .send(WriteCommand { frame, ack: ack_tx }) - .map_err(|_| { - Error::from(std::io::Error::new( - std::io::ErrorKind::BrokenPipe, - "writer actor has shut down", - )) - })?; + /// Send a frame already encoded with [`encode_frame`] and wait for the + /// writer actor's acknowledgement, so a caller can drop what it encoded + /// from before the frame is written. + pub(crate) async fn write_frame(&self, frame: EncodedFrame) -> Result<(), Error> { + Self::send_frame(&self.write_tx, frame).await + } + async fn send_frame( + write_tx: &mpsc::UnboundedSender, + frame: EncodedFrame, + ) -> Result<(), Error> { + let (ack_tx, ack_rx) = oneshot::channel(); + Self::enqueue_frame(write_tx, frame, Some(ack_tx))?; match ack_rx.await { Ok(Ok(())) => Ok(()), Ok(Err(e)) => Err(Error::from(e)), @@ -934,6 +1002,32 @@ impl JsonRpcClient { ))), } } + + fn enqueue_message( + write_tx: &mpsc::UnboundedSender, + message: &T, + ack: Option>>, + ) -> Result<(), Error> { + let frame = encode_frame(message)?; + Self::enqueue_frame(write_tx, frame, ack) + } + + /// Hand an encoded frame to the writer actor. When present, `ack` resolves + /// once the frame is flushed. + fn enqueue_frame( + write_tx: &mpsc::UnboundedSender, + frame: EncodedFrame, + ack: Option>>, + ) -> Result<(), Error> { + write_tx.send(WriteCommand { frame, ack }).map_err(|_| { + Error::from(std::io::Error::new( + std::io::ErrorKind::BrokenPipe, + "writer actor has shut down", + )) + })?; + + Ok(()) + } } impl Drop for JsonRpcClient { diff --git a/rust/src/jsonrpc/tests.rs b/rust/src/jsonrpc/tests.rs index 0b7f154c83..4242151a49 100644 --- a/rust/src/jsonrpc/tests.rs +++ b/rust/src/jsonrpc/tests.rs @@ -138,6 +138,53 @@ fn request_serializes_camel_case() { assert!(json.contains(r#""method":"ping""#)); } +/// The frame the published framing wrote: the header, then `to_vec`'s body. +fn published_frame(message: &T) -> Vec { + let body = serde_json::to_vec(message).unwrap(); + let mut frame = format!("Content-Length: {}\r\n\r\n", body.len()).into_bytes(); + frame.extend_from_slice(&body); + frame +} + +#[test] +fn encode_frame_writes_the_published_frame() { + let message = serde_json::json!({ + "jsonrpc": "2.0", + "id": 7, + "result": {"content": "a \"quoted\" line\n"} + }); + assert_eq!( + encode_frame(&message).unwrap().as_bytes(), + published_frame(&message) + ); + // The length gains a digit at 10, 100, 1000 and 10000 bytes. Each body is a + // JSON string: its characters and two quotes. + for body_len in [9, 10, 99, 100, 999, 1000, 9999, 10000] { + let message = "x".repeat(body_len - 2); + let frame = encode_frame(&message).unwrap(); + assert!( + frame + .as_bytes() + .starts_with(format!("Content-Length: {body_len}\r\n\r\n").as_bytes()) + ); + assert_eq!(frame.as_bytes(), published_frame(&message)); + } +} + +#[test] +fn encode_frame_writes_the_header_into_the_room_before_the_body() { + // A 100-byte body: a JSON string of 98 characters. + let message = "x".repeat(98); + let frame = encode_frame(&message).unwrap(); + let header: &[u8] = b"Content-Length: 100\r\n\r\n"; + assert_eq!(frame.start, FRAME_PREFIX - header.len()); + assert_eq!(&frame.buf[frame.start..FRAME_PREFIX], header); + assert_eq!( + &frame.buf[FRAME_PREFIX..], + serde_json::to_vec(&message).unwrap().as_slice() + ); +} + #[test] fn notification_without_params_omits_field() { let n = JsonRpcNotification { diff --git a/rust/src/lib.rs b/rust/src/lib.rs index 93790a94b8..6db5bf41b0 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -1634,12 +1634,17 @@ impl Client { if !use_logged_in_user { args.push("--no-auto-login".to_string()); } + let integration_id = std::env::var("GITHUB_COPILOT_INTEGRATION_ID") + .ok() + .map(|value| value.trim().to_owned()) + .filter(|value| !value.is_empty()); 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(), + integration_id.as_deref(), environment, args, )?; @@ -2581,6 +2586,11 @@ impl Client { self.inner.rpc.write(response).await } + /// Send a frame already encoded with `jsonrpc::encode_frame`. + pub(crate) async fn send_frame(&self, frame: jsonrpc::EncodedFrame) -> Result<()> { + self.inner.rpc.write_frame(frame).await + } + /// Reconstruct a [`Client`] handle from a shared inner pointer. pub(crate) fn from_inner(inner: Arc) -> Self { Self { diff --git a/rust/src/process_tree.rs b/rust/src/process_tree.rs index ef9d3fc62e..238854bcf9 100644 --- a/rust/src/process_tree.rs +++ b/rust/src/process_tree.rs @@ -56,9 +56,11 @@ mod platform { 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::Foundation::{CloseHandle, ERROR_NO_MORE_ITEMS, HANDLE}; + use windows_sys::Win32::System::Diagnostics::ProcessSnapshotting::{ + HPSS, HPSSWALK, PSS_CAPTURE_THREADS, PSS_THREAD_ENTRY, PSS_WALK_THREADS, + PssCaptureSnapshot, PssFreeSnapshot, PssWalkMarkerCreate, PssWalkMarkerFree, + PssWalkSnapshot, }; use windows_sys::Win32::System::JobObjects::{ AssignProcessToJobObject, CreateJobObjectW, JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE, @@ -66,9 +68,48 @@ mod platform { SetInformationJobObject, TerminateJobObject, }; use windows_sys::Win32::System::Threading::{ - CREATE_NO_WINDOW, CREATE_SUSPENDED, OpenThread, ResumeThread, THREAD_SUSPEND_RESUME, + CREATE_NO_WINDOW, CREATE_SUSPENDED, GetCurrentProcess, GetProcessIdOfThread, OpenThread, + ResumeThread, THREAD_QUERY_LIMITED_INFORMATION, THREAD_SUSPEND_RESUME, }; + #[cfg(test)] + use super::tests::{ + BEFORE_ASSIGNMENT, BEFORE_RESUME, FailurePoint, MARKERS, RESUME_ELAPSED, SNAPSHOTS, + THREADS_EXAMINED, fail_at, + }; + + struct ThreadSnapshot(HPSS); + + impl Drop for ThreadSnapshot { + fn drop(&mut self) { + // SAFETY: this process uniquely owns the local snapshot descriptor. + let status = unsafe { PssFreeSnapshot(GetCurrentProcess(), self.0) }; + if let Err(error) = check_status(status) { + tracing::warn!(%error, "failed to free CLI thread snapshot"); + } + #[cfg(test)] + if status == 0 { + SNAPSHOTS.set((SNAPSHOTS.get().0, SNAPSHOTS.get().1 + 1)); + } + } + } + + struct WalkMarker(HPSSWALK); + + impl Drop for WalkMarker { + fn drop(&mut self) { + // SAFETY: this value uniquely owns a valid walk marker. + let status = unsafe { PssWalkMarkerFree(self.0) }; + if let Err(error) = check_status(status) { + tracing::warn!(%error, "failed to free CLI thread walk marker"); + } + #[cfg(test)] + if status == 0 { + MARKERS.set((MARKERS.get().0, MARKERS.get().1 + 1)); + } + } + } + struct OwnedHandle(HANDLE); // SAFETY: Win32 handles may be used and closed from any thread. @@ -97,13 +138,19 @@ mod platform { match attach_and_resume(&child) { Ok(tree) => Ok((child, tree)), Err(error) => { - let _ = child.start_kill(); + if let Err(kill_error) = child.start_kill() { + tracing::warn!(%kill_error, "failed to terminate suspended CLI child"); + } Err(error) } } } fn attach_and_resume(child: &Child) -> io::Result { + #[cfg(test)] + if let Some(hook) = BEFORE_ASSIGNMENT.with_borrow_mut(Option::take) { + hook(child)?; + } // SAFETY: null security attributes and name create a private, // non-inheritable Job Object. let raw_job = unsafe { CreateJobObjectW(ptr::null(), ptr::null()) }; @@ -138,50 +185,122 @@ mod platform { 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") - })?)?; + #[cfg(test)] + if let Some(hook) = BEFORE_RESUME.with_borrow_mut(Option::take) { + hook(child, job.0)?; + } + #[cfg(test)] + let resume_start = std::time::Instant::now(); + let result = resume_initial_thread( + process.cast(), + child.id().ok_or_else(|| { + io::Error::new(io::ErrorKind::NotFound, "CLI exited before thread resume") + })?, + ); + #[cfg(test)] + RESUME_ELAPSED.set(resume_start.elapsed()); + result?; 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()); + pub(super) fn check_status(status: u32) -> io::Result<()> { + if status == 0 { + Ok(()) + } else { + Err(io::Error::from_raw_os_error(status as i32)) } - 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) }; + } + + pub(super) fn resume_initial_thread(process: HANDLE, pid: u32) -> io::Result<()> { + #[cfg(test)] + THREADS_EXAMINED.set(0); + #[cfg(test)] + fail_at(FailurePoint::Capture)?; + let mut snapshot = ptr::null_mut(); + // SAFETY: the suspended child owns the live process handle throughout + // capture and resume. Only its thread information is captured. + check_status(unsafe { + PssCaptureSnapshot(process, PSS_CAPTURE_THREADS, 0, &mut snapshot) + })?; + let snapshot = ThreadSnapshot(snapshot); + #[cfg(test)] + SNAPSHOTS.set((SNAPSHOTS.get().0 + 1, SNAPSHOTS.get().1)); + + #[cfg(test)] + fail_at(FailurePoint::Marker)?; + let mut marker = ptr::null_mut(); + // SAFETY: Windows initializes the marker with its default allocator. + check_status(unsafe { PssWalkMarkerCreate(ptr::null(), &mut marker) })?; + let marker = WalkMarker(marker); + #[cfg(test)] + MARKERS.set((MARKERS.get().0 + 1, MARKERS.get().1)); + + loop { + #[cfg(test)] + fail_at(FailurePoint::Walk)?; + let mut entry = PSS_THREAD_ENTRY::default(); + // SAFETY: the aligned entry has the layout and size required by + // PSS_WALK_THREADS; both snapshot and marker remain owned. + let status = unsafe { + PssWalkSnapshot( + snapshot.0, + PSS_WALK_THREADS, + marker.0, + ptr::from_mut(&mut entry).cast(), + size_of::() as u32, + ) + }; + if status == ERROR_NO_MORE_ITEMS { + return Err(io::Error::new( + io::ErrorKind::NotFound, + "CLI initial thread was not found in its process snapshot", + )); + } + check_status(status)?; + #[cfg(test)] + THREADS_EXAMINED.set(THREADS_EXAMINED.get() + 1); + if entry.ProcessId == pid { + #[cfg(test)] + fail_at(FailurePoint::OpenThread)?; + // SAFETY: the thread id came from the child's process snapshot. + let raw_thread = unsafe { + OpenThread( + THREAD_SUSPEND_RESUME | THREAD_QUERY_LIMITED_INFORMATION, + 0, + entry.ThreadId, + ) + }; 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(()); + return resume_owned_thread(thread.0, pid); } - // 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", - )) + pub(super) fn resume_owned_thread(thread: HANDLE, pid: u32) -> io::Result<()> { + #[cfg(test)] + fail_at(FailurePoint::QueryThread)?; + // SAFETY: the caller owns this thread handle with limited-query rights. + let owner = unsafe { GetProcessIdOfThread(thread) }; + if owner == 0 { + return Err(io::Error::last_os_error()); + } + if owner != pid { + return Err(io::Error::new( + io::ErrorKind::PermissionDenied, + "CLI initial thread no longer belongs to the spawned process", + )); + } + #[cfg(test)] + fail_at(FailurePoint::Resume)?; + // SAFETY: the open handle's ownership was verified, and the root's + // initial thread remains suspended until Job assignment completes. + if unsafe { ResumeThread(thread) } == u32::MAX { + return Err(io::Error::last_os_error()); + } + Ok(()) } impl Tree { @@ -195,3 +314,6 @@ mod platform { } } } + +#[cfg(all(test, windows, feature = "runtime"))] +mod tests; diff --git a/rust/src/process_tree/tests.rs b/rust/src/process_tree/tests.rs new file mode 100644 index 0000000000..5106081dac --- /dev/null +++ b/rust/src/process_tree/tests.rs @@ -0,0 +1,318 @@ +#![cfg(test)] + +use std::os::windows::io::{AsRawHandle, FromRawHandle, OwnedHandle}; +use std::os::windows::process::CommandExt; +use std::path::{Path, PathBuf}; +use std::sync::Arc; +use std::time::{Duration, Instant}; +use std::{io, ptr}; + +use tokio::process::{Child, Command}; +use windows_sys::Win32::Foundation::{ + DUPLICATE_SAME_ACCESS, DuplicateHandle, ERROR_ACCESS_DENIED, HANDLE, +}; +use windows_sys::Win32::System::JobObjects::IsProcessInJob; +use windows_sys::Win32::System::Threading::{ + GetCurrentProcess, GetCurrentProcessId, GetCurrentThreadId, OpenThread, + THREAD_QUERY_LIMITED_INFORMATION, THREAD_SUSPEND_RESUME, WaitForSingleObject, +}; + +use super::platform::{check_status, resume_initial_thread, resume_owned_thread}; + +#[derive(Clone, Copy, PartialEq)] +pub(super) enum FailurePoint { + Capture, + Marker, + Walk, + OpenThread, + QueryThread, + Resume, +} + +type BeforeAssignment = Box io::Result<()>>; +type BeforeResume = Box io::Result<()>>; + +thread_local! { + pub(super) static THREADS_EXAMINED: std::cell::Cell = const { std::cell::Cell::new(0) }; + pub(super) static RESUME_ELAPSED: std::cell::Cell = + const { std::cell::Cell::new(Duration::ZERO) }; + pub(super) static BEFORE_ASSIGNMENT: std::cell::RefCell> = + const { std::cell::RefCell::new(None) }; + pub(super) static BEFORE_RESUME: std::cell::RefCell> = + const { std::cell::RefCell::new(None) }; + static FAILURE: std::cell::Cell> = + const { std::cell::Cell::new(None) }; + pub(super) static SNAPSHOTS: std::cell::Cell<(u32, u32)> = + const { std::cell::Cell::new((0, 0)) }; + pub(super) static MARKERS: std::cell::Cell<(u32, u32)> = + const { std::cell::Cell::new((0, 0)) }; +} + +pub(super) fn fail_at(point: FailurePoint) -> io::Result<()> { + if FAILURE.get() == Some(point) { + Err(io::Error::from_raw_os_error(ERROR_ACCESS_DENIED as i32)) + } else { + Ok(()) + } +} + +struct ResetHooks; + +impl Drop for ResetHooks { + fn drop(&mut self) { + FAILURE.set(None); + BEFORE_ASSIGNMENT.with_borrow_mut(|hook| *hook = None); + BEFORE_RESUME.with_borrow_mut(|hook| *hook = None); + } +} + +fn command() -> Command { + let mut command = Command::new( + PathBuf::from(std::env::var_os("SystemRoot").expect("SystemRoot")) + .join("System32") + .join("cmd.exe"), + ); + command.args(["/d", "/c"]).kill_on_drop(true); + command +} + +fn marker_command(marker: &Path) -> Command { + let mut command = command(); + command + .as_std_mut() + .raw_arg(format!("echo ready>\"{}\"", marker.display())); + command +} + +fn retain_process(child: &Child) -> OwnedHandle { + let mut handle = ptr::null_mut(); + // SAFETY: the child owns its live process handle; the duplicate is independently owned. + let result = unsafe { + DuplicateHandle( + GetCurrentProcess(), + child.raw_handle().expect("live child").cast(), + GetCurrentProcess(), + &mut handle, + 0, + 0, + DUPLICATE_SAME_ACCESS, + ) + }; + assert_ne!(result, 0, "{}", io::Error::last_os_error()); + // SAFETY: DuplicateHandle returned a new uniquely owned process handle. + unsafe { OwnedHandle::from_raw_handle(handle) } +} + +fn assert_exited(handle: &OwnedHandle) { + // SAFETY: the independently owned process handle remains valid. + assert_eq!( + unsafe { WaitForSingleObject(handle.as_raw_handle().cast(), 5000) }, + 0, + "owned suspended root must terminate" + ); +} + +#[tokio::test] +async fn initial_thread_lookup_is_process_scoped() { + let mut command = command(); + command.args(["exit", "/b", "0"]); + let start = Instant::now(); + let (mut child, tree) = super::spawn(&mut command).expect("spawn owned suspended child"); + let spawn_elapsed = start.elapsed(); + let status = tokio::time::timeout(Duration::from_secs(5), child.wait()) + .await + .expect("initial thread must execute") + .expect("reap child"); + drop(tree); + assert!(status.success()); + eprintln!( + "owned lookup: spawn_ms={:.3} resume_ms={:.3} entries={}", + spawn_elapsed.as_secs_f64() * 1000.0, + RESUME_ELAPSED.get().as_secs_f64() * 1000.0, + THREADS_EXAMINED.get() + ); + assert_eq!( + THREADS_EXAMINED.get(), + 1, + "lookup must not walk unrelated machine-wide threads" + ); +} + +#[tokio::test] +async fn initial_thread_executes_only_after_private_job_assignment() { + let _reset = ResetHooks; + let dir = tempfile::tempdir().expect("fixture directory"); + let marker = dir.path().join("executed.txt"); + BEFORE_ASSIGNMENT.with_borrow_mut(|hook| { + let marker = marker.clone(); + *hook = Some(Box::new(move |_| { + assert!( + !marker.exists(), + "suspended root cannot execute before assignment" + ); + Ok(()) + })); + }); + BEFORE_RESUME.with_borrow_mut(|hook| { + let marker = marker.clone(); + *hook = Some(Box::new(move |child, job| { + assert!( + !marker.exists(), + "suspended initial thread must not have executed" + ); + let mut assigned = 0; + // SAFETY: both handles remain owned throughout the hook. + assert_ne!( + unsafe { + IsProcessInJob( + child.raw_handle().expect("live child").cast(), + job, + &mut assigned, + ) + }, + 0, + "{}", + io::Error::last_os_error() + ); + assert_ne!( + assigned, 0, + "root must belong to its private Job before resume" + ); + Ok(()) + })); + }); + let (mut child, tree) = super::spawn(&mut marker_command(&marker)).expect("owned spawn"); + let status = tokio::time::timeout(Duration::from_secs(5), child.wait()) + .await + .expect("fixture must execute its initial thread") + .expect("reap fixture"); + assert!(status.success()); + assert_eq!( + std::fs::read_to_string(marker) + .expect("executed marker") + .trim(), + "ready" + ); + drop(tree); +} + +#[tokio::test] +async fn lookup_failures_reap_suspended_root_and_release_pss_resources() { + let _reset = ResetHooks; + for point in [ + FailurePoint::Capture, + FailurePoint::Marker, + FailurePoint::Walk, + FailurePoint::OpenThread, + FailurePoint::QueryThread, + FailurePoint::Resume, + ] { + let dir = tempfile::tempdir().expect("fixture directory"); + let marker = dir.path().join("must-not-execute.txt"); + let retained = Arc::new(parking_lot::Mutex::new(None)); + BEFORE_ASSIGNMENT.with_borrow_mut(|hook| { + let retained = retained.clone(); + *hook = Some(Box::new(move |child| { + *retained.lock() = Some(retain_process(child)); + Ok(()) + })); + }); + SNAPSHOTS.set((0, 0)); + MARKERS.set((0, 0)); + FAILURE.set(Some(point)); + let result = super::spawn(&mut marker_command(&marker)); + let error = result.err().expect("injected launch failure"); + assert_eq!(error.raw_os_error(), Some(ERROR_ACCESS_DENIED as i32)); + assert_exited(retained.lock().as_ref().expect("retained suspended root")); + assert!( + !marker.exists(), + "failed lookup must never execute the root" + ); + let snapshots = u32::from(point != FailurePoint::Capture); + let markers = u32::from(point != FailurePoint::Capture && point != FailurePoint::Marker); + assert_eq!(SNAPSHOTS.get(), (snapshots, snapshots)); + assert_eq!(MARKERS.get(), (markers, markers)); + } +} + +#[tokio::test] +async fn pre_assignment_failure_terminates_suspended_root() { + let _reset = ResetHooks; + let dir = tempfile::tempdir().expect("fixture directory"); + let marker = dir.path().join("must-not-execute.txt"); + let retained = Arc::new(parking_lot::Mutex::new(None)); + BEFORE_ASSIGNMENT.with_borrow_mut(|hook| { + let retained = retained.clone(); + *hook = Some(Box::new(move |child| { + *retained.lock() = Some(retain_process(child)); + Err(io::Error::from_raw_os_error(ERROR_ACCESS_DENIED as i32)) + })); + }); + let error = super::spawn(&mut marker_command(&marker)) + .err() + .expect("assignment failure"); + assert_eq!(error.raw_os_error(), Some(ERROR_ACCESS_DENIED as i32)); + assert_exited(retained.lock().as_ref().expect("retained root")); + assert!(!marker.exists()); +} + +#[test] +fn pss_status_uses_returned_win32_error() { + assert!(check_status(0).is_ok()); + assert_eq!( + check_status(ERROR_ACCESS_DENIED) + .expect_err("direct status error") + .raw_os_error(), + Some(ERROR_ACCESS_DENIED as i32) + ); +} + +#[test] +fn invalid_process_capture_returns_error_without_resources() { + let _reset = ResetHooks; + SNAPSHOTS.set((0, 0)); + MARKERS.set((0, 0)); + assert!(resume_initial_thread(ptr::null_mut(), 1).is_err()); + assert_eq!(SNAPSHOTS.get(), (0, 0)); + assert_eq!(MARKERS.get(), (0, 0)); +} + +#[test] +fn foreign_snapshot_entries_cannot_be_resumed() { + let _reset = ResetHooks; + SNAPSHOTS.set((0, 0)); + MARKERS.set((0, 0)); + // SAFETY: the pseudo handle references the current live process. + let error = resume_initial_thread(unsafe { GetCurrentProcess() }, u32::MAX) + .expect_err("no matching owned thread"); + assert_eq!(error.kind(), io::ErrorKind::NotFound); + assert_eq!(SNAPSHOTS.get(), (1, 1)); + assert_eq!(MARKERS.get(), (1, 1)); +} + +#[test] +fn opened_foreign_thread_is_rejected_before_resume() { + let _reset = ResetHooks; + // SAFETY: the thread ID identifies this live test thread. + let handle = unsafe { + OpenThread( + THREAD_QUERY_LIMITED_INFORMATION | THREAD_SUSPEND_RESUME, + 0, + GetCurrentThreadId(), + ) + }; + assert!(!handle.is_null(), "{}", io::Error::last_os_error()); + // SAFETY: OpenThread returned a new uniquely owned handle. + let thread = unsafe { OwnedHandle::from_raw_handle(handle) }; + let error = resume_owned_thread(thread.as_raw_handle().cast(), unsafe { + GetCurrentProcessId().wrapping_add(1) + }) + .expect_err("opened handle belongs to a different process"); + assert_eq!(error.kind(), io::ErrorKind::PermissionDenied); +} + +#[test] +fn invalid_thread_ownership_query_returns_error() { + let _reset = ResetHooks; + assert!(resume_owned_thread(ptr::null_mut(), 1).is_err()); +} diff --git a/rust/src/router.rs b/rust/src/router.rs index 9b0d68464b..dff61da6b1 100644 --- a/rust/src/router.rs +++ b/rust/src/router.rs @@ -302,8 +302,17 @@ impl SessionRouter { // Request routing task (if request_rx is available) if let Some(mut rx) = client.request_rx.lock().take() { let sessions = self.sessions.clone(); + let connection_closed = client.rpc.connection_closed_token(); tokio::spawn(async move { - while let Some(request) = rx.recv().await { + loop { + let request = tokio::select! { + biased; + _ = connection_closed.cancelled() => break, + request = rx.recv() => { + let Some(request) = request else { break }; + request + } + }; if request.method == crate::installation_confirmation::CONFIRM_METHOD { installation_confirmation.dispatch(request); continue; @@ -341,13 +350,14 @@ impl SessionRouter { .map(str::to_owned); let Some(session_id) = session_id else { warn!(method = %request.method, "request missing sessionId"); - send_error( + if !send_error( &weak_client, request.id, error_codes::INVALID_PARAMS, "missing required field: sessionId".to_string(), - ) - .await; + ) { + break; + } continue; }; @@ -368,43 +378,48 @@ impl SessionRouter { method = %request.method, "request for unregistered session" ); - send_error( + if !send_error( &weak_client, request.id, error_codes::INTERNAL_ERROR, format!("Session not found: {session_id}"), - ) - .await; + ) { + break; + } } } + sessions.lock().clear(); }); } } } -async fn send_error( +fn send_error( client: &Weak, request_id: u64, code: i32, message: String, -) { +) -> bool { let Some(inner) = client.upgrade() else { - return; + return false; }; // Retire any cancellation registered for a request that never reaches a handler. drop(inner.rpc.cancellable_requests.claim(request_id)); - let _ = crate::Client::from_inner(inner) - .send_response(&JsonRpcResponse { - jsonrpc: "2.0".to_string(), - id: request_id, - result: None, - error: Some(JsonRpcError { - code, - message, - data: None, - }), - }) - .await; + if let Err(error) = inner.rpc.enqueue_response(&JsonRpcResponse { + jsonrpc: "2.0".to_string(), + id: request_id, + result: None, + error: Some(JsonRpcError { + code, + message, + data: None, + }), + }) { + warn!(%error, "failed to enqueue router rejection; closing connection"); + crate::Client::from_inner(inner).force_stop(); + return false; + } + true } #[cfg(test)] diff --git a/rust/src/router/tests.rs b/rust/src/router/tests.rs index f63ca005f7..997829a50a 100644 --- a/rust/src/router/tests.rs +++ b/rust/src/router/tests.rs @@ -4,8 +4,501 @@ #![cfg(test)] +use std::pin::Pin; +use std::sync::atomic::AtomicBool; +use std::task::{Context, Poll}; +use std::time::Duration; + +use futures_util::task::AtomicWaker; +use serde_json::json; +use tokio::io::{ + AsyncBufReadExt, AsyncReadExt, AsyncWrite, AsyncWriteExt, BufReader, DuplexStream, +}; +use tokio::time::timeout; +use tokio_util::sync::CancellationToken; + use super::*; +const DEADLINE: Duration = Duration::from_secs(5); + +#[derive(Clone, Copy, Debug)] +enum BlockAt { + Write, + Flush, +} + +#[derive(Default)] +struct WriteGate { + released: AtomicBool, + waker: AtomicWaker, + entered: CancellationToken, + dropped: CancellationToken, +} + +impl WriteGate { + fn poll(&self, cx: &mut Context<'_>) -> Poll<()> { + self.waker.register(cx.waker()); + if self.released.load(Ordering::Acquire) { + Poll::Ready(()) + } else { + self.entered.cancel(); + Poll::Pending + } + } + + fn release(&self) { + self.released.store(true, Ordering::Release); + self.waker.wake(); + } +} + +struct GatedWriter { + writer: DuplexStream, + gate: Arc, + block_at: BlockAt, +} + +impl AsyncWrite for GatedWriter { + fn poll_write( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + bytes: &[u8], + ) -> Poll> { + if matches!(self.block_at, BlockAt::Write) && self.gate.poll(cx).is_pending() { + return Poll::Pending; + } + Pin::new(&mut self.writer).poll_write(cx, bytes) + } + + fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + if matches!(self.block_at, BlockAt::Flush) && self.gate.poll(cx).is_pending() { + return Poll::Pending; + } + Pin::new(&mut self.writer).poll_flush(cx) + } + + fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + Pin::new(&mut self.writer).poll_shutdown(cx) + } +} + +impl Drop for GatedWriter { + fn drop(&mut self) { + self.gate.dropped.cancel(); + } +} + +#[derive(Clone, Copy, Debug)] +enum Unroutable { + MissingSessionId, + UnknownSession, + ClosedReceiver, +} + +struct Peer { + client: crate::Client, + input: DuplexStream, + output: BufReader, + gate: Arc, +} + +impl Peer { + fn new(block_at: BlockAt) -> Self { + let (reader, input) = tokio::io::duplex(4096); + let (writer, output) = tokio::io::duplex(4096); + let gate = Arc::new(WriteGate::default()); + let client = crate::Client::from_streams( + reader, + GatedWriter { + writer, + gate: gate.clone(), + block_at, + }, + std::path::PathBuf::from("."), + ) + .unwrap(); + Self { + client, + input, + output: BufReader::new(output), + gate, + } + } + + async fn send(&mut self, message: &impl serde::Serialize) { + let body = serde_json::to_vec(message).unwrap(); + timeout(DEADLINE, async { + self.input + .write_all(format!("Content-Length: {}\r\n\r\n", body.len()).as_bytes()) + .await + .unwrap(); + self.input.write_all(&body).await.unwrap(); + }) + .await + .expect("peer could not inject a complete request"); + } + + async fn reject(&mut self, id: u64, kind: Unroutable) { + let params = match kind { + Unroutable::MissingSessionId => json!({}), + Unroutable::UnknownSession => json!({"sessionId": "gone"}), + Unroutable::ClosedReceiver => { + drop(self.client.register_session(&SessionId::new("closed"))); + json!({"sessionId": "closed"}) + } + }; + self.send(&JsonRpcRequest::new(id, "skillProvider.list", Some(params))) + .await; + } + + async fn read(&mut self) -> JsonRpcResponse { + timeout(DEADLINE, async { + let mut header = String::new(); + self.output.read_line(&mut header).await.unwrap(); + let length = header + .strip_prefix("Content-Length: ") + .expect("missing response header") + .trim() + .parse::() + .unwrap(); + header.clear(); + self.output.read_line(&mut header).await.unwrap(); + assert_eq!(header, "\r\n"); + let mut body = vec![0; length]; + self.output.read_exact(&mut body).await.unwrap(); + serde_json::from_slice(&body).unwrap() + }) + .await + .expect("error frame was not delivered") + } + + async fn stop(&self) { + self.client.force_stop(); + timeout(DEADLINE, self.gate.dropped.cancelled()) + .await + .expect("writer did not exit"); + } +} + +#[track_caller] +fn assert_error(response: JsonRpcResponse, id: u64, kind: Unroutable) { + assert_eq!(response.id, id); + assert!(response.result.is_none()); + let error = response.error.expect("unroutable request received success"); + let (code, message) = match kind { + Unroutable::MissingSessionId => ( + error_codes::INVALID_PARAMS, + "missing required field: sessionId", + ), + Unroutable::UnknownSession => (error_codes::INTERNAL_ERROR, "Session not found: gone"), + Unroutable::ClosedReceiver => (error_codes::INTERNAL_ERROR, "Session not found: closed"), + }; + assert_eq!(error.code, code); + assert_eq!(error.message, message); +} + +#[tokio::test] +async fn stalled_rejection_preserves_live_routing_and_eventual_error_delivery() { + for block_at in [BlockAt::Write, BlockAt::Flush] { + for kind in [ + Unroutable::MissingSessionId, + Unroutable::UnknownSession, + Unroutable::ClosedReceiver, + ] { + let mut peer = Peer::new(block_at); + let mut live = peer + .client + .register_session(&SessionId::new("live")) + .channels + .requests; + peer.reject(101, kind).await; + timeout(DEADLINE, peer.gate.entered.cancelled()) + .await + .unwrap(); + peer.send(&crate::JsonRpcNotification { + jsonrpc: "2.0".into(), + method: "$/cancelRequest".into(), + params: Some(json!({"id": 101})), + }) + .await; + peer.send(&JsonRpcRequest::new( + 102, + "skillProvider.list", + Some(json!({"sessionId": "live"})), + )) + .await; + let routed = timeout(DEADLINE, live.recv()) + .await + .unwrap_or_else(|_| panic!("live callback stalled behind {kind:?} at {block_at:?}")) + .expect("live channel closed"); + assert_eq!(routed.id, 102); + assert!( + peer.client + .inner + .rpc + .cancellable_requests + .claim(101) + .is_none() + ); + assert!( + !peer + .client + .inner + .rpc + .connection_closed_token() + .is_cancelled() + ); + peer.gate.release(); + assert_error(peer.read().await, 101, kind); + peer.stop().await; + } + } +} + +#[tokio::test] +async fn delayed_writer_preserves_a_large_healthy_burst_without_disconnect() { + const BURST: u64 = 512; + for block_at in [BlockAt::Write, BlockAt::Flush] { + let mut peer = Peer::new(block_at); + let mut live = peer + .client + .register_session(&SessionId::new("live")) + .channels + .requests; + peer.reject(100, Unroutable::UnknownSession).await; + timeout(DEADLINE, peer.gate.entered.cancelled()) + .await + .unwrap(); + for id in 101..100 + BURST { + peer.reject(id, Unroutable::UnknownSession).await; + } + peer.send(&JsonRpcRequest::new( + 100 + BURST, + "skillProvider.list", + Some(json!({"sessionId": "live"})), + )) + .await; + assert_eq!( + timeout(DEADLINE, live.recv()).await.unwrap().unwrap().id, + 100 + BURST + ); + assert!( + !peer + .client + .inner + .rpc + .connection_closed_token() + .is_cancelled() + ); + for id in 100..100 + BURST { + assert!( + peer.client + .inner + .rpc + .cancellable_requests + .claim(id) + .is_none() + ); + } + peer.gate.release(); + for id in 100..100 + BURST { + assert_error(peer.read().await, id, Unroutable::UnknownSession); + } + peer.stop().await; + } +} + +#[tokio::test] +async fn pending_rejection_does_not_retain_the_last_client() { + for block_at in [BlockAt::Write, BlockAt::Flush] { + let mut peer = Peer::new(block_at); + let mut live = peer + .client + .register_session(&SessionId::new("live")) + .channels + .requests; + peer.reject(101, Unroutable::UnknownSession).await; + timeout(DEADLINE, peer.gate.entered.cancelled()) + .await + .unwrap(); + let weak = Arc::downgrade(&peer.client.inner); + let gate = peer.gate.clone(); + drop(peer); + assert!( + weak.upgrade().is_none(), + "error delivery retained the client" + ); + timeout(DEADLINE, gate.dropped.cancelled()).await.unwrap(); + assert!(timeout(DEADLINE, live.recv()).await.unwrap().is_none()); + } +} + +#[tokio::test] +async fn force_stop_releases_pending_rejections() { + for kind in [ + Unroutable::MissingSessionId, + Unroutable::UnknownSession, + Unroutable::ClosedReceiver, + ] { + let mut peer = Peer::new(BlockAt::Write); + let mut live = peer + .client + .register_session(&SessionId::new("live")) + .channels + .requests; + peer.reject(101, kind).await; + timeout(DEADLINE, peer.gate.entered.cancelled()) + .await + .unwrap(); + peer.stop().await; + assert!(timeout(DEADLINE, live.recv()).await.unwrap().is_none()); + assert!( + peer.client + .inner + .rpc + .cancellable_requests + .claim(101) + .is_none() + ); + } +} + +#[tokio::test] +async fn client_stop_does_not_await_router_rejection_delivery() { + for block_at in [BlockAt::Write, BlockAt::Flush] { + let mut peer = Peer::new(block_at); + peer.client.inner.router.ensure_started(&peer.client.inner); + peer.reject(101, Unroutable::UnknownSession).await; + timeout(DEADLINE, peer.gate.entered.cancelled()) + .await + .unwrap(); + timeout(DEADLINE, peer.client.stop()) + .await + .expect("stop awaited rejection output") + .unwrap(); + timeout(DEADLINE, peer.gate.dropped.cancelled()) + .await + .unwrap(); + } +} + +#[tokio::test] +async fn late_rejection_delivery_does_not_retire_a_reused_request_id() { + let mut peer = Peer::new(BlockAt::Write); + let mut live = peer + .client + .register_session(&SessionId::new("live")) + .channels + .requests; + peer.reject(101, Unroutable::UnknownSession).await; + timeout(DEADLINE, peer.gate.entered.cancelled()) + .await + .unwrap(); + peer.send(&JsonRpcRequest::new( + 101, + "skillProvider.list", + Some(json!({"sessionId": "live"})), + )) + .await; + assert_eq!( + timeout(DEADLINE, live.recv()).await.unwrap().unwrap().id, + 101 + ); + let pending = peer + .client + .inner + .rpc + .cancellable_requests + .claim(101) + .expect("new request lost cancellation"); + peer.gate.release(); + assert_error(peer.read().await, 101, Unroutable::UnknownSession); + peer.send(&crate::JsonRpcNotification { + jsonrpc: "2.0".into(), + method: "$/cancelRequest".into(), + params: Some(json!({"id": 101})), + }) + .await; + timeout(DEADLINE, pending.cancellation().cancelled()) + .await + .expect("old completion retired new cancellation"); + peer.stop().await; +} + +#[tokio::test] +async fn failed_router_rejection_write_closes_connection_and_wakes_pending_work() { + let mut peer = Peer::new(BlockAt::Write); + let mut live = peer + .client + .register_session(&SessionId::new("live")) + .channels + .requests; + peer.reject(101, Unroutable::UnknownSession).await; + timeout(DEADLINE, peer.gate.entered.cancelled()) + .await + .unwrap(); + let pending = peer + .client + .inner + .rpc + .send_request_with_inline_callback("ping", None, None); + tokio::pin!(pending); + assert!(futures_util::poll!(&mut pending).is_pending()); + let Peer { + client, + input: _input, + output, + gate, + } = peer; + drop(output); + gate.release(); + timeout( + DEADLINE, + client.inner.rpc.connection_closed_token().cancelled(), + ) + .await + .expect("write failure left connection open"); + let error = timeout(DEADLINE, pending) + .await + .expect("pending request did not wake") + .expect_err("request succeeded on broken output"); + assert_eq!(error.kind(), &crate::ErrorKind::Io); + assert!(timeout(DEADLINE, live.recv()).await.unwrap().is_none()); + timeout(DEADLINE, gate.dropped.cancelled()).await.unwrap(); + client.force_stop(); +} + +#[tokio::test] +async fn peer_eof_retires_routing_with_a_committed_rejection_pending() { + let mut peer = Peer::new(BlockAt::Write); + let mut live = peer + .client + .register_session(&SessionId::new("live")) + .channels + .requests; + peer.reject(101, Unroutable::UnknownSession).await; + timeout(DEADLINE, peer.gate.entered.cancelled()) + .await + .unwrap(); + peer.input.shutdown().await.unwrap(); + timeout( + DEADLINE, + peer.client.inner.rpc.connection_closed_token().cancelled(), + ) + .await + .unwrap(); + assert!(timeout(DEADLINE, live.recv()).await.unwrap().is_none()); + assert!( + peer.client + .inner + .rpc + .cancellable_requests + .claim(101) + .is_none() + ); + peer.stop().await; +} + fn session_id() -> SessionId { SessionId::new("router-ownership") } diff --git a/rust/src/session.rs b/rust/src/session.rs index 1ba8325095..3e1620fb70 100644 --- a/rust/src/session.rs +++ b/rust/src/session.rs @@ -3369,9 +3369,12 @@ async fn handle_notification( }; let request = ElicitationRequest { message: elicitation_data.message, + // Forward the wire schema verbatim: the generated type keys + // `properties` by `HashMap`, so re-serializing it scrambles + // the form's field order and drops keys it does not model. requested_schema: elicitation_data .requested_schema - .map(|s| serde_json::to_value(s).unwrap_or(Value::Null)), + .and(notification.event.data.get("requestedSchema").cloned()), mode: elicitation_data.mode.map(|m| match m { crate::generated::session_events::ElicitationRequestedMode::Form => { crate::types::ElicitationMode::Form diff --git a/rust/src/session_fs_dispatch.rs b/rust/src/session_fs_dispatch.rs index 06cbb016b9..577954f03d 100644 --- a/rust/src/session_fs_dispatch.rs +++ b/rust/src/session_fs_dispatch.rs @@ -30,24 +30,49 @@ use crate::{Client, JsonRpcRequest, JsonRpcResponse, error_codes}; const MAX_BINARY_BYTES: usize = (64 * 1024 * 1024 - 1024) / 4 * 3; +/// The success response [`respond`] encodes: [`JsonRpcResponse`]'s fields and +/// their names, with the typed result in place of a `serde_json::Value`. +#[derive(Serialize)] +struct TypedResponse<'a, T: Serialize> { + jsonrpc: &'static str, + id: u64, + result: &'a T, +} + /// Helper: serialize a typed result, send the response. +/// +/// The result is encoded straight into its frame, with no `serde_json::Value` +/// copy of it, and dropped before the frame is written. A large reply, such as +/// a `readFile` of a long session's `events.jsonl` on resume, is then held +/// once, escaped, while the frame is written. async fn respond(client: &Client, request_id: u64, result: T) { - let value = match serde_json::to_value(&result) { - Ok(v) => v, + let frame = match crate::jsonrpc::encode_frame(&TypedResponse { + jsonrpc: "2.0", + id: request_id, + result: &result, + }) { + Ok(frame) => frame, Err(e) => { warn!(error = %e, "failed to serialize sessionFs response"); send_error(client, request_id, "serialization failure").await; return; } }; - let _ = client - .send_response(&JsonRpcResponse { - jsonrpc: "2.0".to_string(), - id: request_id, - result: Some(value), - error: None, - }) - .await; + drop(result); + let _ = client.send_frame(frame).await; +} + +/// [`respond`] for a result holding `HashMap` rows (the sqlite replies). It +/// goes through a `serde_json::Value` first, so each row's keys keep the +/// sorted order a `Value` writes; typed, they would go out in hash order. +async fn respond_sorted(client: &Client, request_id: u64, result: T) { + match serde_json::to_value(&result) { + Ok(value) => respond(client, request_id, value).await, + Err(e) => { + warn!(error = %e, "failed to serialize sessionFs response"); + send_error(client, request_id, "serialization failure").await; + } + } } async fn send_error(client: &Client, request_id: u64, message: &str) { @@ -470,7 +495,7 @@ pub(crate) async fn sqlite_query( rows_affected: 0, }, }; - respond(client, id, result).await; + respond_sorted(client, id, result).await; } pub(crate) async fn sqlite_transaction( @@ -533,7 +558,7 @@ pub(crate) async fn sqlite_transaction( }), }, }; - respond(client, id, result).await; + respond_sorted(client, id, result).await; } pub(crate) async fn sqlite_exists( diff --git a/rust/src/tests.rs b/rust/src/tests.rs index 8656d6f4d3..bd4d519b87 100644 --- a/rust/src/tests.rs +++ b/rust/src/tests.rs @@ -6,6 +6,61 @@ use super::*; +#[tokio::test] +async fn settings_apply_top_key_preserves_value_presence_on_wire() { + use crate::generated::api_types::UserSettingsApplyTopKeyRequest; + + for (name, value, delete) in [ + ("omitted", None, Some(false)), + ("explicit null", Some(serde_json::Value::Null), None), + ("value", Some(serde_json::json!("chosen")), None), + ("delete", None, Some(true)), + ] { + let temp = tempfile::tempdir().unwrap(); + let (client_stream, server_stream) = tokio::io::duplex(8192); + let (client_read, client_write) = tokio::io::split(client_stream); + let (server_read, server_write) = tokio::io::split(server_stream); + let client = + Client::from_streams(client_read, client_write, temp.path().to_owned()).unwrap(); + let (notifications, _) = tokio::sync::broadcast::channel(1); + let (requests, _) = tokio::sync::mpsc::unbounded_channel(); + let server = + jsonrpc::JsonRpcClient::new(server_write, server_read, notifications, requests); + let (captured, mut received) = tokio::sync::mpsc::unbounded_channel(); + server + .register_request_handler( + "user.settings.applyTopKey", + Arc::new(move |params| { + captured.send(params).unwrap(); + Box::pin(async { Ok(serde_json::json!({ "status": "ok" })) }) + }), + ) + .unwrap(); + + let result = tokio::time::timeout( + Duration::from_secs(5), + client + .rpc() + .user() + .settings() + .apply_top_key(UserSettingsApplyTopKeyRequest { + canonical_path: vec!["model".to_owned()], + top_key: "model".to_owned(), + value: value.clone(), + delete, + complete_staff_update_channel_migration: None, + }), + ) + .await; + client.force_stop(); + server.force_close(); + + result.expect(name).expect(name); + let params = received.try_recv().expect(name); + assert_eq!(params.get("value"), value.as_ref(), "{name}: {params}"); + } +} + #[test] fn is_transport_failure_matches_request_cancelled() { let err = Error::from(ErrorKind::Protocol(ProtocolErrorKind::RequestCancelled)); diff --git a/rust/src/tool.rs b/rust/src/tool.rs index 12274dbb8b..bf0709337e 100644 --- a/rust/src/tool.rs +++ b/rust/src/tool.rs @@ -59,6 +59,9 @@ pub fn schema_for() -> serde_json::Value { /// [`Tool::parameters`](crate::types::Tool::parameters) map shape /// expected by the protocol. /// +/// Object keys are sorted at every depth, so the serialized tool definition +/// does not depend on how the schema was built. +/// /// Panics if the input is not a JSON object — tool parameter schemas /// are always top-level objects (`{"type": "object", ...}`). Pair with /// `schema_for` (available with the `derive` feature) or a @@ -87,7 +90,31 @@ pub fn tool_parameters(schema: serde_json::Value) -> IndexMap Result, serde_json::Error> { - serde_json::from_value(schema) + serde_json::from_value(sort_object_keys(schema)) +} + +/// Sort object keys at every depth so a schema serializes identically however +/// it was built. With `serde_json/preserve_order` enabled anywhere in the +/// dependency graph, a schema converted from a `HashMap` would otherwise keep +/// that map's per-process iteration order and change the tool definitions the +/// model sees between sessions, defeating prompt caching. +fn sort_object_keys(value: serde_json::Value) -> serde_json::Value { + match value { + serde_json::Value::Object(map) => { + let mut entries: Vec<_> = map.into_iter().collect(); + entries.sort_by(|(a, _), (b, _)| a.cmp(b)); + serde_json::Value::Object( + entries + .into_iter() + .map(|(key, value)| (key, sort_object_keys(value))) + .collect(), + ) + } + serde_json::Value::Array(items) => { + serde_json::Value::Array(items.into_iter().map(sort_object_keys).collect()) + } + other => other, + } } /// Convert an MCP `CallToolResult` JSON value into a Copilot tool result. diff --git a/rust/src/tool/tests.rs b/rust/src/tool/tests.rs index d307202ce5..f753f8976c 100644 --- a/rust/src/tool/tests.rs +++ b/rust/src/tool/tests.rs @@ -73,6 +73,8 @@ fn tool_parameters_serialize_in_deterministic_order() { // Pin the exact top-level key order so a regression to any // order-randomizing container is caught, not just internal drift. + // Keys are sorted even when `serde_json/preserve_order` is enabled, + // as it is in this crate. let tool = build(); let keys: Vec<&str> = tool.parameters.keys().map(String::as_str).collect(); assert_eq!( @@ -81,6 +83,54 @@ fn tool_parameters_serialize_in_deterministic_order() { ); } +#[test] +fn tool_parameters_sort_keys_of_schemas_built_from_hash_maps() { + // A schema converted from a `HashMap` carries that map's per-process + // iteration order into an insertion-ordered `Value`. The tool definition + // must not depend on it, at any depth. + let properties: std::collections::HashMap = [ + "zeta", "alpha", "mu", "beta", "omega", "gamma", "kappa", "delta", + ] + .into_iter() + .map(|name| { + let field: std::collections::HashMap<&str, &str> = + [("type", "string"), ("description", name)].into(); + (name.to_string(), serde_json::to_value(field).unwrap()) + }) + .collect(); + let mut schema = serde_json::Map::new(); + schema.insert("type".into(), "object".into()); + schema.insert( + "properties".into(), + serde_json::to_value(properties).unwrap(), + ); + + let parameters = tool_parameters(serde_json::Value::Object(schema)); + + assert_eq!( + parameters.keys().map(String::as_str).collect::>(), + ["properties", "type"] + ); + let properties = parameters["properties"].as_object().unwrap(); + assert_eq!( + properties.keys().map(String::as_str).collect::>(), + [ + "alpha", "beta", "delta", "gamma", "kappa", "mu", "omega", "zeta" + ] + ); + for field in properties.values() { + assert_eq!( + field + .as_object() + .unwrap() + .keys() + .map(String::as_str) + .collect::>(), + ["description", "type"] + ); + } +} + #[test] fn convert_mcp_call_tool_result_collects_text_and_binary_content() { let result = convert_mcp_call_tool_result(&serde_json::json!({ diff --git a/rust/src/types.rs b/rust/src/types.rs index ba9c8a7451..4a9d580329 100644 --- a/rust/src/types.rs +++ b/rust/src/types.rs @@ -667,6 +667,9 @@ impl Serialize for CommandDefinition { #[serde(rename_all = "camelCase")] #[non_exhaustive] pub struct CustomAgentConfig { + /// Declarative hook action objects keyed by event name, active only for this agent. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub hooks: Option>>>, /// Unique name of the custom agent. pub name: String, /// Display name for UI purposes. @@ -704,6 +707,12 @@ pub struct CustomAgentConfig { } impl CustomAgentConfig { + /// Set declarative hook action objects scoped to this agent. + pub fn with_hooks(mut self, hooks: IndexMap>>) -> Self { + self.hooks = Some(hooks); + self + } + /// Construct a custom agent configuration with the required `name` /// and `prompt` fields populated. /// @@ -838,6 +847,35 @@ impl LargeToolOutputConfig { } } +/// Opt in to image generation through an authorized Copilot image model. +/// +/// **Experimental.** This API may change or be removed in a future release. +/// +/// Omission disables on create/cold resume and preserves resident resume state. +/// Not persisted: re-supply after runtime restart. Policy, permissions, offline +/// mode, and tool filters still apply. BYOK image generation is not supported. +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +#[non_exhaustive] +pub struct ImageGenerationConfig { + /// True opts in; false disables image generation. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub enabled: Option, +} + +impl ImageGenerationConfig { + /// Construct an unset image generation configuration. + pub fn new() -> Self { + Self::default() + } + + /// Enable or disable image generation. + pub fn with_enabled(mut self, enabled: bool) -> Self { + self.enabled = Some(enabled); + self + } +} + /// Overrides the runtime's built-in tool-search behavior. /// /// Tool search defers tools to keep the model's active tool set small. @@ -2024,6 +2062,13 @@ pub enum AskUserVariant { pub struct SessionConfig { /// Custom session ID. When unset, the CLI generates one. pub session_id: Option, + /// Name the session at creation, as `session.name.set` would, and validated + /// the same way: non-empty, no leading or trailing whitespace, at most 100 + /// UTF-16 units, no control characters, and no double quotes. A name also + /// makes the session persist from the start, so it can be listed and resumed + /// even if it never runs a turn. Local sessions only: cloud creation rejects + /// it. + pub name: Option, /// Model to use (e.g. `"gpt-4"`, `"claude-sonnet-4"`). pub model: Option, /// Exact model IDs this session may use. When unset, the host imposes no @@ -2129,12 +2174,18 @@ pub struct SessionConfig { pub refresh_custom_instructions: Option, /// When true, enables on-demand instruction discovery for this session. pub enable_on_demand_instruction_discovery: Option, - /// When true, enables file hooks for this session. + /// Enables repository-origin file hooks from `.github/hooks/` and trusted + /// repository-configured roots, not explicit `hook_directories` or callback hooks. pub enable_file_hooks: Option, /// When true, allows host Git operations for this session. pub enable_host_git_operations: Option, /// When true, enables the session store for this session. pub enable_session_store: Option, + /// When true, requests the read-only GitHub cache store for this session. + /// + /// The runtime registers `github_sql` only when its `GITHUB_CACHE_STORE` + /// feature flag is enabled. Defaults to disabled. + pub enable_github_cache_store: Option, /// When true, enables skills for this session. pub enable_skills: Option, /// **Experimental.** Supplies session-scoped skills from the SDK host. @@ -2176,10 +2227,30 @@ pub struct SessionConfig { /// `disable_form_deferral` only applies to that server and only has an /// effect when MCP Apps and form-backed GitHub tools are enabled. pub github_mcp_tool_config: Option, - /// Skill directory paths passed through to the GitHub Copilot CLI. + /// Explicit discovery-only skill roots, not general filesystem grants. + /// Available without `enable_config_discovery`, subject to skill/managed disables. + /// Relative paths resolve once when supplied against the request's effective + /// `working_directory`; retained roots do not move on cwd changes. + /// `None` and omission are equivalent; an empty list clears only the explicit SDK contribution. pub skill_directories: Option>, - /// Additional directories to search for custom instruction files. - /// Forwarded to the CLI; not the same as [`skill_directories`](Self::skill_directories). + /// Explicit discovery-only agent roots, separate from `custom_agents` objects. + /// No general filesystem grant. Available without `enable_config_discovery`, + /// subject to source/managed disables. Relative paths resolve once when supplied + /// against the request's effective `working_directory`; retained roots do not move. + /// `None` and omission are equivalent; an empty list clears only the explicit SDK contribution. + pub agent_directories: Option>, + /// Explicit discovery-only file-hook roots, separate from callback `hooks`. + /// No general filesystem grant. Available without `enable_config_discovery` or + /// `enable_file_hooks` (repository-origin hooks only), subject to hook/managed disables. + /// Relative paths resolve once when supplied against the request's effective + /// `working_directory`; retained roots do not move on cwd changes. + /// `None` and omission are equivalent; an empty list clears only the explicit SDK contribution. + pub hook_directories: Option>, + /// Explicit discovery-only instruction roots using existing SDK formats. + /// No general filesystem grant. Available without `enable_config_discovery`, + /// subject to instruction/managed disables. Relative paths resolve once when supplied + /// against the request's effective `working_directory`; retained roots do not move. + /// `None` and omission are equivalent; an empty list clears only the explicit SDK contribution. pub instruction_directories: Option>, /// Open Plugin directory paths passed through to the CLI. pub plugin_directories: Option>, @@ -2189,6 +2260,8 @@ pub struct SessionConfig { /// rarely used tools behind a searchable index. When unset, the runtime /// default applies. pub tool_search: Option, + /// **Experimental.** Non-persisted image generation opt-in. Re-supply after runtime restart. + pub image_generation: Option, /// Skill names to disable. Skills in this set will not be available /// even if found in skill directories. pub disabled_skills: Option>, @@ -2321,6 +2394,12 @@ pub struct SessionConfig { /// defaults remain mutable. Set via /// [`with_enforce_managed_model_defaults`](Self::with_enforce_managed_model_defaults). pub enforce_managed_model_defaults: Option, + /// Declares that a human drives this session's conversation, enabling + /// user-message analysis telemetry for user-authored messages. Automated + /// messages remain excluded. Defaults to false for SDK sessions. Ignored for + /// cloud sessions. Set via + /// [`with_interactive_user`](Self::with_interactive_user). + pub interactive_user: 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 @@ -2412,6 +2491,7 @@ impl std::fmt::Debug for SessionConfig { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("SessionConfig") .field("session_id", &self.session_id) + .field("name", &self.name) .field("model", &self.model) .field("allowed_models", &self.allowed_models) .field("client_name", &self.client_name) @@ -2467,6 +2547,7 @@ impl std::fmt::Debug for SessionConfig { &self.enable_host_git_operations, ) .field("enable_session_store", &self.enable_session_store) + .field("enable_github_cache_store", &self.enable_github_cache_store) .field("enable_skills", &self.enable_skills) .field( "skill_provider", @@ -2474,10 +2555,13 @@ impl std::fmt::Debug for SessionConfig { ) .field("enable_mcp_apps", &self.enable_mcp_apps) .field("skill_directories", &self.skill_directories) + .field("agent_directories", &self.agent_directories) + .field("hook_directories", &self.hook_directories) .field("instruction_directories", &self.instruction_directories) .field("plugin_directories", &self.plugin_directories) .field("large_output", &self.large_output) .field("tool_search", &self.tool_search) + .field("image_generation", &self.image_generation) .field("disabled_skills", &self.disabled_skills) .field("disabled_mcp_servers", &self.disabled_mcp_servers) .field("hooks", &self.hooks) @@ -2521,6 +2605,7 @@ impl std::fmt::Debug for SessionConfig { "enforce_managed_model_defaults", &self.enforce_managed_model_defaults, ) + .field("interactive_user", &self.interactive_user) .field("enable_experimental_mode", &self.enable_experimental_mode) .field("managed_settings", &self.managed_settings) .field( @@ -2573,6 +2658,7 @@ impl Default for SessionConfig { fn default() -> Self { Self { session_id: None, + name: None, model: None, allowed_models: None, client_name: None, @@ -2606,16 +2692,20 @@ impl Default for SessionConfig { enable_file_hooks: None, enable_host_git_operations: None, enable_session_store: None, + enable_github_cache_store: None, enable_skills: None, skill_provider: None, embedding_cache_storage: None, enable_mcp_apps: None, github_mcp_tool_config: None, skill_directories: None, + agent_directories: None, + hook_directories: None, instruction_directories: None, plugin_directories: None, large_output: None, tool_search: None, + image_generation: None, disabled_skills: None, disabled_mcp_servers: None, hooks: None, @@ -2646,6 +2736,7 @@ impl Default for SessionConfig { exp_assignments: None, enable_managed_settings: None, enforce_managed_model_defaults: None, + interactive_user: None, managed_settings: None, session_fs_provider: None, permission_handler: None, @@ -2739,6 +2830,7 @@ impl SessionConfig { let wire = crate::wire::SessionCreateWire { session_id, + name: self.name, model: self.model, allowed_models: self.allowed_models, client_name: self.client_name, @@ -2773,6 +2865,7 @@ impl SessionConfig { enable_file_hooks: self.enable_file_hooks, enable_host_git_operations: self.enable_host_git_operations, enable_session_store: self.enable_session_store, + enable_github_cache_store: self.enable_github_cache_store, enable_skills: self.enable_skills, has_skill_provider: has_skill_provider.then_some(true), request_user_input, @@ -2784,10 +2877,13 @@ impl SessionConfig { github_mcp_tool_config: self.github_mcp_tool_config, hooks: hooks_flag, skill_directories: self.skill_directories, + agent_directories: self.agent_directories, + hook_directories: self.hook_directories, instruction_directories: self.instruction_directories, plugin_directories: self.plugin_directories, large_output: self.large_output, tool_search: self.tool_search, + image_generation: self.image_generation, disabled_skills: self.disabled_skills, disabled_mcp_servers: self.disabled_mcp_servers, custom_agents: self.custom_agents, @@ -2819,6 +2915,7 @@ impl SessionConfig { exp_assignments: self.exp_assignments, enable_managed_settings: self.enable_managed_settings, enforce_managed_model_defaults: self.enforce_managed_model_defaults, + interactive_user: self.interactive_user, is_experimental_mode: self.enable_experimental_mode, managed_settings: self.managed_settings, }; @@ -2964,6 +3061,15 @@ impl SessionConfig { self } + /// Set [`Self::name`]. The runtime rejects a name that is empty, padded with + /// whitespace, longer than 100 UTF-16 units, or contains control characters + /// or double quotes, so `create_session` fails rather than creating an + /// unnamed or unrenameable session. + pub fn with_name(mut self, name: impl Into) -> Self { + self.name = Some(name.into()); + self + } + /// Set the model identifier (e.g. `"claude-sonnet-4"`). pub fn with_model(mut self, model: impl Into) -> Self { self.model = Some(model.into()); @@ -3194,6 +3300,12 @@ impl SessionConfig { self } + /// Set [`Self::enable_github_cache_store`]. + pub fn with_enable_github_cache_store(mut self, value: bool) -> Self { + self.enable_github_cache_store = Some(value); + self + } + /// Set [`Self::enable_skills`]. pub fn with_enable_skills(mut self, value: bool) -> Self { self.enable_skills = Some(value); @@ -3234,6 +3346,26 @@ impl SessionConfig { self } + /// Set explicit discovery-only agent roots. See [`Self::agent_directories`]. + pub fn with_agent_directories(mut self, paths: I) -> Self + where + I: IntoIterator, + P: Into, + { + self.agent_directories = Some(paths.into_iter().map(Into::into).collect()); + self + } + + /// Set explicit discovery-only file-hook roots. See [`Self::hook_directories`]. + pub fn with_hook_directories(mut self, paths: I) -> Self + where + I: IntoIterator, + P: Into, + { + self.hook_directories = Some(paths.into_iter().map(Into::into).collect()); + self + } + /// Set the runtime-bundled skill allowlist. pub fn with_included_builtin_skills(mut self, names: I) -> Self where @@ -3279,6 +3411,12 @@ impl SessionConfig { self } + /// **Experimental.** Set the non-persisted image generation opt-in on create. + pub fn with_image_generation(mut self, config: ImageGenerationConfig) -> Self { + self.image_generation = Some(config); + self + } + /// Set the names of skills to disable (overrides skill discovery). pub fn with_disabled_skills(mut self, names: I) -> Self where @@ -3544,6 +3682,12 @@ impl SessionConfig { self } + /// Declare that a human drives this session's conversation. + pub fn with_interactive_user(mut self, interactive_user: bool) -> Self { + self.interactive_user = Some(interactive_user); + 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 @@ -3654,12 +3798,18 @@ pub struct ResumeSessionConfig { pub organization_custom_instructions: Option, /// When true, enables on-demand instruction discovery on resume. pub enable_on_demand_instruction_discovery: Option, - /// When true, enables file hooks on resume. + /// Enables repository-origin file hooks from `.github/hooks/` and trusted + /// repository-configured roots, not explicit `hook_directories` or callback hooks. pub enable_file_hooks: Option, /// When true, allows host Git operations on resume. pub enable_host_git_operations: Option, /// When true, enables the session store on resume. pub enable_session_store: Option, + /// When true, requests the read-only GitHub cache store on resume. + /// + /// The runtime registers `github_sql` only when its `GITHUB_CACHE_STORE` + /// feature flag is enabled. Defaults to disabled. + pub enable_github_cache_store: Option, /// When true, enables skills on resume. pub enable_skills: Option, /// **Experimental.** Supplies session-scoped skills from the SDK host. @@ -3678,10 +3828,30 @@ pub struct ResumeSessionConfig { /// `disable_form_deferral` only applies to that server and only has an /// effect when MCP Apps and form-backed GitHub tools are enabled. pub github_mcp_tool_config: Option, - /// Skill directory paths passed through to the GitHub Copilot CLI on resume. + /// Explicit discovery-only skill roots, not general filesystem grants. + /// Available without `enable_config_discovery`, subject to skill/managed disables. + /// Relative paths resolve once when supplied against the request's effective + /// `working_directory`; retained roots do not move on cwd changes. + /// `None` and omission are equivalent; an empty list clears only the explicit SDK contribution. pub skill_directories: Option>, - /// Additional directories to search for custom instruction files on - /// resume. Forwarded to the CLI; not the same as [`skill_directories`](Self::skill_directories). + /// Explicit discovery-only agent roots, separate from `custom_agents` objects. + /// No general filesystem grant. Available without `enable_config_discovery`, + /// subject to source/managed disables. Relative paths resolve once when supplied + /// against the request's effective `working_directory`; retained roots do not move. + /// `None` and omission are equivalent; an empty list clears only the explicit SDK contribution. + pub agent_directories: Option>, + /// Explicit discovery-only file-hook roots, separate from callback `hooks`. + /// No general filesystem grant. Available without `enable_config_discovery` or + /// `enable_file_hooks` (repository-origin hooks only), subject to hook/managed disables. + /// Relative paths resolve once when supplied against the request's effective + /// `working_directory`; retained roots do not move on cwd changes. + /// `None` and omission are equivalent; an empty list clears only the explicit SDK contribution. + pub hook_directories: Option>, + /// Explicit discovery-only instruction roots using existing SDK formats. + /// No general filesystem grant. Available without `enable_config_discovery`, + /// subject to instruction/managed disables. Relative paths resolve once when supplied + /// against the request's effective `working_directory`; retained roots do not move. + /// `None` and omission are equivalent; an empty list clears only the explicit SDK contribution. pub instruction_directories: Option>, /// Open Plugin directory paths passed through to the CLI on resume. pub plugin_directories: Option>, @@ -3690,6 +3860,8 @@ pub struct ResumeSessionConfig { /// Overrides the runtime's built-in tool-search behavior on resume. When /// unset, the runtime default applies. pub tool_search: Option, + /// **Experimental.** Non-persisted image generation opt-in. Re-supply after runtime restart. + pub image_generation: Option, /// Skill names to disable on resume. pub disabled_skills: Option>, /// Exact MCP server names to disable on resume. This prevents startup and @@ -3786,6 +3958,11 @@ pub struct ResumeSessionConfig { /// Re-applies managed model lock enforcement on resume. See /// [`SessionConfig::enforce_managed_model_defaults`]. pub enforce_managed_model_defaults: Option, + /// Re-supplies that a human drives this session so a session loaded from + /// storage keeps the declaration. It is not persisted and is ignored if the + /// session is already active in the runtime. Set via + /// [`with_interactive_user`](Self::with_interactive_user). + pub interactive_user: 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 @@ -3910,6 +4087,7 @@ impl std::fmt::Debug for ResumeSessionConfig { &self.enable_host_git_operations, ) .field("enable_session_store", &self.enable_session_store) + .field("enable_github_cache_store", &self.enable_github_cache_store) .field("enable_skills", &self.enable_skills) .field( "skill_provider", @@ -3917,10 +4095,13 @@ impl std::fmt::Debug for ResumeSessionConfig { ) .field("enable_mcp_apps", &self.enable_mcp_apps) .field("skill_directories", &self.skill_directories) + .field("agent_directories", &self.agent_directories) + .field("hook_directories", &self.hook_directories) .field("instruction_directories", &self.instruction_directories) .field("plugin_directories", &self.plugin_directories) .field("large_output", &self.large_output) .field("tool_search", &self.tool_search) + .field("image_generation", &self.image_generation) .field("disabled_skills", &self.disabled_skills) .field("disabled_mcp_servers", &self.disabled_mcp_servers) .field("hooks", &self.hooks) @@ -3963,6 +4144,7 @@ impl std::fmt::Debug for ResumeSessionConfig { "enforce_managed_model_defaults", &self.enforce_managed_model_defaults, ) + .field("interactive_user", &self.interactive_user) .field("enable_experimental_mode", &self.enable_experimental_mode) .field("managed_settings", &self.managed_settings) .field( @@ -4081,6 +4263,7 @@ impl ResumeSessionConfig { enable_file_hooks: self.enable_file_hooks, enable_host_git_operations: self.enable_host_git_operations, enable_session_store: self.enable_session_store, + enable_github_cache_store: self.enable_github_cache_store, enable_skills: self.enable_skills, has_skill_provider: has_skill_provider.then_some(true), request_user_input, @@ -4092,10 +4275,13 @@ impl ResumeSessionConfig { github_mcp_tool_config: self.github_mcp_tool_config, hooks: hooks_flag, skill_directories: self.skill_directories, + agent_directories: self.agent_directories, + hook_directories: self.hook_directories, instruction_directories: self.instruction_directories, plugin_directories: self.plugin_directories, large_output: self.large_output, tool_search: self.tool_search, + image_generation: self.image_generation, disabled_skills: self.disabled_skills, disabled_mcp_servers: self.disabled_mcp_servers, custom_agents: self.custom_agents, @@ -4126,6 +4312,7 @@ impl ResumeSessionConfig { exp_assignments: self.exp_assignments, enable_managed_settings: self.enable_managed_settings, enforce_managed_model_defaults: self.enforce_managed_model_defaults, + interactive_user: self.interactive_user, is_experimental_mode: self.enable_experimental_mode, managed_settings: self.managed_settings, suppress_resume_event: self.suppress_resume_event, @@ -4195,16 +4382,20 @@ impl ResumeSessionConfig { enable_file_hooks: None, enable_host_git_operations: None, enable_session_store: None, + enable_github_cache_store: None, enable_skills: None, skill_provider: None, embedding_cache_storage: None, enable_mcp_apps: None, github_mcp_tool_config: None, skill_directories: None, + agent_directories: None, + hook_directories: None, instruction_directories: None, plugin_directories: None, large_output: None, tool_search: None, + image_generation: None, disabled_skills: None, disabled_mcp_servers: None, hooks: None, @@ -4234,6 +4425,7 @@ impl ResumeSessionConfig { exp_assignments: None, enable_managed_settings: None, enforce_managed_model_defaults: None, + interactive_user: None, managed_settings: None, session_fs_provider: None, suppress_resume_event: None, @@ -4592,6 +4784,12 @@ impl ResumeSessionConfig { self } + /// Set [`Self::enable_github_cache_store`]. + pub fn with_enable_github_cache_store(mut self, value: bool) -> Self { + self.enable_github_cache_store = Some(value); + self + } + /// Set [`Self::enable_skills`]. pub fn with_enable_skills(mut self, value: bool) -> Self { self.enable_skills = Some(value); @@ -4624,6 +4822,26 @@ impl ResumeSessionConfig { self } + /// Set explicit discovery-only agent roots. See [`Self::agent_directories`]. + pub fn with_agent_directories(mut self, paths: I) -> Self + where + I: IntoIterator, + P: Into, + { + self.agent_directories = Some(paths.into_iter().map(Into::into).collect()); + self + } + + /// Set explicit discovery-only file-hook roots. See [`Self::hook_directories`]. + pub fn with_hook_directories(mut self, paths: I) -> Self + where + I: IntoIterator, + P: Into, + { + self.hook_directories = Some(paths.into_iter().map(Into::into).collect()); + self + } + /// Set the runtime-bundled skill allowlist on resume. pub fn with_included_builtin_skills(mut self, names: I) -> Self where @@ -4669,6 +4887,12 @@ impl ResumeSessionConfig { self } + /// **Experimental.** Set the image generation opt-in on resume, including after runtime restart. + pub fn with_image_generation(mut self, config: ImageGenerationConfig) -> Self { + self.image_generation = Some(config); + self + } + /// Set the names of skills to disable on resume. pub fn with_disabled_skills(mut self, names: I) -> Self where @@ -4940,6 +5164,12 @@ impl ResumeSessionConfig { self } + /// Re-supply that a human drives this session. + pub fn with_interactive_user(mut self, interactive_user: bool) -> Self { + self.interactive_user = Some(interactive_user); + 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. @@ -6524,6 +6754,9 @@ impl Default for ExitPlanModeData { } } +#[cfg(test)] +mod customization_directory_tests; + #[cfg(test)] mod tests; diff --git a/rust/src/types/customization_directory_tests.rs b/rust/src/types/customization_directory_tests.rs new file mode 100644 index 0000000000..67a5dadf15 --- /dev/null +++ b/rust/src/types/customization_directory_tests.rs @@ -0,0 +1,88 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. + +#![cfg(test)] + +use std::path::PathBuf; + +use serde_json::{Value, json}; + +use super::{ResumeSessionConfig, SessionConfig, SessionId}; + +fn assert_roots(wire: &Value, roots: Option<&[PathBuf]>) { + for (key, kind) in [ + ("skillDirectories", "skills"), + ("agentDirectories", "agents"), + ("instructionDirectories", "instructions"), + ("hookDirectories", "hooks"), + ] { + match roots { + Some(paths) => assert_eq!( + wire[key], + serde_json::to_value(paths.iter().map(|path| path.join(kind)).collect::>()) + .unwrap() + ), + None => assert!(wire.get(key).is_none(), "unexpected {key}: {wire}"), + } + } + assert!(wire.get("additionalDirectories").is_none()); + assert!(wire.get("customAgents").is_none()); + assert_eq!(wire["hooks"], false); + assert_eq!(wire["enableFileHooks"], false); + assert_eq!(wire["enableConfigDiscovery"], false); +} + +#[test] +fn create_and_resume_preserve_discovery_roots_without_client_rebasing() { + for roots in [ + None, + Some(Vec::new()), + Some(vec![ + PathBuf::from("relative-root"), + PathBuf::from("other-root"), + ]), + ] { + let mut create = SessionConfig::default() + .with_working_directory("request-cwd") + .with_enable_config_discovery(false) + .with_enable_file_hooks(false); + let mut resume = ResumeSessionConfig::new(SessionId::from("resumed")) + .with_working_directory("resume-cwd") + .with_enable_config_discovery(false) + .with_enable_file_hooks(false); + if let Some(paths) = &roots { + create = create + .with_skill_directories(paths.iter().map(|path| path.join("skills"))) + .with_agent_directories(paths.iter().map(|path| path.join("agents"))) + .with_instruction_directories(paths.iter().map(|path| path.join("instructions"))) + .with_hook_directories(paths.iter().map(|path| path.join("hooks"))); + resume = resume + .with_skill_directories(paths.iter().map(|path| path.join("skills"))) + .with_agent_directories(paths.iter().map(|path| path.join("agents"))) + .with_instruction_directories(paths.iter().map(|path| path.join("instructions"))) + .with_hook_directories(paths.iter().map(|path| path.join("hooks"))); + } + let (wire, _) = create.into_wire(None).unwrap(); + assert_roots(&serde_json::to_value(wire).unwrap(), roots.as_deref()); + let (wire, _) = resume.into_wire().unwrap(); + assert_roots(&serde_json::to_value(wire).unwrap(), roots.as_deref()); + } +} + +#[test] +fn host_factory_forwards_agent_and_hook_roots() { + let create = SessionConfig::default() + .with_agent_directories(["relative-agents"]) + .with_hook_directories(Vec::::new()); + let resume = ResumeSessionConfig::new(SessionId::from("resumed")) + .with_agent_directories(["relative-agents"]) + .with_hook_directories(Vec::::new()); + for wire in [ + crate::ahp_host::config_for_host(&create).unwrap(), + crate::ahp_host::resume_config_for_host(&resume).unwrap(), + ] { + assert_eq!(wire["agentDirectories"], json!(["relative-agents"])); + assert_eq!(wire["hookDirectories"], json!([])); + assert!(wire.get("customAgents").is_none()); + assert!(wire.get("hooks").is_none()); + } +} diff --git a/rust/src/types/tests.rs b/rust/src/types/tests.rs index 774feeda9f..451fe35191 100644 --- a/rust/src/types/tests.rs +++ b/rust/src/types/tests.rs @@ -13,11 +13,11 @@ use super::{ AgentMode, Attachment, AttachmentLineRange, AttachmentSelectionPosition, AttachmentSelectionRange, AutoTier, AzureProviderOptions, CapiSessionOptions, ConnectionState, CopilotExpAssignmentResponse, CustomAgentConfig, DeliveryMode, ExpConfigEntry, ExpFlagValue, - ExtensionInfo, GitHubMcpToolConfig, GitHubReferenceType, InfiniteSessionConfig, - LargeToolOutputConfig, McpServerConfig, McpStdioServerConfig, MemoryConfiguration, - NamedProviderConfig, PermissionResponseCapability, ProviderConfig, ProviderModelConfig, - ReasoningSummary, ResumeSessionConfig, SessionConfig, SessionEvent, SessionId, - SystemMessageConfig, Tool, ToolBinaryResult, ToolResult, ToolResultExpanded, + ExtensionInfo, GitHubMcpToolConfig, GitHubReferenceType, ImageGenerationConfig, + InfiniteSessionConfig, LargeToolOutputConfig, McpServerConfig, McpStdioServerConfig, + 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; @@ -135,6 +135,55 @@ fn custom_agent_config_omits_reasoning_effort_when_none() { assert!(wire.get("reasoningEffort").is_none()); } +#[test] +fn custom_agent_config_hooks_round_trip() { + use indexmap::IndexMap; + + let action: IndexMap = IndexMap::from([ + ("type".into(), json!("command")), + ("bash".into(), json!("echo scoped-hook")), + ("timeoutSec".into(), json!(10)), + ]); + let hooks = IndexMap::from([("preToolUse".into(), vec![action])]); + let agent = CustomAgentConfig::new("hook-agent", "prompt").with_hooks(hooks.clone()); + assert_eq!(agent.hooks.as_ref(), Some(&hooks)); + + let wire = serde_json::to_value(&agent).unwrap(); + assert_eq!( + wire["hooks"], + json!({"preToolUse": [{"type": "command", "bash": "echo scoped-hook", "timeoutSec": 10}]}) + ); + let restored: CustomAgentConfig = serde_json::from_value(wire).unwrap(); + assert_eq!(restored.hooks, Some(hooks)); +} + +#[test] +fn custom_agent_config_hooks_reject_non_object_actions() { + for action in [ + json!(null), + json!(true), + json!(42), + json!("command"), + json!([]), + ] { + let result = serde_json::from_value::(json!({ + "name": "invalid-hook-agent", + "prompt": "prompt", + "hooks": {"preToolUse": [action]} + })); + assert!( + result.is_err(), + "expected non-object hook action {action} to fail, got {result:?}" + ); + } +} + +#[test] +fn custom_agent_config_omits_hooks_when_unset() { + let wire = serde_json::to_value(CustomAgentConfig::new("plain-agent", "prompt")).unwrap(); + assert!(wire.get("hooks").is_none()); +} + #[test] #[should_panic(expected = "tool parameter schema must be a JSON object")] fn tool_with_parameters_panics_on_non_object_value() { @@ -1947,3 +1996,40 @@ fn github_anchored_attachment_variants_round_trip() { ); } } +#[test] +fn image_generation_create_and_resume_wire_options() { + for enabled in [true, false] { + let config = ImageGenerationConfig::new().with_enabled(enabled); + let (create, _) = SessionConfig::default() + .with_image_generation(config.clone()) + .into_wire(Some(SessionId::from("image-test"))) + .unwrap(); + let (resume, _) = ResumeSessionConfig::new(SessionId::from("image-test")) + .with_image_generation(config) + .into_wire() + .unwrap(); + for wire in [ + serde_json::to_value(create).unwrap(), + serde_json::to_value(resume).unwrap(), + ] { + assert_eq!( + wire["imageGeneration"], + serde_json::json!({"enabled": enabled}) + ); + } + } + let (create, _) = SessionConfig::default().into_wire(None).unwrap(); + let (resume, _) = ResumeSessionConfig::new(SessionId::from("image-test")) + .into_wire() + .unwrap(); + for wire in [ + serde_json::to_value(create).unwrap(), + serde_json::to_value(resume).unwrap(), + ] { + assert!(wire.get("imageGeneration").is_none(), "{wire}"); + } + assert_eq!( + serde_json::to_value(ImageGenerationConfig::new()).unwrap(), + serde_json::json!({}) + ); +} diff --git a/rust/src/wire.rs b/rust/src/wire.rs index 2720d3bf2d..fe9aacbd17 100644 --- a/rust/src/wire.rs +++ b/rust/src/wire.rs @@ -27,9 +27,9 @@ use crate::generated::session_events::ReasoningSummary; use crate::types::{ AskUserVariant, CanvasProviderIdentity, CapiSessionOptions, CloudSessionOptions, CustomAgentConfig, DefaultAgentConfig, ExtensionInfo, GitHubMcpToolConfig, - InfiniteSessionConfig, LargeToolOutputConfig, McpServerConfig, MemoryConfiguration, - NamedProviderConfig, ProviderConfig, ProviderModelConfig, SessionId, SessionLimitsConfig, - SystemMessageConfig, Tool, ToolSearchConfig, + ImageGenerationConfig, InfiniteSessionConfig, LargeToolOutputConfig, McpServerConfig, + MemoryConfiguration, NamedProviderConfig, ProviderConfig, ProviderModelConfig, SessionId, + SessionLimitsConfig, SystemMessageConfig, Tool, ToolSearchConfig, }; /// Wire representation of a slash command (name + description only). The @@ -51,6 +51,8 @@ pub(crate) struct SessionCreateWire { #[serde(skip_serializing_if = "Option::is_none")] pub session_id: Option, #[serde(skip_serializing_if = "Option::is_none")] + pub name: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub model: Option, #[serde(skip_serializing_if = "Option::is_none")] pub allowed_models: Option>, @@ -121,6 +123,11 @@ pub(crate) struct SessionCreateWire { pub enable_host_git_operations: Option, #[serde(skip_serializing_if = "Option::is_none")] pub enable_session_store: Option, + #[serde( + rename = "enableGitHubCacheStore", + skip_serializing_if = "Option::is_none" + )] + pub enable_github_cache_store: Option, #[serde(skip_serializing_if = "Option::is_none")] pub enable_skills: Option, #[serde(skip_serializing_if = "Option::is_none")] @@ -137,6 +144,10 @@ pub(crate) struct SessionCreateWire { #[serde(skip_serializing_if = "Option::is_none")] pub skill_directories: Option>, #[serde(skip_serializing_if = "Option::is_none")] + pub agent_directories: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub hook_directories: Option>, + #[serde(skip_serializing_if = "Option::is_none")] pub instruction_directories: Option>, #[serde(skip_serializing_if = "Option::is_none")] pub plugin_directories: Option>, @@ -145,6 +156,8 @@ pub(crate) struct SessionCreateWire { #[serde(skip_serializing_if = "Option::is_none")] pub tool_search: Option, #[serde(skip_serializing_if = "Option::is_none")] + pub image_generation: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub disabled_skills: Option>, #[serde(skip_serializing_if = "Option::is_none")] pub disabled_mcp_servers: Option>, @@ -213,6 +226,8 @@ pub(crate) struct SessionCreateWire { #[serde(skip_serializing_if = "Option::is_none")] pub enforce_managed_model_defaults: Option, #[serde(skip_serializing_if = "Option::is_none")] + pub interactive_user: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub is_experimental_mode: Option, #[serde(skip_serializing_if = "Option::is_none")] pub managed_settings: Option, @@ -293,6 +308,11 @@ pub(crate) struct SessionResumeWire { pub enable_host_git_operations: Option, #[serde(skip_serializing_if = "Option::is_none")] pub enable_session_store: Option, + #[serde( + rename = "enableGitHubCacheStore", + skip_serializing_if = "Option::is_none" + )] + pub enable_github_cache_store: Option, #[serde(skip_serializing_if = "Option::is_none")] pub enable_skills: Option, #[serde(skip_serializing_if = "Option::is_none")] @@ -309,6 +329,10 @@ pub(crate) struct SessionResumeWire { #[serde(skip_serializing_if = "Option::is_none")] pub skill_directories: Option>, #[serde(skip_serializing_if = "Option::is_none")] + pub agent_directories: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub hook_directories: Option>, + #[serde(skip_serializing_if = "Option::is_none")] pub instruction_directories: Option>, #[serde(skip_serializing_if = "Option::is_none")] pub plugin_directories: Option>, @@ -317,6 +341,8 @@ pub(crate) struct SessionResumeWire { #[serde(skip_serializing_if = "Option::is_none")] pub tool_search: Option, #[serde(skip_serializing_if = "Option::is_none")] + pub image_generation: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub disabled_skills: Option>, #[serde(skip_serializing_if = "Option::is_none")] pub disabled_mcp_servers: Option>, @@ -390,6 +416,8 @@ pub(crate) struct SessionResumeWire { #[serde(skip_serializing_if = "Option::is_none")] pub enforce_managed_model_defaults: Option, #[serde(skip_serializing_if = "Option::is_none")] + pub interactive_user: 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 2941c8f692..d8b705f075 100644 --- a/rust/tests/api_types_test.rs +++ b/rust/tests/api_types_test.rs @@ -11,6 +11,7 @@ use github_copilot_sdk::rpc::{ CustomizationReloadOutcome, CustomizationReloadStatus, CustomizationReloadSubsystem, EnqueueCommandResult, Extension, ExtensionList, ExtensionSource, ExtensionStatus, ExtensionsDisableRequest, ExtensionsEnableRequest, FleetStartRequest, FleetStartResult, + ManagedPermissionEvaluation, ManagedPermissionOperationKind, ManagedPermissionsEvaluateRequest, McpConfiguredServer, McpConfiguredServerList, McpConfiguredServerState, McpDisableRequest, McpEnableOptions, McpEnableRequest, McpInstallationOperationStatus, McpOauthLoginOptions, McpOauthLoginRequest, McpServer, McpStopServerRequest, ModelSetAllowedModelsRequest, @@ -25,6 +26,30 @@ use github_copilot_sdk::session_events::{ }; use github_copilot_sdk::{AutoTier, AutoTierPreference, SetModelOptions}; +#[test] +fn managed_permission_results_reuse_operations_without_reconstruction() { + let evaluation: ManagedPermissionEvaluation = serde_json::from_value(serde_json::json!({ + "operation": {"kind": "url", "url": "https://example.com/a"}, + "verdict": "allow", + })) + .unwrap(); + let request = ManagedPermissionsEvaluateRequest { + context: serde_json::from_value(serde_json::json!({"failClosed": false})).unwrap(), + operations: vec![evaluation.operation], + }; + assert_eq!( + request.operations[0].kind, + ManagedPermissionOperationKind::Url + ); + assert_eq!( + serde_json::to_value(request).unwrap(), + serde_json::json!({ + "context": {"failClosed": false}, + "operations": [{"kind": "url", "url": "https://example.com/a"}], + }) + ); +} + #[test] fn extensible_tier_unknown_literal_retains_its_provider_identity() { let wire = serde_json::json!("Unknown"); diff --git a/rust/tests/e2e/client_options.rs b/rust/tests/e2e/client_options.rs index c516f84be6..85770746a1 100644 --- a/rust/tests/e2e/client_options.rs +++ b/rust/tests/e2e/client_options.rs @@ -18,6 +18,10 @@ use serde::Deserialize; use serde_json::{Value, json}; use tempfile::TempDir; +#[cfg(windows)] +#[path = "client_options/windows_process_tree.rs"] +mod windows_process_tree; + #[tokio::test] async fn should_forward_advanced_session_creation_options_to_the_cli() { let fake = FakeCli::new(); @@ -32,6 +36,7 @@ async fn should_forward_advanced_session_creation_options_to_the_cli() { .create_session( SessionConfig::default() .with_session_id("advanced-session-id") + .with_name("advanced session") .with_client_name("rust-sdk-e2e-client") .with_model("claude-sonnet-5") .with_reasoning_effort("low") @@ -46,6 +51,7 @@ async fn should_forward_advanced_session_creation_options_to_the_cli() { .with_enable_file_hooks(false) .with_enable_host_git_operations(false) .with_enable_session_store(false) + .with_enable_github_cache_store(true) .with_enable_skills(false) .with_working_directory(working_dir.clone()) .with_streaming(true) @@ -95,6 +101,7 @@ async fn should_forward_advanced_session_creation_options_to_the_cli() { params, [ ("sessionId", json!("advanced-session-id")), + ("name", json!("advanced session")), ("clientName", json!("rust-sdk-e2e-client")), ("model", json!("claude-sonnet-5")), ("reasoningEffort", json!("low")), @@ -112,6 +119,7 @@ async fn should_forward_advanced_session_creation_options_to_the_cli() { ("enableFileHooks", json!(false)), ("enableHostGitOperations", json!(false)), ("enableSessionStore", json!(false)), + ("enableGitHubCacheStore", json!(true)), ("enableSkills", json!(false)), ("workingDirectory", json!(path_string(&working_dir))), ("streaming", json!(true)), diff --git a/rust/tests/e2e/client_options/windows_process_tree.rs b/rust/tests/e2e/client_options/windows_process_tree.rs new file mode 100644 index 0000000000..36035536ac --- /dev/null +++ b/rust/tests/e2e/client_options/windows_process_tree.rs @@ -0,0 +1,252 @@ +#![cfg(windows)] + +use std::io; +use std::os::windows::io::{AsRawHandle, FromRawHandle, OwnedHandle}; +use std::path::Path; +use std::time::Duration; + +use github_copilot_sdk::{CliProgram, Client, ClientOptions, Transport}; +use windows_sys::Win32::System::Threading::{ + OpenProcess, PROCESS_SYNCHRONIZE, PROCESS_TERMINATE, TerminateProcess, WaitForSingleObject, +}; + +use super::ShutdownCli; + +struct FixtureProcess(OwnedHandle); + +impl FixtureProcess { + fn open(pid: u32) -> Self { + // SAFETY: this PID was published by this test's owned fixture. + let handle = unsafe { OpenProcess(PROCESS_SYNCHRONIZE | PROCESS_TERMINATE, 0, pid) }; + assert!(!handle.is_null(), "{}", io::Error::last_os_error()); + // SAFETY: OpenProcess returned a new independently owned handle. + Self(unsafe { OwnedHandle::from_raw_handle(handle) }) + } + + fn alive(&self) -> bool { + // SAFETY: the retained process handle is valid even after the process exits. + match unsafe { WaitForSingleObject(self.0.as_raw_handle().cast(), 0) } { + 0 => false, + 258 => true, + result => panic!( + "process wait failed ({result}): {}", + io::Error::last_os_error() + ), + } + } + + fn terminate(&self) { + // SAFETY: this handle uniquely identifies a fixture-owned process, not a reused PID. + assert_ne!( + unsafe { TerminateProcess(self.0.as_raw_handle().cast(), 1) }, + 0, + "{}", + io::Error::last_os_error() + ); + } + + async fn assert_exited(&self) { + tokio::time::timeout(Duration::from_secs(5), async { + while self.alive() { + tokio::time::sleep(Duration::from_millis(10)).await; + } + }) + .await + .expect("owned root and descendant must terminate"); + } +} + +impl Drop for FixtureProcess { + fn drop(&mut self) { + // SAFETY: fallback cleanup targets only this fixture's retained process handle. + unsafe { + if WaitForSingleObject(self.0.as_raw_handle().cast(), 0) == 258 { + if TerminateProcess(self.0.as_raw_handle().cast(), 1) == 0 { + eprintln!( + "fixture fallback termination failed: {}", + io::Error::last_os_error() + ); + } + if WaitForSingleObject(self.0.as_raw_handle().cast(), 5000) != 0 { + eprintln!( + "fixture fallback reap failed: {}", + io::Error::last_os_error() + ); + } + } + } + } +} + +fn options(fake: &ShutdownCli, tcp: bool) -> ClientOptions { + let mut options = fake.options(); + options.prefix_args.push("--fixture-descendant".into()); + if tcp { + options.prefix_args.push("--fixture-tcp".into()); + options.transport = Transport::Tcp { + port: 0, + connection_token: None, + }; + } + options +} + +async fn published_pid(path: &Path) -> u32 { + tokio::time::timeout(Duration::from_secs(10), async { + loop { + match std::fs::read_to_string(path) { + Ok(value) => { + if let Ok(pid) = value.trim().parse() { + return pid; + } + } + Err(error) if error.kind() == io::ErrorKind::NotFound => {} + Err(error) => panic!("read fixture PID: {error}"), + } + tokio::time::sleep(Duration::from_millis(10)).await; + } + }) + .await + .expect("fixture must publish its PID") +} + +async fn retain_tree(fake: &ShutdownCli) -> (FixtureProcess, FixtureProcess) { + let root = FixtureProcess::open(published_pid(&fake.pid_file).await); + let descendant_path = fake.pid_file.with_file_name("child.pid.descendant"); + let descendant = FixtureProcess::open(published_pid(&descendant_path).await); + assert!(root.alive()); + assert!(descendant.alive()); + (root, descendant) +} + +#[tokio::test] +async fn stop_force_stop_and_drop_terminate_owned_descendants() { + for tcp in [false, true] { + for action in ["stop", "force_stop", "drop"] { + let fake = ShutdownCli::new("force"); + let client = Client::start(options(&fake, tcp)) + .await + .expect("start fixture CLI"); + let (root, descendant) = retain_tree(&fake).await; + match action { + "stop" => client.stop().await.expect("stop fixture CLI"), + "force_stop" => client.force_stop(), + "drop" => {} + _ => unreachable!(), + } + drop(client); + root.assert_exited().await; + descendant.assert_exited().await; + } + } +} + +#[tokio::test] +async fn cancelled_startup_terminates_root_and_descendant() { + for (tcp, mode) in [ + (false, "connect-stall"), + (true, "connect-stall"), + (true, "port-stall"), + ] { + let fake = ShutdownCli::new(mode); + let starting = tokio::spawn(Client::start(options(&fake, tcp))); + let (root, descendant) = retain_tree(&fake).await; + starting.abort(); + assert!(starting.await.expect_err("cancel startup").is_cancelled()); + root.assert_exited().await; + descendant.assert_exited().await; + } +} + +#[tokio::test] +async fn startup_process_exit_terminates_descendant() { + for tcp in [false, true] { + let fake = ShutdownCli::new("connect-stall"); + let starting = tokio::spawn(Client::start(options(&fake, tcp))); + let (root, descendant) = retain_tree(&fake).await; + root.terminate(); + let result = tokio::time::timeout(Duration::from_secs(5), starting) + .await + .expect("startup failure must be bounded") + .expect("join startup task"); + assert!(result.is_err()); + root.assert_exited().await; + descendant.assert_exited().await; + } +} + +#[tokio::test] +async fn last_clone_drop_is_private_to_its_client_job() { + for tcp in [false, true] { + let first = ShutdownCli::new("force"); + let client = Client::start(options(&first, tcp)) + .await + .expect("start first client"); + let retained_client = client.clone(); + let first_tree = retain_tree(&first).await; + let second = ShutdownCli::new("force"); + let second_client = Client::start(options(&second, tcp)) + .await + .expect("start second client"); + let second_tree = retain_tree(&second).await; + + drop(client); + assert!(first_tree.0.alive()); + assert!(first_tree.1.alive()); + drop(retained_client); + first_tree.0.assert_exited().await; + first_tree.1.assert_exited().await; + assert!(second_tree.0.alive()); + assert!(second_tree.1.alive()); + second_client + .ping(Some("private-job-isolation")) + .await + .expect("other client's Job must remain alive"); + second_client.force_stop(); + second_tree.0.assert_exited().await; + second_tree.1.assert_exited().await; + } +} + +#[tokio::test] +async fn abrupt_host_exit_terminates_root_and_descendant_without_rust_cleanup() { + for tcp in [false, true] { + let fake = ShutdownCli::new("force"); + let options = options(&fake, tcp); + let CliProgram::Path(program) = options.program else { + panic!("fixture uses an explicit program"); + }; + let prefix: Vec<_> = options + .prefix_args + .iter() + .map(|arg| arg.to_str().expect("fixture argument is UTF-8")) + .collect(); + let host_pid_file = fake.dir.path().join("host-cli.pid"); + let mut host = + tokio::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).expect("fixture args"), + ) + .env("HOST_CRASH_FIXTURE_CWD", fake.dir.path()) + .env("HOST_CRASH_FIXTURE_ENV_JSON", "[]") + .env("HOST_CRASH_FIXTURE_PID_FILE", &host_pid_file) + .env( + "HOST_CRASH_FIXTURE_TRANSPORT", + if tcp { "tcp" } else { "stdio" }, + ) + .kill_on_drop(true) + .spawn() + .expect("spawn SDK host fixture"); + let reported_root = published_pid(&host_pid_file).await; + assert_eq!(reported_root, published_pid(&fake.pid_file).await); + let (root, descendant) = retain_tree(&fake).await; + tokio::time::timeout(Duration::from_secs(5), host.kill()) + .await + .expect("bound abrupt host termination") + .expect("kill and reap SDK host"); + root.assert_exited().await; + descendant.assert_exited().await; + } +} diff --git a/rust/tests/e2e/session_lifecycle.rs b/rust/tests/e2e/session_lifecycle.rs index 545bb49885..d406297e02 100644 --- a/rust/tests/e2e/session_lifecycle.rs +++ b/rust/tests/e2e/session_lifecycle.rs @@ -1,7 +1,12 @@ +use std::sync::Arc; + +use github_copilot_sdk::ResumeSessionConfig; +use github_copilot_sdk::handler::ApproveAllHandler; use github_copilot_sdk::session_events::SessionEventType; use super::support::{ - assistant_message_content, collect_until_idle, event_types, wait_for_condition, + DEFAULT_TEST_TOKEN, assistant_message_content, collect_until_idle, event_types, + wait_for_condition, }; #[tokio::test] @@ -57,6 +62,75 @@ async fn should_list_created_sessions_after_sending_a_message() { .await; } +#[tokio::test] +async fn should_persist_a_session_named_at_create_without_a_turn() { + // Dedicated context: the persisted name must survive a real CLI restart, and the + // shared group's client is never actually stopped. + super::support::with_dedicated_e2e_context( + "session_lifecycle", + "should_persist_a_session_named_at_create_without_a_turn", + |ctx| { + Box::pin(async move { + ctx.set_default_copilot_user(); + let client = ctx.start_client().await; + let named = client + .create_session( + ctx.approve_all_session_config() + .with_name("Named at create"), + ) + .await + .expect("create named session"); + let named_id = named.id().clone(); + let unnamed = client + .create_session(ctx.approve_all_session_config()) + .await + .expect("create unnamed session"); + let unnamed_id = unnamed.id().clone(); + + let created_name = named.rpc().name().get().await.expect("get name").name; + assert_eq!(created_name.as_deref(), Some("Named at create")); + + named.disconnect().await.expect("disconnect named session"); + unnamed + .disconnect() + .await + .expect("disconnect unnamed session"); + + // Neither session ran a turn: only the create-time name makes one persist. + let listed: std::collections::HashSet<_> = client + .list_sessions(None) + .await + .expect("list sessions") + .into_iter() + .map(|session| session.session_id) + .collect(); + assert!(listed.contains(&named_id), "named session is listed"); + assert!(!listed.contains(&unnamed_id), "unnamed session is not"); + + // Restart the CLI so the resume below reads the name back from disk. + client.stop().await.expect("stop client"); + let cold_client = ctx.start_client().await; + let resumed = cold_client + .resume_session( + ResumeSessionConfig::new(named_id.clone()) + .with_permission_handler(Arc::new(ApproveAllHandler)) + .with_github_token(DEFAULT_TEST_TOKEN), + ) + .await + .expect("resume named session"); + let read = resumed.rpc().name().get().await.expect("get resumed name"); + assert_eq!(read.name.as_deref(), Some("Named at create")); + resumed + .disconnect() + .await + .expect("disconnect resumed session"); + cold_client.stop().await.expect("stop cold client"); + }) + }, + ) + .await; +} + #[tokio::test] async fn should_delete_session_permanently() { super::support::with_shared_e2e_context( diff --git a/rust/tests/e2e/subagent_hooks.rs b/rust/tests/e2e/subagent_hooks.rs index 56cad894cb..2865058207 100644 --- a/rust/tests/e2e/subagent_hooks.rs +++ b/rust/tests/e2e/subagent_hooks.rs @@ -142,8 +142,6 @@ async fn should_apply_subagent_lifecycle_hook_outputs() { ); let requests = request_log.inference_records(); assert_subagent_request_metadata(&requests); - let context_and_task = format!("{CHILD_CONTEXT}\n\nRead the file \"subagent-test.txt\""); - let prompt_has_context = |prompt: &str| prompt.contains(context_and_task.as_str()); assert!( requests .iter() @@ -151,22 +149,7 @@ async fn should_apply_subagent_lifecycle_hook_outputs() { .any(|request| { let body: serde_json::Value = serde_json::from_str(&request.body) .expect("child inference request body"); - body["messages"].as_array().is_some_and(|messages| { - messages.iter().any(|message| { - message["role"] == "user" - && (message["content"] - .as_str() - .is_some_and(prompt_has_context) - || message["content"].as_array().is_some_and(|parts| { - parts.iter().any(|part| { - part["type"] == "text" - && part["text"] - .as_str() - .is_some_and(prompt_has_context) - }) - })) - }) - }) + has_child_context(&body) }), "start hook context should be prepended to the child inference prompt" ); @@ -219,6 +202,55 @@ async fn should_apply_subagent_lifecycle_hook_outputs() { .await; } +fn has_child_context(body: &serde_json::Value) -> bool { + let expected = format!("{CHILD_CONTEXT}\n\nRead the file \"subagent-test.txt\""); + let prompt_has_context = |prompt: &str| prompt.contains(expected.as_str()); + if let Some(input) = body["input"].as_str() { + return prompt_has_context(input); + } + body.get("messages") + .filter(|messages| !messages.is_null()) + .unwrap_or(&body["input"]) + .as_array() + .is_some_and(|messages| { + messages.iter().any(|message| { + message["role"] == "user" + && (message["content"].as_str().is_some_and(prompt_has_context) + || message["content"].as_array().is_some_and(|parts| { + parts.iter().any(|part| { + (part["type"] == "text" || part["type"] == "input_text") + && part["text"].as_str().is_some_and(prompt_has_context) + }) + })) + }) + }) +} + +#[test] +fn recognizes_user_context_across_request_formats() { + use serde_json::json; + + let text = format!("{CHILD_CONTEXT}\n\nRead the file \"subagent-test.txt\""); + let user = json!({"role": "user", "content": text}); + assert!(has_child_context(&json!({"messages": [user]}))); + assert!(has_child_context(&json!({"input": text}))); + assert!(has_child_context(&json!({"input": [user]}))); + for kind in ["text", "input_text"] { + assert!(has_child_context(&json!({"input": [{ + "role": "user", "content": [{"type": kind, "text": text}] + }]}))); + } + assert!(!has_child_context(&json!({"input": [ + {"role": "assistant", "content": text}, + {"type": "function_call_output", "output": text}, + {"role": "user", "content": [ + null, "ignored", {"type": "input_text", "text": 42}, + {"type": "input_image", "text": text} + ]} + ]}))); + assert!(!has_child_context(&json!({}))); +} + #[derive(Clone, Debug)] struct HookEntry { kind: String, diff --git a/rust/tests/e2e/telemetry.rs b/rust/tests/e2e/telemetry.rs index fe83a1e2ea..0d54f31868 100644 --- a/rust/tests/e2e/telemetry.rs +++ b/rust/tests/e2e/telemetry.rs @@ -1,3 +1,5 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. + use std::sync::Arc; use async_trait::async_trait; @@ -194,12 +196,19 @@ async fn should_export_per_request_subagent_chat_spans() { .iter() .filter(|entry| string_property(entry, "type") == Some("span")) .collect(); - assert!(spans.iter().all(|span| { - span.get("instrumentationScope") - .and_then(|scope| string_property(scope, "name")) - == Some(source_name) - && status_code(span) != Some(2) - })); + for span in &spans { + assert_eq!( + span.get("instrumentationScope") + .and_then(|scope| string_property(scope, "name")), + Some(source_name), + "unexpected instrumentation scope: {span:#}" + ); + assert_ne!( + status_code(span), + Some(2), + "unexpected error span: {span:#}" + ); + } let invocations: Vec<_> = spans .iter() .copied() diff --git a/rust/tests/fixtures/host_crash_fixture.rs b/rust/tests/fixtures/host_crash_fixture.rs index 8c48379001..204b5dbb67 100644 --- a/rust/tests/fixtures/host_crash_fixture.rs +++ b/rust/tests/fixtures/host_crash_fixture.rs @@ -13,6 +13,7 @@ //! on the spawned CLI's environment. //! - `HOST_CRASH_FIXTURE_PID_FILE`: path this process atomically publishes the //! CLI child's OS process id to, once the client finishes starting. +//! - `HOST_CRASH_FIXTURE_TRANSPORT`: optional `stdio` (default) or `tcp`. use std::io::Write; use std::path::PathBuf; @@ -38,13 +39,21 @@ async fn main() { std::env::var("HOST_CRASH_FIXTURE_PID_FILE").expect("HOST_CRASH_FIXTURE_PID_FILE"), ); + let transport = match std::env::var("HOST_CRASH_FIXTURE_TRANSPORT").as_deref() { + Ok("tcp") => Transport::Tcp { + port: 0, + connection_token: None, + }, + Ok("stdio") | Err(std::env::VarError::NotPresent) => Transport::Stdio, + value => panic!("invalid HOST_CRASH_FIXTURE_TRANSPORT: {value:?}"), + }; 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); + .with_transport(transport); let client = Client::start(options).await.expect("start CLI client"); let pid = client.pid().expect("client reports spawned CLI pid"); diff --git a/rust/tests/http_request_body_test.rs b/rust/tests/http_request_body_test.rs new file mode 100644 index 0000000000..d200803d63 --- /dev/null +++ b/rust/tests/http_request_body_test.rs @@ -0,0 +1,614 @@ +//! The LLM-callback request body on its way through the dispatcher: the runtime sends an HTTP +//! request body in `llmInference.httpRequestChunk` frames, and the dispatcher moves the body out +//! of the parsed frame instead of copying it (a deep clone of the params `Value`, a clone of +//! `data`, and a copy of the first frame into an empty buffer). The handler must see the +//! runtime's bytes exactly: an escaped JSON body, an empty body, a body in several chunks, a +//! base64 chunk, a chunk that races ahead of its start frame, and a cancel chunk after part of +//! the body. The wire around the body is pinned too: every case asserts the exact ack of each +//! request it sends and the response frames in order, and one more case the exact error reply +//! to params that do not parse. And once the frame is parsed, a single-chunk body must not be +//! copied again: a counting allocator counts the allocations of exactly the body's length +//! between the chunk frame's write and the handler's entry. +//! +//! Each case drives the real dispatcher: `Client::start` over loopback TCP with a request +//! handler, the test playing the CLI. The allocator is process-global and `cargo test` runs a +//! binary's tests on parallel threads, so the counting case is `#[serial]` and every other case +//! `#[parallel]`: nothing else runs while it counts. + +use std::alloc::{GlobalAlloc, Layout, System}; +use std::collections::HashMap; +use std::sync::atomic::{AtomicUsize, Ordering}; +use std::time::Duration; + +use async_trait::async_trait; +use bytes::Bytes; +use futures_util::stream; +use github_copilot_sdk::{ + Client, ClientOptions, CopilotHttpRequest, CopilotHttpResponse, CopilotRequestContext, + CopilotRequestError, CopilotRequestHandler, SDK_PROTOCOL_VERSION, Transport, +}; +use http::HeaderMap; +use serde_json::{Value, json}; +use serial_test::{parallel, serial}; +use tokio::io::{AsyncBufReadExt, AsyncReadExt, AsyncWriteExt, BufReader}; +use tokio::net::TcpListener; +use tokio::net::tcp::{OwnedReadHalf, OwnedWriteHalf}; +use tokio::sync::mpsc; +use tokio::time::timeout; + +/// A backstop against a hang, never a measurement. +const DEADLINE: Duration = Duration::from_secs(30); +/// The single-chunk body the allocation count watches. Odd, so no buffer that grows by doubling +/// (serde_json's unescape scratch) lands on it, and larger than every buffer between the socket +/// and the dispatcher. +const WATCHED_BODY_BYTES: usize = 2 * 1024 * 1024 + 1; + +/// The system allocator, counting the allocations (and reallocations) of exactly [`WATCHED`] +/// bytes while it is non-zero. +struct ExactSizeCounting; + +static WATCHED: AtomicUsize = AtomicUsize::new(0); +static HITS: AtomicUsize = AtomicUsize::new(0); + +fn note(size: usize) { + let watched = WATCHED.load(Ordering::SeqCst); + if watched != 0 && size == watched { + HITS.fetch_add(1, Ordering::SeqCst); + } +} + +// SAFETY: every method forwards to `System` with the caller's own arguments, so `System`'s +// contract is the caller's; the counters are plain atomics and never touch the memory. +unsafe impl GlobalAlloc for ExactSizeCounting { + unsafe fn alloc(&self, layout: Layout) -> *mut u8 { + note(layout.size()); + // SAFETY: forwarded unchanged (see the impl's SAFETY note). + unsafe { System.alloc(layout) } + } + + unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 { + note(layout.size()); + // SAFETY: forwarded unchanged (see the impl's SAFETY note). + unsafe { System.alloc_zeroed(layout) } + } + + unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { + // SAFETY: forwarded unchanged (see the impl's SAFETY note). + unsafe { System.dealloc(ptr, layout) } + } + + unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 { + note(new_size); + // SAFETY: forwarded unchanged (see the impl's SAFETY note). + unsafe { System.realloc(ptr, layout, new_size) } + } +} + +#[global_allocator] +static ALLOCATOR: ExactSizeCounting = ExactSizeCounting; + +/// What the handler saw: the body, and the watched-size allocations counted when it was entered. +struct Captured { + body: Vec, + hits_at_entry: usize, +} + +/// What the SDK wrote while one exchange ran: its replies to the test's requests, by id, and the +/// response requests it sent (method and params), in order. +struct Wire { + replies: HashMap, + response_frames: Vec<(String, Value)>, +} + +/// The wire of a well-formed exchange: the empty-result ack of every request the test sent, and +/// nothing else, then the response head (200, no headers) and its terminal empty chunk. +fn assert_wire(wire: &Wire, request_id: &str, request_ids: &[u64]) { + for id in request_ids { + assert_eq!( + wire.replies[id], + json!({"jsonrpc": "2.0", "id": id, "result": {}}), + "the ack of request {id}" + ); + } + assert_eq!( + wire.replies.len(), + request_ids.len(), + "replies: {:?}", + wire.replies + ); + assert_eq!( + wire.response_frames, + vec![ + ( + "llmInference.httpResponseStart".to_owned(), + json!({"requestId": request_id, "status": 200, "headers": {}}), + ), + ( + "llmInference.httpResponseChunk".to_owned(), + json!({"requestId": request_id, "data": "", "end": true}), + ), + ] + ); +} + +/// Hands every request's body to the test and answers an empty 200. +struct CaptureHandler(mpsc::UnboundedSender); + +#[async_trait] +impl CopilotRequestHandler for CaptureHandler { + async fn send_request( + &self, + request: CopilotHttpRequest, + _ctx: &CopilotRequestContext, + ) -> Result { + let hits_at_entry = HITS.load(Ordering::SeqCst); + let _ = self.0.send(Captured { + body: request.body, + hits_at_entry, + }); + Ok(CopilotHttpResponse::new( + 200, + None, + HeaderMap::new(), + Box::pin(stream::empty::>()), + )) + } +} + +/// One frame in the SDK's JSON-RPC shape: a `Content-Length` header, a blank line, the body. +fn frame_bytes(message: &Value) -> Vec { + let body = serde_json::to_vec(message).expect("a JSON-RPC message serialises"); + let mut frame = format!("Content-Length: {}\r\n\r\n", body.len()).into_bytes(); + frame.extend_from_slice(&body); + frame +} + +/// The next frame the SDK wrote, decoded. +async fn read_frame(read: &mut BufReader) -> Value { + let mut length = None; + loop { + let mut line = String::new(); + let read_bytes = read.read_line(&mut line).await.expect("a header line"); + assert_ne!(read_bytes, 0, "the SDK closed the socket"); + if line == "\r\n" { + break; + } + if let Some(value) = line.strip_prefix("Content-Length:") { + length = Some(value.trim().parse::().expect("a decimal length")); + } + } + let mut body = vec![0; length.expect("a Content-Length header")]; + read.read_exact(&mut body).await.expect("the frame body"); + serde_json::from_slice(&body).expect("a JSON frame body") +} + +/// The CLI's side of one SDK client: the public `Client` over real loopback TCP, with the +/// request handler registered through `llmInference.setProvider`. +struct Peer { + client: Client, + read: BufReader, + write: OwnedWriteHalf, + captured: mpsc::UnboundedReceiver, + _work: tempfile::TempDir, +} + +impl Peer { + async fn start() -> Self { + let listener = TcpListener::bind("127.0.0.1:0") + .await + .expect("a loopback listener"); + let port = listener.local_addr().expect("a bound address").port(); + let work = tempfile::tempdir().expect("a temp dir"); + let (captured_tx, captured) = mpsc::unbounded_channel(); + let options = ClientOptions::new() + .with_program(std::env::current_exe().expect("the test binary's path")) + .with_cwd(work.path()) + .with_transport(Transport::External { + host: "127.0.0.1".into(), + port, + connection_token: None, + }) + .with_request_handler(CaptureHandler(captured_tx)); + let (client, (read, write)) = timeout(DEADLINE, async { + tokio::join!(Client::start(options), async { + let (socket, _) = listener.accept().await.expect("the SDK connects"); + socket.set_nodelay(true).expect("TCP_NODELAY"); + let (read, mut write) = socket.into_split(); + let mut read = BufReader::new(read); + for method in ["connect", "llmInference.setProvider"] { + let message = read_frame(&mut read).await; + assert_eq!(message["method"], method); + let result = if method == "connect" { + json!({ + "ok": true, + "version": "test", + "protocolVersion": SDK_PROTOCOL_VERSION + }) + } else { + json!({"success": true}) + }; + let reply = json!({"jsonrpc": "2.0", "id": message["id"], "result": result}); + write + .write_all(&frame_bytes(&reply)) + .await + .expect("the handshake reply"); + } + (read, write) + }) + }) + .await + .expect("the client handshake finishes"); + Self { + client: client.expect("the client starts"), + read, + write, + captured, + _work: work, + } + } + + /// Bounded: an SDK that stops reading with the socket open would otherwise park a large frame's + /// write on TCP backpressure, past every receive deadline. + async fn send(&mut self, message: &Value) { + timeout(DEADLINE, self.write.write_all(&frame_bytes(message))) + .await + .expect("the SDK takes the frame in time") + .expect("a frame to the SDK"); + } + + async fn start_request(&mut self, request_id: &str, id: u64) { + self.send(&json!({ + "jsonrpc": "2.0", + "id": id, + "method": "llmInference.httpRequestStart", + "params": { + "requestId": request_id, + "method": "POST", + "url": "http://unused.test/chat", + "headers": {} + } + })) + .await; + } + + async fn chunk(&mut self, id: u64, params: Value) { + self.send(&json!({ + "jsonrpc": "2.0", + "id": id, + "method": "llmInference.httpRequestChunk", + "params": params + })) + .await; + } + + /// What the handler received for the next request. + async fn captured(&mut self) -> Captured { + timeout(DEADLINE, self.captured.recv()) + .await + .expect("the handler is entered") + .expect("the handler's channel is open") + } + + /// Serves the CLI's side of the response (acking the SDK's head and body requests) until its + /// terminal body chunk has arrived and every one of `request_ids` has its reply, so the + /// exchange finishes before the client stops; returns everything the SDK wrote meanwhile. + async fn finish_response(&mut self, request_ids: &[u64]) -> Wire { + let mut wire = Wire { + replies: HashMap::new(), + response_frames: Vec::new(), + }; + timeout(DEADLINE, async { + let mut terminal = false; + while !terminal || request_ids.iter().any(|id| !wire.replies.contains_key(id)) { + let message = read_frame(&mut self.read).await; + let Some(method) = message.get("method").and_then(Value::as_str) else { + let id = message["id"] + .as_u64() + .expect("a reply carries its request's id"); + wire.replies.insert(id, message); + continue; + }; + terminal = + method == "llmInference.httpResponseChunk" && message["params"]["end"] == true; + wire.response_frames + .push((method.to_owned(), message["params"].clone())); + let ack = json!({ + "jsonrpc": "2.0", + "id": message["id"], + "result": {"accepted": true} + }); + self.send(&ack).await; + } + }) + .await + .expect("the response finishes and every request is answered"); + wire + } + + /// The SDK's replies to `request_ids`, for frames that start no exchange: any request from the + /// SDK meanwhile fails the test. + async fn replies(&mut self, request_ids: &[u64]) -> HashMap { + let mut replies = HashMap::new(); + timeout(DEADLINE, async { + while request_ids.iter().any(|id| !replies.contains_key(id)) { + let message = read_frame(&mut self.read).await; + assert!( + message.get("method").is_none(), + "an unexpected request: {message}" + ); + let id = message["id"] + .as_u64() + .expect("a reply carries its request's id"); + replies.insert(id, message); + } + }) + .await + .expect("every request is answered"); + replies + } + + async fn stop(self) { + self.client.stop().await.expect("the client stops"); + } +} + +/// A chat-request-shaped JSON body of exactly `len` bytes, dense with quotes and backslashes so +/// its frame carries it escaped, as the runtime's does. +fn escaped_body(len: usize) -> String { + let unit = r#"{"role":"user","content":"a \"quoted\" path C:\\dir\\file, tab\there"},"#; + let mut out = String::with_capacity(len); + out.push_str(r#"{"messages":["#); + while out.len() + unit.len() <= len { + out.push_str(unit); + } + while out.len() < len { + out.push('x'); + } + out +} + +/// Every JSON escape class (quote, backslash, the short escapes, the other control bytes) and +/// multi-byte UTF-8, after a body large enough to span many socket reads. +fn every_escape_class_body() -> String { + let mut out = escaped_body(1024 * 1024); + out.push_str( + "controls: \u{0}\u{1}\u{8}\u{b}\u{c}\u{1f} cr\r lf\n unicode: \u{e9}\u{4e2d}\u{1f600}", + ); + out +} + +#[tokio::test(flavor = "multi_thread")] +#[parallel] +async fn an_escaped_json_body_reaches_the_handler_byte_for_byte() { + let mut peer = Peer::start().await; + let body = every_escape_class_body(); + + peer.start_request("escaped", 10).await; + peer.chunk( + 11, + json!({"requestId": "escaped", "data": body, "end": true}), + ) + .await; + + let captured = peer.captured().await; + assert_eq!(captured.body, body.as_bytes()); + let wire = peer.finish_response(&[10, 11]).await; + assert_wire(&wire, "escaped", &[10, 11]); + peer.stop().await; +} + +#[tokio::test(flavor = "multi_thread")] +#[parallel] +async fn an_empty_body_reaches_the_handler_empty() { + let mut peer = Peer::start().await; + + peer.start_request("empty", 20).await; + peer.chunk(21, json!({"requestId": "empty", "data": "", "end": true})) + .await; + + let captured = peer.captured().await; + assert_eq!(captured.body, Vec::::new()); + let wire = peer.finish_response(&[20, 21]).await; + assert_wire(&wire, "empty", &[20, 21]); + peer.stop().await; +} + +#[tokio::test(flavor = "multi_thread")] +#[parallel] +async fn a_body_in_several_chunks_reaches_the_handler_in_order() { + let mut peer = Peer::start().await; + let parts = [ + escaped_body(64 * 1024), + escaped_body(1000), + escaped_body(70_001), + ]; + + peer.start_request("several", 30).await; + peer.chunk(31, json!({"requestId": "several", "data": parts[0]})) + .await; + // An empty, non-terminal chunk carries no bytes and must not disturb the order. + peer.chunk(32, json!({"requestId": "several", "data": ""})) + .await; + peer.chunk(33, json!({"requestId": "several", "data": parts[1]})) + .await; + peer.chunk( + 34, + json!({"requestId": "several", "data": parts[2], "end": true}), + ) + .await; + + let captured = peer.captured().await; + assert_eq!(captured.body, parts.concat().as_bytes()); + let wire = peer.finish_response(&[30, 31, 32, 33, 34]).await; + assert_wire(&wire, "several", &[30, 31, 32, 33, 34]); + peer.stop().await; +} + +#[tokio::test(flavor = "multi_thread")] +#[parallel] +async fn a_base64_chunk_reaches_the_handler_decoded_before_a_text_chunk() { + let mut peer = Peer::start().await; + + peer.start_request("binary", 40).await; + // "AAEC/w==" is the standard base64 of the bytes 0x00 0x01 0x02 0xff. + peer.chunk( + 41, + json!({"requestId": "binary", "data": "AAEC/w==", "binary": true}), + ) + .await; + peer.chunk( + 42, + json!({"requestId": "binary", "data": "tail", "end": true}), + ) + .await; + + let captured = peer.captured().await; + assert_eq!(captured.body, b"\x00\x01\x02\xfftail".to_vec()); + let wire = peer.finish_response(&[40, 41, 42]).await; + assert_wire(&wire, "binary", &[40, 41, 42]); + peer.stop().await; +} + +#[tokio::test(flavor = "multi_thread")] +#[parallel] +async fn a_chunk_ahead_of_its_start_frame_reaches_the_handler() { + let mut peer = Peer::start().await; + let body = escaped_body(300_001); + + peer.chunk(51, json!({"requestId": "early", "data": body, "end": true})) + .await; + peer.start_request("early", 50).await; + + let captured = peer.captured().await; + assert_eq!(captured.body, body.as_bytes()); + let wire = peer.finish_response(&[50, 51]).await; + assert_wire(&wire, "early", &[50, 51]); + peer.stop().await; +} + +/// A cancel chunk after part of the body, on the `apply_chunk` path that moves `data`: the handler +/// sees the bytes that arrived before it, each request is acked, and the response is its head and +/// then a terminal chunk carrying the cancellation. +#[tokio::test(flavor = "multi_thread")] +#[parallel] +async fn a_cancel_chunk_ends_the_body_and_the_response_with_the_cancellation() { + let mut peer = Peer::start().await; + let part = escaped_body(10_001); + + peer.start_request("cancelled", 80).await; + peer.chunk(81, json!({"requestId": "cancelled", "data": part})) + .await; + peer.chunk( + 82, + json!({"requestId": "cancelled", "data": "", "cancel": true}), + ) + .await; + + let captured = peer.captured().await; + assert_eq!(captured.body, part.as_bytes()); + let wire = peer.finish_response(&[80, 81, 82]).await; + for id in [80, 81, 82] { + assert_eq!( + wire.replies[&id], + json!({"jsonrpc": "2.0", "id": id, "result": {}}), + "the ack of request {id}" + ); + } + assert_eq!(wire.replies.len(), 3, "replies: {:?}", wire.replies); + assert_eq!( + wire.response_frames, + vec![ + ( + "llmInference.httpResponseStart".to_owned(), + json!({"requestId": "cancelled", "status": 200, "headers": {}}), + ), + ( + "llmInference.httpResponseChunk".to_owned(), + json!({ + "requestId": "cancelled", + "data": "", + "end": true, + "error": {"code": "cancelled", "message": "Request cancelled by runtime"} + }), + ), + ] + ); + peer.stop().await; +} + +/// A start or chunk frame whose params do not parse gets the dispatcher's error reply: the params +/// move into the parse instead of being cloned into it, and a failed parse still answers. +#[tokio::test(flavor = "multi_thread")] +#[parallel] +async fn unparsable_start_and_chunk_params_get_the_error_reply() { + let mut peer = Peer::start().await; + + // Neither frame names the requestId that both params types require. + peer.send(&json!({ + "jsonrpc": "2.0", + "id": 70, + "method": "llmInference.httpRequestStart", + "params": {"method": "POST", "url": "http://unused.test/chat", "headers": {}} + })) + .await; + peer.chunk(71, json!({"data": "x", "end": true})).await; + + let replies = peer.replies(&[70, 71]).await; + assert_eq!( + replies[&70], + json!({ + "jsonrpc": "2.0", + "id": 70, + "error": {"code": -32603, "message": "invalid llmInference.httpRequestStart params"} + }) + ); + assert_eq!( + replies[&71], + json!({ + "jsonrpc": "2.0", + "id": 71, + "error": {"code": -32603, "message": "invalid llmInference.httpRequestChunk params"} + }) + ); + peer.stop().await; +} + +/// The mechanism: between the chunk frame's write and the handler's entry, the body is +/// allocated at its exact length once, by the frame parse (`serde_json`'s `Value` string), and +/// the dispatcher moves that buffer to the handler. Copying it instead allocated it three more +/// times (the params `Value` clone, the `data` clone, the drain into an empty `Vec`); reverting +/// any one of the three moves adds one. +#[tokio::test(flavor = "multi_thread")] +#[serial] +async fn a_single_chunk_body_reaches_the_handler_without_a_copy() { + let mut peer = Peer::start().await; + let body = escaped_body(WATCHED_BODY_BYTES); + assert_eq!(body.len(), WATCHED_BODY_BYTES); + peer.start_request("single", 60).await; + // Encoded before the count starts: building the frame allocates the body's length too. + let chunk = frame_bytes(&json!({ + "jsonrpc": "2.0", + "id": 61, + "method": "llmInference.httpRequestChunk", + "params": {"requestId": "single", "data": body, "end": true} + })); + + HITS.store(0, Ordering::SeqCst); + WATCHED.store(WATCHED_BODY_BYTES, Ordering::SeqCst); + timeout(DEADLINE, peer.write.write_all(&chunk)) + .await + .expect("the SDK takes the chunk frame in time") + .expect("the chunk frame to the SDK"); + let captured = peer.captured().await; + WATCHED.store(0, Ordering::SeqCst); + + assert_eq!(captured.body, body.as_bytes()); + assert_eq!( + captured.hits_at_entry, 1, + "allocations of exactly the body's {WATCHED_BODY_BYTES} bytes between the frame's write \ + and the handler's entry: the frame parse's one, and no relay copy" + ); + let wire = peer.finish_response(&[60, 61]).await; + assert_wire(&wire, "single", &[60, 61]); + peer.stop().await; +} diff --git a/rust/tests/session_fs_reply_test.rs b/rust/tests/session_fs_reply_test.rs new file mode 100644 index 0000000000..7dc0fc56c5 --- /dev/null +++ b/rust/tests/session_fs_reply_test.rs @@ -0,0 +1,424 @@ +//! A sessionFs reply on its way out: the SDK encodes the provider's result once, +//! into a single frame buffer, and drops the result before the frame is +//! written. The wire stays as it was: every case asserts the exact frame bytes, +//! a `Content-Length` that states the body's length and the body the published +//! path wrote, `{"jsonrpc":"2.0","id":N,"result":R}` with `R` the result as a +//! `serde_json::Value` writes it (keys sorted). And a large `readFile` reply is +//! allocated once: a counting allocator records every allocation at least as +//! large as the reply's content, from the request's write until the reply's +//! last byte is read. +//! +//! The allocator is process-global and `cargo test` runs a binary's tests on +//! parallel threads, so the counting case is `#[serial]` and every other case +//! `#[parallel]`: nothing else runs while it counts. + +use std::alloc::{GlobalAlloc, Layout, System}; +use std::collections::HashMap; +use std::sync::atomic::{AtomicUsize, Ordering}; +use std::sync::{Arc, Mutex}; +use std::time::Duration; + +use async_trait::async_trait; +use github_copilot_sdk::Client; +use github_copilot_sdk::session::Session; +use github_copilot_sdk::session_fs::{ + DirEntry, DirEntryKind, FileInfo, FsError, FsErrorKind, SessionFsProvider, + SessionFsSqliteProvider, SessionFsSqliteQueryResult, SessionFsSqliteQueryType, +}; +use github_copilot_sdk::types::SessionConfig; +use serde_json::{Value, json}; +use serial_test::{parallel, serial}; +use tokio::io::{AsyncReadExt, AsyncWriteExt, DuplexStream, duplex}; +use tokio::time::timeout; + +/// A backstop against a hang, never a measurement. +const DEADLINE: Duration = Duration::from_secs(30); +/// The content of the counting case's `readFile` reply. Odd, so no buffer that +/// grows by doubling lands on it. +const CONTENT_BYTES: usize = 8 * 1024 * 1024 + 1; +/// The room the SDK reserves in front of a frame's body for its header +/// (`jsonrpc::FRAME_PREFIX`, private to the crate). +const HEADER_ROOM: usize = 40; + +/// The system allocator, recording the size of every allocation (and +/// reallocation) of at least [`WATCH_FROM`] bytes while it is non-zero. +struct LargeAllocationRecorder; + +static WATCH_FROM: AtomicUsize = AtomicUsize::new(0); +static LARGE_COUNT: AtomicUsize = AtomicUsize::new(0); +static LARGE: [AtomicUsize; 16] = [const { AtomicUsize::new(0) }; 16]; + +fn note(size: usize) { + let from = WATCH_FROM.load(Ordering::SeqCst); + if from != 0 && size >= from { + let slot = LARGE_COUNT.fetch_add(1, Ordering::SeqCst); + if let Some(entry) = LARGE.get(slot) { + entry.store(size, Ordering::SeqCst); + } + } +} + +// SAFETY: every method forwards to `System` with the caller's own arguments, so +// `System`'s contract is the caller's; the counters are plain atomics and never +// touch the memory. +unsafe impl GlobalAlloc for LargeAllocationRecorder { + unsafe fn alloc(&self, layout: Layout) -> *mut u8 { + note(layout.size()); + // SAFETY: forwarded unchanged (see the impl's SAFETY note). + unsafe { System.alloc(layout) } + } + + unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 { + note(layout.size()); + // SAFETY: forwarded unchanged (see the impl's SAFETY note). + unsafe { System.alloc_zeroed(layout) } + } + + unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { + // SAFETY: forwarded unchanged (see the impl's SAFETY note). + unsafe { System.dealloc(ptr, layout) } + } + + unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 { + note(new_size); + // SAFETY: forwarded unchanged (see the impl's SAFETY note). + unsafe { System.realloc(ptr, layout, new_size) } + } +} + +#[global_allocator] +static ALLOCATOR: LargeAllocationRecorder = LargeAllocationRecorder; + +/// A provider whose files are handed out once each, so a read allocates +/// nothing; every other answer is fixed. +struct FixedProvider { + files: Mutex>, +} + +impl FixedProvider { + fn with_file(path: &str, content: String) -> Arc { + Arc::new(Self { + files: Mutex::new(HashMap::from([(path.to_string(), content)])), + }) + } +} + +#[async_trait] +impl SessionFsProvider for FixedProvider { + async fn read_file(&self, path: &str) -> Result { + self.files + .lock() + .expect("the files lock") + .remove(path) + .ok_or_else(|| FsError::from(FsErrorKind::NotFound(path.to_string()))) + } + + async fn write_file( + &self, + _path: &str, + _content: &str, + _mode: Option, + ) -> Result<(), FsError> { + Ok(()) + } + + async fn stat(&self, _path: &str) -> Result { + Ok(FileInfo::new( + true, + false, + 42, + "2025-01-01T00:00:00Z", + "2025-01-02T00:00:00Z", + )) + } + + async fn readdir_with_types(&self, _path: &str) -> Result, FsError> { + Ok(vec![ + DirEntry::new("README.md", DirEntryKind::File), + DirEntry::new("src", DirEntryKind::Directory), + ]) + } + + fn sqlite(&self) -> Option<&dyn SessionFsSqliteProvider> { + Some(self) + } +} + +#[async_trait] +impl SessionFsSqliteProvider for FixedProvider { + async fn sqlite_query( + &self, + _query_type: SessionFsSqliteQueryType, + _query: &str, + _params: Option<&HashMap>, + ) -> Result, FsError> { + // Keys inserted out of order: the reply must still write them sorted. + let row = HashMap::from([ + ("zeta".to_string(), json!(1)), + ("alpha".to_string(), json!("a")), + ("mid".to_string(), json!(null)), + ("beta".to_string(), json!([true, 2.5])), + ]); + Ok(Some(SessionFsSqliteQueryResult { + columns: vec![ + "zeta".to_string(), + "alpha".to_string(), + "mid".to_string(), + "beta".to_string(), + ], + rows: vec![row], + rows_affected: 0, + last_insert_rowid: None, + })) + } + + async fn sqlite_exists(&self) -> Result { + Ok(true) + } +} + +/// The CLI's side of one session: the frames the SDK writes, and a writer for +/// the test's requests. +struct Server { + read: DuplexStream, + write: DuplexStream, + session_id: String, +} + +impl Server { + async fn write_frame(&mut self, message: &Value) { + let body = serde_json::to_vec(message).expect("a JSON message"); + let header = format!("Content-Length: {}\r\n\r\n", body.len()); + self.write + .write_all(header.as_bytes()) + .await + .expect("the header to the SDK"); + self.write + .write_all(&body) + .await + .expect("the body to the SDK"); + self.write.flush().await.expect("a flush"); + } + + /// Reads one frame's header and returns the body length it states. + async fn read_header(&mut self) -> usize { + let mut header = String::new(); + while !header.ends_with("\r\n\r\n") { + let byte = self.read.read_u8().await.expect("a header byte"); + header.push(char::from(byte)); + } + header + .trim_end() + .strip_prefix("Content-Length: ") + .expect("a Content-Length header") + .parse() + .expect("a decimal length") + } + + async fn read_frame(&mut self) -> Vec { + let length = self.read_header().await; + let mut body = vec![0; length]; + self.read + .read_exact(&mut body) + .await + .expect("the frame body"); + body + } + + async fn request(&mut self, id: u64, method: &str, mut params: Value) { + params["sessionId"] = json!(self.session_id); + self.write_frame(&json!({"jsonrpc": "2.0", "id": id, "method": method, "params": params})) + .await; + } +} + +/// A session with `provider` installed, the test answering `session.create`. +async fn start(provider: Arc) -> (Session, Server) { + let (client_write, server_read) = duplex(64 * 1024); + let (server_write, client_read) = duplex(64 * 1024); + let client = Client::from_streams(client_read, client_write, std::env::temp_dir()) + .expect("a client over the streams"); + let mut server = Server { + read: server_read, + write: server_write, + session_id: String::new(), + }; + let create = tokio::spawn(async move { + client + .create_session(SessionConfig::default().with_session_fs_provider(provider)) + .await + .expect("the session starts") + }); + let request: Value = serde_json::from_slice(&server.read_frame().await).expect("a request"); + assert_eq!(request["method"], "session.create"); + server.session_id = request["params"]["sessionId"] + .as_str() + .expect("session.create carries the session id") + .to_string(); + let reply = json!({ + "jsonrpc": "2.0", + "id": request["id"], + "result": {"sessionId": server.session_id, "workspacePath": "/tmp/workspace"} + }); + server.write_frame(&reply).await; + let session = timeout(DEADLINE, create) + .await + .expect("session.create is answered in time") + .expect("the create task"); + (session, server) +} + +/// The body the published path wrote for a reply to `id` whose result is +/// `result`: the response's fields in order, the result as a `Value` writes it. +fn published_body(id: u64, result: &Value) -> String { + let result = serde_json::to_string(result).expect("a JSON result"); + format!(r#"{{"jsonrpc":"2.0","id":{id},"result":{result}}}"#) +} + +/// Sends one request and returns the reply's result, after asserting that the +/// reply's bytes are exactly the published path's. +async fn reply_to(server: &mut Server, id: u64, method: &str, params: Value) -> Value { + server.request(id, method, params).await; + let body = timeout(DEADLINE, server.read_frame()) + .await + .expect("the reply arrives in time"); + let reply: Value = serde_json::from_slice(&body).expect("a JSON reply"); + let result = reply["result"].clone(); + assert_eq!( + String::from_utf8(body).expect("a UTF-8 body"), + published_body(id, &result), + "the reply to {method}" + ); + result +} + +/// The reply to a `sessionFs.readFile` of `path`, checked as [`reply_to`] does. +async fn read_file(server: &mut Server, id: u64, path: &str) -> Value { + reply_to(server, id, "sessionFs.readFile", json!({ "path": path })).await +} + +#[tokio::test(flavor = "multi_thread")] +#[parallel] +async fn a_read_file_reply_is_written_as_published_for_every_escape_class() { + let content = "quote \" backslash \\ newline \n return \r tab \t backspace \u{8} \ + formfeed \u{c} controls \u{1}\u{1f} del \u{7f} utf-8 \u{e9}\u{4e2d}\u{1f600}" + .to_string(); + let provider = FixedProvider::with_file("/file.txt", content.clone()); + let (_session, mut server) = start(provider).await; + let result = read_file(&mut server, 7, "/file.txt").await; + assert_eq!(result, json!({"content": content})); +} + +#[tokio::test(flavor = "multi_thread")] +#[parallel] +async fn a_missing_file_reply_is_written_as_published() { + let (_session, mut server) = start(FixedProvider::with_file("/file.txt", String::new())).await; + let result = read_file(&mut server, 8, "/missing.txt").await; + assert_eq!(result["error"]["code"], "ENOENT"); +} + +#[tokio::test(flavor = "multi_thread")] +#[parallel] +async fn write_file_stat_and_readdir_replies_are_written_as_published() { + let (_session, mut server) = start(FixedProvider::with_file("/file.txt", String::new())).await; + let written = reply_to( + &mut server, + 9, + "sessionFs.writeFile", + json!({"path": "/out.txt", "content": "x"}), + ) + .await; + assert_eq!(written, Value::Null); + let path = json!({"path": "/file.txt"}); + let stat = reply_to(&mut server, 10, "sessionFs.stat", path).await; + assert_eq!(stat["size"], 42); + let listing = reply_to( + &mut server, + 11, + "sessionFs.readdirWithTypes", + json!({"path": "/"}), + ) + .await; + assert_eq!(listing["entries"][1]["name"], "src"); +} + +/// A sqlite row is a `HashMap`: its keys must still go out sorted, as the +/// published path's `Value` wrote them. +#[tokio::test(flavor = "multi_thread")] +#[parallel] +async fn a_sqlite_reply_writes_its_row_keys_sorted() { + let (_session, mut server) = start(FixedProvider::with_file("/file.txt", String::new())).await; + let result = reply_to( + &mut server, + 12, + "sessionFs.sqliteQuery", + json!({"queryType": "query", "query": "select 1"}), + ) + .await; + assert_eq!( + result["rows"][0], + json!({"alpha": "a", "beta": [true, 2.5], "mid": null, "zeta": 1}) + ); +} + +#[tokio::test(flavor = "multi_thread")] +#[parallel] +async fn a_six_mebibyte_reply_is_one_frame_written_as_published() { + let content = "0123456789abcdef".repeat(6 * 1024 * 1024 / 16); + let (_session, mut server) = start(FixedProvider::with_file("/big.txt", content.clone())).await; + let result = read_file(&mut server, 13, "/big.txt").await; + let length = result["content"].as_str().map(str::len); + assert_eq!(length, Some(content.len())); +} + +/// The mechanism: between the request's write and the reply's last byte, the +/// only allocation at least as large as the content is the reply's frame +/// buffer. It holds the frame behind the room reserved for the header and grows +/// by doubling, so it is at least the frame's length and less than twice the +/// frame plus that room. The published path allocated the content again (a +/// `Value` copy), a body grown by doubling, and a framed copy of the body. +#[tokio::test(flavor = "multi_thread")] +#[serial] +async fn a_read_file_reply_is_allocated_once_in_its_frame_buffer() { + // A quote every 64 bytes, so escaping changes the length. + let content: String = (0..CONTENT_BYTES) + .map(|i| if i % 64 == 63 { '"' } else { 'a' }) + .collect(); + let (_session, mut server) = start(FixedProvider::with_file("/events.jsonl", content)).await; + // Room for the escaped reply, allocated before the count starts. + let mut body = vec![0u8; 2 * CONTENT_BYTES + 4096]; + + LARGE_COUNT.store(0, Ordering::SeqCst); + WATCH_FROM.store(CONTENT_BYTES, Ordering::SeqCst); + server + .request(20, "sessionFs.readFile", json!({"path": "/events.jsonl"})) + .await; + let length = timeout(DEADLINE, server.read_header()) + .await + .expect("the reply arrives in time"); + timeout(DEADLINE, server.read.read_exact(&mut body[..length])) + .await + .expect("the reply's body arrives in time") + .expect("the reply's body"); + WATCH_FROM.store(0, Ordering::SeqCst); + + let count = LARGE_COUNT.load(Ordering::SeqCst); + let sizes: Vec = LARGE + .iter() + .take(count) + .map(|size| size.load(Ordering::SeqCst)) + .collect(); + let frame = format!("Content-Length: {length}\r\n\r\n").len() + length; + let most = 2 * (frame + HEADER_ROOM); + assert!( + sizes.len() == 1 && (frame..most).contains(&sizes[0]), + "allocations of at least the content's {CONTENT_BYTES} bytes while the reply was built \ + and written: {sizes:?}; want only the frame's buffer, of {frame} to {most} bytes" + ); + let reply: Value = serde_json::from_slice(&body[..length]).expect("a JSON reply"); + assert_eq!(reply["id"], 20); + assert_eq!( + reply["result"]["content"].as_str().map(str::len), + Some(CONTENT_BYTES) + ); +} diff --git a/rust/tests/session_test.rs b/rust/tests/session_test.rs index 40c455549c..48a31e9e02 100644 --- a/rust/tests/session_test.rs +++ b/rust/tests/session_test.rs @@ -1646,6 +1646,7 @@ async fn create_session_omits_unset_diagnostics() { let request = read_framed(&mut server_read).await; assert_eq!(request["method"], "session.create"); assert!(request["params"].get("diagnostics").is_none()); + assert!(request["params"].get("interactiveUser").is_none()); let session_id = requested_session_id(&request).to_string(); server_respond_create(&mut server_write, &request, &session_id).await; @@ -1673,6 +1674,7 @@ async fn cold_resume_session_omits_unset_diagnostics() { let request = read_framed(&mut server_read).await; assert_eq!(request["method"], "session.resume"); assert!(request["params"].get("diagnostics").is_none()); + assert!(request["params"].get("interactiveUser").is_none()); server_respond_create(&mut server_write, &request, "diagnostics-default").await; respond_to_reload(&mut server_read, &mut server_write).await; @@ -1831,6 +1833,7 @@ async fn create_and_resume_send_managed_settings_permissions() { .create_session( SessionConfig::default() .with_enable_managed_settings(true) + .with_interactive_user(true) .with_managed_settings(managed), ) .await @@ -1841,6 +1844,7 @@ async fn create_and_resume_send_managed_settings_permissions() { let request = read_framed(&mut server_read).await; assert_eq!(request["method"], "session.create"); assert_eq!(request["params"]["enableManagedSettings"], true); + assert_eq!(request["params"]["interactiveUser"], true); let perms = &request["params"]["managedSettings"]["permissions"]; assert_eq!(perms["disableBypassPermissionsMode"], "allow-auto-only"); assert_eq!(perms["disableAssistedPermissionsMode"], true); @@ -1866,6 +1870,7 @@ async fn create_and_resume_send_managed_settings_permissions() { client .resume_session( ResumeSessionConfig::new(SessionId::from(session_id)) + .with_interactive_user(true) .with_managed_settings(managed), ) .await @@ -1875,6 +1880,7 @@ async fn create_and_resume_send_managed_settings_permissions() { let request = read_framed(&mut server_read).await; assert_eq!(request["method"], "session.resume"); + assert_eq!(request["params"]["interactiveUser"], true); assert_eq!( request["params"]["managedSettings"]["permissions"]["deny"][0], "shell(rm*)" @@ -1897,6 +1903,51 @@ async fn create_and_resume_send_managed_settings_permissions() { timeout(TIMEOUT, resume_handle).await.unwrap().unwrap(); } +#[tokio::test] +async fn create_and_resume_send_explicit_interactive_user_false() { + use github_copilot_sdk::types::ResumeSessionConfig; + + 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_interactive_user(false)) + .await + .unwrap() + } + }); + + let request = read_framed(&mut server_read).await; + assert_eq!(request["method"], "session.create"); + assert_eq!(request["params"]["interactiveUser"], false); + 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(); + + 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_interactive_user(false), + ) + .await + .unwrap() + } + }); + + let request = read_framed(&mut server_read).await; + assert_eq!(request["method"], "session.resume"); + assert_eq!(request["params"]["interactiveUser"], false); + server_respond_create(&mut server_write, &request, &session_id).await; + respond_to_reload(&mut server_read, &mut server_write).await; + timeout(TIMEOUT, resume_handle).await.unwrap().unwrap(); +} + #[test] fn managed_settings_resolved_event_preserves_client_provenance() { let sources = [ @@ -5549,6 +5600,77 @@ async fn elicitation_requested_dispatches_to_handler_and_responds() { assert_eq!(rpc_call["params"]["result"]["content"]["name"], "Alice"); } +#[tokio::test] +async fn elicitation_requested_forwards_the_wire_schema_verbatim() { + use github_copilot_sdk::types::ElicitationResult; + + struct CaptureHandler(std::sync::Mutex>); + #[async_trait] + impl ElicitationHandler for CaptureHandler { + async fn handle( + &self, + _session_id: SessionId, + _request_id: RequestId, + request: ElicitationRequest, + ) -> ElicitationResult { + *self.0.lock().unwrap() = request.requested_schema; + ElicitationResult { + action: "cancel".to_string(), + content: None, + } + } + } + + let handler = Arc::new(CaptureHandler(std::sync::Mutex::new(None))); + let (_session, mut server) = create_session_pair_with_config({ + let handler = handler.clone(); + |cfg| cfg.with_elicitation_handler(handler) + }) + .await; + + let schema = serde_json::json!({ + "type": "object", + "title": "Deploy", + "properties": { + "service": { "type": "string", "title": "Service name" }, + "replicas": { "type": "integer", "minimum": 1 }, + "tier": { "type": "string", "enum": ["dev", "prod"] } + }, + "required": ["service"] + }); + server + .send_event( + "elicitation.requested", + serde_json::json!({ + "requestId": "elicit-verbatim", + "message": "Deploy settings", + "requestedSchema": schema.clone(), + "mode": "form", + }), + ) + .await; + + let rpc_call = timeout(TIMEOUT, server.read_request()).await.unwrap(); + assert_eq!(rpc_call["method"], "session.ui.handlePendingElicitation"); + let seen = handler + .0 + .lock() + .unwrap() + .clone() + .expect("handler saw a schema"); + assert_eq!(seen, schema); + // Map equality ignores order; assert the wire field order explicitly. + let keys = |v: &serde_json::Value| -> Vec { + v["properties"] + .as_object() + .unwrap() + .keys() + .cloned() + .collect() + }; + assert_eq!(keys(&seen), ["service", "replicas", "tier"]); +} + #[tokio::test] async fn elicitation_requested_cancels_on_handler_error() { struct FailHandler; @@ -7296,6 +7418,104 @@ async fn hooks_invoke_subagent_lifecycle_does_not_warn_as_unknown() { ); } +/// The CLI sends the `error` of the `errorOccurred` and `sessionEnd` hook +/// inputs as an error object. Both inputs reach their hooks with the object's +/// `message`, and no hook dispatch fails. +#[tokio::test] +async fn hooks_invoke_decodes_the_clis_error_objects() { + use github_copilot_sdk::hooks::{ + ErrorOccurredInput, ErrorOccurredOutput, HookContext, SessionEndInput, SessionEndOutput, + SessionHooks, + }; + + #[derive(Default)] + struct Recorder { + errors: std::sync::Mutex>, + } + #[async_trait] + impl SessionHooks for Recorder { + async fn on_error_occurred( + &self, + input: ErrorOccurredInput, + _ctx: HookContext, + ) -> Option { + self.errors.lock().unwrap().push(input.error); + None + } + + async fn on_session_end( + &self, + input: SessionEndInput, + _ctx: HookContext, + ) -> Option { + self.errors + .lock() + .unwrap() + .push(input.error.unwrap_or_default()); + None + } + } + + let hooks = Arc::new(Recorder::default()); + let (capture, _guard) = capture_traces(); + let (_session, mut server) = create_session_pair_with_hooks(hooks.clone()).await; + + for (request_id, hook_type, input) in [ + ( + 305, + "errorOccurred", + serde_json::json!({ + "sessionId": server.session_id, + "timestamp": 1234567890, + "cwd": "/tmp", + "error": {"name": "Error", "message": "model timeout", "stack": "Error: model timeout"}, + "errorContext": "model_call", + "recoverable": true + }), + ), + ( + 306, + "sessionEnd", + serde_json::json!({ + "sessionId": server.session_id, + "timestamp": 1234567890, + "cwd": "/tmp", + "reason": "error", + "error": {"message": "retries exhausted"} + }), + ), + ] { + server + .send_request( + request_id, + "hooks.invoke", + serde_json::json!({ + "sessionId": server.session_id, + "hookType": hook_type, + "input": input + }), + ) + .await; + let response = timeout(TIMEOUT, server.read_response()).await.unwrap(); + assert_eq!(response["id"], request_id); + assert_eq!(response["result"], serde_json::json!({ "output": {} })); + } + + assert_eq!( + *hooks.errors.lock().unwrap(), + vec!["model timeout".to_string(), "retries exhausted".to_string()] + ); + let failures: Vec<_> = capture + .events + .lock() + .unwrap() + .iter() + .filter(|event| event.message_contains("hook dispatch failed")) + .cloned() + .collect(); + assert!(failures.is_empty(), "hook dispatch failures: {failures:?}"); +} + async fn create_session_pair_with_system_message_transforms( transforms: Arc, ) -> (github_copilot_sdk::session::Session, FakeServer) { diff --git a/samples/startup-policy/README.md b/samples/startup-policy/README.md new file mode 100644 index 0000000000..4e9f902da3 --- /dev/null +++ b/samples/startup-policy/README.md @@ -0,0 +1,28 @@ +# Startup policy example + +This source example uses an isolated configuration directory and a local runtime. +It makes no model request and installs no certificate. +It does not read or write your settings or keychain. + +Prepare the same-checkout runtime and generated Node.js client with the +[SDK contributor guide](../../CONTRIBUTING.md#testing-an-unreleased-runtime-api). +From the SDK directory, run: + +```sh +npx --no-install tsx samples/startup-policy/read-settings.mts /absolute/path/to/runtime.node +``` + +The output shows `user-model`, then `repo-model`, and a `changed` repository edit. +The obsolete value in `config.json` does not override `settings.json`. +Managed plugin controls override the user value. +The startup override disables native web search. +The permission query reports the managed bypass restriction. +The ingestion request disables policy discovery and network access. +The managed sandbox floor stays enabled. +`proxyCaRelevant` is `true`; installation support depends on the host platform. + +The example imports internal host helpers directly from the source checkout. +Those helpers are not public SDK exports. +Applications can use `client.rpc.sandbox.proxyCa.checkRelevance` and +`client.rpc.sandbox.proxyCa.checkInstallationSupport` through the public client. +Neither query installs a certificate or requests elevation. diff --git a/samples/startup-policy/read-settings.mts b/samples/startup-policy/read-settings.mts new file mode 100644 index 0000000000..f0a2c9a883 --- /dev/null +++ b/samples/startup-policy/read-settings.mts @@ -0,0 +1,141 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +import assert from "node:assert/strict"; +import { mkdtemp, mkdir, rm, writeFile } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { join, resolve } from "node:path"; +import { + createMessageConnection, + StreamMessageReader, + StreamMessageWriter, +} from "vscode-jsonrpc/node.js"; +import { FfiRuntimeHost } from "../../nodejs/src/ffiRuntimeHost.js"; +import { createInternalServerRpc, createServerRpc } from "../../nodejs/src/generated/rpc.js"; + +const library = process.argv[2]; +assert(library, "Pass the same-checkout runtime.node path as the first argument"); +const home = await mkdtemp(join(tmpdir(), "copilot-startup-policy-")); +let host: FfiRuntimeHost | undefined; +let connection: ReturnType | undefined; +const errors: unknown[] = []; +try { + const configDir = join(home, ".copilot"); + const repo = join(home, "project"); + await mkdir(configDir); + await mkdir(join(repo, ".github", "copilot"), { recursive: true }); + await writeFile(join(configDir, "settings.json"), JSON.stringify({ model: "user-model" })); + await writeFile(join(configDir, "config.json"), JSON.stringify({ model: "obsolete-model" })); + await writeFile( + join(repo, ".github", "copilot", "settings.json"), + JSON.stringify({ model: "repo-model" }) + ); + host = FfiRuntimeHost.create( + resolve(library), + undefined, + { + HOME: home, + USERPROFILE: home, + COPILOT_HOME: configDir, + COPILOT_CACHE_HOME: join(home, "cache"), + COPILOT_OFFLINE: "true", + COPILOT_USE_KEYCHAIN: "false", + }, + ["--no-auto-login"] + ); + await host.start(); + connection = createMessageConnection( + new StreamMessageReader(host.receiveStream), + new StreamMessageWriter(host.sendStream) + ); + connection.listen(); + const internal = createInternalServerRpc(connection); + const rpc = createServerRpc(connection); + const startupGates = await internal.featureFlags.resolveStartupGates({ + flags: ["NATIVE_WEB_SEARCH"], + isStaff: false, + isExperimental: false, + isTeam: false, + streamerMode: false, + refreshWithoutSession: false, + environment: {}, + config: { featureFlags: { NATIVE_WEB_SEARCH: false } }, + }); + assert.equal(startupGates.NATIVE_WEB_SEARCH, false); + const loaded = await internal.user.settings.loadWithWarning(); + assert.equal(loaded.settings.model, "user-model"); + const merged = await internal.user.settings.loadAndMergeRepo({ + repoRoot: repo, + userSettings: loaded.settings, + }); + assert.equal(merged.settings.model, "repo-model"); + const edit = await internal.repo.settings.edit({ + repoRoot: repo, + scope: "local", + key: "deniedUrls", + operation: "add-item", + value: "https://example.com", + }); + assert.equal(edit.status, "changed"); + const controls = await internal.managedSettings.applyControls({ + settings: { enabledPlugins: { required: false } }, + managed: { enabledPlugins: { required: true } }, + }); + assert.deepEqual(controls.settingsJson, { enabledPlugins: { required: true } }); + const permissions = await internal.permissions.evaluateManagedPolicy({ + permissions: { disableBypassPermissionsMode: "disable" }, + }); + assert.equal(permissions.bypassPermissionsDisabled, true); + const ingestion = await internal.managedSettings.ingest({ + selfFetch: false, + userAgent: "startup-policy-example", + environment: {}, + platform: process.platform, + homeDirectory: home, + }); + assert.equal(ingestion.serverFetchFailed, false); + const sandbox = await internal.sandbox.resolveFlooredConfig({ + userSandbox: { enabled: false }, + managedSandbox: { enabled: true, failIfUnavailable: true }, + }); + const relevant = await rpc.sandbox.proxyCa.checkRelevance({ + sandboxConfig: { enabled: true, auth: { gh: true } }, + }); + assert.equal(relevant, true); + console.log( + JSON.stringify( + { + userModel: loaded.settings.model, + repoModel: merged.settings.model, + repoEdit: edit.status, + startupGates, + managedControls: controls.settingsJson, + permissions, + ingestion: ingestion.serverResolution, + sandbox: sandbox.sandboxJson, + proxyCaRelevant: relevant, + proxyCaInstallationSupported: await rpc.sandbox.proxyCa.checkInstallationSupport(), + }, + null, + 2 + ) + ); +} catch (error) { + errors.push(error); +} finally { + connection?.dispose(); + try { + await host?.dispose(); + } catch (error) { + errors.push(error); + } + try { + await rm(home, { recursive: true, force: true }); + } catch (error) { + errors.push(error); + } +} +if (errors.length > 0) { + throw new AggregateError(errors, "Startup policy example failed"); +} diff --git a/scripts/ci/check-generated.test.mjs b/scripts/ci/check-generated.test.mjs index 23c95ced04..6827464fdb 100644 --- a/scripts/ci/check-generated.test.mjs +++ b/scripts/ci/check-generated.test.mjs @@ -8,7 +8,7 @@ import fs from "node:fs"; import os from "node:os"; import path from "node:path"; import test from "node:test"; -import { fileURLToPath } from "node:url"; +import { fileURLToPath, pathToFileURL } from "node:url"; import { SCHEMA_FILES } from "../build-prerequisites.mjs"; import { assertGeneratedClean, @@ -64,6 +64,15 @@ function fixture(t) { return { root, git, write, commit, schemas, options }; } +function shallowClone(t, source, depth) { + const root = fs.mkdtempSync(path.join(os.tmpdir(), "sdk-freshness-shallow-")); + t.after(() => fs.rmSync(root, { recursive: true, force: true })); + execFileSync("git", ["clone", "--quiet", "--depth", String(depth), pathToFileURL(source).href, root]); + const git = (...args) => execFileSync("git", args, { cwd: root, encoding: "utf8" }).trim(); + assert.equal(git("rev-parse", "--is-shallow-repository"), "true"); + return { root, git }; +} + for (const name of SCHEMA_FILES) { test(`rejects stale ${name} before SDK generation`, (t) => { const f = fixture(t); @@ -207,10 +216,39 @@ test("uses the tested PR merge's actual base, not an earlier event base", (t) => const actualBase = f.commit(); f.git("merge", "--quiet", "--no-ff", "topic", "-m", "test merge"); assert.equal(generationBaseline(f.root, "pull_request", { pull_request: { base: { sha: oldBase } } }), actualBase); + const clone = shallowClone(t, f.root, 2); + assert.equal( + generationBaseline(clone.root, "pull_request", { pull_request: { base: { sha: oldBase } } }), + actualBase, + ); const paths = f.git("diff", "--name-only", actualBase, "HEAD").split("\n"); assert.equal(requiresSdkGeneration(paths), false); }); +for (const eventName of ["merge_group", "push"]) { + test(`uses a separately fetched ${eventName} baseline beyond shallow history`, (t) => { + const f = fixture(t); + f.write("generated/api.schema.json", '{"earlierCommit":true}\n'); + f.commit(); + f.write("internal", "last commit"); + f.commit(); + const clone = shallowClone(t, f.root, 1); + const event = + eventName === "merge_group" + ? { merge_group: { base_sha: f.options.baseline } } + : { before: f.options.baseline }; + assert.throws(() => generationBaseline(clone.root, eventName, event)); + clone.git("fetch", "--quiet", "--no-tags", "--depth=1", "origin", f.options.baseline); + const baseline = generationBaseline(clone.root, eventName, event); + assert.equal(baseline, f.options.baseline); + assert.equal( + requiresSdkGeneration(clone.git("diff", "--name-only", baseline, "HEAD").split("\n")), + true, + ); + assert.equal(clone.git("rev-parse", "--is-shallow-repository"), "true"); + }); +} + test("uses the whole merge-group/push range, not just the final commit", (t) => { const f = fixture(t); f.write("generated/api.schema.json", '{"earlierCommit":true}\n'); diff --git a/scripts/ci/run-dotnet-tests.sh b/scripts/ci/run-dotnet-tests.sh index ea0312c4a6..b1840e323e 100755 --- a/scripts/ci/run-dotnet-tests.sh +++ b/scripts/ci/run-dotnet-tests.sh @@ -11,6 +11,7 @@ Usage: run-dotnet-tests.sh [--help] Runs the full .NET SDK test project. Environment variables: DOTNET_TEST_FILTER Optional dotnet test filter (e.g. for a backend or transport). DOTNET_TEST_RUNTIME Optional runtime identifier passed to dotnet test. + DOTNET_TEST_FRAMEWORK Optional target framework (default: all project frameworks). DOTNET_TEST_RESULTS_DIRECTORY Results directory (default: TestResults). EOF } @@ -40,6 +41,7 @@ args=( ) filter="${DOTNET_TEST_FILTER:-}" runtime="${DOTNET_TEST_RUNTIME:-}" +framework="${DOTNET_TEST_FRAMEWORK:-}" if [[ -n "$filter" ]]; then args+=(--filter "$filter") @@ -47,5 +49,8 @@ fi if [[ -n "$runtime" ]]; then args+=(--runtime "$runtime") fi +if [[ -n "$framework" ]]; then + args+=(--framework "$framework") +fi dotnet test "${args[@]}" diff --git a/scripts/ci/run-dotnet-tests.test.mjs b/scripts/ci/run-dotnet-tests.test.mjs index 582fcd6b2c..f979b8042c 100644 --- a/scripts/ci/run-dotnet-tests.test.mjs +++ b/scripts/ci/run-dotnet-tests.test.mjs @@ -19,7 +19,7 @@ function fromBashPath(value) { return value; } -await test("runs all .NET tests with optional filter, runtime, and results directory", (t) => { +await test("runs all .NET tests with optional filter, runtime, framework, and results directory", (t) => { const root = fs.mkdtempSync(path.join(os.tmpdir(), "sdk-dotnet-test-")); t.after(() => fs.rmSync(root, { recursive: true, force: true })); const argumentsPath = path.join(root, "arguments"); @@ -31,9 +31,12 @@ await test("runs all .NET tests with optional filter, runtime, and results direc ); fs.chmodSync(fakeDotnet, 0o755); - for (const [filter, runtime, resultsDirectory] of [ - ["", "", ""], - ["E2EBackend!=CapiOnly", "win-x64", "TestResults/inprocess"], + for (const [filter, runtime, framework, resultsDirectory] of [ + ["", "", "", ""], + ["E2EBackend!=CapiOnly", "win-x64", "net8.0", "TestResults/inprocess"], + ["", "win-x64", "net472", "TestResults/subprocess"], + ["FullyQualifiedName~GitHub.Copilot.Test.E2E.S", "win-x64", "net8.0", "TestResults/subprocess"], + ["FullyQualifiedName!~GitHub.Copilot.Test.E2E.S", "win-x64", "net472", "TestResults/subprocess"], ]) { const result = spawnSync("bash", [script], { encoding: "utf8", @@ -42,6 +45,7 @@ await test("runs all .NET tests with optional filter, runtime, and results direc ARGUMENTS_PATH: argumentsPath, DOTNET_TEST_FILTER: filter, DOTNET_TEST_RUNTIME: runtime, + DOTNET_TEST_FRAMEWORK: framework, DOTNET_TEST_RESULTS_DIRECTORY: resultsDirectory, PATH: `${root}${path.delimiter}${process.env.PATH ?? ""}`, WORKING_DIRECTORY_PATH: workingDirectoryPath, @@ -67,6 +71,10 @@ await test("runs all .NET tests with optional filter, runtime, and results direc if (runtime) { assert.equal(arguments_[arguments_.indexOf("--runtime") + 1], runtime); } + assert.equal(arguments_.includes("--framework"), framework !== ""); + if (framework) { + assert.equal(arguments_[arguments_.indexOf("--framework") + 1], framework); + } } }); @@ -75,6 +83,7 @@ await test("documents its environment and rejects positional arguments", () => { assert.equal(help.status, 0, help.stderr); assert.match(help.stdout, /DOTNET_TEST_FILTER/); assert.match(help.stdout, /DOTNET_TEST_RUNTIME/); + assert.match(help.stdout, /DOTNET_TEST_FRAMEWORK/); assert.match(help.stdout, /DOTNET_TEST_RESULTS_DIRECTORY/); const invalid = spawnSync("bash", [script, "unexpected"], { encoding: "utf8" }); diff --git a/scripts/codegen/csharp-dictionary-roundtrip.test.ts b/scripts/codegen/csharp-dictionary-roundtrip.test.ts new file mode 100644 index 0000000000..604ed86fc2 --- /dev/null +++ b/scripts/codegen/csharp-dictionary-roundtrip.test.ts @@ -0,0 +1,90 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +import assert from "node:assert/strict"; +import { spawnSync } from "node:child_process"; +import { mkdtemp, rm, writeFile } from "node:fs/promises"; +import { join } from "node:path"; +import { test } from "node:test"; +import { generateRpcCode } from "./csharp.ts"; + +test("generated C# open dictionaries retain entries through System.Text.Json", async () => { + const directory = await mkdtemp(join(process.cwd(), ".csharp-dictionary-test-")); + let failure: unknown; + try { + const code = generateRpcCode({ + server: { + sample: { + read: { + rpcMethod: "sample.read", + params: null, + result: { + title: "SampleResult", + type: "object", + properties: { + environment: { type: "object", properties: {} }, + settings: { type: "object", properties: {} }, + }, + required: ["environment"], + }, + }, + }, + }, + }); + await writeFile(join(directory, "Rpc.cs"), code); + await writeFile( + join(directory, "Roundtrip.csproj"), + ` + + Exe + net10.0 + enable + enable + +` + ); + await writeFile( + join(directory, "Program.cs"), + `using System.Text.Json; +using System.Text.Json.Nodes; +using GitHub.Copilot.Rpc; + +const string json = """ +{"environment":{"COPILOT_HOME":"/isolated/config","EMPTY":""},"settings":{"model":"selected-model","enabled":false,"custom":{"nested":42}}} +"""; +var value = JsonSerializer.Deserialize(json)!; +var actual = JsonSerializer.Serialize(value); +if (!JsonNode.DeepEquals(JsonNode.Parse(json), JsonNode.Parse(actual))) + throw new Exception($"Dictionary entries lost: {actual}"); +Console.WriteLine("environment and settings dictionaries round-trip"); + +namespace GitHub.Copilot.Rpc +{ + internal sealed class JsonRpc; + internal static class CopilotClient + { + internal static Task InvokeRpcAsync(JsonRpc rpc, string method, object[] args, + CancellationToken cancellationToken) => throw new NotSupportedException("No RPC is expected"); + } +}` + ); + const result = spawnSync("dotnet", ["run", "--project", join(directory, "Roundtrip.csproj")], { + cwd: directory, + encoding: "utf8", + }); + if (result.error) throw result.error; + assert.equal(result.status, 0, `${result.stdout}\n${result.stderr}`); + assert.match(result.stdout, /environment and settings dictionaries round-trip/); + } catch (error) { + failure = error; + throw error; + } finally { + try { + await rm(directory, { recursive: true, force: true }); + } catch (cleanupError) { + if (failure) throw new AggregateError([failure, cleanupError], "Test and cleanup failed"); + throw cleanupError; + } + } +}); diff --git a/scripts/codegen/csharp.ts b/scripts/codegen/csharp.ts index faef5dd771..fe7e8f185a 100644 --- a/scripts/codegen/csharp.ts +++ b/scripts/codegen/csharp.ts @@ -1894,9 +1894,12 @@ function resolveRpcType(schema: JSONSchema7, isRequired: boolean, parentClassNam if (isOpaqueJson(schema)) { return isRequired ? "JsonElement" : "JsonElement?"; } - if (schema.type === "object" && schema.additionalProperties === true && + if (schema.type === "object" && schema.additionalProperties !== false && (!schema.properties || Object.keys(schema.properties).length === 0)) { - return isRequired ? "IDictionary" : "IDictionary?"; + const valueType = typeof schema.additionalProperties === "object" + ? resolveRpcType(schema.additionalProperties, true, parentClassName, `${propName}Value`, classes) + : "JsonElement"; + return isRequired ? `IDictionary` : `IDictionary?`; } // Handle $ref by resolving against schema definitions and generating the referenced class if (schema.$ref) { @@ -1911,7 +1914,8 @@ function resolveRpcType(schema: JSONSchema7, isRequired: boolean, parentClassNam return isRequired ? enumName : `${enumName}?`; } - if (refSchema.type === "object" && refSchema.properties) { + if (refSchema.type === "object" && refSchema.properties && + (Object.keys(refSchema.properties).length > 0 || refSchema.additionalProperties === false)) { const cls = emitRpcClass(typeName, refSchema, "public", classes); if (cls) classes.push(cls); return isRequired ? typeName : `${typeName}?`; @@ -2005,7 +2009,8 @@ function resolveRpcType(schema: JSONSchema7, isRequired: boolean, parentClassNam } if (schema.type === "array" && schema.items) { const items = schema.items as JSONSchema7; - if (items.type === "object" && items.properties) { + if (items.type === "object" && items.properties && + (Object.keys(items.properties).length > 0 || items.additionalProperties === false)) { const itemClass = (items.title as string) ?? `${parentClassName}${singularPascal(propName)}`; classes.push(emitRpcClass(itemClass, items, "public", classes)); return isRequired ? `IList<${itemClass}>` : `IList<${itemClass}>?`; @@ -2013,11 +2018,6 @@ function resolveRpcType(schema: JSONSchema7, isRequired: boolean, parentClassNam const itemType = resolveRpcType(items, true, parentClassName, `${propName}Item`, classes); return isRequired ? `IList<${itemType}>` : `IList<${itemType}>?`; } - if (schema.type === "object" && schema.additionalProperties && typeof schema.additionalProperties === "object") { - const vs = schema.additionalProperties as JSONSchema7; - const valueType = resolveRpcType(vs, true, parentClassName, `${propName}Value`, classes); - return isRequired ? `IDictionary` : `IDictionary?`; - } return schemaTypeToCSharp(schema, isRequired, rpcKnownTypes, propName); } @@ -2312,7 +2312,7 @@ function emitServerInstanceMethod( const opaqueListOptional = naturalType === "IList?"; const opaque = opaqueRequired || opaqueOptional || opaqueListRequired || opaqueListOptional; const csType = opaqueRequired - ? "object" + ? "object?" : opaqueOptional ? "object?" : opaqueListRequired @@ -2322,7 +2322,7 @@ function emitServerInstanceMethod( : naturalType; sigParams.push(`${csType} ${pName}${isReq ? "" : " = null"}`); const assignedValue = opaqueRequired - ? `CopilotClient.ToJsonElementForWire(${pName})!.Value` + ? `CopilotClient.ToJsonElementForWire(${pName}) ?? JsonElement.Parse("null")` : opaqueOptional ? `CopilotClient.ToJsonElementForWire(${pName})` : opaqueListRequired @@ -2331,7 +2331,7 @@ function emitServerInstanceMethod( ? `${pName}?.Select(static v => CopilotClient.ToJsonElementForWire(v)!.Value).ToList()` : pName; bodyAssignments.push(`${csharpName} = ${assignedValue}`); - if (opaqueRequired || opaqueListRequired || (!opaque && requiresArgumentNullCheck(csType, isReq))) { + if (opaqueListRequired || (!opaque && requiresArgumentNullCheck(csType, isReq))) { argumentNullChecks.push(`${indent} ArgumentNullException.ThrowIfNull(${pName});`); } if (opaqueListRequired || (!opaque && requiresArgumentNullCheck(csType, isReq))) { @@ -2364,7 +2364,18 @@ function emitServerInstanceMethod( } if (requestClassName && bodyAssignments.length > 0) { lines.push(`${indent} var ${localRequestName} = new ${requestClassName} { ${bodyAssignments.join(", ")} };`); - if (!isVoidSchema(resultSchema)) { + if ( + method.rpcMethod === "user.settings.applyTopKey" || + method.rpcMethod === "repo.settings.writeKey" + ) { + lines.push(`${indent} var payload = JsonSerializer.SerializeToNode(${localRequestName}, RpcJsonContext.Default.${requestClassName})!.AsObject();`); + lines.push(`${indent} if (${localRequestName}.Delete != true && ${localRequestName}.Value is null) payload["value"] = null;`); + if (!isVoidSchema(resultSchema)) { + lines.push(`${indent} return await CopilotClient.InvokeRpcAsync<${resultClassName}>(_rpc, "${method.rpcMethod}", [payload], cancellationToken);`); + } else { + lines.push(`${indent} await CopilotClient.InvokeRpcAsync(_rpc, "${method.rpcMethod}", [payload], cancellationToken);`); + } + } else if (!isVoidSchema(resultSchema)) { lines.push(`${indent} return await CopilotClient.InvokeRpcAsync<${resultClassName}>(_rpc, "${method.rpcMethod}", [${localRequestName}], cancellationToken);`); } else { lines.push(`${indent} await CopilotClient.InvokeRpcAsync(_rpc, "${method.rpcMethod}", [${localRequestName}], cancellationToken);`); @@ -2548,7 +2559,7 @@ function emitSessionMethod(key: string, method: RpcMethod, lines: string[], clas const opaqueListOptional = naturalType === "IList?"; const opaque = opaqueRequired || opaqueOptional || opaqueListRequired || opaqueListOptional; const csType = opaqueRequired - ? "object" + ? "object?" : opaqueOptional ? "object?" : opaqueListRequired @@ -2558,7 +2569,7 @@ function emitSessionMethod(key: string, method: RpcMethod, lines: string[], clas : naturalType; sigParams.push(`${csType} ${pName}${isReq ? "" : " = null"}`); const assignedValue = opaqueRequired - ? `CopilotClient.ToJsonElementForWire(${pName})!.Value` + ? `CopilotClient.ToJsonElementForWire(${pName}) ?? JsonElement.Parse("null")` : opaqueOptional ? `CopilotClient.ToJsonElementForWire(${pName})` : opaqueListRequired @@ -2567,7 +2578,7 @@ function emitSessionMethod(key: string, method: RpcMethod, lines: string[], clas ? `${pName}?.Select(static v => CopilotClient.ToJsonElementForWire(v)!.Value).ToList()` : pName; bodyAssignments.push(`${csharpName} = ${assignedValue}`); - if (opaqueRequired || opaqueListRequired || (!opaque && requiresArgumentNullCheck(csType, isReq))) { + if (opaqueListRequired || (!opaque && requiresArgumentNullCheck(csType, isReq))) { argumentNullChecks.push(`${indent} ArgumentNullException.ThrowIfNull(${pName});`); } if (opaqueListRequired || (!opaque && requiresArgumentNullCheck(csType, isReq))) { @@ -2599,7 +2610,18 @@ function emitSessionMethod(key: string, method: RpcMethod, lines: string[], clas lines.push(`${indent} _session.ThrowIfDisposed();`); lines.push(""); lines.push(`${indent} var ${localRequestName} = new ${wireRequestClassName} { ${bodyAssignments.join(", ")} };`); - if (!isVoidSchema(resultSchema)) { + if ( + method.rpcMethod === "user.settings.applyTopKey" || + method.rpcMethod === "repo.settings.writeKey" + ) { + lines.push(`${indent} var payload = JsonSerializer.SerializeToNode(${localRequestName}, RpcJsonContext.Default.${wireRequestClassName})!.AsObject();`); + lines.push(`${indent} if (${localRequestName}.Delete != true && ${localRequestName}.Value is null) payload["value"] = null;`); + if (!isVoidSchema(resultSchema)) { + lines.push(`${indent} return await CopilotClient.InvokeRpcAsync<${resultClassName}>(_session.Rpc, "${method.rpcMethod}", [payload], cancellationToken);`, `${indent}}`); + } else { + lines.push(`${indent} await CopilotClient.InvokeRpcAsync(_session.Rpc, "${method.rpcMethod}", [payload], cancellationToken);`, `${indent}}`); + } + } else if (!isVoidSchema(resultSchema)) { lines.push(`${indent} return await CopilotClient.InvokeRpcAsync<${resultClassName}>(_session.Rpc, "${method.rpcMethod}", [${localRequestName}], cancellationToken);`, `${indent}}`); } else { lines.push(`${indent} await CopilotClient.InvokeRpcAsync(_session.Rpc, "${method.rpcMethod}", [${localRequestName}], cancellationToken);`, `${indent}}`); diff --git a/scripts/codegen/go.ts b/scripts/codegen/go.ts index 378d36678a..a4cabea1b0 100644 --- a/scripts/codegen/go.ts +++ b/scripts/codegen/go.ts @@ -578,6 +578,7 @@ interface GoCodegenCtx { encodingBlocks?: Set; unionVariantMarshalers?: Set; packageName?: string; + numberPreservingTypes?: ReadonlySet; } function extractGoEventVariants(schema: JSONSchema7): GoEventVariant[] { @@ -1142,11 +1143,27 @@ function registerGoExternalUnionUnmarshalers( } } +function pushGoNumberPreservingUnmarshal(typeName: string, ctx: GoCodegenCtx): void { + pushGoEncodingBlock([ + `func (r *${typeName}) UnmarshalJSON(data []byte) error {`, + `\ttype wire ${typeName}`, + `\tdecoder := json.NewDecoder(bytes.NewReader(data))`, + `\tdecoder.UseNumber()`, + `\treturn decoder.Decode((*wire)(r))`, + `}`, + ], ctx); +} + function pushGoStructUnmarshalJSON(lines: string[], typeName: string, fields: GoStructField[], ctx: GoCodegenCtx): void { const unionFields = fields .map((field) => ({ field, unionField: goDiscriminatedUnionField(field.goType, ctx) })) .filter((entry): entry is { field: GoStructField; unionField: GoDiscriminatedUnionField } => entry.unionField !== undefined); - if (unionFields.length === 0) return; + const preservesNumbers = ctx.numberPreservingTypes?.has(typeName) === true + && fields.some((field) => /\bany\b/.test(field.goType)); + if (unionFields.length === 0) { + if (preservesNumbers) pushGoNumberPreservingUnmarshal(typeName, ctx); + return; + } const blockLines: string[] = []; blockLines.push(`func (r *${typeName}) UnmarshalJSON(data []byte) error {`); @@ -1161,7 +1178,11 @@ function pushGoStructUnmarshalJSON(lines: string[], typeName: string, fields: Go } blockLines.push(`\t}`); blockLines.push(`\tvar raw raw${typeName}`); - blockLines.push(`\tif err := json.Unmarshal(data, &raw); err != nil {`); + if (preservesNumbers) { + blockLines.push(`\tdecoder := json.NewDecoder(bytes.NewReader(data))`); + blockLines.push(`\tdecoder.UseNumber()`); + } + blockLines.push(`\tif err := ${preservesNumbers ? "decoder.Decode(&raw)" : "json.Unmarshal(data, &raw)"}; err != nil {`); blockLines.push(`\t\treturn err`); blockLines.push(`\t}`); @@ -3013,8 +3034,12 @@ function emitGoAlias(typeName: string, schema: JSONSchema7, ctx: GoCodegenCtx): if (isSchemaDeprecated(schema)) { pushGoCommentForContext(lines, `Deprecated: ${typeName} is deprecated and will be removed in a future version.`, ctx); } - lines.push(`type ${typeName} ${resolveGoPropertyType(schema, typeName, "Value", true, ctx)}`); + const goType = resolveGoPropertyType(schema, typeName, "Value", true, ctx); + lines.push(`type ${typeName} ${goType}`); ctx.structs.push(lines.join("\n")); + if (ctx.numberPreservingTypes?.has(typeName) && (goType.startsWith("map[") || goType.startsWith("[]")) && /\bany\b/.test(goType)) { + pushGoNumberPreservingUnmarshal(typeName, ctx); + } } /** @@ -3134,6 +3159,9 @@ function goGeneratedEncodingFileCode(schemaFileName: string, packageName: string lines.push(``); const imports = [`"encoding/json"`]; + if (generatedEncodingCode.includes("bytes.")) { + imports.push(`"bytes"`); + } if (generatedEncodingCode.includes("errors.")) { imports.push(`"errors"`); } @@ -3155,7 +3183,7 @@ function goGeneratedEncodingFileCode(schemaFileName: string, packageName: string return wrapComments ? wrapGeneratedGoComments(code) : code; } -function generateGoRpcTypeCode(definitions: Record, definitionCollections: DefinitionCollections): GoGeneratedTypeCode { +function generateGoRpcTypeCode(definitions: Record, definitionCollections: DefinitionCollections, numberPreservingTypes: ReadonlySet): GoGeneratedTypeCode { const ctx: GoCodegenCtx = { structs: [], encoding: [], @@ -3165,6 +3193,7 @@ function generateGoRpcTypeCode(definitions: Record, definit generatedNames: new Set(), definitions: definitionCollections, packageName: "rpc", + numberPreservingTypes, }; ctx.skipDefinitionTypeNames = collectGoDiscriminatedUnionVariantDefinitionTypeNames(definitions, ctx); const schemaKeysByTypeName = new Map(); @@ -4009,8 +4038,10 @@ async function generateRpc(schemaPath?: string): Promise { }; rpcDefinitions = allDefinitionCollections; + // Opt in only the policy result; existing RPC consumers depend on float64 values. + const numberPreservingTypes = new Set(["ContentExclusionValidatePoliciesResult"]); + const generatedRpcCode = generateGoRpcTypeCode(allDefinitions, allDefinitionCollections, numberPreservingTypes); // Strip trailing whitespace from generated output (gofmt requirement) - const generatedRpcCode = generateGoRpcTypeCode(allDefinitions, allDefinitionCollections); let generatedTypeCode = stripTrailingGoWhitespace(generatedRpcCode.typeCode); const generatedEncodingCode = stripTrailingGoWhitespace(generatedRpcCode.encodingCode); @@ -4090,9 +4121,10 @@ async function generateRpc(schemaPath?: string): Promise { } } for (const typeName of internalTypeNames) { + const emittedTypeName = resolveType(typeName); generatedTypeCode = generatedTypeCode.replace( - new RegExp(`^(type ${typeName} struct)`, "m"), - `// Internal: ${typeName} is an internal SDK API and is not part of the public surface.\n$1` + new RegExp(`^(type ${escapeRegExp(emittedTypeName)} struct)`, "m"), + `// Internal: ${emittedTypeName} is an internal SDK API and is not part of the public surface.\n$1` ); } // Remove trailing blank lines before appending. @@ -4303,6 +4335,7 @@ function emitMethod(lines: string[], receiver: string, name: string, method: Rpc const paramsType = hasParams ? resolveType(goParamsTypeName(method)) : ""; const hasRequiredNonSessionParams = nonSessionParams.some((name) => requiredParams.has(name)); const paramsAreOptional = hasParams && !!method.params && !!getNullableInner(method.params) && !hasRequiredNonSessionParams; + const supportsExplicitNullValue = method.rpcMethod === "user.settings.edit"; // For wrapper-level methods, access fields through a.common; for service type aliases, use a directly const clientRef = isWrapper ? "a.common.client" : "a.client"; @@ -4361,14 +4394,41 @@ function emitMethod(lines: string[], receiver: string, name: string, method: Rpc } lines.push(`\traw, err := ${clientRef}.Request(ctx, "${method.rpcMethod}", req)`); } else { - if (method.rpcMethod === "managedSettings.resolve") { - // A typed nil inside Request's any argument marshals as null, not omitted params. - lines.push(`\tif ${paramsRef} == nil {`); - lines.push(`\t\t${paramsRef} = &${paramsType}{}`); + if (hasParams && supportsExplicitNullValue) { + lines.push(`\treq := map[string]any{}`); + lines.push(`\tif ${paramsRef} != nil {`); + for (const pName of nonSessionParams) { + const field = fields.get(paramsType)?.get(pName); + const goField = field?.name ?? toGoFieldName(pName); + const goType = field?.type; + const isOptional = !requiredParams.has(pName); + if (isOptional) { + lines.push(`\t\tif ${paramsRef}.${goField} != nil {`); + const valueExpr = goOptionalFieldNeedsDereference(goType) ? `*${paramsRef}.${goField}` : `${paramsRef}.${goField}`; + lines.push(`\t\t\treq["${pName}"] = ${valueExpr}`); + lines.push(`\t\t}`); + } else { + lines.push(`\t\treq["${pName}"] = ${paramsRef}.${goField}`); + } + } + const operationField = fields.get(paramsType)?.get("operation")?.name ?? toGoFieldName("operation"); + lines.push(`\t\tif ${paramsRef}.${operationField} != SettingsEditOperationRemove {`); + lines.push(`\t\t\tif _, ok := req["value"]; !ok {`); + lines.push(`\t\t\t\treq["value"] = nil`); + lines.push(`\t\t\t}`); + lines.push(`\t\t}`); lines.push(`\t}`); + lines.push(`\traw, err := ${clientRef}.Request(ctx, "${method.rpcMethod}", req)`); + } else { + if (method.rpcMethod === "managedSettings.resolve") { + // A typed nil inside Request's any argument marshals as null, not omitted params. + lines.push(`\tif ${paramsRef} == nil {`); + lines.push(`\t\t${paramsRef} = &${paramsType}{}`); + lines.push(`\t}`); + } + const arg = hasParams ? paramsRef : "nil"; + lines.push(`\traw, err := ${clientRef}.Request(ctx, "${method.rpcMethod}", ${arg})`); } - const arg = hasParams ? paramsRef : "nil"; - lines.push(`\traw, err := ${clientRef}.Request(ctx, "${method.rpcMethod}", ${arg})`); } lines.push(`\tif err != nil {`); diff --git a/scripts/codegen/python.ts b/scripts/codegen/python.ts index 237ebf216f..ceb5598d80 100644 --- a/scripts/codegen/python.ts +++ b/scripts/codegen/python.ts @@ -1854,6 +1854,58 @@ function pythonParamsTypeName(method: RpcMethod): string { return getRpcSchemaTypeName(schema, fallback); } +function applyExplicitNullableFieldPresenceSentinel( + code: string, + resolveType: (name: string) => string +): string { + const escapeRegex = (value: string): string => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + const explicitNullableFields: Array<{ className: string; jsonName: string; pyName: string }> = [ + { + className: resolveType("UserSettingsApplyTopKeyRequest"), + jsonName: "value", + pyName: "value", + }, + { + className: resolveType("RepoSettingsWriteKeyRequest"), + jsonName: "value", + pyName: "value", + }, + { + className: resolveType("UserSettingsEditRequest"), + jsonName: "value", + pyName: "value", + }, + ]; + + let result = code; + for (const { className, jsonName, pyName } of explicitNullableFields) { + const classPattern = new RegExp( + `(@dataclass\\r?\\nclass ${escapeRegex(className)}\\b[\\s\\S]*?\\r?\\n)(?=@dataclass\\r?\\nclass |class\\s+\\w+\\(Enum\\):|def\\s+\\w+\\(|$)`, + "g" + ); + result = result.replace(classPattern, (classBlock) => { + let next = classBlock; + next = next.replace( + new RegExp(`^(\\s{4}${escapeRegex(pyName)}:\\s[^=\\r\\n]+\\=\\s*)None(\\r?\\n)`, "m"), + "$1UNSET_SENTINEL$2" + ); + next = next.replace( + new RegExp( + `^(\\s{8}${escapeRegex(pyName)}\\s*=\\s*)(.+?)obj\\.get\\("${escapeRegex(jsonName)}"\\)(.*)$`, + "m" + ), + `$1$2obj["${jsonName}"]$3 if "${jsonName}" in obj else UNSET_SENTINEL` + ); + next = next.replace( + new RegExp(`^(\\s{8}if\\s+self\\.${escapeRegex(pyName)}\\s+is\\s+not\\s+)None(\\s*:\\s*\\r?\\n)`, "m"), + "$1UNSET_SENTINEL$2" + ); + return next; + }); + } + return result; +} + // ── Session Events ────────────────────────────────────────────────────────── // ── Session Events (custom codegen — dedicated per-event payload types) ───── @@ -3436,6 +3488,25 @@ async function generateRpc(schemaPath?: string, sessionEventsSchema?: JSONSchema const allDefinitions = combinedSchema.definitions! as Record; preservePythonRpcStringDateFields(allDefinitions); + const managedOperationProperties = resolveObjectSchema( + allDefinitions.ManagedPermissionOperation, + rpcDefinitions + )?.properties; + const managedOperationKind = managedOperationProperties?.kind; + if (managedOperationProperties && managedOperationKind && typeof managedOperationKind === "object") { + const values = + managedOperationKind.enum ?? + (typeof managedOperationKind.const === "string" ? [managedOperationKind.const] : undefined); + if (!values?.every((value): value is string => typeof value === "string")) { + throw new Error("Expected a string discriminator for ManagedPermissionOperation.kind"); + } + allDefinitions.ManagedPermissionOperationKind = { + type: "string", + enum: values, + description: managedOperationKind.description, + }; + managedOperationProperties.kind = { $ref: "#/definitions/ManagedPermissionOperationKind" }; + } const allDefinitionCollections: DefinitionCollections = { definitions: { ...(combinedSchema.$defs ?? {}), ...allDefinitions }, $defs: { ...allDefinitions, ...(combinedSchema.$defs ?? {}) }, @@ -3455,13 +3526,13 @@ async function generateRpc(schemaPath?: string, sessionEventsSchema?: JSONSchema }; const externalRefs = rewriteExternalRefsForPython(singleSchema as JSONSchema7 & { definitions?: Record }); const externalEnumNames = collectPythonExternalEnumNames(sessionEventsSchema, externalRefs.placeholderNames); - // Discovery and session availability are independently versioned despite identical values. - // Hide the discovery enum from quicktype's enum merging, then restore it with our enum emitter. + // Independently versioned enums must not be structurally merged by quicktype. + // Hide them from quicktype's enum merging, then restore them with our enum emitter. const distinctEnums: Pick = { enumsByName: new Map(), enums: [], }; - for (const name of ["ConnectorDiscoveryAvailability"]) { + for (const name of ["ConnectorDiscoveryAvailability", "ManagedPermissionOperationKind"]) { const definition = allDefinitions[name]; if (!definition) continue; if (!definition.enum?.every((value): value is string => typeof value === "string")) { @@ -3760,6 +3831,7 @@ import dateutil.parser T = TypeVar("T") EnumT = TypeVar("EnumT", bound=Enum) +UNSET_SENTINEL = object() `); lines.push(typesCode); @@ -3854,6 +3926,7 @@ def _patch_model_capabilities(data: dict) -> dict: finalCode, new Set(Object.keys(allDefinitions).map((name) => name.toLowerCase())) ); + finalCode = applyExplicitNullableFieldPresenceSentinel(finalCode, resolveType); // Apply `_`-prefix to type names of internal RPC types so the leading-underscore // Python convention signals "internal, no stability guarantees" to consumers. @@ -3870,7 +3943,11 @@ def _patch_model_capabilities(data: dict) -> dict: } } if (internalDefs.size > 0) { - finalCode = renameInternalPythonSymbols(finalCode, internalDefs); + // Resolve class-name casing without following aliases to shared public types. + const internalSymbols = new Set( + [...internalDefs].flatMap((name) => [name, actualTypeNames.get(name.toLowerCase()) ?? name]) + ); + finalCode = renameInternalPythonSymbols(finalCode, internalSymbols); } } @@ -4232,10 +4309,16 @@ export function emitMethod(lines: string[], name: string, method: RpcMethod, isS if (isSession) { if (hasParams) { + const serializeParams = + method.rpcMethod === "user.settings.applyTopKey" || + method.rpcMethod === "repo.settings.writeKey" || + method.rpcMethod === "user.settings.edit" + ? "params.to_dict()" + : "{k: v for k, v in params.to_dict().items() if v is not None}"; if (paramsOptional) { - lines.push(` params_dict: dict[str, Any] = {k: v for k, v in params.to_dict().items() if v is not None} if params is not None else {}`); + lines.push(` params_dict: dict[str, Any] = ${serializeParams} if params is not None else {}`); } else { - lines.push(` params_dict: dict[str, Any] = {k: v for k, v in params.to_dict().items() if v is not None}`); + lines.push(` params_dict: dict[str, Any] = ${serializeParams}`); } lines.push(` params_dict["sessionId"] = self._session_id`); emitRequestCall("params_dict"); @@ -4244,10 +4327,16 @@ export function emitMethod(lines: string[], name: string, method: RpcMethod, isS } } else { if (hasParams) { + const serializeParams = + method.rpcMethod === "user.settings.applyTopKey" || + method.rpcMethod === "repo.settings.writeKey" || + method.rpcMethod === "user.settings.edit" + ? "params.to_dict()" + : "{k: v for k, v in params.to_dict().items() if v is not None}"; if (paramsOptional) { - lines.push(` params_dict = {k: v for k, v in params.to_dict().items() if v is not None} if params is not None else {}`); + lines.push(` params_dict = ${serializeParams} if params is not None else {}`); } else { - lines.push(` params_dict = {k: v for k, v in params.to_dict().items() if v is not None}`); + lines.push(` params_dict = ${serializeParams}`); } emitRequestCall("params_dict"); } else { diff --git a/scripts/codegen/rust.ts b/scripts/codegen/rust.ts index 9ce9f54511..20b6f100d7 100644 --- a/scripts/codegen/rust.ts +++ b/scripts/codegen/rust.ts @@ -804,8 +804,11 @@ function resolveRustType( ); return wrapOption(typeName, isRequired); } - if (isObjectSchema(resolved)) { - emitRustStruct(typeName, resolved, ctx); + const namedObject = typeName === "ManagedPermissionOperation" + ? resolveObjectSchema(resolved, ctx.definitions) + : resolved; + if (namedObject && isObjectSchema(namedObject)) { + emitRustStruct(typeName, { ...resolved, ...namedObject }, ctx); return wrapOption(typeName, isRequired); } return resolveRustType( @@ -2519,7 +2522,9 @@ function pushNamespaceMethodBody( // Build the params Value sent over the wire. if (isSession) { if (hasParams) { - out.push(` let mut wire_params = serde_json::to_value(params)?;`); + out.push( + ` let mut wire_params = serde_json::to_value(params)?;`, + ); out.push( ` wire_params["sessionId"] = serde_json::Value::String(self.session.id().to_string());`, ); @@ -2533,7 +2538,9 @@ function pushNamespaceMethodBody( ); } else { if (hasParams) { - out.push(` let wire_params = serde_json::to_value(params)?;`); + out.push( + ` let wire_params = serde_json::to_value(params)?;`, + ); } else { out.push(` let wire_params = serde_json::json!({});`); } diff --git a/scripts/codegen/utils.ts b/scripts/codegen/utils.ts index eca2a34182..fe89663280 100644 --- a/scripts/codegen/utils.ts +++ b/scripts/codegen/utils.ts @@ -532,13 +532,13 @@ const PERMISSION_REQUEST_DEFINITION_NAMES = [ "PermissionRequestExtensionManagement", "PermissionRequestExtensionEnvAccess", "PermissionRequestExtensionPermissionAccess", - "PermissionRequestFactory", "PermissionRequestHook", "PermissionRequestMcp", "PermissionRequestMemory", "PermissionRequestRead", "PermissionRequestShell", "PermissionRequestUrl", + "PermissionRequestWorkflow", "PermissionRequestWrite", ] as const; diff --git a/scripts/corrections/package-lock.json b/scripts/corrections/package-lock.json index a975812af3..faa7a956f7 100644 --- a/scripts/corrections/package-lock.json +++ b/scripts/corrections/package-lock.json @@ -10,7 +10,7 @@ "@octokit/rest": "^22.0.1", "@types/node": "^22.0.0", "typescript": "^5.8.0", - "vitest": "^4.1.0" + "vitest": "^4.1.11" } }, "node_modules/@actions/github": { @@ -40,66 +40,13 @@ "undici": "^6.23.0" } }, - "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==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/wasi-threads": "1.2.1", - "tslib": "^2.4.0" - } - }, - "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==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "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==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.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==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz", + "integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==", "dev": true, "license": "MIT" }, - "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==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@tybys/wasm-util": "^0.10.1" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Brooooooklyn" - }, - "peerDependencies": { - "@emnapi/core": "^1.7.1", - "@emnapi/runtime": "^1.7.1" - } - }, "node_modules/@octokit/auth-token": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-6.0.0.tgz", @@ -268,19 +215,36 @@ } }, "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==", + "version": "0.153.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.153.0.tgz", + "integrity": "sha512-jSNrRIyvPVLL5E1z/tS5zLAprosGz/FuI1PWwEOz59j3E4TTTzSpqKb/RDGqJhhW0QHYISGXxq582wGx6T11Tw==", "dev": true, "license": "MIT", "funding": { - "url": "https://github.com/sponsors/Boshen" + "url": "https://github.com/sponsors/oxc-project" + } + }, + "node_modules/@rolldown/binding-android-arm-eabi": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.13.tgz", + "integrity": "sha512-Vu/NICrgdRvUOeaJZIyB2FP63s6fO/4ud5l8dXmxDx9yDyn+mN0y+gsvsVviSli+T361Aqo0h46EuMoobrwawA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, "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==", + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.13.tgz", + "integrity": "sha512-6LEoIrGy5vvCd2boNnv2soPSUZj/PRvhp8R/t9KlX+mIZxThIE/ZqxrGXxmuNX0FuvQFvLdZr7S4HrYpYBVhEQ==", "cpu": [ "arm64" ], @@ -295,9 +259,9 @@ } }, "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==", + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.13.tgz", + "integrity": "sha512-CaJSRzlQ6Q+zkDMVA9OkVxBjVoPRCDEE3v0FSJ9kIQBgRjFApA3Si4ijhbNVUji7ZJt+PfO0e46qy4nPInHUPA==", "cpu": [ "arm64" ], @@ -312,9 +276,9 @@ } }, "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==", + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.13.tgz", + "integrity": "sha512-e4r340I5hxSzJkD3YJBxgzq/rJ3wwxwdLLb2FPt1Zpyem20nrQPT11DDEEkazwxaHumH8wpglsp/Ozgj/wT6mw==", "cpu": [ "x64" ], @@ -329,9 +293,9 @@ } }, "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==", + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.13.tgz", + "integrity": "sha512-hoBWDZ4/K/mJKVMbsgUd1jfpA2DiOrJ54mpegObU9TBHBBrtX4FLIXJJQRDPZ/2Dn91GvPob+Z2hwKJzf4E5SQ==", "cpu": [ "x64" ], @@ -346,9 +310,9 @@ } }, "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==", + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.13.tgz", + "integrity": "sha512-oF9fZu3vTWnTiVAkDFxU9PJLt1nh20K9RRpuHsR7ZnE2L7XookBbDBVLojCVSLGbvtSlxeUNnjAj6DbNsKyzbg==", "cpu": [ "arm" ], @@ -363,13 +327,16 @@ } }, "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==", + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.13.tgz", + "integrity": "sha512-DTpb/+jMiqWMPgphn4ksW1zW+XLJ6dJJ9FMvnIk9I291LhCctMPhMYJrP5t9p6C9qa3KXadDdUEZHBaPh3kNxQ==", "cpu": [ "arm64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -380,13 +347,16 @@ } }, "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==", + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.13.tgz", + "integrity": "sha512-xU+Kvv9N2A3SY35zFh1FWWNjxbadE/BKeqnkXe7vScU4GZsw757BlVmDLITZEq8DPpk6m7uoDYkK7P+x/uU5Ow==", "cpu": [ "arm64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -397,13 +367,16 @@ } }, "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==", + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.13.tgz", + "integrity": "sha512-XMk/hnhh25oQ756Lclb1Q11h4ll0xC0Esa5w4Vzp+mAE2S3AGJgeM0SonrIgY8fHhjarb6hcoTuwoFG0VVP2ig==", "cpu": [ "ppc64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -414,13 +387,16 @@ } }, "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==", + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.13.tgz", + "integrity": "sha512-U1BoTHYmlywE2n3HUWjzZ+8EQsKr+OwpxYuDWaubx42tUL8QSL7nOqRz/NVHanibbFP3M1Rxuprz/wNcbMKjqw==", "cpu": [ "s390x" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -431,13 +407,16 @@ } }, "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==", + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.13.tgz", + "integrity": "sha512-J4cm80Pc6wEwV5AZ42o62sIQgDk+Y7R2s646jIwuvO+4V46QB58N4vVuMPWCBaNgursXLpmUWJV//WQAkPvHBQ==", "cpu": [ "x64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -448,13 +427,16 @@ } }, "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==", + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.13.tgz", + "integrity": "sha512-W0euZS40U4V6JkmEnVCIX9mcsL6cbXQ4X8c2w+MMOIjFg68TXUW+u3RT+2e1t1pv9mg/EP3pDtK3POXdQ7Rxyw==", "cpu": [ "x64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -465,9 +447,9 @@ } }, "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==", + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.13.tgz", + "integrity": "sha512-do/AF/4n9MRshia3a5aXoy8xM2c6mgfT6D7H/dY6my5z+hfCIR4ab7mk4IRj++nsEQpKy39kd0DQnDq+r692tw==", "cpu": [ "arm64" ], @@ -481,29 +463,10 @@ "node": "^20.19.0 || >=22.12.0" } }, - "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==", - "cpu": [ - "wasm32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/core": "1.10.0", - "@emnapi/runtime": "1.10.0", - "@napi-rs/wasm-runtime": "^1.1.4" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, "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==", + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.13.tgz", + "integrity": "sha512-6FtUf2cJZobutJdxnGYX8z0vtbgqU0b2v8vfBhkXb9wp2MppMFtkunE+IaOntyi01rkIQoPW2GHABtfzsC0BRQ==", "cpu": [ "arm64" ], @@ -518,9 +481,9 @@ } }, "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==", + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.13.tgz", + "integrity": "sha512-UKhFaZTeTcbKrb3iU82m17jVcPEAoqDkCN5MBqgPyZSwp/cQmib21crTS0DdH7T1B7wZRWEI89YQAOJMet5yhg==", "cpu": [ "x64" ], @@ -548,17 +511,6 @@ "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==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, "node_modules/@types/chai": { "version": "5.2.3", "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", @@ -595,31 +547,31 @@ } }, "node_modules/@vitest/expect": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.0.tgz", - "integrity": "sha512-EIxG7k4wlWweuCLG9Y5InKFwpMEOyrMb6ZJ1ihYu02LVj/bzUwn2VMU+13PinsjRW75XnITeFrQBMH5+dLvCDA==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.11.tgz", + "integrity": "sha512-VX2x5vNJXET47KAFzwERI+KRMtTTCSWTfSMKsW7JsUsXV4psq++e3DvZpuTDOpHcxytiDs6p2nhVb2tVDiiUYw==", "dev": true, "license": "MIT", "dependencies": { "@standard-schema/spec": "^1.1.0", "@types/chai": "^5.2.2", - "@vitest/spy": "4.1.0", - "@vitest/utils": "4.1.0", + "@vitest/spy": "4.1.11", + "@vitest/utils": "4.1.11", "chai": "^6.2.2", - "tinyrainbow": "^3.0.3" + "tinyrainbow": "^3.1.0" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/mocker": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.0.tgz", - "integrity": "sha512-evxREh+Hork43+Y4IOhTo+h5lGmVRyjqI739Rz4RlUPqwrkFFDF6EMvOOYjTx4E8Tl6gyCLRL8Mu7Ry12a13Tw==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.11.tgz", + "integrity": "sha512-2XJVD55d1o5AZous5CCGKS74g/riOj9odEt2bQpCVZeblHyHdnMeFl4jl0XjU21stf4mbjUkew2eXQZt65g5CQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "4.1.0", + "@vitest/spy": "4.1.11", "estree-walker": "^3.0.3", "magic-string": "^0.30.21" }, @@ -628,7 +580,7 @@ }, "peerDependencies": { "msw": "^2.4.9", - "vite": "^6.0.0 || ^7.0.0 || ^8.0.0-0" + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" }, "peerDependenciesMeta": { "msw": { @@ -640,26 +592,26 @@ } }, "node_modules/@vitest/pretty-format": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.0.tgz", - "integrity": "sha512-3RZLZlh88Ib0J7NQTRATfc/3ZPOnSUn2uDBUoGNn5T36+bALixmzphN26OUD3LRXWkJu4H0s5vvUeqBiw+kS0A==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.11.tgz", + "integrity": "sha512-yiZzPbGTS9Sr/JpFl8zHrcIkAofNbFV6k21vIgQN/cY/oxZeXhJv5sc/MBJ5jFKWmWs+oJHw0UXLZjmf931+Vw==", "dev": true, "license": "MIT", "dependencies": { - "tinyrainbow": "^3.0.3" + "tinyrainbow": "^3.1.0" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/runner": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.0.tgz", - "integrity": "sha512-Duvx2OzQ7d6OjchL+trw+aSrb9idh7pnNfxrklo14p3zmNL4qPCDeIJAK+eBKYjkIwG96Bc6vYuxhqDXQOWpoQ==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.11.tgz", + "integrity": "sha512-LztvUgdwMNJMIkj3hQnnxiC2Xy1zNxq928W/xhjCLaNCzqTZOudjwbQf6v9IntZGPw132i2Lq2rgTRZHD3JHNw==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "4.1.0", + "@vitest/utils": "4.1.11", "pathe": "^2.0.3" }, "funding": { @@ -667,14 +619,14 @@ } }, "node_modules/@vitest/snapshot": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.0.tgz", - "integrity": "sha512-0Vy9euT1kgsnj1CHttwi9i9o+4rRLEaPRSOJ5gyv579GJkNpgJK+B4HSv/rAWixx2wdAFci1X4CEPjiu2bXIMg==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.11.tgz", + "integrity": "sha512-pN7ikn1ON7h8ee4gIAp4AzyK+zBtJPzVbqOgu5LCEh4VaJVbPQcgYQYJIMGQPXVeJJq1fnfazis7a5pFNPahog==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "4.1.0", - "@vitest/utils": "4.1.0", + "@vitest/pretty-format": "4.1.11", + "@vitest/utils": "4.1.11", "magic-string": "^0.30.21", "pathe": "^2.0.3" }, @@ -683,9 +635,9 @@ } }, "node_modules/@vitest/spy": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.0.tgz", - "integrity": "sha512-pz77k+PgNpyMDv2FV6qmk5ZVau6c3R8HC8v342T2xlFxQKTrSeYw9waIJG8KgV9fFwAtTu4ceRzMivPTH6wSxw==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.11.tgz", + "integrity": "sha512-apNa/prQy2qCeywhnixOHPRCgGNhvg7T4Dapfl1GahLp/R+uhBm5cPyFoNVyqsNd2h1nJxL6BqqdIjiABL60YA==", "dev": true, "license": "MIT", "funding": { @@ -693,15 +645,15 @@ } }, "node_modules/@vitest/utils": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.0.tgz", - "integrity": "sha512-XfPXT6a8TZY3dcGY8EdwsBulFCIw+BeeX0RZn2x/BtiY/75YGh8FeWGG8QISN/WhaqSrE2OrlDgtF8q5uhOTmw==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.11.tgz", + "integrity": "sha512-zTCVGpyFsGWBhllOyKlTw/vnr6D9qxsfSDyfbyZmTyjHw5N/VuvzHpHoQjm2ZJzn4RJgx5w4r7V0er69CmLgPQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "4.1.0", + "@vitest/pretty-format": "4.1.11", "convert-source-map": "^2.0.0", - "tinyrainbow": "^3.0.3" + "tinyrainbow": "^3.1.0" }, "funding": { "url": "https://opencollective.com/vitest" @@ -725,9 +677,9 @@ "license": "Apache-2.0" }, "node_modules/chai": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", - "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.3.0.tgz", + "integrity": "sha512-XWAtwJ6OHO+tj0EKCs0Y2UamnyOxseZWltU4x2U2wh8g4AigdjwvtUjvLP2tqkA/avxHEtzxNaqGq/YGNwckKg==", "dev": true, "license": "MIT", "engines": { @@ -836,9 +788,9 @@ "license": "MIT" }, "node_modules/lightningcss": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", - "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz", + "integrity": "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==", "dev": true, "license": "MPL-2.0", "dependencies": { @@ -852,23 +804,23 @@ "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" + "lightningcss-android-arm64": "1.33.0", + "lightningcss-darwin-arm64": "1.33.0", + "lightningcss-darwin-x64": "1.33.0", + "lightningcss-freebsd-x64": "1.33.0", + "lightningcss-linux-arm-gnueabihf": "1.33.0", + "lightningcss-linux-arm64-gnu": "1.33.0", + "lightningcss-linux-arm64-musl": "1.33.0", + "lightningcss-linux-x64-gnu": "1.33.0", + "lightningcss-linux-x64-musl": "1.33.0", + "lightningcss-win32-arm64-msvc": "1.33.0", + "lightningcss-win32-x64-msvc": "1.33.0" } }, "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==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.33.0.tgz", + "integrity": "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==", "cpu": [ "arm64" ], @@ -887,9 +839,9 @@ } }, "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==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.33.0.tgz", + "integrity": "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==", "cpu": [ "arm64" ], @@ -908,9 +860,9 @@ } }, "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==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.33.0.tgz", + "integrity": "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==", "cpu": [ "x64" ], @@ -929,9 +881,9 @@ } }, "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==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.33.0.tgz", + "integrity": "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==", "cpu": [ "x64" ], @@ -950,9 +902,9 @@ } }, "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==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.33.0.tgz", + "integrity": "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==", "cpu": [ "arm" ], @@ -971,13 +923,16 @@ } }, "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==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.33.0.tgz", + "integrity": "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==", "cpu": [ "arm64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MPL-2.0", "optional": true, "os": [ @@ -992,13 +947,16 @@ } }, "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==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.33.0.tgz", + "integrity": "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==", "cpu": [ "arm64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MPL-2.0", "optional": true, "os": [ @@ -1013,13 +971,16 @@ } }, "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==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.33.0.tgz", + "integrity": "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==", "cpu": [ "x64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MPL-2.0", "optional": true, "os": [ @@ -1034,13 +995,16 @@ } }, "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==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.33.0.tgz", + "integrity": "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==", "cpu": [ "x64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MPL-2.0", "optional": true, "os": [ @@ -1055,9 +1019,9 @@ } }, "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==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.33.0.tgz", + "integrity": "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==", "cpu": [ "arm64" ], @@ -1076,9 +1040,9 @@ } }, "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==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.33.0.tgz", + "integrity": "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==", "cpu": [ "x64" ], @@ -1107,9 +1071,9 @@ } }, "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==", + "version": "3.3.20", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.20.tgz", + "integrity": "sha512-uKdg2G3GNCKQn9byYOpxbGqrT2fGO5KRt5J/8b3pok8rT6qxGWF6hxMyJiEYtAf+FVyYuD9hRaDqX5uPFYJ4ZQ==", "dev": true, "funding": [ { @@ -1151,9 +1115,9 @@ "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==", + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", "dev": true, "license": "MIT", "engines": { @@ -1164,9 +1128,9 @@ } }, "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==", + "version": "8.5.29", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.29.tgz", + "integrity": "sha512-49cGhUbXj8Qenv0iTMxA1cFBzxXoctpC9Ujd77t1WcbJIr6nF/eI7g/8MgxrYldFRuAXvja7xQRwavoW7kgrxQ==", "dev": true, "funding": [ { @@ -1184,22 +1148,22 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.16", + "nanoid": "^3.3.19", "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" + "source-map-js": "^1.2.2" }, "engines": { "node": "^10 || ^12 || >=14" } }, "node_modules/rolldown": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.3.tgz", - "integrity": "sha512-i00lAJ2ks1BYr7rjNjKC7BcqAS7nVfiT3QX1SI5aY+AFHblCmaUf9OE9dbdzDvW6dJxbi2ZCZiy9v3CcwOiX3g==", + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.13.tgz", + "integrity": "sha512-/5apOkwWxtACcQtZ5WlrAsKS4JwXVThQPJZ3RqIruRY9UG9M8e0VlXN068xum/JRKA4aIUoJ13ghe/kGCvidiQ==", "dev": true, "license": "MIT", "dependencies": { - "@oxc-project/types": "=0.133.0", + "@oxc-project/types": "=0.153.0", "@rolldown/pluginutils": "^1.0.0" }, "bin": { @@ -1209,21 +1173,21 @@ "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" + "@rolldown/binding-android-arm-eabi": "1.2.13", + "@rolldown/binding-android-arm64": "1.2.13", + "@rolldown/binding-darwin-arm64": "1.2.13", + "@rolldown/binding-darwin-x64": "1.2.13", + "@rolldown/binding-freebsd-x64": "1.2.13", + "@rolldown/binding-linux-arm-gnueabihf": "1.2.13", + "@rolldown/binding-linux-arm64-gnu": "1.2.13", + "@rolldown/binding-linux-arm64-musl": "1.2.13", + "@rolldown/binding-linux-ppc64-gnu": "1.2.13", + "@rolldown/binding-linux-s390x-gnu": "1.2.13", + "@rolldown/binding-linux-x64-gnu": "1.2.13", + "@rolldown/binding-linux-x64-musl": "1.2.13", + "@rolldown/binding-openharmony-arm64": "1.2.13", + "@rolldown/binding-win32-arm64-msvc": "1.2.13", + "@rolldown/binding-win32-x64-msvc": "1.2.13" } }, "node_modules/siginfo": { @@ -1234,9 +1198,9 @@ "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==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.2.tgz", + "integrity": "sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==", "dev": true, "license": "BSD-3-Clause", "engines": { @@ -1292,23 +1256,15 @@ } }, "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==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.2.0.tgz", + "integrity": "sha512-LgO3D9yZJjApUiuUfl9iFAwrtaX4+lok3wJIqttGoKCHlWUqHqbQpnxCf82L8FjgKsh4iGo78hqJwgL8F6To2A==", "dev": true, "license": "MIT", "engines": { "node": ">=14.0.0" } }, - "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==", - "dev": true, - "license": "0BSD", - "optional": true - }, "node_modules/tunnel": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz", @@ -1358,16 +1314,16 @@ "license": "ISC" }, "node_modules/vite": { - "version": "8.0.16", - "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.16.tgz", - "integrity": "sha512-h9bXPmJichP5fLmVQo3PyaGSDE2n3aPuomeAlVRm0JLmt4rY6zmPKd59HYI4LNW8oTK7tlTsuC7l/m7awx9Jcw==", + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.3.4.tgz", + "integrity": "sha512-IftLE8phJhdNRIGNEV4JCCvfNScoZQVfcXaN8TQropWGJtOHcu+iAB9pHcuGvmEb/kddw0LBe/jS8ztdC6xdiQ==", "dev": true, "license": "MIT", "dependencies": { - "lightningcss": "^1.32.0", - "picomatch": "^4.0.4", - "postcss": "^8.5.15", - "rolldown": "1.0.3", + "lightningcss": "^1.33.0", + "picomatch": "^4.0.7", + "postcss": "^8.5.29", + "rolldown": "~1.2.12", "tinyglobby": "^0.2.17" }, "bin": { @@ -1384,7 +1340,7 @@ }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", - "@vitejs/devtools": "^0.1.18", + "@vitejs/devtools": "^0.7.1", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", @@ -1436,19 +1392,19 @@ } }, "node_modules/vitest": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.0.tgz", - "integrity": "sha512-YbDrMF9jM2Lqc++2530UourxZHmkKLxrs4+mYhEwqWS97WJ7wOYEkcr+QfRgJ3PW9wz3odRijLZjHEaRLTNbqw==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.11.tgz", + "integrity": "sha512-fhACrNXUidIbGSBr5FlbuBkO7VWC1ZyLl0DO4CU2DrQoAPxX84Ysxs+HeGQpii5lZWV1Q4gBZTTu49mF+A6Edw==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/expect": "4.1.0", - "@vitest/mocker": "4.1.0", - "@vitest/pretty-format": "4.1.0", - "@vitest/runner": "4.1.0", - "@vitest/snapshot": "4.1.0", - "@vitest/spy": "4.1.0", - "@vitest/utils": "4.1.0", + "@vitest/expect": "4.1.11", + "@vitest/mocker": "4.1.11", + "@vitest/pretty-format": "4.1.11", + "@vitest/runner": "4.1.11", + "@vitest/snapshot": "4.1.11", + "@vitest/spy": "4.1.11", + "@vitest/utils": "4.1.11", "es-module-lexer": "^2.0.0", "expect-type": "^1.3.0", "magic-string": "^0.30.21", @@ -1459,8 +1415,8 @@ "tinybench": "^2.9.0", "tinyexec": "^1.0.2", "tinyglobby": "^0.2.15", - "tinyrainbow": "^3.0.3", - "vite": "^6.0.0 || ^7.0.0 || ^8.0.0-0", + "tinyrainbow": "^3.1.0", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", "why-is-node-running": "^2.3.0" }, "bin": { @@ -1476,13 +1432,15 @@ "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", - "@vitest/browser-playwright": "4.1.0", - "@vitest/browser-preview": "4.1.0", - "@vitest/browser-webdriverio": "4.1.0", - "@vitest/ui": "4.1.0", + "@vitest/browser-playwright": "4.1.11", + "@vitest/browser-preview": "4.1.11", + "@vitest/browser-webdriverio": "4.1.11", + "@vitest/coverage-istanbul": "4.1.11", + "@vitest/coverage-v8": "4.1.11", + "@vitest/ui": "4.1.11", "happy-dom": "*", "jsdom": "*", - "vite": "^6.0.0 || ^7.0.0 || ^8.0.0-0" + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" }, "peerDependenciesMeta": { "@edge-runtime/vm": { @@ -1503,6 +1461,12 @@ "@vitest/browser-webdriverio": { "optional": true }, + "@vitest/coverage-istanbul": { + "optional": true + }, + "@vitest/coverage-v8": { + "optional": true + }, "@vitest/ui": { "optional": true }, diff --git a/scripts/corrections/package.json b/scripts/corrections/package.json index 0afee52cc9..0dc9cfeb4b 100644 --- a/scripts/corrections/package.json +++ b/scripts/corrections/package.json @@ -10,6 +10,6 @@ "@octokit/rest": "^22.0.1", "@types/node": "^22.0.0", "typescript": "^5.8.0", - "vitest": "^4.1.0" + "vitest": "^4.1.11" } } diff --git a/scripts/docs-validation/package-lock.json b/scripts/docs-validation/package-lock.json index 0c2751fa78..0f0c48a705 100644 --- a/scripts/docs-validation/package-lock.json +++ b/scripts/docs-validation/package-lock.json @@ -480,9 +480,9 @@ } }, "node_modules/brace-expansion": { - "version": "5.0.9", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", - "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.12.tgz", + "integrity": "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==", "license": "MIT", "dependencies": { "balanced-match": "^4.0.2" diff --git a/test/harness/package-lock.json b/test/harness/package-lock.json index 0c31f6ea1d..8a72836f53 100644 --- a/test/harness/package-lock.json +++ b/test/harness/package-lock.json @@ -9,51 +9,20 @@ "version": "1.0.0", "license": "ISC", "devDependencies": { - "@modelcontextprotocol/sdk": "^1.26.0", + "@modelcontextprotocol/sdk": "^1.31.0", "@types/node": "^25.3.3", "@types/node-forge": "^1.3.14", "node-forge": "^1.4.0", "openai": "^6.17.0", "tsx": "^4.21.0", "typescript": "^5.9.3", - "vitest": "^4.0.18", + "vitest": "^4.1.11", "yaml": "^2.8.2" }, "engines": { "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@emnapi/core": { - "version": "1.10.0", - "integrity": "sha1-OAzMjyQS6iLR2XLff47iOjucdGc=", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/wasi-threads": "1.2.1", - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/runtime": { - "version": "1.10.0", - "integrity": "sha1-SyYMDTU0IE6YxhELjbGph9JuyHw=", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/wasi-threads": { - "version": "1.2.1", - "integrity": "sha1-KP7SGhuhznl8RKBwq8lNQvOuhUg=", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, "node_modules/@esbuild/aix-ppc64": { "version": "0.28.1", "integrity": "sha1-egGo0uwvuy2seK2tCbD6eB5Agr4=", @@ -469,28 +438,19 @@ "node": ">=18" } }, - "node_modules/@hono/node-server": { - "version": "1.19.14", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18.14.1" - }, - "peerDependencies": { - "hono": "^4" - } - }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", + "version": "1.6.0", + "integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==", "dev": true, "license": "MIT" }, "node_modules/@modelcontextprotocol/sdk": { - "version": "1.26.0", + "version": "1.32.1", + "integrity": "sha512-2DdE+SJDtzLEEWzY1ZjY7Q+VcPhcV1KisD3zI4u0XZyktsjHum1mwbMI+JaulUBi2OZk+KJAi2uPXzxichPkdw==", "dev": true, "license": "MIT", "dependencies": { - "@hono/node-server": "^1.19.9", + "@hono/node-server": "^1.19.9 || ^2.0.5", "ajv": "^8.17.1", "ajv-formats": "^3.0.1", "content-type": "^1.0.5", @@ -524,38 +484,46 @@ } } }, - "node_modules/@napi-rs/wasm-runtime": { - "version": "1.2.3", - "integrity": "sha1-l+PUXXQk3F2h1OMvO/OykvbBtEw=", + "node_modules/@modelcontextprotocol/sdk/node_modules/@hono/node-server": { + "version": "1.19.17", + "integrity": "sha512-dSneS5qhiauZWGDCeK4o695Xd9nUNjviSZCMQrj10eetr8Uln1ucn6bbphOM6UynAMMtNIzZNSpL9vnASJwrPQ==", "dev": true, "license": "MIT", - "optional": true, - "dependencies": { - "@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" + "node": ">=18.14.1" }, "peerDependencies": { - "@emnapi/core": "^1.7.1 || ^2.0.0-alpha.4", - "@emnapi/runtime": "^1.7.1 || ^2.0.0-alpha.4" + "hono": "^4" } }, "node_modules/@oxc-project/types": { - "version": "0.133.0", + "version": "0.153.0", + "integrity": "sha512-jSNrRIyvPVLL5E1z/tS5zLAprosGz/FuI1PWwEOz59j3E4TTTzSpqKb/RDGqJhhW0QHYISGXxq582wGx6T11Tw==", "dev": true, "license": "MIT", "funding": { - "url": "https://github.com/sponsors/Boshen" + "url": "https://github.com/sponsors/oxc-project" + } + }, + "node_modules/@rolldown/binding-android-arm-eabi": { + "version": "1.2.13", + "integrity": "sha512-Vu/NICrgdRvUOeaJZIyB2FP63s6fO/4ud5l8dXmxDx9yDyn+mN0y+gsvsVviSli+T361Aqo0h46EuMoobrwawA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, "node_modules/@rolldown/binding-android-arm64": { - "version": "1.0.3", - "integrity": "sha1-VM6Pg4IhP0oxSgwve6g/gf/q5ZI=", + "version": "1.2.13", + "integrity": "sha512-6LEoIrGy5vvCd2boNnv2soPSUZj/PRvhp8R/t9KlX+mIZxThIE/ZqxrGXxmuNX0FuvQFvLdZr7S4HrYpYBVhEQ==", "cpu": [ "arm64" ], @@ -570,7 +538,8 @@ } }, "node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.0.3", + "version": "1.2.13", + "integrity": "sha512-CaJSRzlQ6Q+zkDMVA9OkVxBjVoPRCDEE3v0FSJ9kIQBgRjFApA3Si4ijhbNVUji7ZJt+PfO0e46qy4nPInHUPA==", "cpu": [ "arm64" ], @@ -585,8 +554,8 @@ } }, "node_modules/@rolldown/binding-darwin-x64": { - "version": "1.0.3", - "integrity": "sha1-U/V94fWZ7PHbE4I8/IjBj7gJVK0=", + "version": "1.2.13", + "integrity": "sha512-e4r340I5hxSzJkD3YJBxgzq/rJ3wwxwdLLb2FPt1Zpyem20nrQPT11DDEEkazwxaHumH8wpglsp/Ozgj/wT6mw==", "cpu": [ "x64" ], @@ -601,8 +570,8 @@ } }, "node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.0.3", - "integrity": "sha1-bz/dobeuqsnSaKUmgEtPuW5ONfE=", + "version": "1.2.13", + "integrity": "sha512-hoBWDZ4/K/mJKVMbsgUd1jfpA2DiOrJ54mpegObU9TBHBBrtX4FLIXJJQRDPZ/2Dn91GvPob+Z2hwKJzf4E5SQ==", "cpu": [ "x64" ], @@ -617,8 +586,8 @@ } }, "node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.0.3", - "integrity": "sha1-2HpFS/WFzJZ2hJN36R1uN1KXMm8=", + "version": "1.2.13", + "integrity": "sha512-oF9fZu3vTWnTiVAkDFxU9PJLt1nh20K9RRpuHsR7ZnE2L7XookBbDBVLojCVSLGbvtSlxeUNnjAj6DbNsKyzbg==", "cpu": [ "arm" ], @@ -633,12 +602,15 @@ } }, "node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.0.3", - "integrity": "sha1-QZ/Wv2Es80jxBSjLzZTrq5YH2NE=", + "version": "1.2.13", + "integrity": "sha512-DTpb/+jMiqWMPgphn4ksW1zW+XLJ6dJJ9FMvnIk9I291LhCctMPhMYJrP5t9p6C9qa3KXadDdUEZHBaPh3kNxQ==", "cpu": [ "arm64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -649,12 +621,15 @@ } }, "node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.0.3", - "integrity": "sha1-/MaRhpa7doRId+HkkwoY/Q03QGk=", + "version": "1.2.13", + "integrity": "sha512-xU+Kvv9N2A3SY35zFh1FWWNjxbadE/BKeqnkXe7vScU4GZsw757BlVmDLITZEq8DPpk6m7uoDYkK7P+x/uU5Ow==", "cpu": [ "arm64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -665,12 +640,15 @@ } }, "node_modules/@rolldown/binding-linux-ppc64-gnu": { - "version": "1.0.3", - "integrity": "sha1-Mq7LfI2uXU8qjN5XoFjshpkVQvg=", + "version": "1.2.13", + "integrity": "sha512-XMk/hnhh25oQ756Lclb1Q11h4ll0xC0Esa5w4Vzp+mAE2S3AGJgeM0SonrIgY8fHhjarb6hcoTuwoFG0VVP2ig==", "cpu": [ "ppc64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -681,12 +659,15 @@ } }, "node_modules/@rolldown/binding-linux-s390x-gnu": { - "version": "1.0.3", - "integrity": "sha1-vtk0bqgea7i5PPEfXYi3fbiQt2M=", + "version": "1.2.13", + "integrity": "sha512-U1BoTHYmlywE2n3HUWjzZ+8EQsKr+OwpxYuDWaubx42tUL8QSL7nOqRz/NVHanibbFP3M1Rxuprz/wNcbMKjqw==", "cpu": [ "s390x" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -697,12 +678,15 @@ } }, "node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.0.3", - "integrity": "sha1-ZMLSb3Xf/ZtaH5dVegCudyUMjLc=", + "version": "1.2.13", + "integrity": "sha512-J4cm80Pc6wEwV5AZ42o62sIQgDk+Y7R2s646jIwuvO+4V46QB58N4vVuMPWCBaNgursXLpmUWJV//WQAkPvHBQ==", "cpu": [ "x64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -713,12 +697,15 @@ } }, "node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.0.3", - "integrity": "sha1-WkUTLopHZZ7qrztUDClUqXyGD/M=", + "version": "1.2.13", + "integrity": "sha512-W0euZS40U4V6JkmEnVCIX9mcsL6cbXQ4X8c2w+MMOIjFg68TXUW+u3RT+2e1t1pv9mg/EP3pDtK3POXdQ7Rxyw==", "cpu": [ "x64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -729,8 +716,8 @@ } }, "node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.0.3", - "integrity": "sha1-KQUTBoxV6EnchFejKv7h17Csswk=", + "version": "1.2.13", + "integrity": "sha512-do/AF/4n9MRshia3a5aXoy8xM2c6mgfT6D7H/dY6my5z+hfCIR4ab7mk4IRj++nsEQpKy39kd0DQnDq+r692tw==", "cpu": [ "arm64" ], @@ -744,27 +731,9 @@ "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@rolldown/binding-wasm32-wasi": { - "version": "1.0.3", - "integrity": "sha1-PZly2/GpU9PHr6pKDyDvKy458xs=", - "cpu": [ - "wasm32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/core": "1.10.0", - "@emnapi/runtime": "1.10.0", - "@napi-rs/wasm-runtime": "^1.1.4" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, "node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.0.3", - "integrity": "sha1-oASrYHoW1vA7y1VXKP+IivdXc60=", + "version": "1.2.13", + "integrity": "sha512-6FtUf2cJZobutJdxnGYX8z0vtbgqU0b2v8vfBhkXb9wp2MppMFtkunE+IaOntyi01rkIQoPW2GHABtfzsC0BRQ==", "cpu": [ "arm64" ], @@ -779,8 +748,8 @@ } }, "node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.0.3", - "integrity": "sha1-4qJbNGkaHMihIJ195wkGMCbdDNs=", + "version": "1.2.13", + "integrity": "sha512-UKhFaZTeTcbKrb3iU82m17jVcPEAoqDkCN5MBqgPyZSwp/cQmib21crTS0DdH7T1B7wZRWEI89YQAOJMet5yhg==", "cpu": [ "x64" ], @@ -796,26 +765,19 @@ }, "node_modules/@rolldown/pluginutils": { "version": "1.0.1", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", "dev": true, "license": "MIT" }, "node_modules/@standard-schema/spec": { "version": "1.1.0", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", "dev": true, "license": "MIT" }, - "node_modules/@tybys/wasm-util": { - "version": "0.10.3", - "integrity": "sha1-AVy6np3UfOFNA9KoxdVHv7FpZl0=", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, "node_modules/@types/chai": { "version": "5.2.3", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", "dev": true, "license": "MIT", "dependencies": { @@ -825,11 +787,13 @@ }, "node_modules/@types/deep-eql": { "version": "4.0.2", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", "dev": true, "license": "MIT" }, "node_modules/@types/estree": { "version": "1.0.9", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", "dev": true, "license": "MIT" }, @@ -850,14 +814,15 @@ } }, "node_modules/@vitest/expect": { - "version": "4.1.8", + "version": "4.1.11", + "integrity": "sha512-VX2x5vNJXET47KAFzwERI+KRMtTTCSWTfSMKsW7JsUsXV4psq++e3DvZpuTDOpHcxytiDs6p2nhVb2tVDiiUYw==", "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", + "@vitest/spy": "4.1.11", + "@vitest/utils": "4.1.11", "chai": "^6.2.2", "tinyrainbow": "^3.1.0" }, @@ -866,11 +831,12 @@ } }, "node_modules/@vitest/mocker": { - "version": "4.1.8", + "version": "4.1.11", + "integrity": "sha512-2XJVD55d1o5AZous5CCGKS74g/riOj9odEt2bQpCVZeblHyHdnMeFl4jl0XjU21stf4mbjUkew2eXQZt65g5CQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "4.1.8", + "@vitest/spy": "4.1.11", "estree-walker": "^3.0.3", "magic-string": "^0.30.21" }, @@ -891,7 +857,8 @@ } }, "node_modules/@vitest/pretty-format": { - "version": "4.1.8", + "version": "4.1.11", + "integrity": "sha512-yiZzPbGTS9Sr/JpFl8zHrcIkAofNbFV6k21vIgQN/cY/oxZeXhJv5sc/MBJ5jFKWmWs+oJHw0UXLZjmf931+Vw==", "dev": true, "license": "MIT", "dependencies": { @@ -902,11 +869,12 @@ } }, "node_modules/@vitest/runner": { - "version": "4.1.8", + "version": "4.1.11", + "integrity": "sha512-LztvUgdwMNJMIkj3hQnnxiC2Xy1zNxq928W/xhjCLaNCzqTZOudjwbQf6v9IntZGPw132i2Lq2rgTRZHD3JHNw==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "4.1.8", + "@vitest/utils": "4.1.11", "pathe": "^2.0.3" }, "funding": { @@ -914,12 +882,13 @@ } }, "node_modules/@vitest/snapshot": { - "version": "4.1.8", + "version": "4.1.11", + "integrity": "sha512-pN7ikn1ON7h8ee4gIAp4AzyK+zBtJPzVbqOgu5LCEh4VaJVbPQcgYQYJIMGQPXVeJJq1fnfazis7a5pFNPahog==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "4.1.8", - "@vitest/utils": "4.1.8", + "@vitest/pretty-format": "4.1.11", + "@vitest/utils": "4.1.11", "magic-string": "^0.30.21", "pathe": "^2.0.3" }, @@ -928,7 +897,8 @@ } }, "node_modules/@vitest/spy": { - "version": "4.1.8", + "version": "4.1.11", + "integrity": "sha512-apNa/prQy2qCeywhnixOHPRCgGNhvg7T4Dapfl1GahLp/R+uhBm5cPyFoNVyqsNd2h1nJxL6BqqdIjiABL60YA==", "dev": true, "license": "MIT", "funding": { @@ -936,11 +906,12 @@ } }, "node_modules/@vitest/utils": { - "version": "4.1.8", + "version": "4.1.11", + "integrity": "sha512-zTCVGpyFsGWBhllOyKlTw/vnr6D9qxsfSDyfbyZmTyjHw5N/VuvzHpHoQjm2ZJzn4RJgx5w4r7V0er69CmLgPQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "4.1.8", + "@vitest/pretty-format": "4.1.11", "convert-source-map": "^2.0.0", "tinyrainbow": "^3.1.0" }, @@ -993,6 +964,7 @@ }, "node_modules/assertion-error": { "version": "2.0.1", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", "dev": true, "license": "MIT", "engines": { @@ -1000,19 +972,20 @@ } }, "node_modules/body-parser": { - "version": "2.2.2", + "version": "2.3.0", + "integrity": "sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==", "dev": true, "license": "MIT", "dependencies": { "bytes": "^3.1.2", - "content-type": "^1.0.5", + "content-type": "^2.0.0", "debug": "^4.4.3", - "http-errors": "^2.0.0", - "iconv-lite": "^0.7.0", + "http-errors": "^2.0.1", + "iconv-lite": "^0.7.2", "on-finished": "^2.4.1", - "qs": "^6.14.1", - "raw-body": "^3.0.1", - "type-is": "^2.0.1" + "qs": "^6.15.2", + "raw-body": "^3.0.2", + "type-is": "^2.1.0" }, "engines": { "node": ">=18" @@ -1022,6 +995,19 @@ "url": "https://opencollective.com/express" } }, + "node_modules/body-parser/node_modules/content-type": { + "version": "2.1.0", + "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/bytes": { "version": "3.1.2", "dev": true, @@ -1032,6 +1018,7 @@ }, "node_modules/call-bind-apply-helpers": { "version": "1.0.2", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1044,6 +1031,7 @@ }, "node_modules/call-bound": { "version": "1.0.4", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", "dev": true, "license": "MIT", "dependencies": { @@ -1058,7 +1046,8 @@ } }, "node_modules/chai": { - "version": "6.2.2", + "version": "6.3.0", + "integrity": "sha512-XWAtwJ6OHO+tj0EKCs0Y2UamnyOxseZWltU4x2U2wh8g4AigdjwvtUjvLP2tqkA/avxHEtzxNaqGq/YGNwckKg==", "dev": true, "license": "MIT", "engines": { @@ -1087,6 +1076,7 @@ }, "node_modules/convert-source-map": { "version": "2.0.0", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", "dev": true, "license": "MIT" }, @@ -1161,6 +1151,7 @@ }, "node_modules/detect-libc": { "version": "2.1.2", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", "dev": true, "license": "Apache-2.0", "engines": { @@ -1169,6 +1160,7 @@ }, "node_modules/dunder-proto": { "version": "1.0.1", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", "dev": true, "license": "MIT", "dependencies": { @@ -1195,6 +1187,7 @@ }, "node_modules/es-define-property": { "version": "1.0.1", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", "dev": true, "license": "MIT", "engines": { @@ -1203,6 +1196,7 @@ }, "node_modules/es-errors": { "version": "1.3.0", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", "dev": true, "license": "MIT", "engines": { @@ -1215,7 +1209,8 @@ "license": "MIT" }, "node_modules/es-object-atoms": { - "version": "1.1.1", + "version": "1.1.2", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", "dev": true, "license": "MIT", "dependencies": { @@ -1272,6 +1267,7 @@ }, "node_modules/estree-walker": { "version": "3.0.3", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", "dev": true, "license": "MIT", "dependencies": { @@ -1378,8 +1374,8 @@ "license": "MIT" }, "node_modules/fast-uri": { - "version": "3.1.7", - "integrity": "sha512-dOvZVzjdZdz7phd9v6jCbwxrBW3fK6n8Rc0CtdmM4bumzMnxywBYhuph6J819RRw/ku+rLbelwfMunktuzVVHg==", + "version": "3.1.8", + "integrity": "sha512-GZMtZUTNRpOVIECoXwLNZS5xUGE+mVNbTB8h/7Rwh2TFWcBQiPzTgyZi05BF9UMZKkLJv8XBRJTlU7zg8+ZfMg==", "dev": true, "funding": [ { @@ -1395,6 +1391,7 @@ }, "node_modules/fdir": { "version": "6.5.0", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", "dev": true, "license": "MIT", "engines": { @@ -1459,6 +1456,7 @@ }, "node_modules/function-bind": { "version": "1.1.2", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", "dev": true, "license": "MIT", "funding": { @@ -1467,6 +1465,7 @@ }, "node_modules/get-intrinsic": { "version": "1.3.0", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1490,6 +1489,7 @@ }, "node_modules/get-proto": { "version": "1.0.1", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", "dev": true, "license": "MIT", "dependencies": { @@ -1502,6 +1502,7 @@ }, "node_modules/gopd": { "version": "1.2.0", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", "dev": true, "license": "MIT", "engines": { @@ -1513,6 +1514,7 @@ }, "node_modules/has-symbols": { "version": "1.1.0", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", "dev": true, "license": "MIT", "engines": { @@ -1523,7 +1525,8 @@ } }, "node_modules/hasown": { - "version": "2.0.2", + "version": "2.0.4", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", "dev": true, "license": "MIT", "dependencies": { @@ -1582,7 +1585,8 @@ "license": "ISC" }, "node_modules/ip-address": { - "version": "10.4.0", + "version": "10.7.4", + "integrity": "sha512-DWBMKyeM25D70nrIiTkgQth59/G+naKOTep6huF9j+GmeK3DMD60Zvxg5d5qQnz+38AHgou6ean2LECX+lrKDg==", "dev": true, "license": "MIT", "engines": { @@ -1626,7 +1630,8 @@ "license": "BSD-2-Clause" }, "node_modules/lightningcss": { - "version": "1.32.0", + "version": "1.33.0", + "integrity": "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==", "dev": true, "license": "MPL-2.0", "dependencies": { @@ -1640,22 +1645,22 @@ "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" + "lightningcss-android-arm64": "1.33.0", + "lightningcss-darwin-arm64": "1.33.0", + "lightningcss-darwin-x64": "1.33.0", + "lightningcss-freebsd-x64": "1.33.0", + "lightningcss-linux-arm-gnueabihf": "1.33.0", + "lightningcss-linux-arm64-gnu": "1.33.0", + "lightningcss-linux-arm64-musl": "1.33.0", + "lightningcss-linux-x64-gnu": "1.33.0", + "lightningcss-linux-x64-musl": "1.33.0", + "lightningcss-win32-arm64-msvc": "1.33.0", + "lightningcss-win32-x64-msvc": "1.33.0" } }, "node_modules/lightningcss-android-arm64": { - "version": "1.32.0", - "integrity": "sha1-8DOIURbf79nG9UeHUj41FLYeGWg=", + "version": "1.33.0", + "integrity": "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==", "cpu": [ "arm64" ], @@ -1674,7 +1679,8 @@ } }, "node_modules/lightningcss-darwin-arm64": { - "version": "1.32.0", + "version": "1.33.0", + "integrity": "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==", "cpu": [ "arm64" ], @@ -1693,8 +1699,8 @@ } }, "node_modules/lightningcss-darwin-x64": { - "version": "1.32.0", - "integrity": "sha1-NfPpczLRMLnKGB4RtWje1q68bV4=", + "version": "1.33.0", + "integrity": "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==", "cpu": [ "x64" ], @@ -1713,8 +1719,8 @@ } }, "node_modules/lightningcss-freebsd-x64": { - "version": "1.32.0", - "integrity": "sha1-l3enZHK2Ttb/lDQq1kx7r9eUpXU=", + "version": "1.33.0", + "integrity": "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==", "cpu": [ "x64" ], @@ -1733,8 +1739,8 @@ } }, "node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.32.0", - "integrity": "sha1-E65lLhq3O5E117faFy9mbEEK1T0=", + "version": "1.33.0", + "integrity": "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==", "cpu": [ "arm" ], @@ -1753,12 +1759,15 @@ } }, "node_modules/lightningcss-linux-arm64-gnu": { - "version": "1.32.0", - "integrity": "sha1-QXhYeVqUWS9oASOhsfnaig4e8zU=", + "version": "1.33.0", + "integrity": "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==", "cpu": [ "arm64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MPL-2.0", "optional": true, "os": [ @@ -1773,12 +1782,15 @@ } }, "node_modules/lightningcss-linux-arm64-musl": { - "version": "1.32.0", - "integrity": "sha1-a+NmkugQtxgECAL9gJYjz/5zITM=", + "version": "1.33.0", + "integrity": "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==", "cpu": [ "arm64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MPL-2.0", "optional": true, "os": [ @@ -1793,12 +1805,15 @@ } }, "node_modules/lightningcss-linux-x64-gnu": { - "version": "1.32.0", - "integrity": "sha1-C3gDr06yHP043Tn+Kru1PH3QkfY=", + "version": "1.33.0", + "integrity": "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==", "cpu": [ "x64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MPL-2.0", "optional": true, "os": [ @@ -1813,12 +1828,15 @@ } }, "node_modules/lightningcss-linux-x64-musl": { - "version": "1.32.0", - "integrity": "sha1-iNyLqGXd3bGsXvBLDxYYBEGMFjs=", + "version": "1.33.0", + "integrity": "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==", "cpu": [ "x64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MPL-2.0", "optional": true, "os": [ @@ -1833,8 +1851,8 @@ } }, "node_modules/lightningcss-win32-arm64-msvc": { - "version": "1.32.0", - "integrity": "sha1-TzC6P6XpJfW3n5RejMDRdsOxqzg=", + "version": "1.33.0", + "integrity": "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==", "cpu": [ "arm64" ], @@ -1853,8 +1871,8 @@ } }, "node_modules/lightningcss-win32-x64-msvc": { - "version": "1.32.0", - "integrity": "sha1-FBqlYFZFBkkokCu0rwRfp9n0Igo=", + "version": "1.33.0", + "integrity": "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==", "cpu": [ "x64" ], @@ -1874,6 +1892,7 @@ }, "node_modules/magic-string": { "version": "0.30.21", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1882,6 +1901,7 @@ }, "node_modules/math-intrinsics": { "version": "1.1.0", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", "dev": true, "license": "MIT", "engines": { @@ -1936,7 +1956,8 @@ "license": "MIT" }, "node_modules/nanoid": { - "version": "3.3.17", + "version": "3.3.20", + "integrity": "sha512-uKdg2G3GNCKQn9byYOpxbGqrT2fGO5KRt5J/8b3pok8rT6qxGWF6hxMyJiEYtAf+FVyYuD9hRaDqX5uPFYJ4ZQ==", "dev": true, "funding": [ { @@ -1978,6 +1999,7 @@ }, "node_modules/object-inspect": { "version": "1.13.4", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", "dev": true, "license": "MIT", "engines": { @@ -2062,16 +2084,19 @@ }, "node_modules/pathe": { "version": "2.0.3", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", "dev": true, "license": "MIT" }, "node_modules/picocolors": { "version": "1.1.1", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", "dev": true, "license": "ISC" }, "node_modules/picomatch": { - "version": "4.0.4", + "version": "4.0.7", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", "dev": true, "license": "MIT", "engines": { @@ -2090,7 +2115,8 @@ } }, "node_modules/postcss": { - "version": "8.5.25", + "version": "8.5.29", + "integrity": "sha512-49cGhUbXj8Qenv0iTMxA1cFBzxXoctpC9Ujd77t1WcbJIr6nF/eI7g/8MgxrYldFRuAXvja7xQRwavoW7kgrxQ==", "dev": true, "funding": [ { @@ -2108,16 +2134,17 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.16", + "nanoid": "^3.3.19", "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" + "source-map-js": "^1.2.2" }, "engines": { "node": "^10 || ^12 || >=14" } }, "node_modules/proxy-addr": { - "version": "2.0.7", + "version": "2.0.8", + "integrity": "sha512-5nnx0yGyVUcY6t9RnWcARWtwT9F1D8O9rt08htPvnd49W1IgZtmLkhu9WfMzQj1cFxjHIO6connUNVW5k7AVyQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2126,14 +2153,20 @@ }, "engines": { "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/qs": { - "version": "6.15.2", + "version": "6.16.0", + "integrity": "sha512-h6fhOIaRrID2CbEY2fqs+7t+UXZo+MLAnU5gRIq85uFtdiUPCdsApMlHhXogKVM4HM2DVbIjGNTTYH2OcmP1vA==", "dev": true, "license": "BSD-3-Clause", "dependencies": { - "side-channel": "^1.1.0" + "es-define-property": "^1.0.1", + "side-channel": "^1.1.1" }, "engines": { "node": ">=0.6" @@ -2173,11 +2206,12 @@ } }, "node_modules/rolldown": { - "version": "1.0.3", + "version": "1.2.13", + "integrity": "sha512-/5apOkwWxtACcQtZ5WlrAsKS4JwXVThQPJZ3RqIruRY9UG9M8e0VlXN068xum/JRKA4aIUoJ13ghe/kGCvidiQ==", "dev": true, "license": "MIT", "dependencies": { - "@oxc-project/types": "=0.133.0", + "@oxc-project/types": "=0.153.0", "@rolldown/pluginutils": "^1.0.0" }, "bin": { @@ -2187,21 +2221,21 @@ "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" + "@rolldown/binding-android-arm-eabi": "1.2.13", + "@rolldown/binding-android-arm64": "1.2.13", + "@rolldown/binding-darwin-arm64": "1.2.13", + "@rolldown/binding-darwin-x64": "1.2.13", + "@rolldown/binding-freebsd-x64": "1.2.13", + "@rolldown/binding-linux-arm-gnueabihf": "1.2.13", + "@rolldown/binding-linux-arm64-gnu": "1.2.13", + "@rolldown/binding-linux-arm64-musl": "1.2.13", + "@rolldown/binding-linux-ppc64-gnu": "1.2.13", + "@rolldown/binding-linux-s390x-gnu": "1.2.13", + "@rolldown/binding-linux-x64-gnu": "1.2.13", + "@rolldown/binding-linux-x64-musl": "1.2.13", + "@rolldown/binding-openharmony-arm64": "1.2.13", + "@rolldown/binding-win32-arm64-msvc": "1.2.13", + "@rolldown/binding-win32-x64-msvc": "1.2.13" } }, "node_modules/router": { @@ -2292,13 +2326,14 @@ } }, "node_modules/side-channel": { - "version": "1.1.0", + "version": "1.1.1", + "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", - "object-inspect": "^1.13.3", - "side-channel-list": "^1.0.0", + "object-inspect": "^1.13.4", + "side-channel-list": "^1.0.1", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" }, @@ -2310,12 +2345,13 @@ } }, "node_modules/side-channel-list": { - "version": "1.0.0", + "version": "1.0.1", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", - "object-inspect": "^1.13.3" + "object-inspect": "^1.13.4" }, "engines": { "node": ">= 0.4" @@ -2326,6 +2362,7 @@ }, "node_modules/side-channel-map": { "version": "1.0.1", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", "dev": true, "license": "MIT", "dependencies": { @@ -2343,6 +2380,7 @@ }, "node_modules/side-channel-weakmap": { "version": "1.0.2", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", "dev": true, "license": "MIT", "dependencies": { @@ -2365,7 +2403,8 @@ "license": "ISC" }, "node_modules/source-map-js": { - "version": "1.2.1", + "version": "1.2.2", + "integrity": "sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==", "dev": true, "license": "BSD-3-Clause", "engines": { @@ -2405,6 +2444,7 @@ }, "node_modules/tinyglobby": { "version": "0.2.17", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", "dev": true, "license": "MIT", "dependencies": { @@ -2419,7 +2459,8 @@ } }, "node_modules/tinyrainbow": { - "version": "3.1.0", + "version": "3.2.0", + "integrity": "sha512-LgO3D9yZJjApUiuUfl9iFAwrtaX4+lok3wJIqttGoKCHlWUqHqbQpnxCf82L8FjgKsh4iGo78hqJwgL8F6To2A==", "dev": true, "license": "MIT", "engines": { @@ -2434,13 +2475,6 @@ "node": ">=0.6" } }, - "node_modules/tslib": { - "version": "2.8.1", - "integrity": "sha1-YS7+TtI11Wfoq6Xypfq3AoCt6D8=", - "dev": true, - "license": "0BSD", - "optional": true - }, "node_modules/tsx": { "version": "4.22.4", "dev": true, @@ -2459,16 +2493,34 @@ } }, "node_modules/type-is": { - "version": "2.0.1", + "version": "2.1.0", + "integrity": "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==", "dev": true, "license": "MIT", "dependencies": { - "content-type": "^1.0.5", + "content-type": "^2.0.0", "media-typer": "^1.1.0", "mime-types": "^3.0.0" }, "engines": { - "node": ">= 0.6" + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/type-is/node_modules/content-type": { + "version": "2.1.0", + "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/typescript": { @@ -2505,14 +2557,15 @@ } }, "node_modules/vite": { - "version": "8.0.16", + "version": "8.3.4", + "integrity": "sha512-IftLE8phJhdNRIGNEV4JCCvfNScoZQVfcXaN8TQropWGJtOHcu+iAB9pHcuGvmEb/kddw0LBe/jS8ztdC6xdiQ==", "dev": true, "license": "MIT", "dependencies": { - "lightningcss": "^1.32.0", - "picomatch": "^4.0.4", - "postcss": "^8.5.15", - "rolldown": "1.0.3", + "lightningcss": "^1.33.0", + "picomatch": "^4.0.7", + "postcss": "^8.5.29", + "rolldown": "~1.2.12", "tinyglobby": "^0.2.17" }, "bin": { @@ -2529,7 +2582,7 @@ }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", - "@vitejs/devtools": "^0.1.18", + "@vitejs/devtools": "^0.7.1", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", @@ -2581,17 +2634,18 @@ } }, "node_modules/vitest": { - "version": "4.1.8", + "version": "4.1.11", + "integrity": "sha512-fhACrNXUidIbGSBr5FlbuBkO7VWC1ZyLl0DO4CU2DrQoAPxX84Ysxs+HeGQpii5lZWV1Q4gBZTTu49mF+A6Edw==", "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", + "@vitest/expect": "4.1.11", + "@vitest/mocker": "4.1.11", + "@vitest/pretty-format": "4.1.11", + "@vitest/runner": "4.1.11", + "@vitest/snapshot": "4.1.11", + "@vitest/spy": "4.1.11", + "@vitest/utils": "4.1.11", "es-module-lexer": "^2.0.0", "expect-type": "^1.3.0", "magic-string": "^0.30.21", @@ -2619,12 +2673,12 @@ "@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", + "@vitest/browser-playwright": "4.1.11", + "@vitest/browser-preview": "4.1.11", + "@vitest/browser-webdriverio": "4.1.11", + "@vitest/coverage-istanbul": "4.1.11", + "@vitest/coverage-v8": "4.1.11", + "@vitest/ui": "4.1.11", "happy-dom": "*", "jsdom": "*", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" diff --git a/test/harness/package.json b/test/harness/package.json index 9b37dfb9d0..055121c48e 100644 --- a/test/harness/package.json +++ b/test/harness/package.json @@ -14,14 +14,17 @@ "node": "^20.19.0 || >=22.12.0" }, "devDependencies": { - "@modelcontextprotocol/sdk": "^1.26.0", + "@modelcontextprotocol/sdk": "^1.31.0", "@types/node": "^25.3.3", "@types/node-forge": "^1.3.14", "node-forge": "^1.4.0", "openai": "^6.17.0", "tsx": "^4.21.0", "typescript": "^5.9.3", - "vitest": "^4.0.18", + "vitest": "^4.1.11", "yaml": "^2.8.2" + }, + "overrides": { + "@hono/node-server": "^1.19.15" } } diff --git a/test/harness/replayingCapiProxy.test.ts b/test/harness/replayingCapiProxy.test.ts index cbd51c1b09..2d34b144f5 100644 --- a/test/harness/replayingCapiProxy.test.ts +++ b/test/harness/replayingCapiProxy.test.ts @@ -11,6 +11,7 @@ import type { } from "openai/resources/chat/completions"; import os from "os"; import path from "path"; +import { gzipSync } from "zlib"; import { afterEach, beforeEach, describe, expect, test, vi } from "vitest"; import yaml from "yaml"; import { @@ -148,6 +149,31 @@ describe("ReplayingCapiProxy", () => { } }); + test("accepts telemetry locally before replay configuration", async () => { + const proxy = new ReplayingCapiProxy("http://localhost"); + const address = await proxy.start(); + try { + const response = await fetch(`${address}/telemetry`, { + method: "POST", + headers: { + "content-type": "application/x-json-stream", + "content-encoding": "gzip", + }, + body: gzipSync('{"name":"fixture-event"}\n'), + }); + expect(response.status).toBe(204); + expect(await response.text()).toBe(""); + expect(proxy.exchanges).toMatchObject([ + { + request: { url: "/telemetry", method: "POST" }, + response: { statusCode: 204, body: "" }, + }, + ]); + } finally { + await proxy.stop(); + } + }); + test("does not write file when no chat completion exchanges", async () => { const outputPath = path.join(tempDir, "output.yaml"); const proxy = new ReplayingCapiProxy( @@ -2317,89 +2343,116 @@ Related files (use view tool to read): } }); - test("matches shell tool results with shell ID completion markers", 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":"echo ok"}', + test.each([ + { + toolName: "${shell}", + recorded: "ok\n", + actual: "ok\n", + }, + { + toolName: "${shell}", + recorded: "", + actual: "", + }, + { + toolName: "view", + recorded: "ok\n", + actual: "ok\n", + }, + { + toolName: "view", + recorded: "", + actual: "", + }, + ])( + "matches $toolName results containing shell markers: $actual", + async ({ toolName, recorded, actual }) => { + 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: toolName, + arguments: '{"command":"echo ok"}', + }, }, - }, - ], - }, - { - role: "tool", - tool_call_id: "toolcall_0", - content: "ok\n", - }, - { role: "assistant", content: "Done" }, - ], - }, - ], - } satisfies NormalizedData); - await writeFile(cachePath, cacheContent); + ], + }, + { + role: "tool", + tool_call_id: "toolcall_0", + content: recorded, + }, + { role: "assistant", content: "Done" }, + ], + }, + ], + } satisfies NormalizedData); + await writeFile(cachePath, cacheContent); - const proxy = new ReplayingCapiProxy( - "http://localhost:9999", - cachePath, - workDir, - ); - const proxyUrl = await proxy.start(); + const proxy = new ReplayingCapiProxy( + "http://localhost:9999", + cachePath, + workDir, + ); + const proxyUrl = await proxy.start(); - try { - const response = await makeRequest(proxyUrl, "/chat/completions", { - body: { - model: "test-model", - 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":"echo ok"}', + try { + const response = await makeRequest(proxyUrl, "/chat/completions", { + body: { + model: "test-model", + messages: [ + { role: "system", content: "System prompt" }, + { role: "user", content: "Run command" }, + { + role: "assistant", + tool_calls: [ + { + id: "runtime-call-id", + type: "function", + function: { + name: + toolName === "${shell}" + ? originalShellConfig.shellToolName + : toolName, + arguments: '{"command":"echo ok"}', + }, }, - }, - ], - }, - { - role: "tool", - tool_call_id: "runtime-call-id", - content: "ok\n", - }, - ], - }, - }); + ], + }, + { + role: "tool", + tool_call_id: "runtime-call-id", + content: actual, + }, + ], + }, + }); - expect(response.status).toBe(200); - expect( - (JSON.parse(response.body) as ChatCompletion).choices[0].message - .content, - ).toBe("Done"); - } finally { - await proxy.stop(); - } - }); + expect(response.status).toBe(200); + expect( + (JSON.parse(response.body) as ChatCompletion).choices[0].message + .content, + ).toBe("Done"); + } finally { + await proxy.stop(); + } + }, + ); test("matches semantically equivalent interrupted tool results", async () => { const originalShellConfig = diff --git a/test/harness/replayingCapiProxy.ts b/test/harness/replayingCapiProxy.ts index ad21f0d317..8496173e6a 100644 --- a/test/harness/replayingCapiProxy.ts +++ b/test/harness/replayingCapiProxy.ts @@ -352,6 +352,15 @@ export class ReplayingCapiProxy extends CapturingHttpProxy { return; } + if ( + options.requestOptions.path === "/telemetry" && + options.requestOptions.method === "POST" + ) { + options.onResponseStart(204, {}); + options.onResponseEnd(); + return; + } + if ( options.requestOptions.path === "/memory-api-config" && options.requestOptions.method === "POST" @@ -1820,8 +1829,17 @@ function coalesceMessages( // older CLI versions as well as to live requests. function normalizeStoredToolMessages(conversations: NormalizedConversation[]) { for (const conversation of conversations) { + const shellToolCallIds = new Set(); for (const message of conversation.messages) { + for (const toolCall of message.tool_calls ?? []) { + if (toolCall.function?.name === "${shell}") { + shellToolCallIds.add(toolCall.id); + } + } if (message.role === "tool" && typeof message.content === "string") { + if (message.tool_call_id && shellToolCallIds.has(message.tool_call_id)) { + message.content = normalizeShellExitMarkers(message.content); + } message.content = normalizeInterruptedToolResult(message.content); message.content = normalizeAvailableToolNames(message.content); message.content = normalizeBackgroundAgentStartMessage(message.content); @@ -1860,10 +1878,15 @@ function normalizeLargeOutputFilepaths(result: string): string { } function normalizeShellExitMarkers(result: string): string { - return result.replace( - /\r\n]+?\s+completed with exit code (-?\d+)>/g, - "", - ); + return result + .replace( + /\r\n]+?\s+completed with exit code (-?\d+)>/g, + "", + ) + .replace( + //g, + "", + ); } // The `gh` CLI emits different "not authenticated" help text depending on the diff --git a/test/harness/sdk-diagnostics.mjs b/test/harness/sdk-diagnostics.mjs index 817df78c39..829c8ed27d 100644 --- a/test/harness/sdk-diagnostics.mjs +++ b/test/harness/sdk-diagnostics.mjs @@ -46,15 +46,62 @@ const shellState = { }; const records = { "workflow availability decision": { available: "boolean" }, + "sdk transport teardown": { + connection: "count", + active_requests: "count", + driver_finished: "boolean", + phase: [ + "shutdown_writer", + "launcher", + "launcher_complete", + "driver", + "driver_complete", + "driver_failed", + "driver_timeout", + "driver_abort_complete", + "writer_shutdown_complete", + "peer_eof", + "notification_worker", + "notification_worker_complete", + "notification_worker_failed", + "notification_worker_timed_out", + "active_request_grace", + "handle_close", + "handle_close_complete", + "active_requests", + "active_requests_complete", + "active_requests_timed_out", + "flush", + "flush_complete", + "flush_cancelled", + "flush_timed_out", + "flush_unavailable", + "writer_join", + "writer_join_complete", + "complete", + ], + }, "sdk pending permission response": { ...presence, prepared_replay: "boolean", aborting: "boolean", - reason: ["prepared_replay", "completed_request", "unknown_request", "resolved_tool", "accepted"], + reason: [ + "prepared_replay", + "completed_request", + "unknown_request", + "resolved_tool", + "accepted", + ], }, "sdk pending tool response": { ...presence, - reason: ["completed_request", "unknown_request", "non_pending_request", "completed_tool", "accepted"], + reason: [ + "completed_request", + "unknown_request", + "non_pending_request", + "completed_tool", + "accepted", + ], }, "sdk pending permission delivery": { generation: "number", @@ -163,6 +210,105 @@ const records = { child_span_handle: "number", chat_span_handle: "number", }, + "sdk otel admission": { + phase: ["received", "no_tracker", "fenced", "pending", "submitted"], + event_sequence: "count", + policy_revision: "count", + authority_fenced: "boolean", + preparation_pending: "boolean", + policy_enabled: "boolean", + projected_event_count: "count", + submitted: "boolean", + }, + "sdk otel policy state": { + origin: [ + "host_prepare", + "host_commit", + "host_commit_initial", + "host_bind", + "host_rollback", + "explicit_fence", + "legacy_settled", + "native_pending", + "native_settled", + "native_apply", + "reserved_apply", + "direct_apply", + ], + kind: [ + "transition", + "restore", + "fence", + "apply_stale_source", + "apply_retry", + "apply_superseded", + "apply_settled", + ], + expected_some: "boolean", + expected_incarnation: "count", + expected_revision: "count", + previous_incarnation: "count", + previous_revision: "count", + current_incarnation: "count", + current_revision: "count", + previous_authority_fenced: "boolean", + authority_fenced: "boolean", + previous_preparation_pending: "boolean", + preparation_pending: "boolean", + previous_native_owned: "boolean", + native_owned: "boolean", + policy_enabled: "boolean", + returned_some: "boolean", + applied: "boolean", + build_succeeded: "boolean", + }, + "sdk otel core policy publication": { + origin: [ + "clear_account_policy", + "retained_device_update", + "retained_server_update", + "retained_signed_out_update", + "registry_rotation", + "signed_out_policy_clear", + "refresh_start", + "ingest_settlement", + "standalone_provider_rebind", + "standalone_provider_restore", + "construction_options_auth", + "manager_auth_changed", + "resolved_session_auth", + "hydrated_session_auth", + "set_credentials", + "clear_credentials", + "other_auth_mutation", + ], + kind: ["pending_publish", "pending_publish_returned"], + registry_generation: "count", + previous_auth_generation: "count", + auth_generation: "count", + auth_generation_changed: "boolean", + previous_server_pending: "boolean", + server_pending: "boolean", + policy_identity_present: "boolean", + source_recovery: "boolean", + clear_retained: "boolean", + composition_succeeded: "boolean", + refresh_generation: "count", + has_resolved_managed_settings: "boolean", + has_eligible_current_otel_policy: "boolean", + expected_auth_generation: "count", + }, + "sdk otel policy settlement": { + origin: ["standalone_commit"], + kind: ["reservation", "application"], + succeeded: "boolean", + reservation_some: "boolean", + committed_incarnation: "count", + committed_revision: "count", + expected_incarnation: "count", + expected_revision: "count", + applied: "boolean", + }, "sdk assistant message": { ...childParts, scope: ["parent", "child"], @@ -174,7 +320,12 @@ const records = { ...childParts, scope: ["parent", "child"], operation: ["chat"], - reason: [...childCloseReasons, "model_call_start", "assistant_turn_end", "parent_turn_close"], + reason: [ + ...childCloseReasons, + "model_call_start", + "assistant_turn_end", + "parent_turn_close", + ], status: ["unset", "error"], }, "sdk child completion": { @@ -230,7 +381,14 @@ const records = { }, "sdk shell wait": { ...shellState, - phase: ["interrupted", "stale_generation", "completion", "cancelled", "deadline", "completion_after_deadline"], + phase: [ + "interrupted", + "stale_generation", + "completion", + "cancelled", + "deadline", + "completion_after_deadline", + ], wait_generation: "number", current_wait_generation: "number", elapsed_ms: "elapsed", @@ -257,9 +415,11 @@ export function sanitizeDiagnosticLine(line) { ? rule.includes(value) : rule === "number" ? Number.isSafeInteger(value) - : rule === "elapsed" - ? Number.isFinite(value) && value >= 0 - : typeof value === rule; + : rule === "count" + ? Number.isSafeInteger(value) && value >= 0 + : rule === "elapsed" + ? Number.isFinite(value) && value >= 0 + : typeof value === rule; if (!valid) return undefined; output[key] = value; } @@ -285,7 +445,10 @@ export async function readDiagnosticTail(home) { } for (const file of files.sort()) { if (!file.endsWith(".log")) continue; - const lines = createInterface({ input: createReadStream(join(home, "logs", file)), crlfDelay: Infinity }); + const lines = createInterface({ + input: createReadStream(join(home, "logs", file)), + crlfDelay: Infinity, + }); for await (const line of lines) append(line); } return tail; diff --git a/test/harness/sdk-diagnostics.test.ts b/test/harness/sdk-diagnostics.test.ts index 53d372a284..9a57c8b8d9 100644 --- a/test/harness/sdk-diagnostics.test.ts +++ b/test/harness/sdk-diagnostics.test.ts @@ -11,6 +11,251 @@ import { readDiagnosticTail, sanitizeDiagnosticLine } from "./sdk-diagnostics.mj const prefix = "2026-10-05T12:00:00Z [DEBUG] [rust:sdk_diagnostics] "; describe("SDK failure diagnostics", () => { + it("projects model admission phases, authoritative sequences and policy metadata without content", () => { + const cases = [ + { phase: "received" }, + { phase: "no_tracker" }, + { + phase: "fenced", + event_sequence: 17, + policy_revision: 3, + authority_fenced: false, + preparation_pending: true, + policy_enabled: true, + }, + { phase: "pending", event_sequence: 17 }, + { phase: "submitted", event_sequence: 17, projected_event_count: 1, submitted: true }, + { phase: "submitted", projected_event_count: 0, submitted: false }, + ]; + for (const fields of cases) { + const safe = sanitizeDiagnosticLine( + `${prefix}sdk otel admission ${JSON.stringify({ + ...fields, + session_id: "PRIVATE_SESSION", + event_id: "PRIVATE_EVENT", + model: "PRIVATE_MODEL", + payload: { content: "PRIVATE_PAYLOAD" }, + error: "PRIVATE_ERROR", + path: "PRIVATE_PATH", + })}` + ); + expect(safe).toBeDefined(); + expect(JSON.parse(safe!.slice("sdk otel admission ".length))).toEqual(fields); + expect(safe).not.toContain("PRIVATE"); + } + for (const fields of [ + { phase: "PRIVATE_PHASE" }, + { event_sequence: -1 }, + { event_sequence: 0.5 }, + { event_sequence: Number.MAX_SAFE_INTEGER + 1 }, + { event_sequence: "PRIVATE_SEQUENCE" }, + { event_sequence: null }, + { policy_revision: -1 }, + { policy_revision: 0.5 }, + { projected_event_count: -1 }, + { projected_event_count: 0.5 }, + { authority_fenced: "PRIVATE_STATE" }, + { preparation_pending: null }, + { policy_enabled: "PRIVATE_STATE" }, + { submitted: "PRIVATE_STATE" }, + { session_id: "PRIVATE_SESSION" }, + ]) { + expect( + sanitizeDiagnosticLine(`${prefix}sdk otel admission ${JSON.stringify(fields)}`) + ).toBeUndefined(); + } + }); + + it.each([ + { + label: "native authority fence", + record: "sdk otel policy state", + fields: { + origin: "native_pending", + kind: "fence", + expected_some: false, + previous_incarnation: 2, + previous_revision: 10, + current_incarnation: 2, + current_revision: 11, + previous_authority_fenced: false, + authority_fenced: true, + previous_preparation_pending: true, + preparation_pending: true, + previous_native_owned: true, + native_owned: true, + policy_enabled: true, + returned_some: true, + }, + }, + { + label: "resident credential publication", + record: "sdk otel core policy publication", + fields: { + origin: "standalone_provider_rebind", + kind: "pending_publish_returned", + registry_generation: 1, + previous_auth_generation: 2, + auth_generation: 3, + auth_generation_changed: true, + previous_server_pending: false, + server_pending: true, + policy_identity_present: true, + source_recovery: false, + }, + }, + { + label: "superseded host reservation", + record: "sdk otel policy settlement", + fields: { + origin: "standalone_commit", + kind: "reservation", + succeeded: true, + reservation_some: false, + }, + }, + { + label: "successfully disabled application", + record: "sdk otel policy settlement", + fields: { + origin: "standalone_commit", + kind: "application", + expected_incarnation: 2, + expected_revision: 3, + succeeded: true, + applied: false, + }, + }, + ])("projects $label without credential or policy content", ({ record, fields }) => { + const safe = sanitizeDiagnosticLine( + `${prefix}${record} ${JSON.stringify({ + ...fields, + session_id: "PRIVATE_SESSION", + registration: "PRIVATE_REGISTRATION", + account: "PRIVATE_ACCOUNT", + policy: { endpoint: "PRIVATE_ENDPOINT", headers: { token: "PRIVATE_TOKEN" } }, + error: "PRIVATE_ERROR", + })}` + ); + expect(safe).toBeDefined(); + expect(JSON.parse(safe!.slice(record.length + 1))).toEqual(fields); + expect(safe).not.toContain("PRIVATE"); + }); + + it.each([ + ["sdk otel policy state", "current_revision", "authority_fenced"], + ["sdk otel core policy publication", "auth_generation", "server_pending"], + ["sdk otel policy settlement", "committed_revision", "reservation_some"], + ])("rejects unaudited or invalid %s fields", (record, count, flag) => { + for (const fields of [ + { origin: "PRIVATE_ORIGIN" }, + { kind: "PRIVATE_KIND" }, + ...[-1, 0.5, Number.MAX_SAFE_INTEGER + 1, "PRIVATE_COUNT", null].map((value) => ({ + [count]: value, + })), + { [flag]: "PRIVATE_STATE" }, + { [flag]: null }, + { session_id: "PRIVATE_SESSION" }, + ]) { + expect( + sanitizeDiagnosticLine(`${prefix}${record} ${JSON.stringify(fields)}`) + ).toBeUndefined(); + } + }); + + it("preserves policy attribution through the audited file collector", async () => { + const home = await mkdtemp(join(tmpdir(), "sdk-policy-diagnostics-")); + onTestFinished(() => rm(home, { recursive: true, force: true })); + await mkdir(join(home, "logs")); + await writeFile( + join(home, "logs", "policy.log"), + [ + `${prefix}sdk otel core policy publication {"origin":"standalone_provider_rebind","kind":"pending_publish_returned","auth_generation":3,"account":"PRIVATE"}`, + `${prefix}sdk otel policy state {"origin":"native_pending","kind":"fence","current_revision":11,"authority_fenced":true,"policy":"PRIVATE"}`, + `${prefix}sdk otel policy settlement {"origin":"standalone_commit","kind":"reservation","succeeded":true,"reservation_some":false,"session_id":"PRIVATE"}`, + ].join("\n") + ); + expect(await readDiagnosticTail(home)).toEqual([ + 'sdk otel core policy publication {"origin":"standalone_provider_rebind","kind":"pending_publish_returned","auth_generation":3}', + 'sdk otel policy state {"origin":"native_pending","kind":"fence","current_revision":11,"authority_fenced":true}', + 'sdk otel policy settlement {"origin":"standalone_commit","kind":"reservation","succeeded":true,"reservation_some":false}', + ]); + }); + + it("projects fixed transport teardown phases and bounded counts without SDK content", () => { + for (const phase of [ + "shutdown_writer", + "launcher", + "launcher_complete", + "driver", + "driver_complete", + "driver_failed", + "driver_timeout", + "driver_abort_complete", + "writer_shutdown_complete", + "peer_eof", + "notification_worker", + "notification_worker_complete", + "notification_worker_failed", + "notification_worker_timed_out", + "active_request_grace", + "handle_close", + "handle_close_complete", + "active_requests", + "active_requests_complete", + "active_requests_timed_out", + "flush", + "flush_complete", + "flush_cancelled", + "flush_timed_out", + "flush_unavailable", + "writer_join", + "writer_join_complete", + "complete", + ]) { + const safe = sanitizeDiagnosticLine( + `${prefix}sdk transport teardown ${JSON.stringify({ + connection: 4, + active_requests: 2, + driver_finished: false, + phase, + method: "PRIVATE_METHOD", + request: { token: "PRIVATE_TOKEN" }, + sdkLog: "PRIVATE_LOG", + error: "PRIVATE_ERROR", + })}` + ); + expect(safe).toBeDefined(); + expect(JSON.parse(safe!.slice("sdk transport teardown ".length))).toEqual({ + connection: 4, + active_requests: 2, + driver_finished: false, + phase, + }); + expect(safe).not.toContain("PRIVATE"); + } + for (const fields of [ + { phase: "PRIVATE_PHASE" }, + { active_requests: -1 }, + { active_requests: 0.5 }, + { active_requests: Number.MAX_SAFE_INTEGER + 1 }, + { active_requests: "PRIVATE_COUNT" }, + { connection: -1 }, + { connection: null }, + { driver_finished: "PRIVATE_STATE" }, + { method: "PRIVATE_METHOD" }, + ]) { + expect( + sanitizeDiagnosticLine(`${prefix}sdk transport teardown ${JSON.stringify(fields)}`) + ).toBeUndefined(); + } + expect( + sanitizeDiagnosticLine( + `${prefix}sdk transport teardown {"phase":"complete","active_requests":0}` + ) + ).toBe('sdk transport teardown {"active_requests":0,"phase":"complete"}'); + }); + it("projects only audited fields and never falls back to raw data", () => { expect( sanitizeDiagnosticLine( @@ -26,8 +271,8 @@ describe("SDK failure diagnostics", () => { payload: "PRIVATE_PROMPT", credential: "PRIVATE_CREDENTIAL", endpoint: "PRIVATE_ENDPOINT", - })}`, - ), + })}` + ) ).toBe('sdk pending tool response {"generation":9,"reason":"completed_request"}'); expect( sanitizeDiagnosticLine( @@ -40,10 +285,10 @@ describe("SDK failure diagnostics", () => { origin: "client_response", terminal_cancelled: false, requestId: "PRIVATE_REQUEST", - })}`, - ), + })}` + ) ).toBe( - 'sdk pending transition {"generation":9,"event_count":4,"kind":"permission","origin":"client_response","terminal_cancelled":false}', + 'sdk pending transition {"generation":9,"event_count":4,"kind":"permission","origin":"client_response","terminal_cancelled":false}' ); expect( sanitizeDiagnosticLine( @@ -56,9 +301,11 @@ describe("SDK failure diagnostics", () => { requestId: "PRIVATE_REQUEST", result: "PRIVATE_RESULT", error: "PRIVATE_ERROR", - })}`, - ), - ).toBe('sdk pending tool delivery {"generation":9,"origin":"client_response","accepted":true,"success":true,"failed":false}'); + })}` + ) + ).toBe( + 'sdk pending tool delivery {"generation":9,"origin":"client_response","accepted":true,"success":true,"failed":false}' + ); for (const line of [ `${prefix}PRIVATE_MESSAGE {"generation":9}`, `${prefix}sdk pending tool response {"reason":"PRIVATE_REASON"}`, @@ -86,8 +333,8 @@ describe("SDK failure diagnostics", () => { Array.from( { length: 150 }, (_, generation) => - `${prefix}sdk pending stale drain ${JSON.stringify({ generation, expected_generation: 1, secret: "PRIVATE" })}`, - ).join("\n"), + `${prefix}sdk pending stale drain ${JSON.stringify({ generation, expected_generation: 1, secret: "PRIVATE" })}` + ).join("\n") ); } await writeFile( @@ -96,13 +343,15 @@ describe("SDK failure diagnostics", () => { `${prefix}sdk detach {"phase":"received","connection":2,"dispatch":1,"content":"PRIVATE"}`, `${prefix}sdk detach {"phase":"dispatch_complete","connection":2,"dispatch":1,"failed":false,"elapsed_ms":4}`, '[sdk_diagnostics] sdk detach {"phase":"received","content":"PRIVATE"}', - ].join("\n"), + ].join("\n") ); const tail = await readDiagnosticTail(home); expect(tail).toHaveLength(200); expect(tail[0]).toContain('"generation":102'); expect(tail.at(-2)).toBe('sdk detach {"connection":2,"dispatch":1,"phase":"received"}'); - expect(tail.at(-1)).toBe('sdk detach {"connection":2,"dispatch":1,"elapsed_ms":4,"failed":false,"phase":"dispatch_complete"}'); + expect(tail.at(-1)).toBe( + 'sdk detach {"connection":2,"dispatch":1,"elapsed_ms":4,"failed":false,"phase":"dispatch_complete"}' + ); expect(tail.join("\n")).not.toContain("PRIVATE"); expect(tail.join("\n")).not.toContain(home); }); @@ -237,7 +486,7 @@ describe("SDK failure diagnostics", () => { span: { content: "PRIVATE_SPAN" }, sessionId: "PRIVATE_SESSION", childId: "PRIVATE_CHILD", - })}`, + })}` ); expect(safe).toBeDefined(); expect(JSON.parse(safe!.slice(message.length + 1))).toEqual(fields); @@ -250,7 +499,9 @@ describe("SDK failure diagnostics", () => { { phase: "PRIVATE_PHASE" }, { stream: "PRIVATE_STREAM" }, ]) { - expect(sanitizeDiagnosticLine(`${prefix}sdk shell reader ${JSON.stringify(fields)}`)).toBeUndefined(); + expect( + sanitizeDiagnosticLine(`${prefix}sdk shell reader ${JSON.stringify(fields)}`) + ).toBeUndefined(); } }); }); diff --git a/test/harness/server.ts b/test/harness/server.ts index 887a57178b..f190b2d2f5 100644 --- a/test/harness/server.ts +++ b/test/harness/server.ts @@ -6,11 +6,18 @@ import { ReplayingCapiProxy } from "./replayingCapiProxy"; import { ConnectProxy } from "./connectProxy"; import { createE2eRequestHandler } from "./mockHandlers"; +console.error( + `[SDK proxy startup] Server dependencies loaded at ${Math.round(performance.now())}ms`, +); + // Starts up an instance of the ReplayingCapiProxy server // The intention is for this to be usable in E2E tests across all languages const proxy = new ReplayingCapiProxy("https://api.githubcopilot.com"); const proxyUrl = await proxy.start(); +console.error( + `[SDK proxy startup] Replay server listening at ${Math.round(performance.now())}ms`, +); const blockedHosts: string[] = []; const unhandledRequests: string[] = []; @@ -39,6 +46,9 @@ const connectProxy = new ConnectProxy( }, ); await connectProxy.start(); +console.error( + `[SDK proxy startup] CONNECT server listening at ${Math.round(performance.now())}ms`, +); proxy.onStopRequested = async () => { if (blockedHosts.length || unhandledRequests.length) { diff --git a/test/harness/sessionExpiryMcpServer.test.mjs b/test/harness/sessionExpiryMcpServer.test.mjs index 3a24a708e3..091b218072 100644 --- a/test/harness/sessionExpiryMcpServer.test.mjs +++ b/test/harness/sessionExpiryMcpServer.test.mjs @@ -2,15 +2,19 @@ * Copyright (c) Microsoft Corporation. All rights reserved. *--------------------------------------------------------------------------------------------*/ +import { Agent, request } from "node:http"; import { test } from "vitest"; import { startSessionExpiryMcpServer } from "./test-mcp-session-expiry-server.mjs"; -test("negotiates legacy session semantics even when the client requests modern MCP", async ({ +test("legacy session expiry is independent of HTTP connection lifetime even for modern clients", async ({ expect, onTestFinished, }) => { const server = await startSessionExpiryMcpServer(); onTestFinished(() => server.close()); + const agent = new Agent({ keepAlive: true }); + onTestFinished(() => agent.destroy()); + const probeSockets = new Set(); const initialize = () => fetch(`${server.url}/mcp`, { @@ -29,29 +33,70 @@ test("negotiates legacy session semantics even when the client requests modern M }); const initialized = await initialize(); expect(initialized.status).toBe(200); + expect(initialized.headers.get("connection")).toBe("close"); expect((await initialized.json()).result.protocolVersion).toBe("2025-03-26"); const sessionId = initialized.headers.get("mcp-session-id"); expect(sessionId).toBeTruthy(); - const ping = () => - fetch(`${server.url}/mcp`, { - method: "POST", - headers: { - "content-type": "application/json", - "mcp-session-id": sessionId, - }, - body: JSON.stringify({ jsonrpc: "2.0", id: 2, method: "ping" }), + const ping = async () => { + const response = await new Promise((resolve, reject) => { + const req = request( + `${server.url}/mcp`, + { + agent, + method: "POST", + headers: { + "content-type": "application/json", + "mcp-session-id": sessionId, + }, + }, + (res) => { + const socket = res.socket; + let body = ""; + res.setEncoding("utf8"); + res.on("data", (chunk) => (body += chunk)); + res.on("error", reject); + res.on("end", () => + resolve({ status: res.statusCode, headers: res.headers, socket, body }) + ); + } + ); + req.on("error", reject); + req.end(JSON.stringify({ jsonrpc: "2.0", id: 2, method: "ping" })); }); + expect(response.headers.connection).toBe("close"); + expect(probeSockets.has(response.socket)).toBe(false); + probeSockets.add(response.socket); + return { status: response.status, body: JSON.parse(response.body) }; + }; const alive = await ping(); expect(alive.status).toBe(200); - expect(await alive.json()).toEqual({ jsonrpc: "2.0", id: 2, result: {} }); + expect(alive.body).toEqual({ jsonrpc: "2.0", id: 2, result: {} }); const expired = await fetch(`${server.url}/__expire`, { method: "POST" }); expect(expired.status).toBe(200); expect(await expired.json()).toEqual({ expired: 1 }); - const afterExpiry = await ping(); - expect(afterExpiry.status).toBe(404); - expect(await afterExpiry.json()).toEqual({ error: "session_expired" }); + for (let probe = 0; probe < 3; probe++) { + const afterExpiry = await ping(); + expect(afterExpiry.status).toBe(404); + expect(afterExpiry.body).toEqual({ error: "session_expired" }); + } + const observed = await fetch(`${server.url}/__stats`).then((response) => response.json()); + expect(observed.pings).toBe(1); + expect(observed.expiredRequests).toBe(3); + expect(observed.expiredRequestLog).toEqual( + Array.from({ length: 3 }, () => ({ method: "POST", elapsedMs: expect.any(Number) })), + ); + expect(observed.protocolVersions).toEqual(["2026-07-28"]); + expect(observed.recentRequests).toContainEqual( + expect.objectContaining({ + httpMethod: "POST", + sessionAttached: true, + expiredSession: true, + status: 404, + }), + ); + expect(JSON.stringify(observed)).not.toContain(sessionId); const reinitialized = await initialize(); expect(reinitialized.status).toBe(200); @@ -107,3 +152,40 @@ test("distinguishes answered expiry probes from unrelated traffic", async ({ expect(observed.status).toBe(200); expect(await observed.json()).toMatchObject({ activeSessions: 0, expiredSessionProbes: 1 }); }); + +test("bounds session-expiry diagnostics without retaining request payloads", async ({ expect, onTestFinished }) => { + const startingServer = startSessionExpiryMcpServer(); + onTestFinished(async () => (await startingServer).close()); + const server = await startingServer; + for (let i = 0; i < 40; i++) { + const response = await fetch(`${server.url}/mcp`, { + method: "POST", + headers: { "content-type": "application/json" }, + body: JSON.stringify({ + jsonrpc: "2.0", + id: `private-request-${i}`, + method: "initialize", + params: { + protocolVersion: "2025-03-26", + clientInfo: { name: "private-client", version: "1" }, + }, + }), + }); + expect(response.status).toBe(200); + await response.json(); + } + const observed = await fetch(`${server.url}/__stats`).then((response) => response.json()); + expect(observed.initializations).toBe(40); + expect(observed.recentRequests).toHaveLength(32); + for (const request of observed.recentRequests) { + expect(request).toEqual({ + elapsedMs: expect.any(Number), + httpMethod: "POST", + sessionAttached: false, + expiredSession: false, + rpcMethods: ["initialize"], + status: 200, + }); + } + expect(JSON.stringify(observed)).not.toContain("private-"); +}); diff --git a/test/harness/startup.mjs b/test/harness/startup.mjs new file mode 100644 index 0000000000..38f2540b57 --- /dev/null +++ b/test/harness/startup.mjs @@ -0,0 +1,12 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +console.error( + `[SDK proxy startup] Node entered at ${Math.round(performance.now())}ms`, +); +await import("tsx"); +console.error( + `[SDK proxy startup] TypeScript loader ready at ${Math.round(performance.now())}ms`, +); +await import("./server.ts"); diff --git a/test/harness/startup.test.ts b/test/harness/startup.test.ts new file mode 100644 index 0000000000..d3ada9ee3b --- /dev/null +++ b/test/harness/startup.test.ts @@ -0,0 +1,73 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +import { execFile } from "node:child_process"; +import { mkdtemp, rm, writeFile } from "node:fs/promises"; +import os from "node:os"; +import path from "node:path"; +import { fileURLToPath, pathToFileURL } from "node:url"; +import { promisify } from "node:util"; +import { test } from "vitest"; + +const execute = promisify(execFile); +const bootstrap = fileURLToPath(new URL("./startup.mjs", import.meta.url)); + +test.for(["tsx", "./server.ts"])( + "records the last startup phase before import %s fails", + async (failedImport, { expect, onTestFinished, signal }) => { + const creatingDirectory = mkdtemp( + path.join(os.tmpdir(), "sdk-proxy-startup-"), + ); + let executing: ReturnType | undefined; + onTestFinished(async () => { + const [created] = await Promise.allSettled([creatingDirectory]); + try { + if (executing) await Promise.allSettled([executing]); + } finally { + if (created.status === "fulfilled") { + await rm(created.value, { recursive: true, force: true }); + } + } + }); + const directory = await creatingDirectory; + signal.throwIfAborted(); + const loader = path.join(directory, "loader.mjs"); + const registration = path.join(directory, "register.mjs"); + await writeFile( + loader, + `export function resolve(specifier, context, nextResolve) { + if (specifier === ${JSON.stringify(failedImport)}) throw new Error("CONTROLLED_IMPORT_FAILURE"); + if (specifier === "tsx") return { url: "data:text/javascript,export%20%7B%7D", shortCircuit: true }; + return nextResolve(specifier, context); + }`, + ); + await writeFile( + registration, + `import { register } from "node:module"; + register(${JSON.stringify(pathToFileURL(loader).href)}, import.meta.url);`, + ); + + executing = execute( + process.execPath, + ["--import", pathToFileURL(registration).href, bootstrap], + { + signal, + env: { ...process.env, NODE_OPTIONS: "" }, + }, + ); + await expect(executing).rejects.toMatchObject({ + code: 1, + stdout: "", + stderr: expect.stringMatching( + /\[SDK proxy startup\] Node entered.*CONTROLLED_IMPORT_FAILURE/su, + ), + }); + await expect(executing).rejects.toMatchObject({ + stderr: + failedImport === "tsx" + ? expect.not.stringContaining("TypeScript loader ready") + : expect.stringContaining("TypeScript loader ready"), + }); + }, +); diff --git a/test/harness/stdio-shutdown-runtime.cjs b/test/harness/stdio-shutdown-runtime.cjs index 154537a3c0..f3230a041d 100644 --- a/test/harness/stdio-shutdown-runtime.cjs +++ b/test/harness/stdio-shutdown-runtime.cjs @@ -9,13 +9,23 @@ const { dirname } = require("node:path"); const [marker, mode, pidFile] = process.argv.slice(2); const useTcp = process.argv.includes("--fixture-tcp"); -if (!marker || !pidFile || !["stop", "dispose", "force", "fallback", "start-failure", "shutdown-error"].includes(mode)) { +if (!marker || !pidFile || !["stop", "dispose", "force", "fallback", "start-failure", "shutdown-error", "connect-stall", "port-stall"].includes(mode)) { throw new Error("Expected cleanup marker, shutdown mode, and PID file arguments"); } fs.writeFileSync(pidFile, String(process.pid)); -if (mode === "fallback" || mode === "start-failure") { +if (mode === "fallback" || mode === "start-failure" || mode === "connect-stall" || mode === "port-stall") { setInterval(() => {}, 1000); } +if (process.argv.includes("--fixture-descendant")) { + const child = require("node:child_process").spawn( + process.execPath, + ["-e", "setInterval(() => {}, 1000)"], + { stdio: "ignore" }, + ); + child.on("error", (error) => { throw error; }); + fs.writeFileSync(`${pidFile}.descendant`, String(child.pid)); + child.unref(); +} let buffered = Buffer.alloc(0); let shutdownRequested = false; @@ -35,6 +45,7 @@ const handleData = (chunk, output) => { let error; switch (message.method) { case "connect": + if (mode === "connect-stall") continue; result = { ok: true, protocolVersion: mode === "start-failure" ? -1 : 3, @@ -42,7 +53,7 @@ const handleData = (chunk, output) => { }; break; case "ping": - result = { message: message.params?.message, timestamp: Date.now(), protocolVersion: 3 }; + result = { message: message.params?.message, timestamp: new Date().toISOString(), protocolVersion: 3 }; break; case "runtime.shutdown": shutdownRequested = true; @@ -68,7 +79,7 @@ if (useTcp) { socket.on("data", (chunk) => handleData(chunk, socket)); }); server.listen(0, "127.0.0.1", () => { - console.log(`listening on port ${server.address().port}`); + if (mode !== "port-stall") console.log(`listening on port ${server.address().port}`); }); } else { process.stdin.on("data", (chunk) => handleData(chunk, process.stdout)); diff --git a/test/harness/test-mcp-computer-use-oauth-server.mjs b/test/harness/test-mcp-computer-use-oauth-server.mjs new file mode 100644 index 0000000000..2b8dc3c82f --- /dev/null +++ b/test/harness/test-mcp-computer-use-oauth-server.mjs @@ -0,0 +1,173 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +import http from "node:http"; + +export async function startComputerUseOAuthServer({ + deferRegistration = false, + deferInitialChallenge = false, + deferAuthorizationMetadata = false, + preconfiguredClientId = "", +} = {}) { + let clientId = preconfiguredClientId; + let releaseRegistration = () => {}; + const registration = deferRegistration + ? new Promise((resolve) => { + releaseRegistration = resolve; + }) + : Promise.resolve(); + let releaseInitialChallenge = () => {}; + const initialChallenge = deferInitialChallenge + ? new Promise((resolve) => { + releaseInitialChallenge = resolve; + }) + : Promise.resolve(); + let releaseAuthorizationMetadata = () => {}; + const authorizationMetadata = deferAuthorizationMetadata + ? new Promise((resolve) => { + releaseAuthorizationMetadata = resolve; + }) + : Promise.resolve(); + const requests = []; + const token = "computer-use-isolation-token"; + const server = http.createServer(async (request, response) => { + const url = new URL(request.url ?? "/", `http://${request.headers.host}`); + const chunks = []; + for await (const chunk of request) chunks.push(chunk); + const body = Buffer.concat(chunks).toString("utf8"); + requests.push({ + method: request.method, + path: url.pathname, + authorization: request.headers.authorization ?? null, + body, + }); + const respond = (status, value, headers = {}) => { + response.writeHead(status, { "content-type": "application/json", ...headers }); + response.end(JSON.stringify(value)); + }; + if (request.method === "GET" && url.pathname === "/.well-known/oauth-protected-resource") { + respond(200, { + resource: `${url.origin}/mcp`, + authorization_servers: [url.origin], + scopes_supported: ["mcp.read"], + bearer_methods_supported: ["header"], + }); + return; + } + if (request.method === "GET" && url.pathname === "/.well-known/oauth-authorization-server") { + await authorizationMetadata; + respond(200, { + issuer: url.origin, + authorization_endpoint: `${url.origin}/authorize`, + token_endpoint: `${url.origin}/token`, + registration_endpoint: `${url.origin}/register`, + response_types_supported: ["code"], + grant_types_supported: ["authorization_code"], + }); + return; + } + if (request.method === "POST" && url.pathname === "/register") { + await registration; + clientId = "registered-client"; + respond(201, { client_id: clientId, client_id_issued_at: Math.floor(Date.now() / 1000) }); + return; + } + if (request.method === "POST" && url.pathname === "/token") { + const form = new URLSearchParams(body); + if (!clientId || form.get("client_id") !== clientId) { + respond(400, { error: "invalid_client" }); + return; + } + if ( + form.get("grant_type") !== "authorization_code" || + form.get("code") !== "accepted-code" || + !form.get("code_verifier") + ) { + respond(400, { error: "invalid_grant" }); + return; + } + respond(200, { access_token: token, token_type: "Bearer", expires_in: 3600 }); + return; + } + if (url.pathname !== "/mcp") { + respond(404, { error: "not_found" }); + return; + } + if (request.headers.authorization !== `Bearer ${token}`) { + await initialChallenge; + respond( + 401, + { error: "unauthorized" }, + { + "www-authenticate": `Bearer resource_metadata="${url.origin}/.well-known/oauth-protected-resource", scope="mcp.read"`, + }, + ); + return; + } + if (request.method !== "POST") { + respond(405, { error: "method_not_allowed" }); + return; + } + let message; + try { + message = JSON.parse(body); + } catch { + respond(400, { error: "invalid_json" }); + return; + } + if (message.id === undefined) { + response.writeHead(202); + response.end(); + return; + } + let result; + if (message.method === "initialize") { + result = { + protocolVersion: "2025-03-26", + capabilities: { tools: {} }, + serverInfo: { name: "computer-use-oauth-isolation", version: "1.0.0" }, + }; + } else if (message.method === "tools/list") { + result = { + tools: [ + { + name: "whoami", + description: "Reports an authenticated connection", + inputSchema: { type: "object", properties: {}, additionalProperties: false }, + }, + ], + }; + } else if (message.method === "ping") { + result = {}; + } else { + respond(200, { jsonrpc: "2.0", id: message.id, error: { code: -32601, message: "Method not found" } }); + return; + } + respond(200, { jsonrpc: "2.0", id: message.id, result }, { "mcp-session-id": "computer-use-oauth-isolation" }); + }); + await new Promise((resolve, reject) => { + server.once("error", reject); + server.listen(0, "127.0.0.1", () => { + server.off("error", reject); + resolve(); + }); + }); + const address = server.address(); + if (!address || typeof address === "string") throw new Error("Expected TCP server address"); + return { + url: `http://127.0.0.1:${address.port}`, + requests, + releaseRegistration, + releaseInitialChallenge, + releaseAuthorizationMetadata, + close: () => + new Promise((resolve, reject) => { + releaseRegistration(); + releaseInitialChallenge(); + releaseAuthorizationMetadata(); + server.close((error) => (error ? reject(error) : resolve())); + server.closeAllConnections(); + }), + }; +} diff --git a/test/harness/test-mcp-server.mjs b/test/harness/test-mcp-server.mjs index dfc791e127..a1c3609767 100644 --- a/test/harness/test-mcp-server.mjs +++ b/test/harness/test-mcp-server.mjs @@ -33,6 +33,7 @@ const startupMarkerPath = getArgument("--startup-marker"); const diagnosticStderr = getArgument("--diagnostic-stderr"); const serverName = getArgument("--server-name") ?? "env-echo"; const toolName = getArgument("--tool-name") ?? "get_env"; +const toolDescriptionFile = getArgument("--tool-description-file"); const server = new McpServer({ name: serverName, version: "1.0.0" }); const fixtures = JSON.parse(await readFile(new URL("./mcp-prompt-fixtures.json", import.meta.url), "utf8")); // Fixtures contain SDK extension bags; MCP sends those entries as ordinary object fields. @@ -98,7 +99,9 @@ server.server.setRequestHandler(GetPromptRequestSchema, async ({ params }) => { server.tool( toolName, - "Returns the value of the specified environment variable.", + toolDescriptionFile + ? await readFile(toolDescriptionFile, "utf8") + : "Returns the value of the specified environment variable.", { name: z.string().describe("Environment variable name") }, async ({ name }) => ({ content: [{ type: "text", text: process.env[name] ?? "" }], diff --git a/test/harness/test-mcp-session-expiry-server.mjs b/test/harness/test-mcp-session-expiry-server.mjs index a28343b151..4751be0bfd 100644 --- a/test/harness/test-mcp-session-expiry-server.mjs +++ b/test/harness/test-mcp-session-expiry-server.mjs @@ -16,6 +16,8 @@ * server-side idle expiry is observed by a real Streamable HTTP client. * `GET /__stats` reports the counters tests assert on, including answered * expired-session probes so recovery assertions need not budget the idle delay. + * HTTP connections close after each response so socket idle retirement cannot + * interfere with the independently managed MCP session lifetime. */ import http from "node:http"; @@ -24,6 +26,7 @@ import { randomUUID } from "node:crypto"; import { fileURLToPath } from "node:url"; const PROTOCOL_VERSION = "2025-03-26"; +const MAX_RECENT_REQUESTS = 32; export async function startSessionExpiryMcpServer({ host = "127.0.0.1", port = 0 } = {}) { const activeSessions = new Set(); @@ -39,12 +42,16 @@ export async function startSessionExpiryMcpServer({ host = "127.0.0.1", port = 0 protocolVersions: [], expiredSessionProbes: 0, }; + const recentRequests = []; + const startedAt = performance.now(); const server = http.createServer(async (req, res) => { + // MCP session expiry is independent of TCP keep-alive expiry. + res.setHeader("connection", "close"); const url = new URL(req.url ?? "/", `http://${req.headers.host ?? `${host}:${port}`}`); if (req.method === "GET" && url.pathname === "/__stats") { - respondJson(res, 200, { ...stats, activeSessions: activeSessions.size }); + respondJson(res, 200, { ...stats, activeSessions: activeSessions.size, recentRequests }); return; } @@ -64,7 +71,21 @@ export async function startSessionExpiryMcpServer({ host = "127.0.0.1", port = 0 } const sessionId = req.headers["mcp-session-id"]; - if (typeof sessionId === "string" && expiredSessions.has(sessionId)) { + const expiredSession = typeof sessionId === "string" && expiredSessions.has(sessionId); + const observation = { + elapsedMs: Math.round(performance.now() - startedAt), + httpMethod: req.method, + sessionAttached: typeof sessionId === "string", + expiredSession, + rpcMethods: [], + status: null, + }; + recentRequests.push(observation); + if (recentRequests.length > MAX_RECENT_REQUESTS) recentRequests.shift(); + res.once("finish", () => { + observation.status = res.statusCode; + }); + if (expiredSession) { stats.expiredRequests++; if (stats.expiredRequestLog.length < 8) { stats.expiredRequestLog.push({ @@ -99,6 +120,10 @@ export async function startSessionExpiryMcpServer({ host = "127.0.0.1", port = 0 } const messages = Array.isArray(parsedBody.value) ? parsedBody.value : [parsedBody.value]; + observation.rpcMethods = messages + .map((message) => message?.method) + .filter((method) => typeof method === "string") + .slice(0, 4); const isInitialize = messages.some((message) => message?.method === "initialize"); let responseSessionId = typeof sessionId === "string" ? sessionId : undefined; 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 ba9e2c5cea..ea3638ee21 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 @@ -20,6 +20,8 @@ conversations: You are now in interactive mode. Start implementing the plan now, in this same response. Approving the plan is - your go-signal, so do not stop to ask whether to proceed or wait for another message. + your go-signal, so do not stop to ask whether to proceed or wait for another message. Do NOT call + task_complete or claim the task is done until you have actually implemented the plan — approving the plan is + not the same as completing it. - role: assistant content: The greeting file implementation plan was approved. diff --git a/test/snapshots/objective_lifecycle/reduces_goal_completion_before_idle_and_pauses_a_goal_when_leaving_autopilot.yaml b/test/snapshots/objective_lifecycle/reduces_goal_completion_before_idle_and_pauses_a_goal_when_leaving_autopilot.yaml new file mode 100644 index 0000000000..03c6847d02 --- /dev/null +++ b/test/snapshots/objective_lifecycle/reduces_goal_completion_before_idle_and_pauses_a_goal_when_leaving_autopilot.yaml @@ -0,0 +1,32 @@ +models: + - claude-sonnet-5 +conversations: + - messages: + - role: system + content: ${system} + - role: user + content: >- + The user set this explicit autopilot objective with /autopilot: + + + Call task_complete immediately with summary 'The release is ready.' Do not call other tools or perform any + work. + + + Work autonomously toward this objective in clear checkpoints. + + Before acting, identify the success condition and the validation loop needed to prove progress. + + Keep making scoped progress until the objective is fully achieved, blocked by a real issue, or the user + changes the objective. + + Only call task_complete when the objective is fully achieved and verified. + + Do not call task_complete if more implementation, validation, or cleanup remains. + - role: assistant + tool_calls: + - id: toolcall_0 + type: function + function: + name: task_complete + arguments: '{"summary":"The release is ready."}' diff --git a/test/snapshots/pending_work_resume/should_continue_an_approved_permission_after_abandoning_an_earlier_pending_permission.yaml b/test/snapshots/pending_work_resume/should_continue_an_approved_permission_after_abandoning_an_earlier_pending_permission.yaml new file mode 100644 index 0000000000..dac85d274f --- /dev/null +++ b/test/snapshots/pending_work_resume/should_continue_an_approved_permission_after_abandoning_an_earlier_pending_permission.yaml @@ -0,0 +1,58 @@ +models: + - claude-sonnet-5 +conversations: + - messages: + - role: system + content: ${system} + - role: user + content: Use resume_permission_tool with value 'alpha', then reply with the result. + - role: assistant + tool_calls: + - id: toolcall_0 + type: function + function: + name: resume_permission_tool + arguments: '{"value":"alpha"}' + - role: tool + tool_call_id: toolcall_0 + content: Tool execution was interrupted before a result was recorded. Its effects may be absent, partial, or already + complete, and other tools may have completed successfully. Check the current state before retrying; do not + assume success or replay this operation blindly. + - role: user + content: Use resume_permission_tool with value 'beta', then reply with the result. + - role: assistant + tool_calls: + - id: toolcall_1 + type: function + function: + name: resume_permission_tool + arguments: '{"value":"beta"}' + - messages: + - role: system + content: ${system} + - role: user + content: Use resume_permission_tool with value 'alpha', then reply with the result. + - role: assistant + tool_calls: + - id: toolcall_0 + type: function + function: + name: resume_permission_tool + arguments: '{"value":"alpha"}' + - role: tool + tool_call_id: toolcall_0 + content: The execution of this tool, or a previous tool was interrupted. + - role: user + content: Use resume_permission_tool with value 'beta', then reply with the result. + - role: assistant + tool_calls: + - id: toolcall_1 + type: function + function: + name: resume_permission_tool + arguments: '{"value":"beta"}' + - role: tool + tool_call_id: toolcall_1 + content: PERMISSION_RESUMED_BETA + - role: assistant + content: "Result: PERMISSION_RESUMED_BETA" 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 d8ffe5df61..3393a1ce5a 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 @@ -15,7 +15,9 @@ conversations: arguments: '{"value":"beta"}' - role: tool tool_call_id: toolcall_0 - content: The execution of this tool, or a previous tool was interrupted. + content: Tool execution was interrupted before a result was recorded. Its effects may be absent, partial, or already + complete, and other tools may have completed successfully. Check the current state before retrying; do not + assume success or replay this operation blindly. - role: user content: "Reply with exactly: COLD_RESUMED_FOLLOWUP" - role: assistant diff --git a/test/snapshots/pending_work_resume/should_preserve_a_completed_siblings_result_on_cold_resume.yaml b/test/snapshots/pending_work_resume/should_preserve_a_completed_siblings_result_on_cold_resume.yaml index 92b31e143f..48795ca386 100644 --- a/test/snapshots/pending_work_resume/should_preserve_a_completed_siblings_result_on_cold_resume.yaml +++ b/test/snapshots/pending_work_resume/should_preserve_a_completed_siblings_result_on_cold_resume.yaml @@ -1,6 +1,25 @@ models: - claude-sonnet-5 conversations: + - messages: + - role: system + content: ${system} + - role: user + content: Call pending_lookup_a with value 'alpha' and pending_lookup_b with value 'beta', then reply with both results. + - role: assistant + tool_calls: + - id: toolcall_0 + type: function + function: + name: pending_lookup_a + arguments: '{"value":"alpha"}' + - role: assistant + tool_calls: + - id: toolcall_1 + type: function + function: + name: pending_lookup_b + arguments: '{"value":"beta"}' - messages: - role: system content: ${system} @@ -23,8 +42,11 @@ conversations: content: PARALLEL_A_ALPHA - role: tool tool_call_id: toolcall_1 - content: The execution of this tool, or a previous tool was interrupted. + content: Tool execution was interrupted before a result was recorded. Its effects may be absent, partial, or already + complete, and other tools may have completed successfully. Check the current state before retrying; do not + assume success or replay this operation blindly. - role: user - content: "Use the completed lookup result and report it without re-running pending_lookup_a. Reply with exactly: COLD_RESUMED_A_RETAINED" + content: "Use the completed lookup result and report it without re-running pending_lookup_a. Reply with exactly: + COLD_RESUMED_A_RETAINED" - role: assistant content: COLD_RESUMED_A_RETAINED diff --git a/test/snapshots/permissions/should_handle_async_permission_handler_dotnet.yaml b/test/snapshots/permissions/should_handle_async_permission_handler_dotnet.yaml new file mode 100644 index 0000000000..08ee2fa0f2 --- /dev/null +++ b/test/snapshots/permissions/should_handle_async_permission_handler_dotnet.yaml @@ -0,0 +1,20 @@ +models: + - claude-sonnet-5 +conversations: + - messages: + - role: system + content: ${system} + - role: user + content: Call async_permission_echo once and reply with only its returned text. + - role: assistant + tool_calls: + - id: toolcall_0 + type: function + function: + name: async_permission_echo + arguments: "{}" + - role: tool + tool_call_id: toolcall_0 + content: DOTNET_ASYNC_PERMISSION_RESULT + - role: assistant + content: DOTNET_ASYNC_PERMISSION_RESULT diff --git a/test/snapshots/plan_fleet_approval/should_deliver_fleet_instructions_with_the_approved_plan_tool_result.yaml b/test/snapshots/plan_fleet_approval/should_deliver_fleet_instructions_with_the_approved_plan_tool_result.yaml index 25525331ae..bae47c6707 100644 --- a/test/snapshots/plan_fleet_approval/should_deliver_fleet_instructions_with_the_approved_plan_tool_result.yaml +++ b/test/snapshots/plan_fleet_approval/should_deliver_fleet_instructions_with_the_approved_plan_tool_result.yaml @@ -22,8 +22,10 @@ conversations: Plan approved! Exited plan mode. - You are now in autopilot mode with fleet enabled. Proceed with implementing the plan in fleet mode by - following the fleet-mode instructions below. + You are now in autopilot mode with fleet enabled. Proceed with implementing the plan now in fleet mode by + following the fleet-mode instructions below. Do NOT call task_complete or report the task as finished until + the plan has genuinely been implemented and verified. Creating or approving the plan does not count as + completing the task. diff --git a/test/snapshots/rpc_session_state/should_read_the_reasoning_summaries_policy_for_the_current_model.yaml b/test/snapshots/rpc_session_state/should_read_the_reasoning_summaries_policy_for_the_current_model.yaml new file mode 100644 index 0000000000..0c6b353c19 --- /dev/null +++ b/test/snapshots/rpc_session_state/should_read_the_reasoning_summaries_policy_for_the_current_model.yaml @@ -0,0 +1,3 @@ +models: + - claude-sonnet-5 +conversations: [] diff --git a/test/snapshots/rpc_tasks_and_handlers/should_emit_session_idle_after_cancelling_a_running_attached_shell.yaml b/test/snapshots/rpc_tasks_and_handlers/should_emit_session_idle_after_cancelling_a_running_attached_shell.yaml new file mode 100644 index 0000000000..d22c9aefea --- /dev/null +++ b/test/snapshots/rpc_tasks_and_handlers/should_emit_session_idle_after_cancelling_a_running_attached_shell.yaml @@ -0,0 +1,21 @@ +models: + - claude-sonnet-5 +conversations: + - messages: + - role: system + content: ${system} + - role: user + content: Use your shell tool to run `sleep 600` in async mode without detaching. Do not read its output or wait for it. + Then reply with exactly SHELL_STARTED. + - role: assistant + tool_calls: + - id: toolcall_0 + type: function + function: + name: ${shell} + arguments: '{"command":"sleep 600","description":"Sleep for 600 seconds","mode":"async"}' + - role: tool + tool_call_id: toolcall_0 + content: "" + - role: assistant + content: SHELL_STARTED diff --git a/test/snapshots/rpc_tasks_and_handlers/should_run_a_queued_message_after_cancelling_a_running_attached_shell.yaml b/test/snapshots/rpc_tasks_and_handlers/should_run_a_queued_message_after_cancelling_a_running_attached_shell.yaml new file mode 100644 index 0000000000..d2dbce4e77 --- /dev/null +++ b/test/snapshots/rpc_tasks_and_handlers/should_run_a_queued_message_after_cancelling_a_running_attached_shell.yaml @@ -0,0 +1,25 @@ +models: + - claude-sonnet-5 +conversations: + - messages: + - role: system + content: ${system} + - role: user + content: Use your shell tool to run `sleep 600` in async mode without detaching. Do not read its output or wait for it. + Then reply with exactly SHELL_STARTED. + - role: assistant + tool_calls: + - id: toolcall_0 + type: function + function: + name: ${shell} + arguments: '{"command":"sleep 600","description":"Sleep for 600 seconds","mode":"async","initial_wait":10}' + - role: tool + tool_call_id: toolcall_0 + content: "" + - role: assistant + content: SHELL_STARTED + - role: user + content: Reply with exactly QUEUED_DONE. + - role: assistant + content: QUEUED_DONE diff --git a/test/snapshots/scenario_testing_callbacks/should_approve_scenario_exit_plan_with_full_callback_and_event_state.yaml b/test/snapshots/scenario_testing_callbacks/should_approve_scenario_exit_plan_with_full_callback_and_event_state.yaml index 11a5eb48c6..16e52d6fed 100644 --- a/test/snapshots/scenario_testing_callbacks/should_approve_scenario_exit_plan_with_full_callback_and_event_state.yaml +++ b/test/snapshots/scenario_testing_callbacks/should_approve_scenario_exit_plan_with_full_callback_and_event_state.yaml @@ -21,6 +21,8 @@ conversations: You are now in interactive mode. Start implementing the plan now, in this same response. Approving the plan is - your go-signal, so do not stop to ask whether to proceed or wait for another message. + your go-signal, so do not stop to ask whether to proceed or wait for another message. Do NOT call + task_complete or claim the task is done until you have actually implemented the plan — approving the plan is + not the same as completing it. - role: assistant content: The scenario client plan was approved. diff --git a/test/snapshots/skill_provider/should_preserve_skill_prompt_bytes_across_cold_resume_with_reordered_discovery.yaml b/test/snapshots/skill_provider/should_preserve_skill_prompt_bytes_across_cold_resume_with_reordered_discovery.yaml new file mode 100644 index 0000000000..72910c73cd --- /dev/null +++ b/test/snapshots/skill_provider/should_preserve_skill_prompt_bytes_across_cold_resume_with_reordered_discovery.yaml @@ -0,0 +1,14 @@ +models: + - claude-sonnet-5 +conversations: + - messages: + - role: system + content: ${system} + - role: user + content: Without using tools, reply with exactly SKILL_ORDER_INITIAL_OK. + - role: assistant + content: SKILL_ORDER_INITIAL_OK + - role: user + content: Without using tools, reply with exactly SKILL_ORDER_RESUMED_OK. + - role: assistant + content: SKILL_ORDER_RESUMED_OK