From cc0438d66e3e68c333537cb935d9425d4e4ed8d5 Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Fri, 21 Aug 2026 09:31:03 -0400 Subject: [PATCH 01/32] Normalize interrupted shell replay results (#2370) Treat runtime shell-context reconfiguration as the same semantic interruption already represented by abort cassettes, while retaining strict matching for unrelated tool failures. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- test/harness/replayingCapiProxy.test.ts | 135 +++++++++++++++++++++++- test/harness/replayingCapiProxy.ts | 2 +- 2 files changed, 132 insertions(+), 5 deletions(-) diff --git a/test/harness/replayingCapiProxy.test.ts b/test/harness/replayingCapiProxy.test.ts index 59f8fc6f17..245b035c5c 100644 --- a/test/harness/replayingCapiProxy.test.ts +++ b/test/harness/replayingCapiProxy.test.ts @@ -546,7 +546,7 @@ Always include PINEAPPLE_COCONUT_42. expect(toolMessage?.content).toBe("Tool 'report_intent' does not exist."); }); - test("normalizes aborted tool execution results", async () => { + test("normalizes interrupted tool execution results", async () => { const requestBody = JSON.stringify({ messages: [ { role: "user", content: "Run a slow analysis" }, @@ -561,6 +561,14 @@ Always include PINEAPPLE_COCONUT_42. arguments: '{"value":"test_abort"}', }, }, + { + id: "tc2", + type: "function", + function: { + name: "powershell", + arguments: '{"command":"sleep 100"}', + }, + }, ], }, { @@ -569,6 +577,11 @@ Always include PINEAPPLE_COCONUT_42. content: 'Failed to execute `slow_analysis` tool with arguments: {"value":"test_abort"} due to error: Error: Session aborted', }, + { + role: "tool", + tool_call_id: "tc2", + content: "", + }, ], }); const responseBody = JSON.stringify({ @@ -580,12 +593,13 @@ Always include PINEAPPLE_COCONUT_42. ]); const result = await readYamlOutput(outputPath); - const toolMessage = result.conversations[0].messages.find( + const toolMessages = result.conversations[0].messages.filter( (m) => m.role === "tool", ); - expect(toolMessage?.content).toBe( + expect(toolMessages.map((message) => message.content)).toEqual([ "The execution of this tool, or a previous tool was interrupted.", - ); + "The execution of this tool, or a previous tool was interrupted.", + ]); }); test("normalizes background agent IDs and removes runtime advisories", async () => { @@ -945,6 +959,119 @@ Always include PINEAPPLE_COCONUT_42. } }); + test("matches semantically equivalent interrupted shell results", async () => { + const originalShellConfig = + process.platform === "win32" + ? ShellConfig.powerShell + : ShellConfig.bash; + const cachePath = path.join(tempDir, "cache.yaml"); + const cacheContent = yaml.stringify({ + models: ["test-model"], + conversations: [ + { + messages: [ + { role: "system", content: "${system}" }, + { role: "user", content: "Run command" }, + { + role: "assistant", + tool_calls: [ + { + id: "toolcall_0", + type: "function", + function: { + name: "${shell}", + arguments: '{"command":"sleep 100"}', + }, + }, + ], + }, + { + role: "tool", + tool_call_id: "toolcall_0", + content: + "The execution of this tool, or a previous tool was interrupted.", + }, + { role: "assistant", content: "Ready for another request." }, + ], + }, + ], + } satisfies NormalizedData); + await writeFile(cachePath, cacheContent); + + const proxy = new ReplayingCapiProxy( + "http://localhost:9999", + cachePath, + workDir, + ); + const proxyUrl = await proxy.start(); + + try { + const messages = [ + { role: "system", content: "System prompt" }, + { role: "user", content: "Run command" }, + { + role: "assistant", + tool_calls: [ + { + id: "runtime-call-id", + type: "function", + function: { + name: originalShellConfig.shellToolName, + arguments: '{"command":"sleep 100"}', + }, + }, + ], + }, + ]; + const interruptedResponse = await makeRequest( + proxyUrl, + "/chat/completions", + { + body: { + model: "test-model", + messages: [ + ...messages, + { + role: "tool", + tool_call_id: "runtime-call-id", + content: + "", + }, + ], + }, + }, + ); + + expect(interruptedResponse.status).toBe(200); + expect( + (JSON.parse(interruptedResponse.body) as ChatCompletion).choices[0] + .message.content, + ).toBe("Ready for another request."); + + const meaningfulErrorResponse = await makeRequest( + proxyUrl, + "/chat/completions", + { + body: { + model: "test-model", + messages: [ + ...messages, + { + role: "tool", + tool_call_id: "runtime-call-id", + content: + "The command failed because the executable was missing.", + }, + ], + }, + }, + ); + expect(meaningfulErrorResponse.status).toBe(500); + } finally { + await proxy.stop(); + } + }); + test("matches available-tools results after the built-in tool set changes", async () => { const cachePath = path.join(tempDir, "cache.yaml"); // Legacy snapshot recorded before write_agent was a built-in tool: the diff --git a/test/harness/replayingCapiProxy.ts b/test/harness/replayingCapiProxy.ts index 4ed8e06ccd..f30a9fbd77 100644 --- a/test/harness/replayingCapiProxy.ts +++ b/test/harness/replayingCapiProxy.ts @@ -1556,7 +1556,7 @@ function normalizeAvailableToolNames(result: string): string { function normalizeInterruptedToolResult(result: string): string { return result.replace( - /^Failed to execute `[^`]+` tool(?: with arguments: [\s\S]*?)? due to error: (?:Error: )?Session aborted$/, + /^(?:Failed to execute `[^`]+` tool(?: with arguments: [\s\S]*?)? due to error: (?:Error: )?Session aborted|)$/, "The execution of this tool, or a previous tool was interrupted.", ); } From 38b28f9c26ed2a977b6fd5467e97dd81435e0a9d Mon Sep 17 00:00:00 2001 From: Stefano Cordio Date: Mon, 24 Aug 2026 17:13:44 +0200 Subject: [PATCH 02/32] Convert bold title to heading (#2382) --- python/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/README.md b/python/README.md index bb17f68ccd..dc0a6a6794 100644 --- a/python/README.md +++ b/python/README.md @@ -349,7 +349,7 @@ async with await client.create_session( > **Note:** When using `from __future__ import annotations`, define Pydantic models at module level (not inside functions). -**Low-level API (without Pydantic):** +#### Low-level API (without Pydantic) For users who prefer manual schema definition: From 8bc107f78e1d66bf4406cc9fa5c644f1cd00e842 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 24 Aug 2026 14:44:18 +0000 Subject: [PATCH 03/32] build(deps): bump js-yaml (#2286) Bumps the npm_and_yarn group with 1 update in the /scripts/codegen directory: [js-yaml](https://github.com/nodeca/js-yaml). Updates `js-yaml` from 4.2.0 to 4.3.1 - [Changelog](https://github.com/nodeca/js-yaml/blob/4.3.1/CHANGELOG.md) - [Commits](https://github.com/nodeca/js-yaml/compare/4.2.0...4.3.1) --- updated-dependencies: - dependency-name: js-yaml dependency-version: 4.3.1 dependency-type: indirect dependency-group: npm_and_yarn ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- scripts/codegen/package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/codegen/package-lock.json b/scripts/codegen/package-lock.json index 5ed410e943..3d631c1b11 100644 --- a/scripts/codegen/package-lock.json +++ b/scripts/codegen/package-lock.json @@ -697,9 +697,9 @@ "license": "BSD-3-Clause" }, "node_modules/js-yaml": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.2.0.tgz", - "integrity": "sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.1.tgz", + "integrity": "sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ==", "funding": [ { "type": "github", From b6c004684fea1e66e4a89a04fd54e785a3f91c83 Mon Sep 17 00:00:00 2001 From: Rince Yuan Date: Mon, 24 Aug 2026 23:38:45 +0800 Subject: [PATCH 04/32] docs: note the minimum language runtimes in the bundled CLI quick start (#2312) * docs: note the minimum language runtimes in the bundled CLI quick start The quick start jumps straight to code with no version requirements. On a Python older than the declared 3.11 floor, pip resolves to an outdated SDK release instead of reporting the conflict, so the snippet fails with an error that says nothing about the Python version. Fixes #2293. * docs: link to per-language prerequisites instead of duplicating runtime versions Replaces the inline list of minimum language/runtime versions with a single sentence linking to the canonical Prerequisites section in each language's README, so version floors are maintained in one place per language instead of being duplicated (and potentially drifting) in the bundled CLI quick start. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Zhangyi Yuan Co-authored-by: Steve Sanderson Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- docs/setup/bundled-cli.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/setup/bundled-cli.md b/docs/setup/bundled-cli.md index f067de8fde..9d9c80c35b 100644 --- a/docs/setup/bundled-cli.md +++ b/docs/setup/bundled-cli.md @@ -39,6 +39,9 @@ flowchart TB ## Quick start +> [!NOTE] +> Each SDK has a minimum language runtime requirement—see the Prerequisites section of the [Node.js](../../nodejs/README.md#prerequisites), [Python](../../python/README.md#prerequisites), [Go](../../go/README.md#prerequisites), [Rust](../../rust/README.md#prerequisites), [Java](../../java/README.md#prerequisites), or [.NET](../../dotnet/README.md#prerequisites) README—since an unsupported runtime (e.g. Python below the stated floor) can cause `pip`/package managers to silently resolve an outdated SDK release instead of reporting a version conflict. +
Node.js / TypeScript From 7c63e58b50576cb8067bf76ed23384dc959771f4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 24 Aug 2026 16:38:50 +0100 Subject: [PATCH 05/32] build(deps): bump tsx (#2316) Bumps the java-codegen-deps group with 1 update in the /java/scripts/codegen directory: [tsx](https://github.com/privatenumber/tsx). Updates `tsx` from 4.23.1 to 4.23.12 - [Release notes](https://github.com/privatenumber/tsx/releases) - [Changelog](https://github.com/privatenumber/tsx/blob/master/release.config.cjs) - [Commits](https://github.com/privatenumber/tsx/compare/v4.23.1...v4.23.12) --- updated-dependencies: - dependency-name: tsx dependency-version: 4.23.11 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: java-codegen-deps ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- java/scripts/codegen/package-lock.json | 8 ++++---- java/scripts/codegen/package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/java/scripts/codegen/package-lock.json b/java/scripts/codegen/package-lock.json index 0932178ef1..be096fe130 100644 --- a/java/scripts/codegen/package-lock.json +++ b/java/scripts/codegen/package-lock.json @@ -8,7 +8,7 @@ "dependencies": { "@github/copilot": "^1.0.81-6", "json-schema": "^0.4.0", - "tsx": "^4.23.1" + "tsx": "^4.23.12" } }, "node_modules/@esbuild/aix-ppc64": { @@ -648,9 +648,9 @@ "license": "(AFL-2.1 OR BSD-3-Clause)" }, "node_modules/tsx": { - "version": "4.23.1", - "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.23.1.tgz", - "integrity": "sha512-GQHnkIfxyx1wYCOS/wonik5MVRZU9hi1TEZmzGZSCJB1y9YgoZ8H6itNE/u4suE+yLmOzuE4E5S4TZ/ZX2wcWQ==", + "version": "4.23.12", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.23.12.tgz", + "integrity": "sha512-FDf4L4sYzKtzWYhU/Xm0AQFdTjdIxNo9ElTf2mxXM6k8YMHXzYUe4yODVaXP4V9uMFbVg8c0qyBccK2OOxb45Q==", "license": "MIT", "dependencies": { "esbuild": "~0.28.0" diff --git a/java/scripts/codegen/package.json b/java/scripts/codegen/package.json index 8ceacb3e18..18bdca9bcd 100644 --- a/java/scripts/codegen/package.json +++ b/java/scripts/codegen/package.json @@ -9,6 +9,6 @@ "dependencies": { "@github/copilot": "^1.0.81-6", "json-schema": "^0.4.0", - "tsx": "^4.23.1" + "tsx": "^4.23.12" } } From ff2fc25f10819fe5771b866905bfbd9c2575bf56 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 24 Aug 2026 16:39:00 +0100 Subject: [PATCH 06/32] build(deps): bump the java-maven-deps group in /java with 3 updates (#2359) Bumps the java-maven-deps group in /java with 3 updates: [org.apache.maven.plugins:maven-release-plugin](https://github.com/apache/maven-release), [org.codehaus.mojo:flatten-maven-plugin](https://github.com/mojohaus/flatten-maven-plugin) and org.apache.maven:apache-maven. Updates `org.apache.maven.plugins:maven-release-plugin` from 3.1.1 to 3.3.1 - [Release notes](https://github.com/apache/maven-release/releases) - [Commits](https://github.com/apache/maven-release/compare/maven-release-3.1.1...maven-release-3.3.1) Updates `org.codehaus.mojo:flatten-maven-plugin` from 1.7.0 to 1.8.0 - [Release notes](https://github.com/mojohaus/flatten-maven-plugin/releases) - [Commits](https://github.com/mojohaus/flatten-maven-plugin/compare/1.7.0...1.8.0) Updates `org.apache.maven:apache-maven` from 3.9.12 to 3.9.16 --- updated-dependencies: - dependency-name: org.apache.maven.plugins:maven-release-plugin dependency-version: 3.3.1 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: java-maven-deps - dependency-name: org.codehaus.mojo:flatten-maven-plugin dependency-version: 1.8.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: java-maven-deps - dependency-name: org.apache.maven:apache-maven dependency-version: 3.9.16 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: java-maven-deps ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- java/.mvn/wrapper/maven-wrapper.properties | 2 +- java/mvnw.cmd | 378 ++++++++++----------- java/pom.xml | 4 +- 3 files changed, 192 insertions(+), 192 deletions(-) diff --git a/java/.mvn/wrapper/maven-wrapper.properties b/java/.mvn/wrapper/maven-wrapper.properties index 8dea6c227c..216df05897 100644 --- a/java/.mvn/wrapper/maven-wrapper.properties +++ b/java/.mvn/wrapper/maven-wrapper.properties @@ -1,3 +1,3 @@ wrapperVersion=3.3.4 distributionType=only-script -distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.12/apache-maven-3.9.12-bin.zip +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.16/apache-maven-3.9.16-bin.zip diff --git a/java/mvnw.cmd b/java/mvnw.cmd index 92450f9327..5761d94892 100644 --- a/java/mvnw.cmd +++ b/java/mvnw.cmd @@ -1,189 +1,189 @@ -<# : batch portion -@REM ---------------------------------------------------------------------------- -@REM Licensed to the Apache Software Foundation (ASF) under one -@REM or more contributor license agreements. See the NOTICE file -@REM distributed with this work for additional information -@REM regarding copyright ownership. The ASF licenses this file -@REM to you under the Apache License, Version 2.0 (the -@REM "License"); you may not use this file except in compliance -@REM with the License. You may obtain a copy of the License at -@REM -@REM http://www.apache.org/licenses/LICENSE-2.0 -@REM -@REM Unless required by applicable law or agreed to in writing, -@REM software distributed under the License is distributed on an -@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -@REM KIND, either express or implied. See the License for the -@REM specific language governing permissions and limitations -@REM under the License. -@REM ---------------------------------------------------------------------------- - -@REM ---------------------------------------------------------------------------- -@REM Apache Maven Wrapper startup batch script, version 3.3.4 -@REM -@REM Optional ENV vars -@REM MVNW_REPOURL - repo url base for downloading maven distribution -@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven -@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output -@REM ---------------------------------------------------------------------------- - -@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0) -@SET __MVNW_CMD__= -@SET __MVNW_ERROR__= -@SET __MVNW_PSMODULEP_SAVE=%PSModulePath% -@SET PSModulePath= -@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @( - IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B) -) -@SET PSModulePath=%__MVNW_PSMODULEP_SAVE% -@SET __MVNW_PSMODULEP_SAVE= -@SET __MVNW_ARG0_NAME__= -@SET MVNW_USERNAME= -@SET MVNW_PASSWORD= -@IF NOT "%__MVNW_CMD__%"=="" ("%__MVNW_CMD__%" %*) -@echo Cannot start maven from wrapper >&2 && exit /b 1 -@GOTO :EOF -: end batch / begin powershell #> - -$ErrorActionPreference = "Stop" -if ($env:MVNW_VERBOSE -eq "true") { - $VerbosePreference = "Continue" -} - -# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties -$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl -if (!$distributionUrl) { - Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" -} - -switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) { - "maven-mvnd-*" { - $USE_MVND = $true - $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip" - $MVN_CMD = "mvnd.cmd" - break - } - default { - $USE_MVND = $false - $MVN_CMD = $script -replace '^mvnw','mvn' - break - } -} - -# apply MVNW_REPOURL and calculate MAVEN_HOME -# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ -if ($env:MVNW_REPOURL) { - $MVNW_REPO_PATTERN = if ($USE_MVND -eq $False) { "/org/apache/maven/" } else { "/maven/mvnd/" } - $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace "^.*$MVNW_REPO_PATTERN",'')" -} -$distributionUrlName = $distributionUrl -replace '^.*/','' -$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$','' - -$MAVEN_M2_PATH = "$HOME/.m2" -if ($env:MAVEN_USER_HOME) { - $MAVEN_M2_PATH = "$env:MAVEN_USER_HOME" -} - -if (-not (Test-Path -Path $MAVEN_M2_PATH)) { - New-Item -Path $MAVEN_M2_PATH -ItemType Directory | Out-Null -} - -$MAVEN_WRAPPER_DISTS = $null -if ((Get-Item $MAVEN_M2_PATH).Target[0] -eq $null) { - $MAVEN_WRAPPER_DISTS = "$MAVEN_M2_PATH/wrapper/dists" -} else { - $MAVEN_WRAPPER_DISTS = (Get-Item $MAVEN_M2_PATH).Target[0] + "/wrapper/dists" -} - -$MAVEN_HOME_PARENT = "$MAVEN_WRAPPER_DISTS/$distributionUrlNameMain" -$MAVEN_HOME_NAME = ([System.Security.Cryptography.SHA256]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join '' -$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME" - -if (Test-Path -Path "$MAVEN_HOME" -PathType Container) { - Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME" - Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" - exit $? -} - -if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) { - Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl" -} - -# prepare tmp dir -$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile -$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir" -$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null -trap { - if ($TMP_DOWNLOAD_DIR.Exists) { - try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } - catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } - } -} - -New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null - -# Download and Install Apache Maven -Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." -Write-Verbose "Downloading from: $distributionUrl" -Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" - -$webclient = New-Object System.Net.WebClient -if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) { - $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD) -} -[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 -$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null - -# If specified, validate the SHA-256 sum of the Maven distribution zip file -$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum -if ($distributionSha256Sum) { - if ($USE_MVND) { - Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." - } - Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash - if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) { - Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property." - } -} - -# unzip and move -Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null - -# Find the actual extracted directory name (handles snapshots where filename != directory name) -$actualDistributionDir = "" - -# First try the expected directory name (for regular distributions) -$expectedPath = Join-Path "$TMP_DOWNLOAD_DIR" "$distributionUrlNameMain" -$expectedMvnPath = Join-Path "$expectedPath" "bin/$MVN_CMD" -if ((Test-Path -Path $expectedPath -PathType Container) -and (Test-Path -Path $expectedMvnPath -PathType Leaf)) { - $actualDistributionDir = $distributionUrlNameMain -} - -# If not found, search for any directory with the Maven executable (for snapshots) -if (!$actualDistributionDir) { - Get-ChildItem -Path "$TMP_DOWNLOAD_DIR" -Directory | ForEach-Object { - $testPath = Join-Path $_.FullName "bin/$MVN_CMD" - if (Test-Path -Path $testPath -PathType Leaf) { - $actualDistributionDir = $_.Name - } - } -} - -if (!$actualDistributionDir) { - Write-Error "Could not find Maven distribution directory in extracted archive" -} - -Write-Verbose "Found extracted Maven distribution directory: $actualDistributionDir" -Rename-Item -Path "$TMP_DOWNLOAD_DIR/$actualDistributionDir" -NewName $MAVEN_HOME_NAME | Out-Null -try { - Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null -} catch { - if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) { - Write-Error "fail to move MAVEN_HOME" - } -} finally { - try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } - catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } -} - -Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" +<# : batch portion +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Apache Maven Wrapper startup batch script, version 3.3.4 +@REM +@REM Optional ENV vars +@REM MVNW_REPOURL - repo url base for downloading maven distribution +@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output +@REM ---------------------------------------------------------------------------- + +@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0) +@SET __MVNW_CMD__= +@SET __MVNW_ERROR__= +@SET __MVNW_PSMODULEP_SAVE=%PSModulePath% +@SET PSModulePath= +@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @( + IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B) +) +@SET PSModulePath=%__MVNW_PSMODULEP_SAVE% +@SET __MVNW_PSMODULEP_SAVE= +@SET __MVNW_ARG0_NAME__= +@SET MVNW_USERNAME= +@SET MVNW_PASSWORD= +@IF NOT "%__MVNW_CMD__%"=="" ("%__MVNW_CMD__%" %*) +@echo Cannot start maven from wrapper >&2 && exit /b 1 +@GOTO :EOF +: end batch / begin powershell #> + +$ErrorActionPreference = "Stop" +if ($env:MVNW_VERBOSE -eq "true") { + $VerbosePreference = "Continue" +} + +# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties +$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl +if (!$distributionUrl) { + Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" +} + +switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) { + "maven-mvnd-*" { + $USE_MVND = $true + $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip" + $MVN_CMD = "mvnd.cmd" + break + } + default { + $USE_MVND = $false + $MVN_CMD = $script -replace '^mvnw','mvn' + break + } +} + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +if ($env:MVNW_REPOURL) { + $MVNW_REPO_PATTERN = if ($USE_MVND -eq $False) { "/org/apache/maven/" } else { "/maven/mvnd/" } + $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace "^.*$MVNW_REPO_PATTERN",'')" +} +$distributionUrlName = $distributionUrl -replace '^.*/','' +$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$','' + +$MAVEN_M2_PATH = "$HOME/.m2" +if ($env:MAVEN_USER_HOME) { + $MAVEN_M2_PATH = "$env:MAVEN_USER_HOME" +} + +if (-not (Test-Path -Path $MAVEN_M2_PATH)) { + New-Item -Path $MAVEN_M2_PATH -ItemType Directory | Out-Null +} + +$MAVEN_WRAPPER_DISTS = $null +if ((Get-Item $MAVEN_M2_PATH).Target[0] -eq $null) { + $MAVEN_WRAPPER_DISTS = "$MAVEN_M2_PATH/wrapper/dists" +} else { + $MAVEN_WRAPPER_DISTS = (Get-Item $MAVEN_M2_PATH).Target[0] + "/wrapper/dists" +} + +$MAVEN_HOME_PARENT = "$MAVEN_WRAPPER_DISTS/$distributionUrlNameMain" +$MAVEN_HOME_NAME = ([System.Security.Cryptography.SHA256]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join '' +$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME" + +if (Test-Path -Path "$MAVEN_HOME" -PathType Container) { + Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME" + Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" + exit $? +} + +if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) { + Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl" +} + +# prepare tmp dir +$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile +$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir" +$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null +trap { + if ($TMP_DOWNLOAD_DIR.Exists) { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } + } +} + +New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null + +# Download and Install Apache Maven +Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +Write-Verbose "Downloading from: $distributionUrl" +Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +$webclient = New-Object System.Net.WebClient +if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) { + $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD) +} +[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 +$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum +if ($distributionSha256Sum) { + if ($USE_MVND) { + Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." + } + Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash + if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) { + Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property." + } +} + +# unzip and move +Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null + +# Find the actual extracted directory name (handles snapshots where filename != directory name) +$actualDistributionDir = "" + +# First try the expected directory name (for regular distributions) +$expectedPath = Join-Path "$TMP_DOWNLOAD_DIR" "$distributionUrlNameMain" +$expectedMvnPath = Join-Path "$expectedPath" "bin/$MVN_CMD" +if ((Test-Path -Path $expectedPath -PathType Container) -and (Test-Path -Path $expectedMvnPath -PathType Leaf)) { + $actualDistributionDir = $distributionUrlNameMain +} + +# If not found, search for any directory with the Maven executable (for snapshots) +if (!$actualDistributionDir) { + Get-ChildItem -Path "$TMP_DOWNLOAD_DIR" -Directory | ForEach-Object { + $testPath = Join-Path $_.FullName "bin/$MVN_CMD" + if (Test-Path -Path $testPath -PathType Leaf) { + $actualDistributionDir = $_.Name + } + } +} + +if (!$actualDistributionDir) { + Write-Error "Could not find Maven distribution directory in extracted archive" +} + +Write-Verbose "Found extracted Maven distribution directory: $actualDistributionDir" +Rename-Item -Path "$TMP_DOWNLOAD_DIR/$actualDistributionDir" -NewName $MAVEN_HOME_NAME | Out-Null +try { + Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null +} catch { + if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) { + Write-Error "fail to move MAVEN_HOME" + } +} finally { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } +} + +Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" diff --git a/java/pom.xml b/java/pom.xml index 76efcd60b0..d6b0ee3d33 100644 --- a/java/pom.xml +++ b/java/pom.xml @@ -129,7 +129,7 @@ org.apache.maven.plugins maven-release-plugin - 3.1.1 + 3.3.1 org.apache.maven.plugins @@ -169,7 +169,7 @@ org.codehaus.mojo flatten-maven-plugin - 1.7.0 + 1.8.0 ossrh From 87aee6336ee09f0cba797ba67a1c78f0d7518e71 Mon Sep 17 00:00:00 2001 From: OllieinCanada <73385593+OllieinCanada@users.noreply.github.com> Date: Mon, 24 Aug 2026 11:39:11 -0400 Subject: [PATCH 07/32] fix(python): serialize native values in tool results (#2374) Signed-off-by: Oliver Slapinski Co-authored-by: Oliver Slapinski --- python/copilot/tools.py | 14 +++++++++++++- python/test_tools.py | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/python/copilot/tools.py b/python/copilot/tools.py index dc709cf7d5..ad0bcb41bd 100644 --- a/python/copilot/tools.py +++ b/python/copilot/tools.py @@ -11,7 +11,11 @@ import json from collections.abc import Awaitable, Callable from dataclasses import dataclass, field +from datetime import date, datetime, time +from decimal import Decimal +from enum import Enum from typing import TYPE_CHECKING, Any, Literal, TypeVar, get_type_hints, overload +from uuid import UUID from pydantic import BaseModel, ValidationError @@ -363,10 +367,18 @@ def _normalize_result(result: Any) -> ToolResult: result_type="success", ) - # Everything else gets JSON-serialized (with Pydantic model support) + # Everything else gets JSON-serialized (with common Python and Pydantic values) def default(obj: Any) -> Any: if isinstance(obj, BaseModel): return obj.model_dump(mode="json") + if isinstance(obj, (date, datetime, time)): + return obj.isoformat() + if isinstance(obj, (Decimal, UUID)): + return str(obj) + if isinstance(obj, Enum): + return obj.value + if isinstance(obj, set): + return list(obj) raise TypeError(f"Object of type {type(obj).__name__} is not JSON serializable") try: diff --git a/python/test_tools.py b/python/test_tools.py index 97de41df42..031f68c053 100644 --- a/python/test_tools.py +++ b/python/test_tools.py @@ -427,6 +427,39 @@ class Record(BaseModel): assert set(parsed["tags"]) == {"python", "sdk"} assert result.result_type == "success" + def test_plain_dict_with_non_primitive_fields_is_serialized(self): + from datetime import date, datetime, time + from decimal import Decimal + from enum import Enum + from uuid import UUID + + class Status(Enum): + ACTIVE = "active" + + result = _normalize_result( + { + "id": UUID("12345678-1234-5678-1234-567812345678"), + "created": datetime(2026, 1, 15, 10, 30, 0), + "day": date(2026, 1, 15), + "at": time(10, 30, 0), + "score": Decimal("99.5"), + "status": Status.ACTIVE, + "tags": {"python", "sdk"}, + } + ) + parsed = json.loads(result.text_result_for_llm) + assert parsed == { + "id": "12345678-1234-5678-1234-567812345678", + "created": "2026-01-15T10:30:00", + "day": "2026-01-15", + "at": "10:30:00", + "score": "99.5", + "status": "active", + "tags": parsed["tags"], + } + assert set(parsed["tags"]) == {"python", "sdk"} + assert result.result_type == "success" + def test_raises_for_unserializable_value(self): # Functions cannot be JSON serialized with pytest.raises(TypeError, match="Failed to serialize"): From 6e6eb55f4d0f1ee222cfce3e64493781ef7c5be8 Mon Sep 17 00:00:00 2001 From: Ed Burns Date: Mon, 24 Aug 2026 15:02:53 -0700 Subject: [PATCH 08/32] [Java] Clean-up: make it so interim time during implementation produces correct artifacts (#2345) * Gate Java native packaging by host Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 2ea248b2-112a-4548-865e-922721479b0c * docs(java): validate Linux native packaging Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: de80978a-085e-476a-97a2-d2c5858446f6 fix(java): guard native packaging by libc Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: de80978a-085e-476a-97a2-d2c5858446f6 Remove prompts before merge --------- Copilot-Session: 2ea248b2-112a-4548-865e-922721479b0c --- .github/workflows/java-sdk-tests.yml | 9 +- java/README.md | 39 ++++ java/copilot-native/pom.xml | 187 +++++++++++++++--- .../scripts/validate-native-host.mjs | 55 ++++++ .../scripts/validate-native-host.test.mjs | 67 +++++++ .../adr/adr-007-native-bundling-strategy.md | 5 +- 6 files changed, 336 insertions(+), 26 deletions(-) create mode 100644 java/copilot-native/scripts/validate-native-host.mjs create mode 100644 java/copilot-native/scripts/validate-native-host.test.mjs diff --git a/.github/workflows/java-sdk-tests.yml b/.github/workflows/java-sdk-tests.yml index bd0a34bd25..918e6ce10f 100644 --- a/.github/workflows/java-sdk-tests.yml +++ b/.github/workflows/java-sdk-tests.yml @@ -40,10 +40,13 @@ jobs: with: node-version: 22 + - name: Validate Linux glibc native host + run: node copilot-native/scripts/validate-native-host.mjs linux-x64 + - name: Run Java SDK tests (InProcess) env: CI: "true" - run: mvn clean verify -Pinprocess + run: mvn clean verify -Pinprocess -Dcopilot.native.skip.download=false - name: Generate Test Report Summary if: always() @@ -125,7 +128,9 @@ jobs: if: matrix.test-jdk == '25' env: CI: "true" - run: mvn verify -Dskip.test.harness=true + run: | + node copilot-native/scripts/validate-native-host.mjs linux-x64 + mvn verify -Dskip.test.harness=true -Dcopilot.native.libc=glibc -Dcopilot.native.skip.download=false - name: Switch to JDK 17 if: matrix.test-jdk == '17' diff --git a/java/README.md b/java/README.md index 2f8ca3dfa3..6c6124e959 100644 --- a/java/README.md +++ b/java/README.md @@ -487,6 +487,45 @@ mvn verify -Dskip.test.harness=true mvn jacoco:prepare-agent@wire-up-coverage-instrumentation antrun:run@print-test-jdk-banner surefire:test failsafe:integration-test failsafe:verify jacoco:report@build-coverage-report-from-tests -Denforcer.skip=true ``` +#### Development Setup for native embedding + +Run native-runtime Maven commands from the `java` directory. Native packaging requires Node.js and npm in addition to JDK 25 and Maven because `copilot-native/scripts/fetch-native.mjs` retrieves the pinned npm runtime package. + +Validated on a native Linux x64 glibc host: Maven activates the `native-linux-x64` profile on Linux `amd64` when `copilot.native.libc=glibc` is set. The build validates the host before downloading or packaging native files. The profile runs the native script tests, fetches the pinned `@github/copilot-linux-x64` package during `generate-resources`, packages the `linux-x64` classifier JAR during `package`, and verifies its native contents. An absent or explicitly false `copilot.native.skip.download` value preserves normal native packaging. Ensure npm can authenticate to the package registry before running the build. + +Before opting in, validate that Node.js reports glibc for the build host: + +```bash +node copilot-native/scripts/validate-native-host.mjs linux-x64 +mvn -pl copilot-native clean verify -Dcopilot.native.libc=glibc +``` + +The `inprocess` test profile performs the same validation and native packaging automatically, so the full in-process test command remains: + +```bash +mvn -Pinprocess clean verify +``` + +On macOS, Windows, Linux ARM64, Linux x64 musl, and other unsupported hosts, do not set `copilot.native.libc=glibc`. A normal build produces only the OS-neutral primary, sources, and Javadoc JARs; it does not run the Linux x64 native script tests, download or stage Linux native files, or produce a `linux-x64` classifier JAR. + +To build only the OS-neutral artifacts on any host, or override the glibc opt-in, disable native download and packaging: + +```bash +mvn -pl copilot-native clean package -DskipTests -Dcopilot.native.libc=glibc -Dcopilot.native.skip.download=true +``` + +The verified Linux x64 checks are: + +```bash +node --test copilot-native/scripts/fetch-native.test.mjs copilot-native/scripts/validate-native-host.test.mjs +mvn -pl copilot-native help:active-profiles -Dcopilot.native.libc=glibc -Dcopilot.native.skip.download=false +mvn -pl copilot-native test -Dcopilot.native.libc=glibc +mvn clean verify -Dcopilot.native.libc=glibc +mvn clean package -pl copilot-native -DskipTests -Dcopilot.native.libc=glibc -Dcopilot.native.skip.download=true +``` + +On a supported Linux x64 host, the classifier JAR contains `native/linux-x64/runtime.node`, `native/linux-x64/platform.properties`, and `native/linux-x64/copilot`. The placeholder JAR remains OS-neutral and contains no native binaries. Unsupported hosts retain the placeholder-only behavior without producing a `-linux-x64.jar`. + ## License MIT — see [LICENSE](sdk/LICENSE) for details. diff --git a/java/copilot-native/pom.xml b/java/copilot-native/pom.xml index 642f9171a4..775fad47da 100644 --- a/java/copilot-native/pom.xml +++ b/java/copilot-native/pom.xml @@ -17,7 +17,7 @@ jar GitHub Copilot SDK :: Java :: Native Runtime - Native runtime binaries for the GitHub Copilot Java SDK, published as per-platform classifier JARs + Native runtime artifacts for the GitHub Copilot Java SDK, with host-matched platform classifier JARs https://github.com/github/copilot-sdk @@ -35,12 +35,6 @@ its SHA-512 integrity hash. --> ${project.basedir}/../.. - - linux-x64 ${project.build.directory}/native-staging org.codehaus.mojo exec-maven-plugin - fetch-native-linux-x64 - generate-resources + validate-native-host + none + + exec + + + node + + ${project.basedir}/scripts/validate-native-host.mjs + ${copilot.native.classifier} + + + + + fetch-native + none exec @@ -93,15 +102,17 @@ test-fetch-native - test + none exec + ${skipTests} node --test ${project.basedir}/scripts/fetch-native.test.mjs + ${project.basedir}/scripts/validate-native-host.test.mjs @@ -118,8 +129,8 @@ native//platform.properties. --> - jar-linux-x64 - package + jar-native + none jar @@ -173,7 +184,7 @@ verify-native-jars - package + none run @@ -193,10 +204,10 @@ - + - + @@ -221,6 +232,124 @@ + + + native-linux-x64 + + + Linux + amd64 + + + copilot.native.libc + glibc + + + + linux-x64 + copilot + + + + + org.codehaus.mojo + exec-maven-plugin + + + validate-native-host + validate + + + fetch-native + generate-resources + + + test-fetch-native + test + + + + + org.apache.maven.plugins + maven-jar-plugin + + + jar-native + package + + + + + org.apache.maven.plugins + maven-antrun-plugin + + + verify-native-jars + package + + + + + + + + + inprocess + + linux-x64 + copilot + + + + + org.codehaus.mojo + exec-maven-plugin + + + validate-native-host + validate + + + fetch-native + generate-resources + + + test-fetch-native + test + + + + + org.apache.maven.plugins + maven-jar-plugin + + + jar-native + package + + + + + org.apache.maven.plugins + maven-antrun-plugin + + + verify-native-jars + package + + + + + + + com.github copilot-sdk-java-runtime ${copilot.version} linux-x64 + net.java.dev.jna @@ -491,7 +492,7 @@ mvn jacoco:prepare-agent@wire-up-coverage-instrumentation antrun:run@print-test- Run native-runtime Maven commands from the `java` directory. Native packaging requires Node.js and npm in addition to JDK 25 and Maven because `copilot-native/scripts/fetch-native.mjs` retrieves the pinned npm runtime package. -Validated on a native Linux x64 glibc host: Maven activates the `native-linux-x64` profile on Linux `amd64` when `copilot.native.libc=glibc` is set. The build validates the host before downloading or packaging native files. The profile runs the native script tests, fetches the pinned `@github/copilot-linux-x64` package during `generate-resources`, packages the `linux-x64` classifier JAR during `package`, and verifies its native contents. An absent or explicitly false `copilot.native.skip.download` value preserves normal native packaging. Ensure npm can authenticate to the package registry before running the build. +On a native Linux x64 glibc host, Maven activates the `native-linux-x64` profile when `copilot.native.libc=glibc` is set. On Windows x64, Maven activates `native-win32-x64` automatically. The matching profile validates the host, runs the native script tests, fetches the pinned `@github/copilot-` package during `generate-resources`, packages the classifier JAR during `package`, and verifies its native contents. Ensure npm can authenticate to the package registry before running the build. Before opting in, validate that Node.js reports glibc for the build host: @@ -506,7 +507,13 @@ The `inprocess` test profile performs the same validation and native packaging a mvn -Pinprocess clean verify ``` -On macOS, Windows, Linux ARM64, Linux x64 musl, and other unsupported hosts, do not set `copilot.native.libc=glibc`. A normal build produces only the OS-neutral primary, sources, and Javadoc JARs; it does not run the Linux x64 native script tests, download or stage Linux native files, or produce a `linux-x64` classifier JAR. +On Windows PowerShell, initialize Java and run the same profile: + +```powershell +mvn -Pinprocess clean verify +``` + +On macOS, Linux ARM64, Linux x64 musl, and other unsupported hosts, do not set `copilot.native.libc=glibc`. A normal build produces only the OS-neutral primary, sources, and Javadoc JARs; it does not run native script tests, download or stage native files, or produce a platform classifier JAR. To build only the OS-neutral artifacts on any host, or override the glibc opt-in, disable native download and packaging: @@ -524,7 +531,7 @@ mvn clean verify -Dcopilot.native.libc=glibc mvn clean package -pl copilot-native -DskipTests -Dcopilot.native.libc=glibc -Dcopilot.native.skip.download=true ``` -On a supported Linux x64 host, the classifier JAR contains `native/linux-x64/runtime.node`, `native/linux-x64/platform.properties`, and `native/linux-x64/copilot`. The placeholder JAR remains OS-neutral and contains no native binaries. Unsupported hosts retain the placeholder-only behavior without producing a `-linux-x64.jar`. +On Linux x64, the classifier JAR contains `native/linux-x64/runtime.node`, `native/linux-x64/platform.properties`, and `native/linux-x64/copilot`. On Windows x64, it contains `native/win32-x64/runtime.node`, `native/win32-x64/platform.properties`, and `native/win32-x64/copilot.exe`. The placeholder JAR remains OS-neutral and contains no native binaries. Unsupported hosts retain the placeholder-only behavior. ## License diff --git a/java/copilot-native/pom.xml b/java/copilot-native/pom.xml index 775fad47da..15f52ea856 100644 --- a/java/copilot-native/pom.xml +++ b/java/copilot-native/pom.xml @@ -113,6 +113,7 @@ --test ${project.basedir}/scripts/fetch-native.test.mjs ${project.basedir}/scripts/validate-native-host.test.mjs + ${project.basedir}/scripts/validate-native-artifact.test.mjs @@ -125,7 +126,7 @@ @@ -232,6 +233,60 @@ + + + inprocess + + linux-x64 + copilot + + + + + org.codehaus.mojo + exec-maven-plugin + + + validate-native-host + validate + + + fetch-native + generate-resources + + + test-fetch-native + test + + + + + org.apache.maven.plugins + maven-jar-plugin + + + jar-native + package + + + + + org.apache.maven.plugins + maven-antrun-plugin + + + verify-native-jars + package + + + + + + - inprocess + native-win32-x64 + + + Windows + amd64 + + - linux-x64 - copilot + win32-x64 + copilot.exe @@ -350,6 +405,156 @@ + + + attach-external-win32-classifier + + + copilot.native.external.win32.classifier.path + + + + + + org.codehaus.mojo + exec-maven-plugin + + + validate-external-win32-classifier + validate + + exec + + + node + + ${project.basedir}/scripts/validate-native-artifact.mjs + classifier + win32-x64 + ${copilot.native.external.win32.classifier.path} + ${project.build.finalName}-win32-x64.jar + ${copilot.sdk.root} + + + + + + + org.codehaus.mojo + build-helper-maven-plugin + + + attach-external-win32-classifier + package + + attach-artifact + + + + + ${copilot.native.external.win32.classifier.path} + jar + win32-x64 + + + + + + + + + + + + attach-test-linux-classifier + + + copilot.native.test.linux.classifier.path + + + + + + org.codehaus.mojo + exec-maven-plugin + + + validate-test-linux-classifier + validate + + exec + + + node + + ${project.basedir}/scripts/validate-native-artifact.mjs + classifier + linux-x64 + ${copilot.native.test.linux.classifier.path} + ${project.build.finalName}-linux-x64.jar + ${copilot.sdk.root} + + + + + + + org.codehaus.mojo + build-helper-maven-plugin + + + attach-test-linux-classifier + package + + attach-artifact + + + + + ${copilot.native.test.linux.classifier.path} + jar + linux-x64 + + + + + + + + + + + + local-publication-validation + + + copilot.native.test.local.publication + true + + + + + + org.sonatype.central + central-publishing-maven-plugin + + true + + + + + ${copilot.sdk.root}/nodejs/node_modules/@github/copilot/npm-loader.js - - ${copilot.sdk.root}/nodejs/node_modules/@github/copilot-linux-x64/copilot false + + net.java.dev.jna @@ -492,7 +492,7 @@ mvn jacoco:prepare-agent@wire-up-coverage-instrumentation antrun:run@print-test- Run native-runtime Maven commands from the `java` directory. Native packaging requires Node.js and npm in addition to JDK 25 and Maven because `copilot-native/scripts/fetch-native.mjs` retrieves the pinned npm runtime package. -On a native Linux x64 glibc host, Maven activates the `native-linux-x64` profile when `copilot.native.libc=glibc` is set. On Windows x64, Maven activates `native-win32-x64` automatically. The matching profile validates the host, runs the native script tests, fetches the pinned `@github/copilot-` package during `generate-resources`, packages the classifier JAR during `package`, and verifies its native contents. Ensure npm can authenticate to the package registry before running the build. +On a native Linux x64 glibc host, Maven activates the `native-linux-x64` profile when `copilot.native.libc=glibc` is set. On Windows x64, Maven activates `native-win32-x64` automatically. On Apple Silicon macOS, Maven activates `native-darwin-arm64` automatically. The matching profile validates the host, runs the native script tests, fetches the pinned `@github/copilot-` package during `generate-resources`, packages the classifier JAR during `package`, and verifies its native contents. Ensure npm can authenticate to the package registry before running the build. Before opting in, validate that Node.js reports glibc for the build host: @@ -513,7 +513,14 @@ On Windows PowerShell, initialize Java and run the same profile: mvn -Pinprocess clean verify ``` -On macOS, Linux ARM64, Linux x64 musl, and other unsupported hosts, do not set `copilot.native.libc=glibc`. A normal build produces only the OS-neutral primary, sources, and Javadoc JARs; it does not run native script tests, download or stage native files, or produce a platform classifier JAR. +The same command validates in-process mode on Apple Silicon macOS: + +```bash +node copilot-native/scripts/validate-native-host.mjs darwin-arm64 +mvn -Pinprocess clean verify +``` + +On Intel macOS, Linux ARM64, Linux x64 musl, and other unsupported hosts, do not set `copilot.native.libc=glibc`. A normal build produces only the OS-neutral primary, sources, and Javadoc JARs; it does not run native script tests, download or stage native files, or produce a platform classifier JAR. To build only the OS-neutral artifacts on any host, or override the glibc opt-in, disable native download and packaging: @@ -531,7 +538,7 @@ mvn clean verify -Dcopilot.native.libc=glibc mvn clean package -pl copilot-native -DskipTests -Dcopilot.native.libc=glibc -Dcopilot.native.skip.download=true ``` -On Linux x64, the classifier JAR contains `native/linux-x64/runtime.node`, `native/linux-x64/platform.properties`, and `native/linux-x64/copilot`. On Windows x64, it contains `native/win32-x64/runtime.node`, `native/win32-x64/platform.properties`, and `native/win32-x64/copilot.exe`. The placeholder JAR remains OS-neutral and contains no native binaries. Unsupported hosts retain the placeholder-only behavior. +On Linux x64, the classifier JAR contains `native/linux-x64/runtime.node`, `native/linux-x64/platform.properties`, and `native/linux-x64/copilot`. On Windows x64, it contains `native/win32-x64/runtime.node`, `native/win32-x64/platform.properties`, and `native/win32-x64/copilot.exe`. On Apple Silicon macOS, it contains `native/darwin-arm64/runtime.node`, `native/darwin-arm64/platform.properties`, and `native/darwin-arm64/copilot`. The placeholder JAR remains OS-neutral and contains no native binaries. Unsupported hosts retain the placeholder-only behavior. ## License diff --git a/java/copilot-native/pom.xml b/java/copilot-native/pom.xml index 15f52ea856..145c4d5879 100644 --- a/java/copilot-native/pom.xml +++ b/java/copilot-native/pom.xml @@ -235,8 +235,9 @@ inprocess @@ -405,6 +406,61 @@ + + native-darwin-arm64 + + + mac + aarch64 + + + + darwin-arm64 + copilot + + + + + org.codehaus.mojo + exec-maven-plugin + + + validate-native-host + validate + + + fetch-native + generate-resources + + + test-fetch-native + test + + + + + org.apache.maven.plugins + maven-jar-plugin + + + jar-native + package + + + + + org.apache.maven.plugins + maven-antrun-plugin + + + verify-native-jars + package + + + + + + + + attach-external-darwin-classifier + + + copilot.native.external.darwin.classifier.path + + + + + + org.codehaus.mojo + exec-maven-plugin + + + validate-external-darwin-classifier + validate + + exec + + + node + + ${project.basedir}/scripts/validate-native-artifact.mjs + classifier + darwin-arm64 + ${copilot.native.external.darwin.classifier.path} + ${project.build.finalName}-darwin-arm64.jar + ${copilot.sdk.root} + + + + + + + org.codehaus.mojo + build-helper-maven-plugin + + + attach-external-darwin-classifier + package + + attach-artifact + + + + + ${copilot.native.external.darwin.classifier.path} + jar + darwin-arm64 + + + + + + + + + - + - ^1.0.81-6 + ^1.0.81-10 true diff --git a/java/scripts/codegen/package-lock.json b/java/scripts/codegen/package-lock.json index be096fe130..a81af6e0d1 100644 --- a/java/scripts/codegen/package-lock.json +++ b/java/scripts/codegen/package-lock.json @@ -6,7 +6,7 @@ "": { "name": "copilot-sdk-java-codegen", "dependencies": { - "@github/copilot": "^1.0.81-6", + "@github/copilot": "^1.0.81-10", "json-schema": "^0.4.0", "tsx": "^4.23.12" } @@ -428,9 +428,9 @@ } }, "node_modules/@github/copilot": { - "version": "1.0.81-6", - "resolved": "https://registry.npmjs.org/@github/copilot/-/copilot-1.0.81-6.tgz", - "integrity": "sha512-hT29nRkf0EJE3N6lqeLOPszbdEyALZ+fjYG9zKX5a3L5r+o+m4/KF+8l2gn2yORNqOzwUYNj2vnVzKqeYYNLGg==", + "version": "1.0.81-10", + "resolved": "https://registry.npmjs.org/@github/copilot/-/copilot-1.0.81-10.tgz", + "integrity": "sha512-Ac99EvN16s4hKRhJLSEn1HMNaZ6MD8BzIey1zzJNBQy1/yP4PQDZ2CWitEq+XQQEi+6SsqeJRqXOKiWk1EyK7g==", "license": "SEE LICENSE IN LICENSE.md", "dependencies": { "detect-libc": "^2.1.2" @@ -439,20 +439,20 @@ "copilot": "npm-loader.js" }, "optionalDependencies": { - "@github/copilot-darwin-arm64": "1.0.81-6", - "@github/copilot-darwin-x64": "1.0.81-6", - "@github/copilot-linux-arm64": "1.0.81-6", - "@github/copilot-linux-x64": "1.0.81-6", - "@github/copilot-linuxmusl-arm64": "1.0.81-6", - "@github/copilot-linuxmusl-x64": "1.0.81-6", - "@github/copilot-win32-arm64": "1.0.81-6", - "@github/copilot-win32-x64": "1.0.81-6" + "@github/copilot-darwin-arm64": "1.0.81-10", + "@github/copilot-darwin-x64": "1.0.81-10", + "@github/copilot-linux-arm64": "1.0.81-10", + "@github/copilot-linux-x64": "1.0.81-10", + "@github/copilot-linuxmusl-arm64": "1.0.81-10", + "@github/copilot-linuxmusl-x64": "1.0.81-10", + "@github/copilot-win32-arm64": "1.0.81-10", + "@github/copilot-win32-x64": "1.0.81-10" } }, "node_modules/@github/copilot-darwin-arm64": { - "version": "1.0.81-6", - "resolved": "https://registry.npmjs.org/@github/copilot-darwin-arm64/-/copilot-darwin-arm64-1.0.81-6.tgz", - "integrity": "sha512-nALa4e8Jc/g5ltIHrpHBHByJ5rlgzoZFylZIrkQY+B9vr3L57d5F6fOiTbf/OF9blFQX7artWRE1K0TmowGNCA==", + "version": "1.0.81-10", + "resolved": "https://registry.npmjs.org/@github/copilot-darwin-arm64/-/copilot-darwin-arm64-1.0.81-10.tgz", + "integrity": "sha512-s90Av0iwjTSU6Gky8T9wI1PJdlfbdUcPAVgKDtimaOiAwcdLG4fKTpGxrk96KJrnOHHK3x9SiXsw/pW0ThAH/A==", "cpu": [ "arm64" ], @@ -466,9 +466,9 @@ } }, "node_modules/@github/copilot-darwin-x64": { - "version": "1.0.81-6", - "resolved": "https://registry.npmjs.org/@github/copilot-darwin-x64/-/copilot-darwin-x64-1.0.81-6.tgz", - "integrity": "sha512-K+bp799DejrsmxMNyaFAmKo4xnLJXBb8hkv9N8OCQukmTSoRpfqhv2oTDfgVFadwllt+py/FIdxKTZQYvPGGGw==", + "version": "1.0.81-10", + "resolved": "https://registry.npmjs.org/@github/copilot-darwin-x64/-/copilot-darwin-x64-1.0.81-10.tgz", + "integrity": "sha512-8RnPI4J311oJQ0GPB6JxuLJq4JNY/KF9ZIIQm8KpxXBY6d+6fmmAsMDEk7OiF/Asl2I7+LTi+qU2ZVhP7FYhbg==", "cpu": [ "x64" ], @@ -482,9 +482,9 @@ } }, "node_modules/@github/copilot-linux-arm64": { - "version": "1.0.81-6", - "resolved": "https://registry.npmjs.org/@github/copilot-linux-arm64/-/copilot-linux-arm64-1.0.81-6.tgz", - "integrity": "sha512-aEpnfTTjOxpesFo9jqk/phZUivOhNHbdBRfBrS2NiCPrQZFBYUC4wRVo/Xo2PMMQ4J07b6fU7JJQPoUUkKy5Wg==", + "version": "1.0.81-10", + "resolved": "https://registry.npmjs.org/@github/copilot-linux-arm64/-/copilot-linux-arm64-1.0.81-10.tgz", + "integrity": "sha512-2UtK5CBrE6ZVSIzU2KHeIgO8N7056axjbF2lE6WuK+H+oJJ4v3w5eQkalqGzRHhkaPfCW4kT1lDMhZFW+XbLjA==", "cpu": [ "arm64" ], @@ -498,9 +498,9 @@ } }, "node_modules/@github/copilot-linux-x64": { - "version": "1.0.81-6", - "resolved": "https://registry.npmjs.org/@github/copilot-linux-x64/-/copilot-linux-x64-1.0.81-6.tgz", - "integrity": "sha512-NFqonFfJCyA7d3bNoYeLWUQ69zelPr9TTnLpAHCi3scFZqbEvMBDFxW2XsKWwfYuuR9XzfU7/tgOUgq3gmL5aA==", + "version": "1.0.81-10", + "resolved": "https://registry.npmjs.org/@github/copilot-linux-x64/-/copilot-linux-x64-1.0.81-10.tgz", + "integrity": "sha512-61+KAfo1TBARrBfss3w4dfmRVSf0PiFg0c9JNuT9HjoNnytl7maJBPEgUvI4YBcxScNEAlCMXaUUG3Tuuh1g+w==", "cpu": [ "x64" ], @@ -514,9 +514,9 @@ } }, "node_modules/@github/copilot-linuxmusl-arm64": { - "version": "1.0.81-6", - "resolved": "https://registry.npmjs.org/@github/copilot-linuxmusl-arm64/-/copilot-linuxmusl-arm64-1.0.81-6.tgz", - "integrity": "sha512-EE99DFTAgTq6eOFDiiv+OUROD2pDQIrzyyJEfUS9K8JanwNc+Py8vTxrJ0yK0slpJ+Fue5uDRno6c9ys8OM59g==", + "version": "1.0.81-10", + "resolved": "https://registry.npmjs.org/@github/copilot-linuxmusl-arm64/-/copilot-linuxmusl-arm64-1.0.81-10.tgz", + "integrity": "sha512-CR6KRPCFoGkaD8I2an1FyrT5avF1U5aTbwW2sYCP7w1KExYFknxEL8ES6BkFuPEA7YcjmLa0SOq26Z+TgIVHSg==", "cpu": [ "arm64" ], @@ -530,9 +530,9 @@ } }, "node_modules/@github/copilot-linuxmusl-x64": { - "version": "1.0.81-6", - "resolved": "https://registry.npmjs.org/@github/copilot-linuxmusl-x64/-/copilot-linuxmusl-x64-1.0.81-6.tgz", - "integrity": "sha512-90HRKx25EjhlQNOCCdbiC0Ck0fSKyp8XUxUoCvuNdoigdUP54ZGS07dkyJiJq9KZaKilxZBSpXiNt8t5ETA2Sg==", + "version": "1.0.81-10", + "resolved": "https://registry.npmjs.org/@github/copilot-linuxmusl-x64/-/copilot-linuxmusl-x64-1.0.81-10.tgz", + "integrity": "sha512-fvZfyEOfRkvUDPXY6UUjAqV8Mkf08PQV+jgtiAFUryuas5VP9cYaAmQSmNpzNMNi3kSX/ycUJe7oc3zXZ8ylog==", "cpu": [ "x64" ], @@ -546,9 +546,9 @@ } }, "node_modules/@github/copilot-win32-arm64": { - "version": "1.0.81-6", - "resolved": "https://registry.npmjs.org/@github/copilot-win32-arm64/-/copilot-win32-arm64-1.0.81-6.tgz", - "integrity": "sha512-1dRSHF/7PFzB+AGORg8BJh2n1+N7+sIJDLqozrC9INWFp1t6ercptIXgJTF/V7UZVGQLO4LBBK1HH/QhzUmrfA==", + "version": "1.0.81-10", + "resolved": "https://registry.npmjs.org/@github/copilot-win32-arm64/-/copilot-win32-arm64-1.0.81-10.tgz", + "integrity": "sha512-n30PPBgCT4Iq9MgH6is6L3eUEE+sF6xB2fb+dGsclj5j/hCkT7+ef0j8YcAGipsvGfzGAuywIsWlvF7fzYsOKQ==", "cpu": [ "arm64" ], @@ -562,9 +562,9 @@ } }, "node_modules/@github/copilot-win32-x64": { - "version": "1.0.81-6", - "resolved": "https://registry.npmjs.org/@github/copilot-win32-x64/-/copilot-win32-x64-1.0.81-6.tgz", - "integrity": "sha512-lIbN1mk6Rm9bWrWU4/UfrC5OCga7XcBi2LBz5roDnNcuG8mKEevDcNOtbEYz/TaJg+WBMoTbGfXBVd1hGy2DTA==", + "version": "1.0.81-10", + "resolved": "https://registry.npmjs.org/@github/copilot-win32-x64/-/copilot-win32-x64-1.0.81-10.tgz", + "integrity": "sha512-lb8kvhrXwGCN3LeRDQfLHsUp+F43XvPYznaYK1sPtK1kFGa4/kL690tasoSEvzu8ZKoTY6kZ6YmDbUZgqOislw==", "cpu": [ "x64" ], diff --git a/java/scripts/codegen/package.json b/java/scripts/codegen/package.json index 18bdca9bcd..f619ebd627 100644 --- a/java/scripts/codegen/package.json +++ b/java/scripts/codegen/package.json @@ -7,7 +7,7 @@ "generate:java": "tsx java.ts" }, "dependencies": { - "@github/copilot": "^1.0.81-6", + "@github/copilot": "^1.0.81-10", "json-schema": "^0.4.0", "tsx": "^4.23.12" } diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/AssistantMessageEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/AssistantMessageEvent.java index 785e3b49d1..41147f3c55 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/AssistantMessageEvent.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/AssistantMessageEvent.java @@ -73,6 +73,8 @@ public record AssistantMessageEventData( @JsonProperty("apiCallId") String apiCallId, /** Neutral provider-tagged server-side tool-use payload (tool search, advisor) for verbatim round-tripping */ @JsonProperty("serverTools") AssistantMessageServerTools serverTools, + /** Neutral provider-tagged reasoning content blocks preserved verbatim for round-tripping. `reasoningText` and `reasoningOpaque` are a lossy derived view of these blocks, retained for display. */ + @JsonProperty("reasoningBlocks") AssistantMessageReasoningBlocks reasoningBlocks, /** Identifier for the agent loop turn that produced this message, matching the corresponding assistant.turn_start event */ @JsonProperty("turnId") String turnId, /** Tool call ID of the parent tool invocation when this event originates from a sub-agent */ diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/AssistantMessageReasoningBlocks.java b/java/sdk/src/generated/java/com/github/copilot/generated/AssistantMessageReasoningBlocks.java new file mode 100644 index 0000000000..d2ad87f7c4 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/AssistantMessageReasoningBlocks.java @@ -0,0 +1,30 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: session-events.schema.json + +package com.github.copilot.generated; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; +import javax.annotation.processing.Generated; + +/** + * Neutral provider-tagged reasoning content blocks preserved verbatim for round-tripping + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record AssistantMessageReasoningBlocks( + /** Model provider that produced these reasoning blocks. */ + @JsonProperty("provider") String provider, + /** Provider-native reasoning content blocks (e.g. Anthropic `thinking` / `redacted_thinking`) preserved verbatim, in order. A single response can carry several, each signed over the content preceding it, so dropping or reordering any of them invalidates the rest. */ + @JsonProperty("blocks") List blocks +) { +} 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 6d94a553da..ff4cfddec8 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 @@ -56,6 +56,8 @@ public record AssistantUsageEventData( @JsonProperty("duration") Long duration, /** Time to first token in milliseconds. Only available for streaming requests */ @JsonProperty("timeToFirstTokenMs") Double timeToFirstTokenMs, + /** Time to first observable model output in milliseconds. Includes text, reasoning, and tool-call output; only available for streaming requests that produce observable output. */ + @JsonProperty("outputTtftMs") Double outputTtftMs, /** Average inter-token latency in milliseconds. Only available for streaming requests */ @JsonProperty("interTokenLatencyMs") Double interTokenLatencyMs, /** What initiated this API call (e.g., "sub-agent", "mcp-sampling"); absent for user-initiated calls */ diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/ManagedSettingsEnforcedEscalation.java b/java/sdk/src/generated/java/com/github/copilot/generated/ManagedSettingsEnforcedEscalation.java index 3b4f9917fc..619fb326e1 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/ManagedSettingsEnforcedEscalation.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/ManagedSettingsEnforcedEscalation.java @@ -25,7 +25,9 @@ public enum ManagedSettingsEnforcedEscalation { /** The {@code unrestricted_paths} variant. */ UNRESTRICTED_PATHS("unrestricted_paths"), /** The {@code unrestricted_urls} variant. */ - UNRESTRICTED_URLS("unrestricted_urls"); + UNRESTRICTED_URLS("unrestricted_urls"), + /** The {@code server_wide_mcp_approval} variant. */ + SERVER_WIDE_MCP_APPROVAL("server_wide_mcp_approval"); private final String value; ManagedSettingsEnforcedEscalation(String value) { this.value = value; } diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/ModelCallFinishedEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/ModelCallFinishedEvent.java new file mode 100644 index 0000000000..a1b424e807 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/ModelCallFinishedEvent.java @@ -0,0 +1,51 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: session-events.schema.json + +package com.github.copilot.generated; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import javax.annotation.processing.Generated; + +/** + * Session event "model.call_finished". Final lifecycle outcome for one logical model dispatch. A logical dispatch may include internal reconnect or fallback work, so event count is not provider HTTP-request count. + * @since 1.0.0 + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public final class ModelCallFinishedEvent extends SessionEvent { + + @Override + public String getType() { return "model.call_finished"; } + + @JsonProperty("data") + private ModelCallFinishedEventData data; + + public ModelCallFinishedEventData getData() { return data; } + public void setData(ModelCallFinishedEventData data) { this.data = data; } + + /** Data payload for {@link ModelCallFinishedEvent}. */ + @JsonIgnoreProperties(ignoreUnknown = true) + @JsonInclude(JsonInclude.Include.NON_NULL) + public record ModelCallFinishedEventData( + /** Agent-loop iteration within the interaction that initiated the model dispatch */ + @JsonProperty("turnId") String turnId, + /** Identifier of the user interaction that owns the model dispatch, matching assistant.turn_start.interactionId when available */ + @JsonProperty("interactionId") String interactionId, + /** Monotonic elapsed time spent in the logical model dispatch, including any internal transport reconnect or fallback and excluding orchestrator retry backoff, tool execution, confirmations, and post-response processing */ + @JsonProperty("dispatchDurationMs") Double dispatchDurationMs, + /** Final outcome after post-response acceptance processing */ + @JsonProperty("outcome") ModelCallFinishedOutcome outcome, + /** Whether an accepted successful response requested the exact name and command semantics of a built-in file edit tool, including an external tool explicitly replacing that built-in name. Absent when the logical dispatch did not produce an accepted response. */ + @JsonProperty("containsBuiltInFileEditRequest") Boolean containsBuiltInFileEditRequest, + /** Version of the built-in file-edit semantic classifier used for this event */ + @JsonProperty("editClassifierVersion") Long editClassifierVersion + ) { + } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/ModelCallFinishedOutcome.java b/java/sdk/src/generated/java/com/github/copilot/generated/ModelCallFinishedOutcome.java new file mode 100644 index 0000000000..8b86ed0281 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/ModelCallFinishedOutcome.java @@ -0,0 +1,39 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: session-events.schema.json + +package com.github.copilot.generated; + +import javax.annotation.processing.Generated; + +/** + * Final outcome of one logical model dispatch after response acceptance processing + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public enum ModelCallFinishedOutcome { + /** The {@code success} variant. */ + SUCCESS("success"), + /** The {@code error} variant. */ + ERROR("error"), + /** The {@code cancelled} variant. */ + CANCELLED("cancelled"), + /** The {@code rejected} variant. */ + REJECTED("rejected"); + + private final String value; + ModelCallFinishedOutcome(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static ModelCallFinishedOutcome fromValue(String value) { + for (ModelCallFinishedOutcome v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown ModelCallFinishedOutcome value: " + value); + } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/SessionEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/SessionEvent.java index a1da651ddb..0acc3df712 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/SessionEvent.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/SessionEvent.java @@ -74,6 +74,7 @@ @JsonSubTypes.Type(value = AssistantUsageEvent.class, name = "assistant.usage"), @JsonSubTypes.Type(value = PromptCacheBreakEvent.class, name = "prompt_cache_break"), @JsonSubTypes.Type(value = ModelCallFailureEvent.class, name = "model.call_failure"), + @JsonSubTypes.Type(value = ModelCallFinishedEvent.class, name = "model.call_finished"), @JsonSubTypes.Type(value = ModelCallStartEvent.class, name = "model.call_start"), @JsonSubTypes.Type(value = AbortEvent.class, name = "abort"), @JsonSubTypes.Type(value = ToolUserRequestedEvent.class, name = "tool.user_requested"), @@ -198,6 +199,7 @@ public abstract sealed class SessionEvent permits AssistantUsageEvent, PromptCacheBreakEvent, ModelCallFailureEvent, + ModelCallFinishedEvent, ModelCallStartEvent, AbortEvent, ToolUserRequestedEvent, 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 f935f44627..ac3763248a 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 @@ -45,6 +45,8 @@ public record SessionManagedSettingsResolvedEventData( @JsonProperty("clientManaged") Boolean clientManaged, /** Whether managed policy could not be determined (e.g. a failed server fetch) and the session fell back to the fail-closed restriction. When true, restrictions such as disabling bypass-permissions are enforced even though `settings` may be absent. */ @JsonProperty("failClosed") Boolean failClosed, + /** Whether the effective sandbox policy forces the sandbox on *only* because managed policy could not be determined, rather than because the policy requires it. Lets clients tell a user whose `--no-sandbox` was overridden that the sandbox stayed on as a fail-closed fallback, instead of attributing it to an administrator who set no such policy. */ + @JsonProperty("sandboxEnabledByUndeterminedPolicy") Boolean sandboxEnabledByUndeterminedPolicy, /** Whether enterprise policy disables bypass-permissions ("yolo") mode for this session. Deny-wins across layers, and forced on when `failClosed` is true. */ @JsonProperty("bypassPermissionsDisabled") Boolean bypassPermissionsDisabled, /** Whether at least two managed sources supplied permission allowlists, so enforcement intersects them and the flattened settings payload omits `permissions.allow`. */ diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/SubagentCompletedEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/SubagentCompletedEvent.java index f7300ddbf4..62f6803652 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/SubagentCompletedEvent.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/SubagentCompletedEvent.java @@ -42,6 +42,16 @@ public record SubagentCompletedEventData( @JsonProperty("agentDisplayName") String agentDisplayName, /** Model used by the sub-agent */ @JsonProperty("model") String model, + /** First model for which the sub-agent started an inference request, when one was dispatched */ + @JsonProperty("firstDispatchedModel") String firstDispatchedModel, + /** Concrete model the user configured for this sub-agent via `/subagents`, when present */ + @JsonProperty("configuredModelPreference") String configuredModelPreference, + /** Explicit model supplied by the parent agent on the task call, when present */ + @JsonProperty("explicitModelOverride") String explicitModelOverride, + /** Whether the explicit task-call model matched the user's configured preference */ + @JsonProperty("explicitModelMatchesPreference") Boolean explicitModelMatchesPreference, + /** Whether the first model actually dispatched matched the user's configured preference */ + @JsonProperty("configuredModelMatchesActual") Boolean configuredModelMatchesActual, /** Total number of tool calls made by the sub-agent */ @JsonProperty("totalToolCalls") Long totalToolCalls, /** Total tokens (input + output) consumed by the sub-agent */ diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/SubagentFailedEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/SubagentFailedEvent.java index 6a48544ce9..1d1413c64d 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/SubagentFailedEvent.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/SubagentFailedEvent.java @@ -44,6 +44,16 @@ public record SubagentFailedEventData( @JsonProperty("error") String error, /** Model selected for the sub-agent, when known */ @JsonProperty("model") String model, + /** First model for which the sub-agent started an inference request, when one was dispatched */ + @JsonProperty("firstDispatchedModel") String firstDispatchedModel, + /** Concrete model the user configured for this sub-agent via `/subagents`, when present */ + @JsonProperty("configuredModelPreference") String configuredModelPreference, + /** Explicit model supplied by the parent agent on the task call, when present */ + @JsonProperty("explicitModelOverride") String explicitModelOverride, + /** Whether the explicit task-call model matched the user's configured preference */ + @JsonProperty("explicitModelMatchesPreference") Boolean explicitModelMatchesPreference, + /** Whether the first model actually dispatched matched the user's configured preference */ + @JsonProperty("configuredModelMatchesActual") Boolean configuredModelMatchesActual, /** Total number of tool calls made before the sub-agent failed */ @JsonProperty("totalToolCalls") Long totalToolCalls, /** Total tokens (input + output) consumed before the sub-agent failed */ diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/AuthInfo.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/AuthInfo.java index 83040d7fa4..5d82a47f85 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/AuthInfo.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/AuthInfo.java @@ -22,6 +22,7 @@ @JsonSubTypes.Type(value = HMACAuthInfo.class, name = "hmac"), @JsonSubTypes.Type(value = EnvAuthInfo.class, name = "env"), @JsonSubTypes.Type(value = TokenAuthInfo.class, name = "token"), + @JsonSubTypes.Type(value = TokenProviderAuthInfo.class, name = "token-provider"), @JsonSubTypes.Type(value = CopilotApiTokenAuthInfo.class, name = "copilot-api-token"), @JsonSubTypes.Type(value = UserAuthInfo.class, name = "user"), @JsonSubTypes.Type(value = GhCliAuthInfo.class, name = "gh-cli"), diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/AuthInfoType.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/AuthInfoType.java index 1fb4b43ba4..5f81c8cf84 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/AuthInfoType.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/AuthInfoType.java @@ -28,6 +28,8 @@ public enum AuthInfoType { API_KEY("api-key"), /** The {@code token} variant. */ TOKEN("token"), + /** The {@code token-provider} variant. */ + TOKEN_PROVIDER("token-provider"), /** The {@code copilot-api-token} variant. */ COPILOT_API_TOKEN("copilot-api-token"); diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ConnectClientInfo.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ConnectClientInfo.java new file mode 100644 index 0000000000..e5b6b6f24d --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ConnectClientInfo.java @@ -0,0 +1,33 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import javax.annotation.processing.Generated; + +/** + * Identity of the integrating host, declared once on the `server.connect` handshake so telemetry from this connection is attributed to a single, consistent surface. All fields are optional; omit them to keep the default attribution. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record ConnectClientInfo( + /** Name of the host editor, e.g. `"vscode"`. */ + @JsonProperty("editorName") String editorName, + /** Version of the host editor, e.g. `"1.124.2"`. Ignored unless it looks like a version string. */ + @JsonProperty("editorVersion") String editorVersion, + /** Name of the Copilot extension within the host, e.g. `"copilot-chat"`. */ + @JsonProperty("extensionName") String extensionName, + /** Version of the Copilot extension within the host, e.g. `"0.54.0"`. Ignored unless it looks like a version string. */ + @JsonProperty("extensionVersion") String extensionVersion +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ConnectParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ConnectParams.java index d59f8fd6b0..05f2534970 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ConnectParams.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ConnectParams.java @@ -26,6 +26,8 @@ public record ConnectParams( /** Opt this connection in to GitHub telemetry forwarding for its lifetime. When set, the runtime forwards every internal telemetry event it emits — across all sessions, plus sessionless events — to this connection over the `gitHubTelemetry.event` notification. Regular events are also written to the runtime's normal GitHub/CTS path (dual-write); host-only compatibility events are forward-only and intentionally skip that path. Intended for first-party hosts that re-emit the events into their own telemetry stores. Both unrestricted and restricted events are forwarded, each tagged with a `restricted` discriminator; a backstop drops restricted events when restricted telemetry is disabled — using the process-global gate for ordinary events and an explicit session-scoped decision for host-only events. */ @JsonProperty("enableGitHubTelemetryForwarding") Boolean enableGitHubTelemetryForwarding, + /** Identity of the integrating host. Optional; omit it to keep the default attribution. */ + @JsonProperty("clientInfo") ConnectClientInfo clientInfo, /** Connection token; required when the server was started with COPILOT_CONNECTION_TOKEN */ @JsonProperty("token") String token ) { diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/DisableBypassPermissionsMode.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/GitHubTokenAcquireReason.java similarity index 57% rename from java/sdk/src/generated/java/com/github/copilot/generated/rpc/DisableBypassPermissionsMode.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/GitHubTokenAcquireReason.java index 1e6b1e7db6..9f18889a03 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/DisableBypassPermissionsMode.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/GitHubTokenAcquireReason.java @@ -9,20 +9,27 @@ import javax.annotation.processing.Generated; +/** + * Why the runtime is requesting a GitHub credential. + * + * @since 1.0.0 + */ @javax.annotation.processing.Generated("copilot-sdk-codegen") -public enum DisableBypassPermissionsMode { - /** The {@code disable} variant. */ - DISABLE("disable"); +public enum GitHubTokenAcquireReason { + /** The {@code initial} variant. */ + INITIAL("initial"), + /** The {@code refresh} variant. */ + REFRESH("refresh"); private final String value; - DisableBypassPermissionsMode(String value) { this.value = value; } + GitHubTokenAcquireReason(String value) { this.value = value; } @com.fasterxml.jackson.annotation.JsonValue public String getValue() { return value; } @com.fasterxml.jackson.annotation.JsonCreator - public static DisableBypassPermissionsMode fromValue(String value) { - for (DisableBypassPermissionsMode v : values()) { + public static GitHubTokenAcquireReason fromValue(String value) { + for (GitHubTokenAcquireReason v : values()) { if (v.value.equals(value)) return v; } - throw new IllegalArgumentException("Unknown DisableBypassPermissionsMode value: " + value); + throw new IllegalArgumentException("Unknown GitHubTokenAcquireReason value: " + value); } } diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/GitHubTokenAcquireRequest.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/GitHubTokenAcquireRequest.java new file mode 100644 index 0000000000..59ba9d8ed5 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/GitHubTokenAcquireRequest.java @@ -0,0 +1,33 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import javax.annotation.processing.Generated; + +/** + * Asks the SDK client to acquire a GitHub access token from an opaque callback registration. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record GitHubTokenAcquireRequest( + /** Opaque identifier generated by the SDK for this callback registration. */ + @JsonProperty("registrationId") String registrationId, + /** Effective GitHub host for which the callback must return a token. */ + @JsonProperty("host") String host, + /** Session receiving the token. Absent only before a cloud session has been assigned its id. */ + @JsonProperty("sessionId") String sessionId, + /** Why the runtime is requesting a GitHub credential. */ + @JsonProperty("reason") GitHubTokenAcquireReason reason +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/GitHubTokenAcquireResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/GitHubTokenAcquireResult.java new file mode 100644 index 0000000000..a20724c337 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/GitHubTokenAcquireResult.java @@ -0,0 +1,35 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import javax.annotation.processing.Generated; + +/** + * SDK host response to a GitHub credential request. + * + * @since 1.0.0 + */ +@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "kind", visible = true) +@JsonSubTypes({ + @JsonSubTypes.Type(value = GitHubTokenAcquireResultToken.class, name = "token"), + @JsonSubTypes.Type(value = GitHubTokenAcquireResultCancelled.class, name = "cancelled") +}) +@JsonIgnoreProperties(ignoreUnknown = true) +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public abstract class GitHubTokenAcquireResult { + + /** + * Returns the discriminator value for this variant. + * + * @return the kind discriminator + */ + public abstract String getKind(); +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/GitHubTokenAcquireResultCancelled.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/GitHubTokenAcquireResultCancelled.java new file mode 100644 index 0000000000..40ab8d5f74 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/GitHubTokenAcquireResultCancelled.java @@ -0,0 +1,30 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import javax.annotation.processing.Generated; + +/** + * Variant {@code cancelled} of {@link GitHubTokenAcquireResult}. + * + * @since 1.0.0 + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public final class GitHubTokenAcquireResultCancelled extends GitHubTokenAcquireResult { + + @JsonProperty("kind") + private final String kind = "cancelled"; + + @Override + public String getKind() { return kind; } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/GitHubTokenAcquireResultToken.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/GitHubTokenAcquireResultToken.java new file mode 100644 index 0000000000..cc250a54fd --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/GitHubTokenAcquireResultToken.java @@ -0,0 +1,51 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import javax.annotation.processing.Generated; + +/** + * Variant {@code token} of {@link GitHubTokenAcquireResult}. + * + * @since 1.0.0 + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public final class GitHubTokenAcquireResultToken extends GitHubTokenAcquireResult { + + @JsonProperty("kind") + private final String kind = "token"; + + @Override + public String getKind() { return kind; } + + /** GitHub access token acquired by the SDK host. */ + @JsonProperty("accessToken") + private String accessToken; + + /** OAuth token type. Defaults to bearer when omitted. */ + @JsonProperty("tokenType") + private String tokenType; + + /** Remaining token lifetime in seconds when callback execution completes. It must exceed the one-hour preflight refresh threshold. */ + @JsonProperty("expiresIn") + private Long expiresIn; + + public String getAccessToken() { return accessToken; } + public void setAccessToken(String accessToken) { this.accessToken = accessToken; } + + public String getTokenType() { return tokenType; } + public void setTokenType(String tokenType) { this.tokenType = tokenType; } + + public Long getExpiresIn() { return expiresIn; } + public void setExpiresIn(Long expiresIn) { this.expiresIn = expiresIn; } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/InstalledPlugin.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/InstalledPlugin.java index 3da690f47b..e372049403 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/InstalledPlugin.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/InstalledPlugin.java @@ -36,6 +36,8 @@ public record InstalledPlugin( /** Source for direct repo installs (when marketplace is empty) */ @JsonProperty("source") Object source, /** Per-plugin source fingerprint (a SHA-256 hash of the plugin's catalog source spec plus its resolved source subtree — NOT a Git commit SHA) captured at marketplace install/update time. Auto-update compares it against the freshly recomputed fingerprint to detect a content change that does not bump the version. Absent for pre-existing installs and for direct (non-marketplace) installs. */ - @JsonProperty("source_sha") String sourceSha + @JsonProperty("source_sha") String sourceSha, + /** Absolute path of the marketplace directory a live plugin was resolved from. Present only on live, never-persisted records — those synthesized at session start for a directory/local marketplace, whose cache_path points at the real plugin directory on disk rather than a copy under the installed-plugins cache. Its presence is what marks a record as live, and no record carrying it is ever written to the persisted installedPlugins key. */ + @JsonProperty("installed_from") String installedFrom ) { } diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/InstalledPluginInfo.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/InstalledPluginInfo.java index 2f4895690f..2c81e95f2b 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/InstalledPluginInfo.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/InstalledPluginInfo.java @@ -30,6 +30,8 @@ public record InstalledPluginInfo( /** Installed version (when reported by the plugin manifest) */ @JsonProperty("version") String version, /** Whether the plugin is currently enabled for new sessions */ - @JsonProperty("enabled") Boolean enabled + @JsonProperty("enabled") Boolean enabled, + /** Absolute path of the marketplace directory a live plugin was resolved from. Present only on live, never-persisted records — a plugin belonging to a directory/local marketplace, which is loaded from its real directory on every pass instead of a copy under the installed-plugins cache. Its presence is what marks a listed plugin as live: such a plugin is always present on disk, so `enabled` is its only meaningful state and it is never "not installed". */ + @JsonProperty("installedFrom") String installedFrom ) { } diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/Model.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/Model.java index 8aadae4a22..a652e8f4f6 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/Model.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/Model.java @@ -41,6 +41,12 @@ public record Model( /** Model capability category for grouping in the model picker */ @JsonProperty("modelPickerCategory") ModelPickerCategory modelPickerCategory, /** Relative cost tier for token-based billing users */ - @JsonProperty("modelPickerPriceCategory") ModelPickerPriceCategory modelPickerPriceCategory + @JsonProperty("modelPickerPriceCategory") ModelPickerPriceCategory modelPickerPriceCategory, + /** Warning text the service requires hosts to surface for this model. Present only when the service published at least one warning. */ + @JsonProperty("warningText") ModelWarningText warningText, + /** Informational notices the service published for this model, such as an upcoming change or a recommended alternative. Present only when the service published at least one notice. Hosts should surface these without implying anything is wrong with the model. */ + @JsonProperty("infoMessages") List infoMessages, + /** Warnings the service published for this model, such as a deprecated client version. Present only when the service published at least one warning. The model remains usable; hosts should surface these as advisory rather than blocking. */ + @JsonProperty("warningMessages") List warningMessages ) { } diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ModelMessage.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ModelMessage.java new file mode 100644 index 0000000000..35e8a17386 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ModelMessage.java @@ -0,0 +1,29 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import javax.annotation.processing.Generated; + +/** + * A service-published message about a model, carrying a stable machine-readable code alongside human-readable text. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record ModelMessage( + /** Stable machine-readable identifier for the message, such as `client_version_deprecated`. Hosts can key custom presentation off this; unrecognized codes should fall back to displaying `message`. */ + @JsonProperty("code") String code, + /** Human-readable message text intended for display to the user. */ + @JsonProperty("message") String message +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ModelWarningText.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ModelWarningText.java new file mode 100644 index 0000000000..817be420c5 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ModelWarningText.java @@ -0,0 +1,27 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import javax.annotation.processing.Generated; + +/** + * Service-published warning text that hosts should display when presenting a model. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record ModelWarningText( + /** Data-retention warning for the model. The text may contain Markdown links and should be rendered as Markdown when supported. */ + @JsonProperty("dataRetention") String dataRetention +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/PermissionPathsConfig.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/PermissionPathsConfig.java index 29aef6c66f..56dbd73dd8 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/PermissionPathsConfig.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/PermissionPathsConfig.java @@ -24,7 +24,7 @@ public record PermissionPathsConfig( /** If true, the runtime allows access to all paths without prompting. Equivalent to constructing an UnrestrictedPathManager. */ @JsonProperty("unrestricted") Boolean unrestricted, - /** Additional directories to allow tool access to (in addition to the session's working directory). When `unrestricted` is true, these are still pre-populated on the UnrestrictedPathManager so they remain visible via getDirectories() (e.g. for @-mention completion). */ + /** Additional directories to allow tool access to (in addition to the session's working directory). Conventional `.github/skills/` and `.github/agents/` definitions under them also join the session catalogs when their subsystem gates are enabled, so supplying a directory is a trust decision for configuration stored there. When `unrestricted` is true, these are still pre-populated on the UnrestrictedPathManager so they remain visible via getDirectories() (e.g. for @-mention completion). */ @JsonProperty("additionalDirectories") List additionalDirectories, /** Whether to include the system temp directory in the allowed list (defaults to true). Ignored when `unrestricted` is true. */ @JsonProperty("includeTempDirectory") Boolean includeTempDirectory, diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SandboxConfig.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SandboxConfig.java index cae6b6868f..9194ea9661 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SandboxConfig.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SandboxConfig.java @@ -29,7 +29,7 @@ public record SandboxConfig( @JsonProperty("addCurrentWorkingDirectory") Boolean addCurrentWorkingDirectory, /** Credential-injection capability flags. */ @JsonProperty("auth") SandboxConfigAuth auth, - /** Whether to auto-grant read access to the tool directories discovered on PATH and in toolchain environment variables (GOROOT, CARGO_HOME, JAVA_HOME, VIRTUAL_ENV, and similar), and to common developer-tool caches, registries, and toolchains in their default home locations (cargo, go, npm, Maven, and more), plus read-write access to (and, on Unix, up-front creation of) the scratch caches builds write on every run (go-build, ccache, sccache, Gradle caches, Cargo lock/tracker files), so builds work without extra configuration; a relocated CARGO_HOME additionally gets its Cargo lock files granted read-write. Set to false to disable every grant listed above: user-installed toolchains (rustup, nvm, pyenv, conda, pipx) then need explicit userPolicy.filesystem entries — readonlyPaths to read them, plus readwriteFiles for a relocated CARGO_HOME's .package-cache and .global-cache, which Cargo locks on every build. Only these developer-tool grants are affected: the working directory (see addCurrentWorkingDirectory), temporary storage, session log paths, and system locations follow their own rules and stay granted, so commands still run. Default: true (enabled by default; set to false to opt out). */ + /** Whether to auto-grant read access to the tool directories discovered on PATH and in toolchain environment variables (GOROOT, CARGO_HOME, JAVA_HOME, VIRTUAL_ENV, and similar), and to common developer-tool caches, registries, and toolchains in their default home locations (cargo, go, npm, Maven, and more), plus read-write access to (and up-front creation of) the scratch caches builds write on every run (go-build, ccache, sccache, Gradle caches, Cargo lock/tracker files), so builds work without extra configuration; a relocated CARGO_HOME additionally gets its Cargo lock files granted read-write. Set to false to disable every grant listed above: user-installed toolchains (rustup, nvm, pyenv, conda, pipx) then need explicit userPolicy.filesystem entries — readonlyPaths to read them, plus readwriteFiles for a relocated CARGO_HOME's .package-cache and .global-cache, which Cargo locks on every build. Only these developer-tool grants are affected: the working directory (see addCurrentWorkingDirectory), temporary storage, session log paths, and system locations follow their own rules and stay granted, so commands still run. Default: true (enabled by default; set to false to opt out). */ @JsonProperty("allowDevToolAccess") Boolean allowDevToolAccess ) { } diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SandboxConfigSource.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SandboxConfigSource.java new file mode 100644 index 0000000000..73760b08a2 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SandboxConfigSource.java @@ -0,0 +1,45 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import javax.annotation.processing.Generated; + +/** + * Origin of the sandbox choice supplied by an internal client. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public enum SandboxConfigSource { + /** The {@code never_configured} variant. */ + NEVER_CONFIGURED("never_configured"), + /** The {@code user_enabled} variant. */ + USER_ENABLED("user_enabled"), + /** The {@code user_disabled} variant. */ + USER_DISABLED("user_disabled"), + /** The {@code session_flag} variant. */ + SESSION_FLAG("session_flag"), + /** The {@code session_disabled} variant. */ + SESSION_DISABLED("session_disabled"), + /** The {@code unsupported_host} variant. */ + UNSUPPORTED_HOST("unsupported_host"), + /** The {@code repository_policy} variant. */ + REPOSITORY_POLICY("repository_policy"); + + private final String value; + SandboxConfigSource(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static SandboxConfigSource fromValue(String value) { + for (SandboxConfigSource v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown SandboxConfigSource value: " + value); + } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionInstalledPlugin.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionInstalledPlugin.java index 1109f5f231..db8ea12026 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionInstalledPlugin.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionInstalledPlugin.java @@ -36,6 +36,8 @@ public record SessionInstalledPlugin( /** Source descriptor for direct repo installs (when marketplace is empty) */ @JsonProperty("source") Object source, /** Per-plugin source fingerprint (a SHA-256 hash of the plugin's catalog source spec plus its resolved source subtree — NOT a Git commit SHA) captured at marketplace install/update time. Auto-update compares it against the freshly recomputed fingerprint to detect a content change that does not bump the version. Absent for pre-existing installs and for direct (non-marketplace) installs. */ - @JsonProperty("source_sha") String sourceSha + @JsonProperty("source_sha") String sourceSha, + /** Absolute path of the marketplace directory a live plugin was resolved from. Present only on live, never-persisted records — those synthesized at session start for a directory/local marketplace, whose cache_path points at the real plugin directory on disk rather than a copy under the installed-plugins cache. Its presence is what marks a record as live, and no record carrying it is ever written to the persisted installedPlugins key. */ + @JsonProperty("installed_from") String installedFrom ) { } diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionManagedPermissions.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionManagedPermissions.java index 79698b27c4..8d52a1eb1e 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionManagedPermissions.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionManagedPermissions.java @@ -22,8 +22,8 @@ @JsonInclude(JsonInclude.Include.NON_NULL) @JsonIgnoreProperties(ignoreUnknown = true) public record SessionManagedPermissions( - /** When set to `disable`, prevents bypass/allow-all permission modes. */ - @JsonProperty("disableBypassPermissionsMode") DisableBypassPermissionsMode disableBypassPermissionsMode, + /** When set to `disable`, prevents bypass/allow-all permission modes. `allow-auto-only` blocks full allow-all but permits advisory auto-approval. Any other value is accepted rather than failing the session, but is enforced as `disable`: the key is only present to restrict something, so a mode this runtime cannot interpret fails closed to the most restrictive one it knows. Omit the key entirely to impose no restriction. */ + @JsonProperty("disableBypassPermissionsMode") String disableBypassPermissionsMode, /** Permission rules that block matching operations. Deny has highest precedence. */ @JsonProperty("deny") List deny, /** Permission rules that require explicit human approval. */ 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 002f5e82de..34706a68e1 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 @@ -67,7 +67,7 @@ public record SessionOpenOptions( @JsonProperty("models") List models, /** Working directory to anchor the session. */ @JsonProperty("workingDirectory") String workingDirectory, - /** Additional directories the agent may access beyond the working directory. Each entry is granted to the session's file-access allow-list and surfaced to the model (system prompt context and `@`-mention completion). Absolute paths are recommended; a relative path is resolved against the session's working directory. Nonexistent or unresolvable entries are skipped with a warning. This is applied on both session creation and resume, and is not persisted: a resumed session that omits this option does not retain previously supplied directories (re-supply them, exactly as the CLI re-passes `--add-dir`). */ + /** Additional directories the agent may access beyond the working directory. Each entry is granted to the session's file-access allow-list and surfaced to the model (system prompt context and `@`-mention completion). Conventional `.github/skills/` and `.github/agents/` definitions under each directory also join the session's project catalogs when their existing subsystem gates are enabled: added-root skills require both `enableConfigDiscovery` and effective `enableSkills`; added-root agents require `enableConfigDiscovery`. Supplying a directory therefore activates configuration from it and should be treated as a trust decision. Absolute paths are recommended; a relative path is resolved against the session's working directory. Nonexistent or unresolvable entries are skipped with a warning. This is applied during session creation and cold resume and is not persisted, so a cold resume must re-supply the directories. */ @JsonProperty("additionalDirectories") List additionalDirectories, /** Pre-resolved working-directory context for session startup. */ @JsonProperty("workingDirectoryContext") SessionContext workingDirectoryContext, @@ -99,6 +99,8 @@ public record SessionOpenOptions( @JsonProperty("shellProcessFlags") List shellProcessFlags, /** Resolved sandbox configuration. */ @JsonProperty("sandboxConfig") SandboxConfig sandboxConfig, + /** Origin of the sandbox choice. The runtime uses this only for internal telemetry provenance; managed policy is derived independently. */ + @JsonProperty("sandboxConfigSource") SandboxConfigSource sandboxConfigSource, /** Whether interactive shell sessions are logged. */ @JsonProperty("logInteractiveShells") Boolean logInteractiveShells, /** How MCP server environment values are interpreted. */ @@ -109,6 +111,8 @@ public record SessionOpenOptions( @JsonProperty("allowAllMcpServerInstructions") Boolean allowAllMcpServerInstructions, /** Additional directories to search for skills. */ @JsonProperty("skillDirectories") List skillDirectories, + /** Built-in skill names to include in this session. When specified, only these runtime-bundled skills are available. Skills from other sources with the same name remain available. */ + @JsonProperty("includedBuiltinSkills") List includedBuiltinSkills, /** Skill IDs disabled for this session. */ @JsonProperty("disabledSkills") List disabledSkills, /** Installed plugins visible to the session. */ 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 080b47866b..2e8a069a4b 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 @@ -74,6 +74,8 @@ public record SessionOptionsUpdateParams( @JsonProperty("shellProcessFlags") List shellProcessFlags, /** Resolved sandbox configuration. */ @JsonProperty("sandboxConfig") SandboxConfig sandboxConfig, + /** Origin of the sandbox choice. The runtime uses this only for internal telemetry provenance; managed policy is derived independently. */ + @JsonProperty("sandboxConfigSource") SandboxConfigSource sandboxConfigSource, /** Whether interactive shell sessions are logged. */ @JsonProperty("logInteractiveShells") Boolean logInteractiveShells, /** How env values are passed to MCP servers (`direct` inlines literal values; `indirect` resolves at launch). */ @@ -82,6 +84,8 @@ public record SessionOptionsUpdateParams( @JsonProperty("allowAllMcpServerInstructions") Boolean allowAllMcpServerInstructions, /** Additional directories to search for skills. */ @JsonProperty("skillDirectories") List skillDirectories, + /** Built-in skill names to include in this session. When specified, only these runtime-bundled skills are available. Skills from other sources with the same name remain available. Set to null to remove the allowlist restriction. */ + @JsonProperty("includedBuiltinSkills") List includedBuiltinSkills, /** Skill IDs that should be excluded from this session. */ @JsonProperty("disabledSkills") List disabledSkills, /** Whether to discover custom instructions on demand after successful file views (AGENTS.md / CLAUDE.md / .github/copilot-instructions.md surfacing). Combined with `skipCustomInstructions`. */ diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsPathsAddParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsPathsAddParams.java index e5f35a2264..48ee26e0d9 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsPathsAddParams.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsPathsAddParams.java @@ -26,7 +26,7 @@ public record SessionPermissionsPathsAddParams( /** Target session identifier */ @JsonProperty("sessionId") String sessionId, - /** Directory to add to the allow-list. The runtime resolves and validates the path before adding. */ + /** Directory to add to the allow-list. The runtime resolves and validates the path before adding, then loads conventional `.github/skills/` and `.github/agents/` definitions under it when their subsystem gates are enabled. Adding the directory is therefore also a trust decision for configuration stored there. */ @JsonProperty("path") String path ) { } diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueuePendingItemsResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueuePendingItemsResult.java index 7b096480ca..a74d54e245 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueuePendingItemsResult.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueuePendingItemsResult.java @@ -28,6 +28,8 @@ public record SessionQueuePendingItemsResult( /** Pending queued items in submission order. Includes user messages, queued slash commands, and queued model changes; omits internal system items. */ @JsonProperty("items") List items, /** Display text for messages currently in the immediate steering queue (interjections sent during a running turn). */ - @JsonProperty("steeringMessages") List steeringMessages + @JsonProperty("steeringMessages") List steeringMessages, + /** How many leading entries of `steeringMessages` have already been folded into the running turn (and so have an emitted `user.message`), as opposed to still waiting for one. Absent for hosts that do not distinguish the two. */ + @JsonProperty("inFlightSteeringCount") Long inFlightSteeringCount ) { } diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/TokenAuthInfo.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/TokenAuthInfo.java index a4333a61ab..77f580b862 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/TokenAuthInfo.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/TokenAuthInfo.java @@ -36,6 +36,10 @@ public final class TokenAuthInfo extends AuthInfo { @JsonProperty("token") private String token; + /** Opaque native GitHub credential registration backing this token identity, when applicable. */ + @JsonProperty("registrationId") + private String registrationId; + /** Snapshot of the authenticated user's Copilot subscription info, if known. Mirrors the GitHub API `/copilot_internal/v2/token` user response shape — the runtime trusts this verbatim and does not re-fetch when set. */ @JsonProperty("copilotUser") private CopilotUserResponse copilotUser; @@ -46,6 +50,9 @@ public final class TokenAuthInfo extends AuthInfo { public String getToken() { return token; } public void setToken(String token) { this.token = token; } + public String getRegistrationId() { return registrationId; } + public void setRegistrationId(String registrationId) { this.registrationId = registrationId; } + public CopilotUserResponse getCopilotUser() { return copilotUser; } public void setCopilotUser(CopilotUserResponse copilotUser) { this.copilotUser = copilotUser; } } diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/TokenProviderAuthInfo.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/TokenProviderAuthInfo.java new file mode 100644 index 0000000000..6bdb811e2b --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/TokenProviderAuthInfo.java @@ -0,0 +1,51 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import javax.annotation.processing.Generated; + +/** + * Authentication-info variant backed by an SDK GitHub token callback. It carries routing metadata but never a plaintext token. + * + * @since 1.0.0 + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public final class TokenProviderAuthInfo extends AuthInfo { + + @JsonProperty("type") + private final String type = "token-provider"; + + @Override + public String getType() { return type; } + + /** Authentication host. */ + @JsonProperty("host") + private String host; + + /** Opaque SDK callback registration identifier. */ + @JsonProperty("registrationId") + private String registrationId; + + /** Snapshot of the authenticated user's Copilot subscription info, if known. */ + @JsonProperty("copilotUser") + private CopilotUserResponse copilotUser; + + public String getHost() { return host; } + public void setHost(String host) { this.host = host; } + + public String getRegistrationId() { return registrationId; } + public void setRegistrationId(String registrationId) { this.registrationId = registrationId; } + + public CopilotUserResponse getCopilotUser() { return copilotUser; } + public void setCopilotUser(CopilotUserResponse copilotUser) { this.copilotUser = copilotUser; } +} diff --git a/java/sdk/src/main/java/com/github/copilot/CopilotClient.java b/java/sdk/src/main/java/com/github/copilot/CopilotClient.java index cdd1b9ff3c..fe9a3c3b80 100644 --- a/java/sdk/src/main/java/com/github/copilot/CopilotClient.java +++ b/java/sdk/src/main/java/com/github/copilot/CopilotClient.java @@ -1277,10 +1277,12 @@ CompletableFuture updateSessionOptionsForMode(CopilotSession session, Bool null, // shellInitProfile null, // shellProcessFlags null, // sandboxConfig + null, // sandboxConfigSource null, // logInteractiveShells null, // envValueMode null, // allowAllMcpServerInstructions null, // skillDirectories + null, // includedBuiltinSkills null, // disabledSkills null, // enableOnDemandInstructionDiscovery null, // maxInlineBinaryBytes diff --git a/java/sdk/src/main/java/com/github/copilot/rpc/DisableBypassPermissionsModes.java b/java/sdk/src/main/java/com/github/copilot/rpc/DisableBypassPermissionsModes.java new file mode 100644 index 0000000000..cf98f0526d --- /dev/null +++ b/java/sdk/src/main/java/com/github/copilot/rpc/DisableBypassPermissionsModes.java @@ -0,0 +1,23 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ +package com.github.copilot.rpc; + +/** + * Known values for the managed bypass-permissions policy. + * + *

+ * The wire contract is an open string so callers can pass newer fail-closed + * modes directly to + * {@link ManagedSettingsPermissions#setDisableBypassPermissionsMode(String)}. + */ +public final class DisableBypassPermissionsModes { + /** Turns off bypass-permissions mode. */ + public static final String DISABLE = "disable"; + + /** Permits bypass only for automatic operations. */ + public static final String ALLOW_AUTO_ONLY = "allow-auto-only"; + + private DisableBypassPermissionsModes() { + } +} diff --git a/java/sdk/src/main/java/com/github/copilot/rpc/ManagedSettingsPermissions.java b/java/sdk/src/main/java/com/github/copilot/rpc/ManagedSettingsPermissions.java index 0923cea54a..6755d959b3 100644 --- a/java/sdk/src/main/java/com/github/copilot/rpc/ManagedSettingsPermissions.java +++ b/java/sdk/src/main/java/com/github/copilot/rpc/ManagedSettingsPermissions.java @@ -5,7 +5,6 @@ import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; -import com.github.copilot.generated.rpc.DisableBypassPermissionsMode; import java.util.ArrayList; import java.util.List; @@ -15,7 +14,7 @@ @JsonInclude(JsonInclude.Include.NON_NULL) public final class ManagedSettingsPermissions { @JsonProperty("disableBypassPermissionsMode") - private DisableBypassPermissionsMode disableBypassPermissionsMode; + private String disableBypassPermissionsMode; @JsonProperty("deny") private List deny; @@ -27,18 +26,20 @@ public final class ManagedSettingsPermissions { private List allow; /** @return the bypass-permissions policy, or {@code null} when unset */ - public DisableBypassPermissionsMode getDisableBypassPermissionsMode() { + public String getDisableBypassPermissionsMode() { return disableBypassPermissionsMode; } /** - * Disables bypass/allow-all permission modes. + * Restricts bypass/allow-all permission modes. See + * {@link DisableBypassPermissionsModes} for known values. Newer values are + * forwarded unchanged so runtime policies remain fail-closed. * * @param value * bypass-permissions policy * @return this policy */ - public ManagedSettingsPermissions setDisableBypassPermissionsMode(DisableBypassPermissionsMode value) { + public ManagedSettingsPermissions setDisableBypassPermissionsMode(String value) { this.disableBypassPermissionsMode = value; return this; } diff --git a/java/sdk/src/main/java/com/github/copilot/rpc/McpStdioServerConfig.java b/java/sdk/src/main/java/com/github/copilot/rpc/McpStdioServerConfig.java index 8ce739ffbd..c68aecb63f 100644 --- a/java/sdk/src/main/java/com/github/copilot/rpc/McpStdioServerConfig.java +++ b/java/sdk/src/main/java/com/github/copilot/rpc/McpStdioServerConfig.java @@ -45,7 +45,7 @@ public final class McpStdioServerConfig extends McpServerConfig { @JsonProperty("env") private Map env; - @JsonProperty("workingDirectory") + @JsonProperty("cwd") private String workingDirectory; /** diff --git a/java/sdk/src/test/java/com/github/copilot/DataObjectCoverageTest.java b/java/sdk/src/test/java/com/github/copilot/DataObjectCoverageTest.java index f95c5bcc57..c0b0324e3c 100644 --- a/java/sdk/src/test/java/com/github/copilot/DataObjectCoverageTest.java +++ b/java/sdk/src/test/java/com/github/copilot/DataObjectCoverageTest.java @@ -8,6 +8,7 @@ import org.junit.jupiter.api.Test; +import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.JsonNodeFactory; import com.fasterxml.jackson.databind.node.ObjectNode; import com.github.copilot.rpc.CustomAgentConfig; @@ -324,6 +325,15 @@ void mcpStdioServerConfigCoversGettersAndFluentSetters() { assertEquals(30, cfg.getTimeout()); } + @Test + void mcpStdioServerConfigSerializesWorkingDirectoryAsCwd() { + var json = new ObjectMapper() + .valueToTree(new McpStdioServerConfig().setCommand("node").setWorkingDirectory("/workspace")); + + assertEquals("/workspace", json.path("cwd").asText()); + assertFalse(json.has("workingDirectory")); + } + @Test void modelCapabilitiesOverrideCoversNestedSupportsAndLimits() { var supports = new ModelCapabilitiesOverride.Supports().setVision(true).setReasoningEffort(false); diff --git a/java/sdk/src/test/java/com/github/copilot/ManagedSettingsTest.java b/java/sdk/src/test/java/com/github/copilot/ManagedSettingsTest.java index dbd19f3c97..d6341b26c5 100644 --- a/java/sdk/src/test/java/com/github/copilot/ManagedSettingsTest.java +++ b/java/sdk/src/test/java/com/github/copilot/ManagedSettingsTest.java @@ -8,7 +8,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import com.fasterxml.jackson.databind.ObjectMapper; -import com.github.copilot.generated.rpc.DisableBypassPermissionsMode; +import com.github.copilot.rpc.DisableBypassPermissionsModes; import com.github.copilot.rpc.ManagedSettings; import com.github.copilot.rpc.ManagedSettingsPermissions; import com.github.copilot.rpc.PermissionRequestResult; @@ -23,7 +23,7 @@ class ManagedSettingsTest { @Test void forwardsManagedSettingsOnCreateAndResume() throws Exception { var permissions = new ManagedSettingsPermissions() - .setDisableBypassPermissionsMode(DisableBypassPermissionsMode.DISABLE).setDeny(List.of("Shell(rm *)")) + .setDisableBypassPermissionsMode(DisableBypassPermissionsModes.DISABLE).setDeny(List.of("Shell(rm *)")) .setAsk(List.of("Domain(publish.example)")).setAllow(List.of("Read(**)")); var managedSettings = new ManagedSettings().setPermissions(permissions); @@ -41,6 +41,14 @@ void forwardsManagedSettingsOnCreateAndResume() throws Exception { assertTrue(json.contains("\"disableBypassPermissionsMode\":\"disable\"")); } + @Test + void acceptsFutureBypassPermissionsModes() throws Exception { + var permissions = new ManagedSettingsPermissions().setDisableBypassPermissionsMode("future-fail-closed-mode"); + var json = new ObjectMapper().writeValueAsString(permissions); + + assertTrue(json.contains("\"disableBypassPermissionsMode\":\"future-fail-closed-mode\"")); + } + @Test void preservesExplicitEmptyPermissionArrays() throws Exception { // Security-critical: a present empty allow list admits nothing, while an 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 bd38d4962e..47d537f134 100644 --- a/java/sdk/src/test/java/com/github/copilot/SessionEventHandlingTest.java +++ b/java/sdk/src/test/java/com/github/copilot/SessionEventHandlingTest.java @@ -865,7 +865,7 @@ private SessionStartEvent createSessionStartEvent(String sessionId) { private AssistantMessageEvent createAssistantMessageEvent(String content) { var event = new AssistantMessageEvent(); var data = new AssistantMessageEvent.AssistantMessageEventData(null, null, content, null, null, null, null, - null, null, null, null, null, null, null, null, null, null, null, null, null, null, null); + null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null); event.setData(data); return event; } 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 cf5b0426c5..602089d012 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 @@ -818,7 +818,8 @@ void modelsListResult_nested() { var policy = new ModelPolicy(ModelPolicyState.ENABLED, null); var promo = new ModelBillingPromo("summer-2026", 25.0, "2026-08-01T00:00:00Z", "Summer discount"); var billing = new ModelBilling(1.0, null, null, promo); - var modelItem = new Model("gpt-5", "GPT-5", capabilities, policy, billing, null, null, null, null, null); + var modelItem = new Model("gpt-5", "GPT-5", capabilities, policy, billing, null, null, null, null, null, null, + null, null); var result = new ModelsListResult(List.of(modelItem)); assertEquals(1, result.models().size()); diff --git a/nodejs/package-lock.json b/nodejs/package-lock.json index b17b6f55b3..c931069940 100644 --- a/nodejs/package-lock.json +++ b/nodejs/package-lock.json @@ -9,7 +9,7 @@ "version": "0.0.0-dev", "license": "MIT", "dependencies": { - "@github/copilot": "^1.0.81-6", + "@github/copilot": "^1.0.81-10", "koffi": "^3.1.0", "vscode-jsonrpc": "^8.2.1", "zod": "^4.3.6" @@ -658,8 +658,8 @@ } }, "node_modules/@github/copilot": { - "version": "1.0.81-6", - "integrity": "sha512-hT29nRkf0EJE3N6lqeLOPszbdEyALZ+fjYG9zKX5a3L5r+o+m4/KF+8l2gn2yORNqOzwUYNj2vnVzKqeYYNLGg==", + "version": "1.0.81-10", + "integrity": "sha512-Ac99EvN16s4hKRhJLSEn1HMNaZ6MD8BzIey1zzJNBQy1/yP4PQDZ2CWitEq+XQQEi+6SsqeJRqXOKiWk1EyK7g==", "license": "SEE LICENSE IN LICENSE.md", "dependencies": { "detect-libc": "^2.1.2" @@ -668,19 +668,19 @@ "copilot": "npm-loader.js" }, "optionalDependencies": { - "@github/copilot-darwin-arm64": "1.0.81-6", - "@github/copilot-darwin-x64": "1.0.81-6", - "@github/copilot-linux-arm64": "1.0.81-6", - "@github/copilot-linux-x64": "1.0.81-6", - "@github/copilot-linuxmusl-arm64": "1.0.81-6", - "@github/copilot-linuxmusl-x64": "1.0.81-6", - "@github/copilot-win32-arm64": "1.0.81-6", - "@github/copilot-win32-x64": "1.0.81-6" + "@github/copilot-darwin-arm64": "1.0.81-10", + "@github/copilot-darwin-x64": "1.0.81-10", + "@github/copilot-linux-arm64": "1.0.81-10", + "@github/copilot-linux-x64": "1.0.81-10", + "@github/copilot-linuxmusl-arm64": "1.0.81-10", + "@github/copilot-linuxmusl-x64": "1.0.81-10", + "@github/copilot-win32-arm64": "1.0.81-10", + "@github/copilot-win32-x64": "1.0.81-10" } }, "node_modules/@github/copilot-darwin-arm64": { - "version": "1.0.81-6", - "integrity": "sha512-nALa4e8Jc/g5ltIHrpHBHByJ5rlgzoZFylZIrkQY+B9vr3L57d5F6fOiTbf/OF9blFQX7artWRE1K0TmowGNCA==", + "version": "1.0.81-10", + "integrity": "sha512-s90Av0iwjTSU6Gky8T9wI1PJdlfbdUcPAVgKDtimaOiAwcdLG4fKTpGxrk96KJrnOHHK3x9SiXsw/pW0ThAH/A==", "cpu": [ "arm64" ], @@ -694,8 +694,8 @@ } }, "node_modules/@github/copilot-darwin-x64": { - "version": "1.0.81-6", - "integrity": "sha512-K+bp799DejrsmxMNyaFAmKo4xnLJXBb8hkv9N8OCQukmTSoRpfqhv2oTDfgVFadwllt+py/FIdxKTZQYvPGGGw==", + "version": "1.0.81-10", + "integrity": "sha512-8RnPI4J311oJQ0GPB6JxuLJq4JNY/KF9ZIIQm8KpxXBY6d+6fmmAsMDEk7OiF/Asl2I7+LTi+qU2ZVhP7FYhbg==", "cpu": [ "x64" ], @@ -709,8 +709,8 @@ } }, "node_modules/@github/copilot-linux-arm64": { - "version": "1.0.81-6", - "integrity": "sha512-aEpnfTTjOxpesFo9jqk/phZUivOhNHbdBRfBrS2NiCPrQZFBYUC4wRVo/Xo2PMMQ4J07b6fU7JJQPoUUkKy5Wg==", + "version": "1.0.81-10", + "integrity": "sha512-2UtK5CBrE6ZVSIzU2KHeIgO8N7056axjbF2lE6WuK+H+oJJ4v3w5eQkalqGzRHhkaPfCW4kT1lDMhZFW+XbLjA==", "cpu": [ "arm64" ], @@ -724,8 +724,8 @@ } }, "node_modules/@github/copilot-linux-x64": { - "version": "1.0.81-6", - "integrity": "sha512-NFqonFfJCyA7d3bNoYeLWUQ69zelPr9TTnLpAHCi3scFZqbEvMBDFxW2XsKWwfYuuR9XzfU7/tgOUgq3gmL5aA==", + "version": "1.0.81-10", + "integrity": "sha512-61+KAfo1TBARrBfss3w4dfmRVSf0PiFg0c9JNuT9HjoNnytl7maJBPEgUvI4YBcxScNEAlCMXaUUG3Tuuh1g+w==", "cpu": [ "x64" ], @@ -739,8 +739,8 @@ } }, "node_modules/@github/copilot-linuxmusl-arm64": { - "version": "1.0.81-6", - "integrity": "sha512-EE99DFTAgTq6eOFDiiv+OUROD2pDQIrzyyJEfUS9K8JanwNc+Py8vTxrJ0yK0slpJ+Fue5uDRno6c9ys8OM59g==", + "version": "1.0.81-10", + "integrity": "sha512-CR6KRPCFoGkaD8I2an1FyrT5avF1U5aTbwW2sYCP7w1KExYFknxEL8ES6BkFuPEA7YcjmLa0SOq26Z+TgIVHSg==", "cpu": [ "arm64" ], @@ -754,8 +754,8 @@ } }, "node_modules/@github/copilot-linuxmusl-x64": { - "version": "1.0.81-6", - "integrity": "sha512-90HRKx25EjhlQNOCCdbiC0Ck0fSKyp8XUxUoCvuNdoigdUP54ZGS07dkyJiJq9KZaKilxZBSpXiNt8t5ETA2Sg==", + "version": "1.0.81-10", + "integrity": "sha512-fvZfyEOfRkvUDPXY6UUjAqV8Mkf08PQV+jgtiAFUryuas5VP9cYaAmQSmNpzNMNi3kSX/ycUJe7oc3zXZ8ylog==", "cpu": [ "x64" ], @@ -769,8 +769,8 @@ } }, "node_modules/@github/copilot-win32-arm64": { - "version": "1.0.81-6", - "integrity": "sha512-1dRSHF/7PFzB+AGORg8BJh2n1+N7+sIJDLqozrC9INWFp1t6ercptIXgJTF/V7UZVGQLO4LBBK1HH/QhzUmrfA==", + "version": "1.0.81-10", + "integrity": "sha512-n30PPBgCT4Iq9MgH6is6L3eUEE+sF6xB2fb+dGsclj5j/hCkT7+ef0j8YcAGipsvGfzGAuywIsWlvF7fzYsOKQ==", "cpu": [ "arm64" ], @@ -784,8 +784,8 @@ } }, "node_modules/@github/copilot-win32-x64": { - "version": "1.0.81-6", - "integrity": "sha512-lIbN1mk6Rm9bWrWU4/UfrC5OCga7XcBi2LBz5roDnNcuG8mKEevDcNOtbEYz/TaJg+WBMoTbGfXBVd1hGy2DTA==", + "version": "1.0.81-10", + "integrity": "sha512-lb8kvhrXwGCN3LeRDQfLHsUp+F43XvPYznaYK1sPtK1kFGa4/kL690tasoSEvzu8ZKoTY6kZ6YmDbUZgqOislw==", "cpu": [ "x64" ], diff --git a/nodejs/package.json b/nodejs/package.json index 1d41026534..de507419fe 100644 --- a/nodejs/package.json +++ b/nodejs/package.json @@ -56,7 +56,7 @@ "author": "GitHub", "license": "MIT", "dependencies": { - "@github/copilot": "^1.0.81-6", + "@github/copilot": "^1.0.81-10", "koffi": "^3.1.0", "vscode-jsonrpc": "^8.2.1", "zod": "^4.3.6" diff --git a/nodejs/samples/package-lock.json b/nodejs/samples/package-lock.json index 62199ea95a..2b055e025b 100644 --- a/nodejs/samples/package-lock.json +++ b/nodejs/samples/package-lock.json @@ -18,7 +18,7 @@ "version": "0.0.0-dev", "license": "MIT", "dependencies": { - "@github/copilot": "^1.0.81-6", + "@github/copilot": "^1.0.81-10", "koffi": "^3.1.0", "vscode-jsonrpc": "^8.2.1", "zod": "^4.3.6" diff --git a/nodejs/src/generated/rpc.ts b/nodejs/src/generated/rpc.ts index 0174e476bf..65b7c3701a 100644 --- a/nodejs/src/generated/rpc.ts +++ b/nodejs/src/generated/rpc.ts @@ -28,6 +28,7 @@ export type AuthInfo = | HMACAuthInfo | EnvAuthInfo | TokenAuthInfo + | TokenProviderAuthInfo | CopilotApiTokenAuthInfo | UserAuthInfo | GhCliAuthInfo @@ -263,6 +264,8 @@ export type AuthInfoType = | "api-key" /** Authentication from a GitHub token. */ | "token" + /** Authentication from an SDK GitHub token callback. */ + | "token-provider" /** Authentication from a Copilot API token. */ | "copilot-api-token"; /** @@ -834,9 +837,6 @@ export type DebugCollectLogsResultKind = | "archive" /** A directory containing redacted files was written. */ | "directory"; - -/** @experimental */ -export type DisableBypassPermissionsMode = "disable"; /** * Persisted extension discovery source * @@ -1182,6 +1182,50 @@ export type FilterMapping = [k: string]: ContentFilterMode; } | ContentFilterMode; +/** + * Why the runtime is requesting a GitHub credential. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "GitHubTokenAcquireReason". + */ +/** @experimental */ +export type GitHubTokenAcquireReason = + /** The runtime is acquiring the registration's first credential. */ + | "initial" + /** The runtime is replacing a credential that is approaching expiry. */ + | "refresh"; +/** + * SDK host response to a GitHub credential request. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "GitHubTokenAcquireResult". + */ +/** @experimental */ +export type GitHubTokenAcquireResult = + | { + /** + * GitHub access token acquired by the SDK host. + */ + accessToken: string; + /** + * OAuth token type. Defaults to bearer when omitted. + */ + tokenType?: string; + /** + * Remaining token lifetime in seconds when callback execution completes. It must exceed the one-hour preflight refresh threshold. + */ + expiresIn: number; + /** + * GitHub credential response variant discriminator. + */ + kind: "token"; + } + | { + /** + * GitHub credential response variant discriminator. + */ + kind: "cancelled"; + }; /** * Optional compaction parameters. * @@ -1843,7 +1887,7 @@ export type McpOauthPendingRequestResponse = */ accessToken: string; /** - * OAuth token type. Defaults to Bearer when omitted. + * OAuth token type. Defaults to bearer when omitted. */ tokenType?: string; /** @@ -2828,6 +2872,29 @@ export type RemoteSessionMetadataTaskType = | "cca" /** CLI remote task. */ | "cli"; +/** + * Origin of the sandbox choice supplied by an internal client. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "SandboxConfigSource". + */ +/** @experimental */ +/** @internal */ +export type SandboxConfigSource = + /** The client applied the default because no sandbox preference was configured. */ + | "never_configured" + /** The user's persisted settings enabled the sandbox. */ + | "user_enabled" + /** The user's persisted settings disabled the sandbox. */ + | "user_disabled" + /** A command-line flag selected the sandbox state for this session. */ + | "session_flag" + /** The user disabled the sandbox for the current session. */ + | "session_disabled" + /** The client disabled the sandbox because the host cannot enforce it. */ + | "unsupported_host" + /** A repository policy selected the sandbox state. */ + | "repository_policy"; /** * Current authentication information, or null when no authentication is active. * @@ -3241,6 +3308,21 @@ export type SessionsOpenProgressStatus = | "in-progress" /** The step has completed successfully. */ | "complete"; +/** + * Authentication credentials accepted by session.gitHubAuth.setCredentials. Session-owned token-provider identities cannot be installed through this method. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "SettableAuthInfo". + */ +/** @experimental */ +export type SettableAuthInfo = + | HMACAuthInfo + | EnvAuthInfo + | SettableTokenAuthInfo + | CopilotApiTokenAuthInfo + | UserAuthInfo + | GhCliAuthInfo + | ApiKeyAuthInfo; /** * Rust-owned settings predicates exposed across the SDK boundary. Raw feature-flag names are intentionally not part of the contract. * @@ -4181,6 +4263,32 @@ export interface TokenAuthInfo { * The token value itself. Treat as a secret. */ token: string; + /** + * Opaque native GitHub credential registration backing this token identity, when applicable. + */ + registrationId?: string; + copilotUser?: CopilotUserResponse; +} +/** + * Authentication-info variant backed by an SDK GitHub token callback. It carries routing metadata but never a plaintext token. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "TokenProviderAuthInfo". + */ +/** @experimental */ +export interface TokenProviderAuthInfo { + /** + * SDK callback-backed GitHub token authentication. + */ + type: "token-provider"; + /** + * Authentication host. + */ + host: string; + /** + * Opaque SDK callback registration identifier. + */ + registrationId: string; copilotUser?: CopilotUserResponse; } /** @@ -4831,6 +4939,10 @@ export interface AuthIdentity { * Name of the environment variable that supplied the credential, when applicable */ envVar?: string; + /** + * Opaque SDK GitHub credential registration backing this identity. Routing metadata only; never a credential. + */ + registrationId?: string; copilotUser?: CopilotUserResponse; } /** @@ -6199,6 +6311,32 @@ export interface ConfigureSessionExtensionsParams { */ controller?: OpaqueInProcessValue; } +/** + * Identity of the integrating host, declared once on the `server.connect` handshake so telemetry from this connection is attributed to a single, consistent surface. All fields are optional; omit them to keep the default attribution. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "ConnectClientInfo". + */ +/** @experimental */ +/** @internal */ +export interface ConnectClientInfo { + /** + * Name of the host editor, e.g. `"vscode"`. + */ + editorName?: string; + /** + * Version of the host editor, e.g. `"1.124.2"`. Ignored unless it looks like a version string. + */ + editorVersion?: string; + /** + * Name of the Copilot extension within the host, e.g. `"copilot-chat"`. + */ + extensionName?: string; + /** + * Version of the Copilot extension within the host, e.g. `"0.54.0"`. Ignored unless it looks like a version string. + */ + extensionVersion?: string; +} /** * Metadata for a connected remote session. * @@ -6293,6 +6431,7 @@ export interface ConnectRequest { * Opt this connection in to GitHub telemetry forwarding for its lifetime. When set, the runtime forwards every internal telemetry event it emits — across all sessions, plus sessionless events — to this connection over the `gitHubTelemetry.event` notification. Regular events are also written to the runtime's normal GitHub/CTS path (dual-write); host-only compatibility events are forward-only and intentionally skip that path. Intended for first-party hosts that re-emit the events into their own telemetry stores. Both unrestricted and restricted events are forwarded, each tagged with a `restricted` discriminator; a backstop drops restricted events when restricted telemetry is disabled — using the process-global gate for ordinary events and an explicit session-scoped decision for host-only events. */ enableGitHubTelemetryForwarding?: boolean; + clientInfo?: ConnectClientInfo; /** * Connection token; required when the server was started with COPILOT_CONNECTION_TOKEN */ @@ -8304,6 +8443,28 @@ export interface GitHubTelemetryNotification { restricted: boolean; event: GitHubTelemetryEvent; } +/** + * Asks the SDK client to acquire a GitHub access token from an opaque callback registration. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "GitHubTokenAcquireRequest". + */ +/** @experimental */ +export interface GitHubTokenAcquireRequest { + /** + * Opaque identifier generated by the SDK for this callback registration. + */ + registrationId: string; + /** + * Effective GitHub host for which the callback must return a token. + */ + host: string; + /** + * Session receiving the token. Absent only before a cloud session has been assigned its id. + */ + sessionId?: string; + reason: GitHubTokenAcquireReason; +} /** * Pending external tool call request ID, with the tool result or an error describing why it failed. * @@ -8728,6 +8889,10 @@ export interface InstalledPlugin { * Per-plugin source fingerprint (a SHA-256 hash of the plugin's catalog source spec plus its resolved source subtree — NOT a Git commit SHA) captured at marketplace install/update time. Auto-update compares it against the freshly recomputed fingerprint to detect a content change that does not bump the version. Absent for pre-existing installs and for direct (non-marketplace) installs. */ source_sha?: string; + /** + * Absolute path of the marketplace directory a live plugin was resolved from. Present only on live, never-persisted records — those synthesized at session start for a directory/local marketplace, whose cache_path points at the real plugin directory on disk rather than a copy under the installed-plugins cache. Its presence is what marks a record as live, and no record carrying it is ever written to the persisted installedPlugins key. + */ + installed_from?: string; } /** * Source descriptor for a direct GitHub plugin install, with `owner/repo`, optional ref or full commit SHA, and optional subpath. @@ -8832,6 +8997,10 @@ export interface InstalledPluginInfo { * Whether the plugin is currently enabled for new sessions */ enabled: boolean; + /** + * Absolute path of the marketplace directory a live plugin was resolved from. Present only on live, never-persisted records — a plugin belonging to a directory/local marketplace, which is loaded from its real directory on every pass instead of a copy under the installed-plugins cache. Its presence is what marks a listed plugin as live: such a plugin is always present on disk, so `enabled` is its only meaningful state and it is never "not installed". + */ + installedFrom?: string; } /** * Canonical file or directory where custom instructions can be discovered or created, with location, kind, preference, and project path. @@ -11851,6 +12020,15 @@ export interface Model { supportedContextTiers?: string[]; modelPickerCategory?: ModelPickerCategory; modelPickerPriceCategory?: ModelPickerPriceCategory; + warningText?: ModelWarningText; + /** + * Informational notices the service published for this model, such as an upcoming change or a recommended alternative. Present only when the service published at least one notice. Hosts should surface these without implying anything is wrong with the model. + */ + infoMessages?: ModelMessage[]; + /** + * Warnings the service published for this model, such as a deprecated client version. Present only when the service published at least one warning. The model remains usable; hosts should surface these as advisory rather than blocking. + */ + warningMessages?: ModelMessage[]; } /** * Model capabilities and limits @@ -12073,6 +12251,36 @@ export interface ModelBillingPromo { */ message?: string; } +/** + * Service-published warning text that hosts should display when presenting a model. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "ModelWarningText". + */ +/** @experimental */ +export interface ModelWarningText { + /** + * Data-retention warning for the model. The text may contain Markdown links and should be rendered as Markdown when supported. + */ + dataRetention?: string; +} +/** + * A service-published message about a model, carrying a stable machine-readable code alongside human-readable text. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "ModelMessage". + */ +/** @experimental */ +export interface ModelMessage { + /** + * Stable machine-readable identifier for the message, such as `client_version_deprecated`. Hosts can key custom presentation off this; unrecognized codes should fall back to displaying `message`. + */ + code: string; + /** + * Human-readable message text intended for display to the user. + */ + message: string; +} /** * Managed, repository, and CLI model overrides to overlay onto the session at startup. * @@ -13584,7 +13792,7 @@ export interface PermissionLocationResolveResult { /** @experimental */ export interface PermissionPathsAddParams { /** - * Directory to add to the allow-list. The runtime resolves and validates the path before adding. + * Directory to add to the allow-list. The runtime resolves and validates the path before adding, then loads conventional `.github/skills/` and `.github/agents/` definitions under it when their subsystem gates are enabled. Adding the directory is therefore also a trust decision for configuration stored there. */ path: string; } @@ -13627,7 +13835,7 @@ export interface PermissionPathsConfig { */ unrestricted?: boolean; /** - * Additional directories to allow tool access to (in addition to the session's working directory). When `unrestricted` is true, these are still pre-populated on the UnrestrictedPathManager so they remain visible via getDirectories() (e.g. for @-mention completion). + * Additional directories to allow tool access to (in addition to the session's working directory). Conventional `.github/skills/` and `.github/agents/` definitions under them also join the session catalogs when their subsystem gates are enabled, so supplying a directory is a trust decision for configuration stored there. When `unrestricted` is true, these are still pre-populated on the UnrestrictedPathManager so they remain visible via getDirectories() (e.g. for @-mention completion). */ additionalDirectories?: string[]; /** @@ -15550,6 +15758,10 @@ export interface QueuePendingItemsResult { * Display text for messages currently in the immediate steering queue (interjections sent during a running turn). */ steeringMessages: string[]; + /** + * How many leading entries of `steeringMessages` have already been folded into the running turn (and so have an emitted `user.message`), as opposed to still waiting for one. Absent for hosts that do not distinguish the two. + */ + inFlightSteeringCount?: number; } /** * Parameters for removing a queued item by stable id. @@ -16140,7 +16352,7 @@ export interface SandboxConfig { addCurrentWorkingDirectory?: boolean; auth?: SandboxConfigAuth; /** - * Whether to auto-grant read access to the tool directories discovered on PATH and in toolchain environment variables (GOROOT, CARGO_HOME, JAVA_HOME, VIRTUAL_ENV, and similar), and to common developer-tool caches, registries, and toolchains in their default home locations (cargo, go, npm, Maven, and more), plus read-write access to (and, on Unix, up-front creation of) the scratch caches builds write on every run (go-build, ccache, sccache, Gradle caches, Cargo lock/tracker files), so builds work without extra configuration; a relocated CARGO_HOME additionally gets its Cargo lock files granted read-write. Set to false to disable every grant listed above: user-installed toolchains (rustup, nvm, pyenv, conda, pipx) then need explicit userPolicy.filesystem entries — readonlyPaths to read them, plus readwriteFiles for a relocated CARGO_HOME's .package-cache and .global-cache, which Cargo locks on every build. Only these developer-tool grants are affected: the working directory (see addCurrentWorkingDirectory), temporary storage, session log paths, and system locations follow their own rules and stay granted, so commands still run. Default: true (enabled by default; set to false to opt out). + * Whether to auto-grant read access to the tool directories discovered on PATH and in toolchain environment variables (GOROOT, CARGO_HOME, JAVA_HOME, VIRTUAL_ENV, and similar), and to common developer-tool caches, registries, and toolchains in their default home locations (cargo, go, npm, Maven, and more), plus read-write access to (and up-front creation of) the scratch caches builds write on every run (go-build, ccache, sccache, Gradle caches, Cargo lock/tracker files), so builds work without extra configuration; a relocated CARGO_HOME additionally gets its Cargo lock files granted read-write. Set to false to disable every grant listed above: user-installed toolchains (rustup, nvm, pyenv, conda, pipx) then need explicit userPolicy.filesystem entries — readonlyPaths to read them, plus readwriteFiles for a relocated CARGO_HOME's .package-cache and .global-cache, which Cargo locks on every build. Only these developer-tool grants are affected: the working directory (see addCurrentWorkingDirectory), temporary storage, session log paths, and system locations follow their own rules and stay granted, so commands still run. Default: true (enabled by default; set to false to opt out). */ allowDevToolAccess?: boolean; } @@ -17462,6 +17674,10 @@ export interface SessionInstalledPlugin { * Per-plugin source fingerprint (a SHA-256 hash of the plugin's catalog source spec plus its resolved source subtree — NOT a Git commit SHA) captured at marketplace install/update time. Auto-update compares it against the freshly recomputed fingerprint to detect a content change that does not bump the version. Absent for pre-existing installs and for direct (non-marketplace) installs. */ source_sha?: string; + /** + * Absolute path of the marketplace directory a live plugin was resolved from. Present only on live, never-persisted records — those synthesized at session start for a directory/local marketplace, whose cache_path points at the real plugin directory on disk rather than a copy under the installed-plugins cache. Its presence is what marks a record as live, and no record carrying it is ever written to the persisted installedPlugins key. + */ + installed_from?: string; } /** * Source descriptor for a direct GitHub plugin install, with `owner/repo`, optional ref or full commit SHA, and optional subpath. @@ -17665,7 +17881,10 @@ export interface SessionLoadDeferredRepoHooksResult { */ /** @experimental */ export interface SessionManagedPermissions { - disableBypassPermissionsMode?: DisableBypassPermissionsMode; + /** + * When set to `disable`, prevents bypass/allow-all permission modes. `allow-auto-only` blocks full allow-all but permits advisory auto-approval. Any other value is accepted rather than failing the session, but is enforced as `disable`: the key is only present to restrict something, so a mode this runtime cannot interpret fails closed to the most restrictive one it knows. Omit the key entirely to impose no restriction. + */ + disableBypassPermissionsMode?: string; /** * Permission rules that block matching operations. Deny has highest precedence. */ @@ -17876,7 +18095,7 @@ export interface SessionOpenOptions { */ workingDirectory?: string; /** - * Additional directories the agent may access beyond the working directory. Each entry is granted to the session's file-access allow-list and surfaced to the model (system prompt context and `@`-mention completion). Absolute paths are recommended; a relative path is resolved against the session's working directory. Nonexistent or unresolvable entries are skipped with a warning. This is applied on both session creation and resume, and is not persisted: a resumed session that omits this option does not retain previously supplied directories (re-supply them, exactly as the CLI re-passes `--add-dir`). + * Additional directories the agent may access beyond the working directory. Each entry is granted to the session's file-access allow-list and surfaced to the model (system prompt context and `@`-mention completion). Conventional `.github/skills/` and `.github/agents/` definitions under each directory also join the session's project catalogs when their existing subsystem gates are enabled: added-root skills require both `enableConfigDiscovery` and effective `enableSkills`; added-root agents require `enableConfigDiscovery`. Supplying a directory therefore activates configuration from it and should be treated as a trust decision. Absolute paths are recommended; a relative path is resolved against the session's working directory. Nonexistent or unresolvable entries are skipped with a warning. This is applied during session creation and cold resume and is not persisted, so a cold resume must re-supply the directories. */ additionalDirectories?: string[]; workingDirectoryContext?: SessionContext; @@ -17931,6 +18150,12 @@ export interface SessionOpenOptions { */ shellProcessFlags?: string[]; sandboxConfig?: SandboxConfig; + /** + * Origin of the sandbox choice. The runtime uses this only for internal telemetry provenance; managed policy is derived independently. + * + * @internal + */ + sandboxConfigSource?: SandboxConfigSource; /** * Whether interactive shell sessions are logged. */ @@ -17948,6 +18173,10 @@ export interface SessionOpenOptions { * Additional directories to search for skills. */ skillDirectories?: string[]; + /** + * Built-in skill names to include in this session. When specified, only these runtime-bundled skills are available. Skills from other sources with the same name remain available. + */ + includedBuiltinSkills?: string[]; /** * Skill IDs disabled for this session. */ @@ -18514,7 +18743,29 @@ export interface SessionsEnrichMetadataRequest { */ /** @experimental */ export interface SessionSetCredentialsParams { - credentials?: AuthInfo; + credentials?: SettableAuthInfo; +} +/** + * Token authentication accepted by session.gitHubAuth.setCredentials. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "SettableTokenAuthInfo". + */ +/** @experimental */ +export interface SettableTokenAuthInfo { + /** + * SDK-side token authentication; the host configured the token directly via the SDK. + */ + type: "token"; + /** + * Authentication host. + */ + host: string; + /** + * The token value itself. Treat as a secret. + */ + token: string; + copilotUser?: CopilotUserResponse; } /** * Indicates whether the credential update succeeded. @@ -19321,6 +19572,12 @@ export interface SessionUpdateOptionsParams { */ shellProcessFlags?: string[]; sandboxConfig?: SandboxConfig; + /** + * Origin of the sandbox choice. The runtime uses this only for internal telemetry provenance; managed policy is derived independently. + * + * @internal + */ + sandboxConfigSource?: SandboxConfigSource; /** * Whether interactive shell sessions are logged. */ @@ -19334,6 +19591,10 @@ export interface SessionUpdateOptionsParams { * Additional directories to search for skills. */ skillDirectories?: string[]; + /** + * Built-in skill names to include in this session. When specified, only these runtime-bundled skills are available. Skills from other sources with the same name remain available. Set to null to remove the allowlist restriction. + */ + includedBuiltinSkills?: string[] | null; /** * Skill IDs that should be excluded from this session. */ @@ -24645,7 +24906,7 @@ export function createSessionRpc(connection: MessageConnection, sessionId: strin list: async (): Promise => connection.sendRequest("session.permissions.paths.list", { sessionId }), /** - * Adds a directory to the session's allow-list. + * Adds a directory to the session's allow-list and activates conventional skill and agent definitions under it. * * @param params Directory path to add to the session's allowed directories. * @@ -25801,11 +26062,25 @@ export interface GitHubTelemetryHandler { event(params: GitHubTelemetryNotification): Promise; } +/** Handler for `gitHubToken` client global API methods. */ +/** @experimental */ +export interface GitHubTokenHandler { + /** + * Asks the SDK client to mint a GitHub access token for a session whose configuration supplied a GitHub token provider. The runtime acquires the initial token during bootstrap and refreshes it during expiry preflight when one hour or less remains. + * + * @param params Asks the SDK client to acquire a GitHub access token from an opaque callback registration. + * + * @returns SDK host response to a GitHub credential request. + */ + getToken(params: GitHubTokenAcquireRequest): Promise; +} + /** All client global API handler groups. */ export interface ClientGlobalApiHandlers { extensionLaunchProvider?: ExtensionLaunchProviderHandler; llmInference?: LlmInferenceHandler; gitHubTelemetry?: GitHubTelemetryHandler; + gitHubToken?: GitHubTokenHandler; } /** @@ -25839,4 +26114,9 @@ export function registerClientGlobalApiHandlers( if (!handler) return; await handler.event(params); }); + connection.onRequest("gitHubToken.getToken", async (params: GitHubTokenAcquireRequest) => { + const handler = handlers.gitHubToken; + if (!handler) throw new Error("No gitHubToken client-global handler registered"); + return handler.getToken(params); + }); } diff --git a/nodejs/src/generated/session-events.ts b/nodejs/src/generated/session-events.ts index fdb82ab14e..3ec55aacda 100644 --- a/nodejs/src/generated/session-events.ts +++ b/nodejs/src/generated/session-events.ts @@ -56,6 +56,7 @@ export type SessionEvent = | AssistantIdleEvent | AssistantUsageEvent | ModelCallFailureEvent + | ModelCallFinishedEvent | AbortEvent | ToolUserRequestedEvent | ToolExecutionStartEvent @@ -434,6 +435,18 @@ export type ModelCallFailureTransport = | "http" /** WebSocket transport. */ | "websocket"; +/** + * Final outcome of one logical model dispatch after response acceptance processing + */ +export type ModelCallFinishedOutcome = + /** The provider response was accepted for continued agent processing. */ + | "success" + /** The dispatch ended with a provider or transport error. */ + | "error" + /** The dispatch was cancelled before an accepted response was produced. */ + | "cancelled" + /** The provider response was rejected during post-response acceptance processing. */ + | "rejected"; /** * Finite reason code describing why the current turn was aborted */ @@ -855,7 +868,9 @@ export type ManagedSettingsEnforcedEscalation = /** Unrestricted filesystem access outside the session's allowed directories. */ | "unrestricted_paths" /** Unrestricted URL fetch access. */ - | "unrestricted_urls"; + | "unrestricted_urls" + /** A server-wide MCP "Always Allow" (or `--allow-tool `) blanket that would auto-approve every tool from an MCP server. Capped to per-tool approval; each tool still prompts. */ + | "server_wide_mcp_approval"; /** * Exit plan mode action */ @@ -3849,6 +3864,7 @@ export interface AssistantMessageData { * Generation phase for phased-output models (e.g., thinking vs. response phases) */ phase?: string; + reasoningBlocks?: AssistantMessageReasoningBlocks; /** * Opaque/encrypted extended thinking data from Anthropic models. Session-bound and stripped on resume. */ @@ -4011,6 +4027,20 @@ export interface CitationLocationBlock { */ type: "block"; } +/** + * Neutral provider-tagged reasoning content blocks preserved verbatim for round-tripping + */ +/** @experimental */ +export interface AssistantMessageReasoningBlocks { + /** + * Provider-native reasoning content blocks (e.g. Anthropic `thinking` / `redacted_thinking`) preserved verbatim, in order. A single response can carry several, each signed over the content preceding it, so dropping or reordering any of them invalidates the rest. + */ + blocks?: JsonValue[]; + /** + * Model provider that produced these reasoning blocks. + */ + provider: string; +} /** * Neutral provider-tagged server-side tool-use payload (tool search, advisor) for verbatim round-tripping */ @@ -4390,6 +4420,10 @@ export interface AssistantUsageData { * Number of output tokens produced */ outputTokens?: number; + /** + * Time to first observable model output in milliseconds. Includes text, reasoning, and tool-call output; only available for streaming requests that produce observable output. + */ + outputTtftMs?: number; /** * @deprecated * Parent tool call ID when this usage originates from a sub-agent @@ -4706,6 +4740,62 @@ export interface ModelCallFailureRequestFingerprint { */ toolResultMessageCount: number; } +/** + * Session event "model.call_finished". Final lifecycle outcome for one logical model dispatch. A logical dispatch may include internal reconnect or fallback work, so event count is not provider HTTP-request count. + */ +export interface ModelCallFinishedEvent { + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + data: ModelCallFinishedData; + /** + * Always true for events that are transient and not persisted to the session event log on disk. + */ + ephemeral: true; + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + /** + * Type discriminator. Always "model.call_finished". + */ + type: "model.call_finished"; +} +/** + * Final lifecycle outcome for one logical model dispatch. A logical dispatch may include internal reconnect or fallback work, so event count is not provider HTTP-request count. + */ +export interface ModelCallFinishedData { + /** + * Whether an accepted successful response requested the exact name and command semantics of a built-in file edit tool, including an external tool explicitly replacing that built-in name. Absent when the logical dispatch did not produce an accepted response. + */ + containsBuiltInFileEditRequest?: boolean; + /** + * Monotonic elapsed time spent in the logical model dispatch, including any internal transport reconnect or fallback and excluding orchestrator retry backoff, tool execution, confirmations, and post-response processing + */ + dispatchDurationMs: number; + /** + * Version of the built-in file-edit semantic classifier used for this event + */ + editClassifierVersion: number; + /** + * Identifier of the user interaction that owns the model dispatch, matching assistant.turn_start.interactionId when available + */ + interactionId?: string; + outcome: ModelCallFinishedOutcome; + /** + * Agent-loop iteration within the interaction that initiated the model dispatch + */ + turnId: string; +} /** * Session event "abort". Turn abort information including the reason for termination */ @@ -5789,10 +5879,30 @@ export interface SubagentCompletedData { * Whether the sub-agent was torn down by cancellation - its own abort, or an ancestor being killed - instead of finishing its work. Cancellation is not a failure, so the run still reports completion; this distinguishes a torn-down sub-agent from one that ran to the end. */ cancelled?: boolean; + /** + * Whether the first model actually dispatched matched the user's configured preference + */ + configuredModelMatchesActual?: boolean; + /** + * Concrete model the user configured for this sub-agent via `/subagents`, when present + */ + configuredModelPreference?: string; /** * Wall-clock duration of the sub-agent execution in milliseconds */ durationMs?: number; + /** + * Whether the explicit task-call model matched the user's configured preference + */ + explicitModelMatchesPreference?: boolean; + /** + * Explicit model supplied by the parent agent on the task call, when present + */ + explicitModelOverride?: string; + /** + * First model for which the sub-agent started an inference request, when one was dispatched + */ + firstDispatchedModel?: string; /** * Model used by the sub-agent */ @@ -5852,6 +5962,14 @@ export interface SubagentFailedData { * Internal name of the sub-agent */ agentName: string; + /** + * Whether the first model actually dispatched matched the user's configured preference + */ + configuredModelMatchesActual?: boolean; + /** + * Concrete model the user configured for this sub-agent via `/subagents`, when present + */ + configuredModelPreference?: string; /** * Wall-clock duration of the sub-agent execution in milliseconds */ @@ -5860,6 +5978,18 @@ export interface SubagentFailedData { * Error message describing why the sub-agent failed */ error: string; + /** + * Whether the explicit task-call model matched the user's configured preference + */ + explicitModelMatchesPreference?: boolean; + /** + * Explicit model supplied by the parent agent on the task call, when present + */ + explicitModelOverride?: string; + /** + * First model for which the sub-agent started an inference request, when one was dispatched + */ + firstDispatchedModel?: string; /** * Model selected for the sub-agent, when known */ @@ -7172,6 +7302,10 @@ export interface PermissionPromptRequestMcp { * @experimental */ assistedApproval?: PermissionAssistedApproval; + /** + * Whether the host may offer a server-wide "approve all tools from this server" blanket. Absent is treated as true; the runtime sends false when managed policy disables bypass-permissions mode, which forbids the server-wide escalation while still allowing per-tool approval. + */ + canOfferServerWideApproval?: boolean; /** * Prompt kind discriminator */ @@ -9097,6 +9231,10 @@ 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; + /** + * Whether the effective sandbox policy forces the sandbox on *only* because managed policy could not be determined, rather than because the policy requires it. Lets clients tell a user whose `--no-sandbox` was overridden that the sandbox stayed on as a fail-closed fallback, instead of attributing it to an administrator who set no such policy. + */ + sandboxEnabledByUndeterminedPolicy?: boolean; /** * Whether the server (account/org) managed-settings layer was present */ diff --git a/nodejs/src/index.ts b/nodejs/src/index.ts index f91e351d30..ae474eefee 100644 --- a/nodejs/src/index.ts +++ b/nodejs/src/index.ts @@ -9,7 +9,7 @@ */ export { CopilotClient } from "./client.js"; -export { RuntimeConnection } from "./types.js"; +export { DisableBypassPermissionsModes, RuntimeConnection } from "./types.js"; export { BuiltInTools, ToolSet } from "./toolSet.js"; export { CopilotSession, type AssistantMessageEvent } from "./session.js"; export { defineFactory, FactoryResumeError, isFactoryRunTerminal } from "./factory.js"; diff --git a/nodejs/src/types.ts b/nodejs/src/types.ts index 678cd58633..24d23d0826 100644 --- a/nodejs/src/types.ts +++ b/nodejs/src/types.ts @@ -2167,6 +2167,14 @@ export interface GitHubMcpToolConfig { disableFormDeferral?: boolean; } +/** Well-known managed bypass-permissions policies. */ +export const DisableBypassPermissionsModes = { + /** Turn off bypass-permissions mode entirely. */ + Disable: "disable", + /** Permit automatic bypass but block full allow-all. */ + AllowAutoOnly: "allow-auto-only", +} as const; + /** * Permissions-only managed policy injected by the host via * {@link SessionConfigBase.managedSettings}. @@ -2177,11 +2185,11 @@ export interface GitHubMcpToolConfig { */ export interface ManagedSettingsPermissions { /** - * When set to `"disable"`, bypass-permissions ("yolo") mode is turned off - * for the session. This is deny-wins: it cannot be re-enabled by any other - * layer. + * Restricts bypass-permissions mode for the session. See + * {@link DisableBypassPermissionsModes} for well-known values. Unknown + * values are forwarded so newer runtime policies fail closed. */ - disableBypassPermissionsMode?: "disable"; + disableBypassPermissionsMode?: string; /** Operations that must always be denied. Unioned across managed layers. */ deny?: string[]; /** @@ -2721,8 +2729,8 @@ export interface SessionConfigBase { * with the same managed-permission parser it uses for fetched policy and * composes it restrictively with any self-fetched (server) and * device-managed (MDM) layers: `deny`/`ask` rules are unioned, every - * declared `allow` list must admit an operation, and - * `disableBypassPermissionsMode: "disable"` is deny-wins. + * declared `allow` list must admit an operation, and bypass-mode + * restrictions are composed fail-closed. * * This is startup-only. It is **not** persisted: it must be re-supplied on * {@link CopilotClient.resumeSession | resume}, where it replaces the prior diff --git a/nodejs/test/client.test.ts b/nodejs/test/client.test.ts index e2d630ba0e..e0e1981d88 100644 --- a/nodejs/test/client.test.ts +++ b/nodejs/test/client.test.ts @@ -10,8 +10,10 @@ import { createAttributedPermissionResult, CopilotClient, createCanvas, + DisableBypassPermissionsModes, RuntimeConnection, type GitHubTelemetryNotification, + type ManagedSettings, type ModelInfo, } from "../src/index.js"; import { CopilotSession } from "../src/session.js"; @@ -3905,19 +3907,20 @@ describe("managedSettings serialization", () => { } it("forwards the full permissions object on session.create", async () => { - const params = await captureCreateParams({ - managedSettings: { - permissions: { - disableBypassPermissionsMode: "disable", - deny: ["Shell(git push)"], - ask: ["Domain(publish.example)"], - allow: ["Read(**)"], - }, + const managedSettings = { + permissions: { + disableBypassPermissionsMode: DisableBypassPermissionsModes.AllowAutoOnly, + deny: ["Shell(git push)"], + ask: ["Domain(publish.example)"], + allow: ["Read(**)"], }, + } satisfies ManagedSettings; + const params = await captureCreateParams({ + managedSettings, }); expect(params.managedSettings).toEqual({ permissions: { - disableBypassPermissionsMode: "disable", + disableBypassPermissionsMode: "allow-auto-only", deny: ["Shell(git push)"], ask: ["Domain(publish.example)"], allow: ["Read(**)"], @@ -3925,6 +3928,32 @@ describe("managedSettings serialization", () => { }); }); + it("forwards the disable bypass-permissions mode", async () => { + const managedSettings = { + permissions: { + disableBypassPermissionsMode: DisableBypassPermissionsModes.Disable, + }, + } satisfies ManagedSettings; + const params = await captureCreateParams({ managedSettings }); + + expect(params.managedSettings).toEqual({ + permissions: { + disableBypassPermissionsMode: "disable", + }, + }); + }); + + it("forwards unknown bypass-permissions modes", async () => { + const managedSettings = { + permissions: { + disableBypassPermissionsMode: "future-fail-closed-mode", + }, + } satisfies ManagedSettings; + const params = await captureCreateParams({ managedSettings }); + + expect(params.managedSettings).toEqual(managedSettings); + }); + it("marks directly injected sessions as managed", async () => { const client = new CopilotClient(); await client.start(); diff --git a/python/copilot/__init__.py b/python/copilot/__init__.py index f7a71ebe91..8f30632e37 100644 --- a/python/copilot/__init__.py +++ b/python/copilot/__init__.py @@ -35,6 +35,7 @@ CloudSessionRepository, CopilotClient, CopilotExpAssignmentResponse, + DisableBypassPermissionsModes, ExpConfigEntry, ExpFlagValue, GetAuthStatusResponse, @@ -258,6 +259,7 @@ "ExitPlanModeResult", "ExtensionInfo", "CopilotWebSocketForwarder", + "DisableBypassPermissionsModes", "GetAuthStatusResponse", "BearerTokenProvider", "GetStatusResponse", diff --git a/python/copilot/_jsonrpc.py b/python/copilot/_jsonrpc.py index ed70e4e8d0..6427a8007f 100644 --- a/python/copilot/_jsonrpc.py +++ b/python/copilot/_jsonrpc.py @@ -296,6 +296,9 @@ def _read_loop(self): def _fail_pending_requests(self): """Fail all pending requests when process exits""" + if self._stderr_thread and self._stderr_thread is not threading.current_thread(): + self._stderr_thread.join(timeout=1.0) + # Build error message with stderr output stderr_output = self.get_stderr_output() return_code = None diff --git a/python/copilot/client.py b/python/copilot/client.py index 2654c14477..ad4b0fe171 100644 --- a/python/copilot/client.py +++ b/python/copilot/client.py @@ -247,6 +247,16 @@ def _capi_session_options_to_wire(options: CapiSessionOptions) -> dict[str, Any] return wire +class DisableBypassPermissionsModes: + """Well-known managed bypass-permissions policies.""" + + DISABLE: ClassVar[str] = "disable" + """Turn off bypass-permissions mode entirely.""" + + ALLOW_AUTO_ONLY: ClassVar[str] = "allow-auto-only" + """Permit automatic bypass but block full allow-all.""" + + @dataclass class ManagedSettingsPermissions: """Permissions-only managed policy injected via :class:`ManagedSettings`. @@ -256,9 +266,10 @@ class ManagedSettingsPermissions: rules are rejected by the runtime at session creation. """ - disable_bypass_permissions_mode: Literal["disable"] | None = None - """When ``"disable"``, turns off bypass-permissions ("yolo") mode for the - session. Deny-wins: no other layer can re-enable it. Sent on the wire as + disable_bypass_permissions_mode: str | None = None + """Restricts bypass-permissions mode for the session. See + :class:`DisableBypassPermissionsModes` for well-known values. Unknown values + are forwarded so newer runtime policies fail closed. Sent on the wire as ``disableBypassPermissionsMode``.""" deny: list[str] | None = None """Operations that must always be denied. Unioned across managed layers.""" diff --git a/python/copilot/generated/rpc.py b/python/copilot/generated/rpc.py index d7c86509e8..ca782bd363 100644 --- a/python/copilot/generated/rpc.py +++ b/python/copilot/generated/rpc.py @@ -271,6 +271,7 @@ class AuthInfoType(Enum): GH_CLI = "gh-cli" HMAC = "hmac" TOKEN = "token" + TOKEN_PROVIDER = "token-provider" USER = "user" # Experimental: this type is part of an experimental API and may change or be removed. @@ -1752,59 +1753,68 @@ def to_dict(self) -> dict: return result # Experimental: this type is part of an experimental API and may change or be removed. +# Internal: this type is an internal SDK API and is not part of the public surface. @dataclass -class ConnectRemoteSessionParams: - """Remote session connection parameters.""" +class _ConnectClientInfo: + """Identity of the integrating host, declared once on the `server.connect` handshake so + telemetry from this connection is attributed to a single, consistent surface. All fields + are optional; omit them to keep the default attribution. - session_id: str - """Session ID to connect to.""" + Identity of the integrating host. Optional; omit it to keep the default attribution. + """ + editor_name: str | None = None + """Name of the host editor, e.g. `"vscode"`.""" + + editor_version: str | None = None + """Version of the host editor, e.g. `"1.124.2"`. Ignored unless it looks like a version + string. + """ + extension_name: str | None = None + """Name of the Copilot extension within the host, e.g. `"copilot-chat"`.""" + + extension_version: str | None = None + """Version of the Copilot extension within the host, e.g. `"0.54.0"`. Ignored unless it + looks like a version string. + """ @staticmethod - def from_dict(obj: Any) -> 'ConnectRemoteSessionParams': + def from_dict(obj: Any) -> '_ConnectClientInfo': assert isinstance(obj, dict) - session_id = from_str(obj.get("sessionId")) - return ConnectRemoteSessionParams(session_id) + editor_name = from_union([from_str, from_none], obj.get("editorName")) + editor_version = from_union([from_str, from_none], obj.get("editorVersion")) + extension_name = from_union([from_str, from_none], obj.get("extensionName")) + extension_version = from_union([from_str, from_none], obj.get("extensionVersion")) + return _ConnectClientInfo(editor_name, editor_version, extension_name, extension_version) def to_dict(self) -> dict: result: dict = {} - result["sessionId"] = from_str(self.session_id) + if self.editor_name is not None: + result["editorName"] = from_union([from_str, from_none], self.editor_name) + if self.editor_version is not None: + result["editorVersion"] = from_union([from_str, from_none], self.editor_version) + if self.extension_name is not None: + result["extensionName"] = from_union([from_str, from_none], self.extension_name) + if self.extension_version is not None: + result["extensionVersion"] = from_union([from_str, from_none], self.extension_version) return result # Experimental: this type is part of an experimental API and may change or be removed. -# Internal: this type is an internal SDK API and is not part of the public surface. @dataclass -class _ConnectRequest: - """Connection-level opt-ins for the `server.connect` handshake. Transport authentication is - consumed by the native protocol boundary before dispatch. - """ - enable_git_hub_telemetry_forwarding: bool | None = None - """Opt this connection in to GitHub telemetry forwarding for its lifetime. When set, the - runtime forwards every internal telemetry event it emits — across all sessions, plus - sessionless events — to this connection over the `gitHubTelemetry.event` notification. - Regular events are also written to the runtime's normal GitHub/CTS path (dual-write); - host-only compatibility events are forward-only and intentionally skip that path. - Intended for first-party hosts that re-emit the events into their own telemetry stores. - Both unrestricted and restricted events are forwarded, each tagged with a `restricted` - discriminator; a backstop drops restricted events when restricted telemetry is disabled — - using the process-global gate for ordinary events and an explicit session-scoped decision - for host-only events. - """ - token: str | None = None - """Connection token; required when the server was started with COPILOT_CONNECTION_TOKEN""" +class ConnectRemoteSessionParams: + """Remote session connection parameters.""" + + session_id: str + """Session ID to connect to.""" @staticmethod - def from_dict(obj: Any) -> '_ConnectRequest': + def from_dict(obj: Any) -> 'ConnectRemoteSessionParams': assert isinstance(obj, dict) - enable_git_hub_telemetry_forwarding = from_union([from_bool, from_none], obj.get("enableGitHubTelemetryForwarding")) - token = from_union([from_str, from_none], obj.get("token")) - return _ConnectRequest(enable_git_hub_telemetry_forwarding, token) + session_id = from_str(obj.get("sessionId")) + return ConnectRemoteSessionParams(session_id) def to_dict(self) -> dict: result: dict = {} - if self.enable_git_hub_telemetry_forwarding is not None: - result["enableGitHubTelemetryForwarding"] = from_union([from_bool, from_none], self.enable_git_hub_telemetry_forwarding) - if self.token is not None: - result["token"] = from_union([from_str, from_none], self.token) + result["sessionId"] = from_str(self.session_id) return result # Experimental: this type is part of an experimental API and may change or be removed. @@ -2396,12 +2406,6 @@ def to_dict(self) -> dict: result["path"] = from_union([from_str, from_none], self.path) return result -# Experimental: this type is part of an experimental API and may change or be removed. -class DisableBypassPermissionsMode(Enum): - """When set to `disable`, prevents bypass/allow-all permission modes.""" - - DISABLE = "disable" - # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class DiscoveredExtensionPlugin: @@ -3578,6 +3582,16 @@ def to_dict(self) -> dict: result["is_staff"] = from_union([from_bool, from_none], self.is_staff) return result +class GitHubTokenAcquireReason(Enum): + """Why the runtime is requesting a GitHub credential.""" + + INITIAL = "initial" + REFRESH = "refresh" + +class GitHubTokenAcquireResultKind(Enum): + CANCELLED = "cancelled" + TOKEN = "token" + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class HandlePendingToolCallResult: @@ -5474,10 +5488,6 @@ def to_dict(self) -> dict: result["serverName"] = from_union([from_str, from_none], self.server_name) return result -class MCPOauthPendingRequestResponseKind(Enum): - CANCELLED = "cancelled" - TOKEN = "token" - # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class MCPOauthHandlePendingResult: @@ -6698,6 +6708,33 @@ def to_dict(self) -> dict: result["supported_media_types"] = from_list(from_str, self.supported_media_types) return result +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class ModelMessage: + """A service-published message about a model, carrying a stable machine-readable code + alongside human-readable text. + """ + code: str + """Stable machine-readable identifier for the message, such as `client_version_deprecated`. + Hosts can key custom presentation off this; unrecognized codes should fall back to + displaying `message`. + """ + message: str + """Human-readable message text intended for display to the user.""" + + @staticmethod + def from_dict(obj: Any) -> 'ModelMessage': + assert isinstance(obj, dict) + code = from_str(obj.get("code")) + message = from_str(obj.get("message")) + return ModelMessage(code, message) + + def to_dict(self) -> dict: + result: dict = {} + result["code"] = from_str(self.code) + result["message"] = from_str(self.message) + return result + # Experimental: this type is part of an experimental API and may change or be removed. class ModelPickerPriceCategory(Enum): """Relative cost tier for token-based billing users @@ -6717,6 +6754,31 @@ class ModelPolicyState(Enum): ENABLED = "enabled" UNCONFIGURED = "unconfigured" +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class ModelWarningText: + """Warning text the service requires hosts to surface for this model. Present only when the + service published at least one warning. + + Service-published warning text that hosts should display when presenting a model. + """ + data_retention: str | None = None + """Data-retention warning for the model. The text may contain Markdown links and should be + rendered as Markdown when supported. + """ + + @staticmethod + def from_dict(obj: Any) -> 'ModelWarningText': + assert isinstance(obj, dict) + data_retention = from_union([from_str, from_none], obj.get("dataRetention")) + return ModelWarningText(data_retention) + + def to_dict(self) -> dict: + result: dict = {} + if self.data_retention is not None: + result["dataRetention"] = from_union([from_str, from_none], self.data_retention) + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class ModelCapabilitiesOverrideLimitsVision: @@ -7249,7 +7311,9 @@ class PermissionPathsAddParams: path: str """Directory to add to the allow-list. The runtime resolves and validates the path before - adding. + adding, then loads conventional `.github/skills/` and `.github/agents/` definitions under + it when their subsystem gates are enabled. Adding the directory is therefore also a trust + decision for configuration stored there. """ @staticmethod @@ -9560,6 +9624,22 @@ def to_dict(self) -> dict: result["keychainAccess"] = from_union([from_bool, from_none], self.keychain_access) return result +# Experimental: this type is part of an experimental API and may change or be removed. +# Internal: this type is an internal SDK API and is not part of the public surface. +class _SandboxConfigSource(Enum): + """Origin of the sandbox choice supplied by an internal client. + + Origin of the sandbox choice. The runtime uses this only for internal telemetry + provenance; managed policy is derived independently. + """ + NEVER_CONFIGURED = "never_configured" + REPOSITORY_POLICY = "repository_policy" + SESSION_DISABLED = "session_disabled" + SESSION_FLAG = "session_flag" + UNSUPPORTED_HOST = "unsupported_host" + USER_DISABLED = "user_disabled" + USER_ENABLED = "user_enabled" + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class ScheduleAddAtRequest: @@ -10863,6 +10943,53 @@ def to_dict(self) -> dict: result["startupPrompts"] = from_list(from_str, self.startup_prompts) return result +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class SessionManagedPermissions: + """Enterprise permission policy expressed with the runtime's managed permission-rule + syntax. + + Managed permission policy injected by the SDK host. + """ + allow: list[str] | None = None + """Permission rules that allow matching operations unless another managed source, deny, or + ask rule restricts them. + """ + ask: list[str] | None = None + """Permission rules that require explicit human approval.""" + + deny: list[str] | None = None + """Permission rules that block matching operations. Deny has highest precedence.""" + + disable_bypass_permissions_mode: str | None = None + """When set to `disable`, prevents bypass/allow-all permission modes. `allow-auto-only` + blocks full allow-all but permits advisory auto-approval. Any other value is accepted + rather than failing the session, but is enforced as `disable`: the key is only present to + restrict something, so a mode this runtime cannot interpret fails closed to the most + restrictive one it knows. Omit the key entirely to impose no restriction. + """ + + @staticmethod + def from_dict(obj: Any) -> 'SessionManagedPermissions': + assert isinstance(obj, dict) + allow = from_union([lambda x: from_list(from_str, x), from_none], obj.get("allow")) + ask = from_union([lambda x: from_list(from_str, x), from_none], obj.get("ask")) + deny = from_union([lambda x: from_list(from_str, x), from_none], obj.get("deny")) + disable_bypass_permissions_mode = from_union([from_str, from_none], obj.get("disableBypassPermissionsMode")) + return SessionManagedPermissions(allow, ask, deny, disable_bypass_permissions_mode) + + def to_dict(self) -> dict: + result: dict = {} + if self.allow is not None: + result["allow"] = from_union([lambda x: from_list(from_str, x), from_none], self.allow) + if self.ask is not None: + result["ask"] = from_union([lambda x: from_list(from_str, x), from_none], self.ask) + if self.deny is not None: + result["deny"] = from_union([lambda x: from_list(from_str, x), from_none], self.deny) + if self.disable_bypass_permissions_mode is not None: + result["disableBypassPermissionsMode"] = from_union([from_str, from_none], self.disable_bypass_permissions_mode) + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class SessionModelListRequest: @@ -11114,12 +11241,21 @@ def to_dict(self) -> dict: result["skipped"] = from_list(from_str, self.skipped) return result +class SettableAuthInfoType(Enum): + API_KEY = "api-key" + COPILOT_API_TOKEN = "copilot-api-token" + ENV = "env" + GH_CLI = "gh-cli" + HMAC = "hmac" + TOKEN = "token" + USER = "user" + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class SessionSetCredentialsParams: """New auth credentials to install on the session. Omit to leave credentials unchanged.""" - credentials: AuthInfo | None = None + credentials: SettableAuthInfo | None = None """The new auth credentials to install on the session. When omitted or `undefined`, the call is a no-op and the session's existing credentials are preserved. The runtime installs the supplied value immediately for outbound model/API requests. When the credential carries a @@ -11134,7 +11270,7 @@ class SessionSetCredentialsParams: @staticmethod def from_dict(obj: Any) -> 'SessionSetCredentialsParams': assert isinstance(obj, dict) - credentials = from_union([_load_AuthInfo, from_none], obj.get("credentials")) + credentials = from_union([_load_SettableAuthInfo, from_none], obj.get("credentials")) return SessionSetCredentialsParams(credentials) def to_dict(self) -> dict: @@ -12228,6 +12364,9 @@ def to_dict(self) -> dict: result["expectedFromSessionId"] = from_union([from_str, from_none], self.expected_from_session_id) return result +class SettableTokenAuthInfoType(Enum): + TOKEN = "token" + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class ShellCancelUserRequestedRequest: @@ -13236,8 +13375,8 @@ def to_dict(self) -> dict: result["features"] = from_dict(from_str, self.features) return result -class TokenAuthInfoType(Enum): - TOKEN = "token" +class TokenProviderAuthInfoType(Enum): + TOKEN_PROVIDER = "token-provider" # Experimental: this type is part of an experimental API and may change or be removed. @dataclass @@ -15499,6 +15638,49 @@ def to_dict(self) -> dict: result["origin"] = from_union([lambda x: to_enum(CommandsInvocationOrigin, x), from_none], self.origin) return result +# Experimental: this type is part of an experimental API and may change or be removed. +# Internal: this type is an internal SDK API and is not part of the public surface. +@dataclass +class _ConnectRequest: + """Connection-level opt-ins for the `server.connect` handshake. Transport authentication is + consumed by the native protocol boundary before dispatch. + """ + client_info: _ConnectClientInfo | None = None + """Identity of the integrating host. Optional; omit it to keep the default attribution.""" + + enable_git_hub_telemetry_forwarding: bool | None = None + """Opt this connection in to GitHub telemetry forwarding for its lifetime. When set, the + runtime forwards every internal telemetry event it emits — across all sessions, plus + sessionless events — to this connection over the `gitHubTelemetry.event` notification. + Regular events are also written to the runtime's normal GitHub/CTS path (dual-write); + host-only compatibility events are forward-only and intentionally skip that path. + Intended for first-party hosts that re-emit the events into their own telemetry stores. + Both unrestricted and restricted events are forwarded, each tagged with a `restricted` + discriminator; a backstop drops restricted events when restricted telemetry is disabled — + using the process-global gate for ordinary events and an explicit session-scoped decision + for host-only events. + """ + token: str | None = None + """Connection token; required when the server was started with COPILOT_CONNECTION_TOKEN""" + + @staticmethod + def from_dict(obj: Any) -> '_ConnectRequest': + assert isinstance(obj, dict) + client_info = from_union([_ConnectClientInfo.from_dict, from_none], obj.get("clientInfo")) + enable_git_hub_telemetry_forwarding = from_union([from_bool, from_none], obj.get("enableGitHubTelemetryForwarding")) + token = from_union([from_str, from_none], obj.get("token")) + return _ConnectRequest(client_info, enable_git_hub_telemetry_forwarding, token) + + def to_dict(self) -> dict: + result: dict = {} + if self.client_info is not None: + result["clientInfo"] = from_union([lambda x: to_class(_ConnectClientInfo, x), from_none], self.client_info) + if self.enable_git_hub_telemetry_forwarding is not None: + result["enableGitHubTelemetryForwarding"] = from_union([from_bool, from_none], self.enable_git_hub_telemetry_forwarding) + if self.token is not None: + result["token"] = from_union([from_str, from_none], self.token) + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class ConnectedRemoteSessionMetadata: @@ -15972,48 +16154,6 @@ def to_dict(self) -> dict: result["outputDirectory"] = from_union([from_str, from_none], self.output_directory) return result -# Experimental: this type is part of an experimental API and may change or be removed. -@dataclass -class SessionManagedPermissions: - """Enterprise permission policy expressed with the runtime's managed permission-rule - syntax. - - Managed permission policy injected by the SDK host. - """ - allow: list[str] | None = None - """Permission rules that allow matching operations unless another managed source, deny, or - ask rule restricts them. - """ - ask: list[str] | None = None - """Permission rules that require explicit human approval.""" - - deny: list[str] | None = None - """Permission rules that block matching operations. Deny has highest precedence.""" - - disable_bypass_permissions_mode: DisableBypassPermissionsMode | None = None - """When set to `disable`, prevents bypass/allow-all permission modes.""" - - @staticmethod - def from_dict(obj: Any) -> 'SessionManagedPermissions': - assert isinstance(obj, dict) - allow = from_union([lambda x: from_list(from_str, x), from_none], obj.get("allow")) - ask = from_union([lambda x: from_list(from_str, x), from_none], obj.get("ask")) - deny = from_union([lambda x: from_list(from_str, x), from_none], obj.get("deny")) - disable_bypass_permissions_mode = from_union([DisableBypassPermissionsMode, from_none], obj.get("disableBypassPermissionsMode")) - return SessionManagedPermissions(allow, ask, deny, disable_bypass_permissions_mode) - - def to_dict(self) -> dict: - result: dict = {} - if self.allow is not None: - result["allow"] = from_union([lambda x: from_list(from_str, x), from_none], self.allow) - if self.ask is not None: - result["ask"] = from_union([lambda x: from_list(from_str, x), from_none], self.ask) - if self.deny is not None: - result["deny"] = from_union([lambda x: from_list(from_str, x), from_none], self.deny) - if self.disable_bypass_permissions_mode is not None: - result["disableBypassPermissionsMode"] = from_union([lambda x: to_enum(DisableBypassPermissionsMode, x), from_none], self.disable_bypass_permissions_mode) - return result - # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class DiscoveredExtension: @@ -16996,6 +17136,116 @@ def to_dict(self) -> dict: result["resumeFromRunId"] = from_union([from_str, from_none], self.resume_from_run_id) return result +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class GitHubTokenAcquireRequest: + """Asks the SDK client to acquire a GitHub access token from an opaque callback registration.""" + + host: str + """Effective GitHub host for which the callback must return a token.""" + + reason: GitHubTokenAcquireReason + """Why the runtime is requesting a GitHub credential.""" + + registration_id: str + """Opaque identifier generated by the SDK for this callback registration.""" + + session_id: str | None = None + """Session receiving the token. Absent only before a cloud session has been assigned its id.""" + + @staticmethod + def from_dict(obj: Any) -> 'GitHubTokenAcquireRequest': + assert isinstance(obj, dict) + host = from_str(obj.get("host")) + reason = GitHubTokenAcquireReason(obj.get("reason")) + registration_id = from_str(obj.get("registrationId")) + session_id = from_union([from_str, from_none], obj.get("sessionId")) + return GitHubTokenAcquireRequest(host, reason, registration_id, session_id) + + def to_dict(self) -> dict: + result: dict = {} + result["host"] = from_str(self.host) + result["reason"] = to_enum(GitHubTokenAcquireReason, self.reason) + result["registrationId"] = from_str(self.registration_id) + if self.session_id is not None: + result["sessionId"] = from_union([from_str, from_none], self.session_id) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class GitHubTokenAcquireResult: + """SDK host response to a GitHub credential request.""" + + kind: GitHubTokenAcquireResultKind + """GitHub credential response variant discriminator.""" + + access_token: str | None = None + """GitHub access token acquired by the SDK host.""" + + expires_in: int | None = None + """Remaining token lifetime in seconds when callback execution completes. It must exceed the + one-hour preflight refresh threshold. + """ + token_type: str | None = None + """OAuth token type. Defaults to bearer when omitted.""" + + @staticmethod + def from_dict(obj: Any) -> 'GitHubTokenAcquireResult': + assert isinstance(obj, dict) + kind = GitHubTokenAcquireResultKind(obj.get("kind")) + access_token = from_union([from_str, from_none], obj.get("accessToken")) + expires_in = from_union([from_int, from_none], obj.get("expiresIn")) + token_type = from_union([from_str, from_none], obj.get("tokenType")) + return GitHubTokenAcquireResult(kind, access_token, expires_in, token_type) + + def to_dict(self) -> dict: + result: dict = {} + result["kind"] = to_enum(GitHubTokenAcquireResultKind, self.kind) + if self.access_token is not None: + result["accessToken"] = from_union([from_str, from_none], self.access_token) + if self.expires_in is not None: + result["expiresIn"] = from_union([from_int, from_none], self.expires_in) + if self.token_type is not None: + result["tokenType"] = from_union([from_str, from_none], self.token_type) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class MCPOauthPendingRequestResponse: + """Host response to the pending OAuth request.""" + + kind: GitHubTokenAcquireResultKind + """OAuth response variant discriminator.""" + + access_token: str | None = None + """Access token acquired by the SDK host""" + + expires_in: int | None = None + """Token lifetime in seconds, if known.""" + + token_type: str | None = None + """OAuth token type. Defaults to bearer when omitted.""" + + @staticmethod + def from_dict(obj: Any) -> 'MCPOauthPendingRequestResponse': + assert isinstance(obj, dict) + kind = GitHubTokenAcquireResultKind(obj.get("kind")) + access_token = from_union([from_str, from_none], obj.get("accessToken")) + expires_in = from_union([from_int, from_none], obj.get("expiresIn")) + token_type = from_union([from_str, from_none], obj.get("tokenType")) + return MCPOauthPendingRequestResponse(kind, access_token, expires_in, token_type) + + def to_dict(self) -> dict: + result: dict = {} + result["kind"] = to_enum(GitHubTokenAcquireResultKind, self.kind) + if self.access_token is not None: + result["accessToken"] = from_union([from_str, from_none], self.access_token) + if self.expires_in is not None: + result["expiresIn"] = from_union([from_int, from_none], self.expires_in) + if self.token_type is not None: + result["tokenType"] = from_union([from_str, from_none], self.token_type) + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class HistoryCompactResult: @@ -18450,43 +18700,6 @@ def to_dict(self) -> dict: result["reason"] = from_union([from_str, from_none], self.reason) return result -# Experimental: this type is part of an experimental API and may change or be removed. -@dataclass -class MCPOauthPendingRequestResponse: - """Host response to the pending OAuth request.""" - - kind: MCPOauthPendingRequestResponseKind - """OAuth response variant discriminator.""" - - access_token: str | None = None - """Access token acquired by the SDK host""" - - expires_in: int | None = None - """Token lifetime in seconds, if known.""" - - token_type: str | None = None - """OAuth token type. Defaults to Bearer when omitted.""" - - @staticmethod - def from_dict(obj: Any) -> 'MCPOauthPendingRequestResponse': - assert isinstance(obj, dict) - kind = MCPOauthPendingRequestResponseKind(obj.get("kind")) - access_token = from_union([from_str, from_none], obj.get("accessToken")) - expires_in = from_union([from_int, from_none], obj.get("expiresIn")) - token_type = from_union([from_str, from_none], obj.get("tokenType")) - return MCPOauthPendingRequestResponse(kind, access_token, expires_in, token_type) - - def to_dict(self) -> dict: - result: dict = {} - result["kind"] = to_enum(MCPOauthPendingRequestResponseKind, self.kind) - if self.access_token is not None: - result["accessToken"] = from_union([from_str, from_none], self.access_token) - if self.expires_in is not None: - result["expiresIn"] = from_union([from_int, from_none], self.expires_in) - if self.token_type is not None: - result["tokenType"] = from_union([from_str, from_none], self.token_type) - return result - # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class MCPPlanRequiredValueEnum: @@ -20918,6 +21131,14 @@ class InstalledPluginInfo: for direct repo / URL / local installs; absent for marketplace plugins. Same source yields the same id; distinct sources never collide. """ + installed_from: str | None = None + """Absolute path of the marketplace directory a live plugin was resolved from. Present only + on live, never-persisted records — a plugin belonging to a directory/local marketplace, + which is loaded from its real directory on every pass instead of a copy under the + installed-plugins cache. Its presence is what marks a listed plugin as live: such a + plugin is always present on disk, so `enabled` is its only meaningful state and it is + never "not installed". + """ version: str | None = None """Installed version (when reported by the plugin manifest)""" @@ -20928,8 +21149,9 @@ def from_dict(obj: Any) -> 'InstalledPluginInfo': marketplace = from_str(obj.get("marketplace")) name = from_str(obj.get("name")) direct_source_id = from_union([from_str, from_none], obj.get("directSourceId")) + installed_from = from_union([from_str, from_none], obj.get("installedFrom")) version = from_union([from_str, from_none], obj.get("version")) - return InstalledPluginInfo(enabled, marketplace, name, direct_source_id, version) + return InstalledPluginInfo(enabled, marketplace, name, direct_source_id, installed_from, version) def to_dict(self) -> dict: result: dict = {} @@ -20938,6 +21160,8 @@ def to_dict(self) -> dict: result["name"] = from_str(self.name) if self.direct_source_id is not None: result["directSourceId"] = from_union([from_str, from_none], self.direct_source_id) + if self.installed_from is not None: + result["installedFrom"] = from_union([from_str, from_none], self.installed_from) if self.version is not None: result["version"] = from_union([from_str, from_none], self.version) return result @@ -22697,6 +22921,30 @@ def to_dict(self) -> dict: result["tier"] = to_enum(SessionLimitPredictionTier, self.tier) return result +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class SessionManagedSettings: + """Managed settings an SDK host may inject at session startup. Only permissions are accepted + in this initial contract. + + Permissions-only enterprise policy injected by the SDK host at session create or resume. + Composes restrictively with self-fetched and device policy and is not persisted. + """ + permissions: SessionManagedPermissions | None = None + """Managed permission policy injected by the SDK host.""" + + @staticmethod + def from_dict(obj: Any) -> 'SessionManagedSettings': + assert isinstance(obj, dict) + permissions = from_union([SessionManagedPermissions.from_dict, from_none], obj.get("permissions")) + return SessionManagedSettings(permissions) + + def to_dict(self) -> dict: + result: dict = {} + if self.permissions is not None: + result["permissions"] = from_union([lambda x: to_class(SessionManagedPermissions, x), from_none], self.permissions) + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class SessionOpenOptionsAdditionalContentExclusionPolicyRule: @@ -25098,30 +25346,6 @@ def to_dict(self) -> dict: result["skippedEntries"] = from_union([lambda x: from_list(lambda x: to_class(DebugCollectLogsSkippedEntry, x), x), from_none], self.skipped_entries) return result -# Experimental: this type is part of an experimental API and may change or be removed. -@dataclass -class SessionManagedSettings: - """Managed settings an SDK host may inject at session startup. Only permissions are accepted - in this initial contract. - - Permissions-only enterprise policy injected by the SDK host at session create or resume. - Composes restrictively with self-fetched and device policy and is not persisted. - """ - permissions: SessionManagedPermissions | None = None - """Managed permission policy injected by the SDK host.""" - - @staticmethod - def from_dict(obj: Any) -> 'SessionManagedSettings': - assert isinstance(obj, dict) - permissions = from_union([SessionManagedPermissions.from_dict, from_none], obj.get("permissions")) - return SessionManagedSettings(permissions) - - def to_dict(self) -> dict: - result: dict = {} - if self.permissions is not None: - result["permissions"] = from_union([lambda x: to_class(SessionManagedPermissions, x), from_none], self.permissions) - return result - # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class DiscoveredExtensions: @@ -25767,6 +25991,30 @@ def to_dict(self) -> dict: result["options"] = from_union([lambda x: to_class(RunOptions, x), from_none], self.options) return result +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class MCPOauthHandlePendingRequest: + """Pending MCP OAuth request ID and host-provided token or cancellation response.""" + + request_id: str + """OAuth request identifier from the mcp.oauth_required event""" + + result: MCPOauthPendingRequestResponse + """Host response to the pending OAuth request.""" + + @staticmethod + def from_dict(obj: Any) -> 'MCPOauthHandlePendingRequest': + assert isinstance(obj, dict) + request_id = from_str(obj.get("requestId")) + result = MCPOauthPendingRequestResponse.from_dict(obj.get("result")) + return MCPOauthHandlePendingRequest(request_id, result) + + def to_dict(self) -> dict: + result: dict = {} + result["requestId"] = from_str(self.request_id) + result["result"] = to_class(MCPOauthPendingRequestResponse, self.result) + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class HistoryRewindResult: @@ -25885,6 +26133,13 @@ class InstalledPlugin: cache_path: str | None = None """Path where the plugin is cached locally""" + installed_from: str | None = None + """Absolute path of the marketplace directory a live plugin was resolved from. Present only + on live, never-persisted records — those synthesized at session start for a + directory/local marketplace, whose cache_path points at the real plugin directory on disk + rather than a copy under the installed-plugins cache. Its presence is what marks a record + as live, and no record carrying it is ever written to the persisted installedPlugins key. + """ source: InstalledPluginSource | str | None = None """Source for direct repo installs (when marketplace is empty)""" @@ -25906,10 +26161,11 @@ def from_dict(obj: Any) -> 'InstalledPlugin': marketplace = from_str(obj.get("marketplace")) name = from_str(obj.get("name")) cache_path = from_union([from_str, from_none], obj.get("cache_path")) + installed_from = from_union([from_str, from_none], obj.get("installed_from")) source = from_union([InstalledPluginSource.from_dict, from_str, from_none], obj.get("source")) source_sha = from_union([from_str, from_none], obj.get("source_sha")) version = from_union([from_str, from_none], obj.get("version")) - return InstalledPlugin(enabled, installed_at, marketplace, name, cache_path, source, source_sha, version) + return InstalledPlugin(enabled, installed_at, marketplace, name, cache_path, installed_from, source, source_sha, version) def to_dict(self) -> dict: result: dict = {} @@ -25919,6 +26175,8 @@ def to_dict(self) -> dict: result["name"] = from_str(self.name) if self.cache_path is not None: result["cache_path"] = from_union([from_str, from_none], self.cache_path) + if self.installed_from is not None: + result["installed_from"] = from_union([from_str, from_none], self.installed_from) if self.source is not None: result["source"] = from_union([lambda x: to_class(InstalledPluginSource, x), from_str, from_none], self.source) if self.source_sha is not None: @@ -25948,6 +26206,13 @@ class SessionInstalledPlugin: cache_path: str | None = None """Path where the plugin is cached locally""" + installed_from: str | None = None + """Absolute path of the marketplace directory a live plugin was resolved from. Present only + on live, never-persisted records — those synthesized at session start for a + directory/local marketplace, whose cache_path points at the real plugin directory on disk + rather than a copy under the installed-plugins cache. Its presence is what marks a record + as live, and no record carrying it is ever written to the persisted installedPlugins key. + """ source: SessionInstalledPluginSource | str | None = None """Source descriptor for direct repo installs (when marketplace is empty)""" @@ -25969,10 +26234,11 @@ def from_dict(obj: Any) -> 'SessionInstalledPlugin': marketplace = from_str(obj.get("marketplace")) name = from_str(obj.get("name")) cache_path = from_union([from_str, from_none], obj.get("cache_path")) + installed_from = from_union([from_str, from_none], obj.get("installed_from")) source = from_union([SessionInstalledPluginSource.from_dict, from_str, from_none], obj.get("source")) source_sha = from_union([from_str, from_none], obj.get("source_sha")) version = from_union([from_str, from_none], obj.get("version")) - return SessionInstalledPlugin(enabled, installed_at, marketplace, name, cache_path, source, source_sha, version) + return SessionInstalledPlugin(enabled, installed_at, marketplace, name, cache_path, installed_from, source, source_sha, version) def to_dict(self) -> dict: result: dict = {} @@ -25982,6 +26248,8 @@ def to_dict(self) -> dict: result["name"] = from_str(self.name) if self.cache_path is not None: result["cache_path"] = from_union([from_str, from_none], self.cache_path) + if self.installed_from is not None: + result["installed_from"] = from_union([from_str, from_none], self.installed_from) if self.source is not None: result["source"] = from_union([lambda x: to_class(SessionInstalledPluginSource, x), from_str, from_none], self.source) if self.source_sha is not None: @@ -26268,9 +26536,11 @@ class PermissionPathsConfig: """ additional_directories: list[str] | None = None """Additional directories to allow tool access to (in addition to the session's working - directory). When `unrestricted` is true, these are still pre-populated on the - UnrestrictedPathManager so they remain visible via getDirectories() (e.g. for @-mention - completion). + directory). Conventional `.github/skills/` and `.github/agents/` definitions under them + also join the session catalogs when their subsystem gates are enabled, so supplying a + directory is a trust decision for configuration stored there. When `unrestricted` is + true, these are still pre-populated on the UnrestrictedPathManager so they remain visible + via getDirectories() (e.g. for @-mention completion). """ include_temp_directory: bool | None = None """Whether to include the system temp directory in the allowed list (defaults to true). @@ -26524,30 +26794,6 @@ def to_dict(self) -> dict: result["result"] = to_class(MCPHeadersHandlePendingHeadersRefreshRequest, self.result) return result -# Experimental: this type is part of an experimental API and may change or be removed. -@dataclass -class MCPOauthHandlePendingRequest: - """Pending MCP OAuth request ID and host-provided token or cancellation response.""" - - request_id: str - """OAuth request identifier from the mcp.oauth_required event""" - - result: MCPOauthPendingRequestResponse - """Host response to the pending OAuth request.""" - - @staticmethod - def from_dict(obj: Any) -> 'MCPOauthHandlePendingRequest': - assert isinstance(obj, dict) - request_id = from_str(obj.get("requestId")) - result = MCPOauthPendingRequestResponse.from_dict(obj.get("result")) - return MCPOauthHandlePendingRequest(request_id, result) - - def to_dict(self) -> dict: - result: dict = {} - result["requestId"] = from_str(self.request_id) - result["result"] = to_class(MCPOauthPendingRequestResponse, self.result) - return result - # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class DebugCollectLogsEntry: @@ -27165,18 +27411,26 @@ class QueuePendingItemsResult: """Display text for messages currently in the immediate steering queue (interjections sent during a running turn). """ + in_flight_steering_count: int | None = None + """How many leading entries of `steeringMessages` have already been folded into the running + turn (and so have an emitted `user.message`), as opposed to still waiting for one. Absent + for hosts that do not distinguish the two. + """ @staticmethod def from_dict(obj: Any) -> 'QueuePendingItemsResult': assert isinstance(obj, dict) items = from_list(QueuePendingItems.from_dict, obj.get("items")) steering_messages = from_list(from_str, obj.get("steeringMessages")) - return QueuePendingItemsResult(items, steering_messages) + in_flight_steering_count = from_union([from_int, from_none], obj.get("inFlightSteeringCount")) + return QueuePendingItemsResult(items, steering_messages, in_flight_steering_count) def to_dict(self) -> dict: result: dict = {} result["items"] = from_list(lambda x: to_class(QueuePendingItems, x), self.items) result["steeringMessages"] = from_list(from_str, self.steering_messages) + if self.in_flight_steering_count is not None: + result["inFlightSteeringCount"] = from_union([from_int, from_none], self.in_flight_steering_count) return result # Experimental: this type is part of an experimental API and may change or be removed. @@ -29897,9 +30151,9 @@ class SandboxConfig: """Whether to auto-grant read access to the tool directories discovered on PATH and in toolchain environment variables (GOROOT, CARGO_HOME, JAVA_HOME, VIRTUAL_ENV, and similar), and to common developer-tool caches, registries, and toolchains in their - default home locations (cargo, go, npm, Maven, and more), plus read-write access to (and, - on Unix, up-front creation of) the scratch caches builds write on every run (go-build, - ccache, sccache, Gradle caches, Cargo lock/tracker files), so builds work without extra + default home locations (cargo, go, npm, Maven, and more), plus read-write access to (and + up-front creation of) the scratch caches builds write on every run (go-build, ccache, + sccache, Gradle caches, Cargo lock/tracker files), so builds work without extra configuration; a relocated CARGO_HOME additionally gets its Cargo lock files granted read-write. Set to false to disable every grant listed above: user-installed toolchains (rustup, nvm, pyenv, conda, pipx) then need explicit userPolicy.filesystem entries — @@ -30339,11 +30593,15 @@ class SessionOpenOptions: additional_directories: list[str] | None = None """Additional directories the agent may access beyond the working directory. Each entry is granted to the session's file-access allow-list and surfaced to the model (system prompt - context and `@`-mention completion). Absolute paths are recommended; a relative path is - resolved against the session's working directory. Nonexistent or unresolvable entries are - skipped with a warning. This is applied on both session creation and resume, and is not - persisted: a resumed session that omits this option does not retain previously supplied - directories (re-supply them, exactly as the CLI re-passes `--add-dir`). + context and `@`-mention completion). Conventional `.github/skills/` and `.github/agents/` + definitions under each directory also join the session's project catalogs when their + existing subsystem gates are enabled: added-root skills require both + `enableConfigDiscovery` and effective `enableSkills`; added-root agents require + `enableConfigDiscovery`. Supplying a directory therefore activates configuration from it + and should be treated as a trust decision. Absolute paths are recommended; a relative + path is resolved against the session's working directory. Nonexistent or unresolvable + entries are skipped with a warning. This is applied during session creation and cold + resume and is not persisted, so a cold resume must re-supply the directories. """ agent_context: str | None = None """Runtime context discriminator for agent filtering.""" @@ -30472,6 +30730,11 @@ class SessionOpenOptions: are available, subject to runtime availability and exclusions. Custom agents with the same name remain available. """ + included_builtin_skills: list[str] | None = None + """Built-in skill names to include in this session. When specified, only these + runtime-bundled skills are available. Skills from other sources with the same name remain + available. + """ installed_plugins: list[InstalledPlugin] | None = None """Installed plugins visible to the session.""" @@ -30541,6 +30804,11 @@ class SessionOpenOptions: sandbox_config: SandboxConfig | None = None """Resolved sandbox configuration.""" + # Internal: this field is an internal SDK API and is not part of the public surface. + sandbox_config_source: _SandboxConfigSource | None = None + """Origin of the sandbox choice. The runtime uses this only for internal telemetry + provenance; managed policy is derived independently. + """ session_capabilities: list[SessionCapability] | None = None """Capabilities enabled for this session.""" @@ -30614,6 +30882,7 @@ 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")) included_builtin_agents = from_union([lambda x: from_list(from_str, x), from_none], obj.get("includedBuiltinAgents")) + included_builtin_skills = from_union([lambda x: from_list(from_str, x), from_none], obj.get("includedBuiltinSkills")) installed_plugins = from_union([lambda x: from_list(InstalledPlugin.from_dict, x), from_none], obj.get("installedPlugins")) integration_id = from_union([from_str, from_none], obj.get("integrationId")) is_experimental_mode = from_union([from_bool, from_none], obj.get("isExperimentalMode")) @@ -30635,6 +30904,7 @@ def from_dict(obj: Any) -> 'SessionOpenOptions': remote_steerable = from_union([from_bool, from_none], obj.get("remoteSteerable")) running_in_interactive_mode = from_union([from_bool, from_none], obj.get("runningInInteractiveMode")) sandbox_config = from_union([SandboxConfig.from_dict, from_none], obj.get("sandboxConfig")) + sandbox_config_source = from_union([_SandboxConfigSource, from_none], obj.get("sandboxConfigSource")) session_capabilities = from_union([lambda x: from_list(SessionCapability, x), from_none], obj.get("sessionCapabilities")) session_id = from_union([from_str, from_none], obj.get("sessionId")) session_limits = from_union([SessionLimitsConfig.from_dict, from_none], obj.get("sessionLimits")) @@ -30647,7 +30917,7 @@ def from_dict(obj: Any) -> 'SessionOpenOptions': verbosity = from_union([Verbosity, from_none], obj.get("verbosity")) working_directory = from_union([from_str, from_none], obj.get("workingDirectory")) working_directory_context = from_union([SessionContext.from_dict, from_none], obj.get("workingDirectoryContext")) - return SessionOpenOptions(additional_content_exclusion_policies, additional_directories, agent_context, allow_all_mcp_server_instructions, ask_user_disabled, auth_info, available_tools, capi, client_kind, client_name, coauthor_enabled, config_dir, continue_on_auto_mode, copilot_url, custom_agents_local_only, detached_from_spawning_parent_engagement_id, detached_from_spawning_parent_session_id, disabled_instruction_sources, disabled_mcp_servers, disabled_skills, enable_citations, enable_file_change_tracking, enable_managed_settings, enable_on_demand_instruction_discovery, enable_script_safety, enable_streaming, env_value_mode, events_log_directory, events_log_includes_subagents, excluded_builtin_agents, excluded_tools, exp_assignments, feature_flags, included_builtin_agents, installed_plugins, integration_id, is_experimental_mode, log_interactive_shells, lsp_client_name, managed_settings, max_inline_binary_bytes, memory, model, model_capabilities_overrides, models, name, provider, providers, reasoning_effort, reasoning_summary, remote_defaulted_on, remote_exporting, remote_steerable, running_in_interactive_mode, sandbox_config, session_capabilities, session_id, session_limits, shell, shell_init_profile, shell_process_flags, skill_directories, skip_custom_instructions, trajectory_file, verbosity, working_directory, working_directory_context) + return SessionOpenOptions(additional_content_exclusion_policies, additional_directories, agent_context, allow_all_mcp_server_instructions, ask_user_disabled, auth_info, available_tools, capi, client_kind, client_name, coauthor_enabled, config_dir, continue_on_auto_mode, copilot_url, custom_agents_local_only, detached_from_spawning_parent_engagement_id, detached_from_spawning_parent_session_id, disabled_instruction_sources, disabled_mcp_servers, disabled_skills, enable_citations, enable_file_change_tracking, enable_managed_settings, enable_on_demand_instruction_discovery, enable_script_safety, enable_streaming, env_value_mode, events_log_directory, events_log_includes_subagents, excluded_builtin_agents, excluded_tools, exp_assignments, feature_flags, included_builtin_agents, included_builtin_skills, installed_plugins, integration_id, is_experimental_mode, log_interactive_shells, lsp_client_name, managed_settings, max_inline_binary_bytes, memory, model, model_capabilities_overrides, models, name, provider, providers, reasoning_effort, reasoning_summary, remote_defaulted_on, remote_exporting, remote_steerable, running_in_interactive_mode, sandbox_config, sandbox_config_source, session_capabilities, session_id, session_limits, shell, shell_init_profile, shell_process_flags, skill_directories, skip_custom_instructions, trajectory_file, verbosity, working_directory, working_directory_context) def to_dict(self) -> dict: result: dict = {} @@ -30719,6 +30989,8 @@ def to_dict(self) -> dict: result["featureFlags"] = from_union([lambda x: from_dict(from_bool, x), from_none], self.feature_flags) if self.included_builtin_agents is not None: result["includedBuiltinAgents"] = from_union([lambda x: from_list(from_str, x), from_none], self.included_builtin_agents) + if self.included_builtin_skills is not None: + result["includedBuiltinSkills"] = from_union([lambda x: from_list(from_str, x), from_none], self.included_builtin_skills) if self.installed_plugins is not None: result["installedPlugins"] = from_union([lambda x: from_list(lambda x: to_class(InstalledPlugin, x), x), from_none], self.installed_plugins) if self.integration_id is not None: @@ -30761,6 +31033,8 @@ def to_dict(self) -> dict: result["runningInInteractiveMode"] = from_union([from_bool, from_none], self.running_in_interactive_mode) if self.sandbox_config is not None: result["sandboxConfig"] = from_union([lambda x: to_class(SandboxConfig, x), from_none], self.sandbox_config) + if self.sandbox_config_source is not None: + result["sandboxConfigSource"] = from_union([lambda x: to_enum(_SandboxConfigSource, x), from_none], self.sandbox_config_source) if self.session_capabilities is not None: result["sessionCapabilities"] = from_union([lambda x: from_list(lambda x: to_enum(SessionCapability, x), x), from_none], self.session_capabilities) if self.session_id is not None: @@ -30893,6 +31167,11 @@ class SessionUpdateOptionsParams: are available, subject to runtime availability and exclusions. Custom agents with the same name remain available. Set to null to remove the allowlist restriction. """ + included_builtin_skills: list[str] | None = None + """Built-in skill names to include in this session. When specified, only these + runtime-bundled skills are available. Skills from other sources with the same name remain + available. Set to null to remove the allowlist restriction. + """ installed_plugins: list[SessionInstalledPlugin] | None = None """Full set of installed plugins for the session. Replaces the existing list; the runtime invalidates the skills cache only when the list materially changes. @@ -30946,6 +31225,11 @@ class SessionUpdateOptionsParams: sandbox_config: SandboxConfig | None = None """Resolved sandbox configuration.""" + # Internal: this field is an internal SDK API and is not part of the public surface. + sandbox_config_source: _SandboxConfigSource | None = None + """Origin of the sandbox choice. The runtime uses this only for internal telemetry + provenance; managed policy is derived independently. + """ session_capabilities: list[SessionCapability] | None = None """Replaces the session's capability set with the given list. Use to enable or disable capabilities mid-session (e.g., remove `memory` for reproducible scripted runs). Omit the @@ -31022,6 +31306,7 @@ def from_dict(obj: Any) -> 'SessionUpdateOptionsParams': excluded_tools = from_union([lambda x: from_list(from_str, x), from_none], obj.get("excludedTools")) feature_flags = from_union([lambda x: from_dict(from_bool, x), from_none], obj.get("featureFlags")) included_builtin_agents = from_union([lambda x: from_list(from_str, x), from_none], obj.get("includedBuiltinAgents")) + included_builtin_skills = from_union([lambda x: from_list(from_str, x), from_none], obj.get("includedBuiltinSkills")) installed_plugins = from_union([lambda x: from_list(SessionInstalledPlugin.from_dict, x), from_none], obj.get("installedPlugins")) integration_id = from_union([from_str, from_none], obj.get("integrationId")) is_experimental_mode = from_union([from_bool, from_none], obj.get("isExperimentalMode")) @@ -31037,6 +31322,7 @@ def from_dict(obj: Any) -> 'SessionUpdateOptionsParams': reasoning_summary = from_union([ReasoningSummary, from_none], obj.get("reasoningSummary")) running_in_interactive_mode = from_union([from_bool, from_none], obj.get("runningInInteractiveMode")) sandbox_config = from_union([SandboxConfig.from_dict, from_none], obj.get("sandboxConfig")) + sandbox_config_source = from_union([_SandboxConfigSource, from_none], obj.get("sandboxConfigSource")) session_capabilities = from_union([lambda x: from_list(SessionCapability, x), from_none], obj.get("sessionCapabilities")) session_limits = from_union([SessionLimitsConfig.from_dict, from_none], obj.get("sessionLimits")) shell = from_union([ShellOptions.from_dict, from_none], obj.get("shell")) @@ -31050,7 +31336,7 @@ def from_dict(obj: Any) -> 'SessionUpdateOptionsParams': trajectory_file = from_union([from_str, from_none], obj.get("trajectoryFile")) verbosity = from_union([Verbosity, from_none], obj.get("verbosity")) working_directory = from_union([from_str, from_none], obj.get("workingDirectory")) - return SessionUpdateOptionsParams(additional_content_exclusion_policies, agent_context, allow_all_mcp_server_instructions, ask_user_disabled, available_tools, capi, client_name, coauthor_enabled, context_tier, continue_on_auto_mode, copilot_url, custom_agents_local_only, disabled_instruction_sources, disabled_skills, enable_file_hooks, enable_host_git_operations, enable_on_demand_instruction_discovery, enable_reasoning_summaries, enable_script_safety, enable_session_store, enable_skills, enable_streaming, env_value_mode, events_log_directory, events_log_includes_subagents, excluded_builtin_agents, excluded_tools, feature_flags, included_builtin_agents, installed_plugins, integration_id, is_experimental_mode, log_interactive_shells, lsp_client_name, manage_schedule_enabled, max_inline_binary_bytes, model, model_capabilities_overrides, organization_custom_instructions, provider, reasoning_effort, reasoning_summary, running_in_interactive_mode, sandbox_config, session_capabilities, session_limits, shell, shell_init_profile, shell_process_flags, skill_directories, skip_custom_instructions, skip_embedding_retrieval, suppress_custom_agent_prompt, tool_filter_precedence, trajectory_file, verbosity, working_directory) + return SessionUpdateOptionsParams(additional_content_exclusion_policies, agent_context, allow_all_mcp_server_instructions, ask_user_disabled, available_tools, capi, client_name, coauthor_enabled, context_tier, continue_on_auto_mode, copilot_url, custom_agents_local_only, disabled_instruction_sources, disabled_skills, enable_file_hooks, enable_host_git_operations, enable_on_demand_instruction_discovery, enable_reasoning_summaries, enable_script_safety, enable_session_store, enable_skills, enable_streaming, env_value_mode, events_log_directory, events_log_includes_subagents, excluded_builtin_agents, excluded_tools, feature_flags, included_builtin_agents, included_builtin_skills, installed_plugins, integration_id, is_experimental_mode, log_interactive_shells, lsp_client_name, manage_schedule_enabled, max_inline_binary_bytes, model, model_capabilities_overrides, organization_custom_instructions, provider, reasoning_effort, reasoning_summary, running_in_interactive_mode, sandbox_config, sandbox_config_source, session_capabilities, session_limits, shell, shell_init_profile, shell_process_flags, skill_directories, skip_custom_instructions, skip_embedding_retrieval, suppress_custom_agent_prompt, tool_filter_precedence, trajectory_file, verbosity, working_directory) def to_dict(self) -> dict: result: dict = {} @@ -31112,6 +31398,8 @@ def to_dict(self) -> dict: result["featureFlags"] = from_union([lambda x: from_dict(from_bool, x), from_none], self.feature_flags) if self.included_builtin_agents is not None: result["includedBuiltinAgents"] = from_union([lambda x: from_list(from_str, x), from_none], self.included_builtin_agents) + if self.included_builtin_skills is not None: + result["includedBuiltinSkills"] = from_union([lambda x: from_list(from_str, x), from_none], self.included_builtin_skills) if self.installed_plugins is not None: result["installedPlugins"] = from_union([lambda x: from_list(lambda x: to_class(SessionInstalledPlugin, x), x), from_none], self.installed_plugins) if self.integration_id is not None: @@ -31142,6 +31430,8 @@ def to_dict(self) -> dict: result["runningInInteractiveMode"] = from_union([from_bool, from_none], self.running_in_interactive_mode) if self.sandbox_config is not None: result["sandboxConfig"] = from_union([lambda x: to_class(SandboxConfig, x), from_none], self.sandbox_config) + if self.sandbox_config_source is not None: + result["sandboxConfigSource"] = from_union([lambda x: to_enum(_SandboxConfigSource, x), from_none], self.sandbox_config_source) if self.session_capabilities is not None: result["sessionCapabilities"] = from_union([lambda x: from_list(lambda x: to_enum(SessionCapability, x), x), from_none], self.session_capabilities) if self.session_limits is not None: @@ -31361,6 +31651,8 @@ class CopilotUserResponse: GitHub API `/copilot_internal/v2/token` user response shape — the runtime trusts this verbatim and does not re-fetch when set. + Snapshot of the authenticated user's Copilot subscription info, if known. + Snapshot of the authenticated user's Copilot subscription info, if known """ access_type_sku: str | None = None @@ -31812,6 +32104,11 @@ class AuthIdentity: 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) @@ -31820,7 +32117,8 @@ def from_dict(obj: Any) -> 'AuthIdentity': 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")) - return AuthIdentity(host, type, copilot_user, env_var, 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 = {} @@ -31832,6 +32130,8 @@ def to_dict(self) -> dict: 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. @@ -33354,6 +33654,11 @@ class Model: default_reasoning_effort: str | None = None """Default reasoning effort level (only present if model supports reasoning effort)""" + info_messages: list[ModelMessage] | None = None + """Informational notices the service published for this model, such as an upcoming change or + a recommended alternative. Present only when the service published at least one notice. + Hosts should surface these without implying anything is wrong with the model. + """ model_picker_category: ModelPickerCategory | None = None """Model capability category for grouping in the model picker""" @@ -33372,6 +33677,16 @@ class Model: supported_reasoning_efforts: list[str] | None = None """Supported reasoning effort levels (only present if model supports reasoning effort)""" + warning_messages: list[ModelMessage] | None = None + """Warnings the service published for this model, such as a deprecated client version. + Present only when the service published at least one warning. The model remains usable; + hosts should surface these as advisory rather than blocking. + """ + warning_text: ModelWarningText | None = None + """Warning text the service requires hosts to surface for this model. Present only when the + service published at least one warning. + """ + @staticmethod def from_dict(obj: Any) -> 'Model': assert isinstance(obj, dict) @@ -33380,12 +33695,15 @@ def from_dict(obj: Any) -> 'Model': name = from_str(obj.get("name")) billing = from_union([ModelBilling.from_dict, from_none], obj.get("billing")) default_reasoning_effort = from_union([from_str, from_none], obj.get("defaultReasoningEffort")) + info_messages = from_union([lambda x: from_list(ModelMessage.from_dict, x), from_none], obj.get("infoMessages")) model_picker_category = from_union([ModelPickerCategory, from_none], obj.get("modelPickerCategory")) model_picker_price_category = from_union([ModelPickerPriceCategory, from_none], obj.get("modelPickerPriceCategory")) policy = from_union([ModelPolicy.from_dict, from_none], obj.get("policy")) supported_context_tiers = from_union([lambda x: from_list(from_str, x), from_none], obj.get("supportedContextTiers")) supported_reasoning_efforts = from_union([lambda x: from_list(from_str, x), from_none], obj.get("supportedReasoningEfforts")) - return Model(capabilities, id, name, billing, default_reasoning_effort, model_picker_category, model_picker_price_category, policy, supported_context_tiers, supported_reasoning_efforts) + warning_messages = from_union([lambda x: from_list(ModelMessage.from_dict, x), from_none], obj.get("warningMessages")) + warning_text = from_union([ModelWarningText.from_dict, from_none], obj.get("warningText")) + return Model(capabilities, id, name, billing, default_reasoning_effort, info_messages, model_picker_category, model_picker_price_category, policy, supported_context_tiers, supported_reasoning_efforts, warning_messages, warning_text) def to_dict(self) -> dict: result: dict = {} @@ -33396,6 +33714,8 @@ def to_dict(self) -> dict: result["billing"] = from_union([lambda x: to_class(ModelBilling, x), from_none], self.billing) if self.default_reasoning_effort is not None: result["defaultReasoningEffort"] = from_union([from_str, from_none], self.default_reasoning_effort) + if self.info_messages is not None: + result["infoMessages"] = from_union([lambda x: from_list(lambda x: to_class(ModelMessage, x), x), from_none], self.info_messages) if self.model_picker_category is not None: result["modelPickerCategory"] = from_union([lambda x: to_enum(ModelPickerCategory, x), from_none], self.model_picker_category) if self.model_picker_price_category is not None: @@ -33406,6 +33726,10 @@ def to_dict(self) -> dict: result["supportedContextTiers"] = from_union([lambda x: from_list(from_str, x), from_none], self.supported_context_tiers) if self.supported_reasoning_efforts is not None: result["supportedReasoningEfforts"] = from_union([lambda x: from_list(from_str, x), from_none], self.supported_reasoning_efforts) + if self.warning_messages is not None: + result["warningMessages"] = from_union([lambda x: from_list(lambda x: to_class(ModelMessage, x), x), from_none], self.warning_messages) + if self.warning_text is not None: + result["warningText"] = from_union([lambda x: to_class(ModelWarningText, x), from_none], self.warning_text) return result # Experimental: this type is part of an experimental API and may change or be removed. @@ -34115,6 +34439,43 @@ def to_dict(self) -> dict: result["taskType"] = from_union([lambda x: to_enum(TaskType, x), from_none], self.task_type) return result +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class SettableTokenAuthInfo: + """Token authentication accepted by session.gitHubAuth.setCredentials.""" + + host: str + """Authentication host.""" + + token: str + """The token value itself. Treat as a secret.""" + + type: ClassVar[str] = "token" + """SDK-side token authentication; the host configured the token directly via the SDK.""" + + copilot_user: CopilotUserResponse | None = None + """Snapshot of the authenticated user's Copilot subscription info, if known. Mirrors the + GitHub API `/copilot_internal/v2/token` user response shape — the runtime trusts this + verbatim and does not re-fetch when set. + """ + + @staticmethod + def from_dict(obj: Any) -> 'SettableTokenAuthInfo': + assert isinstance(obj, dict) + host = from_str(obj.get("host")) + token = from_str(obj.get("token")) + copilot_user = from_union([CopilotUserResponse.from_dict, from_none], obj.get("copilotUser")) + return SettableTokenAuthInfo(host, token, copilot_user) + + def to_dict(self) -> dict: + result: dict = {} + result["host"] = from_str(self.host) + result["token"] = from_str(self.token) + result["type"] = self.type + if self.copilot_user is not None: + result["copilotUser"] = from_union([lambda x: to_class(CopilotUserResponse, x), from_none], self.copilot_user) + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class SlashCommandModelPickerDialog: @@ -34327,6 +34688,8 @@ class TokenAuthInfo: GitHub API `/copilot_internal/v2/token` user response shape — the runtime trusts this verbatim and does not re-fetch when set. """ + registration_id: str | None = None + """Opaque native GitHub credential registration backing this token identity, when applicable.""" @staticmethod def from_dict(obj: Any) -> 'TokenAuthInfo': @@ -34334,13 +34697,51 @@ def from_dict(obj: Any) -> 'TokenAuthInfo': host = from_str(obj.get("host")) token = from_str(obj.get("token")) copilot_user = from_union([CopilotUserResponse.from_dict, from_none], obj.get("copilotUser")) - return TokenAuthInfo(host, token, copilot_user) + registration_id = from_union([from_str, from_none], obj.get("registrationId")) + return TokenAuthInfo(host, token, copilot_user, registration_id) def to_dict(self) -> dict: result: dict = {} result["host"] = from_str(self.host) result["token"] = from_str(self.token) result["type"] = self.type + if self.copilot_user is not None: + result["copilotUser"] = from_union([lambda x: to_class(CopilotUserResponse, x), from_none], self.copilot_user) + if self.registration_id is not None: + result["registrationId"] = from_union([from_str, from_none], self.registration_id) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class TokenProviderAuthInfo: + """Authentication-info variant backed by an SDK GitHub token callback. It carries routing + metadata but never a plaintext token. + """ + host: str + """Authentication host.""" + + registration_id: str + """Opaque SDK callback registration identifier.""" + + type: ClassVar[str] = "token-provider" + """SDK callback-backed GitHub token authentication.""" + + copilot_user: CopilotUserResponse | None = None + """Snapshot of the authenticated user's Copilot subscription info, if known.""" + + @staticmethod + def from_dict(obj: Any) -> 'TokenProviderAuthInfo': + assert isinstance(obj, dict) + host = from_str(obj.get("host")) + registration_id = from_str(obj.get("registrationId")) + copilot_user = from_union([CopilotUserResponse.from_dict, from_none], obj.get("copilotUser")) + return TokenProviderAuthInfo(host, registration_id, copilot_user) + + def to_dict(self) -> dict: + result: dict = {} + result["host"] = from_str(self.host) + result["registrationId"] = from_str(self.registration_id) + result["type"] = self.type if self.copilot_user is not None: result["copilotUser"] = from_union([lambda x: to_class(CopilotUserResponse, x), from_none], self.copilot_user) return result @@ -34640,6 +35041,7 @@ class RPC: completions_request_request: CompletionsRequestRequest completions_request_result: CompletionsRequestResult configure_session_extensions_params: _ConfigureSessionExtensionsParams + connect_client_info: _ConnectClientInfo connected_remote_session_metadata: ConnectedRemoteSessionMetadata connected_remote_session_metadata_kind: ConnectedRemoteSessionMetadataKind connected_remote_session_metadata_repository: ConnectedRemoteSessionMetadataRepository @@ -34671,7 +35073,6 @@ class RPC: debug_collect_logs_result_kind: DebugCollectLogsResultKind debug_collect_logs_skipped_entry: DebugCollectLogsSkippedEntry debug_collect_logs_source: DebugCollectLogsSource - disable_bypass_permissions_mode: DisableBypassPermissionsMode discovered_canvas: DiscoveredCanvas discovered_extension: DiscoveredExtension discovered_extension_mode: DiscoveredExtensionMode @@ -34768,6 +35169,9 @@ class RPC: git_hub_telemetry_client_info: GitHubTelemetryClientInfo git_hub_telemetry_event: GitHubTelemetryEvent git_hub_telemetry_notification: GitHubTelemetryNotification + git_hub_token_acquire_reason: GitHubTokenAcquireReason + git_hub_token_acquire_request: GitHubTokenAcquireRequest + git_hub_token_acquire_result: GitHubTokenAcquireResult handle_pending_tool_call_request: HandlePendingToolCallRequest handle_pending_tool_call_result: HandlePendingToolCallResult history_abort_manual_compaction_result: HistoryAbortManualCompactionResult @@ -35024,6 +35428,7 @@ class RPC: model_capabilities_supports: ModelCapabilitiesSupports model_list: ModelList model_list_request: Any + model_message: ModelMessage model_picker_category: ModelPickerCategory model_picker_persistence_request: ModelPickerPersistenceRequest model_picker_price_category: ModelPickerPriceCategory @@ -35036,6 +35441,7 @@ class RPC: model_switch_confirmation: ModelSwitchConfirmation model_switch_to_request: ModelSwitchToRequest model_switch_to_result: ModelSwitchToResult + model_warning_text: ModelWarningText mode_set_request: ModeSetRequest mode_set_result: ModeSetResult move_mcp_loading_to_background_result: MoveMCPLoadingToBackgroundResult @@ -35286,6 +35692,7 @@ class RPC: run_options: RunOptions sandbox_config: SandboxConfig sandbox_config_auth: SandboxConfigAuth + sandbox_config_source: _SandboxConfigSource sandbox_config_user_policy: SandboxConfigUserPolicy sandbox_config_user_policy_experimental: SandboxConfigUserPolicyExperimental sandbox_config_user_policy_experimental_seatbelt: SandboxConfigUserPolicyExperimentalSeatbelt @@ -35483,6 +35890,8 @@ class RPC: session_visibility_status: SessionVisibilityStatus session_working_directory_context: SessionWorkingDirectoryContext session_working_directory_context_host_type: HostType + settable_auth_info: SettableAuthInfo + settable_token_auth_info: SettableTokenAuthInfo shell_cancel_user_requested_request: ShellCancelUserRequestedRequest shell_credentials: ShellCredentials shell_exec_request: ShellExecRequest @@ -35559,6 +35968,7 @@ class RPC: tasks_wait_for_pending_result: TasksWaitForPendingResult telemetry_set_feature_overrides_request: TelemetrySetFeatureOverridesRequest token_auth_info: TokenAuthInfo + token_provider_auth_info: TokenProviderAuthInfo tool: Tool tool_list: ToolList tool_result: ToolResultExpanded | str @@ -35810,6 +36220,7 @@ def from_dict(obj: Any) -> 'RPC': completions_request_request = CompletionsRequestRequest.from_dict(obj.get("CompletionsRequestRequest")) completions_request_result = CompletionsRequestResult.from_dict(obj.get("CompletionsRequestResult")) configure_session_extensions_params = _ConfigureSessionExtensionsParams.from_dict(obj.get("ConfigureSessionExtensionsParams")) + connect_client_info = _ConnectClientInfo.from_dict(obj.get("ConnectClientInfo")) connected_remote_session_metadata = ConnectedRemoteSessionMetadata.from_dict(obj.get("ConnectedRemoteSessionMetadata")) connected_remote_session_metadata_kind = ConnectedRemoteSessionMetadataKind(obj.get("ConnectedRemoteSessionMetadataKind")) connected_remote_session_metadata_repository = ConnectedRemoteSessionMetadataRepository.from_dict(obj.get("ConnectedRemoteSessionMetadataRepository")) @@ -35841,7 +36252,6 @@ def from_dict(obj: Any) -> 'RPC': debug_collect_logs_result_kind = DebugCollectLogsResultKind(obj.get("DebugCollectLogsResultKind")) debug_collect_logs_skipped_entry = DebugCollectLogsSkippedEntry.from_dict(obj.get("DebugCollectLogsSkippedEntry")) debug_collect_logs_source = DebugCollectLogsSource(obj.get("DebugCollectLogsSource")) - disable_bypass_permissions_mode = DisableBypassPermissionsMode(obj.get("DisableBypassPermissionsMode")) discovered_canvas = DiscoveredCanvas.from_dict(obj.get("DiscoveredCanvas")) discovered_extension = DiscoveredExtension.from_dict(obj.get("DiscoveredExtension")) discovered_extension_mode = DiscoveredExtensionMode(obj.get("DiscoveredExtensionMode")) @@ -35938,6 +36348,9 @@ def from_dict(obj: Any) -> 'RPC': git_hub_telemetry_client_info = GitHubTelemetryClientInfo.from_dict(obj.get("GitHubTelemetryClientInfo")) git_hub_telemetry_event = GitHubTelemetryEvent.from_dict(obj.get("GitHubTelemetryEvent")) git_hub_telemetry_notification = GitHubTelemetryNotification.from_dict(obj.get("GitHubTelemetryNotification")) + git_hub_token_acquire_reason = GitHubTokenAcquireReason(obj.get("GitHubTokenAcquireReason")) + git_hub_token_acquire_request = GitHubTokenAcquireRequest.from_dict(obj.get("GitHubTokenAcquireRequest")) + git_hub_token_acquire_result = GitHubTokenAcquireResult.from_dict(obj.get("GitHubTokenAcquireResult")) handle_pending_tool_call_request = HandlePendingToolCallRequest.from_dict(obj.get("HandlePendingToolCallRequest")) handle_pending_tool_call_result = HandlePendingToolCallResult.from_dict(obj.get("HandlePendingToolCallResult")) history_abort_manual_compaction_result = HistoryAbortManualCompactionResult.from_dict(obj.get("HistoryAbortManualCompactionResult")) @@ -36194,6 +36607,7 @@ def from_dict(obj: Any) -> 'RPC': model_capabilities_supports = ModelCapabilitiesSupports.from_dict(obj.get("ModelCapabilitiesSupports")) model_list = ModelList.from_dict(obj.get("ModelList")) model_list_request = obj.get("ModelListRequest") + model_message = ModelMessage.from_dict(obj.get("ModelMessage")) model_picker_category = ModelPickerCategory(obj.get("ModelPickerCategory")) model_picker_persistence_request = ModelPickerPersistenceRequest.from_dict(obj.get("ModelPickerPersistenceRequest")) model_picker_price_category = ModelPickerPriceCategory(obj.get("ModelPickerPriceCategory")) @@ -36206,6 +36620,7 @@ def from_dict(obj: Any) -> 'RPC': model_switch_confirmation = ModelSwitchConfirmation.from_dict(obj.get("ModelSwitchConfirmation")) model_switch_to_request = ModelSwitchToRequest.from_dict(obj.get("ModelSwitchToRequest")) model_switch_to_result = ModelSwitchToResult.from_dict(obj.get("ModelSwitchToResult")) + model_warning_text = ModelWarningText.from_dict(obj.get("ModelWarningText")) mode_set_request = ModeSetRequest.from_dict(obj.get("ModeSetRequest")) mode_set_result = ModeSetResult.from_dict(obj.get("ModeSetResult")) move_mcp_loading_to_background_result = MoveMCPLoadingToBackgroundResult.from_dict(obj.get("MoveMcpLoadingToBackgroundResult")) @@ -36456,6 +36871,7 @@ def from_dict(obj: Any) -> 'RPC': run_options = RunOptions.from_dict(obj.get("RunOptions")) sandbox_config = SandboxConfig.from_dict(obj.get("SandboxConfig")) sandbox_config_auth = SandboxConfigAuth.from_dict(obj.get("SandboxConfigAuth")) + sandbox_config_source = _SandboxConfigSource(obj.get("SandboxConfigSource")) sandbox_config_user_policy = SandboxConfigUserPolicy.from_dict(obj.get("SandboxConfigUserPolicy")) sandbox_config_user_policy_experimental = SandboxConfigUserPolicyExperimental.from_dict(obj.get("SandboxConfigUserPolicyExperimental")) sandbox_config_user_policy_experimental_seatbelt = SandboxConfigUserPolicyExperimentalSeatbelt.from_dict(obj.get("SandboxConfigUserPolicyExperimentalSeatbelt")) @@ -36653,6 +37069,8 @@ def from_dict(obj: Any) -> 'RPC': session_visibility_status = SessionVisibilityStatus(obj.get("SessionVisibilityStatus")) session_working_directory_context = SessionWorkingDirectoryContext.from_dict(obj.get("SessionWorkingDirectoryContext")) session_working_directory_context_host_type = HostType(obj.get("SessionWorkingDirectoryContextHostType")) + settable_auth_info = _load_SettableAuthInfo(obj.get("SettableAuthInfo")) + settable_token_auth_info = SettableTokenAuthInfo.from_dict(obj.get("SettableTokenAuthInfo")) shell_cancel_user_requested_request = ShellCancelUserRequestedRequest.from_dict(obj.get("ShellCancelUserRequestedRequest")) shell_credentials = ShellCredentials.from_dict(obj.get("ShellCredentials")) shell_exec_request = ShellExecRequest.from_dict(obj.get("ShellExecRequest")) @@ -36729,6 +37147,7 @@ def from_dict(obj: Any) -> 'RPC': tasks_wait_for_pending_result = TasksWaitForPendingResult.from_dict(obj.get("TasksWaitForPendingResult")) telemetry_set_feature_overrides_request = TelemetrySetFeatureOverridesRequest.from_dict(obj.get("TelemetrySetFeatureOverridesRequest")) token_auth_info = TokenAuthInfo.from_dict(obj.get("TokenAuthInfo")) + token_provider_auth_info = TokenProviderAuthInfo.from_dict(obj.get("TokenProviderAuthInfo")) tool = Tool.from_dict(obj.get("Tool")) tool_list = ToolList.from_dict(obj.get("ToolList")) tool_result = from_union([ToolResultExpanded.from_dict, from_str], obj.get("ToolResult")) @@ -36838,7 +37257,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, account_all_users, account_get_all_users_result, account_get_current_auth_result, account_get_quota_request, account_get_quota_result, account_login_request, account_login_result, account_logout_request, account_logout_result, account_quota_snapshot, adaptive_thinking_support, agent_discovery_path, agent_discovery_path_list, agent_discovery_path_scope, agent_get_current_result, agent_info, agent_info_source, agent_list, agent_list_request, agent_registry_live_target_entry, agent_registry_live_target_entry_attention_kind, agent_registry_live_target_entry_kind, agent_registry_live_target_entry_last_terminal_event, agent_registry_live_target_entry_status, agent_registry_log_capture, agent_registry_log_capture_open_error_reason, agent_registry_spawn_error, agent_registry_spawn_permission_mode, agent_registry_spawn_registry_timeout, agent_registry_spawn_request, agent_registry_spawn_result, agent_registry_spawn_spawned, agent_registry_spawn_validation_error, agent_registry_spawn_validation_error_field, agent_registry_spawn_validation_error_reason, agent_reload_result, agents_discover_request, agent_select_request, agent_select_result, agent_set_prompt_request, agents_get_discovery_paths_request, api_key_auth_info, auth_identity, auth_info, auth_info_type, auth_validation_error, auth_validation_errors, built_in_model_catalog, built_in_model_catalog_entry, builtin_tool_descriptor, builtin_tool_format, builtin_tool_format_type, builtin_tool_input_schema, builtin_tool_input_schema_type, builtin_tool_safe_for_telemetry, builtin_tool_safe_telemetry_fields, cancel_user_requested_shell_command_result, canvas_action, canvas_action_invoke_request, canvas_action_invoke_result, canvas_close_request, canvas_host_context, canvas_host_context_capabilities, canvas_json_schema, canvas_list, canvas_list_open_result, canvas_open_request, canvas_provider_close_request, canvas_provider_invoke_action_request, canvas_provider_open_request, canvas_provider_open_result, canvas_provider_register_request, canvas_provider_unregister_request, canvas_session_context, capi_session_options, card_digest, card_digest_algorithm, card_digest_value, catalog_ai_skill_candidate, catalog_ai_skill_candidate_provenance, catalog_authentication_required_error, catalog_authentication_required_reason, catalog_candidate, catalog_candidate_kind, catalog_candidate_source, catalog_candidate_source_embedded, catalog_candidate_source_url, catalog_capability, catalog_capability_id, catalog_client_contract, catalog_contract_violation_error, catalog_contract_violation_reason, catalog_handle_rejected_error, catalog_handle_rejection_reason, catalog_handle_type, catalog_invalid_request_error, catalog_invalid_request_field, catalog_malformed_card_error, catalog_malformed_card_reason, catalog_mcp_server_candidate, catalog_mcp_server_candidate_provenance, catalog_mcp_server_installability, catalog_media_type, catalog_negotiated_contract, catalog_negotiation_refused_error, catalog_negotiation_refused_reason, catalog_network_failure_error, catalog_network_failure_reason, catalog_not_installable_error, catalog_not_installable_reason, catalog_policy_rejected_error, catalog_search_request, catalog_search_result, catalog_search_succeeded, catalog_unavailable_error, catalog_unavailable_reason, catalog_unavailable_transport_error, catalog_unavailable_transport_reason, catalog_unsafe_retrieval_error, catalog_unsafe_retrieval_reason, catalog_unsupported_kind_error, command_list, commands_finalize_invocation_effect_request, commands_finalize_invocation_effect_result, commands_handle_pending_command_request, commands_handle_pending_command_result, commands_invocation_effect_outcome, commands_invocation_origin, commands_invoke_request, commands_list_request, commands_respond_to_queued_command_request, commands_respond_to_queued_command_result, completions_get_trigger_characters_result, completions_request_request, completions_request_result, configure_session_extensions_params, connected_remote_session_metadata, connected_remote_session_metadata_kind, connected_remote_session_metadata_repository, connect_remote_session_params, connect_request, connect_result, content_exclusion_check_paths_request, content_exclusion_check_paths_result, content_exclusion_path_check, content_filter_mode, context_heaviest_message, copilot_api_token_auth_info, copilot_user_response, copilot_user_response_endpoints, copilot_user_response_quota_snapshots, copilot_user_response_quota_snapshots_chat, copilot_user_response_quota_snapshots_completions, copilot_user_response_quota_snapshots_premium_interactions, current_model, current_tool_metadata, debug_collect_logs_collected_entry, debug_collect_logs_destination, debug_collect_logs_entry, debug_collect_logs_entry_kind, debug_collect_logs_include, debug_collect_logs_redaction, debug_collect_logs_request, debug_collect_logs_result, debug_collect_logs_result_kind, debug_collect_logs_skipped_entry, debug_collect_logs_source, disable_bypass_permissions_mode, discovered_canvas, discovered_extension, discovered_extension_mode, discovered_extension_plugin, discovered_extensions, discovered_extensions_disable_request, discovered_extensions_enable_request, discovered_extension_source, discovered_mcp_server, discovered_mcp_server_type, enqueue_command_params, enqueue_command_result, env_auth_info, event_log_read_request, event_log_release_interest_result, event_log_tail_result, event_log_types, events_agent_scope, events_cursor_status, events_read_direction, events_read_result, execute_command_params, execute_command_result, extension, extension_context_push_input, extension_launch_profile, extension_launch_provider_resolve_request, extension_launch_provider_resolve_result, extension_list, extensions_disable_request, extensions_enable_request, extension_source, extension_status, external_tool_result, external_tool_text_result_for_llm, external_tool_text_result_for_llm_binary_results_for_llm, external_tool_text_result_for_llm_binary_results_for_llm_type, external_tool_text_result_for_llm_content, external_tool_text_result_for_llm_content_audio, external_tool_text_result_for_llm_content_image, external_tool_text_result_for_llm_content_resource, external_tool_text_result_for_llm_content_resource_details, external_tool_text_result_for_llm_content_resource_link, external_tool_text_result_for_llm_content_resource_link_icon, external_tool_text_result_for_llm_content_resource_link_icon_theme, external_tool_text_result_for_llm_content_shell_exit, external_tool_text_result_for_llm_content_terminal, external_tool_text_result_for_llm_content_text, factory_abort_request, factory_ack_result, factory_agent_options, factory_agent_request, factory_agent_result, factory_agent_summary, factory_cancel_request, factory_current_phase, factory_declared_limits, factory_durable_operation, factory_execute_request, factory_execute_result, factory_get_run_progress_request, factory_get_run_request, factory_journal_get_request, factory_journal_get_result, factory_journal_put_request, factory_list_runs_request, factory_list_runs_result, factory_log_line, factory_log_line_kind, factory_log_request, factory_phase_observation, factory_phase_status, factory_progress_line, factory_progress_page, factory_resume_request, factory_resume_result, factory_run_consumed, factory_run_detail, factory_run_failure, factory_run_failure_kind, factory_run_limits, factory_run_request, factory_run_result, factory_run_status, factory_run_summary, factory_run_terminal, filter_mapping, fleet_start_request, fleet_start_result, folder_trust_add_params, folder_trust_check_params, folder_trust_check_result, gh_cli_auth_info, git_hub_telemetry_client_info, git_hub_telemetry_event, git_hub_telemetry_notification, handle_pending_tool_call_request, handle_pending_tool_call_result, history_abort_manual_compaction_result, history_cancel_background_compaction_result, history_clear_context_request, history_clear_context_result, history_compact_context_window, history_compact_request, history_compact_result, history_file_restore_skip_reason, history_list_rewind_points_result, history_preview_rewind_request, history_preview_rewind_result, history_rewind_change_type, history_rewind_file_preview, history_rewind_mode, history_rewind_outcome, history_rewind_point, history_rewind_request, history_rewind_result, history_rewind_unavailable_reason, history_skipped_file_restore, history_summarize_for_handoff_result, history_truncate_request, history_truncate_result, hmac_auth_info, hook_invoke_request, hook_invoke_response, hook_type, installed_plugin, installed_plugin_info, installed_plugin_source, installed_plugin_source_git_hub, installed_plugin_source_local, installed_plugin_source_url, instruction_discovery_path, instruction_discovery_path_kind, instruction_discovery_path_list, instruction_discovery_path_location, instructions_discover_request, instructions_get_discovery_paths_request, instructions_get_sources_result, instruction_source, instruction_source_location, instruction_source_type, interrupt_main_turn_request, interrupt_main_turn_result, llm_inference_headers, llm_inference_http_request_chunk_request, llm_inference_http_request_chunk_result, llm_inference_http_request_start_request, llm_inference_http_request_start_result, llm_inference_http_request_start_transport, llm_inference_http_response_chunk_error, llm_inference_http_response_chunk_request, llm_inference_http_response_chunk_result, llm_inference_http_response_start_request, llm_inference_http_response_start_result, llm_inference_set_provider_result, local_session_metadata_value, log_request, log_result, lsp_initialize_request, managed_settings_read_result, marketplace_add_result, marketplace_browse_result, marketplace_info, marketplace_list_result, marketplace_plugin_info, marketplace_refresh_entry, marketplace_refresh_result, marketplace_remove_result, mcp_allowed_server, mcp_apps_call_tool_request, mcp_apps_diagnose_capability, mcp_apps_diagnose_request, mcp_apps_diagnose_result, mcp_apps_diagnose_server, mcp_apps_host_context, mcp_apps_host_context_details, mcp_apps_host_context_details_available_display_mode, mcp_apps_host_context_details_display_mode, mcp_apps_host_context_details_platform, mcp_apps_host_context_details_theme, mcp_apps_list_tools_request, mcp_apps_list_tools_result, mcp_apps_read_resource_request, mcp_apps_read_resource_result, mcp_apps_resource_content, mcp_apps_set_host_context_details, mcp_apps_set_host_context_details_available_display_mode, mcp_apps_set_host_context_details_display_mode, mcp_apps_set_host_context_details_platform, mcp_apps_set_host_context_details_theme, mcp_apps_set_host_context_request, mcp_cancel_sampling_execution_params, mcp_cancel_sampling_execution_result, mcp_config_add_request, mcp_config_disable_request, mcp_config_enable_request, mcp_config_list, mcp_config_remove_request, mcp_config_update_request, mcp_configure_git_hub_request, mcp_configure_git_hub_result, mcp_disable_request, mcp_discover_request, mcp_discover_result, mcp_elicitation_form_mode, mcp_enable_request, mcp_execute_sampling_params, mcp_execute_sampling_request, mcp_execute_sampling_result, mcp_failed_server, mcp_filtered_server, mcp_headers_handle_pending_headers_refresh_request, mcp_headers_handle_pending_headers_refresh_request_request, mcp_headers_handle_pending_headers_refresh_request_result, mcp_host_state, mcp_install_plan, mcp_is_server_running_request, mcp_is_server_running_result, mcp_list_tools_request, mcp_list_tools_result, mcp_oauth_authentication_state_changed_request, mcp_oauth_handle_pending_request, mcp_oauth_handle_pending_result, mcp_oauth_login_grant_type, mcp_oauth_login_request, mcp_oauth_login_result, mcp_oauth_pending_request_response, mcp_oauth_probe_needs_auth_reason, mcp_oauth_probe_request, mcp_oauth_probe_result, mcp_oauth_respond_request, mcp_oauth_respond_result, mcp_plan_configuration_change, mcp_plan_configuration_operation, mcp_plan_enum_value_type, mcp_plan_install_planned, mcp_plan_install_request, mcp_plan_install_result, mcp_plan_install_source, mcp_plan_install_source_candidate, mcp_plan_install_source_candidate_kind, mcp_plan_install_source_card, mcp_plan_install_source_card_kind, mcp_plan_package_install_method, mcp_plan_package_transport, mcp_plan_policy_decision, mcp_plan_policy_result, mcp_plan_policy_source, mcp_plan_provenance, mcp_plan_remote_install_method, mcp_plan_remote_transport, mcp_plan_required_value, mcp_plan_required_value_enum, mcp_plan_required_value_enum_kind, mcp_plan_required_value_scalar, mcp_plan_required_value_scalar_kind, mcp_plan_resource_identity, mcp_plan_scalar_value_type, mcp_plan_scope, mcp_plan_secret_placeholder, mcp_plan_secret_reference, mcp_plan_target, mcp_plan_transport_choice, mcp_plan_transport_choice_package, mcp_plan_transport_choice_remote, mcp_plan_value_category, mcp_register_external_client_request, mcp_reload_config, mcp_reload_with_config_request, mcp_remove_git_hub_result, mcp_resource, mcp_resource_annotations, mcp_resource_content, mcp_resource_icon, mcp_resources_list_request, mcp_resources_list_result, mcp_resources_list_templates_request, mcp_resources_list_templates_result, mcp_resources_read_request, mcp_resources_read_result, mcp_resource_template, mcp_restart_server_request, mcp_safe_for_telemetry, mcp_safe_for_telemetry_fields, mcp_sampling_execution_action, mcp_sampling_execution_result, mcp_serializable_server_config, mcp_server, mcp_server_auth_config, mcp_server_auth_config_redirect_port, mcp_server_card_embedded, mcp_server_card_embedded_kind, mcp_server_card_media_type, mcp_server_card_reference, mcp_server_card_url, mcp_server_card_url_kind, mcp_server_config, mcp_server_config_defer_tools, mcp_server_config_http, mcp_server_config_http_oauth_grant_type, mcp_server_config_http_type, mcp_server_config_memory, mcp_server_config_memory_type, mcp_server_config_stdio, mcp_server_config_stdio_type, mcp_server_failure_info, mcp_server_list, mcp_server_needs_auth_info, mcp_set_env_value_mode_details, mcp_set_env_value_mode_params, mcp_set_env_value_mode_result, mcp_start_server_request, mcp_start_servers_result, mcp_stop_server_request, mcp_task_metadata, mcp_tools, mcp_tool_ui, mcp_tool_ui_visibility, mcp_unregister_external_client_request, memory_configuration, metadata_context_attribution_result, metadata_context_heaviest_messages_request, metadata_context_heaviest_messages_result, metadata_context_info_request, metadata_context_info_result, metadata_is_processing_result, metadata_recompute_context_tokens_request, metadata_recompute_context_tokens_result, metadata_record_context_change_request, metadata_record_context_change_result, metadata_set_working_directory_request, metadata_set_working_directory_result, metadata_snapshot_current_mode, metadata_snapshot_remote_metadata, metadata_snapshot_remote_metadata_repository, metadata_snapshot_remote_metadata_task_type, model, model_apply_startup_overlay_request, model_billing, model_billing_promo, model_billing_token_prices, model_billing_token_prices_long_context, model_capabilities, model_capabilities_limits, model_capabilities_limits_vision, model_capabilities_override, model_capabilities_override_limits, model_capabilities_override_limits_vision, model_capabilities_override_supports, model_capabilities_supports, model_list, model_list_request, model_picker_category, model_picker_persistence_request, model_picker_price_category, model_picker_settings_context, model_policy, model_policy_state, model_set_reasoning_effort_request, model_set_reasoning_effort_result, models_list_request, model_switch_confirmation, model_switch_to_request, model_switch_to_result, mode_set_request, mode_set_result, move_mcp_loading_to_background_result, named_provider_config, name_get_result, name_set_auto_request, name_set_auto_result, name_set_request, open_canvas_instance, options_update_additional_content_exclusion_policy, options_update_additional_content_exclusion_policy_rule, options_update_additional_content_exclusion_policy_rule_source, options_update_additional_content_exclusion_policy_scope, options_update_context_tier, options_update_env_value_mode, options_update_reasoning_summary, options_update_tool_filter_precedence, pending_permission_request, pending_permission_request_list, permission_decision, permission_decision_approved, permission_decision_approved_for_location, permission_decision_approved_for_session, permission_decision_approve_for_location, permission_decision_approve_for_location_approval, permission_decision_approve_for_location_approval_commands, permission_decision_approve_for_location_approval_custom_tool, permission_decision_approve_for_location_approval_extension_env_access, permission_decision_approve_for_location_approval_extension_management, permission_decision_approve_for_location_approval_extension_permission_access, permission_decision_approve_for_location_approval_factory, permission_decision_approve_for_location_approval_mcp, permission_decision_approve_for_location_approval_mcp_sampling, permission_decision_approve_for_location_approval_memory, permission_decision_approve_for_location_approval_read, permission_decision_approve_for_location_approval_write, permission_decision_approve_for_session, permission_decision_approve_for_session_approval, permission_decision_approve_for_session_approval_commands, permission_decision_approve_for_session_approval_custom_tool, permission_decision_approve_for_session_approval_extension_env_access, permission_decision_approve_for_session_approval_extension_management, permission_decision_approve_for_session_approval_extension_permission_access, permission_decision_approve_for_session_approval_factory, permission_decision_approve_for_session_approval_mcp, permission_decision_approve_for_session_approval_mcp_sampling, permission_decision_approve_for_session_approval_memory, permission_decision_approve_for_session_approval_read, permission_decision_approve_for_session_approval_write, permission_decision_approve_once, permission_decision_approve_permanently, permission_decision_cancelled, permission_decision_context, permission_decision_denied_by_content_exclusion_policy, permission_decision_denied_by_permission_request_hook, permission_decision_denied_by_rules, permission_decision_denied_interactively_by_user, permission_decision_denied_no_approval_rule_and_could_not_request_from_user, permission_decision_outcome, permission_decision_reject, permission_decision_request, permission_decision_source, permission_decision_surface, permission_decision_user_not_available, permission_location_add_tool_approval_params, permission_location_apply_params, permission_location_apply_result, permission_location_resolve_params, permission_location_resolve_result, permission_location_type, permission_mode_source, permission_paths_add_params, permission_paths_allowed_check_params, permission_paths_allowed_check_result, permission_paths_config, permission_paths_list, permission_paths_update_primary_params, permission_paths_workspace_check_params, permission_paths_workspace_check_result, permission_prompt_shown_notification, permission_request_result, permission_rules_set, permissions_configure_additional_content_exclusion_policy, permissions_configure_additional_content_exclusion_policy_rule, permissions_configure_additional_content_exclusion_policy_rule_source, permissions_configure_additional_content_exclusion_policy_scope, permissions_configure_params, permissions_configure_result, permissions_folder_trust_add_trusted_result, permissions_get_mode_request, permissions_get_mode_result, permissions_locations_add_tool_approval_details, permissions_locations_add_tool_approval_details_commands, permissions_locations_add_tool_approval_details_custom_tool, permissions_locations_add_tool_approval_details_extension_env_access, permissions_locations_add_tool_approval_details_extension_management, permissions_locations_add_tool_approval_details_extension_permission_access, permissions_locations_add_tool_approval_details_factory, permissions_locations_add_tool_approval_details_mcp, permissions_locations_add_tool_approval_details_mcp_sampling, permissions_locations_add_tool_approval_details_memory, permissions_locations_add_tool_approval_details_read, permissions_locations_add_tool_approval_details_write, permissions_locations_add_tool_approval_result, permissions_modify_rules_params, permissions_modify_rules_result, permissions_modify_rules_scope, permissions_notify_prompt_shown_result, permissions_paths_add_result, permissions_paths_list_request, permissions_paths_update_primary_result, permissions_pending_requests_request, permissions_reset_session_approvals_request, permissions_reset_session_approvals_result, permissions_set_approve_all_request, permissions_set_approve_all_result, permissions_set_approve_all_source, permissions_set_mode_request, permissions_set_mode_result, permissions_set_required_request, permissions_set_required_result, permissions_urls_set_unrestricted_mode_result, permission_urls_config, permission_urls_set_unrestricted_mode_params, ping_request, ping_result, plan_read_result, plan_read_sql_todos_result, plan_read_sql_todos_with_dependencies_result, plan_sql_todo_dependency, plan_sql_todos_row, plan_update_request, plugin, plugin_install_result, plugin_list, plugin_list_result, plugins_builtin_set_request, plugins_disable_request, plugins_enable_request, plugins_install_request, plugins_marketplaces_add_request, plugins_marketplaces_browse_request, plugins_marketplaces_refresh_request, plugins_marketplaces_remove_request, plugins_reload_request, plugins_uninstall_request, plugins_update_request, plugin_update_all_entry, plugin_update_all_result, plugin_update_result, protocol_external_tool_defer, protocol_external_tool_definition, provider_add_request, provider_add_result, provider_config, provider_config_azure, provider_config_transport, provider_config_type, provider_config_wire_api, provider_endpoint, provider_endpoint_transport, provider_endpoint_type, provider_endpoint_wire_api, provider_get_endpoint_request, provider_model_config, provider_session_token, provider_token_acquire_request, provider_token_acquire_result, push_attachment, push_attachment_blob, push_attachment_directory, push_attachment_file, push_attachment_file_line_range, push_attachment_git_hub_actions_job, push_attachment_git_hub_commit, push_attachment_git_hub_file, push_attachment_git_hub_file_diff, push_attachment_git_hub_file_diff_side, push_attachment_git_hub_reference, push_attachment_git_hub_reference_type, push_attachment_git_hub_release, push_attachment_git_hub_repository, push_attachment_git_hub_snippet, push_attachment_git_hub_tree_comparison, push_attachment_git_hub_tree_comparison_side, push_attachment_git_hub_url, push_attachment_selection, push_attachment_selection_details, push_attachment_selection_details_end, push_attachment_selection_details_start, push_git_hub_repo_ref, queue_begin_deferred_idle_drain_request, queue_begin_deferred_idle_drain_result, queue_consume_system_notifications_request, queued_command_handled, queued_command_not_handled, queued_command_result, queue_defer_session_idle_request, queue_duplicate_at_request, queue_duplicate_at_result, queue_enqueue_resume_pending_result, queue_finish_deferred_idle_drain_request, queue_finish_deferred_idle_drain_result, queue_has_pending_result, queue_insert_at_request, queue_insert_at_result, queue_insert_message, queue_move_item_request, queue_move_item_result, queue_pending_items, queue_pending_items_kind, queue_pending_items_result, queue_remove_at_request, queue_remove_at_result, queue_remove_most_recent_result, queue_send_now_request, queue_send_now_result, queue_set_drain_paused_request, queue_snapshot_result, queue_update_text_request, queue_update_text_result, register_event_interest_params, register_event_interest_result, register_extension_tools_params, register_extension_tools_result, release_event_interest_params, remote_control_config, remote_control_config_existing_mc_session, remote_control_status, remote_control_status_active, remote_control_status_connecting, remote_control_status_error, remote_control_status_off, remote_control_status_result, remote_control_stop_result, remote_control_transfer_result, remote_enable_request, remote_enable_result, remote_notify_steerable_changed_request, remote_notify_steerable_changed_result, remote_session_connection_result, remote_session_host_status, remote_session_metadata_repository, remote_session_metadata_task_type, remote_session_metadata_value, remote_session_mode, remote_session_repository, run_options, sandbox_config, sandbox_config_auth, sandbox_config_user_policy, sandbox_config_user_policy_experimental, sandbox_config_user_policy_experimental_seatbelt, sandbox_config_user_policy_filesystem, sandbox_config_user_policy_network, sandbox_config_user_policy_network_proxy, sandbox_config_user_policy_seatbelt, schedule_add_at_request, schedule_add_cron_request, schedule_add_request, schedule_add_result, schedule_add_self_paced_request, schedule_entry, schedule_has_self_paced_result, schedule_list, schedule_rearm_self_paced_request, schedule_stop_request, schedule_stop_result, secrets_add_filter_values_request, secrets_add_filter_values_result, send_agent_mode, send_attachments_to_message_params, send_message_item, send_messages_request, send_messages_result, send_mode, send_request, send_result, send_system_notification_request, server_agent_list, server_instruction_source_list, server_skill, server_skill_list, session_activity, session_agent_list_request, session_auth_login_request, session_auth_logout_user_request, session_auth_status, session_auth_switch_request, session_bulk_delete_result, session_cancel_all_background_agents_result, session_capability, session_commands_list_request, session_completion_item, session_context, session_context_host_type, session_enrich_metadata_result, session_fs_append_file_request, session_fs_error, session_fs_error_code, session_fs_exists_request, session_fs_exists_result, session_fs_mkdir_request, session_fs_readdir_request, session_fs_readdir_result, session_fs_readdir_with_types_entry, session_fs_readdir_with_types_entry_type, session_fs_readdir_with_types_request, session_fs_readdir_with_types_result, session_fs_read_file_request, session_fs_read_file_result, session_fs_rename_request, session_fs_rm_request, session_fs_set_provider_capabilities, session_fs_set_provider_conventions, session_fs_set_provider_request, session_fs_set_provider_result, session_fs_sqlite_exists_request, session_fs_sqlite_exists_result, session_fs_sqlite_query_request, session_fs_sqlite_query_result, session_fs_sqlite_query_type, session_fs_sqlite_transaction_error, session_fs_sqlite_transaction_error_class, session_fs_sqlite_transaction_request, session_fs_sqlite_transaction_result, session_fs_sqlite_transaction_statement, session_fs_stat_request, session_fs_stat_result, session_fs_write_file_request, session_git_hub_auth_get_all_auth_available_result, session_git_hub_auth_logout_result, session_git_hub_auth_logout_user_result, session_history_compact_request, session_installed_plugin, session_installed_plugin_source, session_installed_plugin_source_git_hub, session_installed_plugin_source_local, session_installed_plugin_source_url, session_limit_prediction_baseline_data, session_limit_prediction_client_type, session_limit_prediction_details, session_limit_prediction_predict_request, session_limit_prediction_request, session_limit_prediction_result, session_limit_prediction_source, session_limit_prediction_tier, session_limit_prediction_tier_option, session_limit_prediction_unavailable_reason, session_list, session_list_entry, session_list_filter, session_load_deferred_repo_hooks_result, session_log_level, session_managed_permissions, session_managed_settings, session_mcp_apps_call_tool_result, session_metadata_snapshot, session_mode, session_model_list, session_model_list_request, session_model_price_category, session_open_options, session_open_options_additional_content_exclusion_policy, session_open_options_additional_content_exclusion_policy_rule, session_open_options_additional_content_exclusion_policy_rule_source, session_open_options_additional_content_exclusion_policy_scope, session_open_options_env_value_mode, session_open_options_reasoning_summary, session_open_params, session_open_result, session_plugins_reload_request, session_provider_get_endpoint_request, session_prune_result, sessions_bulk_delete_request, sessions_check_in_use_request, sessions_check_in_use_result, sessions_close_request, sessions_close_result, sessions_delete_request, sessions_enrich_metadata_request, session_set_credentials_params, session_set_credentials_result, session_settings_built_in_tool_availability_snapshot, session_settings_evaluate_predicate_request, session_settings_evaluate_predicate_result, session_settings_job_snapshot, session_settings_model_snapshot, session_settings_online_evaluation_snapshot, session_settings_predicate_name, session_settings_repo_snapshot, session_settings_snapshot, session_settings_validation_snapshot, sessions_find_by_prefix_request, sessions_find_by_prefix_result, sessions_find_by_task_id_request, sessions_find_by_task_id_result, sessions_fork_request, sessions_fork_result, sessions_get_board_entry_count_request, sessions_get_board_entry_count_result, sessions_get_event_file_path_request, sessions_get_event_file_path_result, sessions_get_last_for_context_request, sessions_get_last_for_context_result, sessions_get_metadata_request, sessions_get_metadata_result, sessions_get_persisted_remote_steerable_request, sessions_get_persisted_remote_steerable_result, session_sizes, sessions_list_non_empty_session_ids_request, sessions_list_non_empty_session_ids_result, sessions_list_request, sessions_load_deferred_repo_hooks_request, sessions_open_attach, sessions_open_cloud, sessions_open_create, sessions_open_handoff, sessions_open_handoff_task_type, sessions_open_progress, sessions_open_progress_status, sessions_open_progress_step, sessions_open_remote, sessions_open_resume, sessions_open_resume_last, sessions_open_status, session_source, sessions_prune_old_request, sessions_register_extension_tools_on_session_options, sessions_release_lock_request, sessions_release_lock_result, sessions_reload_plugin_hooks_request, sessions_reload_plugin_hooks_result, sessions_save_request, sessions_save_result, sessions_set_additional_plugins_request, sessions_set_additional_plugins_result, sessions_set_remote_control_steering_request, sessions_start_remote_control_request, sessions_stop_remote_control_request, sessions_transfer_remote_control_request, session_telemetry_engagement, session_update_options_params, session_update_options_result, session_visibility_status, session_working_directory_context, session_working_directory_context_host_type, shell_cancel_user_requested_request, shell_credentials, shell_exec_request, shell_exec_result, shell_execute_user_requested_request, shell_init_profile, shell_init_script, shell_init_script_shell, shell_kill_request, shell_kill_result, shell_kill_signal, shell_options, shutdown_request, skill, skill_discovery_path, skill_discovery_path_list, skill_discovery_scope, skill_list, skills_config_set_disabled_skills_request, skills_config_set_skill_disabled_request, skills_disable_request, skills_discover_request, skills_enable_request, skills_get_discovery_paths_request, skills_get_invoked_result, skills_invoked_skill, skills_load_diagnostics, slash_command_add_timeline_entry_result, slash_command_agent_prompt_result, slash_command_completed_result, slash_command_info, slash_command_input, slash_command_input_choice, slash_command_input_completion, slash_command_invocation_result, slash_command_kind, slash_command_model_picker_dialog, slash_command_select_subcommand_option, slash_command_select_subcommand_result, slash_command_set_model_result, slash_command_set_plan_model_result, slash_command_show_dialog_result, slash_command_text_result, slash_command_timeline_entry, subagent_settings_entry, subagent_settings_entry_context_tier, task_agent_info, task_agent_progress, task_complete_data, task_completion_decision, task_execution_mode, task_info, task_list, task_progress_line, tasks_cancel_request, tasks_cancel_result, tasks_get_current_promotable_result, tasks_get_progress_request, tasks_get_progress_result, task_shell_info, task_shell_info_attachment_mode, task_shell_progress, tasks_promote_current_to_background_result, tasks_promote_to_background_request, tasks_promote_to_background_result, tasks_refresh_result, tasks_remove_request, tasks_remove_result, tasks_send_message_request, tasks_send_message_result, tasks_start_agent_request, tasks_start_agent_result, task_status, tasks_wait_for_pending_result, telemetry_set_feature_overrides_request, token_auth_info, tool, tool_list, tool_result, tool_result_expanded, tool_result_new_message, tool_result_type, tools_execute_request, tools_get_builtin_descriptors_request, tools_get_builtin_descriptors_result, tools_get_current_metadata_result, tools_initialize_and_validate_result, tools_list_request, tools_set_request, tools_set_result, tools_shell_descriptor_config, tools_task_complete_event_data_request, tools_update_subagent_settings_result, ui_auto_mode_switch_response, ui_elicitation_array_any_of_field, ui_elicitation_array_any_of_field_items, ui_elicitation_array_any_of_field_items_any_of, ui_elicitation_array_enum_field, ui_elicitation_array_enum_field_items, ui_elicitation_field_value, ui_elicitation_request, ui_elicitation_response, ui_elicitation_response_action, ui_elicitation_response_content, ui_elicitation_result, ui_elicitation_schema, ui_elicitation_schema_property, ui_elicitation_schema_property_boolean, ui_elicitation_schema_property_number, ui_elicitation_schema_property_number_type, ui_elicitation_schema_property_string, ui_elicitation_schema_property_string_format, ui_elicitation_string_enum_field, ui_elicitation_string_one_of_field, ui_elicitation_string_one_of_field_one_of, ui_ephemeral_query_request, ui_ephemeral_query_result, ui_exit_plan_mode_action, ui_exit_plan_mode_response, ui_handle_pending_auto_mode_switch_request, ui_handle_pending_elicitation_request, ui_handle_pending_exit_plan_mode_request, ui_handle_pending_result, ui_handle_pending_sampling_request, ui_handle_pending_sampling_response, ui_handle_pending_session_limits_exhausted_request, ui_handle_pending_user_input_request, ui_register_direct_auto_mode_switch_handler_result, ui_session_limits_exhausted_response, ui_session_limits_exhausted_response_action, ui_unregister_direct_auto_mode_switch_handler_request, ui_unregister_direct_auto_mode_switch_handler_result, ui_user_input_response, update_subagent_settings_request, usage_get_metrics_result, usage_metrics_agent_metric, usage_metrics_code_changes, usage_metrics_model_metric, usage_metrics_model_metric_requests, usage_metrics_model_metric_token_detail, usage_metrics_model_metric_usage, usage_metrics_token_detail, user_auth_info, user_requested_shell_command_result, user_setting_metadata, user_settings_get_result, user_settings_set_request, user_settings_set_result, visibility_get_result, visibility_set_request, visibility_set_result, workspace_diff_file_change, workspace_diff_file_change_type, workspace_diff_mode, workspace_diff_result, workspaces_add_summary_request, workspaces_add_summary_result, workspaces_autopilot_objective_exists_result, workspaces_checkpoints, workspaces_create_file_request, workspaces_delete_autopilot_objective_result, workspaces_diff_request, workspaces_ensure_request, workspaces_get_workspace_result, workspaces_list_checkpoints_result, workspaces_list_files_result, workspaces_read_autopilot_objective_result, workspaces_read_checkpoint_request, workspaces_read_checkpoint_result, workspaces_read_file_request, workspaces_read_file_result, workspaces_save_large_paste_request, workspaces_save_large_paste_result, workspaces_truncate_summaries_request, workspace_summary_host_type, workspaces_update_metadata_request, workspaces_workspace_details_host_type, workspaces_write_autopilot_objective_request, workspaces_write_autopilot_objective_result, session_auth_info_result, session_context_attribution, session_context_info, subagent_settings, task_progress, workspace_summary) + return RPC(abort_request, abort_result, account_all_users, account_get_all_users_result, account_get_current_auth_result, account_get_quota_request, account_get_quota_result, account_login_request, account_login_result, account_logout_request, account_logout_result, account_quota_snapshot, adaptive_thinking_support, agent_discovery_path, agent_discovery_path_list, agent_discovery_path_scope, agent_get_current_result, agent_info, agent_info_source, agent_list, agent_list_request, agent_registry_live_target_entry, agent_registry_live_target_entry_attention_kind, agent_registry_live_target_entry_kind, agent_registry_live_target_entry_last_terminal_event, agent_registry_live_target_entry_status, agent_registry_log_capture, agent_registry_log_capture_open_error_reason, agent_registry_spawn_error, agent_registry_spawn_permission_mode, agent_registry_spawn_registry_timeout, agent_registry_spawn_request, agent_registry_spawn_result, agent_registry_spawn_spawned, agent_registry_spawn_validation_error, agent_registry_spawn_validation_error_field, agent_registry_spawn_validation_error_reason, agent_reload_result, agents_discover_request, agent_select_request, agent_select_result, agent_set_prompt_request, agents_get_discovery_paths_request, api_key_auth_info, auth_identity, auth_info, auth_info_type, auth_validation_error, auth_validation_errors, built_in_model_catalog, built_in_model_catalog_entry, builtin_tool_descriptor, builtin_tool_format, builtin_tool_format_type, builtin_tool_input_schema, builtin_tool_input_schema_type, builtin_tool_safe_for_telemetry, builtin_tool_safe_telemetry_fields, cancel_user_requested_shell_command_result, canvas_action, canvas_action_invoke_request, canvas_action_invoke_result, canvas_close_request, canvas_host_context, canvas_host_context_capabilities, canvas_json_schema, canvas_list, canvas_list_open_result, canvas_open_request, canvas_provider_close_request, canvas_provider_invoke_action_request, canvas_provider_open_request, canvas_provider_open_result, canvas_provider_register_request, canvas_provider_unregister_request, canvas_session_context, capi_session_options, card_digest, card_digest_algorithm, card_digest_value, catalog_ai_skill_candidate, catalog_ai_skill_candidate_provenance, catalog_authentication_required_error, catalog_authentication_required_reason, catalog_candidate, catalog_candidate_kind, catalog_candidate_source, catalog_candidate_source_embedded, catalog_candidate_source_url, catalog_capability, catalog_capability_id, catalog_client_contract, catalog_contract_violation_error, catalog_contract_violation_reason, catalog_handle_rejected_error, catalog_handle_rejection_reason, catalog_handle_type, catalog_invalid_request_error, catalog_invalid_request_field, catalog_malformed_card_error, catalog_malformed_card_reason, catalog_mcp_server_candidate, catalog_mcp_server_candidate_provenance, catalog_mcp_server_installability, catalog_media_type, catalog_negotiated_contract, catalog_negotiation_refused_error, catalog_negotiation_refused_reason, catalog_network_failure_error, catalog_network_failure_reason, catalog_not_installable_error, catalog_not_installable_reason, catalog_policy_rejected_error, catalog_search_request, catalog_search_result, catalog_search_succeeded, catalog_unavailable_error, catalog_unavailable_reason, catalog_unavailable_transport_error, catalog_unavailable_transport_reason, catalog_unsafe_retrieval_error, catalog_unsafe_retrieval_reason, catalog_unsupported_kind_error, command_list, commands_finalize_invocation_effect_request, commands_finalize_invocation_effect_result, commands_handle_pending_command_request, commands_handle_pending_command_result, commands_invocation_effect_outcome, commands_invocation_origin, commands_invoke_request, commands_list_request, commands_respond_to_queued_command_request, commands_respond_to_queued_command_result, completions_get_trigger_characters_result, completions_request_request, completions_request_result, configure_session_extensions_params, connect_client_info, connected_remote_session_metadata, connected_remote_session_metadata_kind, connected_remote_session_metadata_repository, connect_remote_session_params, connect_request, connect_result, content_exclusion_check_paths_request, content_exclusion_check_paths_result, content_exclusion_path_check, content_filter_mode, context_heaviest_message, copilot_api_token_auth_info, copilot_user_response, copilot_user_response_endpoints, copilot_user_response_quota_snapshots, copilot_user_response_quota_snapshots_chat, copilot_user_response_quota_snapshots_completions, copilot_user_response_quota_snapshots_premium_interactions, current_model, current_tool_metadata, debug_collect_logs_collected_entry, debug_collect_logs_destination, debug_collect_logs_entry, debug_collect_logs_entry_kind, debug_collect_logs_include, debug_collect_logs_redaction, debug_collect_logs_request, debug_collect_logs_result, debug_collect_logs_result_kind, debug_collect_logs_skipped_entry, debug_collect_logs_source, discovered_canvas, discovered_extension, discovered_extension_mode, discovered_extension_plugin, discovered_extensions, discovered_extensions_disable_request, discovered_extensions_enable_request, discovered_extension_source, discovered_mcp_server, discovered_mcp_server_type, enqueue_command_params, enqueue_command_result, env_auth_info, event_log_read_request, event_log_release_interest_result, event_log_tail_result, event_log_types, events_agent_scope, events_cursor_status, events_read_direction, events_read_result, execute_command_params, execute_command_result, extension, extension_context_push_input, extension_launch_profile, extension_launch_provider_resolve_request, extension_launch_provider_resolve_result, extension_list, extensions_disable_request, extensions_enable_request, extension_source, extension_status, external_tool_result, external_tool_text_result_for_llm, external_tool_text_result_for_llm_binary_results_for_llm, external_tool_text_result_for_llm_binary_results_for_llm_type, external_tool_text_result_for_llm_content, external_tool_text_result_for_llm_content_audio, external_tool_text_result_for_llm_content_image, external_tool_text_result_for_llm_content_resource, external_tool_text_result_for_llm_content_resource_details, external_tool_text_result_for_llm_content_resource_link, external_tool_text_result_for_llm_content_resource_link_icon, external_tool_text_result_for_llm_content_resource_link_icon_theme, external_tool_text_result_for_llm_content_shell_exit, external_tool_text_result_for_llm_content_terminal, external_tool_text_result_for_llm_content_text, factory_abort_request, factory_ack_result, factory_agent_options, factory_agent_request, factory_agent_result, factory_agent_summary, factory_cancel_request, factory_current_phase, factory_declared_limits, factory_durable_operation, factory_execute_request, factory_execute_result, factory_get_run_progress_request, factory_get_run_request, factory_journal_get_request, factory_journal_get_result, factory_journal_put_request, factory_list_runs_request, factory_list_runs_result, factory_log_line, factory_log_line_kind, factory_log_request, factory_phase_observation, factory_phase_status, factory_progress_line, factory_progress_page, factory_resume_request, factory_resume_result, factory_run_consumed, factory_run_detail, factory_run_failure, factory_run_failure_kind, factory_run_limits, factory_run_request, factory_run_result, factory_run_status, factory_run_summary, factory_run_terminal, filter_mapping, fleet_start_request, fleet_start_result, folder_trust_add_params, folder_trust_check_params, folder_trust_check_result, gh_cli_auth_info, git_hub_telemetry_client_info, git_hub_telemetry_event, git_hub_telemetry_notification, git_hub_token_acquire_reason, git_hub_token_acquire_request, git_hub_token_acquire_result, handle_pending_tool_call_request, handle_pending_tool_call_result, history_abort_manual_compaction_result, history_cancel_background_compaction_result, history_clear_context_request, history_clear_context_result, history_compact_context_window, history_compact_request, history_compact_result, history_file_restore_skip_reason, history_list_rewind_points_result, history_preview_rewind_request, history_preview_rewind_result, history_rewind_change_type, history_rewind_file_preview, history_rewind_mode, history_rewind_outcome, history_rewind_point, history_rewind_request, history_rewind_result, history_rewind_unavailable_reason, history_skipped_file_restore, history_summarize_for_handoff_result, history_truncate_request, history_truncate_result, hmac_auth_info, hook_invoke_request, hook_invoke_response, hook_type, installed_plugin, installed_plugin_info, installed_plugin_source, installed_plugin_source_git_hub, installed_plugin_source_local, installed_plugin_source_url, instruction_discovery_path, instruction_discovery_path_kind, instruction_discovery_path_list, instruction_discovery_path_location, instructions_discover_request, instructions_get_discovery_paths_request, instructions_get_sources_result, instruction_source, instruction_source_location, instruction_source_type, interrupt_main_turn_request, interrupt_main_turn_result, llm_inference_headers, llm_inference_http_request_chunk_request, llm_inference_http_request_chunk_result, llm_inference_http_request_start_request, llm_inference_http_request_start_result, llm_inference_http_request_start_transport, llm_inference_http_response_chunk_error, llm_inference_http_response_chunk_request, llm_inference_http_response_chunk_result, llm_inference_http_response_start_request, llm_inference_http_response_start_result, llm_inference_set_provider_result, local_session_metadata_value, log_request, log_result, lsp_initialize_request, managed_settings_read_result, marketplace_add_result, marketplace_browse_result, marketplace_info, marketplace_list_result, marketplace_plugin_info, marketplace_refresh_entry, marketplace_refresh_result, marketplace_remove_result, mcp_allowed_server, mcp_apps_call_tool_request, mcp_apps_diagnose_capability, mcp_apps_diagnose_request, mcp_apps_diagnose_result, mcp_apps_diagnose_server, mcp_apps_host_context, mcp_apps_host_context_details, mcp_apps_host_context_details_available_display_mode, mcp_apps_host_context_details_display_mode, mcp_apps_host_context_details_platform, mcp_apps_host_context_details_theme, mcp_apps_list_tools_request, mcp_apps_list_tools_result, mcp_apps_read_resource_request, mcp_apps_read_resource_result, mcp_apps_resource_content, mcp_apps_set_host_context_details, mcp_apps_set_host_context_details_available_display_mode, mcp_apps_set_host_context_details_display_mode, mcp_apps_set_host_context_details_platform, mcp_apps_set_host_context_details_theme, mcp_apps_set_host_context_request, mcp_cancel_sampling_execution_params, mcp_cancel_sampling_execution_result, mcp_config_add_request, mcp_config_disable_request, mcp_config_enable_request, mcp_config_list, mcp_config_remove_request, mcp_config_update_request, mcp_configure_git_hub_request, mcp_configure_git_hub_result, mcp_disable_request, mcp_discover_request, mcp_discover_result, mcp_elicitation_form_mode, mcp_enable_request, mcp_execute_sampling_params, mcp_execute_sampling_request, mcp_execute_sampling_result, mcp_failed_server, mcp_filtered_server, mcp_headers_handle_pending_headers_refresh_request, mcp_headers_handle_pending_headers_refresh_request_request, mcp_headers_handle_pending_headers_refresh_request_result, mcp_host_state, mcp_install_plan, mcp_is_server_running_request, mcp_is_server_running_result, mcp_list_tools_request, mcp_list_tools_result, mcp_oauth_authentication_state_changed_request, mcp_oauth_handle_pending_request, mcp_oauth_handle_pending_result, mcp_oauth_login_grant_type, mcp_oauth_login_request, mcp_oauth_login_result, mcp_oauth_pending_request_response, mcp_oauth_probe_needs_auth_reason, mcp_oauth_probe_request, mcp_oauth_probe_result, mcp_oauth_respond_request, mcp_oauth_respond_result, mcp_plan_configuration_change, mcp_plan_configuration_operation, mcp_plan_enum_value_type, mcp_plan_install_planned, mcp_plan_install_request, mcp_plan_install_result, mcp_plan_install_source, mcp_plan_install_source_candidate, mcp_plan_install_source_candidate_kind, mcp_plan_install_source_card, mcp_plan_install_source_card_kind, mcp_plan_package_install_method, mcp_plan_package_transport, mcp_plan_policy_decision, mcp_plan_policy_result, mcp_plan_policy_source, mcp_plan_provenance, mcp_plan_remote_install_method, mcp_plan_remote_transport, mcp_plan_required_value, mcp_plan_required_value_enum, mcp_plan_required_value_enum_kind, mcp_plan_required_value_scalar, mcp_plan_required_value_scalar_kind, mcp_plan_resource_identity, mcp_plan_scalar_value_type, mcp_plan_scope, mcp_plan_secret_placeholder, mcp_plan_secret_reference, mcp_plan_target, mcp_plan_transport_choice, mcp_plan_transport_choice_package, mcp_plan_transport_choice_remote, mcp_plan_value_category, mcp_register_external_client_request, mcp_reload_config, mcp_reload_with_config_request, mcp_remove_git_hub_result, mcp_resource, mcp_resource_annotations, mcp_resource_content, mcp_resource_icon, mcp_resources_list_request, mcp_resources_list_result, mcp_resources_list_templates_request, mcp_resources_list_templates_result, mcp_resources_read_request, mcp_resources_read_result, mcp_resource_template, mcp_restart_server_request, mcp_safe_for_telemetry, mcp_safe_for_telemetry_fields, mcp_sampling_execution_action, mcp_sampling_execution_result, mcp_serializable_server_config, mcp_server, mcp_server_auth_config, mcp_server_auth_config_redirect_port, mcp_server_card_embedded, mcp_server_card_embedded_kind, mcp_server_card_media_type, mcp_server_card_reference, mcp_server_card_url, mcp_server_card_url_kind, mcp_server_config, mcp_server_config_defer_tools, mcp_server_config_http, mcp_server_config_http_oauth_grant_type, mcp_server_config_http_type, mcp_server_config_memory, mcp_server_config_memory_type, mcp_server_config_stdio, mcp_server_config_stdio_type, mcp_server_failure_info, mcp_server_list, mcp_server_needs_auth_info, mcp_set_env_value_mode_details, mcp_set_env_value_mode_params, mcp_set_env_value_mode_result, mcp_start_server_request, mcp_start_servers_result, mcp_stop_server_request, mcp_task_metadata, mcp_tools, mcp_tool_ui, mcp_tool_ui_visibility, mcp_unregister_external_client_request, memory_configuration, metadata_context_attribution_result, metadata_context_heaviest_messages_request, metadata_context_heaviest_messages_result, metadata_context_info_request, metadata_context_info_result, metadata_is_processing_result, metadata_recompute_context_tokens_request, metadata_recompute_context_tokens_result, metadata_record_context_change_request, metadata_record_context_change_result, metadata_set_working_directory_request, metadata_set_working_directory_result, metadata_snapshot_current_mode, metadata_snapshot_remote_metadata, metadata_snapshot_remote_metadata_repository, metadata_snapshot_remote_metadata_task_type, model, model_apply_startup_overlay_request, model_billing, model_billing_promo, model_billing_token_prices, model_billing_token_prices_long_context, model_capabilities, model_capabilities_limits, model_capabilities_limits_vision, model_capabilities_override, model_capabilities_override_limits, model_capabilities_override_limits_vision, model_capabilities_override_supports, model_capabilities_supports, model_list, model_list_request, model_message, model_picker_category, model_picker_persistence_request, model_picker_price_category, model_picker_settings_context, model_policy, model_policy_state, model_set_reasoning_effort_request, model_set_reasoning_effort_result, models_list_request, model_switch_confirmation, model_switch_to_request, model_switch_to_result, model_warning_text, mode_set_request, mode_set_result, move_mcp_loading_to_background_result, named_provider_config, name_get_result, name_set_auto_request, name_set_auto_result, name_set_request, open_canvas_instance, options_update_additional_content_exclusion_policy, options_update_additional_content_exclusion_policy_rule, options_update_additional_content_exclusion_policy_rule_source, options_update_additional_content_exclusion_policy_scope, options_update_context_tier, options_update_env_value_mode, options_update_reasoning_summary, options_update_tool_filter_precedence, pending_permission_request, pending_permission_request_list, permission_decision, permission_decision_approved, permission_decision_approved_for_location, permission_decision_approved_for_session, permission_decision_approve_for_location, permission_decision_approve_for_location_approval, permission_decision_approve_for_location_approval_commands, permission_decision_approve_for_location_approval_custom_tool, permission_decision_approve_for_location_approval_extension_env_access, permission_decision_approve_for_location_approval_extension_management, permission_decision_approve_for_location_approval_extension_permission_access, permission_decision_approve_for_location_approval_factory, permission_decision_approve_for_location_approval_mcp, permission_decision_approve_for_location_approval_mcp_sampling, permission_decision_approve_for_location_approval_memory, permission_decision_approve_for_location_approval_read, permission_decision_approve_for_location_approval_write, permission_decision_approve_for_session, permission_decision_approve_for_session_approval, permission_decision_approve_for_session_approval_commands, permission_decision_approve_for_session_approval_custom_tool, permission_decision_approve_for_session_approval_extension_env_access, permission_decision_approve_for_session_approval_extension_management, permission_decision_approve_for_session_approval_extension_permission_access, permission_decision_approve_for_session_approval_factory, permission_decision_approve_for_session_approval_mcp, permission_decision_approve_for_session_approval_mcp_sampling, permission_decision_approve_for_session_approval_memory, permission_decision_approve_for_session_approval_read, permission_decision_approve_for_session_approval_write, permission_decision_approve_once, permission_decision_approve_permanently, permission_decision_cancelled, permission_decision_context, permission_decision_denied_by_content_exclusion_policy, permission_decision_denied_by_permission_request_hook, permission_decision_denied_by_rules, permission_decision_denied_interactively_by_user, permission_decision_denied_no_approval_rule_and_could_not_request_from_user, permission_decision_outcome, permission_decision_reject, permission_decision_request, permission_decision_source, permission_decision_surface, permission_decision_user_not_available, permission_location_add_tool_approval_params, permission_location_apply_params, permission_location_apply_result, permission_location_resolve_params, permission_location_resolve_result, permission_location_type, permission_mode_source, permission_paths_add_params, permission_paths_allowed_check_params, permission_paths_allowed_check_result, permission_paths_config, permission_paths_list, permission_paths_update_primary_params, permission_paths_workspace_check_params, permission_paths_workspace_check_result, permission_prompt_shown_notification, permission_request_result, permission_rules_set, permissions_configure_additional_content_exclusion_policy, permissions_configure_additional_content_exclusion_policy_rule, permissions_configure_additional_content_exclusion_policy_rule_source, permissions_configure_additional_content_exclusion_policy_scope, permissions_configure_params, permissions_configure_result, permissions_folder_trust_add_trusted_result, permissions_get_mode_request, permissions_get_mode_result, permissions_locations_add_tool_approval_details, permissions_locations_add_tool_approval_details_commands, permissions_locations_add_tool_approval_details_custom_tool, permissions_locations_add_tool_approval_details_extension_env_access, permissions_locations_add_tool_approval_details_extension_management, permissions_locations_add_tool_approval_details_extension_permission_access, permissions_locations_add_tool_approval_details_factory, permissions_locations_add_tool_approval_details_mcp, permissions_locations_add_tool_approval_details_mcp_sampling, permissions_locations_add_tool_approval_details_memory, permissions_locations_add_tool_approval_details_read, permissions_locations_add_tool_approval_details_write, permissions_locations_add_tool_approval_result, permissions_modify_rules_params, permissions_modify_rules_result, permissions_modify_rules_scope, permissions_notify_prompt_shown_result, permissions_paths_add_result, permissions_paths_list_request, permissions_paths_update_primary_result, permissions_pending_requests_request, permissions_reset_session_approvals_request, permissions_reset_session_approvals_result, permissions_set_approve_all_request, permissions_set_approve_all_result, permissions_set_approve_all_source, permissions_set_mode_request, permissions_set_mode_result, permissions_set_required_request, permissions_set_required_result, permissions_urls_set_unrestricted_mode_result, permission_urls_config, permission_urls_set_unrestricted_mode_params, ping_request, ping_result, plan_read_result, plan_read_sql_todos_result, plan_read_sql_todos_with_dependencies_result, plan_sql_todo_dependency, plan_sql_todos_row, plan_update_request, plugin, plugin_install_result, plugin_list, plugin_list_result, plugins_builtin_set_request, plugins_disable_request, plugins_enable_request, plugins_install_request, plugins_marketplaces_add_request, plugins_marketplaces_browse_request, plugins_marketplaces_refresh_request, plugins_marketplaces_remove_request, plugins_reload_request, plugins_uninstall_request, plugins_update_request, plugin_update_all_entry, plugin_update_all_result, plugin_update_result, protocol_external_tool_defer, protocol_external_tool_definition, provider_add_request, provider_add_result, provider_config, provider_config_azure, provider_config_transport, provider_config_type, provider_config_wire_api, provider_endpoint, provider_endpoint_transport, provider_endpoint_type, provider_endpoint_wire_api, provider_get_endpoint_request, provider_model_config, provider_session_token, provider_token_acquire_request, provider_token_acquire_result, push_attachment, push_attachment_blob, push_attachment_directory, push_attachment_file, push_attachment_file_line_range, push_attachment_git_hub_actions_job, push_attachment_git_hub_commit, push_attachment_git_hub_file, push_attachment_git_hub_file_diff, push_attachment_git_hub_file_diff_side, push_attachment_git_hub_reference, push_attachment_git_hub_reference_type, push_attachment_git_hub_release, push_attachment_git_hub_repository, push_attachment_git_hub_snippet, push_attachment_git_hub_tree_comparison, push_attachment_git_hub_tree_comparison_side, push_attachment_git_hub_url, push_attachment_selection, push_attachment_selection_details, push_attachment_selection_details_end, push_attachment_selection_details_start, push_git_hub_repo_ref, queue_begin_deferred_idle_drain_request, queue_begin_deferred_idle_drain_result, queue_consume_system_notifications_request, queued_command_handled, queued_command_not_handled, queued_command_result, queue_defer_session_idle_request, queue_duplicate_at_request, queue_duplicate_at_result, queue_enqueue_resume_pending_result, queue_finish_deferred_idle_drain_request, queue_finish_deferred_idle_drain_result, queue_has_pending_result, queue_insert_at_request, queue_insert_at_result, queue_insert_message, queue_move_item_request, queue_move_item_result, queue_pending_items, queue_pending_items_kind, queue_pending_items_result, queue_remove_at_request, queue_remove_at_result, queue_remove_most_recent_result, queue_send_now_request, queue_send_now_result, queue_set_drain_paused_request, queue_snapshot_result, queue_update_text_request, queue_update_text_result, register_event_interest_params, register_event_interest_result, register_extension_tools_params, register_extension_tools_result, release_event_interest_params, remote_control_config, remote_control_config_existing_mc_session, remote_control_status, remote_control_status_active, remote_control_status_connecting, remote_control_status_error, remote_control_status_off, remote_control_status_result, remote_control_stop_result, remote_control_transfer_result, remote_enable_request, remote_enable_result, remote_notify_steerable_changed_request, remote_notify_steerable_changed_result, remote_session_connection_result, remote_session_host_status, remote_session_metadata_repository, remote_session_metadata_task_type, remote_session_metadata_value, remote_session_mode, remote_session_repository, run_options, sandbox_config, sandbox_config_auth, sandbox_config_source, sandbox_config_user_policy, sandbox_config_user_policy_experimental, sandbox_config_user_policy_experimental_seatbelt, sandbox_config_user_policy_filesystem, sandbox_config_user_policy_network, sandbox_config_user_policy_network_proxy, sandbox_config_user_policy_seatbelt, schedule_add_at_request, schedule_add_cron_request, schedule_add_request, schedule_add_result, schedule_add_self_paced_request, schedule_entry, schedule_has_self_paced_result, schedule_list, schedule_rearm_self_paced_request, schedule_stop_request, schedule_stop_result, secrets_add_filter_values_request, secrets_add_filter_values_result, send_agent_mode, send_attachments_to_message_params, send_message_item, send_messages_request, send_messages_result, send_mode, send_request, send_result, send_system_notification_request, server_agent_list, server_instruction_source_list, server_skill, server_skill_list, session_activity, session_agent_list_request, session_auth_login_request, session_auth_logout_user_request, session_auth_status, session_auth_switch_request, session_bulk_delete_result, session_cancel_all_background_agents_result, session_capability, session_commands_list_request, session_completion_item, session_context, session_context_host_type, session_enrich_metadata_result, session_fs_append_file_request, session_fs_error, session_fs_error_code, session_fs_exists_request, session_fs_exists_result, session_fs_mkdir_request, session_fs_readdir_request, session_fs_readdir_result, session_fs_readdir_with_types_entry, session_fs_readdir_with_types_entry_type, session_fs_readdir_with_types_request, session_fs_readdir_with_types_result, session_fs_read_file_request, session_fs_read_file_result, session_fs_rename_request, session_fs_rm_request, session_fs_set_provider_capabilities, session_fs_set_provider_conventions, session_fs_set_provider_request, session_fs_set_provider_result, session_fs_sqlite_exists_request, session_fs_sqlite_exists_result, session_fs_sqlite_query_request, session_fs_sqlite_query_result, session_fs_sqlite_query_type, session_fs_sqlite_transaction_error, session_fs_sqlite_transaction_error_class, session_fs_sqlite_transaction_request, session_fs_sqlite_transaction_result, session_fs_sqlite_transaction_statement, session_fs_stat_request, session_fs_stat_result, session_fs_write_file_request, session_git_hub_auth_get_all_auth_available_result, session_git_hub_auth_logout_result, session_git_hub_auth_logout_user_result, session_history_compact_request, session_installed_plugin, session_installed_plugin_source, session_installed_plugin_source_git_hub, session_installed_plugin_source_local, session_installed_plugin_source_url, session_limit_prediction_baseline_data, session_limit_prediction_client_type, session_limit_prediction_details, session_limit_prediction_predict_request, session_limit_prediction_request, session_limit_prediction_result, session_limit_prediction_source, session_limit_prediction_tier, session_limit_prediction_tier_option, session_limit_prediction_unavailable_reason, session_list, session_list_entry, session_list_filter, session_load_deferred_repo_hooks_result, session_log_level, session_managed_permissions, session_managed_settings, session_mcp_apps_call_tool_result, session_metadata_snapshot, session_mode, session_model_list, session_model_list_request, session_model_price_category, session_open_options, session_open_options_additional_content_exclusion_policy, session_open_options_additional_content_exclusion_policy_rule, session_open_options_additional_content_exclusion_policy_rule_source, session_open_options_additional_content_exclusion_policy_scope, session_open_options_env_value_mode, session_open_options_reasoning_summary, session_open_params, session_open_result, session_plugins_reload_request, session_provider_get_endpoint_request, session_prune_result, sessions_bulk_delete_request, sessions_check_in_use_request, sessions_check_in_use_result, sessions_close_request, sessions_close_result, sessions_delete_request, sessions_enrich_metadata_request, session_set_credentials_params, session_set_credentials_result, session_settings_built_in_tool_availability_snapshot, session_settings_evaluate_predicate_request, session_settings_evaluate_predicate_result, session_settings_job_snapshot, session_settings_model_snapshot, session_settings_online_evaluation_snapshot, session_settings_predicate_name, session_settings_repo_snapshot, session_settings_snapshot, session_settings_validation_snapshot, sessions_find_by_prefix_request, sessions_find_by_prefix_result, sessions_find_by_task_id_request, sessions_find_by_task_id_result, sessions_fork_request, sessions_fork_result, sessions_get_board_entry_count_request, sessions_get_board_entry_count_result, sessions_get_event_file_path_request, sessions_get_event_file_path_result, sessions_get_last_for_context_request, sessions_get_last_for_context_result, sessions_get_metadata_request, sessions_get_metadata_result, sessions_get_persisted_remote_steerable_request, sessions_get_persisted_remote_steerable_result, session_sizes, sessions_list_non_empty_session_ids_request, sessions_list_non_empty_session_ids_result, sessions_list_request, sessions_load_deferred_repo_hooks_request, sessions_open_attach, sessions_open_cloud, sessions_open_create, sessions_open_handoff, sessions_open_handoff_task_type, sessions_open_progress, sessions_open_progress_status, sessions_open_progress_step, sessions_open_remote, sessions_open_resume, sessions_open_resume_last, sessions_open_status, session_source, sessions_prune_old_request, sessions_register_extension_tools_on_session_options, sessions_release_lock_request, sessions_release_lock_result, sessions_reload_plugin_hooks_request, sessions_reload_plugin_hooks_result, sessions_save_request, sessions_save_result, sessions_set_additional_plugins_request, sessions_set_additional_plugins_result, sessions_set_remote_control_steering_request, sessions_start_remote_control_request, sessions_stop_remote_control_request, sessions_transfer_remote_control_request, session_telemetry_engagement, session_update_options_params, session_update_options_result, session_visibility_status, session_working_directory_context, session_working_directory_context_host_type, settable_auth_info, settable_token_auth_info, shell_cancel_user_requested_request, shell_credentials, shell_exec_request, shell_exec_result, shell_execute_user_requested_request, shell_init_profile, shell_init_script, shell_init_script_shell, shell_kill_request, shell_kill_result, shell_kill_signal, shell_options, shutdown_request, skill, skill_discovery_path, skill_discovery_path_list, skill_discovery_scope, skill_list, skills_config_set_disabled_skills_request, skills_config_set_skill_disabled_request, skills_disable_request, skills_discover_request, skills_enable_request, skills_get_discovery_paths_request, skills_get_invoked_result, skills_invoked_skill, skills_load_diagnostics, slash_command_add_timeline_entry_result, slash_command_agent_prompt_result, slash_command_completed_result, slash_command_info, slash_command_input, slash_command_input_choice, slash_command_input_completion, slash_command_invocation_result, slash_command_kind, slash_command_model_picker_dialog, slash_command_select_subcommand_option, slash_command_select_subcommand_result, slash_command_set_model_result, slash_command_set_plan_model_result, slash_command_show_dialog_result, slash_command_text_result, slash_command_timeline_entry, subagent_settings_entry, subagent_settings_entry_context_tier, task_agent_info, task_agent_progress, task_complete_data, task_completion_decision, task_execution_mode, task_info, task_list, task_progress_line, tasks_cancel_request, tasks_cancel_result, tasks_get_current_promotable_result, tasks_get_progress_request, tasks_get_progress_result, task_shell_info, task_shell_info_attachment_mode, task_shell_progress, tasks_promote_current_to_background_result, tasks_promote_to_background_request, tasks_promote_to_background_result, tasks_refresh_result, tasks_remove_request, tasks_remove_result, tasks_send_message_request, tasks_send_message_result, tasks_start_agent_request, tasks_start_agent_result, task_status, tasks_wait_for_pending_result, telemetry_set_feature_overrides_request, token_auth_info, token_provider_auth_info, tool, tool_list, tool_result, tool_result_expanded, tool_result_new_message, tool_result_type, tools_execute_request, tools_get_builtin_descriptors_request, tools_get_builtin_descriptors_result, tools_get_current_metadata_result, tools_initialize_and_validate_result, tools_list_request, tools_set_request, tools_set_result, tools_shell_descriptor_config, tools_task_complete_event_data_request, tools_update_subagent_settings_result, ui_auto_mode_switch_response, ui_elicitation_array_any_of_field, ui_elicitation_array_any_of_field_items, ui_elicitation_array_any_of_field_items_any_of, ui_elicitation_array_enum_field, ui_elicitation_array_enum_field_items, ui_elicitation_field_value, ui_elicitation_request, ui_elicitation_response, ui_elicitation_response_action, ui_elicitation_response_content, ui_elicitation_result, ui_elicitation_schema, ui_elicitation_schema_property, ui_elicitation_schema_property_boolean, ui_elicitation_schema_property_number, ui_elicitation_schema_property_number_type, ui_elicitation_schema_property_string, ui_elicitation_schema_property_string_format, ui_elicitation_string_enum_field, ui_elicitation_string_one_of_field, ui_elicitation_string_one_of_field_one_of, ui_ephemeral_query_request, ui_ephemeral_query_result, ui_exit_plan_mode_action, ui_exit_plan_mode_response, ui_handle_pending_auto_mode_switch_request, ui_handle_pending_elicitation_request, ui_handle_pending_exit_plan_mode_request, ui_handle_pending_result, ui_handle_pending_sampling_request, ui_handle_pending_sampling_response, ui_handle_pending_session_limits_exhausted_request, ui_handle_pending_user_input_request, ui_register_direct_auto_mode_switch_handler_result, ui_session_limits_exhausted_response, ui_session_limits_exhausted_response_action, ui_unregister_direct_auto_mode_switch_handler_request, ui_unregister_direct_auto_mode_switch_handler_result, ui_user_input_response, update_subagent_settings_request, usage_get_metrics_result, usage_metrics_agent_metric, usage_metrics_code_changes, usage_metrics_model_metric, usage_metrics_model_metric_requests, usage_metrics_model_metric_token_detail, usage_metrics_model_metric_usage, usage_metrics_token_detail, user_auth_info, user_requested_shell_command_result, user_setting_metadata, user_settings_get_result, user_settings_set_request, user_settings_set_result, visibility_get_result, visibility_set_request, visibility_set_result, workspace_diff_file_change, workspace_diff_file_change_type, workspace_diff_mode, workspace_diff_result, workspaces_add_summary_request, workspaces_add_summary_result, workspaces_autopilot_objective_exists_result, workspaces_checkpoints, workspaces_create_file_request, workspaces_delete_autopilot_objective_result, workspaces_diff_request, workspaces_ensure_request, workspaces_get_workspace_result, workspaces_list_checkpoints_result, workspaces_list_files_result, workspaces_read_autopilot_objective_result, workspaces_read_checkpoint_request, workspaces_read_checkpoint_result, workspaces_read_file_request, workspaces_read_file_result, workspaces_save_large_paste_request, workspaces_save_large_paste_result, workspaces_truncate_summaries_request, workspace_summary_host_type, workspaces_update_metadata_request, workspaces_workspace_details_host_type, workspaces_write_autopilot_objective_request, workspaces_write_autopilot_objective_result, session_auth_info_result, session_context_attribution, session_context_info, subagent_settings, task_progress, workspace_summary) def to_dict(self) -> dict: result: dict = {} @@ -36980,6 +37399,7 @@ def to_dict(self) -> dict: result["CompletionsRequestRequest"] = to_class(CompletionsRequestRequest, self.completions_request_request) result["CompletionsRequestResult"] = to_class(CompletionsRequestResult, self.completions_request_result) result["ConfigureSessionExtensionsParams"] = to_class(_ConfigureSessionExtensionsParams, self.configure_session_extensions_params) + result["ConnectClientInfo"] = to_class(_ConnectClientInfo, self.connect_client_info) result["ConnectedRemoteSessionMetadata"] = to_class(ConnectedRemoteSessionMetadata, self.connected_remote_session_metadata) result["ConnectedRemoteSessionMetadataKind"] = to_enum(ConnectedRemoteSessionMetadataKind, self.connected_remote_session_metadata_kind) result["ConnectedRemoteSessionMetadataRepository"] = to_class(ConnectedRemoteSessionMetadataRepository, self.connected_remote_session_metadata_repository) @@ -37011,7 +37431,6 @@ def to_dict(self) -> dict: result["DebugCollectLogsResultKind"] = to_enum(DebugCollectLogsResultKind, self.debug_collect_logs_result_kind) result["DebugCollectLogsSkippedEntry"] = to_class(DebugCollectLogsSkippedEntry, self.debug_collect_logs_skipped_entry) result["DebugCollectLogsSource"] = to_enum(DebugCollectLogsSource, self.debug_collect_logs_source) - result["DisableBypassPermissionsMode"] = to_enum(DisableBypassPermissionsMode, self.disable_bypass_permissions_mode) result["DiscoveredCanvas"] = to_class(DiscoveredCanvas, self.discovered_canvas) result["DiscoveredExtension"] = to_class(DiscoveredExtension, self.discovered_extension) result["DiscoveredExtensionMode"] = to_enum(DiscoveredExtensionMode, self.discovered_extension_mode) @@ -37108,6 +37527,9 @@ def to_dict(self) -> dict: result["GitHubTelemetryClientInfo"] = to_class(GitHubTelemetryClientInfo, self.git_hub_telemetry_client_info) result["GitHubTelemetryEvent"] = to_class(GitHubTelemetryEvent, self.git_hub_telemetry_event) result["GitHubTelemetryNotification"] = to_class(GitHubTelemetryNotification, self.git_hub_telemetry_notification) + result["GitHubTokenAcquireReason"] = to_enum(GitHubTokenAcquireReason, self.git_hub_token_acquire_reason) + result["GitHubTokenAcquireRequest"] = to_class(GitHubTokenAcquireRequest, self.git_hub_token_acquire_request) + result["GitHubTokenAcquireResult"] = to_class(GitHubTokenAcquireResult, self.git_hub_token_acquire_result) result["HandlePendingToolCallRequest"] = to_class(HandlePendingToolCallRequest, self.handle_pending_tool_call_request) result["HandlePendingToolCallResult"] = to_class(HandlePendingToolCallResult, self.handle_pending_tool_call_result) result["HistoryAbortManualCompactionResult"] = to_class(HistoryAbortManualCompactionResult, self.history_abort_manual_compaction_result) @@ -37364,6 +37786,7 @@ def to_dict(self) -> dict: result["ModelCapabilitiesSupports"] = to_class(ModelCapabilitiesSupports, self.model_capabilities_supports) result["ModelList"] = to_class(ModelList, self.model_list) result["ModelListRequest"] = self.model_list_request + result["ModelMessage"] = to_class(ModelMessage, self.model_message) result["ModelPickerCategory"] = to_enum(ModelPickerCategory, self.model_picker_category) result["ModelPickerPersistenceRequest"] = to_class(ModelPickerPersistenceRequest, self.model_picker_persistence_request) result["ModelPickerPriceCategory"] = to_enum(ModelPickerPriceCategory, self.model_picker_price_category) @@ -37376,6 +37799,7 @@ def to_dict(self) -> dict: result["ModelSwitchConfirmation"] = to_class(ModelSwitchConfirmation, self.model_switch_confirmation) result["ModelSwitchToRequest"] = to_class(ModelSwitchToRequest, self.model_switch_to_request) result["ModelSwitchToResult"] = to_class(ModelSwitchToResult, self.model_switch_to_result) + result["ModelWarningText"] = to_class(ModelWarningText, self.model_warning_text) result["ModeSetRequest"] = to_class(ModeSetRequest, self.mode_set_request) result["ModeSetResult"] = to_class(ModeSetResult, self.mode_set_result) result["MoveMcpLoadingToBackgroundResult"] = to_class(MoveMCPLoadingToBackgroundResult, self.move_mcp_loading_to_background_result) @@ -37626,6 +38050,7 @@ def to_dict(self) -> dict: result["RunOptions"] = to_class(RunOptions, self.run_options) result["SandboxConfig"] = to_class(SandboxConfig, self.sandbox_config) result["SandboxConfigAuth"] = to_class(SandboxConfigAuth, self.sandbox_config_auth) + result["SandboxConfigSource"] = to_enum(_SandboxConfigSource, self.sandbox_config_source) result["SandboxConfigUserPolicy"] = to_class(SandboxConfigUserPolicy, self.sandbox_config_user_policy) result["SandboxConfigUserPolicyExperimental"] = to_class(SandboxConfigUserPolicyExperimental, self.sandbox_config_user_policy_experimental) result["SandboxConfigUserPolicyExperimentalSeatbelt"] = to_class(SandboxConfigUserPolicyExperimentalSeatbelt, self.sandbox_config_user_policy_experimental_seatbelt) @@ -37823,6 +38248,8 @@ def to_dict(self) -> dict: result["SessionVisibilityStatus"] = to_enum(SessionVisibilityStatus, self.session_visibility_status) result["SessionWorkingDirectoryContext"] = to_class(SessionWorkingDirectoryContext, self.session_working_directory_context) result["SessionWorkingDirectoryContextHostType"] = to_enum(HostType, self.session_working_directory_context_host_type) + result["SettableAuthInfo"] = (self.settable_auth_info).to_dict() + result["SettableTokenAuthInfo"] = to_class(SettableTokenAuthInfo, self.settable_token_auth_info) result["ShellCancelUserRequestedRequest"] = to_class(ShellCancelUserRequestedRequest, self.shell_cancel_user_requested_request) result["ShellCredentials"] = to_class(ShellCredentials, self.shell_credentials) result["ShellExecRequest"] = to_class(ShellExecRequest, self.shell_exec_request) @@ -37899,6 +38326,7 @@ def to_dict(self) -> dict: result["TasksWaitForPendingResult"] = to_class(TasksWaitForPendingResult, self.tasks_wait_for_pending_result) result["TelemetrySetFeatureOverridesRequest"] = to_class(TelemetrySetFeatureOverridesRequest, self.telemetry_set_feature_overrides_request) result["TokenAuthInfo"] = to_class(TokenAuthInfo, self.token_auth_info) + result["TokenProviderAuthInfo"] = to_class(TokenProviderAuthInfo, self.token_provider_auth_info) result["Tool"] = to_class(Tool, self.tool) result["ToolList"] = to_class(ToolList, self.tool_list) result["ToolResult"] = from_union([lambda x: to_class(ToolResultExpanded, x), from_str], self.tool_result) @@ -38030,7 +38458,7 @@ def _load_AgentRegistrySpawnResult(obj: Any) -> "AgentRegistrySpawnResult": case _: raise ValueError(f"Unknown AgentRegistrySpawnResult kind: {kind!r}") # Authentication credentials accepted only at native protocol ingress. Runtime outputs use credential-free `AuthIdentity` metadata. -AuthInfo = HMACAuthInfo | EnvAuthInfo | TokenAuthInfo | CopilotAPITokenAuthInfo | UserAuthInfo | GhCLIAuthInfo | APIKeyAuthInfo +AuthInfo = HMACAuthInfo | EnvAuthInfo | TokenAuthInfo | TokenProviderAuthInfo | CopilotAPITokenAuthInfo | UserAuthInfo | GhCLIAuthInfo | APIKeyAuthInfo def _load_AuthInfo(obj: Any) -> "AuthInfo": assert isinstance(obj, dict) @@ -38039,6 +38467,7 @@ def _load_AuthInfo(obj: Any) -> "AuthInfo": case "hmac": return HMACAuthInfo.from_dict(obj) case "env": return EnvAuthInfo.from_dict(obj) case "token": return TokenAuthInfo.from_dict(obj) + case "token-provider": return TokenProviderAuthInfo.from_dict(obj) case "copilot-api-token": return CopilotAPITokenAuthInfo.from_dict(obj) case "user": return UserAuthInfo.from_dict(obj) case "gh-cli": return GhCLIAuthInfo.from_dict(obj) @@ -38317,6 +38746,22 @@ def _load_SessionOpenParams(obj: Any) -> "SessionOpenParams": case "handoff": return SessionsOpenHandoff.from_dict(obj) case _: raise ValueError(f"Unknown SessionOpenParams kind: {kind!r}") +# Authentication credentials accepted by session.gitHubAuth.setCredentials. Session-owned token-provider identities cannot be installed through this method. +SettableAuthInfo = HMACAuthInfo | EnvAuthInfo | SettableTokenAuthInfo | CopilotAPITokenAuthInfo | UserAuthInfo | GhCLIAuthInfo | APIKeyAuthInfo + +def _load_SettableAuthInfo(obj: Any) -> "SettableAuthInfo": + assert isinstance(obj, dict) + kind = obj.get("type") + match kind: + case "hmac": return HMACAuthInfo.from_dict(obj) + case "env": return EnvAuthInfo.from_dict(obj) + case "token": return SettableTokenAuthInfo.from_dict(obj) + case "copilot-api-token": return CopilotAPITokenAuthInfo.from_dict(obj) + case "user": return UserAuthInfo.from_dict(obj) + case "gh-cli": return GhCLIAuthInfo.from_dict(obj) + case "api-key": return APIKeyAuthInfo.from_dict(obj) + case _: raise ValueError(f"Unknown SettableAuthInfo type: {kind!r}") + # Result of invoking the slash command (text output, prompt to send to the agent, completion, or subcommand selection). SlashCommandInvocationResult = SlashCommandTextResult | SlashCommandAgentPromptResult | SlashCommandCompletedResult | SlashCommandSelectSubcommandResult | SlashCommandAddTimelineEntryResult | SlashCommandShowDialogResult | SlashCommandSetModelResult | SlashCommandSetPlanModelResult @@ -40063,7 +40508,7 @@ async def list(self, *, timeout: float | None = None) -> PermissionPathsList: return PermissionPathsList.from_dict(await self._client.request("session.permissions.paths.list", {"sessionId": self._session_id}, **_timeout_kwargs(timeout))) async def add(self, params: PermissionPathsAddParams, *, timeout: float | None = None) -> PermissionsPathsAddResult: - "Adds a directory to the session's allow-list.\n\nArgs:\n params: Directory path to add to the session's allowed directories.\n\nReturns:\n Indicates whether the operation succeeded." + "Adds a directory to the session's allow-list and activates conventional skill and agent definitions under it.\n\nArgs:\n params: Directory path to add to the session's allowed directories.\n\nReturns:\n Indicates whether the operation succeeded." params_dict: dict[str, Any] = {k: v for k, v in params.to_dict().items() if v is not None} params_dict["sessionId"] = self._session_id return PermissionsPathsAddResult.from_dict(await self._client.request("session.permissions.paths.add", params_dict, **_timeout_kwargs(timeout))) @@ -41123,12 +41568,19 @@ async def event(self, params: GitHubTelemetryNotification) -> None: "Forwards a single GitHub telemetry event to a host connection that opted into telemetry forwarding during the `server.connect` handshake. Opted-in connections receive every event the runtime emits after the handshake — across all sessions, plus sessionless events (for example, `server.sendTelemetry` calls with no session id).\n\nArgs:\n params: Payload for a `gitHubTelemetry.event` notification: a single GitHub telemetry event the runtime forwards to a host connection that opted into telemetry forwarding during the `server.connect` handshake." pass +# Experimental: this API group is experimental and may change or be removed. +class GitHubTokenHandler(Protocol): + async def get_token(self, params: GitHubTokenAcquireRequest) -> GitHubTokenAcquireResult: + "Asks the SDK client to mint a GitHub access token for a session whose configuration supplied a GitHub token provider. The runtime acquires the initial token during bootstrap and refreshes it during expiry preflight when one hour or less remains.\n\nArgs:\n params: Asks the SDK client to acquire a GitHub access token from an opaque callback registration.\n\nReturns:\n SDK host response to a GitHub credential request." + pass + @dataclass class ClientGlobalApiHandlers: hooks: HooksHandler | None = None extension_launch_provider: ExtensionLaunchProviderHandler | None = None llm_inference: LlmInferenceHandler | None = None git_hub_telemetry: GitHubTelemetryHandler | None = None + git_hub_token: GitHubTokenHandler | None = None def register_client_global_api_handlers( client: "JsonRpcClient", @@ -41175,6 +41627,13 @@ async def handle_git_hub_telemetry_event(params: dict) -> None: await handler.event(request) return None client.set_notification_method_handler("gitHubTelemetry.event", handle_git_hub_telemetry_event) + async def handle_git_hub_token_get_token(params: dict) -> dict | None: + request = GitHubTokenAcquireRequest.from_dict(params) + handler = handlers.git_hub_token + if handler is None: raise RuntimeError("No git_hub_token client-global handler registered") + result = await handler.get_token(request) + return result.value if hasattr(result, 'value') else result + client.set_request_handler("gitHubToken.getToken", handle_git_hub_token_get_token) __all__ = [ "APIKeyAuthInfo", @@ -41386,7 +41845,6 @@ async def handle_git_hub_telemetry_event(params: dict) -> None: "DebugCollectLogsResultKind", "DebugCollectLogsSkippedEntry", "DebugCollectLogsSource", - "DisableBypassPermissionsMode", "DiscoveredCanvas", "DiscoveredExtension", "DiscoveredExtensionMode", @@ -41506,6 +41964,11 @@ async def handle_git_hub_telemetry_event(params: dict) -> None: "GitHubTelemetryEvent", "GitHubTelemetryHandler", "GitHubTelemetryNotification", + "GitHubTokenAcquireReason", + "GitHubTokenAcquireRequest", + "GitHubTokenAcquireResult", + "GitHubTokenAcquireResultKind", + "GitHubTokenHandler", "HMACAuthInfo", "HMACAuthInfoType", "HandlePendingToolCallRequest", @@ -41632,7 +42095,6 @@ async def handle_git_hub_telemetry_event(params: dict) -> None: "MCPOauthLoginRequest", "MCPOauthLoginResult", "MCPOauthPendingRequestResponse", - "MCPOauthPendingRequestResponseKind", "MCPOauthProbeNeedsAuthReason", "MCPOauthProbeRequest", "MCPOauthProbeResult", @@ -41804,6 +42266,7 @@ async def handle_git_hub_telemetry_event(params: dict) -> None: "ModelCapabilitiesSupports", "ModelList", "ModelListRequest", + "ModelMessage", "ModelPickerCategory", "ModelPickerPersistenceRequest", "ModelPickerPriceCategory", @@ -41815,6 +42278,7 @@ async def handle_git_hub_telemetry_event(params: dict) -> None: "ModelSwitchConfirmation", "ModelSwitchToRequest", "ModelSwitchToResult", + "ModelWarningText", "ModelsListRequest", "MoveMCPLoadingToBackgroundResult", "NameApi", @@ -42362,6 +42826,10 @@ async def handle_git_hub_telemetry_event(params: dict) -> None: "SessionsStartRemoteControlRequest", "SessionsStopRemoteControlRequest", "SessionsTransferRemoteControlRequest", + "SettableAuthInfo", + "SettableAuthInfoType", + "SettableTokenAuthInfo", + "SettableTokenAuthInfoType", "ShellApi", "ShellCancelUserRequestedRequest", "ShellCredentials", @@ -42461,7 +42929,8 @@ async def handle_git_hub_telemetry_event(params: dict) -> None: "TelemetrySetFeatureOverridesRequest", "Theme", "TokenAuthInfo", - "TokenAuthInfoType", + "TokenProviderAuthInfo", + "TokenProviderAuthInfoType", "Tool", "ToolList", "ToolResult", diff --git a/python/copilot/generated/session_events.py b/python/copilot/generated/session_events.py index 68117bdc01..528e6657fc 100644 --- a/python/copilot/generated/session_events.py +++ b/python/copilot/generated/session_events.py @@ -173,6 +173,7 @@ class SessionEventType(Enum): ASSISTANT_USAGE = "assistant.usage" PROMPT_CACHE_BREAK = "prompt_cache_break" MODEL_CALL_FAILURE = "model.call_failure" + MODEL_CALL_FINISHED = "model.call_finished" MODEL_CALL_START = "model.call_start" ABORT = "abort" TOOL_USER_REQUESTED = "tool.user_requested" @@ -381,6 +382,31 @@ def to_dict(self) -> dict: return result +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class AssistantMessageReasoningBlocks: + "Neutral provider-tagged reasoning content blocks preserved verbatim for round-tripping" + provider: str + blocks: list[Any] | None = None + + @staticmethod + def from_dict(obj: Any) -> "AssistantMessageReasoningBlocks": + assert isinstance(obj, dict) + provider = from_str(obj.get("provider")) + blocks = from_union([from_none, lambda x: from_list(lambda x: x, x)], obj.get("blocks")) + return AssistantMessageReasoningBlocks( + provider=provider, + blocks=blocks, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["provider"] = from_str(self.provider) + if self.blocks is not None: + result["blocks"] = from_union([from_none, lambda x: from_list(lambda x: x, x)], self.blocks) + return result + + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class AssistantMessageServerTools: @@ -1297,6 +1323,7 @@ class SessionManagedSettingsResolvedData: source: ManagedSettingsResolvedSource client_managed: bool | None = None permissions_allow_intersected: bool | None = None + sandbox_enabled_by_undetermined_policy: bool | None = None settings: Any = None @staticmethod @@ -1310,6 +1337,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")) + sandbox_enabled_by_undetermined_policy = from_union([from_none, from_bool], obj.get("sandboxEnabledByUndeterminedPolicy")) settings = obj.get("settings") return SessionManagedSettingsResolvedData( bypass_permissions_disabled=bypass_permissions_disabled, @@ -1320,6 +1348,7 @@ def from_dict(obj: Any) -> "SessionManagedSettingsResolvedData": source=source, client_managed=client_managed, permissions_allow_intersected=permissions_allow_intersected, + sandbox_enabled_by_undetermined_policy=sandbox_enabled_by_undetermined_policy, settings=settings, ) @@ -1335,6 +1364,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.sandbox_enabled_by_undetermined_policy is not None: + result["sandboxEnabledByUndeterminedPolicy"] = from_union([from_none, from_bool], self.sandbox_enabled_by_undetermined_policy) if self.settings is not None: result["settings"] = self.settings return result @@ -1564,6 +1595,7 @@ class AssistantMessageData: # Deprecated: this field is deprecated. parent_tool_call_id: str | None = None phase: str | None = None + reasoning_blocks: AssistantMessageReasoningBlocks | None = None reasoning_opaque: str | None = None reasoning_text: str | None = None reasoning_wire_field: str | None = None @@ -1590,6 +1622,7 @@ def from_dict(obj: Any) -> "AssistantMessageData": output_tokens = from_union([from_none, from_int], obj.get("outputTokens")) parent_tool_call_id = from_union([from_none, from_str], obj.get("parentToolCallId")) phase = from_union([from_none, from_str], obj.get("phase")) + reasoning_blocks = from_union([from_none, AssistantMessageReasoningBlocks.from_dict], obj.get("reasoningBlocks")) reasoning_opaque = from_union([from_none, from_str], obj.get("reasoningOpaque")) reasoning_text = from_union([from_none, from_str], obj.get("reasoningText")) reasoning_wire_field = from_union([from_none, from_str], obj.get("reasoningWireField")) @@ -1613,6 +1646,7 @@ def from_dict(obj: Any) -> "AssistantMessageData": output_tokens=output_tokens, parent_tool_call_id=parent_tool_call_id, phase=phase, + reasoning_blocks=reasoning_blocks, reasoning_opaque=reasoning_opaque, reasoning_text=reasoning_text, reasoning_wire_field=reasoning_wire_field, @@ -1650,6 +1684,8 @@ def to_dict(self) -> dict: result["parentToolCallId"] = from_union([from_none, from_str], self.parent_tool_call_id) if self.phase is not None: result["phase"] = from_union([from_none, from_str], self.phase) + if self.reasoning_blocks is not None: + result["reasoningBlocks"] = from_union([from_none, lambda x: to_class(AssistantMessageReasoningBlocks, x)], self.reasoning_blocks) if self.reasoning_opaque is not None: result["reasoningOpaque"] = from_union([from_none, from_str], self.reasoning_opaque) if self.reasoning_text is not None: @@ -2080,6 +2116,7 @@ class AssistantUsageData: # Internal: this field is an internal SDK API and is not part of the public surface. _num_tool_calls: int | None = None output_tokens: int | None = None + output_ttft: timedelta | None = None # Deprecated: this field is deprecated. parent_tool_call_id: str | None = None provider_call_id: str | None = None @@ -2127,6 +2164,7 @@ def from_dict(obj: Any) -> "AssistantUsageData": max_prompt_tokens = from_union([from_none, from_int], obj.get("maxPromptTokens")) _num_tool_calls = from_union([from_none, from_int], obj.get("numToolCalls")) output_tokens = from_union([from_none, from_int], obj.get("outputTokens")) + output_ttft = from_union([from_none, from_timedelta], obj.get("outputTtftMs")) parent_tool_call_id = from_union([from_none, from_str], obj.get("parentToolCallId")) provider_call_id = from_union([from_none, from_str], obj.get("providerCallId")) _quota_snapshots = from_union([from_none, lambda x: from_dict(_AssistantUsageQuotaSnapshot.from_dict, x)], obj.get("quotaSnapshots")) @@ -2167,6 +2205,7 @@ def from_dict(obj: Any) -> "AssistantUsageData": max_prompt_tokens=max_prompt_tokens, _num_tool_calls=_num_tool_calls, output_tokens=output_tokens, + output_ttft=output_ttft, parent_tool_call_id=parent_tool_call_id, provider_call_id=provider_call_id, _quota_snapshots=_quota_snapshots, @@ -2235,6 +2274,8 @@ def to_dict(self) -> dict: result["numToolCalls"] = from_union([from_none, to_int], self._num_tool_calls) if self.output_tokens is not None: result["outputTokens"] = from_union([from_none, to_int], self.output_tokens) + if self.output_ttft is not None: + result["outputTtftMs"] = from_union([from_none, to_timedelta], self.output_ttft) if self.parent_tool_call_id is not None: result["parentToolCallId"] = from_union([from_none, from_str], self.parent_tool_call_id) if self.provider_call_id is not None: @@ -4613,6 +4654,47 @@ def to_dict(self) -> dict: return result +@dataclass +class ModelCallFinishedData: + "Final lifecycle outcome for one logical model dispatch. A logical dispatch may include internal reconnect or fallback work, so event count is not provider HTTP-request count." + dispatch_duration: timedelta + edit_classifier_version: int + outcome: ModelCallFinishedOutcome + turn_id: str + contains_built_in_file_edit_request: bool | None = None + interaction_id: str | None = None + + @staticmethod + def from_dict(obj: Any) -> "ModelCallFinishedData": + assert isinstance(obj, dict) + dispatch_duration = from_timedelta(obj.get("dispatchDurationMs")) + edit_classifier_version = from_int(obj.get("editClassifierVersion")) + outcome = parse_enum(ModelCallFinishedOutcome, obj.get("outcome")) + turn_id = from_str(obj.get("turnId")) + contains_built_in_file_edit_request = from_union([from_none, from_bool], obj.get("containsBuiltInFileEditRequest")) + interaction_id = from_union([from_none, from_str], obj.get("interactionId")) + return ModelCallFinishedData( + dispatch_duration=dispatch_duration, + edit_classifier_version=edit_classifier_version, + outcome=outcome, + turn_id=turn_id, + contains_built_in_file_edit_request=contains_built_in_file_edit_request, + interaction_id=interaction_id, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["dispatchDurationMs"] = to_timedelta(self.dispatch_duration) + result["editClassifierVersion"] = to_int(self.edit_classifier_version) + result["outcome"] = to_enum(ModelCallFinishedOutcome, self.outcome) + result["turnId"] = from_str(self.turn_id) + if self.contains_built_in_file_edit_request is not None: + result["containsBuiltInFileEditRequest"] = from_union([from_none, from_bool], self.contains_built_in_file_edit_request) + if self.interaction_id is not None: + result["interactionId"] = from_union([from_none, from_str], self.interaction_id) + return result + + @dataclass class ModelCallStartData: "Model API dispatch metadata for internal telemetry" @@ -5253,6 +5335,7 @@ class PermissionPromptRequestMcp: args: Any = None # Experimental: this field is part of an experimental API and may change or be removed. assisted_approval: PermissionAssistedApproval | None = None + can_offer_server_wide_approval: bool | None = None # Experimental: this field is part of an experimental API and may change or be removed. permission_recommendation: PermissionRecommendation | None = None tool_call_id: str | None = None @@ -5265,6 +5348,7 @@ def from_dict(obj: Any) -> "PermissionPromptRequestMcp": tool_title = from_str(obj.get("toolTitle")) args = obj.get("args") assisted_approval = from_union([from_none, PermissionAssistedApproval.from_dict], obj.get("assistedApproval")) + can_offer_server_wide_approval = from_union([from_none, from_bool], obj.get("canOfferServerWideApproval")) permission_recommendation = from_union([from_none, lambda x: parse_enum(PermissionRecommendation, x)], obj.get("permissionRecommendation")) tool_call_id = from_union([from_none, from_str], obj.get("toolCallId")) return PermissionPromptRequestMcp( @@ -5273,6 +5357,7 @@ def from_dict(obj: Any) -> "PermissionPromptRequestMcp": tool_title=tool_title, args=args, assisted_approval=assisted_approval, + can_offer_server_wide_approval=can_offer_server_wide_approval, permission_recommendation=permission_recommendation, tool_call_id=tool_call_id, ) @@ -5287,6 +5372,8 @@ def to_dict(self) -> dict: result["args"] = self.args if self.assisted_approval is not None: result["assistedApproval"] = from_union([from_none, lambda x: to_class(PermissionAssistedApproval, x)], self.assisted_approval) + if self.can_offer_server_wide_approval is not None: + result["canOfferServerWideApproval"] = from_union([from_none, from_bool], self.can_offer_server_wide_approval) if self.permission_recommendation is not None: result["permissionRecommendation"] = from_union([from_none, lambda x: to_enum(PermissionRecommendation, x)], self.permission_recommendation) if self.tool_call_id is not None: @@ -8428,7 +8515,12 @@ class SubagentCompletedData: agent_name: str tool_call_id: str cancelled: bool | None = None + configured_model_matches_actual: bool | None = None + configured_model_preference: str | None = None duration: timedelta | None = None + explicit_model_matches_preference: bool | None = None + explicit_model_override: str | None = None + first_dispatched_model: str | None = None model: str | None = None total_tokens: int | None = None total_tool_calls: int | None = None @@ -8440,7 +8532,12 @@ def from_dict(obj: Any) -> "SubagentCompletedData": agent_name = from_str(obj.get("agentName")) tool_call_id = from_str(obj.get("toolCallId")) cancelled = from_union([from_none, from_bool], obj.get("cancelled")) + configured_model_matches_actual = from_union([from_none, from_bool], obj.get("configuredModelMatchesActual")) + configured_model_preference = from_union([from_none, from_str], obj.get("configuredModelPreference")) duration = from_union([from_none, from_timedelta], obj.get("durationMs")) + explicit_model_matches_preference = from_union([from_none, from_bool], obj.get("explicitModelMatchesPreference")) + explicit_model_override = from_union([from_none, from_str], obj.get("explicitModelOverride")) + first_dispatched_model = from_union([from_none, from_str], obj.get("firstDispatchedModel")) model = from_union([from_none, from_str], obj.get("model")) total_tokens = from_union([from_none, from_int], obj.get("totalTokens")) total_tool_calls = from_union([from_none, from_int], obj.get("totalToolCalls")) @@ -8449,7 +8546,12 @@ def from_dict(obj: Any) -> "SubagentCompletedData": agent_name=agent_name, tool_call_id=tool_call_id, cancelled=cancelled, + configured_model_matches_actual=configured_model_matches_actual, + configured_model_preference=configured_model_preference, duration=duration, + explicit_model_matches_preference=explicit_model_matches_preference, + explicit_model_override=explicit_model_override, + first_dispatched_model=first_dispatched_model, model=model, total_tokens=total_tokens, total_tool_calls=total_tool_calls, @@ -8462,8 +8564,18 @@ def to_dict(self) -> dict: result["toolCallId"] = from_str(self.tool_call_id) if self.cancelled is not None: result["cancelled"] = from_union([from_none, from_bool], self.cancelled) + if self.configured_model_matches_actual is not None: + result["configuredModelMatchesActual"] = from_union([from_none, from_bool], self.configured_model_matches_actual) + if self.configured_model_preference is not None: + result["configuredModelPreference"] = from_union([from_none, from_str], self.configured_model_preference) if self.duration is not None: result["durationMs"] = from_union([from_none, to_timedelta_int], self.duration) + if self.explicit_model_matches_preference is not None: + result["explicitModelMatchesPreference"] = from_union([from_none, from_bool], self.explicit_model_matches_preference) + if self.explicit_model_override is not None: + result["explicitModelOverride"] = from_union([from_none, from_str], self.explicit_model_override) + if self.first_dispatched_model is not None: + result["firstDispatchedModel"] = from_union([from_none, from_str], self.first_dispatched_model) if self.model is not None: result["model"] = from_union([from_none, from_str], self.model) if self.total_tokens is not None: @@ -8492,7 +8604,12 @@ class SubagentFailedData: agent_name: str error: str tool_call_id: str + configured_model_matches_actual: bool | None = None + configured_model_preference: str | None = None duration: timedelta | None = None + explicit_model_matches_preference: bool | None = None + explicit_model_override: str | None = None + first_dispatched_model: str | None = None model: str | None = None total_tokens: int | None = None total_tool_calls: int | None = None @@ -8504,7 +8621,12 @@ def from_dict(obj: Any) -> "SubagentFailedData": agent_name = from_str(obj.get("agentName")) error = from_str(obj.get("error")) tool_call_id = from_str(obj.get("toolCallId")) + configured_model_matches_actual = from_union([from_none, from_bool], obj.get("configuredModelMatchesActual")) + configured_model_preference = from_union([from_none, from_str], obj.get("configuredModelPreference")) duration = from_union([from_none, from_timedelta], obj.get("durationMs")) + explicit_model_matches_preference = from_union([from_none, from_bool], obj.get("explicitModelMatchesPreference")) + explicit_model_override = from_union([from_none, from_str], obj.get("explicitModelOverride")) + first_dispatched_model = from_union([from_none, from_str], obj.get("firstDispatchedModel")) model = from_union([from_none, from_str], obj.get("model")) total_tokens = from_union([from_none, from_int], obj.get("totalTokens")) total_tool_calls = from_union([from_none, from_int], obj.get("totalToolCalls")) @@ -8513,7 +8635,12 @@ def from_dict(obj: Any) -> "SubagentFailedData": agent_name=agent_name, error=error, tool_call_id=tool_call_id, + configured_model_matches_actual=configured_model_matches_actual, + configured_model_preference=configured_model_preference, duration=duration, + explicit_model_matches_preference=explicit_model_matches_preference, + explicit_model_override=explicit_model_override, + first_dispatched_model=first_dispatched_model, model=model, total_tokens=total_tokens, total_tool_calls=total_tool_calls, @@ -8525,8 +8652,18 @@ def to_dict(self) -> dict: result["agentName"] = from_str(self.agent_name) result["error"] = from_str(self.error) result["toolCallId"] = from_str(self.tool_call_id) + if self.configured_model_matches_actual is not None: + result["configuredModelMatchesActual"] = from_union([from_none, from_bool], self.configured_model_matches_actual) + if self.configured_model_preference is not None: + result["configuredModelPreference"] = from_union([from_none, from_str], self.configured_model_preference) if self.duration is not None: result["durationMs"] = from_union([from_none, to_timedelta_int], self.duration) + if self.explicit_model_matches_preference is not None: + result["explicitModelMatchesPreference"] = from_union([from_none, from_bool], self.explicit_model_matches_preference) + if self.explicit_model_override is not None: + result["explicitModelOverride"] = from_union([from_none, from_str], self.explicit_model_override) + if self.first_dispatched_model is not None: + result["firstDispatchedModel"] = from_union([from_none, from_str], self.first_dispatched_model) if self.model is not None: result["model"] = from_union([from_none, from_str], self.model) if self.total_tokens is not None: @@ -10847,6 +10984,8 @@ class ManagedSettingsEnforcedEscalation(Enum): UNRESTRICTED_PATHS = "unrestricted_paths" # Unrestricted URL fetch access. UNRESTRICTED_URLS = "unrestricted_urls" + # A server-wide MCP "Always Allow" (or `--allow-tool `) blanket that would auto-approve every tool from an MCP server. Capped to per-tool approval; each tool still prompts. + SERVER_WIDE_MCP_APPROVAL = "server_wide_mcp_approval" class ManagedSettingsResolvedSource(Enum): @@ -10979,6 +11118,18 @@ class ModelCallFailureTransport(Enum): WEBSOCKET = "websocket" +class ModelCallFinishedOutcome(Enum): + "Final outcome of one logical model dispatch after response acceptance processing" + # The provider response was accepted for continued agent processing. + SUCCESS = "success" + # The dispatch ended with a provider or transport error. + ERROR = "error" + # The dispatch was cancelled before an accepted response was produced. + CANCELLED = "cancelled" + # The provider response was rejected during post-response acceptance processing. + REJECTED = "rejected" + + class ModelChangeSource(Enum): "Origin of an effective session model change." # The user selected a model directly with `/model `. @@ -11259,7 +11410,7 @@ class WorkspaceFileChangedOperation(Enum): UPDATE = "update" -SessionEventData = SessionStartData | SessionResumeData | SessionRemoteSteerableChangedData | SessionErrorData | SessionIdleData | SessionTitleChangedData | SessionScheduleCreatedData | SessionScheduleCancelledData | SessionScheduleRearmedData | SessionAutopilotObjectiveChangedData | SessionInfoData | SessionWarningData | SessionModelChangeData | SessionModeChangedData | SessionSessionLimitsChangedData | SessionPermissionsChangedData | SessionPlanChangedData | SessionTodosChangedData | SessionWorkspaceFileChangedData | SessionHandoffData | SessionTruncationData | SessionSnapshotRewindData | SessionShutdownData | SessionUsageCheckpointData | SessionContextChangedData | SessionUsageInfoData | SessionContextClearedData | SessionCompactionStartData | SessionCompactionCompleteData | SessionTaskCompleteData | UserMessageData | PendingMessagesModifiedData | AssistantTurnStartData | AssistantTurnRetryData | AgentInterruptedData | AssistantIntentData | AssistantServerToolProgressData | AssistantReasoningData | AssistantReasoningDeltaData | AssistantToolCallDeltaData | AssistantStreamingDeltaData | AssistantMessageData | AssistantMessageStartData | AssistantMessageDeltaData | AssistantTurnEndData | AssistantIdleData | AssistantUsageData | PromptCacheBreakData | ModelCallFailureData | ModelCallStartData | AbortData | ToolUserRequestedData | ToolExecutionStartData | ToolExecutionPartialResultData | ToolExecutionProgressData | ToolExecutionCompleteData | ToolSearchActivatedData | SkillInvokedData | SandboxDecisionData | SubagentStartedData | SubagentCompletedData | SubagentFailedData | SubagentSelectedData | SubagentDeselectedData | HookStartData | HookEndData | HookProgressData | SessionBinaryAssetData | SystemMessageData | SystemNotificationData | PermissionRequestedData | PermissionCompletedData | UserInputRequestedData | UserInputCompletedData | ElicitationRequestedData | ElicitationCompletedData | SamplingRequestedData | SamplingCompletedData | McpOauthRequiredData | McpOauthCompletedData | McpHeadersRefreshRequiredData | McpHeadersRefreshCompletedData | SessionCustomNotificationData | UiEphemeralQueryData | ExternalToolRequestedData | ExternalToolCompletedData | CommandQueuedData | CommandExecuteData | CommandCompletedData | AutoModeSwitchRequestedData | AutoModeSwitchCompletedData | SessionLimitsExhaustedRequestedData | SessionLimitsExhaustedCompletedData | SessionAutoModeResolvedData | SessionManagedSettingsResolvedData | SessionManagedSettingsEnforcedData | CommandsChangedData | CapabilitiesChangedData | ExitPlanModeRequestedData | ExitPlanModeCompletedData | SessionToolsUpdatedData | SessionBackgroundTasksChangedData | FactoryRunUpdatedData | FactoryRunStartedData | FactoryRunSettledData | SessionSkillsLoadedData | SessionCustomAgentsUpdatedData | SessionMcpServersLoadedData | SessionMcpServerStatusChangedData | McpToolsListChangedData | McpResourcesListChangedData | McpPromptsListChangedData | SessionExtensionsLoadedData | SessionCanvasOpenedData | SessionCanvasRegistryChangedData | SessionCanvasClosedData | SessionCanvasUnavailableData | SessionCanvasRecordedData | SessionCanvasRemovedData | SessionExtensionsAttachmentsPushedData | McpAppToolCallCompleteData | RawSessionEventData | Data +SessionEventData = SessionStartData | SessionResumeData | SessionRemoteSteerableChangedData | SessionErrorData | SessionIdleData | SessionTitleChangedData | SessionScheduleCreatedData | SessionScheduleCancelledData | SessionScheduleRearmedData | SessionAutopilotObjectiveChangedData | SessionInfoData | SessionWarningData | SessionModelChangeData | SessionModeChangedData | SessionSessionLimitsChangedData | SessionPermissionsChangedData | SessionPlanChangedData | SessionTodosChangedData | SessionWorkspaceFileChangedData | SessionHandoffData | SessionTruncationData | SessionSnapshotRewindData | SessionShutdownData | SessionUsageCheckpointData | SessionContextChangedData | SessionUsageInfoData | SessionContextClearedData | SessionCompactionStartData | SessionCompactionCompleteData | SessionTaskCompleteData | UserMessageData | PendingMessagesModifiedData | AssistantTurnStartData | AssistantTurnRetryData | AgentInterruptedData | AssistantIntentData | AssistantServerToolProgressData | AssistantReasoningData | AssistantReasoningDeltaData | AssistantToolCallDeltaData | AssistantStreamingDeltaData | AssistantMessageData | AssistantMessageStartData | AssistantMessageDeltaData | AssistantTurnEndData | AssistantIdleData | AssistantUsageData | PromptCacheBreakData | ModelCallFailureData | ModelCallFinishedData | ModelCallStartData | AbortData | ToolUserRequestedData | ToolExecutionStartData | ToolExecutionPartialResultData | ToolExecutionProgressData | ToolExecutionCompleteData | ToolSearchActivatedData | SkillInvokedData | SandboxDecisionData | SubagentStartedData | SubagentCompletedData | SubagentFailedData | SubagentSelectedData | SubagentDeselectedData | HookStartData | HookEndData | HookProgressData | SessionBinaryAssetData | SystemMessageData | SystemNotificationData | PermissionRequestedData | PermissionCompletedData | UserInputRequestedData | UserInputCompletedData | ElicitationRequestedData | ElicitationCompletedData | SamplingRequestedData | SamplingCompletedData | McpOauthRequiredData | McpOauthCompletedData | McpHeadersRefreshRequiredData | McpHeadersRefreshCompletedData | SessionCustomNotificationData | UiEphemeralQueryData | ExternalToolRequestedData | ExternalToolCompletedData | CommandQueuedData | CommandExecuteData | CommandCompletedData | AutoModeSwitchRequestedData | AutoModeSwitchCompletedData | SessionLimitsExhaustedRequestedData | SessionLimitsExhaustedCompletedData | SessionAutoModeResolvedData | SessionManagedSettingsResolvedData | SessionManagedSettingsEnforcedData | CommandsChangedData | CapabilitiesChangedData | ExitPlanModeRequestedData | ExitPlanModeCompletedData | SessionToolsUpdatedData | SessionBackgroundTasksChangedData | FactoryRunUpdatedData | FactoryRunStartedData | FactoryRunSettledData | SessionSkillsLoadedData | SessionCustomAgentsUpdatedData | SessionMcpServersLoadedData | SessionMcpServerStatusChangedData | McpToolsListChangedData | McpResourcesListChangedData | McpPromptsListChangedData | SessionExtensionsLoadedData | SessionCanvasOpenedData | SessionCanvasRegistryChangedData | SessionCanvasClosedData | SessionCanvasUnavailableData | SessionCanvasRecordedData | SessionCanvasRemovedData | SessionExtensionsAttachmentsPushedData | McpAppToolCallCompleteData | RawSessionEventData | Data @dataclass @@ -11334,6 +11485,7 @@ def from_dict(obj: Any) -> "SessionEvent": case SessionEventType.ASSISTANT_USAGE: data = AssistantUsageData.from_dict(data_obj) case SessionEventType.PROMPT_CACHE_BREAK: data = PromptCacheBreakData.from_dict(data_obj) case SessionEventType.MODEL_CALL_FAILURE: data = ModelCallFailureData.from_dict(data_obj) + case SessionEventType.MODEL_CALL_FINISHED: data = ModelCallFinishedData.from_dict(data_obj) case SessionEventType.MODEL_CALL_START: data = ModelCallStartData.from_dict(data_obj) case SessionEventType.ABORT: data = AbortData.from_dict(data_obj) case SessionEventType.TOOL_USER_REQUESTED: data = ToolUserRequestedData.from_dict(data_obj) @@ -11449,6 +11601,7 @@ def session_event_to_dict(x: SessionEvent) -> Any: "AssistantIntentData", "AssistantMessageData", "AssistantMessageDeltaData", + "AssistantMessageReasoningBlocks", "AssistantMessageServerTools", "AssistantMessageStartData", "AssistantMessageToolRequest", @@ -11586,6 +11739,8 @@ def session_event_to_dict(x: SessionEvent) -> Any: "ModelCallFailureRequestFingerprint", "ModelCallFailureSource", "ModelCallFailureTransport", + "ModelCallFinishedData", + "ModelCallFinishedOutcome", "ModelCallStartData", "ModelChangeSource", "OmittedBinaryOmittedReason", diff --git a/python/copilot/session.py b/python/copilot/session.py index 30a555a389..21c74bcaf5 100644 --- a/python/copilot/session.py +++ b/python/copilot/session.py @@ -34,11 +34,11 @@ CanvasProviderOpenResult, ClientSessionApiHandlers, CommandsHandlePendingCommandRequest, + GitHubTokenAcquireResultKind, HandlePendingToolCallRequest, LogRequest, MCPOauthHandlePendingRequest, MCPOauthPendingRequestResponse, - MCPOauthPendingRequestResponseKind, ModelSwitchToRequest, PermissionDecision, PermissionDecisionApproveOnce, @@ -2279,14 +2279,14 @@ async def _execute_mcp_auth_and_respond( if result and result.get("kind", "token") == "token": rpc_result = MCPOauthPendingRequestResponse( - kind=MCPOauthPendingRequestResponseKind.TOKEN, + kind=GitHubTokenAcquireResultKind.TOKEN, access_token=result["accessToken"], expires_in=result.get("expiresIn"), token_type=result.get("tokenType"), ) else: rpc_result = MCPOauthPendingRequestResponse( - kind=MCPOauthPendingRequestResponseKind.CANCELLED + kind=GitHubTokenAcquireResultKind.CANCELLED ) await self.rpc.mcp.oauth.handle_pending_request( MCPOauthHandlePendingRequest( @@ -2300,7 +2300,7 @@ async def _execute_mcp_auth_and_respond( MCPOauthHandlePendingRequest( request_id=request_id, result=MCPOauthPendingRequestResponse( - kind=MCPOauthPendingRequestResponseKind.CANCELLED + kind=GitHubTokenAcquireResultKind.CANCELLED ), ) ) diff --git a/python/e2e/test_mcp_oauth_e2e.py b/python/e2e/test_mcp_oauth_e2e.py index 9d70597c3e..76f1cbf721 100644 --- a/python/e2e/test_mcp_oauth_e2e.py +++ b/python/e2e/test_mcp_oauth_e2e.py @@ -8,11 +8,11 @@ import pytest from copilot.generated.rpc import ( + GitHubTokenAcquireResultKind, MCPAppsCallToolRequest, MCPListToolsRequest, MCPOauthHandlePendingRequest, MCPOauthPendingRequestResponse, - MCPOauthPendingRequestResponseKind, ) from copilot.session import MCPServerConfig, PermissionHandler from copilot.session_events import McpServerStatus @@ -206,7 +206,7 @@ async def on_mcp_auth_request(request, _invocation): MCPOauthHandlePendingRequest( request_id=request["requestId"], result=MCPOauthPendingRequestResponse( - kind=MCPOauthPendingRequestResponseKind.TOKEN, + kind=GitHubTokenAcquireResultKind.TOKEN, access_token=EXPECTED_TOKEN, token_type="Bearer", expires_in=3600, diff --git a/python/test_client.py b/python/test_client.py index cf4bdf192b..a33f0ecd60 100644 --- a/python/test_client.py +++ b/python/test_client.py @@ -17,6 +17,7 @@ CanvasProviderIdentity, CapiSessionOptions, CopilotClient, + DisableBypassPermissionsModes, ExtensionInfo, ModelBillingTokenPrices, ModelBillingTokenPricesLongContext, @@ -721,7 +722,7 @@ async def mock_request(method, params, **kwargs): enable_managed_settings=True, managed_settings=ManagedSettings( permissions=ManagedSettingsPermissions( - disable_bypass_permissions_mode="disable", + disable_bypass_permissions_mode=DisableBypassPermissionsModes.ALLOW_AUTO_ONLY, deny=["Shell(git push)"], ask=["Domain(publish.example)"], allow=["Read(**)"], @@ -732,7 +733,10 @@ async def mock_request(method, params, **kwargs): session.session_id, on_permission_request=PermissionHandler.approve_all, managed_settings=ManagedSettings( - permissions=ManagedSettingsPermissions(ask=["Domain(publish.example)"]) + permissions=ManagedSettingsPermissions( + disable_bypass_permissions_mode="future-fail-closed-mode", + ask=["Domain(publish.example)"], + ) ), ) @@ -741,14 +745,31 @@ async def mock_request(method, params, **kwargs): assert captured["session.create"]["enableManagedSettings"] is True assert captured["session.create"]["managedSettings"] == { "permissions": { - "disableBypassPermissionsMode": "disable", + "disableBypassPermissionsMode": "allow-auto-only", "deny": ["Shell(git push)"], "ask": ["Domain(publish.example)"], "allow": ["Read(**)"], } } assert captured["session.resume"]["managedSettings"] == { - "permissions": {"ask": ["Domain(publish.example)"]} + "permissions": { + "disableBypassPermissionsMode": "future-fail-closed-mode", + "ask": ["Domain(publish.example)"], + } + } + + await client.create_session( + on_permission_request=PermissionHandler.approve_all, + managed_settings=ManagedSettings( + permissions=ManagedSettingsPermissions( + disable_bypass_permissions_mode=DisableBypassPermissionsModes.DISABLE, + ) + ), + ) + assert captured["session.create"]["managedSettings"] == { + "permissions": { + "disableBypassPermissionsMode": "disable", + } } finally: await client.force_stop() diff --git a/python/test_jsonrpc.py b/python/test_jsonrpc.py index 56ce44e374..2f2ecafce9 100644 --- a/python/test_jsonrpc.py +++ b/python/test_jsonrpc.py @@ -5,6 +5,7 @@ of large payloads and short reads from pipes. """ +import asyncio import io import json import os @@ -13,7 +14,7 @@ import pytest -from copilot._jsonrpc import JsonRpcClient +from copilot._jsonrpc import JsonRpcClient, ProcessExitedError class MockProcess: @@ -162,6 +163,29 @@ def test_read_exact_partial_data_raises_eof(self): client._read_exact(100) +@pytest.mark.asyncio +async def test_process_exit_waits_for_stderr_reader(): + process = MockProcess() + process.returncode = 1 + client = JsonRpcClient(process) + future = asyncio.get_running_loop().create_future() + client.pending_requests["request-id"] = future + + def capture_stderr(): + time.sleep(0.01) + with client._stderr_lock: + client._stderr_output.append("unsupported argument\n") + + client._stderr_thread = threading.Thread(target=capture_stderr) + client._stderr_thread.start() + + client._fail_pending_requests() + + await asyncio.sleep(0) + with pytest.raises(ProcessExitedError, match=r"stderr: unsupported argument"): + future.result() + + class TestReadMessageWithLargePayloads: """Tests for _read_message() with large JSON-RPC messages""" diff --git a/rust/src/generated/api_types.rs b/rust/src/generated/api_types.rs index 0583c09229..43ea119f8a 100644 --- a/rust/src/generated/api_types.rs +++ b/rust/src/generated/api_types.rs @@ -1205,12 +1205,37 @@ pub struct TokenAuthInfo { pub copilot_user: Option, /// Authentication host. pub host: String, + /// Opaque native GitHub credential registration backing this token identity, when applicable. + #[serde(skip_serializing_if = "Option::is_none")] + pub registration_id: Option, /// The token value itself. Treat as a secret. pub token: String, /// SDK-side token authentication; the host configured the token directly via the SDK. pub r#type: TokenAuthInfoType, } +/// Authentication-info variant backed by an SDK GitHub token callback. It carries routing metadata but never a plaintext token. +/// +///

+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct TokenProviderAuthInfo { + /// Snapshot of the authenticated user's Copilot subscription info, if known. + #[serde(skip_serializing_if = "Option::is_none")] + pub copilot_user: Option, + /// Authentication host. + pub host: String, + /// Opaque SDK callback registration identifier. + pub registration_id: String, + /// SDK callback-backed GitHub token authentication. + pub r#type: TokenProviderAuthInfoType, +} + /// Authentication-info variant for direct Copilot API token auth sourced from environment variables, with public GitHub host. /// ///
@@ -2435,6 +2460,9 @@ pub struct AuthIdentity { /// 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, } @@ -3822,6 +3850,31 @@ pub(crate) struct ConfigureSessionExtensionsParams { pub session_id: SessionId, } +/// Identity of the integrating host, declared once on the `server.connect` handshake so telemetry from this connection is attributed to a single, consistent surface. All fields are optional; omit them to keep the default attribution. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol 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 ConnectClientInfo { + /// Name of the host editor, e.g. `"vscode"`. + #[serde(skip_serializing_if = "Option::is_none")] + pub editor_name: Option, + /// Version of the host editor, e.g. `"1.124.2"`. Ignored unless it looks like a version string. + #[serde(skip_serializing_if = "Option::is_none")] + pub editor_version: Option, + /// Name of the Copilot extension within the host, e.g. `"copilot-chat"`. + #[serde(skip_serializing_if = "Option::is_none")] + pub extension_name: Option, + /// Version of the Copilot extension within the host, e.g. `"0.54.0"`. Ignored unless it looks like a version string. + #[serde(skip_serializing_if = "Option::is_none")] + pub extension_version: Option, +} + /// Repository associated with the connected remote session. /// ///
@@ -3908,6 +3961,10 @@ pub struct ConnectRemoteSessionParams { #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub(crate) struct ConnectRequest { + /// Identity of the integrating host. Optional; omit it to keep the default attribution. + #[doc(hidden)] + #[serde(skip_serializing_if = "Option::is_none")] + pub(crate) client_info: Option, /// Opt this connection in to GitHub telemetry forwarding for its lifetime. When set, the runtime forwards every internal telemetry event it emits — across all sessions, plus sessionless events — to this connection over the `gitHubTelemetry.event` notification. Regular events are also written to the runtime's normal GitHub/CTS path (dual-write); host-only compatibility events are forward-only and intentionally skip that path. Intended for first-party hosts that re-emit the events into their own telemetry stores. Both unrestricted and restricted events are forwarded, each tagged with a `restricted` discriminator; a backstop drops restricted events when restricted telemetry is disabled — using the process-global gate for ordinary events and an explicit session-scoped decision for host-only events. #[serde(skip_serializing_if = "Option::is_none")] pub enable_git_hub_telemetry_forwarding: Option, @@ -5861,6 +5918,49 @@ pub struct GitHubTelemetryNotification { pub session_id: Option, } +/// Asks the SDK client to acquire a GitHub access token from an opaque callback registration. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct GitHubTokenAcquireRequest { + /// Effective GitHub host for which the callback must return a token. + pub host: String, + /// Why the runtime is requesting a GitHub credential. + pub reason: GitHubTokenAcquireReason, + /// Opaque identifier generated by the SDK for this callback registration. + pub registration_id: String, + /// Session receiving the token. Absent only before a cloud session has been assigned its id. + #[serde(skip_serializing_if = "Option::is_none")] + pub session_id: Option, +} + +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct GitHubTokenAcquireResultToken { + /// GitHub access token acquired by the SDK host. + pub access_token: String, + /// Remaining token lifetime in seconds when callback execution completes. It must exceed the one-hour preflight refresh threshold. + pub expires_in: i64, + /// GitHub credential response variant discriminator. + pub kind: GitHubTokenAcquireResultTokenKind, + /// OAuth token type. Defaults to bearer when omitted. + #[serde(skip_serializing_if = "Option::is_none")] + pub token_type: Option, +} + +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct GitHubTokenAcquireResultCancelled { + /// GitHub credential response variant discriminator. + pub kind: GitHubTokenAcquireResultCancelledKind, +} + /// Pending external tool call request ID, with the tool result or an error describing why it failed. /// ///
@@ -6288,6 +6388,9 @@ pub struct InstalledPlugin { /// Installation timestamp #[serde(rename = "installed_at")] pub installed_at: String, + /// Absolute path of the marketplace directory a live plugin was resolved from. Present only on live, never-persisted records — those synthesized at session start for a directory/local marketplace, whose cache_path points at the real plugin directory on disk rather than a copy under the installed-plugins cache. Its presence is what marks a record as live, and no record carrying it is ever written to the persisted installedPlugins key. + #[serde(rename = "installed_from", skip_serializing_if = "Option::is_none")] + pub installed_from: Option, /// Marketplace the plugin came from (empty string for direct repo installs) pub marketplace: String, /// Plugin name @@ -6319,6 +6422,9 @@ pub struct InstalledPluginInfo { pub direct_source_id: Option, /// Whether the plugin is currently enabled for new sessions pub enabled: bool, + /// Absolute path of the marketplace directory a live plugin was resolved from. Present only on live, never-persisted records — a plugin belonging to a directory/local marketplace, which is loaded from its real directory on every pass instead of a copy under the installed-plugins cache. Its presence is what marks a listed plugin as live: such a plugin is always present on disk, so `enabled` is its only meaningful state and it is never "not installed". + #[serde(skip_serializing_if = "Option::is_none")] + pub installed_from: Option, /// Marketplace the plugin came from. Empty string ("") for direct repo / URL / local installs. pub marketplace: String, /// Plugin name @@ -7992,7 +8098,7 @@ pub struct McpOauthPendingRequestResponseToken { pub expires_in: Option, /// OAuth response variant discriminator. pub kind: McpOauthPendingRequestResponseTokenKind, - /// OAuth token type. Defaults to Bearer when omitted. + /// OAuth token type. Defaults to bearer when omitted. #[serde(skip_serializing_if = "Option::is_none")] pub token_type: Option, } @@ -9888,6 +9994,23 @@ pub struct ModelCapabilities { pub supports: Option, } +/// A service-published message about a model, carrying a stable machine-readable code alongside human-readable text. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ModelMessage { + /// Stable machine-readable identifier for the message, such as `client_version_deprecated`. Hosts can key custom presentation off this; unrecognized codes should fall back to displaying `message`. + pub code: String, + /// Human-readable message text intended for display to the user. + pub message: String, +} + /// Policy state (if applicable) /// ///
@@ -9906,6 +10029,22 @@ pub struct ModelPolicy { pub terms: Option, } +/// Service-published warning text that hosts should display when presenting a 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 ModelWarningText { + /// Data-retention warning for the model. The text may contain Markdown links and should be rendered as Markdown when supported. + #[serde(skip_serializing_if = "Option::is_none")] + pub data_retention: Option, +} + /// Copilot model metadata, including identifier, display name, capabilities, policy, billing, reasoning efforts, and picker categories. /// ///
@@ -9927,6 +10066,9 @@ pub struct Model { pub default_reasoning_effort: Option, /// Model identifier (e.g., "claude-sonnet-4.5") pub id: String, + /// Informational notices the service published for this model, such as an upcoming change or a recommended alternative. Present only when the service published at least one notice. Hosts should surface these without implying anything is wrong with the model. + #[serde(skip_serializing_if = "Option::is_none")] + pub info_messages: Option>, /// Model capability category for grouping in the model picker #[serde(skip_serializing_if = "Option::is_none")] pub model_picker_category: Option, @@ -9944,6 +10086,12 @@ pub struct Model { /// Supported reasoning effort levels (only present if model supports reasoning effort) #[serde(skip_serializing_if = "Option::is_none")] pub supported_reasoning_efforts: Option>, + /// Warnings the service published for this model, such as a deprecated client version. Present only when the service published at least one warning. The model remains usable; hosts should surface these as advisory rather than blocking. + #[serde(skip_serializing_if = "Option::is_none")] + pub warning_messages: Option>, + /// Warning text the service requires hosts to surface for this model. Present only when the service published at least one warning. + #[serde(skip_serializing_if = "Option::is_none")] + pub warning_text: Option, } /// Managed, repository, and CLI model overrides to overlay onto the session at startup. @@ -11580,7 +11728,7 @@ pub struct PermissionLocationResolveResult { #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct PermissionPathsAddParams { - /// Directory to add to the allow-list. The runtime resolves and validates the path before adding. + /// Directory to add to the allow-list. The runtime resolves and validates the path before adding, then loads conventional `.github/skills/` and `.github/agents/` definitions under it when their subsystem gates are enabled. Adding the directory is therefore also a trust decision for configuration stored there. pub path: String, } @@ -11625,7 +11773,7 @@ pub struct PermissionPathsAllowedCheckResult { #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct PermissionPathsConfig { - /// Additional directories to allow tool access to (in addition to the session's working directory). When `unrestricted` is true, these are still pre-populated on the UnrestrictedPathManager so they remain visible via getDirectories() (e.g. for @-mention completion). + /// Additional directories to allow tool access to (in addition to the session's working directory). Conventional `.github/skills/` and `.github/agents/` definitions under them also join the session catalogs when their subsystem gates are enabled, so supplying a directory is a trust decision for configuration stored there. When `unrestricted` is true, these are still pre-populated on the UnrestrictedPathManager so they remain visible via getDirectories() (e.g. for @-mention completion). #[serde(skip_serializing_if = "Option::is_none")] pub additional_directories: Option>, /// Whether to include the system temp directory in the allowed list (defaults to true). Ignored when `unrestricted` is true. @@ -13726,6 +13874,9 @@ pub struct QueuePendingItems { #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct QueuePendingItemsResult { + /// How many leading entries of `steeringMessages` have already been folded into the running turn (and so have an emitted `user.message`), as opposed to still waiting for one. Absent for hosts that do not distinguish the two. + #[serde(skip_serializing_if = "Option::is_none")] + pub in_flight_steering_count: Option, /// Pending queued items in submission order. Includes user messages, queued slash commands, and queued model changes; omits internal system items. pub items: Vec, /// Display text for messages currently in the immediate steering queue (interjections sent during a running turn). @@ -14500,7 +14651,7 @@ pub struct SandboxConfig { /// Whether to auto-add the current working directory to readwritePaths. Default: true. #[serde(skip_serializing_if = "Option::is_none")] pub add_current_working_directory: Option, - /// Whether to auto-grant read access to the tool directories discovered on PATH and in toolchain environment variables (GOROOT, CARGO_HOME, JAVA_HOME, VIRTUAL_ENV, and similar), and to common developer-tool caches, registries, and toolchains in their default home locations (cargo, go, npm, Maven, and more), plus read-write access to (and, on Unix, up-front creation of) the scratch caches builds write on every run (go-build, ccache, sccache, Gradle caches, Cargo lock/tracker files), so builds work without extra configuration; a relocated CARGO_HOME additionally gets its Cargo lock files granted read-write. Set to false to disable every grant listed above: user-installed toolchains (rustup, nvm, pyenv, conda, pipx) then need explicit userPolicy.filesystem entries — readonlyPaths to read them, plus readwriteFiles for a relocated CARGO_HOME's .package-cache and .global-cache, which Cargo locks on every build. Only these developer-tool grants are affected: the working directory (see addCurrentWorkingDirectory), temporary storage, session log paths, and system locations follow their own rules and stay granted, so commands still run. Default: true (enabled by default; set to false to opt out). + /// Whether to auto-grant read access to the tool directories discovered on PATH and in toolchain environment variables (GOROOT, CARGO_HOME, JAVA_HOME, VIRTUAL_ENV, and similar), and to common developer-tool caches, registries, and toolchains in their default home locations (cargo, go, npm, Maven, and more), plus read-write access to (and up-front creation of) the scratch caches builds write on every run (go-build, ccache, sccache, Gradle caches, Cargo lock/tracker files), so builds work without extra configuration; a relocated CARGO_HOME additionally gets its Cargo lock files granted read-write. Set to false to disable every grant listed above: user-installed toolchains (rustup, nvm, pyenv, conda, pipx) then need explicit userPolicy.filesystem entries — readonlyPaths to read them, plus readwriteFiles for a relocated CARGO_HOME's .package-cache and .global-cache, which Cargo locks on every build. Only these developer-tool grants are affected: the working directory (see addCurrentWorkingDirectory), temporary storage, session log paths, and system locations follow their own rules and stay granted, so commands still run. Default: true (enabled by default; set to false to opt out). #[serde(skip_serializing_if = "Option::is_none")] pub allow_dev_tool_access: Option, /// Credential-injection capability flags. @@ -15080,6 +15231,9 @@ pub struct SessionAuthInfoResult { /// 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, } @@ -15842,6 +15996,9 @@ pub struct SessionInstalledPlugin { /// Installation timestamp (ISO-8601) #[serde(rename = "installed_at")] pub installed_at: String, + /// Absolute path of the marketplace directory a live plugin was resolved from. Present only on live, never-persisted records — those synthesized at session start for a directory/local marketplace, whose cache_path points at the real plugin directory on disk rather than a copy under the installed-plugins cache. Its presence is what marks a record as live, and no record carrying it is ever written to the persisted installedPlugins key. + #[serde(rename = "installed_from", skip_serializing_if = "Option::is_none")] + pub installed_from: Option, /// Marketplace the plugin came from (empty string for direct repo installs) pub marketplace: String, /// Plugin name @@ -16106,9 +16263,9 @@ pub struct SessionManagedPermissions { /// Permission rules that block matching operations. Deny has highest precedence. #[serde(skip_serializing_if = "Option::is_none")] pub deny: Option>, - /// When set to `disable`, prevents bypass/allow-all permission modes. + /// When set to `disable`, prevents bypass/allow-all permission modes. `allow-auto-only` blocks full allow-all but permits advisory auto-approval. Any other value is accepted rather than failing the session, but is enforced as `disable`: the key is only present to restrict something, so a mode this runtime cannot interpret fails closed to the most restrictive one it knows. Omit the key entirely to impose no restriction. #[serde(skip_serializing_if = "Option::is_none")] - pub disable_bypass_permissions_mode: Option, + pub disable_bypass_permissions_mode: Option, } /// Managed settings an SDK host may inject at session startup. Only permissions are accepted in this initial contract. @@ -16429,7 +16586,7 @@ pub struct SessionOpenOptions { #[serde(skip_serializing_if = "Option::is_none")] pub additional_content_exclusion_policies: Option>, - /// Additional directories the agent may access beyond the working directory. Each entry is granted to the session's file-access allow-list and surfaced to the model (system prompt context and `@`-mention completion). Absolute paths are recommended; a relative path is resolved against the session's working directory. Nonexistent or unresolvable entries are skipped with a warning. This is applied on both session creation and resume, and is not persisted: a resumed session that omits this option does not retain previously supplied directories (re-supply them, exactly as the CLI re-passes `--add-dir`). + /// Additional directories the agent may access beyond the working directory. Each entry is granted to the session's file-access allow-list and surfaced to the model (system prompt context and `@`-mention completion). Conventional `.github/skills/` and `.github/agents/` definitions under each directory also join the session's project catalogs when their existing subsystem gates are enabled: added-root skills require both `enableConfigDiscovery` and effective `enableSkills`; added-root agents require `enableConfigDiscovery`. Supplying a directory therefore activates configuration from it and should be treated as a trust decision. Absolute paths are recommended; a relative path is resolved against the session's working directory. Nonexistent or unresolvable entries are skipped with a warning. This is applied during session creation and cold resume and is not persisted, so a cold resume must re-supply the directories. #[serde(skip_serializing_if = "Option::is_none")] pub additional_directories: Option>, /// Runtime context discriminator for agent filtering. @@ -16536,6 +16693,9 @@ pub struct SessionOpenOptions { /// Built-in subagent names to include in this session. When specified, only these built-ins are available, subject to runtime availability and exclusions. Custom agents with the same name remain available. #[serde(skip_serializing_if = "Option::is_none")] pub included_builtin_agents: Option>, + /// Built-in skill names to include in this session. When specified, only these runtime-bundled skills are available. Skills from other sources with the same name remain available. + #[serde(skip_serializing_if = "Option::is_none")] + pub included_builtin_skills: Option>, /// Installed plugins visible to the session. #[serde(skip_serializing_if = "Option::is_none")] pub installed_plugins: Option>, @@ -16613,6 +16773,10 @@ pub struct SessionOpenOptions { /// Resolved sandbox configuration. #[serde(skip_serializing_if = "Option::is_none")] pub sandbox_config: Option, + /// Origin of the sandbox choice. The runtime uses this only for internal telemetry provenance; managed policy is derived independently. + #[doc(hidden)] + #[serde(skip_serializing_if = "Option::is_none")] + pub(crate) sandbox_config_source: Option, /// Capabilities enabled for this session. #[serde(skip_serializing_if = "Option::is_none")] pub session_capabilities: Option>, @@ -17005,6 +17169,28 @@ pub struct SessionsEnrichMetadataRequest { pub sessions: Vec, } +/// Token authentication accepted by session.gitHubAuth.setCredentials. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SettableTokenAuthInfo { + /// Snapshot of the authenticated user's Copilot subscription info, if known. Mirrors the GitHub API `/copilot_internal/v2/token` user response shape — the runtime trusts this verbatim and does not re-fetch when set. + #[serde(skip_serializing_if = "Option::is_none")] + pub copilot_user: Option, + /// Authentication host. + pub host: String, + /// The token value itself. Treat as a secret. + pub token: String, + /// SDK-side token authentication; the host configured the token directly via the SDK. + pub r#type: SettableTokenAuthInfoType, +} + /// New auth credentials to install on the session. Omit to leave credentials unchanged. /// ///
@@ -17018,7 +17204,7 @@ pub struct SessionsEnrichMetadataRequest { pub struct SessionSetCredentialsParams { /// The new auth credentials to install on the session. When omitted or `undefined`, the call is a no-op and the session's existing credentials are preserved. The runtime installs the supplied value immediately for outbound model/API requests. When the credential carries a raw token (`token`, `env`, or `gh-cli`) but no `copilotUser`, the runtime additionally re-resolves `copilotUser` server-side (best-effort, asynchronously, after the synchronous install) so plan/quota/billing metadata regains fidelity; on resolution failure the verbatim credential remains installed. It does NOT otherwise validate the credential. Several variants carry secret material; treat this method's params as containing secrets at rest and in transit. #[serde(skip_serializing_if = "Option::is_none")] - pub credentials: Option, + pub credentials: Option, } /// Indicates whether the credential update succeeded. @@ -17952,6 +18138,9 @@ pub struct SessionUpdateOptionsParams { /// Built-in subagent names to include in this session. When specified, only these built-ins are available, subject to runtime availability and exclusions. Custom agents with the same name remain available. Set to null to remove the allowlist restriction. #[serde(skip_serializing_if = "Option::is_none")] pub included_builtin_agents: Option>, + /// Built-in skill names to include in this session. When specified, only these runtime-bundled skills are available. Skills from other sources with the same name remain available. Set to null to remove the allowlist restriction. + #[serde(skip_serializing_if = "Option::is_none")] + pub included_builtin_skills: Option>, /// Full set of installed plugins for the session. Replaces the existing list; the runtime invalidates the skills cache only when the list materially changes. #[serde(skip_serializing_if = "Option::is_none")] pub installed_plugins: Option>, @@ -17997,6 +18186,10 @@ pub struct SessionUpdateOptionsParams { /// Resolved sandbox configuration. #[serde(skip_serializing_if = "Option::is_none")] pub sandbox_config: Option, + /// Origin of the sandbox choice. The runtime uses this only for internal telemetry provenance; managed policy is derived independently. + #[doc(hidden)] + #[serde(skip_serializing_if = "Option::is_none")] + pub(crate) sandbox_config_source: Option, /// Replaces the session's capability set with the given list. Use to enable or disable capabilities mid-session (e.g., remove `memory` for reproducible scripted runs). Omit the field to leave the existing capability set unchanged. #[serde(skip_serializing_if = "Option::is_none")] pub session_capabilities: Option>, @@ -25891,6 +26084,9 @@ pub struct SessionQueuePendingItemsParams { #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct SessionQueuePendingItemsResult { + /// How many leading entries of `steeringMessages` have already been folded into the running turn (and so have an emitted `user.message`), as opposed to still waiting for one. Absent for hosts that do not distinguish the two. + #[serde(skip_serializing_if = "Option::is_none")] + pub in_flight_steering_count: Option, /// Pending queued items in submission order. Includes user messages, queued slash commands, and queued model changes; omits internal system items. pub items: Vec, /// Display text for messages currently in the immediate steering queue (interjections sent during a running turn). @@ -26853,6 +27049,14 @@ pub enum TokenAuthInfoType { Token, } +/// SDK callback-backed GitHub token authentication. +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum TokenProviderAuthInfoType { + #[serde(rename = "token-provider")] + #[default] + TokenProvider, +} + /// Authentication host (always the public GitHub host). #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] pub enum CopilotApiTokenAuthInfoHost { @@ -26907,6 +27111,7 @@ pub enum AuthInfo { Hmac(HMACAuthInfo), Env(EnvAuthInfo), Token(TokenAuthInfo), + TokenProvider(TokenProviderAuthInfo), CopilotApiToken(CopilotApiTokenAuthInfo), User(UserAuthInfo), GhCli(GhCliAuthInfo), @@ -27423,6 +27628,9 @@ pub enum AuthInfoType { /// 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, @@ -28461,23 +28669,6 @@ pub enum DebugCollectLogsResultKind { Unknown, } -/// -///
-/// -/// **Experimental.** This type is part of an experimental wire-protocol surface -/// and may change or be removed in future SDK or CLI releases. -/// -///
-#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -pub enum DisableBypassPermissionsMode { - #[serde(rename = "disable")] - Disable, - /// Unknown variant for forward compatibility. - #[default] - #[serde(other)] - Unknown, -} - /// Persisted extension discovery source /// ///
@@ -28941,6 +29132,52 @@ pub enum FactoryRunFailureKind { Unknown, } +/// Why the runtime is requesting a GitHub credential. +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum GitHubTokenAcquireReason { + /// The runtime is acquiring the registration's first credential. + #[serde(rename = "initial")] + Initial, + /// The runtime is replacing a credential that is approaching expiry. + #[serde(rename = "refresh")] + Refresh, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, +} + +/// GitHub credential response variant discriminator. +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum GitHubTokenAcquireResultTokenKind { + #[serde(rename = "token")] + #[default] + Token, +} + +/// GitHub credential response variant discriminator. +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum GitHubTokenAcquireResultCancelledKind { + #[serde(rename = "cancelled")] + #[default] + Cancelled, +} + +/// SDK host response to a GitHub credential 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, Serialize, Deserialize)] +#[serde(untagged)] +pub enum GitHubTokenAcquireResult { + Token(GitHubTokenAcquireResultToken), + Cancelled(GitHubTokenAcquireResultCancelled), +} + /// What initiated this compaction request, recorded as the `trigger` on the persisted `session.compaction_start` / `session.compaction_complete` events. When absent, the compaction is persisted without trigger attribution (initiator unknown). #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] pub enum HistoryCompactRequestTrigger { @@ -31743,6 +31980,43 @@ pub enum RemoteSessionMetadataTaskType { Unknown, } +/// Origin of the sandbox choice supplied by an internal client. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum SandboxConfigSource { + /// The client applied the default because no sandbox preference was configured. + #[serde(rename = "never_configured")] + NeverConfigured, + /// The user's persisted settings enabled the sandbox. + #[serde(rename = "user_enabled")] + UserEnabled, + /// The user's persisted settings disabled the sandbox. + #[serde(rename = "user_disabled")] + UserDisabled, + /// A command-line flag selected the sandbox state for this session. + #[serde(rename = "session_flag")] + SessionFlag, + /// The user disabled the sandbox for the current session. + #[serde(rename = "session_disabled")] + SessionDisabled, + /// The client disabled the sandbox because the host cannot enforce it. + #[serde(rename = "unsupported_host")] + UnsupportedHost, + /// A repository policy selected the sandbox state. + #[serde(rename = "repository_policy")] + RepositoryPolicy, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, +} + /// Session capability enabled for this session /// ///
@@ -32378,6 +32652,34 @@ pub enum SessionsOpenStatus { Unknown, } +/// SDK-side token authentication; the host configured the token directly via the SDK. +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum SettableTokenAuthInfoType { + #[serde(rename = "token")] + #[default] + Token, +} + +/// Authentication credentials accepted by session.gitHubAuth.setCredentials. Session-owned token-provider identities cannot be installed through this method. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum SettableAuthInfo { + Hmac(HMACAuthInfo), + Env(EnvAuthInfo), + Token(SettableTokenAuthInfo), + CopilotApiToken(CopilotApiTokenAuthInfo), + User(UserAuthInfo), + GhCli(GhCliAuthInfo), + ApiKey(ApiKeyAuthInfo), +} + /// Rust-owned settings predicates exposed across the SDK boundary. Raw feature-flag names are intentionally not part of the contract. /// ///
diff --git a/rust/src/generated/rpc.rs b/rust/src/generated/rpc.rs index c955637e31..60bf9d804f 100644 --- a/rust/src/generated/rpc.rs +++ b/rust/src/generated/rpc.rs @@ -8205,7 +8205,7 @@ impl<'a> SessionRpcPermissionsPaths<'a> { Ok(serde_json::from_value(_value)?) } - /// Adds a directory to the session's allow-list. + /// Adds a directory to the session's allow-list and activates conventional skill and agent definitions under it. /// /// Wire method: `session.permissions.paths.add`. /// diff --git a/rust/src/generated/session_events.rs b/rust/src/generated/session_events.rs index 1a7ca36cbd..f0508660b4 100644 --- a/rust/src/generated/session_events.rs +++ b/rust/src/generated/session_events.rs @@ -116,6 +116,8 @@ pub enum SessionEventType { PromptCacheBreak, #[serde(rename = "model.call_failure")] ModelCallFailure, + #[serde(rename = "model.call_finished")] + ModelCallFinished, #[serde(rename = "model.call_start")] ModelCallStart, #[serde(rename = "abort")] @@ -475,6 +477,8 @@ pub enum SessionEventData { PromptCacheBreak(PromptCacheBreakData), #[serde(rename = "model.call_failure")] ModelCallFailure(ModelCallFailureData), + #[serde(rename = "model.call_finished")] + ModelCallFinished(ModelCallFinishedData), #[serde(rename = "model.call_start")] ModelCallStart(ModelCallStartData), #[serde(rename = "abort")] @@ -1940,6 +1944,24 @@ pub struct Citations { pub spans: Vec, } +/// Neutral provider-tagged reasoning content blocks preserved verbatim for round-tripping +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct AssistantMessageReasoningBlocks { + /// Provider-native reasoning content blocks (e.g. Anthropic `thinking` / `redacted_thinking`) preserved verbatim, in order. A single response can carry several, each signed over the content preceding it, so dropping or reordering any of them invalidates the rest. + #[serde(skip_serializing_if = "Option::is_none")] + pub blocks: Option>, + /// Model provider that produced these reasoning blocks. + pub provider: String, +} + /// Neutral provider-tagged server-side tool-use payload (tool search, advisor) for verbatim round-tripping /// ///
@@ -2045,6 +2067,9 @@ pub struct AssistantMessageData { /// Generation phase for phased-output models (e.g., thinking vs. response phases) #[serde(skip_serializing_if = "Option::is_none")] pub phase: Option, + /// Neutral provider-tagged reasoning content blocks preserved verbatim for round-tripping. `reasoningText` and `reasoningOpaque` are a lossy derived view of these blocks, retained for display. + #[serde(skip_serializing_if = "Option::is_none")] + pub reasoning_blocks: Option, /// Opaque/encrypted extended thinking data from Anthropic models. Session-bound and stripped on resume. #[serde(skip_serializing_if = "Option::is_none")] pub reasoning_opaque: Option, @@ -2282,6 +2307,9 @@ pub struct AssistantUsageData { /// Number of output tokens produced #[serde(skip_serializing_if = "Option::is_none")] pub output_tokens: Option, + /// Time to first observable model output in milliseconds. Includes text, reasoning, and tool-call output; only available for streaming requests that produce observable output. + #[serde(skip_serializing_if = "Option::is_none")] + pub output_ttft_ms: Option, /// Parent tool call ID when this usage originates from a sub-agent #[doc(hidden)] #[deprecated] @@ -2513,6 +2541,26 @@ pub struct ModelCallFailureData { pub transport: Option, } +/// Session event "model.call_finished". Final lifecycle outcome for one logical model dispatch. A logical dispatch may include internal reconnect or fallback work, so event count is not provider HTTP-request count. +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ModelCallFinishedData { + /// Whether an accepted successful response requested the exact name and command semantics of a built-in file edit tool, including an external tool explicitly replacing that built-in name. Absent when the logical dispatch did not produce an accepted response. + #[serde(skip_serializing_if = "Option::is_none")] + pub contains_built_in_file_edit_request: Option, + /// Monotonic elapsed time spent in the logical model dispatch, including any internal transport reconnect or fallback and excluding orchestrator retry backoff, tool execution, confirmations, and post-response processing + pub dispatch_duration_ms: f64, + /// Version of the built-in file-edit semantic classifier used for this event + pub edit_classifier_version: i64, + /// Identifier of the user interaction that owns the model dispatch, matching assistant.turn_start.interactionId when available + #[serde(skip_serializing_if = "Option::is_none")] + pub interaction_id: Option, + /// Final outcome after post-response acceptance processing + pub outcome: ModelCallFinishedOutcome, + /// Agent-loop iteration within the interaction that initiated the model dispatch + pub turn_id: String, +} + /// Session event "model.call_start". Model API dispatch metadata for internal telemetry #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] @@ -3249,9 +3297,24 @@ pub struct SubagentCompletedData { /// Whether the sub-agent was torn down by cancellation - its own abort, or an ancestor being killed - instead of finishing its work. Cancellation is not a failure, so the run still reports completion; this distinguishes a torn-down sub-agent from one that ran to the end. #[serde(skip_serializing_if = "Option::is_none")] pub cancelled: Option, + /// Whether the first model actually dispatched matched the user's configured preference + #[serde(skip_serializing_if = "Option::is_none")] + pub configured_model_matches_actual: Option, + /// Concrete model the user configured for this sub-agent via `/subagents`, when present + #[serde(skip_serializing_if = "Option::is_none")] + pub configured_model_preference: Option, /// Wall-clock duration of the sub-agent execution in milliseconds #[serde(skip_serializing_if = "Option::is_none")] pub duration_ms: Option, + /// Whether the explicit task-call model matched the user's configured preference + #[serde(skip_serializing_if = "Option::is_none")] + pub explicit_model_matches_preference: Option, + /// Explicit model supplied by the parent agent on the task call, when present + #[serde(skip_serializing_if = "Option::is_none")] + pub explicit_model_override: Option, + /// First model for which the sub-agent started an inference request, when one was dispatched + #[serde(skip_serializing_if = "Option::is_none")] + pub first_dispatched_model: Option, /// Model used by the sub-agent #[serde(skip_serializing_if = "Option::is_none")] pub model: Option, @@ -3273,11 +3336,26 @@ pub struct SubagentFailedData { pub agent_display_name: String, /// Internal name of the sub-agent pub agent_name: String, + /// Whether the first model actually dispatched matched the user's configured preference + #[serde(skip_serializing_if = "Option::is_none")] + pub configured_model_matches_actual: Option, + /// Concrete model the user configured for this sub-agent via `/subagents`, when present + #[serde(skip_serializing_if = "Option::is_none")] + pub configured_model_preference: Option, /// Wall-clock duration of the sub-agent execution in milliseconds #[serde(skip_serializing_if = "Option::is_none")] pub duration_ms: Option, /// Error message describing why the sub-agent failed pub error: String, + /// Whether the explicit task-call model matched the user's configured preference + #[serde(skip_serializing_if = "Option::is_none")] + pub explicit_model_matches_preference: Option, + /// Explicit model supplied by the parent agent on the task call, when present + #[serde(skip_serializing_if = "Option::is_none")] + pub explicit_model_override: Option, + /// First model for which the sub-agent started an inference request, when one was dispatched + #[serde(skip_serializing_if = "Option::is_none")] + pub first_dispatched_model: Option, /// Model selected for the sub-agent, when known #[serde(skip_serializing_if = "Option::is_none")] pub model: Option, @@ -3960,6 +4038,9 @@ pub struct PermissionPromptRequestMcp { ///
#[serde(skip_serializing_if = "Option::is_none")] pub assisted_approval: Option, + /// Whether the host may offer a server-wide "approve all tools from this server" blanket. Absent is treated as true; the runtime sends false when managed policy disables bypass-permissions mode, which forbids the server-wide escalation while still allowing per-tool approval. + #[serde(skip_serializing_if = "Option::is_none")] + pub can_offer_server_wide_approval: Option, /// Prompt kind discriminator pub kind: PermissionPromptRequestMcpKind, /// Advisory runtime permission recommendation. The host remains responsible for deciding the request and may reject it. @@ -5001,6 +5082,9 @@ pub struct SessionManagedSettingsResolvedData { /// Whether at least two managed sources supplied permission allowlists, so enforcement intersects them and the flattened settings payload omits `permissions.allow`. #[serde(skip_serializing_if = "Option::is_none")] pub permissions_allow_intersected: Option, + /// Whether the effective sandbox policy forces the sandbox on *only* because managed policy could not be determined, rather than because the policy requires it. Lets clients tell a user whose `--no-sandbox` was overridden that the sandbox stayed on as a fail-closed fallback, instead of attributing it to an administrator who set no such policy. + #[serde(skip_serializing_if = "Option::is_none")] + pub sandbox_enabled_by_undetermined_policy: Option, /// Whether the server (account/org) managed-settings layer was present pub server_managed: bool, /// The effective (resolved) managed settings values, so clients can render exactly what is enforced. Absent when no managed policy is in force. @@ -6126,6 +6210,27 @@ pub enum ModelCallFailureSource { Unknown, } +/// Final outcome of one logical model dispatch after response acceptance processing +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum ModelCallFinishedOutcome { + /// The provider response was accepted for continued agent processing. + #[serde(rename = "success")] + Success, + /// The dispatch ended with a provider or transport error. + #[serde(rename = "error")] + Error, + /// The dispatch was cancelled before an accepted response was produced. + #[serde(rename = "cancelled")] + Cancelled, + /// The provider response was rejected during post-response acceptance processing. + #[serde(rename = "rejected")] + Rejected, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, +} + /// Finite reason code describing why the current turn was aborted #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] pub enum AbortReason { @@ -7234,6 +7339,9 @@ pub enum ManagedSettingsEnforcedEscalation { /// Unrestricted URL fetch access. #[serde(rename = "unrestricted_urls")] UnrestrictedUrls, + /// A server-wide MCP "Always Allow" (or `--allow-tool `) blanket that would auto-approve every tool from an MCP server. Capped to per-tool approval; each tool still prompts. + #[serde(rename = "server_wide_mcp_approval")] + ServerWideMcpApproval, /// Unknown variant for forward compatibility. #[default] #[serde(other)] diff --git a/rust/src/lib.rs b/rust/src/lib.rs index 5c06744698..bf50adfd48 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -2163,6 +2163,7 @@ impl Client { .on_github_telemetry .is_some() .then_some(true), + ..Default::default() }; let value = self .call( diff --git a/rust/src/types.rs b/rust/src/types.rs index afcb4d515b..06c0fc4e79 100644 --- a/rust/src/types.rs +++ b/rust/src/types.rs @@ -1760,13 +1760,14 @@ pub struct CopilotExpAssignmentResponse { pub assignment_context: String, } -/// Controls whether bypass-permissions mode is available in a managed session. -#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "lowercase")] -#[non_exhaustive] -pub enum DisableBypassPermissionsMode { - /// Turn off bypass-permissions mode. - Disable, +/// Well-known managed bypass-permissions policies. +pub struct DisableBypassPermissionsModes; + +impl DisableBypassPermissionsModes { + /// Permit automatic bypass but block full allow-all. + pub const ALLOW_AUTO_ONLY: &'static str = "allow-auto-only"; + /// Turn off bypass-permissions mode entirely. + pub const DISABLE: &'static str = "disable"; } /// Permission rules injected as a managed-settings layer at session bootstrap. @@ -1775,18 +1776,17 @@ pub enum DisableBypassPermissionsMode { /// layer. This layer composes restrictively with any server- or device-level /// managed settings: [`deny`](Self::deny) and [`ask`](Self::ask) rules are /// unioned across layers, every present [`allow`](Self::allow) list must admit a -/// tool for it to be allowed, and -/// [`disable_bypass_permissions_mode`](Self::disable_bypass_permissions_mode) is -/// honored if any layer sets it (deny-wins). +/// tool for it to be allowed, and bypass-mode restrictions compose to the most +/// restrictive setting. #[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] #[non_exhaustive] pub struct ManagedSettingsPermissions { - /// When set to `"disable"`, bypass-permissions mode is turned off for the - /// session regardless of other layers. Serialized as - /// `disableBypassPermissionsMode`. + /// Restricts bypass-permissions mode for the session. See + /// [`DisableBypassPermissionsModes`] for well-known values. Unknown values + /// are forwarded so newer runtime policies fail closed. #[serde(default, skip_serializing_if = "Option::is_none")] - pub disable_bypass_permissions_mode: Option, + pub disable_bypass_permissions_mode: Option, /// Tool-permission patterns that are always denied. #[serde(default, skip_serializing_if = "Option::is_none")] pub deny: Option>, @@ -1800,11 +1800,8 @@ pub struct ManagedSettingsPermissions { impl ManagedSettingsPermissions { /// Sets the bypass-permissions policy for this managed layer. - pub fn with_disable_bypass_permissions_mode( - mut self, - value: DisableBypassPermissionsMode, - ) -> Self { - self.disable_bypass_permissions_mode = Some(value); + pub fn with_disable_bypass_permissions_mode(mut self, value: impl Into) -> Self { + self.disable_bypass_permissions_mode = Some(value.into()); self } diff --git a/rust/tests/e2e/rpc_session_state.rs b/rust/tests/e2e/rpc_session_state.rs index 15db6e3a9a..aa67312473 100644 --- a/rust/tests/e2e/rpc_session_state.rs +++ b/rust/tests/e2e/rpc_session_state.rs @@ -1,14 +1,13 @@ use std::collections::HashMap; use github_copilot_sdk::rpc::{ - AuthInfo, AuthInfoType, HistoryTruncateRequest, LspInitializeRequest, - MetadataContextInfoRequest, MetadataRecomputeContextTokensRequest, - MetadataRecordContextChangeRequest, MetadataSetWorkingDirectoryRequest, - MetadataSnapshotCurrentMode, ModeSetRequest, ModelSetReasoningEffortRequest, - ModelSwitchToRequest, NameSetAutoRequest, NameSetRequest, + AuthInfoType, HistoryTruncateRequest, LspInitializeRequest, MetadataContextInfoRequest, + MetadataRecomputeContextTokensRequest, MetadataRecordContextChangeRequest, + MetadataSetWorkingDirectoryRequest, MetadataSnapshotCurrentMode, ModeSetRequest, + ModelSetReasoningEffortRequest, ModelSwitchToRequest, NameSetAutoRequest, NameSetRequest, PermissionsResetSessionApprovalsRequest, PermissionsSetApproveAllRequest, PlanUpdateRequest, SessionSetCredentialsParams, SessionUpdateOptionsParams, SessionWorkingDirectoryContext, - SessionWorkingDirectoryContextHostType, SessionsForkRequest, ShutdownRequest, + SessionWorkingDirectoryContextHostType, SessionsForkRequest, SettableAuthInfo, ShutdownRequest, TelemetrySetFeatureOverridesRequest, UserAuthInfo, WorkspacesCreateFileRequest, WorkspacesReadFileRequest, }; @@ -762,18 +761,18 @@ async fn should_update_options_and_initialize_session_services() { .await .expect("create session"); + let mut update_options = SessionUpdateOptionsParams::default(); + update_options.ask_user_disabled = Some(true); + update_options.available_tools = Some(vec!["view".to_string()]); + update_options.client_name = Some("rust-rpc-e2e".to_string()); + update_options.enable_streaming = Some(true); + update_options.model = Some(MODEL_ID.to_string()); + update_options.working_directory = Some(ctx.work_dir().display().to_string()); + let options = session .rpc() .options() - .update(SessionUpdateOptionsParams { - ask_user_disabled: Some(true), - available_tools: Some(vec!["view".to_string()]), - client_name: Some("rust-rpc-e2e".to_string()), - enable_streaming: Some(true), - model: Some(MODEL_ID.to_string()), - working_directory: Some(ctx.work_dir().display().to_string()), - ..SessionUpdateOptionsParams::default() - }) + .update(update_options) .await .expect("update options"); assert!(options.success); @@ -893,7 +892,7 @@ async fn should_set_auth_credentials() { .rpc() .git_hub_auth() .set_credentials(SessionSetCredentialsParams { - credentials: Some(AuthInfo::User(UserAuthInfo { + credentials: Some(SettableAuthInfo::User(UserAuthInfo { host: "github.com".to_string(), login: "rpc-session-user".to_string(), ..Default::default() diff --git a/rust/tests/protocol_version_test.rs b/rust/tests/protocol_version_test.rs index 9d613d8d76..0d1268c59e 100644 --- a/rust/tests/protocol_version_test.rs +++ b/rust/tests/protocol_version_test.rs @@ -127,6 +127,7 @@ async fn connect_handshake_supplies_protocol_version() { assert_eq!(req["method"], "connect"); // Token is None for the from_streams entry point (no transport spawn). assert!(req["params"].get("token").is_none()); + assert!(req["params"].get("clientInfo").is_none()); let response = serde_json::json!({ "jsonrpc": "2.0", "id": req["id"], diff --git a/rust/tests/session_test.rs b/rust/tests/session_test.rs index 69a65a558a..e20d9d0885 100644 --- a/rust/tests/session_test.rs +++ b/rust/tests/session_test.rs @@ -22,7 +22,7 @@ use github_copilot_sdk::session_events::{ }; use github_copilot_sdk::types::{ CanvasProviderIdentity, CloudSessionOptions, CloudSessionRepository, CommandContext, - CommandDefinition, CommandHandler, DeliveryMode, DisableBypassPermissionsMode, + CommandDefinition, CommandHandler, DeliveryMode, DisableBypassPermissionsModes, ElicitationRequest, ElicitationResult, ExitPlanModeData, ExtensionInfo, ManagedSettings, ManagedSettingsPermissions, MessageOptions, PermissionDecisionContext, PermissionDecisionOutcome, PermissionDecisionSource, PermissionDecisionSurface, RequestId, @@ -788,6 +788,30 @@ async fn create_session_sends_canvas_wire_fields() { timeout(TIMEOUT, create_handle).await.unwrap().unwrap(); } +#[test] +fn managed_bypass_permissions_modes_use_wire_values() { + let disabled = ManagedSettingsPermissions::default() + .with_disable_bypass_permissions_mode(DisableBypassPermissionsModes::DISABLE); + assert_eq!( + serde_json::to_value(disabled).unwrap()["disableBypassPermissionsMode"], + "disable" + ); + + let known = ManagedSettingsPermissions::default() + .with_disable_bypass_permissions_mode(DisableBypassPermissionsModes::ALLOW_AUTO_ONLY); + assert_eq!( + serde_json::to_value(known).unwrap()["disableBypassPermissionsMode"], + "allow-auto-only" + ); + + let future = ManagedSettingsPermissions::default() + .with_disable_bypass_permissions_mode("future-fail-closed-mode"); + assert_eq!( + serde_json::to_value(future).unwrap()["disableBypassPermissionsMode"], + "future-fail-closed-mode" + ); +} + #[tokio::test] async fn create_and_resume_send_managed_settings_permissions() { use github_copilot_sdk::types::ResumeSessionConfig; @@ -796,7 +820,7 @@ async fn create_and_resume_send_managed_settings_permissions() { let managed = ManagedSettings::default().with_permissions( ManagedSettingsPermissions::default() - .with_disable_bypass_permissions_mode(DisableBypassPermissionsMode::Disable) + .with_disable_bypass_permissions_mode(DisableBypassPermissionsModes::ALLOW_AUTO_ONLY) .with_deny(vec!["shell(rm*)".to_string()]) .with_ask(vec!["write".to_string()]) .with_allow(vec![]), @@ -821,7 +845,7 @@ async fn create_and_resume_send_managed_settings_permissions() { assert_eq!(request["method"], "session.create"); assert_eq!(request["params"]["enableManagedSettings"], true); let perms = &request["params"]["managedSettings"]["permissions"]; - assert_eq!(perms["disableBypassPermissionsMode"], "disable"); + assert_eq!(perms["disableBypassPermissionsMode"], "allow-auto-only"); assert_eq!(perms["deny"][0], "shell(rm*)"); assert_eq!(perms["ask"][0], "write"); assert_eq!(perms["allow"], serde_json::json!([])); diff --git a/scripts/codegen/rust.ts b/scripts/codegen/rust.ts index b5235ce1ea..0feec5e98a 100644 --- a/scripts/codegen/rust.ts +++ b/scripts/codegen/rust.ts @@ -1462,7 +1462,7 @@ function generateApiTypesCode( ); const ctx = makeCtx(defCollections, { nonDefaultableTypes, - allowedUnionTypeNames: ["AuthInfo", "McpOauthProbeResult", "ToolResult"], + allowedUnionTypeNames: ["AuthInfo", "McpOauthProbeResult", "SettableAuthInfo", "ToolResult"], }); // Collect all RPC methods before emitting shared definitions so method stability diff --git a/test/harness/package-lock.json b/test/harness/package-lock.json index 2c0a117602..d2340d6c94 100644 --- a/test/harness/package-lock.json +++ b/test/harness/package-lock.json @@ -9,7 +9,7 @@ "version": "1.0.0", "license": "ISC", "devDependencies": { - "@github/copilot": "^1.0.81-6", + "@github/copilot": "^1.0.81-10", "@modelcontextprotocol/sdk": "^1.26.0", "@types/node": "^25.3.3", "@types/node-forge": "^1.3.14", @@ -472,8 +472,8 @@ } }, "node_modules/@github/copilot": { - "version": "1.0.81-6", - "integrity": "sha512-hT29nRkf0EJE3N6lqeLOPszbdEyALZ+fjYG9zKX5a3L5r+o+m4/KF+8l2gn2yORNqOzwUYNj2vnVzKqeYYNLGg==", + "version": "1.0.81-10", + "integrity": "sha512-Ac99EvN16s4hKRhJLSEn1HMNaZ6MD8BzIey1zzJNBQy1/yP4PQDZ2CWitEq+XQQEi+6SsqeJRqXOKiWk1EyK7g==", "dev": true, "license": "SEE LICENSE IN LICENSE.md", "dependencies": { @@ -483,19 +483,19 @@ "copilot": "npm-loader.js" }, "optionalDependencies": { - "@github/copilot-darwin-arm64": "1.0.81-6", - "@github/copilot-darwin-x64": "1.0.81-6", - "@github/copilot-linux-arm64": "1.0.81-6", - "@github/copilot-linux-x64": "1.0.81-6", - "@github/copilot-linuxmusl-arm64": "1.0.81-6", - "@github/copilot-linuxmusl-x64": "1.0.81-6", - "@github/copilot-win32-arm64": "1.0.81-6", - "@github/copilot-win32-x64": "1.0.81-6" + "@github/copilot-darwin-arm64": "1.0.81-10", + "@github/copilot-darwin-x64": "1.0.81-10", + "@github/copilot-linux-arm64": "1.0.81-10", + "@github/copilot-linux-x64": "1.0.81-10", + "@github/copilot-linuxmusl-arm64": "1.0.81-10", + "@github/copilot-linuxmusl-x64": "1.0.81-10", + "@github/copilot-win32-arm64": "1.0.81-10", + "@github/copilot-win32-x64": "1.0.81-10" } }, "node_modules/@github/copilot-darwin-arm64": { - "version": "1.0.81-6", - "integrity": "sha512-nALa4e8Jc/g5ltIHrpHBHByJ5rlgzoZFylZIrkQY+B9vr3L57d5F6fOiTbf/OF9blFQX7artWRE1K0TmowGNCA==", + "version": "1.0.81-10", + "integrity": "sha512-s90Av0iwjTSU6Gky8T9wI1PJdlfbdUcPAVgKDtimaOiAwcdLG4fKTpGxrk96KJrnOHHK3x9SiXsw/pW0ThAH/A==", "cpu": [ "arm64" ], @@ -510,8 +510,8 @@ } }, "node_modules/@github/copilot-darwin-x64": { - "version": "1.0.81-6", - "integrity": "sha512-K+bp799DejrsmxMNyaFAmKo4xnLJXBb8hkv9N8OCQukmTSoRpfqhv2oTDfgVFadwllt+py/FIdxKTZQYvPGGGw==", + "version": "1.0.81-10", + "integrity": "sha512-8RnPI4J311oJQ0GPB6JxuLJq4JNY/KF9ZIIQm8KpxXBY6d+6fmmAsMDEk7OiF/Asl2I7+LTi+qU2ZVhP7FYhbg==", "cpu": [ "x64" ], @@ -526,8 +526,8 @@ } }, "node_modules/@github/copilot-linux-arm64": { - "version": "1.0.81-6", - "integrity": "sha512-aEpnfTTjOxpesFo9jqk/phZUivOhNHbdBRfBrS2NiCPrQZFBYUC4wRVo/Xo2PMMQ4J07b6fU7JJQPoUUkKy5Wg==", + "version": "1.0.81-10", + "integrity": "sha512-2UtK5CBrE6ZVSIzU2KHeIgO8N7056axjbF2lE6WuK+H+oJJ4v3w5eQkalqGzRHhkaPfCW4kT1lDMhZFW+XbLjA==", "cpu": [ "arm64" ], @@ -542,8 +542,8 @@ } }, "node_modules/@github/copilot-linux-x64": { - "version": "1.0.81-6", - "integrity": "sha512-NFqonFfJCyA7d3bNoYeLWUQ69zelPr9TTnLpAHCi3scFZqbEvMBDFxW2XsKWwfYuuR9XzfU7/tgOUgq3gmL5aA==", + "version": "1.0.81-10", + "integrity": "sha512-61+KAfo1TBARrBfss3w4dfmRVSf0PiFg0c9JNuT9HjoNnytl7maJBPEgUvI4YBcxScNEAlCMXaUUG3Tuuh1g+w==", "cpu": [ "x64" ], @@ -558,8 +558,8 @@ } }, "node_modules/@github/copilot-linuxmusl-arm64": { - "version": "1.0.81-6", - "integrity": "sha512-EE99DFTAgTq6eOFDiiv+OUROD2pDQIrzyyJEfUS9K8JanwNc+Py8vTxrJ0yK0slpJ+Fue5uDRno6c9ys8OM59g==", + "version": "1.0.81-10", + "integrity": "sha512-CR6KRPCFoGkaD8I2an1FyrT5avF1U5aTbwW2sYCP7w1KExYFknxEL8ES6BkFuPEA7YcjmLa0SOq26Z+TgIVHSg==", "cpu": [ "arm64" ], @@ -574,8 +574,8 @@ } }, "node_modules/@github/copilot-linuxmusl-x64": { - "version": "1.0.81-6", - "integrity": "sha512-90HRKx25EjhlQNOCCdbiC0Ck0fSKyp8XUxUoCvuNdoigdUP54ZGS07dkyJiJq9KZaKilxZBSpXiNt8t5ETA2Sg==", + "version": "1.0.81-10", + "integrity": "sha512-fvZfyEOfRkvUDPXY6UUjAqV8Mkf08PQV+jgtiAFUryuas5VP9cYaAmQSmNpzNMNi3kSX/ycUJe7oc3zXZ8ylog==", "cpu": [ "x64" ], @@ -590,8 +590,8 @@ } }, "node_modules/@github/copilot-win32-arm64": { - "version": "1.0.81-6", - "integrity": "sha512-1dRSHF/7PFzB+AGORg8BJh2n1+N7+sIJDLqozrC9INWFp1t6ercptIXgJTF/V7UZVGQLO4LBBK1HH/QhzUmrfA==", + "version": "1.0.81-10", + "integrity": "sha512-n30PPBgCT4Iq9MgH6is6L3eUEE+sF6xB2fb+dGsclj5j/hCkT7+ef0j8YcAGipsvGfzGAuywIsWlvF7fzYsOKQ==", "cpu": [ "arm64" ], @@ -606,8 +606,8 @@ } }, "node_modules/@github/copilot-win32-x64": { - "version": "1.0.81-6", - "integrity": "sha512-lIbN1mk6Rm9bWrWU4/UfrC5OCga7XcBi2LBz5roDnNcuG8mKEevDcNOtbEYz/TaJg+WBMoTbGfXBVd1hGy2DTA==", + "version": "1.0.81-10", + "integrity": "sha512-lb8kvhrXwGCN3LeRDQfLHsUp+F43XvPYznaYK1sPtK1kFGa4/kL690tasoSEvzu8ZKoTY6kZ6YmDbUZgqOislw==", "cpu": [ "x64" ], @@ -1751,7 +1751,6 @@ }, "node_modules/hono": { "version": "4.13.1", - "resolved": "https://registry.npmjs.org/hono/-/hono-4.13.1.tgz", "integrity": "sha512-kdJoFVv2xmayw6cY09H7AbMJMt8Jn5jdlEdXsP7AGBdF2DIptVlKlOLKXP41yPip4/a3yQPv9gVcJYI8YY04dw==", "dev": true, "license": "MIT", diff --git a/test/harness/package.json b/test/harness/package.json index 23b30b9aac..e968848315 100644 --- a/test/harness/package.json +++ b/test/harness/package.json @@ -14,7 +14,7 @@ "node": "^20.19.0 || >=22.12.0" }, "devDependencies": { - "@github/copilot": "^1.0.81-6", + "@github/copilot": "^1.0.81-10", "@modelcontextprotocol/sdk": "^1.26.0", "@types/node": "^25.3.3", "@types/node-forge": "^1.3.14", diff --git a/test/snapshots/subagent_hooks/should_invoke_pretooluse_and_posttooluse_hooks_for_sub_agent_tool_calls.yaml b/test/snapshots/subagent_hooks/should_invoke_pretooluse_and_posttooluse_hooks_for_sub_agent_tool_calls.yaml index 2a73f1ef84..c905726ee3 100644 --- a/test/snapshots/subagent_hooks/should_invoke_pretooluse_and_posttooluse_hooks_for_sub_agent_tool_calls.yaml +++ b/test/snapshots/subagent_hooks/should_invoke_pretooluse_and_posttooluse_hooks_for_sub_agent_tool_calls.yaml @@ -25,38 +25,6 @@ conversations: arguments: '{"agent_type":"explore","name":"read-file","description":"Reading subagent-test.txt","prompt":"Read the file \"subagent-test.txt\" in the current directory (${workdir}) and report its complete contents. Use the view tool to read the file and provide the full content in your response.","mode":"background"}' - - messages: - - role: system - content: ${system} - - role: user - content: Use the task tool to spawn an explore agent that reads the file subagent-test.txt in the current directory and - reports its contents. You must use the task tool. - - role: assistant - content: I'll spawn an explore agent to read the file and report its contents. - tool_calls: - - id: toolcall_0 - type: function - function: - name: report_intent - arguments: '{"intent":"Spawning explore agent"}' - - id: toolcall_1 - type: function - function: - name: task - arguments: '{"agent_type":"explore","name":"read-file","description":"Reading subagent-test.txt","prompt":"Read the file - \"subagent-test.txt\" in the current directory (${workdir}) and report its complete contents. Use the - view tool to read the file and provide the full content in your response.","mode":"background"}' - - role: tool - tool_call_id: toolcall_0 - content: Tool 'report_intent' does not exist. Available tools that can be called are ${shell}, ${read_shell}, - ${stop_shell}, ${list_shell}, view, create, edit, web_fetch, skill, sql, read_agent, list_agents, grep, glob, - task. - - role: tool - tool_call_id: toolcall_1 - content: "Agent started in background with agent_id: read-file. You'll be notified when it completes. Tell the user - you're waiting and end your response, or continue unrelated work until notified." - - role: assistant - content: I've launched an explore agent to read subagent-test.txt. Waiting for it to complete... - messages: - role: system content: ${system} From 017c9a3ba1c097dad39200a8409da7ca59b53293 Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Wed, 26 Aug 2026 14:46:40 +0000 Subject: [PATCH 16/32] Default ClientMode::Empty to no built-in skills (#2410) * Add ClientMode::Empty built-in-skill isolation across SDK bindings Forward the new runtime `includedBuiltinSkills` session option in all six bindings (Node/TS, Python, Go, .NET, Java, Rust) and, in the fail-closed ClientMode::Empty post-create/post-resume `session.options.update` patch, unconditionally set `includedBuiltinSkills: []` adjacent to the existing `installedPlugins: []`. This excludes every runtime-bundled built-in skill in Empty mode on both create and resume while leaving custom/project/plugin/ personal/remote skills eligible; callers can still opt into their own custom skills (enableSkills + skillDirectories) but cannot weaken the Empty post-patch. CopilotCli mode is unchanged (field omitted unless the caller sets the general session option). Fail-closed cleanup on a failed Empty patch is preserved. Adds focused create/resume tests (including caller enableSkills=true and a non-Empty/CopilotCli negative) in every binding, and documents the behavior in the multi-tenancy and skills guides. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: f48912d3-420b-486f-84c3-211311aa502b * Fix Python Empty skill policy update Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: f48912d3-420b-486f-84c3-211311aa502b * Fix Java Empty skill patch after rebase Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 614bbfdb-0dc5-4956-8db6-b2191449b2a5 * Address Empty mode documentation review Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 614bbfdb-0dc5-4956-8db6-b2191449b2a5 * Allow explicit built-in skills in Empty mode Default Empty sessions to no runtime-bundled skills while preserving a caller-supplied allowlist across create and resume in every SDK binding. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 614bbfdb-0dc5-4956-8db6-b2191449b2a5 * Fix Java mode patch Javadoc Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 614bbfdb-0dc5-4956-8db6-b2191449b2a5 --------- Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: f48912d3-420b-486f-84c3-211311aa502b Copilot-Session: 614bbfdb-0dc5-4956-8db6-b2191449b2a5 --- docs/features/skills.md | 16 ++ docs/setup/multi-tenancy.md | 3 + dotnet/src/Client.cs | 9 +- dotnet/src/Types.cs | 9 + .../test/Unit/ClientSessionLifetimeTests.cs | 144 ++++++++++++++ go/client.go | 2 + go/client_test.go | 176 +++++++++++++++++- go/mode_empty.go | 13 +- go/types.go | 8 + .../com/github/copilot/CopilotClient.java | 33 +++- .../copilot/rpc/ResumeSessionConfig.java | 26 +++ .../com/github/copilot/rpc/SessionConfig.java | 26 +++ .../UpdateSessionOptionsForModeTest.java | 28 ++- nodejs/src/client.ts | 6 +- nodejs/src/types.ts | 7 + nodejs/test/client.test.ts | 128 +++++++++++++ python/copilot/_mode.py | 8 + python/copilot/client.py | 9 + python/test_tool_set.py | 96 +++++++++- rust/src/session.rs | 134 ++++++++++++- rust/src/types.rs | 32 ++++ 21 files changed, 891 insertions(+), 22 deletions(-) diff --git a/docs/features/skills.md b/docs/features/skills.md index 5b8388162a..d76196429e 100644 --- a/docs/features/skills.md +++ b/docs/features/skills.md @@ -351,6 +351,22 @@ The markdown body contains the instructions that are injected into the session c | .NET | `SkillDirectories` | `List` | Directories to load skills from | | .NET | `DisabledSkills` | `List` | Skills to disable | +### Built-in skills and `mode: "empty"` + +The runtime ships with a set of bundled **built-in** skills that are eligible by +default. When you run the client in `mode: "empty"` (the recommended baseline for +[multi-tenant servers](../setup/multi-tenancy.md)), the SDK excludes every +runtime-bundled built-in skill: it sends an empty `includedBuiltinSkills` list on +the post-create and post-resume options patch, alongside the empty +`installedPlugins` list. + +This exclusion is the default, not a permanent restriction. To allow selected +runtime-bundled skills, set `includedBuiltinSkills` (or the language-specific +casing) to their names. You can also opt into your **own** custom skills under +`mode: "empty"`—enable skills and pass your own `skillDirectories`—and those +remain fully usable, including a custom skill that shares a name with a built-in. +Under `mode: "copilot-cli"` the field is omitted unless you set the option. + ## Best practices 1. **Organize by domain** - Group related skills together (e.g., `skills/security/`, `skills/testing/`) diff --git a/docs/setup/multi-tenancy.md b/docs/setup/multi-tenancy.md index 2f82dde0bf..3bc9b6c748 100644 --- a/docs/setup/multi-tenancy.md +++ b/docs/setup/multi-tenancy.md @@ -396,10 +396,13 @@ Session-level isolation means the runtime keeps user-specific model and state in | Session state | Per session ID under `COPILOT_HOME/session-state/{sessionId}`. | | GitHub identity | Per-session when `gitHubToken` is set on the session. | | Tools | Explicit in `mode: "empty"`; ambient in `mode: "copilot-cli"`. | +| Skills | In `mode: "empty"` no runtime-bundled built-in skills are eligible by default; callers can allow selected built-ins or opt into their own custom skills. Ambient in `mode: "copilot-cli"`. | | Host filesystem | Shared by the runtime process if host tools are available. | `mode: "empty"` is what makes shared runtime patterns viable: no ambient OS tools are exposed unless your application registers or allows them. With `mode: "copilot-cli"`, OS filesystem access is shared through the host process, so do not use that mode for multi-user server mode. +Under `mode: "empty"` the SDK excludes every runtime-bundled built-in skill by default (it sends an empty `includedBuiltinSkills` list on the post-create/post-resume options patch, alongside the empty `installedPlugins` list). Set `includedBuiltinSkills` (or the language-specific casing) to explicitly allow selected built-ins, just as `availableTools` allows selected runtime-bundled tools. A caller can also opt into its **own** custom skills—for example by enabling skills and pointing at its own skill directories—and those remain usable. + Session state is stored under `COPILOT_HOME/session-state/{sessionId}` unless you route it through `sessionFs`. Use unique session IDs that include your own tenant or user boundary, and enforce access control before resuming or deleting sessions. ## Pattern comparison diff --git a/dotnet/src/Client.cs b/dotnet/src/Client.cs index 86178ba74b..edd9f92738 100644 --- a/dotnet/src/Client.cs +++ b/dotnet/src/Client.cs @@ -1030,8 +1030,9 @@ private void ApplyConfigDefaultsForMode(SessionConfigBase config) /// patch for the current mode. In empty mode this defaults the four /// overridable feature flags to safe values (caller values from /// win); installedPlugins=[] is - /// unconditional under empty mode so apps that need plugins must switch - /// modes. In copilot-cli mode only explicitly-set fields are forwarded. + /// unconditional under empty mode. includedBuiltinSkills defaults to + /// an empty list, but callers can explicitly allow selected runtime-bundled + /// skills. In copilot-cli mode only explicitly-set fields are forwarded. /// private async Task UpdateSessionOptionsForModeAsync(CopilotSession session, SessionConfigBase config, CancellationToken cancellationToken) { @@ -1041,6 +1042,7 @@ private async Task UpdateSessionOptionsForModeAsync(CopilotSession session, Sess bool? coauthorEnabled = null; bool? manageScheduleEnabled = null; IList? installedPlugins = null; + IList? includedBuiltinSkills = null; if (_options.Mode == CopilotClientMode.Empty) { @@ -1049,6 +1051,7 @@ private async Task UpdateSessionOptionsForModeAsync(CopilotSession session, Sess coauthorEnabled = config.CoauthorEnabled ?? false; manageScheduleEnabled = config.ManageScheduleEnabled ?? false; installedPlugins = []; + includedBuiltinSkills = config.IncludedBuiltinSkills ?? []; hasAnyPatch = true; } else @@ -1057,6 +1060,7 @@ private async Task UpdateSessionOptionsForModeAsync(CopilotSession session, Sess if (config.CustomAgentsLocalOnly is not null) { customAgentsLocalOnly = config.CustomAgentsLocalOnly; hasAnyPatch = true; } if (config.CoauthorEnabled is not null) { coauthorEnabled = config.CoauthorEnabled; hasAnyPatch = true; } if (config.ManageScheduleEnabled is not null) { manageScheduleEnabled = config.ManageScheduleEnabled; hasAnyPatch = true; } + if (config.IncludedBuiltinSkills is not null) { includedBuiltinSkills = config.IncludedBuiltinSkills; hasAnyPatch = true; } } if (!hasAnyPatch) return; @@ -1070,6 +1074,7 @@ await session.Rpc.Options.UpdateAsync( coauthorEnabled: coauthorEnabled, manageScheduleEnabled: manageScheduleEnabled, installedPlugins: installedPlugins, + includedBuiltinSkills: includedBuiltinSkills, cancellationToken: cancellationToken).ConfigureAwait(false); #pragma warning restore GHCP001 } diff --git a/dotnet/src/Types.cs b/dotnet/src/Types.cs index 54595bfed7..f6a46520ec 100644 --- a/dotnet/src/Types.cs +++ b/dotnet/src/Types.cs @@ -3142,6 +3142,7 @@ protected SessionConfigBase(SessionConfigBase? other) DefaultAgent = other.DefaultAgent; Agent = other.Agent; DisabledSkills = other.DisabledSkills is not null ? [.. other.DisabledSkills] : null; + IncludedBuiltinSkills = other.IncludedBuiltinSkills is not null ? [.. other.IncludedBuiltinSkills] : null; DisabledMcpServers = other.DisabledMcpServers is not null ? [.. other.DisabledMcpServers] : null; EnableCitations = other.EnableCitations; EnableFileChangeTracking = other.EnableFileChangeTracking; @@ -3352,6 +3353,14 @@ protected SessionConfigBase(SessionConfigBase? other) /// public bool? EnableSkills { get; set; } + /// + /// Built-in skill names to include in the session. In + /// , omitting this option excludes all + /// runtime-bundled skills; specifying names opts those built-ins back in. + /// Skills from other sources remain eligible. + /// + public IList? IncludedBuiltinSkills { get; set; } + /// /// Custom tool declarations available to the language model during the session. /// Declarations backed by an are invoked automatically; declarations without one diff --git a/dotnet/test/Unit/ClientSessionLifetimeTests.cs b/dotnet/test/Unit/ClientSessionLifetimeTests.cs index 47ee14bb69..8746b53b5e 100644 --- a/dotnet/test/Unit/ClientSessionLifetimeTests.cs +++ b/dotnet/test/Unit/ClientSessionLifetimeTests.cs @@ -342,6 +342,146 @@ public async Task SessionRequests_Serialize_Terminal_Tools() Assert.True(resumeRequest.Params.GetProperty("tools")[0].GetProperty("isTerminal").GetBoolean()); } + [Fact] + public async Task EmptyMode_Create_Sends_Empty_IncludedBuiltinSkills() + { + await using var server = await FakeCopilotServer.StartAsync(); + await using var client = new CopilotClient(new CopilotClientOptions + { + Connection = RuntimeConnection.ForUri(server.Url), + Mode = CopilotClientMode.Empty, + BaseDirectory = Path.GetTempPath(), + }); + + await using var created = await client.CreateSessionAsync(new SessionConfig + { + AvailableTools = [], + OnPermissionRequest = PermissionHandler.ApproveAll + }); + + var update = Assert.Single(server.Requests, request => request.Method == "session.options.update"); + Assert.True(update.Params.TryGetProperty("includedBuiltinSkills", out var skills)); + Assert.Equal(JsonValueKind.Array, skills.ValueKind); + Assert.Equal(0, skills.GetArrayLength()); + // Adjacent unconditional plugin isolation is still present. + Assert.True(update.Params.TryGetProperty("installedPlugins", out var plugins)); + Assert.Equal(0, plugins.GetArrayLength()); + } + + [Fact] + public async Task EmptyMode_Resume_Sends_Empty_IncludedBuiltinSkills() + { + await using var server = await FakeCopilotServer.StartAsync(); + await using var client = new CopilotClient(new CopilotClientOptions + { + Connection = RuntimeConnection.ForUri(server.Url), + Mode = CopilotClientMode.Empty, + BaseDirectory = Path.GetTempPath(), + }); + + await using var resumed = await client.ResumeSessionAsync("resume-empty-skills", new ResumeSessionConfig + { + AvailableTools = [], + OnPermissionRequest = PermissionHandler.ApproveAll + }); + + var update = Assert.Single(server.Requests, request => request.Method == "session.options.update"); + Assert.True(update.Params.TryGetProperty("includedBuiltinSkills", out var skills)); + Assert.Equal(JsonValueKind.Array, skills.ValueKind); + Assert.Equal(0, skills.GetArrayLength()); + } + + [Fact] + public async Task EmptyMode_Resume_Preserves_Explicit_IncludedBuiltinSkills() + { + await using var server = await FakeCopilotServer.StartAsync(); + await using var client = new CopilotClient(new CopilotClientOptions + { + Connection = RuntimeConnection.ForUri(server.Url), + Mode = CopilotClientMode.Empty, + BaseDirectory = Path.GetTempPath(), + }); + + await using var resumed = await client.ResumeSessionAsync("resume-selected-skills", new ResumeSessionConfig + { + AvailableTools = [], + IncludedBuiltinSkills = ["code-review"], + OnPermissionRequest = PermissionHandler.ApproveAll + }); + + var update = Assert.Single(server.Requests, request => request.Method == "session.options.update"); + var skills = update.Params.GetProperty("includedBuiltinSkills"); + Assert.Equal(["code-review"], skills.EnumerateArray().Select(value => value.GetString())); + } + + [Fact] + public async Task EmptyMode_Create_With_EnableSkills_Still_Sends_Empty_IncludedBuiltinSkills() + { + await using var server = await FakeCopilotServer.StartAsync(); + await using var client = new CopilotClient(new CopilotClientOptions + { + Connection = RuntimeConnection.ForUri(server.Url), + Mode = CopilotClientMode.Empty, + BaseDirectory = Path.GetTempPath(), + }); + + // Caller opts into their own custom skills. Runtime-bundled built-ins must + // still be excluded: the empty post-patch cannot be weakened by the caller. + await using var created = await client.CreateSessionAsync(new SessionConfig + { + AvailableTools = [], + EnableSkills = true, + SkillDirectories = [Path.Combine(Path.GetTempPath(), "skills")], + OnPermissionRequest = PermissionHandler.ApproveAll + }); + + var update = Assert.Single(server.Requests, request => request.Method == "session.options.update"); + Assert.True(update.Params.TryGetProperty("includedBuiltinSkills", out var skills)); + Assert.Equal(JsonValueKind.Array, skills.ValueKind); + Assert.Equal(0, skills.GetArrayLength()); + } + + [Fact] + public async Task EmptyMode_Create_Preserves_Explicit_IncludedBuiltinSkills() + { + await using var server = await FakeCopilotServer.StartAsync(); + await using var client = new CopilotClient(new CopilotClientOptions + { + Connection = RuntimeConnection.ForUri(server.Url), + Mode = CopilotClientMode.Empty, + BaseDirectory = Path.GetTempPath(), + }); + + await using var created = await client.CreateSessionAsync(new SessionConfig + { + AvailableTools = [], + IncludedBuiltinSkills = ["code-review"], + OnPermissionRequest = PermissionHandler.ApproveAll + }); + + var update = Assert.Single(server.Requests, request => request.Method == "session.options.update"); + var skills = update.Params.GetProperty("includedBuiltinSkills"); + Assert.Equal(["code-review"], skills.EnumerateArray().Select(value => value.GetString())); + } + + [Fact] + public async Task CopilotCliMode_Create_Does_Not_Inject_IncludedBuiltinSkills() + { + await using var server = await FakeCopilotServer.StartAsync(); + await using var client = new CopilotClient(new CopilotClientOptions { Connection = RuntimeConnection.ForUri(server.Url) }); + + await using var created = await client.CreateSessionAsync(new SessionConfig + { + OnPermissionRequest = PermissionHandler.ApproveAll + }); + + // In the default copilot-cli mode with no overridable options set, no + // options patch is sent at all, so the field is never injected. + Assert.DoesNotContain(server.Requests, request => + request.Method == "session.options.update" + && request.Params.TryGetProperty("includedBuiltinSkills", out _)); + } + [Fact] public async Task CreateSessionAsync_Registers_McpAuth_Interest_Only_When_Handler_Configured() { @@ -1067,6 +1207,10 @@ private async Task HandleRequestAsync(Stream stream, JsonElement request, Cancel { ["messageId"] = "message-1" }, + "session.options.update" => new Dictionary + { + ["success"] = true + }, "session.mcp.oauth.handlePendingRequest" => new Dictionary { ["success"] = true diff --git a/go/client.go b/go/client.go index fb02897f91..47e308b3fc 100644 --- a/go/client.go +++ b/go/client.go @@ -1099,6 +1099,7 @@ func (c *Client) CreateSession(ctx context.Context, config *SessionConfig) (*Ses CustomAgentsLocalOnly: config.CustomAgentsLocalOnly, CoauthorEnabled: config.CoauthorEnabled, ManageScheduleEnabled: config.ManageScheduleEnabled, + IncludedBuiltinSkills: config.IncludedBuiltinSkills, }); err != nil { return nil, err } @@ -1377,6 +1378,7 @@ func (c *Client) ResumeSessionWithOptions(ctx context.Context, sessionID string, CustomAgentsLocalOnly: config.CustomAgentsLocalOnly, CoauthorEnabled: config.CoauthorEnabled, ManageScheduleEnabled: config.ManageScheduleEnabled, + IncludedBuiltinSkills: config.IncludedBuiltinSkills, }); err != nil { return nil, err } diff --git a/go/client_test.go b/go/client_test.go index 2332a77011..c6ab0808cb 100644 --- a/go/client_test.go +++ b/go/client_test.go @@ -2139,6 +2139,175 @@ func TestClient_MCPAuthInterestRegistration(t *testing.T) { }) } +func findRequest(requests []recordedRequest, method string) (recordedRequest, bool) { + for _, r := range requests { + if r.Method == method { + return r, true + } + } + return recordedRequest{}, false +} + +func TestClient_EmptyModeIncludedBuiltinSkills(t *testing.T) { + t.Run("create post-patch sends empty includedBuiltinSkills", func(t *testing.T) { + client, requests, cleanup := newInMemoryClientWithOptions(t, &ClientOptions{ + Mode: ModeEmpty, + BaseDirectory: "/tmp/copilot-test", + }) + defer cleanup() + + session, err := client.CreateSession(t.Context(), &SessionConfig{ + OnPermissionRequest: PermissionHandler.ApproveAll, + OnEvent: func(SessionEvent) {}, + AvailableTools: []string{}, + }) + if err != nil { + t.Fatalf("CreateSession failed: %v", err) + } + defer session.Disconnect() + + update, ok := findRequest(requests.snapshot(), "session.options.update") + if !ok { + t.Fatalf("expected session.options.update in %+v", requests.snapshot()) + } + skills, present := update.Params["includedBuiltinSkills"] + if !present { + t.Fatalf("expected includedBuiltinSkills in patch, got %+v", update.Params) + } + if arr, isArr := skills.([]any); !isArr || len(arr) != 0 { + t.Fatalf("expected includedBuiltinSkills=[], got %#v", skills) + } + }) + + t.Run("resume post-patch sends empty includedBuiltinSkills", func(t *testing.T) { + client, requests, cleanup := newInMemoryClientWithOptions(t, &ClientOptions{ + Mode: ModeEmpty, + BaseDirectory: "/tmp/copilot-test", + }) + defer cleanup() + + session, err := client.ResumeSession(t.Context(), "session-empty", &ResumeSessionConfig{ + OnPermissionRequest: PermissionHandler.ApproveAll, + OnEvent: func(SessionEvent) {}, + AvailableTools: []string{}, + }) + if err != nil { + t.Fatalf("ResumeSession failed: %v", err) + } + defer session.Disconnect() + + update, ok := findRequest(requests.snapshot(), "session.options.update") + if !ok { + t.Fatalf("expected session.options.update in %+v", requests.snapshot()) + } + if arr, isArr := update.Params["includedBuiltinSkills"].([]any); !isArr || len(arr) != 0 { + t.Fatalf("expected includedBuiltinSkills=[], got %#v", update.Params["includedBuiltinSkills"]) + } + }) + + t.Run("resume preserves explicit built-in skill allowlist", func(t *testing.T) { + client, requests, cleanup := newInMemoryClientWithOptions(t, &ClientOptions{ + Mode: ModeEmpty, + BaseDirectory: "/tmp/copilot-test", + }) + defer cleanup() + + session, err := client.ResumeSessionWithOptions(t.Context(), "resume-skills", &ResumeSessionConfig{ + OnPermissionRequest: PermissionHandler.ApproveAll, + AvailableTools: []string{}, + IncludedBuiltinSkills: []string{"code-review"}, + }) + if err != nil { + t.Fatalf("ResumeSessionWithOptions failed: %v", err) + } + defer session.Disconnect() + + update, ok := findRequest(requests.snapshot(), "session.options.update") + if !ok { + t.Fatalf("expected session.options.update in %+v", requests.snapshot()) + } + skills, ok := update.Params["includedBuiltinSkills"].([]any) + if !ok || len(skills) != 1 || skills[0] != "code-review" { + t.Fatalf("expected includedBuiltinSkills=[code-review], got %#v", update.Params["includedBuiltinSkills"]) + } + }) + + t.Run("caller opting into custom skills keeps includedBuiltinSkills empty", func(t *testing.T) { + client, requests, cleanup := newInMemoryClientWithOptions(t, &ClientOptions{ + Mode: ModeEmpty, + BaseDirectory: "/tmp/copilot-test", + }) + defer cleanup() + + session, err := client.CreateSession(t.Context(), &SessionConfig{ + OnPermissionRequest: PermissionHandler.ApproveAll, + OnEvent: func(SessionEvent) {}, + AvailableTools: []string{}, + EnableSkills: Bool(true), + SkillDirectories: []string{"/tmp/custom-skills"}, + }) + if err != nil { + t.Fatalf("CreateSession failed: %v", err) + } + defer session.Disconnect() + + update, ok := findRequest(requests.snapshot(), "session.options.update") + if !ok { + t.Fatalf("expected session.options.update in %+v", requests.snapshot()) + } + if arr, isArr := update.Params["includedBuiltinSkills"].([]any); !isArr || len(arr) != 0 { + t.Fatalf("expected includedBuiltinSkills=[], got %#v", update.Params["includedBuiltinSkills"]) + } + }) + + t.Run("explicit built-in skill allowlist is preserved in empty mode", func(t *testing.T) { + client, requests, cleanup := newInMemoryClientWithOptions(t, &ClientOptions{ + Mode: ModeEmpty, + BaseDirectory: "/tmp/copilot-test", + }) + defer cleanup() + + session, err := client.CreateSession(t.Context(), &SessionConfig{ + OnPermissionRequest: PermissionHandler.ApproveAll, + AvailableTools: []string{}, + IncludedBuiltinSkills: []string{"code-review"}, + }) + if err != nil { + t.Fatalf("CreateSession failed: %v", err) + } + defer session.Disconnect() + + update, ok := findRequest(requests.snapshot(), "session.options.update") + if !ok { + t.Fatalf("expected session.options.update in %+v", requests.snapshot()) + } + skills, ok := update.Params["includedBuiltinSkills"].([]any) + if !ok || len(skills) != 1 || skills[0] != "code-review" { + t.Fatalf("expected includedBuiltinSkills=[code-review], got %#v", update.Params["includedBuiltinSkills"]) + } + }) + + t.Run("copilot-cli mode does not inject includedBuiltinSkills", func(t *testing.T) { + client, requests, cleanup := newInMemoryClient(t) + defer cleanup() + + session, err := client.CreateSession(t.Context(), &SessionConfig{ + OnPermissionRequest: PermissionHandler.ApproveAll, + OnEvent: func(SessionEvent) {}, + }) + if err != nil { + t.Fatalf("CreateSession failed: %v", err) + } + defer session.Disconnect() + + for _, r := range requests.snapshot() { + if _, present := r.Params["includedBuiltinSkills"]; present { + t.Fatalf("did not expect includedBuiltinSkills in %s params %+v", r.Method, r.Params) + } + } + }) +} + type recordedRequest struct { Method string Params map[string]any @@ -2171,13 +2340,18 @@ func (r *requestRecorder) clear() { func newInMemoryClient(t *testing.T) (*Client, *requestRecorder, func()) { t.Helper() + return newInMemoryClientWithOptions(t, &ClientOptions{}) +} + +func newInMemoryClientWithOptions(t *testing.T, opts *ClientOptions) (*Client, *requestRecorder, func()) { + t.Helper() stdinR, stdinW := io.Pipe() stdoutR, stdoutW := io.Pipe() rpcClient := jsonrpc2.NewClient(stdinW, stdoutR) rpcClient.Start() - client := NewClient(&ClientOptions{}) + client := NewClient(opts) client.client = rpcClient client.RPC = rpc.NewServerRPC(rpcClient) client.state = stateConnected diff --git a/go/mode_empty.go b/go/mode_empty.go index 6057b2661f..6e238c58c4 100644 --- a/go/mode_empty.go +++ b/go/mode_empty.go @@ -225,7 +225,8 @@ func (c *Client) applyResumeDefaultsForMode(config *ResumeSessionConfig) { // updateSessionOptionsForMode applies the per-mode safe-defaults patch via // session.options.update after create/resume succeeds. In empty mode the // four overridable feature flags default to safe values; caller values win. -// installedPlugins=[] is unconditional in empty mode. +// installedPlugins=[] is unconditional in empty mode. IncludedBuiltinSkills +// defaults to [] but callers can explicitly allow selected runtime-bundled skills. func (c *Client) updateSessionOptionsForMode(ctx context.Context, session *Session, base optBackInFields) error { patch := &rpc.SessionUpdateOptionsParams{} hasAny := false @@ -255,6 +256,11 @@ func (c *Client) updateSessionOptionsForMode(ctx context.Context, session *Sessi patch.ManageScheduleEnabled = &f } patch.InstalledPlugins = []rpc.SessionInstalledPlugin{} + if base.IncludedBuiltinSkills != nil { + patch.IncludedBuiltinSkills = base.IncludedBuiltinSkills + } else { + patch.IncludedBuiltinSkills = []string{} + } hasAny = true } else { if base.SkipCustomInstructions != nil { @@ -273,6 +279,10 @@ func (c *Client) updateSessionOptionsForMode(ctx context.Context, session *Sessi patch.ManageScheduleEnabled = base.ManageScheduleEnabled hasAny = true } + if base.IncludedBuiltinSkills != nil { + patch.IncludedBuiltinSkills = base.IncludedBuiltinSkills + hasAny = true + } } if !hasAny { return nil @@ -297,4 +307,5 @@ type optBackInFields struct { CustomAgentsLocalOnly *bool CoauthorEnabled *bool ManageScheduleEnabled *bool + IncludedBuiltinSkills []string } diff --git a/go/types.go b/go/types.go index ff77c9e0c7..c411290fa2 100644 --- a/go/types.go +++ b/go/types.go @@ -1298,6 +1298,10 @@ type SessionConfig struct { // and discovered skill directories). When false, no skills are loaded regardless // of SkillDirectories or EnableConfigDiscovery settings. EnableSkills *bool + // IncludedBuiltinSkills is the allowlist of runtime-bundled skill names. + // In ModeEmpty, nil excludes all built-in skills; a non-nil list opts the + // named built-ins back in. Skills from other sources remain eligible. + IncludedBuiltinSkills []string // Tools exposes caller-implemented tools to the CLI. A Tool with a nil Handler // is declaration-only; the consumer must resolve its calls via pending tool RPCs. Tools []Tool @@ -1816,6 +1820,10 @@ type ResumeSessionConfig struct { // be selected or invoked unless a custom agent with the same name is // configured. ExcludedBuiltInAgents []string + // IncludedBuiltinSkills is the allowlist of runtime-bundled skill names. + // In ModeEmpty, nil excludes all built-in skills; a non-nil list opts the + // named built-ins back in. Skills from other sources remain eligible. + IncludedBuiltinSkills []string // Provider configures a custom model provider Provider *ProviderConfig // Capi configures provider-scoped CAPI (Copilot API) session options. 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 fe9a3c3b80..ef7ca481ba 100644 --- a/java/sdk/src/main/java/com/github/copilot/CopilotClient.java +++ b/java/sdk/src/main/java/com/github/copilot/CopilotClient.java @@ -1010,7 +1010,7 @@ public CompletableFuture createSession(SessionConfig config) { return updateSessionOptionsForMode(session, config.getSkipCustomInstructions().orElse(null), config.getCustomAgentsLocalOnly().orElse(null), config.getCoauthorEnabled().orElse(null), - config.getManageScheduleEnabled().orElse(null)); + config.getManageScheduleEnabled().orElse(null), config.getIncludedBuiltinSkills()); }).thenApply(v -> { LoggingHelpers.logTiming(LOG, Level.FINE, "CopilotClient.createSession complete. Elapsed={Elapsed}, SessionId=" @@ -1170,7 +1170,8 @@ public CompletableFuture resumeSession(String sessionId, ResumeS return updateSessionOptionsForMode(session, config.getSkipCustomInstructions().orElse(null), config.getCustomAgentsLocalOnly().orElse(null), config.getCoauthorEnabled().orElse(null), - config.getManageScheduleEnabled().orElse(null)).thenApply(v -> { + config.getManageScheduleEnabled().orElse(null), config.getIncludedBuiltinSkills()) + .thenApply(v -> { LoggingHelpers.logTiming(LOG, Level.FINE, "CopilotClient.resumeSession complete. Elapsed={Elapsed}, SessionId=" + sessionId, @@ -1192,14 +1193,22 @@ public CompletableFuture resumeSession(String sessionId, ResumeS }); } + CompletableFuture updateSessionOptionsForMode(CopilotSession session, Boolean skipCustomInstructions, + Boolean customAgentsLocalOnly, Boolean coauthorEnabled, Boolean manageScheduleEnabled) { + return updateSessionOptionsForMode(session, skipCustomInstructions, customAgentsLocalOnly, coauthorEnabled, + manageScheduleEnabled, null); + } + /** * Applies the post-create / post-resume {@code session.options.update} patch. *

* In {@link CopilotClientMode#EMPTY EMPTY} mode this defaults the four * overridable feature flags to safe values (caller values from the config win); - * {@code installedPlugins=[]} is unconditional under empty mode so apps that - * need plugins must switch modes. In {@link CopilotClientMode#COPILOT_CLI - * COPILOT_CLI} mode only explicitly-set fields are forwarded. + * {@code installedPlugins=[]} is unconditional under empty mode. + * {@code includedBuiltinSkills} defaults to an empty list, but callers can + * explicitly allow selected runtime-bundled skills. In + * {@link CopilotClientMode#COPILOT_CLI COPILOT_CLI} mode only explicitly-set + * fields are forwarded. * * @param session * the session to patch @@ -1211,16 +1220,21 @@ public CompletableFuture resumeSession(String sessionId, ResumeS * caller-supplied value, or {@code null} if not set * @param manageScheduleEnabled * caller-supplied value, or {@code null} if not set + * @param includedBuiltinSkills + * caller-supplied built-in skill allowlist, or {@code null} if not + * set * @return a future that completes when the patch has been applied */ CompletableFuture updateSessionOptionsForMode(CopilotSession session, Boolean skipCustomInstructions, - Boolean customAgentsLocalOnly, Boolean coauthorEnabled, Boolean manageScheduleEnabled) { + Boolean customAgentsLocalOnly, Boolean coauthorEnabled, Boolean manageScheduleEnabled, + List includedBuiltinSkills) { Boolean patchSkip = null; Boolean patchAgents = null; Boolean patchCoauthor = null; Boolean patchSchedule = null; List patchPlugins = null; + List patchSkills = null; boolean hasAnyPatch = false; if (options.getMode() == CopilotClientMode.EMPTY) { @@ -1229,6 +1243,7 @@ CompletableFuture updateSessionOptionsForMode(CopilotSession session, Bool patchCoauthor = coauthorEnabled != null ? coauthorEnabled : false; patchSchedule = manageScheduleEnabled != null ? manageScheduleEnabled : false; patchPlugins = List.of(); + patchSkills = includedBuiltinSkills != null ? includedBuiltinSkills : List.of(); hasAnyPatch = true; } else { if (skipCustomInstructions != null) { @@ -1247,6 +1262,10 @@ CompletableFuture updateSessionOptionsForMode(CopilotSession session, Bool patchSchedule = manageScheduleEnabled; hasAnyPatch = true; } + if (includedBuiltinSkills != null) { + patchSkills = includedBuiltinSkills; + hasAnyPatch = true; + } } if (!hasAnyPatch) { @@ -1282,7 +1301,7 @@ CompletableFuture updateSessionOptionsForMode(CopilotSession session, Bool null, // envValueMode null, // allowAllMcpServerInstructions null, // skillDirectories - null, // includedBuiltinSkills + patchSkills, // includedBuiltinSkills null, // disabledSkills null, // enableOnDemandInstructionDiscovery null, // maxInlineBinaryBytes 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 a188036372..7b43a852dd 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 @@ -89,6 +89,7 @@ public class ResumeSessionConfig { private DefaultAgentConfig defaultAgent; private String agent; private List skillDirectories; + private List includedBuiltinSkills; private List instructionDirectories; private List pluginDirectories; private LargeToolOutputConfig largeOutput; @@ -1489,6 +1490,28 @@ public ResumeSessionConfig setSkillDirectories(List skillDirectories) { return this; } + /** + * Gets the runtime-bundled skill allowlist. + * + * @return the built-in skill names, or {@code null} when unspecified + */ + public List getIncludedBuiltinSkills() { + return includedBuiltinSkills == null ? null : Collections.unmodifiableList(includedBuiltinSkills); + } + + /** + * Sets the runtime-bundled skill allowlist. In empty mode, omitting this option + * excludes all built-in skills; specifying names opts those built-ins back in. + * + * @param includedBuiltinSkills + * the built-in skill names to allow + * @return this config for method chaining + */ + public ResumeSessionConfig setIncludedBuiltinSkills(List includedBuiltinSkills) { + this.includedBuiltinSkills = includedBuiltinSkills; + return this; + } + /** * Gets the additional directories to search for custom instruction files. * @@ -2026,6 +2049,9 @@ public ResumeSessionConfig clone() { copy.defaultAgent = this.defaultAgent; copy.agent = this.agent; copy.skillDirectories = this.skillDirectories != null ? new ArrayList<>(this.skillDirectories) : null; + copy.includedBuiltinSkills = this.includedBuiltinSkills != null + ? new ArrayList<>(this.includedBuiltinSkills) + : null; copy.instructionDirectories = this.instructionDirectories != null ? new ArrayList<>(this.instructionDirectories) : null; 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 1127e6777b..1c08628dfe 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 @@ -79,6 +79,7 @@ public class SessionConfig { private String agent; private InfiniteSessionConfig infiniteSessions; private List skillDirectories; + private List includedBuiltinSkills; private List instructionDirectories; private List pluginDirectories; private LargeToolOutputConfig largeOutput; @@ -1175,6 +1176,28 @@ public SessionConfig setSkillDirectories(List skillDirectories) { return this; } + /** + * Gets the runtime-bundled skill allowlist. + * + * @return the built-in skill names, or {@code null} when unspecified + */ + public List getIncludedBuiltinSkills() { + return includedBuiltinSkills == null ? null : Collections.unmodifiableList(includedBuiltinSkills); + } + + /** + * Sets the runtime-bundled skill allowlist. In empty mode, omitting this option + * excludes all built-in skills; specifying names opts those built-ins back in. + * + * @param includedBuiltinSkills + * the built-in skill names to allow + * @return this config instance for method chaining + */ + public SessionConfig setIncludedBuiltinSkills(List includedBuiltinSkills) { + this.includedBuiltinSkills = includedBuiltinSkills; + return this; + } + /** * Gets the additional directories to search for custom instruction files. * @@ -2155,6 +2178,9 @@ public SessionConfig clone() { copy.agent = this.agent; copy.infiniteSessions = this.infiniteSessions; copy.skillDirectories = this.skillDirectories != null ? new ArrayList<>(this.skillDirectories) : null; + copy.includedBuiltinSkills = this.includedBuiltinSkills != null + ? new ArrayList<>(this.includedBuiltinSkills) + : null; copy.instructionDirectories = this.instructionDirectories != null ? new ArrayList<>(this.instructionDirectories) : null; diff --git a/java/sdk/src/test/java/com/github/copilot/UpdateSessionOptionsForModeTest.java b/java/sdk/src/test/java/com/github/copilot/UpdateSessionOptionsForModeTest.java index d02ea3097d..24b4257a52 100644 --- a/java/sdk/src/test/java/com/github/copilot/UpdateSessionOptionsForModeTest.java +++ b/java/sdk/src/test/java/com/github/copilot/UpdateSessionOptionsForModeTest.java @@ -10,6 +10,7 @@ import java.net.ServerSocket; import java.net.Socket; import java.nio.charset.StandardCharsets; +import java.util.List; import org.junit.jupiter.api.Test; @@ -144,6 +145,8 @@ void copilotCliMode_skipCustomInstructionsSet_patchContainsOnlyThatField() throw assertTrue(pair.lastParams.path("manageScheduleEnabled").isMissingNode(), "manageScheduleEnabled should be absent"); assertTrue(pair.lastParams.path("installedPlugins").isMissingNode(), "installedPlugins should be absent"); + assertTrue(pair.lastParams.path("includedBuiltinSkills").isMissingNode(), + "includedBuiltinSkills should be absent in COPILOT_CLI mode"); client.close(); } } @@ -154,13 +157,14 @@ void copilotCliMode_allFieldsSet_allPropagated() throws Exception { var session = new CopilotSession("sess-1", pair.rpcClient); var client = new CopilotClient(new CopilotClientOptions().setAutoStart(false)); - client.updateSessionOptionsForMode(session, false, true, true, false).get(); + client.updateSessionOptionsForMode(session, false, true, true, false, List.of("code-review")).get(); assertEquals("session.options.update", pair.lastMethod); assertFalse(pair.lastParams.get("skipCustomInstructions").asBoolean()); assertTrue(pair.lastParams.get("customAgentsLocalOnly").asBoolean()); assertTrue(pair.lastParams.get("coauthorEnabled").asBoolean()); assertFalse(pair.lastParams.get("manageScheduleEnabled").asBoolean()); + assertEquals("code-review", pair.lastParams.get("includedBuiltinSkills").get(0).asText()); client.close(); } } @@ -197,6 +201,9 @@ void emptyMode_noFieldsSet_safeDefaultsSent() throws Exception { assertFalse(pair.lastParams.get("manageScheduleEnabled").asBoolean(), "default: schedule disabled"); assertTrue(pair.lastParams.get("installedPlugins").isArray(), "installedPlugins should be empty array"); assertEquals(0, pair.lastParams.get("installedPlugins").size()); + assertTrue(pair.lastParams.get("includedBuiltinSkills").isArray(), + "includedBuiltinSkills should be empty array"); + assertEquals(0, pair.lastParams.get("includedBuiltinSkills").size()); client.close(); } } @@ -218,6 +225,25 @@ void emptyMode_callerOverridesWin() throws Exception { assertTrue(pair.lastParams.get("installedPlugins").isArray(), "installedPlugins always empty in EMPTY mode"); assertEquals(0, pair.lastParams.get("installedPlugins").size()); + assertTrue(pair.lastParams.get("includedBuiltinSkills").isArray(), + "includedBuiltinSkills always empty in EMPTY mode"); + assertEquals(0, pair.lastParams.get("includedBuiltinSkills").size()); + client.close(); + } + } + + @Test + void emptyMode_explicitBuiltinSkillAllowlistWins() throws Exception { + try (var pair = new AutoReplyPair()) { + var session = new CopilotSession("sess-1", pair.rpcClient); + var client = new CopilotClient(new CopilotClientOptions().setMode(CopilotClientMode.EMPTY) + .setCopilotHome("/tmp/copilot-home").setAutoStart(false)); + + client.updateSessionOptionsForMode(session, null, null, null, null, List.of("code-review")).get(); + + assertEquals("session.options.update", pair.lastMethod); + assertEquals(1, pair.lastParams.get("includedBuiltinSkills").size()); + assertEquals("code-review", pair.lastParams.get("includedBuiltinSkills").get(0).asText()); client.close(); } } diff --git a/nodejs/src/client.ts b/nodejs/src/client.ts index 1be2cbb941..2dfae099f1 100644 --- a/nodejs/src/client.ts +++ b/nodejs/src/client.ts @@ -1401,7 +1401,8 @@ export class CopilotClient { * * In empty mode, defaults the four overridable feature flags to safe values * (caller values from `config` win). `installedPlugins=[]` is unconditional - * in empty mode — apps that need custom plugins should switch modes. + * in empty mode. `includedBuiltinSkills` defaults to `[]`, but callers can + * explicitly allow selected runtime-bundled skills. */ private async updateSessionOptionsForMode( session: CopilotSession, @@ -1414,6 +1415,7 @@ export class CopilotClient { patch.coauthorEnabled = config.coauthorEnabled ?? false; patch.manageScheduleEnabled = config.manageScheduleEnabled ?? false; patch.installedPlugins = []; + patch.includedBuiltinSkills = config.includedBuiltinSkills ?? []; } else { if (config.skipCustomInstructions !== undefined) patch.skipCustomInstructions = config.skipCustomInstructions; @@ -1423,6 +1425,8 @@ export class CopilotClient { patch.coauthorEnabled = config.coauthorEnabled; if (config.manageScheduleEnabled !== undefined) patch.manageScheduleEnabled = config.manageScheduleEnabled; + if (config.includedBuiltinSkills !== undefined) + patch.includedBuiltinSkills = config.includedBuiltinSkills; } if (Object.keys(patch).length === 0) { return; diff --git a/nodejs/src/types.ts b/nodejs/src/types.ts index 24d23d0826..5b5d8da482 100644 --- a/nodejs/src/types.ts +++ b/nodejs/src/types.ts @@ -2394,6 +2394,13 @@ export interface SessionConfigBase { */ excludedBuiltinAgents?: string[]; + /** + * Built-in skill names to include in the session. In `mode: "empty"`, + * omitting this option excludes all runtime-bundled skills; specifying names + * opts those built-ins back in. Skills from other sources remain eligible. + */ + includedBuiltinSkills?: string[]; + /** * Custom provider configuration (BYOK - Bring Your Own Key). * When specified, uses the provided API endpoint instead of the Copilot API. diff --git a/nodejs/test/client.test.ts b/nodejs/test/client.test.ts index e0e1981d88..3ffda2fa71 100644 --- a/nodejs/test/client.test.ts +++ b/nodejs/test/client.test.ts @@ -1958,6 +1958,134 @@ describe("CopilotClient", () => { spy.mockRestore(); }); + it("sends includedBuiltinSkills=[] in the empty-mode post-create options patch", async () => { + const client = new CopilotClient({ mode: "empty", baseDirectory: "/tmp/copilot-test" }); + await client.start(); + onTestFinished(() => stopClient(client)); + + const spy = vi + .spyOn((client as any).connection!, "sendRequest") + .mockImplementation(async (method: string, params: any) => { + if (method === "session.create") return { sessionId: params.sessionId ?? "s1" }; + if (method === "session.options.update") return {}; + throw new Error(`Unexpected method: ${method}`); + }); + await client.createSession({ onPermissionRequest: approveAll, availableTools: [] }); + + const patch = spy.mock.calls.find((c) => c[0] === "session.options.update")![1] as any; + expect(patch.includedBuiltinSkills).toEqual([]); + expect(patch.installedPlugins).toEqual([]); + }); + + it("sends includedBuiltinSkills=[] in the empty-mode post-resume options patch", async () => { + const client = new CopilotClient({ mode: "empty", baseDirectory: "/tmp/copilot-test" }); + await client.start(); + onTestFinished(() => stopClient(client)); + + const spy = vi + .spyOn((client as any).connection!, "sendRequest") + .mockImplementation(async (method: string, params: any) => { + if (method === "session.create") return { sessionId: params.sessionId ?? "s1" }; + if (method === "session.resume") return { sessionId: params.sessionId }; + if (method === "session.options.update") return {}; + throw new Error(`Unexpected method: ${method}`); + }); + await client.createSession({ onPermissionRequest: approveAll, availableTools: [] }); + spy.mockClear(); + await client.resumeSession("s1", { onPermissionRequest: approveAll, availableTools: [] }); + + const patch = spy.mock.calls.find((c) => c[0] === "session.options.update")![1] as any; + expect(patch.includedBuiltinSkills).toEqual([]); + }); + + it("preserves an explicit built-in skill allowlist after empty-mode resume", async () => { + const client = new CopilotClient({ mode: "empty", baseDirectory: "/tmp/copilot-test" }); + await client.start(); + onTestFinished(() => stopClient(client)); + + const spy = vi + .spyOn((client as any).connection!, "sendRequest") + .mockImplementation(async (method: string) => { + if (method === "session.resume") return { sessionId: "s1" }; + if (method === "session.options.update") return { success: true }; + throw new Error(`Unexpected method: ${method}`); + }); + await client.resumeSession("s1", { + onPermissionRequest: approveAll, + availableTools: [], + includedBuiltinSkills: ["code-review"], + }); + + const patch = spy.mock.calls.find((c) => c[0] === "session.options.update")![1] as any; + expect(patch.includedBuiltinSkills).toEqual(["code-review"]); + }); + + it("keeps includedBuiltinSkills=[] even when the caller opts into custom skills in empty mode", async () => { + const client = new CopilotClient({ mode: "empty", baseDirectory: "/tmp/copilot-test" }); + await client.start(); + onTestFinished(() => stopClient(client)); + + const spy = vi + .spyOn((client as any).connection!, "sendRequest") + .mockImplementation(async (method: string, params: any) => { + if (method === "session.create") return { sessionId: params.sessionId ?? "s1" }; + if (method === "session.options.update") return {}; + throw new Error(`Unexpected method: ${method}`); + }); + await client.createSession({ + onPermissionRequest: approveAll, + availableTools: [], + enableSkills: true, + skillDirectories: ["/tmp/custom-skills"], + }); + + const createPayload = spy.mock.calls.find((c) => c[0] === "session.create")![1] as any; + expect(createPayload.enableSkills).toBe(true); + expect(createPayload.skillDirectories).toEqual(["/tmp/custom-skills"]); + const patch = spy.mock.calls.find((c) => c[0] === "session.options.update")![1] as any; + expect(patch.includedBuiltinSkills).toEqual([]); + }); + + it("preserves an explicit built-in skill allowlist in empty mode", async () => { + const client = new CopilotClient({ mode: "empty", baseDirectory: "/tmp/copilot-test" }); + await client.start(); + onTestFinished(() => stopClient(client)); + + const spy = vi + .spyOn((client as any).connection!, "sendRequest") + .mockImplementation(async (method: string, params: any) => { + if (method === "session.create") return { sessionId: params.sessionId }; + if (method === "session.options.update") return { success: true }; + throw new Error(`Unexpected method: ${method}`); + }); + await client.createSession({ + onPermissionRequest: approveAll, + availableTools: [], + includedBuiltinSkills: ["code-review"], + }); + + const patch = spy.mock.calls.find((c) => c[0] === "session.options.update")![1] as any; + expect(patch.includedBuiltinSkills).toEqual(["code-review"]); + }); + + it("does not send includedBuiltinSkills in copilot-cli mode", async () => { + const client = new CopilotClient(); + await client.start(); + onTestFinished(() => stopClient(client)); + + const spy = vi.spyOn((client as any).connection!, "sendRequest"); + await client.createSession({ onPermissionRequest: approveAll }); + + const patch = spy.mock.calls.find((c) => c[0] === "session.options.update"); + // copilot-cli mode sends no post-create options patch at all here. + if (patch) { + expect((patch[1] as any).includedBuiltinSkills).toBeUndefined(); + } + const createPayload = spy.mock.calls.find((c) => c[0] === "session.create")![1] as any; + expect(createPayload.includedBuiltinSkills).toBeUndefined(); + spy.mockRestore(); + }); + it("forwards continuePendingWork in session.resume request", async () => { const client = new CopilotClient(); await client.start(); diff --git a/python/copilot/_mode.py b/python/copilot/_mode.py index 1a9ed6e1f5..5e7de02d02 100644 --- a/python/copilot/_mode.py +++ b/python/copilot/_mode.py @@ -298,11 +298,14 @@ def _post_create_options_patch( custom_agents_local_only: bool | None, coauthor_enabled: bool | None, manage_schedule_enabled: bool | None, + included_builtin_skills: list[str] | None = None, ) -> dict[str, Any] | None: """Build the patch sent via ``session.options.update`` after create/resume. In empty mode the four overridable flags default to safe values (caller-supplied values win); ``installedPlugins=[]`` is unconditional. + ``includedBuiltinSkills`` defaults to an empty list, but callers can + explicitly allow selected runtime-bundled skills. Returns ``None`` if no patch should be sent. """ if mode == "empty": @@ -318,6 +321,9 @@ def _post_create_options_patch( manage_schedule_enabled if manage_schedule_enabled is not None else False ), "installedPlugins": [], + "includedBuiltinSkills": ( + included_builtin_skills if included_builtin_skills is not None else [] + ), } return patch patch = {} @@ -329,6 +335,8 @@ def _post_create_options_patch( patch["coauthorEnabled"] = coauthor_enabled if manage_schedule_enabled is not None: patch["manageScheduleEnabled"] = manage_schedule_enabled + if included_builtin_skills is not None: + patch["includedBuiltinSkills"] = included_builtin_skills return patch or None diff --git a/python/copilot/client.py b/python/copilot/client.py index ad4b0fe171..20bf2c44e3 100644 --- a/python/copilot/client.py +++ b/python/copilot/client.py @@ -2152,6 +2152,7 @@ async def create_session( enable_host_git_operations: bool | None = None, enable_session_store: bool | None = None, enable_skills: bool | None = None, + included_builtin_skills: list[str] | None = None, skill_directories: list[str] | None = None, plugin_directories: list[str] | None = None, instruction_directories: list[str] | None = None, @@ -2818,6 +2819,7 @@ def _register_inline(raw_response: Any) -> None: custom_agents_local_only, coauthor_enabled, manage_schedule_enabled, + included_builtin_skills, ) log_timing( @@ -2880,6 +2882,7 @@ async def resume_session( enable_host_git_operations: bool | None = None, enable_session_store: bool | None = None, enable_skills: bool | None = None, + included_builtin_skills: list[str] | None = None, skill_directories: list[str] | None = None, plugin_directories: list[str] | None = None, instruction_directories: list[str] | None = None, @@ -3460,6 +3463,7 @@ async def resume_session( custom_agents_local_only, coauthor_enabled, manage_schedule_enabled, + included_builtin_skills, ) log_timing( @@ -4515,6 +4519,7 @@ async def _apply_post_create_options_patch( custom_agents_local_only: bool | None, coauthor_enabled: bool | None, manage_schedule_enabled: bool | None, + included_builtin_skills: list[str] | None = None, ) -> None: """Apply empty-mode safe defaults (or caller-supplied overrides in copilot-cli mode) via ``session.options.update`` after create/resume. @@ -4530,6 +4535,7 @@ async def _apply_post_create_options_patch( custom_agents_local_only, coauthor_enabled, manage_schedule_enabled, + included_builtin_skills, ) if patch is None: return @@ -4548,6 +4554,9 @@ async def _apply_post_create_options_patch( SessionInstalledPlugin.from_dict(p) if isinstance(p, dict) else p for p in patch["installedPlugins"] ] + if "includedBuiltinSkills" in patch: + skills = patch["includedBuiltinSkills"] + params.included_builtin_skills = list(skills) if skills is not None else None try: await session.rpc.options.update(params) diff --git a/python/test_tool_set.py b/python/test_tool_set.py index 0674b488a2..3468ccee34 100644 --- a/python/test_tool_set.py +++ b/python/test_tool_set.py @@ -248,6 +248,7 @@ def test_empty_mode_defaults(self): "coauthorEnabled": False, "manageScheduleEnabled": False, "installedPlugins": [], + "includedBuiltinSkills": [], } def test_empty_mode_caller_wins(self): @@ -258,14 +259,105 @@ def test_empty_mode_caller_wins(self): "coauthorEnabled": True, "manageScheduleEnabled": True, "installedPlugins": [], + "includedBuiltinSkills": [], } + def test_empty_mode_preserves_explicit_builtin_skill_allowlist(self): + patch = _post_create_options_patch("empty", None, None, None, None, ["code-review"]) + assert patch is not None + assert patch["includedBuiltinSkills"] == ["code-review"] + def test_copilot_cli_returns_none_when_unset(self): assert _post_create_options_patch("copilot-cli", None, None, None, None) is None + # Non-empty mode never injects the built-in skill restriction. + assert "includedBuiltinSkills" not in ( + _post_create_options_patch("copilot-cli", True, None, False, None) or {} + ) def test_copilot_cli_passes_through_explicit_values(self): - patch = _post_create_options_patch("copilot-cli", True, None, False, None) - assert patch == {"skipCustomInstructions": True, "coauthorEnabled": False} + patch = _post_create_options_patch("copilot-cli", True, None, False, None, ["code-review"]) + assert patch == { + "skipCustomInstructions": True, + "coauthorEnabled": False, + "includedBuiltinSkills": ["code-review"], + } + + +class _CapturingOptions: + """Captures the params passed to ``session.rpc.options.update``.""" + + def __init__(self) -> None: + self.captured: list = [] + + async def update(self, params) -> None: + self.captured.append(params) + + +class _CapturingRpc: + def __init__(self) -> None: + self.options = _CapturingOptions() + + +class _FakeSession: + def __init__(self, session_id: str = "sid-1") -> None: + self.session_id = session_id + self.rpc = _CapturingRpc() + + async def disconnect(self) -> None: + pass + + +class TestApplyPostCreateOptionsPatch: + """Guards the translation from the patch dict to ``SessionUpdateOptionsParams``. + + This covers the wire request emitted on both create and resume, which both + funnel through ``_apply_post_create_options_patch``. + """ + + def _make_client(self): + return CopilotClient( + mode="empty", + connection=UriRuntimeConnection(url="http://localhost:1234"), + ) + + async def test_empty_mode_sends_included_builtin_skills_empty(self): + client = self._make_client() + session = _FakeSession() + await client._apply_post_create_options_patch(session, "empty", None, None, None, None) + assert len(session.rpc.options.captured) == 1 + params = session.rpc.options.captured[0] + # The Empty post-patch must reach the wire request as an empty list, not + # be silently dropped during translation. + assert params.included_builtin_skills == [] + assert params.installed_plugins == [] + + async def test_empty_mode_explicit_allowlist_reaches_wire(self): + client = self._make_client() + session = _FakeSession() + await client._apply_post_create_options_patch( + session, "empty", False, False, True, True, ["code-review"] + ) + params = session.rpc.options.captured[0] + assert params.included_builtin_skills == ["code-review"] + + async def test_copilot_cli_mode_omits_included_builtin_skills(self): + client = self._make_client() + session = _FakeSession() + # Non-empty mode with no overrides sends no patch at all. + await client._apply_post_create_options_patch( + session, "copilot-cli", None, None, None, None + ) + assert session.rpc.options.captured == [] + + async def test_copilot_cli_mode_never_sets_included_builtin_skills(self): + client = self._make_client() + session = _FakeSession() + await client._apply_post_create_options_patch( + session, "copilot-cli", True, None, False, None + ) + params = session.rpc.options.captured[0] + # copilot-cli mode must not inject the built-in skill restriction. + assert params.included_builtin_skills is None class TestClientConstruction: diff --git a/rust/src/session.rs b/rust/src/session.rs index 7676c2ad70..3e5ae13dee 100644 --- a/rust/src/session.rs +++ b/rust/src/session.rs @@ -903,6 +903,7 @@ impl Client { let opt_custom_agents_local_only = config.custom_agents_local_only; let opt_coauthor_enabled = config.coauthor_enabled; let opt_manage_schedule_enabled = config.manage_schedule_enabled; + let opt_included_builtin_skills = config.included_builtin_skills.take(); let (mut wire, mut runtime) = config.into_wire(local_session_id.clone())?; wire.enable_github_telemetry_forwarding = self.inner.on_github_telemetry.is_some().then_some(true); @@ -1098,6 +1099,7 @@ impl Client { opt_custom_agents_local_only, opt_coauthor_enabled, opt_manage_schedule_enabled, + opt_included_builtin_skills, ) .await?; Ok(session) @@ -1176,6 +1178,7 @@ impl Client { let opt_custom_agents_local_only = config.custom_agents_local_only; let opt_coauthor_enabled = config.coauthor_enabled; let opt_manage_schedule_enabled = config.manage_schedule_enabled; + let opt_included_builtin_skills = config.included_builtin_skills.take(); let (mut wire, mut runtime) = config.into_wire()?; wire.enable_github_telemetry_forwarding = self.inner.on_github_telemetry.is_some().then_some(true); @@ -1358,6 +1361,7 @@ impl Client { opt_custom_agents_local_only, opt_coauthor_enabled, opt_manage_schedule_enabled, + opt_included_builtin_skills, ) .await?; Ok(session) @@ -1373,7 +1377,42 @@ async fn apply_mode_post_create_patch( opt_custom_agents_local_only: Option, opt_coauthor_enabled: Option, opt_manage_schedule_enabled: Option, + opt_included_builtin_skills: Option>, ) -> Result<(), Error> { + let Some(patch) = build_mode_post_create_patch( + mode, + opt_skip_custom_instructions, + opt_custom_agents_local_only, + opt_coauthor_enabled, + opt_manage_schedule_enabled, + opt_included_builtin_skills, + ) else { + return Ok(()); + }; + if let Err(error) = session.rpc().options().update(patch).await { + let _ = session.disconnect().await; + return Err(error); + } + Ok(()) +} + +/// Builds the `session.options.update` patch applied immediately after a session +/// is created or resumed, or returns `None` when no patch should be sent. +/// +/// Under [`ClientMode::Empty`](crate::ClientMode::Empty) the overridable feature +/// flags fall back to safe defaults (caller values win), while +/// `installed_plugins` is unconditionally empty. `included_builtin_skills` +/// defaults to an empty list, but callers can explicitly allow selected +/// runtime-bundled skills. Under other modes only explicitly-set fields are +/// forwarded. +fn build_mode_post_create_patch( + mode: crate::ClientMode, + opt_skip_custom_instructions: Option, + opt_custom_agents_local_only: Option, + opt_coauthor_enabled: Option, + opt_manage_schedule_enabled: Option, + opt_included_builtin_skills: Option>, +) -> Option { use crate::generated::api_types::SessionUpdateOptionsParams; let mut patch = SessionUpdateOptionsParams::default(); let should_send = if mode == crate::ClientMode::Empty { @@ -1382,6 +1421,7 @@ async fn apply_mode_post_create_patch( patch.coauthor_enabled = Some(opt_coauthor_enabled.unwrap_or(false)); patch.manage_schedule_enabled = Some(opt_manage_schedule_enabled.unwrap_or(false)); patch.installed_plugins = Some(Vec::new()); + patch.included_builtin_skills = Some(opt_included_builtin_skills.unwrap_or_default()); true } else { let mut any = false; @@ -1401,16 +1441,16 @@ async fn apply_mode_post_create_patch( patch.manage_schedule_enabled = Some(v); any = true; } + if let Some(v) = opt_included_builtin_skills { + patch.included_builtin_skills = Some(v); + any = true; + } any }; if !should_send { - return Ok(()); + return None; } - if let Err(error) = session.rpc().options().update(patch).await { - let _ = session.disconnect().await; - return Err(error); - } - Ok(()) + Some(patch) } fn build_command_handler_map(commands: Option<&[CommandDefinition]>) -> Arc { @@ -2581,13 +2621,93 @@ fn inject_transform_sections_resume( mod tests { use serde_json::json; - use super::{has_managed_settings, permission_request_data, permission_response_params}; + use super::{ + build_mode_post_create_patch, has_managed_settings, permission_request_data, + permission_response_params, + }; use crate::handler::PermissionResult; use crate::types::{ PermissionDecisionContext, PermissionDecisionOutcome, PermissionDecisionSource, PermissionDecisionSurface, RequestId, SessionId, }; + #[test] + fn empty_mode_post_patch_sets_empty_included_builtin_skills() { + let patch = + build_mode_post_create_patch(crate::ClientMode::Empty, None, None, None, None, None) + .expect("empty mode always sends a patch"); + assert_eq!( + patch.included_builtin_skills, + Some(Vec::new()), + "empty mode must fail closed with an empty includedBuiltinSkills list" + ); + assert_eq!(patch.installed_plugins.as_ref().map(|p| p.len()), Some(0)); + // Serializes as an explicit empty array (not omitted). + let value = serde_json::to_value(&patch).expect("serialize patch"); + assert_eq!(value["includedBuiltinSkills"], serde_json::json!([])); + } + + #[test] + fn empty_mode_post_patch_preserves_explicit_builtin_skill_allowlist() { + let patch = build_mode_post_create_patch( + crate::ClientMode::Empty, + Some(false), + Some(false), + Some(true), + Some(true), + Some(vec!["code-review".to_string()]), + ) + .expect("empty mode always sends a patch"); + assert_eq!( + patch.included_builtin_skills, + Some(vec!["code-review".to_string()]) + ); + } + + #[test] + fn copilot_cli_mode_does_not_inject_included_builtin_skills() { + // No fields set -> no patch at all. + assert!( + build_mode_post_create_patch( + crate::ClientMode::CopilotCli, + None, + None, + None, + None, + None + ) + .is_none() + ); + // A field set -> patch sent, but skills field stays absent. + let patch = build_mode_post_create_patch( + crate::ClientMode::CopilotCli, + Some(true), + None, + None, + None, + None, + ) + .expect("a set field triggers a patch"); + assert_eq!(patch.included_builtin_skills, None); + assert!(patch.installed_plugins.is_none()); + let value = serde_json::to_value(&patch).expect("serialize patch"); + assert!(value.get("includedBuiltinSkills").is_none()); + + let patch = build_mode_post_create_patch( + crate::ClientMode::CopilotCli, + None, + None, + None, + None, + Some(vec!["code-review".to_string()]), + ) + .expect("an explicit allowlist triggers a patch"); + assert_eq!( + patch.included_builtin_skills, + Some(vec!["code-review".to_string()]) + ); + } + #[test] fn direct_injection_enables_managed_safeguards() { let settings = crate::types::ManagedSettings::default(); diff --git a/rust/src/types.rs b/rust/src/types.rs index 06c0fc4e79..2db631db3c 100644 --- a/rust/src/types.rs +++ b/rust/src/types.rs @@ -1955,6 +1955,10 @@ pub struct SessionConfig { /// selected or invoked unless a custom agent with the same name is /// configured. pub excluded_builtin_agents: Option>, + /// Built-in skill names to include in the session. In + /// [`ClientMode::Empty`](crate::ClientMode::Empty), `None` excludes all + /// runtime-bundled skills; `Some` opts the named built-ins back in. + pub included_builtin_skills: Option>, /// MCP server configurations passed through to the CLI. pub mcp_servers: Option>, /// Controls how MCP OAuth tokens are stored for this session. @@ -2240,6 +2244,7 @@ impl std::fmt::Debug for SessionConfig { .field("available_tools", &self.available_tools) .field("excluded_tools", &self.excluded_tools) .field("excluded_builtin_agents", &self.excluded_builtin_agents) + .field("included_builtin_skills", &self.included_builtin_skills) .field("mcp_servers", &self.mcp_servers) .field("mcp_oauth_token_storage", &self.mcp_oauth_token_storage) .field("embedding_cache_storage", &self.embedding_cache_storage) @@ -2372,6 +2377,7 @@ impl Default for SessionConfig { available_tools: None, excluded_tools: None, excluded_builtin_agents: None, + included_builtin_skills: None, mcp_servers: None, mcp_oauth_token_storage: None, enable_config_discovery: None, @@ -2951,6 +2957,16 @@ impl SessionConfig { self } + /// Set the runtime-bundled skill allowlist. + pub fn with_included_builtin_skills(mut self, names: I) -> Self + where + I: IntoIterator, + S: Into, + { + self.included_builtin_skills = Some(names.into_iter().map(Into::into).collect()); + self + } + /// Set additional directories to search for custom instruction files. /// Forwarded to the CLI on session create; not the same as /// [`with_skill_directories`](Self::with_skill_directories). @@ -3287,6 +3303,10 @@ pub struct ResumeSessionConfig { /// selected or invoked unless a custom agent with the same name is /// configured. pub excluded_builtin_agents: Option>, + /// Built-in skill names to include in the resumed session. In + /// [`ClientMode::Empty`](crate::ClientMode::Empty), `None` excludes all + /// runtime-bundled skills; `Some` opts the named built-ins back in. + pub included_builtin_skills: Option>, /// Re-supply MCP servers so they remain available after app restart. pub mcp_servers: Option>, /// Controls how MCP OAuth tokens are stored for this session. @@ -3506,6 +3526,7 @@ impl std::fmt::Debug for ResumeSessionConfig { .field("available_tools", &self.available_tools) .field("excluded_tools", &self.excluded_tools) .field("excluded_builtin_agents", &self.excluded_builtin_agents) + .field("included_builtin_skills", &self.included_builtin_skills) .field("mcp_servers", &self.mcp_servers) .field("mcp_oauth_token_storage", &self.mcp_oauth_token_storage) .field("embedding_cache_storage", &self.embedding_cache_storage) @@ -3779,6 +3800,7 @@ impl ResumeSessionConfig { available_tools: None, excluded_tools: None, excluded_builtin_agents: None, + included_builtin_skills: None, mcp_servers: None, mcp_oauth_token_storage: None, enable_config_discovery: None, @@ -4172,6 +4194,16 @@ impl ResumeSessionConfig { self } + /// Set the runtime-bundled skill allowlist on resume. + pub fn with_included_builtin_skills(mut self, names: I) -> Self + where + I: IntoIterator, + S: Into, + { + self.included_builtin_skills = Some(names.into_iter().map(Into::into).collect()); + self + } + /// Set additional directories to search for custom instruction files /// on resume. Forwarded to the CLI; not the same as /// [`with_skill_directories`](Self::with_skill_directories). From 7016935558b01b44d716013212d00f8d50b7a63c Mon Sep 17 00:00:00 2001 From: Ed Burns Date: Wed, 26 Aug 2026 11:46:42 -0700 Subject: [PATCH 17/32] ci(java): show Maven coordinates in publish summaries (#2413) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: e2a48f51-02fe-445b-a253-40776b09a44f --- .github/workflows/java-publish-maven.yml | 25 ++++++++++++++++++++- .github/workflows/java-publish-snapshot.yml | 17 ++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/.github/workflows/java-publish-maven.yml b/.github/workflows/java-publish-maven.yml index ff5de9e11b..1ce4f51e38 100644 --- a/.github/workflows/java-publish-maven.yml +++ b/.github/workflows/java-publish-maven.yml @@ -401,6 +401,13 @@ jobs: node copilot-native/scripts/validate-native-artifact.mjs \ classifier linux-x64 "$LINUX_JAR" "$(basename "$LINUX_JAR")" .. LINUX_SHA=$(sha256sum "$LINUX_JAR" | cut -d ' ' -f 1) + GROUP_ID=$(mvn -q -pl copilot-native help:evaluate -Dexpression=project.groupId -DforceStdout) + ARTIFACT_ID=$(mvn -q -pl copilot-native help:evaluate -Dexpression=project.artifactId -DforceStdout) + POM_VERSION=$(mvn -q -pl copilot-native help:evaluate -Dexpression=project.version -DforceStdout) + if [ -z "$GROUP_ID" ] || [ -z "$ARTIFACT_ID" ] || [ "$POM_VERSION" != "$VERSION" ]; then + echo "::error::Unexpected copilot-native Maven coordinates: $GROUP_ID:$ARTIFACT_ID:$POM_VERSION (expected version $VERSION)" + exit 1 + fi { echo "### Maven Central Release" echo "- **Version:** $VERSION" @@ -409,6 +416,16 @@ jobs: echo "- **Next development version:** ${{ needs.prepare-release.outputs.development_version }}" echo "- **Repository:** Maven Central" echo "" + echo "#### Maven Coordinates" + echo "" + echo '```xml' + echo "" + echo " $GROUP_ID" + echo " $ARTIFACT_ID" + echo " $POM_VERSION" + echo "" + echo '```' + echo "" echo "#### Published Native Classifiers" echo "" echo "| Classifier | Build runner | Artifact | SHA-256 | Status |" @@ -424,7 +441,13 @@ jobs: rollback-release: name: Roll back failed Java release preparation - needs: [prepare-release, build-windows-classifier, build-darwin-classifier, deploy-maven] + needs: + [ + prepare-release, + build-windows-classifier, + build-darwin-classifier, + deploy-maven, + ] if: ${{ failure() && needs.prepare-release.outputs.docs_commit != '' }} runs-on: ubuntu-latest permissions: diff --git a/.github/workflows/java-publish-snapshot.yml b/.github/workflows/java-publish-snapshot.yml index 6974deb1ca..3b67f7c1b1 100644 --- a/.github/workflows/java-publish-snapshot.yml +++ b/.github/workflows/java-publish-snapshot.yml @@ -264,12 +264,29 @@ jobs: node copilot-native/scripts/validate-native-artifact.mjs \ classifier linux-x64 "$LINUX_JAR" "$(basename "$LINUX_JAR")" .. LINUX_SHA=$(sha256sum "$LINUX_JAR" | cut -d ' ' -f 1) + GROUP_ID=$(mvn -q -pl copilot-native help:evaluate -Dexpression=project.groupId -DforceStdout) + ARTIFACT_ID=$(mvn -q -pl copilot-native help:evaluate -Dexpression=project.artifactId -DforceStdout) + POM_VERSION=$(mvn -q -pl copilot-native help:evaluate -Dexpression=project.version -DforceStdout) + if [ -z "$GROUP_ID" ] || [ -z "$ARTIFACT_ID" ] || [ "$POM_VERSION" != "$VERSION" ]; then + echo "::error::Unexpected copilot-native Maven coordinates: $GROUP_ID:$ARTIFACT_ID:$POM_VERSION (expected version $VERSION)" + exit 1 + fi { echo "### Snapshot Publish" echo "- **Version:** $VERSION" echo "- **Source commit:** \`${{ needs.resolve-source.outputs.source_sha }}\`" echo "- **Repository:** Maven Central Snapshots" echo "" + echo "#### Maven Coordinates" + echo "" + echo '```xml' + echo "" + echo " $GROUP_ID" + echo " $ARTIFACT_ID" + echo " $POM_VERSION" + echo "" + echo '```' + echo "" echo "#### Published Native Classifiers" echo "" echo "| Classifier | Build runner | Artifact | SHA-256 | Status |" From de17ac8ba101899eac555b4f70e4fb88eb00aeab Mon Sep 17 00:00:00 2001 From: Ed Burns Date: Wed, 26 Aug 2026 12:00:50 -0700 Subject: [PATCH 18/32] Test real turns over Java in-process transport (#2414) Add a replay-backed session round trip to InProcessTransportIT so the native transport covers authentication, tool initialization, model traffic, assistant responses, and idle event delivery. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 1e36f86f-389d-4ea7-8050-d11db997c7c7 --- .../copilot/e2e/InProcessTransportIT.java | 38 ++++++++++++++++--- 1 file changed, 32 insertions(+), 6 deletions(-) diff --git a/java/sdk/src/test/java/com/github/copilot/e2e/InProcessTransportIT.java b/java/sdk/src/test/java/com/github/copilot/e2e/InProcessTransportIT.java index 1b8595401a..23408faa2a 100644 --- a/java/sdk/src/test/java/com/github/copilot/e2e/InProcessTransportIT.java +++ b/java/sdk/src/test/java/com/github/copilot/e2e/InProcessTransportIT.java @@ -8,6 +8,8 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; @@ -15,23 +17,27 @@ import com.github.copilot.AllowCopilotExperimental; import com.github.copilot.CopilotClient; +import com.github.copilot.CopilotSession; import com.github.copilot.E2ETestContext; import com.github.copilot.ffi.InProcessEnvGuard; +import com.github.copilot.generated.AssistantMessageEvent; +import com.github.copilot.generated.SessionIdleEvent; import com.github.copilot.rpc.CopilotClientOptions; +import com.github.copilot.rpc.MessageOptions; +import com.github.copilot.rpc.PermissionHandler; import com.github.copilot.rpc.PingResponse; import com.github.copilot.rpc.RuntimeConnection; +import com.github.copilot.rpc.SessionConfig; /** * Failsafe integration test for the in-process (FFI) transport. * *

* Loads the real {@code runtime.node} native library into this test process via - * {@link com.github.copilot.ffi.FfiRuntimeHost}, performs a purely local - * {@code ping} round-trip through the runtime, and stops cleanly. {@code ping} - * is answered by the runtime itself, so no auth or replay proxy is involved — - * this mirrors {@code nodejs/test/e2e/inprocess_ffi.e2e.test.ts}, - * {@code go/internal/e2e/inprocess_ffi_e2e_test.go}, and - * {@code python/e2e/test_inprocess_ffi_e2e.py}. + * {@link com.github.copilot.ffi.FfiRuntimeHost}. Coverage includes both a + * purely local {@code ping} round-trip and a replay-backed session turn that + * exercises authentication, tool initialization, model traffic, and + * asynchronous session event delivery over the FFI transport. * *

* {@link InProcessEnvGuard} demonstrates how the harness redirects the native @@ -98,4 +104,24 @@ void shouldStartPingAndStopOverInProcessFfi() throws Exception { } } } + + @Test + void shouldCreateSessionAndCompleteTurnOverInProcessFfi() throws Exception { + ctx.configureForTest("session", "should_receive_session_events"); + + try (CopilotClient client = ctx.createClient(); + CopilotSession session = client + .createSession(new SessionConfig().setOnPermissionRequest(PermissionHandler.APPROVE_ALL)) + .get(30, TimeUnit.SECONDS)) { + var idleReceived = new CompletableFuture(); + session.on(SessionIdleEvent.class, idleReceived::complete); + + AssistantMessageEvent response = session.sendAndWait(new MessageOptions().setPrompt("What is 100+200?")) + .get(60, TimeUnit.SECONDS); + + assertNotNull(response); + assertEquals("100 + 200 = 300", response.getData().content()); + assertNotNull(idleReceived.get(5, TimeUnit.SECONDS)); + } + } } From f0a575aaad366e93e93349544d91035d0fb2e511 Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Wed, 26 Aug 2026 18:53:43 +0000 Subject: [PATCH 19/32] Stabilize Python and Java E2E harnesses (#2411) * fix(python): avoid nested PowerShell in shell RPC test Use the Windows shell's built-in echo command and a relative marker path so the test avoids a flaky child-process launch while continuing to validate shell execution and cwd handling. Ensure the session is disconnected on assertion failures. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * fix(python): await cold-resume lock release Observe the session lock from a separate runtime and wait for the TCP server to process the original client's disconnect before resuming. This preserves the cold-resume assertion without relying on timing. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * test(python): verify shell RPC working directory Run the marker command from a distinct subdirectory and use a relative filename on every platform so the test fails if shell.exec ignores cwd. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Stabilize Java E2E fixture handling Parse folded YAML prompt scalars correctly and share replay proxy HTTP resources across the monolithic test JVM. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../java/com/github/copilot/CapiProxy.java | 13 +- .../com/github/copilot/CapiProxyTest.java | 21 ++++ .../com/github/copilot/E2ETestContext.java | 116 +++++++++++++++--- .../github/copilot/E2ETestContextTest.java | 39 ++++++ python/e2e/test_pending_work_resume_e2e.py | 39 +++++- python/e2e/test_rpc_shell_and_fleet_e2e.py | 35 +++--- 6 files changed, 222 insertions(+), 41 deletions(-) create mode 100644 java/sdk/src/test/java/com/github/copilot/CapiProxyTest.java create mode 100644 java/sdk/src/test/java/com/github/copilot/E2ETestContextTest.java diff --git a/java/sdk/src/test/java/com/github/copilot/CapiProxy.java b/java/sdk/src/test/java/com/github/copilot/CapiProxy.java index 53d5e11666..3124cc3d6b 100644 --- a/java/sdk/src/test/java/com/github/copilot/CapiProxy.java +++ b/java/sdk/src/test/java/com/github/copilot/CapiProxy.java @@ -57,16 +57,15 @@ public class CapiProxy implements AutoCloseable { private static final ObjectMapper MAPPER = new ObjectMapper(); private static final Pattern LISTENING_PATTERN = Pattern.compile("Listening: (http://[^\\s]+)(?:\\s+(\\{.*\\}))?$"); + private static final HttpClient HTTP_CLIENT = HttpClient.newHttpClient(); private Process process; private String proxyUrl; private String connectProxyUrl; private String caFilePath; - private final HttpClient httpClient; private BufferedReader stdoutReader; public CapiProxy() { - this.httpClient = HttpClient.newHttpClient(); } /** @@ -212,7 +211,7 @@ public void configure(String filePath, String workDir, TestInfo testInfo) throws HttpRequest request = HttpRequest.newBuilder().uri(URI.create(proxyUrl + "/config")) .header("Content-Type", "application/json").POST(HttpRequest.BodyPublishers.ofString(body)).build(); - HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); + HttpResponse response = HTTP_CLIENT.send(request, HttpResponse.BodyHandlers.ofString()); if (response.statusCode() != 200) { throw new IOException("Proxy config failed with status " + response.statusCode() + ": " + response.body()); } @@ -234,7 +233,7 @@ public List> getExchanges() throws IOException, InterruptedE HttpRequest request = HttpRequest.newBuilder().uri(URI.create(proxyUrl + "/exchanges")).GET().build(); - HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); + HttpResponse response = HTTP_CLIENT.send(request, HttpResponse.BodyHandlers.ofString()); if (response.statusCode() != 200) { throw new IOException("Failed to get exchanges: " + response.statusCode()); } @@ -284,7 +283,7 @@ public void setCopilotUserByToken(String token, String login, String copilotPlan HttpRequest request = HttpRequest.newBuilder().uri(URI.create(proxyUrl + "/copilot-user-config")) .header("Content-Type", "application/json").POST(HttpRequest.BodyPublishers.ofString(body)).build(); - HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); + HttpResponse response = HTTP_CLIENT.send(request, HttpResponse.BodyHandlers.ofString()); if (response.statusCode() != 200) { throw new IOException( "Failed to set copilot user config: " + response.statusCode() + ": " + response.body()); @@ -331,7 +330,7 @@ public void setCopilotUserByToken(String token, Map response) HttpRequest request = HttpRequest.newBuilder().uri(URI.create(proxyUrl + "/copilot-user-config")) .header("Content-Type", "application/json").POST(HttpRequest.BodyPublishers.ofString(body)).build(); - HttpResponse response2 = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); + HttpResponse response2 = HTTP_CLIENT.send(request, HttpResponse.BodyHandlers.ofString()); if (response2.statusCode() != 200) { throw new IOException( "Failed to set copilot user config: " + response2.statusCode() + ": " + response2.body()); @@ -376,7 +375,7 @@ public void stop(boolean skipWritingCache) throws IOException, InterruptedExcept HttpRequest request = HttpRequest.newBuilder().uri(URI.create(stopUrl)) .POST(HttpRequest.BodyPublishers.noBody()).build(); - httpClient.send(request, HttpResponse.BodyHandlers.ofString()); + HTTP_CLIENT.send(request, HttpResponse.BodyHandlers.ofString()); } catch (Exception e) { // Best effort - ignore errors } diff --git a/java/sdk/src/test/java/com/github/copilot/CapiProxyTest.java b/java/sdk/src/test/java/com/github/copilot/CapiProxyTest.java new file mode 100644 index 0000000000..9ac044a2b8 --- /dev/null +++ b/java/sdk/src/test/java/com/github/copilot/CapiProxyTest.java @@ -0,0 +1,21 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +package com.github.copilot; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.lang.reflect.Modifier; + +import org.junit.jupiter.api.Test; + +class CapiProxyTest { + + @Test + void proxyInstancesShareHttpClientResources() throws Exception { + var field = CapiProxy.class.getDeclaredField("HTTP_CLIENT"); + assertTrue(Modifier.isStatic(field.getModifiers()), + "E2E contexts must not retain one HttpClient selector manager per test class"); + } +} diff --git a/java/sdk/src/test/java/com/github/copilot/E2ETestContext.java b/java/sdk/src/test/java/com/github/copilot/E2ETestContext.java index 60dcf1fa39..cb302a8cd2 100644 --- a/java/sdk/src/test/java/com/github/copilot/E2ETestContext.java +++ b/java/sdk/src/test/java/com/github/copilot/E2ETestContext.java @@ -65,8 +65,8 @@ public class E2ETestContext implements AutoCloseable { */ private static final String DEFAULT_GITHUB_TOKEN = "fake-token-for-e2e-tests"; private static final Pattern SNAKE_CASE = Pattern.compile("[^a-zA-Z0-9]"); - private static final Pattern USER_CONTENT_PATTERN = Pattern - .compile("^\\s+-\\s+role:\\s+user\\s*$\\s+content:\\s*(.+?)$", Pattern.MULTILINE); + private static final Pattern USER_ROLE_PATTERN = Pattern.compile("^(\\s*)-\\s+role:\\s+user\\s*$"); + private static final Pattern CONTENT_PATTERN = Pattern.compile("^(\\s*)content:\\s*(.*)$"); private final String cliPath; private final Path homeDir; @@ -227,25 +227,109 @@ public List getExpectedUserPrompts() { return List.of(); } try { - String content = Files.readString(currentSnapshotFile); - List prompts = new ArrayList<>(); - Matcher matcher = USER_CONTENT_PATTERN.matcher(content); - while (matcher.find()) { - String prompt = matcher.group(1).trim(); - // Remove quotes if present - if ((prompt.startsWith("\"") && prompt.endsWith("\"")) - || (prompt.startsWith("'") && prompt.endsWith("'"))) { - prompt = prompt.substring(1, prompt.length() - 1); + return parseExpectedUserPrompts(Files.readString(currentSnapshotFile)); + } catch (IOException e) { + LOG.warning("Failed to read snapshot file: " + e.getMessage()); + return List.of(); + } + } + + static List parseExpectedUserPrompts(String yaml) { + String[] lines = yaml.split("\\R", -1); + List prompts = new ArrayList<>(); + + for (int i = 0; i < lines.length; i++) { + Matcher roleMatcher = USER_ROLE_PATTERN.matcher(lines[i]); + if (!roleMatcher.matches()) { + continue; + } + + int roleIndent = roleMatcher.group(1).length(); + for (i++; i < lines.length; i++) { + String line = lines[i]; + if (!line.isBlank() && leadingWhitespace(line) <= roleIndent) { + i--; + break; } - if (!prompts.contains(prompt)) { + + Matcher contentMatcher = CONTENT_PATTERN.matcher(line); + if (!contentMatcher.matches()) { + continue; + } + + int contentIndent = contentMatcher.group(1).length(); + String value = contentMatcher.group(2).trim(); + List continuation = new ArrayList<>(); + while (i + 1 < lines.length + && (lines[i + 1].isBlank() || leadingWhitespace(lines[i + 1]) > contentIndent)) { + continuation.add(lines[++i]); + } + + String prompt = isBlockScalar(value) + ? parseBlockScalar(value.charAt(0), continuation) + : parsePlainScalar(value, continuation); + if (!prompt.isEmpty() && !prompts.contains(prompt)) { prompts.add(prompt); } + break; } - return prompts; - } catch (IOException e) { - LOG.warning("Failed to read snapshot file: " + e.getMessage()); - return List.of(); } + + return prompts; + } + + private static String parsePlainScalar(String firstLine, List continuation) { + StringBuilder value = new StringBuilder(unquote(firstLine)); + for (String line : continuation) { + if (!line.isBlank()) { + if (!value.isEmpty()) { + value.append(' '); + } + value.append(line.trim()); + } + } + return value.toString(); + } + + private static String parseBlockScalar(char style, List lines) { + int contentIndent = lines.stream().filter(line -> !line.isBlank()).mapToInt(E2ETestContext::leadingWhitespace) + .min().orElse(0); + StringBuilder value = new StringBuilder(); + boolean previousWasContent = false; + for (String line : lines) { + String text = line.isBlank() ? "" : line.substring(Math.min(contentIndent, line.length())); + if (text.isEmpty()) { + value.append('\n'); + previousWasContent = false; + } else { + if (previousWasContent) { + value.append(style == '>' ? ' ' : '\n'); + } + value.append(text); + previousWasContent = true; + } + } + return value.toString().stripTrailing(); + } + + private static boolean isBlockScalar(String value) { + return value.matches("[>|][+-]?"); + } + + private static String unquote(String value) { + if (value.length() >= 2 && ((value.startsWith("\"") && value.endsWith("\"")) + || (value.startsWith("'") && value.endsWith("'")))) { + return value.substring(1, value.length() - 1); + } + return value; + } + + private static int leadingWhitespace(String value) { + int index = 0; + while (index < value.length() && Character.isWhitespace(value.charAt(index))) { + index++; + } + return index; } /** diff --git a/java/sdk/src/test/java/com/github/copilot/E2ETestContextTest.java b/java/sdk/src/test/java/com/github/copilot/E2ETestContextTest.java new file mode 100644 index 0000000000..bf307f6547 --- /dev/null +++ b/java/sdk/src/test/java/com/github/copilot/E2ETestContextTest.java @@ -0,0 +1,39 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +package com.github.copilot; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.List; + +import org.junit.jupiter.api.Test; + +class E2ETestContextTest { + + @Test + void expectedUserPromptsParseFoldedBlockScalars() { + String snapshot = """ + conversations: + - messages: + - role: user + content: First prompt + continued here. + - role: assistant + content: response + - role: user + content: >- + + + Agent completed successfully. + + + """; + + assertEquals( + List.of("First prompt continued here.", + "\nAgent completed successfully.\n"), + E2ETestContext.parseExpectedUserPrompts(snapshot)); + } +} diff --git a/python/e2e/test_pending_work_resume_e2e.py b/python/e2e/test_pending_work_resume_e2e.py index 64c06c0421..5b6d978f31 100644 --- a/python/e2e/test_pending_work_resume_e2e.py +++ b/python/e2e/test_pending_work_resume_e2e.py @@ -20,11 +20,12 @@ HandlePendingToolCallRequest, PermissionDecisionRequest, PermissionDecisionUserNotAvailable, + SessionsCheckInUseRequest, ) from copilot.session import PermissionHandler from copilot.tools import Tool, ToolInvocation, ToolResult -from .testharness import DEFAULT_GITHUB_TOKEN, E2ETestContext +from .testharness import DEFAULT_GITHUB_TOKEN, E2ETestContext, wait_for_condition pytestmark = pytest.mark.asyncio(loop_scope="module") @@ -464,6 +465,7 @@ async def blocking_external_tool(args): await server.start() try: cli_url = f"localhost:{server.runtime_port}" + lock_observer: CopilotClient | None = None suspended_client = CopilotClient( connection=RuntimeConnection.for_uri( @@ -487,8 +489,41 @@ async def blocking_external_tool(args): assert (await asyncio.wait_for(tool_started, PENDING_WORK_TIMEOUT)) == "beta" if disconnect_original_client: + # force_stop closes the local socket before the server necessarily + # processes that disconnect. Observe the session lock from another + # runtime so resume cannot race the server's active-session cleanup. + lock_observer = _make_subprocess_client(ctx) + await lock_observer.start() + + async def session_lock_is_held() -> bool: + result = await lock_observer.rpc.sessions.check_in_use( + SessionsCheckInUseRequest(session_ids=[session_id]) + ) + return session_id in result.in_use + + await wait_for_condition( + session_lock_is_held, + timeout=PENDING_WORK_TIMEOUT, + timeout_message=( + f"Timed out waiting for session '{session_id}' to acquire its lock." + ), + ) await suspended_client.force_stop() + async def session_lock_is_released() -> bool: + result = await lock_observer.rpc.sessions.check_in_use( + SessionsCheckInUseRequest(session_ids=[session_id]) + ) + return session_id not in result.in_use + + await wait_for_condition( + session_lock_is_released, + timeout=PENDING_WORK_TIMEOUT, + timeout_message=( + f"Timed out waiting for session '{session_id}' to release its lock." + ), + ) + resumed_client = CopilotClient( connection=RuntimeConnection.for_uri( cli_url, connection_token="py-tcp-shared-test-token" @@ -550,6 +585,8 @@ async def resumed_external_tool(args): if not release_original.done(): release_original.set_result("ORIGINAL_SHOULD_NOT_WIN") await _safe_force_stop(suspended_client) + if lock_observer is not None: + await _safe_force_stop(lock_observer) finally: await _safe_force_stop(server) diff --git a/python/e2e/test_rpc_shell_and_fleet_e2e.py b/python/e2e/test_rpc_shell_and_fleet_e2e.py index d5a88456ab..422feab520 100644 --- a/python/e2e/test_rpc_shell_and_fleet_e2e.py +++ b/python/e2e/test_rpc_shell_and_fleet_e2e.py @@ -37,11 +37,10 @@ def _write_file_command(marker_path: Path, marker: str) -> str: if sys.platform == "win32": - return ( - f"powershell -NoLogo -NoProfile -Command " - f"\"Set-Content -LiteralPath '{marker_path}' -Value '{marker}'\"" - ) - return f"sh -c \"printf '%s' '{marker}' > '{marker_path}'\"" + # shell.exec already runs through cmd.exe on Windows. Use its built-in echo + # instead of spawning a nested PowerShell process just to write the marker. + return f'echo {marker}>"{marker_path.name}"' + return f"sh -c \"printf '%s' '{marker}' > '{marker_path.name}'\"" async def _wait_for_file_text(path: Path, expected: str, *, timeout: float = 30.0) -> None: @@ -57,19 +56,21 @@ async def _wait_for_file_text(path: Path, expected: str, *, timeout: float = 30. class TestRpcShellAndFleet: async def test_should_execute_shell_command(self, ctx: E2ETestContext): - session = await ctx.client.create_session( + async with await ctx.client.create_session( on_permission_request=PermissionHandler.approve_all, - ) - marker_path = Path(ctx.work_dir) / f"shell-rpc-{uuid.uuid4().hex}.txt" - marker = "copilot-sdk-shell-rpc" - - result = await session.rpc.shell.exec( - ShellExecRequest(command=_write_file_command(marker_path, marker), cwd=ctx.work_dir) - ) - assert (result.process_id or "").strip() - await _wait_for_file_text(marker_path, marker) - - await session.disconnect() + ) as session: + command_dir = Path(ctx.work_dir) / f"shell-rpc-{uuid.uuid4().hex}" + command_dir.mkdir() + marker_path = command_dir / "marker.txt" + marker = "copilot-sdk-shell-rpc" + + result = await session.rpc.shell.exec( + ShellExecRequest( + command=_write_file_command(marker_path, marker), cwd=str(command_dir) + ) + ) + assert (result.process_id or "").strip() + await _wait_for_file_text(marker_path, marker) async def test_should_kill_shell_process(self, ctx: E2ETestContext): session = await ctx.client.create_session( From 83c179a3a82096dc8e0f524c03dfa95140f497c8 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 26 Aug 2026 19:57:31 +0000 Subject: [PATCH 20/32] docs: update version references to 1.0.13-preview.1 --- java/README.md | 8 ++++---- java/sdk/jbang-example.java | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/java/README.md b/java/README.md index 02ffcdefff..1874dd4781 100644 --- a/java/README.md +++ b/java/README.md @@ -32,14 +32,14 @@ Replace `${copilot.sdk.version}` with the latest release from Maven Central. com.github copilot-sdk-java - 1.0.13-preview.0 + 1.0.13-preview.1 ``` ### Gradle ```groovy -implementation 'com.github:copilot-sdk-java:1.0.13-preview.0' +implementation 'com.github:copilot-sdk-java:1.0.13-preview.1' ``` #### Snapshot Builds @@ -58,7 +58,7 @@ Snapshot builds of the next development version are published to Maven Central S com.github copilot-sdk-java - 1.0.14-preview.0-SNAPSHOT + 1.0.14-preview.1-SNAPSHOT ``` @@ -67,7 +67,7 @@ Snapshot builds of the next development version are published to Maven Central S Replace `${copilot.sdk.version}` with the latest release from Maven Central. ```groovy -implementation 'com.github:copilot-sdk-java:1.0.14-preview.0-SNAPSHOT' +implementation 'com.github:copilot-sdk-java:1.0.14-preview.1-SNAPSHOT' ``` ## In-process mode (experimental) diff --git a/java/sdk/jbang-example.java b/java/sdk/jbang-example.java index 49a84cb0ac..cf091d3c12 100644 --- a/java/sdk/jbang-example.java +++ b/java/sdk/jbang-example.java @@ -1,5 +1,5 @@ ///usr/bin/env jbang "$0" "$@" ; exit $? -//DEPS com.github:copilot-sdk-java:1.0.13-preview.0 +//DEPS com.github:copilot-sdk-java:1.0.13-preview.1 import com.github.copilot.CopilotClient; import com.github.copilot.generated.AssistantMessageEvent; import com.github.copilot.generated.SessionUsageInfoEvent; From 69c503a80d24020e79bd4fb2f5bb179da92e0075 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 26 Aug 2026 19:57:59 +0000 Subject: [PATCH 21/32] [maven-release-plugin] prepare release java/v1.0.13-preview.1 --- java/copilot-native/pom.xml | 4 ++-- java/pom.xml | 4 ++-- java/sdk/pom.xml | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/java/copilot-native/pom.xml b/java/copilot-native/pom.xml index bdc3edca92..12d534437d 100644 --- a/java/copilot-native/pom.xml +++ b/java/copilot-native/pom.xml @@ -8,7 +8,7 @@ com.github copilot-sdk-java-parent - 1.0.14-preview.0-SNAPSHOT + 1.0.13-preview.1 ../pom.xml @@ -24,7 +24,7 @@ scm:git:https://github.com/github/copilot-sdk.git scm:git:https://github.com/github/copilot-sdk.git https://github.com/github/copilot-sdk - HEAD + java/v1.0.13-preview.1 diff --git a/java/pom.xml b/java/pom.xml index 9d6ac59a6d..c14f116659 100644 --- a/java/pom.xml +++ b/java/pom.xml @@ -7,7 +7,7 @@ com.github copilot-sdk-java-parent - 1.0.14-preview.0-SNAPSHOT + 1.0.13-preview.1 pom GitHub Copilot SDK :: Java :: Parent @@ -33,7 +33,7 @@ scm:git:https://github.com/github/copilot-sdk.git scm:git:https://github.com/github/copilot-sdk.git https://github.com/github/copilot-sdk - HEAD + java/v1.0.13-preview.1 diff --git a/java/sdk/pom.xml b/java/sdk/pom.xml index 2dd1ac2d64..688eb0f4c7 100644 --- a/java/sdk/pom.xml +++ b/java/sdk/pom.xml @@ -8,7 +8,7 @@ com.github copilot-sdk-java-parent - 1.0.14-preview.0-SNAPSHOT + 1.0.13-preview.1 ../pom.xml @@ -24,7 +24,7 @@ scm:git:https://github.com/github/copilot-sdk.git scm:git:https://github.com/github/copilot-sdk.git https://github.com/github/copilot-sdk - HEAD + java/v1.0.13-preview.1 From f0e388bf844fb377daae34e6841a5096020427a4 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 26 Aug 2026 19:58:04 +0000 Subject: [PATCH 22/32] [maven-release-plugin] prepare for next development iteration --- java/copilot-native/pom.xml | 4 ++-- java/pom.xml | 4 ++-- java/sdk/pom.xml | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/java/copilot-native/pom.xml b/java/copilot-native/pom.xml index 12d534437d..4ad0e02929 100644 --- a/java/copilot-native/pom.xml +++ b/java/copilot-native/pom.xml @@ -8,7 +8,7 @@ com.github copilot-sdk-java-parent - 1.0.13-preview.1 + 1.0.14-preview.1-SNAPSHOT ../pom.xml @@ -24,7 +24,7 @@ scm:git:https://github.com/github/copilot-sdk.git scm:git:https://github.com/github/copilot-sdk.git https://github.com/github/copilot-sdk - java/v1.0.13-preview.1 + HEAD diff --git a/java/pom.xml b/java/pom.xml index c14f116659..358eb64985 100644 --- a/java/pom.xml +++ b/java/pom.xml @@ -7,7 +7,7 @@ com.github copilot-sdk-java-parent - 1.0.13-preview.1 + 1.0.14-preview.1-SNAPSHOT pom GitHub Copilot SDK :: Java :: Parent @@ -33,7 +33,7 @@ scm:git:https://github.com/github/copilot-sdk.git scm:git:https://github.com/github/copilot-sdk.git https://github.com/github/copilot-sdk - java/v1.0.13-preview.1 + HEAD diff --git a/java/sdk/pom.xml b/java/sdk/pom.xml index 688eb0f4c7..905647fbed 100644 --- a/java/sdk/pom.xml +++ b/java/sdk/pom.xml @@ -8,7 +8,7 @@ com.github copilot-sdk-java-parent - 1.0.13-preview.1 + 1.0.14-preview.1-SNAPSHOT ../pom.xml @@ -24,7 +24,7 @@ scm:git:https://github.com/github/copilot-sdk.git scm:git:https://github.com/github/copilot-sdk.git https://github.com/github/copilot-sdk - java/v1.0.13-preview.1 + HEAD From 29141a4cc779191f9b292a280daaddd3597cacac Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Wed, 26 Aug 2026 20:02:09 +0000 Subject: [PATCH 23/32] Add session-scoped GitHub token providers (#2412) * Add session GitHub token providers Expose lifecycle-safe GitHub credential callbacks across all six SDKs, with idiomatic APIs, tagged results, tests, and documentation. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: a447a8bc-8687-47ea-a545-4370979e8128 * Clarify GitHub token provider lifecycle Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: a447a8bc-8687-47ea-a545-4370979e8128 * Fix GitHub token provider cleanup Release session-owned provider registrations after successful session deletion and treat empty static .NET tokens as configured for mutual-exclusion validation. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: a447a8bc-8687-47ea-a545-4370979e8128 * Fix Rust import formatting Order the new GitHub token re-export according to the nightly rustfmt configuration used by CI. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: a447a8bc-8687-47ea-a545-4370979e8128 --------- Copilot-Session: a447a8bc-8687-47ea-a545-4370979e8128 --- CHANGELOG.md | 8 + docs/auth/authenticate.md | 125 ++++++ docs/setup/multi-tenancy.md | 4 +- dotnet/README.md | 19 + dotnet/src/Client.cs | 178 +++++++-- dotnet/src/GitHubTokenProvider.cs | 77 ++++ dotnet/src/JsonRpc.cs | 26 +- dotnet/src/Session.cs | 15 + dotnet/src/Types.cs | 11 + .../test/Unit/ClientSessionLifetimeTests.cs | 258 +++++++++++- go/README.md | 19 + go/client.go | 266 +++++++++--- go/github_token_provider.go | 84 ++++ go/github_token_provider_test.go | 344 ++++++++++++++++ go/session.go | 108 +++-- go/types.go | 8 + java/README.md | 19 + .../com/github/copilot/CopilotClient.java | 61 ++- .../com/github/copilot/CopilotSession.java | 14 + .../copilot/GitHubTokenProviderRegistry.java | 79 ++++ .../com/github/copilot/JsonRpcClient.java | 35 +- .../github/copilot/RpcHandlerDispatcher.java | 74 +++- .../copilot/rpc/CreateSessionRequest.java | 18 + .../copilot/rpc/GitHubTokenProvider.java | 31 ++ .../copilot/rpc/GitHubTokenProviderArgs.java | 22 + .../rpc/GitHubTokenProviderResult.java | 113 ++++++ .../copilot/rpc/ResumeSessionConfig.java | 30 ++ .../copilot/rpc/ResumeSessionRequest.java | 18 + .../com/github/copilot/rpc/SessionConfig.java | 39 +- .../com/github/copilot/CopilotClientTest.java | 31 ++ .../GitHubTokenProviderRegistryTest.java | 74 ++++ .../com/github/copilot/JsonRpcClientTest.java | 16 + .../copilot/RpcHandlerDispatcherTest.java | 59 ++- .../copilot/SessionRequestBuilderTest.java | 22 + nodejs/README.md | 13 + nodejs/src/client.ts | 120 +++++- nodejs/src/index.ts | 5 + nodejs/src/session.ts | 23 +- nodejs/src/types.ts | 40 ++ nodejs/test/github-token-provider.test.ts | 292 ++++++++++++++ python/README.md | 16 + python/copilot/__init__.py | 16 + python/copilot/client.py | 203 +++++++++- python/copilot/session.py | 13 + python/test_github_token_provider.py | 273 +++++++++++++ rust/README.md | 28 ++ rust/src/errors.rs | 3 + rust/src/github_token.rs | 378 ++++++++++++++++++ rust/src/lib.rs | 37 ++ rust/src/router.rs | 5 + rust/src/session.rs | 30 ++ rust/src/types.rs | 71 +++- rust/src/wire.rs | 10 + rust/tests/session_test.rs | 225 +++++++++++ 54 files changed, 3931 insertions(+), 175 deletions(-) create mode 100644 dotnet/src/GitHubTokenProvider.cs create mode 100644 go/github_token_provider.go create mode 100644 go/github_token_provider_test.go create mode 100644 java/sdk/src/main/java/com/github/copilot/GitHubTokenProviderRegistry.java create mode 100644 java/sdk/src/main/java/com/github/copilot/rpc/GitHubTokenProvider.java create mode 100644 java/sdk/src/main/java/com/github/copilot/rpc/GitHubTokenProviderArgs.java create mode 100644 java/sdk/src/main/java/com/github/copilot/rpc/GitHubTokenProviderResult.java create mode 100644 java/sdk/src/test/java/com/github/copilot/GitHubTokenProviderRegistryTest.java create mode 100644 nodejs/test/github-token-provider.test.ts create mode 100644 python/test_github_token_provider.py create mode 100644 rust/src/github_token.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index a5974c67d7..d695ed6fb6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,14 @@ See [GitHub Releases](https://github.com/github/copilot-sdk/releases) for the fu ## [Unreleased] +### Feature: rotating session-scoped GitHub credentials + +All six SDKs can now acquire short-lived GitHub credentials through a session-scoped callback. The SDK registers the callback before session create or resume, maps `initial` and `refresh` requests to the owning session, and removes registrations on rollback, replacement, session close, and client close. Static per-session `gitHubToken` credentials remain supported and are mutually exclusive with the callback. + +Token responses use the shared tagged token/cancelled shape and require `expiresIn`, expressed as the positive number of seconds remaining when the callback completes. See [github/copilot-agent-runtime#16381](https://github.com/github/copilot-agent-runtime/pull/16381) for the runtime credential-authority implementation. + +Initial acquisition occurs during create or resume; cancellation, callback errors, and invalid credentials reject that operation instead of falling back to ambient authentication. Idle sessions refresh only before their next credential-consuming operation. + ### Feature: extensions can request sensitive environment variables Copilot CLI extensions can now ask for named sensitive environment variables when they join a session. `joinSession()` accepts a `requestedEnvironmentVariables` option listing the variable names the extension needs. The CLI shows a permission prompt naming the extension and the exact variables requested. On approval, only those variables reach that extension and their values are written into the extension process's `process.env` before `joinSession()` resolves. On denial, `joinSession()` rejects, the extension does not load, and its tools never reach the model. diff --git a/docs/auth/authenticate.md b/docs/auth/authenticate.md index 2fc80dd0aa..a7582b5510 100644 --- a/docs/auth/authenticate.md +++ b/docs/auth/authenticate.md @@ -262,6 +262,131 @@ const client = new CopilotClient({ For more information, see [GitHub OAuth](../setup/github-oauth.md). +## Rotating session-scoped GitHub tokens + +For multi-user services and integrations, set a token provider on each session instead of storing one long-lived token. The runtime calls the provider for the effective GitHub host and identifies the request as `initial` or `refresh`. The session ID is absent only when a cloud session has not received its ID yet. + +Return a tagged token result or an explicit cancellation. Every token result must include `expiresIn`: the positive number of seconds remaining when the callback completes. Production GitHub tokens typically last eight hours, so `8 * 60 * 60` is a common value. Do not set both the static per-session token and the provider. + +

+TypeScript + + +```typescript +const session = await client.createSession({ + gitHubTokenProvider: async ({ host, sessionId, reason }) => { + const token = await acquireGitHubToken({ host, sessionId, reason }); + return { + kind: "token", + accessToken: token.value, + expiresIn: token.secondsRemaining, + }; + }, +}); +``` + +
+
+Python + + +```python +async def provide_github_token(args): + token = await acquire_github_token( + host=args["host"], + session_id=args["session_id"], + reason=args["reason"], + ) + return { + "kind": "token", + "accessToken": token.value, + "expiresIn": token.seconds_remaining, + } + + +session = await client.create_session(github_token_provider=provide_github_token) +``` + +
+
+Go + + +```go +session, err := client.CreateSession(ctx, &copilot.SessionConfig{ + GitHubTokenProvider: func(args copilot.GitHubTokenProviderArgs) (*copilot.GitHubTokenProviderResult, error) { + token, secondsRemaining, err := acquireGitHubToken(args.Host, args.SessionID, args.Reason) + if err != nil { + return nil, err + } + return copilot.GitHubTokenResult(&copilot.GitHubToken{ + AccessToken: token, + ExpiresIn: secondsRemaining, + }), nil + }, +}) +``` + +
+
+.NET + + +```csharp +await using var session = await client.CreateSessionAsync(new SessionConfig +{ + GitHubTokenProvider = async args => + { + var token = await AcquireGitHubTokenAsync(args.Host, args.SessionId, args.Reason); + return GitHubTokenProviderResult.FromToken(new GitHubToken + { + AccessToken = token.Value, + ExpiresIn = token.SecondsRemaining, + }); + }, +}); +``` + +
+
+Java + + +```java +var session = client.createSession(new SessionConfig() + .setGitHubTokenProvider(args -> + acquireGitHubToken(args.host(), args.sessionId(), args.reason()) + .thenApply(token -> GitHubTokenProviderResult.token( + token.value(), token.secondsRemaining()))) + .setOnPermissionRequest(PermissionHandler.APPROVE_ALL) +).get(); +``` + +
+
+Rust + + +```rust +let provider = Arc::new(|args: GitHubTokenProviderArgs| async move { + let token = acquire_github_token(&args.host, args.session_id.as_ref(), args.reason).await?; + Ok(GitHubTokenProviderResult::Token(GitHubToken::new( + token.value, + token.seconds_remaining, + ))) +}); + +let session = client + .create_session(SessionConfig::default().with_github_token_provider(provider)) + .await?; +``` + +
+ +The runtime performs the `initial` acquisition as part of session creation or resume. A cancelled acquisition, provider error, invalid response, or token without a stable account identity rejects the create or resume operation. The runtime does not fall back to ambient authentication. + +After the session is established, the runtime performs async preflight before each credential-consuming operation. It requests a `refresh` when the current token has one hour or less remaining. Idle sessions are not refreshed until their next credential-consuming operation. The runtime does not use background timers, rejection-driven replay, 401/403 challenge propagation, or upscope for this callback. + ## Environment variables For automation, CI/CD pipelines, and server-to-server scenarios, you can authenticate using environment variables. diff --git a/docs/setup/multi-tenancy.md b/docs/setup/multi-tenancy.md index 3bc9b6c748..ebbafe4fb0 100644 --- a/docs/setup/multi-tenancy.md +++ b/docs/setup/multi-tenancy.md @@ -24,7 +24,9 @@ This guide is a sister to [Scaling and multi-tenancy](./scaling.md). Use that gu | `baseDirectory` | Isolating `COPILOT_HOME` per runtime instance | Ignored when connecting to an existing runtime. | | `sessionFs` | Routing session filesystem storage off local disk | Pair with per-session filesystem providers. | | `RuntimeConnection.forUri(url)` | Sharing one already-running runtime | Language names vary; see samples below. | -| Per-session `gitHubToken` | Scoping auth to the requesting user | Prefer this over a single shared user token. | +| Per-session GitHub token or provider | Scoping auth to the requesting user | Prefer a rotating provider for short-lived credentials; use a static `gitHubToken` only when rotation is unnecessary. | + +For callback-backed credentials, see [Rotating session-scoped GitHub tokens](../auth/authenticate.md#rotating-session-scoped-github-tokens). Each session owns its provider registration, so concurrent sessions can use different GitHub hosts and accounts without sharing callback state. ### `mode: "empty"` diff --git a/dotnet/README.md b/dotnet/README.md index 6efd6e094c..461ff0cf94 100644 --- a/dotnet/README.md +++ b/dotnet/README.md @@ -133,6 +133,7 @@ Create a new conversation session. - `InfiniteSessions` - Configure automatic context compaction (see below) - `WorkingDirectory` - Working directory for the session. When not set, the runtime uses its own process working directory. - `EnableSessionStore` - Enables the cross-session store for search and retrieval across sessions. When unset in `CopilotClientMode.CopilotCli`, the runtime default applies (enabled). In `CopilotClientMode.Empty`, defaults to disabled. +- `GitHubTokenProvider` - Acquires session-scoped GitHub tokens on demand. Return `GitHubTokenProviderResult.FromToken` with a positive `ExpiresIn` value (production GitHub tokens typically use `8 * 60 * 60` seconds), or `GitHubTokenProviderResult.Cancel()`. Cannot be combined with `GitHubToken`. - `OnPermissionRequest` - Optional handler called before each tool execution to approve or deny it. When omitted, permission requests are emitted as events and left pending for manual resolution. `PermissionHandler.ApproveAll` approves requests when managed settings are disabled and throws when `EnableManagedSettings` is true. Custom handlers can inspect `ManagedApprovalRequired` for human-facing confirmation logic. See [Permission Handling](#permission-handling) section. - `OnUserInputRequest` - Handler for user input requests from the agent (enables ask_user tool). See [User Input Requests](#user-input-requests) section. - `Hooks` - Hook handlers for session lifecycle events. See [Session Hooks](#session-hooks) section. @@ -144,6 +145,24 @@ Resume an existing session. Returns the session with `WorkspacePath` populated i **ResumeSessionConfig:** - `OnPermissionRequest` - Optional handler called before each tool execution to approve or deny it. See [Permission Handling](#permission-handling) section. +- `GitHubTokenProvider` - Replaces the session-scoped token provider when resuming. Cannot be combined with `GitHubToken`. + +```csharp +await using var session = await client.CreateSessionAsync(new SessionConfig +{ + GitHubTokenProvider = async args => + { + var token = await AcquireTokenAsync(args.Host); + return GitHubTokenProviderResult.FromToken(new GitHubToken + { + AccessToken = token, + ExpiresIn = 8 * 60 * 60 + }); + } +}); +``` + +Initial acquisition runs during session creation or resume. Cancellation, provider errors, and invalid token responses reject that operation instead of falling back to ambient authentication. Idle sessions refresh only before their next credential-consuming operation; there is no background refresh timer. ##### `PingAsync(string? message = null): Task` diff --git a/dotnet/src/Client.cs b/dotnet/src/Client.cs index edd9f92738..f2da0a48f7 100644 --- a/dotnet/src/Client.cs +++ b/dotnet/src/Client.cs @@ -70,6 +70,7 @@ public sealed partial class CopilotClient : IDisposable, IAsyncDisposable /// that has not been explicitly disposed or removed. /// internal readonly ConcurrentDictionary _sessions = new(); + private readonly ConcurrentDictionary>> _gitHubTokenProviders = new(); private readonly CopilotClientOptions _options; private readonly RuntimeConnection _connection; @@ -91,8 +92,7 @@ public sealed partial class CopilotClient : IDisposable, IAsyncDisposable /// /// Client-global RPC handlers (e.g. the LLM inference provider adapter), - /// built once at construction when the corresponding option is configured and - /// registered on every connection. Null when no client-global API is enabled. + /// built once at construction and registered on every connection. /// private readonly ClientGlobalApiHandlers? _clientGlobalApis; @@ -541,6 +541,7 @@ public async Task StopAsync() } _sessions.Clear(); + ClearGitHubTokenProviders(); await CleanupConnectionAsync(errors, gracefulRuntimeShutdown: true); @@ -572,6 +573,7 @@ public async Task StopAsync() public async Task ForceStopAsync() { _sessions.Clear(); + ClearGitHubTokenProviders(); var errors = new List(); await CleanupConnectionAsync(errors, gracefulRuntimeShutdown: false); @@ -1124,6 +1126,7 @@ await session.Rpc.Options.UpdateAsync( public async Task CreateSessionAsync(SessionConfig config, CancellationToken cancellationToken = default) { ArgumentNullException.ThrowIfNull(config); + ValidateGitHubTokenConfig(config); var connection = await EnsureConnectedAsync(cancellationToken); var totalTimestamp = Stopwatch.GetTimestamp(); @@ -1158,19 +1161,22 @@ public async Task CreateSessionAsync(SessionConfig config, Cance ? null : (string.IsNullOrEmpty(config.SessionId) ? Guid.NewGuid().ToString() : config.SessionId); + var registrationId = RegisterGitHubTokenProvider(config.GitHubTokenProvider); + var registrationTransferred = false; CopilotSession? session = null; - if (localSessionId != null) - { - session = InitializeSession( - localSessionId, - connection.Rpc, - config, - transformCallbacks, - hasHooks, - "CopilotClient.CreateSessionAsync"); - } try { + if (localSessionId != null) + { + session = InitializeSession( + localSessionId, + connection.Rpc, + config, + transformCallbacks, + hasHooks, + "CopilotClient.CreateSessionAsync"); + } + var (traceparent, tracestate) = TelemetryHelpers.GetTraceContext(); var request = new CreateSessionRequest( @@ -1227,6 +1233,7 @@ public async Task CreateSessionAsync(SessionConfig config, Cance Tracestate: tracestate, ModelCapabilities: config.ModelCapabilities, GitHubToken: config.GitHubToken, + GitHubTokenProviderRegistrationId: registrationId, RemoteSession: config.RemoteSession, Cloud: config.Cloud, InstructionDirectories: config.InstructionDirectories, @@ -1306,6 +1313,11 @@ public async Task CreateSessionAsync(SessionConfig config, Cance session.SetOpenCanvases(response.OpenCanvases); await UpdateSessionOptionsForModeAsync(session, config, cancellationToken).ConfigureAwait(false); + if (registrationId is not null) + { + session.SetGitHubTokenProviderRegistration(registrationId); + registrationTransferred = true; + } } catch (Exception ex) { @@ -1321,6 +1333,13 @@ public async Task CreateSessionAsync(SessionConfig config, Cance throw; } + finally + { + if (!registrationTransferred && registrationId is not null) + { + UnregisterGitHubTokenProvider(registrationId); + } + } LoggingHelpers.LogTiming(_logger, LogLevel.Debug, null, "CopilotClient.CreateSessionAsync complete. Elapsed={Elapsed}, SessionId={SessionId}", @@ -1358,6 +1377,7 @@ public async Task ResumeSessionAsync(string sessionId, ResumeSes { ArgumentNullException.ThrowIfNull(sessionId); ArgumentNullException.ThrowIfNull(config); + ValidateGitHubTokenConfig(config); var connection = await EnsureConnectedAsync(cancellationToken); var totalTimestamp = Stopwatch.GetTimestamp(); @@ -1380,17 +1400,21 @@ public async Task ResumeSessionAsync(string sessionId, ResumeSes var (wireSystemMessage, transformCallbacks) = ExtractTransformCallbacks(config.SystemMessage); - // Create and register the session before issuing the RPC so that - // events emitted by the CLI (e.g. session.start) are not dropped. - var session = InitializeSession( - sessionId, - connection.Rpc, - config, - transformCallbacks, - hasHooks, - "CopilotClient.ResumeSessionAsync"); + var registrationId = RegisterGitHubTokenProvider(config.GitHubTokenProvider); + var registrationTransferred = false; + CopilotSession? session = null; try { + // Create and register the session before issuing the RPC so that + // events emitted by the CLI (e.g. session.start) are not dropped. + session = InitializeSession( + sessionId, + connection.Rpc, + config, + transformCallbacks, + hasHooks, + "CopilotClient.ResumeSessionAsync"); + var (traceparent, tracestate) = TelemetryHelpers.GetTraceContext(); var request = new ResumeSessionRequest( @@ -1448,6 +1472,7 @@ public async Task ResumeSessionAsync(string sessionId, ResumeSes Tracestate: tracestate, ModelCapabilities: config.ModelCapabilities, GitHubToken: config.GitHubToken, + GitHubTokenProviderRegistrationId: registrationId, RemoteSession: config.RemoteSession, ContinuePendingWork: config.ContinuePendingWork, InstructionDirectories: config.InstructionDirectories, @@ -1491,10 +1516,15 @@ public async Task ResumeSessionAsync(string sessionId, ResumeSes } await UpdateSessionOptionsForModeAsync(session, config, cancellationToken).ConfigureAwait(false); + if (registrationId is not null) + { + session.SetGitHubTokenProviderRegistration(registrationId); + registrationTransferred = true; + } } catch (Exception ex) { - session.RemoveFromClient(); + session?.RemoveFromClient(); if (ex is not OperationCanceledException) { LoggingHelpers.LogTiming(_logger, LogLevel.Warning, ex, @@ -1504,12 +1534,19 @@ public async Task ResumeSessionAsync(string sessionId, ResumeSes } throw; } + finally + { + if (!registrationTransferred && registrationId is not null) + { + UnregisterGitHubTokenProvider(registrationId); + } + } LoggingHelpers.LogTiming(_logger, LogLevel.Debug, null, "CopilotClient.ResumeSessionAsync complete. Elapsed={Elapsed}, SessionId={SessionId}", totalTimestamp, sessionId); - return session; + return session!; } /// @@ -1669,7 +1706,10 @@ public async Task DeleteSessionAsync(string sessionId, CancellationToken cancell throw new InvalidOperationException($"Failed to delete session {sessionId}: {response.Error}"); } - RemoveSession(sessionId); + if (_sessions.TryRemove(sessionId, out var session)) + { + session.ReleaseGitHubTokenProviderRegistration(); + } } /// @@ -1951,25 +1991,94 @@ await Rpc.SessionFs.SetProviderAsync( /// /// Builds the client-global RPC handler bag at construction time. Registers /// the LLM inference provider adapter and/or the GitHub telemetry adapter - /// depending on which options are configured; returns null when no - /// client-global API is configured so the registration is skipped entirely. + /// depending on which options are configured. The GitHub token dispatcher is + /// always registered because providers are configured per session. /// private ClientGlobalApiHandlers? BuildClientGlobalApis() { var handler = _options.RequestHandler; var onGitHubTelemetry = _options.OnGitHubTelemetry; - if (handler is null && onGitHubTelemetry is null) - { - return null; - } - return new ClientGlobalApiHandlers { LlmInference = handler is null ? null : new LlmInferenceAdapter(handler, () => _serverRpc), GitHubTelemetry = onGitHubTelemetry is null ? null : new GitHubTelemetryAdapter(onGitHubTelemetry, _logger), + GitHubToken = new GitHubTokenAdapter(this), }; } + private static void ValidateGitHubTokenConfig(SessionConfigBase config) + { + if (config.GitHubToken is not null && config.GitHubTokenProvider is not null) + { + throw new ArgumentException( + $"{nameof(SessionConfigBase.GitHubToken)} and {nameof(SessionConfigBase.GitHubTokenProvider)} cannot be used together.", + nameof(config)); + } + } + + private string? RegisterGitHubTokenProvider( + Func>? provider) + { + if (provider is null) + { + return null; + } + + var registrationId = Guid.NewGuid().ToString(); + if (!_gitHubTokenProviders.TryAdd(registrationId, provider)) + { + throw new InvalidOperationException("Failed to register GitHub token provider."); + } + return registrationId; + } + + internal void UnregisterGitHubTokenProvider(string registrationId) + => _gitHubTokenProviders.TryRemove(registrationId, out _); + + private void ClearGitHubTokenProviders() => _gitHubTokenProviders.Clear(); + + private sealed class GitHubTokenAdapter(CopilotClient client) : IGitHubTokenHandler + { + public async Task GetTokenAsync( + GitHubTokenAcquireRequest request, + CancellationToken cancellationToken = default) + { + if (!client._gitHubTokenProviders.TryGetValue(request.RegistrationId, out var provider)) + { + throw new InvalidOperationException( + $"Unknown GitHub token provider registration ID '{request.RegistrationId}'."); + } + + var reason = request.Reason == GitHubTokenAcquireReason.Initial + ? GitHubTokenRequestReason.Initial + : request.Reason == GitHubTokenAcquireReason.Refresh + ? GitHubTokenRequestReason.Refresh + : throw new InvalidOperationException($"Unknown GitHub token request reason '{request.Reason}'."); + var result = await provider(new GitHubTokenProviderArgs + { + Host = request.Host, + SessionId = request.SessionId, + Reason = reason, + }).ConfigureAwait(false); + + if (result is { Cancelled: true }) + { + return new GitHubTokenAcquireResultCancelled(); + } + if (result?.Token is not { } token) + { + throw new InvalidOperationException( + "GitHub token provider returned neither a token nor cancellation."); + } + return new GitHubTokenAcquireResultToken + { + AccessToken = token.AccessToken, + TokenType = token.TokenType, + ExpiresIn = token.ExpiresIn, + }; + } + } + /// /// Tells the runtime to route its outbound model-layer requests through this /// client's LLM inference provider. No-op when interception is not configured. @@ -2547,11 +2656,6 @@ private void RegisterSession(CopilotSession session) } } - private void RemoveSession(string sessionId) - { - _sessions.TryRemove(sessionId, out _); - } - /// /// Disposes the synchronously. /// @@ -2807,6 +2911,7 @@ internal record CreateSessionRequest( string? Tracestate = null, ModelCapabilitiesOverride? ModelCapabilities = null, string? GitHubToken = null, + [property: JsonPropertyName("gitHubTokenProviderRegistrationId")] string? GitHubTokenProviderRegistrationId = null, RemoteSessionMode? RemoteSession = null, CloudSessionOptions? Cloud = null, IList? InstructionDirectories = null, @@ -2922,6 +3027,7 @@ internal record ResumeSessionRequest( string? Tracestate = null, ModelCapabilitiesOverride? ModelCapabilities = null, string? GitHubToken = null, + [property: JsonPropertyName("gitHubTokenProviderRegistrationId")] string? GitHubTokenProviderRegistrationId = null, RemoteSessionMode? RemoteSession = null, bool? ContinuePendingWork = null, IList? InstructionDirectories = null, diff --git a/dotnet/src/GitHubTokenProvider.cs b/dotnet/src/GitHubTokenProvider.cs new file mode 100644 index 0000000000..3a79d09662 --- /dev/null +++ b/dotnet/src/GitHubTokenProvider.cs @@ -0,0 +1,77 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +using System.Diagnostics.CodeAnalysis; + +namespace GitHub.Copilot; + +/// Why the runtime is requesting a GitHub token. +[Experimental(Diagnostics.Experimental)] +public enum GitHubTokenRequestReason +{ + /// The session needs its initial token. + Initial, + + /// The session needs a refreshed token. + Refresh, +} + +/// Arguments passed to a session-scoped GitHub token provider. +[Experimental(Diagnostics.Experimental)] +public sealed class GitHubTokenProviderArgs +{ + /// Gets the effective GitHub host for which a token is needed. + public required string Host { get; init; } + + /// + /// Gets the session receiving the token, or before a + /// cloud session has been assigned an identifier. + /// + public string? SessionId { get; init; } + + /// Gets whether the runtime needs an initial or refreshed token. + public required GitHubTokenRequestReason Reason { get; init; } +} + +/// A GitHub access token returned by a session-scoped provider. +[Experimental(Diagnostics.Experimental)] +public sealed class GitHubToken +{ + /// Gets or sets the GitHub access token. + public required string AccessToken { get; set; } + + /// Gets or sets the OAuth token type. The runtime defaults it to bearer. + public string? TokenType { get; set; } + + /// + /// Gets or sets the required positive number of seconds remaining when the + /// callback completes. Production GitHub tokens typically last eight hours. + /// + public required long ExpiresIn { get; set; } + + /// + public override string ToString() + => $"{nameof(GitHubToken)} {{ {nameof(TokenType)} = {TokenType}, {nameof(ExpiresIn)} = {ExpiresIn}, {nameof(AccessToken)} = }}"; +} + +/// The result returned by a session-scoped GitHub token provider. +[Experimental(Diagnostics.Experimental)] +public sealed class GitHubTokenProviderResult +{ + /// Gets whether token acquisition was cancelled. + public bool Cancelled { get; private init; } + + /// Gets the acquired token, if acquisition was not cancelled. + public GitHubToken? Token { get; private init; } + + /// Creates a successful token result. + public static GitHubTokenProviderResult FromToken(GitHubToken token) + { + ArgumentNullException.ThrowIfNull(token); + return new() { Token = token }; + } + + /// Creates a cancelled result. + public static GitHubTokenProviderResult Cancel() => new() { Cancelled = true }; +} diff --git a/dotnet/src/JsonRpc.cs b/dotnet/src/JsonRpc.cs index 36289d2e68..c5c444ea70 100644 --- a/dotnet/src/JsonRpc.cs +++ b/dotnet/src/JsonRpc.cs @@ -548,7 +548,11 @@ private async Task HandleIncomingMethodAsync(string methodName, JsonElement mess if (requestId.HasValue) { - await SendResultResponseAsync(requestId.Value, result, cancellationToken).ConfigureAwait(false); + await SendResultResponseAsync( + requestId.Value, + result, + registration.ResultType, + cancellationToken).ConfigureAwait(false); } } catch (Exception ex) when (ex is not OperationCanceledException) @@ -772,18 +776,20 @@ private static bool TryGetPropertyCaseInsensitive(JsonElement obj, string name, return doc.RootElement.Clone(); } - private async Task SendResultResponseAsync(JsonElement id, object? result, CancellationToken cancellationToken) + private async Task SendResultResponseAsync( + JsonElement id, + object? result, + Type? declaredResultType, + CancellationToken cancellationToken) { try { - // Convert the result to a JsonElement using the runtime type, looked up via - // the merged resolver. Source-gen serialization of an `object`-typed property - // would otherwise have no way to find metadata for the actual response type - // (e.g. SystemMessageTransformRpcResponse, SessionFsReadFileResult, ...). + // Prefer the handler's declared result type so polymorphic base types emit + // their discriminator. Fall back to the runtime type for untyped handlers. JsonElement? resultElement = null; if (result is not null) { - var typeInfo = _serializerOptions.GetTypeInfo(result.GetType()); + var typeInfo = _serializerOptions.GetTypeInfo(declaredResultType ?? result.GetType()); resultElement = JsonSerializer.SerializeToElement(result, typeInfo); } @@ -863,6 +869,11 @@ public MethodRegistration(Delegate handler, bool singleObjectParam) { ValueTaskAsTaskMethod = GetMethodFromGenericMethodDefinition(returnType, s_valueTaskAsTask); TaskResultGetter = GetMethodFromGenericMethodDefinition(ValueTaskAsTaskMethod.ReturnType, s_taskGetResult); + ResultType = returnType.GetGenericArguments()[0]; + } + else if (returnType != typeof(void) && returnType != typeof(Task) && returnType != typeof(ValueTask)) + { + ResultType = returnType; } } @@ -871,6 +882,7 @@ public MethodRegistration(Delegate handler, bool singleObjectParam) public ParameterInfo[] Parameters { get; } public MethodInfo? ValueTaskAsTaskMethod { get; } public MethodInfo? TaskResultGetter { get; } + public Type? ResultType { get; } } private static MethodInfo GetMethodFromGenericMethodDefinition(Type specializedType, MethodInfo genericMethodDefinition) diff --git a/dotnet/src/Session.cs b/dotnet/src/Session.cs index a6fad7dcfa..3176e7db23 100644 --- a/dotnet/src/Session.cs +++ b/dotnet/src/Session.cs @@ -82,6 +82,7 @@ private sealed record EventSubscription(Type EventType, Action Han private IReadOnlyList _openCanvases = Array.Empty(); private int _isDisposed; + private string? _gitHubTokenProviderRegistrationId; /// /// Channel that serializes event dispatch. enqueues; @@ -203,6 +204,19 @@ internal void RemoveFromClient() ((ICollection>)_parentClient._sessions).Remove(new(SessionId, this)); } + internal void SetGitHubTokenProviderRegistration(string registrationId) + { + _gitHubTokenProviderRegistrationId = registrationId; + } + + internal void ReleaseGitHubTokenProviderRegistration() + { + if (Interlocked.Exchange(ref _gitHubTokenProviderRegistrationId, null) is { } registrationId) + { + _parentClient.UnregisterGitHubTokenProvider(registrationId); + } + } + internal void StartProcessingEvents() { _ = ProcessEventsAsync(); @@ -1936,6 +1950,7 @@ await InvokeRpcAsync( } finally { + ReleaseGitHubTokenProviderRegistration(); RemoveFromClient(); GC.SuppressFinalize(this); } diff --git a/dotnet/src/Types.cs b/dotnet/src/Types.cs index f6a46520ec..6ed05e3064 100644 --- a/dotnet/src/Types.cs +++ b/dotnet/src/Types.cs @@ -3207,6 +3207,7 @@ protected SessionConfigBase(SessionConfigBase? other) ContextTier = other.ContextTier; CreateSessionFsProvider = other.CreateSessionFsProvider; GitHubToken = other.GitHubToken; + GitHubTokenProvider = other.GitHubTokenProvider; RemoteSession = other.RemoteSession; ExpAssignments = other.ExpAssignments; EnableManagedSettings = other.EnableManagedSettings; @@ -3658,6 +3659,16 @@ protected SessionConfigBase(SessionConfigBase? other) /// public string? GitHubToken { get; set; } + /// + /// Gets or sets a callback that acquires session-scoped GitHub tokens on + /// demand. Initial cancellation, callback errors, and invalid token responses + /// reject session creation or resume instead of falling back to ambient + /// authentication. This cannot be combined with . + /// + [Experimental(Diagnostics.Experimental)] + [JsonIgnore] + public Func>? GitHubTokenProvider { get; set; } + /// /// Per-session remote behavior control: /// diff --git a/dotnet/test/Unit/ClientSessionLifetimeTests.cs b/dotnet/test/Unit/ClientSessionLifetimeTests.cs index 8746b53b5e..98bd36f928 100644 --- a/dotnet/test/Unit/ClientSessionLifetimeTests.cs +++ b/dotnet/test/Unit/ClientSessionLifetimeTests.cs @@ -3,6 +3,7 @@ *--------------------------------------------------------------------------------------------*/ #if NET8_0_OR_GREATER +using System.Collections.Concurrent; using System.Net; using System.Net.Sockets; using System.Diagnostics; @@ -19,6 +20,192 @@ public sealed class ClientSessionLifetimeTests { private sealed record RpcRequestRecord(string Method, JsonElement Params); + [Theory] + [InlineData("static")] + [InlineData("")] + public async Task GitHubTokenProvider_Is_Mutually_Exclusive_With_Static_Token(string staticToken) + { + await using var client = new CopilotClient(); + var config = new SessionConfig + { + GitHubToken = staticToken, + GitHubTokenProvider = _ => Task.FromResult(GitHubTokenProviderResult.Cancel()) + }; + + var error = await Assert.ThrowsAsync(() => client.CreateSessionAsync(config)); + + Assert.Contains("cannot be used together", error.Message); + } + + [Fact] + public async Task GitHubTokenProvider_Is_Released_When_Session_Is_Deleted() + { + await using var server = await FakeCopilotServer.StartAsync(); + await using var client = new CopilotClient(new CopilotClientOptions { Connection = RuntimeConnection.ForUri(server.Url) }); + var session = await client.CreateSessionAsync(new SessionConfig + { + GitHubTokenProvider = _ => Task.FromResult(GitHubTokenProviderResult.Cancel()) + }); + var registrationId = Assert.Single(server.Requests, request => request.Method == "session.create") + .Params.GetProperty("gitHubTokenProviderRegistrationId").GetString(); + + await client.DeleteSessionAsync(session.SessionId); + + var error = await Assert.ThrowsAsync(() => + server.SendRequestAsync("gitHubToken.getToken", TokenRequest(registrationId))); + Assert.Contains("Unknown GitHub token provider registration ID", error.Message); + await session.DisposeAsync(); + } + + [Fact] + public async Task GitHubTokenProvider_Is_Serialized_And_Maps_Callbacks() + { + await using var server = await FakeCopilotServer.StartAsync(); + await using var client = new CopilotClient(new CopilotClientOptions { Connection = RuntimeConnection.ForUri(server.Url) }); + GitHubTokenProviderArgs? callbackArgs = null; + var session = await client.CreateSessionAsync(new SessionConfig + { + GitHubTokenProvider = args => + { + callbackArgs = args; + return Task.FromResult(GitHubTokenProviderResult.FromToken(new GitHubToken + { + AccessToken = "secret-token", + TokenType = "bearer", + ExpiresIn = 8 * 60 * 60 + })); + } + }); + var request = Assert.Single(server.Requests, request => request.Method == "session.create"); + var registrationId = request.Params.GetProperty("gitHubTokenProviderRegistrationId").GetString(); + Assert.False(string.IsNullOrEmpty(registrationId)); + Assert.False(request.Params.TryGetProperty("gitHubToken", out _)); + + var result = await server.SendRequestAsync("gitHubToken.getToken", new Dictionary + { + ["registrationId"] = registrationId, + ["host"] = "github.example.com", + ["sessionId"] = session.SessionId, + ["reason"] = "refresh" + }); + + Assert.True(result.TryGetProperty("kind", out var kind), result.ToString()); + Assert.Equal("token", kind.GetString()); + Assert.Equal("secret-token", result.GetProperty("accessToken").GetString()); + Assert.Equal(8 * 60 * 60, result.GetProperty("expiresIn").GetInt64()); + Assert.NotNull(callbackArgs); + Assert.Equal("github.example.com", callbackArgs.Host); + Assert.Equal(session.SessionId, callbackArgs.SessionId); + Assert.Equal(GitHubTokenRequestReason.Refresh, callbackArgs.Reason); + Assert.DoesNotContain("secret-token", new GitHubToken + { + AccessToken = "secret-token", + ExpiresIn = 8 * 60 * 60 + }.ToString()); + + await session.DisposeAsync(); + var error = await Assert.ThrowsAsync(() => + server.SendRequestAsync("gitHubToken.getToken", new Dictionary + { + ["registrationId"] = registrationId, + ["host"] = "github.com", + ["reason"] = "initial" + })); + Assert.Contains("Unknown GitHub token provider registration ID", error.Message); + + server.ClearRequests(); + var resumed = await client.ResumeSessionAsync("resumed-session", new ResumeSessionConfig + { + GitHubTokenProvider = _ => Task.FromResult(GitHubTokenProviderResult.Cancel()) + }); + var resumeRequest = Assert.Single(server.Requests, request => request.Method == "session.resume"); + Assert.False(string.IsNullOrEmpty( + resumeRequest.Params.GetProperty("gitHubTokenProviderRegistrationId").GetString())); + await resumed.DisposeAsync(); + } + + [Fact] + public async Task GitHubTokenProvider_Handles_Cancellation_Errors_And_Rollback() + { + await using var server = await FakeCopilotServer.StartAsync(); + await using var client = new CopilotClient(new CopilotClientOptions { Connection = RuntimeConnection.ForUri(server.Url) }); + var cancelledSession = await client.CreateSessionAsync(new SessionConfig + { + GitHubTokenProvider = _ => Task.FromResult(GitHubTokenProviderResult.Cancel()) + }); + var cancelledId = Assert.Single(server.Requests, request => request.Method == "session.create") + .Params.GetProperty("gitHubTokenProviderRegistrationId").GetString(); + var cancelled = await server.SendRequestAsync("gitHubToken.getToken", TokenRequest(cancelledId)); + Assert.True(cancelled.TryGetProperty("kind", out var cancelledKind), cancelled.ToString()); + Assert.Equal("cancelled", cancelledKind.GetString()); + await cancelledSession.DisposeAsync(); + + server.ClearRequests(); + var providerSession = await client.CreateSessionAsync(new SessionConfig + { + GitHubTokenProvider = _ => Task.FromException( + new InvalidOperationException("provider failed")) + }); + var providerId = Assert.Single(server.Requests, request => request.Method == "session.create") + .Params.GetProperty("gitHubTokenProviderRegistrationId").GetString(); + var callbackError = await Assert.ThrowsAsync(() => + server.SendRequestAsync("gitHubToken.getToken", TokenRequest(providerId))); + Assert.Contains("provider failed", callbackError.Message); + await providerSession.DisposeAsync(); + + server.ClearRequests(); + server.FailSessionCreate(); + await Assert.ThrowsAsync(() => client.CreateSessionAsync(new SessionConfig + { + GitHubTokenProvider = _ => Task.FromResult(GitHubTokenProviderResult.Cancel()) + })); + var rolledBackId = Assert.Single(server.Requests, request => request.Method == "session.create") + .Params.GetProperty("gitHubTokenProviderRegistrationId").GetString(); + var rollbackError = await Assert.ThrowsAsync(() => + server.SendRequestAsync("gitHubToken.getToken", TokenRequest(rolledBackId))); + Assert.Contains("Unknown GitHub token provider registration ID", rollbackError.Message); + } + + [Fact] + public async Task GitHubTokenProvider_Resume_Replaces_Ownership() + { + await using var server = await FakeCopilotServer.StartAsync(); + await using var client = new CopilotClient(new CopilotClientOptions { Connection = RuntimeConnection.ForUri(server.Url) }); + var first = await client.CreateSessionAsync(new SessionConfig + { + SessionId = "replacement-session", + GitHubTokenProvider = _ => Task.FromResult(GitHubTokenProviderResult.Cancel()) + }); + var firstId = Assert.Single(server.Requests, request => request.Method == "session.create") + .Params.GetProperty("gitHubTokenProviderRegistrationId").GetString(); + + await first.DisposeAsync(); + await Assert.ThrowsAsync(() => + server.SendRequestAsync("gitHubToken.getToken", TokenRequest(firstId))); + + server.ClearRequests(); + var resumed = await client.ResumeSessionAsync("replacement-session", new ResumeSessionConfig + { + GitHubTokenProvider = _ => Task.FromResult(GitHubTokenProviderResult.Cancel()) + }); + var secondId = Assert.Single(server.Requests, request => request.Method == "session.resume") + .Params.GetProperty("gitHubTokenProviderRegistrationId").GetString(); + + var result = await server.SendRequestAsync("gitHubToken.getToken", TokenRequest(secondId)); + Assert.Equal("cancelled", result.GetProperty("kind").GetString()); + + await resumed.DisposeAsync(); + await Assert.ThrowsAsync(() => + server.SendRequestAsync("gitHubToken.getToken", TokenRequest(secondId))); + } + + private static Dictionary TokenRequest(string? registrationId) => new() + { + ["registrationId"] = registrationId, + ["host"] = "github.com", + ["reason"] = "initial" + }; + [Fact] public async Task StopAsync_Requests_Runtime_Shutdown_For_Owned_Process() { @@ -1058,9 +1245,13 @@ private sealed class FakeCopilotServer : IAsyncDisposable private readonly Task _serverTask; private readonly List _requests = []; private readonly object _requestsLock = new(); + private readonly ConcurrentDictionary> _pendingRequests = new(); + private NetworkStream? _stream; + private int _nextRequestId; private string? _lastSessionId; private bool _delayDestroy; private bool _failRuntimeShutdown; + private bool _failSessionCreate; private FakeCopilotServer(TcpListener listener) { @@ -1122,6 +1313,31 @@ public void FailRuntimeShutdown() _failRuntimeShutdown = true; } + public void FailSessionCreate() + { + _failSessionCreate = true; + } + + public async Task SendRequestAsync(string method, Dictionary parameters) + { + var stream = _stream ?? throw new InvalidOperationException("Client is not connected."); + var id = Interlocked.Increment(ref _nextRequestId); + var completion = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + if (!_pendingRequests.TryAdd(id, completion)) + { + throw new InvalidOperationException("Failed to track callback request."); + } + + await WriteMessageAsync(stream, new Dictionary + { + ["jsonrpc"] = "2.0", + ["id"] = id, + ["method"] = method, + ["params"] = parameters + }, _cts.Token); + return await completion.Task.WaitAsync(_cts.Token); + } + public async ValueTask DisposeAsync() { _allowDestroy.TrySetResult(); @@ -1144,16 +1360,37 @@ private async Task RunAsync() { using var tcpClient = await _listener.AcceptTcpClientAsync(_cts.Token); using var stream = tcpClient.GetStream(); + _stream = stream; while (!_cts.Token.IsCancellationRequested) { - using var request = await ReadMessageAsync(stream, _cts.Token); - if (request is null) + using var message = await ReadMessageAsync(stream, _cts.Token); + if (message is null) { return; } - await HandleRequestAsync(stream, request.RootElement, _cts.Token); + var root = message.RootElement; + if (root.TryGetProperty("method", out _)) + { + await HandleRequestAsync(stream, root, _cts.Token); + continue; + } + + if (root.TryGetProperty("id", out var responseId) + && responseId.TryGetInt32(out var id) + && _pendingRequests.TryRemove(id, out var completion)) + { + if (root.TryGetProperty("error", out var error)) + { + completion.TrySetException(new InvalidOperationException( + error.GetProperty("message").GetString())); + } + else + { + completion.TrySetResult(root.GetProperty("result").Clone()); + } + } } } @@ -1189,6 +1426,21 @@ private async Task HandleRequestAsync(Stream stream, JsonElement request, Cancel { _requests.Add(new RpcRequestRecord(method!, paramsElement)); } + if (method == "session.create" && _failSessionCreate) + { + _failSessionCreate = false; + await WriteMessageAsync(stream, new Dictionary + { + ["jsonrpc"] = "2.0", + ["id"] = id, + ["error"] = new Dictionary + { + ["code"] = -32000, + ["message"] = "session create failed" + } + }, cancellationToken); + return; + } object? result = method switch { "connect" => new Dictionary diff --git a/go/README.md b/go/README.md index d8588699c4..ddd74b91aa 100644 --- a/go/README.md +++ b/go/README.md @@ -222,6 +222,7 @@ Event types: `SessionLifecycleCreated`, `SessionLifecycleDeleted`, `SessionLifec - `InfiniteSessions` (\*InfiniteSessionConfig): Automatic context compaction configuration - `WorkingDirectory` (string): Working directory for the session (default: runtime process working directory) - `EnableSessionStore` (\*bool): Enables the cross-session store for search and retrieval across sessions. When unset in `ModeCopilotCli`, the runtime default applies (enabled). In `ModeEmpty`, defaults to disabled. +- `GitHubTokenProvider` (GitHubTokenProvider): Acquires session-scoped GitHub tokens on demand. Return `GitHubTokenResult` with a positive `ExpiresIn` value (production GitHub tokens typically use `8 * 60 * 60` seconds), or `GitHubTokenCancelled`. Cannot be combined with `GitHubToken`. - `OnPermissionRequest` (PermissionHandlerFunc): Optional handler called before each tool execution to approve or deny it. When nil, permission requests are emitted as events and left pending for manual resolution. `copilot.PermissionHandler.ApproveAll` approves requests when managed settings are disabled and returns an error when `EnableManagedSettings` is true. Custom handlers can inspect `RequiresManagedApproval()` for human-facing confirmation logic. See [Permission Handling](#permission-handling) section. - `OnUserInputRequest` (UserInputHandler): Handler for user input requests from the agent (enables ask_user tool). See [User Input Requests](#user-input-requests) section. - `Hooks` (\*SessionHooks): Hook handlers for session lifecycle events. See [Session Hooks](#session-hooks) section. @@ -237,6 +238,24 @@ Event types: `SessionLifecycleCreated`, `SessionLifecycleDeleted`, `SessionLifec - `Streaming` (*bool): Enable streaming delta events (nil = runtime default) - `Commands` ([]CommandDefinition): Slash-commands. See [Commands](#commands) section. - `OnElicitationRequest` (ElicitationHandler): Elicitation handler. See [Elicitation Requests](#elicitation-requests-serverclient) section. +- `GitHubTokenProvider` (GitHubTokenProvider): Replaces the session-scoped token provider when resuming. Cannot be combined with `GitHubToken`. + +```go +session, err := client.CreateSession(ctx, &copilot.SessionConfig{ + GitHubTokenProvider: func(args copilot.GitHubTokenProviderArgs) (*copilot.GitHubTokenProviderResult, error) { + token, err := acquireToken(args.Host) + if err != nil { + return nil, err + } + return copilot.GitHubTokenResult(&copilot.GitHubToken{ + AccessToken: token, + ExpiresIn: 8 * 60 * 60, + }), nil + }, +}) +``` + +Initial acquisition runs during session creation or resume. Cancellation, provider errors, and invalid token responses reject that operation instead of falling back to ambient authentication. Idle sessions refresh only before their next credential-consuming operation; there is no background refresh timer. ### Session diff --git a/go/client.go b/go/client.go index 47e308b3fc..4e44696a55 100644 --- a/go/client.go +++ b/go/client.go @@ -145,19 +145,23 @@ func validateEnvironmentOptions(connection RuntimeConnection, opts *ClientOption // } // defer client.Stop() type Client struct { - options ClientOptions - process *exec.Cmd - client *jsonrpc2.Client - actualPort int - actualHost string - state connectionState - sessions map[string]*Session - sessionsMux sync.Mutex - isExternalServer bool - conn net.Conn // stores net.Conn for external TCP connections - useStdio bool // resolved value from options - useInProcess bool // true for InProcessConnection (FFI transport) - ffiHost inProcessHost + options ClientOptions + process *exec.Cmd + client *jsonrpc2.Client + actualPort int + actualHost string + state connectionState + sessions map[string]*Session + sessionsMux sync.Mutex + gitHubTokenProviders map[string]GitHubTokenProvider + gitHubTokenProvidersMux sync.RWMutex + sessionOperations map[string]*sessionOperation + sessionOperationsMux sync.Mutex + isExternalServer bool + conn net.Conn // stores net.Conn for external TCP connections + useStdio bool // resolved value from options + useInProcess bool // true for InProcessConnection (FFI transport) + ffiHost inProcessHost // resolved process options for the spawned runtime (zero values for URIConnection) cliPath string cliArgs []string @@ -189,6 +193,11 @@ type Client struct { internalRPC *rpc.InternalServerRPC } +type sessionOperation struct { + mutex sync.Mutex + users int +} + // NewClient creates a new Copilot runtime client with the given options. // // If options is nil, default options are used (spawns the bundled runtime over @@ -215,12 +224,13 @@ func NewClient(options *ClientOptions) *Client { opts := ClientOptions{} client := &Client{ - options: opts, - state: stateDisconnected, - sessions: make(map[string]*Session), - actualHost: "localhost", - isExternalServer: false, - useStdio: true, + options: opts, + state: stateDisconnected, + sessions: make(map[string]*Session), + gitHubTokenProviders: make(map[string]GitHubTokenProvider), + actualHost: "localhost", + isExternalServer: false, + useStdio: true, } if options != nil { @@ -548,6 +558,7 @@ func (c *Client) Stop() error { c.sessionsMux.Lock() c.sessions = make(map[string]*Session) c.sessionsMux.Unlock() + c.clearGitHubTokenProviders() c.startStopMux.Lock() defer c.startStopMux.Unlock() @@ -663,6 +674,7 @@ func (c *Client) ForceStop() { c.sessionsMux.Lock() c.sessions = make(map[string]*Session) c.sessionsMux.Unlock() + c.clearGitHubTokenProviders() c.startStopMux.Lock() defer c.startStopMux.Unlock() @@ -780,6 +792,9 @@ func (c *Client) CreateSession(ctx context.Context, config *SessionConfig) (*Ses if config == nil { config = &SessionConfig{} } + if config.GitHubToken != "" && config.GitHubTokenProvider != nil { + return nil, fmt.Errorf("GitHubToken and GitHubTokenProvider cannot be used together") + } if err := c.ensureConnected(ctx); err != nil { return nil, err @@ -787,6 +802,14 @@ func (c *Client) CreateSession(ctx context.Context, config *SessionConfig) (*Ses c.applyConfigDefaultsForMode(config) + registrationID := c.registerGitHubTokenProvider(config.GitHubTokenProvider) + registrationTransferred := false + defer func() { + if !registrationTransferred { + c.unregisterGitHubTokenProvider(registrationID) + } + }() + req := createSessionRequest{} req.Model = config.Model req.ClientName = config.ClientName @@ -849,6 +872,7 @@ func (c *Client) CreateSession(ctx context.Context, config *SessionConfig) (*Ses req.ToolSearch = config.ToolSearch req.Memory = config.Memory req.GitHubToken = config.GitHubToken + req.GitHubTokenProviderRegistrationID = registrationID req.RemoteSession = config.RemoteSession req.Cloud = config.Cloud req.Canvases = config.Canvases @@ -1104,6 +1128,12 @@ func (c *Client) CreateSession(ctx context.Context, config *SessionConfig) (*Ses return nil, err } + if registrationID != "" { + session.setGitHubTokenProviderRegistrationRelease(func() { + c.unregisterGitHubTokenProvider(registrationID) + }) + registrationTransferred = true + } return session, nil } @@ -1131,9 +1161,15 @@ func (c *Client) ResumeSession(ctx context.Context, sessionID string, config *Re // Tools: []copilot.Tool{myNewTool}, // }) func (c *Client) ResumeSessionWithOptions(ctx context.Context, sessionID string, config *ResumeSessionConfig) (*Session, error) { + unlockSession := c.lockSessionOperation(sessionID) + defer unlockSession() + if config == nil { config = &ResumeSessionConfig{} } + if config.GitHubToken != "" && config.GitHubTokenProvider != nil { + return nil, fmt.Errorf("GitHubToken and GitHubTokenProvider cannot be used together") + } if err := c.ensureConnected(ctx); err != nil { return nil, err @@ -1141,6 +1177,14 @@ func (c *Client) ResumeSessionWithOptions(ctx context.Context, sessionID string, c.applyResumeDefaultsForMode(config) + registrationID := c.registerGitHubTokenProvider(config.GitHubTokenProvider) + registrationTransferred := false + defer func() { + if !registrationTransferred { + c.unregisterGitHubTokenProvider(registrationID) + } + }() + var req resumeSessionRequest req.SessionID = sessionID req.ClientName = config.ClientName @@ -1234,6 +1278,7 @@ func (c *Client) ResumeSessionWithOptions(ctx context.Context, sessionID string, req.ToolSearch = config.ToolSearch req.Memory = config.Memory req.GitHubToken = config.GitHubToken + req.GitHubTokenProviderRegistrationID = registrationID req.RemoteSession = config.RemoteSession req.Canvases = config.Canvases req.OpenCanvases = config.OpenCanvases @@ -1319,22 +1364,31 @@ func (c *Client) ResumeSessionWithOptions(ctx context.Context, sessionID string, } c.sessionsMux.Lock() + replacedSession := c.sessions[sessionID] c.sessions[sessionID] = session c.sessionsMux.Unlock() + restoreReplacedSession := func() { + c.sessionsMux.Lock() + if current := c.sessions[sessionID]; current == nil || current == session { + if replacedSession != nil { + c.sessions[sessionID] = replacedSession + } else { + delete(c.sessions, sessionID) + } + } + c.sessionsMux.Unlock() + } + if c.options.SessionFS != nil { if config.CreateSessionFSProvider == nil { - c.sessionsMux.Lock() - delete(c.sessions, sessionID) - c.sessionsMux.Unlock() + restoreReplacedSession() return nil, fmt.Errorf("CreateSessionFSProvider is required in session config when SessionFS is enabled in client options") } provider := config.CreateSessionFSProvider(session) if c.options.SessionFS.Capabilities != nil && c.options.SessionFS.Capabilities.Sqlite { if _, ok := provider.(SessionFSSqliteProvider); !ok { - c.sessionsMux.Lock() - delete(c.sessions, sessionID) - c.sessionsMux.Unlock() + restoreReplacedSession() return nil, fmt.Errorf("SessionFS capabilities declare SQLite support but the provider does not implement SessionFSSqliteProvider") } } @@ -1343,17 +1397,13 @@ func (c *Client) ResumeSessionWithOptions(ctx context.Context, sessionID string, result, err := c.client.Request(ctx, "session.resume", req) if err != nil { - c.sessionsMux.Lock() - delete(c.sessions, sessionID) - c.sessionsMux.Unlock() + restoreReplacedSession() return nil, fmt.Errorf("failed to resume session: %w", err) } var response resumeSessionResponse if err := json.Unmarshal(result, &response); err != nil { - c.sessionsMux.Lock() - delete(c.sessions, sessionID) - c.sessionsMux.Unlock() + restoreReplacedSession() return nil, fmt.Errorf("failed to unmarshal response: %w", err) } @@ -1362,9 +1412,7 @@ func (c *Client) ResumeSessionWithOptions(ctx context.Context, sessionID string, "sessionId": sessionID, "eventType": "mcp.oauth_required", }); err != nil { - c.sessionsMux.Lock() - delete(c.sessions, sessionID) - c.sessionsMux.Unlock() + restoreReplacedSession() return nil, err } } @@ -1380,9 +1428,19 @@ func (c *Client) ResumeSessionWithOptions(ctx context.Context, sessionID string, ManageScheduleEnabled: config.ManageScheduleEnabled, IncludedBuiltinSkills: config.IncludedBuiltinSkills, }); err != nil { + restoreReplacedSession() return nil, err } + if registrationID != "" { + session.setGitHubTokenProviderRegistrationRelease(func() { + c.unregisterGitHubTokenProvider(registrationID) + }) + registrationTransferred = true + } + if replacedSession != nil && replacedSession != session { + replacedSession.releaseGitHubTokenProviderRegistration() + } return session, nil } @@ -1474,6 +1532,9 @@ func (c *Client) GetSessionMetadata(ctx context.Context, sessionID string) (*Ses // log.Fatal(err) // } func (c *Client) DeleteSession(ctx context.Context, sessionID string) error { + unlockSession := c.lockSessionOperation(sessionID) + defer unlockSession() + if err := c.ensureConnected(ctx); err != nil { return err } @@ -1498,8 +1559,12 @@ func (c *Client) DeleteSession(ctx context.Context, sessionID string) error { // Remove from local sessions map if present c.sessionsMux.Lock() + session := c.sessions[sessionID] delete(c.sessions, sessionID) c.sessionsMux.Unlock() + if session != nil { + session.releaseGitHubTokenProviderRegistration() + } return nil } @@ -2041,15 +2106,7 @@ func (c *Client) startCLIServer(ctx context.Context) error { // Create JSON-RPC client immediately c.client = jsonrpc2.NewClient(stdin, stdout) c.client.SetProcessDone(c.processDone, c.processErrorPtr) - c.client.SetOnClose(func() { - // Run in a goroutine to avoid deadlocking with Stop/ForceStop, - // which hold startStopMux while waiting for readLoop to finish. - go func() { - c.startStopMux.Lock() - defer c.startStopMux.Unlock() - c.state = stateDisconnected - }() - }) + c.client.SetOnClose(c.handleConnectionClose) c.RPC = rpc.NewServerRPC(c.client) c.internalRPC = rpc.NewInternalServerRPC(c.client) c.setupNotificationHandler() @@ -2168,15 +2225,7 @@ func (c *Client) startInProcess(ctx context.Context) error { } c.client = jsonrpc2.NewClient(host.Writer(), host.Reader()) - c.client.SetOnClose(func() { - // Run in a goroutine to avoid deadlocking with Stop/ForceStop, which hold - // startStopMux while waiting for readLoop to finish. - go func() { - c.startStopMux.Lock() - defer c.startStopMux.Unlock() - c.state = stateDisconnected - }() - }) + c.client.SetOnClose(c.handleConnectionClose) c.RPC = rpc.NewServerRPC(c.client) c.internalRPC = rpc.NewInternalServerRPC(c.client) c.setupNotificationHandler() @@ -2326,13 +2375,7 @@ func (c *Client) connectViaTCP(ctx context.Context) error { if c.processDone != nil { c.client.SetProcessDone(c.processDone, c.processErrorPtr) } - c.client.SetOnClose(func() { - go func() { - c.startStopMux.Lock() - defer c.startStopMux.Unlock() - c.state = stateDisconnected - }() - }) + c.client.SetOnClose(c.handleConnectionClose) c.RPC = rpc.NewServerRPC(c.client) c.internalRPC = rpc.NewInternalServerRPC(c.client) c.setupNotificationHandler() @@ -2363,8 +2406,10 @@ func (c *Client) setupNotificationHandler() { // payload's sessionId. Always register the global handlers so the generated // hooks.invoke handler is wired to our dispatcher. handlers := &rpc.ClientGlobalAPIHandlers{ - Hooks: &hooksAdapter{client: c}, + Hooks: &hooksAdapter{client: c}, + GitHubToken: &gitHubTokenAdapter{client: c}, } + if c.options.RequestHandler != nil { handlers.LlmInference = newCopilotRequestAdapter(c.options.RequestHandler, func() *rpc.ServerLlmInferenceAPI { if c.RPC == nil { @@ -2379,6 +2424,107 @@ func (c *Client) setupNotificationHandler() { rpc.RegisterClientGlobalAPIHandlers(c.client, handlers) } +func (c *Client) registerGitHubTokenProvider(provider GitHubTokenProvider) string { + if provider == nil { + return "" + } + registrationID := uuid.NewString() + c.gitHubTokenProvidersMux.Lock() + if c.gitHubTokenProviders == nil { + c.gitHubTokenProviders = make(map[string]GitHubTokenProvider) + } + c.gitHubTokenProviders[registrationID] = provider + c.gitHubTokenProvidersMux.Unlock() + return registrationID +} + +func (c *Client) unregisterGitHubTokenProvider(registrationID string) { + if registrationID == "" { + return + } + c.gitHubTokenProvidersMux.Lock() + delete(c.gitHubTokenProviders, registrationID) + c.gitHubTokenProvidersMux.Unlock() +} + +func (c *Client) clearGitHubTokenProviders() { + c.gitHubTokenProvidersMux.Lock() + c.gitHubTokenProviders = make(map[string]GitHubTokenProvider) + c.gitHubTokenProvidersMux.Unlock() +} + +func (c *Client) handleConnectionClose() { + c.clearGitHubTokenProviders() + // Avoid deadlocking with Stop/ForceStop, which hold startStopMux while + // waiting for the JSON-RPC read loop to finish. + go func() { + c.startStopMux.Lock() + defer c.startStopMux.Unlock() + c.state = stateDisconnected + }() +} + +func (c *Client) lockSessionOperation(sessionID string) func() { + c.sessionOperationsMux.Lock() + if c.sessionOperations == nil { + c.sessionOperations = make(map[string]*sessionOperation) + } + operation := c.sessionOperations[sessionID] + if operation == nil { + operation = &sessionOperation{} + c.sessionOperations[sessionID] = operation + } + operation.users++ + c.sessionOperationsMux.Unlock() + + operation.mutex.Lock() + return func() { + operation.mutex.Unlock() + c.sessionOperationsMux.Lock() + operation.users-- + if operation.users == 0 { + delete(c.sessionOperations, sessionID) + } + c.sessionOperationsMux.Unlock() + } +} + +type gitHubTokenAdapter struct { + client *Client +} + +func (a *gitHubTokenAdapter) GetToken(request *rpc.GitHubTokenAcquireRequest) (rpc.GitHubTokenAcquireResult, error) { + if request == nil { + return nil, fmt.Errorf("missing GitHub token acquire request") + } + a.client.gitHubTokenProvidersMux.RLock() + provider := a.client.gitHubTokenProviders[request.RegistrationID] + a.client.gitHubTokenProvidersMux.RUnlock() + if provider == nil { + return nil, fmt.Errorf("unknown GitHub token provider registration ID %q", request.RegistrationID) + } + + result, err := provider(GitHubTokenProviderArgs{ + Host: request.Host, + SessionID: request.SessionID, + Reason: request.Reason, + }) + if err != nil { + return nil, err + } + if result != nil && result.Cancelled { + return &rpc.GitHubTokenAcquireResultCancelled{}, nil + } + if result == nil || result.Token == nil { + return nil, fmt.Errorf("GitHub token provider returned neither a token nor cancellation") + } + return &rpc.GitHubTokenAcquireResultToken{ + AccessToken: result.Token.AccessToken, + TokenType: result.Token.TokenType, + ExpiresIn: result.Token.ExpiresIn, + }, nil +} + // gitHubTelemetryAdapter adapts the OnGitHubTelemetry option to the generated // rpc.GitHubTelemetryHandler interface. type gitHubTelemetryAdapter struct { diff --git a/go/github_token_provider.go b/go/github_token_provider.go new file mode 100644 index 0000000000..8f1233b7cd --- /dev/null +++ b/go/github_token_provider.go @@ -0,0 +1,84 @@ +package copilot + +import ( + "fmt" + + "github.com/github/copilot-sdk/go/rpc" +) + +// GitHubTokenRequestReason describes why the runtime needs a GitHub token. +// +// Experimental: GitHubTokenRequestReason may change or be removed. +type GitHubTokenRequestReason = rpc.GitHubTokenAcquireReason + +const ( + // GitHubTokenRequestReasonInitial indicates the session needs its initial token. + GitHubTokenRequestReasonInitial = rpc.GitHubTokenAcquireReasonInitial + // GitHubTokenRequestReasonRefresh indicates the session needs a refreshed token. + GitHubTokenRequestReasonRefresh = rpc.GitHubTokenAcquireReasonRefresh +) + +// GitHubTokenProviderArgs contains the context for a GitHub token request. +// +// Experimental: GitHubTokenProviderArgs may change or be removed. +type GitHubTokenProviderArgs struct { + // Host is the effective GitHub host for which a token is needed. + Host string + // SessionID identifies the session receiving the token. It is nil before a + // cloud session has been assigned an ID. + SessionID *string + // Reason indicates whether this is the initial token or a refresh. + Reason GitHubTokenRequestReason +} + +// GitHubToken contains a GitHub access token returned by a provider. +// +// Experimental: GitHubToken may change or be removed. +type GitHubToken struct { + // AccessToken is the GitHub access token. + AccessToken string + // TokenType is the OAuth token type. The runtime defaults it to "bearer". + TokenType *string + // ExpiresIn is the required positive number of seconds remaining when the + // callback completes. Production GitHub tokens typically last eight hours. + ExpiresIn int64 +} + +// String returns a redacted description that never includes the access token. +func (t GitHubToken) String() string { + tokenType := "" + if t.TokenType != nil { + tokenType = *t.TokenType + } + return fmt.Sprintf("GitHubToken{TokenType:%q, ExpiresIn:%d, AccessToken:}", tokenType, t.ExpiresIn) +} + +// GoString returns a redacted Go-syntax description that never includes the access token. +func (t GitHubToken) GoString() string { + return t.String() +} + +// GitHubTokenProviderResult is the result of a GitHub token request. +// +// Experimental: GitHubTokenProviderResult may change or be removed. +type GitHubTokenProviderResult struct { + Cancelled bool + Token *GitHubToken +} + +// GitHubTokenResult returns a successful token-provider result. +func GitHubTokenResult(token *GitHubToken) *GitHubTokenProviderResult { + return &GitHubTokenProviderResult{Token: token} +} + +// GitHubTokenCancelled returns a result indicating that token acquisition was cancelled. +func GitHubTokenCancelled() *GitHubTokenProviderResult { + return &GitHubTokenProviderResult{Cancelled: true} +} + +// GitHubTokenProvider acquires session-scoped GitHub tokens on demand. Initial +// cancellation, errors, and invalid token responses reject session creation or +// resume instead of falling back to ambient authentication. +// +// Experimental: GitHubTokenProvider may change or be removed. +type GitHubTokenProvider func(args GitHubTokenProviderArgs) (*GitHubTokenProviderResult, error) diff --git a/go/github_token_provider_test.go b/go/github_token_provider_test.go new file mode 100644 index 0000000000..f00837379f --- /dev/null +++ b/go/github_token_provider_test.go @@ -0,0 +1,344 @@ +package copilot + +import ( + "encoding/json" + "errors" + "fmt" + "strings" + "sync" + "testing" + "time" + + "github.com/github/copilot-sdk/go/internal/jsonrpc2" + "github.com/github/copilot-sdk/go/rpc" +) + +func TestGitHubTokenProviderConfigValidation(t *testing.T) { + provider := func(GitHubTokenProviderArgs) (*GitHubTokenProviderResult, error) { + return GitHubTokenCancelled(), nil + } + + if _, err := NewClient(nil).CreateSession(t.Context(), &SessionConfig{ + GitHubToken: "static", + GitHubTokenProvider: provider, + }); err == nil || !strings.Contains(err.Error(), "cannot be used together") { + t.Fatalf("CreateSession error = %v", err) + } + if _, err := NewClient(nil).ResumeSession(t.Context(), "session", &ResumeSessionConfig{ + GitHubToken: "static", + GitHubTokenProvider: provider, + }); err == nil || !strings.Contains(err.Error(), "cannot be used together") { + t.Fatalf("ResumeSession error = %v", err) + } +} + +func TestGitHubTokenProviderCreateRequestAndCallback(t *testing.T) { + rpcClient, server, _ := newRuntimeShutdownRpcPair(t) + t.Cleanup(server.Stop) + client := &Client{ + client: rpcClient, + RPC: rpc.NewServerRPC(rpcClient), + sessions: make(map[string]*Session), + } + client.setupNotificationHandler() + + var createParams json.RawMessage + server.SetRequestHandler("session.create", func(params json.RawMessage) (json.RawMessage, *jsonrpc2.Error) { + createParams = append(json.RawMessage(nil), params...) + sessionID := sessionIDFromParams(t, params) + return []byte(`{"sessionId":"` + sessionID + `","workspacePath":"/workspace"}`), nil + }) + server.SetRequestHandler("session.destroy", func(json.RawMessage) (json.RawMessage, *jsonrpc2.Error) { + return []byte(`{}`), nil + }) + + var gotArgs GitHubTokenProviderArgs + session, err := client.CreateSession(t.Context(), &SessionConfig{ + GitHubTokenProvider: func(args GitHubTokenProviderArgs) (*GitHubTokenProviderResult, error) { + gotArgs = args + return GitHubTokenResult(&GitHubToken{ + AccessToken: "secret-token", + TokenType: String("bearer"), + ExpiresIn: 8 * 60 * 60, + }), nil + }, + }) + if err != nil { + t.Fatalf("CreateSession failed: %v", err) + } + + var wire struct { + RegistrationID string `json:"gitHubTokenProviderRegistrationId"` + GitHubToken string `json:"gitHubToken"` + } + if err := json.Unmarshal(createParams, &wire); err != nil { + t.Fatal(err) + } + if wire.RegistrationID == "" { + t.Fatal("gitHubTokenProviderRegistrationId was not serialized") + } + if wire.GitHubToken != "" { + t.Fatal("static gitHubToken should not be serialized") + } + + sessionID := session.SessionID + raw, rpcErr := server.Request(t.Context(), "gitHubToken.getToken", &rpc.GitHubTokenAcquireRequest{ + RegistrationID: wire.RegistrationID, + Host: "github.example.com", + SessionID: &sessionID, + Reason: rpc.GitHubTokenAcquireReasonRefresh, + }) + if rpcErr != nil { + t.Fatalf("getToken failed: %v", rpcErr) + } + var tokenResult struct { + Kind string `json:"kind"` + AccessToken string `json:"accessToken"` + ExpiresIn int64 `json:"expiresIn"` + } + if err := json.Unmarshal(raw, &tokenResult); err != nil { + t.Fatal(err) + } + if tokenResult.Kind != "token" || tokenResult.AccessToken != "secret-token" || tokenResult.ExpiresIn != 8*60*60 { + t.Fatalf("unexpected token result: %+v", tokenResult) + } + if gotArgs.Host != "github.example.com" || gotArgs.SessionID == nil || + *gotArgs.SessionID != sessionID || gotArgs.Reason != GitHubTokenRequestReasonRefresh { + t.Fatalf("unexpected callback args: %+v", gotArgs) + } + + if err := session.Disconnect(); err != nil { + t.Fatal(err) + } + if len(client.gitHubTokenProviders) != 0 { + t.Fatal("provider registration was not removed on disconnect") + } + if _, rpcErr := server.Request(t.Context(), "gitHubToken.getToken", &rpc.GitHubTokenAcquireRequest{ + RegistrationID: wire.RegistrationID, + Host: "github.com", + Reason: rpc.GitHubTokenAcquireReasonInitial, + }); rpcErr == nil { + t.Fatal("unknown registration ID should return a handler error") + } + + var resumeParams json.RawMessage + server.SetRequestHandler("session.resume", func(params json.RawMessage) (json.RawMessage, *jsonrpc2.Error) { + resumeParams = append(json.RawMessage(nil), params...) + return []byte(`{"sessionId":"resumed-session","workspacePath":"/workspace"}`), nil + }) + resumed, err := client.ResumeSession(t.Context(), "resumed-session", &ResumeSessionConfig{ + GitHubTokenProvider: func(GitHubTokenProviderArgs) (*GitHubTokenProviderResult, error) { + return GitHubTokenCancelled(), nil + }, + }) + if err != nil { + t.Fatal(err) + } + if err := json.Unmarshal(resumeParams, &wire); err != nil { + t.Fatal(err) + } + if wire.RegistrationID == "" { + t.Fatal("resume did not serialize gitHubTokenProviderRegistrationId") + } + if err := resumed.Disconnect(); err != nil { + t.Fatal(err) + } +} + +func TestGitHubTokenProviderResultsErrorsAndRollback(t *testing.T) { + client := &Client{} + adapter := &gitHubTokenAdapter{client: client} + + cancelID := client.registerGitHubTokenProvider(func(GitHubTokenProviderArgs) (*GitHubTokenProviderResult, error) { + return GitHubTokenCancelled(), nil + }) + result, err := adapter.GetToken(&rpc.GitHubTokenAcquireRequest{RegistrationID: cancelID}) + if err != nil { + t.Fatal(err) + } + if _, ok := result.(*rpc.GitHubTokenAcquireResultCancelled); !ok { + t.Fatalf("result type = %T", result) + } + + sentinel := errors.New("provider failed") + errorID := client.registerGitHubTokenProvider(func(GitHubTokenProviderArgs) (*GitHubTokenProviderResult, error) { + return nil, sentinel + }) + if _, err := adapter.GetToken(&rpc.GitHubTokenAcquireRequest{RegistrationID: errorID}); !errors.Is(err, sentinel) { + t.Fatalf("provider error = %v", err) + } + + rpcClient, server, _ := newRuntimeShutdownRpcPair(t) + t.Cleanup(server.Stop) + rollbackClient := &Client{ + client: rpcClient, + RPC: rpc.NewServerRPC(rpcClient), + sessions: make(map[string]*Session), + } + server.SetRequestHandler("session.create", func(json.RawMessage) (json.RawMessage, *jsonrpc2.Error) { + return nil, &jsonrpc2.Error{Code: -32000, Message: "create failed"} + }) + if _, err := rollbackClient.CreateSession(t.Context(), &SessionConfig{ + GitHubTokenProvider: func(GitHubTokenProviderArgs) (*GitHubTokenProviderResult, error) { + return GitHubTokenCancelled(), nil + }, + }); err == nil { + t.Fatal("expected create failure") + } + if len(rollbackClient.gitHubTokenProviders) != 0 { + t.Fatal("provider registration was not rolled back") + } +} + +func TestGitHubTokenStringRedactsAccessToken(t *testing.T) { + token := GitHubToken{AccessToken: "secret-token", ExpiresIn: 28_800} + + if got := fmt.Sprintf("%v %#v", token, token); strings.Contains(got, token.AccessToken) { + t.Fatalf("GitHubToken formatting exposed the access token: %s", got) + } +} + +func TestGitHubTokenProviderCleanupOnDisconnectError(t *testing.T) { + rpcClient, server, _ := newRuntimeShutdownRpcPair(t) + t.Cleanup(server.Stop) + server.SetRequestHandler("session.destroy", func(json.RawMessage) (json.RawMessage, *jsonrpc2.Error) { + return nil, &jsonrpc2.Error{Code: -32000, Message: "destroy failed"} + }) + client := &Client{} + registrationID := client.registerGitHubTokenProvider(func(GitHubTokenProviderArgs) (*GitHubTokenProviderResult, error) { + return GitHubTokenCancelled(), nil + }) + session := newSession("cleanup-session", rpcClient, "", false) + session.setGitHubTokenProviderRegistrationRelease(func() { + client.unregisterGitHubTokenProvider(registrationID) + }) + + if err := session.Disconnect(); err == nil || !strings.Contains(err.Error(), "destroy failed") { + t.Fatalf("Disconnect error = %v", err) + } + if len(client.gitHubTokenProviders) != 0 { + t.Fatal("provider registration was not removed after disconnect failed") + } +} + +func TestGitHubTokenProviderReleaseBeforeOwnershipTransfer(t *testing.T) { + client := &Client{} + registrationID := client.registerGitHubTokenProvider(func(GitHubTokenProviderArgs) (*GitHubTokenProviderResult, error) { + return GitHubTokenCancelled(), nil + }) + session := &Session{} + + session.releaseGitHubTokenProviderRegistration() + session.setGitHubTokenProviderRegistrationRelease(func() { + client.unregisterGitHubTokenProvider(registrationID) + }) + + if len(client.gitHubTokenProviders) != 0 { + t.Fatal("provider registration was not removed after a pending session had already been retired") + } +} + +func TestGitHubTokenProviderCleanupOnDelete(t *testing.T) { + rpcClient, server, _ := newRuntimeShutdownRpcPair(t) + t.Cleanup(server.Stop) + server.SetRequestHandler("session.delete", func(json.RawMessage) (json.RawMessage, *jsonrpc2.Error) { + return []byte(`{"success":true}`), nil + }) + client := &Client{ + client: rpcClient, + sessions: make(map[string]*Session), + state: stateConnected, + } + registrationID := client.registerGitHubTokenProvider(func(GitHubTokenProviderArgs) (*GitHubTokenProviderResult, error) { + return GitHubTokenCancelled(), nil + }) + session := newSession("delete-session", rpcClient, "", false) + session.setGitHubTokenProviderRegistrationRelease(func() { + client.unregisterGitHubTokenProvider(registrationID) + }) + client.sessions[session.SessionID] = session + + if err := client.DeleteSession(t.Context(), session.SessionID); err != nil { + t.Fatal(err) + } + if len(client.gitHubTokenProviders) != 0 { + t.Fatal("provider registration was not removed after session deletion") + } +} + +func TestSessionOperationsSerializeBySessionID(t *testing.T) { + client := &Client{} + unlockFirst := client.lockSessionOperation("same-session") + sameSessionAcquired := make(chan struct{}) + go func() { + unlock := client.lockSessionOperation("same-session") + close(sameSessionAcquired) + unlock() + }() + + select { + case <-sameSessionAcquired: + t.Fatal("same-session operation was not serialized") + case <-time.After(25 * time.Millisecond): + } + + otherSessionAcquired := make(chan struct{}) + go func() { + unlock := client.lockSessionOperation("other-session") + close(otherSessionAcquired) + unlock() + }() + select { + case <-otherSessionAcquired: + case <-time.After(time.Second): + t.Fatal("different-session operation was unnecessarily blocked") + } + + unlockFirst() + select { + case <-sameSessionAcquired: + case <-time.After(time.Second): + t.Fatal("same-session operation did not proceed after release") + } +} + +func TestGitHubTokenProvidersClearedOnConnectionClose(t *testing.T) { + client := &Client{state: stateConnected} + client.registerGitHubTokenProvider(func(GitHubTokenProviderArgs) (*GitHubTokenProviderResult, error) { + return GitHubTokenCancelled(), nil + }) + + client.handleConnectionClose() + + if len(client.gitHubTokenProviders) != 0 { + t.Fatal("provider registrations were not cleared after connection closure") + } +} + +func TestGitHubTokenProviderConcurrentRegistrationsAreIsolated(t *testing.T) { + client := &Client{} + adapter := &gitHubTokenAdapter{client: client} + idA := client.registerGitHubTokenProvider(func(GitHubTokenProviderArgs) (*GitHubTokenProviderResult, error) { + return GitHubTokenResult(&GitHubToken{AccessToken: "a", ExpiresIn: 1}), nil + }) + idB := client.registerGitHubTokenProvider(func(GitHubTokenProviderArgs) (*GitHubTokenProviderResult, error) { + return GitHubTokenResult(&GitHubToken{AccessToken: "b", ExpiresIn: 1}), nil + }) + + var wg sync.WaitGroup + for id, want := range map[string]string{idA: "a", idB: "b"} { + wg.Add(1) + go func() { + defer wg.Done() + result, err := adapter.GetToken(&rpc.GitHubTokenAcquireRequest{RegistrationID: id}) + if err != nil { + t.Error(err) + return + } + if got := result.(*rpc.GitHubTokenAcquireResultToken).AccessToken; got != want { + t.Errorf("token = %q, want %q", got, want) + } + }() + } + wg.Wait() +} diff --git a/go/session.go b/go/session.go index 600a4bbebc..3f6c4f605f 100644 --- a/go/session.go +++ b/go/session.go @@ -56,42 +56,45 @@ type sessionHandler struct { // }) type Session struct { // SessionID is the unique identifier for this session. - SessionID string - workspacePath string - client *jsonrpc2.Client - clientSessionAPIs *rpc.ClientSessionAPIHandlers - handlers []sessionHandler - nextHandlerID uint64 - handlerMutex sync.RWMutex - toolHandlers map[string]ToolHandler - toolHandlersM sync.RWMutex - permissionHandler PermissionHandlerFunc - permissionMux sync.RWMutex - managedSettings bool - mcpAuthHandler MCPAuthHandler - mcpAuthMu sync.RWMutex - userInputHandler UserInputHandler - userInputMux sync.RWMutex - exitPlanModeHandler ExitPlanModeRequestHandler - exitPlanModeMu sync.RWMutex - autoModeSwitchHandler AutoModeSwitchRequestHandler - autoModeSwitchMu sync.RWMutex - hooks *SessionHooks - hooksMux sync.RWMutex - transformCallbacks map[string]SectionTransformFn - transformMu sync.Mutex - commandHandlers map[string]CommandHandler - commandHandlersMu sync.RWMutex - elicitationHandler ElicitationHandler - elicitationMu sync.RWMutex - canvasHandler CanvasHandler - canvasMu sync.RWMutex - bearerTokenProviders map[string]BearerTokenProvider - bearerTokenMu sync.RWMutex - openCanvases []rpc.OpenCanvasInstance - openCanvasesMu sync.RWMutex - capabilities SessionCapabilities - capabilitiesMu sync.RWMutex + SessionID string + workspacePath string + client *jsonrpc2.Client + clientSessionAPIs *rpc.ClientSessionAPIHandlers + handlers []sessionHandler + nextHandlerID uint64 + handlerMutex sync.RWMutex + toolHandlers map[string]ToolHandler + toolHandlersM sync.RWMutex + permissionHandler PermissionHandlerFunc + permissionMux sync.RWMutex + managedSettings bool + mcpAuthHandler MCPAuthHandler + mcpAuthMu sync.RWMutex + userInputHandler UserInputHandler + userInputMux sync.RWMutex + exitPlanModeHandler ExitPlanModeRequestHandler + exitPlanModeMu sync.RWMutex + autoModeSwitchHandler AutoModeSwitchRequestHandler + autoModeSwitchMu sync.RWMutex + hooks *SessionHooks + hooksMux sync.RWMutex + transformCallbacks map[string]SectionTransformFn + transformMu sync.Mutex + commandHandlers map[string]CommandHandler + commandHandlersMu sync.RWMutex + elicitationHandler ElicitationHandler + elicitationMu sync.RWMutex + canvasHandler CanvasHandler + canvasMu sync.RWMutex + bearerTokenProviders map[string]BearerTokenProvider + bearerTokenMu sync.RWMutex + releaseGitHubTokenProvider func() + gitHubTokenProviderMu sync.Mutex + gitHubTokenProviderReleased bool + openCanvases []rpc.OpenCanvasInstance + openCanvasesMu sync.RWMutex + capabilities SessionCapabilities + capabilitiesMu sync.RWMutex // eventCh serializes user event handler dispatch. dispatchEvent enqueues; // a single goroutine (processEvents) dequeues and invokes handlers in FIFO order. @@ -1722,11 +1725,9 @@ func (s *Session) GetEvents(ctx context.Context) ([]SessionEvent, error) { // } func (s *Session) Disconnect() error { _, err := s.client.Request(context.Background(), "session.destroy", sessionDestroyRequest{SessionID: s.SessionID}) - if err != nil { - return fmt.Errorf("failed to disconnect session: %w", err) - } s.closeOnce.Do(func() { close(s.eventCh) }) + s.releaseGitHubTokenProviderRegistration() // Clear handlers s.handlerMutex.Lock() @@ -1749,9 +1750,38 @@ func (s *Session) Disconnect() error { s.elicitationHandler = nil s.elicitationMu.Unlock() + if err != nil { + return fmt.Errorf("failed to disconnect session: %w", err) + } return nil } +func (s *Session) releaseGitHubTokenProviderRegistration() { + s.gitHubTokenProviderMu.Lock() + if s.gitHubTokenProviderReleased { + s.gitHubTokenProviderMu.Unlock() + return + } + s.gitHubTokenProviderReleased = true + release := s.releaseGitHubTokenProvider + s.releaseGitHubTokenProvider = nil + s.gitHubTokenProviderMu.Unlock() + if release != nil { + release() + } +} + +func (s *Session) setGitHubTokenProviderRegistrationRelease(release func()) { + s.gitHubTokenProviderMu.Lock() + if !s.gitHubTokenProviderReleased { + s.releaseGitHubTokenProvider = release + s.gitHubTokenProviderMu.Unlock() + return + } + s.gitHubTokenProviderMu.Unlock() + release() +} + // Abort aborts the currently processing message in this session. // // Use this to cancel a long-running request. The session remains valid diff --git a/go/types.go b/go/types.go index c411290fa2..38ddaf5663 100644 --- a/go/types.go +++ b/go/types.go @@ -1326,6 +1326,9 @@ type SessionConfig struct { // When provided, the SDK can satisfy MCP server OAuth requests with host-provided // token data or cancellation. OnMCPAuthRequest MCPAuthHandler + // GitHubTokenProvider acquires session-scoped GitHub tokens on demand. It + // cannot be combined with GitHubToken. + GitHubTokenProvider GitHubTokenProvider // OnUserInputRequest is a handler for user input requests from the agent (enables ask_user tool) OnUserInputRequest UserInputHandler // Hooks configures hook handlers for session lifecycle events @@ -1802,6 +1805,9 @@ type ResumeSessionConfig struct { // ClientName identifies the application using the SDK. // Included in the User-Agent header for API requests. ClientName string + // GitHubTokenProvider acquires session-scoped GitHub tokens on demand. It + // cannot be combined with GitHubToken. + GitHubTokenProvider GitHubTokenProvider // Model to use for this session. Can change the model when resuming. Model string // Tools exposes caller-implemented tools to the CLI. A Tool with a nil Handler @@ -2530,6 +2536,7 @@ type createSessionRequest struct { RequestMCPApps *bool `json:"requestMcpApps,omitempty"` GitHubMCPToolConfig *GitHubMCPToolConfig `json:"githubMcpToolConfig,omitempty"` GitHubToken string `json:"gitHubToken,omitempty"` + GitHubTokenProviderRegistrationID string `json:"gitHubTokenProviderRegistrationId,omitempty"` RemoteSession rpc.RemoteSessionMode `json:"remoteSession,omitempty"` Cloud *CloudSessionOptions `json:"cloud,omitempty"` Canvases []CanvasDeclaration `json:"canvases,omitempty"` @@ -2628,6 +2635,7 @@ type resumeSessionRequest struct { RequestMCPApps *bool `json:"requestMcpApps,omitempty"` GitHubMCPToolConfig *GitHubMCPToolConfig `json:"githubMcpToolConfig,omitempty"` GitHubToken string `json:"gitHubToken,omitempty"` + GitHubTokenProviderRegistrationID string `json:"gitHubTokenProviderRegistrationId,omitempty"` RemoteSession rpc.RemoteSessionMode `json:"remoteSession,omitempty"` Canvases []CanvasDeclaration `json:"canvases,omitempty"` OpenCanvases []rpc.OpenCanvasInstance `json:"openCanvases,omitempty"` diff --git a/java/README.md b/java/README.md index 1874dd4781..d7d6bdf269 100644 --- a/java/README.md +++ b/java/README.md @@ -176,6 +176,25 @@ directly. `CopilotClientOptions.setCwd(...)` sets the runtime process working directory, which otherwise inherits the current process working directory. `SessionConfig.setWorkingDirectory(...)` sets the session working directory, which otherwise defaults to the runtime process working directory. +For rotating per-session GitHub credentials, use +`SessionConfig.setGitHubTokenProvider(...)` (or the equivalent +`ResumeSessionConfig` setter) instead of `setGitHubToken(...)`: + +```java +var config = new SessionConfig().setGitHubTokenProvider(args -> + acquireForHost(args.host()).thenApply(token -> + GitHubTokenProviderResult.token(token, 8 * 60 * 60))); +``` + +The remaining lifetime is required and must be positive when the callback +completes; production GitHub tokens typically last eight hours. A static token +and a provider are mutually exclusive. + +Initial acquisition runs during session creation or resume. Cancellation, +provider errors, and invalid token responses reject that operation instead of +falling back to ambient authentication. Idle sessions refresh only before their +next credential-consuming operation; there is no background refresh timer. + ## Permission Handling `PermissionHandler.APPROVE_ALL` approves requests when managed settings are disabled. When `enableManagedSettings` is true, it completes exceptionally. Custom handlers can inspect `request.getManagedApprovalRequired()` for human-facing confirmation logic. 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 ef7ca481ba..ea2b0b67dd 100644 --- a/java/sdk/src/main/java/com/github/copilot/CopilotClient.java +++ b/java/sdk/src/main/java/com/github/copilot/CopilotClient.java @@ -117,6 +117,7 @@ public final class CopilotClient implements AutoCloseable { private final CliServerManager serverManager; private final LifecycleEventManager lifecycleManager = new LifecycleEventManager(); private final Map sessions = new ConcurrentHashMap<>(); + private final GitHubTokenProviderRegistry gitHubTokenProviders = new GitHubTokenProviderRegistry(); private volatile CompletableFuture connectionFuture; private volatile boolean disposed = false; private final String optionsHost; @@ -558,7 +559,8 @@ private Connection startCoreBody() { inProcessTransport == null ? null : inProcessTransport.host()); // Register handlers for server-to-client calls - RpcHandlerDispatcher dispatcher = new RpcHandlerDispatcher(sessions, lifecycleManager::dispatch, executor); + RpcHandlerDispatcher dispatcher = new RpcHandlerDispatcher(sessions, lifecycleManager::dispatch, executor, + gitHubTokenProviders); dispatcher.registerHandlers(connectedRpc); // Register the LLM inference request handler when configured. @@ -727,6 +729,7 @@ public CompletableFuture stop() { closeFutures.add(future); } sessions.clear(); + gitHubTokenProviders.clear(); return CompletableFuture.allOf(closeFutures.toArray(new CompletableFuture[0])) .thenCompose(v -> cleanupConnection(true)); @@ -740,6 +743,7 @@ public CompletableFuture stop() { public CompletableFuture forceStop() { disposed = true; sessions.clear(); + gitHubTokenProviders.clear(); // Dispatch the blocking shutdownOwnedExecutor() on a dedicated thread: // cleanupConnection() is chained off async work running on the owned // executor, so a plain whenComplete(...) here could land the awaitTermination @@ -873,6 +877,10 @@ public CompletableFuture createSession(SessionConfig config) { + "For example, to allow all permissions, use: " + "new SessionConfig().setOnPermissionRequest(PermissionHandler.APPROVE_ALL)")); } + if (config.getGitHubToken() != null && config.getGitHubTokenProvider() != null) { + return CompletableFuture.failedFuture( + new IllegalArgumentException("gitHubToken and gitHubTokenProvider are mutually exclusive")); + } return ensureConnected().thenCompose(connection -> { long totalNanos = System.nanoTime(); // For cloud sessions, let the CLI/server assign the session id @@ -978,6 +986,16 @@ public CompletableFuture createSession(SessionConfig config) { } } + GitHubTokenProviderRegistry.Registration tokenRegistration = config.getGitHubTokenProvider() == null + ? null + : gitHubTokenProviders.register(config.getGitHubTokenProvider()); + if (tokenRegistration != null) { + request.setGitHubTokenProviderRegistrationId(tokenRegistration.id()); + if (preRegisteredSessionHolder[0] != null) { + preRegisteredSessionHolder[0].setGitHubTokenProviderRegistration(tokenRegistration); + } + } + long rpcNanos = System.nanoTime(); return connection.rpc.invoke("session.create", request, CreateSessionResponse.class) .thenCompose(response -> { @@ -996,6 +1014,9 @@ public CompletableFuture createSession(SessionConfig config) { CopilotSession session = preRegisteredSessionHolder[0] != null ? preRegisteredSessionHolder[0] : initializeSession.apply(returnedId); + if (tokenRegistration != null) { + session.setGitHubTokenProviderRegistration(tokenRegistration); + } registeredIdHolder[0] = returnedId; CompletableFuture interest = config.getOnMcpAuthRequest() != null ? session.getRpc().eventLog.registerInterest( @@ -1012,6 +1033,11 @@ public CompletableFuture createSession(SessionConfig config) { config.getCoauthorEnabled().orElse(null), config.getManageScheduleEnabled().orElse(null), config.getIncludedBuiltinSkills()); }).thenApply(v -> { + if (tokenRegistration != null) { + tokenRegistration.claim(session.getSessionId()); + } else { + gitHubTokenProviders.retire(session.getSessionId()); + } LoggingHelpers.logTiming(LOG, Level.FINE, "CopilotClient.createSession complete. Elapsed={Elapsed}, SessionId=" + session.getSessionId(), @@ -1022,6 +1048,9 @@ public CompletableFuture createSession(SessionConfig config) { if (registeredIdHolder[0] != null) { sessions.remove(registeredIdHolder[0]); } + if (tokenRegistration != null) { + tokenRegistration.close(); + } LoggingHelpers.logTiming(LOG, Level.WARNING, ex, "CopilotClient.createSession failed. Elapsed={Elapsed}, SessionId=" + (registeredIdHolder[0] != null ? registeredIdHolder[0] : ""), @@ -1069,10 +1098,15 @@ public CompletableFuture resumeSession(String sessionId, ResumeS + "For example, to allow all permissions, use: " + "new ResumeSessionConfig().setOnPermissionRequest(PermissionHandler.APPROVE_ALL)")); } + if (config.getGitHubToken() != null && config.getGitHubTokenProvider() != null) { + return CompletableFuture.failedFuture( + new IllegalArgumentException("gitHubToken and gitHubTokenProvider are mutually exclusive")); + } return ensureConnected().thenCompose(connection -> { long totalNanos = System.nanoTime(); // Register the session before the RPC call to avoid missing early events. long setupNanos = System.nanoTime(); + CopilotSession replacedSession = sessions.get(sessionId); var session = new CopilotSession(sessionId, connection.rpc); session.setExecutor(executor); SessionRequestBuilder.configureSession(session, config); @@ -1137,6 +1171,14 @@ public CompletableFuture resumeSession(String sessionId, ResumeS } } + GitHubTokenProviderRegistry.Registration tokenRegistration = config.getGitHubTokenProvider() == null + ? null + : gitHubTokenProviders.register(config.getGitHubTokenProvider()); + if (tokenRegistration != null) { + request.setGitHubTokenProviderRegistrationId(tokenRegistration.id()); + session.setGitHubTokenProviderRegistration(tokenRegistration); + } + long rpcNanos = System.nanoTime(); return connection.rpc.invoke("session.resume", request, ResumeSessionResponse.class) .thenCompose(response -> { @@ -1166,7 +1208,6 @@ public CompletableFuture resumeSession(String sessionId, ResumeS session.setActiveSessionId(returnedId); sessions.put(returnedId, session); } - return updateSessionOptionsForMode(session, config.getSkipCustomInstructions().orElse(null), config.getCustomAgentsLocalOnly().orElse(null), config.getCoauthorEnabled().orElse(null), @@ -1176,6 +1217,11 @@ public CompletableFuture resumeSession(String sessionId, ResumeS "CopilotClient.resumeSession complete. Elapsed={Elapsed}, SessionId=" + sessionId, totalNanos); + if (tokenRegistration != null) { + tokenRegistration.claim(session.getSessionId()); + } else { + gitHubTokenProviders.retire(session.getSessionId()); + } return session; }); }).exceptionally(ex -> { @@ -1185,6 +1231,12 @@ public CompletableFuture resumeSession(String sessionId, ResumeS if (!sessionId.equals(activeId)) { sessions.remove(activeId); } + if (replacedSession != null) { + sessions.putIfAbsent(sessionId, replacedSession); + } + if (tokenRegistration != null) { + tokenRegistration.close(); + } LoggingHelpers.logTiming(LOG, Level.WARNING, ex, "CopilotClient.resumeSession failed. Elapsed={Elapsed}, SessionId=" + sessionId, totalNanos); @@ -1524,7 +1576,10 @@ public CompletableFuture deleteSession(String sessionId) { if (!response.success()) { throw new RuntimeException("Failed to delete session " + sessionId + ": " + response.error()); } - sessions.remove(sessionId); + CopilotSession session = sessions.remove(sessionId); + if (session != null) { + session.releaseGitHubTokenProviderRegistration(); + } })); } diff --git a/java/sdk/src/main/java/com/github/copilot/CopilotSession.java b/java/sdk/src/main/java/com/github/copilot/CopilotSession.java index bccf914db7..83ce49654e 100644 --- a/java/sdk/src/main/java/com/github/copilot/CopilotSession.java +++ b/java/sdk/src/main/java/com/github/copilot/CopilotSession.java @@ -198,6 +198,7 @@ public final class CopilotSession implements AutoCloseable { private volatile Map>> transformCallbacks; private final ScheduledExecutorService timeoutScheduler; private volatile Executor executor; + private volatile GitHubTokenProviderRegistry.Registration gitHubTokenProviderRegistration; /** Tracks whether this session instance has been terminated via close(). */ private volatile boolean isTerminated = false; @@ -252,6 +253,18 @@ void setExecutor(Executor executor) { this.executor = executor; } + void setGitHubTokenProviderRegistration(GitHubTokenProviderRegistry.Registration registration) { + this.gitHubTokenProviderRegistration = registration; + } + + synchronized void releaseGitHubTokenProviderRegistration() { + GitHubTokenProviderRegistry.Registration registration = gitHubTokenProviderRegistration; + gitHubTokenProviderRegistration = null; + if (registration != null) { + registration.close(); + } + } + /** * Gets the unique identifier for this session. * @@ -2301,6 +2314,7 @@ public void close() { } timeoutScheduler.shutdownNow(); + releaseGitHubTokenProviderRegistration(); try { rpc.invoke("session.destroy", Map.of("sessionId", sessionId), Void.class).get(5, TimeUnit.SECONDS); diff --git a/java/sdk/src/main/java/com/github/copilot/GitHubTokenProviderRegistry.java b/java/sdk/src/main/java/com/github/copilot/GitHubTokenProviderRegistry.java new file mode 100644 index 0000000000..59b4f61469 --- /dev/null +++ b/java/sdk/src/main/java/com/github/copilot/GitHubTokenProviderRegistry.java @@ -0,0 +1,79 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +package com.github.copilot; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +import com.github.copilot.rpc.GitHubTokenProvider; + +final class GitHubTokenProviderRegistry { + + private final Map providers = new HashMap<>(); + private final Map sessionOwners = new HashMap<>(); + + synchronized Registration register(GitHubTokenProvider provider) { + String id = UUID.randomUUID().toString(); + providers.put(id, provider); + return new Registration(this, id); + } + + synchronized GitHubTokenProvider get(String registrationId) { + return providers.get(registrationId); + } + + private synchronized void claim(String registrationId, String sessionId) { + String previous = sessionOwners.put(sessionId, registrationId); + if (previous != null && !previous.equals(registrationId)) { + providers.remove(previous); + } + } + + private synchronized void unregister(String registrationId) { + providers.remove(registrationId); + sessionOwners.values().removeIf(registrationId::equals); + } + + synchronized void retire(String sessionId) { + String registrationId = sessionOwners.remove(sessionId); + if (registrationId != null) { + providers.remove(registrationId); + } + } + + synchronized void clear() { + providers.clear(); + sessionOwners.clear(); + } + + static final class Registration implements AutoCloseable { + + private final GitHubTokenProviderRegistry registry; + private final String id; + private boolean closed; + + private Registration(GitHubTokenProviderRegistry registry, String id) { + this.registry = registry; + this.id = id; + } + + String id() { + return id; + } + + void claim(String sessionId) { + registry.claim(id, sessionId); + } + + @Override + public synchronized void close() { + if (!closed) { + closed = true; + registry.unregister(id); + } + } + } +} diff --git a/java/sdk/src/main/java/com/github/copilot/JsonRpcClient.java b/java/sdk/src/main/java/com/github/copilot/JsonRpcClient.java index 550bd4ca42..7eda069d23 100644 --- a/java/sdk/src/main/java/com/github/copilot/JsonRpcClient.java +++ b/java/sdk/src/main/java/com/github/copilot/JsonRpcClient.java @@ -28,6 +28,7 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import com.github.copilot.rpc.JsonRpcError; import com.github.copilot.rpc.JsonRpcRequest; @@ -220,7 +221,39 @@ private synchronized void sendMessage(Object message) throws IOException { outputStream.write(content); outputStream.flush(); - LOG.fine("Sent: " + json); + if (LOG.isLoggable(Level.FINE)) { + LOG.fine("Sent: " + redactCredentialsForLogging(json)); + } + } + + static String redactCredentialsForLogging(String json) { + try { + JsonNode root = MAPPER.readTree(json); + redactCredentials(root); + return MAPPER.writeValueAsString(root); + } catch (JsonProcessingException error) { + return ""; + } + } + + private static void redactCredentials(JsonNode node) { + if (node.isObject()) { + var object = (ObjectNode) node; + object.properties().forEach(entry -> { + if (isCredentialField(entry.getKey())) { + object.put(entry.getKey(), ""); + } else { + redactCredentials(entry.getValue()); + } + }); + } else if (node.isArray()) { + node.forEach(JsonRpcClient::redactCredentials); + } + } + + private static boolean isCredentialField(String name) { + return name.equals("accessToken") || name.equals("gitHubToken") || name.equals("bearerToken") + || name.equals("apiKey"); } private void startReader() { diff --git a/java/sdk/src/main/java/com/github/copilot/RpcHandlerDispatcher.java b/java/sdk/src/main/java/com/github/copilot/RpcHandlerDispatcher.java index d2dff958dc..0dd8a5dc98 100644 --- a/java/sdk/src/main/java/com/github/copilot/RpcHandlerDispatcher.java +++ b/java/sdk/src/main/java/com/github/copilot/RpcHandlerDispatcher.java @@ -20,7 +20,12 @@ import com.github.copilot.rpc.AutoModeSwitchRequest; import com.github.copilot.rpc.ExitPlanModeRequest; import com.github.copilot.rpc.BearerTokenProvider; +import com.github.copilot.rpc.GitHubTokenProviderArgs; +import com.github.copilot.rpc.GitHubTokenProviderResult; import com.github.copilot.rpc.ProviderTokenArgs; +import com.github.copilot.generated.rpc.GitHubTokenAcquireRequest; +import com.github.copilot.generated.rpc.GitHubTokenAcquireResultCancelled; +import com.github.copilot.generated.rpc.GitHubTokenAcquireResultToken; import com.github.copilot.rpc.PermissionRequestResult; import com.github.copilot.rpc.PermissionRequestResultKind; import com.github.copilot.rpc.SessionLifecycleEvent; @@ -51,6 +56,7 @@ final class RpcHandlerDispatcher { private final Map sessions; private final LifecycleEventDispatcher lifecycleDispatcher; private final Executor executor; + private final GitHubTokenProviderRegistry gitHubTokenProviders; /** * Creates a dispatcher with session registry and lifecycle dispatcher. @@ -63,10 +69,11 @@ final class RpcHandlerDispatcher { * the executor for async dispatch, or {@code null} for default */ RpcHandlerDispatcher(Map sessions, LifecycleEventDispatcher lifecycleDispatcher, - Executor executor) { + Executor executor, GitHubTokenProviderRegistry gitHubTokenProviders) { this.sessions = sessions; this.lifecycleDispatcher = lifecycleDispatcher; this.executor = executor; + this.gitHubTokenProviders = gitHubTokenProviders; } /** @@ -92,6 +99,71 @@ void registerHandlers(JsonRpcClient rpc) { (requestId, params) -> handleSystemMessageTransform(rpc, requestId, params)); rpc.registerMethodHandler("providerToken.getToken", (requestId, params) -> handleProviderTokenGetToken(rpc, requestId, params)); + rpc.registerMethodHandler("gitHubToken.getToken", + (requestId, params) -> handleGitHubTokenGetToken(rpc, requestId, params)); + } + + private void handleGitHubTokenGetToken(JsonRpcClient rpc, String requestId, JsonNode params) { + runAsync(() -> { + final long requestIdLong = parseRequestId(requestId, "gitHubToken.getToken"); + if (requestIdLong == -1) { + return; + } + try { + GitHubTokenAcquireRequest request = MAPPER.treeToValue(params, GitHubTokenAcquireRequest.class); + var provider = gitHubTokenProviders.get(request.registrationId()); + if (provider == null) { + rpc.sendErrorResponse(requestIdLong, -32603, "Unknown GitHub token provider registration"); + return; + } + + var resultFuture = provider + .getToken(new GitHubTokenProviderArgs(request.host(), request.sessionId(), request.reason())); + if (resultFuture == null) { + rpc.sendErrorResponse(requestIdLong, -32603, "GitHub token provider returned a null future"); + return; + } + resultFuture.thenAccept(result -> sendGitHubTokenResult(rpc, requestIdLong, result)) + .exceptionally(error -> { + try { + Throwable cause = error instanceof java.util.concurrent.CompletionException + && error.getCause() != null ? error.getCause() : error; + rpc.sendErrorResponse(requestIdLong, -32603, + "GitHub token provider failed: " + cause.getMessage()); + } catch (IOException sendError) { + LOG.log(Level.SEVERE, "Error sending GitHub token provider error", sendError); + } + return null; + }); + } catch (Exception error) { + try { + rpc.sendErrorResponse(requestIdLong, -32603, + "GitHub token provider handler failed: " + error.getMessage()); + } catch (IOException sendError) { + LOG.log(Level.SEVERE, "Error sending GitHub token provider handler error", sendError); + } + } + }); + } + + private void sendGitHubTokenResult(JsonRpcClient rpc, long requestId, GitHubTokenProviderResult result) { + try { + if (result == null) { + rpc.sendErrorResponse(requestId, -32603, "GitHub token provider returned a null result"); + return; + } + if (result.isCancelled()) { + rpc.sendResponse(requestId, new GitHubTokenAcquireResultCancelled()); + return; + } + var wireResult = new GitHubTokenAcquireResultToken(); + wireResult.setAccessToken(result.getAccessToken()); + wireResult.setExpiresIn(result.getExpiresIn()); + wireResult.setTokenType(result.getTokenType()); + rpc.sendResponse(requestId, wireResult); + } catch (IOException error) { + LOG.log(Level.SEVERE, "Error sending GitHub token provider result", error); + } } private void handleSessionEvent(JsonNode params) { diff --git a/java/sdk/src/main/java/com/github/copilot/rpc/CreateSessionRequest.java b/java/sdk/src/main/java/com/github/copilot/rpc/CreateSessionRequest.java index 2eab977db1..403893987d 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 @@ -224,6 +224,9 @@ public final class CreateSessionRequest { @JsonProperty("gitHubToken") private String gitHubToken; + @JsonProperty("gitHubTokenProviderRegistrationId") + private String gitHubTokenProviderRegistrationId; + @JsonProperty("remoteSession") private String remoteSession; @@ -1061,6 +1064,21 @@ public void setGitHubToken(String gitHubToken) { this.gitHubToken = gitHubToken; } + /** + * Gets the token-provider registration ID. @return the opaque registration ID + */ + public String getGitHubTokenProviderRegistrationId() { + return gitHubTokenProviderRegistrationId; + } + + /** + * Sets the token-provider registration ID. @param registrationId the opaque + * registration ID + */ + public void setGitHubTokenProviderRegistrationId(String registrationId) { + this.gitHubTokenProviderRegistrationId = registrationId; + } + /** Gets the remote session mode. @return the remote session mode */ public String getRemoteSession() { return remoteSession; diff --git a/java/sdk/src/main/java/com/github/copilot/rpc/GitHubTokenProvider.java b/java/sdk/src/main/java/com/github/copilot/rpc/GitHubTokenProvider.java new file mode 100644 index 0000000000..daefc2f8d8 --- /dev/null +++ b/java/sdk/src/main/java/com/github/copilot/rpc/GitHubTokenProvider.java @@ -0,0 +1,31 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +package com.github.copilot.rpc; + +import java.util.concurrent.CompletableFuture; + +/** + * Acquires rotating GitHub tokens for one session. + *

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

+ * Token values are redacted from {@link #toString()}. + * + * @since 1.0.0 + */ +public final class GitHubTokenProviderResult { + + private final String accessToken; + private final long expiresIn; + private final String tokenType; + private final boolean cancelled; + + private GitHubTokenProviderResult(String accessToken, long expiresIn, String tokenType, boolean cancelled) { + this.accessToken = accessToken; + this.expiresIn = expiresIn; + this.tokenType = tokenType; + this.cancelled = cancelled; + } + + /** + * Creates a token result. + * + * @param accessToken + * GitHub access token + * @param expiresIn + * positive remaining lifetime in seconds when the callback completes + * @return the token result + */ + public static GitHubTokenProviderResult token(String accessToken, long expiresIn) { + return token(accessToken, expiresIn, null); + } + + /** + * Creates a token result with an explicit OAuth token type. + * + * @param accessToken + * GitHub access token + * @param expiresIn + * positive remaining lifetime in seconds when the callback completes + * @param tokenType + * OAuth token type, or {@code null} to use the runtime's bearer + * default + * @return the token result + */ + public static GitHubTokenProviderResult token(String accessToken, long expiresIn, String tokenType) { + Objects.requireNonNull(accessToken, "accessToken must not be null"); + return new GitHubTokenProviderResult(accessToken, expiresIn, tokenType, false); + } + + /** + * Creates an explicit cancellation result. + * + * @return the cancellation result + */ + public static GitHubTokenProviderResult cancelled() { + return new GitHubTokenProviderResult(null, 0, null, true); + } + + /** + * Gets whether acquisition was cancelled. + * + * @return {@code true} for a cancellation result + */ + public boolean isCancelled() { + return cancelled; + } + + /** + * Gets the access token. + * + * @return the token, or {@code null} for cancellation + */ + public String getAccessToken() { + return accessToken; + } + + /** + * Gets the remaining token lifetime. + * + * @return remaining lifetime in seconds + */ + public long getExpiresIn() { + return expiresIn; + } + + /** + * Gets the OAuth token type. + * + * @return token type, or {@code null} for the runtime default + */ + public String getTokenType() { + return tokenType; + } + + @Override + public String toString() { + if (cancelled) { + return "GitHubTokenProviderResult{cancelled}"; + } + return "GitHubTokenProviderResult{accessToken=, expiresIn=" + expiresIn + ", tokenType=" + tokenType + + "}"; + } +} diff --git a/java/sdk/src/main/java/com/github/copilot/rpc/ResumeSessionConfig.java b/java/sdk/src/main/java/com/github/copilot/rpc/ResumeSessionConfig.java index 7b43a852dd..a55c3454e7 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 @@ -106,6 +106,8 @@ public class ResumeSessionConfig { private boolean enableMcpApps; private GitHubMcpToolConfig githubMcpToolConfig; private String gitHubToken; + @JsonIgnore + private GitHubTokenProvider gitHubTokenProvider; private String remoteSession; private CopilotExpAssignmentResponse expAssignments; private Boolean enableManagedSettings; @@ -1887,6 +1889,33 @@ public ResumeSessionConfig setGitHubToken(String gitHubToken) { return this; } + /** + * Gets the rotating GitHub token provider for the resumed session. + * + * @return the provider, or {@code null} when a static token is used + */ + public GitHubTokenProvider getGitHubTokenProvider() { + return gitHubTokenProvider; + } + + /** + * Sets the rotating GitHub token provider for the resumed session. + *

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

@@ -2071,6 +2100,7 @@ public ResumeSessionConfig clone() { copy.enableMcpApps = this.enableMcpApps; copy.githubMcpToolConfig = this.githubMcpToolConfig; copy.gitHubToken = this.gitHubToken; + copy.gitHubTokenProvider = this.gitHubTokenProvider; copy.remoteSession = this.remoteSession; copy.expAssignments = this.expAssignments; copy.enableManagedSettings = this.enableManagedSettings; 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 e52892477e..9b8e897fda 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 @@ -229,6 +229,9 @@ public final class ResumeSessionRequest { @JsonProperty("gitHubToken") private String gitHubToken; + @JsonProperty("gitHubTokenProviderRegistrationId") + private String gitHubTokenProviderRegistrationId; + @JsonProperty("remoteSession") private String remoteSession; @@ -1086,6 +1089,21 @@ public void setGitHubToken(String gitHubToken) { this.gitHubToken = gitHubToken; } + /** + * Gets the token-provider registration ID. @return the opaque registration ID + */ + public String getGitHubTokenProviderRegistrationId() { + return gitHubTokenProviderRegistrationId; + } + + /** + * Sets the token-provider registration ID. @param registrationId the opaque + * registration ID + */ + public void setGitHubTokenProviderRegistrationId(String registrationId) { + this.gitHubTokenProviderRegistrationId = registrationId; + } + /** Gets the remote session mode. @return the remote session mode */ public String getRemoteSession() { return remoteSession; 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 1c08628dfe..9f6ddb5efa 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 @@ -106,6 +106,8 @@ public class SessionConfig { private boolean enableMcpApps; private GitHubMcpToolConfig githubMcpToolConfig; private String gitHubToken; + @JsonIgnore + private GitHubTokenProvider gitHubTokenProvider; private String remoteSession; private CloudSessionOptions cloud; private CopilotExpAssignmentResponse expAssignments; @@ -1968,6 +1970,33 @@ public SessionConfig setGitHubToken(String gitHubToken) { return this; } + /** + * Gets the rotating GitHub token provider for this session. + * + * @return the provider, or {@code null} when a static token is used + */ + public GitHubTokenProvider getGitHubTokenProvider() { + return gitHubTokenProvider; + } + + /** + * Sets the rotating GitHub token provider for this session. + *

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

@@ -2086,10 +2115,11 @@ public Optional getEnableManagedSettings() { * (bypass-permissions policy) at session bootstrap. *

* When {@code true}, the runtime self-fetches enterprise managed settings using - * the session's {@link #getGitHubToken() gitHubToken}. Requires - * {@code gitHubToken} to be set; if omitted, the runtime is expected to reject - * session creation (fail-closed). When unset, behaves exactly as before. - * Serialized on the wire as {@code enableManagedSettings}. + * the session's static {@link #getGitHubToken() gitHubToken} or + * {@link #getGitHubTokenProvider() gitHubTokenProvider}. Requires one of those + * credentials; if both are omitted, the runtime is expected to reject session + * creation (fail-closed). When unset, behaves exactly as before. Serialized on + * the wire as {@code enableManagedSettings}. * * @param enableManagedSettings * {@code true} to opt into self-fetching managed settings @@ -2210,6 +2240,7 @@ public SessionConfig clone() { copy.enableMcpApps = this.enableMcpApps; copy.githubMcpToolConfig = this.githubMcpToolConfig; copy.gitHubToken = this.gitHubToken; + copy.gitHubTokenProvider = this.gitHubTokenProvider; copy.remoteSession = this.remoteSession; copy.cloud = this.cloud; copy.expAssignments = this.expAssignments; diff --git a/java/sdk/src/test/java/com/github/copilot/CopilotClientTest.java b/java/sdk/src/test/java/com/github/copilot/CopilotClientTest.java index 067571df13..4cfd7f4c48 100644 --- a/java/sdk/src/test/java/com/github/copilot/CopilotClientTest.java +++ b/java/sdk/src/test/java/com/github/copilot/CopilotClientTest.java @@ -8,6 +8,8 @@ import org.junit.jupiter.api.Test; import com.github.copilot.rpc.CopilotClientOptions; +import com.github.copilot.rpc.DeleteSessionResponse; +import com.github.copilot.rpc.GitHubTokenProviderResult; import com.github.copilot.rpc.PermissionHandler; import com.github.copilot.rpc.PingResponse; import com.github.copilot.rpc.SessionConfig; @@ -111,6 +113,35 @@ void testForceStopAndExternalStopDoNotRequestRuntimeShutdown() throws Exception verify(externalRpc, never()).invoke(eq("runtime.shutdown"), any(), eq(Void.class)); } + @Test + @SuppressWarnings("unchecked") + void testDeleteSessionReleasesGitHubTokenProvider() throws Exception { + var client = new CopilotClient(new CopilotClientOptions().setAutoStart(false)); + var rpc = mock(JsonRpcClient.class); + when(rpc.invoke(eq("session.delete"), any(), eq(DeleteSessionResponse.class))) + .thenReturn(CompletableFuture.completedFuture(new DeleteSessionResponse(true, null))); + when(rpc.invoke(eq("session.destroy"), any(), eq(Void.class))) + .thenReturn(CompletableFuture.completedFuture(null)); + setConnectionFuture(client, rpc, null); + + var registry = new GitHubTokenProviderRegistry(); + var registration = registry + .register(args -> CompletableFuture.completedFuture(GitHubTokenProviderResult.cancelled())); + var session = new CopilotSession("delete-session", rpc); + session.setGitHubTokenProviderRegistration(registration); + Field sessionsField = CopilotClient.class.getDeclaredField("sessions"); + sessionsField.setAccessible(true); + ((Map) sessionsField.get(client)).put(session.getSessionId(), session); + + try { + client.deleteSession(session.getSessionId()).join(); + assertNull(registry.get(registration.id())); + } finally { + session.close(); + client.close(); + } + } + @Test void testClientConstruction() { var client = new CopilotClient(); diff --git a/java/sdk/src/test/java/com/github/copilot/GitHubTokenProviderRegistryTest.java b/java/sdk/src/test/java/com/github/copilot/GitHubTokenProviderRegistryTest.java new file mode 100644 index 0000000000..8f2c7d3314 --- /dev/null +++ b/java/sdk/src/test/java/com/github/copilot/GitHubTokenProviderRegistryTest.java @@ -0,0 +1,74 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +package com.github.copilot; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; + +import org.junit.jupiter.api.Test; + +import com.github.copilot.rpc.GitHubTokenProviderResult; +import com.github.copilot.rpc.PermissionHandler; +import com.github.copilot.rpc.ResumeSessionConfig; +import com.github.copilot.rpc.SessionConfig; + +class GitHubTokenProviderRegistryTest { + + @Test + void staticTokenAndProviderAreMutuallyExclusive() { + try (var client = new CopilotClient()) { + var create = new SessionConfig().setGitHubToken("static") + .setGitHubTokenProvider( + args -> CompletableFuture.completedFuture(GitHubTokenProviderResult.cancelled())) + .setOnPermissionRequest(PermissionHandler.APPROVE_ALL); + var createError = assertThrows(CompletionException.class, () -> client.createSession(create).join()); + assertInstanceOf(IllegalArgumentException.class, createError.getCause()); + + var resume = new ResumeSessionConfig().setGitHubToken("static") + .setGitHubTokenProvider( + args -> CompletableFuture.completedFuture(GitHubTokenProviderResult.cancelled())) + .setOnPermissionRequest(PermissionHandler.APPROVE_ALL); + var resumeError = assertThrows(CompletionException.class, + () -> client.resumeSession("session", resume).join()); + assertInstanceOf(IllegalArgumentException.class, resumeError.getCause()); + } + } + + @Test + void registrationRollbackAndResumeReplacementAreIsolated() { + var registry = new GitHubTokenProviderRegistry(); + var first = registry.register(args -> CompletableFuture.completedFuture(GitHubTokenProviderResult.cancelled())); + var second = registry + .register(args -> CompletableFuture.completedFuture(GitHubTokenProviderResult.cancelled())); + + assertNotNull(registry.get(first.id())); + assertNotNull(registry.get(second.id())); + first.claim("session-1"); + second.claim("session-1"); + assertNull(registry.get(first.id())); + assertNotNull(registry.get(second.id())); + + first.close(); + assertNotNull(registry.get(second.id())); + second.close(); + assertNull(registry.get(second.id())); + + var retired = registry + .register(args -> CompletableFuture.completedFuture(GitHubTokenProviderResult.cancelled())); + retired.claim("session-2"); + registry.retire("session-2"); + assertNull(registry.get(retired.id())); + + var failed = registry + .register(args -> CompletableFuture.completedFuture(GitHubTokenProviderResult.cancelled())); + failed.close(); + assertNull(registry.get(failed.id())); + } +} diff --git a/java/sdk/src/test/java/com/github/copilot/JsonRpcClientTest.java b/java/sdk/src/test/java/com/github/copilot/JsonRpcClientTest.java index 79aaea10d8..009f15c200 100644 --- a/java/sdk/src/test/java/com/github/copilot/JsonRpcClientTest.java +++ b/java/sdk/src/test/java/com/github/copilot/JsonRpcClientTest.java @@ -115,6 +115,22 @@ void testNotify() throws Exception { } } + @Test + void testCredentialValuesAreRedactedOnlyFromDiagnosticRendering() throws Exception { + String json = """ + {"jsonrpc":"2.0","result":{"accessToken":"secret","nested":{"gitHubToken":"static"}}, + "metadata":{"tokenType":"Bearer"}} + """; + + String rendered = JsonRpcClient.redactCredentialsForLogging(json); + + assertFalse(rendered.contains("secret")); + assertFalse(rendered.contains("static")); + assertEquals("", MAPPER.readTree(rendered).at("/result/accessToken").asText()); + assertEquals("", MAPPER.readTree(rendered).at("/result/nested/gitHubToken").asText()); + assertEquals("Bearer", MAPPER.readTree(rendered).at("/metadata/tokenType").asText()); + } + // ---- isConnected() ---- @Test 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 76c2d41b29..1cb3ae82ec 100644 --- a/java/sdk/src/test/java/com/github/copilot/RpcHandlerDispatcherTest.java +++ b/java/sdk/src/test/java/com/github/copilot/RpcHandlerDispatcherTest.java @@ -15,6 +15,7 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.atomic.AtomicReference; import java.util.function.BiConsumer; import org.junit.jupiter.api.AfterEach; @@ -32,6 +33,9 @@ import com.github.copilot.rpc.ToolDefinition; import com.github.copilot.rpc.ToolResultObject; import com.github.copilot.rpc.UserInputResponse; +import com.github.copilot.generated.rpc.GitHubTokenAcquireReason; +import com.github.copilot.rpc.GitHubTokenProviderArgs; +import com.github.copilot.rpc.GitHubTokenProviderResult; /** * Unit tests for {@link RpcHandlerDispatcher} focusing on coverage gaps @@ -51,6 +55,7 @@ class RpcHandlerDispatcherTest { private RpcHandlerDispatcher dispatcher; private InputStream responseStream; private Map> handlers; + private GitHubTokenProviderRegistry gitHubTokenProviders; @BeforeEach void setup() throws Exception { @@ -66,7 +71,8 @@ void setup() throws Exception { sessions = new ConcurrentHashMap<>(); lifecycleEvents = new CopyOnWriteArrayList<>(); - dispatcher = new RpcHandlerDispatcher(sessions, lifecycleEvents::add, null); + gitHubTokenProviders = new GitHubTokenProviderRegistry(); + dispatcher = new RpcHandlerDispatcher(sessions, lifecycleEvents::add, null, gitHubTokenProviders); dispatcher.registerHandlers(rpc); // Extract the registered handlers via reflection so we can invoke them directly @@ -119,6 +125,57 @@ private CopilotSession createSession(String sessionId) { return session; } + @Test + void gitHubTokenCallbackMapsRequestAndTokenResult() throws Exception { + AtomicReference received = new AtomicReference<>(); + var registration = gitHubTokenProviders.register(args -> { + received.set(args); + return CompletableFuture.completedFuture(GitHubTokenProviderResult.token("secret", 28_800, "bearer")); + }); + ObjectNode params = MAPPER.createObjectNode(); + params.put("registrationId", registration.id()); + params.put("host", "github.example"); + params.put("sessionId", "session-1"); + params.put("reason", "initial"); + + invokeHandler("gitHubToken.getToken", "80", params); + + JsonNode response = readResponse(); + assertEquals("token", response.at("/result/kind").asText()); + assertEquals("secret", response.at("/result/accessToken").asText()); + assertEquals(28_800, response.at("/result/expiresIn").asLong()); + assertEquals("github.example", received.get().host()); + assertEquals("session-1", received.get().sessionId()); + assertEquals(GitHubTokenAcquireReason.INITIAL, received.get().reason()); + } + + @Test + void gitHubTokenCallbackPreservesCancellationAndErrors() throws Exception { + var cancelled = gitHubTokenProviders + .register(args -> CompletableFuture.completedFuture(GitHubTokenProviderResult.cancelled())); + ObjectNode cancelledParams = MAPPER.createObjectNode(); + cancelledParams.put("registrationId", cancelled.id()); + cancelledParams.put("host", "github.com"); + cancelledParams.put("reason", "refresh"); + invokeHandler("gitHubToken.getToken", "81", cancelledParams); + assertEquals("cancelled", readResponse().at("/result/kind").asText()); + + var failed = gitHubTokenProviders.register( + args -> CompletableFuture.failedFuture(new IllegalStateException("credential service unavailable"))); + ObjectNode failedParams = cancelledParams.deepCopy(); + failedParams.put("registrationId", failed.id()); + invokeHandler("gitHubToken.getToken", "82", failedParams); + JsonNode error = readResponse(); + assertEquals(-32603, error.at("/error/code").asInt()); + assertTrue(error.at("/error/message").asText().contains("credential service unavailable")); + + failedParams.put("registrationId", "unknown"); + invokeHandler("gitHubToken.getToken", "83", failedParams); + JsonNode unknown = readResponse(); + assertEquals(-32603, unknown.at("/error/code").asInt()); + assertTrue(unknown.at("/error/message").asText().contains("Unknown GitHub token provider registration")); + } + // ===== session.event tests ===== @Test diff --git a/java/sdk/src/test/java/com/github/copilot/SessionRequestBuilderTest.java b/java/sdk/src/test/java/com/github/copilot/SessionRequestBuilderTest.java index 0525786de6..9d76d18ee2 100644 --- a/java/sdk/src/test/java/com/github/copilot/SessionRequestBuilderTest.java +++ b/java/sdk/src/test/java/com/github/copilot/SessionRequestBuilderTest.java @@ -26,6 +26,7 @@ import com.github.copilot.rpc.ExitPlanModeResult; import com.github.copilot.rpc.ExpConfigEntry; import com.github.copilot.rpc.GitHubMcpToolConfig; +import com.github.copilot.rpc.GitHubTokenProviderResult; import com.github.copilot.rpc.LargeToolOutputConfig; import com.github.copilot.rpc.MemoryConfiguration; import com.github.copilot.rpc.ResumeSessionConfig; @@ -56,6 +57,27 @@ void testBuildCreateRequestNullConfig() { assertEquals("direct", request.getEnvValueMode(), "envValueMode should be 'direct' even for null config"); } + @Test + void testGitHubTokenProviderRegistrationWireFieldHasExactCasing() throws Exception { + var create = SessionRequestBuilder.buildCreateRequest(new SessionConfig(), "create-session"); + create.setGitHubTokenProviderRegistrationId("create-registration"); + var resume = SessionRequestBuilder.buildResumeRequest("resume-session", new ResumeSessionConfig()); + resume.setGitHubTokenProviderRegistrationId("resume-registration"); + var mapper = JsonRpcClient.getObjectMapper(); + + assertEquals("create-registration", + mapper.readTree(mapper.writeValueAsBytes(create)).path("gitHubTokenProviderRegistrationId").asText()); + assertEquals("resume-registration", + mapper.readTree(mapper.writeValueAsBytes(resume)).path("gitHubTokenProviderRegistrationId").asText()); + assertFalse(mapper.readTree(mapper.writeValueAsBytes(create)).has("gitHubToken")); + } + + @Test + void testGitHubTokenProviderResultRedactsToken() { + var result = GitHubTokenProviderResult.token("do-not-print", 28_800); + assertFalse(result.toString().contains("do-not-print")); + } + @Test void testBuildCreateRequestHooksNonNullButEmpty() { // Hooks object exists but hasHooks() returns false diff --git a/nodejs/README.md b/nodejs/README.md index eec674ce4e..93f9c3fa6b 100644 --- a/nodejs/README.md +++ b/nodejs/README.md @@ -137,12 +137,25 @@ Create a new conversation session. - `infiniteSessions?: InfiniteSessionConfig` - Configure automatic context compaction (see below) - `workingDirectory?: string` - Working directory for the session (default: runtime process cwd). - `enableSessionStore?: boolean` - Enables the cross-session store for search and retrieval across sessions. When unset in `"copilot-cli"` mode, the runtime default applies (enabled). In `"empty"` mode, defaults to disabled. +- `gitHubTokenProvider?: GitHubTokenProvider` - Acquires rotating, session-scoped GitHub tokens. Token results require a positive `expiresIn` value in seconds remaining when the callback completes; production tokens typically last eight hours. Cannot be combined with `gitHubToken`. - `provider?: ProviderConfig` - Custom API provider configuration (BYOK - Bring Your Own Key). See [Custom Providers](#custom-providers) section. - `onPermissionRequest?: PermissionHandler` - Optional handler called before each tool execution to approve or deny it. When omitted, permission requests are emitted as events and left pending for manual resolution. `approveAll` approves requests when managed settings are disabled and throws when `enableManagedSettings` is true. Custom handlers can inspect `managedApprovalRequired` for human-facing confirmation logic. See [Permission Handling](#permission-handling) section. - `onUserInputRequest?: UserInputHandler` - Handler for user input requests from the agent. Enables the `ask_user` tool. See [User Input Requests](#user-input-requests) section. - `onElicitationRequest?: ElicitationHandler` - Handler for elicitation requests dispatched by the server. Enables this client to present form-based UI dialogs on behalf of the agent or other session participants. See [Elicitation Requests](#elicitation-requests) section. - `hooks?: SessionHooks` - Hook handlers for session lifecycle events. See [Session Hooks](#session-hooks) section. +```typescript +const session = await client.createSession({ + gitHubTokenProvider: async ({ host }) => ({ + kind: "token", + accessToken: await acquireTokenForHost(host), + expiresIn: 8 * 60 * 60, + }), +}); +``` + +Initial acquisition runs during session creation or resume. Cancellation, provider errors, and invalid token responses reject that operation instead of falling back to ambient authentication. Idle sessions refresh only before their next credential-consuming operation; there is no background refresh timer. + ##### `resumeSession(sessionId: string, config?: ResumeSessionConfig): Promise` Resume an existing session. Returns the session with `workspacePath` populated if infinite sessions were enabled. diff --git a/nodejs/src/client.ts b/nodejs/src/client.ts index 2dfae099f1..9b853aa597 100644 --- a/nodejs/src/client.ts +++ b/nodejs/src/client.ts @@ -35,6 +35,8 @@ import { } from "./generated/rpc.js"; import type { GitHubTelemetryNotification, + GitHubTokenAcquireRequest, + GitHubTokenAcquireResult, OpenCanvasInstance, SessionUpdateOptionsParams, } from "./generated/rpc.js"; @@ -58,6 +60,7 @@ import type { ForegroundSessionInfo, GetAuthStatusResponse, BearerTokenProvider, + GitHubTokenProvider, GetStatusResponse, InternalRuntimeConnection, RuntimeConnection, @@ -524,6 +527,10 @@ export class CopilotClient { private builtinPluginDirectories: string[] = []; private onGitHubTelemetry?: (notification: GitHubTelemetryNotification) => void | Promise; private clientGlobalHandlers: import("./generated/rpc.js").ClientGlobalApiHandlers = {}; + private githubTokenProviders = new Map< + string, + { provider: GitHubTokenProvider; sessionId?: string; committed: boolean } + >(); /** * Typed server-scoped RPC methods. @@ -862,9 +869,65 @@ export class CopilotClient { }, }; } + handlers.gitHubToken = { + getToken: (params) => this.acquireGitHubToken(params), + }; this.clientGlobalHandlers = handlers; } + private async acquireGitHubToken( + params: GitHubTokenAcquireRequest + ): Promise { + const registration = this.githubTokenProviders.get(params.registrationId); + if (!registration) { + throw new Error( + `No GitHub token provider registered for registration ID "${params.registrationId}"` + ); + } + return await registration.provider({ + host: params.host, + sessionId: params.sessionId ?? registration.sessionId, + reason: params.reason, + }); + } + + private registerGitHubTokenProvider( + provider: GitHubTokenProvider | undefined, + sessionId?: string + ): string | undefined { + if (!provider) { + return undefined; + } + const registrationId = randomUUID(); + this.githubTokenProviders.set(registrationId, { provider, sessionId, committed: false }); + return registrationId; + } + + private assignGitHubTokenProvider(registrationId: string | undefined, sessionId: string): void { + if (!registrationId) { + return; + } + const registration = this.githubTokenProviders.get(registrationId); + if (registration) { + registration.sessionId = sessionId; + } + } + + private commitGitHubTokenProvider(sessionId: string, registrationId?: string): void { + for (const [candidateId, registration] of this.githubTokenProviders) { + if (registration.sessionId === sessionId && registration.committed) { + this.githubTokenProviders.delete(candidateId); + } + } + const registration = registrationId + ? this.githubTokenProviders.get(registrationId) + : undefined; + if (registration) { + registration.sessionId = sessionId; + registration.committed = true; + } + } + /** * Starts the CLI server and establishes a connection. * @@ -1015,6 +1078,7 @@ export class CopilotClient { session._markDisconnected(); } this.sessions.clear(); + this.githubTokenProviders.clear(); // Ask SDK-owned runtimes to flush and clean up before we tear down // their transport/process. External runtimes may be shared, so only @@ -1197,6 +1261,7 @@ export class CopilotClient { session._markDisconnected(); } this.sessions.clear(); + this.githubTokenProviders.clear(); // Force close connection. Suppress writer failures first so teardown // write rejections don't surface as unhandled rejections. @@ -1448,6 +1513,9 @@ export class CopilotClient { } async createSession(config: SessionConfig): Promise { + if (config.gitHubToken !== undefined && config.gitHubTokenProvider !== undefined) { + throw new Error("gitHubToken and gitHubTokenProvider are mutually exclusive"); + } if (!this.connection) { await this.start(); } @@ -1467,6 +1535,11 @@ export class CopilotClient { const callerSessionId = config.sessionId; const useServerGeneratedId = config.cloud != null && callerSessionId == null; const localSessionId = useServerGeneratedId ? undefined : (callerSessionId ?? randomUUID()); + const toolFilterOptions = this.resolveToolFilterOptions(config); + const gitHubTokenProviderRegistrationId = this.registerGitHubTokenProvider( + config.gitHubTokenProvider, + localSessionId + ); // Strip non-serializable bearerTokenProvider callbacks from provider configs, // replacing them with a wire flag; keep the callbacks for session-side @@ -1495,6 +1568,13 @@ export class CopilotClient { managedSettingsEnabled: config.enableManagedSettings === true || config.managedSettings !== undefined, + onDisconnected: + gitHubTokenProviderRegistrationId === undefined + ? undefined + : () => + this.githubTokenProviders.delete( + gitHubTokenProviderRegistrationId + ), } ); s.registerTools(config.tools); @@ -1538,12 +1618,17 @@ export class CopilotClient { // processing (e.g. sessionFs.writeFile for workspace metadata) can be // routed to the correct handlers. if (localSessionId !== undefined) { - session = initializeSession(localSessionId); - registeredId = localSessionId; + try { + session = initializeSession(localSessionId); + registeredId = localSessionId; + } catch (error) { + if (gitHubTokenProviderRegistrationId !== undefined) { + this.githubTokenProviders.delete(gitHubTokenProviderRegistrationId); + } + throw error; + } } - const toolFilterOptions = this.resolveToolFilterOptions(config); - try { const response = await this.connection!.sendRequest("session.create", { ...(await getTraceContext(this.onGetTraceContext)), @@ -1632,6 +1717,7 @@ export class CopilotClient { infiniteSessions: config.infiniteSessions, memory: config.memory, gitHubToken: config.gitHubToken, + gitHubTokenProviderRegistrationId, remoteSession: config.remoteSession, cloud: config.cloud, expAssignments: config.expAssignments, @@ -1662,6 +1748,7 @@ export class CopilotClient { session = initializeSession(returnedSessionId); registeredId = returnedSessionId; } + this.assignGitHubTokenProvider(gitHubTokenProviderRegistrationId, returnedSessionId); if (config.onMcpAuthRequest) { await this.connection!.sendRequest("session.eventLog.registerInterest", { sessionId: returnedSessionId, @@ -1672,10 +1759,14 @@ export class CopilotClient { session.setCapabilities(capabilities); await this.updateSessionOptionsForMode(session, config); + this.commitGitHubTokenProvider(returnedSessionId, gitHubTokenProviderRegistrationId); } catch (e) { if (registeredId !== undefined) { this.sessions.delete(registeredId); } + if (gitHubTokenProviderRegistrationId !== undefined) { + this.githubTokenProviders.delete(gitHubTokenProviderRegistrationId); + } throw e; } @@ -1726,6 +1817,9 @@ export class CopilotClient { factories?: FactoryHandle[], extensionOptions?: ExtensionJoinOptions ): Promise { + if (config.gitHubToken !== undefined && config.gitHubTokenProvider !== undefined) { + throw new Error("gitHubToken and gitHubTokenProvider are mutually exclusive"); + } if (!this.connection) { await this.start(); } @@ -1791,6 +1885,15 @@ export class CopilotClient { this.setupSessionFs(session, config); const toolFilterOptions = this.resolveToolFilterOptions(config); + const gitHubTokenProviderRegistrationId = this.registerGitHubTokenProvider( + config.gitHubTokenProvider, + sessionId + ); + if (gitHubTokenProviderRegistrationId !== undefined) { + session._setOnDisconnected(() => + this.githubTokenProviders.delete(gitHubTokenProviderRegistrationId) + ); + } try { const response = await this.connection!.sendRequest("session.resume", { @@ -1884,6 +1987,7 @@ export class CopilotClient { disableResume: config.suppressResumeEvent, continuePendingWork: config.continuePendingWork, gitHubToken: config.gitHubToken, + gitHubTokenProviderRegistrationId, remoteSession: config.remoteSession, openCanvases: config.openCanvases, expAssignments: config.expAssignments, @@ -1931,8 +2035,12 @@ export class CopilotClient { } await this.updateSessionOptionsForMode(session, config); + this.commitGitHubTokenProvider(sessionId, gitHubTokenProviderRegistrationId); } catch (e) { this.sessions.delete(sessionId); + if (gitHubTokenProviderRegistrationId !== undefined) { + this.githubTokenProviders.delete(gitHubTokenProviderRegistrationId); + } throw e; } @@ -2176,8 +2284,9 @@ export class CopilotClient { throw new Error(`Failed to delete session ${sessionId}: ${error || "Unknown error"}`); } - // Remove from local sessions map if present + const session = this.sessions.get(sessionId); this.sessions.delete(sessionId); + session?._runOnDisconnected(); } /** @@ -2940,6 +3049,7 @@ export class CopilotClient { this.connection.onClose(() => { this.state = "disconnected"; + this.githubTokenProviders.clear(); }); this.connection.onError((_error) => { diff --git a/nodejs/src/index.ts b/nodejs/src/index.ts index ae474eefee..bf7405c87b 100644 --- a/nodejs/src/index.ts +++ b/nodejs/src/index.ts @@ -97,6 +97,11 @@ export type { GitHubTelemetryNotification, GitHubTelemetryEvent, GitHubTelemetryClientInfo, + GitHubTokenAcquireReason, + GitHubTokenAcquireResult, + GitHubTokenProvider, + GitHubTokenProviderArgs, + GitHubTokenProviderResult, InfiniteSessionConfig, LargeToolOutputConfig, MemoryConfiguration, diff --git a/nodejs/src/session.ts b/nodejs/src/session.ts index efe9fa3e91..d8b67133ff 100644 --- a/nodejs/src/session.ts +++ b/nodejs/src/session.ts @@ -438,6 +438,7 @@ export class CopilotSession { private _capabilities: SessionCapabilities = {}; private openCanvasInstances: OpenCanvasInstance[] = []; private disconnected = false; + private onDisconnected?: () => void; /** @internal Client session API handlers, populated by CopilotClient during create/resume. */ clientSessionApis: ClientSessionApiHandlers = {}; @@ -617,11 +618,16 @@ export class CopilotSession { private connection: MessageConnection, private _workspacePath?: string, traceContextProvider?: TraceContextProvider, - options?: { mcpAuthHandler?: McpAuthHandler; managedSettingsEnabled?: boolean } + options?: { + mcpAuthHandler?: McpAuthHandler; + managedSettingsEnabled?: boolean; + onDisconnected?: () => void; + } ) { this.traceContextProvider = traceContextProvider; this.mcpAuthHandler = options?.mcpAuthHandler; this.managedSettingsEnabled = options?.managedSettingsEnabled === true; + this.onDisconnected = options?.onDisconnected; } /** @@ -798,7 +804,11 @@ export class CopilotSession { /** @internal */ _markDisconnected(): void { + if (this.disconnected) { + return; + } this.disconnected = true; + this._runOnDisconnected(); this.eventHandlers.clear(); this.typedEventHandlers.clear(); this.toolHandlers.clear(); @@ -819,6 +829,17 @@ export class CopilotSession { this.transformCallbacks?.clear(); } + /** @internal */ + _runOnDisconnected(): void { + this.onDisconnected?.(); + this.onDisconnected = undefined; + } + + /** @internal */ + _setOnDisconnected(callback: () => void): void { + this.onDisconnected = callback; + } + /** * Subscribes to events from this session. * diff --git a/nodejs/src/types.ts b/nodejs/src/types.ts index 5b5d8da482..f9c3c6110e 100644 --- a/nodejs/src/types.ts +++ b/nodejs/src/types.ts @@ -21,6 +21,8 @@ import type { import type { CopilotSession } from "./session.js"; import type { FactoryJsonSchema, JsonValue } from "./factory.js"; import type { + GitHubTokenAcquireRequest, + GitHubTokenAcquireResult, GitHubTelemetryNotification, ModelBillingTokenPrices, OpenCanvasInstance, @@ -31,10 +33,38 @@ import type { ToolSet } from "./toolSet.js"; export type { RemoteSessionMode } from "./generated/rpc.js"; export type { CurrentToolMetadata } from "./generated/rpc.js"; export type { + GitHubTokenAcquireReason, + GitHubTokenAcquireResult, GitHubTelemetryNotification, GitHubTelemetryEvent, GitHubTelemetryClientInfo, } from "./generated/rpc.js"; + +/** + * Arguments passed to a session's {@link GitHubTokenProvider}. + * + * The callback registration identifier is intentionally kept inside the SDK. + */ +export type GitHubTokenProviderArgs = Pick< + GitHubTokenAcquireRequest, + "host" | "sessionId" | "reason" +>; + +/** Tagged token or cancellation returned by a {@link GitHubTokenProvider}. */ +export type GitHubTokenProviderResult = GitHubTokenAcquireResult; + +/** + * Acquires a GitHub token for one session. + * + * A token result must include `expiresIn`: the positive number of seconds of + * remaining lifetime when the callback completes. Production GitHub tokens + * typically last eight hours. Initial cancellation, callback errors, and + * invalid token responses reject session creation or resume instead of falling + * back to ambient authentication. + */ +export type GitHubTokenProvider = ( + args: GitHubTokenProviderArgs +) => GitHubTokenProviderResult | Promise; export type { ModelBillingTokenPrices, ModelBillingTokenPricesLongContext, @@ -2719,6 +2749,16 @@ export interface SessionConfigBase { */ gitHubToken?: string; + /** + * Acquires short-lived GitHub credentials for this session on demand. + * + * Mutually exclusive with {@link SessionConfigBase.gitHubToken}. The + * callback receives the effective GitHub host, the session ID when known, + * and whether this is the initial acquisition or a refresh. Its opaque + * registration ID remains internal to the SDK. + */ + gitHubTokenProvider?: GitHubTokenProvider; + /** * Opt-in: when true, the runtime self-fetches enterprise managed settings * (bypass-permissions policy) at session bootstrap using the session's diff --git a/nodejs/test/github-token-provider.test.ts b/nodejs/test/github-token-provider.test.ts new file mode 100644 index 0000000000..2202f466a1 --- /dev/null +++ b/nodejs/test/github-token-provider.test.ts @@ -0,0 +1,292 @@ +import { describe, expect, it, vi } from "vitest"; +import { CopilotClient, RuntimeConnection, type GitHubTokenProvider } from "../src/index.js"; + +function createMockClient( + request: (method: string, params: Record) => Promise +): CopilotClient { + const client = new CopilotClient({ + connection: RuntimeConnection.forUri("localhost:1234"), + }); + (client as unknown as { connection: unknown }).connection = { + sendRequest: request, + dispose: vi.fn(), + }; + return client; +} + +function getTokenHandler(client: CopilotClient) { + return ( + client as unknown as { + clientGlobalHandlers: { + gitHubToken: { + getToken(params: { + registrationId: string; + host: string; + sessionId?: string; + reason: "initial" | "refresh"; + }): Promise; + }; + }; + } + ).clientGlobalHandlers.gitHubToken.getToken; +} + +describe("session GitHub token providers", () => { + it("rejects a static token and provider together", async () => { + const client = new CopilotClient({ + connection: RuntimeConnection.forUri("localhost:1234"), + }); + + await expect( + client.createSession({ + gitHubToken: "static", + gitHubTokenProvider: async () => ({ + kind: "token", + accessToken: "dynamic", + expiresIn: 28_800, + }), + }) + ).rejects.toThrow("gitHubToken and gitHubTokenProvider are mutually exclusive"); + }); + + it("serializes only the opaque registration and maps token and cancellation results", async () => { + let createPayload: Record | undefined; + const request = vi.fn(async (method: string, params: Record) => { + if (method === "session.create") { + createPayload = params; + return { sessionId: params.sessionId }; + } + throw new Error(`Unexpected method: ${method}`); + }); + const observed: unknown[] = []; + const provider: GitHubTokenProvider = vi + .fn() + .mockImplementationOnce(async (args) => { + observed.push(args); + return { + kind: "token", + accessToken: "secret-token", + tokenType: "Bearer", + expiresIn: 28_800, + }; + }) + .mockImplementationOnce(async (args) => { + observed.push(args); + return { kind: "cancelled" }; + }); + const client = createMockClient(request); + const session = await client.createSession({ + sessionId: "session-one", + gitHubTokenProvider: provider, + }); + + const registrationId = createPayload?.gitHubTokenProviderRegistrationId; + expect(registrationId).toMatch( + /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/ + ); + expect(createPayload).not.toHaveProperty("gitHubTokenProvider"); + expect(createPayload?.gitHubToken).toBeUndefined(); + + const handler = getTokenHandler(client); + await expect( + handler({ + registrationId: registrationId as string, + host: "github.example.com", + reason: "initial", + }) + ).resolves.toEqual({ + kind: "token", + accessToken: "secret-token", + tokenType: "Bearer", + expiresIn: 28_800, + }); + await expect( + handler({ + registrationId: registrationId as string, + host: "github.example.com", + sessionId: session.sessionId, + reason: "refresh", + }) + ).resolves.toEqual({ kind: "cancelled" }); + expect(observed).toEqual([ + { + host: "github.example.com", + sessionId: "session-one", + reason: "initial", + }, + { + host: "github.example.com", + sessionId: "session-one", + reason: "refresh", + }, + ]); + }); + + it("preserves callback errors and rejects unknown registrations", async () => { + const client = createMockClient(async (_method, params) => ({ + sessionId: params.sessionId, + })); + const failure = new Error("credential broker failed"); + await client.createSession({ + sessionId: "error-session", + gitHubTokenProvider: () => { + throw failure; + }, + }); + const registrationId = [ + ...( + client as unknown as { + githubTokenProviders: Map; + } + ).githubTokenProviders.keys(), + ][0]; + const handler = getTokenHandler(client); + + await expect( + handler({ + registrationId, + host: "github.com", + reason: "initial", + }) + ).rejects.toBe(failure); + await expect( + handler({ + registrationId: "unknown", + host: "github.com", + reason: "refresh", + }) + ).rejects.toThrow("No GitHub token provider registered"); + }); + + it("rolls back failed creation and cleans up on session and client close", async () => { + const failingClient = createMockClient(async () => { + throw new Error("create failed"); + }); + await expect( + failingClient.createSession({ + gitHubTokenProvider: async () => ({ kind: "cancelled" }), + }) + ).rejects.toThrow("create failed"); + expect( + ( + failingClient as unknown as { + githubTokenProviders: Map; + } + ).githubTokenProviders + ).toHaveLength(0); + + const client = createMockClient(async (method, params) => { + if (method === "session.create") return { sessionId: params.sessionId }; + if (method === "session.destroy") return {}; + if (method === "session.delete") return { success: true }; + throw new Error(`Unexpected method: ${method}`); + }); + const first = await client.createSession({ + sessionId: "first", + gitHubTokenProvider: async () => ({ kind: "cancelled" }), + }); + await client.createSession({ + sessionId: "second", + gitHubTokenProvider: async () => ({ kind: "cancelled" }), + }); + const registrations = ( + client as unknown as { + githubTokenProviders: Map; + } + ).githubTokenProviders; + expect(registrations).toHaveLength(2); + + await first.disconnect(); + expect(registrations).toHaveLength(1); + await client.deleteSession("second"); + expect(registrations).toHaveLength(0); + await client.forceStop(); + expect(registrations).toHaveLength(0); + }); + + it("rotates a resumed session only after resume succeeds", async () => { + const payloads: Record[] = []; + const client = createMockClient(async (method, params) => { + payloads.push(params); + if (method === "session.create" || method === "session.resume") { + return { sessionId: params.sessionId }; + } + throw new Error(`Unexpected method: ${method}`); + }); + const firstProvider = vi.fn(async () => ({ kind: "cancelled" as const })); + const secondProvider = vi.fn(async () => ({ kind: "cancelled" as const })); + await client.createSession({ + sessionId: "resumed", + gitHubTokenProvider: firstProvider, + }); + const firstRegistration = payloads[0].gitHubTokenProviderRegistrationId as string; + + await client.resumeSession("resumed", { + gitHubTokenProvider: secondProvider, + }); + const secondRegistration = payloads[1].gitHubTokenProviderRegistrationId as string; + const handler = getTokenHandler(client); + + await expect( + handler({ + registrationId: firstRegistration, + host: "github.com", + reason: "refresh", + }) + ).rejects.toThrow("No GitHub token provider registered"); + await expect( + handler({ + registrationId: secondRegistration, + host: "github.com", + reason: "refresh", + }) + ).resolves.toEqual({ kind: "cancelled" }); + expect(secondProvider).toHaveBeenCalledOnce(); + expect(firstProvider).not.toHaveBeenCalled(); + }); + + it("does not retire a concurrent pending registration", async () => { + const resumeResolvers: Array<(value: { sessionId: string }) => void> = []; + const payloads: Record[] = []; + const client = createMockClient(async (method, params) => { + payloads.push(params); + if (method === "session.create") { + return { sessionId: params.sessionId }; + } + if (method === "session.resume") { + return await new Promise<{ sessionId: string }>((resolve) => { + resumeResolvers.push(resolve); + }); + } + throw new Error(`Unexpected method: ${method}`); + }); + await client.createSession({ + sessionId: "concurrent", + gitHubTokenProvider: async () => ({ kind: "cancelled" }), + }); + + const firstResume = client.resumeSession("concurrent", { + gitHubTokenProvider: async () => ({ kind: "cancelled" }), + }); + const secondResume = client.resumeSession("concurrent", { + gitHubTokenProvider: async () => ({ kind: "cancelled" }), + }); + await vi.waitFor(() => expect(resumeResolvers).toHaveLength(2)); + + resumeResolvers[0]({ sessionId: "concurrent" }); + await firstResume; + const registrations = ( + client as unknown as { + githubTokenProviders: Map; + } + ).githubTokenProviders; + expect(registrations).toHaveLength(2); + + resumeResolvers[1]({ sessionId: "concurrent" }); + await secondResume; + expect(registrations).toHaveLength(1); + expect(registrations.has(payloads[2].gitHubTokenProviderRegistrationId as string)).toBe( + true + ); + }); +}); diff --git a/python/README.md b/python/README.md index dc0a6a6794..61608c16a0 100644 --- a/python/README.md +++ b/python/README.md @@ -281,9 +281,25 @@ These are passed as keyword arguments to `create_session()`: - `infinite_sessions` (InfiniteSessionConfig): Automatic context compaction configuration - `working_directory` (str | None): Working directory for the session (default: runtime process working directory). - `enable_session_store` (bool): Enables the cross-session store for search and retrieval across sessions. When unset in `"copilot-cli"` mode, the runtime default applies (enabled). In `"empty"` mode, defaults to disabled. +- `github_token_provider` (callable): Acquires rotating, session-scoped GitHub tokens. Token results require a positive `expiresIn` value in seconds remaining when the callback completes; production tokens typically last eight hours. Cannot be combined with `github_token`. - `on_permission_request` (callable): Optional handler called before each tool execution to approve or deny it. When omitted, permission requests are emitted as events and left pending for manual resolution. `PermissionHandler.approve_all` approves requests when managed settings are disabled and raises an error when `enable_managed_settings` is true. Custom handlers can inspect `managed_approval_required` for human-facing confirmation logic. See [Permission Handling](#permission-handling) section. - `on_user_input_request` (callable): Handler for user input requests from the agent (enables ask_user tool). See [User Input Requests](#user-input-requests) section. - `hooks` (SessionHooks): Hook handlers for session lifecycle events. See [Session Hooks](#session-hooks) section. + +```python +async def provide_github_token(args): + return { + "kind": "token", + "accessToken": await acquire_token_for_host(args["host"]), + "expiresIn": 8 * 60 * 60, + } + + +session = await client.create_session(github_token_provider=provide_github_token) +``` + +Initial acquisition runs during session creation or resume. Cancellation, provider errors, and invalid token responses reject that operation instead of falling back to ambient authentication. Idle sessions refresh only before their next credential-consuming operation; there is no background refresh timer. + - `available_tools` / `excluded_tools` / `default_agent.excluded_tools` / custom-agent `tools`: MCP tools registered from `mcp_servers` are exposed to the runtime as `-`. For `available_tools` and `excluded_tools`, prefer `ToolSet().add_mcp("-")` or the raw `mcp:-` form. For custom-agent `tools` and `default_agent.excluded_tools`, use `-` directly. **Session Lifecycle Methods:** diff --git a/python/copilot/__init__.py b/python/copilot/__init__.py index 8f30632e37..5f5bef88bd 100644 --- a/python/copilot/__init__.py +++ b/python/copilot/__init__.py @@ -40,6 +40,11 @@ ExpFlagValue, GetAuthStatusResponse, GetStatusResponse, + GitHubTokenCancelledResult, + GitHubTokenProvider, + GitHubTokenProviderArgs, + GitHubTokenProviderResult, + GitHubTokenResult, InProcessRuntimeConnection, LogLevel, ManagedSettings, @@ -86,6 +91,9 @@ GitHubTelemetryClientInfo, GitHubTelemetryEvent, GitHubTelemetryNotification, + GitHubTokenAcquireReason, + GitHubTokenAcquireResult, + GitHubTokenAcquireResultKind, ModelBillingTokenPrices, ModelBillingTokenPricesLongContext, PermissionDecisionContext, @@ -267,6 +275,14 @@ "GitHubTelemetryClientInfo", "GitHubTelemetryEvent", "GitHubTelemetryNotification", + "GitHubTokenAcquireReason", + "GitHubTokenAcquireResult", + "GitHubTokenAcquireResultKind", + "GitHubTokenProvider", + "GitHubTokenProviderArgs", + "GitHubTokenProviderResult", + "GitHubTokenResult", + "GitHubTokenCancelledResult", "InfiniteSessionConfig", "InProcessRuntimeConnection", "InputOptions", diff --git a/python/copilot/client.py b/python/copilot/client.py index 20bf2c44e3..271fad626c 100644 --- a/python/copilot/client.py +++ b/python/copilot/client.py @@ -29,7 +29,7 @@ from dataclasses import dataclass, field from datetime import UTC, datetime from types import TracebackType -from typing import Any, ClassVar, Literal, TypedDict, cast, overload +from typing import Any, ClassVar, Literal, NotRequired, TypedDict, cast, overload from ._diagnostics import log_timing from ._ffi_runtime_host import FfiRuntimeHost @@ -69,6 +69,9 @@ ClientGlobalApiHandlers, ClientSessionApiHandlers, GitHubTelemetryNotification, + GitHubTokenAcquireReason, + GitHubTokenAcquireRequest, + GitHubTokenAcquireResult, ModelBillingTokenPrices, ModelBillingTokenPricesLongContext, # noqa: F401 OpenCanvasInstance, @@ -123,6 +126,50 @@ logger = logging.getLogger(__name__) + +class GitHubTokenProviderArgs(TypedDict): + """Arguments passed to a session-scoped :data:`GitHubTokenProvider`. + + The opaque callback registration identifier is intentionally not exposed. + """ + + host: str + session_id: str | None + reason: GitHubTokenAcquireReason + + +class GitHubTokenResult(TypedDict): + """A GitHub token returned by a session-scoped provider.""" + + kind: Literal["token"] + accessToken: str + expiresIn: int + tokenType: NotRequired[str] + + +class GitHubTokenCancelledResult(TypedDict): + """An explicit cancellation returned by a session-scoped provider.""" + + kind: Literal["cancelled"] + + +GitHubTokenProviderResult = GitHubTokenResult | GitHubTokenCancelledResult +"""Result returned by a session-scoped GitHub token provider.""" + + +GitHubTokenProvider = Callable[ + [GitHubTokenProviderArgs], + GitHubTokenProviderResult | Awaitable[GitHubTokenProviderResult], +] +"""Acquire a GitHub credential for one session. + +Token results require ``expiresIn`` to be the positive number of seconds of +remaining lifetime when the callback completes. Production GitHub tokens +typically last eight hours. Initial cancellation, callback errors, and invalid +token responses reject session creation or resume instead of falling back to +ambient authentication. +""" + # ============================================================================ # Connection Types # ============================================================================ @@ -633,6 +680,43 @@ async def event(self, params: GitHubTelemetryNotification) -> None: logger.warning("Error handling gitHubTelemetry.event notification", exc_info=True) +@dataclass +class _GitHubTokenProviderRegistration: + provider: GitHubTokenProvider + session_id: str | None = None + committed: bool = False + + +class _GitHubTokenProviderAdapter: + """Routes global GitHub token requests to opaque session registrations.""" + + def __init__(self, client: CopilotClient) -> None: + self._client = client + + async def get_token(self, params: GitHubTokenAcquireRequest) -> GitHubTokenAcquireResult: + with self._client._github_token_providers_lock: + registration = self._client._github_token_providers.get(params.registration_id) + if registration is None: + raise JsonRpcError( + -32603, + "No GitHub token provider registered for registration ID " + f"{params.registration_id!r}", + ) + + result = registration.provider( + GitHubTokenProviderArgs( + host=params.host, + session_id=params.session_id or registration.session_id, + reason=params.reason, + ) + ) + if inspect.isawaitable(result): + result = await result + # The generated global-handler wrapper forwards callback results directly, + # so the public tagged dictionary is already in the expected wire shape. + return cast(GitHubTokenAcquireResult, result) + + class _HooksAdapter: """Adapts session-scoped hook dispatch to the generated ``HooksHandler`` protocol. @@ -1622,6 +1706,9 @@ def __init__( self._state: _ConnectionState = "disconnected" self._sessions: dict[str, CopilotSession] = {} self._sessions_lock = threading.Lock() + self._github_token_providers: dict[str, _GitHubTokenProviderRegistration] = {} + self._github_token_providers_lock = threading.Lock() + self._github_token_provider_adapter = _GitHubTokenProviderAdapter(self) self._models_cache: list[ModelInfo] | None = None self._models_cache_lock = asyncio.Lock() self._lifecycle_handlers: list[SessionLifecycleHandler] = [] @@ -1936,6 +2023,8 @@ async def stop(self) -> None: errors.append( StopError(message=f"Failed to disconnect session {session.session_id}: {e}") ) + with self._github_token_providers_lock: + self._github_token_providers.clear() if ( self._rpc is not None @@ -2052,6 +2141,8 @@ async def force_stop(self) -> None: # Clear sessions immediately without trying to destroy them with self._sessions_lock: self._sessions.clear() + with self._github_token_providers_lock: + self._github_token_providers.clear() # Close the transport first to signal the server immediately. # For external servers (TCP), this closes the socket. @@ -2170,6 +2261,7 @@ async def create_session( on_auto_mode_switch_request: AutoModeSwitchHandler | None = None, create_session_fs_handler: CreateSessionFsHandler | None = None, github_token: str | None = None, + github_token_provider: GitHubTokenProvider | None = None, remote_session: RemoteSessionMode | None = None, cloud: CloudSessionOptions | None = None, canvases: list[CanvasDeclaration] | None = None, @@ -2345,6 +2437,11 @@ async def create_session( May be combined with ``enable_managed_settings``. Requires a runtime whose RPC schema includes ``managedSettings``. Sent on the wire as ``managedSettings``. + github_token_provider: Callback that acquires a short-lived GitHub + credential for this session. Mutually exclusive with + ``github_token``. It receives the effective host, session ID + when assigned, and acquisition reason; the registration ID is + kept internal. Returns: A :class:`CopilotSession` instance for the new session. @@ -2366,6 +2463,8 @@ async def create_session( """ if on_permission_request is not None and not callable(on_permission_request): raise ValueError("on_permission_request must be callable when provided.") + if github_token is not None and github_token_provider is not None: + raise ValueError("github_token and github_token_provider are mutually exclusive") if not self._client: await self.start() @@ -2667,6 +2766,11 @@ async def create_session( ) if local_session_id is not None: payload["sessionId"] = local_session_id + github_token_provider_registration_id = self._register_github_token_provider( + github_token_provider, local_session_id + ) + if github_token_provider_registration_id is not None: + payload["gitHubTokenProviderRegistrationId"] = github_token_provider_registration_id # Propagate W3C Trace Context to CLI if OpenTelemetry is active trace_ctx = get_trace_context() @@ -2687,6 +2791,13 @@ def _initialize_session(sid: str) -> CopilotSession: workspace_path=None, managed_settings_enabled=enable_managed_settings is True or managed_settings is not None, + on_disconnect=( + None + if github_token_provider_registration_id is None + else lambda: self._unregister_github_token_provider( + github_token_provider_registration_id + ) + ), ) if self._session_fs_config: if create_session_fs_handler is None: @@ -2748,8 +2859,12 @@ def _initialize_session(sid: str) -> CopilotSession: # processing (e.g. sessionFs.writeFile for workspace metadata) can be # routed to the correct handlers. if local_session_id is not None: - session = _initialize_session(local_session_id) - registered_session_id = local_session_id + try: + session = _initialize_session(local_session_id) + registered_session_id = local_session_id + except BaseException: + self._unregister_github_token_provider(github_token_provider_registration_id) + raise try: rpc_start = time.perf_counter() @@ -2789,6 +2904,9 @@ def _register_inline(raw_response: Any) -> None: f"session.create returned sessionId {response.get('sessionId')} " f"but the caller requested {local_session_id}" ) + self._assign_github_token_provider( + github_token_provider_registration_id, session.session_id + ) if on_mcp_auth_request is not None: await self._client.request( "session.eventLog.registerInterest", @@ -2801,6 +2919,7 @@ def _register_inline(raw_response: Any) -> None: if registered_session_id is not None: with self._sessions_lock: self._sessions.pop(registered_session_id, None) + self._unregister_github_token_provider(github_token_provider_registration_id) if not isinstance(exc, asyncio.CancelledError): log_timing( logger, @@ -2821,6 +2940,9 @@ def _register_inline(raw_response: Any) -> None: manage_schedule_enabled, included_builtin_skills, ) + self._commit_github_token_provider( + session.session_id, github_token_provider_registration_id + ) log_timing( logger, @@ -2900,6 +3022,7 @@ async def resume_session( on_auto_mode_switch_request: AutoModeSwitchHandler | None = None, create_session_fs_handler: CreateSessionFsHandler | None = None, github_token: str | None = None, + github_token_provider: GitHubTokenProvider | None = None, remote_session: RemoteSessionMode | None = None, continue_pending_work: bool | None = None, canvases: list[CanvasDeclaration] | None = None, @@ -3074,6 +3197,10 @@ async def resume_session( injected layer, and omitting it clears that layer so warm and cold resume behave identically. See :meth:`create_session`. Sent on the wire as ``managedSettings``. + github_token_provider: Callback that acquires a short-lived GitHub + credential for this resumed session. Mutually exclusive with + ``github_token``. The new registration replaces the prior + provider only after resume succeeds. Returns: A :class:`CopilotSession` instance for the resumed session. @@ -3097,6 +3224,8 @@ async def resume_session( """ if on_permission_request is not None and not callable(on_permission_request): raise ValueError("on_permission_request must be callable when provided.") + if github_token is not None and github_token_provider is not None: + raise ValueError("github_token and github_token_provider are mutually exclusive") if not self._client: await self.start() @@ -3418,6 +3547,16 @@ async def resume_session( commands_count=len(commands or []), has_hooks=hooks is not None, ) + github_token_provider_registration_id = self._register_github_token_provider( + github_token_provider, session_id + ) + if github_token_provider_registration_id is not None: + payload["gitHubTokenProviderRegistrationId"] = github_token_provider_registration_id + session._set_disconnect_callback( + lambda: self._unregister_github_token_provider( + github_token_provider_registration_id + ) + ) try: rpc_start = time.perf_counter() @@ -3445,6 +3584,7 @@ async def resume_session( except BaseException as exc: with self._sessions_lock: self._sessions.pop(session_id, None) + self._unregister_github_token_provider(github_token_provider_registration_id) if not isinstance(exc, asyncio.CancelledError): log_timing( logger, @@ -3465,6 +3605,7 @@ async def resume_session( manage_schedule_enabled, included_builtin_skills, ) + self._commit_github_token_provider(session_id, github_token_provider_registration_id) log_timing( logger, @@ -3684,8 +3825,9 @@ async def delete_session(self, session_id: str) -> None: # Remove from local sessions map if present with self._sessions_lock: - if session_id in self._sessions: - del self._sessions[session_id] + session = self._sessions.pop(session_id, None) + if session is not None: + session._run_disconnect_callback() async def get_last_session_id(self) -> str | None: """ @@ -4354,7 +4496,7 @@ async def _connect_via_stdio(self) -> None: # Create JSON-RPC client with the process self._client = JsonRpcClient(self._process) - self._client.on_close = lambda: setattr(self, "_state", "disconnected") + self._client.on_close = self._handle_connection_close self._rpc = ServerRpc(self._client) # Set up notification handler for session events @@ -4475,7 +4617,7 @@ def wait(self, timeout=None): self._process = SocketWrapper(sock_file, sock) self._client = JsonRpcClient(self._process) - self._client.on_close = lambda: setattr(self, "_state", "disconnected") + self._client.on_close = self._handle_connection_close self._rpc = ServerRpc(self._client) # Set up notification handler for session events @@ -4601,9 +4743,56 @@ def _register_client_global_handlers(self) -> None: hooks=_HooksAdapter(self._get_session), llm_inference=llm_inference_adapter, git_hub_telemetry=github_telemetry_adapter, + git_hub_token=self._github_token_provider_adapter, ), ) + def _register_github_token_provider( + self, provider: GitHubTokenProvider | None, session_id: str | None + ) -> str | None: + if provider is None: + return None + registration_id = str(uuid.uuid4()) + with self._github_token_providers_lock: + self._github_token_providers[registration_id] = _GitHubTokenProviderRegistration( + provider, session_id + ) + return registration_id + + def _handle_connection_close(self) -> None: + self._state = "disconnected" + with self._github_token_providers_lock: + self._github_token_providers.clear() + + def _assign_github_token_provider(self, registration_id: str | None, session_id: str) -> None: + if registration_id is None: + return + with self._github_token_providers_lock: + registration = self._github_token_providers.get(registration_id) + if registration is not None: + registration.session_id = session_id + + def _unregister_github_token_provider(self, registration_id: str | None) -> None: + if registration_id is None: + return + with self._github_token_providers_lock: + self._github_token_providers.pop(registration_id, None) + + def _commit_github_token_provider(self, session_id: str, registration_id: str | None) -> None: + with self._github_token_providers_lock: + stale = [ + candidate_id + for candidate_id, registration in self._github_token_providers.items() + if registration.session_id == session_id and registration.committed + ] + for candidate_id in stale: + self._github_token_providers.pop(candidate_id, None) + if registration_id is not None: + registration = self._github_token_providers.get(registration_id) + if registration is not None: + registration.session_id = session_id + registration.committed = True + def _get_session(self, session_id: str) -> CopilotSession | None: with self._sessions_lock: return self._sessions.get(session_id) diff --git a/python/copilot/session.py b/python/copilot/session.py index 21c74bcaf5..b3d8aa45ce 100644 --- a/python/copilot/session.py +++ b/python/copilot/session.py @@ -1548,6 +1548,7 @@ def __init__( client: Any, workspace_path: os.PathLike[str] | str | None = None, managed_settings_enabled: bool = False, + on_disconnect: Callable[[], None] | None = None, ): """ Initialize a new CopilotSession. @@ -1600,6 +1601,17 @@ def __init__( self._open_canvases_lock = threading.Lock() self._rpc: SessionRpc | None = None self._destroyed = False + self._on_disconnect = on_disconnect + + def _set_disconnect_callback(self, callback: Callable[[], None]) -> None: + """Set the client-owned cleanup callback before the session becomes active.""" + self._on_disconnect = callback + + def _run_disconnect_callback(self) -> None: + callback = self._on_disconnect + self._on_disconnect = None + if callback is not None: + callback() @property def rpc(self) -> SessionRpc: @@ -2973,6 +2985,7 @@ async def disconnect(self) -> None: try: await self._client.request("session.destroy", {"sessionId": self.session_id}) finally: + self._run_disconnect_callback() # Clear handlers even if the request fails. with self._event_handlers_lock: self._event_handlers.clear() diff --git a/python/test_github_token_provider.py b/python/test_github_token_provider.py new file mode 100644 index 0000000000..5b203f3e34 --- /dev/null +++ b/python/test_github_token_provider.py @@ -0,0 +1,273 @@ +from __future__ import annotations + +import asyncio +from typing import Any, cast + +import pytest + +from copilot import ( + CopilotClient, + GitHubTokenAcquireReason, + RuntimeConnection, +) +from copilot._jsonrpc import JsonRpcClient, JsonRpcError +from copilot.rpc import GitHubTokenAcquireRequest + + +class FakeJsonRpcClient: + def __init__(self, *, fail_method: str | None = None) -> None: + self.fail_method = fail_method + self.requests: list[tuple[str, dict[str, Any]]] = [] + self.request_handlers: dict[str, Any] = {} + self.notification_method_handlers: dict[str, Any] = {} + + async def request(self, method: str, params: dict[str, Any], **kwargs: Any) -> dict[str, Any]: + self.requests.append((method, params)) + if method == self.fail_method: + raise RuntimeError(f"{method} failed") + if method in {"session.create", "session.resume"}: + response = {"sessionId": params["sessionId"]} + callback = kwargs.get("on_response_inline") + if callback is not None: + callback(response) + return response + if method == "session.destroy": + return {} + if method == "session.delete": + return {"success": True} + raise RuntimeError(f"Unexpected method: {method}") + + async def stop(self) -> None: + pass + + def set_request_handler(self, method: str, handler: Any) -> None: + self.request_handlers[method] = handler + + def set_notification_method_handler(self, method: str, handler: Any) -> None: + self.notification_method_handlers[method] = handler + + +class ConcurrentResumeJsonRpcClient(FakeJsonRpcClient): + def __init__(self) -> None: + super().__init__() + self.resume_responses: list[asyncio.Future[dict[str, Any]]] = [] + + async def request(self, method: str, params: dict[str, Any], **kwargs: Any) -> dict[str, Any]: + self.requests.append((method, params)) + if method == "session.create": + response = {"sessionId": params["sessionId"]} + callback = kwargs.get("on_response_inline") + if callback is not None: + callback(response) + return response + if method == "session.resume": + response = asyncio.get_running_loop().create_future() + self.resume_responses.append(response) + return await response + raise RuntimeError(f"Unexpected method: {method}") + + +def make_client(fake: FakeJsonRpcClient) -> CopilotClient: + client = CopilotClient(connection=RuntimeConnection.for_uri("localhost:1234")) + client._client = cast(JsonRpcClient, fake) + return client + + +class TestGitHubTokenProvider: + async def test_mutual_exclusion(self) -> None: + client = CopilotClient(connection=RuntimeConnection.for_uri("localhost:1234")) + + with pytest.raises( + ValueError, match="github_token and github_token_provider are mutually exclusive" + ): + await client.create_session( + github_token="static", + github_token_provider=lambda _: {"kind": "cancelled"}, + ) + + async def test_wire_mapping_token_and_cancelled(self) -> None: + fake = FakeJsonRpcClient() + client = make_client(fake) + observed: list[dict[str, Any]] = [] + + async def provider(args): + observed.append(dict(args)) + if len(observed) == 1: + return { + "kind": "token", + "accessToken": "secret-token", + "tokenType": "Bearer", + "expiresIn": 28_800, + } + return {"kind": "cancelled"} + + await client.create_session( + session_id="python-session", + github_token_provider=provider, + ) + create_payload = fake.requests[0][1] + registration_id = create_payload["gitHubTokenProviderRegistrationId"] + assert "github_token_provider" not in create_payload + assert "gitHubToken" not in create_payload + client._register_client_global_handlers() + get_token = fake.request_handlers["gitHubToken.getToken"] + + token = await get_token( + { + "registrationId": registration_id, + "host": "github.example.com", + "reason": "initial", + } + ) + cancelled = await get_token( + { + "registrationId": registration_id, + "host": "github.example.com", + "reason": "refresh", + "sessionId": "python-session", + } + ) + + assert token == { + "kind": "token", + "accessToken": "secret-token", + "tokenType": "Bearer", + "expiresIn": 28_800, + } + assert cancelled == {"kind": "cancelled"} + assert observed == [ + { + "host": "github.example.com", + "session_id": "python-session", + "reason": GitHubTokenAcquireReason.INITIAL, + }, + { + "host": "github.example.com", + "session_id": "python-session", + "reason": GitHubTokenAcquireReason.REFRESH, + }, + ] + + async def test_callback_and_unknown_registration_errors(self) -> None: + fake = FakeJsonRpcClient() + client = make_client(fake) + failure = RuntimeError("credential broker failed") + + def provider(_args): + raise failure + + await client.create_session( + session_id="error-session", + github_token_provider=provider, + ) + registration_id = fake.requests[0][1]["gitHubTokenProviderRegistrationId"] + + with pytest.raises(RuntimeError, match="credential broker failed") as exc: + await client._github_token_provider_adapter.get_token( + GitHubTokenAcquireRequest( + registration_id=registration_id, + host="github.com", + reason=GitHubTokenAcquireReason.INITIAL, + ) + ) + assert exc.value is failure + + with pytest.raises(JsonRpcError, match="No GitHub token provider registered"): + await client._github_token_provider_adapter.get_token( + GitHubTokenAcquireRequest( + registration_id="unknown", + host="github.com", + reason=GitHubTokenAcquireReason.REFRESH, + ) + ) + + async def test_failure_session_close_and_client_close_cleanup(self) -> None: + failing = make_client(FakeJsonRpcClient(fail_method="session.create")) + with pytest.raises(RuntimeError, match="session.create failed"): + await failing.create_session(github_token_provider=lambda _: {"kind": "cancelled"}) + assert failing._github_token_providers == {} + + fake = FakeJsonRpcClient() + client = make_client(fake) + first = await client.create_session( + session_id="first", + github_token_provider=lambda _: {"kind": "cancelled"}, + ) + await client.create_session( + session_id="second", + github_token_provider=lambda _: {"kind": "cancelled"}, + ) + assert len(client._github_token_providers) == 2 + + await first.disconnect() + assert len(client._github_token_providers) == 1 + await client.delete_session("second") + assert client._github_token_providers == {} + await client.force_stop() + assert client._github_token_providers == {} + + async def test_resume_rotates_provider(self) -> None: + fake = FakeJsonRpcClient() + client = make_client(fake) + calls: list[str] = [] + + await client.create_session( + session_id="resumed", + github_token_provider=lambda _: calls.append("first") or {"kind": "cancelled"}, + ) + first_registration = fake.requests[0][1]["gitHubTokenProviderRegistrationId"] + await client.resume_session( + "resumed", + github_token_provider=lambda _: calls.append("second") or {"kind": "cancelled"}, + ) + second_registration = fake.requests[1][1]["gitHubTokenProviderRegistrationId"] + + with pytest.raises(JsonRpcError): + await client._github_token_provider_adapter.get_token( + GitHubTokenAcquireRequest( + registration_id=first_registration, + host="github.com", + reason=GitHubTokenAcquireReason.REFRESH, + ) + ) + assert await client._github_token_provider_adapter.get_token( + GitHubTokenAcquireRequest( + registration_id=second_registration, + host="github.com", + reason=GitHubTokenAcquireReason.REFRESH, + ) + ) == {"kind": "cancelled"} + assert calls == ["second"] + + async def test_concurrent_resume_keeps_pending_registration(self) -> None: + fake = ConcurrentResumeJsonRpcClient() + client = make_client(fake) + await client.create_session( + session_id="concurrent", + github_token_provider=lambda _: {"kind": "cancelled"}, + ) + + first_resume = asyncio.create_task( + client.resume_session( + "concurrent", + github_token_provider=lambda _: {"kind": "cancelled"}, + ) + ) + second_resume = asyncio.create_task( + client.resume_session( + "concurrent", + github_token_provider=lambda _: {"kind": "cancelled"}, + ) + ) + while len(fake.resume_responses) < 2: + await asyncio.sleep(0) + + fake.resume_responses[0].set_result({"sessionId": "concurrent"}) + await first_resume + assert len(client._github_token_providers) == 2 + + fake.resume_responses[1].set_result({"sessionId": "concurrent"}) + await second_resume + assert len(client._github_token_providers) == 1 + second_registration = fake.requests[2][1]["gitHubTokenProviderRegistrationId"] + assert second_registration in client._github_token_providers diff --git a/rust/README.md b/rust/README.md index 29fe673558..323d525d37 100644 --- a/rust/README.md +++ b/rust/README.md @@ -274,6 +274,34 @@ let config = SessionConfig { let session = client.create_session(config).await?; ``` +For rotating per-session GitHub credentials, install a `GitHubTokenProvider` +instead of setting `github_token`: + +```rust,ignore +use github_copilot_sdk::{ + GitHubToken, GitHubTokenProviderArgs, GitHubTokenProviderResult, SessionConfig, +}; + +let provider = Arc::new(|args: GitHubTokenProviderArgs| async move { + let access_token = acquire_for_host(&args.host).await?; + Ok(GitHubTokenProviderResult::Token(GitHubToken::new( + access_token, + 8 * 60 * 60, + ))) +}); +let config = SessionConfig::default().with_github_token_provider(provider); +``` + +The remaining lifetime is required and must be positive when the callback +completes; production GitHub tokens typically last eight hours. Static +`github_token` and a provider are mutually exclusive. The same provider API is +available on `ResumeSessionConfig`. + +Initial acquisition runs during session creation or resume. Cancellation, +provider errors, and invalid token responses reject that operation instead of +falling back to ambient authentication. Idle sessions refresh only before their +next credential-consuming operation; there is no background refresh timer. + ### Session Hooks Hooks intercept CLI behavior at lifecycle points — tool use, prompt submission, session start/end, and errors. Install a `SessionHooks` impl with [`SessionConfig::with_hooks`] — the SDK auto-enables `hooks` in `SessionConfig` when one is set. diff --git a/rust/src/errors.rs b/rust/src/errors.rs index 6e05bbfae1..70f4c14ff1 100644 --- a/rust/src/errors.rs +++ b/rust/src/errors.rs @@ -218,6 +218,8 @@ pub enum ErrorKind { }, /// Invalid combination of options or configuration. InvalidConfig, + /// A session-scoped GitHub token provider failed or returned invalid data. + GitHubTokenProvider, } impl fmt::Display for ErrorKind { @@ -238,6 +240,7 @@ impl fmt::Display for ErrorKind { write!(f, "binary not found: {name}") } ErrorKind::InvalidConfig => write!(f, "invalid configuration"), + ErrorKind::GitHubTokenProvider => write!(f, "GitHub token provider error"), } } } diff --git a/rust/src/github_token.rs b/rust/src/github_token.rs new file mode 100644 index 0000000000..c5eaa63adf --- /dev/null +++ b/rust/src/github_token.rs @@ -0,0 +1,378 @@ +//! Session-scoped GitHub token provider callbacks. + +use std::collections::HashMap; +use std::future::Future; +use std::sync::{Arc, OnceLock, Weak}; + +use async_trait::async_trait; +use parking_lot::Mutex; +use serde_json::Value; + +use crate::generated::api_types::{ + GitHubTokenAcquireReason, GitHubTokenAcquireRequest, GitHubTokenAcquireResult, + GitHubTokenAcquireResultCancelled, GitHubTokenAcquireResultToken, +}; +use crate::{Client, ClientInner, JsonRpcError, JsonRpcRequest, JsonRpcResponse, error_codes}; + +/// Why the runtime is requesting a GitHub token. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum GitHubTokenRequestReason { + /// The session needs its initial token. + Initial, + /// The session needs a refreshed token. + Refresh, +} + +/// Context supplied when the runtime needs a GitHub token for a session. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct GitHubTokenProviderArgs { + /// Effective GitHub host for which a token is required. + pub host: String, + /// Session receiving the token, when the runtime has assigned its ID. + pub session_id: Option, + /// Whether this is the initial token acquisition or a refresh. + pub reason: GitHubTokenRequestReason, +} + +/// A GitHub access token returned by a session token provider. +/// +/// `expires_in_seconds` is the positive remaining lifetime when the callback +/// completes. Production GitHub tokens typically last eight hours. +pub struct GitHubToken { + access_token: String, + expires_in_seconds: i64, + token_type: Option, +} + +impl GitHubToken { + /// Construct a token response with its remaining lifetime in seconds. + pub fn new(access_token: impl Into, expires_in_seconds: i64) -> Self { + Self { + access_token: access_token.into(), + expires_in_seconds, + token_type: None, + } + } + + /// Override the OAuth token type. The runtime defaults to `bearer` when unset. + pub fn with_token_type(mut self, token_type: impl Into) -> Self { + self.token_type = Some(token_type.into()); + self + } + + fn into_wire(self) -> GitHubTokenAcquireResultToken { + GitHubTokenAcquireResultToken { + access_token: self.access_token, + expires_in: self.expires_in_seconds, + kind: Default::default(), + token_type: self.token_type, + } + } +} + +impl std::fmt::Debug for GitHubToken { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("GitHubToken") + .field("access_token", &"") + .field("expires_in_seconds", &self.expires_in_seconds) + .field("token_type", &self.token_type) + .finish() + } +} + +/// Result of acquiring a session-scoped GitHub token. +pub enum GitHubTokenProviderResult { + /// A token was acquired. + Token(GitHubToken), + /// The host cancelled acquisition. + Cancelled, +} + +impl std::fmt::Debug for GitHubTokenProviderResult { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Token(token) => f.debug_tuple("Token").field(token).finish(), + Self::Cancelled => f.write_str("Cancelled"), + } + } +} + +/// Async callback used to acquire GitHub tokens for one session. +#[async_trait] +pub trait GitHubTokenProvider: Send + Sync { + /// Acquire a token or explicitly cancel the request. + /// + /// Initial cancellation, errors, and invalid token responses reject session + /// creation or resume instead of falling back to ambient authentication. + async fn get_token( + &self, + args: GitHubTokenProviderArgs, + ) -> Result; +} + +#[async_trait] +impl GitHubTokenProvider for F +where + F: Fn(GitHubTokenProviderArgs) -> Fut + Send + Sync, + Fut: Future> + Send, +{ + async fn get_token( + &self, + args: GitHubTokenProviderArgs, + ) -> Result { + (self)(args).await + } +} + +#[derive(Default)] +struct RegistryState { + providers: HashMap>, + session_owners: HashMap, +} + +pub(crate) struct GitHubTokenRegistry { + state: Mutex, + client: OnceLock>, +} + +impl GitHubTokenRegistry { + pub(crate) fn new() -> Self { + Self { + state: Mutex::new(RegistryState::default()), + client: OnceLock::new(), + } + } + + pub(crate) fn set_client(&self, client: Weak) { + let _ = self.client.set(client); + } + + pub(crate) fn register(&self, provider: Arc) -> String { + let registration_id = uuid::Uuid::new_v4().to_string(); + self.state + .lock() + .providers + .insert(registration_id.clone(), provider); + registration_id + } + + pub(crate) fn claim(&self, registration_id: &str, session_id: crate::SessionId) { + let mut state = self.state.lock(); + if let Some(previous) = state + .session_owners + .insert(session_id, registration_id.to_string()) + && previous != registration_id + { + state.providers.remove(&previous); + } + } + + pub(crate) fn unregister(&self, registration_id: &str) { + let mut state = self.state.lock(); + state.providers.remove(registration_id); + state + .session_owners + .retain(|_, owned| owned != registration_id); + } + + pub(crate) fn retire_session(&self, session_id: &crate::SessionId) { + let mut state = self.state.lock(); + if let Some(registration_id) = state.session_owners.remove(session_id) { + state.providers.remove(®istration_id); + } + } + + pub(crate) fn clear(&self) { + let mut state = self.state.lock(); + state.providers.clear(); + state.session_owners.clear(); + } + + pub(crate) async fn dispatch(&self, request: JsonRpcRequest) { + let Some(inner) = self.client.get().and_then(Weak::upgrade) else { + return; + }; + let client = Client::from_inner(inner); + let params = request + .params + .clone() + .unwrap_or(Value::Object(serde_json::Map::new())); + let params: GitHubTokenAcquireRequest = match serde_json::from_value(params) { + Ok(params) => params, + Err(error) => { + send_error( + &client, + request.id, + error_codes::INVALID_PARAMS, + &format!("invalid params: {error}"), + ) + .await; + return; + } + }; + let provider = self + .state + .lock() + .providers + .get(¶ms.registration_id) + .cloned(); + let Some(provider) = provider else { + send_error( + &client, + request.id, + error_codes::INTERNAL_ERROR, + "unknown GitHub token provider registration", + ) + .await; + return; + }; + + let reason = match params.reason { + GitHubTokenAcquireReason::Initial => GitHubTokenRequestReason::Initial, + GitHubTokenAcquireReason::Refresh => GitHubTokenRequestReason::Refresh, + GitHubTokenAcquireReason::Unknown => { + send_error( + &client, + request.id, + error_codes::INVALID_PARAMS, + "unknown GitHub token acquisition reason", + ) + .await; + return; + } + }; + + match provider + .get_token(GitHubTokenProviderArgs { + host: params.host, + session_id: params.session_id, + reason, + }) + .await + { + Ok(GitHubTokenProviderResult::Token(token)) => { + respond( + &client, + request.id, + GitHubTokenAcquireResult::Token(token.into_wire()), + ) + .await; + } + Ok(GitHubTokenProviderResult::Cancelled) => { + respond( + &client, + request.id, + GitHubTokenAcquireResult::Cancelled(GitHubTokenAcquireResultCancelled { + kind: Default::default(), + }), + ) + .await; + } + Err(error) => { + send_error( + &client, + request.id, + error_codes::INTERNAL_ERROR, + &format!("GitHub token provider failed: {error}"), + ) + .await; + } + } + } +} + +pub(crate) struct GitHubTokenRegistration { + registry: Arc, + id: String, +} + +impl GitHubTokenRegistration { + pub(crate) fn new(registry: Arc, id: String) -> Self { + Self { registry, id } + } + + pub(crate) fn id(&self) -> &str { + &self.id + } + + pub(crate) fn claim(&self, session_id: crate::SessionId) { + self.registry.claim(&self.id, session_id); + } +} + +impl Drop for GitHubTokenRegistration { + fn drop(&mut self) { + self.registry.unregister(&self.id); + } +} + +async fn respond(client: &Client, request_id: u64, result: GitHubTokenAcquireResult) { + match serde_json::to_value(result) { + Ok(result) => { + let _ = client + .send_response(&JsonRpcResponse { + jsonrpc: "2.0".to_string(), + id: request_id, + result: Some(result), + error: None, + }) + .await; + } + Err(_) => { + send_error( + client, + request_id, + error_codes::INTERNAL_ERROR, + "serialization failure", + ) + .await; + } + } +} + +async fn send_error(client: &Client, request_id: u64, code: i32, message: &str) { + let _ = client + .send_response(&JsonRpcResponse { + jsonrpc: "2.0".to_string(), + id: request_id, + result: None, + error: Some(JsonRpcError { + code, + message: message.to_string(), + data: None, + }), + }) + .await; +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn token_debug_is_redacted() { + let token = GitHubToken::new("do-not-print", 28_800); + assert!(!format!("{token:?}").contains("do-not-print")); + } + + #[test] + fn retiring_session_removes_its_provider() { + let registry = GitHubTokenRegistry::new(); + let provider = Arc::new(|_args: GitHubTokenProviderArgs| async { + Ok(GitHubTokenProviderResult::Cancelled) + }); + let registration_id = registry.register(provider); + let session_id = crate::SessionId::from("session-1"); + registry.claim(®istration_id, session_id.clone()); + + registry.retire_session(&session_id); + + assert!( + !registry + .state + .lock() + .providers + .contains_key(®istration_id) + ); + } +} diff --git a/rust/src/lib.rs b/rust/src/lib.rs index bf50adfd48..4a9f73ca4c 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -22,6 +22,8 @@ pub mod copilot_request_handler; /// `#[doc(hidden)]` — re-exports the generated telemetry payload types. #[doc(hidden)] pub mod github_telemetry; +/// Session-scoped GitHub token provider callbacks. +pub mod github_token; /// Event handler traits for session lifecycle. pub mod handler; /// Lifecycle hook callbacks (pre/post tool use, prompt submission, session start/end). @@ -78,6 +80,10 @@ use std::sync::{Arc, OnceLock}; use std::time::{Duration, Instant}; use async_trait::async_trait; +pub use github_token::{ + GitHubToken, GitHubTokenProvider, GitHubTokenProviderArgs, GitHubTokenProviderResult, + GitHubTokenRequestReason, +}; /// Re-export of [`indexmap::IndexMap`], used for order-preserving maps in the /// public API (e.g. [`Tool::parameters`](types::Tool::parameters) and /// `SessionConfig::mcp_servers`) so serialized key order stays deterministic. @@ -1020,6 +1026,7 @@ struct ClientInner { request_rx: parking_lot::Mutex>>, notification_tx: broadcast::Sender, router: router::SessionRouter, + github_token_registry: Arc, negotiated_protocol_version: OnceLock, state: parking_lot::Mutex, lifecycle_tx: broadcast::Sender, @@ -1423,6 +1430,7 @@ impl Client { &client.inner.request_rx, Some(dispatcher.clone()), client.inner.on_github_telemetry.clone(), + client.inner.github_token_registry.clone(), ); client.rpc().llm_inference().set_provider().await?; let llm_inference_elapsed = llm_inference_start.elapsed(); @@ -1603,6 +1611,7 @@ impl Client { let pid = child.as_ref().and_then(|c| c.id()); info!(pid = ?pid, "copilot CLI client ready"); + let github_token_registry = Arc::new(github_token::GitHubTokenRegistry::new()); let client = Self { inner: Arc::new(ClientInner { child: parking_lot::Mutex::new(child), @@ -1613,6 +1622,7 @@ impl Client { request_rx: parking_lot::Mutex::new(Some(request_rx)), notification_tx: notification_broadcast_tx, router: router::SessionRouter::new(), + github_token_registry: github_token_registry.clone(), negotiated_protocol_version: OnceLock::new(), state: parking_lot::Mutex::new(ConnectionState::Connected), lifecycle_tx: broadcast::channel(256).0, @@ -1628,6 +1638,7 @@ impl Client { startup_timings: OnceLock::new(), }), }; + github_token_registry.set_client(Arc::downgrade(&client.inner)); client.spawn_lifecycle_dispatcher(); debug!( elapsed_ms = setup_start.elapsed().as_millis(), @@ -2046,6 +2057,7 @@ impl Client { &self.inner.request_rx, self.inner.llm_inference.get().cloned(), self.inner.on_github_telemetry.clone(), + self.inner.github_token_registry.clone(), ); self.inner.router.register(session_id) } @@ -2055,6 +2067,25 @@ impl Client { self.inner.router.unregister(session_id); } + pub(crate) fn register_github_token_provider( + &self, + provider: Arc, + ) -> github_token::GitHubTokenRegistration { + self.inner.router.ensure_started( + &self.inner.notification_tx, + &self.inner.request_rx, + self.inner.llm_inference.get().cloned(), + self.inner.on_github_telemetry.clone(), + self.inner.github_token_registry.clone(), + ); + let id = self.inner.github_token_registry.register(provider); + github_token::GitHubTokenRegistration::new(self.inner.github_token_registry.clone(), id) + } + + pub(crate) fn retire_github_token_provider(&self, session_id: &SessionId) { + self.inner.github_token_registry.retire_session(session_id); + } + /// Returns the protocol version negotiated with the CLI server, if any. /// /// Set during [`start`](Self::start). Returns `None` if the server didn't @@ -2250,6 +2281,7 @@ impl Client { Some(serde_json::json!({ "sessionId": session_id })), ) .await?; + self.retire_github_token_provider(session_id); Ok(()) } @@ -2263,6 +2295,7 @@ impl Client { &self.inner.request_rx, self.inner.llm_inference.get().cloned(), self.inner.on_github_telemetry.clone(), + self.inner.github_token_registry.clone(), ); } @@ -2286,6 +2319,7 @@ impl Client { } self.inner.router.unregister(&session_id); } + self.inner.github_token_registry.clear(); match self.list_sessions(None).await { Ok(sessions) => { @@ -2455,6 +2489,7 @@ impl Client { } self.inner.router.unregister(&session_id); } + self.inner.github_token_registry.clear(); let should_shutdown_runtime = self.inner.child.lock().is_some(); #[cfg(feature = "bundled-in-process")] @@ -2581,6 +2616,7 @@ impl Client { // Drop all session channels so any awaiters see a closed channel // instead of waiting for responses that will never arrive. self.inner.router.clear(); + self.inner.github_token_registry.clear(); *self.inner.state.lock() = ConnectionState::Disconnected; *self.inner.models_cache.lock() = Arc::new(tokio::sync::OnceCell::new()); } @@ -3388,6 +3424,7 @@ mod tests { request_rx: parking_lot::Mutex::new(None), notification_tx: broadcast::channel(16).0, router: router::SessionRouter::new(), + github_token_registry: Arc::new(github_token::GitHubTokenRegistry::new()), negotiated_protocol_version: OnceLock::new(), state: parking_lot::Mutex::new(ConnectionState::Connected), lifecycle_tx: broadcast::channel(16).0, diff --git a/rust/src/router.rs b/rust/src/router.rs index adc1923824..1dec9d16f3 100644 --- a/rust/src/router.rs +++ b/rust/src/router.rs @@ -87,6 +87,7 @@ impl SessionRouter { request_rx: &Mutex>>, llm_inference: Option>, github_telemetry: Option, + github_token_registry: Arc, ) { let mut started = self.started.lock(); if *started { @@ -181,6 +182,10 @@ impl SessionRouter { let sessions = self.sessions.clone(); tokio::spawn(async move { while let Some(request) = rx.recv().await { + if request.method == "gitHubToken.getToken" { + github_token_registry.dispatch(request).await; + continue; + } // Client-global `llmInference.*` requests carry no routable // session and are handled by the inference dispatcher. if request.method.starts_with("llmInference.") { diff --git a/rust/src/session.rs b/rust/src/session.rs index 3e5ae13dee..767b4cb78d 100644 --- a/rust/src/session.rs +++ b/rust/src/session.rs @@ -190,6 +190,8 @@ pub struct Session { open_canvases: Arc>>, /// Broadcast channel for runtime event subscribers — see [`Session::subscribe`]. event_tx: tokio::sync::broadcast::Sender, + github_token_registration: + ParkingLotMutex>, } impl Session { @@ -584,6 +586,7 @@ impl Session { .await?; self.stop_event_loop().await; self.client.unregister_session(&self.id); + self.github_token_registration.lock().take(); Ok(()) } @@ -661,6 +664,7 @@ impl Drop for Session { // it here because Drop is sync. self.shutdown.cancel(); self.client.unregister_session(&self.id); + self.github_token_registration.lock().take(); } } @@ -934,6 +938,13 @@ impl Client { let canvas_handler = runtime.canvas_handler.take(); let session_fs_provider = runtime.session_fs_provider.take(); let bearer_token_providers = std::mem::take(&mut runtime.bearer_token_providers); + let github_token_registration = runtime + .github_token_provider + .take() + .map(|provider| self.register_github_token_provider(provider)); + wire.github_token_provider_registration_id = github_token_registration + .as_ref() + .map(|registration| registration.id().to_string()); let has_mcp_auth_handler = handlers.mcp_auth.is_some(); if self.inner.session_fs_configured && session_fs_provider.is_none() { return Err(ErrorKind::Session(SessionErrorKind::SessionFsProviderRequired).into()); @@ -1091,6 +1102,7 @@ impl Client { capabilities, open_canvases, event_tx, + github_token_registration: ParkingLotMutex::new(github_token_registration), }; apply_mode_post_create_patch( &session, @@ -1102,6 +1114,11 @@ impl Client { opt_included_builtin_skills, ) .await?; + if let Some(registration) = session.github_token_registration.lock().as_ref() { + registration.claim(session.id.clone()); + } else { + self.retire_github_token_provider(&session.id); + } Ok(session) } @@ -1209,6 +1226,13 @@ impl Client { let canvas_handler = runtime.canvas_handler.take(); let session_fs_provider = runtime.session_fs_provider.take(); let bearer_token_providers = std::mem::take(&mut runtime.bearer_token_providers); + let github_token_registration = runtime + .github_token_provider + .take() + .map(|provider| self.register_github_token_provider(provider)); + wire.github_token_provider_registration_id = github_token_registration + .as_ref() + .map(|registration| registration.id().to_string()); let has_mcp_auth_handler = handlers.mcp_auth.is_some(); if self.inner.session_fs_configured && session_fs_provider.is_none() { return Err(ErrorKind::Session(SessionErrorKind::SessionFsProviderRequired).into()); @@ -1353,6 +1377,7 @@ impl Client { capabilities, open_canvases, event_tx, + github_token_registration: ParkingLotMutex::new(github_token_registration), }; apply_mode_post_create_patch( &session, @@ -1364,6 +1389,11 @@ impl Client { opt_included_builtin_skills, ) .await?; + if let Some(registration) = session.github_token_registration.lock().as_ref() { + registration.claim(session.id.clone()); + } else { + self.retire_github_token_provider(&session.id); + } Ok(session) } } diff --git a/rust/src/types.rs b/rust/src/types.rs index 2db631db3c..1d64a7a0ea 100644 --- a/rust/src/types.rs +++ b/rust/src/types.rs @@ -24,6 +24,7 @@ use crate::generated::api_types::{CurrentToolMetadata, OpenCanvasInstance}; use crate::generated::session_events::ReasoningSummary; /// Context window tier for models that support tiered context windows. pub use crate::generated::session_events::{ContextTier, SessionLimitsConfig}; +use crate::github_token::GitHubTokenProvider; use crate::handler::{ AutoModeSwitchHandler, ElicitationHandler, ExitPlanModeHandler, McpAuthHandler, PermissionHandler, UserInputHandler, @@ -2117,6 +2118,12 @@ pub struct SessionConfig { /// the GitHub identity used for content exclusion, model routing, and /// quota checks for *this session*. pub github_token: Option, + /// Provider used to acquire rotating GitHub tokens for this session. + /// + /// Mutually exclusive with [`github_token`](Self::github_token). The callback + /// receives the effective host, optional assigned session ID, and acquisition + /// reason; its opaque registration ID is never exposed. + pub github_token_provider: Option>, /// Per-session remote behavior control: /// - `Off` — local only, no remote export (default) /// - `Export` — export session events to GitHub without @@ -2144,9 +2151,10 @@ pub struct SessionConfig { pub exp_assignments: Option, /// Opt-in: when `Some(true)`, the runtime self-fetches enterprise managed /// settings (bypass-permissions policy) at session bootstrap using the - /// session's [`github_token`](Self::github_token). Requires `github_token` - /// to be set; if omitted, the runtime is expected to reject session creation - /// (fail-closed). When `None`, behaves exactly as before. Set via + /// session's static [`github_token`](Self::github_token) or + /// [`github_token_provider`](Self::github_token_provider). Requires one of + /// those credentials; if both are omitted, the runtime is expected to reject + /// session creation (fail-closed). When `None`, behaves exactly as before. Set via /// [`with_enable_managed_settings`](Self::with_enable_managed_settings). pub enable_managed_settings: Option, /// Optional managed-settings layer injected at session bootstrap. Currently @@ -2299,6 +2307,10 @@ impl std::fmt::Debug for SessionConfig { "github_token", &self.github_token.as_ref().map(|_| ""), ) + .field( + "github_token_provider", + &self.github_token_provider.as_ref().map(|_| ""), + ) .field("remote_session", &self.remote_session) .field("cloud", &self.cloud) .field( @@ -2417,6 +2429,7 @@ impl Default for SessionConfig { working_directory: None, additional_directories: None, github_token: None, + github_token_provider: None, remote_session: None, cloud: None, include_sub_agent_streaming_events: None, @@ -2462,6 +2475,7 @@ pub(crate) struct SessionConfigRuntime { pub canvas_handler: Option>, pub session_fs_provider: Option>, pub bearer_token_providers: HashMap>, + pub github_token_provider: Option>, pub commands: Option>, } @@ -2481,6 +2495,12 @@ impl SessionConfig { mut self, session_id: Option, ) -> Result<(crate::wire::SessionCreateWire, SessionConfigRuntime), crate::Error> { + if self.github_token.is_some() && self.github_token_provider.is_some() { + return Err(crate::Error::with_message( + crate::ErrorKind::InvalidConfig, + "github_token and github_token_provider are mutually exclusive", + )); + } let permission_active = self.permission_handler.is_some() || self.permission_policy.is_some(); let request_user_input = self.user_input_handler.is_some(); @@ -2582,6 +2602,7 @@ impl SessionConfig { working_directory: self.working_directory, additional_directories: self.additional_directories, github_token: self.github_token, + github_token_provider_registration_id: None, remote_session: self.remote_session, cloud: self.cloud, include_sub_agent_streaming_events: self.include_sub_agent_streaming_events, @@ -2607,6 +2628,7 @@ impl SessionConfig { canvas_handler, session_fs_provider: self.session_fs_provider, bearer_token_providers, + github_token_provider: self.github_token_provider, commands: self.commands, }; @@ -3157,6 +3179,16 @@ impl SessionConfig { self } + /// Install a rotating GitHub token provider for this session. + /// + /// The provider must return a positive remaining lifetime in seconds when + /// its callback completes. Production GitHub tokens typically last eight + /// hours. This option is mutually exclusive with [`with_github_token`](Self::with_github_token). + pub fn with_github_token_provider(mut self, provider: Arc) -> Self { + self.github_token_provider = Some(provider); + self + } + /// Forward sub-agent streaming events to this connection. Defaults /// to true on the CLI when unset. pub fn with_include_sub_agent_streaming_events(mut self, include: bool) -> Self { @@ -3224,9 +3256,10 @@ impl SessionConfig { /// Opt the runtime into self-fetching enterprise managed settings /// (bypass-permissions policy) at session bootstrap using the session's - /// [`github_token`](Self::github_token). Requires `github_token` to be set; - /// if omitted, the runtime is expected to reject session creation - /// (fail-closed). + /// static [`github_token`](Self::github_token) or + /// [`github_token_provider`](Self::github_token_provider). Requires one of + /// those credentials; if both are omitted, the runtime is expected to reject + /// session creation (fail-closed). pub fn with_enable_managed_settings(mut self, enabled: bool) -> Self { self.enable_managed_settings = Some(enabled); self @@ -3419,6 +3452,9 @@ pub struct ResumeSessionConfig { /// Per-session GitHub token on resume. See /// [`SessionConfig::github_token`]. pub github_token: Option, + /// Rotating GitHub token provider on resume. See + /// [`SessionConfig::github_token_provider`]. + pub github_token_provider: Option>, /// Per-session remote behavior control on resume. See /// [`SessionConfig::remote_session`]. pub remote_session: Option, @@ -3581,6 +3617,10 @@ impl std::fmt::Debug for ResumeSessionConfig { "github_token", &self.github_token.as_ref().map(|_| ""), ) + .field( + "github_token_provider", + &self.github_token_provider.as_ref().map(|_| ""), + ) .field("remote_session", &self.remote_session) .field( "include_sub_agent_streaming_events", @@ -3640,6 +3680,12 @@ impl ResumeSessionConfig { pub(crate) fn into_wire( mut self, ) -> Result<(crate::wire::SessionResumeWire, SessionConfigRuntime), crate::Error> { + if self.github_token.is_some() && self.github_token_provider.is_some() { + return Err(crate::Error::with_message( + crate::ErrorKind::InvalidConfig, + "github_token and github_token_provider are mutually exclusive", + )); + } let permission_active = self.permission_handler.is_some() || self.permission_policy.is_some(); let request_user_input = self.user_input_handler.is_some(); @@ -3742,6 +3788,7 @@ impl ResumeSessionConfig { working_directory: self.working_directory, additional_directories: self.additional_directories, github_token: self.github_token, + github_token_provider_registration_id: None, remote_session: self.remote_session, include_sub_agent_streaming_events: self.include_sub_agent_streaming_events, enable_github_telemetry_forwarding: None, @@ -3768,6 +3815,7 @@ impl ResumeSessionConfig { canvas_handler, session_fs_provider: self.session_fs_provider, bearer_token_providers, + github_token_provider: self.github_token_provider, commands: self.commands, }; @@ -3840,6 +3888,7 @@ impl ResumeSessionConfig { working_directory: None, additional_directories: None, github_token: None, + github_token_provider: None, remote_session: None, include_sub_agent_streaming_events: None, commands: None, @@ -4390,6 +4439,16 @@ impl ResumeSessionConfig { self } + /// Install a rotating GitHub token provider for the resumed session. + /// + /// The provider must return a positive remaining lifetime in seconds when + /// its callback completes. Production GitHub tokens typically last eight + /// hours. Mutually exclusive with [`with_github_token`](Self::with_github_token). + pub fn with_github_token_provider(mut self, provider: Arc) -> Self { + self.github_token_provider = Some(provider); + self + } + /// Forward sub-agent streaming events to this connection on resume. pub fn with_include_sub_agent_streaming_events(mut self, include: bool) -> Self { self.include_sub_agent_streaming_events = Some(include); diff --git a/rust/src/wire.rs b/rust/src/wire.rs index 8c08b017b7..f7de33839c 100644 --- a/rust/src/wire.rs +++ b/rust/src/wire.rs @@ -170,6 +170,11 @@ pub(crate) struct SessionCreateWire { pub additional_directories: Option>, #[serde(rename = "gitHubToken", skip_serializing_if = "Option::is_none")] pub github_token: Option, + #[serde( + rename = "gitHubTokenProviderRegistrationId", + skip_serializing_if = "Option::is_none" + )] + pub github_token_provider_registration_id: Option, #[serde(skip_serializing_if = "Option::is_none")] pub remote_session: Option, #[serde(skip_serializing_if = "Option::is_none")] @@ -320,6 +325,11 @@ pub(crate) struct SessionResumeWire { pub additional_directories: Option>, #[serde(rename = "gitHubToken", skip_serializing_if = "Option::is_none")] pub github_token: Option, + #[serde( + rename = "gitHubTokenProviderRegistrationId", + skip_serializing_if = "Option::is_none" + )] + pub github_token_provider_registration_id: Option, #[serde(skip_serializing_if = "Option::is_none")] pub remote_session: Option, #[serde(skip_serializing_if = "Option::is_none")] diff --git a/rust/tests/session_test.rs b/rust/tests/session_test.rs index e20d9d0885..378d7120bf 100644 --- a/rust/tests/session_test.rs +++ b/rust/tests/session_test.rs @@ -7,6 +7,9 @@ use std::time::Duration; use async_trait::async_trait; use github_copilot_sdk::canvas::{CanvasDeclaration, CanvasHandler, CanvasResult}; +use github_copilot_sdk::github_token::{ + GitHubToken, GitHubTokenProviderArgs, GitHubTokenProviderResult, GitHubTokenRequestReason, +}; use github_copilot_sdk::handler::{ ApproveAllHandler, AutoModeSwitchHandler, AutoModeSwitchResponse, ElicitationHandler, ExitPlanModeHandler, ExitPlanModeResult, McpAuthHandler, McpAuthRequest, McpAuthResult, @@ -254,6 +257,228 @@ where (session, server) } +#[tokio::test] +async fn github_token_provider_uses_global_registration_and_maps_results() { + let (client, server_read, server_write) = make_client(); + let mut server = FakeServer { + read: server_read, + write: server_write, + session_id: "github-token-session".to_string(), + }; + let (args_tx, mut args_rx) = tokio::sync::mpsc::unbounded_channel(); + let provider = Arc::new(move |args: GitHubTokenProviderArgs| { + let args_tx = args_tx.clone(); + async move { + args_tx.send(args.clone()).unwrap(); + match args.host.as_str() { + "github.com" => Ok(GitHubTokenProviderResult::Token(GitHubToken::new( + "secret-token", + 8 * 60 * 60, + ))), + "cancel.example" => Ok(GitHubTokenProviderResult::Cancelled), + _ => Err(github_copilot_sdk::Error::with_message( + ErrorKind::GitHubTokenProvider, + "credential service unavailable", + )), + } + } + }); + let create_handle = tokio::spawn({ + let client = client.clone(); + async move { + client + .create_session( + SessionConfig::default() + .with_session_id("github-token-session") + .with_github_token_provider(provider), + ) + .await + .unwrap() + } + }); + + let create_request = server.read_request().await; + assert_eq!(create_request["method"], "session.create"); + assert!(create_request["params"].get("gitHubToken").is_none()); + let registration_id = create_request["params"]["gitHubTokenProviderRegistrationId"] + .as_str() + .unwrap() + .to_string(); + + server + .send_request( + 900, + "gitHubToken.getToken", + serde_json::json!({ + "registrationId": registration_id, + "host": "github.com", + "sessionId": "github-token-session", + "reason": "initial" + }), + ) + .await; + let token_response = server.read_response().await; + assert_eq!(token_response["result"]["kind"], "token"); + assert_eq!(token_response["result"]["accessToken"], "secret-token"); + assert_eq!(token_response["result"]["expiresIn"], 8 * 60 * 60); + let args = args_rx.recv().await.unwrap(); + assert_eq!(args.host, "github.com"); + assert_eq!( + args.session_id.as_ref().map(SessionId::as_str), + Some("github-token-session") + ); + assert_eq!(args.reason, GitHubTokenRequestReason::Initial); + + server + .send_request( + 901, + "gitHubToken.getToken", + serde_json::json!({ + "registrationId": registration_id, + "host": "cancel.example", + "reason": "refresh" + }), + ) + .await; + let cancelled = server.read_response().await; + assert_eq!(cancelled["result"]["kind"], "cancelled"); + + server + .send_request( + 902, + "gitHubToken.getToken", + serde_json::json!({ + "registrationId": registration_id, + "host": "error.example", + "reason": "refresh" + }), + ) + .await; + let provider_error = server.read_response().await; + assert_eq!(provider_error["error"]["code"], -32603); + assert!( + provider_error["error"]["message"] + .as_str() + .unwrap() + .contains("credential service unavailable") + ); + + server + .respond( + &create_request, + serde_json::json!({"sessionId": "github-token-session"}), + ) + .await; + let session = timeout(TIMEOUT, create_handle).await.unwrap().unwrap(); + + let delete_handle = tokio::spawn({ + let client = client.clone(); + async move { + client + .delete_session(&SessionId::new("github-token-session")) + .await + } + }); + let delete_request = server.read_request().await; + assert_eq!(delete_request["method"], "session.delete"); + server.respond(&delete_request, serde_json::json!({})).await; + timeout(TIMEOUT, delete_handle) + .await + .unwrap() + .unwrap() + .unwrap(); + + server + .send_request( + 903, + "gitHubToken.getToken", + serde_json::json!({ + "registrationId": registration_id, + "host": "github.com", + "reason": "refresh" + }), + ) + .await; + let unknown = server.read_response().await; + assert_eq!(unknown["error"]["code"], -32603); + assert!( + unknown["error"]["message"] + .as_str() + .unwrap() + .contains("unknown GitHub token provider registration") + ); + drop(session); +} + +#[tokio::test] +async fn github_token_provider_is_mutually_exclusive_and_rolls_back_failed_create() { + let provider = Arc::new(|_args: GitHubTokenProviderArgs| async { + Ok(GitHubTokenProviderResult::Cancelled) + }); + let (client, server_read, server_write) = make_client(); + let result = client + .create_session( + SessionConfig::default() + .with_github_token("static") + .with_github_token_provider(provider.clone()), + ) + .await; + let Err(error) = result else { + panic!("mutually exclusive GitHub credentials must be rejected"); + }; + assert!(matches!(error.kind(), ErrorKind::InvalidConfig)); + + let mut server = FakeServer { + read: server_read, + write: server_write, + session_id: "failed-create".to_string(), + }; + let create_handle = tokio::spawn({ + let client = client.clone(); + async move { + client + .create_session( + SessionConfig::default() + .with_session_id("failed-create") + .with_github_token_provider(provider), + ) + .await + } + }); + let create_request = server.read_request().await; + let registration_id = create_request["params"]["gitHubTokenProviderRegistrationId"] + .as_str() + .unwrap() + .to_string(); + let response = serde_json::json!({ + "jsonrpc": "2.0", + "id": create_request["id"], + "error": {"code": -32603, "message": "create failed"} + }); + write_framed(&mut server.write, &serde_json::to_vec(&response).unwrap()).await; + assert!( + timeout(TIMEOUT, create_handle) + .await + .unwrap() + .unwrap() + .is_err() + ); + + server + .send_request( + 904, + "gitHubToken.getToken", + serde_json::json!({ + "registrationId": registration_id, + "host": "github.com", + "reason": "initial" + }), + ) + .await; + let unknown = server.read_response().await; + assert_eq!(unknown["error"]["code"], -32603); +} + fn rand_id() -> u64 { static COUNTER: AtomicUsize = AtomicUsize::new(0); COUNTER.fetch_add(1, Ordering::Relaxed) as u64 From 3d630a790e3b1f8c74b4443d144a52429a232b28 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 26 Aug 2026 21:30:42 +0000 Subject: [PATCH 24/32] Update @github/copilot to 1.0.81-11 (#2409) * Update @github/copilot to 1.0.81-11 - Updated nodejs and test harness dependencies - Re-ran code generators - Formatted generated code * Update permission context call sites Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Handle autopilot continuation idles Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Handle autopilot continuation idles across SDKs Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Export permission response capability Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Export Rust permission response capability Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Stephen Toub Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- dotnet/src/Generated/Rpc.cs | 81 +++++++++++ dotnet/src/Generated/SessionEvents.cs | 133 +++++++++--------- dotnet/src/Session.cs | 2 +- .../test/Unit/ClientSessionLifetimeTests.cs | 60 ++++++++ go/rpc/zrpc.go | 23 +++ go/rpc/zsession_events.go | 2 + go/session.go | 3 + go/session_test.go | 132 +++++++++++++++++ go/types.go | 11 ++ java/pom.xml | 2 +- java/scripts/codegen/package-lock.json | 72 +++++----- java/scripts/codegen/package.json | 2 +- .../copilot/generated/SessionIdleEvent.java | 4 +- .../rpc/GitHubTelemetryClientInfo.java | 6 +- .../rpc/PermissionDecisionContext.java | 4 +- .../rpc/PermissionDecisionSurface.java | 2 + .../rpc/PermissionResponseCapability.java | 37 +++++ .../com/github/copilot/CopilotSession.java | 4 +- .../copilot/SessionEventHandlingTest.java | 39 +++++ ...ssionRequestResultDecisionContextTest.java | 4 +- nodejs/package-lock.json | 54 +++---- nodejs/package.json | 2 +- nodejs/samples/package-lock.json | 2 +- nodejs/src/generated/rpc.ts | 25 ++++ nodejs/src/generated/session-events.ts | 21 +-- nodejs/src/index.ts | 1 + nodejs/src/session.ts | 2 +- nodejs/src/types.ts | 1 + nodejs/test/session-event-types.test.ts | 2 + nodejs/test/session-send-and-wait.test.ts | 23 ++- python/copilot/__init__.py | 2 + python/copilot/generated/rpc.py | 42 +++++- python/copilot/generated/session_events.py | 5 + python/copilot/session.py | 3 +- python/test_permission_decision_context.py | 5 + python/test_session.py | 69 +++++++++ rust/src/generated/api_types.rs | 37 +++++ rust/src/generated/session_events.rs | 39 ++--- rust/src/handler.rs | 1 + rust/src/session.rs | 40 +++++- rust/src/types.rs | 16 ++- rust/tests/session_test.rs | 1 + test/harness/package-lock.json | 54 +++---- test/harness/package.json | 2 +- 44 files changed, 864 insertions(+), 208 deletions(-) create mode 100644 java/sdk/src/generated/java/com/github/copilot/generated/rpc/PermissionResponseCapability.java create mode 100644 python/test_session.py diff --git a/dotnet/src/Generated/Rpc.cs b/dotnet/src/Generated/Rpc.cs index 3b9445f726..ba8b844d83 100644 --- a/dotnet/src/Generated/Rpc.cs +++ b/dotnet/src/Generated/Rpc.cs @@ -13882,6 +13882,10 @@ public sealed class PermissionDecisionContext [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; } @@ -18522,6 +18526,14 @@ public sealed class GitHubTelemetryClientInfo [JsonPropertyName("copilot_plan")] public string? CopilotPlan { get; set; } + /// Number of logical CPU cores on the host. + [JsonPropertyName("cpu_count")] + public long? CpuCount { get; set; } + + /// Distinct CPU model names for the host, comma-separated. + [JsonPropertyName("cpu_model")] + public string? CpuModel { get; set; } + /// Stable machine identifier for the device. [JsonPropertyName("dev_device_id")] public string? DevDeviceId { get; set; } @@ -27137,6 +27149,72 @@ public override void Write(Utf8JsonWriter writer, PermissionDecisionOutcome valu } +/// Response capability available to the client when it settled a permission request. +[Experimental(Diagnostics.Experimental)] +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct PermissionResponseCapability : IEquatable +{ + private readonly string? _value; + + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public PermissionResponseCapability(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } + + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; + + /// The client could ask a user for this decision. + public static PermissionResponseCapability Interactive { get; } = new("interactive"); + + /// The client could return an automated response but could not ask a user. + public static PermissionResponseCapability Headless { get; } = new("headless"); + + /// The client had no response path available. + public static PermissionResponseCapability None { get; } = new("none"); + + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(PermissionResponseCapability left, PermissionResponseCapability right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(PermissionResponseCapability left, PermissionResponseCapability right) => !(left == right); + + /// + public override bool Equals(object? obj) => obj is PermissionResponseCapability other && Equals(other); + + /// + public bool Equals(PermissionResponseCapability other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + + /// + public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); + + /// + public override string ToString() => Value; + + /// Provides a for serializing instances. + [EditorBrowsable(EditorBrowsableState.Never)] + public sealed class Converter : JsonConverter + { + /// + public override PermissionResponseCapability Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } + + /// + public override void Write(Utf8JsonWriter writer, PermissionResponseCapability value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(PermissionResponseCapability)); + } + } +} + + /// Controlled reason or actor responsible for a permission response. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] @@ -27235,6 +27313,9 @@ public PermissionDecisionSurface(string value) /// The Copilot App client. public static PermissionDecisionSurface CopilotApp { get; } = new("copilot_app"); + /// An Agent Client Protocol host. + public static PermissionDecisionSurface Acp { get; } = new("acp"); + /// A generic Copilot SDK client. public static PermissionDecisionSurface Sdk { get; } = new("sdk"); diff --git a/dotnet/src/Generated/SessionEvents.cs b/dotnet/src/Generated/SessionEvents.cs index ccfda24fa3..bb2f3f9979 100644 --- a/dotnet/src/Generated/SessionEvents.cs +++ b/dotnet/src/Generated/SessionEvents.cs @@ -2002,6 +2002,11 @@ public sealed partial class SessionIdleData [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("aborted")] public bool? Aborted { get; set; } + + /// The session mode the agent was operating in when it went idle, when the mode is known. Lets turn-scoped consumers distinguish an autopilot continuation boundary (where the agent keeps working after this idle) from a genuine turn completion. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("mode")] + public SessionMode? Mode { get; set; } } /// Session title change payload containing the new display title. @@ -9797,6 +9802,70 @@ public override void Write(Utf8JsonWriter writer, Verbosity value, JsonSerialize } } +/// The session mode the agent is operating in. +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct SessionMode : IEquatable +{ + private readonly string? _value; + + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public SessionMode(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } + + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; + + /// The agent is responding interactively to the user. + public static SessionMode Interactive { get; } = new("interactive"); + + /// The agent is preparing a plan before making changes. + public static SessionMode Plan { get; } = new("plan"); + + /// The agent is working autonomously toward task completion. + public static SessionMode Autopilot { get; } = new("autopilot"); + + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(SessionMode left, SessionMode right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(SessionMode left, SessionMode right) => !(left == right); + + /// + public override bool Equals(object? obj) => obj is SessionMode other && Equals(other); + + /// + public bool Equals(SessionMode other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + + /// + public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); + + /// + public override string ToString() => Value; + + /// Provides a for serializing instances. + [EditorBrowsable(EditorBrowsableState.Never)] + public sealed class Converter : JsonConverter + { + /// + public override SessionMode Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } + + /// + public override void Write(Utf8JsonWriter writer, SessionMode value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(SessionMode)); + } + } +} + /// Who created the schedule: `user` (an explicit user action such as `/every` or `/after`) or `model` (the agent via the `manage_schedule` tool). Gates whether a scheduled skill that opted out of model invocation may fire: only user-created schedules may. [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] @@ -10077,70 +10146,6 @@ public override void Write(Utf8JsonWriter writer, ModelChangeSource value, JsonS } } -/// The session mode the agent is operating in. -[JsonConverter(typeof(Converter))] -[DebuggerDisplay("{Value,nq}")] -public readonly struct SessionMode : IEquatable -{ - private readonly string? _value; - - /// Initializes a new instance of the struct. - /// The value to associate with this . - [JsonConstructor] - public SessionMode(string value) - { - ArgumentException.ThrowIfNullOrWhiteSpace(value); - _value = value; - } - - /// Gets the value associated with this . - public string Value => _value ?? string.Empty; - - /// The agent is responding interactively to the user. - public static SessionMode Interactive { get; } = new("interactive"); - - /// The agent is preparing a plan before making changes. - public static SessionMode Plan { get; } = new("plan"); - - /// The agent is working autonomously toward task completion. - public static SessionMode Autopilot { get; } = new("autopilot"); - - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(SessionMode left, SessionMode right) => left.Equals(right); - - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(SessionMode left, SessionMode right) => !(left == right); - - /// - public override bool Equals(object? obj) => obj is SessionMode other && Equals(other); - - /// - public bool Equals(SessionMode other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); - - /// - public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); - - /// - public override string ToString() => Value; - - /// Provides a for serializing instances. - [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class Converter : JsonConverter - { - /// - public override SessionMode Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); - } - - /// - public override void Write(Utf8JsonWriter writer, SessionMode value, JsonSerializerOptions options) - { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(SessionMode)); - } - } -} - /// Permission mode for the session. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] diff --git a/dotnet/src/Session.cs b/dotnet/src/Session.cs index 3176e7db23..7d076d14b0 100644 --- a/dotnet/src/Session.cs +++ b/dotnet/src/Session.cs @@ -371,7 +371,7 @@ void Handler(SessionEvent evt) } break; - case SessionIdleEvent: + case SessionIdleEvent idleEvent when idleEvent.Data.Mode != SessionMode.Autopilot: LoggingHelpers.LogTiming(_logger, LogLevel.Debug, null, "CopilotSession.SendAndWaitAsync idle received. Elapsed={Elapsed}, SessionId={SessionId}", totalTimestamp, diff --git a/dotnet/test/Unit/ClientSessionLifetimeTests.cs b/dotnet/test/Unit/ClientSessionLifetimeTests.cs index 98bd36f928..b61546c650 100644 --- a/dotnet/test/Unit/ClientSessionLifetimeTests.cs +++ b/dotnet/test/Unit/ClientSessionLifetimeTests.cs @@ -829,6 +829,66 @@ public async Task Generated_Session_Rpc_Throws_When_Session_Disposed() await Assert.ThrowsAsync(() => session.Rpc.Model.GetCurrentAsync()); } + [Fact] + public async Task SendAndWaitAsync_Skips_Autopilot_Continuation_Idle() + { + await using var server = await FakeCopilotServer.StartAsync(); + await using var client = new CopilotClient(new CopilotClientOptions { Connection = RuntimeConnection.ForUri(server.Url) }); + await using var session = await client.CreateSessionAsync(new SessionConfig + { + OnPermissionRequest = PermissionHandler.ApproveAll + }); + + var sendTask = session.SendAndWaitAsync(new MessageOptions { Prompt = "keep going" }); + await WaitForRequestAsync(server, "session.send"); + + var continuationIdleProcessed = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + using var subscription = session.On(idle => + { + if (idle.Data.Mode == SessionMode.Autopilot) + { + continuationIdleProcessed.TrySetResult(); + } + }); + + DispatchEvent(session, new AssistantMessageEvent + { + Id = Guid.NewGuid(), + Data = new AssistantMessageData + { + Content = "intermediate", + MessageId = "assistant-1" + } + }); + DispatchEvent(session, new SessionIdleEvent + { + Id = Guid.NewGuid(), + Data = new SessionIdleData { Mode = SessionMode.Autopilot } + }); + + await continuationIdleProcessed.Task.WaitAsync(TimeSpan.FromSeconds(5)); + Assert.False(sendTask.IsCompleted); + + DispatchEvent(session, new AssistantMessageEvent + { + Id = Guid.NewGuid(), + Data = new AssistantMessageData + { + Content = "final", + MessageId = "assistant-2" + } + }); + DispatchEvent(session, new SessionIdleEvent + { + Id = Guid.NewGuid(), + Data = new SessionIdleData { Mode = SessionMode.Interactive } + }); + + var result = await sendTask.WaitAsync(TimeSpan.FromSeconds(5)); + Assert.NotNull(result); + Assert.Equal("final", result.Data.Content); + } + [MethodImpl(MethodImplOptions.NoInlining)] private static async Task> CreateDroppedSessionAsync(CopilotClient client) { diff --git a/go/rpc/zrpc.go b/go/rpc/zrpc.go index ad82a27823..2b3943988d 100644 --- a/go/rpc/zrpc.go +++ b/go/rpc/zrpc.go @@ -3746,6 +3746,10 @@ type GitHubTelemetryClientInfo struct { CLIVersion string `json:"cli_version"` // Copilot subscription plan, when known. CopilotPlan *string `json:"copilot_plan,omitempty"` + // Number of logical CPU cores on the host. + CpuCount *int64 `json:"cpu_count,omitempty"` + // Distinct CPU model names for the host, comma-separated. + CpuModel *string `json:"cpu_model,omitempty"` // Stable machine identifier for the device. DevDeviceID *string `json:"dev_device_id,omitempty"` // Whether the user is a GitHub/Microsoft staff member. @@ -7994,6 +7998,9 @@ func (PermissionDecisionApproveForSessionApprovalWrite) Kind() PermissionDecisio type PermissionDecisionContext struct { // Disposition of the permission request as observed by the responding client. Outcome PermissionDecisionOutcome `json:"outcome"` + // Whether the responding client could ask a user interactively, was running headlessly, or + // had no response path. Omit when the client cannot determine this authoritatively. + ResponseCapability *PermissionResponseCapability `json:"responseCapability,omitempty"` // Controlled reason or actor responsible for the response. Source PermissionDecisionSource `json:"source"` // Client surface that submitted the response. @@ -17675,6 +17682,8 @@ const ( type PermissionDecisionSurface string const ( + // An Agent Client Protocol host. + PermissionDecisionSurfaceAcp PermissionDecisionSurface = "acp" // The Copilot App client. PermissionDecisionSurfaceCopilotApp PermissionDecisionSurface = "copilot_app" // The non-interactive Copilot CLI prompt mode. @@ -17730,6 +17739,20 @@ const ( PermissionModeSourceUserSetting PermissionModeSource = "user_setting" ) +// Response capability available to the client when it settled a permission request. +// Experimental: PermissionResponseCapability is part of an experimental API and may change +// or be removed. +type PermissionResponseCapability string + +const ( + // The client could return an automated response but could not ask a user. + PermissionResponseCapabilityHeadless PermissionResponseCapability = "headless" + // The client could ask a user for this decision. + PermissionResponseCapabilityInteractive PermissionResponseCapability = "interactive" + // The client had no response path available. + PermissionResponseCapabilityNone PermissionResponseCapability = "none" +) + // Allowed values for the `PermissionsConfigureAdditionalContentExclusionPolicyScope` // enumeration. // Experimental: PermissionsConfigureAdditionalContentExclusionPolicyScope is part of an diff --git a/go/rpc/zsession_events.go b/go/rpc/zsession_events.go index 282d616be0..2abfd75a66 100644 --- a/go/rpc/zsession_events.go +++ b/go/rpc/zsession_events.go @@ -1376,6 +1376,8 @@ func (*MCPToolsListChangedData) Type() SessionEventType { return SessionEventTyp type SessionIdleData struct { // True when the preceding agentic loop was cancelled via abort signal Aborted *bool `json:"aborted,omitempty"` + // The session mode the agent was operating in when it went idle, when the mode is known. Lets turn-scoped consumers distinguish an autopilot continuation boundary (where the agent keeps working after this idle) from a genuine turn completion. + Mode *SessionMode `json:"mode,omitempty"` } func (*SessionIdleData) sessionEventData() {} diff --git a/go/session.go b/go/session.go index 3f6c4f605f..5d45d19ef2 100644 --- a/go/session.go +++ b/go/session.go @@ -498,6 +498,9 @@ func (s *Session) SendAndWait(ctx context.Context, options MessageOptions) (*Ses lastAssistantMessage = &eventCopy mu.Unlock() case *SessionIdleData: + if d.Mode != nil && *d.Mode == SessionModeAutopilot { + break + } select { case idleCh <- struct{}{}: default: diff --git a/go/session_test.go b/go/session_test.go index 9c5f4df8c9..74e212c418 100644 --- a/go/session_test.go +++ b/go/session_test.go @@ -366,6 +366,138 @@ func readTestJSONRPCFrame(r io.Reader) ([]byte, error) { return data, err } +func TestSession_SendAndWaitSkipsAutopilotContinuationIdle(t *testing.T) { + stdinR, stdinW := io.Pipe() + stdoutR, stdoutW := io.Pipe() + defer stdinR.Close() + defer stdinW.Close() + defer stdoutR.Close() + defer stdoutW.Close() + + client := jsonrpc2.NewClient(stdinW, stdoutR) + client.Start() + defer client.Stop() + + requestReceived := make(chan struct{}) + errCh := make(chan error, 1) + go func() { + frame, err := readTestJSONRPCFrame(stdinR) + if err != nil { + errCh <- err + return + } + + var request struct { + ID json.RawMessage `json:"id"` + Method string `json:"method"` + } + if err := json.Unmarshal(frame, &request); err != nil { + errCh <- err + return + } + if request.Method != "session.send" { + errCh <- fmt.Errorf("expected session.send, got %s", request.Method) + return + } + + response := map[string]any{ + "jsonrpc": "2.0", + "id": json.RawMessage(request.ID), + "result": map[string]any{"messageId": "message-1"}, + } + data, err := json.Marshal(response) + if err != nil { + errCh <- err + return + } + if _, err := fmt.Fprintf(stdoutW, "Content-Length: %d\r\n\r\n%s", len(data), data); err != nil { + errCh <- err + return + } + close(requestReceived) + }() + + session := &Session{ + SessionID: "session-1", + client: client, + RPC: rpc.NewSessionRPC(client, "session-1"), + handlers: make([]sessionHandler, 0), + eventCh: make(chan SessionEvent, 8), + } + go session.processEvents() + defer close(session.eventCh) + + resultCh := make(chan *SessionEvent, 1) + go func() { + result, err := session.SendAndWait(t.Context(), MessageOptions{Prompt: "keep going"}) + if err != nil { + errCh <- err + return + } + resultCh <- result + }() + + select { + case <-requestReceived: + case err := <-errCh: + t.Fatal(err) + case <-time.After(2 * time.Second): + t.Fatal("timed out waiting for session.send request") + } + + continuationIdleProcessed := make(chan struct{}) + unsubscribe := session.On(func(event SessionEvent) { + if idle, ok := event.Data.(*SessionIdleData); ok && + idle.Mode != nil && *idle.Mode == SessionModeAutopilot { + close(continuationIdleProcessed) + } + }) + defer unsubscribe() + + autopilot := SessionModeAutopilot + session.dispatchEvent(SessionEvent{Data: &AssistantMessageData{ + Content: "intermediate", + MessageID: "assistant-1", + }}) + session.dispatchEvent(SessionEvent{Data: &SessionIdleData{Mode: &autopilot}}) + + select { + case <-continuationIdleProcessed: + case err := <-errCh: + t.Fatal(err) + case <-time.After(2 * time.Second): + t.Fatal("timed out waiting for autopilot continuation idle") + } + + select { + case <-resultCh: + t.Fatal("SendAndWait returned at an autopilot continuation idle") + default: + } + + interactive := SessionModeInteractive + session.dispatchEvent(SessionEvent{Data: &AssistantMessageData{ + Content: "final", + MessageID: "assistant-2", + }}) + session.dispatchEvent(SessionEvent{Data: &SessionIdleData{Mode: &interactive}}) + + select { + case result := <-resultCh: + message, ok := result.Data.(*AssistantMessageData) + if !ok { + t.Fatalf("expected assistant message, got %T", result.Data) + } + if message.Content != "final" { + t.Fatalf("expected final assistant message, got %q", message.Content) + } + case err := <-errCh: + t.Fatal(err) + case <-time.After(2 * time.Second): + t.Fatal("timed out waiting for terminal idle") + } +} + func TestSession_On(t *testing.T) { t.Run("multiple handlers all receive events", func(t *testing.T) { session, cleanup := newTestSession() diff --git a/go/types.go b/go/types.go index 38ddaf5663..1d98e06158 100644 --- a/go/types.go +++ b/go/types.go @@ -393,6 +393,16 @@ type PermissionInvocation struct { // may change or be removed. type PermissionDecisionContext = rpc.PermissionDecisionContext +// PermissionResponseCapability describes whether the responding client could +// ask a user for a permission decision. +type PermissionResponseCapability = rpc.PermissionResponseCapability + +const ( + PermissionResponseCapabilityHeadless = rpc.PermissionResponseCapabilityHeadless + PermissionResponseCapabilityInteractive = rpc.PermissionResponseCapabilityInteractive + PermissionResponseCapabilityNone = rpc.PermissionResponseCapabilityNone +) + // PermissionDecisionOutcome describes the disposition of a permission request // as observed by the responding client. type PermissionDecisionOutcome = rpc.PermissionDecisionOutcome @@ -419,6 +429,7 @@ const ( type PermissionDecisionSurface = rpc.PermissionDecisionSurface const ( + PermissionDecisionSurfaceAcp = rpc.PermissionDecisionSurfaceAcp PermissionDecisionSurfaceCopilotApp = rpc.PermissionDecisionSurfaceCopilotApp PermissionDecisionSurfacePromptMode = rpc.PermissionDecisionSurfacePromptMode PermissionDecisionSurfaceSDK = rpc.PermissionDecisionSurfaceSDK diff --git a/java/pom.xml b/java/pom.xml index 358eb64985..b7618b897a 100644 --- a/java/pom.xml +++ b/java/pom.xml @@ -63,7 +63,7 @@ DO NOT EDIT MANUALLY. Updated by the update-copilot-dependency workflow. --> - ^1.0.81-10 + ^1.0.81-11 true diff --git a/java/scripts/codegen/package-lock.json b/java/scripts/codegen/package-lock.json index a81af6e0d1..d0b602e846 100644 --- a/java/scripts/codegen/package-lock.json +++ b/java/scripts/codegen/package-lock.json @@ -6,7 +6,7 @@ "": { "name": "copilot-sdk-java-codegen", "dependencies": { - "@github/copilot": "^1.0.81-10", + "@github/copilot": "^1.0.81-11", "json-schema": "^0.4.0", "tsx": "^4.23.12" } @@ -428,9 +428,9 @@ } }, "node_modules/@github/copilot": { - "version": "1.0.81-10", - "resolved": "https://registry.npmjs.org/@github/copilot/-/copilot-1.0.81-10.tgz", - "integrity": "sha512-Ac99EvN16s4hKRhJLSEn1HMNaZ6MD8BzIey1zzJNBQy1/yP4PQDZ2CWitEq+XQQEi+6SsqeJRqXOKiWk1EyK7g==", + "version": "1.0.81-11", + "resolved": "https://registry.npmjs.org/@github/copilot/-/copilot-1.0.81-11.tgz", + "integrity": "sha512-F7hZ6G6fhWH4uq862mbs2JE3nL0KIVBOc94/EFOdEjux3oHUQst9a06gKibEJS6VRULaYTXzatA1EAgNC9dzFA==", "license": "SEE LICENSE IN LICENSE.md", "dependencies": { "detect-libc": "^2.1.2" @@ -439,20 +439,20 @@ "copilot": "npm-loader.js" }, "optionalDependencies": { - "@github/copilot-darwin-arm64": "1.0.81-10", - "@github/copilot-darwin-x64": "1.0.81-10", - "@github/copilot-linux-arm64": "1.0.81-10", - "@github/copilot-linux-x64": "1.0.81-10", - "@github/copilot-linuxmusl-arm64": "1.0.81-10", - "@github/copilot-linuxmusl-x64": "1.0.81-10", - "@github/copilot-win32-arm64": "1.0.81-10", - "@github/copilot-win32-x64": "1.0.81-10" + "@github/copilot-darwin-arm64": "1.0.81-11", + "@github/copilot-darwin-x64": "1.0.81-11", + "@github/copilot-linux-arm64": "1.0.81-11", + "@github/copilot-linux-x64": "1.0.81-11", + "@github/copilot-linuxmusl-arm64": "1.0.81-11", + "@github/copilot-linuxmusl-x64": "1.0.81-11", + "@github/copilot-win32-arm64": "1.0.81-11", + "@github/copilot-win32-x64": "1.0.81-11" } }, "node_modules/@github/copilot-darwin-arm64": { - "version": "1.0.81-10", - "resolved": "https://registry.npmjs.org/@github/copilot-darwin-arm64/-/copilot-darwin-arm64-1.0.81-10.tgz", - "integrity": "sha512-s90Av0iwjTSU6Gky8T9wI1PJdlfbdUcPAVgKDtimaOiAwcdLG4fKTpGxrk96KJrnOHHK3x9SiXsw/pW0ThAH/A==", + "version": "1.0.81-11", + "resolved": "https://registry.npmjs.org/@github/copilot-darwin-arm64/-/copilot-darwin-arm64-1.0.81-11.tgz", + "integrity": "sha512-3eLs71CLnJH9RNnESnv4esipZPeGXMlBxQeKXwZY+crwcW2RAR8YuovQyfoZ/5by1PLPbYrOjXNfQL6kXSisrA==", "cpu": [ "arm64" ], @@ -466,9 +466,9 @@ } }, "node_modules/@github/copilot-darwin-x64": { - "version": "1.0.81-10", - "resolved": "https://registry.npmjs.org/@github/copilot-darwin-x64/-/copilot-darwin-x64-1.0.81-10.tgz", - "integrity": "sha512-8RnPI4J311oJQ0GPB6JxuLJq4JNY/KF9ZIIQm8KpxXBY6d+6fmmAsMDEk7OiF/Asl2I7+LTi+qU2ZVhP7FYhbg==", + "version": "1.0.81-11", + "resolved": "https://registry.npmjs.org/@github/copilot-darwin-x64/-/copilot-darwin-x64-1.0.81-11.tgz", + "integrity": "sha512-GdFLiUC8UL9k6K+woG8AyL3zBafd0Br1TIPDV5iiZsyBtTe4g67FjL7DGCYDt6AN9G43jWdK0M0InoNDVq1K1A==", "cpu": [ "x64" ], @@ -482,9 +482,9 @@ } }, "node_modules/@github/copilot-linux-arm64": { - "version": "1.0.81-10", - "resolved": "https://registry.npmjs.org/@github/copilot-linux-arm64/-/copilot-linux-arm64-1.0.81-10.tgz", - "integrity": "sha512-2UtK5CBrE6ZVSIzU2KHeIgO8N7056axjbF2lE6WuK+H+oJJ4v3w5eQkalqGzRHhkaPfCW4kT1lDMhZFW+XbLjA==", + "version": "1.0.81-11", + "resolved": "https://registry.npmjs.org/@github/copilot-linux-arm64/-/copilot-linux-arm64-1.0.81-11.tgz", + "integrity": "sha512-C4hcAow5CdaVJITbdtGqFgxWpW7TqwyCPNn8OtcbvdWeiKcfOBDrgkvbsezG98/5Ovs3HWxZRT4oZqCEmGF9Ww==", "cpu": [ "arm64" ], @@ -498,9 +498,9 @@ } }, "node_modules/@github/copilot-linux-x64": { - "version": "1.0.81-10", - "resolved": "https://registry.npmjs.org/@github/copilot-linux-x64/-/copilot-linux-x64-1.0.81-10.tgz", - "integrity": "sha512-61+KAfo1TBARrBfss3w4dfmRVSf0PiFg0c9JNuT9HjoNnytl7maJBPEgUvI4YBcxScNEAlCMXaUUG3Tuuh1g+w==", + "version": "1.0.81-11", + "resolved": "https://registry.npmjs.org/@github/copilot-linux-x64/-/copilot-linux-x64-1.0.81-11.tgz", + "integrity": "sha512-izu0PwWx+wL4zxacO6cd6r0zMMMQ3pTz+2euWcAd7HCJ/CIR6+YYfjU3TI3TPBZ9zDLZGoxHKYYPfJ1ZbSTzEg==", "cpu": [ "x64" ], @@ -514,9 +514,9 @@ } }, "node_modules/@github/copilot-linuxmusl-arm64": { - "version": "1.0.81-10", - "resolved": "https://registry.npmjs.org/@github/copilot-linuxmusl-arm64/-/copilot-linuxmusl-arm64-1.0.81-10.tgz", - "integrity": "sha512-CR6KRPCFoGkaD8I2an1FyrT5avF1U5aTbwW2sYCP7w1KExYFknxEL8ES6BkFuPEA7YcjmLa0SOq26Z+TgIVHSg==", + "version": "1.0.81-11", + "resolved": "https://registry.npmjs.org/@github/copilot-linuxmusl-arm64/-/copilot-linuxmusl-arm64-1.0.81-11.tgz", + "integrity": "sha512-uWGUjaxOSMu6dKFWcTvXGUUp76vC3OV7nlSupecRkQ7gA3OxdrrB8rXE5/leC606Hk6oe9Wl7wKCH9EwuZ6afg==", "cpu": [ "arm64" ], @@ -530,9 +530,9 @@ } }, "node_modules/@github/copilot-linuxmusl-x64": { - "version": "1.0.81-10", - "resolved": "https://registry.npmjs.org/@github/copilot-linuxmusl-x64/-/copilot-linuxmusl-x64-1.0.81-10.tgz", - "integrity": "sha512-fvZfyEOfRkvUDPXY6UUjAqV8Mkf08PQV+jgtiAFUryuas5VP9cYaAmQSmNpzNMNi3kSX/ycUJe7oc3zXZ8ylog==", + "version": "1.0.81-11", + "resolved": "https://registry.npmjs.org/@github/copilot-linuxmusl-x64/-/copilot-linuxmusl-x64-1.0.81-11.tgz", + "integrity": "sha512-BpWKd/iu1tTyuPR2zuPFs1pVOlley4yt/TXkn6/gVwt52VI0rUxEO+n77RVjDcyMxWaUApbad2VNRPIH77guCA==", "cpu": [ "x64" ], @@ -546,9 +546,9 @@ } }, "node_modules/@github/copilot-win32-arm64": { - "version": "1.0.81-10", - "resolved": "https://registry.npmjs.org/@github/copilot-win32-arm64/-/copilot-win32-arm64-1.0.81-10.tgz", - "integrity": "sha512-n30PPBgCT4Iq9MgH6is6L3eUEE+sF6xB2fb+dGsclj5j/hCkT7+ef0j8YcAGipsvGfzGAuywIsWlvF7fzYsOKQ==", + "version": "1.0.81-11", + "resolved": "https://registry.npmjs.org/@github/copilot-win32-arm64/-/copilot-win32-arm64-1.0.81-11.tgz", + "integrity": "sha512-GOK3cACgD96m065uJxbgcXL7MQ1qm+wq1qtvGBpprY0r9wTYJG/rVCtayjYB6B57rnaBwPll3+PQ2J1qgfO57A==", "cpu": [ "arm64" ], @@ -562,9 +562,9 @@ } }, "node_modules/@github/copilot-win32-x64": { - "version": "1.0.81-10", - "resolved": "https://registry.npmjs.org/@github/copilot-win32-x64/-/copilot-win32-x64-1.0.81-10.tgz", - "integrity": "sha512-lb8kvhrXwGCN3LeRDQfLHsUp+F43XvPYznaYK1sPtK1kFGa4/kL690tasoSEvzu8ZKoTY6kZ6YmDbUZgqOislw==", + "version": "1.0.81-11", + "resolved": "https://registry.npmjs.org/@github/copilot-win32-x64/-/copilot-win32-x64-1.0.81-11.tgz", + "integrity": "sha512-u4K6UU2iGQJqQwsdHNilJBwiaC48PfMsWVFnTSlJD5lCYp9zCk1odrvrcmB3ARYinE/IcXLUosiHaM/ChR/pdA==", "cpu": [ "x64" ], diff --git a/java/scripts/codegen/package.json b/java/scripts/codegen/package.json index f619ebd627..d5669c39f0 100644 --- a/java/scripts/codegen/package.json +++ b/java/scripts/codegen/package.json @@ -7,7 +7,7 @@ "generate:java": "tsx java.ts" }, "dependencies": { - "@github/copilot": "^1.0.81-10", + "@github/copilot": "^1.0.81-11", "json-schema": "^0.4.0", "tsx": "^4.23.12" } diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/SessionIdleEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/SessionIdleEvent.java index e51a26e5ae..ae509c4b42 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/SessionIdleEvent.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/SessionIdleEvent.java @@ -35,7 +35,9 @@ public final class SessionIdleEvent extends SessionEvent { @JsonInclude(JsonInclude.Include.NON_NULL) public record SessionIdleEventData( /** True when the preceding agentic loop was cancelled via abort signal */ - @JsonProperty("aborted") Boolean aborted + @JsonProperty("aborted") Boolean aborted, + /** The session mode the agent was operating in when it went idle, when the mode is known. Lets turn-scoped consumers distinguish an autopilot continuation boundary (where the agent keeps working after this idle) from a genuine turn completion. */ + @JsonProperty("mode") SessionMode mode ) { } } diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/GitHubTelemetryClientInfo.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/GitHubTelemetryClientInfo.java index 7d7a1eaf72..b6bef1bffd 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/GitHubTelemetryClientInfo.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/GitHubTelemetryClientInfo.java @@ -40,6 +40,10 @@ public record GitHubTelemetryClientInfo( /** Whether the user is a GitHub/Microsoft staff member. */ @JsonProperty("is_staff") Boolean isStaff, /** Stable machine identifier for the device. */ - @JsonProperty("dev_device_id") String devDeviceId + @JsonProperty("dev_device_id") String devDeviceId, + /** Distinct CPU model names for the host, comma-separated. */ + @JsonProperty("cpu_model") String cpuModel, + /** Number of logical CPU cores on the host. */ + @JsonProperty("cpu_count") Long cpuCount ) { } diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/PermissionDecisionContext.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/PermissionDecisionContext.java index 73934eea66..5200f4cd51 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/PermissionDecisionContext.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/PermissionDecisionContext.java @@ -26,6 +26,8 @@ public record PermissionDecisionContext( /** Controlled reason or actor responsible for the response. */ @JsonProperty("source") PermissionDecisionSource source, /** Client surface that submitted the response. */ - @JsonProperty("surface") PermissionDecisionSurface surface + @JsonProperty("surface") PermissionDecisionSurface surface, + /** Whether the responding client could ask a user interactively, was running headlessly, or had no response path. Omit when the client cannot determine this authoritatively. */ + @JsonProperty("responseCapability") PermissionResponseCapability responseCapability ) { } diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/PermissionDecisionSurface.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/PermissionDecisionSurface.java index 2cf6348794..c6d2db8c5f 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/PermissionDecisionSurface.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/PermissionDecisionSurface.java @@ -22,6 +22,8 @@ public enum PermissionDecisionSurface { PROMPT_MODE("prompt_mode"), /** The {@code copilot_app} variant. */ COPILOT_APP("copilot_app"), + /** The {@code acp} variant. */ + ACP("acp"), /** The {@code sdk} variant. */ SDK("sdk"); diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/PermissionResponseCapability.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/PermissionResponseCapability.java new file mode 100644 index 0000000000..c52bfd3234 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/PermissionResponseCapability.java @@ -0,0 +1,37 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import javax.annotation.processing.Generated; + +/** + * Response capability available to the client when it settled a permission request. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public enum PermissionResponseCapability { + /** The {@code interactive} variant. */ + INTERACTIVE("interactive"), + /** The {@code headless} variant. */ + HEADLESS("headless"), + /** The {@code none} variant. */ + NONE("none"); + + private final String value; + PermissionResponseCapability(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static PermissionResponseCapability fromValue(String value) { + for (PermissionResponseCapability v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown PermissionResponseCapability value: " + value); + } +} diff --git a/java/sdk/src/main/java/com/github/copilot/CopilotSession.java b/java/sdk/src/main/java/com/github/copilot/CopilotSession.java index 83ce49654e..f3a35967d3 100644 --- a/java/sdk/src/main/java/com/github/copilot/CopilotSession.java +++ b/java/sdk/src/main/java/com/github/copilot/CopilotSession.java @@ -57,6 +57,7 @@ import com.github.copilot.generated.SessionErrorEvent; import com.github.copilot.generated.SessionEvent; import com.github.copilot.generated.SessionIdleEvent; +import com.github.copilot.generated.SessionMode; import com.github.copilot.generated.rpc.OpenCanvasInstance; import com.github.copilot.rpc.AgentInfo; import com.github.copilot.rpc.AutoModeSwitchHandler; @@ -562,7 +563,8 @@ public CompletableFuture sendAndWait(MessageOptions optio + sessionId, totalNanos); } - } else if (evt instanceof SessionIdleEvent) { + } else if (evt instanceof SessionIdleEvent idleEvent + && (idleEvent.getData() == null || idleEvent.getData().mode() != SessionMode.AUTOPILOT)) { LoggingHelpers.logTiming(LOG, Level.FINE, "CopilotSession.sendAndWait idle received. Elapsed={Elapsed}, SessionId=" + sessionId, totalNanos); 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 47d537f134..a1a22674b9 100644 --- a/java/sdk/src/test/java/com/github/copilot/SessionEventHandlingTest.java +++ b/java/sdk/src/test/java/com/github/copilot/SessionEventHandlingTest.java @@ -5,11 +5,14 @@ package com.github.copilot; import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; import java.io.Closeable; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @@ -24,7 +27,10 @@ import com.github.copilot.generated.SessionEvent; import com.github.copilot.generated.AssistantMessageEvent; import com.github.copilot.generated.SessionIdleEvent; +import com.github.copilot.generated.SessionMode; import com.github.copilot.generated.SessionStartEvent; +import com.github.copilot.rpc.MessageOptions; +import com.github.copilot.rpc.SendMessageResponse; /** * Unit tests for session event handling API. @@ -85,6 +91,33 @@ void testTypedEventHandler() { assertEquals("Second message", receivedMessages.get(1).getData().content()); } + @Test + void testSendAndWaitSkipsAutopilotContinuationIdle() throws Exception { + var rpc = mock(JsonRpcClient.class); + when(rpc.invoke(eq("session.send"), any(), eq(SendMessageResponse.class))) + .thenReturn(CompletableFuture.completedFuture(new SendMessageResponse("message-1"))); + when(rpc.invoke(eq("session.destroy"), any(), eq(Void.class))) + .thenReturn(CompletableFuture.completedFuture(null)); + session = new CopilotSession("test-session-id", rpc); + + try { + var pending = session.sendAndWait(new MessageOptions().setPrompt("keep going")); + + dispatchEvent(createAssistantMessageEvent("intermediate")); + dispatchEvent(createSessionIdleEvent(SessionMode.AUTOPILOT)); + assertFalse(pending.isDone(), "Autopilot continuation idle must not complete sendAndWait"); + + dispatchEvent(createAssistantMessageEvent("final")); + dispatchEvent(createSessionIdleEvent(SessionMode.INTERACTIVE)); + + var result = pending.get(5, TimeUnit.SECONDS); + assertNotNull(result); + assertEquals("final", result.getData().content()); + } finally { + session.close(); + } + } + @Test void testMultipleTypedHandlers() { var messages = new ArrayList(); @@ -873,4 +906,10 @@ private AssistantMessageEvent createAssistantMessageEvent(String content) { private SessionIdleEvent createSessionIdleEvent() { return new SessionIdleEvent(); } + + private SessionIdleEvent createSessionIdleEvent(SessionMode mode) { + var event = new SessionIdleEvent(); + event.setData(new SessionIdleEvent.SessionIdleEventData(null, mode)); + return event; + } } diff --git a/java/sdk/src/test/java/com/github/copilot/rpc/PermissionRequestResultDecisionContextTest.java b/java/sdk/src/test/java/com/github/copilot/rpc/PermissionRequestResultDecisionContextTest.java index 395ad50ad6..1097b9ea0e 100644 --- a/java/sdk/src/test/java/com/github/copilot/rpc/PermissionRequestResultDecisionContextTest.java +++ b/java/sdk/src/test/java/com/github/copilot/rpc/PermissionRequestResultDecisionContextTest.java @@ -31,7 +31,7 @@ class PermissionRequestResultDecisionContextTest { private static PermissionDecisionContext sampleContext() { return new PermissionDecisionContext(PermissionDecisionOutcome.AUTO_APPROVED, - PermissionDecisionSource.HOST_POLICY, PermissionDecisionSurface.SDK); + PermissionDecisionSource.HOST_POLICY, PermissionDecisionSurface.SDK, null); } @Test @@ -68,7 +68,7 @@ void withoutContextOmitsDecisionContextKey() throws Exception { void setDecisionContextTwiceReplacesRatherThanNests() { var first = sampleContext(); var second = new PermissionDecisionContext(PermissionDecisionOutcome.PROMPTED_USER, - PermissionDecisionSource.HUMAN_RESPONSE, PermissionDecisionSurface.TUI); + PermissionDecisionSource.HUMAN_RESPONSE, PermissionDecisionSurface.TUI, null); var result = PermissionRequestResult.approveOnce().setDecisionContext(first).setDecisionContext(second); diff --git a/nodejs/package-lock.json b/nodejs/package-lock.json index c931069940..84430ac141 100644 --- a/nodejs/package-lock.json +++ b/nodejs/package-lock.json @@ -9,7 +9,7 @@ "version": "0.0.0-dev", "license": "MIT", "dependencies": { - "@github/copilot": "^1.0.81-10", + "@github/copilot": "^1.0.81-11", "koffi": "^3.1.0", "vscode-jsonrpc": "^8.2.1", "zod": "^4.3.6" @@ -658,8 +658,8 @@ } }, "node_modules/@github/copilot": { - "version": "1.0.81-10", - "integrity": "sha512-Ac99EvN16s4hKRhJLSEn1HMNaZ6MD8BzIey1zzJNBQy1/yP4PQDZ2CWitEq+XQQEi+6SsqeJRqXOKiWk1EyK7g==", + "version": "1.0.81-11", + "integrity": "sha512-F7hZ6G6fhWH4uq862mbs2JE3nL0KIVBOc94/EFOdEjux3oHUQst9a06gKibEJS6VRULaYTXzatA1EAgNC9dzFA==", "license": "SEE LICENSE IN LICENSE.md", "dependencies": { "detect-libc": "^2.1.2" @@ -668,19 +668,19 @@ "copilot": "npm-loader.js" }, "optionalDependencies": { - "@github/copilot-darwin-arm64": "1.0.81-10", - "@github/copilot-darwin-x64": "1.0.81-10", - "@github/copilot-linux-arm64": "1.0.81-10", - "@github/copilot-linux-x64": "1.0.81-10", - "@github/copilot-linuxmusl-arm64": "1.0.81-10", - "@github/copilot-linuxmusl-x64": "1.0.81-10", - "@github/copilot-win32-arm64": "1.0.81-10", - "@github/copilot-win32-x64": "1.0.81-10" + "@github/copilot-darwin-arm64": "1.0.81-11", + "@github/copilot-darwin-x64": "1.0.81-11", + "@github/copilot-linux-arm64": "1.0.81-11", + "@github/copilot-linux-x64": "1.0.81-11", + "@github/copilot-linuxmusl-arm64": "1.0.81-11", + "@github/copilot-linuxmusl-x64": "1.0.81-11", + "@github/copilot-win32-arm64": "1.0.81-11", + "@github/copilot-win32-x64": "1.0.81-11" } }, "node_modules/@github/copilot-darwin-arm64": { - "version": "1.0.81-10", - "integrity": "sha512-s90Av0iwjTSU6Gky8T9wI1PJdlfbdUcPAVgKDtimaOiAwcdLG4fKTpGxrk96KJrnOHHK3x9SiXsw/pW0ThAH/A==", + "version": "1.0.81-11", + "integrity": "sha512-3eLs71CLnJH9RNnESnv4esipZPeGXMlBxQeKXwZY+crwcW2RAR8YuovQyfoZ/5by1PLPbYrOjXNfQL6kXSisrA==", "cpu": [ "arm64" ], @@ -694,8 +694,8 @@ } }, "node_modules/@github/copilot-darwin-x64": { - "version": "1.0.81-10", - "integrity": "sha512-8RnPI4J311oJQ0GPB6JxuLJq4JNY/KF9ZIIQm8KpxXBY6d+6fmmAsMDEk7OiF/Asl2I7+LTi+qU2ZVhP7FYhbg==", + "version": "1.0.81-11", + "integrity": "sha512-GdFLiUC8UL9k6K+woG8AyL3zBafd0Br1TIPDV5iiZsyBtTe4g67FjL7DGCYDt6AN9G43jWdK0M0InoNDVq1K1A==", "cpu": [ "x64" ], @@ -709,8 +709,8 @@ } }, "node_modules/@github/copilot-linux-arm64": { - "version": "1.0.81-10", - "integrity": "sha512-2UtK5CBrE6ZVSIzU2KHeIgO8N7056axjbF2lE6WuK+H+oJJ4v3w5eQkalqGzRHhkaPfCW4kT1lDMhZFW+XbLjA==", + "version": "1.0.81-11", + "integrity": "sha512-C4hcAow5CdaVJITbdtGqFgxWpW7TqwyCPNn8OtcbvdWeiKcfOBDrgkvbsezG98/5Ovs3HWxZRT4oZqCEmGF9Ww==", "cpu": [ "arm64" ], @@ -724,8 +724,8 @@ } }, "node_modules/@github/copilot-linux-x64": { - "version": "1.0.81-10", - "integrity": "sha512-61+KAfo1TBARrBfss3w4dfmRVSf0PiFg0c9JNuT9HjoNnytl7maJBPEgUvI4YBcxScNEAlCMXaUUG3Tuuh1g+w==", + "version": "1.0.81-11", + "integrity": "sha512-izu0PwWx+wL4zxacO6cd6r0zMMMQ3pTz+2euWcAd7HCJ/CIR6+YYfjU3TI3TPBZ9zDLZGoxHKYYPfJ1ZbSTzEg==", "cpu": [ "x64" ], @@ -739,8 +739,8 @@ } }, "node_modules/@github/copilot-linuxmusl-arm64": { - "version": "1.0.81-10", - "integrity": "sha512-CR6KRPCFoGkaD8I2an1FyrT5avF1U5aTbwW2sYCP7w1KExYFknxEL8ES6BkFuPEA7YcjmLa0SOq26Z+TgIVHSg==", + "version": "1.0.81-11", + "integrity": "sha512-uWGUjaxOSMu6dKFWcTvXGUUp76vC3OV7nlSupecRkQ7gA3OxdrrB8rXE5/leC606Hk6oe9Wl7wKCH9EwuZ6afg==", "cpu": [ "arm64" ], @@ -754,8 +754,8 @@ } }, "node_modules/@github/copilot-linuxmusl-x64": { - "version": "1.0.81-10", - "integrity": "sha512-fvZfyEOfRkvUDPXY6UUjAqV8Mkf08PQV+jgtiAFUryuas5VP9cYaAmQSmNpzNMNi3kSX/ycUJe7oc3zXZ8ylog==", + "version": "1.0.81-11", + "integrity": "sha512-BpWKd/iu1tTyuPR2zuPFs1pVOlley4yt/TXkn6/gVwt52VI0rUxEO+n77RVjDcyMxWaUApbad2VNRPIH77guCA==", "cpu": [ "x64" ], @@ -769,8 +769,8 @@ } }, "node_modules/@github/copilot-win32-arm64": { - "version": "1.0.81-10", - "integrity": "sha512-n30PPBgCT4Iq9MgH6is6L3eUEE+sF6xB2fb+dGsclj5j/hCkT7+ef0j8YcAGipsvGfzGAuywIsWlvF7fzYsOKQ==", + "version": "1.0.81-11", + "integrity": "sha512-GOK3cACgD96m065uJxbgcXL7MQ1qm+wq1qtvGBpprY0r9wTYJG/rVCtayjYB6B57rnaBwPll3+PQ2J1qgfO57A==", "cpu": [ "arm64" ], @@ -784,8 +784,8 @@ } }, "node_modules/@github/copilot-win32-x64": { - "version": "1.0.81-10", - "integrity": "sha512-lb8kvhrXwGCN3LeRDQfLHsUp+F43XvPYznaYK1sPtK1kFGa4/kL690tasoSEvzu8ZKoTY6kZ6YmDbUZgqOislw==", + "version": "1.0.81-11", + "integrity": "sha512-u4K6UU2iGQJqQwsdHNilJBwiaC48PfMsWVFnTSlJD5lCYp9zCk1odrvrcmB3ARYinE/IcXLUosiHaM/ChR/pdA==", "cpu": [ "x64" ], diff --git a/nodejs/package.json b/nodejs/package.json index de507419fe..9f464788ee 100644 --- a/nodejs/package.json +++ b/nodejs/package.json @@ -56,7 +56,7 @@ "author": "GitHub", "license": "MIT", "dependencies": { - "@github/copilot": "^1.0.81-10", + "@github/copilot": "^1.0.81-11", "koffi": "^3.1.0", "vscode-jsonrpc": "^8.2.1", "zod": "^4.3.6" diff --git a/nodejs/samples/package-lock.json b/nodejs/samples/package-lock.json index 2b055e025b..7c2a1052d8 100644 --- a/nodejs/samples/package-lock.json +++ b/nodejs/samples/package-lock.json @@ -18,7 +18,7 @@ "version": "0.0.0-dev", "license": "MIT", "dependencies": { - "@github/copilot": "^1.0.81-10", + "@github/copilot": "^1.0.81-11", "koffi": "^3.1.0", "vscode-jsonrpc": "^8.2.1", "zod": "^4.3.6" diff --git a/nodejs/src/generated/rpc.ts b/nodejs/src/generated/rpc.ts index 65b7c3701a..9e7304bde2 100644 --- a/nodejs/src/generated/rpc.ts +++ b/nodejs/src/generated/rpc.ts @@ -2548,8 +2548,24 @@ export type PermissionDecisionSurface = | "prompt_mode" /** The Copilot App client. */ | "copilot_app" + /** An Agent Client Protocol host. */ + | "acp" /** A generic Copilot SDK client. */ | "sdk"; +/** + * Response capability available to the client when it settled a permission request. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "PermissionResponseCapability". + */ +/** @experimental */ +export type PermissionResponseCapability = + /** The client could ask a user for this decision. */ + | "interactive" + /** The client could return an automated response but could not ask a user. */ + | "headless" + /** The client had no response path available. */ + | "none"; /** * Tool approval to persist and apply * @@ -8372,6 +8388,14 @@ export interface GitHubTelemetryClientInfo { * Stable machine identifier for the device. */ dev_device_id?: string; + /** + * Distinct CPU model names for the host, comma-separated. + */ + cpu_model?: string; + /** + * Number of logical CPU cores on the host. + */ + cpu_count?: number; } /** * A single telemetry event in the runtime's native GitHub-shaped telemetry format, forwarded verbatim to opted-in hosts. The `restricted` flag on the enclosing GitHubTelemetryNotification distinguishes standard from restricted events; the payload shape is identical for both. @@ -13498,6 +13522,7 @@ export interface PermissionDecisionContext { outcome: PermissionDecisionOutcome; source: PermissionDecisionSource; surface: PermissionDecisionSurface; + responseCapability?: PermissionResponseCapability; } /** * Pending permission request ID and the decision to apply (approve/reject and scope). diff --git a/nodejs/src/generated/session-events.ts b/nodejs/src/generated/session-events.ts index 3ec55aacda..473a2dbe16 100644 --- a/nodejs/src/generated/session-events.ts +++ b/nodejs/src/generated/session-events.ts @@ -163,6 +163,16 @@ export type Verbosity = | "medium" /** A more detailed response was requested. */ | "high"; +/** + * The session mode the agent is operating in + */ +export type SessionMode = + /** The agent is responding interactively to the user. */ + | "interactive" + /** The agent is preparing a plan before making changes. */ + | "plan" + /** The agent is working autonomously toward task completion. */ + | "autopilot"; /** * Who created the schedule: `user` (an explicit user action such as `/every` or `/after`) or `model` (the agent via the `manage_schedule` tool). Gates whether a scheduled skill that opted out of model invocation may fire: only user-created schedules may. */ @@ -219,16 +229,6 @@ export type ModelChangeSource = | "automatic" /** An SDK or RPC caller selected the model. */ | "sdk"; -/** - * The session mode the agent is operating in - */ -export type SessionMode = - /** The agent is responding interactively to the user. */ - | "interactive" - /** The agent is preparing a plan before making changes. */ - | "plan" - /** The agent is working autonomously toward task completion. */ - | "autopilot"; /** * Permission mode for the session. */ @@ -1362,6 +1362,7 @@ export interface IdleData { * True when the preceding agentic loop was cancelled via abort signal */ aborted?: boolean; + mode?: SessionMode; } /** * Session event "session.title_changed". Session title change payload containing the new display title diff --git a/nodejs/src/index.ts b/nodejs/src/index.ts index bf7405c87b..9d55ab1d10 100644 --- a/nodejs/src/index.ts +++ b/nodejs/src/index.ts @@ -135,6 +135,7 @@ export type { PermissionDecisionOutcome, PermissionDecisionSource, PermissionDecisionSurface, + PermissionResponseCapability, ProviderConfig, ProviderModelConfig, ProviderTokenArgs, diff --git a/nodejs/src/session.ts b/nodejs/src/session.ts index d8b67133ff..65ff00921c 100644 --- a/nodejs/src/session.ts +++ b/nodejs/src/session.ts @@ -764,7 +764,7 @@ export class CopilotSession { const unsubscribe = this.on((event) => { if (event.type === "assistant.message") { lastAssistantMessage = event; - } else if (event.type === "session.idle") { + } else if (event.type === "session.idle" && event.data.mode !== "autopilot") { resolveOutcome({ kind: "idle" }); } else if (event.type === "session.error") { const error = new Error(event.data.message); diff --git a/nodejs/src/types.ts b/nodejs/src/types.ts index f9c3c6110e..616e15a467 100644 --- a/nodejs/src/types.ts +++ b/nodejs/src/types.ts @@ -88,6 +88,7 @@ export type { PermissionDecisionOutcome, PermissionDecisionSource, PermissionDecisionSurface, + PermissionResponseCapability, } from "./generated/rpc.js"; export type { CopilotRequestContext } from "./copilotRequestHandler.js"; export { diff --git a/nodejs/test/session-event-types.test.ts b/nodejs/test/session-event-types.test.ts index 213670216d..5c41f2216a 100644 --- a/nodejs/test/session-event-types.test.ts +++ b/nodejs/test/session-event-types.test.ts @@ -24,6 +24,7 @@ import type { PermissionRequest, PermissionRequestedData, PermissionRequestedEvent, + PermissionResponseCapability, ManagedSettingsResolvedData, ManagedSettingsResolvedEvent, ManagedSettingsResolvedSource, @@ -321,6 +322,7 @@ describe("Session event type exports (#1156)", () => { assertImportable(); assertImportable(); assertImportable(); + assertImportable(); assertImportable(); assertImportable(); diff --git a/nodejs/test/session-send-and-wait.test.ts b/nodejs/test/session-send-and-wait.test.ts index 8b6e390c4a..4ee2e8eebc 100644 --- a/nodejs/test/session-send-and-wait.test.ts +++ b/nodejs/test/session-send-and-wait.test.ts @@ -7,7 +7,10 @@ import type { MessageConnection } from "vscode-jsonrpc/node.js"; import { CopilotSession } from "../src/session.js"; import type { SessionEvent } from "../src/generated/session-events.js"; -function sessionEvent(type: "session.idle", data: Record = {}): SessionEvent { +function sessionEvent( + type: "session.idle", + data: { mode?: "interactive" | "plan" | "autopilot" } = {} +): SessionEvent { return { type, id: "00000000-0000-4000-8000-000000000001", @@ -106,6 +109,24 @@ describe("sendAndWait", () => { await expect(pending).resolves.toBeUndefined(); }); + it("ignores autopilot continuation idle events", async () => { + const { session, sendStarted, resolveSend } = controlledSession(); + const pending = session.sendAndWait({ prompt: "hi" }); + await sendStarted; + + session._dispatchEvent(sessionEvent("session.idle", { mode: "autopilot" })); + resolveSend(); + + const stateAfterContinuation = await Promise.race([ + pending.then(() => "settled"), + new Promise<"pending">((resolve) => setTimeout(() => resolve("pending"), 0)), + ]); + expect(stateAfterContinuation).toBe("pending"); + + session._dispatchEvent(sessionEvent("session.idle", { mode: "interactive" })); + await expect(pending).resolves.toBeUndefined(); + }); + it("preserves the send rejection when a session error arrives first", async () => { const { session, sendStarted, rejectSend } = controlledSession(); const pending = session.sendAndWait({ prompt: "hi" }); diff --git a/python/copilot/__init__.py b/python/copilot/__init__.py index 5f5bef88bd..608dacf253 100644 --- a/python/copilot/__init__.py +++ b/python/copilot/__init__.py @@ -100,6 +100,7 @@ PermissionDecisionOutcome, PermissionDecisionSource, PermissionDecisionSurface, + PermissionResponseCapability, ) from .generated.session_events import ( PermissionRequest, @@ -324,6 +325,7 @@ "PermissionDecisionOutcome", "PermissionDecisionSource", "PermissionDecisionSurface", + "PermissionResponseCapability", "PingResponse", "PostToolUseHandler", "PostToolUseFailureHandler", diff --git a/python/copilot/generated/rpc.py b/python/copilot/generated/rpc.py index ca782bd363..242e4041ed 100644 --- a/python/copilot/generated/rpc.py +++ b/python/copilot/generated/rpc.py @@ -3542,6 +3542,12 @@ class GitHubTelemetryClientInfo: copilot_plan: str | None = None """Copilot subscription plan, when known.""" + cpu_count: int | None = None + """Number of logical CPU cores on the host.""" + + cpu_model: str | None = None + """Distinct CPU model names for the host, comma-separated.""" + dev_device_id: str | None = None """Stable machine identifier for the device.""" @@ -3559,9 +3565,11 @@ def from_dict(obj: Any) -> 'GitHubTelemetryClientInfo': client_name = from_union([from_str, from_none], obj.get("client_name")) client_type = from_union([from_str, from_none], obj.get("client_type")) copilot_plan = from_union([from_str, from_none], obj.get("copilot_plan")) + cpu_count = from_union([from_int, from_none], obj.get("cpu_count")) + cpu_model = from_union([from_str, from_none], obj.get("cpu_model")) dev_device_id = from_union([from_str, from_none], obj.get("dev_device_id")) is_staff = from_union([from_bool, from_none], obj.get("is_staff")) - return GitHubTelemetryClientInfo(cli_version, node_version, os_arch, os_platform, os_version, client_name, client_type, copilot_plan, dev_device_id, is_staff) + return GitHubTelemetryClientInfo(cli_version, node_version, os_arch, os_platform, os_version, client_name, client_type, copilot_plan, cpu_count, cpu_model, dev_device_id, is_staff) def to_dict(self) -> dict: result: dict = {} @@ -3576,6 +3584,10 @@ def to_dict(self) -> dict: result["client_type"] = from_union([from_str, from_none], self.client_type) if self.copilot_plan is not None: result["copilot_plan"] = from_union([from_str, from_none], self.copilot_plan) + if self.cpu_count is not None: + result["cpu_count"] = from_union([from_int, from_none], self.cpu_count) + if self.cpu_model is not None: + result["cpu_model"] = from_union([from_str, from_none], self.cpu_model) if self.dev_device_id is not None: result["dev_device_id"] = from_union([from_str, from_none], self.dev_device_id) if self.is_staff is not None: @@ -7216,6 +7228,17 @@ class PermissionDecisionOutcome(Enum): AUTO_APPROVED = "auto_approved" PROMPTED_USER = "prompted_user" +# Experimental: this type is part of an experimental API and may change or be removed. +class PermissionResponseCapability(Enum): + """Whether the responding client could ask a user interactively, was running headlessly, or + had no response path. Omit when the client cannot determine this authoritatively. + + Response capability available to the client when it settled a permission request. + """ + HEADLESS = "headless" + INTERACTIVE = "interactive" + NONE = "none" + # Experimental: this type is part of an experimental API and may change or be removed. class PermissionDecisionSource(Enum): """Controlled reason or actor responsible for the response. @@ -7233,6 +7256,7 @@ class PermissionDecisionSurface(Enum): Client surface that submitted a permission response. """ + ACP = "acp" COPILOT_APP = "copilot_app" PROMPT_MODE = "prompt_mode" SDK = "sdk" @@ -20648,19 +20672,27 @@ class PermissionDecisionContext: surface: PermissionDecisionSurface """Client surface that submitted the response.""" + response_capability: PermissionResponseCapability | None = None + """Whether the responding client could ask a user interactively, was running headlessly, or + had no response path. Omit when the client cannot determine this authoritatively. + """ + @staticmethod def from_dict(obj: Any) -> 'PermissionDecisionContext': assert isinstance(obj, dict) outcome = PermissionDecisionOutcome(obj.get("outcome")) source = PermissionDecisionSource(obj.get("source")) surface = PermissionDecisionSurface(obj.get("surface")) - return PermissionDecisionContext(outcome, source, surface) + response_capability = from_union([PermissionResponseCapability, from_none], obj.get("responseCapability")) + return PermissionDecisionContext(outcome, source, surface, response_capability) def to_dict(self) -> dict: result: dict = {} result["outcome"] = to_enum(PermissionDecisionOutcome, self.outcome) result["source"] = to_enum(PermissionDecisionSource, self.source) result["surface"] = to_enum(PermissionDecisionSurface, self.surface) + if self.response_capability is not None: + result["responseCapability"] = from_union([lambda x: to_enum(PermissionResponseCapability, x), from_none], self.response_capability) return result # Experimental: this type is part of an experimental API and may change or be removed. @@ -35523,6 +35555,7 @@ class RPC: permission_paths_workspace_check_result: PermissionPathsWorkspaceCheckResult permission_prompt_shown_notification: PermissionPromptShownNotification permission_request_result: PermissionRequestResult + permission_response_capability: PermissionResponseCapability permission_rules_set: PermissionRulesSet permissions_configure_additional_content_exclusion_policy: PermissionsConfigureAdditionalContentExclusionPolicy permissions_configure_additional_content_exclusion_policy_rule: PermissionsConfigureAdditionalContentExclusionPolicyRule @@ -36702,6 +36735,7 @@ def from_dict(obj: Any) -> 'RPC': permission_paths_workspace_check_result = PermissionPathsWorkspaceCheckResult.from_dict(obj.get("PermissionPathsWorkspaceCheckResult")) permission_prompt_shown_notification = PermissionPromptShownNotification.from_dict(obj.get("PermissionPromptShownNotification")) permission_request_result = PermissionRequestResult.from_dict(obj.get("PermissionRequestResult")) + permission_response_capability = PermissionResponseCapability(obj.get("PermissionResponseCapability")) permission_rules_set = PermissionRulesSet.from_dict(obj.get("PermissionRulesSet")) permissions_configure_additional_content_exclusion_policy = PermissionsConfigureAdditionalContentExclusionPolicy.from_dict(obj.get("PermissionsConfigureAdditionalContentExclusionPolicy")) permissions_configure_additional_content_exclusion_policy_rule = PermissionsConfigureAdditionalContentExclusionPolicyRule.from_dict(obj.get("PermissionsConfigureAdditionalContentExclusionPolicyRule")) @@ -37257,7 +37291,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, account_all_users, account_get_all_users_result, account_get_current_auth_result, account_get_quota_request, account_get_quota_result, account_login_request, account_login_result, account_logout_request, account_logout_result, account_quota_snapshot, adaptive_thinking_support, agent_discovery_path, agent_discovery_path_list, agent_discovery_path_scope, agent_get_current_result, agent_info, agent_info_source, agent_list, agent_list_request, agent_registry_live_target_entry, agent_registry_live_target_entry_attention_kind, agent_registry_live_target_entry_kind, agent_registry_live_target_entry_last_terminal_event, agent_registry_live_target_entry_status, agent_registry_log_capture, agent_registry_log_capture_open_error_reason, agent_registry_spawn_error, agent_registry_spawn_permission_mode, agent_registry_spawn_registry_timeout, agent_registry_spawn_request, agent_registry_spawn_result, agent_registry_spawn_spawned, agent_registry_spawn_validation_error, agent_registry_spawn_validation_error_field, agent_registry_spawn_validation_error_reason, agent_reload_result, agents_discover_request, agent_select_request, agent_select_result, agent_set_prompt_request, agents_get_discovery_paths_request, api_key_auth_info, auth_identity, auth_info, auth_info_type, auth_validation_error, auth_validation_errors, built_in_model_catalog, built_in_model_catalog_entry, builtin_tool_descriptor, builtin_tool_format, builtin_tool_format_type, builtin_tool_input_schema, builtin_tool_input_schema_type, builtin_tool_safe_for_telemetry, builtin_tool_safe_telemetry_fields, cancel_user_requested_shell_command_result, canvas_action, canvas_action_invoke_request, canvas_action_invoke_result, canvas_close_request, canvas_host_context, canvas_host_context_capabilities, canvas_json_schema, canvas_list, canvas_list_open_result, canvas_open_request, canvas_provider_close_request, canvas_provider_invoke_action_request, canvas_provider_open_request, canvas_provider_open_result, canvas_provider_register_request, canvas_provider_unregister_request, canvas_session_context, capi_session_options, card_digest, card_digest_algorithm, card_digest_value, catalog_ai_skill_candidate, catalog_ai_skill_candidate_provenance, catalog_authentication_required_error, catalog_authentication_required_reason, catalog_candidate, catalog_candidate_kind, catalog_candidate_source, catalog_candidate_source_embedded, catalog_candidate_source_url, catalog_capability, catalog_capability_id, catalog_client_contract, catalog_contract_violation_error, catalog_contract_violation_reason, catalog_handle_rejected_error, catalog_handle_rejection_reason, catalog_handle_type, catalog_invalid_request_error, catalog_invalid_request_field, catalog_malformed_card_error, catalog_malformed_card_reason, catalog_mcp_server_candidate, catalog_mcp_server_candidate_provenance, catalog_mcp_server_installability, catalog_media_type, catalog_negotiated_contract, catalog_negotiation_refused_error, catalog_negotiation_refused_reason, catalog_network_failure_error, catalog_network_failure_reason, catalog_not_installable_error, catalog_not_installable_reason, catalog_policy_rejected_error, catalog_search_request, catalog_search_result, catalog_search_succeeded, catalog_unavailable_error, catalog_unavailable_reason, catalog_unavailable_transport_error, catalog_unavailable_transport_reason, catalog_unsafe_retrieval_error, catalog_unsafe_retrieval_reason, catalog_unsupported_kind_error, command_list, commands_finalize_invocation_effect_request, commands_finalize_invocation_effect_result, commands_handle_pending_command_request, commands_handle_pending_command_result, commands_invocation_effect_outcome, commands_invocation_origin, commands_invoke_request, commands_list_request, commands_respond_to_queued_command_request, commands_respond_to_queued_command_result, completions_get_trigger_characters_result, completions_request_request, completions_request_result, configure_session_extensions_params, connect_client_info, connected_remote_session_metadata, connected_remote_session_metadata_kind, connected_remote_session_metadata_repository, connect_remote_session_params, connect_request, connect_result, content_exclusion_check_paths_request, content_exclusion_check_paths_result, content_exclusion_path_check, content_filter_mode, context_heaviest_message, copilot_api_token_auth_info, copilot_user_response, copilot_user_response_endpoints, copilot_user_response_quota_snapshots, copilot_user_response_quota_snapshots_chat, copilot_user_response_quota_snapshots_completions, copilot_user_response_quota_snapshots_premium_interactions, current_model, current_tool_metadata, debug_collect_logs_collected_entry, debug_collect_logs_destination, debug_collect_logs_entry, debug_collect_logs_entry_kind, debug_collect_logs_include, debug_collect_logs_redaction, debug_collect_logs_request, debug_collect_logs_result, debug_collect_logs_result_kind, debug_collect_logs_skipped_entry, debug_collect_logs_source, discovered_canvas, discovered_extension, discovered_extension_mode, discovered_extension_plugin, discovered_extensions, discovered_extensions_disable_request, discovered_extensions_enable_request, discovered_extension_source, discovered_mcp_server, discovered_mcp_server_type, enqueue_command_params, enqueue_command_result, env_auth_info, event_log_read_request, event_log_release_interest_result, event_log_tail_result, event_log_types, events_agent_scope, events_cursor_status, events_read_direction, events_read_result, execute_command_params, execute_command_result, extension, extension_context_push_input, extension_launch_profile, extension_launch_provider_resolve_request, extension_launch_provider_resolve_result, extension_list, extensions_disable_request, extensions_enable_request, extension_source, extension_status, external_tool_result, external_tool_text_result_for_llm, external_tool_text_result_for_llm_binary_results_for_llm, external_tool_text_result_for_llm_binary_results_for_llm_type, external_tool_text_result_for_llm_content, external_tool_text_result_for_llm_content_audio, external_tool_text_result_for_llm_content_image, external_tool_text_result_for_llm_content_resource, external_tool_text_result_for_llm_content_resource_details, external_tool_text_result_for_llm_content_resource_link, external_tool_text_result_for_llm_content_resource_link_icon, external_tool_text_result_for_llm_content_resource_link_icon_theme, external_tool_text_result_for_llm_content_shell_exit, external_tool_text_result_for_llm_content_terminal, external_tool_text_result_for_llm_content_text, factory_abort_request, factory_ack_result, factory_agent_options, factory_agent_request, factory_agent_result, factory_agent_summary, factory_cancel_request, factory_current_phase, factory_declared_limits, factory_durable_operation, factory_execute_request, factory_execute_result, factory_get_run_progress_request, factory_get_run_request, factory_journal_get_request, factory_journal_get_result, factory_journal_put_request, factory_list_runs_request, factory_list_runs_result, factory_log_line, factory_log_line_kind, factory_log_request, factory_phase_observation, factory_phase_status, factory_progress_line, factory_progress_page, factory_resume_request, factory_resume_result, factory_run_consumed, factory_run_detail, factory_run_failure, factory_run_failure_kind, factory_run_limits, factory_run_request, factory_run_result, factory_run_status, factory_run_summary, factory_run_terminal, filter_mapping, fleet_start_request, fleet_start_result, folder_trust_add_params, folder_trust_check_params, folder_trust_check_result, gh_cli_auth_info, git_hub_telemetry_client_info, git_hub_telemetry_event, git_hub_telemetry_notification, git_hub_token_acquire_reason, git_hub_token_acquire_request, git_hub_token_acquire_result, handle_pending_tool_call_request, handle_pending_tool_call_result, history_abort_manual_compaction_result, history_cancel_background_compaction_result, history_clear_context_request, history_clear_context_result, history_compact_context_window, history_compact_request, history_compact_result, history_file_restore_skip_reason, history_list_rewind_points_result, history_preview_rewind_request, history_preview_rewind_result, history_rewind_change_type, history_rewind_file_preview, history_rewind_mode, history_rewind_outcome, history_rewind_point, history_rewind_request, history_rewind_result, history_rewind_unavailable_reason, history_skipped_file_restore, history_summarize_for_handoff_result, history_truncate_request, history_truncate_result, hmac_auth_info, hook_invoke_request, hook_invoke_response, hook_type, installed_plugin, installed_plugin_info, installed_plugin_source, installed_plugin_source_git_hub, installed_plugin_source_local, installed_plugin_source_url, instruction_discovery_path, instruction_discovery_path_kind, instruction_discovery_path_list, instruction_discovery_path_location, instructions_discover_request, instructions_get_discovery_paths_request, instructions_get_sources_result, instruction_source, instruction_source_location, instruction_source_type, interrupt_main_turn_request, interrupt_main_turn_result, llm_inference_headers, llm_inference_http_request_chunk_request, llm_inference_http_request_chunk_result, llm_inference_http_request_start_request, llm_inference_http_request_start_result, llm_inference_http_request_start_transport, llm_inference_http_response_chunk_error, llm_inference_http_response_chunk_request, llm_inference_http_response_chunk_result, llm_inference_http_response_start_request, llm_inference_http_response_start_result, llm_inference_set_provider_result, local_session_metadata_value, log_request, log_result, lsp_initialize_request, managed_settings_read_result, marketplace_add_result, marketplace_browse_result, marketplace_info, marketplace_list_result, marketplace_plugin_info, marketplace_refresh_entry, marketplace_refresh_result, marketplace_remove_result, mcp_allowed_server, mcp_apps_call_tool_request, mcp_apps_diagnose_capability, mcp_apps_diagnose_request, mcp_apps_diagnose_result, mcp_apps_diagnose_server, mcp_apps_host_context, mcp_apps_host_context_details, mcp_apps_host_context_details_available_display_mode, mcp_apps_host_context_details_display_mode, mcp_apps_host_context_details_platform, mcp_apps_host_context_details_theme, mcp_apps_list_tools_request, mcp_apps_list_tools_result, mcp_apps_read_resource_request, mcp_apps_read_resource_result, mcp_apps_resource_content, mcp_apps_set_host_context_details, mcp_apps_set_host_context_details_available_display_mode, mcp_apps_set_host_context_details_display_mode, mcp_apps_set_host_context_details_platform, mcp_apps_set_host_context_details_theme, mcp_apps_set_host_context_request, mcp_cancel_sampling_execution_params, mcp_cancel_sampling_execution_result, mcp_config_add_request, mcp_config_disable_request, mcp_config_enable_request, mcp_config_list, mcp_config_remove_request, mcp_config_update_request, mcp_configure_git_hub_request, mcp_configure_git_hub_result, mcp_disable_request, mcp_discover_request, mcp_discover_result, mcp_elicitation_form_mode, mcp_enable_request, mcp_execute_sampling_params, mcp_execute_sampling_request, mcp_execute_sampling_result, mcp_failed_server, mcp_filtered_server, mcp_headers_handle_pending_headers_refresh_request, mcp_headers_handle_pending_headers_refresh_request_request, mcp_headers_handle_pending_headers_refresh_request_result, mcp_host_state, mcp_install_plan, mcp_is_server_running_request, mcp_is_server_running_result, mcp_list_tools_request, mcp_list_tools_result, mcp_oauth_authentication_state_changed_request, mcp_oauth_handle_pending_request, mcp_oauth_handle_pending_result, mcp_oauth_login_grant_type, mcp_oauth_login_request, mcp_oauth_login_result, mcp_oauth_pending_request_response, mcp_oauth_probe_needs_auth_reason, mcp_oauth_probe_request, mcp_oauth_probe_result, mcp_oauth_respond_request, mcp_oauth_respond_result, mcp_plan_configuration_change, mcp_plan_configuration_operation, mcp_plan_enum_value_type, mcp_plan_install_planned, mcp_plan_install_request, mcp_plan_install_result, mcp_plan_install_source, mcp_plan_install_source_candidate, mcp_plan_install_source_candidate_kind, mcp_plan_install_source_card, mcp_plan_install_source_card_kind, mcp_plan_package_install_method, mcp_plan_package_transport, mcp_plan_policy_decision, mcp_plan_policy_result, mcp_plan_policy_source, mcp_plan_provenance, mcp_plan_remote_install_method, mcp_plan_remote_transport, mcp_plan_required_value, mcp_plan_required_value_enum, mcp_plan_required_value_enum_kind, mcp_plan_required_value_scalar, mcp_plan_required_value_scalar_kind, mcp_plan_resource_identity, mcp_plan_scalar_value_type, mcp_plan_scope, mcp_plan_secret_placeholder, mcp_plan_secret_reference, mcp_plan_target, mcp_plan_transport_choice, mcp_plan_transport_choice_package, mcp_plan_transport_choice_remote, mcp_plan_value_category, mcp_register_external_client_request, mcp_reload_config, mcp_reload_with_config_request, mcp_remove_git_hub_result, mcp_resource, mcp_resource_annotations, mcp_resource_content, mcp_resource_icon, mcp_resources_list_request, mcp_resources_list_result, mcp_resources_list_templates_request, mcp_resources_list_templates_result, mcp_resources_read_request, mcp_resources_read_result, mcp_resource_template, mcp_restart_server_request, mcp_safe_for_telemetry, mcp_safe_for_telemetry_fields, mcp_sampling_execution_action, mcp_sampling_execution_result, mcp_serializable_server_config, mcp_server, mcp_server_auth_config, mcp_server_auth_config_redirect_port, mcp_server_card_embedded, mcp_server_card_embedded_kind, mcp_server_card_media_type, mcp_server_card_reference, mcp_server_card_url, mcp_server_card_url_kind, mcp_server_config, mcp_server_config_defer_tools, mcp_server_config_http, mcp_server_config_http_oauth_grant_type, mcp_server_config_http_type, mcp_server_config_memory, mcp_server_config_memory_type, mcp_server_config_stdio, mcp_server_config_stdio_type, mcp_server_failure_info, mcp_server_list, mcp_server_needs_auth_info, mcp_set_env_value_mode_details, mcp_set_env_value_mode_params, mcp_set_env_value_mode_result, mcp_start_server_request, mcp_start_servers_result, mcp_stop_server_request, mcp_task_metadata, mcp_tools, mcp_tool_ui, mcp_tool_ui_visibility, mcp_unregister_external_client_request, memory_configuration, metadata_context_attribution_result, metadata_context_heaviest_messages_request, metadata_context_heaviest_messages_result, metadata_context_info_request, metadata_context_info_result, metadata_is_processing_result, metadata_recompute_context_tokens_request, metadata_recompute_context_tokens_result, metadata_record_context_change_request, metadata_record_context_change_result, metadata_set_working_directory_request, metadata_set_working_directory_result, metadata_snapshot_current_mode, metadata_snapshot_remote_metadata, metadata_snapshot_remote_metadata_repository, metadata_snapshot_remote_metadata_task_type, model, model_apply_startup_overlay_request, model_billing, model_billing_promo, model_billing_token_prices, model_billing_token_prices_long_context, model_capabilities, model_capabilities_limits, model_capabilities_limits_vision, model_capabilities_override, model_capabilities_override_limits, model_capabilities_override_limits_vision, model_capabilities_override_supports, model_capabilities_supports, model_list, model_list_request, model_message, model_picker_category, model_picker_persistence_request, model_picker_price_category, model_picker_settings_context, model_policy, model_policy_state, model_set_reasoning_effort_request, model_set_reasoning_effort_result, models_list_request, model_switch_confirmation, model_switch_to_request, model_switch_to_result, model_warning_text, mode_set_request, mode_set_result, move_mcp_loading_to_background_result, named_provider_config, name_get_result, name_set_auto_request, name_set_auto_result, name_set_request, open_canvas_instance, options_update_additional_content_exclusion_policy, options_update_additional_content_exclusion_policy_rule, options_update_additional_content_exclusion_policy_rule_source, options_update_additional_content_exclusion_policy_scope, options_update_context_tier, options_update_env_value_mode, options_update_reasoning_summary, options_update_tool_filter_precedence, pending_permission_request, pending_permission_request_list, permission_decision, permission_decision_approved, permission_decision_approved_for_location, permission_decision_approved_for_session, permission_decision_approve_for_location, permission_decision_approve_for_location_approval, permission_decision_approve_for_location_approval_commands, permission_decision_approve_for_location_approval_custom_tool, permission_decision_approve_for_location_approval_extension_env_access, permission_decision_approve_for_location_approval_extension_management, permission_decision_approve_for_location_approval_extension_permission_access, permission_decision_approve_for_location_approval_factory, permission_decision_approve_for_location_approval_mcp, permission_decision_approve_for_location_approval_mcp_sampling, permission_decision_approve_for_location_approval_memory, permission_decision_approve_for_location_approval_read, permission_decision_approve_for_location_approval_write, permission_decision_approve_for_session, permission_decision_approve_for_session_approval, permission_decision_approve_for_session_approval_commands, permission_decision_approve_for_session_approval_custom_tool, permission_decision_approve_for_session_approval_extension_env_access, permission_decision_approve_for_session_approval_extension_management, permission_decision_approve_for_session_approval_extension_permission_access, permission_decision_approve_for_session_approval_factory, permission_decision_approve_for_session_approval_mcp, permission_decision_approve_for_session_approval_mcp_sampling, permission_decision_approve_for_session_approval_memory, permission_decision_approve_for_session_approval_read, permission_decision_approve_for_session_approval_write, permission_decision_approve_once, permission_decision_approve_permanently, permission_decision_cancelled, permission_decision_context, permission_decision_denied_by_content_exclusion_policy, permission_decision_denied_by_permission_request_hook, permission_decision_denied_by_rules, permission_decision_denied_interactively_by_user, permission_decision_denied_no_approval_rule_and_could_not_request_from_user, permission_decision_outcome, permission_decision_reject, permission_decision_request, permission_decision_source, permission_decision_surface, permission_decision_user_not_available, permission_location_add_tool_approval_params, permission_location_apply_params, permission_location_apply_result, permission_location_resolve_params, permission_location_resolve_result, permission_location_type, permission_mode_source, permission_paths_add_params, permission_paths_allowed_check_params, permission_paths_allowed_check_result, permission_paths_config, permission_paths_list, permission_paths_update_primary_params, permission_paths_workspace_check_params, permission_paths_workspace_check_result, permission_prompt_shown_notification, permission_request_result, permission_rules_set, permissions_configure_additional_content_exclusion_policy, permissions_configure_additional_content_exclusion_policy_rule, permissions_configure_additional_content_exclusion_policy_rule_source, permissions_configure_additional_content_exclusion_policy_scope, permissions_configure_params, permissions_configure_result, permissions_folder_trust_add_trusted_result, permissions_get_mode_request, permissions_get_mode_result, permissions_locations_add_tool_approval_details, permissions_locations_add_tool_approval_details_commands, permissions_locations_add_tool_approval_details_custom_tool, permissions_locations_add_tool_approval_details_extension_env_access, permissions_locations_add_tool_approval_details_extension_management, permissions_locations_add_tool_approval_details_extension_permission_access, permissions_locations_add_tool_approval_details_factory, permissions_locations_add_tool_approval_details_mcp, permissions_locations_add_tool_approval_details_mcp_sampling, permissions_locations_add_tool_approval_details_memory, permissions_locations_add_tool_approval_details_read, permissions_locations_add_tool_approval_details_write, permissions_locations_add_tool_approval_result, permissions_modify_rules_params, permissions_modify_rules_result, permissions_modify_rules_scope, permissions_notify_prompt_shown_result, permissions_paths_add_result, permissions_paths_list_request, permissions_paths_update_primary_result, permissions_pending_requests_request, permissions_reset_session_approvals_request, permissions_reset_session_approvals_result, permissions_set_approve_all_request, permissions_set_approve_all_result, permissions_set_approve_all_source, permissions_set_mode_request, permissions_set_mode_result, permissions_set_required_request, permissions_set_required_result, permissions_urls_set_unrestricted_mode_result, permission_urls_config, permission_urls_set_unrestricted_mode_params, ping_request, ping_result, plan_read_result, plan_read_sql_todos_result, plan_read_sql_todos_with_dependencies_result, plan_sql_todo_dependency, plan_sql_todos_row, plan_update_request, plugin, plugin_install_result, plugin_list, plugin_list_result, plugins_builtin_set_request, plugins_disable_request, plugins_enable_request, plugins_install_request, plugins_marketplaces_add_request, plugins_marketplaces_browse_request, plugins_marketplaces_refresh_request, plugins_marketplaces_remove_request, plugins_reload_request, plugins_uninstall_request, plugins_update_request, plugin_update_all_entry, plugin_update_all_result, plugin_update_result, protocol_external_tool_defer, protocol_external_tool_definition, provider_add_request, provider_add_result, provider_config, provider_config_azure, provider_config_transport, provider_config_type, provider_config_wire_api, provider_endpoint, provider_endpoint_transport, provider_endpoint_type, provider_endpoint_wire_api, provider_get_endpoint_request, provider_model_config, provider_session_token, provider_token_acquire_request, provider_token_acquire_result, push_attachment, push_attachment_blob, push_attachment_directory, push_attachment_file, push_attachment_file_line_range, push_attachment_git_hub_actions_job, push_attachment_git_hub_commit, push_attachment_git_hub_file, push_attachment_git_hub_file_diff, push_attachment_git_hub_file_diff_side, push_attachment_git_hub_reference, push_attachment_git_hub_reference_type, push_attachment_git_hub_release, push_attachment_git_hub_repository, push_attachment_git_hub_snippet, push_attachment_git_hub_tree_comparison, push_attachment_git_hub_tree_comparison_side, push_attachment_git_hub_url, push_attachment_selection, push_attachment_selection_details, push_attachment_selection_details_end, push_attachment_selection_details_start, push_git_hub_repo_ref, queue_begin_deferred_idle_drain_request, queue_begin_deferred_idle_drain_result, queue_consume_system_notifications_request, queued_command_handled, queued_command_not_handled, queued_command_result, queue_defer_session_idle_request, queue_duplicate_at_request, queue_duplicate_at_result, queue_enqueue_resume_pending_result, queue_finish_deferred_idle_drain_request, queue_finish_deferred_idle_drain_result, queue_has_pending_result, queue_insert_at_request, queue_insert_at_result, queue_insert_message, queue_move_item_request, queue_move_item_result, queue_pending_items, queue_pending_items_kind, queue_pending_items_result, queue_remove_at_request, queue_remove_at_result, queue_remove_most_recent_result, queue_send_now_request, queue_send_now_result, queue_set_drain_paused_request, queue_snapshot_result, queue_update_text_request, queue_update_text_result, register_event_interest_params, register_event_interest_result, register_extension_tools_params, register_extension_tools_result, release_event_interest_params, remote_control_config, remote_control_config_existing_mc_session, remote_control_status, remote_control_status_active, remote_control_status_connecting, remote_control_status_error, remote_control_status_off, remote_control_status_result, remote_control_stop_result, remote_control_transfer_result, remote_enable_request, remote_enable_result, remote_notify_steerable_changed_request, remote_notify_steerable_changed_result, remote_session_connection_result, remote_session_host_status, remote_session_metadata_repository, remote_session_metadata_task_type, remote_session_metadata_value, remote_session_mode, remote_session_repository, run_options, sandbox_config, sandbox_config_auth, sandbox_config_source, sandbox_config_user_policy, sandbox_config_user_policy_experimental, sandbox_config_user_policy_experimental_seatbelt, sandbox_config_user_policy_filesystem, sandbox_config_user_policy_network, sandbox_config_user_policy_network_proxy, sandbox_config_user_policy_seatbelt, schedule_add_at_request, schedule_add_cron_request, schedule_add_request, schedule_add_result, schedule_add_self_paced_request, schedule_entry, schedule_has_self_paced_result, schedule_list, schedule_rearm_self_paced_request, schedule_stop_request, schedule_stop_result, secrets_add_filter_values_request, secrets_add_filter_values_result, send_agent_mode, send_attachments_to_message_params, send_message_item, send_messages_request, send_messages_result, send_mode, send_request, send_result, send_system_notification_request, server_agent_list, server_instruction_source_list, server_skill, server_skill_list, session_activity, session_agent_list_request, session_auth_login_request, session_auth_logout_user_request, session_auth_status, session_auth_switch_request, session_bulk_delete_result, session_cancel_all_background_agents_result, session_capability, session_commands_list_request, session_completion_item, session_context, session_context_host_type, session_enrich_metadata_result, session_fs_append_file_request, session_fs_error, session_fs_error_code, session_fs_exists_request, session_fs_exists_result, session_fs_mkdir_request, session_fs_readdir_request, session_fs_readdir_result, session_fs_readdir_with_types_entry, session_fs_readdir_with_types_entry_type, session_fs_readdir_with_types_request, session_fs_readdir_with_types_result, session_fs_read_file_request, session_fs_read_file_result, session_fs_rename_request, session_fs_rm_request, session_fs_set_provider_capabilities, session_fs_set_provider_conventions, session_fs_set_provider_request, session_fs_set_provider_result, session_fs_sqlite_exists_request, session_fs_sqlite_exists_result, session_fs_sqlite_query_request, session_fs_sqlite_query_result, session_fs_sqlite_query_type, session_fs_sqlite_transaction_error, session_fs_sqlite_transaction_error_class, session_fs_sqlite_transaction_request, session_fs_sqlite_transaction_result, session_fs_sqlite_transaction_statement, session_fs_stat_request, session_fs_stat_result, session_fs_write_file_request, session_git_hub_auth_get_all_auth_available_result, session_git_hub_auth_logout_result, session_git_hub_auth_logout_user_result, session_history_compact_request, session_installed_plugin, session_installed_plugin_source, session_installed_plugin_source_git_hub, session_installed_plugin_source_local, session_installed_plugin_source_url, session_limit_prediction_baseline_data, session_limit_prediction_client_type, session_limit_prediction_details, session_limit_prediction_predict_request, session_limit_prediction_request, session_limit_prediction_result, session_limit_prediction_source, session_limit_prediction_tier, session_limit_prediction_tier_option, session_limit_prediction_unavailable_reason, session_list, session_list_entry, session_list_filter, session_load_deferred_repo_hooks_result, session_log_level, session_managed_permissions, session_managed_settings, session_mcp_apps_call_tool_result, session_metadata_snapshot, session_mode, session_model_list, session_model_list_request, session_model_price_category, session_open_options, session_open_options_additional_content_exclusion_policy, session_open_options_additional_content_exclusion_policy_rule, session_open_options_additional_content_exclusion_policy_rule_source, session_open_options_additional_content_exclusion_policy_scope, session_open_options_env_value_mode, session_open_options_reasoning_summary, session_open_params, session_open_result, session_plugins_reload_request, session_provider_get_endpoint_request, session_prune_result, sessions_bulk_delete_request, sessions_check_in_use_request, sessions_check_in_use_result, sessions_close_request, sessions_close_result, sessions_delete_request, sessions_enrich_metadata_request, session_set_credentials_params, session_set_credentials_result, session_settings_built_in_tool_availability_snapshot, session_settings_evaluate_predicate_request, session_settings_evaluate_predicate_result, session_settings_job_snapshot, session_settings_model_snapshot, session_settings_online_evaluation_snapshot, session_settings_predicate_name, session_settings_repo_snapshot, session_settings_snapshot, session_settings_validation_snapshot, sessions_find_by_prefix_request, sessions_find_by_prefix_result, sessions_find_by_task_id_request, sessions_find_by_task_id_result, sessions_fork_request, sessions_fork_result, sessions_get_board_entry_count_request, sessions_get_board_entry_count_result, sessions_get_event_file_path_request, sessions_get_event_file_path_result, sessions_get_last_for_context_request, sessions_get_last_for_context_result, sessions_get_metadata_request, sessions_get_metadata_result, sessions_get_persisted_remote_steerable_request, sessions_get_persisted_remote_steerable_result, session_sizes, sessions_list_non_empty_session_ids_request, sessions_list_non_empty_session_ids_result, sessions_list_request, sessions_load_deferred_repo_hooks_request, sessions_open_attach, sessions_open_cloud, sessions_open_create, sessions_open_handoff, sessions_open_handoff_task_type, sessions_open_progress, sessions_open_progress_status, sessions_open_progress_step, sessions_open_remote, sessions_open_resume, sessions_open_resume_last, sessions_open_status, session_source, sessions_prune_old_request, sessions_register_extension_tools_on_session_options, sessions_release_lock_request, sessions_release_lock_result, sessions_reload_plugin_hooks_request, sessions_reload_plugin_hooks_result, sessions_save_request, sessions_save_result, sessions_set_additional_plugins_request, sessions_set_additional_plugins_result, sessions_set_remote_control_steering_request, sessions_start_remote_control_request, sessions_stop_remote_control_request, sessions_transfer_remote_control_request, session_telemetry_engagement, session_update_options_params, session_update_options_result, session_visibility_status, session_working_directory_context, session_working_directory_context_host_type, settable_auth_info, settable_token_auth_info, shell_cancel_user_requested_request, shell_credentials, shell_exec_request, shell_exec_result, shell_execute_user_requested_request, shell_init_profile, shell_init_script, shell_init_script_shell, shell_kill_request, shell_kill_result, shell_kill_signal, shell_options, shutdown_request, skill, skill_discovery_path, skill_discovery_path_list, skill_discovery_scope, skill_list, skills_config_set_disabled_skills_request, skills_config_set_skill_disabled_request, skills_disable_request, skills_discover_request, skills_enable_request, skills_get_discovery_paths_request, skills_get_invoked_result, skills_invoked_skill, skills_load_diagnostics, slash_command_add_timeline_entry_result, slash_command_agent_prompt_result, slash_command_completed_result, slash_command_info, slash_command_input, slash_command_input_choice, slash_command_input_completion, slash_command_invocation_result, slash_command_kind, slash_command_model_picker_dialog, slash_command_select_subcommand_option, slash_command_select_subcommand_result, slash_command_set_model_result, slash_command_set_plan_model_result, slash_command_show_dialog_result, slash_command_text_result, slash_command_timeline_entry, subagent_settings_entry, subagent_settings_entry_context_tier, task_agent_info, task_agent_progress, task_complete_data, task_completion_decision, task_execution_mode, task_info, task_list, task_progress_line, tasks_cancel_request, tasks_cancel_result, tasks_get_current_promotable_result, tasks_get_progress_request, tasks_get_progress_result, task_shell_info, task_shell_info_attachment_mode, task_shell_progress, tasks_promote_current_to_background_result, tasks_promote_to_background_request, tasks_promote_to_background_result, tasks_refresh_result, tasks_remove_request, tasks_remove_result, tasks_send_message_request, tasks_send_message_result, tasks_start_agent_request, tasks_start_agent_result, task_status, tasks_wait_for_pending_result, telemetry_set_feature_overrides_request, token_auth_info, token_provider_auth_info, tool, tool_list, tool_result, tool_result_expanded, tool_result_new_message, tool_result_type, tools_execute_request, tools_get_builtin_descriptors_request, tools_get_builtin_descriptors_result, tools_get_current_metadata_result, tools_initialize_and_validate_result, tools_list_request, tools_set_request, tools_set_result, tools_shell_descriptor_config, tools_task_complete_event_data_request, tools_update_subagent_settings_result, ui_auto_mode_switch_response, ui_elicitation_array_any_of_field, ui_elicitation_array_any_of_field_items, ui_elicitation_array_any_of_field_items_any_of, ui_elicitation_array_enum_field, ui_elicitation_array_enum_field_items, ui_elicitation_field_value, ui_elicitation_request, ui_elicitation_response, ui_elicitation_response_action, ui_elicitation_response_content, ui_elicitation_result, ui_elicitation_schema, ui_elicitation_schema_property, ui_elicitation_schema_property_boolean, ui_elicitation_schema_property_number, ui_elicitation_schema_property_number_type, ui_elicitation_schema_property_string, ui_elicitation_schema_property_string_format, ui_elicitation_string_enum_field, ui_elicitation_string_one_of_field, ui_elicitation_string_one_of_field_one_of, ui_ephemeral_query_request, ui_ephemeral_query_result, ui_exit_plan_mode_action, ui_exit_plan_mode_response, ui_handle_pending_auto_mode_switch_request, ui_handle_pending_elicitation_request, ui_handle_pending_exit_plan_mode_request, ui_handle_pending_result, ui_handle_pending_sampling_request, ui_handle_pending_sampling_response, ui_handle_pending_session_limits_exhausted_request, ui_handle_pending_user_input_request, ui_register_direct_auto_mode_switch_handler_result, ui_session_limits_exhausted_response, ui_session_limits_exhausted_response_action, ui_unregister_direct_auto_mode_switch_handler_request, ui_unregister_direct_auto_mode_switch_handler_result, ui_user_input_response, update_subagent_settings_request, usage_get_metrics_result, usage_metrics_agent_metric, usage_metrics_code_changes, usage_metrics_model_metric, usage_metrics_model_metric_requests, usage_metrics_model_metric_token_detail, usage_metrics_model_metric_usage, usage_metrics_token_detail, user_auth_info, user_requested_shell_command_result, user_setting_metadata, user_settings_get_result, user_settings_set_request, user_settings_set_result, visibility_get_result, visibility_set_request, visibility_set_result, workspace_diff_file_change, workspace_diff_file_change_type, workspace_diff_mode, workspace_diff_result, workspaces_add_summary_request, workspaces_add_summary_result, workspaces_autopilot_objective_exists_result, workspaces_checkpoints, workspaces_create_file_request, workspaces_delete_autopilot_objective_result, workspaces_diff_request, workspaces_ensure_request, workspaces_get_workspace_result, workspaces_list_checkpoints_result, workspaces_list_files_result, workspaces_read_autopilot_objective_result, workspaces_read_checkpoint_request, workspaces_read_checkpoint_result, workspaces_read_file_request, workspaces_read_file_result, workspaces_save_large_paste_request, workspaces_save_large_paste_result, workspaces_truncate_summaries_request, workspace_summary_host_type, workspaces_update_metadata_request, workspaces_workspace_details_host_type, workspaces_write_autopilot_objective_request, workspaces_write_autopilot_objective_result, session_auth_info_result, session_context_attribution, session_context_info, subagent_settings, task_progress, workspace_summary) + return RPC(abort_request, abort_result, account_all_users, account_get_all_users_result, account_get_current_auth_result, account_get_quota_request, account_get_quota_result, account_login_request, account_login_result, account_logout_request, account_logout_result, account_quota_snapshot, adaptive_thinking_support, agent_discovery_path, agent_discovery_path_list, agent_discovery_path_scope, agent_get_current_result, agent_info, agent_info_source, agent_list, agent_list_request, agent_registry_live_target_entry, agent_registry_live_target_entry_attention_kind, agent_registry_live_target_entry_kind, agent_registry_live_target_entry_last_terminal_event, agent_registry_live_target_entry_status, agent_registry_log_capture, agent_registry_log_capture_open_error_reason, agent_registry_spawn_error, agent_registry_spawn_permission_mode, agent_registry_spawn_registry_timeout, agent_registry_spawn_request, agent_registry_spawn_result, agent_registry_spawn_spawned, agent_registry_spawn_validation_error, agent_registry_spawn_validation_error_field, agent_registry_spawn_validation_error_reason, agent_reload_result, agents_discover_request, agent_select_request, agent_select_result, agent_set_prompt_request, agents_get_discovery_paths_request, api_key_auth_info, auth_identity, auth_info, auth_info_type, auth_validation_error, auth_validation_errors, built_in_model_catalog, built_in_model_catalog_entry, builtin_tool_descriptor, builtin_tool_format, builtin_tool_format_type, builtin_tool_input_schema, builtin_tool_input_schema_type, builtin_tool_safe_for_telemetry, builtin_tool_safe_telemetry_fields, cancel_user_requested_shell_command_result, canvas_action, canvas_action_invoke_request, canvas_action_invoke_result, canvas_close_request, canvas_host_context, canvas_host_context_capabilities, canvas_json_schema, canvas_list, canvas_list_open_result, canvas_open_request, canvas_provider_close_request, canvas_provider_invoke_action_request, canvas_provider_open_request, canvas_provider_open_result, canvas_provider_register_request, canvas_provider_unregister_request, canvas_session_context, capi_session_options, card_digest, card_digest_algorithm, card_digest_value, catalog_ai_skill_candidate, catalog_ai_skill_candidate_provenance, catalog_authentication_required_error, catalog_authentication_required_reason, catalog_candidate, catalog_candidate_kind, catalog_candidate_source, catalog_candidate_source_embedded, catalog_candidate_source_url, catalog_capability, catalog_capability_id, catalog_client_contract, catalog_contract_violation_error, catalog_contract_violation_reason, catalog_handle_rejected_error, catalog_handle_rejection_reason, catalog_handle_type, catalog_invalid_request_error, catalog_invalid_request_field, catalog_malformed_card_error, catalog_malformed_card_reason, catalog_mcp_server_candidate, catalog_mcp_server_candidate_provenance, catalog_mcp_server_installability, catalog_media_type, catalog_negotiated_contract, catalog_negotiation_refused_error, catalog_negotiation_refused_reason, catalog_network_failure_error, catalog_network_failure_reason, catalog_not_installable_error, catalog_not_installable_reason, catalog_policy_rejected_error, catalog_search_request, catalog_search_result, catalog_search_succeeded, catalog_unavailable_error, catalog_unavailable_reason, catalog_unavailable_transport_error, catalog_unavailable_transport_reason, catalog_unsafe_retrieval_error, catalog_unsafe_retrieval_reason, catalog_unsupported_kind_error, command_list, commands_finalize_invocation_effect_request, commands_finalize_invocation_effect_result, commands_handle_pending_command_request, commands_handle_pending_command_result, commands_invocation_effect_outcome, commands_invocation_origin, commands_invoke_request, commands_list_request, commands_respond_to_queued_command_request, commands_respond_to_queued_command_result, completions_get_trigger_characters_result, completions_request_request, completions_request_result, configure_session_extensions_params, connect_client_info, connected_remote_session_metadata, connected_remote_session_metadata_kind, connected_remote_session_metadata_repository, connect_remote_session_params, connect_request, connect_result, content_exclusion_check_paths_request, content_exclusion_check_paths_result, content_exclusion_path_check, content_filter_mode, context_heaviest_message, copilot_api_token_auth_info, copilot_user_response, copilot_user_response_endpoints, copilot_user_response_quota_snapshots, copilot_user_response_quota_snapshots_chat, copilot_user_response_quota_snapshots_completions, copilot_user_response_quota_snapshots_premium_interactions, current_model, current_tool_metadata, debug_collect_logs_collected_entry, debug_collect_logs_destination, debug_collect_logs_entry, debug_collect_logs_entry_kind, debug_collect_logs_include, debug_collect_logs_redaction, debug_collect_logs_request, debug_collect_logs_result, debug_collect_logs_result_kind, debug_collect_logs_skipped_entry, debug_collect_logs_source, discovered_canvas, discovered_extension, discovered_extension_mode, discovered_extension_plugin, discovered_extensions, discovered_extensions_disable_request, discovered_extensions_enable_request, discovered_extension_source, discovered_mcp_server, discovered_mcp_server_type, enqueue_command_params, enqueue_command_result, env_auth_info, event_log_read_request, event_log_release_interest_result, event_log_tail_result, event_log_types, events_agent_scope, events_cursor_status, events_read_direction, events_read_result, execute_command_params, execute_command_result, extension, extension_context_push_input, extension_launch_profile, extension_launch_provider_resolve_request, extension_launch_provider_resolve_result, extension_list, extensions_disable_request, extensions_enable_request, extension_source, extension_status, external_tool_result, external_tool_text_result_for_llm, external_tool_text_result_for_llm_binary_results_for_llm, external_tool_text_result_for_llm_binary_results_for_llm_type, external_tool_text_result_for_llm_content, external_tool_text_result_for_llm_content_audio, external_tool_text_result_for_llm_content_image, external_tool_text_result_for_llm_content_resource, external_tool_text_result_for_llm_content_resource_details, external_tool_text_result_for_llm_content_resource_link, external_tool_text_result_for_llm_content_resource_link_icon, external_tool_text_result_for_llm_content_resource_link_icon_theme, external_tool_text_result_for_llm_content_shell_exit, external_tool_text_result_for_llm_content_terminal, external_tool_text_result_for_llm_content_text, factory_abort_request, factory_ack_result, factory_agent_options, factory_agent_request, factory_agent_result, factory_agent_summary, factory_cancel_request, factory_current_phase, factory_declared_limits, factory_durable_operation, factory_execute_request, factory_execute_result, factory_get_run_progress_request, factory_get_run_request, factory_journal_get_request, factory_journal_get_result, factory_journal_put_request, factory_list_runs_request, factory_list_runs_result, factory_log_line, factory_log_line_kind, factory_log_request, factory_phase_observation, factory_phase_status, factory_progress_line, factory_progress_page, factory_resume_request, factory_resume_result, factory_run_consumed, factory_run_detail, factory_run_failure, factory_run_failure_kind, factory_run_limits, factory_run_request, factory_run_result, factory_run_status, factory_run_summary, factory_run_terminal, filter_mapping, fleet_start_request, fleet_start_result, folder_trust_add_params, folder_trust_check_params, folder_trust_check_result, gh_cli_auth_info, git_hub_telemetry_client_info, git_hub_telemetry_event, git_hub_telemetry_notification, git_hub_token_acquire_reason, git_hub_token_acquire_request, git_hub_token_acquire_result, handle_pending_tool_call_request, handle_pending_tool_call_result, history_abort_manual_compaction_result, history_cancel_background_compaction_result, history_clear_context_request, history_clear_context_result, history_compact_context_window, history_compact_request, history_compact_result, history_file_restore_skip_reason, history_list_rewind_points_result, history_preview_rewind_request, history_preview_rewind_result, history_rewind_change_type, history_rewind_file_preview, history_rewind_mode, history_rewind_outcome, history_rewind_point, history_rewind_request, history_rewind_result, history_rewind_unavailable_reason, history_skipped_file_restore, history_summarize_for_handoff_result, history_truncate_request, history_truncate_result, hmac_auth_info, hook_invoke_request, hook_invoke_response, hook_type, installed_plugin, installed_plugin_info, installed_plugin_source, installed_plugin_source_git_hub, installed_plugin_source_local, installed_plugin_source_url, instruction_discovery_path, instruction_discovery_path_kind, instruction_discovery_path_list, instruction_discovery_path_location, instructions_discover_request, instructions_get_discovery_paths_request, instructions_get_sources_result, instruction_source, instruction_source_location, instruction_source_type, interrupt_main_turn_request, interrupt_main_turn_result, llm_inference_headers, llm_inference_http_request_chunk_request, llm_inference_http_request_chunk_result, llm_inference_http_request_start_request, llm_inference_http_request_start_result, llm_inference_http_request_start_transport, llm_inference_http_response_chunk_error, llm_inference_http_response_chunk_request, llm_inference_http_response_chunk_result, llm_inference_http_response_start_request, llm_inference_http_response_start_result, llm_inference_set_provider_result, local_session_metadata_value, log_request, log_result, lsp_initialize_request, managed_settings_read_result, marketplace_add_result, marketplace_browse_result, marketplace_info, marketplace_list_result, marketplace_plugin_info, marketplace_refresh_entry, marketplace_refresh_result, marketplace_remove_result, mcp_allowed_server, mcp_apps_call_tool_request, mcp_apps_diagnose_capability, mcp_apps_diagnose_request, mcp_apps_diagnose_result, mcp_apps_diagnose_server, mcp_apps_host_context, mcp_apps_host_context_details, mcp_apps_host_context_details_available_display_mode, mcp_apps_host_context_details_display_mode, mcp_apps_host_context_details_platform, mcp_apps_host_context_details_theme, mcp_apps_list_tools_request, mcp_apps_list_tools_result, mcp_apps_read_resource_request, mcp_apps_read_resource_result, mcp_apps_resource_content, mcp_apps_set_host_context_details, mcp_apps_set_host_context_details_available_display_mode, mcp_apps_set_host_context_details_display_mode, mcp_apps_set_host_context_details_platform, mcp_apps_set_host_context_details_theme, mcp_apps_set_host_context_request, mcp_cancel_sampling_execution_params, mcp_cancel_sampling_execution_result, mcp_config_add_request, mcp_config_disable_request, mcp_config_enable_request, mcp_config_list, mcp_config_remove_request, mcp_config_update_request, mcp_configure_git_hub_request, mcp_configure_git_hub_result, mcp_disable_request, mcp_discover_request, mcp_discover_result, mcp_elicitation_form_mode, mcp_enable_request, mcp_execute_sampling_params, mcp_execute_sampling_request, mcp_execute_sampling_result, mcp_failed_server, mcp_filtered_server, mcp_headers_handle_pending_headers_refresh_request, mcp_headers_handle_pending_headers_refresh_request_request, mcp_headers_handle_pending_headers_refresh_request_result, mcp_host_state, mcp_install_plan, mcp_is_server_running_request, mcp_is_server_running_result, mcp_list_tools_request, mcp_list_tools_result, mcp_oauth_authentication_state_changed_request, mcp_oauth_handle_pending_request, mcp_oauth_handle_pending_result, mcp_oauth_login_grant_type, mcp_oauth_login_request, mcp_oauth_login_result, mcp_oauth_pending_request_response, mcp_oauth_probe_needs_auth_reason, mcp_oauth_probe_request, mcp_oauth_probe_result, mcp_oauth_respond_request, mcp_oauth_respond_result, mcp_plan_configuration_change, mcp_plan_configuration_operation, mcp_plan_enum_value_type, mcp_plan_install_planned, mcp_plan_install_request, mcp_plan_install_result, mcp_plan_install_source, mcp_plan_install_source_candidate, mcp_plan_install_source_candidate_kind, mcp_plan_install_source_card, mcp_plan_install_source_card_kind, mcp_plan_package_install_method, mcp_plan_package_transport, mcp_plan_policy_decision, mcp_plan_policy_result, mcp_plan_policy_source, mcp_plan_provenance, mcp_plan_remote_install_method, mcp_plan_remote_transport, mcp_plan_required_value, mcp_plan_required_value_enum, mcp_plan_required_value_enum_kind, mcp_plan_required_value_scalar, mcp_plan_required_value_scalar_kind, mcp_plan_resource_identity, mcp_plan_scalar_value_type, mcp_plan_scope, mcp_plan_secret_placeholder, mcp_plan_secret_reference, mcp_plan_target, mcp_plan_transport_choice, mcp_plan_transport_choice_package, mcp_plan_transport_choice_remote, mcp_plan_value_category, mcp_register_external_client_request, mcp_reload_config, mcp_reload_with_config_request, mcp_remove_git_hub_result, mcp_resource, mcp_resource_annotations, mcp_resource_content, mcp_resource_icon, mcp_resources_list_request, mcp_resources_list_result, mcp_resources_list_templates_request, mcp_resources_list_templates_result, mcp_resources_read_request, mcp_resources_read_result, mcp_resource_template, mcp_restart_server_request, mcp_safe_for_telemetry, mcp_safe_for_telemetry_fields, mcp_sampling_execution_action, mcp_sampling_execution_result, mcp_serializable_server_config, mcp_server, mcp_server_auth_config, mcp_server_auth_config_redirect_port, mcp_server_card_embedded, mcp_server_card_embedded_kind, mcp_server_card_media_type, mcp_server_card_reference, mcp_server_card_url, mcp_server_card_url_kind, mcp_server_config, mcp_server_config_defer_tools, mcp_server_config_http, mcp_server_config_http_oauth_grant_type, mcp_server_config_http_type, mcp_server_config_memory, mcp_server_config_memory_type, mcp_server_config_stdio, mcp_server_config_stdio_type, mcp_server_failure_info, mcp_server_list, mcp_server_needs_auth_info, mcp_set_env_value_mode_details, mcp_set_env_value_mode_params, mcp_set_env_value_mode_result, mcp_start_server_request, mcp_start_servers_result, mcp_stop_server_request, mcp_task_metadata, mcp_tools, mcp_tool_ui, mcp_tool_ui_visibility, mcp_unregister_external_client_request, memory_configuration, metadata_context_attribution_result, metadata_context_heaviest_messages_request, metadata_context_heaviest_messages_result, metadata_context_info_request, metadata_context_info_result, metadata_is_processing_result, metadata_recompute_context_tokens_request, metadata_recompute_context_tokens_result, metadata_record_context_change_request, metadata_record_context_change_result, metadata_set_working_directory_request, metadata_set_working_directory_result, metadata_snapshot_current_mode, metadata_snapshot_remote_metadata, metadata_snapshot_remote_metadata_repository, metadata_snapshot_remote_metadata_task_type, model, model_apply_startup_overlay_request, model_billing, model_billing_promo, model_billing_token_prices, model_billing_token_prices_long_context, model_capabilities, model_capabilities_limits, model_capabilities_limits_vision, model_capabilities_override, model_capabilities_override_limits, model_capabilities_override_limits_vision, model_capabilities_override_supports, model_capabilities_supports, model_list, model_list_request, model_message, model_picker_category, model_picker_persistence_request, model_picker_price_category, model_picker_settings_context, model_policy, model_policy_state, model_set_reasoning_effort_request, model_set_reasoning_effort_result, models_list_request, model_switch_confirmation, model_switch_to_request, model_switch_to_result, model_warning_text, mode_set_request, mode_set_result, move_mcp_loading_to_background_result, named_provider_config, name_get_result, name_set_auto_request, name_set_auto_result, name_set_request, open_canvas_instance, options_update_additional_content_exclusion_policy, options_update_additional_content_exclusion_policy_rule, options_update_additional_content_exclusion_policy_rule_source, options_update_additional_content_exclusion_policy_scope, options_update_context_tier, options_update_env_value_mode, options_update_reasoning_summary, options_update_tool_filter_precedence, pending_permission_request, pending_permission_request_list, permission_decision, permission_decision_approved, permission_decision_approved_for_location, permission_decision_approved_for_session, permission_decision_approve_for_location, permission_decision_approve_for_location_approval, permission_decision_approve_for_location_approval_commands, permission_decision_approve_for_location_approval_custom_tool, permission_decision_approve_for_location_approval_extension_env_access, permission_decision_approve_for_location_approval_extension_management, permission_decision_approve_for_location_approval_extension_permission_access, permission_decision_approve_for_location_approval_factory, permission_decision_approve_for_location_approval_mcp, permission_decision_approve_for_location_approval_mcp_sampling, permission_decision_approve_for_location_approval_memory, permission_decision_approve_for_location_approval_read, permission_decision_approve_for_location_approval_write, permission_decision_approve_for_session, permission_decision_approve_for_session_approval, permission_decision_approve_for_session_approval_commands, permission_decision_approve_for_session_approval_custom_tool, permission_decision_approve_for_session_approval_extension_env_access, permission_decision_approve_for_session_approval_extension_management, permission_decision_approve_for_session_approval_extension_permission_access, permission_decision_approve_for_session_approval_factory, permission_decision_approve_for_session_approval_mcp, permission_decision_approve_for_session_approval_mcp_sampling, permission_decision_approve_for_session_approval_memory, permission_decision_approve_for_session_approval_read, permission_decision_approve_for_session_approval_write, permission_decision_approve_once, permission_decision_approve_permanently, permission_decision_cancelled, permission_decision_context, permission_decision_denied_by_content_exclusion_policy, permission_decision_denied_by_permission_request_hook, permission_decision_denied_by_rules, permission_decision_denied_interactively_by_user, permission_decision_denied_no_approval_rule_and_could_not_request_from_user, permission_decision_outcome, permission_decision_reject, permission_decision_request, permission_decision_source, permission_decision_surface, permission_decision_user_not_available, permission_location_add_tool_approval_params, permission_location_apply_params, permission_location_apply_result, permission_location_resolve_params, permission_location_resolve_result, permission_location_type, permission_mode_source, permission_paths_add_params, permission_paths_allowed_check_params, permission_paths_allowed_check_result, permission_paths_config, permission_paths_list, permission_paths_update_primary_params, permission_paths_workspace_check_params, permission_paths_workspace_check_result, permission_prompt_shown_notification, permission_request_result, permission_response_capability, permission_rules_set, permissions_configure_additional_content_exclusion_policy, permissions_configure_additional_content_exclusion_policy_rule, permissions_configure_additional_content_exclusion_policy_rule_source, permissions_configure_additional_content_exclusion_policy_scope, permissions_configure_params, permissions_configure_result, permissions_folder_trust_add_trusted_result, permissions_get_mode_request, permissions_get_mode_result, permissions_locations_add_tool_approval_details, permissions_locations_add_tool_approval_details_commands, permissions_locations_add_tool_approval_details_custom_tool, permissions_locations_add_tool_approval_details_extension_env_access, permissions_locations_add_tool_approval_details_extension_management, permissions_locations_add_tool_approval_details_extension_permission_access, permissions_locations_add_tool_approval_details_factory, permissions_locations_add_tool_approval_details_mcp, permissions_locations_add_tool_approval_details_mcp_sampling, permissions_locations_add_tool_approval_details_memory, permissions_locations_add_tool_approval_details_read, permissions_locations_add_tool_approval_details_write, permissions_locations_add_tool_approval_result, permissions_modify_rules_params, permissions_modify_rules_result, permissions_modify_rules_scope, permissions_notify_prompt_shown_result, permissions_paths_add_result, permissions_paths_list_request, permissions_paths_update_primary_result, permissions_pending_requests_request, permissions_reset_session_approvals_request, permissions_reset_session_approvals_result, permissions_set_approve_all_request, permissions_set_approve_all_result, permissions_set_approve_all_source, permissions_set_mode_request, permissions_set_mode_result, permissions_set_required_request, permissions_set_required_result, permissions_urls_set_unrestricted_mode_result, permission_urls_config, permission_urls_set_unrestricted_mode_params, ping_request, ping_result, plan_read_result, plan_read_sql_todos_result, plan_read_sql_todos_with_dependencies_result, plan_sql_todo_dependency, plan_sql_todos_row, plan_update_request, plugin, plugin_install_result, plugin_list, plugin_list_result, plugins_builtin_set_request, plugins_disable_request, plugins_enable_request, plugins_install_request, plugins_marketplaces_add_request, plugins_marketplaces_browse_request, plugins_marketplaces_refresh_request, plugins_marketplaces_remove_request, plugins_reload_request, plugins_uninstall_request, plugins_update_request, plugin_update_all_entry, plugin_update_all_result, plugin_update_result, protocol_external_tool_defer, protocol_external_tool_definition, provider_add_request, provider_add_result, provider_config, provider_config_azure, provider_config_transport, provider_config_type, provider_config_wire_api, provider_endpoint, provider_endpoint_transport, provider_endpoint_type, provider_endpoint_wire_api, provider_get_endpoint_request, provider_model_config, provider_session_token, provider_token_acquire_request, provider_token_acquire_result, push_attachment, push_attachment_blob, push_attachment_directory, push_attachment_file, push_attachment_file_line_range, push_attachment_git_hub_actions_job, push_attachment_git_hub_commit, push_attachment_git_hub_file, push_attachment_git_hub_file_diff, push_attachment_git_hub_file_diff_side, push_attachment_git_hub_reference, push_attachment_git_hub_reference_type, push_attachment_git_hub_release, push_attachment_git_hub_repository, push_attachment_git_hub_snippet, push_attachment_git_hub_tree_comparison, push_attachment_git_hub_tree_comparison_side, push_attachment_git_hub_url, push_attachment_selection, push_attachment_selection_details, push_attachment_selection_details_end, push_attachment_selection_details_start, push_git_hub_repo_ref, queue_begin_deferred_idle_drain_request, queue_begin_deferred_idle_drain_result, queue_consume_system_notifications_request, queued_command_handled, queued_command_not_handled, queued_command_result, queue_defer_session_idle_request, queue_duplicate_at_request, queue_duplicate_at_result, queue_enqueue_resume_pending_result, queue_finish_deferred_idle_drain_request, queue_finish_deferred_idle_drain_result, queue_has_pending_result, queue_insert_at_request, queue_insert_at_result, queue_insert_message, queue_move_item_request, queue_move_item_result, queue_pending_items, queue_pending_items_kind, queue_pending_items_result, queue_remove_at_request, queue_remove_at_result, queue_remove_most_recent_result, queue_send_now_request, queue_send_now_result, queue_set_drain_paused_request, queue_snapshot_result, queue_update_text_request, queue_update_text_result, register_event_interest_params, register_event_interest_result, register_extension_tools_params, register_extension_tools_result, release_event_interest_params, remote_control_config, remote_control_config_existing_mc_session, remote_control_status, remote_control_status_active, remote_control_status_connecting, remote_control_status_error, remote_control_status_off, remote_control_status_result, remote_control_stop_result, remote_control_transfer_result, remote_enable_request, remote_enable_result, remote_notify_steerable_changed_request, remote_notify_steerable_changed_result, remote_session_connection_result, remote_session_host_status, remote_session_metadata_repository, remote_session_metadata_task_type, remote_session_metadata_value, remote_session_mode, remote_session_repository, run_options, sandbox_config, sandbox_config_auth, sandbox_config_source, sandbox_config_user_policy, sandbox_config_user_policy_experimental, sandbox_config_user_policy_experimental_seatbelt, sandbox_config_user_policy_filesystem, sandbox_config_user_policy_network, sandbox_config_user_policy_network_proxy, sandbox_config_user_policy_seatbelt, schedule_add_at_request, schedule_add_cron_request, schedule_add_request, schedule_add_result, schedule_add_self_paced_request, schedule_entry, schedule_has_self_paced_result, schedule_list, schedule_rearm_self_paced_request, schedule_stop_request, schedule_stop_result, secrets_add_filter_values_request, secrets_add_filter_values_result, send_agent_mode, send_attachments_to_message_params, send_message_item, send_messages_request, send_messages_result, send_mode, send_request, send_result, send_system_notification_request, server_agent_list, server_instruction_source_list, server_skill, server_skill_list, session_activity, session_agent_list_request, session_auth_login_request, session_auth_logout_user_request, session_auth_status, session_auth_switch_request, session_bulk_delete_result, session_cancel_all_background_agents_result, session_capability, session_commands_list_request, session_completion_item, session_context, session_context_host_type, session_enrich_metadata_result, session_fs_append_file_request, session_fs_error, session_fs_error_code, session_fs_exists_request, session_fs_exists_result, session_fs_mkdir_request, session_fs_readdir_request, session_fs_readdir_result, session_fs_readdir_with_types_entry, session_fs_readdir_with_types_entry_type, session_fs_readdir_with_types_request, session_fs_readdir_with_types_result, session_fs_read_file_request, session_fs_read_file_result, session_fs_rename_request, session_fs_rm_request, session_fs_set_provider_capabilities, session_fs_set_provider_conventions, session_fs_set_provider_request, session_fs_set_provider_result, session_fs_sqlite_exists_request, session_fs_sqlite_exists_result, session_fs_sqlite_query_request, session_fs_sqlite_query_result, session_fs_sqlite_query_type, session_fs_sqlite_transaction_error, session_fs_sqlite_transaction_error_class, session_fs_sqlite_transaction_request, session_fs_sqlite_transaction_result, session_fs_sqlite_transaction_statement, session_fs_stat_request, session_fs_stat_result, session_fs_write_file_request, session_git_hub_auth_get_all_auth_available_result, session_git_hub_auth_logout_result, session_git_hub_auth_logout_user_result, session_history_compact_request, session_installed_plugin, session_installed_plugin_source, session_installed_plugin_source_git_hub, session_installed_plugin_source_local, session_installed_plugin_source_url, session_limit_prediction_baseline_data, session_limit_prediction_client_type, session_limit_prediction_details, session_limit_prediction_predict_request, session_limit_prediction_request, session_limit_prediction_result, session_limit_prediction_source, session_limit_prediction_tier, session_limit_prediction_tier_option, session_limit_prediction_unavailable_reason, session_list, session_list_entry, session_list_filter, session_load_deferred_repo_hooks_result, session_log_level, session_managed_permissions, session_managed_settings, session_mcp_apps_call_tool_result, session_metadata_snapshot, session_mode, session_model_list, session_model_list_request, session_model_price_category, session_open_options, session_open_options_additional_content_exclusion_policy, session_open_options_additional_content_exclusion_policy_rule, session_open_options_additional_content_exclusion_policy_rule_source, session_open_options_additional_content_exclusion_policy_scope, session_open_options_env_value_mode, session_open_options_reasoning_summary, session_open_params, session_open_result, session_plugins_reload_request, session_provider_get_endpoint_request, session_prune_result, sessions_bulk_delete_request, sessions_check_in_use_request, sessions_check_in_use_result, sessions_close_request, sessions_close_result, sessions_delete_request, sessions_enrich_metadata_request, session_set_credentials_params, session_set_credentials_result, session_settings_built_in_tool_availability_snapshot, session_settings_evaluate_predicate_request, session_settings_evaluate_predicate_result, session_settings_job_snapshot, session_settings_model_snapshot, session_settings_online_evaluation_snapshot, session_settings_predicate_name, session_settings_repo_snapshot, session_settings_snapshot, session_settings_validation_snapshot, sessions_find_by_prefix_request, sessions_find_by_prefix_result, sessions_find_by_task_id_request, sessions_find_by_task_id_result, sessions_fork_request, sessions_fork_result, sessions_get_board_entry_count_request, sessions_get_board_entry_count_result, sessions_get_event_file_path_request, sessions_get_event_file_path_result, sessions_get_last_for_context_request, sessions_get_last_for_context_result, sessions_get_metadata_request, sessions_get_metadata_result, sessions_get_persisted_remote_steerable_request, sessions_get_persisted_remote_steerable_result, session_sizes, sessions_list_non_empty_session_ids_request, sessions_list_non_empty_session_ids_result, sessions_list_request, sessions_load_deferred_repo_hooks_request, sessions_open_attach, sessions_open_cloud, sessions_open_create, sessions_open_handoff, sessions_open_handoff_task_type, sessions_open_progress, sessions_open_progress_status, sessions_open_progress_step, sessions_open_remote, sessions_open_resume, sessions_open_resume_last, sessions_open_status, session_source, sessions_prune_old_request, sessions_register_extension_tools_on_session_options, sessions_release_lock_request, sessions_release_lock_result, sessions_reload_plugin_hooks_request, sessions_reload_plugin_hooks_result, sessions_save_request, sessions_save_result, sessions_set_additional_plugins_request, sessions_set_additional_plugins_result, sessions_set_remote_control_steering_request, sessions_start_remote_control_request, sessions_stop_remote_control_request, sessions_transfer_remote_control_request, session_telemetry_engagement, session_update_options_params, session_update_options_result, session_visibility_status, session_working_directory_context, session_working_directory_context_host_type, settable_auth_info, settable_token_auth_info, shell_cancel_user_requested_request, shell_credentials, shell_exec_request, shell_exec_result, shell_execute_user_requested_request, shell_init_profile, shell_init_script, shell_init_script_shell, shell_kill_request, shell_kill_result, shell_kill_signal, shell_options, shutdown_request, skill, skill_discovery_path, skill_discovery_path_list, skill_discovery_scope, skill_list, skills_config_set_disabled_skills_request, skills_config_set_skill_disabled_request, skills_disable_request, skills_discover_request, skills_enable_request, skills_get_discovery_paths_request, skills_get_invoked_result, skills_invoked_skill, skills_load_diagnostics, slash_command_add_timeline_entry_result, slash_command_agent_prompt_result, slash_command_completed_result, slash_command_info, slash_command_input, slash_command_input_choice, slash_command_input_completion, slash_command_invocation_result, slash_command_kind, slash_command_model_picker_dialog, slash_command_select_subcommand_option, slash_command_select_subcommand_result, slash_command_set_model_result, slash_command_set_plan_model_result, slash_command_show_dialog_result, slash_command_text_result, slash_command_timeline_entry, subagent_settings_entry, subagent_settings_entry_context_tier, task_agent_info, task_agent_progress, task_complete_data, task_completion_decision, task_execution_mode, task_info, task_list, task_progress_line, tasks_cancel_request, tasks_cancel_result, tasks_get_current_promotable_result, tasks_get_progress_request, tasks_get_progress_result, task_shell_info, task_shell_info_attachment_mode, task_shell_progress, tasks_promote_current_to_background_result, tasks_promote_to_background_request, tasks_promote_to_background_result, tasks_refresh_result, tasks_remove_request, tasks_remove_result, tasks_send_message_request, tasks_send_message_result, tasks_start_agent_request, tasks_start_agent_result, task_status, tasks_wait_for_pending_result, telemetry_set_feature_overrides_request, token_auth_info, token_provider_auth_info, tool, tool_list, tool_result, tool_result_expanded, tool_result_new_message, tool_result_type, tools_execute_request, tools_get_builtin_descriptors_request, tools_get_builtin_descriptors_result, tools_get_current_metadata_result, tools_initialize_and_validate_result, tools_list_request, tools_set_request, tools_set_result, tools_shell_descriptor_config, tools_task_complete_event_data_request, tools_update_subagent_settings_result, ui_auto_mode_switch_response, ui_elicitation_array_any_of_field, ui_elicitation_array_any_of_field_items, ui_elicitation_array_any_of_field_items_any_of, ui_elicitation_array_enum_field, ui_elicitation_array_enum_field_items, ui_elicitation_field_value, ui_elicitation_request, ui_elicitation_response, ui_elicitation_response_action, ui_elicitation_response_content, ui_elicitation_result, ui_elicitation_schema, ui_elicitation_schema_property, ui_elicitation_schema_property_boolean, ui_elicitation_schema_property_number, ui_elicitation_schema_property_number_type, ui_elicitation_schema_property_string, ui_elicitation_schema_property_string_format, ui_elicitation_string_enum_field, ui_elicitation_string_one_of_field, ui_elicitation_string_one_of_field_one_of, ui_ephemeral_query_request, ui_ephemeral_query_result, ui_exit_plan_mode_action, ui_exit_plan_mode_response, ui_handle_pending_auto_mode_switch_request, ui_handle_pending_elicitation_request, ui_handle_pending_exit_plan_mode_request, ui_handle_pending_result, ui_handle_pending_sampling_request, ui_handle_pending_sampling_response, ui_handle_pending_session_limits_exhausted_request, ui_handle_pending_user_input_request, ui_register_direct_auto_mode_switch_handler_result, ui_session_limits_exhausted_response, ui_session_limits_exhausted_response_action, ui_unregister_direct_auto_mode_switch_handler_request, ui_unregister_direct_auto_mode_switch_handler_result, ui_user_input_response, update_subagent_settings_request, usage_get_metrics_result, usage_metrics_agent_metric, usage_metrics_code_changes, usage_metrics_model_metric, usage_metrics_model_metric_requests, usage_metrics_model_metric_token_detail, usage_metrics_model_metric_usage, usage_metrics_token_detail, user_auth_info, user_requested_shell_command_result, user_setting_metadata, user_settings_get_result, user_settings_set_request, user_settings_set_result, visibility_get_result, visibility_set_request, visibility_set_result, workspace_diff_file_change, workspace_diff_file_change_type, workspace_diff_mode, workspace_diff_result, workspaces_add_summary_request, workspaces_add_summary_result, workspaces_autopilot_objective_exists_result, workspaces_checkpoints, workspaces_create_file_request, workspaces_delete_autopilot_objective_result, workspaces_diff_request, workspaces_ensure_request, workspaces_get_workspace_result, workspaces_list_checkpoints_result, workspaces_list_files_result, workspaces_read_autopilot_objective_result, workspaces_read_checkpoint_request, workspaces_read_checkpoint_result, workspaces_read_file_request, workspaces_read_file_result, workspaces_save_large_paste_request, workspaces_save_large_paste_result, workspaces_truncate_summaries_request, workspace_summary_host_type, workspaces_update_metadata_request, workspaces_workspace_details_host_type, workspaces_write_autopilot_objective_request, workspaces_write_autopilot_objective_result, session_auth_info_result, session_context_attribution, session_context_info, subagent_settings, task_progress, workspace_summary) def to_dict(self) -> dict: result: dict = {} @@ -37881,6 +37915,7 @@ def to_dict(self) -> dict: result["PermissionPathsWorkspaceCheckResult"] = to_class(PermissionPathsWorkspaceCheckResult, self.permission_paths_workspace_check_result) result["PermissionPromptShownNotification"] = to_class(PermissionPromptShownNotification, self.permission_prompt_shown_notification) result["PermissionRequestResult"] = to_class(PermissionRequestResult, self.permission_request_result) + result["PermissionResponseCapability"] = to_enum(PermissionResponseCapability, self.permission_response_capability) result["PermissionRulesSet"] = to_class(PermissionRulesSet, self.permission_rules_set) result["PermissionsConfigureAdditionalContentExclusionPolicy"] = to_class(PermissionsConfigureAdditionalContentExclusionPolicy, self.permissions_configure_additional_content_exclusion_policy) result["PermissionsConfigureAdditionalContentExclusionPolicyRule"] = to_class(PermissionsConfigureAdditionalContentExclusionPolicyRule, self.permissions_configure_additional_content_exclusion_policy_rule) @@ -42387,6 +42422,7 @@ async def handle_git_hub_token_get_token(params: dict) -> dict | None: "PermissionPathsWorkspaceCheckResult", "PermissionPromptShownNotification", "PermissionRequestResult", + "PermissionResponseCapability", "PermissionRulesSet", "PermissionSource", "PermissionUrlsConfig", diff --git a/python/copilot/generated/session_events.py b/python/copilot/generated/session_events.py index 528e6657fc..d4af5f2735 100644 --- a/python/copilot/generated/session_events.py +++ b/python/copilot/generated/session_events.py @@ -7169,19 +7169,24 @@ def to_dict(self) -> dict: class SessionIdleData: "Payload indicating the session is idle with no background agents or attached shell commands in flight" aborted: bool | None = None + mode: SessionMode | None = None @staticmethod def from_dict(obj: Any) -> "SessionIdleData": assert isinstance(obj, dict) aborted = from_union([from_none, from_bool], obj.get("aborted")) + mode = from_union([from_none, lambda x: parse_enum(SessionMode, x)], obj.get("mode")) return SessionIdleData( aborted=aborted, + mode=mode, ) def to_dict(self) -> dict: result: dict = {} if self.aborted is not None: result["aborted"] = from_union([from_none, from_bool], self.aborted) + if self.mode is not None: + result["mode"] = from_union([from_none, lambda x: to_enum(SessionMode, x)], self.mode) return result diff --git a/python/copilot/session.py b/python/copilot/session.py index b3d8aa45ce..78afdde139 100644 --- a/python/copilot/session.py +++ b/python/copilot/session.py @@ -78,6 +78,7 @@ SessionErrorData, SessionEvent, SessionIdleData, + SessionMode, session_event_from_dict, ) from .generated.session_events import ( @@ -1793,7 +1794,7 @@ def handler(event: SessionEventTypeAlias) -> None: total_start, session_id=self.session_id, ) - case SessionIdleData(): + case SessionIdleData() as data if data.mode != SessionMode.AUTOPILOT: log_timing( logger, logging.DEBUG, diff --git a/python/test_permission_decision_context.py b/python/test_permission_decision_context.py index 2b013942d7..70b3a74de5 100644 --- a/python/test_permission_decision_context.py +++ b/python/test_permission_decision_context.py @@ -1,5 +1,6 @@ from unittest.mock import AsyncMock, MagicMock +from copilot import PermissionResponseCapability from copilot.rpc import ( PermissionDecisionApproveOnce, PermissionDecisionContext, @@ -16,6 +17,10 @@ from copilot.session_events import PermissionRequestRead +def test_permission_response_capability_is_exported_from_package_root() -> None: + assert PermissionResponseCapability.INTERACTIVE.value == "interactive" + + def _context() -> PermissionDecisionContext: return PermissionDecisionContext( outcome=PermissionDecisionOutcome.AUTO_APPROVED, diff --git a/python/test_session.py b/python/test_session.py new file mode 100644 index 0000000000..dd2d0a72f6 --- /dev/null +++ b/python/test_session.py @@ -0,0 +1,69 @@ +"""CopilotSession unit tests.""" + +import asyncio +from datetime import UTC, datetime +from unittest.mock import AsyncMock, Mock +from uuid import uuid4 + +import pytest + +from copilot.session import CopilotSession +from copilot.session_events import ( + AssistantMessageData, + SessionEvent, + SessionEventType, + SessionIdleData, + SessionMode, +) + + +def _event(data, event_type: SessionEventType) -> SessionEvent: + return SessionEvent( + data=data, + id=uuid4(), + timestamp=datetime.now(UTC), + type=event_type, + ) + + +@pytest.mark.asyncio +async def test_send_and_wait_skips_autopilot_continuation_idle(): + client = Mock() + client.request = AsyncMock(return_value={"messageId": "message-1"}) + session = CopilotSession("session-1", client) + + pending = asyncio.create_task(session.send_and_wait("keep going")) + await asyncio.sleep(0) + client.request.assert_awaited_once() + + session._dispatch_event( + _event( + AssistantMessageData(content="intermediate", message_id="assistant-1"), + SessionEventType.ASSISTANT_MESSAGE, + ) + ) + session._dispatch_event( + _event( + SessionIdleData(mode=SessionMode.AUTOPILOT), + SessionEventType.SESSION_IDLE, + ) + ) + assert not pending.done() + + session._dispatch_event( + _event( + AssistantMessageData(content="final", message_id="assistant-2"), + SessionEventType.ASSISTANT_MESSAGE, + ) + ) + session._dispatch_event( + _event( + SessionIdleData(mode=SessionMode.INTERACTIVE), + SessionEventType.SESSION_IDLE, + ) + ) + + result = await asyncio.wait_for(pending, timeout=1) + assert result is not None + assert isinstance(result.data, AssistantMessageData) + assert result.data.content == "final" diff --git a/rust/src/generated/api_types.rs b/rust/src/generated/api_types.rs index 43ea119f8a..bdf291e599 100644 --- a/rust/src/generated/api_types.rs +++ b/rust/src/generated/api_types.rs @@ -5832,6 +5832,12 @@ pub struct GitHubTelemetryClientInfo { /// Copilot subscription plan, when known. #[serde(rename = "copilot_plan", skip_serializing_if = "Option::is_none")] pub copilot_plan: Option, + /// Number of logical CPU cores on the host. + #[serde(rename = "cpu_count", skip_serializing_if = "Option::is_none")] + pub cpu_count: Option, + /// Distinct CPU model names for the host, comma-separated. + #[serde(rename = "cpu_model", skip_serializing_if = "Option::is_none")] + pub cpu_model: Option, /// Stable machine identifier for the device. #[serde(rename = "dev_device_id", skip_serializing_if = "Option::is_none")] pub dev_device_id: Option, @@ -11415,6 +11421,9 @@ pub struct PermissionDecisionDeniedByPermissionRequestHook { pub struct PermissionDecisionContext { /// Disposition of the permission request as observed by the responding client. pub outcome: PermissionDecisionOutcome, + /// Whether the responding client could ask a user interactively, was running headlessly, or had no response path. Omit when the client cannot determine this authoritatively. + #[serde(skip_serializing_if = "Option::is_none")] + pub response_capability: Option, /// Controlled reason or actor responsible for the response. pub source: PermissionDecisionSource, /// Client surface that submitted the response. @@ -31285,6 +31294,31 @@ pub enum PermissionDecisionOutcome { Unknown, } +/// Response capability available to the client when it settled a permission request. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum PermissionResponseCapability { + /// The client could ask a user for this decision. + #[serde(rename = "interactive")] + Interactive, + /// The client could return an automated response but could not ask a user. + #[serde(rename = "headless")] + Headless, + /// The client had no response path available. + #[serde(rename = "none")] + None, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, +} + /// Controlled reason or actor responsible for a permission response. /// ///
@@ -31332,6 +31366,9 @@ pub enum PermissionDecisionSurface { /// The Copilot App client. #[serde(rename = "copilot_app")] CopilotApp, + /// An Agent Client Protocol host. + #[serde(rename = "acp")] + Acp, /// A generic Copilot SDK client. #[serde(rename = "sdk")] Sdk, diff --git a/rust/src/generated/session_events.rs b/rust/src/generated/session_events.rs index f0508660b4..a5b123d3b5 100644 --- a/rust/src/generated/session_events.rs +++ b/rust/src/generated/session_events.rs @@ -943,6 +943,9 @@ pub struct SessionIdleData { /// True when the preceding agentic loop was cancelled via abort signal #[serde(skip_serializing_if = "Option::is_none")] pub aborted: Option, + /// The session mode the agent was operating in when it went idle, when the mode is known. Lets turn-scoped consumers distinguish an autopilot continuation boundary (where the agent keeps working after this idle) from a genuine turn completion. + #[serde(skip_serializing_if = "Option::is_none")] + pub mode: Option, } /// Session event "session.title_changed". Session title change payload containing the new display title @@ -5752,6 +5755,24 @@ pub enum Verbosity { Unknown, } +/// The session mode the agent is operating in +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum SessionMode { + /// The agent is responding interactively to the user. + #[serde(rename = "interactive")] + Interactive, + /// The agent is preparing a plan before making changes. + #[serde(rename = "plan")] + Plan, + /// The agent is working autonomously toward task completion. + #[serde(rename = "autopilot")] + Autopilot, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, +} + /// Who created the schedule: `user` (an explicit user action such as `/every` or `/after`) or `model` (the agent via the `manage_schedule` tool). Gates whether a scheduled skill that opted out of model invocation may fire: only user-created schedules may. #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] pub enum ScheduleOrigin { @@ -5848,24 +5869,6 @@ pub enum ModelChangeSource { Unknown, } -/// The session mode the agent is operating in -#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -pub enum SessionMode { - /// The agent is responding interactively to the user. - #[serde(rename = "interactive")] - Interactive, - /// The agent is preparing a plan before making changes. - #[serde(rename = "plan")] - Plan, - /// The agent is working autonomously toward task completion. - #[serde(rename = "autopilot")] - Autopilot, - /// Unknown variant for forward compatibility. - #[default] - #[serde(other)] - Unknown, -} - /// Permission mode for the session. /// ///
diff --git a/rust/src/handler.rs b/rust/src/handler.rs index f1f0d9566d..e036b75a10 100644 --- a/rust/src/handler.rs +++ b/rust/src/handler.rs @@ -114,6 +114,7 @@ impl PermissionResult { /// /// let result = PermissionResult::approve_once().with_context(PermissionDecisionContext { /// outcome: PermissionDecisionOutcome::AutoApproved, + /// response_capability: None, /// source: PermissionDecisionSource::HostPolicy, /// surface: PermissionDecisionSurface::Sdk, /// }); diff --git a/rust/src/session.rs b/rust/src/session.rs index 767b4cb78d..b9d2173055 100644 --- a/rust/src/session.rs +++ b/rust/src/session.rs @@ -17,7 +17,7 @@ use crate::generated::api_types::{ }; use crate::generated::session_events::{ CommandExecuteData, ElicitationRequestedData, ExternalToolRequestedData, McpOauthRequiredData, - SessionCanvasClosedData, SessionErrorData, SessionEventType, + SessionCanvasClosedData, SessionErrorData, SessionEventType, SessionIdleData, SessionMode, }; use crate::handler::{ AutoModeSwitchHandler, AutoModeSwitchResponse, ElicitationHandler, ExitPlanModeHandler, @@ -1701,6 +1701,12 @@ fn tool_failure_result(message: impl Into) -> ToolResult { }) } +fn is_autopilot_continuation_idle(event: &SessionEvent) -> bool { + event + .typed_data::() + .is_some_and(|data| data.mode == Some(SessionMode::Autopilot)) +} + /// Process a notification from the CLI's broadcast channel. #[allow(clippy::too_many_arguments)] async fn handle_notification( @@ -1745,6 +1751,7 @@ async fn handle_notification( } waiter.last_assistant_message = Some(event.clone()); } + SessionEventType::SessionIdle if is_autopilot_continuation_idle(&event) => {} SessionEventType::SessionIdle | SessionEventType::SessionError => { if let Some(waiter) = guard.take() { if event_type == SessionEventType::SessionIdle { @@ -2652,15 +2659,38 @@ mod tests { use serde_json::json; use super::{ - build_mode_post_create_patch, has_managed_settings, permission_request_data, - permission_response_params, + build_mode_post_create_patch, has_managed_settings, is_autopilot_continuation_idle, + permission_request_data, permission_response_params, }; use crate::handler::PermissionResult; use crate::types::{ PermissionDecisionContext, PermissionDecisionOutcome, PermissionDecisionSource, - PermissionDecisionSurface, RequestId, SessionId, + PermissionDecisionSurface, RequestId, SessionEvent, SessionId, }; + #[test] + fn identifies_only_autopilot_continuation_idles() { + let mut event = SessionEvent { + id: "event-1".to_string(), + timestamp: "2026-01-01T00:00:00Z".to_string(), + parent_id: None, + ephemeral: None, + agent_id: None, + debug_cli_received_at_ms: None, + debug_ws_forwarded_at_ms: None, + event_type: "session.idle".to_string(), + data: json!({ "mode": "autopilot" }), + }; + + assert!(is_autopilot_continuation_idle(&event)); + + event.data = json!({ "mode": "interactive" }); + assert!(!is_autopilot_continuation_idle(&event)); + + event.data = json!({}); + assert!(!is_autopilot_continuation_idle(&event)); + } + #[test] fn empty_mode_post_patch_sets_empty_included_builtin_skills() { let patch = @@ -2748,6 +2778,7 @@ mod tests { fn attribution_context() -> PermissionDecisionContext { PermissionDecisionContext { outcome: PermissionDecisionOutcome::AutoApproved, + response_capability: None, source: PermissionDecisionSource::AssistedApproval, surface: PermissionDecisionSurface::CopilotApp, } @@ -2836,6 +2867,7 @@ mod tests { .with_context(attribution_context()) .with_context(PermissionDecisionContext { outcome: PermissionDecisionOutcome::PromptedUser, + response_capability: None, source: PermissionDecisionSource::HumanResponse, surface: PermissionDecisionSurface::Sdk, }); diff --git a/rust/src/types.rs b/rust/src/types.rs index 1d64a7a0ea..6e451eb452 100644 --- a/rust/src/types.rs +++ b/rust/src/types.rs @@ -5860,7 +5860,7 @@ pub use crate::generated::api_types::{ ModelCapabilitiesSupports, ModelList, ModelPolicy, PermissionDecision, PermissionDecisionApproveOnce, PermissionDecisionContext, PermissionDecisionOutcome, PermissionDecisionReject, PermissionDecisionSource, PermissionDecisionSurface, - PermissionDecisionUserNotAvailable, + PermissionDecisionUserNotAvailable, PermissionResponseCapability, }; /// Permission categories the CLI may request approval for. @@ -5970,13 +5970,21 @@ mod tests { CopilotExpAssignmentResponse, CustomAgentConfig, DeliveryMode, ExpConfigEntry, ExpFlagValue, ExtensionInfo, GitHubMcpToolConfig, GitHubReferenceType, InfiniteSessionConfig, LargeToolOutputConfig, McpServerConfig, McpStdioServerConfig, - MemoryConfiguration, NamedProviderConfig, ProviderConfig, ProviderModelConfig, - ReasoningSummary, ResumeSessionConfig, SessionConfig, SessionEvent, SessionId, - SystemMessageConfig, Tool, ToolBinaryResult, ToolResult, ToolResultExpanded, + MemoryConfiguration, NamedProviderConfig, PermissionResponseCapability, ProviderConfig, + ProviderModelConfig, ReasoningSummary, ResumeSessionConfig, SessionConfig, SessionEvent, + SessionId, SystemMessageConfig, Tool, ToolBinaryResult, ToolResult, ToolResultExpanded, ToolResultResponse, ensure_attachment_display_names, }; use crate::generated::session_events::TypedSessionEvent; + #[test] + fn permission_response_capability_is_publicly_exported() { + assert_eq!( + serde_json::to_value(PermissionResponseCapability::Interactive).unwrap(), + json!("interactive") + ); + } + #[test] fn tool_builder_composes() { let tool = Tool::new("greet") diff --git a/rust/tests/session_test.rs b/rust/tests/session_test.rs index 378d7120bf..a51d61910d 100644 --- a/rust/tests/session_test.rs +++ b/rust/tests/session_test.rs @@ -54,6 +54,7 @@ impl PermissionHandler for ContextualApproveHandler { ) -> PermissionResult { PermissionResult::approve_once().with_context(PermissionDecisionContext { outcome: PermissionDecisionOutcome::PromptedUser, + response_capability: None, source: PermissionDecisionSource::HumanResponse, surface: PermissionDecisionSurface::CopilotApp, }) diff --git a/test/harness/package-lock.json b/test/harness/package-lock.json index d2340d6c94..5566e2e34c 100644 --- a/test/harness/package-lock.json +++ b/test/harness/package-lock.json @@ -9,7 +9,7 @@ "version": "1.0.0", "license": "ISC", "devDependencies": { - "@github/copilot": "^1.0.81-10", + "@github/copilot": "^1.0.81-11", "@modelcontextprotocol/sdk": "^1.26.0", "@types/node": "^25.3.3", "@types/node-forge": "^1.3.14", @@ -472,8 +472,8 @@ } }, "node_modules/@github/copilot": { - "version": "1.0.81-10", - "integrity": "sha512-Ac99EvN16s4hKRhJLSEn1HMNaZ6MD8BzIey1zzJNBQy1/yP4PQDZ2CWitEq+XQQEi+6SsqeJRqXOKiWk1EyK7g==", + "version": "1.0.81-11", + "integrity": "sha512-F7hZ6G6fhWH4uq862mbs2JE3nL0KIVBOc94/EFOdEjux3oHUQst9a06gKibEJS6VRULaYTXzatA1EAgNC9dzFA==", "dev": true, "license": "SEE LICENSE IN LICENSE.md", "dependencies": { @@ -483,19 +483,19 @@ "copilot": "npm-loader.js" }, "optionalDependencies": { - "@github/copilot-darwin-arm64": "1.0.81-10", - "@github/copilot-darwin-x64": "1.0.81-10", - "@github/copilot-linux-arm64": "1.0.81-10", - "@github/copilot-linux-x64": "1.0.81-10", - "@github/copilot-linuxmusl-arm64": "1.0.81-10", - "@github/copilot-linuxmusl-x64": "1.0.81-10", - "@github/copilot-win32-arm64": "1.0.81-10", - "@github/copilot-win32-x64": "1.0.81-10" + "@github/copilot-darwin-arm64": "1.0.81-11", + "@github/copilot-darwin-x64": "1.0.81-11", + "@github/copilot-linux-arm64": "1.0.81-11", + "@github/copilot-linux-x64": "1.0.81-11", + "@github/copilot-linuxmusl-arm64": "1.0.81-11", + "@github/copilot-linuxmusl-x64": "1.0.81-11", + "@github/copilot-win32-arm64": "1.0.81-11", + "@github/copilot-win32-x64": "1.0.81-11" } }, "node_modules/@github/copilot-darwin-arm64": { - "version": "1.0.81-10", - "integrity": "sha512-s90Av0iwjTSU6Gky8T9wI1PJdlfbdUcPAVgKDtimaOiAwcdLG4fKTpGxrk96KJrnOHHK3x9SiXsw/pW0ThAH/A==", + "version": "1.0.81-11", + "integrity": "sha512-3eLs71CLnJH9RNnESnv4esipZPeGXMlBxQeKXwZY+crwcW2RAR8YuovQyfoZ/5by1PLPbYrOjXNfQL6kXSisrA==", "cpu": [ "arm64" ], @@ -510,8 +510,8 @@ } }, "node_modules/@github/copilot-darwin-x64": { - "version": "1.0.81-10", - "integrity": "sha512-8RnPI4J311oJQ0GPB6JxuLJq4JNY/KF9ZIIQm8KpxXBY6d+6fmmAsMDEk7OiF/Asl2I7+LTi+qU2ZVhP7FYhbg==", + "version": "1.0.81-11", + "integrity": "sha512-GdFLiUC8UL9k6K+woG8AyL3zBafd0Br1TIPDV5iiZsyBtTe4g67FjL7DGCYDt6AN9G43jWdK0M0InoNDVq1K1A==", "cpu": [ "x64" ], @@ -526,8 +526,8 @@ } }, "node_modules/@github/copilot-linux-arm64": { - "version": "1.0.81-10", - "integrity": "sha512-2UtK5CBrE6ZVSIzU2KHeIgO8N7056axjbF2lE6WuK+H+oJJ4v3w5eQkalqGzRHhkaPfCW4kT1lDMhZFW+XbLjA==", + "version": "1.0.81-11", + "integrity": "sha512-C4hcAow5CdaVJITbdtGqFgxWpW7TqwyCPNn8OtcbvdWeiKcfOBDrgkvbsezG98/5Ovs3HWxZRT4oZqCEmGF9Ww==", "cpu": [ "arm64" ], @@ -542,8 +542,8 @@ } }, "node_modules/@github/copilot-linux-x64": { - "version": "1.0.81-10", - "integrity": "sha512-61+KAfo1TBARrBfss3w4dfmRVSf0PiFg0c9JNuT9HjoNnytl7maJBPEgUvI4YBcxScNEAlCMXaUUG3Tuuh1g+w==", + "version": "1.0.81-11", + "integrity": "sha512-izu0PwWx+wL4zxacO6cd6r0zMMMQ3pTz+2euWcAd7HCJ/CIR6+YYfjU3TI3TPBZ9zDLZGoxHKYYPfJ1ZbSTzEg==", "cpu": [ "x64" ], @@ -558,8 +558,8 @@ } }, "node_modules/@github/copilot-linuxmusl-arm64": { - "version": "1.0.81-10", - "integrity": "sha512-CR6KRPCFoGkaD8I2an1FyrT5avF1U5aTbwW2sYCP7w1KExYFknxEL8ES6BkFuPEA7YcjmLa0SOq26Z+TgIVHSg==", + "version": "1.0.81-11", + "integrity": "sha512-uWGUjaxOSMu6dKFWcTvXGUUp76vC3OV7nlSupecRkQ7gA3OxdrrB8rXE5/leC606Hk6oe9Wl7wKCH9EwuZ6afg==", "cpu": [ "arm64" ], @@ -574,8 +574,8 @@ } }, "node_modules/@github/copilot-linuxmusl-x64": { - "version": "1.0.81-10", - "integrity": "sha512-fvZfyEOfRkvUDPXY6UUjAqV8Mkf08PQV+jgtiAFUryuas5VP9cYaAmQSmNpzNMNi3kSX/ycUJe7oc3zXZ8ylog==", + "version": "1.0.81-11", + "integrity": "sha512-BpWKd/iu1tTyuPR2zuPFs1pVOlley4yt/TXkn6/gVwt52VI0rUxEO+n77RVjDcyMxWaUApbad2VNRPIH77guCA==", "cpu": [ "x64" ], @@ -590,8 +590,8 @@ } }, "node_modules/@github/copilot-win32-arm64": { - "version": "1.0.81-10", - "integrity": "sha512-n30PPBgCT4Iq9MgH6is6L3eUEE+sF6xB2fb+dGsclj5j/hCkT7+ef0j8YcAGipsvGfzGAuywIsWlvF7fzYsOKQ==", + "version": "1.0.81-11", + "integrity": "sha512-GOK3cACgD96m065uJxbgcXL7MQ1qm+wq1qtvGBpprY0r9wTYJG/rVCtayjYB6B57rnaBwPll3+PQ2J1qgfO57A==", "cpu": [ "arm64" ], @@ -606,8 +606,8 @@ } }, "node_modules/@github/copilot-win32-x64": { - "version": "1.0.81-10", - "integrity": "sha512-lb8kvhrXwGCN3LeRDQfLHsUp+F43XvPYznaYK1sPtK1kFGa4/kL690tasoSEvzu8ZKoTY6kZ6YmDbUZgqOislw==", + "version": "1.0.81-11", + "integrity": "sha512-u4K6UU2iGQJqQwsdHNilJBwiaC48PfMsWVFnTSlJD5lCYp9zCk1odrvrcmB3ARYinE/IcXLUosiHaM/ChR/pdA==", "cpu": [ "x64" ], diff --git a/test/harness/package.json b/test/harness/package.json index e968848315..e8868750a0 100644 --- a/test/harness/package.json +++ b/test/harness/package.json @@ -14,7 +14,7 @@ "node": "^20.19.0 || >=22.12.0" }, "devDependencies": { - "@github/copilot": "^1.0.81-10", + "@github/copilot": "^1.0.81-11", "@modelcontextprotocol/sdk": "^1.26.0", "@types/node": "^25.3.3", "@types/node-forge": "^1.3.14", From 521aa26b29e35a25cd483c589b35ee4f0b7b8750 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Thu, 27 Aug 2026 16:12:00 +0000 Subject: [PATCH 25/32] Java: add linux-arm64 native runtime support (#2421) * Initial plan * Add Java linux-arm64 native runtime support Co-authored-by: edburns <75821+edburns@users.noreply.github.com> * Scope Java glibc option to ARM64 CI Co-authored-by: edburns <75821+edburns@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: edburns <75821+edburns@users.noreply.github.com> --- .github/workflows/java-publish-maven.yml | 96 ++++++++++++- .github/workflows/java-publish-snapshot.yml | 108 ++++++++++++++- .github/workflows/java-sdk-tests.yml | 85 +++++++++++- java/README.md | 17 ++- java/copilot-native/pom.xml | 127 +++++++++++++++++- .../scripts/fetch-native.test.mjs | 2 +- .../scripts/validate-local-publication.mjs | 8 +- .../scripts/validate-native-artifact.test.mjs | 43 ++++++ .../scripts/validate-native-host.mjs | 8 +- .../scripts/validate-native-host.test.mjs | 35 +++++ .../adr/adr-007-native-bundling-strategy.md | 6 +- java/sdk/pom.xml | 12 ++ 12 files changed, 525 insertions(+), 22 deletions(-) diff --git a/.github/workflows/java-publish-maven.yml b/.github/workflows/java-publish-maven.yml index 1ce4f51e38..51c50d979c 100644 --- a/.github/workflows/java-publish-maven.yml +++ b/.github/workflows/java-publish-maven.yml @@ -184,6 +184,65 @@ jobs: echo "tag_commit=$TAG_COMMIT" >> "$GITHUB_OUTPUT" echo "post_prepare_commit=$POST_PREPARE_COMMIT" >> "$GITHUB_OUTPUT" + build-linux-arm64-classifier: + name: Build Linux ARM64 native classifier + needs: prepare-release + runs-on: ubuntu-24.04-arm + permissions: + contents: read + defaults: + run: + shell: bash + working-directory: ./java + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + ref: ${{ needs.prepare-release.outputs.release_tag }} + fetch-depth: 1 + persist-credentials: false + + - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 + with: + java-version: "25" + distribution: "microsoft" + cache: "maven" + + - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 + with: + node-version: 22 + + - name: Build and validate linux-arm64 classifier + run: | + set -euo pipefail + SOURCE_COMMIT=$(git rev-parse HEAD) + if [ "$SOURCE_COMMIT" != "${{ needs.prepare-release.outputs.tag_commit }}" ]; then + echo "::error::Checked out $SOURCE_COMMIT instead of the prepared tag commit." + exit 1 + fi + node copilot-native/scripts/validate-native-host.mjs linux-arm64 + mvn -B -pl copilot-native package -DskipTests -Dcopilot.native.libc=glibc + VERSION="${{ needs.prepare-release.outputs.release_version }}" + JAR="copilot-native/target/copilot-sdk-java-runtime-$VERSION-linux-arm64.jar" + PRIMARY_JAR="copilot-native/target/copilot-sdk-java-runtime-$VERSION.jar" + test -f "$JAR" + node copilot-native/scripts/validate-native-artifact.mjs \ + classifier linux-arm64 "$JAR" "$(basename "$JAR")" .. + node copilot-native/scripts/validate-native-artifact.mjs placeholder "$PRIMARY_JAR" + MANIFEST="copilot-native/target/linux-arm64-$VERSION.sha256" + HASH=$(sha256sum "$JAR" | cut -d ' ' -f 1) + printf '%s %s' "$HASH" "$(basename "$JAR")" > "$MANIFEST" + node copilot-native/scripts/validate-native-artifact.mjs \ + checksum "$JAR" "$MANIFEST" "$(basename "$JAR")" + + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 + with: + name: java-native-linux-arm64-release-${{ github.run_id }}-${{ github.run_attempt }} + path: | + java/copilot-native/target/copilot-sdk-java-runtime-${{ needs.prepare-release.outputs.release_version }}-linux-arm64.jar + java/copilot-native/target/linux-arm64-${{ needs.prepare-release.outputs.release_version }}.sha256 + if-no-files-found: error + retention-days: 1 + build-windows-classifier: name: Build Windows native classifier needs: prepare-release @@ -302,7 +361,13 @@ jobs: deploy-maven: name: Deploy Java release to Maven Central - needs: [prepare-release, build-windows-classifier, build-darwin-classifier] + needs: + [ + prepare-release, + build-linux-arm64-classifier, + build-windows-classifier, + build-darwin-classifier, + ] runs-on: ubuntu-latest permissions: contents: read @@ -337,6 +402,11 @@ jobs: with: node-version: 22 + - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 + with: + name: java-native-linux-arm64-release-${{ github.run_id }}-${{ github.run_attempt }} + path: ${{ runner.temp }}/java-native-linux-arm64 + - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 with: name: java-native-win32-x64-release-${{ github.run_id }}-${{ github.run_attempt }} @@ -347,6 +417,27 @@ jobs: name: java-native-darwin-arm64-release-${{ github.run_id }}-${{ github.run_attempt }} path: ${{ runner.temp }}/java-native-darwin-arm64 + - name: Verify immutable source and Linux ARM64 classifier + id: linux-arm64-artifact + run: | + SOURCE_COMMIT=$(git rev-parse HEAD) + if [ "$SOURCE_COMMIT" != "${{ needs.prepare-release.outputs.tag_commit }}" ]; then + echo "::error::Checked out $SOURCE_COMMIT instead of the prepared tag commit." + exit 1 + fi + VERSION="${{ needs.prepare-release.outputs.release_version }}" + ARTIFACT_DIRECTORY="${{ runner.temp }}/java-native-linux-arm64" + JAR="$ARTIFACT_DIRECTORY/copilot-sdk-java-runtime-$VERSION-linux-arm64.jar" + MANIFEST="$ARTIFACT_DIRECTORY/linux-arm64-$VERSION.sha256" + test -f "$JAR" + test -f "$MANIFEST" + node "$GITHUB_WORKSPACE/java/copilot-native/scripts/validate-native-artifact.mjs" \ + checksum "$JAR" "$MANIFEST" "$(basename "$JAR")" + node "$GITHUB_WORKSPACE/java/copilot-native/scripts/validate-native-artifact.mjs" \ + classifier linux-arm64 "$JAR" "$(basename "$JAR")" "$GITHUB_WORKSPACE" + echo "linux_arm64_jar=$JAR" >> "$GITHUB_OUTPUT" + echo "linux_arm64_sha=$(cut -d ' ' -f 1 "$MANIFEST")" >> "$GITHUB_OUTPUT" + - name: Verify immutable source and Windows classifier id: windows-artifact run: | @@ -394,6 +485,7 @@ jobs: run: | VERSION="${{ needs.prepare-release.outputs.release_version }}" mvn -B deploy -DskipTests -Prelease -Dcopilot.native.libc=glibc \ + "-Dcopilot.native.external.linux.arm64.classifier.path=${{ steps.linux-arm64-artifact.outputs.linux_arm64_jar }}" \ "-Dcopilot.native.external.win32.classifier.path=${{ steps.windows-artifact.outputs.windows_jar }}" \ "-Dcopilot.native.external.darwin.classifier.path=${{ steps.darwin-artifact.outputs.darwin_jar }}" LINUX_JAR="copilot-native/target/copilot-sdk-java-runtime-$VERSION-linux-x64.jar" @@ -431,6 +523,7 @@ jobs: echo "| Classifier | Build runner | Artifact | SHA-256 | Status |" echo "| --- | --- | --- | --- | --- |" echo "| \`linux-x64\` | \`ubuntu-latest\` | \`$(basename "$LINUX_JAR")\` | \`$LINUX_SHA\` | Published |" + echo "| \`linux-arm64\` | \`ubuntu-24.04-arm\` | \`$(basename "${{ steps.linux-arm64-artifact.outputs.linux_arm64_jar }}")\` | \`${{ steps.linux-arm64-artifact.outputs.linux_arm64_sha }}\` | Published |" echo "| \`win32-x64\` | \`windows-latest\` | \`$(basename "${{ steps.windows-artifact.outputs.windows_jar }}")\` | \`${{ steps.windows-artifact.outputs.windows_sha }}\` | Published |" echo "| \`darwin-arm64\` | \`macos-26\` | \`$(basename "${{ steps.darwin-artifact.outputs.darwin_jar }}")\` | \`${{ steps.darwin-artifact.outputs.darwin_sha }}\` | Published |" } >> "$GITHUB_STEP_SUMMARY" @@ -444,6 +537,7 @@ jobs: needs: [ prepare-release, + build-linux-arm64-classifier, build-windows-classifier, build-darwin-classifier, deploy-maven, diff --git a/.github/workflows/java-publish-snapshot.yml b/.github/workflows/java-publish-snapshot.yml index 3b67f7c1b1..3b15c3f0dc 100644 --- a/.github/workflows/java-publish-snapshot.yml +++ b/.github/workflows/java-publish-snapshot.yml @@ -34,6 +34,69 @@ jobs: SHA=$(git rev-parse HEAD) echo "sha=$SHA" >> "$GITHUB_OUTPUT" + build-linux-arm64-classifier: + name: Build Linux ARM64 snapshot classifier + needs: resolve-source + runs-on: ubuntu-24.04-arm + permissions: + contents: read + outputs: + version: ${{ steps.build.outputs.version }} + defaults: + run: + shell: bash + working-directory: ./java + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + ref: ${{ github.sha }} + fetch-depth: 1 + persist-credentials: false + + - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 + with: + java-version: "25" + distribution: "microsoft" + cache: "maven" + + - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 + with: + node-version: 22 + + - name: Build and validate linux-arm64 classifier + id: build + run: | + set -euo pipefail + SOURCE_COMMIT=$(git rev-parse HEAD) + if [ "$SOURCE_COMMIT" != "${{ needs.resolve-source.outputs.source_sha }}" ]; then + echo "::error::Checked out $SOURCE_COMMIT instead of the resolved snapshot source." + exit 1 + fi + node copilot-native/scripts/validate-native-host.mjs linux-arm64 + mvn -B -pl copilot-native package -DskipTests -Dcopilot.native.libc=glibc + VERSION=$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout) + JAR="copilot-native/target/copilot-sdk-java-runtime-$VERSION-linux-arm64.jar" + PRIMARY_JAR="copilot-native/target/copilot-sdk-java-runtime-$VERSION.jar" + test -f "$JAR" + node copilot-native/scripts/validate-native-artifact.mjs \ + classifier linux-arm64 "$JAR" "$(basename "$JAR")" .. + node copilot-native/scripts/validate-native-artifact.mjs placeholder "$PRIMARY_JAR" + MANIFEST="copilot-native/target/linux-arm64-$VERSION.sha256" + HASH=$(sha256sum "$JAR" | cut -d ' ' -f 1) + printf '%s %s' "$HASH" "$(basename "$JAR")" > "$MANIFEST" + node copilot-native/scripts/validate-native-artifact.mjs \ + checksum "$JAR" "$MANIFEST" "$(basename "$JAR")" + echo "version=$VERSION" >> "$GITHUB_OUTPUT" + + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 + with: + name: java-native-linux-arm64-snapshot-${{ github.run_id }}-${{ github.run_attempt }} + path: | + java/copilot-native/target/copilot-sdk-java-runtime-${{ steps.build.outputs.version }}-linux-arm64.jar + java/copilot-native/target/linux-arm64-${{ steps.build.outputs.version }}.sha256 + if-no-files-found: error + retention-days: 1 + build-windows-classifier: name: Build Windows snapshot classifier needs: resolve-source @@ -160,7 +223,13 @@ jobs: deploy-snapshot: name: Publish SNAPSHOT to Maven Central - needs: [resolve-source, build-windows-classifier, build-darwin-classifier] + needs: + [ + resolve-source, + build-linux-arm64-classifier, + build-windows-classifier, + build-darwin-classifier, + ] runs-on: ubuntu-latest defaults: run: @@ -188,6 +257,11 @@ jobs: with: node-version: 22 + - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 + with: + name: java-native-linux-arm64-snapshot-${{ github.run_id }}-${{ github.run_attempt }} + path: ${{ runner.temp }}/java-native-linux-arm64 + - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 with: name: java-native-win32-x64-snapshot-${{ github.run_id }}-${{ github.run_attempt }} @@ -198,8 +272,8 @@ jobs: name: java-native-darwin-arm64-snapshot-${{ github.run_id }}-${{ github.run_attempt }} path: ${{ runner.temp }}/java-native-darwin-arm64 - - name: Verify version, source, and Windows classifier - id: windows-artifact + - name: Verify version, source, and Linux ARM64 classifier + id: linux-arm64-artifact run: | SOURCE_COMMIT=$(git rev-parse HEAD) if [ "$SOURCE_COMMIT" != "${{ needs.resolve-source.outputs.source_sha }}" ]; then @@ -211,6 +285,32 @@ jobs: echo "::error::This workflow only publishes SNAPSHOT versions. Current version: $VERSION" exit 1 fi + if [ "$VERSION" != "${{ needs.build-linux-arm64-classifier.outputs.version }}" ]; then + echo "::error::Linux ARM64 classifier version does not match deploy version." + exit 1 + fi + ARTIFACT_DIRECTORY="${{ runner.temp }}/java-native-linux-arm64" + JAR="$ARTIFACT_DIRECTORY/copilot-sdk-java-runtime-$VERSION-linux-arm64.jar" + MANIFEST="$ARTIFACT_DIRECTORY/linux-arm64-$VERSION.sha256" + test -f "$JAR" + test -f "$MANIFEST" + node "$GITHUB_WORKSPACE/java/copilot-native/scripts/validate-native-artifact.mjs" \ + checksum "$JAR" "$MANIFEST" "$(basename "$JAR")" + node "$GITHUB_WORKSPACE/java/copilot-native/scripts/validate-native-artifact.mjs" \ + classifier linux-arm64 "$JAR" "$(basename "$JAR")" "$GITHUB_WORKSPACE" + echo "linux_arm64_jar=$JAR" >> "$GITHUB_OUTPUT" + echo "version=$VERSION" >> "$GITHUB_OUTPUT" + echo "linux_arm64_sha=$(cut -d ' ' -f 1 "$MANIFEST")" >> "$GITHUB_OUTPUT" + + - name: Verify version, source, and Windows classifier + id: windows-artifact + run: | + SOURCE_COMMIT=$(git rev-parse HEAD) + if [ "$SOURCE_COMMIT" != "${{ needs.resolve-source.outputs.source_sha }}" ]; then + echo "::error::Checked out $SOURCE_COMMIT instead of the resolved snapshot source." + exit 1 + fi + VERSION="${{ steps.linux-arm64-artifact.outputs.version }}" if [ "$VERSION" != "${{ needs.build-windows-classifier.outputs.version }}" ]; then echo "::error::Windows classifier version does not match deploy version." exit 1 @@ -257,6 +357,7 @@ jobs: run: | VERSION="${{ steps.windows-artifact.outputs.version }}" mvn -B deploy -DskipTests -Dcopilot.native.libc=glibc \ + "-Dcopilot.native.external.linux.arm64.classifier.path=${{ steps.linux-arm64-artifact.outputs.linux_arm64_jar }}" \ "-Dcopilot.native.external.win32.classifier.path=${{ steps.windows-artifact.outputs.windows_jar }}" \ "-Dcopilot.native.external.darwin.classifier.path=${{ steps.darwin-artifact.outputs.darwin_jar }}" LINUX_JAR="copilot-native/target/copilot-sdk-java-runtime-$VERSION-linux-x64.jar" @@ -292,6 +393,7 @@ jobs: echo "| Classifier | Build runner | Artifact | SHA-256 | Status |" echo "| --- | --- | --- | --- | --- |" echo "| \`linux-x64\` | \`ubuntu-latest\` | \`$(basename "$LINUX_JAR")\` | \`$LINUX_SHA\` | Published |" + echo "| \`linux-arm64\` | \`ubuntu-24.04-arm\` | \`$(basename "${{ steps.linux-arm64-artifact.outputs.linux_arm64_jar }}")\` | \`${{ steps.linux-arm64-artifact.outputs.linux_arm64_sha }}\` | Published |" echo "| \`win32-x64\` | \`windows-latest\` | \`$(basename "${{ steps.windows-artifact.outputs.windows_jar }}")\` | \`${{ steps.windows-artifact.outputs.windows_sha }}\` | Published |" echo "| \`darwin-arm64\` | \`macos-26\` | \`$(basename "${{ steps.darwin-artifact.outputs.darwin_jar }}")\` | \`${{ steps.darwin-artifact.outputs.darwin_sha }}\` | Published |" } >> "$GITHUB_STEP_SUMMARY" diff --git a/.github/workflows/java-sdk-tests.yml b/.github/workflows/java-sdk-tests.yml index 5f3d7377a0..48fb2174a3 100644 --- a/.github/workflows/java-sdk-tests.yml +++ b/.github/workflows/java-sdk-tests.yml @@ -26,6 +26,9 @@ jobs: include: - os: ubuntu-latest classifier: linux-x64 + - os: ubuntu-24.04-arm + classifier: linux-arm64 + maven-args: -Dcopilot.native.libc=glibc - os: windows-latest classifier: win32-x64 - os: macos-26 @@ -55,7 +58,7 @@ jobs: - name: Run Java SDK tests (InProcess) env: CI: "true" - run: mvn clean verify -Pinprocess + run: mvn clean verify -Pinprocess ${{ matrix.maven-args }} - name: Generate Test Report Summary if: always() @@ -74,6 +77,64 @@ jobs: java/sdk/target/failsafe-reports/ retention-days: 7 + java-native-publication-linux-arm64: + name: "Java Native Publication Input (linux-arm64)" + if: github.event.repository.fork == false + runs-on: ubuntu-24.04-arm + permissions: + contents: read + outputs: + source_sha: ${{ steps.build.outputs.source_sha }} + version: ${{ steps.build.outputs.version }} + defaults: + run: + shell: bash + working-directory: ./java + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + + - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 + with: + java-version: "25" + distribution: "microsoft" + cache: "maven" + + - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 + with: + node-version: 22 + + - name: Build and validate linux-arm64 classifier + id: build + run: | + set -euo pipefail + node copilot-native/scripts/validate-native-host.mjs linux-arm64 + mvn -B -pl copilot-native package -DskipTests -Dcopilot.native.libc=glibc + VERSION=$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout) + JAR="copilot-native/target/copilot-sdk-java-runtime-$VERSION-linux-arm64.jar" + PRIMARY_JAR="copilot-native/target/copilot-sdk-java-runtime-$VERSION.jar" + test -f "$JAR" + node copilot-native/scripts/validate-native-artifact.mjs \ + classifier linux-arm64 "$JAR" "$(basename "$JAR")" .. + node copilot-native/scripts/validate-native-artifact.mjs placeholder "$PRIMARY_JAR" + MANIFEST="copilot-native/target/linux-arm64-$VERSION.sha256" + HASH=$(sha256sum "$JAR" | cut -d ' ' -f 1) + printf '%s %s' "$HASH" "$(basename "$JAR")" > "$MANIFEST" + node copilot-native/scripts/validate-native-artifact.mjs \ + checksum "$JAR" "$MANIFEST" "$(basename "$JAR")" + echo "source_sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT" + echo "version=$VERSION" >> "$GITHUB_OUTPUT" + + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 + with: + name: java-native-publication-linux-arm64-${{ github.run_id }}-${{ github.run_attempt }} + path: | + java/copilot-native/target/copilot-sdk-java-runtime-${{ steps.build.outputs.version }}-linux-arm64.jar + java/copilot-native/target/linux-arm64-${{ steps.build.outputs.version }}.sha256 + if-no-files-found: error + retention-days: 1 + java-native-publication-windows: name: "Java Native Publication Input (win32-x64)" if: github.event.repository.fork == false @@ -192,7 +253,12 @@ jobs: java-native-publication-assembly: name: "Java Native Publication Assembly" if: github.event.repository.fork == false - needs: [java-native-publication-windows, java-native-publication-darwin] + needs: + [ + java-native-publication-linux-arm64, + java-native-publication-windows, + java-native-publication-darwin, + ] runs-on: ubuntu-latest defaults: run: @@ -214,6 +280,11 @@ jobs: with: node-version: 22 + - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 + with: + name: java-native-publication-linux-arm64-${{ github.run_id }}-${{ github.run_attempt }} + path: ${{ github.workspace }}/java/native-publication-input/linux-arm64 + - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 with: name: java-native-publication-win32-x64-${{ github.run_id }}-${{ github.run_attempt }} @@ -227,16 +298,25 @@ jobs: - name: Verify native inputs and deploy the complete local release run: | set -euo pipefail + test "$(git rev-parse HEAD)" = "${{ needs.java-native-publication-linux-arm64.outputs.source_sha }}" test "$(git rev-parse HEAD)" = "${{ needs.java-native-publication-windows.outputs.source_sha }}" test "$(git rev-parse HEAD)" = "${{ needs.java-native-publication-darwin.outputs.source_sha }}" VERSION="${{ needs.java-native-publication-windows.outputs.version }}" + test "$VERSION" = "${{ needs.java-native-publication-linux-arm64.outputs.version }}" test "$VERSION" = "${{ needs.java-native-publication-darwin.outputs.version }}" + LINUX_ARM64_DIRECTORY="$GITHUB_WORKSPACE/java/native-publication-input/linux-arm64" WINDOWS_DIRECTORY="$GITHUB_WORKSPACE/java/native-publication-input/windows" DARWIN_DIRECTORY="$GITHUB_WORKSPACE/java/native-publication-input/darwin" + LINUX_ARM64_JAR="$LINUX_ARM64_DIRECTORY/copilot-sdk-java-runtime-$VERSION-linux-arm64.jar" + LINUX_ARM64_MANIFEST="$LINUX_ARM64_DIRECTORY/linux-arm64-$VERSION.sha256" WINDOWS_JAR="$WINDOWS_DIRECTORY/copilot-sdk-java-runtime-$VERSION-win32-x64.jar" WINDOWS_MANIFEST="$WINDOWS_DIRECTORY/win32-x64-$VERSION.sha256" DARWIN_JAR="$DARWIN_DIRECTORY/copilot-sdk-java-runtime-$VERSION-darwin-arm64.jar" DARWIN_MANIFEST="$DARWIN_DIRECTORY/darwin-arm64-$VERSION.sha256" + node copilot-native/scripts/validate-native-artifact.mjs \ + checksum "$LINUX_ARM64_JAR" "$LINUX_ARM64_MANIFEST" "$(basename "$LINUX_ARM64_JAR")" + node copilot-native/scripts/validate-native-artifact.mjs \ + classifier linux-arm64 "$LINUX_ARM64_JAR" "$(basename "$LINUX_ARM64_JAR")" .. node copilot-native/scripts/validate-native-artifact.mjs \ checksum "$WINDOWS_JAR" "$WINDOWS_MANIFEST" "$(basename "$WINDOWS_JAR")" node copilot-native/scripts/validate-native-artifact.mjs \ @@ -256,6 +336,7 @@ jobs: mvn -B -pl copilot-native deploy -Prelease -DskipTests \ -Dcopilot.native.libc=glibc \ -Dcopilot.native.test.local.publication=true \ + "-Dcopilot.native.external.linux.arm64.classifier.path=$LINUX_ARM64_JAR" \ "-Dcopilot.native.external.win32.classifier.path=$WINDOWS_JAR" \ "-Dcopilot.native.external.darwin.classifier.path=$DARWIN_JAR" \ "-Dmaven.repo.local=$LOCAL_REPOSITORY" diff --git a/java/README.md b/java/README.md index d7d6bdf269..e699c67369 100644 --- a/java/README.md +++ b/java/README.md @@ -72,7 +72,7 @@ implementation 'com.github:copilot-sdk-java:1.0.14-preview.1-SNAPSHOT' ## In-process mode (experimental) -The SDK supports running the Copilot runtime **in-process** as a native library instead of spawning a separate CLI process. This eliminates process management overhead and simplifies deployment. In-process mode is currently experimental and supported on **linux-x64** (glibc), **win32-x64**, and **darwin-arm64**. +The SDK supports running the Copilot runtime **in-process** as a native library instead of spawning a separate CLI process. This eliminates process management overhead and simplifies deployment. In-process mode is currently experimental and supported on **linux-x64** (glibc), **linux-arm64** (glibc), **win32-x64**, and **darwin-arm64**. Because in-process mode is experimental, see the [Using experimental APIs](#using-experimental-apis) section for how to opt in. @@ -95,7 +95,7 @@ Add both the SDK and the platform-specific native runtime to your project: ${copilot.version} linux-x64 - + net.java.dev.jna @@ -511,7 +511,7 @@ mvn jacoco:prepare-agent@wire-up-coverage-instrumentation antrun:run@print-test- Run native-runtime Maven commands from the `java` directory. Native packaging requires Node.js and npm in addition to JDK 25 and Maven because `copilot-native/scripts/fetch-native.mjs` retrieves the pinned npm runtime package. -On a native Linux x64 glibc host, Maven activates the `native-linux-x64` profile when `copilot.native.libc=glibc` is set. On Windows x64, Maven activates `native-win32-x64` automatically. On Apple Silicon macOS, Maven activates `native-darwin-arm64` automatically. The matching profile validates the host, runs the native script tests, fetches the pinned `@github/copilot-` package during `generate-resources`, packages the classifier JAR during `package`, and verifies its native contents. Ensure npm can authenticate to the package registry before running the build. +On a native Linux glibc host, Maven activates `native-linux-x64` or `native-linux-arm64` for the matching architecture when `copilot.native.libc=glibc` is set. On Windows x64 and Apple Silicon macOS, Maven activates `native-win32-x64` or `native-darwin-arm64` automatically. The matching profile validates the host, runs the native script tests, fetches the pinned `@github/copilot-` package during `generate-resources`, packages the classifier JAR during `package`, and verifies its native contents. Ensure npm can authenticate to the package registry before running the build. Before opting in, validate that Node.js reports glibc for the build host: @@ -539,7 +539,14 @@ node copilot-native/scripts/validate-native-host.mjs darwin-arm64 mvn -Pinprocess clean verify ``` -On Intel macOS, Linux ARM64, Linux x64 musl, and other unsupported hosts, do not set `copilot.native.libc=glibc`. A normal build produces only the OS-neutral primary, sources, and Javadoc JARs; it does not run native script tests, download or stage native files, or produce a platform classifier JAR. +The same command validates in-process mode on Linux ARM64: + +```bash +node copilot-native/scripts/validate-native-host.mjs linux-arm64 +mvn -Pinprocess clean verify -Dcopilot.native.libc=glibc +``` + +On Intel macOS, Linux musl, and other unsupported hosts, do not set `copilot.native.libc=glibc`. A normal build produces only the OS-neutral primary, sources, and Javadoc JARs; it does not run native script tests, download or stage native files, or produce a platform classifier JAR. To build only the OS-neutral artifacts on any host, or override the glibc opt-in, disable native download and packaging: @@ -557,7 +564,7 @@ mvn clean verify -Dcopilot.native.libc=glibc mvn clean package -pl copilot-native -DskipTests -Dcopilot.native.libc=glibc -Dcopilot.native.skip.download=true ``` -On Linux x64, the classifier JAR contains `native/linux-x64/runtime.node`, `native/linux-x64/platform.properties`, and `native/linux-x64/copilot`. On Windows x64, it contains `native/win32-x64/runtime.node`, `native/win32-x64/platform.properties`, and `native/win32-x64/copilot.exe`. On Apple Silicon macOS, it contains `native/darwin-arm64/runtime.node`, `native/darwin-arm64/platform.properties`, and `native/darwin-arm64/copilot`. The placeholder JAR remains OS-neutral and contains no native binaries. Unsupported hosts retain the placeholder-only behavior. +On Linux, the classifier JAR contains `runtime.node`, `platform.properties`, and `copilot` under `native/linux-x64` or `native/linux-arm64`. On Windows x64, it contains those resources under `native/win32-x64`, with the CLI named `copilot.exe`. On Apple Silicon macOS, it contains them under `native/darwin-arm64`. The placeholder JAR remains OS-neutral and contains no native binaries. Unsupported hosts retain the placeholder-only behavior. ## License diff --git a/java/copilot-native/pom.xml b/java/copilot-native/pom.xml index 4ad0e02929..48f11e4404 100644 --- a/java/copilot-native/pom.xml +++ b/java/copilot-native/pom.xml @@ -235,9 +235,9 @@ inprocess @@ -351,6 +351,65 @@ + + native-linux-arm64 + + + Linux + aarch64 + + + copilot.native.libc + glibc + + + + linux-arm64 + copilot + + + + + org.codehaus.mojo + exec-maven-plugin + + + validate-native-host + validate + + + fetch-native + generate-resources + + + test-fetch-native + test + + + + + org.apache.maven.plugins + maven-jar-plugin + + + jar-native + package + + + + + org.apache.maven.plugins + maven-antrun-plugin + + + verify-native-jars + package + + + + + + native-win32-x64 @@ -461,6 +520,68 @@ + + + attach-external-linux-arm64-classifier + + + copilot.native.external.linux.arm64.classifier.path + + + + + + org.codehaus.mojo + exec-maven-plugin + + + validate-external-linux-arm64-classifier + validate + + exec + + + node + + ${project.basedir}/scripts/validate-native-artifact.mjs + classifier + linux-arm64 + ${copilot.native.external.linux.arm64.classifier.path} + ${project.build.finalName}-linux-arm64.jar + ${copilot.sdk.root} + + + + + + + org.codehaus.mojo + build-helper-maven-plugin + + + attach-external-linux-arm64-classifier + package + + attach-artifact + + + + + ${copilot.native.external.linux.arm64.classifier.path} + jar + linux-arm64 + + + + + + + + + + net.java.dev.jna @@ -511,7 +511,7 @@ mvn jacoco:prepare-agent@wire-up-coverage-instrumentation antrun:run@print-test- Run native-runtime Maven commands from the `java` directory. Native packaging requires Node.js and npm in addition to JDK 25 and Maven because `copilot-native/scripts/fetch-native.mjs` retrieves the pinned npm runtime package. -On a native Linux glibc host, Maven activates `native-linux-x64` or `native-linux-arm64` for the matching architecture when `copilot.native.libc=glibc` is set. On Windows x64 and Apple Silicon macOS, Maven activates `native-win32-x64` or `native-darwin-arm64` automatically. The matching profile validates the host, runs the native script tests, fetches the pinned `@github/copilot-` package during `generate-resources`, packages the classifier JAR during `package`, and verifies its native contents. Ensure npm can authenticate to the package registry before running the build. +On a native Linux glibc host, Maven activates `native-linux-x64` or `native-linux-arm64` for the matching architecture when `copilot.native.libc=glibc` is set. On Windows x64, Windows ARM64, and Apple Silicon macOS, Maven activates `native-win32-x64`, `native-win32-arm64`, or `native-darwin-arm64` automatically. The matching profile validates the host, runs the native script tests, fetches the pinned `@github/copilot-` package during `generate-resources`, packages the classifier JAR during `package`, and verifies its native contents. Ensure npm can authenticate to the package registry before running the build. Before opting in, validate that Node.js reports glibc for the build host: @@ -526,7 +526,7 @@ The `inprocess` test profile performs the same validation and native packaging a mvn -Pinprocess clean verify ``` -On Windows PowerShell, initialize Java and run the same profile: +On Windows x64 or ARM64 PowerShell, initialize Java and run the same profile: ```powershell mvn -Pinprocess clean verify @@ -564,7 +564,7 @@ mvn clean verify -Dcopilot.native.libc=glibc mvn clean package -pl copilot-native -DskipTests -Dcopilot.native.libc=glibc -Dcopilot.native.skip.download=true ``` -On Linux, the classifier JAR contains `runtime.node`, `platform.properties`, and `copilot` under `native/linux-x64` or `native/linux-arm64`. On Windows x64, it contains those resources under `native/win32-x64`, with the CLI named `copilot.exe`. On Apple Silicon macOS, it contains them under `native/darwin-arm64`. The placeholder JAR remains OS-neutral and contains no native binaries. Unsupported hosts retain the placeholder-only behavior. +On Linux, the classifier JAR contains `runtime.node`, `platform.properties`, and `copilot` under `native/linux-x64` or `native/linux-arm64`. On Windows, it contains those resources under `native/win32-x64` or `native/win32-arm64`, with the CLI named `copilot.exe`. On Apple Silicon macOS, it contains them under `native/darwin-arm64`. The placeholder JAR remains OS-neutral and contains no native binaries. Unsupported hosts retain the placeholder-only behavior. ## License diff --git a/java/copilot-native/pom.xml b/java/copilot-native/pom.xml index 48f11e4404..f3694457d8 100644 --- a/java/copilot-native/pom.xml +++ b/java/copilot-native/pom.xml @@ -235,8 +235,8 @@ @@ -465,6 +465,61 @@ + + native-win32-arm64 + + + Windows + aarch64 + + + + win32-arm64 + copilot.exe + + + + + org.codehaus.mojo + exec-maven-plugin + + + validate-native-host + validate + + + fetch-native + generate-resources + + + test-fetch-native + test + + + + + org.apache.maven.plugins + maven-jar-plugin + + + jar-native + package + + + + + org.apache.maven.plugins + maven-antrun-plugin + + + verify-native-jars + package + + + + + + native-darwin-arm64 @@ -583,8 +638,8 @@ @@ -645,9 +700,71 @@ + + + attach-external-win32-arm64-classifier + + + copilot.native.external.win32.arm64.classifier.path + + + + + + org.codehaus.mojo + exec-maven-plugin + + + validate-external-win32-arm64-classifier + validate + + exec + + + node + + ${project.basedir}/scripts/validate-native-artifact.mjs + classifier + win32-arm64 + ${copilot.native.external.win32.arm64.classifier.path} + ${project.build.finalName}-win32-arm64.jar + ${copilot.sdk.root} + + + + + + + org.codehaus.mojo + build-helper-maven-plugin + + + attach-external-win32-arm64-classifier + package + + attach-artifact + + + + + ${copilot.native.external.win32.arm64.classifier.path} + jar + win32-arm64 + + + + + + + + + attach-external-darwin-classifier diff --git a/java/copilot-native/scripts/fetch-native.test.mjs b/java/copilot-native/scripts/fetch-native.test.mjs index a2e7ad2a0f..3ca1e2fde6 100644 --- a/java/copilot-native/scripts/fetch-native.test.mjs +++ b/java/copilot-native/scripts/fetch-native.test.mjs @@ -17,7 +17,7 @@ const runtimeContent = 'runtime content'; const cliContent = 'cli content'; const scriptPath = fileURLToPath(new URL('./fetch-native.mjs', import.meta.url)); -for (const classifier of ['linux-x64', 'linux-arm64', 'win32-x64', 'darwin-arm64']) { +for (const classifier of ['linux-x64', 'linux-arm64', 'win32-x64', 'win32-arm64', 'darwin-arm64']) { test(`${classifier}: missing CLI does not use incremental fast path`, (t) => { const fixture = createFixture(t, classifier); fs.rmSync(fixture.cliPath); diff --git a/java/copilot-native/scripts/validate-local-publication.mjs b/java/copilot-native/scripts/validate-local-publication.mjs index a2586f6c14..c83f9682dd 100644 --- a/java/copilot-native/scripts/validate-local-publication.mjs +++ b/java/copilot-native/scripts/validate-local-publication.mjs @@ -33,6 +33,7 @@ export function validateLocalPublication({ `${artifactId}-${version}-linux-x64.jar`, `${artifactId}-${version}-linux-arm64.jar`, `${artifactId}-${version}-win32-x64.jar`, + `${artifactId}-${version}-win32-arm64.jar`, `${artifactId}-${version}-darwin-arm64.jar`, ]; const files = new Set(fs.readdirSync(artifactDirectory)); @@ -67,6 +68,7 @@ export function validateLocalPublication({ "linux-x64", "linux-arm64", "win32-x64", + "win32-arm64", "darwin-arm64", ]) { const filename = `${artifactId}-${version}-${classifier}.jar`; diff --git a/java/copilot-native/scripts/validate-native-artifact.test.mjs b/java/copilot-native/scripts/validate-native-artifact.test.mjs index f84b4add61..eb99d432db 100644 --- a/java/copilot-native/scripts/validate-native-artifact.test.mjs +++ b/java/copilot-native/scripts/validate-native-artifact.test.mjs @@ -43,6 +43,36 @@ test("accepts a matching, complete Windows classifier", (t) => { ); }); +test("accepts a matching, complete Windows ARM64 classifier", (t) => { + const fixture = createFixture(t); + const windowsArm64Classifier = "win32-arm64"; + const windowsArm64ArtifactName = + "copilot-sdk-java-runtime-1.2.3-win32-arm64.jar"; + const windowsArm64JarPath = path.join( + fixture.root, + windowsArm64ArtifactName, + ); + createNativeClassifierTestFixture({ + classifier: windowsArm64Classifier, + outputPath: windowsArm64JarPath, + repoRoot: fixture.repoRoot, + }); + + assert.deepEqual( + validateNativeClassifierJar({ + classifier: windowsArm64Classifier, + jarPath: windowsArm64JarPath, + expectedFilename: windowsArm64ArtifactName, + repoRoot: fixture.repoRoot, + }), + { + classifier: windowsArm64Classifier, + nativeVersion: "9.8.10", + sha256: undefined, + }, + ); +}); + test("accepts a matching, complete Darwin classifier", (t) => { const fixture = createFixture(t); const darwinClassifier = "darwin-arm64"; @@ -328,6 +358,7 @@ test("validates one complete signed local publication", (t) => { const linuxJar = `${artifactId}-${version}-linux-x64.jar`; const linuxArm64Jar = `${artifactId}-${version}-linux-arm64.jar`; const windowsJar = `${artifactId}-${version}-win32-x64.jar`; + const windowsArm64Jar = `${artifactId}-${version}-win32-arm64.jar`; const darwinJar = `${artifactId}-${version}-darwin-arm64.jar`; writeStoredZip(path.join(publicationDirectory, primaryJar), [ ["META-INF/MANIFEST.MF", "Manifest-Version: 1.0\n"], @@ -355,6 +386,11 @@ test("validates one complete signed local publication", (t) => { outputPath: path.join(publicationDirectory, windowsJar), repoRoot: fixture.repoRoot, }); + createNativeClassifierTestFixture({ + classifier: "win32-arm64", + outputPath: path.join(publicationDirectory, windowsArm64Jar), + repoRoot: fixture.repoRoot, + }); createNativeClassifierTestFixture({ classifier: "darwin-arm64", outputPath: path.join(publicationDirectory, darwinJar), @@ -372,6 +408,7 @@ test("validates one complete signed local publication", (t) => { linuxJar, linuxArm64Jar, windowsJar, + windowsArm64Jar, darwinJar, ]) { fs.writeFileSync( @@ -446,6 +483,14 @@ test("local publication validation rejects cross-classifier contamination", (t) ), repoRoot: fixture.repoRoot, }); + createNativeClassifierTestFixture({ + classifier: "win32-arm64", + outputPath: path.join( + publicationDirectory, + `${artifactId}-${version}-win32-arm64.jar`, + ), + repoRoot: fixture.repoRoot, + }); createNativeClassifierTestFixture({ classifier: "darwin-arm64", outputPath: path.join( @@ -496,6 +541,7 @@ function createFixture(t) { JSON.stringify({ packages: { "node_modules/@github/copilot-win32-x64": { version: "9.8.7" }, + "node_modules/@github/copilot-win32-arm64": { version: "9.8.10" }, "node_modules/@github/copilot-linux-x64": { version: "9.8.6" }, "node_modules/@github/copilot-linux-arm64": { version: "9.8.9" }, "node_modules/@github/copilot-darwin-arm64": { version: "9.8.8" }, diff --git a/java/copilot-native/scripts/validate-native-host.mjs b/java/copilot-native/scripts/validate-native-host.mjs index a07dddee34..a0b7a70047 100644 --- a/java/copilot-native/scripts/validate-native-host.mjs +++ b/java/copilot-native/scripts/validate-native-host.mjs @@ -21,10 +21,12 @@ export function validateNativeHost(classifier, host) { return `Validated native build host: ${classifier} (glibc ${host.glibcVersionRuntime})`; } - if (classifier === "win32-x64") { - if (host.platform !== "win32" || host.arch !== "x64") { + if (classifier === "win32-x64" || classifier === "win32-arm64") { + const expectedArch = classifier === "win32-x64" ? "x64" : "arm64"; + const displayArch = expectedArch === "x64" ? "x64" : "ARM64"; + if (host.platform !== "win32" || host.arch !== expectedArch) { throw new Error( - `Native ${classifier} packaging requires Windows x64; detected ${host.platform}-${host.arch}`, + `Native ${classifier} packaging requires Windows ${displayArch}; detected ${host.platform}-${host.arch}`, ); } return `Validated native build host: ${classifier}`; diff --git a/java/copilot-native/scripts/validate-native-host.test.mjs b/java/copilot-native/scripts/validate-native-host.test.mjs index 1a71eff44f..cb3429489c 100644 --- a/java/copilot-native/scripts/validate-native-host.test.mjs +++ b/java/copilot-native/scripts/validate-native-host.test.mjs @@ -40,6 +40,17 @@ test("accepts Windows x64 without a libc requirement", () => { ); }); +test("accepts Windows ARM64 without a libc requirement", () => { + assert.equal( + validateNativeHost("win32-arm64", { + platform: "win32", + arch: "arm64", + glibcVersionRuntime: undefined, + }), + "Validated native build host: win32-arm64", + ); +}); + test("accepts macOS ARM64 without a libc requirement", () => { assert.equal( validateNativeHost("darwin-arm64", { @@ -135,6 +146,18 @@ test("rejects Windows ARM64 for the Windows x64 classifier", () => { ); }); +test("rejects Windows x64 for the Windows ARM64 classifier", () => { + assert.throws( + () => + validateNativeHost("win32-arm64", { + platform: "win32", + arch: "x64", + glibcVersionRuntime: undefined, + }), + /requires Windows ARM64/, + ); +}); + test("rejects a non-macOS host for the macOS classifier", () => { assert.throws( () => diff --git a/java/docs/adr/adr-007-native-bundling-strategy.md b/java/docs/adr/adr-007-native-bundling-strategy.md index 78276f3481..1540829366 100644 --- a/java/docs/adr/adr-007-native-bundling-strategy.md +++ b/java/docs/adr/adr-007-native-bundling-strategy.md @@ -208,9 +208,9 @@ If none succeeds, startup fails. The PATH fallback does not claim to support eve ### Current platform scope -The platform detector recognizes the 8 classifiers listed in this ADR. The Maven build binds native packaging only for a host matching an implemented classifier. Linux x64 and ARM64 glibc hosts can opt in with `copilot.native.libc=glibc`, while Windows x64 and Apple Silicon macOS hosts package `win32-x64` or `darwin-arm64` automatically. The `inprocess` test profile selects the matching implemented classifier automatically on all four hosts. Every path validates the host before downloading or packaging native files. Linux musl and other unsupported hosts build only the OS-neutral placeholder, sources, and Javadoc artifacts unless they explicitly request in-process tests, which fail during host validation. Additional classifier artifacts remain follow-up work. +The platform detector recognizes the 8 classifiers listed in this ADR. The Maven build binds native packaging only for a host matching an implemented classifier. Linux x64 and ARM64 glibc hosts can opt in with `copilot.native.libc=glibc`, while Windows x64, Windows ARM64, and Apple Silicon macOS hosts package `win32-x64`, `win32-arm64`, or `darwin-arm64` automatically. The `inprocess` test profile selects the matching implemented classifier automatically on all five hosts. Every path validates the host before downloading or packaging native files. Linux musl and other unsupported hosts build only the OS-neutral placeholder, sources, and Javadoc artifacts unless they explicitly request in-process tests, which fail during host validation. Additional classifier artifacts remain follow-up work. -Maven Central release and snapshot workflows build each classifier on its matching native host from the same immutable source. The Linux ARM64, Windows, and macOS jobs each upload only their verified classifier and checksum manifest. The Ubuntu x64 job verifies and attaches all three, builds `linux-x64` with the glibc opt-in, and performs the only Maven deployment. Release signing therefore covers the neutral artifacts and all four classifiers in one deployment. +Maven Central release and snapshot workflows build each classifier on its matching native host from the same immutable source. The Linux ARM64, Windows x64, Windows ARM64, and macOS jobs each upload only their verified classifier and checksum manifest. The Ubuntu x64 job verifies and attaches all four, builds `linux-x64` with the glibc opt-in, and performs the only Maven deployment. Release signing therefore covers the neutral artifacts and all five classifiers in one deployment. ## Binding technology: JNA over Panama FFM @@ -362,7 +362,7 @@ The pattern follows DJL's `LibUtils.loadLibrary()` approach: detect the platform 3. Extracts `runtime.node` and the transitional CLI entrypoint into `~/.copilot/runtime-cache/` if valid cached files are not already present. 4. Loads it via [JNA](#references) using the C ABI entry points, per the [binding technology decision](#binding-technology-jna-over-panama-ffm) above. The JNA-specific code is confined behind an internal binding interface to preserve a future FFM migration path. * A validated supported-host profile fetches the pinned matching `@github/copilot-` npm package, verifies its SHA-512 integrity from `nodejs/package-lock.json`, and packages the version-matched runtime and CLI files. -* The current release work publishes the `linux-x64`, `linux-arm64`, `win32-x64`, and `darwin-arm64` classifiers. The planned classifier set expands to the other detected platforms. +* The current release work publishes the `linux-x64`, `linux-arm64`, `win32-x64`, `win32-arm64`, and `darwin-arm64` classifiers. The planned classifier set expands to the other detected platforms. * Adding an implemented platform requires validated host activation, a profile that supplies the classifier and platform CLI filename, and lifecycle bindings for the shared host validation, fetch, script test, package, and verification executions. * `cli-native.node` is not bundled. It provides terminal UI features that are irrelevant to the Java SDK's programmatic API surface. diff --git a/java/sdk/pom.xml b/java/sdk/pom.xml index 6bad3d2f6e..4f9ece6354 100644 --- a/java/sdk/pom.xml +++ b/java/sdk/pom.xml @@ -708,6 +708,18 @@ did not produce the multi-release output. Re-build on JDK 25+ and verify the win32-x64 + + native-win32-arm64 + + + Windows + aarch64 + + + + win32-arm64 + + native-darwin-arm64 From 3ce052769fb00c24587c639b892d9d31dbf995fc Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 27 Aug 2026 22:55:19 +0000 Subject: [PATCH 27/32] Update @github/copilot to 1.0.81 (#2430) * Update @github/copilot to 1.0.81 - Updated nodejs and test harness dependencies - Re-ran code generators - Formatted generated code * Adapt handwritten tests to CLI 1.0.81 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 80bb8114-ee7c-47fd-a08f-d641039f2f64 * Refresh replay snapshots for CLI file output Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 80bb8114-ee7c-47fd-a08f-d641039f2f64 * Adapt GitHub MCP initialization expectation Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 80bb8114-ee7c-47fd-a08f-d641039f2f64 * Stabilize eager GitHub MCP assertion Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 80bb8114-ee7c-47fd-a08f-d641039f2f64 * Preserve GitHub MCP first-turn assertion Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 80bb8114-ee7c-47fd-a08f-d641039f2f64 * Wait for restorable rewind points Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 80bb8114-ee7c-47fd-a08f-d641039f2f64 * Apply Rust rewind test formatting Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 80bb8114-ee7c-47fd-a08f-d641039f2f64 * Skip rewind E2E on Windows for CLI 1.0.81 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 80bb8114-ee7c-47fd-a08f-d641039f2f64 --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Matthew Rayermann Copilot-Session: 80bb8114-ee7c-47fd-a08f-d641039f2f64 --- dotnet/src/Generated/Rpc.cs | 151 +- dotnet/src/Generated/SessionEvents.cs | 1235 ++++++++++++++- dotnet/test/E2E/RewindE2ETests.cs | 6 +- go/internal/e2e/rewind_e2e_test.go | 13 +- go/rpc/zrpc.go | 137 +- go/rpc/zsession_encoding.go | 48 + go/rpc/zsession_events.go | 490 +++++- go/zsession_events.go | 48 + java/pom.xml | 2 +- java/scripts/codegen/package-lock.json | 72 +- java/scripts/codegen/package.json | 2 +- .../AssistantFusionPhaseCompletedEvent.java | 67 + .../AssistantFusionPhaseFailedEvent.java | 63 + .../AssistantFusionPhaseStartedEvent.java | 53 + .../generated/AssistantMessageEvent.java | 4 +- .../generated/AssistantUsageEvent.java | 4 +- .../copilot/generated/FusionAttribution.java | 47 + .../generated/FusionConversationScope.java | 35 + .../generated/FusionFollowUpAction.java | 35 + .../FusionFollowUpRecommendation.java | 29 + .../copilot/generated/FusionPattern.java | 37 + .../copilot/generated/FusionPhaseKind.java | 45 + .../copilot/generated/FusionPhaseStatus.java | 37 + .../copilot/generated/FusionPhaseUsage.java | 37 + .../generated/FusionProjectionMode.java | 37 + .../copilot/generated/FusionScores.java | 33 + .../generated/FusionStagedTerminal.java | 30 + .../copilot/generated/FusionTurnKind.java | 35 + .../copilot/generated/HookEndEvent.java | 4 +- .../copilot/generated/HookStartEvent.java | 4 +- .../generated/ModelCallFailureEvent.java | 4 +- .../generated/ModelCallStartEvent.java | 4 +- .../copilot/generated/SessionEvent.java | 16 + .../SessionFusionCompletedEvent.java | 75 + .../generated/SessionFusionResolvedEvent.java | 79 + .../SessionFusionRouteFailedEvent.java | 53 + .../SessionFusionRouteStartedEvent.java | 47 + .../generated/SubagentConfiguredEvent.java | 47 + .../generated/SubagentStartedEvent.java | 10 +- .../generated/ToolExecutionCompleteEvent.java | 4 +- .../generated/ToolExecutionStartEvent.java | 4 +- .../generated/rpc/FactoryToolRunOptions.java | 29 + .../copilot/generated/rpc/RunOptions.java | 4 + .../generated/rpc/SessionFactoryApi.java | 32 + .../SessionFactoryResumeFromToolParams.java | 36 + .../SessionFactoryResumeFromToolResult.java | 32 + .../rpc/SessionFactoryResumeParams.java | 6 +- .../rpc/SessionFactoryRunFromToolParams.java | 38 + .../rpc/SessionFactoryRunFromToolResult.java | 42 + ...ssionToolsGetBuiltinDescriptorsParams.java | 4 - .../copilot/SessionEventHandlingTest.java | 2 +- .../java/com/github/copilot/e2e/RewindIT.java | 6 +- nodejs/package-lock.json | 54 +- nodejs/package.json | 2 +- nodejs/samples/package-lock.json | 2 +- nodejs/src/generated/rpc.ts | 106 +- nodejs/src/generated/session-events.ts | 1324 ++++++++++++++--- .../test/e2e/disabled_mcp_servers.e2e.test.ts | 6 +- nodejs/test/e2e/rewind.e2e.test.ts | 104 +- python/copilot/generated/rpc.py | 180 ++- python/copilot/generated/session_events.py | 889 ++++++++++- python/e2e/test_rewind_e2e.py | 6 +- rust/src/generated/api_types.rs | 138 +- rust/src/generated/rpc.rs | 69 + rust/src/generated/session_events.rs | 807 ++++++++++ rust/tests/e2e/rewind.rs | 16 +- test/harness/package-lock.json | 54 +- test/harness/package.json | 2 +- .../should_create_a_new_file.yaml | 2 +- .../should_edit_a_file_successfully.yaml | 5 +- .../should_read_file_with_line_range.yaml | 6 +- ...ient_cwd_for_default_workingdirectory.yaml | 2 +- ...ect_order_for_tool_using_conversation.yaml | 2 +- ..._execution_events_with_correct_fields.yaml | 2 +- ...e_order_in_getmessages_after_tool_use.yaml | 2 +- ...nvoke_both_hooks_for_single_tool_call.yaml | 2 +- ...tool_use_hook_after_model_runs_a_tool.yaml | 2 +- ..._tool_use_hook_when_model_runs_a_tool.yaml | 2 +- ...ttooluse_hooks_for_a_single_tool_call.yaml | 2 +- ...osttooluse_hooks_for_single_tool_call.yaml | 2 +- ...ttooluse_hook_after_model_runs_a_tool.yaml | 2 +- ...retooluse_hook_when_model_runs_a_tool.yaml | 2 +- ...le_creation_then_reading_across_turns.yaml | 2 +- ..._use_tool_results_from_previous_turns.yaml | 4 +- ...rmission_handler_for_write_operations.yaml | 4 +- ...rmission_handler_for_write_operations.yaml | 4 +- .../should_send_with_file_attachment.yaml | 2 +- .../should_accept_message_attachments.yaml | 2 +- ...ly_workingdirectory_on_session_resume.yaml | 2 +- ...e_workingdirectory_for_tool_execution.yaml | 2 +- ...ooluse_hooks_for_sub_agent_tool_calls.yaml | 2 +- ...form_modifications_to_section_content.yaml | 2 +- ...nsform_callbacks_with_section_content.yaml | 2 +- ...tic_overrides_and_transforms_together.yaml | 2 +- .../tools/invokes_built_in_tools.yaml | 2 +- 95 files changed, 6737 insertions(+), 503 deletions(-) create mode 100644 java/sdk/src/generated/java/com/github/copilot/generated/AssistantFusionPhaseCompletedEvent.java create mode 100644 java/sdk/src/generated/java/com/github/copilot/generated/AssistantFusionPhaseFailedEvent.java create mode 100644 java/sdk/src/generated/java/com/github/copilot/generated/AssistantFusionPhaseStartedEvent.java create mode 100644 java/sdk/src/generated/java/com/github/copilot/generated/FusionAttribution.java create mode 100644 java/sdk/src/generated/java/com/github/copilot/generated/FusionConversationScope.java create mode 100644 java/sdk/src/generated/java/com/github/copilot/generated/FusionFollowUpAction.java create mode 100644 java/sdk/src/generated/java/com/github/copilot/generated/FusionFollowUpRecommendation.java create mode 100644 java/sdk/src/generated/java/com/github/copilot/generated/FusionPattern.java create mode 100644 java/sdk/src/generated/java/com/github/copilot/generated/FusionPhaseKind.java create mode 100644 java/sdk/src/generated/java/com/github/copilot/generated/FusionPhaseStatus.java create mode 100644 java/sdk/src/generated/java/com/github/copilot/generated/FusionPhaseUsage.java create mode 100644 java/sdk/src/generated/java/com/github/copilot/generated/FusionProjectionMode.java create mode 100644 java/sdk/src/generated/java/com/github/copilot/generated/FusionScores.java create mode 100644 java/sdk/src/generated/java/com/github/copilot/generated/FusionStagedTerminal.java create mode 100644 java/sdk/src/generated/java/com/github/copilot/generated/FusionTurnKind.java create mode 100644 java/sdk/src/generated/java/com/github/copilot/generated/SessionFusionCompletedEvent.java create mode 100644 java/sdk/src/generated/java/com/github/copilot/generated/SessionFusionResolvedEvent.java create mode 100644 java/sdk/src/generated/java/com/github/copilot/generated/SessionFusionRouteFailedEvent.java create mode 100644 java/sdk/src/generated/java/com/github/copilot/generated/SessionFusionRouteStartedEvent.java create mode 100644 java/sdk/src/generated/java/com/github/copilot/generated/SubagentConfiguredEvent.java create mode 100644 java/sdk/src/generated/java/com/github/copilot/generated/rpc/FactoryToolRunOptions.java create mode 100644 java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryResumeFromToolParams.java create mode 100644 java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryResumeFromToolResult.java create mode 100644 java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryRunFromToolParams.java create mode 100644 java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryRunFromToolResult.java diff --git a/dotnet/src/Generated/Rpc.cs b/dotnet/src/Generated/Rpc.cs index ba8b844d83..5e658452ae 100644 --- a/dotnet/src/Generated/Rpc.cs +++ b/dotnet/src/Generated/Rpc.cs @@ -6365,6 +6365,14 @@ public sealed class RunOptions [JsonPropertyName("limits")] public FactoryRunLimits? Limits { get; set; } + /// Whether to emit factory phase names to the session transcript. + [JsonPropertyName("logPhaseNames")] + public bool? LogPhaseNames { get; set; } + + /// Whether to notify the originating session when the factory completes. + [JsonPropertyName("notifyOnComplete")] + public bool? NotifyOnComplete { get; set; } + /// Run identifier whose journal and progress should seed this resumed run. [JsonPropertyName("resumeFromRunId")] public string? ResumeFromRunId { get; set; } @@ -6412,6 +6420,69 @@ internal sealed class FactoryResumeRequest [JsonPropertyName("limits")] public FactoryRunLimits? Limits { get; set; } + /// Whether to emit factory phase names to the session transcript. + [JsonPropertyName("logPhaseNames")] + public bool? LogPhaseNames { get; set; } + + /// Whether to notify the originating session when the factory completes. + [JsonPropertyName("notifyOnComplete")] + public bool? NotifyOnComplete { get; set; } + + /// Factory run identifier. + [JsonPropertyName("runId")] + public string RunId { get; set; } = string.Empty; + + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + +/// Options for an internal tool-originated factory invocation. +[Experimental(Diagnostics.Experimental)] +internal sealed class FactoryToolRunOptions +{ + /// Per-invocation resource ceiling overrides. + [JsonPropertyName("limits")] + public FactoryRunLimits? Limits { get; set; } + + /// Run identifier whose journal and progress should seed this resumed run. + [JsonPropertyName("resumeFromRunId")] + public string? ResumeFromRunId { get; set; } +} + +/// Internal parameters for invoking a registered factory from a tool. +[Experimental(Diagnostics.Experimental)] +internal sealed class FactoryToolRunRequest +{ + /// Factory input value. + [JsonPropertyName("args")] + public JsonElement Args { get; set; } + + /// Registered factory name. + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; + + /// Tool-originated factory invocation options. + [JsonPropertyName("options")] + public FactoryToolRunOptions? Options { get; set; } + + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; + + /// Opaque identifier of the originating tool call. + [JsonPropertyName("toolCallId")] + public string? ToolCallId { get; set; } +} + +/// Internal parameters for resuming a factory run from a tool. +[Experimental(Diagnostics.Experimental)] +internal sealed class FactoryToolResumeRequest +{ + /// Optional per-invocation resource ceiling overrides. + [JsonPropertyName("limits")] + public FactoryRunLimits? Limits { get; set; } + /// Factory run identifier. [JsonPropertyName("runId")] public string RunId { get; set; } = string.Empty; @@ -6419,6 +6490,10 @@ internal sealed class FactoryResumeRequest /// 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; } } /// Parameters for retrieving a factory run. @@ -12222,10 +12297,6 @@ internal sealed class ToolsGetBuiltinDescriptorsRequest [JsonPropertyName("includeAuthor")] public bool? IncludeAuthor { get; set; } - /// Whether line numbers should be omitted from the view tool descriptor. - [JsonPropertyName("noViewLineNumbers")] - public bool? NoViewLineNumbers { get; set; } - /// Whether descriptors should favor fewer user-intervention prompts. [JsonPropertyName("reduceUserIntervention")] public bool? ReduceUserIntervention { get; set; } @@ -12234,10 +12305,6 @@ internal sealed class ToolsGetBuiltinDescriptorsRequest [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; - /// Whether shell commands may only run asynchronously. - [JsonPropertyName("shellAsyncOnlyEnabled")] - public bool? ShellAsyncOnlyEnabled { get; set; } - /// Shell-specific names and description lines for shell tools. [JsonPropertyName("shellConfig")] public ToolsShellDescriptorConfig? ShellConfig { get; set; } @@ -12383,6 +12450,11 @@ public partial class ExternalToolTextResultForLlmContentShellExit : ExternalTool [JsonPropertyName("exitCode")] public required long ExitCode { get; set; } + /// Path reported in the shell session's filesystem namespace when shell output exceeded the configured large-output threshold. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("outputFilePath")] + public string? OutputFilePath { get; set; } + /// Output associated with this shell command, if available. May be partial, truncated, or a preview; not guaranteed to be full output. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("outputPreview")] @@ -31575,17 +31647,51 @@ public async Task RunAsync(string name, object args, RunOption /// Resumes a factory run using its persisted name, arguments, journal, and accounting. /// Factory run identifier. /// Optional per-invocation resource ceiling overrides. + /// Whether to notify the originating session when the factory completes. + /// Whether to emit factory phase names to the session transcript. /// The to monitor for cancellation requests. The default is . /// Resolved persisted factory identity and resumed run envelope. - public async Task ResumeAsync(string runId, FactoryRunLimits? limits = null, CancellationToken cancellationToken = default) + public async Task ResumeAsync(string runId, FactoryRunLimits? limits = null, bool? notifyOnComplete = null, bool? logPhaseNames = null, CancellationToken cancellationToken = default) { ArgumentNullException.ThrowIfNull(runId); _session.ThrowIfDisposed(); - var request = new FactoryResumeRequest { SessionId = _session.SessionId, RunId = runId, Limits = limits }; + var request = new FactoryResumeRequest { SessionId = _session.SessionId, RunId = runId, Limits = limits, NotifyOnComplete = notifyOnComplete, LogPhaseNames = logPhaseNames }; return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.factory.resume", [request], cancellationToken); } + /// Internal tool-originated factory invocation. + /// Registered factory name. + /// Factory input value. + /// Tool-originated factory invocation options. + /// Opaque identifier of the originating tool call. + /// The to monitor for cancellation requests. The default is . + /// Complete current or terminal factory run envelope. + internal async Task RunFromToolAsync(string name, object args, FactoryToolRunOptions? options = null, string? toolCallId = null, CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(name); + ArgumentNullException.ThrowIfNull(args); + _session.ThrowIfDisposed(); + + var request = new FactoryToolRunRequest { SessionId = _session.SessionId, Name = name, Args = CopilotClient.ToJsonElementForWire(args)!.Value, Options = options, ToolCallId = toolCallId }; + return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.factory.runFromTool", [request], cancellationToken); + } + + /// Internal tool-originated factory resume. + /// Factory run identifier. + /// Optional per-invocation resource ceiling overrides. + /// Opaque identifier of the originating tool call. + /// The to monitor for cancellation requests. The default is . + /// Resolved persisted factory identity and resumed run envelope. + internal async Task ResumeFromToolAsync(string runId, FactoryRunLimits? limits = null, string? toolCallId = null, CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(runId); + _session.ThrowIfDisposed(); + + var request = new FactoryToolResumeRequest { SessionId = _session.SessionId, RunId = runId, Limits = limits, ToolCallId = toolCallId }; + return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.factory.resumeFromTool", [request], cancellationToken); + } + /// Gets the current or settled envelope for a factory run. /// Factory run identifier. /// The to monitor for cancellation requests. The default is . @@ -33403,22 +33509,20 @@ public async Task ExecuteAsync(string name, object arguments, strin } /// Returns the Rust-owned built-in tool descriptors used to construct the session's offered tool set. - /// Whether line numbers should be omitted from the view tool descriptor. /// Whether descriptors should favor fewer user-intervention prompts. /// Whether tool descriptors should include authoring metadata. /// Whether semantic skill lookup is available. /// Shell-specific names and description lines for shell tools. - /// Whether shell commands may only run asynchronously. /// Whether the configured shell supports PowerShell 7 syntax. /// Default shell timeout in milliseconds. /// Whether background task completion notifications are enabled. /// The to monitor for cancellation requests. The default is . /// Rust-owned built-in tool descriptors for the session. - public async Task GetBuiltinDescriptorsAsync(bool? noViewLineNumbers = null, bool? reduceUserIntervention = null, bool? includeAuthor = null, bool? skillEmbeddingEnabled = null, ToolsShellDescriptorConfig? shellConfig = null, bool? shellAsyncOnlyEnabled = null, bool? shellSupportsPowerShell7Syntax = null, double? shellTimeoutMs = null, bool? backgroundTaskNotificationsEnabled = null, CancellationToken cancellationToken = default) + public async Task GetBuiltinDescriptorsAsync(bool? reduceUserIntervention = null, bool? includeAuthor = null, bool? skillEmbeddingEnabled = null, ToolsShellDescriptorConfig? shellConfig = null, bool? shellSupportsPowerShell7Syntax = null, double? shellTimeoutMs = null, bool? backgroundTaskNotificationsEnabled = null, CancellationToken cancellationToken = default) { _session.ThrowIfDisposed(); - var request = new ToolsGetBuiltinDescriptorsRequest { SessionId = _session.SessionId, NoViewLineNumbers = noViewLineNumbers, ReduceUserIntervention = reduceUserIntervention, IncludeAuthor = includeAuthor, SkillEmbeddingEnabled = skillEmbeddingEnabled, ShellConfig = shellConfig, ShellAsyncOnlyEnabled = shellAsyncOnlyEnabled, ShellSupportsPowerShell7Syntax = shellSupportsPowerShell7Syntax, ShellTimeoutMs = shellTimeoutMs, BackgroundTaskNotificationsEnabled = backgroundTaskNotificationsEnabled }; + var request = new ToolsGetBuiltinDescriptorsRequest { SessionId = _session.SessionId, ReduceUserIntervention = reduceUserIntervention, IncludeAuthor = includeAuthor, SkillEmbeddingEnabled = skillEmbeddingEnabled, ShellConfig = shellConfig, ShellSupportsPowerShell7Syntax = shellSupportsPowerShell7Syntax, ShellTimeoutMs = shellTimeoutMs, BackgroundTaskNotificationsEnabled = backgroundTaskNotificationsEnabled }; return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.tools.getBuiltinDescriptors", [request], cancellationToken); } @@ -35472,6 +35576,9 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH [JsonSerializable(typeof(GitHub.Copilot.AgentInterruptedCancelPhase), TypeInfoPropertyName = "SessionEventsAgentInterruptedCancelPhase")] [JsonSerializable(typeof(GitHub.Copilot.AgentInterruptedData), TypeInfoPropertyName = "SessionEventsAgentInterruptedData")] [JsonSerializable(typeof(GitHub.Copilot.AgentInterruptedEvent), TypeInfoPropertyName = "SessionEventsAgentInterruptedEvent")] +[JsonSerializable(typeof(GitHub.Copilot.AssistantFusionPhaseCompletedEvent), TypeInfoPropertyName = "SessionEventsAssistantFusionPhaseCompletedEvent")] +[JsonSerializable(typeof(GitHub.Copilot.AssistantFusionPhaseFailedEvent), TypeInfoPropertyName = "SessionEventsAssistantFusionPhaseFailedEvent")] +[JsonSerializable(typeof(GitHub.Copilot.AssistantFusionPhaseStartedEvent), TypeInfoPropertyName = "SessionEventsAssistantFusionPhaseStartedEvent")] [JsonSerializable(typeof(GitHub.Copilot.AssistantIdleData), TypeInfoPropertyName = "SessionEventsAssistantIdleData")] [JsonSerializable(typeof(GitHub.Copilot.AssistantIdleEvent), TypeInfoPropertyName = "SessionEventsAssistantIdleEvent")] [JsonSerializable(typeof(GitHub.Copilot.AssistantIntentData), TypeInfoPropertyName = "SessionEventsAssistantIntentData")] @@ -35603,6 +35710,17 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH [JsonSerializable(typeof(GitHub.Copilot.FactoryRunStartedEvent), TypeInfoPropertyName = "SessionEventsFactoryRunStartedEvent")] [JsonSerializable(typeof(GitHub.Copilot.FactoryRunUpdatedData), TypeInfoPropertyName = "SessionEventsFactoryRunUpdatedData")] [JsonSerializable(typeof(GitHub.Copilot.FactoryRunUpdatedEvent), TypeInfoPropertyName = "SessionEventsFactoryRunUpdatedEvent")] +[JsonSerializable(typeof(GitHub.Copilot.FusionAttribution), TypeInfoPropertyName = "SessionEventsFusionAttribution")] +[JsonSerializable(typeof(GitHub.Copilot.FusionConversationScope), TypeInfoPropertyName = "SessionEventsFusionConversationScope")] +[JsonSerializable(typeof(GitHub.Copilot.FusionFollowUpAction), TypeInfoPropertyName = "SessionEventsFusionFollowUpAction")] +[JsonSerializable(typeof(GitHub.Copilot.FusionFollowUpRecommendation), TypeInfoPropertyName = "SessionEventsFusionFollowUpRecommendation")] +[JsonSerializable(typeof(GitHub.Copilot.FusionPattern), TypeInfoPropertyName = "SessionEventsFusionPattern")] +[JsonSerializable(typeof(GitHub.Copilot.FusionPhaseKind), TypeInfoPropertyName = "SessionEventsFusionPhaseKind")] +[JsonSerializable(typeof(GitHub.Copilot.FusionPhaseStatus), TypeInfoPropertyName = "SessionEventsFusionPhaseStatus")] +[JsonSerializable(typeof(GitHub.Copilot.FusionPhaseUsage), TypeInfoPropertyName = "SessionEventsFusionPhaseUsage")] +[JsonSerializable(typeof(GitHub.Copilot.FusionProjectionMode), TypeInfoPropertyName = "SessionEventsFusionProjectionMode")] +[JsonSerializable(typeof(GitHub.Copilot.FusionScores), TypeInfoPropertyName = "SessionEventsFusionScores")] +[JsonSerializable(typeof(GitHub.Copilot.FusionTurnKind), TypeInfoPropertyName = "SessionEventsFusionTurnKind")] [JsonSerializable(typeof(GitHub.Copilot.GitHubMcpToolConfig), TypeInfoPropertyName = "SessionEventsGitHubMcpToolConfig")] [JsonSerializable(typeof(GitHub.Copilot.GitHubRepoRef), TypeInfoPropertyName = "SessionEventsGitHubRepoRef")] [JsonSerializable(typeof(GitHub.Copilot.HandoffRepository), TypeInfoPropertyName = "SessionEventsHandoffRepository")] @@ -35744,6 +35862,8 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH [JsonSerializable(typeof(GitHub.Copilot.SkillsLoadedSkill), TypeInfoPropertyName = "SessionEventsSkillsLoadedSkill")] [JsonSerializable(typeof(GitHub.Copilot.SubagentCompletedData), TypeInfoPropertyName = "SessionEventsSubagentCompletedData")] [JsonSerializable(typeof(GitHub.Copilot.SubagentCompletedEvent), TypeInfoPropertyName = "SessionEventsSubagentCompletedEvent")] +[JsonSerializable(typeof(GitHub.Copilot.SubagentConfiguredData), TypeInfoPropertyName = "SessionEventsSubagentConfiguredData")] +[JsonSerializable(typeof(GitHub.Copilot.SubagentConfiguredEvent), TypeInfoPropertyName = "SessionEventsSubagentConfiguredEvent")] [JsonSerializable(typeof(GitHub.Copilot.SubagentDeselectedData), TypeInfoPropertyName = "SessionEventsSubagentDeselectedData")] [JsonSerializable(typeof(GitHub.Copilot.SubagentDeselectedEvent), TypeInfoPropertyName = "SessionEventsSubagentDeselectedEvent")] [JsonSerializable(typeof(GitHub.Copilot.SubagentFailedData), TypeInfoPropertyName = "SessionEventsSubagentFailedData")] @@ -35994,6 +36114,9 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH [JsonSerializable(typeof(FactoryRunResult))] [JsonSerializable(typeof(FactoryRunSummary))] [JsonSerializable(typeof(FactoryRunTerminal))] +[JsonSerializable(typeof(FactoryToolResumeRequest))] +[JsonSerializable(typeof(FactoryToolRunOptions))] +[JsonSerializable(typeof(FactoryToolRunRequest))] [JsonSerializable(typeof(FleetStartRequest))] [JsonSerializable(typeof(FleetStartResult))] [JsonSerializable(typeof(FolderTrustAddParams))] diff --git a/dotnet/src/Generated/SessionEvents.cs b/dotnet/src/Generated/SessionEvents.cs index bb2f3f9979..d7eb108ed3 100644 --- a/dotnet/src/Generated/SessionEvents.cs +++ b/dotnet/src/Generated/SessionEvents.cs @@ -26,6 +26,9 @@ namespace GitHub.Copilot; IgnoreUnrecognizedTypeDiscriminators = true)] [JsonDerivedType(typeof(AbortEvent), "abort")] [JsonDerivedType(typeof(AgentInterruptedEvent), "agent.interrupted")] +[JsonDerivedType(typeof(AssistantFusionPhaseCompletedEvent), "assistant.fusion_phase_completed")] +[JsonDerivedType(typeof(AssistantFusionPhaseFailedEvent), "assistant.fusion_phase_failed")] +[JsonDerivedType(typeof(AssistantFusionPhaseStartedEvent), "assistant.fusion_phase_started")] [JsonDerivedType(typeof(AssistantIdleEvent), "assistant.idle")] [JsonDerivedType(typeof(AssistantIntentEvent), "assistant.intent")] [JsonDerivedType(typeof(AssistantMessageEvent), "assistant.message")] @@ -98,6 +101,10 @@ namespace GitHub.Copilot; [JsonDerivedType(typeof(SessionErrorEvent), "session.error")] [JsonDerivedType(typeof(SessionExtensionsLoadedEvent), "session.extensions_loaded")] [JsonDerivedType(typeof(SessionExtensionsAttachmentsPushedEvent), "session.extensions.attachments_pushed")] +[JsonDerivedType(typeof(SessionFusionCompletedEvent), "session.fusion_completed")] +[JsonDerivedType(typeof(SessionFusionResolvedEvent), "session.fusion_resolved")] +[JsonDerivedType(typeof(SessionFusionRouteFailedEvent), "session.fusion_route_failed")] +[JsonDerivedType(typeof(SessionFusionRouteStartedEvent), "session.fusion_route_started")] [JsonDerivedType(typeof(SessionHandoffEvent), "session.handoff")] [JsonDerivedType(typeof(SessionIdleEvent), "session.idle")] [JsonDerivedType(typeof(SessionInfoEvent), "session.info")] @@ -130,6 +137,7 @@ namespace GitHub.Copilot; [JsonDerivedType(typeof(SessionWorkspaceFileChangedEvent), "session.workspace_file_changed")] [JsonDerivedType(typeof(SkillInvokedEvent), "skill.invoked")] [JsonDerivedType(typeof(SubagentCompletedEvent), "subagent.completed")] +[JsonDerivedType(typeof(SubagentConfiguredEvent), "subagent.configured")] [JsonDerivedType(typeof(SubagentDeselectedEvent), "subagent.deselected")] [JsonDerivedType(typeof(SubagentFailedEvent), "subagent.failed")] [JsonDerivedType(typeof(SubagentSelectedEvent), "subagent.selected")] @@ -579,6 +587,62 @@ public sealed partial class SessionTaskCompleteEvent : SessionEvent public required SessionTaskCompleteData Data { get; set; } } +/// Experimental transient signal that HydraFusion routing has started for an eligible turn. +/// Represents the session.fusion_route_started event. +[Experimental(Diagnostics.Experimental)] +public sealed partial class SessionFusionRouteStartedEvent : SessionEvent +{ + /// + [JsonIgnore] + public override string Type => "session.fusion_route_started"; + + /// The session.fusion_route_started event payload. + [JsonPropertyName("data")] + public required SessionFusionRouteStartedData Data { get; set; } +} + +/// Experimental durable HydraFusion routing failure and the deterministic concrete fallback selected for the turn. +/// Represents the session.fusion_route_failed event. +[Experimental(Diagnostics.Experimental)] +public sealed partial class SessionFusionRouteFailedEvent : SessionEvent +{ + /// + [JsonIgnore] + public override string Type => "session.fusion_route_failed"; + + /// The session.fusion_route_failed event payload. + [JsonPropertyName("data")] + public required SessionFusionRouteFailedData Data { get; set; } +} + +/// Experimental durable validated HydraFusion route and turn policy. +/// Represents the session.fusion_resolved event. +[Experimental(Diagnostics.Experimental)] +public sealed partial class SessionFusionResolvedEvent : SessionEvent +{ + /// + [JsonIgnore] + public override string Type => "session.fusion_resolved"; + + /// The session.fusion_resolved event payload. + [JsonPropertyName("data")] + public required SessionFusionResolvedData Data { get; set; } +} + +/// Experimental durable aggregate outcome of a HydraFusion turn. +/// Represents the session.fusion_completed event. +[Experimental(Diagnostics.Experimental)] +public sealed partial class SessionFusionCompletedEvent : SessionEvent +{ + /// + [JsonIgnore] + public override string Type => "session.fusion_completed"; + + /// The session.fusion_completed event payload. + [JsonPropertyName("data")] + public required SessionFusionCompletedData Data { get; set; } +} + /// Payload of `user.message` with displayed and model-transformed content, attachments, source/delivery metadata, mode, and telemetry IDs. /// Represents the user.message event. public sealed partial class UserMessageEvent : SessionEvent @@ -657,6 +721,48 @@ public sealed partial class AssistantIntentEvent : SessionEvent public required AssistantIntentData Data { get; set; } } +/// Experimental transient HydraFusion phase/model/role signal. +/// Represents the assistant.fusion_phase_started event. +[Experimental(Diagnostics.Experimental)] +public sealed partial class AssistantFusionPhaseStartedEvent : SessionEvent +{ + /// + [JsonIgnore] + public override string Type => "assistant.fusion_phase_started"; + + /// The assistant.fusion_phase_started event payload. + [JsonPropertyName("data")] + public required AssistantFusionPhaseStartedData Data { get; set; } +} + +/// Experimental durable HydraFusion phase output and lossless replay checkpoint. +/// Represents the assistant.fusion_phase_completed event. +[Experimental(Diagnostics.Experimental)] +public sealed partial class AssistantFusionPhaseCompletedEvent : SessionEvent +{ + /// + [JsonIgnore] + public override string Type => "assistant.fusion_phase_completed"; + + /// The assistant.fusion_phase_completed event payload. + [JsonPropertyName("data")] + public required AssistantFusionPhaseCompletedData Data { get; set; } +} + +/// Experimental durable typed HydraFusion phase failure and degradation transition. +/// Represents the assistant.fusion_phase_failed event. +[Experimental(Diagnostics.Experimental)] +public sealed partial class AssistantFusionPhaseFailedEvent : SessionEvent +{ + /// + [JsonIgnore] + public override string Type => "assistant.fusion_phase_failed"; + + /// The assistant.fusion_phase_failed event payload. + [JsonPropertyName("data")] + public required AssistantFusionPhaseFailedData Data { get; set; } +} + /// Live progress signal for a provider-hosted server tool (e.g. hosted web search) while it runs, before the finalized serverTools envelope lands on the terminal assistant.message. /// Represents the assistant.server_tool_progress event. public sealed partial class AssistantServerToolProgressEvent : SessionEvent @@ -982,6 +1088,19 @@ public sealed partial class SubagentStartedEvent : SessionEvent public required SubagentStartedData Data { get; set; } } +/// Resolved runtime configuration for a configured sub-agent. +/// Represents the subagent.configured event. +public sealed partial class SubagentConfiguredEvent : SessionEvent +{ + /// + [JsonIgnore] + public override string Type => "subagent.configured"; + + /// The subagent.configured event payload. + [JsonPropertyName("data")] + public required SubagentConfiguredData Data { get; set; } +} + /// Sub-agent completion details for successful execution. /// Represents the subagent.completed event. public sealed partial class SubagentCompletedEvent : SessionEvent @@ -2732,6 +2851,237 @@ public sealed partial class SessionTaskCompleteData public string? Summary { get; set; } } +/// Experimental transient signal that HydraFusion routing has started for an eligible turn. +[Experimental(Diagnostics.Experimental)] +public sealed partial class SessionFusionRouteStartedData +{ + /// Identifier for this routing attempt before a durable Fusion turn exists. + [JsonPropertyName("attemptId")] + public required string AttemptId { get; set; } + + /// HydraFusion routing policy requested for the turn. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("policy")] + public string? Policy { get; set; } + + /// Synthetic HydraFusion model selected for the session. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("syntheticModel")] + public string? SyntheticModel { get; set; } + + /// Kind of turn being routed. + [JsonPropertyName("turnKind")] + public required FusionTurnKind TurnKind { get; set; } +} + +/// Experimental durable HydraFusion routing failure and the deterministic concrete fallback selected for the turn. +[Experimental(Diagnostics.Experimental)] +public sealed partial class SessionFusionRouteFailedData +{ + /// Identifier of the routing attempt that failed. + [JsonPropertyName("attemptId")] + public required string AttemptId { get; set; } + + /// Provider or validation error detail, when available. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("errorMessage")] + public string? ErrorMessage { get; set; } + + /// Concrete model selected as the deterministic fallback. + [JsonPropertyName("fallbackModel")] + public required string FallbackModel { get; set; } + + /// HydraFusion routing policy requested for the turn. + [JsonPropertyName("policy")] + public required string Policy { get; set; } + + /// Stable machine-readable reason for the routing failure. + [JsonPropertyName("reason")] + public required string Reason { get; set; } + + /// Elapsed routing time in milliseconds before the failure. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("routingLatencyMs")] + public double? RoutingLatencyMs { get; set; } + + /// Synthetic HydraFusion model selected for the session. + [JsonPropertyName("syntheticModel")] + public required string SyntheticModel { get; set; } +} + +/// Experimental durable validated HydraFusion route and turn policy. +[Experimental(Diagnostics.Experimental)] +public sealed partial class SessionFusionResolvedData +{ + /// Version of the validated HydraFusion event contract. + [JsonPropertyName("contractVersion")] + public required long ContractVersion { get; set; } + + /// Concrete model used when the planned primary model cannot execute. + [JsonPropertyName("fallbackModel")] + public required string FallbackModel { get; set; } + + /// Router recommendation controlling reuse or rerouting on later turns. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("followUp")] + public FusionFollowUpRecommendation? FollowUp { get; set; } + + /// Concrete model recommended for eligible follow-up turns. + [JsonPropertyName("followUpModel")] + public required string FollowUpModel { get; set; } + + /// Stable identifier for the resolved HydraFusion turn. + [JsonPropertyName("fusionId")] + public required string FusionId { get; set; } + + /// Version of the executable model universe used for selection. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("modelUniverseVersion")] + public string? ModelUniverseVersion { get; set; } + + /// Validated orchestration pattern selected for the turn. + [JsonPropertyName("pattern")] + public required FusionPattern Pattern { get; set; } + + /// Version of the validated execution-plan format. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("planVersion")] + public string? PlanVersion { get; set; } + + /// HydraFusion routing policy used to resolve the plan. + [JsonPropertyName("policy")] + public required string Policy { get; set; } + + /// Version of the local routing policy. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("policyVersion")] + public string? PolicyVersion { get; set; } + + /// Concrete model selected for the primary solver phase. + [JsonPropertyName("primaryModel")] + public required string PrimaryModel { get; set; } + + /// Router implementation that supplied the plan. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("routeSource")] + public string? RouteSource { get; set; } + + /// Elapsed time in milliseconds required to resolve and validate the route. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("routingLatencyMs")] + public double? RoutingLatencyMs { get; set; } + + /// Identifier of the local policy rule that matched. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("ruleId")] + public string? RuleId { get; set; } + + /// Zero-based index of the local policy rule that matched. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("ruleIndex")] + public long? RuleIndex { get; set; } + + /// Human-readable name of the local policy rule that matched. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("ruleName")] + public string? RuleName { get; set; } + + /// Validated capability scores used to select the route. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("scores")] + public FusionScores? Scores { get; set; } + + /// Concrete model selected for the review or judge phase, when required. + [JsonPropertyName("secondaryModel")] + public string? SecondaryModel { get; set; } + + /// Synthetic HydraFusion model selected for the session. + [JsonPropertyName("syntheticModel")] + public required string SyntheticModel { get; set; } + + /// Identifier of the session turn associated with the route. + [JsonPropertyName("turnId")] + public required string TurnId { get; set; } +} + +/// Experimental durable aggregate outcome of a HydraFusion turn. +[Experimental(Diagnostics.Experimental)] +public sealed partial class SessionFusionCompletedData +{ + /// Total cached input tokens reported across all phases. + [JsonPropertyName("cachedTokens")] + public required long CachedTokens { get; set; } + + /// Total tokens written to prompt cache across all phases. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("cacheWriteTokens")] + public long? CacheWriteTokens { get; set; } + + /// Idempotency identifier for the authoritative final commit. + [JsonPropertyName("commitId")] + public required string CommitId { get; set; } + + /// Reason the turn used a degraded route, when applicable. + [JsonPropertyName("degradedReason")] + public string? DegradedReason { get; set; } + + /// Total elapsed execution time for the HydraFusion turn in milliseconds. + [JsonPropertyName("durationMs")] + public required double DurationMs { get; set; } + + /// Concrete model that supplied the authoritative final content. + [JsonPropertyName("finalSourceModel")] + public string? FinalSourceModel { get; set; } + + /// Phase whose output supplied the authoritative final content. + [JsonPropertyName("finalSourcePhaseId")] + public string? FinalSourcePhaseId { get; set; } + + /// Concrete model recommended for eligible follow-up turns. + [JsonPropertyName("followUpModel")] + public required string FollowUpModel { get; set; } + + /// Stable identifier for the completed HydraFusion turn. + [JsonPropertyName("fusionId")] + public required string FusionId { get; set; } + + /// Total input tokens consumed across all phases. + [JsonPropertyName("inputTokens")] + public required long InputTokens { get; set; } + + /// Stable aggregate outcome of the HydraFusion turn. + [JsonPropertyName("outcome")] + public required string Outcome { get; set; } + + /// Total output tokens produced across all phases. + [JsonPropertyName("outputTokens")] + public required long OutputTokens { get; set; } + + /// HydraFusion orchestration pattern executed for the turn. + [JsonPropertyName("pattern")] + public required FusionPattern Pattern { get; set; } + + /// Number of concrete phases attempted by the turn. + [JsonPropertyName("phaseCount")] + public required long PhaseCount { get; set; } + + /// Total concrete model requests made across all phases. + [JsonPropertyName("requestCount")] + public required long RequestCount { get; set; } + + /// Synthetic HydraFusion model selected for the session. + [JsonPropertyName("syntheticModel")] + public required string SyntheticModel { get; set; } + + /// Total normalized AI-unit cost reported across all phases, in nano-AIU. + [JsonPropertyName("totalNanoAiu")] + public required double TotalNanoAiu { get; set; } + + /// Identifier of the session turn associated with the completion. + [JsonPropertyName("turnId")] + public required string TurnId { get; set; } +} + /// Payload of `user.message` with displayed and model-transformed content, attachments, source/delivery metadata, mode, and telemetry IDs. public sealed partial class UserMessageData { @@ -2912,6 +3262,161 @@ public sealed partial class AssistantIntentData public required string Intent { get; set; } } +/// Experimental transient HydraFusion phase/model/role signal. +[Experimental(Diagnostics.Experimental)] +public sealed partial class AssistantFusionPhaseStartedData +{ + /// Conversation scope in which the phase executes. + [JsonPropertyName("conversationScope")] + public required FusionConversationScope ConversationScope { get; set; } + + /// Identifier of the HydraFusion turn containing the phase. + [JsonPropertyName("fusionId")] + public required string FusionId { get; set; } + + /// Concrete model executing the phase. + [JsonPropertyName("model")] + public required string Model { get; set; } + + /// HydraFusion orchestration pattern containing the phase. + [JsonPropertyName("pattern")] + public required FusionPattern Pattern { get; set; } + + /// Stable identifier for the concrete phase. + [JsonPropertyName("phaseId")] + public required string PhaseId { get; set; } + + /// Kind of phase being executed. + [JsonPropertyName("phaseKind")] + public required FusionPhaseKind PhaseKind { get; set; } + + /// Semantic role assigned to the phase. + [JsonPropertyName("role")] + public required string Role { get; set; } +} + +/// Experimental durable HydraFusion phase output and lossless replay checkpoint. +[Experimental(Diagnostics.Experimental)] +public sealed partial class AssistantFusionPhaseCompletedData +{ + /// Provider-normalized textual output produced by the phase. + [JsonPropertyName("content")] + public required string Content { get; set; } + + /// Conversation scope in which the phase executed. + [JsonPropertyName("conversationScope")] + public required FusionConversationScope ConversationScope { get; set; } + + /// Elapsed execution time for the phase in milliseconds. + [JsonPropertyName("durationMs")] + public required double DurationMs { get; set; } + + /// Identifier of the HydraFusion turn containing the phase. + [JsonPropertyName("fusionId")] + public required string FusionId { get; set; } + + /// Concrete model that executed the phase. + [JsonPropertyName("model")] + public required string Model { get; set; } + + /// Stable identifier for the completed phase. + [JsonPropertyName("phaseId")] + public required string PhaseId { get; set; } + + /// Kind of phase that completed. + [JsonPropertyName("phaseKind")] + public required FusionPhaseKind PhaseKind { get; set; } + + /// Exact provider-normalized message used to reconstruct canonical model history. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonInclude] + [JsonPropertyName("projectionMessage")] + internal JsonElement? ProjectionMessage { get; set; } + + /// Projection action for the exact internal message. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonInclude] + [JsonPropertyName("projectionMode")] + internal FusionProjectionMode? ProjectionMode { get; set; } + + /// Semantic role assigned to the completed phase. + [JsonPropertyName("role")] + public required string Role { get; set; } + + /// Terminal request held outside canonical state until selected by the final commit. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonInclude] + [JsonPropertyName("stagedTerminal")] + internal FusionStagedTerminal? StagedTerminal { get; set; } + + /// Durable outcome status of the phase. + [JsonPropertyName("status")] + public required FusionPhaseStatus Status { get; set; } + + /// Aggregate concrete-model usage consumed by the phase. + [JsonPropertyName("usage")] + public required FusionPhaseUsage Usage { get; set; } + + /// Structured judge or critic verdict, when the phase produces one. + [JsonPropertyName("verdict")] + public string? Verdict { get; set; } +} + +/// Experimental durable typed HydraFusion phase failure and degradation transition. +[Experimental(Diagnostics.Experimental)] +public sealed partial class AssistantFusionPhaseFailedData +{ + /// Conversation scope in which the phase executed. + [JsonPropertyName("conversationScope")] + public required FusionConversationScope ConversationScope { get; set; } + + /// Identifier of the fallback phase used to continue the turn after degradation. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("degradedToPhaseId")] + public string? DegradedToPhaseId { get; set; } + + /// Elapsed execution time before the phase failed, in milliseconds. + [JsonPropertyName("durationMs")] + public required double DurationMs { get; set; } + + /// Provider or execution error detail, when available. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("errorMessage")] + public string? ErrorMessage { get; set; } + + /// Identifier of the HydraFusion turn containing the phase. + [JsonPropertyName("fusionId")] + public required string FusionId { get; set; } + + /// Concrete model that attempted the phase. + [JsonPropertyName("model")] + public required string Model { get; set; } + + /// Stable identifier for the failed phase. + [JsonPropertyName("phaseId")] + public required string PhaseId { get; set; } + + /// Kind of phase that failed. + [JsonPropertyName("phaseKind")] + public required FusionPhaseKind PhaseKind { get; set; } + + /// Stable machine-readable reason for the phase failure. + [JsonPropertyName("reason")] + public required string Reason { get; set; } + + /// Semantic role assigned to the failed phase. + [JsonPropertyName("role")] + public required string Role { get; set; } + + /// Durable outcome status of the phase. + [JsonPropertyName("status")] + public required FusionPhaseStatus Status { get; set; } + + /// Aggregate concrete-model usage consumed before the failure. + [JsonPropertyName("usage")] + public required FusionPhaseUsage Usage { get; set; } +} + /// Live progress signal for a provider-hosted server tool (e.g. hosted web search) while it runs, before the finalized serverTools envelope lands on the terminal assistant.message. public sealed partial class AssistantServerToolProgressData { @@ -3025,6 +3530,12 @@ public sealed partial class AssistantMessageData [JsonPropertyName("encryptedContent")] public string? EncryptedContent { get; set; } + /// Experimental HydraFusion source attribution for this ordinary authoritative assistant message. + [Experimental(Diagnostics.Experimental)] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("fusion")] + public FusionAttribution? Fusion { get; set; } + /// CAPI interaction ID for correlating this message with upstream telemetry. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("interactionId")] @@ -3249,6 +3760,12 @@ public sealed partial class AssistantUsageData [JsonPropertyName("frontierSource")] internal string? FrontierSource { get; set; } + /// Experimental HydraFusion attribution for this concrete model call's usage. + [Experimental(Diagnostics.Experimental)] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("fusion")] + public FusionAttribution? Fusion { get; set; } + /// What initiated this API call (e.g., "sub-agent", "mcp-sampling"); absent for user-initiated calls. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("initiator")] @@ -3559,6 +4076,12 @@ public sealed partial class ModelCallFailureData [JsonPropertyName("failureKind")] public ModelCallFailureKind? FailureKind { get; set; } + /// Experimental HydraFusion attribution for this failed concrete model call. + [Experimental(Diagnostics.Experimental)] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("fusion")] + public FusionAttribution? Fusion { get; set; } + /// What initiated this API call (e.g., "sub-agent", "mcp-sampling"); absent for user-initiated calls. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("initiator")] @@ -3674,6 +4197,12 @@ public sealed partial class ModelCallFinishedData /// Model API dispatch metadata for internal telemetry. public sealed partial class ModelCallStartData { + /// Experimental HydraFusion attribution for this concrete model call. + [Experimental(Diagnostics.Experimental)] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("fusion")] + public FusionAttribution? Fusion { get; set; } + /// Model identifier used for this API call, when known. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("model")] @@ -3728,6 +4257,12 @@ public sealed partial class ToolExecutionStartData [JsonPropertyName("displayVerbatim")] public bool? DisplayVerbatim { get; set; } + /// Experimental HydraFusion attribution for this tool execution. + [Experimental(Diagnostics.Experimental)] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("fusion")] + public FusionAttribution? Fusion { get; set; } + /// Name of the MCP server hosting this tool, when the tool is an MCP tool. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("mcpServerName")] @@ -3813,6 +4348,12 @@ public sealed partial class ToolExecutionCompleteData [JsonPropertyName("error")] public ToolExecutionCompleteError? Error { get; set; } + /// Experimental HydraFusion attribution for this tool completion. + [Experimental(Diagnostics.Experimental)] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("fusion")] + public FusionAttribution? Fusion { get; set; } + /// CAPI interaction ID for correlating this tool execution with upstream telemetry. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("interactionId")] @@ -3963,6 +4504,16 @@ public sealed partial class SubagentStartedData [JsonPropertyName("agentName")] public required string AgentName { get; set; } + /// Type of the sub-agent selected at spawn time. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("agentType")] + public string? AgentType { get; set; } + + /// Whether the sub-agent runs synchronously or in the background. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("executionMode")] + public string? ExecutionMode { get; set; } + /// Root id of the factory run that spawned this sub-agent, when it was spawned by one. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("factoryRunId")] @@ -3973,10 +4524,42 @@ public sealed partial class SubagentStartedData [JsonPropertyName("model")] public string? Model { get; set; } - /// Tool call ID of the parent tool invocation that spawned this sub-agent. - [JsonPropertyName("toolCallId")] - public required string ToolCallId { get; set; } -} + /// Task-registry ID of the spawning sub-agent. Absent when the root session spawned this child. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("parentId")] + public string? ParentId { get; set; } + + /// Whether this sub-agent can be resumed. Currently always false. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("resumable")] + public bool? Resumable { get; set; } + + /// Tool call ID of the parent tool invocation that spawned this sub-agent. + [JsonPropertyName("toolCallId")] + public required string ToolCallId { get; set; } +} + +/// Resolved runtime configuration for a configured sub-agent. +public sealed partial class SubagentConfiguredData +{ + /// Resolved context tier, when configured for the model. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("contextTier")] + public string? ContextTier { get; set; } + + /// Resolved model the sub-agent will run with. + [JsonPropertyName("model")] + public required string Model { get; set; } + + /// Whether the sub-agent accepts follow-up turns. + [JsonPropertyName("multiTurn")] + public required bool MultiTurn { get; set; } + + /// Resolved reasoning effort, when configured for the model. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("reasoningEffort")] + public string? ReasoningEffort { get; set; } +} /// Sub-agent completion details for successful execution. public sealed partial class SubagentCompletedData @@ -4147,6 +4730,11 @@ public sealed partial class HookStartData [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("input")] public JsonElement? Input { get; set; } + + /// Tool call ID of the parent tool invocation when this event originates from a sub-agent. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("parentToolCallId")] + public string? ParentToolCallId { get; set; } } /// Hook invocation completion details including output, success status, and error information. @@ -4170,6 +4758,11 @@ public sealed partial class HookEndData [JsonPropertyName("output")] public JsonElement? Output { get; set; } + /// Tool call ID of the parent tool invocation when this event originates from a sub-agent. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("parentToolCallId")] + public string? ParentToolCallId { get; set; } + /// Whether the hook completed successfully. [JsonPropertyName("success")] public required bool Success { get; set; } @@ -5598,6 +6191,42 @@ public sealed partial class CompactionCompleteCompactionTokensUsed public long? OutputTokens { get; set; } } +/// Durable server recommendation for subsequent HydraFusion turns. +/// Nested data type for FusionFollowUpRecommendation. +[Experimental(Diagnostics.Experimental)] +public sealed partial class FusionFollowUpRecommendation +{ + /// Recommended routing action for the next compaction turn. + [JsonPropertyName("compactionTurn")] + public required FusionFollowUpAction CompactionTurn { get; set; } + + /// Recommended routing action for the next user-message turn. + [JsonPropertyName("userTurn")] + public required FusionFollowUpAction UserTurn { get; set; } +} + +/// Validated HydraFusion routing capability scores. +/// Nested data type for FusionScores. +[Experimental(Diagnostics.Experimental)] +public sealed partial class FusionScores +{ + /// Code-generation capability score returned by the authenticated router. + [JsonPropertyName("codeGen")] + public required double CodeGen { get; set; } + + /// Debugging capability score returned by the authenticated router. + [JsonPropertyName("debugging")] + public required double Debugging { get; set; } + + /// Reasoning capability score returned by the authenticated router. + [JsonPropertyName("reasoning")] + public required double Reasoning { get; set; } + + /// Tool-use capability score returned by the authenticated router. + [JsonPropertyName("toolUse")] + public required double ToolUse { get; set; } +} + /// Optional line range to scope the attachment to a specific section of the file. /// Nested data type for AttachmentFileLineRange. public sealed partial class AttachmentFileLineRange @@ -6148,6 +6777,63 @@ public partial class Attachment } +/// Internal durable terminal request staged by a HydraFusion phase until an idempotent final commit selects it. +/// Nested data type for FusionStagedTerminal. +[Experimental(Diagnostics.Experimental)] +internal sealed partial class FusionStagedTerminal +{ + /// Gets or sets the arguments value. + [JsonPropertyName("arguments")] + public required string Arguments { get; set; } + + /// Gets or sets the assistantMessage value. + [JsonPropertyName("assistantMessage")] + public required JsonElement AssistantMessage { get; set; } + + /// Gets or sets the phaseId value. + [JsonPropertyName("phaseId")] + public required string PhaseId { get; set; } + + /// Gets or sets the toolCallId value. + [JsonPropertyName("toolCallId")] + public required string ToolCallId { get; set; } + + /// Gets or sets the toolName value. + [JsonPropertyName("toolName")] + public required string ToolName { get; set; } +} + +/// Aggregate concrete-model usage for one HydraFusion phase. +/// Nested data type for FusionPhaseUsage. +[Experimental(Diagnostics.Experimental)] +public sealed partial class FusionPhaseUsage +{ + /// Total cached input tokens reported for the phase. + [JsonPropertyName("cachedTokens")] + public required long CachedTokens { get; set; } + + /// Total tokens written to prompt cache during the phase. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("cacheWriteTokens")] + public long? CacheWriteTokens { get; set; } + + /// Total input tokens consumed by the phase. + [JsonPropertyName("inputTokens")] + public required long InputTokens { get; set; } + + /// Total output tokens produced by the phase. + [JsonPropertyName("outputTokens")] + public required long OutputTokens { get; set; } + + /// Number of concrete model requests made by the phase. + [JsonPropertyName("requestCount")] + public required long RequestCount { get; set; } + + /// Total normalized AI-unit cost reported for the phase, in nano-AIU. + [JsonPropertyName("totalNanoAiu")] + public required double TotalNanoAiu { get; set; } +} + /// A source that backs one or more cited spans in the assistant's response. /// Nested data type for CitationSource. [Experimental(Diagnostics.Experimental)] @@ -6305,6 +6991,63 @@ public sealed partial class Citations public required CitationSpan[] Spans { get; set; } } +/// Experimental attribution linking an ordinary event to the HydraFusion turn, phase, and concrete source that produced it. +/// Nested data type for FusionAttribution. +[Experimental(Diagnostics.Experimental)] +public sealed partial class FusionAttribution +{ + /// Idempotency identifier for the authoritative commit, when the event belongs to the selected output. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("commitId")] + public string? CommitId { get; set; } + + /// Conversation scope in which the concrete phase executed. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("conversationScope")] + public string? ConversationScope { get; set; } + + /// Stable identifier for the HydraFusion turn that produced the event. + [JsonPropertyName("fusionId")] + public required string FusionId { get; set; } + + /// HydraFusion orchestration pattern selected for the turn. + [JsonPropertyName("pattern")] + public required string Pattern { get; set; } + + /// Identifier of the concrete phase that produced the event. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("phaseId")] + public string? PhaseId { get; set; } + + /// Kind of concrete phase that produced the event. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("phaseKind")] + public string? PhaseKind { get; set; } + + /// HydraFusion routing policy used for the turn. + [JsonPropertyName("policy")] + public required string Policy { get; set; } + + /// Semantic role assigned to the concrete phase. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("role")] + public string? Role { get; set; } + + /// Concrete model that produced the attributed event. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("sourceModel")] + public string? SourceModel { get; set; } + + /// Phase whose output supplied the authoritative content, when different from the executing phase. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("sourcePhaseId")] + public string? SourcePhaseId { get; set; } + + /// Synthetic HydraFusion model selected for the session. + [JsonPropertyName("syntheticModel")] + public required string SyntheticModel { get; set; } +} + /// Neutral provider-tagged reasoning content blocks preserved verbatim for round-tripping. /// Nested data type for AssistantMessageReasoningBlocks. [Experimental(Diagnostics.Experimental)] @@ -6883,6 +7626,11 @@ public sealed partial class ToolExecutionCompleteContentShellExit : ToolExecutio [JsonPropertyName("exitCode")] public required long ExitCode { get; set; } + /// Path reported in the shell session's filesystem namespace when shell output exceeded the configured large-output threshold. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("outputFilePath")] + public string? OutputFilePath { get; set; } + /// Output associated with this shell command, if available. May be partial, truncated, or a preview; not guaranteed to be full output. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("outputPreview")] @@ -10592,6 +11340,195 @@ public override void Write(Utf8JsonWriter writer, TaskCompletionOutcome value, J } } +/// Kind of turn for which HydraFusion routing is running. +[Experimental(Diagnostics.Experimental)] +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct FusionTurnKind : IEquatable +{ + private readonly string? _value; + + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public FusionTurnKind(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } + + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; + + /// A user-message turn. + public static FusionTurnKind User { get; } = new("user"); + + /// A conversation-compaction turn. + public static FusionTurnKind Compaction { get; } = new("compaction"); + + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(FusionTurnKind left, FusionTurnKind right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(FusionTurnKind left, FusionTurnKind right) => !(left == right); + + /// + public override bool Equals(object? obj) => obj is FusionTurnKind other && Equals(other); + + /// + public bool Equals(FusionTurnKind 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 FusionTurnKind Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } + + /// + public override void Write(Utf8JsonWriter writer, FusionTurnKind value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(FusionTurnKind)); + } + } +} + +/// Server-recommended routing behavior for a later HydraFusion turn. +[Experimental(Diagnostics.Experimental)] +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct FusionFollowUpAction : IEquatable +{ + private readonly string? _value; + + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public FusionFollowUpAction(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } + + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; + + /// Reuse the durable primary model without routing. + public static FusionFollowUpAction ReusePrimary { get; } = new("reuse_primary"); + + /// Request a new routing decision. + public static FusionFollowUpAction Reroute { get; } = new("reroute"); + + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(FusionFollowUpAction left, FusionFollowUpAction right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(FusionFollowUpAction left, FusionFollowUpAction right) => !(left == right); + + /// + public override bool Equals(object? obj) => obj is FusionFollowUpAction other && Equals(other); + + /// + public bool Equals(FusionFollowUpAction 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 FusionFollowUpAction Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } + + /// + public override void Write(Utf8JsonWriter writer, FusionFollowUpAction value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(FusionFollowUpAction)); + } + } +} + +/// Validated HydraFusion execution pattern. +[Experimental(Diagnostics.Experimental)] +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct FusionPattern : IEquatable +{ + private readonly string? _value; + + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public FusionPattern(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } + + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; + + /// Run one primary solver phase. + public static FusionPattern Single { get; } = new("single"); + + /// Run a primary phase, a judge, and an optional repair. + public static FusionPattern Cascade { get; } = new("cascade"); + + /// Run a primary draft, a read-only critique, and a revision. + public static FusionPattern Critique { get; } = new("critique"); + + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(FusionPattern left, FusionPattern right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(FusionPattern left, FusionPattern right) => !(left == right); + + /// + public override bool Equals(object? obj) => obj is FusionPattern other && Equals(other); + + /// + public bool Equals(FusionPattern 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 FusionPattern Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } + + /// + public override void Write(Utf8JsonWriter writer, FusionPattern value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(FusionPattern)); + } + } +} + /// The agent mode that was active when this message was sent. [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] @@ -11037,6 +11974,275 @@ public override void Write(Utf8JsonWriter writer, ModelCallFailureTransport valu } } +/// Conversation scope in which a HydraFusion phase executes. +[Experimental(Diagnostics.Experimental)] +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct FusionConversationScope : IEquatable +{ + private readonly string? _value; + + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public FusionConversationScope(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } + + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; + + /// Canonical root conversation history. + public static FusionConversationScope Root { get; } = new("root"); + + /// Isolated read-only review history that does not enter the root conversation. + public static FusionConversationScope Review { get; } = new("review"); + + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(FusionConversationScope left, FusionConversationScope right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(FusionConversationScope left, FusionConversationScope right) => !(left == right); + + /// + public override bool Equals(object? obj) => obj is FusionConversationScope other && Equals(other); + + /// + public bool Equals(FusionConversationScope 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 FusionConversationScope Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } + + /// + public override void Write(Utf8JsonWriter writer, FusionConversationScope value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(FusionConversationScope)); + } + } +} + +/// HydraFusion phase kind. +[Experimental(Diagnostics.Experimental)] +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct FusionPhaseKind : IEquatable +{ + private readonly string? _value; + + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public FusionPhaseKind(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } + + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; + + /// Primary solver phase. + public static FusionPhaseKind Primary { get; } = new("primary"); + + /// Read-only cascade judge phase. + public static FusionPhaseKind Judge { get; } = new("judge"); + + /// Cascade repair phase. + public static FusionPhaseKind Repair { get; } = new("repair"); + + /// Initial critique-pattern draft phase. + public static FusionPhaseKind Draft { get; } = new("draft"); + + /// Read-only critique phase. + public static FusionPhaseKind Critic { get; } = new("critic"); + + /// Critique-pattern revision phase. + public static FusionPhaseKind Revision { get; } = new("revision"); + + /// Follow-up phase continuing from the resolved model. + public static FusionPhaseKind FollowUp { get; } = new("follow_up"); + + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(FusionPhaseKind left, FusionPhaseKind right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(FusionPhaseKind left, FusionPhaseKind right) => !(left == right); + + /// + public override bool Equals(object? obj) => obj is FusionPhaseKind other && Equals(other); + + /// + public bool Equals(FusionPhaseKind 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 FusionPhaseKind Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } + + /// + public override void Write(Utf8JsonWriter writer, FusionPhaseKind value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(FusionPhaseKind)); + } + } +} + +/// How a durable phase checkpoint contributes its exact message to canonical root history. +[Experimental(Diagnostics.Experimental)] +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct FusionProjectionMode : IEquatable +{ + private readonly string? _value; + + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public FusionProjectionMode(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } + + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; + + /// Append the exact root message immediately. + public static FusionProjectionMode Append { get; } = new("append"); + + /// Hold a terminal message outside canonical history until the final commit selects it. + public static FusionProjectionMode Staged { get; } = new("staged"); + + /// Do not project the checkpoint into root history. + public static FusionProjectionMode None { get; } = new("none"); + + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(FusionProjectionMode left, FusionProjectionMode right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(FusionProjectionMode left, FusionProjectionMode right) => !(left == right); + + /// + public override bool Equals(object? obj) => obj is FusionProjectionMode other && Equals(other); + + /// + public bool Equals(FusionProjectionMode 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 FusionProjectionMode Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } + + /// + public override void Write(Utf8JsonWriter writer, FusionProjectionMode value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(FusionProjectionMode)); + } + } +} + +/// Durable outcome status of a HydraFusion phase. +[Experimental(Diagnostics.Experimental)] +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct FusionPhaseStatus : IEquatable +{ + private readonly string? _value; + + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public FusionPhaseStatus(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } + + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; + + /// The phase completed successfully. + public static FusionPhaseStatus Succeeded { get; } = new("succeeded"); + + /// The phase failed. + public static FusionPhaseStatus Failed { get; } = new("failed"); + + /// The phase was cancelled. + public static FusionPhaseStatus Cancelled { get; } = new("cancelled"); + + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(FusionPhaseStatus left, FusionPhaseStatus right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(FusionPhaseStatus left, FusionPhaseStatus right) => !(left == right); + + /// + public override bool Equals(object? obj) => obj is FusionPhaseStatus other && Equals(other); + + /// + public bool Equals(FusionPhaseStatus 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 FusionPhaseStatus Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } + + /// + public override void Write(Utf8JsonWriter writer, FusionPhaseStatus value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(FusionPhaseStatus)); + } + } +} + /// Tool call type: "function" for standard tool calls, "custom" for grammar-based tool calls. Defaults to "function" when absent. [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] @@ -14208,6 +15414,12 @@ public override void Write(Utf8JsonWriter writer, ExtensionsLoadedExtensionStatu [JsonSerializable(typeof(AbortEvent))] [JsonSerializable(typeof(AgentInterruptedData))] [JsonSerializable(typeof(AgentInterruptedEvent))] +[JsonSerializable(typeof(AssistantFusionPhaseCompletedData))] +[JsonSerializable(typeof(AssistantFusionPhaseCompletedEvent))] +[JsonSerializable(typeof(AssistantFusionPhaseFailedData))] +[JsonSerializable(typeof(AssistantFusionPhaseFailedEvent))] +[JsonSerializable(typeof(AssistantFusionPhaseStartedData))] +[JsonSerializable(typeof(AssistantFusionPhaseStartedEvent))] [JsonSerializable(typeof(AssistantIdleData))] [JsonSerializable(typeof(AssistantIdleEvent))] [JsonSerializable(typeof(AssistantIntentData))] @@ -14319,6 +15531,11 @@ public override void Write(Utf8JsonWriter writer, ExtensionsLoadedExtensionStatu [JsonSerializable(typeof(FactoryRunStartedEvent))] [JsonSerializable(typeof(FactoryRunUpdatedData))] [JsonSerializable(typeof(FactoryRunUpdatedEvent))] +[JsonSerializable(typeof(FusionAttribution))] +[JsonSerializable(typeof(FusionFollowUpRecommendation))] +[JsonSerializable(typeof(FusionPhaseUsage))] +[JsonSerializable(typeof(FusionScores))] +[JsonSerializable(typeof(FusionStagedTerminal))] [JsonSerializable(typeof(GitHubMcpToolConfig))] [JsonSerializable(typeof(GitHubRepoRef))] [JsonSerializable(typeof(HandoffRepository))] @@ -14458,6 +15675,14 @@ public override void Write(Utf8JsonWriter writer, ExtensionsLoadedExtensionStatu [JsonSerializable(typeof(SessionExtensionsAttachmentsPushedEvent))] [JsonSerializable(typeof(SessionExtensionsLoadedData))] [JsonSerializable(typeof(SessionExtensionsLoadedEvent))] +[JsonSerializable(typeof(SessionFusionCompletedData))] +[JsonSerializable(typeof(SessionFusionCompletedEvent))] +[JsonSerializable(typeof(SessionFusionResolvedData))] +[JsonSerializable(typeof(SessionFusionResolvedEvent))] +[JsonSerializable(typeof(SessionFusionRouteFailedData))] +[JsonSerializable(typeof(SessionFusionRouteFailedEvent))] +[JsonSerializable(typeof(SessionFusionRouteStartedData))] +[JsonSerializable(typeof(SessionFusionRouteStartedEvent))] [JsonSerializable(typeof(SessionHandoffData))] [JsonSerializable(typeof(SessionHandoffEvent))] [JsonSerializable(typeof(SessionIdleData))] @@ -14536,6 +15761,8 @@ public override void Write(Utf8JsonWriter writer, ExtensionsLoadedExtensionStatu [JsonSerializable(typeof(SkillsLoadedSkill))] [JsonSerializable(typeof(SubagentCompletedData))] [JsonSerializable(typeof(SubagentCompletedEvent))] +[JsonSerializable(typeof(SubagentConfiguredData))] +[JsonSerializable(typeof(SubagentConfiguredEvent))] [JsonSerializable(typeof(SubagentDeselectedData))] [JsonSerializable(typeof(SubagentDeselectedEvent))] [JsonSerializable(typeof(SubagentFailedData))] diff --git a/dotnet/test/E2E/RewindE2ETests.cs b/dotnet/test/E2E/RewindE2ETests.cs index 75aa328abe..74c0b8ab9f 100644 --- a/dotnet/test/E2E/RewindE2ETests.cs +++ b/dotnet/test/E2E/RewindE2ETests.cs @@ -18,6 +18,10 @@ public class RewindE2ETests(E2ETestFixture fixture, ITestOutputHelper output) [Fact] public async Task Should_Restore_Tracked_File_And_Conversation() { + // TODO(cli-1.0.81): Re-enable when Windows file-change tracking records built-in create tool writes. + if (OperatingSystem.IsWindows()) + return; + var filePath = Path.Join(Ctx.WorkDir, FileName); await using var session = await CreateSessionAsync(new SessionConfig { @@ -47,7 +51,7 @@ await TestHelper.WaitForConditionAsync( && rewindPoints.Points[0].CanRestoreFiles && rewindPoints.Points[0].FileCount == 1; }, - timeout: TimeSpan.FromSeconds(10), + timeout: TimeSpan.FromSeconds(30), timeoutMessage: "Timed out waiting for a restorable file rewind point.", pollInterval: TimeSpan.FromMilliseconds(100)); diff --git a/go/internal/e2e/rewind_e2e_test.go b/go/internal/e2e/rewind_e2e_test.go index b15e546eb1..5fc29a13e8 100644 --- a/go/internal/e2e/rewind_e2e_test.go +++ b/go/internal/e2e/rewind_e2e_test.go @@ -24,6 +24,10 @@ func TestRewindE2E(t *testing.T) { t.Cleanup(func() { client.ForceStop() }) t.Run("should restore tracked file and conversation", func(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("blocked on CLI 1.0.81 file-change tracking regression on Windows") + } + ctx.ConfigureForTest(t) filePath := filepath.Join(ctx.WorkDir, rewindFileName) session, err := client.CreateSession(t.Context(), &copilot.SessionConfig{ @@ -113,17 +117,20 @@ func TestRewindE2E(t *testing.T) { func waitForRewindPoints(t *testing.T, session *copilot.Session) *rpc.HistoryListRewindPointsResult { t.Helper() - deadline := time.Now().Add(10 * time.Second) + deadline := time.Now().Add(30 * time.Second) for { result, err := session.RPC.History.ListRewindPoints(t.Context()) if err != nil { t.Fatalf("ListRewindPoints failed: %v", err) } - if result.UnavailableReason == nil { + if result.UnavailableReason == nil && + len(result.Points) == 1 && + result.Points[0].CanRestoreFiles && + result.Points[0].FileCount == 1 { return result } if time.Now().After(deadline) { - t.Fatalf("Timed out waiting for rewind points: %s", *result.UnavailableReason) + t.Fatalf("Timed out waiting for a restorable rewind point: %+v", result) } time.Sleep(100 * time.Millisecond) } diff --git a/go/rpc/zrpc.go b/go/rpc/zrpc.go index 2b3943988d..25da3dea4d 100644 --- a/go/rpc/zrpc.go +++ b/go/rpc/zrpc.go @@ -2980,6 +2980,9 @@ type ExternalToolTextResultForLlmContentShellExit struct { Cwd *string `json:"cwd,omitempty"` // Exit code from the completed shell command ExitCode int64 `json:"exitCode"` + // Path reported in the shell session's filesystem namespace when shell output exceeded the + // configured large-output threshold. + OutputFilePath *string `json:"outputFilePath,omitempty"` // Output associated with this shell command, if available. May be partial, truncated, or a // preview; not guaranteed to be full output. OutputPreview *string `json:"outputPreview,omitempty"` @@ -3416,6 +3419,10 @@ type FactoryProgressPage struct { type FactoryResumeRequest struct { // Optional per-invocation resource ceiling overrides. Limits *FactoryRunLimits `json:"limits,omitempty"` + // Whether to emit factory phase names to the session transcript. + LogPhaseNames *bool `json:"logPhaseNames,omitempty"` + // Whether to notify the originating session when the factory completes. + NotifyOnComplete *bool `json:"notifyOnComplete,omitempty"` // Factory run identifier. RunID string `json:"runId"` } @@ -3670,6 +3677,48 @@ type FactoryRunTerminal struct { ResultPreview *string `json:"resultPreview,omitempty"` } +// Internal parameters for resuming a factory run from a tool. +// Experimental: FactoryToolResumeRequest is part of an experimental API and may change or +// be removed. +// Internal: FactoryToolResumeRequest is an internal SDK API and is not part of the public +// surface. +type FactoryToolResumeRequest struct { + // Optional per-invocation resource ceiling overrides. + Limits *FactoryRunLimits `json:"limits,omitempty"` + // Factory run identifier. + RunID string `json:"runId"` + // Opaque identifier of the originating tool call. + ToolCallID *string `json:"toolCallId,omitempty"` +} + +// Options for an internal tool-originated factory invocation. +// Experimental: FactoryToolRunOptions is part of an experimental API and may change or be +// removed. +// Internal: FactoryToolRunOptions is an internal SDK API and is not part of the public +// surface. +type FactoryToolRunOptions struct { + // Per-invocation resource ceiling overrides. + Limits *FactoryRunLimits `json:"limits,omitempty"` + // Run identifier whose journal and progress should seed this resumed run. + ResumeFromRunID *string `json:"resumeFromRunId,omitempty"` +} + +// Internal parameters for invoking a registered factory from a tool. +// Experimental: FactoryToolRunRequest is part of an experimental API and may change or be +// removed. +// Internal: FactoryToolRunRequest is an internal SDK API and is not part of the public +// surface. +type FactoryToolRunRequest struct { + // Factory input value. + Args any `json:"args"` + // Registered factory name. + Name string `json:"name"` + // Tool-originated factory invocation options. + Options *FactoryToolRunOptions `json:"options,omitempty"` + // Opaque identifier of the originating tool call. + ToolCallID *string `json:"toolCallId,omitempty"` +} + // Content filtering mode to apply to all tools, or a map of tool name to content filtering // mode. // Experimental: FilterMapping is part of an experimental API and may change or be removed. @@ -10146,6 +10195,10 @@ type RemoteSessionRepository struct { type RunOptions struct { // Per-invocation resource ceiling overrides. Limits *FactoryRunLimits `json:"limits,omitempty"` + // Whether to emit factory phase names to the session transcript. + LogPhaseNames *bool `json:"logPhaseNames,omitempty"` + // Whether to notify the originating session when the factory completes. + NotifyOnComplete *bool `json:"notifyOnComplete,omitempty"` // Run identifier whose journal and progress should seed this resumed run. ResumeFromRunID *string `json:"resumeFromRunId,omitempty"` } @@ -14284,12 +14337,8 @@ type ToolsGetBuiltinDescriptorsRequest struct { BackgroundTaskNotificationsEnabled *bool `json:"backgroundTaskNotificationsEnabled,omitempty"` // Whether tool descriptors should include authoring metadata. IncludeAuthor *bool `json:"includeAuthor,omitempty"` - // Whether line numbers should be omitted from the view tool descriptor. - NoViewLineNumbers *bool `json:"noViewLineNumbers,omitempty"` // Whether descriptors should favor fewer user-intervention prompts. ReduceUserIntervention *bool `json:"reduceUserIntervention,omitempty"` - // Whether shell commands may only run asynchronously. - ShellAsyncOnlyEnabled *bool `json:"shellAsyncOnlyEnabled,omitempty"` // Shell-specific names and description lines for shell tools. ShellConfig *ToolsShellDescriptorConfig `json:"shellConfig,omitempty"` // Whether the configured shell supports PowerShell 7 syntax. @@ -21903,6 +21952,12 @@ func (a *FactoryAPI) Resume(ctx context.Context, params *FactoryResumeRequest) ( if params.Limits != nil { req["limits"] = *params.Limits } + if params.LogPhaseNames != nil { + req["logPhaseNames"] = *params.LogPhaseNames + } + if params.NotifyOnComplete != nil { + req["notifyOnComplete"] = *params.NotifyOnComplete + } req["runId"] = params.RunID } raw, err := a.client.Request(ctx, "session.factory.resume", req) @@ -25617,15 +25672,9 @@ func (a *ToolsAPI) GetBuiltinDescriptors(ctx context.Context, params *ToolsGetBu if params.IncludeAuthor != nil { req["includeAuthor"] = *params.IncludeAuthor } - if params.NoViewLineNumbers != nil { - req["noViewLineNumbers"] = *params.NoViewLineNumbers - } if params.ReduceUserIntervention != nil { req["reduceUserIntervention"] = *params.ReduceUserIntervention } - if params.ShellAsyncOnlyEnabled != nil { - req["shellAsyncOnlyEnabled"] = *params.ShellAsyncOnlyEnabled - } if params.ShellConfig != nil { req["shellConfig"] = *params.ShellConfig } @@ -26973,6 +27022,72 @@ func (a *InternalCommandsAPI) FinalizeInvocationEffect(ctx context.Context, para return &result, nil } +// Experimental: InternalFactoryAPI contains experimental APIs that may change or be removed. +type InternalFactoryAPI internalSessionAPI + +// ResumeFromTool internal tool-originated factory resume. +// +// RPC method: session.factory.resumeFromTool. +// +// Parameters: Internal parameters for resuming a factory run from a tool. +// +// Returns: Resolved persisted factory identity and resumed run envelope. +// Internal: ResumeFromTool is part of the SDK's internal handshake/plumbing; external +// callers should not use it. +func (a *InternalFactoryAPI) ResumeFromTool(ctx context.Context, params *FactoryToolResumeRequest) (*FactoryResumeResult, error) { + req := map[string]any{"sessionId": a.sessionID} + if params != nil { + if params.Limits != nil { + req["limits"] = *params.Limits + } + req["runId"] = params.RunID + if params.ToolCallID != nil { + req["toolCallId"] = *params.ToolCallID + } + } + raw, err := a.client.Request(ctx, "session.factory.resumeFromTool", req) + if err != nil { + return nil, err + } + var result FactoryResumeResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +// RunFromTool internal tool-originated factory invocation. +// +// RPC method: session.factory.runFromTool. +// +// Parameters: Internal parameters for invoking a registered factory from a tool. +// +// Returns: Complete current or terminal factory run envelope. +// Internal: RunFromTool is part of the SDK's internal handshake/plumbing; external callers +// should not use it. +func (a *InternalFactoryAPI) RunFromTool(ctx context.Context, params *FactoryToolRunRequest) (*FactoryRunResult, error) { + req := map[string]any{"sessionId": a.sessionID} + if params != nil { + req["args"] = params.Args + req["name"] = params.Name + if params.Options != nil { + req["options"] = *params.Options + } + if params.ToolCallID != nil { + req["toolCallId"] = *params.ToolCallID + } + } + raw, err := a.client.Request(ctx, "session.factory.runFromTool", req) + if err != nil { + return nil, err + } + var result FactoryRunResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + // Experimental: InternalGitHubAuthAPI contains experimental APIs that may change or be // removed. type InternalGitHubAuthAPI internalSessionAPI @@ -27771,6 +27886,7 @@ type InternalSessionRPC struct { Canvas *InternalCanvasAPI Commands *InternalCommandsAPI + Factory *InternalFactoryAPI GitHubAuth *InternalGitHubAuthAPI MCP *InternalMCPAPI Model *InternalModelAPI @@ -27816,6 +27932,7 @@ func NewInternalSessionRPC(client *jsonrpc2.Client, sessionID string) *InternalS r.common = internalSessionAPI{client: client, sessionID: sessionID} r.Canvas = (*InternalCanvasAPI)(&r.common) r.Commands = (*InternalCommandsAPI)(&r.common) + r.Factory = (*InternalFactoryAPI)(&r.common) r.GitHubAuth = (*InternalGitHubAuthAPI)(&r.common) r.MCP = (*InternalMCPAPI)(&r.common) r.Model = (*InternalModelAPI)(&r.common) diff --git a/go/rpc/zsession_encoding.go b/go/rpc/zsession_encoding.go index 5c5d3bca27..4c03a42c00 100644 --- a/go/rpc/zsession_encoding.go +++ b/go/rpc/zsession_encoding.go @@ -47,6 +47,24 @@ func (e *SessionEvent) UnmarshalJSON(data []byte) error { return err } e.Data = &d + case SessionEventTypeAssistantFusionPhaseCompleted: + var d AssistantFusionPhaseCompletedData + if err := json.Unmarshal(raw.Data, &d); err != nil { + return err + } + e.Data = &d + case SessionEventTypeAssistantFusionPhaseFailed: + var d AssistantFusionPhaseFailedData + if err := json.Unmarshal(raw.Data, &d); err != nil { + return err + } + e.Data = &d + case SessionEventTypeAssistantFusionPhaseStarted: + var d AssistantFusionPhaseStartedData + if err := json.Unmarshal(raw.Data, &d); err != nil { + return err + } + e.Data = &d case SessionEventTypeAssistantIdle: var d AssistantIdleData if err := json.Unmarshal(raw.Data, &d); err != nil { @@ -467,6 +485,30 @@ func (e *SessionEvent) UnmarshalJSON(data []byte) error { return err } e.Data = &d + case SessionEventTypeSessionFusionCompleted: + var d SessionFusionCompletedData + if err := json.Unmarshal(raw.Data, &d); err != nil { + return err + } + e.Data = &d + case SessionEventTypeSessionFusionResolved: + var d SessionFusionResolvedData + if err := json.Unmarshal(raw.Data, &d); err != nil { + return err + } + e.Data = &d + case SessionEventTypeSessionFusionRouteFailed: + var d SessionFusionRouteFailedData + if err := json.Unmarshal(raw.Data, &d); err != nil { + return err + } + e.Data = &d + case SessionEventTypeSessionFusionRouteStarted: + var d SessionFusionRouteStartedData + if err := json.Unmarshal(raw.Data, &d); err != nil { + return err + } + e.Data = &d case SessionEventTypeSessionHandoff: var d SessionHandoffData if err := json.Unmarshal(raw.Data, &d); err != nil { @@ -671,6 +713,12 @@ func (e *SessionEvent) UnmarshalJSON(data []byte) error { return err } e.Data = &d + case SessionEventTypeSubagentConfigured: + var d SubagentConfiguredData + if err := json.Unmarshal(raw.Data, &d); err != nil { + return err + } + e.Data = &d case SessionEventTypeSubagentDeselected: var d SubagentDeselectedData if err := json.Unmarshal(raw.Data, &d); err != nil { diff --git a/go/rpc/zsession_events.go b/go/rpc/zsession_events.go index 2abfd75a66..1d0110b7a0 100644 --- a/go/rpc/zsession_events.go +++ b/go/rpc/zsession_events.go @@ -53,8 +53,17 @@ func (r RawSessionEventData) Type() SessionEventType { type SessionEventType string const ( - SessionEventTypeAbort SessionEventType = "abort" - SessionEventTypeAgentInterrupted SessionEventType = "agent.interrupted" + SessionEventTypeAbort SessionEventType = "abort" + SessionEventTypeAgentInterrupted SessionEventType = "agent.interrupted" + // Experimental: SessionEventTypeAssistantFusionPhaseCompleted identifies an experimental + // event that may change or be removed. + SessionEventTypeAssistantFusionPhaseCompleted SessionEventType = "assistant.fusion_phase_completed" + // Experimental: SessionEventTypeAssistantFusionPhaseFailed identifies an experimental event + // that may change or be removed. + SessionEventTypeAssistantFusionPhaseFailed SessionEventType = "assistant.fusion_phase_failed" + // Experimental: SessionEventTypeAssistantFusionPhaseStarted identifies an experimental + // event that may change or be removed. + SessionEventTypeAssistantFusionPhaseStarted SessionEventType = "assistant.fusion_phase_started" SessionEventTypeAssistantIdle SessionEventType = "assistant.idle" SessionEventTypeAssistantIntent SessionEventType = "assistant.intent" SessionEventTypeAssistantMessage SessionEventType = "assistant.message" @@ -147,11 +156,23 @@ const ( SessionEventTypeSessionError SessionEventType = "session.error" SessionEventTypeSessionExtensionsAttachmentsPushed SessionEventType = "session.extensions.attachments_pushed" SessionEventTypeSessionExtensionsLoaded SessionEventType = "session.extensions_loaded" - SessionEventTypeSessionHandoff SessionEventType = "session.handoff" - SessionEventTypeSessionIdle SessionEventType = "session.idle" - SessionEventTypeSessionInfo SessionEventType = "session.info" - SessionEventTypeSessionLimitsExhaustedCompleted SessionEventType = "session_limits_exhausted.completed" - SessionEventTypeSessionLimitsExhaustedRequested SessionEventType = "session_limits_exhausted.requested" + // Experimental: SessionEventTypeSessionFusionCompleted identifies an experimental event + // that may change or be removed. + SessionEventTypeSessionFusionCompleted SessionEventType = "session.fusion_completed" + // Experimental: SessionEventTypeSessionFusionResolved identifies an experimental event that + // may change or be removed. + SessionEventTypeSessionFusionResolved SessionEventType = "session.fusion_resolved" + // Experimental: SessionEventTypeSessionFusionRouteFailed identifies an experimental event + // that may change or be removed. + SessionEventTypeSessionFusionRouteFailed SessionEventType = "session.fusion_route_failed" + // Experimental: SessionEventTypeSessionFusionRouteStarted identifies an experimental event + // that may change or be removed. + SessionEventTypeSessionFusionRouteStarted SessionEventType = "session.fusion_route_started" + SessionEventTypeSessionHandoff SessionEventType = "session.handoff" + SessionEventTypeSessionIdle SessionEventType = "session.idle" + SessionEventTypeSessionInfo SessionEventType = "session.info" + SessionEventTypeSessionLimitsExhaustedCompleted SessionEventType = "session_limits_exhausted.completed" + SessionEventTypeSessionLimitsExhaustedRequested SessionEventType = "session_limits_exhausted.requested" // Experimental: SessionEventTypeSessionManagedSettingsEnforced identifies an experimental // event that may change or be removed. SessionEventTypeSessionManagedSettingsEnforced SessionEventType = "session.managed_settings_enforced" @@ -187,6 +208,7 @@ const ( SessionEventTypeSessionWorkspaceFileChanged SessionEventType = "session.workspace_file_changed" SessionEventTypeSkillInvoked SessionEventType = "skill.invoked" SessionEventTypeSubagentCompleted SessionEventType = "subagent.completed" + SessionEventTypeSubagentConfigured SessionEventType = "subagent.configured" SessionEventTypeSubagentDeselected SessionEventType = "subagent.deselected" SessionEventTypeSubagentFailed SessionEventType = "subagent.failed" SessionEventTypeSubagentSelected SessionEventType = "subagent.selected" @@ -327,6 +349,9 @@ type AssistantMessageData struct { Content string `json:"content"` // Encrypted reasoning content from OpenAI models. Session-bound and stripped on resume. EncryptedContent *string `json:"encryptedContent,omitempty"` + // Experimental HydraFusion source attribution for this ordinary authoritative assistant message. + // Experimental: Fusion is part of an experimental API and may change or be removed. + Fusion *FusionAttribution `json:"fusion,omitempty"` // CAPI interaction ID for correlating this message with upstream telemetry InteractionID *string `json:"interactionId,omitempty"` // Unique identifier for this assistant message @@ -853,6 +878,243 @@ type SessionErrorData struct { func (*SessionErrorData) sessionEventData() {} func (*SessionErrorData) Type() SessionEventType { return SessionEventTypeSessionError } +// Experimental durable HydraFusion phase output and lossless replay checkpoint. +// Experimental: AssistantFusionPhaseCompletedData is part of an experimental API and may change or be removed. +type AssistantFusionPhaseCompletedData struct { + // Provider-normalized textual output produced by the phase. + Content string `json:"content"` + // Conversation scope in which the phase executed. + ConversationScope FusionConversationScope `json:"conversationScope"` + // Elapsed execution time for the phase in milliseconds. + DurationMs float64 `json:"durationMs"` + // Identifier of the HydraFusion turn containing the phase. + FusionID string `json:"fusionId"` + // Concrete model that executed the phase. + Model string `json:"model"` + // Stable identifier for the completed phase. + PhaseID string `json:"phaseId"` + // Kind of phase that completed. + PhaseKind FusionPhaseKind `json:"phaseKind"` + // Exact provider-normalized message used to reconstruct canonical model history. + // Internal: ProjectionMessage is part of the SDK's internal API surface and is not intended for external use. + ProjectionMessage any `json:"projectionMessage,omitempty"` + // Projection action for the exact internal message. + // Internal: ProjectionMode is part of the SDK's internal API surface and is not intended for external use. + ProjectionMode *FusionProjectionMode `json:"projectionMode,omitempty"` + // Semantic role assigned to the completed phase. + Role string `json:"role"` + // Terminal request held outside canonical state until selected by the final commit. + // Internal: StagedTerminal is part of the SDK's internal API surface and is not intended for external use. + StagedTerminal *FusionStagedTerminal `json:"stagedTerminal,omitempty"` + // Durable outcome status of the phase. + Status FusionPhaseStatus `json:"status"` + // Aggregate concrete-model usage consumed by the phase. + Usage FusionPhaseUsage `json:"usage"` + // Structured judge or critic verdict, when the phase produces one. + Verdict *string `json:"verdict"` +} + +func (*AssistantFusionPhaseCompletedData) sessionEventData() {} +func (*AssistantFusionPhaseCompletedData) Type() SessionEventType { + return SessionEventTypeAssistantFusionPhaseCompleted +} + +// Experimental durable HydraFusion routing failure and the deterministic concrete fallback selected for the turn. +// Experimental: SessionFusionRouteFailedData is part of an experimental API and may change or be removed. +type SessionFusionRouteFailedData struct { + // Identifier of the routing attempt that failed. + AttemptID string `json:"attemptId"` + // Provider or validation error detail, when available. + ErrorMessage *string `json:"errorMessage,omitempty"` + // Concrete model selected as the deterministic fallback. + FallbackModel string `json:"fallbackModel"` + // HydraFusion routing policy requested for the turn. + Policy string `json:"policy"` + // Stable machine-readable reason for the routing failure. + Reason string `json:"reason"` + // Elapsed routing time in milliseconds before the failure. + RoutingLatencyMs *float64 `json:"routingLatencyMs,omitempty"` + // Synthetic HydraFusion model selected for the session. + SyntheticModel string `json:"syntheticModel"` +} + +func (*SessionFusionRouteFailedData) sessionEventData() {} +func (*SessionFusionRouteFailedData) Type() SessionEventType { + return SessionEventTypeSessionFusionRouteFailed +} + +// Experimental durable aggregate outcome of a HydraFusion turn. +// Experimental: SessionFusionCompletedData is part of an experimental API and may change or be removed. +type SessionFusionCompletedData struct { + // Total cached input tokens reported across all phases. + CachedTokens int64 `json:"cachedTokens"` + // Total tokens written to prompt cache across all phases. + CacheWriteTokens *int64 `json:"cacheWriteTokens,omitempty"` + // Idempotency identifier for the authoritative final commit. + CommitID string `json:"commitId"` + // Reason the turn used a degraded route, when applicable. + DegradedReason *string `json:"degradedReason"` + // Total elapsed execution time for the HydraFusion turn in milliseconds. + DurationMs float64 `json:"durationMs"` + // Concrete model that supplied the authoritative final content. + FinalSourceModel *string `json:"finalSourceModel"` + // Phase whose output supplied the authoritative final content. + FinalSourcePhaseID *string `json:"finalSourcePhaseId"` + // Concrete model recommended for eligible follow-up turns. + FollowUpModel string `json:"followUpModel"` + // Stable identifier for the completed HydraFusion turn. + FusionID string `json:"fusionId"` + // Total input tokens consumed across all phases. + InputTokens int64 `json:"inputTokens"` + // Stable aggregate outcome of the HydraFusion turn. + Outcome string `json:"outcome"` + // Total output tokens produced across all phases. + OutputTokens int64 `json:"outputTokens"` + // HydraFusion orchestration pattern executed for the turn. + Pattern FusionPattern `json:"pattern"` + // Number of concrete phases attempted by the turn. + PhaseCount int64 `json:"phaseCount"` + // Total concrete model requests made across all phases. + RequestCount int64 `json:"requestCount"` + // Synthetic HydraFusion model selected for the session. + SyntheticModel string `json:"syntheticModel"` + // Total normalized AI-unit cost reported across all phases, in nano-AIU. + TotalNanoAiu float64 `json:"totalNanoAiu"` + // Identifier of the session turn associated with the completion. + TurnID string `json:"turnId"` +} + +func (*SessionFusionCompletedData) sessionEventData() {} +func (*SessionFusionCompletedData) Type() SessionEventType { + return SessionEventTypeSessionFusionCompleted +} + +// Experimental durable typed HydraFusion phase failure and degradation transition. +// Experimental: AssistantFusionPhaseFailedData is part of an experimental API and may change or be removed. +type AssistantFusionPhaseFailedData struct { + // Conversation scope in which the phase executed. + ConversationScope FusionConversationScope `json:"conversationScope"` + // Identifier of the fallback phase used to continue the turn after degradation. + DegradedToPhaseID *string `json:"degradedToPhaseId,omitempty"` + // Elapsed execution time before the phase failed, in milliseconds. + DurationMs float64 `json:"durationMs"` + // Provider or execution error detail, when available. + ErrorMessage *string `json:"errorMessage,omitempty"` + // Identifier of the HydraFusion turn containing the phase. + FusionID string `json:"fusionId"` + // Concrete model that attempted the phase. + Model string `json:"model"` + // Stable identifier for the failed phase. + PhaseID string `json:"phaseId"` + // Kind of phase that failed. + PhaseKind FusionPhaseKind `json:"phaseKind"` + // Stable machine-readable reason for the phase failure. + Reason string `json:"reason"` + // Semantic role assigned to the failed phase. + Role string `json:"role"` + // Durable outcome status of the phase. + Status FusionPhaseStatus `json:"status"` + // Aggregate concrete-model usage consumed before the failure. + Usage FusionPhaseUsage `json:"usage"` +} + +func (*AssistantFusionPhaseFailedData) sessionEventData() {} +func (*AssistantFusionPhaseFailedData) Type() SessionEventType { + return SessionEventTypeAssistantFusionPhaseFailed +} + +// Experimental durable validated HydraFusion route and turn policy. +// Experimental: SessionFusionResolvedData is part of an experimental API and may change or be removed. +type SessionFusionResolvedData struct { + // Version of the validated HydraFusion event contract. + ContractVersion int64 `json:"contractVersion"` + // Concrete model used when the planned primary model cannot execute. + FallbackModel string `json:"fallbackModel"` + // Router recommendation controlling reuse or rerouting on later turns. + FollowUp *FusionFollowUpRecommendation `json:"followUp,omitempty"` + // Concrete model recommended for eligible follow-up turns. + FollowUpModel string `json:"followUpModel"` + // Stable identifier for the resolved HydraFusion turn. + FusionID string `json:"fusionId"` + // Version of the executable model universe used for selection. + ModelUniverseVersion *string `json:"modelUniverseVersion,omitempty"` + // Validated orchestration pattern selected for the turn. + Pattern FusionPattern `json:"pattern"` + // Version of the validated execution-plan format. + PlanVersion *string `json:"planVersion,omitempty"` + // HydraFusion routing policy used to resolve the plan. + Policy string `json:"policy"` + // Version of the local routing policy. + PolicyVersion *string `json:"policyVersion,omitempty"` + // Concrete model selected for the primary solver phase. + PrimaryModel string `json:"primaryModel"` + // Router implementation that supplied the plan. + RouteSource *string `json:"routeSource,omitempty"` + // Elapsed time in milliseconds required to resolve and validate the route. + RoutingLatencyMs *float64 `json:"routingLatencyMs,omitempty"` + // Identifier of the local policy rule that matched. + RuleID *string `json:"ruleId,omitempty"` + // Zero-based index of the local policy rule that matched. + RuleIndex *int64 `json:"ruleIndex,omitempty"` + // Human-readable name of the local policy rule that matched. + RuleName *string `json:"ruleName,omitempty"` + // Validated capability scores used to select the route. + Scores *FusionScores `json:"scores,omitempty"` + // Concrete model selected for the review or judge phase, when required. + SecondaryModel *string `json:"secondaryModel"` + // Synthetic HydraFusion model selected for the session. + SyntheticModel string `json:"syntheticModel"` + // Identifier of the session turn associated with the route. + TurnID string `json:"turnId"` +} + +func (*SessionFusionResolvedData) sessionEventData() {} +func (*SessionFusionResolvedData) Type() SessionEventType { + return SessionEventTypeSessionFusionResolved +} + +// Experimental transient HydraFusion phase/model/role signal. +// Experimental: AssistantFusionPhaseStartedData is part of an experimental API and may change or be removed. +type AssistantFusionPhaseStartedData struct { + // Conversation scope in which the phase executes. + ConversationScope FusionConversationScope `json:"conversationScope"` + // Identifier of the HydraFusion turn containing the phase. + FusionID string `json:"fusionId"` + // Concrete model executing the phase. + Model string `json:"model"` + // HydraFusion orchestration pattern containing the phase. + Pattern FusionPattern `json:"pattern"` + // Stable identifier for the concrete phase. + PhaseID string `json:"phaseId"` + // Kind of phase being executed. + PhaseKind FusionPhaseKind `json:"phaseKind"` + // Semantic role assigned to the phase. + Role string `json:"role"` +} + +func (*AssistantFusionPhaseStartedData) sessionEventData() {} +func (*AssistantFusionPhaseStartedData) Type() SessionEventType { + return SessionEventTypeAssistantFusionPhaseStarted +} + +// Experimental transient signal that HydraFusion routing has started for an eligible turn. +// Experimental: SessionFusionRouteStartedData is part of an experimental API and may change or be removed. +type SessionFusionRouteStartedData struct { + // Identifier for this routing attempt before a durable Fusion turn exists. + AttemptID string `json:"attemptId"` + // HydraFusion routing policy requested for the turn. + Policy *string `json:"policy,omitempty"` + // Synthetic HydraFusion model selected for the session. + SyntheticModel *string `json:"syntheticModel,omitempty"` + // Kind of turn being routed. + TurnKind FusionTurnKind `json:"turnKind"` +} + +func (*SessionFusionRouteStartedData) sessionEventData() {} +func (*SessionFusionRouteStartedData) Type() SessionEventType { + return SessionEventTypeSessionFusionRouteStarted +} + // External tool completion notification signaling UI dismissal type ExternalToolCompletedData struct { // Request ID of the resolved external tool request; clients should dismiss any UI for this request @@ -909,6 +1171,9 @@ type ModelCallFailureData struct { ErrorType *string `json:"errorType,omitempty"` // Whether the failure originated from an API response or the request transport FailureKind *ModelCallFailureKind `json:"failureKind,omitempty"` + // Experimental HydraFusion attribution for this failed concrete model call. + // Experimental: Fusion is part of an experimental API and may change or be removed. + Fusion *FusionAttribution `json:"fusion,omitempty"` // What initiated this API call (e.g., "sub-agent", "mcp-sampling"); absent for user-initiated calls Initiator *string `json:"initiator,omitempty"` // Authoritative interaction classification for the failed call, matching `assistant.usage.interactionType` (for example `conversation-agent`, `conversation-subagent`, or `conversation-sampling`). Absent when the producer cannot classify the interaction. @@ -976,6 +1241,8 @@ type HookEndData struct { HookType string `json:"hookType"` // Output data produced by the hook Output any `json:"output,omitempty"` + // Tool call ID of the parent tool invocation when this event originates from a sub-agent + ParentToolCallID *string `json:"parentToolCallId,omitempty"` // Whether the hook completed successfully Success bool `json:"success"` } @@ -991,6 +1258,8 @@ type HookStartData struct { HookType string `json:"hookType"` // Input data passed to the hook Input any `json:"input,omitempty"` + // Tool call ID of the parent tool invocation when this event originates from a sub-agent + ParentToolCallID *string `json:"parentToolCallId,omitempty"` } func (*HookStartData) sessionEventData() {} @@ -1048,6 +1317,9 @@ type AssistantUsageData struct { // How the prompt-cache frontier was determined for this call // Internal: FrontierSource is part of the SDK's internal API surface and is not intended for external use. FrontierSource *string `json:"frontierSource,omitempty"` + // Experimental HydraFusion attribution for this concrete model call's usage. + // Experimental: Fusion is part of an experimental API and may change or be removed. + Fusion *FusionAttribution `json:"fusion,omitempty"` // What initiated this API call (e.g., "sub-agent", "mcp-sampling"); absent for user-initiated calls Initiator *string `json:"initiator,omitempty"` // Number of input tokens consumed @@ -1220,6 +1492,9 @@ func (*AgentInterruptedData) Type() SessionEventType { return SessionEventTypeAg // Model API dispatch metadata for internal telemetry type ModelCallStartData struct { + // Experimental HydraFusion attribution for this concrete model call. + // Experimental: Fusion is part of an experimental API and may change or be removed. + Fusion *FusionAttribution `json:"fusion,omitempty"` // Model identifier used for this API call, when known Model *string `json:"model,omitempty"` // Previous response or interaction identifier included in the model request, when present @@ -1698,6 +1973,21 @@ type CommandExecuteData struct { func (*CommandExecuteData) sessionEventData() {} func (*CommandExecuteData) Type() SessionEventType { return SessionEventTypeCommandExecute } +// Resolved runtime configuration for a configured sub-agent +type SubagentConfiguredData struct { + // Resolved context tier, when configured for the model + ContextTier *string `json:"contextTier,omitempty"` + // Resolved model the sub-agent will run with + Model string `json:"model"` + // Whether the sub-agent accepts follow-up turns + MultiTurn bool `json:"multiTurn"` + // Resolved reasoning effort, when configured for the model + ReasoningEffort *string `json:"reasoningEffort,omitempty"` +} + +func (*SubagentConfiguredData) sessionEventData() {} +func (*SubagentConfiguredData) Type() SessionEventType { return SessionEventTypeSubagentConfigured } + // Runtime enforcement of enterprise managed settings: fires when the session blocks or caps a runtime action because enterprise policy governs it, so SDK clients can explain *why* an action was governed. Unlike `session.managed_settings_resolved` (which reports *what* is managed), this reports a concrete governed action — e.g. a user or host tried to turn on a bypass-permissions escalation while policy disables it. Emitted live (not persisted to the session event log) on user/host-initiated attempts only, never for silent policy application. Marked experimental while the managed-settings surface stabilizes. // Experimental: SessionManagedSettingsEnforcedData is part of an experimental API and may change or be removed. type SessionManagedSettingsEnforcedData struct { @@ -2199,10 +2489,18 @@ type SubagentStartedData struct { AgentDisplayName string `json:"agentDisplayName"` // Internal name of the sub-agent AgentName string `json:"agentName"` + // Type of the sub-agent selected at spawn time. + AgentType *string `json:"agentType,omitempty"` + // Whether the sub-agent runs synchronously or in the background. + ExecutionMode *string `json:"executionMode,omitempty"` // Root id of the factory run that spawned this sub-agent, when it was spawned by one. FactoryRunID *string `json:"factoryRunId,omitempty"` // Model the sub-agent will run with, when known at start. Model *string `json:"model,omitempty"` + // Task-registry ID of the spawning sub-agent. Absent when the root session spawned this child. + ParentID *string `json:"parentId,omitempty"` + // Whether this sub-agent can be resumed. Currently always false. + Resumable *bool `json:"resumable,omitempty"` // Tool call ID of the parent tool invocation that spawned this sub-agent ToolCallID string `json:"toolCallId"` } @@ -2249,6 +2547,9 @@ func (*SessionTaskCompleteData) Type() SessionEventType { return SessionEventTyp type ToolExecutionCompleteData struct { // Error details when the tool execution failed Error *ToolExecutionCompleteError `json:"error,omitempty"` + // Experimental HydraFusion attribution for this tool completion. + // Experimental: Fusion is part of an experimental API and may change or be removed. + Fusion *FusionAttribution `json:"fusion,omitempty"` // CAPI interaction ID for correlating this tool execution with upstream telemetry InteractionID *string `json:"interactionId,omitempty"` // Whether this tool call was explicitly requested by the user rather than the assistant @@ -2303,6 +2604,9 @@ type ToolExecutionStartData struct { Arguments any `json:"arguments,omitempty"` // When true, the tool output should be displayed expanded (verbatim) in the CLI timeline DisplayVerbatim *bool `json:"displayVerbatim,omitempty"` + // Experimental HydraFusion attribution for this tool execution. + // Experimental: Fusion is part of an experimental API and may change or be removed. + Fusion *FusionAttribution `json:"fusion,omitempty"` // Name of the MCP server hosting this tool, when the tool is an MCP tool MCPServerName *string `json:"mcpServerName,omitempty"` // Original tool name on the MCP server, when the tool is an MCP tool @@ -2837,6 +3141,83 @@ type FactoryPermissionPhase struct { Title string `json:"title"` } +// Experimental attribution linking an ordinary event to the HydraFusion turn, phase, and concrete source that produced it. +// Experimental: FusionAttribution is part of an experimental API and may change or be removed. +type FusionAttribution struct { + // Idempotency identifier for the authoritative commit, when the event belongs to the selected output. + CommitID *string `json:"commitId,omitempty"` + // Conversation scope in which the concrete phase executed. + ConversationScope *string `json:"conversationScope,omitempty"` + // Stable identifier for the HydraFusion turn that produced the event. + FusionID string `json:"fusionId"` + // HydraFusion orchestration pattern selected for the turn. + Pattern string `json:"pattern"` + // Identifier of the concrete phase that produced the event. + PhaseID *string `json:"phaseId,omitempty"` + // Kind of concrete phase that produced the event. + PhaseKind *string `json:"phaseKind,omitempty"` + // HydraFusion routing policy used for the turn. + Policy string `json:"policy"` + // Semantic role assigned to the concrete phase. + Role *string `json:"role,omitempty"` + // Concrete model that produced the attributed event. + SourceModel *string `json:"sourceModel,omitempty"` + // Phase whose output supplied the authoritative content, when different from the executing phase. + SourcePhaseID *string `json:"sourcePhaseId,omitempty"` + // Synthetic HydraFusion model selected for the session. + SyntheticModel string `json:"syntheticModel"` +} + +// Durable server recommendation for subsequent HydraFusion turns. +// Experimental: FusionFollowUpRecommendation is part of an experimental API and may change or be removed. +type FusionFollowUpRecommendation struct { + // Recommended routing action for the next compaction turn. + CompactionTurn FusionFollowUpAction `json:"compactionTurn"` + // Recommended routing action for the next user-message turn. + UserTurn FusionFollowUpAction `json:"userTurn"` +} + +// Aggregate concrete-model usage for one HydraFusion phase. +// Experimental: FusionPhaseUsage is part of an experimental API and may change or be removed. +type FusionPhaseUsage struct { + // Total cached input tokens reported for the phase. + CachedTokens int64 `json:"cachedTokens"` + // Total tokens written to prompt cache during the phase. + CacheWriteTokens *int64 `json:"cacheWriteTokens,omitempty"` + // Total input tokens consumed by the phase. + InputTokens int64 `json:"inputTokens"` + // Total output tokens produced by the phase. + OutputTokens int64 `json:"outputTokens"` + // Number of concrete model requests made by the phase. + RequestCount int64 `json:"requestCount"` + // Total normalized AI-unit cost reported for the phase, in nano-AIU. + TotalNanoAiu float64 `json:"totalNanoAiu"` +} + +// Validated HydraFusion routing capability scores. +// Experimental: FusionScores is part of an experimental API and may change or be removed. +type FusionScores struct { + // Code-generation capability score returned by the authenticated router. + CodeGen float64 `json:"codeGen"` + // Debugging capability score returned by the authenticated router. + Debugging float64 `json:"debugging"` + // Reasoning capability score returned by the authenticated router. + Reasoning float64 `json:"reasoning"` + // Tool-use capability score returned by the authenticated router. + ToolUse float64 `json:"toolUse"` +} + +// Internal durable terminal request staged by a HydraFusion phase until an idempotent final commit selects it. +// Experimental: FusionStagedTerminal is part of an experimental API and may change or be removed. +// Internal: FusionStagedTerminal is an internal SDK API and is not part of the public surface. +type FusionStagedTerminal struct { + Arguments string `json:"arguments"` + AssistantMessage any `json:"assistantMessage"` + PhaseID string `json:"phaseId"` + ToolCallID string `json:"toolCallId"` + ToolName string `json:"toolName"` +} + // Per-session configuration for the built-in GitHub MCP server type GitHubMCPToolConfig struct { // Additional GitHub MCP tools requested by the session @@ -4194,6 +4575,8 @@ type ToolExecutionCompleteContentShellExit struct { Cwd *string `json:"cwd,omitempty"` // Exit code from the completed shell command ExitCode int64 `json:"exitCode"` + // Path reported in the shell session's filesystem namespace when shell output exceeded the configured large-output threshold. + OutputFilePath *string `json:"outputFilePath,omitempty"` // Output associated with this shell command, if available. May be partial, truncated, or a preview; not guaranteed to be full output. OutputPreview *string `json:"outputPreview,omitempty"` // Whether outputPreview is known to be incomplete or truncated @@ -4741,6 +5124,99 @@ const ( FactoryRunSettledStatusHalted FactoryRunSettledStatus = "halted" ) +// Conversation scope in which a HydraFusion phase executes. +// Experimental: FusionConversationScope is part of an experimental API and may change or be removed. +type FusionConversationScope string + +const ( + // Isolated read-only review history that does not enter the root conversation. + FusionConversationScopeReview FusionConversationScope = "review" + // Canonical root conversation history. + FusionConversationScopeRoot FusionConversationScope = "root" +) + +// Server-recommended routing behavior for a later HydraFusion turn. +// Experimental: FusionFollowUpAction is part of an experimental API and may change or be removed. +type FusionFollowUpAction string + +const ( + // Request a new routing decision. + FusionFollowUpActionReroute FusionFollowUpAction = "reroute" + // Reuse the durable primary model without routing. + FusionFollowUpActionReusePrimary FusionFollowUpAction = "reuse_primary" +) + +// Validated HydraFusion execution pattern. +// Experimental: FusionPattern is part of an experimental API and may change or be removed. +type FusionPattern string + +const ( + // Run a primary phase, a judge, and an optional repair. + FusionPatternCascade FusionPattern = "cascade" + // Run a primary draft, a read-only critique, and a revision. + FusionPatternCritique FusionPattern = "critique" + // Run one primary solver phase. + FusionPatternSingle FusionPattern = "single" +) + +// HydraFusion phase kind. +// Experimental: FusionPhaseKind is part of an experimental API and may change or be removed. +type FusionPhaseKind string + +const ( + // Read-only critique phase. + FusionPhaseKindCritic FusionPhaseKind = "critic" + // Initial critique-pattern draft phase. + FusionPhaseKindDraft FusionPhaseKind = "draft" + // Follow-up phase continuing from the resolved model. + FusionPhaseKindFollowUp FusionPhaseKind = "follow_up" + // Read-only cascade judge phase. + FusionPhaseKindJudge FusionPhaseKind = "judge" + // Primary solver phase. + FusionPhaseKindPrimary FusionPhaseKind = "primary" + // Cascade repair phase. + FusionPhaseKindRepair FusionPhaseKind = "repair" + // Critique-pattern revision phase. + FusionPhaseKindRevision FusionPhaseKind = "revision" +) + +// Durable outcome status of a HydraFusion phase. +// Experimental: FusionPhaseStatus is part of an experimental API and may change or be removed. +type FusionPhaseStatus string + +const ( + // The phase was cancelled. + FusionPhaseStatusCancelled FusionPhaseStatus = "cancelled" + // The phase failed. + FusionPhaseStatusFailed FusionPhaseStatus = "failed" + // The phase completed successfully. + FusionPhaseStatusSucceeded FusionPhaseStatus = "succeeded" +) + +// How a durable phase checkpoint contributes its exact message to canonical root history. +// Experimental: FusionProjectionMode is part of an experimental API and may change or be removed. +type FusionProjectionMode string + +const ( + // Append the exact root message immediately. + FusionProjectionModeAppend FusionProjectionMode = "append" + // Do not project the checkpoint into root history. + FusionProjectionModeNone FusionProjectionMode = "none" + // Hold a terminal message outside canonical history until the final commit selects it. + FusionProjectionModeStaged FusionProjectionMode = "staged" +) + +// Kind of turn for which HydraFusion routing is running. +// Experimental: FusionTurnKind is part of an experimental API and may change or be removed. +type FusionTurnKind string + +const ( + // A conversation-compaction turn. + FusionTurnKindCompaction FusionTurnKind = "compaction" + // A user-message turn. + FusionTurnKindUser FusionTurnKind = "user" +) + // Origin type of the session being handed off type HandoffSourceType string diff --git a/go/zsession_events.go b/go/zsession_events.go index 11a1bd666f..38ea78087b 100644 --- a/go/zsession_events.go +++ b/go/zsession_events.go @@ -12,6 +12,9 @@ type ( AgentInterruptedActivity = rpc.AgentInterruptedActivity AgentInterruptedCancelPhase = rpc.AgentInterruptedCancelPhase AgentInterruptedData = rpc.AgentInterruptedData + AssistantFusionPhaseCompletedData = rpc.AssistantFusionPhaseCompletedData + AssistantFusionPhaseFailedData = rpc.AssistantFusionPhaseFailedData + AssistantFusionPhaseStartedData = rpc.AssistantFusionPhaseStartedData AssistantIdleData = rpc.AssistantIdleData AssistantIntentData = rpc.AssistantIntentData AssistantMessageData = rpc.AssistantMessageData @@ -116,6 +119,16 @@ type ( FactoryRunSettledStatus = rpc.FactoryRunSettledStatus FactoryRunStartedData = rpc.FactoryRunStartedData FactoryRunUpdatedData = rpc.FactoryRunUpdatedData + FusionAttribution = rpc.FusionAttribution + FusionConversationScope = rpc.FusionConversationScope + FusionFollowUpAction = rpc.FusionFollowUpAction + FusionFollowUpRecommendation = rpc.FusionFollowUpRecommendation + FusionPattern = rpc.FusionPattern + FusionPhaseKind = rpc.FusionPhaseKind + FusionPhaseStatus = rpc.FusionPhaseStatus + FusionPhaseUsage = rpc.FusionPhaseUsage + FusionScores = rpc.FusionScores + FusionTurnKind = rpc.FusionTurnKind GitHubRepoRef = rpc.GitHubRepoRef HandoffRepository = rpc.HandoffRepository HandoffSourceType = rpc.HandoffSourceType @@ -260,6 +273,10 @@ type ( SessionEventType = rpc.SessionEventType SessionExtensionsAttachmentsPushedData = rpc.SessionExtensionsAttachmentsPushedData SessionExtensionsLoadedData = rpc.SessionExtensionsLoadedData + SessionFusionCompletedData = rpc.SessionFusionCompletedData + SessionFusionResolvedData = rpc.SessionFusionResolvedData + SessionFusionRouteFailedData = rpc.SessionFusionRouteFailedData + SessionFusionRouteStartedData = rpc.SessionFusionRouteStartedData SessionHandoffData = rpc.SessionHandoffData SessionIdleData = rpc.SessionIdleData SessionInfoData = rpc.SessionInfoData @@ -309,6 +326,7 @@ type ( SkillsLoadedSkill = rpc.SkillsLoadedSkill SkillSource = rpc.SkillSource SubagentCompletedData = rpc.SubagentCompletedData + SubagentConfiguredData = rpc.SubagentConfiguredData SubagentDeselectedData = rpc.SubagentDeselectedData SubagentFailedData = rpc.SubagentFailedData SubagentSelectedData = rpc.SubagentSelectedData @@ -495,6 +513,28 @@ const ( FactoryRunSettledStatusCompleted = rpc.FactoryRunSettledStatusCompleted FactoryRunSettledStatusError = rpc.FactoryRunSettledStatusError FactoryRunSettledStatusHalted = rpc.FactoryRunSettledStatusHalted + FusionConversationScopeReview = rpc.FusionConversationScopeReview + FusionConversationScopeRoot = rpc.FusionConversationScopeRoot + FusionFollowUpActionReroute = rpc.FusionFollowUpActionReroute + FusionFollowUpActionReusePrimary = rpc.FusionFollowUpActionReusePrimary + FusionPatternCascade = rpc.FusionPatternCascade + FusionPatternCritique = rpc.FusionPatternCritique + FusionPatternSingle = rpc.FusionPatternSingle + FusionPhaseKindCritic = rpc.FusionPhaseKindCritic + FusionPhaseKindDraft = rpc.FusionPhaseKindDraft + FusionPhaseKindFollowUp = rpc.FusionPhaseKindFollowUp + FusionPhaseKindJudge = rpc.FusionPhaseKindJudge + FusionPhaseKindPrimary = rpc.FusionPhaseKindPrimary + FusionPhaseKindRepair = rpc.FusionPhaseKindRepair + FusionPhaseKindRevision = rpc.FusionPhaseKindRevision + FusionPhaseStatusCancelled = rpc.FusionPhaseStatusCancelled + FusionPhaseStatusFailed = rpc.FusionPhaseStatusFailed + FusionPhaseStatusSucceeded = rpc.FusionPhaseStatusSucceeded + FusionProjectionModeAppend = rpc.FusionProjectionModeAppend + FusionProjectionModeNone = rpc.FusionProjectionModeNone + FusionProjectionModeStaged = rpc.FusionProjectionModeStaged + FusionTurnKindCompaction = rpc.FusionTurnKindCompaction + FusionTurnKindUser = rpc.FusionTurnKindUser HandoffSourceTypeLocal = rpc.HandoffSourceTypeLocal HandoffSourceTypeRemote = rpc.HandoffSourceTypeRemote ManagedSettingsEnforcedActionBypassPermissionsBlocked = rpc.ManagedSettingsEnforcedActionBypassPermissionsBlocked @@ -626,6 +666,9 @@ const ( ScheduleOriginUser = rpc.ScheduleOriginUser SessionEventTypeAbort = rpc.SessionEventTypeAbort SessionEventTypeAgentInterrupted = rpc.SessionEventTypeAgentInterrupted + SessionEventTypeAssistantFusionPhaseCompleted = rpc.SessionEventTypeAssistantFusionPhaseCompleted + SessionEventTypeAssistantFusionPhaseFailed = rpc.SessionEventTypeAssistantFusionPhaseFailed + SessionEventTypeAssistantFusionPhaseStarted = rpc.SessionEventTypeAssistantFusionPhaseStarted SessionEventTypeAssistantIdle = rpc.SessionEventTypeAssistantIdle SessionEventTypeAssistantIntent = rpc.SessionEventTypeAssistantIntent SessionEventTypeAssistantMessage = rpc.SessionEventTypeAssistantMessage @@ -696,6 +739,10 @@ const ( SessionEventTypeSessionError = rpc.SessionEventTypeSessionError SessionEventTypeSessionExtensionsAttachmentsPushed = rpc.SessionEventTypeSessionExtensionsAttachmentsPushed SessionEventTypeSessionExtensionsLoaded = rpc.SessionEventTypeSessionExtensionsLoaded + SessionEventTypeSessionFusionCompleted = rpc.SessionEventTypeSessionFusionCompleted + SessionEventTypeSessionFusionResolved = rpc.SessionEventTypeSessionFusionResolved + SessionEventTypeSessionFusionRouteFailed = rpc.SessionEventTypeSessionFusionRouteFailed + SessionEventTypeSessionFusionRouteStarted = rpc.SessionEventTypeSessionFusionRouteStarted SessionEventTypeSessionHandoff = rpc.SessionEventTypeSessionHandoff SessionEventTypeSessionIdle = rpc.SessionEventTypeSessionIdle SessionEventTypeSessionInfo = rpc.SessionEventTypeSessionInfo @@ -730,6 +777,7 @@ const ( SessionEventTypeSessionWorkspaceFileChanged = rpc.SessionEventTypeSessionWorkspaceFileChanged SessionEventTypeSkillInvoked = rpc.SessionEventTypeSkillInvoked SessionEventTypeSubagentCompleted = rpc.SessionEventTypeSubagentCompleted + SessionEventTypeSubagentConfigured = rpc.SessionEventTypeSubagentConfigured SessionEventTypeSubagentDeselected = rpc.SessionEventTypeSubagentDeselected SessionEventTypeSubagentFailed = rpc.SessionEventTypeSubagentFailed SessionEventTypeSubagentSelected = rpc.SessionEventTypeSubagentSelected diff --git a/java/pom.xml b/java/pom.xml index b7618b897a..bb7a7bc0ae 100644 --- a/java/pom.xml +++ b/java/pom.xml @@ -63,7 +63,7 @@ DO NOT EDIT MANUALLY. Updated by the update-copilot-dependency workflow. --> - ^1.0.81-11 + ^1.0.81 true diff --git a/java/scripts/codegen/package-lock.json b/java/scripts/codegen/package-lock.json index d0b602e846..e40b3b2907 100644 --- a/java/scripts/codegen/package-lock.json +++ b/java/scripts/codegen/package-lock.json @@ -6,7 +6,7 @@ "": { "name": "copilot-sdk-java-codegen", "dependencies": { - "@github/copilot": "^1.0.81-11", + "@github/copilot": "^1.0.81", "json-schema": "^0.4.0", "tsx": "^4.23.12" } @@ -428,9 +428,9 @@ } }, "node_modules/@github/copilot": { - "version": "1.0.81-11", - "resolved": "https://registry.npmjs.org/@github/copilot/-/copilot-1.0.81-11.tgz", - "integrity": "sha512-F7hZ6G6fhWH4uq862mbs2JE3nL0KIVBOc94/EFOdEjux3oHUQst9a06gKibEJS6VRULaYTXzatA1EAgNC9dzFA==", + "version": "1.0.81", + "resolved": "https://registry.npmjs.org/@github/copilot/-/copilot-1.0.81.tgz", + "integrity": "sha512-Yif+wnRY1rT6FMmxr+SMZCq60mBTTPvbAHGd42Jty9wf1ZmeTsJYAcaGDXC9oyNm3RYRc3wkL0MSscNiEZADAA==", "license": "SEE LICENSE IN LICENSE.md", "dependencies": { "detect-libc": "^2.1.2" @@ -439,20 +439,20 @@ "copilot": "npm-loader.js" }, "optionalDependencies": { - "@github/copilot-darwin-arm64": "1.0.81-11", - "@github/copilot-darwin-x64": "1.0.81-11", - "@github/copilot-linux-arm64": "1.0.81-11", - "@github/copilot-linux-x64": "1.0.81-11", - "@github/copilot-linuxmusl-arm64": "1.0.81-11", - "@github/copilot-linuxmusl-x64": "1.0.81-11", - "@github/copilot-win32-arm64": "1.0.81-11", - "@github/copilot-win32-x64": "1.0.81-11" + "@github/copilot-darwin-arm64": "1.0.81", + "@github/copilot-darwin-x64": "1.0.81", + "@github/copilot-linux-arm64": "1.0.81", + "@github/copilot-linux-x64": "1.0.81", + "@github/copilot-linuxmusl-arm64": "1.0.81", + "@github/copilot-linuxmusl-x64": "1.0.81", + "@github/copilot-win32-arm64": "1.0.81", + "@github/copilot-win32-x64": "1.0.81" } }, "node_modules/@github/copilot-darwin-arm64": { - "version": "1.0.81-11", - "resolved": "https://registry.npmjs.org/@github/copilot-darwin-arm64/-/copilot-darwin-arm64-1.0.81-11.tgz", - "integrity": "sha512-3eLs71CLnJH9RNnESnv4esipZPeGXMlBxQeKXwZY+crwcW2RAR8YuovQyfoZ/5by1PLPbYrOjXNfQL6kXSisrA==", + "version": "1.0.81", + "resolved": "https://registry.npmjs.org/@github/copilot-darwin-arm64/-/copilot-darwin-arm64-1.0.81.tgz", + "integrity": "sha512-VKHJTwRVaNXOmSkMjuFAotZxWNsNLSz3ZEiB1vpUqOYT3AsGMPrj8MIwh64AGfoLa91n4GyotGVbxwnsW8+K4g==", "cpu": [ "arm64" ], @@ -466,9 +466,9 @@ } }, "node_modules/@github/copilot-darwin-x64": { - "version": "1.0.81-11", - "resolved": "https://registry.npmjs.org/@github/copilot-darwin-x64/-/copilot-darwin-x64-1.0.81-11.tgz", - "integrity": "sha512-GdFLiUC8UL9k6K+woG8AyL3zBafd0Br1TIPDV5iiZsyBtTe4g67FjL7DGCYDt6AN9G43jWdK0M0InoNDVq1K1A==", + "version": "1.0.81", + "resolved": "https://registry.npmjs.org/@github/copilot-darwin-x64/-/copilot-darwin-x64-1.0.81.tgz", + "integrity": "sha512-O8BHh9d9j86RokqSYhgX3D1mA4t6MZp5OOneL1n3TPR7J+bEq+Catp+UBVGsJdhsuJ1Oasfk/z59Wit9wnc22w==", "cpu": [ "x64" ], @@ -482,9 +482,9 @@ } }, "node_modules/@github/copilot-linux-arm64": { - "version": "1.0.81-11", - "resolved": "https://registry.npmjs.org/@github/copilot-linux-arm64/-/copilot-linux-arm64-1.0.81-11.tgz", - "integrity": "sha512-C4hcAow5CdaVJITbdtGqFgxWpW7TqwyCPNn8OtcbvdWeiKcfOBDrgkvbsezG98/5Ovs3HWxZRT4oZqCEmGF9Ww==", + "version": "1.0.81", + "resolved": "https://registry.npmjs.org/@github/copilot-linux-arm64/-/copilot-linux-arm64-1.0.81.tgz", + "integrity": "sha512-GhHDhRkeWM3IfuouVGrU8UuXF26kPlV6aINSZyLIkrjY2AG/rro7NXNPeMKYT07HksoAWiMUrjEel8R/bpPplg==", "cpu": [ "arm64" ], @@ -498,9 +498,9 @@ } }, "node_modules/@github/copilot-linux-x64": { - "version": "1.0.81-11", - "resolved": "https://registry.npmjs.org/@github/copilot-linux-x64/-/copilot-linux-x64-1.0.81-11.tgz", - "integrity": "sha512-izu0PwWx+wL4zxacO6cd6r0zMMMQ3pTz+2euWcAd7HCJ/CIR6+YYfjU3TI3TPBZ9zDLZGoxHKYYPfJ1ZbSTzEg==", + "version": "1.0.81", + "resolved": "https://registry.npmjs.org/@github/copilot-linux-x64/-/copilot-linux-x64-1.0.81.tgz", + "integrity": "sha512-qhoiWIqfpvHcajJ8AVKa+ibKjD5k8Nccd9sfAJt5AMYN+Rv/aSCZojmnUazYv5UJAwStaGzep+rmVbTiN+sWUQ==", "cpu": [ "x64" ], @@ -514,9 +514,9 @@ } }, "node_modules/@github/copilot-linuxmusl-arm64": { - "version": "1.0.81-11", - "resolved": "https://registry.npmjs.org/@github/copilot-linuxmusl-arm64/-/copilot-linuxmusl-arm64-1.0.81-11.tgz", - "integrity": "sha512-uWGUjaxOSMu6dKFWcTvXGUUp76vC3OV7nlSupecRkQ7gA3OxdrrB8rXE5/leC606Hk6oe9Wl7wKCH9EwuZ6afg==", + "version": "1.0.81", + "resolved": "https://registry.npmjs.org/@github/copilot-linuxmusl-arm64/-/copilot-linuxmusl-arm64-1.0.81.tgz", + "integrity": "sha512-sOwSiqIM5H3AeYCbFW3f36qvm+YWyPinHwNiJC2DzuwpX/ujg8Warwm1zuokOJV5iyHC8AQxbwQRQ7LRQx19sQ==", "cpu": [ "arm64" ], @@ -530,9 +530,9 @@ } }, "node_modules/@github/copilot-linuxmusl-x64": { - "version": "1.0.81-11", - "resolved": "https://registry.npmjs.org/@github/copilot-linuxmusl-x64/-/copilot-linuxmusl-x64-1.0.81-11.tgz", - "integrity": "sha512-BpWKd/iu1tTyuPR2zuPFs1pVOlley4yt/TXkn6/gVwt52VI0rUxEO+n77RVjDcyMxWaUApbad2VNRPIH77guCA==", + "version": "1.0.81", + "resolved": "https://registry.npmjs.org/@github/copilot-linuxmusl-x64/-/copilot-linuxmusl-x64-1.0.81.tgz", + "integrity": "sha512-9lbAC0jDtlGNagKz9DycgzovHk35lS1lP5xV+EW1dTChep9uQt7n00d7Ru7ErvHS79QMRJw9PkPhKL9Jyz9jiA==", "cpu": [ "x64" ], @@ -546,9 +546,9 @@ } }, "node_modules/@github/copilot-win32-arm64": { - "version": "1.0.81-11", - "resolved": "https://registry.npmjs.org/@github/copilot-win32-arm64/-/copilot-win32-arm64-1.0.81-11.tgz", - "integrity": "sha512-GOK3cACgD96m065uJxbgcXL7MQ1qm+wq1qtvGBpprY0r9wTYJG/rVCtayjYB6B57rnaBwPll3+PQ2J1qgfO57A==", + "version": "1.0.81", + "resolved": "https://registry.npmjs.org/@github/copilot-win32-arm64/-/copilot-win32-arm64-1.0.81.tgz", + "integrity": "sha512-LBUennWqLDcAuYP3HrO9iwhxCjNA97g9jJplte8bEGFWoYrtOEs2UlEscorbHCQHeBpXXSn1slsUBpLl1CrDBQ==", "cpu": [ "arm64" ], @@ -562,9 +562,9 @@ } }, "node_modules/@github/copilot-win32-x64": { - "version": "1.0.81-11", - "resolved": "https://registry.npmjs.org/@github/copilot-win32-x64/-/copilot-win32-x64-1.0.81-11.tgz", - "integrity": "sha512-u4K6UU2iGQJqQwsdHNilJBwiaC48PfMsWVFnTSlJD5lCYp9zCk1odrvrcmB3ARYinE/IcXLUosiHaM/ChR/pdA==", + "version": "1.0.81", + "resolved": "https://registry.npmjs.org/@github/copilot-win32-x64/-/copilot-win32-x64-1.0.81.tgz", + "integrity": "sha512-1O1F1OdMO5Z/S9IjD3dQVA8QAKCuPgOlfhSvI19QfTp5zeDRZb2vCAERosY9UrURinawCHEoWmrg3EalXc4zaQ==", "cpu": [ "x64" ], diff --git a/java/scripts/codegen/package.json b/java/scripts/codegen/package.json index d5669c39f0..d7f775c798 100644 --- a/java/scripts/codegen/package.json +++ b/java/scripts/codegen/package.json @@ -7,7 +7,7 @@ "generate:java": "tsx java.ts" }, "dependencies": { - "@github/copilot": "^1.0.81-11", + "@github/copilot": "^1.0.81", "json-schema": "^0.4.0", "tsx": "^4.23.12" } diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/AssistantFusionPhaseCompletedEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/AssistantFusionPhaseCompletedEvent.java new file mode 100644 index 0000000000..5ad9beee17 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/AssistantFusionPhaseCompletedEvent.java @@ -0,0 +1,67 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: session-events.schema.json + +package com.github.copilot.generated; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import javax.annotation.processing.Generated; + +/** + * Session event "assistant.fusion_phase_completed". Experimental durable HydraFusion phase output and lossless replay checkpoint. + * @since 1.0.0 + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public final class AssistantFusionPhaseCompletedEvent extends SessionEvent { + + @Override + public String getType() { return "assistant.fusion_phase_completed"; } + + @JsonProperty("data") + private AssistantFusionPhaseCompletedEventData data; + + public AssistantFusionPhaseCompletedEventData getData() { return data; } + public void setData(AssistantFusionPhaseCompletedEventData data) { this.data = data; } + + /** Data payload for {@link AssistantFusionPhaseCompletedEvent}. */ + @JsonIgnoreProperties(ignoreUnknown = true) + @JsonInclude(JsonInclude.Include.NON_NULL) + public record AssistantFusionPhaseCompletedEventData( + /** Identifier of the HydraFusion turn containing the phase. */ + @JsonProperty("fusionId") String fusionId, + /** Stable identifier for the completed phase. */ + @JsonProperty("phaseId") String phaseId, + /** Kind of phase that completed. */ + @JsonProperty("phaseKind") FusionPhaseKind phaseKind, + /** Semantic role assigned to the completed phase. */ + @JsonProperty("role") String role, + /** Conversation scope in which the phase executed. */ + @JsonProperty("conversationScope") FusionConversationScope conversationScope, + /** Concrete model that executed the phase. */ + @JsonProperty("model") String model, + /** Durable outcome status of the phase. */ + @JsonProperty("status") FusionPhaseStatus status, + /** Provider-normalized textual output produced by the phase. */ + @JsonProperty("content") String content, + /** Structured judge or critic verdict, when the phase produces one. */ + @JsonProperty("verdict") String verdict, + /** Elapsed execution time for the phase in milliseconds. */ + @JsonProperty("durationMs") Double durationMs, + /** Aggregate concrete-model usage consumed by the phase. */ + @JsonProperty("usage") FusionPhaseUsage usage, + /** Exact provider-normalized message used to reconstruct canonical model history. */ + @JsonProperty("projectionMessage") Object projectionMessage, + /** Projection action for the exact internal message. */ + @JsonProperty("projectionMode") FusionProjectionMode projectionMode, + /** Terminal request held outside canonical state until selected by the final commit. */ + @JsonProperty("stagedTerminal") FusionStagedTerminal stagedTerminal + ) { + } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/AssistantFusionPhaseFailedEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/AssistantFusionPhaseFailedEvent.java new file mode 100644 index 0000000000..17de3c7190 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/AssistantFusionPhaseFailedEvent.java @@ -0,0 +1,63 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: session-events.schema.json + +package com.github.copilot.generated; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import javax.annotation.processing.Generated; + +/** + * Session event "assistant.fusion_phase_failed". Experimental durable typed HydraFusion phase failure and degradation transition. + * @since 1.0.0 + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public final class AssistantFusionPhaseFailedEvent extends SessionEvent { + + @Override + public String getType() { return "assistant.fusion_phase_failed"; } + + @JsonProperty("data") + private AssistantFusionPhaseFailedEventData data; + + public AssistantFusionPhaseFailedEventData getData() { return data; } + public void setData(AssistantFusionPhaseFailedEventData data) { this.data = data; } + + /** Data payload for {@link AssistantFusionPhaseFailedEvent}. */ + @JsonIgnoreProperties(ignoreUnknown = true) + @JsonInclude(JsonInclude.Include.NON_NULL) + public record AssistantFusionPhaseFailedEventData( + /** Identifier of the HydraFusion turn containing the phase. */ + @JsonProperty("fusionId") String fusionId, + /** Stable identifier for the failed phase. */ + @JsonProperty("phaseId") String phaseId, + /** Kind of phase that failed. */ + @JsonProperty("phaseKind") FusionPhaseKind phaseKind, + /** Semantic role assigned to the failed phase. */ + @JsonProperty("role") String role, + /** Conversation scope in which the phase executed. */ + @JsonProperty("conversationScope") FusionConversationScope conversationScope, + /** Concrete model that attempted the phase. */ + @JsonProperty("model") String model, + /** Durable outcome status of the phase. */ + @JsonProperty("status") FusionPhaseStatus status, + /** Stable machine-readable reason for the phase failure. */ + @JsonProperty("reason") String reason, + /** Elapsed execution time before the phase failed, in milliseconds. */ + @JsonProperty("durationMs") Double durationMs, + /** Aggregate concrete-model usage consumed before the failure. */ + @JsonProperty("usage") FusionPhaseUsage usage, + /** Provider or execution error detail, when available. */ + @JsonProperty("errorMessage") String errorMessage, + /** Identifier of the fallback phase used to continue the turn after degradation. */ + @JsonProperty("degradedToPhaseId") String degradedToPhaseId + ) { + } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/AssistantFusionPhaseStartedEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/AssistantFusionPhaseStartedEvent.java new file mode 100644 index 0000000000..88c4064a73 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/AssistantFusionPhaseStartedEvent.java @@ -0,0 +1,53 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: session-events.schema.json + +package com.github.copilot.generated; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import javax.annotation.processing.Generated; + +/** + * Session event "assistant.fusion_phase_started". Experimental transient HydraFusion phase/model/role signal. + * @since 1.0.0 + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public final class AssistantFusionPhaseStartedEvent extends SessionEvent { + + @Override + public String getType() { return "assistant.fusion_phase_started"; } + + @JsonProperty("data") + private AssistantFusionPhaseStartedEventData data; + + public AssistantFusionPhaseStartedEventData getData() { return data; } + public void setData(AssistantFusionPhaseStartedEventData data) { this.data = data; } + + /** Data payload for {@link AssistantFusionPhaseStartedEvent}. */ + @JsonIgnoreProperties(ignoreUnknown = true) + @JsonInclude(JsonInclude.Include.NON_NULL) + public record AssistantFusionPhaseStartedEventData( + /** Identifier of the HydraFusion turn containing the phase. */ + @JsonProperty("fusionId") String fusionId, + /** Stable identifier for the concrete phase. */ + @JsonProperty("phaseId") String phaseId, + /** Kind of phase being executed. */ + @JsonProperty("phaseKind") FusionPhaseKind phaseKind, + /** HydraFusion orchestration pattern containing the phase. */ + @JsonProperty("pattern") FusionPattern pattern, + /** Semantic role assigned to the phase. */ + @JsonProperty("role") String role, + /** Conversation scope in which the phase executes. */ + @JsonProperty("conversationScope") FusionConversationScope conversationScope, + /** Concrete model executing the phase. */ + @JsonProperty("model") String model + ) { + } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/AssistantMessageEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/AssistantMessageEvent.java index 41147f3c55..9ba4f05618 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/AssistantMessageEvent.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/AssistantMessageEvent.java @@ -80,7 +80,9 @@ public record AssistantMessageEventData( /** Tool call ID of the parent tool invocation when this event originates from a sub-agent */ @JsonProperty("parentToolCallId") String parentToolCallId, /** Provider-agnostic citations linking spans of this message's content to the sources that support them. Experimental; only populated when citation emission is enabled. */ - @JsonProperty("citations") Citations citations + @JsonProperty("citations") Citations citations, + /** Experimental HydraFusion source attribution for this ordinary authoritative assistant message. */ + @JsonProperty("fusion") FusionAttribution fusion ) { } } diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/AssistantUsageEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/AssistantUsageEvent.java index ff4cfddec8..d0956f5a77 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 @@ -115,7 +115,9 @@ public record AssistantUsageEventData( /** Finish reason reported by the model for this API call (e.g. "stop", "length", "tool_calls", "content_filter"). Normalized to OpenAI vocabulary; for Anthropic models a "refusal" stop reason maps to "content_filter". */ @JsonProperty("finishReason") String finishReason, /** Whether the model response was blocked or truncated by content filtering (finish_reason === 'content_filter'). For Anthropic models this corresponds to a 'refusal' stop reason. */ - @JsonProperty("contentFilterTriggered") Boolean contentFilterTriggered + @JsonProperty("contentFilterTriggered") Boolean contentFilterTriggered, + /** Experimental HydraFusion attribution for this concrete model call's usage. */ + @JsonProperty("fusion") FusionAttribution fusion ) { } } diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/FusionAttribution.java b/java/sdk/src/generated/java/com/github/copilot/generated/FusionAttribution.java new file mode 100644 index 0000000000..8e4ae7e937 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/FusionAttribution.java @@ -0,0 +1,47 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: session-events.schema.json + +package com.github.copilot.generated; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import javax.annotation.processing.Generated; + +/** + * Experimental attribution linking an ordinary event to the HydraFusion turn, phase, and concrete source that produced it. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record FusionAttribution( + /** Stable identifier for the HydraFusion turn that produced the event. */ + @JsonProperty("fusionId") String fusionId, + /** Idempotency identifier for the authoritative commit, when the event belongs to the selected output. */ + @JsonProperty("commitId") String commitId, + /** Synthetic HydraFusion model selected for the session. */ + @JsonProperty("syntheticModel") String syntheticModel, + /** HydraFusion routing policy used for the turn. */ + @JsonProperty("policy") String policy, + /** HydraFusion orchestration pattern selected for the turn. */ + @JsonProperty("pattern") String pattern, + /** Identifier of the concrete phase that produced the event. */ + @JsonProperty("phaseId") String phaseId, + /** Kind of concrete phase that produced the event. */ + @JsonProperty("phaseKind") String phaseKind, + /** Semantic role assigned to the concrete phase. */ + @JsonProperty("role") String role, + /** Concrete model that produced the attributed event. */ + @JsonProperty("sourceModel") String sourceModel, + /** Conversation scope in which the concrete phase executed. */ + @JsonProperty("conversationScope") String conversationScope, + /** Phase whose output supplied the authoritative content, when different from the executing phase. */ + @JsonProperty("sourcePhaseId") String sourcePhaseId +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/FusionConversationScope.java b/java/sdk/src/generated/java/com/github/copilot/generated/FusionConversationScope.java new file mode 100644 index 0000000000..d38381ed48 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/FusionConversationScope.java @@ -0,0 +1,35 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: session-events.schema.json + +package com.github.copilot.generated; + +import javax.annotation.processing.Generated; + +/** + * Conversation scope in which a HydraFusion phase executes. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public enum FusionConversationScope { + /** The {@code root} variant. */ + ROOT("root"), + /** The {@code review} variant. */ + REVIEW("review"); + + private final String value; + FusionConversationScope(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static FusionConversationScope fromValue(String value) { + for (FusionConversationScope v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown FusionConversationScope value: " + value); + } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/FusionFollowUpAction.java b/java/sdk/src/generated/java/com/github/copilot/generated/FusionFollowUpAction.java new file mode 100644 index 0000000000..e54bf4604f --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/FusionFollowUpAction.java @@ -0,0 +1,35 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: session-events.schema.json + +package com.github.copilot.generated; + +import javax.annotation.processing.Generated; + +/** + * Server-recommended routing behavior for a later HydraFusion turn. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public enum FusionFollowUpAction { + /** The {@code reuse_primary} variant. */ + REUSE_PRIMARY("reuse_primary"), + /** The {@code reroute} variant. */ + REROUTE("reroute"); + + private final String value; + FusionFollowUpAction(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static FusionFollowUpAction fromValue(String value) { + for (FusionFollowUpAction v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown FusionFollowUpAction value: " + value); + } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/FusionFollowUpRecommendation.java b/java/sdk/src/generated/java/com/github/copilot/generated/FusionFollowUpRecommendation.java new file mode 100644 index 0000000000..101d497fb2 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/FusionFollowUpRecommendation.java @@ -0,0 +1,29 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: session-events.schema.json + +package com.github.copilot.generated; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import javax.annotation.processing.Generated; + +/** + * Durable server recommendation for subsequent HydraFusion turns. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record FusionFollowUpRecommendation( + /** Recommended routing action for the next user-message turn. */ + @JsonProperty("userTurn") FusionFollowUpAction userTurn, + /** Recommended routing action for the next compaction turn. */ + @JsonProperty("compactionTurn") FusionFollowUpAction compactionTurn +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/FusionPattern.java b/java/sdk/src/generated/java/com/github/copilot/generated/FusionPattern.java new file mode 100644 index 0000000000..d37ef4ee83 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/FusionPattern.java @@ -0,0 +1,37 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: session-events.schema.json + +package com.github.copilot.generated; + +import javax.annotation.processing.Generated; + +/** + * Validated HydraFusion execution pattern. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public enum FusionPattern { + /** The {@code single} variant. */ + SINGLE("single"), + /** The {@code cascade} variant. */ + CASCADE("cascade"), + /** The {@code critique} variant. */ + CRITIQUE("critique"); + + private final String value; + FusionPattern(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static FusionPattern fromValue(String value) { + for (FusionPattern v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown FusionPattern value: " + value); + } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/FusionPhaseKind.java b/java/sdk/src/generated/java/com/github/copilot/generated/FusionPhaseKind.java new file mode 100644 index 0000000000..ead79b75fc --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/FusionPhaseKind.java @@ -0,0 +1,45 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: session-events.schema.json + +package com.github.copilot.generated; + +import javax.annotation.processing.Generated; + +/** + * HydraFusion phase kind. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public enum FusionPhaseKind { + /** The {@code primary} variant. */ + PRIMARY("primary"), + /** The {@code judge} variant. */ + JUDGE("judge"), + /** The {@code repair} variant. */ + REPAIR("repair"), + /** The {@code draft} variant. */ + DRAFT("draft"), + /** The {@code critic} variant. */ + CRITIC("critic"), + /** The {@code revision} variant. */ + REVISION("revision"), + /** The {@code follow_up} variant. */ + FOLLOW_UP("follow_up"); + + private final String value; + FusionPhaseKind(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static FusionPhaseKind fromValue(String value) { + for (FusionPhaseKind v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown FusionPhaseKind value: " + value); + } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/FusionPhaseStatus.java b/java/sdk/src/generated/java/com/github/copilot/generated/FusionPhaseStatus.java new file mode 100644 index 0000000000..f670332b5c --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/FusionPhaseStatus.java @@ -0,0 +1,37 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: session-events.schema.json + +package com.github.copilot.generated; + +import javax.annotation.processing.Generated; + +/** + * Durable outcome status of a HydraFusion phase. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public enum FusionPhaseStatus { + /** The {@code succeeded} variant. */ + SUCCEEDED("succeeded"), + /** The {@code failed} variant. */ + FAILED("failed"), + /** The {@code cancelled} variant. */ + CANCELLED("cancelled"); + + private final String value; + FusionPhaseStatus(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static FusionPhaseStatus fromValue(String value) { + for (FusionPhaseStatus v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown FusionPhaseStatus value: " + value); + } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/FusionPhaseUsage.java b/java/sdk/src/generated/java/com/github/copilot/generated/FusionPhaseUsage.java new file mode 100644 index 0000000000..4a707bb69e --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/FusionPhaseUsage.java @@ -0,0 +1,37 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: session-events.schema.json + +package com.github.copilot.generated; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import javax.annotation.processing.Generated; + +/** + * Aggregate concrete-model usage for one HydraFusion phase. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record FusionPhaseUsage( + /** Number of concrete model requests made by the phase. */ + @JsonProperty("requestCount") Long requestCount, + /** Total input tokens consumed by the phase. */ + @JsonProperty("inputTokens") Long inputTokens, + /** Total output tokens produced by the phase. */ + @JsonProperty("outputTokens") Long outputTokens, + /** Total cached input tokens reported for the phase. */ + @JsonProperty("cachedTokens") Long cachedTokens, + /** Total tokens written to prompt cache during the phase. */ + @JsonProperty("cacheWriteTokens") Long cacheWriteTokens, + /** Total normalized AI-unit cost reported for the phase, in nano-AIU. */ + @JsonProperty("totalNanoAiu") Double totalNanoAiu +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/FusionProjectionMode.java b/java/sdk/src/generated/java/com/github/copilot/generated/FusionProjectionMode.java new file mode 100644 index 0000000000..9681efc0ba --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/FusionProjectionMode.java @@ -0,0 +1,37 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: session-events.schema.json + +package com.github.copilot.generated; + +import javax.annotation.processing.Generated; + +/** + * How a durable phase checkpoint contributes its exact message to canonical root history. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public enum FusionProjectionMode { + /** The {@code append} variant. */ + APPEND("append"), + /** The {@code staged} variant. */ + STAGED("staged"), + /** The {@code none} variant. */ + NONE("none"); + + private final String value; + FusionProjectionMode(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static FusionProjectionMode fromValue(String value) { + for (FusionProjectionMode v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown FusionProjectionMode value: " + value); + } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/FusionScores.java b/java/sdk/src/generated/java/com/github/copilot/generated/FusionScores.java new file mode 100644 index 0000000000..eefb8190a1 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/FusionScores.java @@ -0,0 +1,33 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: session-events.schema.json + +package com.github.copilot.generated; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import javax.annotation.processing.Generated; + +/** + * Validated HydraFusion routing capability scores. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record FusionScores( + /** Reasoning capability score returned by the authenticated router. */ + @JsonProperty("reasoning") Double reasoning, + /** Code-generation capability score returned by the authenticated router. */ + @JsonProperty("codeGen") Double codeGen, + /** Debugging capability score returned by the authenticated router. */ + @JsonProperty("debugging") Double debugging, + /** Tool-use capability score returned by the authenticated router. */ + @JsonProperty("toolUse") Double toolUse +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/FusionStagedTerminal.java b/java/sdk/src/generated/java/com/github/copilot/generated/FusionStagedTerminal.java new file mode 100644 index 0000000000..262c8c664b --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/FusionStagedTerminal.java @@ -0,0 +1,30 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: session-events.schema.json + +package com.github.copilot.generated; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import javax.annotation.processing.Generated; + +/** + * Internal durable terminal request staged by a HydraFusion phase until an idempotent final commit selects it. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record FusionStagedTerminal( + @JsonProperty("assistantMessage") Object assistantMessage, + @JsonProperty("toolName") String toolName, + @JsonProperty("toolCallId") String toolCallId, + @JsonProperty("arguments") String arguments, + @JsonProperty("phaseId") String phaseId +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/FusionTurnKind.java b/java/sdk/src/generated/java/com/github/copilot/generated/FusionTurnKind.java new file mode 100644 index 0000000000..24e39b976c --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/FusionTurnKind.java @@ -0,0 +1,35 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: session-events.schema.json + +package com.github.copilot.generated; + +import javax.annotation.processing.Generated; + +/** + * Kind of turn for which HydraFusion routing is running. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public enum FusionTurnKind { + /** The {@code user} variant. */ + USER("user"), + /** The {@code compaction} variant. */ + COMPACTION("compaction"); + + private final String value; + FusionTurnKind(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static FusionTurnKind fromValue(String value) { + for (FusionTurnKind v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown FusionTurnKind value: " + value); + } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/HookEndEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/HookEndEvent.java index cd081dc87f..8d4afe7a1d 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/HookEndEvent.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/HookEndEvent.java @@ -43,7 +43,9 @@ public record HookEndEventData( /** Whether the hook completed successfully */ @JsonProperty("success") Boolean success, /** Error details when the hook failed */ - @JsonProperty("error") HookEndError error + @JsonProperty("error") HookEndError error, + /** Tool call ID of the parent tool invocation when this event originates from a sub-agent */ + @JsonProperty("parentToolCallId") String parentToolCallId ) { } } diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/HookStartEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/HookStartEvent.java index 4c5de1a1d6..3b8b41fafb 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/HookStartEvent.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/HookStartEvent.java @@ -39,7 +39,9 @@ public record HookStartEventData( /** Type of hook being invoked (e.g., "preToolUse", "postToolUse", "sessionStart") */ @JsonProperty("hookType") String hookType, /** Input data passed to the hook */ - @JsonProperty("input") Object input + @JsonProperty("input") Object input, + /** Tool call ID of the parent tool invocation when this event originates from a sub-agent */ + @JsonProperty("parentToolCallId") String parentToolCallId ) { } } diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/ModelCallFailureEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/ModelCallFailureEvent.java index fb1ddcecd4..d3049cd472 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 @@ -82,7 +82,9 @@ public record ModelCallFailureEventData( /** Per-quota usage snapshots parsed from the failed response's quota headers, keyed by quota identifier. Present when the error response carried quota headers (e.g. a 402 once the additional spend limit is reached) so the UI can refresh the quota display on failure. */ @JsonProperty("quotaSnapshots") Map quotaSnapshots, /** Content-free structural summary of the failing request. Contains only counts and shape flags (no prompt content), so it is safe for unrestricted telemetry. Populated only for client-error (4xx) failures. */ - @JsonProperty("requestFingerprint") ModelCallFailureRequestFingerprint requestFingerprint + @JsonProperty("requestFingerprint") ModelCallFailureRequestFingerprint requestFingerprint, + /** Experimental HydraFusion attribution for this failed concrete model call. */ + @JsonProperty("fusion") FusionAttribution fusion ) { } } diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/ModelCallStartEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/ModelCallStartEvent.java index 9f00e2ac2f..a68e455420 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/ModelCallStartEvent.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/ModelCallStartEvent.java @@ -39,7 +39,9 @@ public record ModelCallStartEventData( /** Model identifier used for this API call, when known */ @JsonProperty("model") String model, /** Previous response or interaction identifier included in the model request, when present */ - @JsonProperty("previousResponseId") String previousResponseId + @JsonProperty("previousResponseId") String previousResponseId, + /** Experimental HydraFusion attribution for this concrete model call. */ + @JsonProperty("fusion") FusionAttribution fusion ) { } } diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/SessionEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/SessionEvent.java index 0acc3df712..e22561b914 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/SessionEvent.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/SessionEvent.java @@ -55,12 +55,19 @@ @JsonSubTypes.Type(value = SessionCompactionStartEvent.class, name = "session.compaction_start"), @JsonSubTypes.Type(value = SessionCompactionCompleteEvent.class, name = "session.compaction_complete"), @JsonSubTypes.Type(value = SessionTaskCompleteEvent.class, name = "session.task_complete"), + @JsonSubTypes.Type(value = SessionFusionRouteStartedEvent.class, name = "session.fusion_route_started"), + @JsonSubTypes.Type(value = SessionFusionRouteFailedEvent.class, name = "session.fusion_route_failed"), + @JsonSubTypes.Type(value = SessionFusionResolvedEvent.class, name = "session.fusion_resolved"), + @JsonSubTypes.Type(value = SessionFusionCompletedEvent.class, name = "session.fusion_completed"), @JsonSubTypes.Type(value = UserMessageEvent.class, name = "user.message"), @JsonSubTypes.Type(value = PendingMessagesModifiedEvent.class, name = "pending_messages.modified"), @JsonSubTypes.Type(value = AssistantTurnStartEvent.class, name = "assistant.turn_start"), @JsonSubTypes.Type(value = AssistantTurnRetryEvent.class, name = "assistant.turn_retry"), @JsonSubTypes.Type(value = AgentInterruptedEvent.class, name = "agent.interrupted"), @JsonSubTypes.Type(value = AssistantIntentEvent.class, name = "assistant.intent"), + @JsonSubTypes.Type(value = AssistantFusionPhaseStartedEvent.class, name = "assistant.fusion_phase_started"), + @JsonSubTypes.Type(value = AssistantFusionPhaseCompletedEvent.class, name = "assistant.fusion_phase_completed"), + @JsonSubTypes.Type(value = AssistantFusionPhaseFailedEvent.class, name = "assistant.fusion_phase_failed"), @JsonSubTypes.Type(value = AssistantServerToolProgressEvent.class, name = "assistant.server_tool_progress"), @JsonSubTypes.Type(value = AssistantReasoningEvent.class, name = "assistant.reasoning"), @JsonSubTypes.Type(value = AssistantReasoningDeltaEvent.class, name = "assistant.reasoning_delta"), @@ -86,6 +93,7 @@ @JsonSubTypes.Type(value = SkillInvokedEvent.class, name = "skill.invoked"), @JsonSubTypes.Type(value = SandboxDecisionEvent.class, name = "sandbox.decision"), @JsonSubTypes.Type(value = SubagentStartedEvent.class, name = "subagent.started"), + @JsonSubTypes.Type(value = SubagentConfiguredEvent.class, name = "subagent.configured"), @JsonSubTypes.Type(value = SubagentCompletedEvent.class, name = "subagent.completed"), @JsonSubTypes.Type(value = SubagentFailedEvent.class, name = "subagent.failed"), @JsonSubTypes.Type(value = SubagentSelectedEvent.class, name = "subagent.selected"), @@ -180,12 +188,19 @@ public abstract sealed class SessionEvent permits SessionCompactionStartEvent, SessionCompactionCompleteEvent, SessionTaskCompleteEvent, + SessionFusionRouteStartedEvent, + SessionFusionRouteFailedEvent, + SessionFusionResolvedEvent, + SessionFusionCompletedEvent, UserMessageEvent, PendingMessagesModifiedEvent, AssistantTurnStartEvent, AssistantTurnRetryEvent, AgentInterruptedEvent, AssistantIntentEvent, + AssistantFusionPhaseStartedEvent, + AssistantFusionPhaseCompletedEvent, + AssistantFusionPhaseFailedEvent, AssistantServerToolProgressEvent, AssistantReasoningEvent, AssistantReasoningDeltaEvent, @@ -211,6 +226,7 @@ public abstract sealed class SessionEvent permits SkillInvokedEvent, SandboxDecisionEvent, SubagentStartedEvent, + SubagentConfiguredEvent, SubagentCompletedEvent, SubagentFailedEvent, SubagentSelectedEvent, diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/SessionFusionCompletedEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/SessionFusionCompletedEvent.java new file mode 100644 index 0000000000..6742cdc3e3 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/SessionFusionCompletedEvent.java @@ -0,0 +1,75 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: session-events.schema.json + +package com.github.copilot.generated; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import javax.annotation.processing.Generated; + +/** + * Session event "session.fusion_completed". Experimental durable aggregate outcome of a HydraFusion turn. + * @since 1.0.0 + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public final class SessionFusionCompletedEvent extends SessionEvent { + + @Override + public String getType() { return "session.fusion_completed"; } + + @JsonProperty("data") + private SessionFusionCompletedEventData data; + + public SessionFusionCompletedEventData getData() { return data; } + public void setData(SessionFusionCompletedEventData data) { this.data = data; } + + /** Data payload for {@link SessionFusionCompletedEvent}. */ + @JsonIgnoreProperties(ignoreUnknown = true) + @JsonInclude(JsonInclude.Include.NON_NULL) + public record SessionFusionCompletedEventData( + /** Stable identifier for the completed HydraFusion turn. */ + @JsonProperty("fusionId") String fusionId, + /** Idempotency identifier for the authoritative final commit. */ + @JsonProperty("commitId") String commitId, + /** Identifier of the session turn associated with the completion. */ + @JsonProperty("turnId") String turnId, + /** Synthetic HydraFusion model selected for the session. */ + @JsonProperty("syntheticModel") String syntheticModel, + /** HydraFusion orchestration pattern executed for the turn. */ + @JsonProperty("pattern") FusionPattern pattern, + /** Stable aggregate outcome of the HydraFusion turn. */ + @JsonProperty("outcome") String outcome, + /** Phase whose output supplied the authoritative final content. */ + @JsonProperty("finalSourcePhaseId") String finalSourcePhaseId, + /** Concrete model that supplied the authoritative final content. */ + @JsonProperty("finalSourceModel") String finalSourceModel, + /** Concrete model recommended for eligible follow-up turns. */ + @JsonProperty("followUpModel") String followUpModel, + /** Reason the turn used a degraded route, when applicable. */ + @JsonProperty("degradedReason") String degradedReason, + /** Number of concrete phases attempted by the turn. */ + @JsonProperty("phaseCount") Long phaseCount, + /** Total concrete model requests made across all phases. */ + @JsonProperty("requestCount") Long requestCount, + /** Total input tokens consumed across all phases. */ + @JsonProperty("inputTokens") Long inputTokens, + /** Total output tokens produced across all phases. */ + @JsonProperty("outputTokens") Long outputTokens, + /** Total cached input tokens reported across all phases. */ + @JsonProperty("cachedTokens") Long cachedTokens, + /** Total tokens written to prompt cache across all phases. */ + @JsonProperty("cacheWriteTokens") Long cacheWriteTokens, + /** Total normalized AI-unit cost reported across all phases, in nano-AIU. */ + @JsonProperty("totalNanoAiu") Double totalNanoAiu, + /** Total elapsed execution time for the HydraFusion turn in milliseconds. */ + @JsonProperty("durationMs") Double durationMs + ) { + } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/SessionFusionResolvedEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/SessionFusionResolvedEvent.java new file mode 100644 index 0000000000..7553c6eb43 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/SessionFusionResolvedEvent.java @@ -0,0 +1,79 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: session-events.schema.json + +package com.github.copilot.generated; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import javax.annotation.processing.Generated; + +/** + * Session event "session.fusion_resolved". Experimental durable validated HydraFusion route and turn policy. + * @since 1.0.0 + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public final class SessionFusionResolvedEvent extends SessionEvent { + + @Override + public String getType() { return "session.fusion_resolved"; } + + @JsonProperty("data") + private SessionFusionResolvedEventData data; + + public SessionFusionResolvedEventData getData() { return data; } + public void setData(SessionFusionResolvedEventData data) { this.data = data; } + + /** Data payload for {@link SessionFusionResolvedEvent}. */ + @JsonIgnoreProperties(ignoreUnknown = true) + @JsonInclude(JsonInclude.Include.NON_NULL) + public record SessionFusionResolvedEventData( + /** Stable identifier for the resolved HydraFusion turn. */ + @JsonProperty("fusionId") String fusionId, + /** Identifier of the session turn associated with the route. */ + @JsonProperty("turnId") String turnId, + /** Version of the validated HydraFusion event contract. */ + @JsonProperty("contractVersion") Long contractVersion, + /** Synthetic HydraFusion model selected for the session. */ + @JsonProperty("syntheticModel") String syntheticModel, + /** HydraFusion routing policy used to resolve the plan. */ + @JsonProperty("policy") String policy, + /** Router implementation that supplied the plan. */ + @JsonProperty("routeSource") String routeSource, + /** Version of the validated execution-plan format. */ + @JsonProperty("planVersion") String planVersion, + /** Version of the local routing policy. */ + @JsonProperty("policyVersion") String policyVersion, + /** Version of the executable model universe used for selection. */ + @JsonProperty("modelUniverseVersion") String modelUniverseVersion, + /** Identifier of the local policy rule that matched. */ + @JsonProperty("ruleId") String ruleId, + /** Zero-based index of the local policy rule that matched. */ + @JsonProperty("ruleIndex") Long ruleIndex, + /** Human-readable name of the local policy rule that matched. */ + @JsonProperty("ruleName") String ruleName, + /** Validated capability scores used to select the route. */ + @JsonProperty("scores") FusionScores scores, + /** Validated orchestration pattern selected for the turn. */ + @JsonProperty("pattern") FusionPattern pattern, + /** Concrete model selected for the primary solver phase. */ + @JsonProperty("primaryModel") String primaryModel, + /** Concrete model selected for the review or judge phase, when required. */ + @JsonProperty("secondaryModel") String secondaryModel, + /** Concrete model used when the planned primary model cannot execute. */ + @JsonProperty("fallbackModel") String fallbackModel, + /** Concrete model recommended for eligible follow-up turns. */ + @JsonProperty("followUpModel") String followUpModel, + /** Router recommendation controlling reuse or rerouting on later turns. */ + @JsonProperty("followUp") FusionFollowUpRecommendation followUp, + /** Elapsed time in milliseconds required to resolve and validate the route. */ + @JsonProperty("routingLatencyMs") Double routingLatencyMs + ) { + } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/SessionFusionRouteFailedEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/SessionFusionRouteFailedEvent.java new file mode 100644 index 0000000000..25698b336b --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/SessionFusionRouteFailedEvent.java @@ -0,0 +1,53 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: session-events.schema.json + +package com.github.copilot.generated; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import javax.annotation.processing.Generated; + +/** + * Session event "session.fusion_route_failed". Experimental durable HydraFusion routing failure and the deterministic concrete fallback selected for the turn. + * @since 1.0.0 + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public final class SessionFusionRouteFailedEvent extends SessionEvent { + + @Override + public String getType() { return "session.fusion_route_failed"; } + + @JsonProperty("data") + private SessionFusionRouteFailedEventData data; + + public SessionFusionRouteFailedEventData getData() { return data; } + public void setData(SessionFusionRouteFailedEventData data) { this.data = data; } + + /** Data payload for {@link SessionFusionRouteFailedEvent}. */ + @JsonIgnoreProperties(ignoreUnknown = true) + @JsonInclude(JsonInclude.Include.NON_NULL) + public record SessionFusionRouteFailedEventData( + /** Identifier of the routing attempt that failed. */ + @JsonProperty("attemptId") String attemptId, + /** Synthetic HydraFusion model selected for the session. */ + @JsonProperty("syntheticModel") String syntheticModel, + /** HydraFusion routing policy requested for the turn. */ + @JsonProperty("policy") String policy, + /** Stable machine-readable reason for the routing failure. */ + @JsonProperty("reason") String reason, + /** Provider or validation error detail, when available. */ + @JsonProperty("errorMessage") String errorMessage, + /** Concrete model selected as the deterministic fallback. */ + @JsonProperty("fallbackModel") String fallbackModel, + /** Elapsed routing time in milliseconds before the failure. */ + @JsonProperty("routingLatencyMs") Double routingLatencyMs + ) { + } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/SessionFusionRouteStartedEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/SessionFusionRouteStartedEvent.java new file mode 100644 index 0000000000..139cb4f011 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/SessionFusionRouteStartedEvent.java @@ -0,0 +1,47 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: session-events.schema.json + +package com.github.copilot.generated; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import javax.annotation.processing.Generated; + +/** + * Session event "session.fusion_route_started". Experimental transient signal that HydraFusion routing has started for an eligible turn. + * @since 1.0.0 + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public final class SessionFusionRouteStartedEvent extends SessionEvent { + + @Override + public String getType() { return "session.fusion_route_started"; } + + @JsonProperty("data") + private SessionFusionRouteStartedEventData data; + + public SessionFusionRouteStartedEventData getData() { return data; } + public void setData(SessionFusionRouteStartedEventData data) { this.data = data; } + + /** Data payload for {@link SessionFusionRouteStartedEvent}. */ + @JsonIgnoreProperties(ignoreUnknown = true) + @JsonInclude(JsonInclude.Include.NON_NULL) + public record SessionFusionRouteStartedEventData( + /** Identifier for this routing attempt before a durable Fusion turn exists. */ + @JsonProperty("attemptId") String attemptId, + /** Kind of turn being routed. */ + @JsonProperty("turnKind") FusionTurnKind turnKind, + /** Synthetic HydraFusion model selected for the session. */ + @JsonProperty("syntheticModel") String syntheticModel, + /** HydraFusion routing policy requested for the turn. */ + @JsonProperty("policy") String policy + ) { + } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/SubagentConfiguredEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/SubagentConfiguredEvent.java new file mode 100644 index 0000000000..235066fade --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/SubagentConfiguredEvent.java @@ -0,0 +1,47 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: session-events.schema.json + +package com.github.copilot.generated; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import javax.annotation.processing.Generated; + +/** + * Session event "subagent.configured". Resolved runtime configuration for a configured sub-agent + * @since 1.0.0 + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public final class SubagentConfiguredEvent extends SessionEvent { + + @Override + public String getType() { return "subagent.configured"; } + + @JsonProperty("data") + private SubagentConfiguredEventData data; + + public SubagentConfiguredEventData getData() { return data; } + public void setData(SubagentConfiguredEventData data) { this.data = data; } + + /** Data payload for {@link SubagentConfiguredEvent}. */ + @JsonIgnoreProperties(ignoreUnknown = true) + @JsonInclude(JsonInclude.Include.NON_NULL) + public record SubagentConfiguredEventData( + /** Resolved model the sub-agent will run with */ + @JsonProperty("model") String model, + /** Resolved reasoning effort, when configured for the model */ + @JsonProperty("reasoningEffort") String reasoningEffort, + /** Resolved context tier, when configured for the model */ + @JsonProperty("contextTier") String contextTier, + /** Whether the sub-agent accepts follow-up turns */ + @JsonProperty("multiTurn") Boolean multiTurn + ) { + } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/SubagentStartedEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/SubagentStartedEvent.java index 291dc5e040..c246fac1ea 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 @@ -45,7 +45,15 @@ public record SubagentStartedEventData( /** Model the sub-agent will run with, when known at start. */ @JsonProperty("model") String model, /** Root id of the factory run that spawned this sub-agent, when it was spawned by one. */ - @JsonProperty("factoryRunId") String factoryRunId + @JsonProperty("factoryRunId") String factoryRunId, + /** Task-registry ID of the spawning sub-agent. Absent when the root session spawned this child. */ + @JsonProperty("parentId") String parentId, + /** Whether this sub-agent can be resumed. Currently always false. */ + @JsonProperty("resumable") Boolean resumable, + /** Type of the sub-agent selected at spawn time. */ + @JsonProperty("agentType") String agentType, + /** Whether the sub-agent runs synchronously or in the background. */ + @JsonProperty("executionMode") String executionMode ) { } } diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/ToolExecutionCompleteEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/ToolExecutionCompleteEvent.java index 574d3aa6b9..4cb949c80b 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/ToolExecutionCompleteEvent.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/ToolExecutionCompleteEvent.java @@ -62,7 +62,9 @@ public record ToolExecutionCompleteEventData( /** Whether this tool execution ran inside a sandbox container */ @JsonProperty("sandboxed") Boolean sandboxed, /** Tool call ID of the parent tool invocation when this event originates from a sub-agent */ - @JsonProperty("parentToolCallId") String parentToolCallId + @JsonProperty("parentToolCallId") String parentToolCallId, + /** Experimental HydraFusion attribution for this tool completion. */ + @JsonProperty("fusion") FusionAttribution fusion ) { } } diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/ToolExecutionStartEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/ToolExecutionStartEvent.java index 1aa69c6c20..36691ca41e 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/ToolExecutionStartEvent.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/ToolExecutionStartEvent.java @@ -57,7 +57,9 @@ public record ToolExecutionStartEventData( /** Tool definition metadata, present for MCP tools with MCP Apps support */ @JsonProperty("toolDescription") ToolExecutionStartToolDescription toolDescription, /** Tool call ID of the parent tool invocation when this event originates from a sub-agent */ - @JsonProperty("parentToolCallId") String parentToolCallId + @JsonProperty("parentToolCallId") String parentToolCallId, + /** Experimental HydraFusion attribution for this tool execution. */ + @JsonProperty("fusion") FusionAttribution fusion ) { } } diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/FactoryToolRunOptions.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/FactoryToolRunOptions.java new file mode 100644 index 0000000000..cc3e7c8da1 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/FactoryToolRunOptions.java @@ -0,0 +1,29 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import javax.annotation.processing.Generated; + +/** + * Options for an internal tool-originated factory invocation. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record FactoryToolRunOptions( + /** Per-invocation resource ceiling overrides. */ + @JsonProperty("limits") FactoryRunLimits limits, + /** Run identifier whose journal and progress should seed this resumed run. */ + @JsonProperty("resumeFromRunId") String resumeFromRunId +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/RunOptions.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/RunOptions.java index 92e4c401f8..11ac51827a 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/RunOptions.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/RunOptions.java @@ -23,6 +23,10 @@ public record RunOptions( /** Per-invocation resource ceiling overrides. */ @JsonProperty("limits") FactoryRunLimits limits, + /** Whether to notify the originating session when the factory completes. */ + @JsonProperty("notifyOnComplete") Boolean notifyOnComplete, + /** Whether to emit factory phase names to the session transcript. */ + @JsonProperty("logPhaseNames") Boolean logPhaseNames, /** Run identifier whose journal and progress should seed this resumed run. */ @JsonProperty("resumeFromRunId") String resumeFromRunId ) { diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryApi.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryApi.java index e0628ea3dc..e9a3e9f08a 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryApi.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryApi.java @@ -66,6 +66,38 @@ public CompletableFuture resume(SessionFactoryResume return caller.invoke("session.factory.resume", _p, SessionFactoryResumeResult.class); } + /** + * Internal parameters for invoking a registered factory from a tool. + *

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

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

diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryResumeFromToolParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryResumeFromToolParams.java new file mode 100644 index 0000000000..3a54179329 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryResumeFromToolParams.java @@ -0,0 +1,36 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import javax.annotation.processing.Generated; + +/** + * Internal parameters for resuming a factory run from a tool. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ +@CopilotExperimental +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record SessionFactoryResumeFromToolParams( + /** Target session identifier */ + @JsonProperty("sessionId") String sessionId, + /** Factory run identifier. */ + @JsonProperty("runId") String runId, + /** Optional per-invocation resource ceiling overrides. */ + @JsonProperty("limits") FactoryRunLimits limits, + /** Opaque identifier of the originating tool call. */ + @JsonProperty("toolCallId") String toolCallId +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryResumeFromToolResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryResumeFromToolResult.java new file mode 100644 index 0000000000..d4566384c5 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryResumeFromToolResult.java @@ -0,0 +1,32 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import javax.annotation.processing.Generated; + +/** + * Resolved persisted factory identity and resumed run envelope. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ +@CopilotExperimental +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record SessionFactoryResumeFromToolResult( + /** Persisted factory name resolved for the resumed run. */ + @JsonProperty("factoryName") String factoryName, + /** Terminal resumed run envelope. */ + @JsonProperty("run") FactoryRunResult run +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryResumeParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryResumeParams.java index 9c264284fc..edb39e0811 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryResumeParams.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryResumeParams.java @@ -29,6 +29,10 @@ public record SessionFactoryResumeParams( /** Factory run identifier. */ @JsonProperty("runId") String runId, /** Optional per-invocation resource ceiling overrides. */ - @JsonProperty("limits") FactoryRunLimits limits + @JsonProperty("limits") FactoryRunLimits limits, + /** Whether to notify the originating session when the factory completes. */ + @JsonProperty("notifyOnComplete") Boolean notifyOnComplete, + /** Whether to emit factory phase names to the session transcript. */ + @JsonProperty("logPhaseNames") Boolean logPhaseNames ) { } diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryRunFromToolParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryRunFromToolParams.java new file mode 100644 index 0000000000..dab8ae120d --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryRunFromToolParams.java @@ -0,0 +1,38 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import javax.annotation.processing.Generated; + +/** + * Internal parameters for invoking a registered factory from a tool. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ +@CopilotExperimental +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record SessionFactoryRunFromToolParams( + /** Target session identifier */ + @JsonProperty("sessionId") String sessionId, + /** Registered factory name. */ + @JsonProperty("name") String name, + /** Factory input value. */ + @JsonProperty("args") Object args, + /** Tool-originated factory invocation options. */ + @JsonProperty("options") FactoryToolRunOptions options, + /** Opaque identifier of the originating tool call. */ + @JsonProperty("toolCallId") String toolCallId +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryRunFromToolResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryRunFromToolResult.java new file mode 100644 index 0000000000..71b12f94f1 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryRunFromToolResult.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 com.github.copilot.CopilotExperimental; +import javax.annotation.processing.Generated; + +/** + * Complete current or terminal factory run envelope. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ +@CopilotExperimental +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record SessionFactoryRunFromToolResult( + /** Factory run identifier. */ + @JsonProperty("runId") String runId, + /** Current or terminal factory run status. */ + @JsonProperty("status") FactoryRunStatus status, + /** Completed factory result. */ + @JsonProperty("result") Object result, + /** Error message for an errored run. */ + @JsonProperty("error") String error, + /** Machine-readable failure details for an errored run. */ + @JsonProperty("failure") Object failure, + /** Reason for a halted or cancelled run. */ + @JsonProperty("reason") String reason, + /** Partial journal and progress snapshot for a halted, cancelled, or errored run. */ + @JsonProperty("snapshot") Object snapshot +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionToolsGetBuiltinDescriptorsParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionToolsGetBuiltinDescriptorsParams.java index 7740409548..43dd20e722 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionToolsGetBuiltinDescriptorsParams.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionToolsGetBuiltinDescriptorsParams.java @@ -26,8 +26,6 @@ public record SessionToolsGetBuiltinDescriptorsParams( /** Target session identifier */ @JsonProperty("sessionId") String sessionId, - /** Whether line numbers should be omitted from the view tool descriptor. */ - @JsonProperty("noViewLineNumbers") Boolean noViewLineNumbers, /** Whether descriptors should favor fewer user-intervention prompts. */ @JsonProperty("reduceUserIntervention") Boolean reduceUserIntervention, /** Whether tool descriptors should include authoring metadata. */ @@ -36,8 +34,6 @@ public record SessionToolsGetBuiltinDescriptorsParams( @JsonProperty("skillEmbeddingEnabled") Boolean skillEmbeddingEnabled, /** Shell-specific names and description lines for shell tools. */ @JsonProperty("shellConfig") ToolsShellDescriptorConfig shellConfig, - /** Whether shell commands may only run asynchronously. */ - @JsonProperty("shellAsyncOnlyEnabled") Boolean shellAsyncOnlyEnabled, /** Whether the configured shell supports PowerShell 7 syntax. */ @JsonProperty("shellSupportsPowerShell7Syntax") Boolean shellSupportsPowerShell7Syntax, /** Default shell timeout in milliseconds. */ 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 a1a22674b9..529c42f2bb 100644 --- a/java/sdk/src/test/java/com/github/copilot/SessionEventHandlingTest.java +++ b/java/sdk/src/test/java/com/github/copilot/SessionEventHandlingTest.java @@ -898,7 +898,7 @@ private SessionStartEvent createSessionStartEvent(String sessionId) { private AssistantMessageEvent createAssistantMessageEvent(String content) { var event = new AssistantMessageEvent(); var data = new AssistantMessageEvent.AssistantMessageEventData(null, null, content, null, null, null, null, - null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null); + null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null); event.setData(data); return event; } diff --git a/java/sdk/src/test/java/com/github/copilot/e2e/RewindIT.java b/java/sdk/src/test/java/com/github/copilot/e2e/RewindIT.java index 9598aac6f4..9321618575 100644 --- a/java/sdk/src/test/java/com/github/copilot/e2e/RewindIT.java +++ b/java/sdk/src/test/java/com/github/copilot/e2e/RewindIT.java @@ -9,6 +9,7 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assumptions.assumeFalse; import java.nio.file.Files; import java.nio.file.Path; @@ -54,6 +55,9 @@ static void teardown() throws Exception { @Test void shouldRestoreTrackedFileAndConversation() throws Exception { + assumeFalse(System.getProperty("os.name").startsWith("Windows"), + "blocked on CLI 1.0.81 file-change tracking regression on Windows"); + ctx.configureForTest("rewind", "should_restore_tracked_file_and_conversation"); Path filePath = ctx.getWorkDir().resolve(FILE_NAME); @@ -102,7 +106,7 @@ void shouldRestoreTrackedFileAndConversation() throws Exception { } private static SessionHistoryListRewindPointsResult waitForRewindPoints(CopilotSession session) throws Exception { - long deadline = System.nanoTime() + TimeUnit.SECONDS.toNanos(10); + long deadline = System.nanoTime() + TimeUnit.SECONDS.toNanos(30); SessionHistoryListRewindPointsResult result; do { result = session.getRpc().history.listRewindPoints().get(10, TimeUnit.SECONDS); diff --git a/nodejs/package-lock.json b/nodejs/package-lock.json index 84430ac141..6a3e01adb0 100644 --- a/nodejs/package-lock.json +++ b/nodejs/package-lock.json @@ -9,7 +9,7 @@ "version": "0.0.0-dev", "license": "MIT", "dependencies": { - "@github/copilot": "^1.0.81-11", + "@github/copilot": "^1.0.81", "koffi": "^3.1.0", "vscode-jsonrpc": "^8.2.1", "zod": "^4.3.6" @@ -658,8 +658,8 @@ } }, "node_modules/@github/copilot": { - "version": "1.0.81-11", - "integrity": "sha512-F7hZ6G6fhWH4uq862mbs2JE3nL0KIVBOc94/EFOdEjux3oHUQst9a06gKibEJS6VRULaYTXzatA1EAgNC9dzFA==", + "version": "1.0.81", + "integrity": "sha512-Yif+wnRY1rT6FMmxr+SMZCq60mBTTPvbAHGd42Jty9wf1ZmeTsJYAcaGDXC9oyNm3RYRc3wkL0MSscNiEZADAA==", "license": "SEE LICENSE IN LICENSE.md", "dependencies": { "detect-libc": "^2.1.2" @@ -668,19 +668,19 @@ "copilot": "npm-loader.js" }, "optionalDependencies": { - "@github/copilot-darwin-arm64": "1.0.81-11", - "@github/copilot-darwin-x64": "1.0.81-11", - "@github/copilot-linux-arm64": "1.0.81-11", - "@github/copilot-linux-x64": "1.0.81-11", - "@github/copilot-linuxmusl-arm64": "1.0.81-11", - "@github/copilot-linuxmusl-x64": "1.0.81-11", - "@github/copilot-win32-arm64": "1.0.81-11", - "@github/copilot-win32-x64": "1.0.81-11" + "@github/copilot-darwin-arm64": "1.0.81", + "@github/copilot-darwin-x64": "1.0.81", + "@github/copilot-linux-arm64": "1.0.81", + "@github/copilot-linux-x64": "1.0.81", + "@github/copilot-linuxmusl-arm64": "1.0.81", + "@github/copilot-linuxmusl-x64": "1.0.81", + "@github/copilot-win32-arm64": "1.0.81", + "@github/copilot-win32-x64": "1.0.81" } }, "node_modules/@github/copilot-darwin-arm64": { - "version": "1.0.81-11", - "integrity": "sha512-3eLs71CLnJH9RNnESnv4esipZPeGXMlBxQeKXwZY+crwcW2RAR8YuovQyfoZ/5by1PLPbYrOjXNfQL6kXSisrA==", + "version": "1.0.81", + "integrity": "sha512-VKHJTwRVaNXOmSkMjuFAotZxWNsNLSz3ZEiB1vpUqOYT3AsGMPrj8MIwh64AGfoLa91n4GyotGVbxwnsW8+K4g==", "cpu": [ "arm64" ], @@ -694,8 +694,8 @@ } }, "node_modules/@github/copilot-darwin-x64": { - "version": "1.0.81-11", - "integrity": "sha512-GdFLiUC8UL9k6K+woG8AyL3zBafd0Br1TIPDV5iiZsyBtTe4g67FjL7DGCYDt6AN9G43jWdK0M0InoNDVq1K1A==", + "version": "1.0.81", + "integrity": "sha512-O8BHh9d9j86RokqSYhgX3D1mA4t6MZp5OOneL1n3TPR7J+bEq+Catp+UBVGsJdhsuJ1Oasfk/z59Wit9wnc22w==", "cpu": [ "x64" ], @@ -709,8 +709,8 @@ } }, "node_modules/@github/copilot-linux-arm64": { - "version": "1.0.81-11", - "integrity": "sha512-C4hcAow5CdaVJITbdtGqFgxWpW7TqwyCPNn8OtcbvdWeiKcfOBDrgkvbsezG98/5Ovs3HWxZRT4oZqCEmGF9Ww==", + "version": "1.0.81", + "integrity": "sha512-GhHDhRkeWM3IfuouVGrU8UuXF26kPlV6aINSZyLIkrjY2AG/rro7NXNPeMKYT07HksoAWiMUrjEel8R/bpPplg==", "cpu": [ "arm64" ], @@ -724,8 +724,8 @@ } }, "node_modules/@github/copilot-linux-x64": { - "version": "1.0.81-11", - "integrity": "sha512-izu0PwWx+wL4zxacO6cd6r0zMMMQ3pTz+2euWcAd7HCJ/CIR6+YYfjU3TI3TPBZ9zDLZGoxHKYYPfJ1ZbSTzEg==", + "version": "1.0.81", + "integrity": "sha512-qhoiWIqfpvHcajJ8AVKa+ibKjD5k8Nccd9sfAJt5AMYN+Rv/aSCZojmnUazYv5UJAwStaGzep+rmVbTiN+sWUQ==", "cpu": [ "x64" ], @@ -739,8 +739,8 @@ } }, "node_modules/@github/copilot-linuxmusl-arm64": { - "version": "1.0.81-11", - "integrity": "sha512-uWGUjaxOSMu6dKFWcTvXGUUp76vC3OV7nlSupecRkQ7gA3OxdrrB8rXE5/leC606Hk6oe9Wl7wKCH9EwuZ6afg==", + "version": "1.0.81", + "integrity": "sha512-sOwSiqIM5H3AeYCbFW3f36qvm+YWyPinHwNiJC2DzuwpX/ujg8Warwm1zuokOJV5iyHC8AQxbwQRQ7LRQx19sQ==", "cpu": [ "arm64" ], @@ -754,8 +754,8 @@ } }, "node_modules/@github/copilot-linuxmusl-x64": { - "version": "1.0.81-11", - "integrity": "sha512-BpWKd/iu1tTyuPR2zuPFs1pVOlley4yt/TXkn6/gVwt52VI0rUxEO+n77RVjDcyMxWaUApbad2VNRPIH77guCA==", + "version": "1.0.81", + "integrity": "sha512-9lbAC0jDtlGNagKz9DycgzovHk35lS1lP5xV+EW1dTChep9uQt7n00d7Ru7ErvHS79QMRJw9PkPhKL9Jyz9jiA==", "cpu": [ "x64" ], @@ -769,8 +769,8 @@ } }, "node_modules/@github/copilot-win32-arm64": { - "version": "1.0.81-11", - "integrity": "sha512-GOK3cACgD96m065uJxbgcXL7MQ1qm+wq1qtvGBpprY0r9wTYJG/rVCtayjYB6B57rnaBwPll3+PQ2J1qgfO57A==", + "version": "1.0.81", + "integrity": "sha512-LBUennWqLDcAuYP3HrO9iwhxCjNA97g9jJplte8bEGFWoYrtOEs2UlEscorbHCQHeBpXXSn1slsUBpLl1CrDBQ==", "cpu": [ "arm64" ], @@ -784,8 +784,8 @@ } }, "node_modules/@github/copilot-win32-x64": { - "version": "1.0.81-11", - "integrity": "sha512-u4K6UU2iGQJqQwsdHNilJBwiaC48PfMsWVFnTSlJD5lCYp9zCk1odrvrcmB3ARYinE/IcXLUosiHaM/ChR/pdA==", + "version": "1.0.81", + "integrity": "sha512-1O1F1OdMO5Z/S9IjD3dQVA8QAKCuPgOlfhSvI19QfTp5zeDRZb2vCAERosY9UrURinawCHEoWmrg3EalXc4zaQ==", "cpu": [ "x64" ], diff --git a/nodejs/package.json b/nodejs/package.json index 9f464788ee..5298004835 100644 --- a/nodejs/package.json +++ b/nodejs/package.json @@ -56,7 +56,7 @@ "author": "GitHub", "license": "MIT", "dependencies": { - "@github/copilot": "^1.0.81-11", + "@github/copilot": "^1.0.81", "koffi": "^3.1.0", "vscode-jsonrpc": "^8.2.1", "zod": "^4.3.6" diff --git a/nodejs/samples/package-lock.json b/nodejs/samples/package-lock.json index 7c2a1052d8..6bf74301ea 100644 --- a/nodejs/samples/package-lock.json +++ b/nodejs/samples/package-lock.json @@ -18,7 +18,7 @@ "version": "0.0.0-dev", "license": "MIT", "dependencies": { - "@github/copilot": "^1.0.81-11", + "@github/copilot": "^1.0.81", "koffi": "^3.1.0", "vscode-jsonrpc": "^8.2.1", "zod": "^4.3.6" diff --git a/nodejs/src/generated/rpc.ts b/nodejs/src/generated/rpc.ts index 9e7304bde2..520426660a 100644 --- a/nodejs/src/generated/rpc.ts +++ b/nodejs/src/generated/rpc.ts @@ -7268,6 +7268,10 @@ export interface ExternalToolTextResultForLlmContentShellExit { * Whether outputPreview is known to be incomplete or truncated */ outputTruncated?: boolean; + /** + * Path reported in the shell session's filesystem namespace when shell output exceeded the configured large-output threshold. + */ + outputFilePath?: string; } /** * Image content block with base64-encoded data @@ -8087,6 +8091,14 @@ export interface FactoryResumeRequest { */ runId: string; limits?: FactoryRunLimits; + /** + * Whether to notify the originating session when the factory completes. + */ + notifyOnComplete?: boolean; + /** + * Whether to emit factory phase names to the session transcript. + */ + logPhaseNames?: boolean; } /** * Wire-only per-invocation factory resource ceiling overrides. @@ -8269,12 +8281,77 @@ export interface FactoryRunRequest { */ /** @experimental */ export interface RunOptions { + limits?: FactoryRunLimits; + /** + * Whether to notify the originating session when the factory completes. + */ + notifyOnComplete?: boolean; + /** + * Whether to emit factory phase names to the session transcript. + */ + logPhaseNames?: boolean; + /** + * Run identifier whose journal and progress should seed this resumed run. + */ + resumeFromRunId?: string; +} +/** + * Internal parameters for resuming a factory run from a tool. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "FactoryToolResumeRequest". + */ +/** @experimental */ +/** @internal */ +export interface FactoryToolResumeRequest { + /** + * Factory run identifier. + */ + runId: string; + limits?: FactoryRunLimits; + /** + * Opaque identifier of the originating tool call. + */ + toolCallId?: string; +} +/** + * Options for an internal tool-originated factory invocation. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "FactoryToolRunOptions". + */ +/** @experimental */ +/** @internal */ +export interface FactoryToolRunOptions { limits?: FactoryRunLimits; /** * Run identifier whose journal and progress should seed this resumed run. */ resumeFromRunId?: string; } +/** + * Internal parameters for invoking a registered factory from a tool. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "FactoryToolRunRequest". + */ +/** @experimental */ +/** @internal */ +export interface FactoryToolRunRequest { + /** + * Registered factory name. + */ + name: string; + /** + * Factory input value. + */ + args: JsonValue; + options?: FactoryToolRunOptions; + /** + * Opaque identifier of the originating tool call. + */ + toolCallId?: string; +} /** * Optional user prompt to combine with the fleet orchestration instructions. * @@ -21005,10 +21082,6 @@ export interface ToolsExecuteRequest { */ /** @experimental */ export interface ToolsGetBuiltinDescriptorsRequest { - /** - * Whether line numbers should be omitted from the view tool descriptor. - */ - noViewLineNumbers?: boolean; /** * Whether descriptors should favor fewer user-intervention prompts. */ @@ -21022,10 +21095,6 @@ export interface ToolsGetBuiltinDescriptorsRequest { */ skillEmbeddingEnabled?: boolean; shellConfig?: ToolsShellDescriptorConfig; - /** - * Whether shell commands may only run asynchronously. - */ - shellAsyncOnlyEnabled?: boolean; /** * Whether the configured shell supports PowerShell 7 syntax. */ @@ -25554,6 +25623,27 @@ export function createInternalSessionRpc(connection: MessageConnection, sessionI }, }, /** @experimental */ + factory: { + /** + * Internal tool-originated factory invocation. + * + * @param params Internal parameters for invoking a registered factory from a tool. + * + * @returns Complete current or terminal factory run envelope. + */ + runFromTool: async (params: FactoryToolRunRequest): Promise => + connection.sendRequest("session.factory.runFromTool", { sessionId, ...params }), + /** + * Internal tool-originated factory resume. + * + * @param params Internal parameters for resuming a factory run from a tool. + * + * @returns Resolved persisted factory identity and resumed run envelope. + */ + resumeFromTool: async (params: FactoryToolResumeRequest): Promise => + connection.sendRequest("session.factory.resumeFromTool", { sessionId, ...params }), + }, + /** @experimental */ model: { /** * Resolves and applies organization-managed and repository model overlays. diff --git a/nodejs/src/generated/session-events.ts b/nodejs/src/generated/session-events.ts index 473a2dbe16..512ffb398b 100644 --- a/nodejs/src/generated/session-events.ts +++ b/nodejs/src/generated/session-events.ts @@ -40,10 +40,17 @@ export type SessionEvent = | CompactionStartEvent | CompactionCompleteEvent | TaskCompleteEvent + | FusionRouteStartedEvent + | FusionRouteFailedEvent + | FusionResolvedEvent + | FusionCompletedEvent | UserMessageEvent | PendingMessagesModifiedEvent | AssistantTurnStartEvent | AssistantIntentEvent + | AssistantFusionPhaseStartedEvent + | AssistantFusionPhaseCompletedEvent + | AssistantFusionPhaseFailedEvent | AssistantServerToolProgressEvent | AssistantReasoningEvent | AssistantReasoningDeltaEvent @@ -66,6 +73,7 @@ export type SessionEvent = | ToolSearchActivatedEvent | SkillInvokedEvent | SubagentStartedEvent + | SubagentConfiguredEvent | SubagentCompletedEvent | SubagentFailedEvent | SubagentSelectedEvent @@ -298,6 +306,35 @@ export type TaskCompletionOutcome = | "continue" /** Completion cannot proceed without intervention; the active objective is paused when one is identified. */ | "blocked"; +/** + * Kind of turn for which HydraFusion routing is running. + */ +/** @experimental */ +export type FusionTurnKind = + /** A user-message turn. */ + | "user" + /** A conversation-compaction turn. */ + | "compaction"; +/** + * Server-recommended routing behavior for a later HydraFusion turn. + */ +/** @experimental */ +export type FusionFollowUpAction = + /** Reuse the durable primary model without routing. */ + | "reuse_primary" + /** Request a new routing decision. */ + | "reroute"; +/** + * Validated HydraFusion execution pattern. + */ +/** @experimental */ +export type FusionPattern = + /** Run one primary solver phase. */ + | "single" + /** Run a primary phase, a judge, and an optional repair. */ + | "cascade" + /** Run a primary draft, a read-only critique, and a revision. */ + | "critique"; /** * The agent mode that was active when this message was sent */ @@ -357,6 +394,57 @@ export type UserMessageDelivery = | "steering" /** Enqueued while the agent was busy; processed as its own run afterward. */ | "queued"; +/** + * Conversation scope in which a HydraFusion phase executes. + */ +/** @experimental */ +export type FusionConversationScope = + /** Canonical root conversation history. */ + | "root" + /** Isolated read-only review history that does not enter the root conversation. */ + | "review"; +/** + * HydraFusion phase kind. + */ +/** @experimental */ +export type FusionPhaseKind = + /** Primary solver phase. */ + | "primary" + /** Read-only cascade judge phase. */ + | "judge" + /** Cascade repair phase. */ + | "repair" + /** Initial critique-pattern draft phase. */ + | "draft" + /** Read-only critique phase. */ + | "critic" + /** Critique-pattern revision phase. */ + | "revision" + /** Follow-up phase continuing from the resolved model. */ + | "follow_up"; +/** + * How a durable phase checkpoint contributes its exact message to canonical root history. + */ +/** @experimental */ +/** @internal */ +export type FusionProjectionMode = + /** Append the exact root message immediately. */ + | "append" + /** Hold a terminal message outside canonical history until the final commit selects it. */ + | "staged" + /** Do not project the checkpoint into root history. */ + | "none"; +/** + * Durable outcome status of a HydraFusion phase. + */ +/** @experimental */ +export type FusionPhaseStatus = + /** The phase completed successfully. */ + | "succeeded" + /** The phase failed. */ + | "failed" + /** The phase was cancelled. */ + | "cancelled"; /** * Tool call type: "function" for standard tool calls, "custom" for grammar-based tool calls. Defaults to "function" when absent. */ @@ -2918,18 +3006,19 @@ export interface TaskCompleteData { summary?: string; } /** - * Session event "user.message". Payload of `user.message` with displayed and model-transformed content, attachments, source/delivery metadata, mode, and telemetry IDs. + * Session event "session.fusion_route_started". Experimental transient signal that HydraFusion routing has started for an eligible turn. */ -export interface UserMessageEvent { +/** @experimental */ +export interface FusionRouteStartedEvent { /** * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. */ agentId?: string; - data: UserMessageData; + data: FusionRouteStartedData; /** - * When true, the event is transient and not persisted to the session event log on disk + * Always true for events that are transient and not persisted to the session event log on disk. */ - ephemeral?: boolean; + ephemeral: true; /** * Unique event identifier (UUID v4), generated when the event is emitted */ @@ -2943,311 +3032,669 @@ export interface UserMessageEvent { */ timestamp: string; /** - * Type discriminator. Always "user.message". + * Type discriminator. Always "session.fusion_route_started". */ - type: "user.message"; + type: "session.fusion_route_started"; } /** - * Payload of `user.message` with displayed and model-transformed content, attachments, source/delivery metadata, mode, and telemetry IDs. + * Experimental transient signal that HydraFusion routing has started for an eligible turn. */ -export interface UserMessageData { - agentMode?: UserMessageAgentMode; - /** - * Files, selections, or GitHub references attached to the message - */ - attachments?: Attachment[]; +/** @experimental */ +export interface FusionRouteStartedData { /** - * The user's message text as displayed in the timeline + * Identifier for this routing attempt before a durable Fusion turn exists. */ - content: string; - delivery?: UserMessageDelivery; + attemptId: string; /** - * CAPI interaction ID for correlating this user message with its turn + * HydraFusion routing policy requested for the turn. */ - interactionId?: string; + policy?: string; /** - * True when this user message was auto-injected by autopilot's continuation loop rather than typed by the user; used to distinguish autopilot-driven turns in telemetry. + * Synthetic HydraFusion model selected for the session. */ - isAutopilotContinuation?: boolean; + syntheticModel?: string; + turnKind: FusionTurnKind; +} +/** + * Session event "session.fusion_route_failed". Experimental durable HydraFusion routing failure and the deterministic concrete fallback selected for the turn. + */ +/** @experimental */ +export interface FusionRouteFailedEvent { /** - * Path-backed native document attachments that stayed on the tagged_files path flow because native upload could not read them or would exceed the request size limit + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. */ - nativeDocumentPathFallbackPaths?: string[]; + agentId?: string; + data: FusionRouteFailedData; /** - * Parent agent task ID for background telemetry correlated to this user turn + * When true, the event is transient and not persisted to the session event log on disk */ - parentAgentTaskId?: string; + ephemeral?: boolean; /** - * Origin of this message, used for timeline filtering and attribution (e.g., `skill-pdf` for hidden skill injection or `agent-` for an inter-agent prompt) + * Unique event identifier (UUID v4), generated when the event is emitted */ - source?: string; + id: string; /** - * Normalized document MIME types that were sent natively instead of through tagged_files XML + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. */ - supportedNativeDocumentMimeTypes?: string[]; + parentId: string | null; /** - * Transformed version of the message sent to the model, with XML wrapping, timestamps, and other augmentations for prompt caching + * ISO 8601 timestamp when the event was created */ - transformedContent?: string; + timestamp: string; /** - * The agent-loop turn ID that consumed this message; absent when no agent-loop turn consumed it + * Type discriminator. Always "session.fusion_route_failed". */ - turnId?: string; + type: "session.fusion_route_failed"; } /** - * File attachment + * Experimental durable HydraFusion routing failure and the deterministic concrete fallback selected for the turn. */ -export interface AttachmentFile { +/** @experimental */ +export interface FusionRouteFailedData { /** - * Internal: content-addressed id of the session.binary_asset event holding this attachment's model-facing bytes (e.g. "sha256:..."). Absent externally. + * Identifier of the routing attempt that failed. */ - assetId?: string; + attemptId: string; /** - * Internal: decoded byte length of the attachment's model-facing bytes. Absent externally. + * Provider or validation error detail, when available. */ - byteLength?: number; + errorMessage?: string; /** - * User-facing display name for the attachment + * Concrete model selected as the deterministic fallback. */ - displayName: string; - lineRange?: AttachmentFileLineRange; + fallbackModel: string; /** - * Internal: MIME type of the file's model-facing bytes (post-resize for images). Set when the file's bytes are interned to an asset. Absent externally. + * HydraFusion routing policy requested for the turn. */ - mimeType?: string; - omittedReason?: OmittedBinaryOmittedReason; + policy: string; /** - * Absolute file path + * Stable machine-readable reason for the routing failure. */ - path: string; + reason: string; /** - * Frozen rendered line this attachment contributed to the prompt block (e.g. "* /path (123 lines)"). Captured at send time so resumed history reproduces the exact text the model saw, independent of later filesystem changes. Present only for attachments routed to (mutually exclusive with assetId, which marks bytes sent natively). + * Elapsed routing time in milliseconds before the failure. */ - taggedFilesEntry?: string; + routingLatencyMs?: number; /** - * Attachment type discriminator + * Synthetic HydraFusion model selected for the session. */ - type: "file"; + syntheticModel: string; } /** - * Optional line range to scope the attachment to a specific section of the file + * Session event "session.fusion_resolved". Experimental durable validated HydraFusion route and turn policy. */ -export interface AttachmentFileLineRange { +/** @experimental */ +export interface FusionResolvedEvent { /** - * End line number (1-based, inclusive) + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. */ - end: number; + agentId?: string; + data: FusionResolvedData; /** - * Start line number (1-based) + * When true, the event is transient and not persisted to the session event log on disk */ - start: number; -} -/** - * Directory attachment - */ -export interface AttachmentDirectory { + ephemeral?: boolean; /** - * User-facing display name for the attachment + * Unique event identifier (UUID v4), generated when the event is emitted */ - displayName: string; + id: string; /** - * Absolute directory path + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. */ - path: string; + parentId: string | null; /** - * Frozen rendered line this attachment contributed to the prompt block (e.g. "* /path (12 items)"). Captured at send time so resumed history reproduces the exact text the model saw, independent of later filesystem changes. + * ISO 8601 timestamp when the event was created */ - taggedFilesEntry?: string; + timestamp: string; /** - * Attachment type discriminator + * Type discriminator. Always "session.fusion_resolved". */ - type: "directory"; + type: "session.fusion_resolved"; } /** - * Code selection attachment from an editor + * Experimental durable validated HydraFusion route and turn policy. */ -export interface AttachmentSelection { +/** @experimental */ +export interface FusionResolvedData { /** - * User-facing display name for the selection + * Version of the validated HydraFusion event contract. */ - displayName: string; + contractVersion: number; /** - * Absolute path to the file containing the selection + * Concrete model used when the planned primary model cannot execute. */ - filePath: string; - selection: AttachmentSelectionDetails; + fallbackModel: string; + followUp?: FusionFollowUpRecommendation; /** - * The selected text content + * Concrete model recommended for eligible follow-up turns. */ - text: string; + followUpModel: string; /** - * Attachment type discriminator + * Stable identifier for the resolved HydraFusion turn. */ - type: "selection"; -} -/** - * Position range of the selection within the file - */ -export interface AttachmentSelectionDetails { - end: AttachmentSelectionDetailsEnd; - start: AttachmentSelectionDetailsStart; -} -/** - * End position of the selection - */ -export interface AttachmentSelectionDetailsEnd { + fusionId: string; /** - * End character offset within the line (0-based) + * Version of the executable model universe used for selection. */ - character: number; + modelUniverseVersion?: string; + pattern: FusionPattern; /** - * End line number (0-based) + * Version of the validated execution-plan format. */ - line: number; -} -/** - * Start position of the selection - */ -export interface AttachmentSelectionDetailsStart { + planVersion?: string; /** - * Start character offset within the line (0-based) + * HydraFusion routing policy used to resolve the plan. */ - character: number; + policy: string; /** - * Start line number (0-based) + * Version of the local routing policy. */ - line: number; -} -/** - * GitHub issue, pull request, or discussion reference - */ -export interface AttachmentGitHubReference { + policyVersion?: string; /** - * Issue, pull request, or discussion number + * Concrete model selected for the primary solver phase. */ - number: number; - referenceType: AttachmentGitHubReferenceType; + primaryModel: string; /** - * Current state of the referenced item (e.g., open, closed, merged) + * Router implementation that supplied the plan. */ - state: string; + routeSource?: string; /** - * Title of the referenced item + * Elapsed time in milliseconds required to resolve and validate the route. */ - title: string; + routingLatencyMs?: number; /** - * Attachment type discriminator + * Identifier of the local policy rule that matched. */ - type: "github_reference"; + ruleId?: string; /** - * URL to the referenced item on GitHub + * Zero-based index of the local policy rule that matched. */ - url: string; -} -/** - * Pointer to a GitHub commit. - */ -export interface AttachmentGitHubCommit { + ruleIndex?: number; /** - * First line of the commit message + * Human-readable name of the local policy rule that matched. */ - message: string; + ruleName?: string; + scores?: FusionScores; /** - * Full commit SHA + * Concrete model selected for the review or judge phase, when required. */ - oid: string; - repo: GitHubRepoRef; + secondaryModel: string | null; /** - * Attachment type discriminator + * Synthetic HydraFusion model selected for the session. */ - type: "github_commit"; + syntheticModel: string; /** - * URL to the commit on GitHub + * Identifier of the session turn associated with the route. */ - url: string; + turnId: string; } /** - * Pointer to a GitHub repository. + * Durable server recommendation for subsequent HydraFusion turns. */ -export interface GitHubRepoRef { - /** - * Numeric GitHub repository id - */ - id?: number; - /** - * Repository name (without owner) - */ - name: string; - /** - * Repository owner login (user or organization) - */ - owner: string; +/** @experimental */ +export interface FusionFollowUpRecommendation { + compactionTurn: FusionFollowUpAction; + userTurn: FusionFollowUpAction; } /** - * Pointer to a GitHub release. + * Validated HydraFusion routing capability scores. */ -export interface AttachmentGitHubRelease { +/** @experimental */ +export interface FusionScores { /** - * Human-readable release name + * Code-generation capability score returned by the authenticated router. */ - name: string; - repo: GitHubRepoRef; + codeGen: number; /** - * Git tag the release is anchored to + * Debugging capability score returned by the authenticated router. */ - tagName: string; + debugging: number; /** - * Attachment type discriminator + * Reasoning capability score returned by the authenticated router. */ - type: "github_release"; + reasoning: number; /** - * URL to the release on GitHub + * Tool-use capability score returned by the authenticated router. */ - url: string; + toolUse: number; } /** - * Pointer to a GitHub Actions job. + * Session event "session.fusion_completed". Experimental durable aggregate outcome of a HydraFusion turn. */ -export interface AttachmentGitHubActionsJob { +/** @experimental */ +export interface FusionCompletedEvent { /** - * Terminal conclusion of the job when finished (e.g., success, failure, cancelled). Absent for in-progress jobs. + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. */ - conclusion?: string; + agentId?: string; + data: FusionCompletedData; /** - * Job id within the workflow run + * When true, the event is transient and not persisted to the session event log on disk */ - jobId: number; + ephemeral?: boolean; /** - * Display name of the job + * Unique event identifier (UUID v4), generated when the event is emitted */ - jobName: string; - repo: GitHubRepoRef; + id: string; /** - * Attachment type discriminator + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. */ - type: "github_actions_job"; + parentId: string | null; /** - * URL to the job on GitHub + * ISO 8601 timestamp when the event was created */ - url: string; + timestamp: string; /** - * Display name of the workflow the job ran in + * Type discriminator. Always "session.fusion_completed". */ - workflowName: string; + type: "session.fusion_completed"; } /** - * Pointer to a GitHub repository. + * Experimental durable aggregate outcome of a HydraFusion turn. */ -export interface AttachmentGitHubRepository { +/** @experimental */ +export interface FusionCompletedData { /** - * Short description of the repository + * Total cached input tokens reported across all phases. */ - description?: string; + cachedTokens: number; /** - * Git ref this attachment is anchored at (branch, tag, or commit). When absent the default branch is implied. + * Total tokens written to prompt cache across all phases. */ - ref?: string; - repo: GitHubRepoRef; + cacheWriteTokens?: number; + /** + * Idempotency identifier for the authoritative final commit. + */ + commitId: string; + /** + * Reason the turn used a degraded route, when applicable. + */ + degradedReason: string | null; + /** + * Total elapsed execution time for the HydraFusion turn in milliseconds. + */ + durationMs: number; + /** + * Concrete model that supplied the authoritative final content. + */ + finalSourceModel: string | null; + /** + * Phase whose output supplied the authoritative final content. + */ + finalSourcePhaseId: string | null; + /** + * Concrete model recommended for eligible follow-up turns. + */ + followUpModel: string; + /** + * Stable identifier for the completed HydraFusion turn. + */ + fusionId: string; + /** + * Total input tokens consumed across all phases. + */ + inputTokens: number; + /** + * Stable aggregate outcome of the HydraFusion turn. + */ + outcome: string; + /** + * Total output tokens produced across all phases. + */ + outputTokens: number; + pattern: FusionPattern; + /** + * Number of concrete phases attempted by the turn. + */ + phaseCount: number; + /** + * Total concrete model requests made across all phases. + */ + requestCount: number; + /** + * Synthetic HydraFusion model selected for the session. + */ + syntheticModel: string; + /** + * Total normalized AI-unit cost reported across all phases, in nano-AIU. + */ + totalNanoAiu: number; + /** + * Identifier of the session turn associated with the completion. + */ + turnId: string; +} +/** + * Session event "user.message". Payload of `user.message` with displayed and model-transformed content, attachments, source/delivery metadata, mode, and telemetry IDs. + */ +export interface UserMessageEvent { + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + data: UserMessageData; + /** + * When true, the event is transient and not persisted to the session event log on disk + */ + ephemeral?: boolean; + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + /** + * Type discriminator. Always "user.message". + */ + type: "user.message"; +} +/** + * Payload of `user.message` with displayed and model-transformed content, attachments, source/delivery metadata, mode, and telemetry IDs. + */ +export interface UserMessageData { + agentMode?: UserMessageAgentMode; + /** + * Files, selections, or GitHub references attached to the message + */ + attachments?: Attachment[]; + /** + * The user's message text as displayed in the timeline + */ + content: string; + delivery?: UserMessageDelivery; + /** + * CAPI interaction ID for correlating this user message with its turn + */ + interactionId?: string; + /** + * True when this user message was auto-injected by autopilot's continuation loop rather than typed by the user; used to distinguish autopilot-driven turns in telemetry. + */ + isAutopilotContinuation?: boolean; + /** + * Path-backed native document attachments that stayed on the tagged_files path flow because native upload could not read them or would exceed the request size limit + */ + nativeDocumentPathFallbackPaths?: string[]; + /** + * Parent agent task ID for background telemetry correlated to this user turn + */ + parentAgentTaskId?: string; + /** + * Origin of this message, used for timeline filtering and attribution (e.g., `skill-pdf` for hidden skill injection or `agent-` for an inter-agent prompt) + */ + source?: string; + /** + * Normalized document MIME types that were sent natively instead of through tagged_files XML + */ + supportedNativeDocumentMimeTypes?: string[]; + /** + * Transformed version of the message sent to the model, with XML wrapping, timestamps, and other augmentations for prompt caching + */ + transformedContent?: string; + /** + * The agent-loop turn ID that consumed this message; absent when no agent-loop turn consumed it + */ + turnId?: string; +} +/** + * File attachment + */ +export interface AttachmentFile { + /** + * Internal: content-addressed id of the session.binary_asset event holding this attachment's model-facing bytes (e.g. "sha256:..."). Absent externally. + */ + assetId?: string; + /** + * Internal: decoded byte length of the attachment's model-facing bytes. Absent externally. + */ + byteLength?: number; + /** + * User-facing display name for the attachment + */ + displayName: string; + lineRange?: AttachmentFileLineRange; + /** + * Internal: MIME type of the file's model-facing bytes (post-resize for images). Set when the file's bytes are interned to an asset. Absent externally. + */ + mimeType?: string; + omittedReason?: OmittedBinaryOmittedReason; + /** + * Absolute file path + */ + path: string; + /** + * Frozen rendered line this attachment contributed to the prompt block (e.g. "* /path (123 lines)"). Captured at send time so resumed history reproduces the exact text the model saw, independent of later filesystem changes. Present only for attachments routed to (mutually exclusive with assetId, which marks bytes sent natively). + */ + taggedFilesEntry?: string; + /** + * Attachment type discriminator + */ + type: "file"; +} +/** + * Optional line range to scope the attachment to a specific section of the file + */ +export interface AttachmentFileLineRange { + /** + * End line number (1-based, inclusive) + */ + end: number; + /** + * Start line number (1-based) + */ + start: number; +} +/** + * Directory attachment + */ +export interface AttachmentDirectory { + /** + * User-facing display name for the attachment + */ + displayName: string; + /** + * Absolute directory path + */ + path: string; + /** + * Frozen rendered line this attachment contributed to the prompt block (e.g. "* /path (12 items)"). Captured at send time so resumed history reproduces the exact text the model saw, independent of later filesystem changes. + */ + taggedFilesEntry?: string; + /** + * Attachment type discriminator + */ + type: "directory"; +} +/** + * Code selection attachment from an editor + */ +export interface AttachmentSelection { + /** + * User-facing display name for the selection + */ + displayName: string; + /** + * Absolute path to the file containing the selection + */ + filePath: string; + selection: AttachmentSelectionDetails; + /** + * The selected text content + */ + text: string; + /** + * Attachment type discriminator + */ + type: "selection"; +} +/** + * Position range of the selection within the file + */ +export interface AttachmentSelectionDetails { + end: AttachmentSelectionDetailsEnd; + start: AttachmentSelectionDetailsStart; +} +/** + * End position of the selection + */ +export interface AttachmentSelectionDetailsEnd { + /** + * End character offset within the line (0-based) + */ + character: number; + /** + * End line number (0-based) + */ + line: number; +} +/** + * Start position of the selection + */ +export interface AttachmentSelectionDetailsStart { + /** + * Start character offset within the line (0-based) + */ + character: number; + /** + * Start line number (0-based) + */ + line: number; +} +/** + * GitHub issue, pull request, or discussion reference + */ +export interface AttachmentGitHubReference { + /** + * Issue, pull request, or discussion number + */ + number: number; + referenceType: AttachmentGitHubReferenceType; + /** + * Current state of the referenced item (e.g., open, closed, merged) + */ + state: string; + /** + * Title of the referenced item + */ + title: string; + /** + * Attachment type discriminator + */ + type: "github_reference"; + /** + * URL to the referenced item on GitHub + */ + url: string; +} +/** + * Pointer to a GitHub commit. + */ +export interface AttachmentGitHubCommit { + /** + * First line of the commit message + */ + message: string; + /** + * Full commit SHA + */ + oid: string; + repo: GitHubRepoRef; + /** + * Attachment type discriminator + */ + type: "github_commit"; + /** + * URL to the commit on GitHub + */ + url: string; +} +/** + * Pointer to a GitHub repository. + */ +export interface GitHubRepoRef { + /** + * Numeric GitHub repository id + */ + id?: number; + /** + * Repository name (without owner) + */ + name: string; + /** + * Repository owner login (user or organization) + */ + owner: string; +} +/** + * Pointer to a GitHub release. + */ +export interface AttachmentGitHubRelease { + /** + * Human-readable release name + */ + name: string; + repo: GitHubRepoRef; + /** + * Git tag the release is anchored to + */ + tagName: string; + /** + * Attachment type discriminator + */ + type: "github_release"; + /** + * URL to the release on GitHub + */ + url: string; +} +/** + * Pointer to a GitHub Actions job. + */ +export interface AttachmentGitHubActionsJob { + /** + * Terminal conclusion of the job when finished (e.g., success, failure, cancelled). Absent for in-progress jobs. + */ + conclusion?: string; + /** + * Job id within the workflow run + */ + jobId: number; + /** + * Display name of the job + */ + jobName: string; + repo: GitHubRepoRef; + /** + * Attachment type discriminator + */ + type: "github_actions_job"; + /** + * URL to the job on GitHub + */ + url: string; + /** + * Display name of the workflow the job ran in + */ + workflowName: string; +} +/** + * Pointer to a GitHub repository. + */ +export interface AttachmentGitHubRepository { + /** + * Short description of the repository + */ + description?: string; + /** + * Git ref this attachment is anchored at (branch, tag, or commit). When absent the default branch is implied. + */ + ref?: string; + repo: GitHubRepoRef; /** * Attachment type discriminator */ @@ -3400,47 +3847,168 @@ export interface AttachmentBlob { type: "blob"; } /** - * Structured context contributed by an extension. Composer pills displayed in the host are forwarded back through session.send.attachments, then rendered into the model prompt as an XML block. + * Structured context contributed by an extension. Composer pills displayed in the host are forwarded back through session.send.attachments, then rendered into the model prompt as an XML block. + */ +export interface AttachmentExtensionContext { + /** + * Provider-local canvas identifier when the push was bound to a canvas instance + */ + canvasId?: string; + /** + * ISO 8601 timestamp captured by the runtime when the push was accepted + */ + capturedAt: string; + /** + * Owning extension identifier. Runtime-derived from the caller's connection when produced via session.extensions.sendAttachmentsToMessage; preserved verbatim on subsequent transports. + */ + extensionId: string; + /** + * Open canvas instance identifier when the push was bound to a canvas instance + */ + instanceId?: string; + /** + * Caller-supplied JSON payload + */ + payload?: JsonValue; + /** + * Human-readable composer pill label + */ + title: string; + /** + * Attachment type discriminator + */ + type: "extension_context"; +} +/** + * Session event "pending_messages.modified". Empty payload; the event signals that the pending message queue has changed + */ +export interface PendingMessagesModifiedEvent { + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + data: PendingMessagesModifiedData; + /** + * Always true for events that are transient and not persisted to the session event log on disk. + */ + ephemeral: true; + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + /** + * Type discriminator. Always "pending_messages.modified". + */ + type: "pending_messages.modified"; +} +/** + * Empty payload; the event signals that the pending message queue has changed + */ +export interface PendingMessagesModifiedData {} +/** + * Session event "assistant.turn_start". Turn initialization metadata including identifier and interaction tracking + */ +export interface AssistantTurnStartEvent { + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + data: AssistantTurnStartData; + /** + * When true, the event is transient and not persisted to the session event log on disk + */ + ephemeral?: boolean; + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + /** + * Type discriminator. Always "assistant.turn_start". + */ + type: "assistant.turn_start"; +} +/** + * Turn initialization metadata including identifier and interaction tracking + */ +export interface AssistantTurnStartData { + /** + * CAPI interaction ID for correlating this turn with upstream telemetry + */ + interactionId?: string; + /** + * Model identifier used for this turn, when known + */ + model?: string; + /** + * Identifier for this turn within the agentic loop, typically a stringified turn number + */ + turnId: string; +} +/** + * Session event "assistant.intent". Agent intent description for current activity or plan */ -export interface AttachmentExtensionContext { +export interface AssistantIntentEvent { /** - * Provider-local canvas identifier when the push was bound to a canvas instance + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. */ - canvasId?: string; + agentId?: string; + data: AssistantIntentData; /** - * ISO 8601 timestamp captured by the runtime when the push was accepted + * Always true for events that are transient and not persisted to the session event log on disk. */ - capturedAt: string; + ephemeral: true; /** - * Owning extension identifier. Runtime-derived from the caller's connection when produced via session.extensions.sendAttachmentsToMessage; preserved verbatim on subsequent transports. + * Unique event identifier (UUID v4), generated when the event is emitted */ - extensionId: string; + id: string; /** - * Open canvas instance identifier when the push was bound to a canvas instance + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. */ - instanceId?: string; + parentId: string | null; /** - * Caller-supplied JSON payload + * ISO 8601 timestamp when the event was created */ - payload?: JsonValue; + timestamp: string; /** - * Human-readable composer pill label + * Type discriminator. Always "assistant.intent". */ - title: string; + type: "assistant.intent"; +} +/** + * Agent intent description for current activity or plan + */ +export interface AssistantIntentData { /** - * Attachment type discriminator + * Short description of what the agent is currently doing or planning to do */ - type: "extension_context"; + intent: string; } /** - * Session event "pending_messages.modified". Empty payload; the event signals that the pending message queue has changed + * Session event "assistant.fusion_phase_started". Experimental transient HydraFusion phase/model/role signal. */ -export interface PendingMessagesModifiedEvent { +/** @experimental */ +export interface AssistantFusionPhaseStartedEvent { /** * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. */ agentId?: string; - data: PendingMessagesModifiedData; + data: FusionPhaseStartedData; /** * Always true for events that are transient and not persisted to the session event log on disk. */ @@ -3458,23 +4026,45 @@ export interface PendingMessagesModifiedEvent { */ timestamp: string; /** - * Type discriminator. Always "pending_messages.modified". + * Type discriminator. Always "assistant.fusion_phase_started". */ - type: "pending_messages.modified"; + type: "assistant.fusion_phase_started"; } /** - * Empty payload; the event signals that the pending message queue has changed + * Experimental transient HydraFusion phase/model/role signal. */ -export interface PendingMessagesModifiedData {} +/** @experimental */ +export interface FusionPhaseStartedData { + conversationScope: FusionConversationScope; + /** + * Identifier of the HydraFusion turn containing the phase. + */ + fusionId: string; + /** + * Concrete model executing the phase. + */ + model: string; + pattern: FusionPattern; + /** + * Stable identifier for the concrete phase. + */ + phaseId: string; + phaseKind: FusionPhaseKind; + /** + * Semantic role assigned to the phase. + */ + role: string; +} /** - * Session event "assistant.turn_start". Turn initialization metadata including identifier and interaction tracking + * Session event "assistant.fusion_phase_completed". Experimental durable HydraFusion phase output and lossless replay checkpoint. */ -export interface AssistantTurnStartEvent { +/** @experimental */ +export interface AssistantFusionPhaseCompletedEvent { /** * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. */ agentId?: string; - data: AssistantTurnStartData; + data: FusionPhaseCompletedData; /** * When true, the event is transient and not persisted to the session event log on disk */ @@ -3492,40 +4082,122 @@ export interface AssistantTurnStartEvent { */ timestamp: string; /** - * Type discriminator. Always "assistant.turn_start". + * Type discriminator. Always "assistant.fusion_phase_completed". */ - type: "assistant.turn_start"; + type: "assistant.fusion_phase_completed"; } /** - * Turn initialization metadata including identifier and interaction tracking + * Experimental durable HydraFusion phase output and lossless replay checkpoint. */ -export interface AssistantTurnStartData { +/** @experimental */ +export interface FusionPhaseCompletedData { /** - * CAPI interaction ID for correlating this turn with upstream telemetry + * Provider-normalized textual output produced by the phase. */ - interactionId?: string; + content: string; + conversationScope: FusionConversationScope; /** - * Model identifier used for this turn, when known + * Elapsed execution time for the phase in milliseconds. */ - model?: string; + durationMs: number; /** - * Identifier for this turn within the agentic loop, typically a stringified turn number + * Identifier of the HydraFusion turn containing the phase. */ - turnId: string; + fusionId: string; + /** + * Concrete model that executed the phase. + */ + model: string; + /** + * Stable identifier for the completed phase. + */ + phaseId: string; + phaseKind: FusionPhaseKind; + /** + * Exact provider-normalized message used to reconstruct canonical model history. + * + * @internal + */ + projectionMessage?: JsonValue; + /** + * Projection action for the exact internal message. + * + * @internal + */ + projectionMode?: FusionProjectionMode; + /** + * Semantic role assigned to the completed phase. + */ + role: string; + /** + * Terminal request held outside canonical state until selected by the final commit. + * + * @internal + */ + stagedTerminal?: FusionStagedTerminal; + status: FusionPhaseStatus; + usage: FusionPhaseUsage; + /** + * Structured judge or critic verdict, when the phase produces one. + */ + verdict: string | null; } /** - * Session event "assistant.intent". Agent intent description for current activity or plan + * Internal durable terminal request staged by a HydraFusion phase until an idempotent final commit selects it. */ -export interface AssistantIntentEvent { +/** @experimental */ +/** @internal */ +export interface FusionStagedTerminal { + arguments: string; + assistantMessage: JsonValue; + phaseId: string; + toolCallId: string; + toolName: string; +} +/** + * Aggregate concrete-model usage for one HydraFusion phase. + */ +/** @experimental */ +export interface FusionPhaseUsage { + /** + * Total cached input tokens reported for the phase. + */ + cachedTokens: number; + /** + * Total tokens written to prompt cache during the phase. + */ + cacheWriteTokens?: number; + /** + * Total input tokens consumed by the phase. + */ + inputTokens: number; + /** + * Total output tokens produced by the phase. + */ + outputTokens: number; + /** + * Number of concrete model requests made by the phase. + */ + requestCount: number; + /** + * Total normalized AI-unit cost reported for the phase, in nano-AIU. + */ + totalNanoAiu: number; +} +/** + * Session event "assistant.fusion_phase_failed". Experimental durable typed HydraFusion phase failure and degradation transition. + */ +/** @experimental */ +export interface AssistantFusionPhaseFailedEvent { /** * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. */ agentId?: string; - data: AssistantIntentData; + data: FusionPhaseFailedData; /** - * Always true for events that are transient and not persisted to the session event log on disk. + * When true, the event is transient and not persisted to the session event log on disk */ - ephemeral: true; + ephemeral?: boolean; /** * Unique event identifier (UUID v4), generated when the event is emitted */ @@ -3539,18 +4211,51 @@ export interface AssistantIntentEvent { */ timestamp: string; /** - * Type discriminator. Always "assistant.intent". + * Type discriminator. Always "assistant.fusion_phase_failed". */ - type: "assistant.intent"; + type: "assistant.fusion_phase_failed"; } /** - * Agent intent description for current activity or plan + * Experimental durable typed HydraFusion phase failure and degradation transition. */ -export interface AssistantIntentData { +/** @experimental */ +export interface FusionPhaseFailedData { + conversationScope: FusionConversationScope; /** - * Short description of what the agent is currently doing or planning to do + * Identifier of the fallback phase used to continue the turn after degradation. */ - intent: string; + degradedToPhaseId?: string; + /** + * Elapsed execution time before the phase failed, in milliseconds. + */ + durationMs: number; + /** + * Provider or execution error detail, when available. + */ + errorMessage?: string; + /** + * Identifier of the HydraFusion turn containing the phase. + */ + fusionId: string; + /** + * Concrete model that attempted the phase. + */ + model: string; + /** + * Stable identifier for the failed phase. + */ + phaseId: string; + phaseKind: FusionPhaseKind; + /** + * Stable machine-readable reason for the phase failure. + */ + reason: string; + /** + * Semantic role assigned to the failed phase. + */ + role: string; + status: FusionPhaseStatus; + usage: FusionPhaseUsage; } /** * Session event "assistant.server_tool_progress". Live progress signal for a provider-hosted server tool (e.g. hosted web search) while it runs, before the finalized serverTools envelope lands on the terminal assistant.message @@ -3840,6 +4545,12 @@ export interface AssistantMessageData { * Encrypted reasoning content from OpenAI models. Session-bound and stripped on resume. */ encryptedContent?: string; + /** + * Experimental HydraFusion source attribution for this ordinary authoritative assistant message. + * + * @experimental + */ + fusion?: FusionAttribution; /** * CAPI interaction ID for correlating this message with upstream telemetry */ @@ -4028,6 +4739,56 @@ export interface CitationLocationBlock { */ type: "block"; } +/** + * Experimental attribution linking an ordinary event to the HydraFusion turn, phase, and concrete source that produced it. + */ +/** @experimental */ +export interface FusionAttribution { + /** + * Idempotency identifier for the authoritative commit, when the event belongs to the selected output. + */ + commitId?: string; + /** + * Conversation scope in which the concrete phase executed. + */ + conversationScope?: string; + /** + * Stable identifier for the HydraFusion turn that produced the event. + */ + fusionId: string; + /** + * HydraFusion orchestration pattern selected for the turn. + */ + pattern: string; + /** + * Identifier of the concrete phase that produced the event. + */ + phaseId?: string; + /** + * Kind of concrete phase that produced the event. + */ + phaseKind?: string; + /** + * HydraFusion routing policy used for the turn. + */ + policy: string; + /** + * Semantic role assigned to the concrete phase. + */ + role?: string; + /** + * Concrete model that produced the attributed event. + */ + sourceModel?: string; + /** + * Phase whose output supplied the authoritative content, when different from the executing phase. + */ + sourcePhaseId?: string; + /** + * Synthetic HydraFusion model selected for the session. + */ + syntheticModel: string; +} /** * Neutral provider-tagged reasoning content blocks preserved verbatim for round-tripping */ @@ -4375,6 +5136,12 @@ export interface AssistantUsageData { * @internal */ frontierSource?: string; + /** + * Experimental HydraFusion attribution for this concrete model call's usage. + * + * @experimental + */ + fusion?: FusionAttribution; /** * What initiated this API call (e.g., "sub-agent", "mcp-sampling"); absent for user-initiated calls */ @@ -4648,6 +5415,12 @@ export interface ModelCallFailureData { */ errorType?: string; failureKind?: ModelCallFailureKind; + /** + * Experimental HydraFusion attribution for this failed concrete model call. + * + * @experimental + */ + fusion?: FusionAttribution; /** * What initiated this API call (e.g., "sub-agent", "mcp-sampling"); absent for user-initiated calls */ @@ -4922,6 +5695,12 @@ export interface ToolExecutionStartData { * When true, the tool output should be displayed expanded (verbatim) in the CLI timeline */ displayVerbatim?: boolean; + /** + * Experimental HydraFusion attribution for this tool execution. + * + * @experimental + */ + fusion?: FusionAttribution; /** * Name of the MCP server hosting this tool, when the tool is an MCP tool */ @@ -5131,6 +5910,12 @@ export interface ToolExecutionCompleteEvent { */ export interface ToolExecutionCompleteData { error?: ToolExecutionCompleteError; + /** + * Experimental HydraFusion attribution for this tool completion. + * + * @experimental + */ + fusion?: FusionAttribution; /** * CAPI interaction ID for correlating this tool execution with upstream telemetry */ @@ -5388,6 +6173,10 @@ export interface ToolExecutionCompleteContentShellExit { * Exit code from the completed shell command */ exitCode: number; + /** + * Path reported in the shell session's filesystem namespace when shell output exceeded the configured large-output threshold. + */ + outputFilePath?: string; /** * Output associated with this shell command, if available. May be partial, truncated, or a preview; not guaranteed to be full output. */ @@ -5821,6 +6610,14 @@ export interface SubagentStartedData { * Internal name of the sub-agent */ agentName: string; + /** + * Type of the sub-agent selected at spawn time. + */ + agentType?: string; + /** + * Whether the sub-agent runs synchronously or in the background. + */ + executionMode?: string; /** * Root id of the factory run that spawned this sub-agent, when it was spawned by one. */ @@ -5829,11 +6626,70 @@ export interface SubagentStartedData { * Model the sub-agent will run with, when known at start. */ model?: string; + /** + * Task-registry ID of the spawning sub-agent. Absent when the root session spawned this child. + */ + parentId?: string; + /** + * Whether this sub-agent can be resumed. Currently always false. + */ + resumable?: boolean; /** * Tool call ID of the parent tool invocation that spawned this sub-agent */ toolCallId: string; } +/** + * Session event "subagent.configured". Resolved runtime configuration for a configured sub-agent + */ +export interface SubagentConfiguredEvent { + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + data: SubagentConfiguredData; + /** + * When true, the event is transient and not persisted to the session event log on disk + */ + ephemeral?: boolean; + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + /** + * Type discriminator. Always "subagent.configured". + */ + type: "subagent.configured"; +} +/** + * Resolved runtime configuration for a configured sub-agent + */ +export interface SubagentConfiguredData { + /** + * Resolved context tier, when configured for the model + */ + contextTier?: string; + /** + * Resolved model the sub-agent will run with + */ + model: string; + /** + * Whether the sub-agent accepts follow-up turns + */ + multiTurn: boolean; + /** + * Resolved reasoning effort, when configured for the model + */ + reasoningEffort?: string; +} /** * Session event "subagent.completed". Sub-agent completion details for successful execution */ @@ -6135,6 +6991,10 @@ export interface HookStartData { * Input data passed to the hook */ input?: JsonValue; + /** + * Tool call ID of the parent tool invocation when this event originates from a sub-agent + */ + parentToolCallId?: string; } /** * Session event "hook.end". Hook invocation completion details including output, success status, and error information @@ -6183,6 +7043,10 @@ export interface HookEndData { * Output data produced by the hook */ output?: JsonValue; + /** + * Tool call ID of the parent tool invocation when this event originates from a sub-agent + */ + parentToolCallId?: string; /** * Whether the hook completed successfully */ diff --git a/nodejs/test/e2e/disabled_mcp_servers.e2e.test.ts b/nodejs/test/e2e/disabled_mcp_servers.e2e.test.ts index ce1a504e8f..611898f11d 100644 --- a/nodejs/test/e2e/disabled_mcp_servers.e2e.test.ts +++ b/nodejs/test/e2e/disabled_mcp_servers.e2e.test.ts @@ -314,7 +314,7 @@ describe("disabled MCP servers", async () => { } async function drainPostCreateRpc(session: CopilotSession): Promise { - // Drain a non-MCP post-create RPC without initializing MCP before the first model turn. + // Drain a non-MCP post-create RPC so session initialization settles before assertions. await session.rpc.metadata.snapshot(); } @@ -424,13 +424,13 @@ describe("disabled MCP servers", async () => { githubMcpToolConfig: { enableAllTools: true }, }); await drainPostCreateRpc(enabledSession); + await waitForMcpStatus(enabledSession, "github-mcp-server", "connected"); const requestsBeforeFirstMessage = await mcpRequestCount(); - expect(requestsBeforeFirstMessage).toBe(0); + expect(requestsBeforeFirstMessage).toBeGreaterThan(0); expectSyntheticResponse( await enabledSession.sendAndWait({ prompt: MCP_TRIGGER_PROMPT }) ); await waitForMcpRequestCount(requestsBeforeFirstMessage + 1); - await waitForMcpStatus(enabledSession, "github-mcp-server", "connected"); } ); diff --git a/nodejs/test/e2e/rewind.e2e.test.ts b/nodejs/test/e2e/rewind.e2e.test.ts index 2d32ef1f6c..49c2b3b8f0 100644 --- a/nodejs/test/e2e/rewind.e2e.test.ts +++ b/nodejs/test/e2e/rewind.e2e.test.ts @@ -24,62 +24,66 @@ function expectSamePath(actual: string, expected: string): void { describe("Rewind", async () => { const { copilotClient: client, workDir } = await createSdkTestContext(); - it("should restore tracked file and conversation", async () => { - const filePath = join(workDir, FILE_NAME); - const session = await client.createSession({ - model: "claude-sonnet-4.5", - enableFileChangeTracking: true, - onPermissionRequest: approveAll, - }); - - try { - const response = await session.sendAndWait({ - prompt: `Use the create tool to create ${FILE_NAME} containing exactly ${FILE_CONTENT}. After the tool succeeds, reply with exactly SDK_REWIND_DONE.`, + // TODO(cli-1.0.81): Re-enable when Windows file-change tracking records built-in create tool writes. + it.skipIf(process.platform === "win32")( + "should restore tracked file and conversation", + async () => { + const filePath = join(workDir, FILE_NAME); + const session = await client.createSession({ + model: "claude-sonnet-4.5", + enableFileChangeTracking: true, + onPermissionRequest: approveAll, }); - expect(response?.data.content).toBe("SDK_REWIND_DONE"); - expect(existsSync(filePath)).toBe(true); - expect(readFileSync(filePath, "utf8")).toBe(FILE_CONTENT); + try { + const response = await session.sendAndWait({ + prompt: `Use the create tool to create ${FILE_NAME} containing exactly ${FILE_CONTENT}. After the tool succeeds, reply with exactly SDK_REWIND_DONE.`, + }); - let rewindPoints = await session.rpc.history.listRewindPoints(); - const deadline = Date.now() + 10_000; - while ( - Date.now() < deadline && - (rewindPoints.unavailableReason !== undefined || - !rewindPoints.points[0]?.canRestoreFiles) - ) { - await new Promise((resolveDelay) => setTimeout(resolveDelay, 100)); - rewindPoints = await session.rpc.history.listRewindPoints(); - } + expect(response?.data.content).toBe("SDK_REWIND_DONE"); + expect(existsSync(filePath)).toBe(true); + expect(readFileSync(filePath, "utf8")).toBe(FILE_CONTENT); - expect(rewindPoints.unavailableReason).toBeUndefined(); - expect(rewindPoints.fileChangeTrackingEnabled).toBe(true); - expect(rewindPoints.points).toHaveLength(1); - const rewindPoint = rewindPoints.points[0]; - expect(rewindPoint.canRestoreFiles).toBe(true); - expect(rewindPoint.fileCount).toBe(1); + let rewindPoints = await session.rpc.history.listRewindPoints(); + const deadline = Date.now() + 30_000; + while ( + Date.now() < deadline && + (rewindPoints.unavailableReason !== undefined || + !rewindPoints.points[0]?.canRestoreFiles) + ) { + await new Promise((resolveDelay) => setTimeout(resolveDelay, 100)); + rewindPoints = await session.rpc.history.listRewindPoints(); + } - const preview = await session.rpc.history.previewRewind({ - eventId: rewindPoint.eventId, - }); - expect(preview.available).toBe(true); - expect(preview.files).toHaveLength(1); - expectSamePath(preview.files[0].path, filePath); + expect(rewindPoints.unavailableReason).toBeUndefined(); + expect(rewindPoints.fileChangeTrackingEnabled).toBe(true); + expect(rewindPoints.points).toHaveLength(1); + const rewindPoint = rewindPoints.points[0]; + expect(rewindPoint.canRestoreFiles).toBe(true); + expect(rewindPoint.fileCount).toBe(1); - const rewind = await session.rpc.history.rewind({ - eventId: rewindPoint.eventId, - mode: "conversation-and-files", - }); - expect(rewind.outcome).toBe("success"); - expect(rewind.eventsRemoved).toBeGreaterThan(0); - expect(rewind.restoredFiles).toHaveLength(1); - expectSamePath(rewind.restoredFiles[0], filePath); - expect(existsSync(filePath)).toBe(false); + const preview = await session.rpc.history.previewRewind({ + eventId: rewindPoint.eventId, + }); + expect(preview.available).toBe(true); + expect(preview.files).toHaveLength(1); + expectSamePath(preview.files[0].path, filePath); + + const rewind = await session.rpc.history.rewind({ + eventId: rewindPoint.eventId, + mode: "conversation-and-files", + }); + expect(rewind.outcome).toBe("success"); + expect(rewind.eventsRemoved).toBeGreaterThan(0); + expect(rewind.restoredFiles).toHaveLength(1); + expectSamePath(rewind.restoredFiles[0], filePath); + expect(existsSync(filePath)).toBe(false); - const events = await session.getEvents(); - expect(events.some((event) => event.id === rewindPoint.eventId)).toBe(false); - } finally { - await session.disconnect(); + const events = await session.getEvents(); + expect(events.some((event) => event.id === rewindPoint.eventId)).toBe(false); + } finally { + await session.disconnect(); + } } - }); + ); }); diff --git a/python/copilot/generated/rpc.py b/python/copilot/generated/rpc.py index 242e4041ed..1f28ce3585 100644 --- a/python/copilot/generated/rpc.py +++ b/python/copilot/generated/rpc.py @@ -16715,6 +16715,10 @@ class ExternalToolTextResultForLlmContentShellExit: cwd: str | None = None """Working directory where the shell command was executed""" + output_file_path: str | None = None + """Path reported in the shell session's filesystem namespace when shell output exceeded the + configured large-output threshold. + """ output_preview: str | None = None """Output associated with this shell command, if available. May be partial, truncated, or a preview; not guaranteed to be full output. @@ -16728,9 +16732,10 @@ def from_dict(obj: Any) -> 'ExternalToolTextResultForLlmContentShellExit': exit_code = from_int(obj.get("exitCode")) shell_id = from_str(obj.get("shellId")) cwd = from_union([from_str, from_none], obj.get("cwd")) + output_file_path = from_union([from_str, from_none], obj.get("outputFilePath")) output_preview = from_union([from_str, from_none], obj.get("outputPreview")) output_truncated = from_union([from_bool, from_none], obj.get("outputTruncated")) - return ExternalToolTextResultForLlmContentShellExit(exit_code, shell_id, cwd, output_preview, output_truncated) + return ExternalToolTextResultForLlmContentShellExit(exit_code, shell_id, cwd, output_file_path, output_preview, output_truncated) def to_dict(self) -> dict: result: dict = {} @@ -16739,6 +16744,8 @@ def to_dict(self) -> dict: result["type"] = self.type if self.cwd is not None: result["cwd"] = from_union([from_str, from_none], self.cwd) + if self.output_file_path is not None: + result["outputFilePath"] = from_union([from_str, from_none], self.output_file_path) if self.output_preview is not None: result["outputPreview"] = from_union([from_str, from_none], self.output_preview) if self.output_truncated is not None: @@ -17118,18 +17125,30 @@ class FactoryResumeRequest: limits: FactoryRunLimits | None = None """Optional per-invocation resource ceiling overrides.""" + log_phase_names: bool | None = None + """Whether to emit factory phase names to the session transcript.""" + + notify_on_complete: bool | None = None + """Whether to notify the originating session when the factory completes.""" + @staticmethod def from_dict(obj: Any) -> 'FactoryResumeRequest': assert isinstance(obj, dict) run_id = from_str(obj.get("runId")) limits = from_union([FactoryRunLimits.from_dict, from_none], obj.get("limits")) - return FactoryResumeRequest(run_id, limits) + log_phase_names = from_union([from_bool, from_none], obj.get("logPhaseNames")) + notify_on_complete = from_union([from_bool, from_none], obj.get("notifyOnComplete")) + return FactoryResumeRequest(run_id, limits, log_phase_names, notify_on_complete) def to_dict(self) -> dict: result: dict = {} result["runId"] = from_str(self.run_id) if self.limits is not None: result["limits"] = from_union([lambda x: to_class(FactoryRunLimits, x), from_none], self.limits) + if self.log_phase_names is not None: + result["logPhaseNames"] = from_union([from_bool, from_none], self.log_phase_names) + if self.notify_on_complete is not None: + result["notifyOnComplete"] = from_union([from_bool, from_none], self.notify_on_complete) return result # Experimental: this type is part of an experimental API and may change or be removed. @@ -17142,6 +17161,12 @@ class RunOptions: limits: FactoryRunLimits | None = None """Per-invocation resource ceiling overrides.""" + log_phase_names: bool | None = None + """Whether to emit factory phase names to the session transcript.""" + + notify_on_complete: bool | None = None + """Whether to notify the originating session when the factory completes.""" + resume_from_run_id: str | None = None """Run identifier whose journal and progress should seed this resumed run.""" @@ -17149,17 +17174,55 @@ class RunOptions: def from_dict(obj: Any) -> 'RunOptions': assert isinstance(obj, dict) limits = from_union([FactoryRunLimits.from_dict, from_none], obj.get("limits")) + log_phase_names = from_union([from_bool, from_none], obj.get("logPhaseNames")) + notify_on_complete = from_union([from_bool, from_none], obj.get("notifyOnComplete")) resume_from_run_id = from_union([from_str, from_none], obj.get("resumeFromRunId")) - return RunOptions(limits, resume_from_run_id) + return RunOptions(limits, log_phase_names, notify_on_complete, resume_from_run_id) def to_dict(self) -> dict: result: dict = {} if self.limits is not None: result["limits"] = from_union([lambda x: to_class(FactoryRunLimits, x), from_none], self.limits) + if self.log_phase_names is not None: + result["logPhaseNames"] = from_union([from_bool, from_none], self.log_phase_names) + if self.notify_on_complete is not None: + result["notifyOnComplete"] = from_union([from_bool, from_none], self.notify_on_complete) if self.resume_from_run_id is not None: result["resumeFromRunId"] = from_union([from_str, from_none], self.resume_from_run_id) return result +# Experimental: this type is part of an experimental API and may change or be removed. +# Internal: this type is an internal SDK API and is not part of the public surface. +@dataclass +class _FactoryToolResumeRequest: + """Internal parameters for resuming a factory run from a tool.""" + + run_id: str + """Factory run identifier.""" + + limits: FactoryRunLimits | None = None + """Optional per-invocation resource ceiling overrides.""" + + tool_call_id: str | None = None + """Opaque identifier of the originating tool call.""" + + @staticmethod + def from_dict(obj: Any) -> '_FactoryToolResumeRequest': + assert isinstance(obj, dict) + run_id = from_str(obj.get("runId")) + limits = from_union([FactoryRunLimits.from_dict, from_none], obj.get("limits")) + tool_call_id = from_union([from_str, from_none], obj.get("toolCallId")) + return _FactoryToolResumeRequest(run_id, limits, tool_call_id) + + def to_dict(self) -> dict: + result: dict = {} + result["runId"] = from_str(self.run_id) + if self.limits is not None: + result["limits"] = from_union([lambda x: to_class(FactoryRunLimits, x), from_none], self.limits) + if self.tool_call_id is not None: + result["toolCallId"] = from_union([from_str, from_none], self.tool_call_id) + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class GitHubTokenAcquireRequest: @@ -23886,6 +23949,35 @@ def to_dict(self) -> dict: result["pid"] = from_union([from_int, from_none], self.pid) return result +# Experimental: this type is part of an experimental API and may change or be removed. +# Internal: this type is an internal SDK API and is not part of the public surface. +@dataclass +class _FactoryToolRunOptions: + """Options for an internal tool-originated factory invocation. + + Tool-originated factory invocation options. + """ + limits: FactoryRunLimits | None = None + """Per-invocation resource ceiling overrides.""" + + resume_from_run_id: str | None = None + """Run identifier whose journal and progress should seed this resumed run.""" + + @staticmethod + def from_dict(obj: Any) -> '_FactoryToolRunOptions': + assert isinstance(obj, dict) + limits = from_union([FactoryRunLimits.from_dict, from_none], obj.get("limits")) + resume_from_run_id = from_union([from_str, from_none], obj.get("resumeFromRunId")) + return _FactoryToolRunOptions(limits, resume_from_run_id) + + def to_dict(self) -> dict: + result: dict = {} + if self.limits is not None: + result["limits"] = from_union([lambda x: to_class(FactoryRunLimits, x), from_none], self.limits) + if self.resume_from_run_id is not None: + result["resumeFromRunId"] = from_union([from_str, from_none], self.resume_from_run_id) + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class MCPAppsCallToolRequest: @@ -28000,6 +28092,43 @@ def to_dict(self) -> dict: result["progress"] = from_union([lambda x: to_class(TaskProgress, x), from_none], self.progress) return result +# Experimental: this type is part of an experimental API and may change or be removed. +# Internal: this type is an internal SDK API and is not part of the public surface. +@dataclass +class _FactoryToolRunRequest: + """Internal parameters for invoking a registered factory from a tool.""" + + args: Any + """Factory input value.""" + + name: str + """Registered factory name.""" + + options: _FactoryToolRunOptions | None = None + """Tool-originated factory invocation options.""" + + tool_call_id: str | None = None + """Opaque identifier of the originating tool call.""" + + @staticmethod + def from_dict(obj: Any) -> '_FactoryToolRunRequest': + assert isinstance(obj, dict) + args = obj.get("args") + name = from_str(obj.get("name")) + options = from_union([_FactoryToolRunOptions.from_dict, from_none], obj.get("options")) + tool_call_id = from_union([from_str, from_none], obj.get("toolCallId")) + return _FactoryToolRunRequest(args, name, options, tool_call_id) + + def to_dict(self) -> dict: + result: dict = {} + result["args"] = self.args + result["name"] = from_str(self.name) + if self.options is not None: + result["options"] = from_union([lambda x: to_class(_FactoryToolRunOptions, x), from_none], self.options) + if self.tool_call_id is not None: + result["toolCallId"] = from_union([from_str, from_none], self.tool_call_id) + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class MCPSerializableServerConfig: @@ -28609,15 +28738,9 @@ class ToolsGetBuiltinDescriptorsRequest: include_author: bool | None = None """Whether tool descriptors should include authoring metadata.""" - no_view_line_numbers: bool | None = None - """Whether line numbers should be omitted from the view tool descriptor.""" - reduce_user_intervention: bool | None = None """Whether descriptors should favor fewer user-intervention prompts.""" - shell_async_only_enabled: bool | None = None - """Whether shell commands may only run asynchronously.""" - shell_config: ToolsShellDescriptorConfig | None = None """Shell-specific names and description lines for shell tools.""" @@ -28635,14 +28758,12 @@ def from_dict(obj: Any) -> 'ToolsGetBuiltinDescriptorsRequest': assert isinstance(obj, dict) background_task_notifications_enabled = from_union([from_bool, from_none], obj.get("backgroundTaskNotificationsEnabled")) include_author = from_union([from_bool, from_none], obj.get("includeAuthor")) - no_view_line_numbers = from_union([from_bool, from_none], obj.get("noViewLineNumbers")) reduce_user_intervention = from_union([from_bool, from_none], obj.get("reduceUserIntervention")) - shell_async_only_enabled = from_union([from_bool, from_none], obj.get("shellAsyncOnlyEnabled")) shell_config = from_union([ToolsShellDescriptorConfig.from_dict, from_none], obj.get("shellConfig")) shell_supports_power_shell7_syntax = from_union([from_bool, from_none], obj.get("shellSupportsPowerShell7Syntax")) shell_timeout_ms = from_union([from_float, from_none], obj.get("shellTimeoutMs")) skill_embedding_enabled = from_union([from_bool, from_none], obj.get("skillEmbeddingEnabled")) - return ToolsGetBuiltinDescriptorsRequest(background_task_notifications_enabled, include_author, no_view_line_numbers, reduce_user_intervention, shell_async_only_enabled, shell_config, shell_supports_power_shell7_syntax, shell_timeout_ms, skill_embedding_enabled) + return ToolsGetBuiltinDescriptorsRequest(background_task_notifications_enabled, include_author, reduce_user_intervention, shell_config, shell_supports_power_shell7_syntax, shell_timeout_ms, skill_embedding_enabled) def to_dict(self) -> dict: result: dict = {} @@ -28650,12 +28771,8 @@ def to_dict(self) -> dict: result["backgroundTaskNotificationsEnabled"] = from_union([from_bool, from_none], self.background_task_notifications_enabled) if self.include_author is not None: result["includeAuthor"] = from_union([from_bool, from_none], self.include_author) - if self.no_view_line_numbers is not None: - result["noViewLineNumbers"] = from_union([from_bool, from_none], self.no_view_line_numbers) if self.reduce_user_intervention is not None: result["reduceUserIntervention"] = from_union([from_bool, from_none], self.reduce_user_intervention) - if self.shell_async_only_enabled is not None: - result["shellAsyncOnlyEnabled"] = from_union([from_bool, from_none], self.shell_async_only_enabled) if self.shell_config is not None: result["shellConfig"] = from_union([lambda x: to_class(ToolsShellDescriptorConfig, x), from_none], self.shell_config) if self.shell_supports_power_shell7_syntax is not None: @@ -35191,6 +35308,9 @@ class RPC: factory_run_status: FactoryRunStatus factory_run_summary: FactoryRunSummary factory_run_terminal: FactoryRunTerminal + factory_tool_resume_request: _FactoryToolResumeRequest + factory_tool_run_options: _FactoryToolRunOptions + factory_tool_run_request: _FactoryToolRunRequest filter_mapping: dict[str, ContentFilterMode] | ContentFilterMode fleet_start_request: FleetStartRequest fleet_start_result: FleetStartResult @@ -36371,6 +36491,9 @@ def from_dict(obj: Any) -> 'RPC': factory_run_status = FactoryRunStatus(obj.get("FactoryRunStatus")) factory_run_summary = FactoryRunSummary.from_dict(obj.get("FactoryRunSummary")) factory_run_terminal = FactoryRunTerminal.from_dict(obj.get("FactoryRunTerminal")) + factory_tool_resume_request = _FactoryToolResumeRequest.from_dict(obj.get("FactoryToolResumeRequest")) + factory_tool_run_options = _FactoryToolRunOptions.from_dict(obj.get("FactoryToolRunOptions")) + factory_tool_run_request = _FactoryToolRunRequest.from_dict(obj.get("FactoryToolRunRequest")) filter_mapping = from_union([lambda x: from_dict(ContentFilterMode, x), ContentFilterMode], obj.get("FilterMapping")) fleet_start_request = FleetStartRequest.from_dict(obj.get("FleetStartRequest")) fleet_start_result = FleetStartResult.from_dict(obj.get("FleetStartResult")) @@ -37291,7 +37414,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, account_all_users, account_get_all_users_result, account_get_current_auth_result, account_get_quota_request, account_get_quota_result, account_login_request, account_login_result, account_logout_request, account_logout_result, account_quota_snapshot, adaptive_thinking_support, agent_discovery_path, agent_discovery_path_list, agent_discovery_path_scope, agent_get_current_result, agent_info, agent_info_source, agent_list, agent_list_request, agent_registry_live_target_entry, agent_registry_live_target_entry_attention_kind, agent_registry_live_target_entry_kind, agent_registry_live_target_entry_last_terminal_event, agent_registry_live_target_entry_status, agent_registry_log_capture, agent_registry_log_capture_open_error_reason, agent_registry_spawn_error, agent_registry_spawn_permission_mode, agent_registry_spawn_registry_timeout, agent_registry_spawn_request, agent_registry_spawn_result, agent_registry_spawn_spawned, agent_registry_spawn_validation_error, agent_registry_spawn_validation_error_field, agent_registry_spawn_validation_error_reason, agent_reload_result, agents_discover_request, agent_select_request, agent_select_result, agent_set_prompt_request, agents_get_discovery_paths_request, api_key_auth_info, auth_identity, auth_info, auth_info_type, auth_validation_error, auth_validation_errors, built_in_model_catalog, built_in_model_catalog_entry, builtin_tool_descriptor, builtin_tool_format, builtin_tool_format_type, builtin_tool_input_schema, builtin_tool_input_schema_type, builtin_tool_safe_for_telemetry, builtin_tool_safe_telemetry_fields, cancel_user_requested_shell_command_result, canvas_action, canvas_action_invoke_request, canvas_action_invoke_result, canvas_close_request, canvas_host_context, canvas_host_context_capabilities, canvas_json_schema, canvas_list, canvas_list_open_result, canvas_open_request, canvas_provider_close_request, canvas_provider_invoke_action_request, canvas_provider_open_request, canvas_provider_open_result, canvas_provider_register_request, canvas_provider_unregister_request, canvas_session_context, capi_session_options, card_digest, card_digest_algorithm, card_digest_value, catalog_ai_skill_candidate, catalog_ai_skill_candidate_provenance, catalog_authentication_required_error, catalog_authentication_required_reason, catalog_candidate, catalog_candidate_kind, catalog_candidate_source, catalog_candidate_source_embedded, catalog_candidate_source_url, catalog_capability, catalog_capability_id, catalog_client_contract, catalog_contract_violation_error, catalog_contract_violation_reason, catalog_handle_rejected_error, catalog_handle_rejection_reason, catalog_handle_type, catalog_invalid_request_error, catalog_invalid_request_field, catalog_malformed_card_error, catalog_malformed_card_reason, catalog_mcp_server_candidate, catalog_mcp_server_candidate_provenance, catalog_mcp_server_installability, catalog_media_type, catalog_negotiated_contract, catalog_negotiation_refused_error, catalog_negotiation_refused_reason, catalog_network_failure_error, catalog_network_failure_reason, catalog_not_installable_error, catalog_not_installable_reason, catalog_policy_rejected_error, catalog_search_request, catalog_search_result, catalog_search_succeeded, catalog_unavailable_error, catalog_unavailable_reason, catalog_unavailable_transport_error, catalog_unavailable_transport_reason, catalog_unsafe_retrieval_error, catalog_unsafe_retrieval_reason, catalog_unsupported_kind_error, command_list, commands_finalize_invocation_effect_request, commands_finalize_invocation_effect_result, commands_handle_pending_command_request, commands_handle_pending_command_result, commands_invocation_effect_outcome, commands_invocation_origin, commands_invoke_request, commands_list_request, commands_respond_to_queued_command_request, commands_respond_to_queued_command_result, completions_get_trigger_characters_result, completions_request_request, completions_request_result, configure_session_extensions_params, connect_client_info, connected_remote_session_metadata, connected_remote_session_metadata_kind, connected_remote_session_metadata_repository, connect_remote_session_params, connect_request, connect_result, content_exclusion_check_paths_request, content_exclusion_check_paths_result, content_exclusion_path_check, content_filter_mode, context_heaviest_message, copilot_api_token_auth_info, copilot_user_response, copilot_user_response_endpoints, copilot_user_response_quota_snapshots, copilot_user_response_quota_snapshots_chat, copilot_user_response_quota_snapshots_completions, copilot_user_response_quota_snapshots_premium_interactions, current_model, current_tool_metadata, debug_collect_logs_collected_entry, debug_collect_logs_destination, debug_collect_logs_entry, debug_collect_logs_entry_kind, debug_collect_logs_include, debug_collect_logs_redaction, debug_collect_logs_request, debug_collect_logs_result, debug_collect_logs_result_kind, debug_collect_logs_skipped_entry, debug_collect_logs_source, discovered_canvas, discovered_extension, discovered_extension_mode, discovered_extension_plugin, discovered_extensions, discovered_extensions_disable_request, discovered_extensions_enable_request, discovered_extension_source, discovered_mcp_server, discovered_mcp_server_type, enqueue_command_params, enqueue_command_result, env_auth_info, event_log_read_request, event_log_release_interest_result, event_log_tail_result, event_log_types, events_agent_scope, events_cursor_status, events_read_direction, events_read_result, execute_command_params, execute_command_result, extension, extension_context_push_input, extension_launch_profile, extension_launch_provider_resolve_request, extension_launch_provider_resolve_result, extension_list, extensions_disable_request, extensions_enable_request, extension_source, extension_status, external_tool_result, external_tool_text_result_for_llm, external_tool_text_result_for_llm_binary_results_for_llm, external_tool_text_result_for_llm_binary_results_for_llm_type, external_tool_text_result_for_llm_content, external_tool_text_result_for_llm_content_audio, external_tool_text_result_for_llm_content_image, external_tool_text_result_for_llm_content_resource, external_tool_text_result_for_llm_content_resource_details, external_tool_text_result_for_llm_content_resource_link, external_tool_text_result_for_llm_content_resource_link_icon, external_tool_text_result_for_llm_content_resource_link_icon_theme, external_tool_text_result_for_llm_content_shell_exit, external_tool_text_result_for_llm_content_terminal, external_tool_text_result_for_llm_content_text, factory_abort_request, factory_ack_result, factory_agent_options, factory_agent_request, factory_agent_result, factory_agent_summary, factory_cancel_request, factory_current_phase, factory_declared_limits, factory_durable_operation, factory_execute_request, factory_execute_result, factory_get_run_progress_request, factory_get_run_request, factory_journal_get_request, factory_journal_get_result, factory_journal_put_request, factory_list_runs_request, factory_list_runs_result, factory_log_line, factory_log_line_kind, factory_log_request, factory_phase_observation, factory_phase_status, factory_progress_line, factory_progress_page, factory_resume_request, factory_resume_result, factory_run_consumed, factory_run_detail, factory_run_failure, factory_run_failure_kind, factory_run_limits, factory_run_request, factory_run_result, factory_run_status, factory_run_summary, factory_run_terminal, filter_mapping, fleet_start_request, fleet_start_result, folder_trust_add_params, folder_trust_check_params, folder_trust_check_result, gh_cli_auth_info, git_hub_telemetry_client_info, git_hub_telemetry_event, git_hub_telemetry_notification, git_hub_token_acquire_reason, git_hub_token_acquire_request, git_hub_token_acquire_result, handle_pending_tool_call_request, handle_pending_tool_call_result, history_abort_manual_compaction_result, history_cancel_background_compaction_result, history_clear_context_request, history_clear_context_result, history_compact_context_window, history_compact_request, history_compact_result, history_file_restore_skip_reason, history_list_rewind_points_result, history_preview_rewind_request, history_preview_rewind_result, history_rewind_change_type, history_rewind_file_preview, history_rewind_mode, history_rewind_outcome, history_rewind_point, history_rewind_request, history_rewind_result, history_rewind_unavailable_reason, history_skipped_file_restore, history_summarize_for_handoff_result, history_truncate_request, history_truncate_result, hmac_auth_info, hook_invoke_request, hook_invoke_response, hook_type, installed_plugin, installed_plugin_info, installed_plugin_source, installed_plugin_source_git_hub, installed_plugin_source_local, installed_plugin_source_url, instruction_discovery_path, instruction_discovery_path_kind, instruction_discovery_path_list, instruction_discovery_path_location, instructions_discover_request, instructions_get_discovery_paths_request, instructions_get_sources_result, instruction_source, instruction_source_location, instruction_source_type, interrupt_main_turn_request, interrupt_main_turn_result, llm_inference_headers, llm_inference_http_request_chunk_request, llm_inference_http_request_chunk_result, llm_inference_http_request_start_request, llm_inference_http_request_start_result, llm_inference_http_request_start_transport, llm_inference_http_response_chunk_error, llm_inference_http_response_chunk_request, llm_inference_http_response_chunk_result, llm_inference_http_response_start_request, llm_inference_http_response_start_result, llm_inference_set_provider_result, local_session_metadata_value, log_request, log_result, lsp_initialize_request, managed_settings_read_result, marketplace_add_result, marketplace_browse_result, marketplace_info, marketplace_list_result, marketplace_plugin_info, marketplace_refresh_entry, marketplace_refresh_result, marketplace_remove_result, mcp_allowed_server, mcp_apps_call_tool_request, mcp_apps_diagnose_capability, mcp_apps_diagnose_request, mcp_apps_diagnose_result, mcp_apps_diagnose_server, mcp_apps_host_context, mcp_apps_host_context_details, mcp_apps_host_context_details_available_display_mode, mcp_apps_host_context_details_display_mode, mcp_apps_host_context_details_platform, mcp_apps_host_context_details_theme, mcp_apps_list_tools_request, mcp_apps_list_tools_result, mcp_apps_read_resource_request, mcp_apps_read_resource_result, mcp_apps_resource_content, mcp_apps_set_host_context_details, mcp_apps_set_host_context_details_available_display_mode, mcp_apps_set_host_context_details_display_mode, mcp_apps_set_host_context_details_platform, mcp_apps_set_host_context_details_theme, mcp_apps_set_host_context_request, mcp_cancel_sampling_execution_params, mcp_cancel_sampling_execution_result, mcp_config_add_request, mcp_config_disable_request, mcp_config_enable_request, mcp_config_list, mcp_config_remove_request, mcp_config_update_request, mcp_configure_git_hub_request, mcp_configure_git_hub_result, mcp_disable_request, mcp_discover_request, mcp_discover_result, mcp_elicitation_form_mode, mcp_enable_request, mcp_execute_sampling_params, mcp_execute_sampling_request, mcp_execute_sampling_result, mcp_failed_server, mcp_filtered_server, mcp_headers_handle_pending_headers_refresh_request, mcp_headers_handle_pending_headers_refresh_request_request, mcp_headers_handle_pending_headers_refresh_request_result, mcp_host_state, mcp_install_plan, mcp_is_server_running_request, mcp_is_server_running_result, mcp_list_tools_request, mcp_list_tools_result, mcp_oauth_authentication_state_changed_request, mcp_oauth_handle_pending_request, mcp_oauth_handle_pending_result, mcp_oauth_login_grant_type, mcp_oauth_login_request, mcp_oauth_login_result, mcp_oauth_pending_request_response, mcp_oauth_probe_needs_auth_reason, mcp_oauth_probe_request, mcp_oauth_probe_result, mcp_oauth_respond_request, mcp_oauth_respond_result, mcp_plan_configuration_change, mcp_plan_configuration_operation, mcp_plan_enum_value_type, mcp_plan_install_planned, mcp_plan_install_request, mcp_plan_install_result, mcp_plan_install_source, mcp_plan_install_source_candidate, mcp_plan_install_source_candidate_kind, mcp_plan_install_source_card, mcp_plan_install_source_card_kind, mcp_plan_package_install_method, mcp_plan_package_transport, mcp_plan_policy_decision, mcp_plan_policy_result, mcp_plan_policy_source, mcp_plan_provenance, mcp_plan_remote_install_method, mcp_plan_remote_transport, mcp_plan_required_value, mcp_plan_required_value_enum, mcp_plan_required_value_enum_kind, mcp_plan_required_value_scalar, mcp_plan_required_value_scalar_kind, mcp_plan_resource_identity, mcp_plan_scalar_value_type, mcp_plan_scope, mcp_plan_secret_placeholder, mcp_plan_secret_reference, mcp_plan_target, mcp_plan_transport_choice, mcp_plan_transport_choice_package, mcp_plan_transport_choice_remote, mcp_plan_value_category, mcp_register_external_client_request, mcp_reload_config, mcp_reload_with_config_request, mcp_remove_git_hub_result, mcp_resource, mcp_resource_annotations, mcp_resource_content, mcp_resource_icon, mcp_resources_list_request, mcp_resources_list_result, mcp_resources_list_templates_request, mcp_resources_list_templates_result, mcp_resources_read_request, mcp_resources_read_result, mcp_resource_template, mcp_restart_server_request, mcp_safe_for_telemetry, mcp_safe_for_telemetry_fields, mcp_sampling_execution_action, mcp_sampling_execution_result, mcp_serializable_server_config, mcp_server, mcp_server_auth_config, mcp_server_auth_config_redirect_port, mcp_server_card_embedded, mcp_server_card_embedded_kind, mcp_server_card_media_type, mcp_server_card_reference, mcp_server_card_url, mcp_server_card_url_kind, mcp_server_config, mcp_server_config_defer_tools, mcp_server_config_http, mcp_server_config_http_oauth_grant_type, mcp_server_config_http_type, mcp_server_config_memory, mcp_server_config_memory_type, mcp_server_config_stdio, mcp_server_config_stdio_type, mcp_server_failure_info, mcp_server_list, mcp_server_needs_auth_info, mcp_set_env_value_mode_details, mcp_set_env_value_mode_params, mcp_set_env_value_mode_result, mcp_start_server_request, mcp_start_servers_result, mcp_stop_server_request, mcp_task_metadata, mcp_tools, mcp_tool_ui, mcp_tool_ui_visibility, mcp_unregister_external_client_request, memory_configuration, metadata_context_attribution_result, metadata_context_heaviest_messages_request, metadata_context_heaviest_messages_result, metadata_context_info_request, metadata_context_info_result, metadata_is_processing_result, metadata_recompute_context_tokens_request, metadata_recompute_context_tokens_result, metadata_record_context_change_request, metadata_record_context_change_result, metadata_set_working_directory_request, metadata_set_working_directory_result, metadata_snapshot_current_mode, metadata_snapshot_remote_metadata, metadata_snapshot_remote_metadata_repository, metadata_snapshot_remote_metadata_task_type, model, model_apply_startup_overlay_request, model_billing, model_billing_promo, model_billing_token_prices, model_billing_token_prices_long_context, model_capabilities, model_capabilities_limits, model_capabilities_limits_vision, model_capabilities_override, model_capabilities_override_limits, model_capabilities_override_limits_vision, model_capabilities_override_supports, model_capabilities_supports, model_list, model_list_request, model_message, model_picker_category, model_picker_persistence_request, model_picker_price_category, model_picker_settings_context, model_policy, model_policy_state, model_set_reasoning_effort_request, model_set_reasoning_effort_result, models_list_request, model_switch_confirmation, model_switch_to_request, model_switch_to_result, model_warning_text, mode_set_request, mode_set_result, move_mcp_loading_to_background_result, named_provider_config, name_get_result, name_set_auto_request, name_set_auto_result, name_set_request, open_canvas_instance, options_update_additional_content_exclusion_policy, options_update_additional_content_exclusion_policy_rule, options_update_additional_content_exclusion_policy_rule_source, options_update_additional_content_exclusion_policy_scope, options_update_context_tier, options_update_env_value_mode, options_update_reasoning_summary, options_update_tool_filter_precedence, pending_permission_request, pending_permission_request_list, permission_decision, permission_decision_approved, permission_decision_approved_for_location, permission_decision_approved_for_session, permission_decision_approve_for_location, permission_decision_approve_for_location_approval, permission_decision_approve_for_location_approval_commands, permission_decision_approve_for_location_approval_custom_tool, permission_decision_approve_for_location_approval_extension_env_access, permission_decision_approve_for_location_approval_extension_management, permission_decision_approve_for_location_approval_extension_permission_access, permission_decision_approve_for_location_approval_factory, permission_decision_approve_for_location_approval_mcp, permission_decision_approve_for_location_approval_mcp_sampling, permission_decision_approve_for_location_approval_memory, permission_decision_approve_for_location_approval_read, permission_decision_approve_for_location_approval_write, permission_decision_approve_for_session, permission_decision_approve_for_session_approval, permission_decision_approve_for_session_approval_commands, permission_decision_approve_for_session_approval_custom_tool, permission_decision_approve_for_session_approval_extension_env_access, permission_decision_approve_for_session_approval_extension_management, permission_decision_approve_for_session_approval_extension_permission_access, permission_decision_approve_for_session_approval_factory, permission_decision_approve_for_session_approval_mcp, permission_decision_approve_for_session_approval_mcp_sampling, permission_decision_approve_for_session_approval_memory, permission_decision_approve_for_session_approval_read, permission_decision_approve_for_session_approval_write, permission_decision_approve_once, permission_decision_approve_permanently, permission_decision_cancelled, permission_decision_context, permission_decision_denied_by_content_exclusion_policy, permission_decision_denied_by_permission_request_hook, permission_decision_denied_by_rules, permission_decision_denied_interactively_by_user, permission_decision_denied_no_approval_rule_and_could_not_request_from_user, permission_decision_outcome, permission_decision_reject, permission_decision_request, permission_decision_source, permission_decision_surface, permission_decision_user_not_available, permission_location_add_tool_approval_params, permission_location_apply_params, permission_location_apply_result, permission_location_resolve_params, permission_location_resolve_result, permission_location_type, permission_mode_source, permission_paths_add_params, permission_paths_allowed_check_params, permission_paths_allowed_check_result, permission_paths_config, permission_paths_list, permission_paths_update_primary_params, permission_paths_workspace_check_params, permission_paths_workspace_check_result, permission_prompt_shown_notification, permission_request_result, permission_response_capability, permission_rules_set, permissions_configure_additional_content_exclusion_policy, permissions_configure_additional_content_exclusion_policy_rule, permissions_configure_additional_content_exclusion_policy_rule_source, permissions_configure_additional_content_exclusion_policy_scope, permissions_configure_params, permissions_configure_result, permissions_folder_trust_add_trusted_result, permissions_get_mode_request, permissions_get_mode_result, permissions_locations_add_tool_approval_details, permissions_locations_add_tool_approval_details_commands, permissions_locations_add_tool_approval_details_custom_tool, permissions_locations_add_tool_approval_details_extension_env_access, permissions_locations_add_tool_approval_details_extension_management, permissions_locations_add_tool_approval_details_extension_permission_access, permissions_locations_add_tool_approval_details_factory, permissions_locations_add_tool_approval_details_mcp, permissions_locations_add_tool_approval_details_mcp_sampling, permissions_locations_add_tool_approval_details_memory, permissions_locations_add_tool_approval_details_read, permissions_locations_add_tool_approval_details_write, permissions_locations_add_tool_approval_result, permissions_modify_rules_params, permissions_modify_rules_result, permissions_modify_rules_scope, permissions_notify_prompt_shown_result, permissions_paths_add_result, permissions_paths_list_request, permissions_paths_update_primary_result, permissions_pending_requests_request, permissions_reset_session_approvals_request, permissions_reset_session_approvals_result, permissions_set_approve_all_request, permissions_set_approve_all_result, permissions_set_approve_all_source, permissions_set_mode_request, permissions_set_mode_result, permissions_set_required_request, permissions_set_required_result, permissions_urls_set_unrestricted_mode_result, permission_urls_config, permission_urls_set_unrestricted_mode_params, ping_request, ping_result, plan_read_result, plan_read_sql_todos_result, plan_read_sql_todos_with_dependencies_result, plan_sql_todo_dependency, plan_sql_todos_row, plan_update_request, plugin, plugin_install_result, plugin_list, plugin_list_result, plugins_builtin_set_request, plugins_disable_request, plugins_enable_request, plugins_install_request, plugins_marketplaces_add_request, plugins_marketplaces_browse_request, plugins_marketplaces_refresh_request, plugins_marketplaces_remove_request, plugins_reload_request, plugins_uninstall_request, plugins_update_request, plugin_update_all_entry, plugin_update_all_result, plugin_update_result, protocol_external_tool_defer, protocol_external_tool_definition, provider_add_request, provider_add_result, provider_config, provider_config_azure, provider_config_transport, provider_config_type, provider_config_wire_api, provider_endpoint, provider_endpoint_transport, provider_endpoint_type, provider_endpoint_wire_api, provider_get_endpoint_request, provider_model_config, provider_session_token, provider_token_acquire_request, provider_token_acquire_result, push_attachment, push_attachment_blob, push_attachment_directory, push_attachment_file, push_attachment_file_line_range, push_attachment_git_hub_actions_job, push_attachment_git_hub_commit, push_attachment_git_hub_file, push_attachment_git_hub_file_diff, push_attachment_git_hub_file_diff_side, push_attachment_git_hub_reference, push_attachment_git_hub_reference_type, push_attachment_git_hub_release, push_attachment_git_hub_repository, push_attachment_git_hub_snippet, push_attachment_git_hub_tree_comparison, push_attachment_git_hub_tree_comparison_side, push_attachment_git_hub_url, push_attachment_selection, push_attachment_selection_details, push_attachment_selection_details_end, push_attachment_selection_details_start, push_git_hub_repo_ref, queue_begin_deferred_idle_drain_request, queue_begin_deferred_idle_drain_result, queue_consume_system_notifications_request, queued_command_handled, queued_command_not_handled, queued_command_result, queue_defer_session_idle_request, queue_duplicate_at_request, queue_duplicate_at_result, queue_enqueue_resume_pending_result, queue_finish_deferred_idle_drain_request, queue_finish_deferred_idle_drain_result, queue_has_pending_result, queue_insert_at_request, queue_insert_at_result, queue_insert_message, queue_move_item_request, queue_move_item_result, queue_pending_items, queue_pending_items_kind, queue_pending_items_result, queue_remove_at_request, queue_remove_at_result, queue_remove_most_recent_result, queue_send_now_request, queue_send_now_result, queue_set_drain_paused_request, queue_snapshot_result, queue_update_text_request, queue_update_text_result, register_event_interest_params, register_event_interest_result, register_extension_tools_params, register_extension_tools_result, release_event_interest_params, remote_control_config, remote_control_config_existing_mc_session, remote_control_status, remote_control_status_active, remote_control_status_connecting, remote_control_status_error, remote_control_status_off, remote_control_status_result, remote_control_stop_result, remote_control_transfer_result, remote_enable_request, remote_enable_result, remote_notify_steerable_changed_request, remote_notify_steerable_changed_result, remote_session_connection_result, remote_session_host_status, remote_session_metadata_repository, remote_session_metadata_task_type, remote_session_metadata_value, remote_session_mode, remote_session_repository, run_options, sandbox_config, sandbox_config_auth, sandbox_config_source, sandbox_config_user_policy, sandbox_config_user_policy_experimental, sandbox_config_user_policy_experimental_seatbelt, sandbox_config_user_policy_filesystem, sandbox_config_user_policy_network, sandbox_config_user_policy_network_proxy, sandbox_config_user_policy_seatbelt, schedule_add_at_request, schedule_add_cron_request, schedule_add_request, schedule_add_result, schedule_add_self_paced_request, schedule_entry, schedule_has_self_paced_result, schedule_list, schedule_rearm_self_paced_request, schedule_stop_request, schedule_stop_result, secrets_add_filter_values_request, secrets_add_filter_values_result, send_agent_mode, send_attachments_to_message_params, send_message_item, send_messages_request, send_messages_result, send_mode, send_request, send_result, send_system_notification_request, server_agent_list, server_instruction_source_list, server_skill, server_skill_list, session_activity, session_agent_list_request, session_auth_login_request, session_auth_logout_user_request, session_auth_status, session_auth_switch_request, session_bulk_delete_result, session_cancel_all_background_agents_result, session_capability, session_commands_list_request, session_completion_item, session_context, session_context_host_type, session_enrich_metadata_result, session_fs_append_file_request, session_fs_error, session_fs_error_code, session_fs_exists_request, session_fs_exists_result, session_fs_mkdir_request, session_fs_readdir_request, session_fs_readdir_result, session_fs_readdir_with_types_entry, session_fs_readdir_with_types_entry_type, session_fs_readdir_with_types_request, session_fs_readdir_with_types_result, session_fs_read_file_request, session_fs_read_file_result, session_fs_rename_request, session_fs_rm_request, session_fs_set_provider_capabilities, session_fs_set_provider_conventions, session_fs_set_provider_request, session_fs_set_provider_result, session_fs_sqlite_exists_request, session_fs_sqlite_exists_result, session_fs_sqlite_query_request, session_fs_sqlite_query_result, session_fs_sqlite_query_type, session_fs_sqlite_transaction_error, session_fs_sqlite_transaction_error_class, session_fs_sqlite_transaction_request, session_fs_sqlite_transaction_result, session_fs_sqlite_transaction_statement, session_fs_stat_request, session_fs_stat_result, session_fs_write_file_request, session_git_hub_auth_get_all_auth_available_result, session_git_hub_auth_logout_result, session_git_hub_auth_logout_user_result, session_history_compact_request, session_installed_plugin, session_installed_plugin_source, session_installed_plugin_source_git_hub, session_installed_plugin_source_local, session_installed_plugin_source_url, session_limit_prediction_baseline_data, session_limit_prediction_client_type, session_limit_prediction_details, session_limit_prediction_predict_request, session_limit_prediction_request, session_limit_prediction_result, session_limit_prediction_source, session_limit_prediction_tier, session_limit_prediction_tier_option, session_limit_prediction_unavailable_reason, session_list, session_list_entry, session_list_filter, session_load_deferred_repo_hooks_result, session_log_level, session_managed_permissions, session_managed_settings, session_mcp_apps_call_tool_result, session_metadata_snapshot, session_mode, session_model_list, session_model_list_request, session_model_price_category, session_open_options, session_open_options_additional_content_exclusion_policy, session_open_options_additional_content_exclusion_policy_rule, session_open_options_additional_content_exclusion_policy_rule_source, session_open_options_additional_content_exclusion_policy_scope, session_open_options_env_value_mode, session_open_options_reasoning_summary, session_open_params, session_open_result, session_plugins_reload_request, session_provider_get_endpoint_request, session_prune_result, sessions_bulk_delete_request, sessions_check_in_use_request, sessions_check_in_use_result, sessions_close_request, sessions_close_result, sessions_delete_request, sessions_enrich_metadata_request, session_set_credentials_params, session_set_credentials_result, session_settings_built_in_tool_availability_snapshot, session_settings_evaluate_predicate_request, session_settings_evaluate_predicate_result, session_settings_job_snapshot, session_settings_model_snapshot, session_settings_online_evaluation_snapshot, session_settings_predicate_name, session_settings_repo_snapshot, session_settings_snapshot, session_settings_validation_snapshot, sessions_find_by_prefix_request, sessions_find_by_prefix_result, sessions_find_by_task_id_request, sessions_find_by_task_id_result, sessions_fork_request, sessions_fork_result, sessions_get_board_entry_count_request, sessions_get_board_entry_count_result, sessions_get_event_file_path_request, sessions_get_event_file_path_result, sessions_get_last_for_context_request, sessions_get_last_for_context_result, sessions_get_metadata_request, sessions_get_metadata_result, sessions_get_persisted_remote_steerable_request, sessions_get_persisted_remote_steerable_result, session_sizes, sessions_list_non_empty_session_ids_request, sessions_list_non_empty_session_ids_result, sessions_list_request, sessions_load_deferred_repo_hooks_request, sessions_open_attach, sessions_open_cloud, sessions_open_create, sessions_open_handoff, sessions_open_handoff_task_type, sessions_open_progress, sessions_open_progress_status, sessions_open_progress_step, sessions_open_remote, sessions_open_resume, sessions_open_resume_last, sessions_open_status, session_source, sessions_prune_old_request, sessions_register_extension_tools_on_session_options, sessions_release_lock_request, sessions_release_lock_result, sessions_reload_plugin_hooks_request, sessions_reload_plugin_hooks_result, sessions_save_request, sessions_save_result, sessions_set_additional_plugins_request, sessions_set_additional_plugins_result, sessions_set_remote_control_steering_request, sessions_start_remote_control_request, sessions_stop_remote_control_request, sessions_transfer_remote_control_request, session_telemetry_engagement, session_update_options_params, session_update_options_result, session_visibility_status, session_working_directory_context, session_working_directory_context_host_type, settable_auth_info, settable_token_auth_info, shell_cancel_user_requested_request, shell_credentials, shell_exec_request, shell_exec_result, shell_execute_user_requested_request, shell_init_profile, shell_init_script, shell_init_script_shell, shell_kill_request, shell_kill_result, shell_kill_signal, shell_options, shutdown_request, skill, skill_discovery_path, skill_discovery_path_list, skill_discovery_scope, skill_list, skills_config_set_disabled_skills_request, skills_config_set_skill_disabled_request, skills_disable_request, skills_discover_request, skills_enable_request, skills_get_discovery_paths_request, skills_get_invoked_result, skills_invoked_skill, skills_load_diagnostics, slash_command_add_timeline_entry_result, slash_command_agent_prompt_result, slash_command_completed_result, slash_command_info, slash_command_input, slash_command_input_choice, slash_command_input_completion, slash_command_invocation_result, slash_command_kind, slash_command_model_picker_dialog, slash_command_select_subcommand_option, slash_command_select_subcommand_result, slash_command_set_model_result, slash_command_set_plan_model_result, slash_command_show_dialog_result, slash_command_text_result, slash_command_timeline_entry, subagent_settings_entry, subagent_settings_entry_context_tier, task_agent_info, task_agent_progress, task_complete_data, task_completion_decision, task_execution_mode, task_info, task_list, task_progress_line, tasks_cancel_request, tasks_cancel_result, tasks_get_current_promotable_result, tasks_get_progress_request, tasks_get_progress_result, task_shell_info, task_shell_info_attachment_mode, task_shell_progress, tasks_promote_current_to_background_result, tasks_promote_to_background_request, tasks_promote_to_background_result, tasks_refresh_result, tasks_remove_request, tasks_remove_result, tasks_send_message_request, tasks_send_message_result, tasks_start_agent_request, tasks_start_agent_result, task_status, tasks_wait_for_pending_result, telemetry_set_feature_overrides_request, token_auth_info, token_provider_auth_info, tool, tool_list, tool_result, tool_result_expanded, tool_result_new_message, tool_result_type, tools_execute_request, tools_get_builtin_descriptors_request, tools_get_builtin_descriptors_result, tools_get_current_metadata_result, tools_initialize_and_validate_result, tools_list_request, tools_set_request, tools_set_result, tools_shell_descriptor_config, tools_task_complete_event_data_request, tools_update_subagent_settings_result, ui_auto_mode_switch_response, ui_elicitation_array_any_of_field, ui_elicitation_array_any_of_field_items, ui_elicitation_array_any_of_field_items_any_of, ui_elicitation_array_enum_field, ui_elicitation_array_enum_field_items, ui_elicitation_field_value, ui_elicitation_request, ui_elicitation_response, ui_elicitation_response_action, ui_elicitation_response_content, ui_elicitation_result, ui_elicitation_schema, ui_elicitation_schema_property, ui_elicitation_schema_property_boolean, ui_elicitation_schema_property_number, ui_elicitation_schema_property_number_type, ui_elicitation_schema_property_string, ui_elicitation_schema_property_string_format, ui_elicitation_string_enum_field, ui_elicitation_string_one_of_field, ui_elicitation_string_one_of_field_one_of, ui_ephemeral_query_request, ui_ephemeral_query_result, ui_exit_plan_mode_action, ui_exit_plan_mode_response, ui_handle_pending_auto_mode_switch_request, ui_handle_pending_elicitation_request, ui_handle_pending_exit_plan_mode_request, ui_handle_pending_result, ui_handle_pending_sampling_request, ui_handle_pending_sampling_response, ui_handle_pending_session_limits_exhausted_request, ui_handle_pending_user_input_request, ui_register_direct_auto_mode_switch_handler_result, ui_session_limits_exhausted_response, ui_session_limits_exhausted_response_action, ui_unregister_direct_auto_mode_switch_handler_request, ui_unregister_direct_auto_mode_switch_handler_result, ui_user_input_response, update_subagent_settings_request, usage_get_metrics_result, usage_metrics_agent_metric, usage_metrics_code_changes, usage_metrics_model_metric, usage_metrics_model_metric_requests, usage_metrics_model_metric_token_detail, usage_metrics_model_metric_usage, usage_metrics_token_detail, user_auth_info, user_requested_shell_command_result, user_setting_metadata, user_settings_get_result, user_settings_set_request, user_settings_set_result, visibility_get_result, visibility_set_request, visibility_set_result, workspace_diff_file_change, workspace_diff_file_change_type, workspace_diff_mode, workspace_diff_result, workspaces_add_summary_request, workspaces_add_summary_result, workspaces_autopilot_objective_exists_result, workspaces_checkpoints, workspaces_create_file_request, workspaces_delete_autopilot_objective_result, workspaces_diff_request, workspaces_ensure_request, workspaces_get_workspace_result, workspaces_list_checkpoints_result, workspaces_list_files_result, workspaces_read_autopilot_objective_result, workspaces_read_checkpoint_request, workspaces_read_checkpoint_result, workspaces_read_file_request, workspaces_read_file_result, workspaces_save_large_paste_request, workspaces_save_large_paste_result, workspaces_truncate_summaries_request, workspace_summary_host_type, workspaces_update_metadata_request, workspaces_workspace_details_host_type, workspaces_write_autopilot_objective_request, workspaces_write_autopilot_objective_result, session_auth_info_result, session_context_attribution, session_context_info, subagent_settings, task_progress, workspace_summary) + return RPC(abort_request, abort_result, account_all_users, account_get_all_users_result, account_get_current_auth_result, account_get_quota_request, account_get_quota_result, account_login_request, account_login_result, account_logout_request, account_logout_result, account_quota_snapshot, adaptive_thinking_support, agent_discovery_path, agent_discovery_path_list, agent_discovery_path_scope, agent_get_current_result, agent_info, agent_info_source, agent_list, agent_list_request, agent_registry_live_target_entry, agent_registry_live_target_entry_attention_kind, agent_registry_live_target_entry_kind, agent_registry_live_target_entry_last_terminal_event, agent_registry_live_target_entry_status, agent_registry_log_capture, agent_registry_log_capture_open_error_reason, agent_registry_spawn_error, agent_registry_spawn_permission_mode, agent_registry_spawn_registry_timeout, agent_registry_spawn_request, agent_registry_spawn_result, agent_registry_spawn_spawned, agent_registry_spawn_validation_error, agent_registry_spawn_validation_error_field, agent_registry_spawn_validation_error_reason, agent_reload_result, agents_discover_request, agent_select_request, agent_select_result, agent_set_prompt_request, agents_get_discovery_paths_request, api_key_auth_info, auth_identity, auth_info, auth_info_type, auth_validation_error, auth_validation_errors, built_in_model_catalog, built_in_model_catalog_entry, builtin_tool_descriptor, builtin_tool_format, builtin_tool_format_type, builtin_tool_input_schema, builtin_tool_input_schema_type, builtin_tool_safe_for_telemetry, builtin_tool_safe_telemetry_fields, cancel_user_requested_shell_command_result, canvas_action, canvas_action_invoke_request, canvas_action_invoke_result, canvas_close_request, canvas_host_context, canvas_host_context_capabilities, canvas_json_schema, canvas_list, canvas_list_open_result, canvas_open_request, canvas_provider_close_request, canvas_provider_invoke_action_request, canvas_provider_open_request, canvas_provider_open_result, canvas_provider_register_request, canvas_provider_unregister_request, canvas_session_context, capi_session_options, card_digest, card_digest_algorithm, card_digest_value, catalog_ai_skill_candidate, catalog_ai_skill_candidate_provenance, catalog_authentication_required_error, catalog_authentication_required_reason, catalog_candidate, catalog_candidate_kind, catalog_candidate_source, catalog_candidate_source_embedded, catalog_candidate_source_url, catalog_capability, catalog_capability_id, catalog_client_contract, catalog_contract_violation_error, catalog_contract_violation_reason, catalog_handle_rejected_error, catalog_handle_rejection_reason, catalog_handle_type, catalog_invalid_request_error, catalog_invalid_request_field, catalog_malformed_card_error, catalog_malformed_card_reason, catalog_mcp_server_candidate, catalog_mcp_server_candidate_provenance, catalog_mcp_server_installability, catalog_media_type, catalog_negotiated_contract, catalog_negotiation_refused_error, catalog_negotiation_refused_reason, catalog_network_failure_error, catalog_network_failure_reason, catalog_not_installable_error, catalog_not_installable_reason, catalog_policy_rejected_error, catalog_search_request, catalog_search_result, catalog_search_succeeded, catalog_unavailable_error, catalog_unavailable_reason, catalog_unavailable_transport_error, catalog_unavailable_transport_reason, catalog_unsafe_retrieval_error, catalog_unsafe_retrieval_reason, catalog_unsupported_kind_error, command_list, commands_finalize_invocation_effect_request, commands_finalize_invocation_effect_result, commands_handle_pending_command_request, commands_handle_pending_command_result, commands_invocation_effect_outcome, commands_invocation_origin, commands_invoke_request, commands_list_request, commands_respond_to_queued_command_request, commands_respond_to_queued_command_result, completions_get_trigger_characters_result, completions_request_request, completions_request_result, configure_session_extensions_params, connect_client_info, connected_remote_session_metadata, connected_remote_session_metadata_kind, connected_remote_session_metadata_repository, connect_remote_session_params, connect_request, connect_result, content_exclusion_check_paths_request, content_exclusion_check_paths_result, content_exclusion_path_check, content_filter_mode, context_heaviest_message, copilot_api_token_auth_info, copilot_user_response, copilot_user_response_endpoints, copilot_user_response_quota_snapshots, copilot_user_response_quota_snapshots_chat, copilot_user_response_quota_snapshots_completions, copilot_user_response_quota_snapshots_premium_interactions, current_model, current_tool_metadata, debug_collect_logs_collected_entry, debug_collect_logs_destination, debug_collect_logs_entry, debug_collect_logs_entry_kind, debug_collect_logs_include, debug_collect_logs_redaction, debug_collect_logs_request, debug_collect_logs_result, debug_collect_logs_result_kind, debug_collect_logs_skipped_entry, debug_collect_logs_source, discovered_canvas, discovered_extension, discovered_extension_mode, discovered_extension_plugin, discovered_extensions, discovered_extensions_disable_request, discovered_extensions_enable_request, discovered_extension_source, discovered_mcp_server, discovered_mcp_server_type, enqueue_command_params, enqueue_command_result, env_auth_info, event_log_read_request, event_log_release_interest_result, event_log_tail_result, event_log_types, events_agent_scope, events_cursor_status, events_read_direction, events_read_result, execute_command_params, execute_command_result, extension, extension_context_push_input, extension_launch_profile, extension_launch_provider_resolve_request, extension_launch_provider_resolve_result, extension_list, extensions_disable_request, extensions_enable_request, extension_source, extension_status, external_tool_result, external_tool_text_result_for_llm, external_tool_text_result_for_llm_binary_results_for_llm, external_tool_text_result_for_llm_binary_results_for_llm_type, external_tool_text_result_for_llm_content, external_tool_text_result_for_llm_content_audio, external_tool_text_result_for_llm_content_image, external_tool_text_result_for_llm_content_resource, external_tool_text_result_for_llm_content_resource_details, external_tool_text_result_for_llm_content_resource_link, external_tool_text_result_for_llm_content_resource_link_icon, external_tool_text_result_for_llm_content_resource_link_icon_theme, external_tool_text_result_for_llm_content_shell_exit, external_tool_text_result_for_llm_content_terminal, external_tool_text_result_for_llm_content_text, factory_abort_request, factory_ack_result, factory_agent_options, factory_agent_request, factory_agent_result, factory_agent_summary, factory_cancel_request, factory_current_phase, factory_declared_limits, factory_durable_operation, factory_execute_request, factory_execute_result, factory_get_run_progress_request, factory_get_run_request, factory_journal_get_request, factory_journal_get_result, factory_journal_put_request, factory_list_runs_request, factory_list_runs_result, factory_log_line, factory_log_line_kind, factory_log_request, factory_phase_observation, factory_phase_status, factory_progress_line, factory_progress_page, factory_resume_request, factory_resume_result, factory_run_consumed, factory_run_detail, factory_run_failure, factory_run_failure_kind, factory_run_limits, factory_run_request, factory_run_result, factory_run_status, factory_run_summary, factory_run_terminal, factory_tool_resume_request, factory_tool_run_options, factory_tool_run_request, filter_mapping, fleet_start_request, fleet_start_result, folder_trust_add_params, folder_trust_check_params, folder_trust_check_result, gh_cli_auth_info, git_hub_telemetry_client_info, git_hub_telemetry_event, git_hub_telemetry_notification, git_hub_token_acquire_reason, git_hub_token_acquire_request, git_hub_token_acquire_result, handle_pending_tool_call_request, handle_pending_tool_call_result, history_abort_manual_compaction_result, history_cancel_background_compaction_result, history_clear_context_request, history_clear_context_result, history_compact_context_window, history_compact_request, history_compact_result, history_file_restore_skip_reason, history_list_rewind_points_result, history_preview_rewind_request, history_preview_rewind_result, history_rewind_change_type, history_rewind_file_preview, history_rewind_mode, history_rewind_outcome, history_rewind_point, history_rewind_request, history_rewind_result, history_rewind_unavailable_reason, history_skipped_file_restore, history_summarize_for_handoff_result, history_truncate_request, history_truncate_result, hmac_auth_info, hook_invoke_request, hook_invoke_response, hook_type, installed_plugin, installed_plugin_info, installed_plugin_source, installed_plugin_source_git_hub, installed_plugin_source_local, installed_plugin_source_url, instruction_discovery_path, instruction_discovery_path_kind, instruction_discovery_path_list, instruction_discovery_path_location, instructions_discover_request, instructions_get_discovery_paths_request, instructions_get_sources_result, instruction_source, instruction_source_location, instruction_source_type, interrupt_main_turn_request, interrupt_main_turn_result, llm_inference_headers, llm_inference_http_request_chunk_request, llm_inference_http_request_chunk_result, llm_inference_http_request_start_request, llm_inference_http_request_start_result, llm_inference_http_request_start_transport, llm_inference_http_response_chunk_error, llm_inference_http_response_chunk_request, llm_inference_http_response_chunk_result, llm_inference_http_response_start_request, llm_inference_http_response_start_result, llm_inference_set_provider_result, local_session_metadata_value, log_request, log_result, lsp_initialize_request, managed_settings_read_result, marketplace_add_result, marketplace_browse_result, marketplace_info, marketplace_list_result, marketplace_plugin_info, marketplace_refresh_entry, marketplace_refresh_result, marketplace_remove_result, mcp_allowed_server, mcp_apps_call_tool_request, mcp_apps_diagnose_capability, mcp_apps_diagnose_request, mcp_apps_diagnose_result, mcp_apps_diagnose_server, mcp_apps_host_context, mcp_apps_host_context_details, mcp_apps_host_context_details_available_display_mode, mcp_apps_host_context_details_display_mode, mcp_apps_host_context_details_platform, mcp_apps_host_context_details_theme, mcp_apps_list_tools_request, mcp_apps_list_tools_result, mcp_apps_read_resource_request, mcp_apps_read_resource_result, mcp_apps_resource_content, mcp_apps_set_host_context_details, mcp_apps_set_host_context_details_available_display_mode, mcp_apps_set_host_context_details_display_mode, mcp_apps_set_host_context_details_platform, mcp_apps_set_host_context_details_theme, mcp_apps_set_host_context_request, mcp_cancel_sampling_execution_params, mcp_cancel_sampling_execution_result, mcp_config_add_request, mcp_config_disable_request, mcp_config_enable_request, mcp_config_list, mcp_config_remove_request, mcp_config_update_request, mcp_configure_git_hub_request, mcp_configure_git_hub_result, mcp_disable_request, mcp_discover_request, mcp_discover_result, mcp_elicitation_form_mode, mcp_enable_request, mcp_execute_sampling_params, mcp_execute_sampling_request, mcp_execute_sampling_result, mcp_failed_server, mcp_filtered_server, mcp_headers_handle_pending_headers_refresh_request, mcp_headers_handle_pending_headers_refresh_request_request, mcp_headers_handle_pending_headers_refresh_request_result, mcp_host_state, mcp_install_plan, mcp_is_server_running_request, mcp_is_server_running_result, mcp_list_tools_request, mcp_list_tools_result, mcp_oauth_authentication_state_changed_request, mcp_oauth_handle_pending_request, mcp_oauth_handle_pending_result, mcp_oauth_login_grant_type, mcp_oauth_login_request, mcp_oauth_login_result, mcp_oauth_pending_request_response, mcp_oauth_probe_needs_auth_reason, mcp_oauth_probe_request, mcp_oauth_probe_result, mcp_oauth_respond_request, mcp_oauth_respond_result, mcp_plan_configuration_change, mcp_plan_configuration_operation, mcp_plan_enum_value_type, mcp_plan_install_planned, mcp_plan_install_request, mcp_plan_install_result, mcp_plan_install_source, mcp_plan_install_source_candidate, mcp_plan_install_source_candidate_kind, mcp_plan_install_source_card, mcp_plan_install_source_card_kind, mcp_plan_package_install_method, mcp_plan_package_transport, mcp_plan_policy_decision, mcp_plan_policy_result, mcp_plan_policy_source, mcp_plan_provenance, mcp_plan_remote_install_method, mcp_plan_remote_transport, mcp_plan_required_value, mcp_plan_required_value_enum, mcp_plan_required_value_enum_kind, mcp_plan_required_value_scalar, mcp_plan_required_value_scalar_kind, mcp_plan_resource_identity, mcp_plan_scalar_value_type, mcp_plan_scope, mcp_plan_secret_placeholder, mcp_plan_secret_reference, mcp_plan_target, mcp_plan_transport_choice, mcp_plan_transport_choice_package, mcp_plan_transport_choice_remote, mcp_plan_value_category, mcp_register_external_client_request, mcp_reload_config, mcp_reload_with_config_request, mcp_remove_git_hub_result, mcp_resource, mcp_resource_annotations, mcp_resource_content, mcp_resource_icon, mcp_resources_list_request, mcp_resources_list_result, mcp_resources_list_templates_request, mcp_resources_list_templates_result, mcp_resources_read_request, mcp_resources_read_result, mcp_resource_template, mcp_restart_server_request, mcp_safe_for_telemetry, mcp_safe_for_telemetry_fields, mcp_sampling_execution_action, mcp_sampling_execution_result, mcp_serializable_server_config, mcp_server, mcp_server_auth_config, mcp_server_auth_config_redirect_port, mcp_server_card_embedded, mcp_server_card_embedded_kind, mcp_server_card_media_type, mcp_server_card_reference, mcp_server_card_url, mcp_server_card_url_kind, mcp_server_config, mcp_server_config_defer_tools, mcp_server_config_http, mcp_server_config_http_oauth_grant_type, mcp_server_config_http_type, mcp_server_config_memory, mcp_server_config_memory_type, mcp_server_config_stdio, mcp_server_config_stdio_type, mcp_server_failure_info, mcp_server_list, mcp_server_needs_auth_info, mcp_set_env_value_mode_details, mcp_set_env_value_mode_params, mcp_set_env_value_mode_result, mcp_start_server_request, mcp_start_servers_result, mcp_stop_server_request, mcp_task_metadata, mcp_tools, mcp_tool_ui, mcp_tool_ui_visibility, mcp_unregister_external_client_request, memory_configuration, metadata_context_attribution_result, metadata_context_heaviest_messages_request, metadata_context_heaviest_messages_result, metadata_context_info_request, metadata_context_info_result, metadata_is_processing_result, metadata_recompute_context_tokens_request, metadata_recompute_context_tokens_result, metadata_record_context_change_request, metadata_record_context_change_result, metadata_set_working_directory_request, metadata_set_working_directory_result, metadata_snapshot_current_mode, metadata_snapshot_remote_metadata, metadata_snapshot_remote_metadata_repository, metadata_snapshot_remote_metadata_task_type, model, model_apply_startup_overlay_request, model_billing, model_billing_promo, model_billing_token_prices, model_billing_token_prices_long_context, model_capabilities, model_capabilities_limits, model_capabilities_limits_vision, model_capabilities_override, model_capabilities_override_limits, model_capabilities_override_limits_vision, model_capabilities_override_supports, model_capabilities_supports, model_list, model_list_request, model_message, model_picker_category, model_picker_persistence_request, model_picker_price_category, model_picker_settings_context, model_policy, model_policy_state, model_set_reasoning_effort_request, model_set_reasoning_effort_result, models_list_request, model_switch_confirmation, model_switch_to_request, model_switch_to_result, model_warning_text, mode_set_request, mode_set_result, move_mcp_loading_to_background_result, named_provider_config, name_get_result, name_set_auto_request, name_set_auto_result, name_set_request, open_canvas_instance, options_update_additional_content_exclusion_policy, options_update_additional_content_exclusion_policy_rule, options_update_additional_content_exclusion_policy_rule_source, options_update_additional_content_exclusion_policy_scope, options_update_context_tier, options_update_env_value_mode, options_update_reasoning_summary, options_update_tool_filter_precedence, pending_permission_request, pending_permission_request_list, permission_decision, permission_decision_approved, permission_decision_approved_for_location, permission_decision_approved_for_session, permission_decision_approve_for_location, permission_decision_approve_for_location_approval, permission_decision_approve_for_location_approval_commands, permission_decision_approve_for_location_approval_custom_tool, permission_decision_approve_for_location_approval_extension_env_access, permission_decision_approve_for_location_approval_extension_management, permission_decision_approve_for_location_approval_extension_permission_access, permission_decision_approve_for_location_approval_factory, permission_decision_approve_for_location_approval_mcp, permission_decision_approve_for_location_approval_mcp_sampling, permission_decision_approve_for_location_approval_memory, permission_decision_approve_for_location_approval_read, permission_decision_approve_for_location_approval_write, permission_decision_approve_for_session, permission_decision_approve_for_session_approval, permission_decision_approve_for_session_approval_commands, permission_decision_approve_for_session_approval_custom_tool, permission_decision_approve_for_session_approval_extension_env_access, permission_decision_approve_for_session_approval_extension_management, permission_decision_approve_for_session_approval_extension_permission_access, permission_decision_approve_for_session_approval_factory, permission_decision_approve_for_session_approval_mcp, permission_decision_approve_for_session_approval_mcp_sampling, permission_decision_approve_for_session_approval_memory, permission_decision_approve_for_session_approval_read, permission_decision_approve_for_session_approval_write, permission_decision_approve_once, permission_decision_approve_permanently, permission_decision_cancelled, permission_decision_context, permission_decision_denied_by_content_exclusion_policy, permission_decision_denied_by_permission_request_hook, permission_decision_denied_by_rules, permission_decision_denied_interactively_by_user, permission_decision_denied_no_approval_rule_and_could_not_request_from_user, permission_decision_outcome, permission_decision_reject, permission_decision_request, permission_decision_source, permission_decision_surface, permission_decision_user_not_available, permission_location_add_tool_approval_params, permission_location_apply_params, permission_location_apply_result, permission_location_resolve_params, permission_location_resolve_result, permission_location_type, permission_mode_source, permission_paths_add_params, permission_paths_allowed_check_params, permission_paths_allowed_check_result, permission_paths_config, permission_paths_list, permission_paths_update_primary_params, permission_paths_workspace_check_params, permission_paths_workspace_check_result, permission_prompt_shown_notification, permission_request_result, permission_response_capability, permission_rules_set, permissions_configure_additional_content_exclusion_policy, permissions_configure_additional_content_exclusion_policy_rule, permissions_configure_additional_content_exclusion_policy_rule_source, permissions_configure_additional_content_exclusion_policy_scope, permissions_configure_params, permissions_configure_result, permissions_folder_trust_add_trusted_result, permissions_get_mode_request, permissions_get_mode_result, permissions_locations_add_tool_approval_details, permissions_locations_add_tool_approval_details_commands, permissions_locations_add_tool_approval_details_custom_tool, permissions_locations_add_tool_approval_details_extension_env_access, permissions_locations_add_tool_approval_details_extension_management, permissions_locations_add_tool_approval_details_extension_permission_access, permissions_locations_add_tool_approval_details_factory, permissions_locations_add_tool_approval_details_mcp, permissions_locations_add_tool_approval_details_mcp_sampling, permissions_locations_add_tool_approval_details_memory, permissions_locations_add_tool_approval_details_read, permissions_locations_add_tool_approval_details_write, permissions_locations_add_tool_approval_result, permissions_modify_rules_params, permissions_modify_rules_result, permissions_modify_rules_scope, permissions_notify_prompt_shown_result, permissions_paths_add_result, permissions_paths_list_request, permissions_paths_update_primary_result, permissions_pending_requests_request, permissions_reset_session_approvals_request, permissions_reset_session_approvals_result, permissions_set_approve_all_request, permissions_set_approve_all_result, permissions_set_approve_all_source, permissions_set_mode_request, permissions_set_mode_result, permissions_set_required_request, permissions_set_required_result, permissions_urls_set_unrestricted_mode_result, permission_urls_config, permission_urls_set_unrestricted_mode_params, ping_request, ping_result, plan_read_result, plan_read_sql_todos_result, plan_read_sql_todos_with_dependencies_result, plan_sql_todo_dependency, plan_sql_todos_row, plan_update_request, plugin, plugin_install_result, plugin_list, plugin_list_result, plugins_builtin_set_request, plugins_disable_request, plugins_enable_request, plugins_install_request, plugins_marketplaces_add_request, plugins_marketplaces_browse_request, plugins_marketplaces_refresh_request, plugins_marketplaces_remove_request, plugins_reload_request, plugins_uninstall_request, plugins_update_request, plugin_update_all_entry, plugin_update_all_result, plugin_update_result, protocol_external_tool_defer, protocol_external_tool_definition, provider_add_request, provider_add_result, provider_config, provider_config_azure, provider_config_transport, provider_config_type, provider_config_wire_api, provider_endpoint, provider_endpoint_transport, provider_endpoint_type, provider_endpoint_wire_api, provider_get_endpoint_request, provider_model_config, provider_session_token, provider_token_acquire_request, provider_token_acquire_result, push_attachment, push_attachment_blob, push_attachment_directory, push_attachment_file, push_attachment_file_line_range, push_attachment_git_hub_actions_job, push_attachment_git_hub_commit, push_attachment_git_hub_file, push_attachment_git_hub_file_diff, push_attachment_git_hub_file_diff_side, push_attachment_git_hub_reference, push_attachment_git_hub_reference_type, push_attachment_git_hub_release, push_attachment_git_hub_repository, push_attachment_git_hub_snippet, push_attachment_git_hub_tree_comparison, push_attachment_git_hub_tree_comparison_side, push_attachment_git_hub_url, push_attachment_selection, push_attachment_selection_details, push_attachment_selection_details_end, push_attachment_selection_details_start, push_git_hub_repo_ref, queue_begin_deferred_idle_drain_request, queue_begin_deferred_idle_drain_result, queue_consume_system_notifications_request, queued_command_handled, queued_command_not_handled, queued_command_result, queue_defer_session_idle_request, queue_duplicate_at_request, queue_duplicate_at_result, queue_enqueue_resume_pending_result, queue_finish_deferred_idle_drain_request, queue_finish_deferred_idle_drain_result, queue_has_pending_result, queue_insert_at_request, queue_insert_at_result, queue_insert_message, queue_move_item_request, queue_move_item_result, queue_pending_items, queue_pending_items_kind, queue_pending_items_result, queue_remove_at_request, queue_remove_at_result, queue_remove_most_recent_result, queue_send_now_request, queue_send_now_result, queue_set_drain_paused_request, queue_snapshot_result, queue_update_text_request, queue_update_text_result, register_event_interest_params, register_event_interest_result, register_extension_tools_params, register_extension_tools_result, release_event_interest_params, remote_control_config, remote_control_config_existing_mc_session, remote_control_status, remote_control_status_active, remote_control_status_connecting, remote_control_status_error, remote_control_status_off, remote_control_status_result, remote_control_stop_result, remote_control_transfer_result, remote_enable_request, remote_enable_result, remote_notify_steerable_changed_request, remote_notify_steerable_changed_result, remote_session_connection_result, remote_session_host_status, remote_session_metadata_repository, remote_session_metadata_task_type, remote_session_metadata_value, remote_session_mode, remote_session_repository, run_options, sandbox_config, sandbox_config_auth, sandbox_config_source, sandbox_config_user_policy, sandbox_config_user_policy_experimental, sandbox_config_user_policy_experimental_seatbelt, sandbox_config_user_policy_filesystem, sandbox_config_user_policy_network, sandbox_config_user_policy_network_proxy, sandbox_config_user_policy_seatbelt, schedule_add_at_request, schedule_add_cron_request, schedule_add_request, schedule_add_result, schedule_add_self_paced_request, schedule_entry, schedule_has_self_paced_result, schedule_list, schedule_rearm_self_paced_request, schedule_stop_request, schedule_stop_result, secrets_add_filter_values_request, secrets_add_filter_values_result, send_agent_mode, send_attachments_to_message_params, send_message_item, send_messages_request, send_messages_result, send_mode, send_request, send_result, send_system_notification_request, server_agent_list, server_instruction_source_list, server_skill, server_skill_list, session_activity, session_agent_list_request, session_auth_login_request, session_auth_logout_user_request, session_auth_status, session_auth_switch_request, session_bulk_delete_result, session_cancel_all_background_agents_result, session_capability, session_commands_list_request, session_completion_item, session_context, session_context_host_type, session_enrich_metadata_result, session_fs_append_file_request, session_fs_error, session_fs_error_code, session_fs_exists_request, session_fs_exists_result, session_fs_mkdir_request, session_fs_readdir_request, session_fs_readdir_result, session_fs_readdir_with_types_entry, session_fs_readdir_with_types_entry_type, session_fs_readdir_with_types_request, session_fs_readdir_with_types_result, session_fs_read_file_request, session_fs_read_file_result, session_fs_rename_request, session_fs_rm_request, session_fs_set_provider_capabilities, session_fs_set_provider_conventions, session_fs_set_provider_request, session_fs_set_provider_result, session_fs_sqlite_exists_request, session_fs_sqlite_exists_result, session_fs_sqlite_query_request, session_fs_sqlite_query_result, session_fs_sqlite_query_type, session_fs_sqlite_transaction_error, session_fs_sqlite_transaction_error_class, session_fs_sqlite_transaction_request, session_fs_sqlite_transaction_result, session_fs_sqlite_transaction_statement, session_fs_stat_request, session_fs_stat_result, session_fs_write_file_request, session_git_hub_auth_get_all_auth_available_result, session_git_hub_auth_logout_result, session_git_hub_auth_logout_user_result, session_history_compact_request, session_installed_plugin, session_installed_plugin_source, session_installed_plugin_source_git_hub, session_installed_plugin_source_local, session_installed_plugin_source_url, session_limit_prediction_baseline_data, session_limit_prediction_client_type, session_limit_prediction_details, session_limit_prediction_predict_request, session_limit_prediction_request, session_limit_prediction_result, session_limit_prediction_source, session_limit_prediction_tier, session_limit_prediction_tier_option, session_limit_prediction_unavailable_reason, session_list, session_list_entry, session_list_filter, session_load_deferred_repo_hooks_result, session_log_level, session_managed_permissions, session_managed_settings, session_mcp_apps_call_tool_result, session_metadata_snapshot, session_mode, session_model_list, session_model_list_request, session_model_price_category, session_open_options, session_open_options_additional_content_exclusion_policy, session_open_options_additional_content_exclusion_policy_rule, session_open_options_additional_content_exclusion_policy_rule_source, session_open_options_additional_content_exclusion_policy_scope, session_open_options_env_value_mode, session_open_options_reasoning_summary, session_open_params, session_open_result, session_plugins_reload_request, session_provider_get_endpoint_request, session_prune_result, sessions_bulk_delete_request, sessions_check_in_use_request, sessions_check_in_use_result, sessions_close_request, sessions_close_result, sessions_delete_request, sessions_enrich_metadata_request, session_set_credentials_params, session_set_credentials_result, session_settings_built_in_tool_availability_snapshot, session_settings_evaluate_predicate_request, session_settings_evaluate_predicate_result, session_settings_job_snapshot, session_settings_model_snapshot, session_settings_online_evaluation_snapshot, session_settings_predicate_name, session_settings_repo_snapshot, session_settings_snapshot, session_settings_validation_snapshot, sessions_find_by_prefix_request, sessions_find_by_prefix_result, sessions_find_by_task_id_request, sessions_find_by_task_id_result, sessions_fork_request, sessions_fork_result, sessions_get_board_entry_count_request, sessions_get_board_entry_count_result, sessions_get_event_file_path_request, sessions_get_event_file_path_result, sessions_get_last_for_context_request, sessions_get_last_for_context_result, sessions_get_metadata_request, sessions_get_metadata_result, sessions_get_persisted_remote_steerable_request, sessions_get_persisted_remote_steerable_result, session_sizes, sessions_list_non_empty_session_ids_request, sessions_list_non_empty_session_ids_result, sessions_list_request, sessions_load_deferred_repo_hooks_request, sessions_open_attach, sessions_open_cloud, sessions_open_create, sessions_open_handoff, sessions_open_handoff_task_type, sessions_open_progress, sessions_open_progress_status, sessions_open_progress_step, sessions_open_remote, sessions_open_resume, sessions_open_resume_last, sessions_open_status, session_source, sessions_prune_old_request, sessions_register_extension_tools_on_session_options, sessions_release_lock_request, sessions_release_lock_result, sessions_reload_plugin_hooks_request, sessions_reload_plugin_hooks_result, sessions_save_request, sessions_save_result, sessions_set_additional_plugins_request, sessions_set_additional_plugins_result, sessions_set_remote_control_steering_request, sessions_start_remote_control_request, sessions_stop_remote_control_request, sessions_transfer_remote_control_request, session_telemetry_engagement, session_update_options_params, session_update_options_result, session_visibility_status, session_working_directory_context, session_working_directory_context_host_type, settable_auth_info, settable_token_auth_info, shell_cancel_user_requested_request, shell_credentials, shell_exec_request, shell_exec_result, shell_execute_user_requested_request, shell_init_profile, shell_init_script, shell_init_script_shell, shell_kill_request, shell_kill_result, shell_kill_signal, shell_options, shutdown_request, skill, skill_discovery_path, skill_discovery_path_list, skill_discovery_scope, skill_list, skills_config_set_disabled_skills_request, skills_config_set_skill_disabled_request, skills_disable_request, skills_discover_request, skills_enable_request, skills_get_discovery_paths_request, skills_get_invoked_result, skills_invoked_skill, skills_load_diagnostics, slash_command_add_timeline_entry_result, slash_command_agent_prompt_result, slash_command_completed_result, slash_command_info, slash_command_input, slash_command_input_choice, slash_command_input_completion, slash_command_invocation_result, slash_command_kind, slash_command_model_picker_dialog, slash_command_select_subcommand_option, slash_command_select_subcommand_result, slash_command_set_model_result, slash_command_set_plan_model_result, slash_command_show_dialog_result, slash_command_text_result, slash_command_timeline_entry, subagent_settings_entry, subagent_settings_entry_context_tier, task_agent_info, task_agent_progress, task_complete_data, task_completion_decision, task_execution_mode, task_info, task_list, task_progress_line, tasks_cancel_request, tasks_cancel_result, tasks_get_current_promotable_result, tasks_get_progress_request, tasks_get_progress_result, task_shell_info, task_shell_info_attachment_mode, task_shell_progress, tasks_promote_current_to_background_result, tasks_promote_to_background_request, tasks_promote_to_background_result, tasks_refresh_result, tasks_remove_request, tasks_remove_result, tasks_send_message_request, tasks_send_message_result, tasks_start_agent_request, tasks_start_agent_result, task_status, tasks_wait_for_pending_result, telemetry_set_feature_overrides_request, token_auth_info, token_provider_auth_info, tool, tool_list, tool_result, tool_result_expanded, tool_result_new_message, tool_result_type, tools_execute_request, tools_get_builtin_descriptors_request, tools_get_builtin_descriptors_result, tools_get_current_metadata_result, tools_initialize_and_validate_result, tools_list_request, tools_set_request, tools_set_result, tools_shell_descriptor_config, tools_task_complete_event_data_request, tools_update_subagent_settings_result, ui_auto_mode_switch_response, ui_elicitation_array_any_of_field, ui_elicitation_array_any_of_field_items, ui_elicitation_array_any_of_field_items_any_of, ui_elicitation_array_enum_field, ui_elicitation_array_enum_field_items, ui_elicitation_field_value, ui_elicitation_request, ui_elicitation_response, ui_elicitation_response_action, ui_elicitation_response_content, ui_elicitation_result, ui_elicitation_schema, ui_elicitation_schema_property, ui_elicitation_schema_property_boolean, ui_elicitation_schema_property_number, ui_elicitation_schema_property_number_type, ui_elicitation_schema_property_string, ui_elicitation_schema_property_string_format, ui_elicitation_string_enum_field, ui_elicitation_string_one_of_field, ui_elicitation_string_one_of_field_one_of, ui_ephemeral_query_request, ui_ephemeral_query_result, ui_exit_plan_mode_action, ui_exit_plan_mode_response, ui_handle_pending_auto_mode_switch_request, ui_handle_pending_elicitation_request, ui_handle_pending_exit_plan_mode_request, ui_handle_pending_result, ui_handle_pending_sampling_request, ui_handle_pending_sampling_response, ui_handle_pending_session_limits_exhausted_request, ui_handle_pending_user_input_request, ui_register_direct_auto_mode_switch_handler_result, ui_session_limits_exhausted_response, ui_session_limits_exhausted_response_action, ui_unregister_direct_auto_mode_switch_handler_request, ui_unregister_direct_auto_mode_switch_handler_result, ui_user_input_response, update_subagent_settings_request, usage_get_metrics_result, usage_metrics_agent_metric, usage_metrics_code_changes, usage_metrics_model_metric, usage_metrics_model_metric_requests, usage_metrics_model_metric_token_detail, usage_metrics_model_metric_usage, usage_metrics_token_detail, user_auth_info, user_requested_shell_command_result, user_setting_metadata, user_settings_get_result, user_settings_set_request, user_settings_set_result, visibility_get_result, visibility_set_request, visibility_set_result, workspace_diff_file_change, workspace_diff_file_change_type, workspace_diff_mode, workspace_diff_result, workspaces_add_summary_request, workspaces_add_summary_result, workspaces_autopilot_objective_exists_result, workspaces_checkpoints, workspaces_create_file_request, workspaces_delete_autopilot_objective_result, workspaces_diff_request, workspaces_ensure_request, workspaces_get_workspace_result, workspaces_list_checkpoints_result, workspaces_list_files_result, workspaces_read_autopilot_objective_result, workspaces_read_checkpoint_request, workspaces_read_checkpoint_result, workspaces_read_file_request, workspaces_read_file_result, workspaces_save_large_paste_request, workspaces_save_large_paste_result, workspaces_truncate_summaries_request, workspace_summary_host_type, workspaces_update_metadata_request, workspaces_workspace_details_host_type, workspaces_write_autopilot_objective_request, workspaces_write_autopilot_objective_result, session_auth_info_result, session_context_attribution, session_context_info, subagent_settings, task_progress, workspace_summary) def to_dict(self) -> dict: result: dict = {} @@ -37551,6 +37674,9 @@ def to_dict(self) -> dict: result["FactoryRunStatus"] = to_enum(FactoryRunStatus, self.factory_run_status) result["FactoryRunSummary"] = to_class(FactoryRunSummary, self.factory_run_summary) result["FactoryRunTerminal"] = to_class(FactoryRunTerminal, self.factory_run_terminal) + result["FactoryToolResumeRequest"] = to_class(_FactoryToolResumeRequest, self.factory_tool_resume_request) + result["FactoryToolRunOptions"] = to_class(_FactoryToolRunOptions, self.factory_tool_run_options) + result["FactoryToolRunRequest"] = to_class(_FactoryToolRunRequest, self.factory_tool_run_request) result["FilterMapping"] = from_union([lambda x: from_dict(lambda x: to_enum(ContentFilterMode, x), x), lambda x: to_enum(ContentFilterMode, x)], self.filter_mapping) result["FleetStartRequest"] = to_class(FleetStartRequest, self.fleet_start_request) result["FleetStartResult"] = to_class(FleetStartResult, self.fleet_start_result) @@ -41174,6 +41300,25 @@ def __init__(self, client: "JsonRpcClient", session_id: str): self.provider = _InternalCanvasProviderApi(client, session_id) +# Experimental: this API group is experimental and may change or be removed. +class _InternalFactoryApi: + def __init__(self, client: "JsonRpcClient", session_id: str): + self._client = client + self._session_id = session_id + + async def _run_from_tool(self, params: _FactoryToolRunRequest, *, timeout: float | None = None) -> FactoryRunResult: + "Internal tool-originated factory invocation.\n\nArgs:\n params: Internal parameters for invoking a registered factory from a tool.\n\nReturns:\n Complete current or terminal factory run envelope.\n\n:meta private:\n\nInternal SDK API; not part of the public surface." + params_dict: dict[str, Any] = {k: v for k, v in params.to_dict().items() if v is not None} + params_dict["sessionId"] = self._session_id + return FactoryRunResult.from_dict(await self._client.request("session.factory.runFromTool", params_dict, **_timeout_kwargs(timeout))) + + async def _resume_from_tool(self, params: _FactoryToolResumeRequest, *, timeout: float | None = None) -> FactoryResumeResult: + "Internal tool-originated factory resume.\n\nArgs:\n params: Internal parameters for resuming a factory run from a tool.\n\nReturns:\n Resolved persisted factory identity and resumed run envelope.\n\n:meta private:\n\nInternal SDK API; not part of the public surface." + params_dict: dict[str, Any] = {k: v for k, v in params.to_dict().items() if v is not None} + params_dict["sessionId"] = self._session_id + return FactoryResumeResult.from_dict(await self._client.request("session.factory.resumeFromTool", params_dict, **_timeout_kwargs(timeout))) + + # Experimental: this API group is experimental and may change or be removed. class _InternalModelApi: def __init__(self, client: "JsonRpcClient", session_id: str): @@ -41347,6 +41492,7 @@ def __init__(self, client: "JsonRpcClient", session_id: str): self._session_id = session_id self.git_hub_auth = _InternalGitHubAuthApi(client, session_id) self.canvas = _InternalCanvasApi(client, session_id) + self.factory = _InternalFactoryApi(client, session_id) self.model = _InternalModelApi(client, session_id) self.mcp = _InternalMcpApi(client, session_id) self.commands = _InternalCommandsApi(client, session_id) diff --git a/python/copilot/generated/session_events.py b/python/copilot/generated/session_events.py index d4af5f2735..e6c5084861 100644 --- a/python/copilot/generated/session_events.py +++ b/python/copilot/generated/session_events.py @@ -154,12 +154,26 @@ class SessionEventType(Enum): SESSION_COMPACTION_START = "session.compaction_start" SESSION_COMPACTION_COMPLETE = "session.compaction_complete" SESSION_TASK_COMPLETE = "session.task_complete" + # Experimental: this event is part of an experimental API and may change or be removed. + SESSION_FUSION_ROUTE_STARTED = "session.fusion_route_started" + # Experimental: this event is part of an experimental API and may change or be removed. + SESSION_FUSION_ROUTE_FAILED = "session.fusion_route_failed" + # Experimental: this event is part of an experimental API and may change or be removed. + SESSION_FUSION_RESOLVED = "session.fusion_resolved" + # Experimental: this event is part of an experimental API and may change or be removed. + SESSION_FUSION_COMPLETED = "session.fusion_completed" USER_MESSAGE = "user.message" PENDING_MESSAGES_MODIFIED = "pending_messages.modified" ASSISTANT_TURN_START = "assistant.turn_start" ASSISTANT_TURN_RETRY = "assistant.turn_retry" AGENT_INTERRUPTED = "agent.interrupted" ASSISTANT_INTENT = "assistant.intent" + # Experimental: this event is part of an experimental API and may change or be removed. + ASSISTANT_FUSION_PHASE_STARTED = "assistant.fusion_phase_started" + # Experimental: this event is part of an experimental API and may change or be removed. + ASSISTANT_FUSION_PHASE_COMPLETED = "assistant.fusion_phase_completed" + # Experimental: this event is part of an experimental API and may change or be removed. + ASSISTANT_FUSION_PHASE_FAILED = "assistant.fusion_phase_failed" ASSISTANT_SERVER_TOOL_PROGRESS = "assistant.server_tool_progress" ASSISTANT_REASONING = "assistant.reasoning" ASSISTANT_REASONING_DELTA = "assistant.reasoning_delta" @@ -185,6 +199,7 @@ class SessionEventType(Enum): SKILL_INVOKED = "skill.invoked" SANDBOX_DECISION = "sandbox.decision" SUBAGENT_STARTED = "subagent.started" + SUBAGENT_CONFIGURED = "subagent.configured" SUBAGENT_COMPLETED = "subagent.completed" SUBAGENT_FAILED = "subagent.failed" SUBAGENT_SELECTED = "subagent.selected" @@ -382,6 +397,194 @@ def to_dict(self) -> dict: return result +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class AssistantFusionPhaseCompletedData: + "Experimental durable HydraFusion phase output and lossless replay checkpoint." + content: str + conversation_scope: FusionConversationScope + duration_ms: float + fusion_id: str + model: str + phase_id: str + phase_kind: FusionPhaseKind + role: str + status: FusionPhaseStatus + usage: FusionPhaseUsage + verdict: str | None + # Internal: this field is an internal SDK API and is not part of the public surface. + _projection_message: Any = None + # Internal: this field is an internal SDK API and is not part of the public surface. + _projection_mode: _FusionProjectionMode | None = None + # Internal: this field is an internal SDK API and is not part of the public surface. + _staged_terminal: _FusionStagedTerminal | None = None + + @staticmethod + def from_dict(obj: Any) -> "AssistantFusionPhaseCompletedData": + assert isinstance(obj, dict) + content = from_str(obj.get("content")) + conversation_scope = parse_enum(FusionConversationScope, obj.get("conversationScope")) + duration_ms = from_float(obj.get("durationMs")) + fusion_id = from_str(obj.get("fusionId")) + model = from_str(obj.get("model")) + phase_id = from_str(obj.get("phaseId")) + phase_kind = parse_enum(FusionPhaseKind, obj.get("phaseKind")) + role = from_str(obj.get("role")) + status = parse_enum(FusionPhaseStatus, obj.get("status")) + usage = FusionPhaseUsage.from_dict(obj.get("usage")) + verdict = from_union([from_none, from_str], obj.get("verdict")) + _projection_message = obj.get("projectionMessage") + _projection_mode = from_union([from_none, lambda x: parse_enum(_FusionProjectionMode, x)], obj.get("projectionMode")) + _staged_terminal = from_union([from_none, _FusionStagedTerminal.from_dict], obj.get("stagedTerminal")) + return AssistantFusionPhaseCompletedData( + content=content, + conversation_scope=conversation_scope, + duration_ms=duration_ms, + fusion_id=fusion_id, + model=model, + phase_id=phase_id, + phase_kind=phase_kind, + role=role, + status=status, + usage=usage, + verdict=verdict, + _projection_message=_projection_message, + _projection_mode=_projection_mode, + _staged_terminal=_staged_terminal, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["content"] = from_str(self.content) + result["conversationScope"] = to_enum(FusionConversationScope, self.conversation_scope) + result["durationMs"] = to_float(self.duration_ms) + result["fusionId"] = from_str(self.fusion_id) + result["model"] = from_str(self.model) + result["phaseId"] = from_str(self.phase_id) + result["phaseKind"] = to_enum(FusionPhaseKind, self.phase_kind) + result["role"] = from_str(self.role) + result["status"] = to_enum(FusionPhaseStatus, self.status) + result["usage"] = to_class(FusionPhaseUsage, self.usage) + result["verdict"] = from_union([from_none, from_str], self.verdict) + if self._projection_message is not None: + result["projectionMessage"] = self._projection_message + if self._projection_mode is not None: + result["projectionMode"] = from_union([from_none, lambda x: to_enum(_FusionProjectionMode, x)], self._projection_mode) + if self._staged_terminal is not None: + result["stagedTerminal"] = from_union([from_none, lambda x: to_class(_FusionStagedTerminal, x)], self._staged_terminal) + return result + + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class AssistantFusionPhaseFailedData: + "Experimental durable typed HydraFusion phase failure and degradation transition." + conversation_scope: FusionConversationScope + duration_ms: float + fusion_id: str + model: str + phase_id: str + phase_kind: FusionPhaseKind + reason: str + role: str + status: FusionPhaseStatus + usage: FusionPhaseUsage + degraded_to_phase_id: str | None = None + error_message: str | None = None + + @staticmethod + def from_dict(obj: Any) -> "AssistantFusionPhaseFailedData": + assert isinstance(obj, dict) + conversation_scope = parse_enum(FusionConversationScope, obj.get("conversationScope")) + duration_ms = from_float(obj.get("durationMs")) + fusion_id = from_str(obj.get("fusionId")) + model = from_str(obj.get("model")) + phase_id = from_str(obj.get("phaseId")) + phase_kind = parse_enum(FusionPhaseKind, obj.get("phaseKind")) + reason = from_str(obj.get("reason")) + role = from_str(obj.get("role")) + status = parse_enum(FusionPhaseStatus, obj.get("status")) + usage = FusionPhaseUsage.from_dict(obj.get("usage")) + degraded_to_phase_id = from_union([from_none, from_str], obj.get("degradedToPhaseId")) + error_message = from_union([from_none, from_str], obj.get("errorMessage")) + return AssistantFusionPhaseFailedData( + conversation_scope=conversation_scope, + duration_ms=duration_ms, + fusion_id=fusion_id, + model=model, + phase_id=phase_id, + phase_kind=phase_kind, + reason=reason, + role=role, + status=status, + usage=usage, + degraded_to_phase_id=degraded_to_phase_id, + error_message=error_message, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["conversationScope"] = to_enum(FusionConversationScope, self.conversation_scope) + result["durationMs"] = to_float(self.duration_ms) + result["fusionId"] = from_str(self.fusion_id) + result["model"] = from_str(self.model) + result["phaseId"] = from_str(self.phase_id) + result["phaseKind"] = to_enum(FusionPhaseKind, self.phase_kind) + result["reason"] = from_str(self.reason) + result["role"] = from_str(self.role) + result["status"] = to_enum(FusionPhaseStatus, self.status) + result["usage"] = to_class(FusionPhaseUsage, self.usage) + if self.degraded_to_phase_id is not None: + result["degradedToPhaseId"] = from_union([from_none, from_str], self.degraded_to_phase_id) + if self.error_message is not None: + result["errorMessage"] = from_union([from_none, from_str], self.error_message) + return result + + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class AssistantFusionPhaseStartedData: + "Experimental transient HydraFusion phase/model/role signal." + conversation_scope: FusionConversationScope + fusion_id: str + model: str + pattern: FusionPattern + phase_id: str + phase_kind: FusionPhaseKind + role: str + + @staticmethod + def from_dict(obj: Any) -> "AssistantFusionPhaseStartedData": + assert isinstance(obj, dict) + conversation_scope = parse_enum(FusionConversationScope, obj.get("conversationScope")) + fusion_id = from_str(obj.get("fusionId")) + model = from_str(obj.get("model")) + pattern = parse_enum(FusionPattern, obj.get("pattern")) + phase_id = from_str(obj.get("phaseId")) + phase_kind = parse_enum(FusionPhaseKind, obj.get("phaseKind")) + role = from_str(obj.get("role")) + return AssistantFusionPhaseStartedData( + conversation_scope=conversation_scope, + fusion_id=fusion_id, + model=model, + pattern=pattern, + phase_id=phase_id, + phase_kind=phase_kind, + role=role, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["conversationScope"] = to_enum(FusionConversationScope, self.conversation_scope) + result["fusionId"] = from_str(self.fusion_id) + result["model"] = from_str(self.model) + result["pattern"] = to_enum(FusionPattern, self.pattern) + result["phaseId"] = from_str(self.phase_id) + result["phaseKind"] = to_enum(FusionPhaseKind, self.phase_kind) + result["role"] = from_str(self.role) + return result + + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class AssistantMessageReasoningBlocks: @@ -907,6 +1110,206 @@ def to_dict(self) -> dict: return result +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class FusionAttribution: + "Experimental attribution linking an ordinary event to the HydraFusion turn, phase, and concrete source that produced it." + fusion_id: str + pattern: str + policy: str + synthetic_model: str + commit_id: str | None = None + conversation_scope: str | None = None + phase_id: str | None = None + phase_kind: str | None = None + role: str | None = None + source_model: str | None = None + source_phase_id: str | None = None + + @staticmethod + def from_dict(obj: Any) -> "FusionAttribution": + assert isinstance(obj, dict) + fusion_id = from_str(obj.get("fusionId")) + pattern = from_str(obj.get("pattern")) + policy = from_str(obj.get("policy")) + synthetic_model = from_str(obj.get("syntheticModel")) + commit_id = from_union([from_none, from_str], obj.get("commitId")) + conversation_scope = from_union([from_none, from_str], obj.get("conversationScope")) + phase_id = from_union([from_none, from_str], obj.get("phaseId")) + phase_kind = from_union([from_none, from_str], obj.get("phaseKind")) + role = from_union([from_none, from_str], obj.get("role")) + source_model = from_union([from_none, from_str], obj.get("sourceModel")) + source_phase_id = from_union([from_none, from_str], obj.get("sourcePhaseId")) + return FusionAttribution( + fusion_id=fusion_id, + pattern=pattern, + policy=policy, + synthetic_model=synthetic_model, + commit_id=commit_id, + conversation_scope=conversation_scope, + phase_id=phase_id, + phase_kind=phase_kind, + role=role, + source_model=source_model, + source_phase_id=source_phase_id, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["fusionId"] = from_str(self.fusion_id) + result["pattern"] = from_str(self.pattern) + result["policy"] = from_str(self.policy) + result["syntheticModel"] = from_str(self.synthetic_model) + if self.commit_id is not None: + result["commitId"] = from_union([from_none, from_str], self.commit_id) + if self.conversation_scope is not None: + result["conversationScope"] = from_union([from_none, from_str], self.conversation_scope) + if self.phase_id is not None: + result["phaseId"] = from_union([from_none, from_str], self.phase_id) + if self.phase_kind is not None: + result["phaseKind"] = from_union([from_none, from_str], self.phase_kind) + if self.role is not None: + result["role"] = from_union([from_none, from_str], self.role) + if self.source_model is not None: + result["sourceModel"] = from_union([from_none, from_str], self.source_model) + if self.source_phase_id is not None: + result["sourcePhaseId"] = from_union([from_none, from_str], self.source_phase_id) + return result + + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class FusionFollowUpRecommendation: + "Durable server recommendation for subsequent HydraFusion turns." + compaction_turn: FusionFollowUpAction + user_turn: FusionFollowUpAction + + @staticmethod + def from_dict(obj: Any) -> "FusionFollowUpRecommendation": + assert isinstance(obj, dict) + compaction_turn = parse_enum(FusionFollowUpAction, obj.get("compactionTurn")) + user_turn = parse_enum(FusionFollowUpAction, obj.get("userTurn")) + return FusionFollowUpRecommendation( + compaction_turn=compaction_turn, + user_turn=user_turn, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["compactionTurn"] = to_enum(FusionFollowUpAction, self.compaction_turn) + result["userTurn"] = to_enum(FusionFollowUpAction, self.user_turn) + return result + + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class FusionPhaseUsage: + "Aggregate concrete-model usage for one HydraFusion phase." + cached_tokens: int + input_tokens: int + output_tokens: int + request_count: int + total_nano_aiu: float + cache_write_tokens: int | None = None + + @staticmethod + def from_dict(obj: Any) -> "FusionPhaseUsage": + assert isinstance(obj, dict) + cached_tokens = from_int(obj.get("cachedTokens")) + input_tokens = from_int(obj.get("inputTokens")) + output_tokens = from_int(obj.get("outputTokens")) + request_count = from_int(obj.get("requestCount")) + total_nano_aiu = from_float(obj.get("totalNanoAiu")) + cache_write_tokens = from_union([from_none, from_int], obj.get("cacheWriteTokens")) + return FusionPhaseUsage( + cached_tokens=cached_tokens, + input_tokens=input_tokens, + output_tokens=output_tokens, + request_count=request_count, + total_nano_aiu=total_nano_aiu, + cache_write_tokens=cache_write_tokens, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["cachedTokens"] = to_int(self.cached_tokens) + result["inputTokens"] = to_int(self.input_tokens) + result["outputTokens"] = to_int(self.output_tokens) + result["requestCount"] = to_int(self.request_count) + result["totalNanoAiu"] = to_float(self.total_nano_aiu) + if self.cache_write_tokens is not None: + result["cacheWriteTokens"] = from_union([from_none, to_int], self.cache_write_tokens) + return result + + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class FusionScores: + "Validated HydraFusion routing capability scores." + code_gen: float + debugging: float + reasoning: float + tool_use: float + + @staticmethod + def from_dict(obj: Any) -> "FusionScores": + assert isinstance(obj, dict) + code_gen = from_float(obj.get("codeGen")) + debugging = from_float(obj.get("debugging")) + reasoning = from_float(obj.get("reasoning")) + tool_use = from_float(obj.get("toolUse")) + return FusionScores( + code_gen=code_gen, + debugging=debugging, + reasoning=reasoning, + tool_use=tool_use, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["codeGen"] = to_float(self.code_gen) + result["debugging"] = to_float(self.debugging) + result["reasoning"] = to_float(self.reasoning) + result["toolUse"] = to_float(self.tool_use) + return result + + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class _FusionStagedTerminal: + "Internal durable terminal request staged by a HydraFusion phase until an idempotent final commit selects it." + arguments: str + assistant_message: Any + phase_id: str + tool_call_id: str + tool_name: str + + @staticmethod + def from_dict(obj: Any) -> "_FusionStagedTerminal": + assert isinstance(obj, dict) + arguments = from_str(obj.get("arguments")) + assistant_message = obj.get("assistantMessage") + phase_id = from_str(obj.get("phaseId")) + tool_call_id = from_str(obj.get("toolCallId")) + tool_name = from_str(obj.get("toolName")) + return _FusionStagedTerminal( + arguments=arguments, + assistant_message=assistant_message, + phase_id=phase_id, + tool_call_id=tool_call_id, + tool_name=tool_name, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["arguments"] = from_str(self.arguments) + result["assistantMessage"] = self.assistant_message + result["phaseId"] = from_str(self.phase_id) + result["toolCallId"] = from_str(self.tool_call_id) + result["toolName"] = from_str(self.tool_name) + return result + + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class OmittedBinaryResult: @@ -1274,6 +1677,281 @@ def to_dict(self) -> dict: return result +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class SessionFusionCompletedData: + "Experimental durable aggregate outcome of a HydraFusion turn." + cached_tokens: int + commit_id: str + degraded_reason: str | None + duration_ms: float + final_source_model: str | None + final_source_phase_id: str | None + follow_up_model: str + fusion_id: str + input_tokens: int + outcome: str + output_tokens: int + pattern: FusionPattern + phase_count: int + request_count: int + synthetic_model: str + total_nano_aiu: float + turn_id: str + cache_write_tokens: int | None = None + + @staticmethod + def from_dict(obj: Any) -> "SessionFusionCompletedData": + assert isinstance(obj, dict) + cached_tokens = from_int(obj.get("cachedTokens")) + commit_id = from_str(obj.get("commitId")) + degraded_reason = from_union([from_none, from_str], obj.get("degradedReason")) + duration_ms = from_float(obj.get("durationMs")) + final_source_model = from_union([from_none, from_str], obj.get("finalSourceModel")) + final_source_phase_id = from_union([from_none, from_str], obj.get("finalSourcePhaseId")) + follow_up_model = from_str(obj.get("followUpModel")) + fusion_id = from_str(obj.get("fusionId")) + input_tokens = from_int(obj.get("inputTokens")) + outcome = from_str(obj.get("outcome")) + output_tokens = from_int(obj.get("outputTokens")) + pattern = parse_enum(FusionPattern, obj.get("pattern")) + phase_count = from_int(obj.get("phaseCount")) + request_count = from_int(obj.get("requestCount")) + synthetic_model = from_str(obj.get("syntheticModel")) + total_nano_aiu = from_float(obj.get("totalNanoAiu")) + turn_id = from_str(obj.get("turnId")) + cache_write_tokens = from_union([from_none, from_int], obj.get("cacheWriteTokens")) + return SessionFusionCompletedData( + cached_tokens=cached_tokens, + commit_id=commit_id, + degraded_reason=degraded_reason, + duration_ms=duration_ms, + final_source_model=final_source_model, + final_source_phase_id=final_source_phase_id, + follow_up_model=follow_up_model, + fusion_id=fusion_id, + input_tokens=input_tokens, + outcome=outcome, + output_tokens=output_tokens, + pattern=pattern, + phase_count=phase_count, + request_count=request_count, + synthetic_model=synthetic_model, + total_nano_aiu=total_nano_aiu, + turn_id=turn_id, + cache_write_tokens=cache_write_tokens, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["cachedTokens"] = to_int(self.cached_tokens) + result["commitId"] = from_str(self.commit_id) + result["degradedReason"] = from_union([from_none, from_str], self.degraded_reason) + result["durationMs"] = to_float(self.duration_ms) + result["finalSourceModel"] = from_union([from_none, from_str], self.final_source_model) + result["finalSourcePhaseId"] = from_union([from_none, from_str], self.final_source_phase_id) + result["followUpModel"] = from_str(self.follow_up_model) + result["fusionId"] = from_str(self.fusion_id) + result["inputTokens"] = to_int(self.input_tokens) + result["outcome"] = from_str(self.outcome) + result["outputTokens"] = to_int(self.output_tokens) + result["pattern"] = to_enum(FusionPattern, self.pattern) + result["phaseCount"] = to_int(self.phase_count) + result["requestCount"] = to_int(self.request_count) + result["syntheticModel"] = from_str(self.synthetic_model) + result["totalNanoAiu"] = to_float(self.total_nano_aiu) + result["turnId"] = from_str(self.turn_id) + if self.cache_write_tokens is not None: + result["cacheWriteTokens"] = from_union([from_none, to_int], self.cache_write_tokens) + return result + + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class SessionFusionResolvedData: + "Experimental durable validated HydraFusion route and turn policy." + contract_version: int + fallback_model: str + follow_up_model: str + fusion_id: str + pattern: FusionPattern + policy: str + primary_model: str + secondary_model: str | None + synthetic_model: str + turn_id: str + follow_up: FusionFollowUpRecommendation | None = None + model_universe_version: str | None = None + plan_version: str | None = None + policy_version: str | None = None + route_source: str | None = None + routing_latency_ms: float | None = None + rule_id: str | None = None + rule_index: int | None = None + rule_name: str | None = None + scores: FusionScores | None = None + + @staticmethod + def from_dict(obj: Any) -> "SessionFusionResolvedData": + assert isinstance(obj, dict) + contract_version = from_int(obj.get("contractVersion")) + fallback_model = from_str(obj.get("fallbackModel")) + follow_up_model = from_str(obj.get("followUpModel")) + fusion_id = from_str(obj.get("fusionId")) + pattern = parse_enum(FusionPattern, obj.get("pattern")) + policy = from_str(obj.get("policy")) + primary_model = from_str(obj.get("primaryModel")) + secondary_model = from_union([from_none, from_str], obj.get("secondaryModel")) + synthetic_model = from_str(obj.get("syntheticModel")) + turn_id = from_str(obj.get("turnId")) + follow_up = from_union([from_none, FusionFollowUpRecommendation.from_dict], obj.get("followUp")) + model_universe_version = from_union([from_none, from_str], obj.get("modelUniverseVersion")) + plan_version = from_union([from_none, from_str], obj.get("planVersion")) + policy_version = from_union([from_none, from_str], obj.get("policyVersion")) + route_source = from_union([from_none, from_str], obj.get("routeSource")) + routing_latency_ms = from_union([from_none, from_float], obj.get("routingLatencyMs")) + rule_id = from_union([from_none, from_str], obj.get("ruleId")) + rule_index = from_union([from_none, from_int], obj.get("ruleIndex")) + rule_name = from_union([from_none, from_str], obj.get("ruleName")) + scores = from_union([from_none, FusionScores.from_dict], obj.get("scores")) + return SessionFusionResolvedData( + contract_version=contract_version, + fallback_model=fallback_model, + follow_up_model=follow_up_model, + fusion_id=fusion_id, + pattern=pattern, + policy=policy, + primary_model=primary_model, + secondary_model=secondary_model, + synthetic_model=synthetic_model, + turn_id=turn_id, + follow_up=follow_up, + model_universe_version=model_universe_version, + plan_version=plan_version, + policy_version=policy_version, + route_source=route_source, + routing_latency_ms=routing_latency_ms, + rule_id=rule_id, + rule_index=rule_index, + rule_name=rule_name, + scores=scores, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["contractVersion"] = to_int(self.contract_version) + result["fallbackModel"] = from_str(self.fallback_model) + result["followUpModel"] = from_str(self.follow_up_model) + result["fusionId"] = from_str(self.fusion_id) + result["pattern"] = to_enum(FusionPattern, self.pattern) + result["policy"] = from_str(self.policy) + result["primaryModel"] = from_str(self.primary_model) + result["secondaryModel"] = from_union([from_none, from_str], self.secondary_model) + result["syntheticModel"] = from_str(self.synthetic_model) + result["turnId"] = from_str(self.turn_id) + if self.follow_up is not None: + result["followUp"] = from_union([from_none, lambda x: to_class(FusionFollowUpRecommendation, x)], self.follow_up) + if self.model_universe_version is not None: + result["modelUniverseVersion"] = from_union([from_none, from_str], self.model_universe_version) + if self.plan_version is not None: + result["planVersion"] = from_union([from_none, from_str], self.plan_version) + if self.policy_version is not None: + result["policyVersion"] = from_union([from_none, from_str], self.policy_version) + if self.route_source is not None: + result["routeSource"] = from_union([from_none, from_str], self.route_source) + if self.routing_latency_ms is not None: + result["routingLatencyMs"] = from_union([from_none, to_float], self.routing_latency_ms) + if self.rule_id is not None: + result["ruleId"] = from_union([from_none, from_str], self.rule_id) + if self.rule_index is not None: + result["ruleIndex"] = from_union([from_none, to_int], self.rule_index) + if self.rule_name is not None: + result["ruleName"] = from_union([from_none, from_str], self.rule_name) + if self.scores is not None: + result["scores"] = from_union([from_none, lambda x: to_class(FusionScores, x)], self.scores) + return result + + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class SessionFusionRouteFailedData: + "Experimental durable HydraFusion routing failure and the deterministic concrete fallback selected for the turn." + attempt_id: str + fallback_model: str + policy: str + reason: str + synthetic_model: str + error_message: str | None = None + routing_latency_ms: float | None = None + + @staticmethod + def from_dict(obj: Any) -> "SessionFusionRouteFailedData": + assert isinstance(obj, dict) + attempt_id = from_str(obj.get("attemptId")) + fallback_model = from_str(obj.get("fallbackModel")) + policy = from_str(obj.get("policy")) + reason = from_str(obj.get("reason")) + synthetic_model = from_str(obj.get("syntheticModel")) + error_message = from_union([from_none, from_str], obj.get("errorMessage")) + routing_latency_ms = from_union([from_none, from_float], obj.get("routingLatencyMs")) + return SessionFusionRouteFailedData( + attempt_id=attempt_id, + fallback_model=fallback_model, + policy=policy, + reason=reason, + synthetic_model=synthetic_model, + error_message=error_message, + routing_latency_ms=routing_latency_ms, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["attemptId"] = from_str(self.attempt_id) + result["fallbackModel"] = from_str(self.fallback_model) + result["policy"] = from_str(self.policy) + result["reason"] = from_str(self.reason) + result["syntheticModel"] = from_str(self.synthetic_model) + if self.error_message is not None: + result["errorMessage"] = from_union([from_none, from_str], self.error_message) + if self.routing_latency_ms is not None: + result["routingLatencyMs"] = from_union([from_none, to_float], self.routing_latency_ms) + return result + + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class SessionFusionRouteStartedData: + "Experimental transient signal that HydraFusion routing has started for an eligible turn." + attempt_id: str + turn_kind: FusionTurnKind + policy: str | None = None + synthetic_model: str | None = None + + @staticmethod + def from_dict(obj: Any) -> "SessionFusionRouteStartedData": + assert isinstance(obj, dict) + attempt_id = from_str(obj.get("attemptId")) + turn_kind = parse_enum(FusionTurnKind, obj.get("turnKind")) + policy = from_union([from_none, from_str], obj.get("policy")) + synthetic_model = from_union([from_none, from_str], obj.get("syntheticModel")) + return SessionFusionRouteStartedData( + attempt_id=attempt_id, + turn_kind=turn_kind, + policy=policy, + synthetic_model=synthetic_model, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["attemptId"] = from_str(self.attempt_id) + result["turnKind"] = to_enum(FusionTurnKind, self.turn_kind) + if self.policy is not None: + result["policy"] = from_union([from_none, from_str], self.policy) + if self.synthetic_model is not None: + result["syntheticModel"] = from_union([from_none, from_str], self.synthetic_model) + return result + + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class SessionManagedSettingsEnforcedData: @@ -1589,6 +2267,8 @@ class AssistantMessageData: citations: Citations | None = None client_request_id: str | None = None encrypted_content: str | None = None + # Experimental: this field is part of an experimental API and may change or be removed. + fusion: FusionAttribution | None = None interaction_id: str | None = None model: str | None = None output_tokens: int | None = None @@ -1617,6 +2297,7 @@ def from_dict(obj: Any) -> "AssistantMessageData": citations = from_union([from_none, Citations.from_dict], obj.get("citations")) client_request_id = from_union([from_none, from_str], obj.get("clientRequestId")) encrypted_content = from_union([from_none, from_str], obj.get("encryptedContent")) + fusion = from_union([from_none, FusionAttribution.from_dict], obj.get("fusion")) interaction_id = from_union([from_none, from_str], obj.get("interactionId")) model = from_union([from_none, from_str], obj.get("model")) output_tokens = from_union([from_none, from_int], obj.get("outputTokens")) @@ -1641,6 +2322,7 @@ def from_dict(obj: Any) -> "AssistantMessageData": citations=citations, client_request_id=client_request_id, encrypted_content=encrypted_content, + fusion=fusion, interaction_id=interaction_id, model=model, output_tokens=output_tokens, @@ -1674,6 +2356,8 @@ def to_dict(self) -> dict: result["clientRequestId"] = from_union([from_none, from_str], self.client_request_id) if self.encrypted_content is not None: result["encryptedContent"] = from_union([from_none, from_str], self.encrypted_content) + if self.fusion is not None: + result["fusion"] = from_union([from_none, lambda x: to_class(FusionAttribution, x)], self.fusion) if self.interaction_id is not None: result["interactionId"] = from_union([from_none, from_str], self.interaction_id) if self.model is not None: @@ -2105,6 +2789,8 @@ class AssistantUsageData: finish_reason: str | None = None # Internal: this field is an internal SDK API and is not part of the public surface. _frontier_source: str | None = None + # Experimental: this field is part of an experimental API and may change or be removed. + fusion: FusionAttribution | None = None initiator: str | None = None input_tokens: int | None = None interaction_type: str | None = None @@ -2154,6 +2840,7 @@ def from_dict(obj: Any) -> "AssistantUsageData": duration = from_union([from_none, from_timedelta], obj.get("duration")) finish_reason = from_union([from_none, from_str], obj.get("finishReason")) _frontier_source = from_union([from_none, from_str], obj.get("frontierSource")) + fusion = from_union([from_none, FusionAttribution.from_dict], obj.get("fusion")) initiator = from_union([from_none, from_str], obj.get("initiator")) input_tokens = from_union([from_none, from_int], obj.get("inputTokens")) interaction_type = from_union([from_none, from_str], obj.get("interactionType")) @@ -2195,6 +2882,7 @@ def from_dict(obj: Any) -> "AssistantUsageData": duration=duration, finish_reason=finish_reason, _frontier_source=_frontier_source, + fusion=fusion, initiator=initiator, input_tokens=input_tokens, interaction_type=interaction_type, @@ -2254,6 +2942,8 @@ def to_dict(self) -> dict: result["finishReason"] = from_union([from_none, from_str], self.finish_reason) if self._frontier_source is not None: result["frontierSource"] = from_union([from_none, from_str], self._frontier_source) + if self.fusion is not None: + result["fusion"] = from_union([from_none, lambda x: to_class(FusionAttribution, x)], self.fusion) if self.initiator is not None: result["initiator"] = from_union([from_none, from_str], self.initiator) if self.input_tokens is not None: @@ -3925,6 +4615,7 @@ class HookEndData: success: bool error: HookEndError | None = None output: Any = None + parent_tool_call_id: str | None = None @staticmethod def from_dict(obj: Any) -> "HookEndData": @@ -3934,12 +4625,14 @@ def from_dict(obj: Any) -> "HookEndData": success = from_bool(obj.get("success")) error = from_union([from_none, HookEndError.from_dict], obj.get("error")) output = obj.get("output") + parent_tool_call_id = from_union([from_none, from_str], obj.get("parentToolCallId")) return HookEndData( hook_invocation_id=hook_invocation_id, hook_type=hook_type, success=success, error=error, output=output, + parent_tool_call_id=parent_tool_call_id, ) def to_dict(self) -> dict: @@ -3951,6 +4644,8 @@ def to_dict(self) -> dict: result["error"] = from_union([from_none, lambda x: to_class(HookEndError, x)], self.error) if self.output is not None: result["output"] = self.output + if self.parent_tool_call_id is not None: + result["parentToolCallId"] = from_union([from_none, from_str], self.parent_tool_call_id) return result @@ -4013,6 +4708,7 @@ class HookStartData: hook_invocation_id: str hook_type: str input: Any = None + parent_tool_call_id: str | None = None @staticmethod def from_dict(obj: Any) -> "HookStartData": @@ -4020,10 +4716,12 @@ def from_dict(obj: Any) -> "HookStartData": hook_invocation_id = from_str(obj.get("hookInvocationId")) hook_type = from_str(obj.get("hookType")) input = obj.get("input") + parent_tool_call_id = from_union([from_none, from_str], obj.get("parentToolCallId")) return HookStartData( hook_invocation_id=hook_invocation_id, hook_type=hook_type, input=input, + parent_tool_call_id=parent_tool_call_id, ) def to_dict(self) -> dict: @@ -4032,6 +4730,8 @@ def to_dict(self) -> dict: result["hookType"] = from_str(self.hook_type) if self.input is not None: result["input"] = self.input + if self.parent_tool_call_id is not None: + result["parentToolCallId"] = from_union([from_none, from_str], self.parent_tool_call_id) return result @@ -4487,6 +5187,8 @@ class ModelCallFailureData: error_message: str | None = None error_type: str | None = None failure_kind: ModelCallFailureKind | None = None + # Experimental: this field is part of an experimental API and may change or be removed. + fusion: FusionAttribution | None = None initiator: str | None = None interaction_type: str | None = None is_auto: bool | None = None @@ -4516,6 +5218,7 @@ def from_dict(obj: Any) -> "ModelCallFailureData": error_message = from_union([from_none, from_str], obj.get("errorMessage")) error_type = from_union([from_none, from_str], obj.get("errorType")) failure_kind = from_union([from_none, lambda x: parse_enum(ModelCallFailureKind, x)], obj.get("failureKind")) + fusion = from_union([from_none, FusionAttribution.from_dict], obj.get("fusion")) initiator = from_union([from_none, from_str], obj.get("initiator")) interaction_type = from_union([from_none, from_str], obj.get("interactionType")) is_auto = from_union([from_none, from_bool], obj.get("isAuto")) @@ -4541,6 +5244,7 @@ def from_dict(obj: Any) -> "ModelCallFailureData": error_message=error_message, error_type=error_type, failure_kind=failure_kind, + fusion=fusion, initiator=initiator, interaction_type=interaction_type, is_auto=is_auto, @@ -4577,6 +5281,8 @@ def to_dict(self) -> dict: result["errorType"] = from_union([from_none, from_str], self.error_type) if self.failure_kind is not None: result["failureKind"] = from_union([from_none, lambda x: to_enum(ModelCallFailureKind, x)], self.failure_kind) + if self.fusion is not None: + result["fusion"] = from_union([from_none, lambda x: to_class(FusionAttribution, x)], self.fusion) if self.initiator is not None: result["initiator"] = from_union([from_none, from_str], self.initiator) if self.interaction_type is not None: @@ -4699,6 +5405,8 @@ def to_dict(self) -> dict: class ModelCallStartData: "Model API dispatch metadata for internal telemetry" turn_id: str + # Experimental: this field is part of an experimental API and may change or be removed. + fusion: FusionAttribution | None = None model: str | None = None # Internal: this field is an internal SDK API and is not part of the public surface. _previous_response_id: str | None = None @@ -4707,10 +5415,12 @@ class ModelCallStartData: def from_dict(obj: Any) -> "ModelCallStartData": assert isinstance(obj, dict) turn_id = from_str(obj.get("turnId")) + fusion = from_union([from_none, FusionAttribution.from_dict], obj.get("fusion")) model = from_union([from_none, from_str], obj.get("model")) _previous_response_id = from_union([from_none, from_str], obj.get("previousResponseId")) return ModelCallStartData( turn_id=turn_id, + fusion=fusion, model=model, _previous_response_id=_previous_response_id, ) @@ -4718,6 +5428,8 @@ def from_dict(obj: Any) -> "ModelCallStartData": def to_dict(self) -> dict: result: dict = {} result["turnId"] = from_str(self.turn_id) + if self.fusion is not None: + result["fusion"] = from_union([from_none, lambda x: to_class(FusionAttribution, x)], self.fusion) if self.model is not None: result["model"] = from_union([from_none, from_str], self.model) if self._previous_response_id is not None: @@ -8590,6 +9302,39 @@ def to_dict(self) -> dict: return result +@dataclass +class SubagentConfiguredData: + "Resolved runtime configuration for a configured sub-agent" + model: str + multi_turn: bool + context_tier: str | None = None + reasoning_effort: str | None = None + + @staticmethod + def from_dict(obj: Any) -> "SubagentConfiguredData": + assert isinstance(obj, dict) + model = from_str(obj.get("model")) + multi_turn = from_bool(obj.get("multiTurn")) + context_tier = from_union([from_none, from_str], obj.get("contextTier")) + reasoning_effort = from_union([from_none, from_str], obj.get("reasoningEffort")) + return SubagentConfiguredData( + model=model, + multi_turn=multi_turn, + context_tier=context_tier, + reasoning_effort=reasoning_effort, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["model"] = from_str(self.model) + result["multiTurn"] = from_bool(self.multi_turn) + if self.context_tier is not None: + result["contextTier"] = from_union([from_none, from_str], self.context_tier) + if self.reasoning_effort is not None: + result["reasoningEffort"] = from_union([from_none, from_str], self.reasoning_effort) + return result + + @dataclass class SubagentDeselectedData: "Empty payload; the event signals that the custom agent was deselected, returning to the default agent" @@ -8712,8 +9457,12 @@ class SubagentStartedData: agent_display_name: str agent_name: str tool_call_id: str + agent_type: str | None = None + execution_mode: str | None = None factory_run_id: str | None = None model: str | None = None + parent_id: str | None = None + resumable: bool | None = None @staticmethod def from_dict(obj: Any) -> "SubagentStartedData": @@ -8722,15 +9471,23 @@ def from_dict(obj: Any) -> "SubagentStartedData": agent_display_name = from_str(obj.get("agentDisplayName")) agent_name = from_str(obj.get("agentName")) tool_call_id = from_str(obj.get("toolCallId")) + agent_type = from_union([from_none, from_str], obj.get("agentType")) + execution_mode = from_union([from_none, from_str], obj.get("executionMode")) factory_run_id = from_union([from_none, from_str], obj.get("factoryRunId")) model = from_union([from_none, from_str], obj.get("model")) + parent_id = from_union([from_none, from_str], obj.get("parentId")) + resumable = from_union([from_none, from_bool], obj.get("resumable")) return SubagentStartedData( agent_description=agent_description, agent_display_name=agent_display_name, agent_name=agent_name, tool_call_id=tool_call_id, + agent_type=agent_type, + execution_mode=execution_mode, factory_run_id=factory_run_id, model=model, + parent_id=parent_id, + resumable=resumable, ) def to_dict(self) -> dict: @@ -8739,10 +9496,18 @@ def to_dict(self) -> dict: result["agentDisplayName"] = from_str(self.agent_display_name) result["agentName"] = from_str(self.agent_name) result["toolCallId"] = from_str(self.tool_call_id) + if self.agent_type is not None: + result["agentType"] = from_union([from_none, from_str], self.agent_type) + if self.execution_mode is not None: + result["executionMode"] = from_union([from_none, from_str], self.execution_mode) if self.factory_run_id is not None: result["factoryRunId"] = from_union([from_none, from_str], self.factory_run_id) if self.model is not None: result["model"] = from_union([from_none, from_str], self.model) + if self.parent_id is not None: + result["parentId"] = from_union([from_none, from_str], self.parent_id) + if self.resumable is not None: + result["resumable"] = from_union([from_none, from_bool], self.resumable) return result @@ -9279,6 +10044,7 @@ class ToolExecutionCompleteContentShellExit: shell_id: str type: ClassVar[str] = "shell_exit" cwd: str | None = None + output_file_path: str | None = None output_preview: str | None = None output_truncated: bool | None = None @@ -9288,12 +10054,14 @@ def from_dict(obj: Any) -> "ToolExecutionCompleteContentShellExit": exit_code = from_int(obj.get("exitCode")) shell_id = from_str(obj.get("shellId")) cwd = from_union([from_none, from_str], obj.get("cwd")) + output_file_path = from_union([from_none, from_str], obj.get("outputFilePath")) output_preview = from_union([from_none, from_str], obj.get("outputPreview")) output_truncated = from_union([from_none, from_bool], obj.get("outputTruncated")) return ToolExecutionCompleteContentShellExit( exit_code=exit_code, shell_id=shell_id, cwd=cwd, + output_file_path=output_file_path, output_preview=output_preview, output_truncated=output_truncated, ) @@ -9305,6 +10073,8 @@ def to_dict(self) -> dict: result["type"] = self.type if self.cwd is not None: result["cwd"] = from_union([from_none, from_str], self.cwd) + if self.output_file_path is not None: + result["outputFilePath"] = from_union([from_none, from_str], self.output_file_path) if self.output_preview is not None: result["outputPreview"] = from_union([from_none, from_str], self.output_preview) if self.output_truncated is not None: @@ -9339,6 +10109,8 @@ class ToolExecutionCompleteData: success: bool tool_call_id: str error: ToolExecutionCompleteError | None = None + # Experimental: this field is part of an experimental API and may change or be removed. + fusion: FusionAttribution | None = None interaction_id: str | None = None is_user_requested: bool | None = None # Experimental: this field is part of an experimental API and may change or be removed. @@ -9359,6 +10131,7 @@ def from_dict(obj: Any) -> "ToolExecutionCompleteData": success = from_bool(obj.get("success")) tool_call_id = from_str(obj.get("toolCallId")) error = from_union([from_none, ToolExecutionCompleteError.from_dict], obj.get("error")) + fusion = from_union([from_none, FusionAttribution.from_dict], obj.get("fusion")) interaction_id = from_union([from_none, from_str], obj.get("interactionId")) is_user_requested = from_union([from_none, from_bool], obj.get("isUserRequested")) mcp_meta = obj.get("mcpMeta") @@ -9374,6 +10147,7 @@ def from_dict(obj: Any) -> "ToolExecutionCompleteData": success=success, tool_call_id=tool_call_id, error=error, + fusion=fusion, interaction_id=interaction_id, is_user_requested=is_user_requested, mcp_meta=mcp_meta, @@ -9393,6 +10167,8 @@ def to_dict(self) -> dict: result["toolCallId"] = from_str(self.tool_call_id) if self.error is not None: result["error"] = from_union([from_none, lambda x: to_class(ToolExecutionCompleteError, x)], self.error) + if self.fusion is not None: + result["fusion"] = from_union([from_none, lambda x: to_class(FusionAttribution, x)], self.fusion) if self.interaction_id is not None: result["interactionId"] = from_union([from_none, from_str], self.interaction_id) if self.is_user_requested is not None: @@ -9837,6 +10613,8 @@ class ToolExecutionStartData: tool_name: str arguments: Any = None display_verbatim: bool | None = None + # Experimental: this field is part of an experimental API and may change or be removed. + fusion: FusionAttribution | None = None mcp_server_name: str | None = None mcp_tool_name: str | None = None model: str | None = None @@ -9854,6 +10632,7 @@ def from_dict(obj: Any) -> "ToolExecutionStartData": tool_name = from_str(obj.get("toolName")) arguments = obj.get("arguments") display_verbatim = from_union([from_none, from_bool], obj.get("displayVerbatim")) + fusion = from_union([from_none, FusionAttribution.from_dict], obj.get("fusion")) mcp_server_name = from_union([from_none, from_str], obj.get("mcpServerName")) mcp_tool_name = from_union([from_none, from_str], obj.get("mcpToolName")) model = from_union([from_none, from_str], obj.get("model")) @@ -9867,6 +10646,7 @@ def from_dict(obj: Any) -> "ToolExecutionStartData": tool_name=tool_name, arguments=arguments, display_verbatim=display_verbatim, + fusion=fusion, mcp_server_name=mcp_server_name, mcp_tool_name=mcp_tool_name, model=model, @@ -9885,6 +10665,8 @@ def to_dict(self) -> dict: result["arguments"] = self.arguments if self.display_verbatim is not None: result["displayVerbatim"] = from_union([from_none, from_bool], self.display_verbatim) + if self.fusion is not None: + result["fusion"] = from_union([from_none, lambda x: to_class(FusionAttribution, x)], self.fusion) if self.mcp_server_name is not None: result["mcpServerName"] = from_union([from_none, from_str], self.mcp_server_name) if self.mcp_tool_name is not None: @@ -10706,6 +11488,85 @@ class CitationProvider(Enum): CLIENT = "client" +# Experimental: this enum is part of an experimental API and may change or be removed. +class FusionConversationScope(Enum): + "Conversation scope in which a HydraFusion phase executes." + # Canonical root conversation history. + ROOT = "root" + # Isolated read-only review history that does not enter the root conversation. + REVIEW = "review" + + +# Experimental: this enum is part of an experimental API and may change or be removed. +class FusionFollowUpAction(Enum): + "Server-recommended routing behavior for a later HydraFusion turn." + # Reuse the durable primary model without routing. + REUSE_PRIMARY = "reuse_primary" + # Request a new routing decision. + REROUTE = "reroute" + + +# Experimental: this enum is part of an experimental API and may change or be removed. +class FusionPattern(Enum): + "Validated HydraFusion execution pattern." + # Run one primary solver phase. + SINGLE = "single" + # Run a primary phase, a judge, and an optional repair. + CASCADE = "cascade" + # Run a primary draft, a read-only critique, and a revision. + CRITIQUE = "critique" + + +# Experimental: this enum is part of an experimental API and may change or be removed. +class FusionPhaseKind(Enum): + "HydraFusion phase kind." + # Primary solver phase. + PRIMARY = "primary" + # Read-only cascade judge phase. + JUDGE = "judge" + # Cascade repair phase. + REPAIR = "repair" + # Initial critique-pattern draft phase. + DRAFT = "draft" + # Read-only critique phase. + CRITIC = "critic" + # Critique-pattern revision phase. + REVISION = "revision" + # Follow-up phase continuing from the resolved model. + FOLLOW_UP = "follow_up" + + +# Experimental: this enum is part of an experimental API and may change or be removed. +class FusionPhaseStatus(Enum): + "Durable outcome status of a HydraFusion phase." + # The phase completed successfully. + SUCCEEDED = "succeeded" + # The phase failed. + FAILED = "failed" + # The phase was cancelled. + CANCELLED = "cancelled" + + +# Experimental: this enum is part of an experimental API and may change or be removed. +class _FusionProjectionMode(Enum): + "How a durable phase checkpoint contributes its exact message to canonical root history." + # Append the exact root message immediately. + APPEND = "append" + # Hold a terminal message outside canonical history until the final commit selects it. + STAGED = "staged" + # Do not project the checkpoint into root history. + NONE = "none" + + +# Experimental: this enum is part of an experimental API and may change or be removed. +class FusionTurnKind(Enum): + "Kind of turn for which HydraFusion routing is running." + # A user-message turn. + USER = "user" + # A conversation-compaction turn. + COMPACTION = "compaction" + + # Experimental: this enum is part of an experimental API and may change or be removed. class PermissionMode(Enum): "Permission mode for the session." @@ -11415,7 +12276,7 @@ class WorkspaceFileChangedOperation(Enum): UPDATE = "update" -SessionEventData = SessionStartData | SessionResumeData | SessionRemoteSteerableChangedData | SessionErrorData | SessionIdleData | SessionTitleChangedData | SessionScheduleCreatedData | SessionScheduleCancelledData | SessionScheduleRearmedData | SessionAutopilotObjectiveChangedData | SessionInfoData | SessionWarningData | SessionModelChangeData | SessionModeChangedData | SessionSessionLimitsChangedData | SessionPermissionsChangedData | SessionPlanChangedData | SessionTodosChangedData | SessionWorkspaceFileChangedData | SessionHandoffData | SessionTruncationData | SessionSnapshotRewindData | SessionShutdownData | SessionUsageCheckpointData | SessionContextChangedData | SessionUsageInfoData | SessionContextClearedData | SessionCompactionStartData | SessionCompactionCompleteData | SessionTaskCompleteData | UserMessageData | PendingMessagesModifiedData | AssistantTurnStartData | AssistantTurnRetryData | AgentInterruptedData | AssistantIntentData | AssistantServerToolProgressData | AssistantReasoningData | AssistantReasoningDeltaData | AssistantToolCallDeltaData | AssistantStreamingDeltaData | AssistantMessageData | AssistantMessageStartData | AssistantMessageDeltaData | AssistantTurnEndData | AssistantIdleData | AssistantUsageData | PromptCacheBreakData | ModelCallFailureData | ModelCallFinishedData | ModelCallStartData | AbortData | ToolUserRequestedData | ToolExecutionStartData | ToolExecutionPartialResultData | ToolExecutionProgressData | ToolExecutionCompleteData | ToolSearchActivatedData | SkillInvokedData | SandboxDecisionData | SubagentStartedData | SubagentCompletedData | SubagentFailedData | SubagentSelectedData | SubagentDeselectedData | HookStartData | HookEndData | HookProgressData | SessionBinaryAssetData | SystemMessageData | SystemNotificationData | PermissionRequestedData | PermissionCompletedData | UserInputRequestedData | UserInputCompletedData | ElicitationRequestedData | ElicitationCompletedData | SamplingRequestedData | SamplingCompletedData | McpOauthRequiredData | McpOauthCompletedData | McpHeadersRefreshRequiredData | McpHeadersRefreshCompletedData | SessionCustomNotificationData | UiEphemeralQueryData | ExternalToolRequestedData | ExternalToolCompletedData | CommandQueuedData | CommandExecuteData | CommandCompletedData | AutoModeSwitchRequestedData | AutoModeSwitchCompletedData | SessionLimitsExhaustedRequestedData | SessionLimitsExhaustedCompletedData | SessionAutoModeResolvedData | SessionManagedSettingsResolvedData | SessionManagedSettingsEnforcedData | CommandsChangedData | CapabilitiesChangedData | ExitPlanModeRequestedData | ExitPlanModeCompletedData | SessionToolsUpdatedData | SessionBackgroundTasksChangedData | FactoryRunUpdatedData | FactoryRunStartedData | FactoryRunSettledData | SessionSkillsLoadedData | SessionCustomAgentsUpdatedData | SessionMcpServersLoadedData | SessionMcpServerStatusChangedData | McpToolsListChangedData | McpResourcesListChangedData | McpPromptsListChangedData | SessionExtensionsLoadedData | SessionCanvasOpenedData | SessionCanvasRegistryChangedData | SessionCanvasClosedData | SessionCanvasUnavailableData | SessionCanvasRecordedData | SessionCanvasRemovedData | SessionExtensionsAttachmentsPushedData | McpAppToolCallCompleteData | RawSessionEventData | Data +SessionEventData = SessionStartData | SessionResumeData | SessionRemoteSteerableChangedData | SessionErrorData | SessionIdleData | SessionTitleChangedData | SessionScheduleCreatedData | SessionScheduleCancelledData | SessionScheduleRearmedData | SessionAutopilotObjectiveChangedData | SessionInfoData | SessionWarningData | SessionModelChangeData | SessionModeChangedData | SessionSessionLimitsChangedData | SessionPermissionsChangedData | SessionPlanChangedData | SessionTodosChangedData | SessionWorkspaceFileChangedData | SessionHandoffData | SessionTruncationData | SessionSnapshotRewindData | SessionShutdownData | SessionUsageCheckpointData | SessionContextChangedData | SessionUsageInfoData | SessionContextClearedData | SessionCompactionStartData | SessionCompactionCompleteData | SessionTaskCompleteData | SessionFusionRouteStartedData | SessionFusionRouteFailedData | SessionFusionResolvedData | SessionFusionCompletedData | UserMessageData | PendingMessagesModifiedData | AssistantTurnStartData | AssistantTurnRetryData | AgentInterruptedData | AssistantIntentData | AssistantFusionPhaseStartedData | AssistantFusionPhaseCompletedData | AssistantFusionPhaseFailedData | AssistantServerToolProgressData | AssistantReasoningData | AssistantReasoningDeltaData | AssistantToolCallDeltaData | AssistantStreamingDeltaData | AssistantMessageData | AssistantMessageStartData | AssistantMessageDeltaData | AssistantTurnEndData | AssistantIdleData | AssistantUsageData | PromptCacheBreakData | ModelCallFailureData | ModelCallFinishedData | ModelCallStartData | AbortData | ToolUserRequestedData | ToolExecutionStartData | ToolExecutionPartialResultData | ToolExecutionProgressData | ToolExecutionCompleteData | ToolSearchActivatedData | SkillInvokedData | SandboxDecisionData | SubagentStartedData | SubagentConfiguredData | SubagentCompletedData | SubagentFailedData | SubagentSelectedData | SubagentDeselectedData | HookStartData | HookEndData | HookProgressData | SessionBinaryAssetData | SystemMessageData | SystemNotificationData | PermissionRequestedData | PermissionCompletedData | UserInputRequestedData | UserInputCompletedData | ElicitationRequestedData | ElicitationCompletedData | SamplingRequestedData | SamplingCompletedData | McpOauthRequiredData | McpOauthCompletedData | McpHeadersRefreshRequiredData | McpHeadersRefreshCompletedData | SessionCustomNotificationData | UiEphemeralQueryData | ExternalToolRequestedData | ExternalToolCompletedData | CommandQueuedData | CommandExecuteData | CommandCompletedData | AutoModeSwitchRequestedData | AutoModeSwitchCompletedData | SessionLimitsExhaustedRequestedData | SessionLimitsExhaustedCompletedData | SessionAutoModeResolvedData | SessionManagedSettingsResolvedData | SessionManagedSettingsEnforcedData | CommandsChangedData | CapabilitiesChangedData | ExitPlanModeRequestedData | ExitPlanModeCompletedData | SessionToolsUpdatedData | SessionBackgroundTasksChangedData | FactoryRunUpdatedData | FactoryRunStartedData | FactoryRunSettledData | SessionSkillsLoadedData | SessionCustomAgentsUpdatedData | SessionMcpServersLoadedData | SessionMcpServerStatusChangedData | McpToolsListChangedData | McpResourcesListChangedData | McpPromptsListChangedData | SessionExtensionsLoadedData | SessionCanvasOpenedData | SessionCanvasRegistryChangedData | SessionCanvasClosedData | SessionCanvasUnavailableData | SessionCanvasRecordedData | SessionCanvasRemovedData | SessionExtensionsAttachmentsPushedData | McpAppToolCallCompleteData | RawSessionEventData | Data @dataclass @@ -11471,12 +12332,19 @@ def from_dict(obj: Any) -> "SessionEvent": case SessionEventType.SESSION_COMPACTION_START: data = SessionCompactionStartData.from_dict(data_obj) case SessionEventType.SESSION_COMPACTION_COMPLETE: data = SessionCompactionCompleteData.from_dict(data_obj) case SessionEventType.SESSION_TASK_COMPLETE: data = SessionTaskCompleteData.from_dict(data_obj) + case SessionEventType.SESSION_FUSION_ROUTE_STARTED: data = SessionFusionRouteStartedData.from_dict(data_obj) + case SessionEventType.SESSION_FUSION_ROUTE_FAILED: data = SessionFusionRouteFailedData.from_dict(data_obj) + case SessionEventType.SESSION_FUSION_RESOLVED: data = SessionFusionResolvedData.from_dict(data_obj) + case SessionEventType.SESSION_FUSION_COMPLETED: data = SessionFusionCompletedData.from_dict(data_obj) case SessionEventType.USER_MESSAGE: data = UserMessageData.from_dict(data_obj) case SessionEventType.PENDING_MESSAGES_MODIFIED: data = PendingMessagesModifiedData.from_dict(data_obj) case SessionEventType.ASSISTANT_TURN_START: data = AssistantTurnStartData.from_dict(data_obj) case SessionEventType.ASSISTANT_TURN_RETRY: data = AssistantTurnRetryData.from_dict(data_obj) case SessionEventType.AGENT_INTERRUPTED: data = AgentInterruptedData.from_dict(data_obj) case SessionEventType.ASSISTANT_INTENT: data = AssistantIntentData.from_dict(data_obj) + case SessionEventType.ASSISTANT_FUSION_PHASE_STARTED: data = AssistantFusionPhaseStartedData.from_dict(data_obj) + case SessionEventType.ASSISTANT_FUSION_PHASE_COMPLETED: data = AssistantFusionPhaseCompletedData.from_dict(data_obj) + case SessionEventType.ASSISTANT_FUSION_PHASE_FAILED: data = AssistantFusionPhaseFailedData.from_dict(data_obj) case SessionEventType.ASSISTANT_SERVER_TOOL_PROGRESS: data = AssistantServerToolProgressData.from_dict(data_obj) case SessionEventType.ASSISTANT_REASONING: data = AssistantReasoningData.from_dict(data_obj) case SessionEventType.ASSISTANT_REASONING_DELTA: data = AssistantReasoningDeltaData.from_dict(data_obj) @@ -11502,6 +12370,7 @@ def from_dict(obj: Any) -> "SessionEvent": case SessionEventType.SKILL_INVOKED: data = SkillInvokedData.from_dict(data_obj) case SessionEventType.SANDBOX_DECISION: data = SandboxDecisionData.from_dict(data_obj) case SessionEventType.SUBAGENT_STARTED: data = SubagentStartedData.from_dict(data_obj) + case SessionEventType.SUBAGENT_CONFIGURED: data = SubagentConfiguredData.from_dict(data_obj) case SessionEventType.SUBAGENT_COMPLETED: data = SubagentCompletedData.from_dict(data_obj) case SessionEventType.SUBAGENT_FAILED: data = SubagentFailedData.from_dict(data_obj) case SessionEventType.SUBAGENT_SELECTED: data = SubagentSelectedData.from_dict(data_obj) @@ -11602,6 +12471,9 @@ def session_event_to_dict(x: SessionEvent) -> Any: "AgentInterruptedActivity", "AgentInterruptedCancelPhase", "AgentInterruptedData", + "AssistantFusionPhaseCompletedData", + "AssistantFusionPhaseFailedData", + "AssistantFusionPhaseStartedData", "AssistantIdleData", "AssistantIntentData", "AssistantMessageData", @@ -11704,6 +12576,16 @@ def session_event_to_dict(x: SessionEvent) -> Any: "FactoryRunSettledStatus", "FactoryRunStartedData", "FactoryRunUpdatedData", + "FusionAttribution", + "FusionConversationScope", + "FusionFollowUpAction", + "FusionFollowUpRecommendation", + "FusionPattern", + "FusionPhaseKind", + "FusionPhaseStatus", + "FusionPhaseUsage", + "FusionScores", + "FusionTurnKind", "GitHubMcpToolConfig", "GitHubRepoRef", "HandoffRepository", @@ -11835,6 +12717,10 @@ def session_event_to_dict(x: SessionEvent) -> Any: "SessionEventType", "SessionExtensionsAttachmentsPushedData", "SessionExtensionsLoadedData", + "SessionFusionCompletedData", + "SessionFusionResolvedData", + "SessionFusionRouteFailedData", + "SessionFusionRouteStartedData", "SessionHandoffData", "SessionIdleData", "SessionInfoData", @@ -11884,6 +12770,7 @@ def session_event_to_dict(x: SessionEvent) -> Any: "SkillSource", "SkillsLoadedSkill", "SubagentCompletedData", + "SubagentConfiguredData", "SubagentDeselectedData", "SubagentFailedData", "SubagentSelectedData", diff --git a/python/e2e/test_rewind_e2e.py b/python/e2e/test_rewind_e2e.py index 179d9c2db4..10e7bc4dd9 100644 --- a/python/e2e/test_rewind_e2e.py +++ b/python/e2e/test_rewind_e2e.py @@ -4,6 +4,7 @@ import asyncio import os +import sys from pathlib import Path import pytest @@ -30,6 +31,9 @@ def _same_path(left: str | Path, right: str | Path) -> bool: class TestRewind: async def test_should_restore_tracked_file_and_conversation(self, ctx: E2ETestContext): + if sys.platform == "win32": + pytest.skip("blocked on CLI 1.0.81 file-change tracking regression on Windows") + file_path = Path(ctx.work_dir) / FILE_NAME session = await ctx.client.create_session( model="claude-sonnet-4.5", @@ -52,7 +56,7 @@ async def test_should_restore_tracked_file_and_conversation(self, ctx: E2ETestCo # capture lands instead of sampling once; the assertions below still run if # it never does. rewind_points = await session.rpc.history.list_rewind_points() - deadline = asyncio.get_running_loop().time() + 10 + deadline = asyncio.get_running_loop().time() + 30 while asyncio.get_running_loop().time() < deadline and not ( rewind_points.unavailable_reason is None and rewind_points.points diff --git a/rust/src/generated/api_types.rs b/rust/src/generated/api_types.rs index bdf291e599..9bf64ac404 100644 --- a/rust/src/generated/api_types.rs +++ b/rust/src/generated/api_types.rs @@ -253,6 +253,10 @@ pub mod rpc_methods { pub const SESSION_FACTORY_RUN: &str = "session.factory.run"; /// `session.factory.resume` pub const SESSION_FACTORY_RESUME: &str = "session.factory.resume"; + /// `session.factory.runFromTool` + pub const SESSION_FACTORY_RUNFROMTOOL: &str = "session.factory.runFromTool"; + /// `session.factory.resumeFromTool` + pub const SESSION_FACTORY_RESUMEFROMTOOL: &str = "session.factory.resumeFromTool"; /// `session.factory.getRun` pub const SESSION_FACTORY_GETRUN: &str = "session.factory.getRun"; /// `session.factory.listRuns` @@ -4879,6 +4883,9 @@ pub struct ExternalToolTextResultForLlmContentShellExit { pub cwd: Option, /// Exit code from the completed shell command pub exit_code: i64, + /// Path reported in the shell session's filesystem namespace when shell output exceeded the configured large-output threshold. + #[serde(skip_serializing_if = "Option::is_none")] + pub output_file_path: Option, /// Output associated with this shell command, if available. May be partial, truncated, or a preview; not guaranteed to be full output. #[serde(skip_serializing_if = "Option::is_none")] pub output_preview: Option, @@ -5584,6 +5591,12 @@ pub struct FactoryResumeRequest { /// Optional per-invocation resource ceiling overrides. #[serde(skip_serializing_if = "Option::is_none")] pub limits: Option, + /// Whether to emit factory phase names to the session transcript. + #[serde(skip_serializing_if = "Option::is_none")] + pub log_phase_names: Option, + /// Whether to notify the originating session when the factory completes. + #[serde(skip_serializing_if = "Option::is_none")] + pub notify_on_complete: Option, /// Factory run identifier. pub run_id: String, } @@ -5708,6 +5721,12 @@ pub struct RunOptions { /// Per-invocation resource ceiling overrides. #[serde(skip_serializing_if = "Option::is_none")] pub limits: Option, + /// Whether to emit factory phase names to the session transcript. + #[serde(skip_serializing_if = "Option::is_none")] + pub log_phase_names: Option, + /// Whether to notify the originating session when the factory completes. + #[serde(skip_serializing_if = "Option::is_none")] + pub notify_on_complete: Option, /// Run identifier whose journal and progress should seed this resumed run. #[serde(skip_serializing_if = "Option::is_none")] pub resume_from_run_id: Option, @@ -5733,6 +5752,70 @@ pub struct FactoryRunRequest { pub options: Option, } +/// Internal parameters for resuming a factory run from a tool. +/// +///

+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub(crate) struct FactoryToolResumeRequest { + /// Optional per-invocation resource ceiling overrides. + #[serde(skip_serializing_if = "Option::is_none")] + pub limits: Option, + /// Factory run identifier. + pub run_id: String, + /// Opaque identifier of the originating tool call. + #[serde(skip_serializing_if = "Option::is_none")] + pub tool_call_id: Option, +} + +/// Options for an internal tool-originated factory invocation. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub(crate) struct FactoryToolRunOptions { + /// Per-invocation resource ceiling overrides. + #[serde(skip_serializing_if = "Option::is_none")] + pub limits: Option, + /// Run identifier whose journal and progress should seed this resumed run. + #[serde(skip_serializing_if = "Option::is_none")] + pub resume_from_run_id: Option, +} + +/// Internal parameters for invoking a registered factory from a tool. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub(crate) struct FactoryToolRunRequest { + /// Factory input value. + pub args: serde_json::Value, + /// Registered factory name. + pub name: String, + /// Tool-originated factory invocation options. + #[doc(hidden)] + #[serde(skip_serializing_if = "Option::is_none")] + pub(crate) options: Option, + /// Opaque identifier of the originating tool call. + #[serde(skip_serializing_if = "Option::is_none")] + pub tool_call_id: Option, +} + /// Optional user prompt to combine with the fleet orchestration instructions. /// ///
@@ -19623,15 +19706,9 @@ pub struct ToolsGetBuiltinDescriptorsRequest { /// Whether tool descriptors should include authoring metadata. #[serde(skip_serializing_if = "Option::is_none")] pub include_author: Option, - /// Whether line numbers should be omitted from the view tool descriptor. - #[serde(skip_serializing_if = "Option::is_none")] - pub no_view_line_numbers: Option, /// Whether descriptors should favor fewer user-intervention prompts. #[serde(skip_serializing_if = "Option::is_none")] pub reduce_user_intervention: Option, - /// Whether shell commands may only run asynchronously. - #[serde(skip_serializing_if = "Option::is_none")] - pub shell_async_only_enabled: Option, /// Shell-specific names and description lines for shell tools. #[serde(skip_serializing_if = "Option::is_none")] pub shell_config: Option, @@ -22285,6 +22362,55 @@ pub struct SessionFactoryResumeResult { pub run: FactoryRunResult, } +/// Complete current or terminal factory run envelope. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionFactoryRunFromToolResult { + /// Error message for an errored run. + #[serde(skip_serializing_if = "Option::is_none")] + pub error: Option, + /// Machine-readable failure details for an errored run. + #[serde(skip_serializing_if = "Option::is_none")] + pub failure: Option, + /// Reason for a halted or cancelled run. + #[serde(skip_serializing_if = "Option::is_none")] + pub reason: Option, + /// Completed factory result. + #[serde(skip_serializing_if = "Option::is_none")] + pub result: Option, + /// Factory run identifier. + pub run_id: String, + /// Partial journal and progress snapshot for a halted, cancelled, or errored run. + #[serde(skip_serializing_if = "Option::is_none")] + pub snapshot: Option, + /// Current or terminal factory run status. + pub status: FactoryRunStatus, +} + +/// Resolved persisted factory identity and resumed run envelope. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionFactoryResumeFromToolResult { + /// Persisted factory name resolved for the resumed run. + pub factory_name: String, + /// Terminal resumed run envelope. + pub run: FactoryRunResult, +} + /// Complete current or terminal factory run envelope. /// ///
diff --git a/rust/src/generated/rpc.rs b/rust/src/generated/rpc.rs index 60bf9d804f..4d5b7f1538 100644 --- a/rust/src/generated/rpc.rs +++ b/rust/src/generated/rpc.rs @@ -4735,6 +4735,75 @@ impl<'a> SessionRpcFactory<'a> { Ok(serde_json::from_value(_value)?) } + /// Internal tool-originated factory invocation. + /// + /// Wire method: `session.factory.runFromTool`. + /// + /// # Parameters + /// + /// * `params` - Internal parameters for invoking a registered factory from a tool. + /// + /// # Returns + /// + /// Complete current or terminal factory run envelope. + /// + ///
+ /// + /// **Experimental.** This API is part of an experimental wire-protocol surface + /// and may change or be removed in future SDK or CLI releases. Pin both the + /// SDK and CLI versions if your code depends on it. + /// + ///
+ pub(crate) async fn run_from_tool( + &self, + params: FactoryToolRunRequest, + ) -> Result { + let mut wire_params = serde_json::to_value(params)?; + wire_params["sessionId"] = serde_json::Value::String(self.session.id().to_string()); + let _value = self + .session + .client() + .call(rpc_methods::SESSION_FACTORY_RUNFROMTOOL, Some(wire_params)) + .await?; + Ok(serde_json::from_value(_value)?) + } + + /// Internal tool-originated factory resume. + /// + /// Wire method: `session.factory.resumeFromTool`. + /// + /// # Parameters + /// + /// * `params` - Internal parameters for resuming a factory run from a tool. + /// + /// # Returns + /// + /// Resolved persisted factory identity and resumed run envelope. + /// + ///
+ /// + /// **Experimental.** This API is part of an experimental wire-protocol surface + /// and may change or be removed in future SDK or CLI releases. Pin both the + /// SDK and CLI versions if your code depends on it. + /// + ///
+ pub(crate) async fn resume_from_tool( + &self, + params: FactoryToolResumeRequest, + ) -> Result { + let mut wire_params = serde_json::to_value(params)?; + wire_params["sessionId"] = serde_json::Value::String(self.session.id().to_string()); + let _value = self + .session + .client() + .call( + rpc_methods::SESSION_FACTORY_RESUMEFROMTOOL, + Some(wire_params), + ) + .await?; + Ok(serde_json::from_value(_value)?) + } + /// Gets the current or settled envelope for a factory run. /// /// Wire method: `session.factory.getRun`. diff --git a/rust/src/generated/session_events.rs b/rust/src/generated/session_events.rs index a5b123d3b5..a14a5eab26 100644 --- a/rust/src/generated/session_events.rs +++ b/rust/src/generated/session_events.rs @@ -78,6 +78,42 @@ pub enum SessionEventType { SessionCompactionComplete, #[serde(rename = "session.task_complete")] SessionTaskComplete, + /// + ///
+ /// + /// **Experimental.** This type is part of an experimental wire-protocol surface + /// and may change or be removed in future SDK or CLI releases. + /// + ///
+ #[serde(rename = "session.fusion_route_started")] + SessionFusionRouteStarted, + /// + ///
+ /// + /// **Experimental.** This type is part of an experimental wire-protocol surface + /// and may change or be removed in future SDK or CLI releases. + /// + ///
+ #[serde(rename = "session.fusion_route_failed")] + SessionFusionRouteFailed, + /// + ///
+ /// + /// **Experimental.** This type is part of an experimental wire-protocol surface + /// and may change or be removed in future SDK or CLI releases. + /// + ///
+ #[serde(rename = "session.fusion_resolved")] + SessionFusionResolved, + /// + ///
+ /// + /// **Experimental.** This type is part of an experimental wire-protocol surface + /// and may change or be removed in future SDK or CLI releases. + /// + ///
+ #[serde(rename = "session.fusion_completed")] + SessionFusionCompleted, #[serde(rename = "user.message")] UserMessage, #[serde(rename = "pending_messages.modified")] @@ -90,6 +126,33 @@ pub enum SessionEventType { AgentInterrupted, #[serde(rename = "assistant.intent")] AssistantIntent, + /// + ///
+ /// + /// **Experimental.** This type is part of an experimental wire-protocol surface + /// and may change or be removed in future SDK or CLI releases. + /// + ///
+ #[serde(rename = "assistant.fusion_phase_started")] + AssistantFusionPhaseStarted, + /// + ///
+ /// + /// **Experimental.** This type is part of an experimental wire-protocol surface + /// and may change or be removed in future SDK or CLI releases. + /// + ///
+ #[serde(rename = "assistant.fusion_phase_completed")] + AssistantFusionPhaseCompleted, + /// + ///
+ /// + /// **Experimental.** This type is part of an experimental wire-protocol surface + /// and may change or be removed in future SDK or CLI releases. + /// + ///
+ #[serde(rename = "assistant.fusion_phase_failed")] + AssistantFusionPhaseFailed, #[serde(rename = "assistant.server_tool_progress")] AssistantServerToolProgress, #[serde(rename = "assistant.reasoning")] @@ -140,6 +203,8 @@ pub enum SessionEventType { SandboxDecision, #[serde(rename = "subagent.started")] SubagentStarted, + #[serde(rename = "subagent.configured")] + SubagentConfigured, #[serde(rename = "subagent.completed")] SubagentCompleted, #[serde(rename = "subagent.failed")] @@ -439,6 +504,42 @@ pub enum SessionEventData { SessionCompactionComplete(SessionCompactionCompleteData), #[serde(rename = "session.task_complete")] SessionTaskComplete(SessionTaskCompleteData), + /// + ///
+ /// + /// **Experimental.** This type is part of an experimental wire-protocol surface + /// and may change or be removed in future SDK or CLI releases. + /// + ///
+ #[serde(rename = "session.fusion_route_started")] + SessionFusionRouteStarted(SessionFusionRouteStartedData), + /// + ///
+ /// + /// **Experimental.** This type is part of an experimental wire-protocol surface + /// and may change or be removed in future SDK or CLI releases. + /// + ///
+ #[serde(rename = "session.fusion_route_failed")] + SessionFusionRouteFailed(SessionFusionRouteFailedData), + /// + ///
+ /// + /// **Experimental.** This type is part of an experimental wire-protocol surface + /// and may change or be removed in future SDK or CLI releases. + /// + ///
+ #[serde(rename = "session.fusion_resolved")] + SessionFusionResolved(SessionFusionResolvedData), + /// + ///
+ /// + /// **Experimental.** This type is part of an experimental wire-protocol surface + /// and may change or be removed in future SDK or CLI releases. + /// + ///
+ #[serde(rename = "session.fusion_completed")] + SessionFusionCompleted(SessionFusionCompletedData), #[serde(rename = "user.message")] UserMessage(UserMessageData), #[serde(rename = "pending_messages.modified")] @@ -451,6 +552,33 @@ pub enum SessionEventData { AgentInterrupted(AgentInterruptedData), #[serde(rename = "assistant.intent")] AssistantIntent(AssistantIntentData), + /// + ///
+ /// + /// **Experimental.** This type is part of an experimental wire-protocol surface + /// and may change or be removed in future SDK or CLI releases. + /// + ///
+ #[serde(rename = "assistant.fusion_phase_started")] + AssistantFusionPhaseStarted(AssistantFusionPhaseStartedData), + /// + ///
+ /// + /// **Experimental.** This type is part of an experimental wire-protocol surface + /// and may change or be removed in future SDK or CLI releases. + /// + ///
+ #[serde(rename = "assistant.fusion_phase_completed")] + AssistantFusionPhaseCompleted(AssistantFusionPhaseCompletedData), + /// + ///
+ /// + /// **Experimental.** This type is part of an experimental wire-protocol surface + /// and may change or be removed in future SDK or CLI releases. + /// + ///
+ #[serde(rename = "assistant.fusion_phase_failed")] + AssistantFusionPhaseFailed(AssistantFusionPhaseFailedData), #[serde(rename = "assistant.server_tool_progress")] AssistantServerToolProgress(AssistantServerToolProgressData), #[serde(rename = "assistant.reasoning")] @@ -501,6 +629,8 @@ pub enum SessionEventData { SandboxDecision(SandboxDecisionData), #[serde(rename = "subagent.started")] SubagentStarted(SubagentStartedData), + #[serde(rename = "subagent.configured")] + SubagentConfigured(SubagentConfiguredData), #[serde(rename = "subagent.completed")] SubagentCompleted(SubagentCompletedData), #[serde(rename = "subagent.failed")] @@ -1678,6 +1808,209 @@ pub struct SessionTaskCompleteData { pub summary: Option, } +/// Session event "session.fusion_route_started". Experimental transient signal that HydraFusion routing has started for an eligible turn. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionFusionRouteStartedData { + /// Identifier for this routing attempt before a durable Fusion turn exists. + pub attempt_id: String, + /// HydraFusion routing policy requested for the turn. + #[serde(skip_serializing_if = "Option::is_none")] + pub policy: Option, + /// Synthetic HydraFusion model selected for the session. + #[serde(skip_serializing_if = "Option::is_none")] + pub synthetic_model: Option, + /// Kind of turn being routed. + pub turn_kind: FusionTurnKind, +} + +/// Session event "session.fusion_route_failed". Experimental durable HydraFusion routing failure and the deterministic concrete fallback selected for the turn. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionFusionRouteFailedData { + /// Identifier of the routing attempt that failed. + pub attempt_id: String, + /// Provider or validation error detail, when available. + #[serde(skip_serializing_if = "Option::is_none")] + pub error_message: Option, + /// Concrete model selected as the deterministic fallback. + pub fallback_model: String, + /// HydraFusion routing policy requested for the turn. + pub policy: String, + /// Stable machine-readable reason for the routing failure. + pub reason: String, + /// Elapsed routing time in milliseconds before the failure. + #[serde(skip_serializing_if = "Option::is_none")] + pub routing_latency_ms: Option, + /// Synthetic HydraFusion model selected for the session. + pub synthetic_model: String, +} + +/// Durable server recommendation for subsequent HydraFusion turns. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct FusionFollowUpRecommendation { + /// Recommended routing action for the next compaction turn. + pub compaction_turn: FusionFollowUpAction, + /// Recommended routing action for the next user-message turn. + pub user_turn: FusionFollowUpAction, +} + +/// Validated HydraFusion routing capability scores. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct FusionScores { + /// Code-generation capability score returned by the authenticated router. + pub code_gen: f64, + /// Debugging capability score returned by the authenticated router. + pub debugging: f64, + /// Reasoning capability score returned by the authenticated router. + pub reasoning: f64, + /// Tool-use capability score returned by the authenticated router. + pub tool_use: f64, +} + +/// Session event "session.fusion_resolved". Experimental durable validated HydraFusion route and turn policy. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionFusionResolvedData { + /// Version of the validated HydraFusion event contract. + pub contract_version: i64, + /// Concrete model used when the planned primary model cannot execute. + pub fallback_model: String, + /// Router recommendation controlling reuse or rerouting on later turns. + #[serde(skip_serializing_if = "Option::is_none")] + pub follow_up: Option, + /// Concrete model recommended for eligible follow-up turns. + pub follow_up_model: String, + /// Stable identifier for the resolved HydraFusion turn. + pub fusion_id: String, + /// Version of the executable model universe used for selection. + #[serde(skip_serializing_if = "Option::is_none")] + pub model_universe_version: Option, + /// Validated orchestration pattern selected for the turn. + pub pattern: FusionPattern, + /// Version of the validated execution-plan format. + #[serde(skip_serializing_if = "Option::is_none")] + pub plan_version: Option, + /// HydraFusion routing policy used to resolve the plan. + pub policy: String, + /// Version of the local routing policy. + #[serde(skip_serializing_if = "Option::is_none")] + pub policy_version: Option, + /// Concrete model selected for the primary solver phase. + pub primary_model: String, + /// Router implementation that supplied the plan. + #[serde(skip_serializing_if = "Option::is_none")] + pub route_source: Option, + /// Elapsed time in milliseconds required to resolve and validate the route. + #[serde(skip_serializing_if = "Option::is_none")] + pub routing_latency_ms: Option, + /// Identifier of the local policy rule that matched. + #[serde(skip_serializing_if = "Option::is_none")] + pub rule_id: Option, + /// Zero-based index of the local policy rule that matched. + #[serde(skip_serializing_if = "Option::is_none")] + pub rule_index: Option, + /// Human-readable name of the local policy rule that matched. + #[serde(skip_serializing_if = "Option::is_none")] + pub rule_name: Option, + /// Validated capability scores used to select the route. + #[serde(skip_serializing_if = "Option::is_none")] + pub scores: Option, + /// Concrete model selected for the review or judge phase, when required. + pub secondary_model: Option, + /// Synthetic HydraFusion model selected for the session. + pub synthetic_model: String, + /// Identifier of the session turn associated with the route. + pub turn_id: String, +} + +/// Session event "session.fusion_completed". Experimental durable aggregate outcome of a HydraFusion turn. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionFusionCompletedData { + /// Total cached input tokens reported across all phases. + pub cached_tokens: i64, + /// Total tokens written to prompt cache across all phases. + #[serde(skip_serializing_if = "Option::is_none")] + pub cache_write_tokens: Option, + /// Idempotency identifier for the authoritative final commit. + pub commit_id: String, + /// Reason the turn used a degraded route, when applicable. + pub degraded_reason: Option, + /// Total elapsed execution time for the HydraFusion turn in milliseconds. + pub duration_ms: f64, + /// Concrete model that supplied the authoritative final content. + pub final_source_model: Option, + /// Phase whose output supplied the authoritative final content. + pub final_source_phase_id: Option, + /// Concrete model recommended for eligible follow-up turns. + pub follow_up_model: String, + /// Stable identifier for the completed HydraFusion turn. + pub fusion_id: String, + /// Total input tokens consumed across all phases. + pub input_tokens: i64, + /// Stable aggregate outcome of the HydraFusion turn. + pub outcome: String, + /// Total output tokens produced across all phases. + pub output_tokens: i64, + /// HydraFusion orchestration pattern executed for the turn. + pub pattern: FusionPattern, + /// Number of concrete phases attempted by the turn. + pub phase_count: i64, + /// Total concrete model requests made across all phases. + pub request_count: i64, + /// Synthetic HydraFusion model selected for the session. + pub synthetic_model: String, + /// Total normalized AI-unit cost reported across all phases, in nano-AIU. + pub total_nano_aiu: f64, + /// Identifier of the session turn associated with the completion. + pub turn_id: String, +} + /// Session event "user.message". Payload of `user.message` with displayed and model-transformed content, attachments, source/delivery metadata, mode, and telemetry IDs. #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] @@ -1802,6 +2135,163 @@ pub struct AssistantIntentData { pub intent: String, } +/// Session event "assistant.fusion_phase_started". Experimental transient HydraFusion phase/model/role signal. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct AssistantFusionPhaseStartedData { + /// Conversation scope in which the phase executes. + pub conversation_scope: FusionConversationScope, + /// Identifier of the HydraFusion turn containing the phase. + pub fusion_id: String, + /// Concrete model executing the phase. + pub model: String, + /// HydraFusion orchestration pattern containing the phase. + pub pattern: FusionPattern, + /// Stable identifier for the concrete phase. + pub phase_id: String, + /// Kind of phase being executed. + pub phase_kind: FusionPhaseKind, + /// Semantic role assigned to the phase. + pub role: String, +} + +/// Internal durable terminal request staged by a HydraFusion phase until an idempotent final commit selects it. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub(crate) struct FusionStagedTerminal { + pub arguments: String, + pub assistant_message: serde_json::Value, + pub phase_id: String, + pub tool_call_id: String, + pub tool_name: String, +} + +/// Aggregate concrete-model usage for one HydraFusion phase. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct FusionPhaseUsage { + /// Total cached input tokens reported for the phase. + pub cached_tokens: i64, + /// Total tokens written to prompt cache during the phase. + #[serde(skip_serializing_if = "Option::is_none")] + pub cache_write_tokens: Option, + /// Total input tokens consumed by the phase. + pub input_tokens: i64, + /// Total output tokens produced by the phase. + pub output_tokens: i64, + /// Number of concrete model requests made by the phase. + pub request_count: i64, + /// Total normalized AI-unit cost reported for the phase, in nano-AIU. + pub total_nano_aiu: f64, +} + +/// Session event "assistant.fusion_phase_completed". Experimental durable HydraFusion phase output and lossless replay checkpoint. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct AssistantFusionPhaseCompletedData { + /// Provider-normalized textual output produced by the phase. + pub content: String, + /// Conversation scope in which the phase executed. + pub conversation_scope: FusionConversationScope, + /// Elapsed execution time for the phase in milliseconds. + pub duration_ms: f64, + /// Identifier of the HydraFusion turn containing the phase. + pub fusion_id: String, + /// Concrete model that executed the phase. + pub model: String, + /// Stable identifier for the completed phase. + pub phase_id: String, + /// Kind of phase that completed. + pub phase_kind: FusionPhaseKind, + /// Exact provider-normalized message used to reconstruct canonical model history. + #[doc(hidden)] + #[serde(skip_serializing_if = "Option::is_none")] + pub(crate) projection_message: Option, + /// Projection action for the exact internal message. + #[doc(hidden)] + #[serde(skip_serializing_if = "Option::is_none")] + pub(crate) projection_mode: Option, + /// Semantic role assigned to the completed phase. + pub role: String, + /// Terminal request held outside canonical state until selected by the final commit. + #[doc(hidden)] + #[serde(skip_serializing_if = "Option::is_none")] + pub(crate) staged_terminal: Option, + /// Durable outcome status of the phase. + pub status: FusionPhaseStatus, + /// Aggregate concrete-model usage consumed by the phase. + pub usage: FusionPhaseUsage, + /// Structured judge or critic verdict, when the phase produces one. + pub verdict: Option, +} + +/// Session event "assistant.fusion_phase_failed". Experimental durable typed HydraFusion phase failure and degradation transition. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct AssistantFusionPhaseFailedData { + /// Conversation scope in which the phase executed. + pub conversation_scope: FusionConversationScope, + /// Identifier of the fallback phase used to continue the turn after degradation. + #[serde(skip_serializing_if = "Option::is_none")] + pub degraded_to_phase_id: Option, + /// Elapsed execution time before the phase failed, in milliseconds. + pub duration_ms: f64, + /// Provider or execution error detail, when available. + #[serde(skip_serializing_if = "Option::is_none")] + pub error_message: Option, + /// Identifier of the HydraFusion turn containing the phase. + pub fusion_id: String, + /// Concrete model that attempted the phase. + pub model: String, + /// Stable identifier for the failed phase. + pub phase_id: String, + /// Kind of phase that failed. + pub phase_kind: FusionPhaseKind, + /// Stable machine-readable reason for the phase failure. + pub reason: String, + /// Semantic role assigned to the failed phase. + pub role: String, + /// Durable outcome status of the phase. + pub status: FusionPhaseStatus, + /// Aggregate concrete-model usage consumed before the failure. + pub usage: FusionPhaseUsage, +} + /// Session event "assistant.server_tool_progress". Live progress signal for a provider-hosted server tool (e.g. hosted web search) while it runs, before the finalized serverTools envelope lands on the terminal assistant.message #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] @@ -1947,6 +2437,48 @@ pub struct Citations { pub spans: Vec, } +/// Experimental attribution linking an ordinary event to the HydraFusion turn, phase, and concrete source that produced it. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct FusionAttribution { + /// Idempotency identifier for the authoritative commit, when the event belongs to the selected output. + #[serde(skip_serializing_if = "Option::is_none")] + pub commit_id: Option, + /// Conversation scope in which the concrete phase executed. + #[serde(skip_serializing_if = "Option::is_none")] + pub conversation_scope: Option, + /// Stable identifier for the HydraFusion turn that produced the event. + pub fusion_id: String, + /// HydraFusion orchestration pattern selected for the turn. + pub pattern: String, + /// Identifier of the concrete phase that produced the event. + #[serde(skip_serializing_if = "Option::is_none")] + pub phase_id: Option, + /// Kind of concrete phase that produced the event. + #[serde(skip_serializing_if = "Option::is_none")] + pub phase_kind: Option, + /// HydraFusion routing policy used for the turn. + pub policy: String, + /// Semantic role assigned to the concrete phase. + #[serde(skip_serializing_if = "Option::is_none")] + pub role: Option, + /// Concrete model that produced the attributed event. + #[serde(skip_serializing_if = "Option::is_none")] + pub source_model: Option, + /// Phase whose output supplied the authoritative content, when different from the executing phase. + #[serde(skip_serializing_if = "Option::is_none")] + pub source_phase_id: Option, + /// Synthetic HydraFusion model selected for the session. + pub synthetic_model: String, +} + /// Neutral provider-tagged reasoning content blocks preserved verbatim for round-tripping /// ///
@@ -2051,6 +2583,16 @@ pub struct AssistantMessageData { /// Encrypted reasoning content from OpenAI models. Session-bound and stripped on resume. #[serde(skip_serializing_if = "Option::is_none")] pub encrypted_content: Option, + /// Experimental HydraFusion source attribution for this ordinary authoritative assistant message. + /// + ///
+ /// + /// **Experimental.** This type is part of an experimental wire-protocol surface + /// and may change or be removed in future SDK or CLI releases. + /// + ///
+ #[serde(skip_serializing_if = "Option::is_none")] + pub fusion: Option, /// CAPI interaction ID for correlating this message with upstream telemetry #[serde(skip_serializing_if = "Option::is_none")] pub interaction_id: Option, @@ -2277,6 +2819,16 @@ pub struct AssistantUsageData { #[doc(hidden)] #[serde(skip_serializing_if = "Option::is_none")] pub(crate) frontier_source: Option, + /// Experimental HydraFusion attribution for this concrete model call's usage. + /// + ///
+ /// + /// **Experimental.** This type is part of an experimental wire-protocol surface + /// and may change or be removed in future SDK or CLI releases. + /// + ///
+ #[serde(skip_serializing_if = "Option::is_none")] + pub fusion: Option, /// What initiated this API call (e.g., "sub-agent", "mcp-sampling"); absent for user-initiated calls #[serde(skip_serializing_if = "Option::is_none")] pub initiator: Option, @@ -2494,6 +3046,16 @@ pub struct ModelCallFailureData { /// Whether the failure originated from an API response or the request transport #[serde(skip_serializing_if = "Option::is_none")] pub failure_kind: Option, + /// Experimental HydraFusion attribution for this failed concrete model call. + /// + ///
+ /// + /// **Experimental.** This type is part of an experimental wire-protocol surface + /// and may change or be removed in future SDK or CLI releases. + /// + ///
+ #[serde(skip_serializing_if = "Option::is_none")] + pub fusion: Option, /// What initiated this API call (e.g., "sub-agent", "mcp-sampling"); absent for user-initiated calls #[serde(skip_serializing_if = "Option::is_none")] pub initiator: Option, @@ -2568,6 +3130,16 @@ pub struct ModelCallFinishedData { #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct ModelCallStartData { + /// Experimental HydraFusion attribution for this concrete model call. + /// + ///
+ /// + /// **Experimental.** This type is part of an experimental wire-protocol surface + /// and may change or be removed in future SDK or CLI releases. + /// + ///
+ #[serde(skip_serializing_if = "Option::is_none")] + pub fusion: Option, /// Model identifier used for this API call, when known #[serde(skip_serializing_if = "Option::is_none")] pub model: Option, @@ -2665,6 +3237,16 @@ pub struct ToolExecutionStartData { /// When true, the tool output should be displayed expanded (verbatim) in the CLI timeline #[serde(skip_serializing_if = "Option::is_none")] pub display_verbatim: Option, + /// Experimental HydraFusion attribution for this tool execution. + /// + ///
+ /// + /// **Experimental.** This type is part of an experimental wire-protocol surface + /// and may change or be removed in future SDK or CLI releases. + /// + ///
+ #[serde(skip_serializing_if = "Option::is_none")] + pub fusion: Option, /// Name of the MCP server hosting this tool, when the tool is an MCP tool #[serde(skip_serializing_if = "Option::is_none")] pub mcp_server_name: Option, @@ -2863,6 +3445,9 @@ pub struct ToolExecutionCompleteContentShellExit { pub cwd: Option, /// Exit code from the completed shell command pub exit_code: i64, + /// Path reported in the shell session's filesystem namespace when shell output exceeded the configured large-output threshold. + #[serde(skip_serializing_if = "Option::is_none")] + pub output_file_path: Option, /// Output associated with this shell command, if available. May be partial, truncated, or a preview; not guaranteed to be full output. #[serde(skip_serializing_if = "Option::is_none")] pub output_preview: Option, @@ -3173,6 +3758,16 @@ pub struct ToolExecutionCompleteData { /// Error details when the tool execution failed #[serde(skip_serializing_if = "Option::is_none")] pub error: Option, + /// Experimental HydraFusion attribution for this tool completion. + /// + ///
+ /// + /// **Experimental.** This type is part of an experimental wire-protocol surface + /// and may change or be removed in future SDK or CLI releases. + /// + ///
+ #[serde(skip_serializing_if = "Option::is_none")] + pub fusion: Option, /// CAPI interaction ID for correlating this tool execution with upstream telemetry #[serde(skip_serializing_if = "Option::is_none")] pub interaction_id: Option, @@ -3279,16 +3874,44 @@ pub struct SubagentStartedData { pub agent_display_name: String, /// Internal name of the sub-agent pub agent_name: String, + /// Type of the sub-agent selected at spawn time. + #[serde(skip_serializing_if = "Option::is_none")] + pub agent_type: Option, + /// Whether the sub-agent runs synchronously or in the background. + #[serde(skip_serializing_if = "Option::is_none")] + pub execution_mode: Option, /// Root id of the factory run that spawned this sub-agent, when it was spawned by one. #[serde(skip_serializing_if = "Option::is_none")] pub factory_run_id: Option, /// Model the sub-agent will run with, when known at start. #[serde(skip_serializing_if = "Option::is_none")] pub model: Option, + /// Task-registry ID of the spawning sub-agent. Absent when the root session spawned this child. + #[serde(skip_serializing_if = "Option::is_none")] + pub parent_id: Option, + /// Whether this sub-agent can be resumed. Currently always false. + #[serde(skip_serializing_if = "Option::is_none")] + pub resumable: Option, /// Tool call ID of the parent tool invocation that spawned this sub-agent pub tool_call_id: String, } +/// Session event "subagent.configured". Resolved runtime configuration for a configured sub-agent +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SubagentConfiguredData { + /// Resolved context tier, when configured for the model + #[serde(skip_serializing_if = "Option::is_none")] + pub context_tier: Option, + /// Resolved model the sub-agent will run with + pub model: String, + /// Whether the sub-agent accepts follow-up turns + pub multi_turn: bool, + /// Resolved reasoning effort, when configured for the model + #[serde(skip_serializing_if = "Option::is_none")] + pub reasoning_effort: Option, +} + /// Session event "subagent.completed". Sub-agent completion details for successful execution #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] @@ -3400,6 +4023,9 @@ pub struct HookStartData { /// Input data passed to the hook #[serde(skip_serializing_if = "Option::is_none")] pub input: Option, + /// Tool call ID of the parent tool invocation when this event originates from a sub-agent + #[serde(skip_serializing_if = "Option::is_none")] + pub parent_tool_call_id: Option, } /// Error details when the hook failed @@ -3430,6 +4056,9 @@ pub struct HookEndData { /// Output data produced by the hook #[serde(skip_serializing_if = "Option::is_none")] pub output: Option, + /// Tool call ID of the parent tool invocation when this event originates from a sub-agent + #[serde(skip_serializing_if = "Option::is_none")] + pub parent_tool_call_id: Option, /// Whether the hook completed successfully pub success: bool, } @@ -5999,6 +6628,75 @@ pub enum TaskCompletionOutcome { Unknown, } +/// Kind of turn for which HydraFusion routing is running. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum FusionTurnKind { + /// A user-message turn. + #[serde(rename = "user")] + User, + /// A conversation-compaction turn. + #[serde(rename = "compaction")] + Compaction, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, +} + +/// Server-recommended routing behavior for a later HydraFusion turn. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum FusionFollowUpAction { + /// Reuse the durable primary model without routing. + #[serde(rename = "reuse_primary")] + ReusePrimary, + /// Request a new routing decision. + #[serde(rename = "reroute")] + Reroute, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, +} + +/// Validated HydraFusion execution pattern. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum FusionPattern { + /// Run one primary solver phase. + #[serde(rename = "single")] + Single, + /// Run a primary phase, a judge, and an optional repair. + #[serde(rename = "cascade")] + Cascade, + /// Run a primary draft, a read-only critique, and a revision. + #[serde(rename = "critique")] + Critique, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, +} + /// The agent mode that was active when this message was sent #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] pub enum UserMessageAgentMode { @@ -6089,6 +6787,115 @@ pub enum ModelCallFailureTransport { Unknown, } +/// Conversation scope in which a HydraFusion phase executes. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum FusionConversationScope { + /// Canonical root conversation history. + #[serde(rename = "root")] + Root, + /// Isolated read-only review history that does not enter the root conversation. + #[serde(rename = "review")] + Review, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, +} + +/// HydraFusion phase kind. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum FusionPhaseKind { + /// Primary solver phase. + #[serde(rename = "primary")] + Primary, + /// Read-only cascade judge phase. + #[serde(rename = "judge")] + Judge, + /// Cascade repair phase. + #[serde(rename = "repair")] + Repair, + /// Initial critique-pattern draft phase. + #[serde(rename = "draft")] + Draft, + /// Read-only critique phase. + #[serde(rename = "critic")] + Critic, + /// Critique-pattern revision phase. + #[serde(rename = "revision")] + Revision, + /// Follow-up phase continuing from the resolved model. + #[serde(rename = "follow_up")] + FollowUp, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, +} + +/// How a durable phase checkpoint contributes its exact message to canonical root history. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum FusionProjectionMode { + /// Append the exact root message immediately. + #[serde(rename = "append")] + Append, + /// Hold a terminal message outside canonical history until the final commit selects it. + #[serde(rename = "staged")] + Staged, + /// Do not project the checkpoint into root history. + #[serde(rename = "none")] + None, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, +} + +/// Durable outcome status of a HydraFusion phase. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum FusionPhaseStatus { + /// The phase completed successfully. + #[serde(rename = "succeeded")] + Succeeded, + /// The phase failed. + #[serde(rename = "failed")] + Failed, + /// The phase was cancelled. + #[serde(rename = "cancelled")] + Cancelled, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, +} + /// Tool call type: "function" for standard tool calls, "custom" for grammar-based tool calls. Defaults to "function" when absent. #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] pub enum AssistantMessageToolRequestType { diff --git a/rust/tests/e2e/rewind.rs b/rust/tests/e2e/rewind.rs index 990c45091d..c998f96df6 100644 --- a/rust/tests/e2e/rewind.rs +++ b/rust/tests/e2e/rewind.rs @@ -13,6 +13,11 @@ const FILE_CONTENT: &str = "SDK rewind content"; #[tokio::test] async fn should_restore_tracked_file_and_conversation() { + // TODO(cli-1.0.81): Re-enable when Windows file-change tracking records built-in create tool writes. + if cfg!(windows) { + return; + } + super::support::with_shared_e2e_context( &E2E, "rewind", @@ -94,7 +99,7 @@ async fn should_restore_tracked_file_and_conversation() { async fn wait_for_rewind_points( session: &github_copilot_sdk::session::Session, ) -> HistoryListRewindPointsResult { - let deadline = tokio::time::Instant::now() + Duration::from_secs(10); + let deadline = tokio::time::Instant::now() + Duration::from_secs(30); loop { let result = session .rpc() @@ -102,12 +107,17 @@ async fn wait_for_rewind_points( .list_rewind_points() .await .expect("list rewind points"); - if result.unavailable_reason.is_none() { + if result.unavailable_reason.is_none() + && result + .points + .first() + .is_some_and(|point| point.can_restore_files && point.file_count == 1) + { return result; } assert!( tokio::time::Instant::now() < deadline, - "timed out waiting for rewind points" + "timed out waiting for a restorable rewind point: {result:?}" ); tokio::time::sleep(Duration::from_millis(100)).await; } diff --git a/test/harness/package-lock.json b/test/harness/package-lock.json index 5566e2e34c..9f29dc7c15 100644 --- a/test/harness/package-lock.json +++ b/test/harness/package-lock.json @@ -9,7 +9,7 @@ "version": "1.0.0", "license": "ISC", "devDependencies": { - "@github/copilot": "^1.0.81-11", + "@github/copilot": "^1.0.81", "@modelcontextprotocol/sdk": "^1.26.0", "@types/node": "^25.3.3", "@types/node-forge": "^1.3.14", @@ -472,8 +472,8 @@ } }, "node_modules/@github/copilot": { - "version": "1.0.81-11", - "integrity": "sha512-F7hZ6G6fhWH4uq862mbs2JE3nL0KIVBOc94/EFOdEjux3oHUQst9a06gKibEJS6VRULaYTXzatA1EAgNC9dzFA==", + "version": "1.0.81", + "integrity": "sha512-Yif+wnRY1rT6FMmxr+SMZCq60mBTTPvbAHGd42Jty9wf1ZmeTsJYAcaGDXC9oyNm3RYRc3wkL0MSscNiEZADAA==", "dev": true, "license": "SEE LICENSE IN LICENSE.md", "dependencies": { @@ -483,19 +483,19 @@ "copilot": "npm-loader.js" }, "optionalDependencies": { - "@github/copilot-darwin-arm64": "1.0.81-11", - "@github/copilot-darwin-x64": "1.0.81-11", - "@github/copilot-linux-arm64": "1.0.81-11", - "@github/copilot-linux-x64": "1.0.81-11", - "@github/copilot-linuxmusl-arm64": "1.0.81-11", - "@github/copilot-linuxmusl-x64": "1.0.81-11", - "@github/copilot-win32-arm64": "1.0.81-11", - "@github/copilot-win32-x64": "1.0.81-11" + "@github/copilot-darwin-arm64": "1.0.81", + "@github/copilot-darwin-x64": "1.0.81", + "@github/copilot-linux-arm64": "1.0.81", + "@github/copilot-linux-x64": "1.0.81", + "@github/copilot-linuxmusl-arm64": "1.0.81", + "@github/copilot-linuxmusl-x64": "1.0.81", + "@github/copilot-win32-arm64": "1.0.81", + "@github/copilot-win32-x64": "1.0.81" } }, "node_modules/@github/copilot-darwin-arm64": { - "version": "1.0.81-11", - "integrity": "sha512-3eLs71CLnJH9RNnESnv4esipZPeGXMlBxQeKXwZY+crwcW2RAR8YuovQyfoZ/5by1PLPbYrOjXNfQL6kXSisrA==", + "version": "1.0.81", + "integrity": "sha512-VKHJTwRVaNXOmSkMjuFAotZxWNsNLSz3ZEiB1vpUqOYT3AsGMPrj8MIwh64AGfoLa91n4GyotGVbxwnsW8+K4g==", "cpu": [ "arm64" ], @@ -510,8 +510,8 @@ } }, "node_modules/@github/copilot-darwin-x64": { - "version": "1.0.81-11", - "integrity": "sha512-GdFLiUC8UL9k6K+woG8AyL3zBafd0Br1TIPDV5iiZsyBtTe4g67FjL7DGCYDt6AN9G43jWdK0M0InoNDVq1K1A==", + "version": "1.0.81", + "integrity": "sha512-O8BHh9d9j86RokqSYhgX3D1mA4t6MZp5OOneL1n3TPR7J+bEq+Catp+UBVGsJdhsuJ1Oasfk/z59Wit9wnc22w==", "cpu": [ "x64" ], @@ -526,8 +526,8 @@ } }, "node_modules/@github/copilot-linux-arm64": { - "version": "1.0.81-11", - "integrity": "sha512-C4hcAow5CdaVJITbdtGqFgxWpW7TqwyCPNn8OtcbvdWeiKcfOBDrgkvbsezG98/5Ovs3HWxZRT4oZqCEmGF9Ww==", + "version": "1.0.81", + "integrity": "sha512-GhHDhRkeWM3IfuouVGrU8UuXF26kPlV6aINSZyLIkrjY2AG/rro7NXNPeMKYT07HksoAWiMUrjEel8R/bpPplg==", "cpu": [ "arm64" ], @@ -542,8 +542,8 @@ } }, "node_modules/@github/copilot-linux-x64": { - "version": "1.0.81-11", - "integrity": "sha512-izu0PwWx+wL4zxacO6cd6r0zMMMQ3pTz+2euWcAd7HCJ/CIR6+YYfjU3TI3TPBZ9zDLZGoxHKYYPfJ1ZbSTzEg==", + "version": "1.0.81", + "integrity": "sha512-qhoiWIqfpvHcajJ8AVKa+ibKjD5k8Nccd9sfAJt5AMYN+Rv/aSCZojmnUazYv5UJAwStaGzep+rmVbTiN+sWUQ==", "cpu": [ "x64" ], @@ -558,8 +558,8 @@ } }, "node_modules/@github/copilot-linuxmusl-arm64": { - "version": "1.0.81-11", - "integrity": "sha512-uWGUjaxOSMu6dKFWcTvXGUUp76vC3OV7nlSupecRkQ7gA3OxdrrB8rXE5/leC606Hk6oe9Wl7wKCH9EwuZ6afg==", + "version": "1.0.81", + "integrity": "sha512-sOwSiqIM5H3AeYCbFW3f36qvm+YWyPinHwNiJC2DzuwpX/ujg8Warwm1zuokOJV5iyHC8AQxbwQRQ7LRQx19sQ==", "cpu": [ "arm64" ], @@ -574,8 +574,8 @@ } }, "node_modules/@github/copilot-linuxmusl-x64": { - "version": "1.0.81-11", - "integrity": "sha512-BpWKd/iu1tTyuPR2zuPFs1pVOlley4yt/TXkn6/gVwt52VI0rUxEO+n77RVjDcyMxWaUApbad2VNRPIH77guCA==", + "version": "1.0.81", + "integrity": "sha512-9lbAC0jDtlGNagKz9DycgzovHk35lS1lP5xV+EW1dTChep9uQt7n00d7Ru7ErvHS79QMRJw9PkPhKL9Jyz9jiA==", "cpu": [ "x64" ], @@ -590,8 +590,8 @@ } }, "node_modules/@github/copilot-win32-arm64": { - "version": "1.0.81-11", - "integrity": "sha512-GOK3cACgD96m065uJxbgcXL7MQ1qm+wq1qtvGBpprY0r9wTYJG/rVCtayjYB6B57rnaBwPll3+PQ2J1qgfO57A==", + "version": "1.0.81", + "integrity": "sha512-LBUennWqLDcAuYP3HrO9iwhxCjNA97g9jJplte8bEGFWoYrtOEs2UlEscorbHCQHeBpXXSn1slsUBpLl1CrDBQ==", "cpu": [ "arm64" ], @@ -606,8 +606,8 @@ } }, "node_modules/@github/copilot-win32-x64": { - "version": "1.0.81-11", - "integrity": "sha512-u4K6UU2iGQJqQwsdHNilJBwiaC48PfMsWVFnTSlJD5lCYp9zCk1odrvrcmB3ARYinE/IcXLUosiHaM/ChR/pdA==", + "version": "1.0.81", + "integrity": "sha512-1O1F1OdMO5Z/S9IjD3dQVA8QAKCuPgOlfhSvI19QfTp5zeDRZb2vCAERosY9UrURinawCHEoWmrg3EalXc4zaQ==", "cpu": [ "x64" ], diff --git a/test/harness/package.json b/test/harness/package.json index e8868750a0..c048ec5f5b 100644 --- a/test/harness/package.json +++ b/test/harness/package.json @@ -14,7 +14,7 @@ "node": "^20.19.0 || >=22.12.0" }, "devDependencies": { - "@github/copilot": "^1.0.81-11", + "@github/copilot": "^1.0.81", "@modelcontextprotocol/sdk": "^1.26.0", "@types/node": "^25.3.3", "@types/node-forge": "^1.3.14", diff --git a/test/snapshots/builtin_tools/should_create_a_new_file.yaml b/test/snapshots/builtin_tools/should_create_a_new_file.yaml index bf9288cf01..8afe8b38b6 100644 --- a/test/snapshots/builtin_tools/should_create_a_new_file.yaml +++ b/test/snapshots/builtin_tools/should_create_a_new_file.yaml @@ -54,6 +54,6 @@ conversations: arguments: '{"path":"${workdir}/new_file.txt"}' - role: tool tool_call_id: toolcall_2 - content: 1. Created by test + content: Created by test - role: assistant content: ✓ Done! Created `new_file.txt` with content "Created by test" and confirmed the content matches. diff --git a/test/snapshots/builtin_tools/should_edit_a_file_successfully.yaml b/test/snapshots/builtin_tools/should_edit_a_file_successfully.yaml index 0f21418628..3f4e986906 100644 --- a/test/snapshots/builtin_tools/should_edit_a_file_successfully.yaml +++ b/test/snapshots/builtin_tools/should_edit_a_file_successfully.yaml @@ -57,9 +57,8 @@ conversations: - role: tool tool_call_id: toolcall_2 content: |- - 1. Hi Universe - 2. Goodbye World - 3. + Hi Universe + Goodbye World - role: assistant content: |- Done! The file now contains: diff --git a/test/snapshots/builtin_tools/should_read_file_with_line_range.yaml b/test/snapshots/builtin_tools/should_read_file_with_line_range.yaml index cd17d86708..601ae0f04c 100644 --- a/test/snapshots/builtin_tools/should_read_file_with_line_range.yaml +++ b/test/snapshots/builtin_tools/should_read_file_with_line_range.yaml @@ -45,9 +45,9 @@ conversations: - role: tool tool_call_id: toolcall_1 content: |- - 2. line2 - 3. line3 - 4. line4 + line2 + line3 + line4 - role: assistant content: |- Lines 2 through 4 of 'lines.txt' contain: diff --git a/test/snapshots/client_options/should_use_client_cwd_for_default_workingdirectory.yaml b/test/snapshots/client_options/should_use_client_cwd_for_default_workingdirectory.yaml index 6d9167e94a..469d091288 100644 --- a/test/snapshots/client_options/should_use_client_cwd_for_default_workingdirectory.yaml +++ b/test/snapshots/client_options/should_use_client_cwd_for_default_workingdirectory.yaml @@ -25,6 +25,6 @@ conversations: task. - role: tool tool_call_id: toolcall_1 - content: 1. I am in the client cwd + content: I am in the client cwd - role: assistant content: 'The file `marker.txt` says: "I am in the client cwd"' diff --git a/test/snapshots/event_fidelity/should_emit_events_in_correct_order_for_tool_using_conversation.yaml b/test/snapshots/event_fidelity/should_emit_events_in_correct_order_for_tool_using_conversation.yaml index 8ce730f0fb..c8f272e6b9 100644 --- a/test/snapshots/event_fidelity/should_emit_events_in_correct_order_for_tool_using_conversation.yaml +++ b/test/snapshots/event_fidelity/should_emit_events_in_correct_order_for_tool_using_conversation.yaml @@ -44,7 +44,7 @@ conversations: task. - role: tool tool_call_id: toolcall_1 - content: 1. Hello World + content: Hello World - role: assistant content: |- The file 'hello.txt' contains: diff --git a/test/snapshots/event_fidelity/should_emit_tool_execution_events_with_correct_fields.yaml b/test/snapshots/event_fidelity/should_emit_tool_execution_events_with_correct_fields.yaml index a6583a15ec..46fd7715ab 100644 --- a/test/snapshots/event_fidelity/should_emit_tool_execution_events_with_correct_fields.yaml +++ b/test/snapshots/event_fidelity/should_emit_tool_execution_events_with_correct_fields.yaml @@ -44,7 +44,7 @@ conversations: task. - role: tool tool_call_id: toolcall_1 - content: 1. test data + content: test data - role: assistant content: |- The file `data.txt` contains: diff --git a/test/snapshots/event_fidelity/should_preserve_message_order_in_getmessages_after_tool_use.yaml b/test/snapshots/event_fidelity/should_preserve_message_order_in_getmessages_after_tool_use.yaml index 2799cdec61..1797cc16b1 100644 --- a/test/snapshots/event_fidelity/should_preserve_message_order_in_getmessages_after_tool_use.yaml +++ b/test/snapshots/event_fidelity/should_preserve_message_order_in_getmessages_after_tool_use.yaml @@ -15,6 +15,6 @@ conversations: arguments: '{"path":"order.txt"}' - role: tool tool_call_id: toolcall_0 - content: 1. ORDER_CONTENT_42 + content: ORDER_CONTENT_42 - role: assistant content: The number in 'order.txt' is **42**. diff --git a/test/snapshots/hooks/invoke_both_hooks_for_single_tool_call.yaml b/test/snapshots/hooks/invoke_both_hooks_for_single_tool_call.yaml index 6a51857ab0..9ed9431545 100644 --- a/test/snapshots/hooks/invoke_both_hooks_for_single_tool_call.yaml +++ b/test/snapshots/hooks/invoke_both_hooks_for_single_tool_call.yaml @@ -44,7 +44,7 @@ conversations: task. - role: tool tool_call_id: toolcall_1 - content: 1. Testing both hooks! + content: Testing both hooks! - role: assistant content: |- The file **both.txt** contains: diff --git a/test/snapshots/hooks/invoke_post_tool_use_hook_after_model_runs_a_tool.yaml b/test/snapshots/hooks/invoke_post_tool_use_hook_after_model_runs_a_tool.yaml index 18b324f098..2a5f1ae446 100644 --- a/test/snapshots/hooks/invoke_post_tool_use_hook_after_model_runs_a_tool.yaml +++ b/test/snapshots/hooks/invoke_post_tool_use_hook_after_model_runs_a_tool.yaml @@ -44,7 +44,7 @@ conversations: task. - role: tool tool_call_id: toolcall_1 - content: 1. World from the test! + content: World from the test! - role: assistant content: |- The file `world.txt` contains: diff --git a/test/snapshots/hooks/invoke_pre_tool_use_hook_when_model_runs_a_tool.yaml b/test/snapshots/hooks/invoke_pre_tool_use_hook_when_model_runs_a_tool.yaml index 1ce0fe67a0..f695c60f3d 100644 --- a/test/snapshots/hooks/invoke_pre_tool_use_hook_when_model_runs_a_tool.yaml +++ b/test/snapshots/hooks/invoke_pre_tool_use_hook_when_model_runs_a_tool.yaml @@ -44,7 +44,7 @@ conversations: task. - role: tool tool_call_id: toolcall_1 - content: 1. Hello from the test! + content: Hello from the test! - role: assistant content: |- The file **hello.txt** contains: diff --git a/test/snapshots/hooks/should_invoke_both_pretooluse_and_posttooluse_hooks_for_a_single_tool_call.yaml b/test/snapshots/hooks/should_invoke_both_pretooluse_and_posttooluse_hooks_for_a_single_tool_call.yaml index 6a51857ab0..9ed9431545 100644 --- a/test/snapshots/hooks/should_invoke_both_pretooluse_and_posttooluse_hooks_for_a_single_tool_call.yaml +++ b/test/snapshots/hooks/should_invoke_both_pretooluse_and_posttooluse_hooks_for_a_single_tool_call.yaml @@ -44,7 +44,7 @@ conversations: task. - role: tool tool_call_id: toolcall_1 - content: 1. Testing both hooks! + content: Testing both hooks! - role: assistant content: |- The file **both.txt** contains: diff --git a/test/snapshots/hooks/should_invoke_both_pretooluse_and_posttooluse_hooks_for_single_tool_call.yaml b/test/snapshots/hooks/should_invoke_both_pretooluse_and_posttooluse_hooks_for_single_tool_call.yaml index 6a51857ab0..9ed9431545 100644 --- a/test/snapshots/hooks/should_invoke_both_pretooluse_and_posttooluse_hooks_for_single_tool_call.yaml +++ b/test/snapshots/hooks/should_invoke_both_pretooluse_and_posttooluse_hooks_for_single_tool_call.yaml @@ -44,7 +44,7 @@ conversations: task. - role: tool tool_call_id: toolcall_1 - content: 1. Testing both hooks! + content: Testing both hooks! - role: assistant content: |- The file **both.txt** contains: diff --git a/test/snapshots/hooks/should_invoke_posttooluse_hook_after_model_runs_a_tool.yaml b/test/snapshots/hooks/should_invoke_posttooluse_hook_after_model_runs_a_tool.yaml index 18b324f098..2a5f1ae446 100644 --- a/test/snapshots/hooks/should_invoke_posttooluse_hook_after_model_runs_a_tool.yaml +++ b/test/snapshots/hooks/should_invoke_posttooluse_hook_after_model_runs_a_tool.yaml @@ -44,7 +44,7 @@ conversations: task. - role: tool tool_call_id: toolcall_1 - content: 1. World from the test! + content: World from the test! - role: assistant content: |- The file `world.txt` contains: diff --git a/test/snapshots/hooks/should_invoke_pretooluse_hook_when_model_runs_a_tool.yaml b/test/snapshots/hooks/should_invoke_pretooluse_hook_when_model_runs_a_tool.yaml index 1ce0fe67a0..f695c60f3d 100644 --- a/test/snapshots/hooks/should_invoke_pretooluse_hook_when_model_runs_a_tool.yaml +++ b/test/snapshots/hooks/should_invoke_pretooluse_hook_when_model_runs_a_tool.yaml @@ -44,7 +44,7 @@ conversations: task. - role: tool tool_call_id: toolcall_1 - content: 1. Hello from the test! + content: Hello from the test! - role: assistant content: |- The file **hello.txt** contains: diff --git a/test/snapshots/multi_turn/should_handle_file_creation_then_reading_across_turns.yaml b/test/snapshots/multi_turn/should_handle_file_creation_then_reading_across_turns.yaml index 0d79c3e1ab..583366363a 100644 --- a/test/snapshots/multi_turn/should_handle_file_creation_then_reading_across_turns.yaml +++ b/test/snapshots/multi_turn/should_handle_file_creation_then_reading_across_turns.yaml @@ -83,7 +83,7 @@ conversations: arguments: '{"path":"${workdir}/greeting.txt"}' - role: tool tool_call_id: toolcall_2 - content: 1. Hello from multi-turn test + content: Hello from multi-turn test - role: assistant content: |- The exact contents of `greeting.txt` are: diff --git a/test/snapshots/multi_turn/should_use_tool_results_from_previous_turns.yaml b/test/snapshots/multi_turn/should_use_tool_results_from_previous_turns.yaml index b930bb46ac..96dc365c65 100644 --- a/test/snapshots/multi_turn/should_use_tool_results_from_previous_turns.yaml +++ b/test/snapshots/multi_turn/should_use_tool_results_from_previous_turns.yaml @@ -44,7 +44,7 @@ conversations: task. - role: tool tool_call_id: toolcall_1 - content: 1. The magic number is 42. + content: The magic number is 42. - role: assistant content: The magic number is **42**. - messages: @@ -69,7 +69,7 @@ conversations: content: Tool 'report_intent' does not exist. - role: tool tool_call_id: toolcall_1 - content: 1. The magic number is 42. + content: The magic number is 42. - role: assistant content: The magic number is **42**. - role: user diff --git a/test/snapshots/permissions/permission_handler_for_write_operations.yaml b/test/snapshots/permissions/permission_handler_for_write_operations.yaml index a4ede6fcb1..3f05a8c6de 100644 --- a/test/snapshots/permissions/permission_handler_for_write_operations.yaml +++ b/test/snapshots/permissions/permission_handler_for_write_operations.yaml @@ -47,7 +47,7 @@ conversations: task. - role: tool tool_call_id: toolcall_1 - content: 1. original content + content: original content - role: assistant content: "Now I'll replace 'original' with 'modified':" - role: assistant @@ -82,7 +82,7 @@ conversations: task. - role: tool tool_call_id: toolcall_1 - content: 1. original content + content: original content - role: assistant content: "Now I'll replace 'original' with 'modified':" tool_calls: diff --git a/test/snapshots/permissions/should_invoke_permission_handler_for_write_operations.yaml b/test/snapshots/permissions/should_invoke_permission_handler_for_write_operations.yaml index a4ede6fcb1..3f05a8c6de 100644 --- a/test/snapshots/permissions/should_invoke_permission_handler_for_write_operations.yaml +++ b/test/snapshots/permissions/should_invoke_permission_handler_for_write_operations.yaml @@ -47,7 +47,7 @@ conversations: task. - role: tool tool_call_id: toolcall_1 - content: 1. original content + content: original content - role: assistant content: "Now I'll replace 'original' with 'modified':" - role: assistant @@ -82,7 +82,7 @@ conversations: task. - role: tool tool_call_id: toolcall_1 - content: 1. original content + content: original content - role: assistant content: "Now I'll replace 'original' with 'modified':" tool_calls: diff --git a/test/snapshots/session/should_send_with_file_attachment.yaml b/test/snapshots/session/should_send_with_file_attachment.yaml index 23e05d946b..2e8e4d1d2d 100644 --- a/test/snapshots/session/should_send_with_file_attachment.yaml +++ b/test/snapshots/session/should_send_with_file_attachment.yaml @@ -58,7 +58,7 @@ conversations: task. - role: tool tool_call_id: toolcall_1 - content: 1. FILE_ATTACHMENT_SENTINEL + content: FILE_ATTACHMENT_SENTINEL - role: assistant content: |- The file contains: diff --git a/test/snapshots/session_config/should_accept_message_attachments.yaml b/test/snapshots/session_config/should_accept_message_attachments.yaml index e9fbabb05e..5525d1fb04 100644 --- a/test/snapshots/session_config/should_accept_message_attachments.yaml +++ b/test/snapshots/session_config/should_accept_message_attachments.yaml @@ -61,7 +61,7 @@ conversations: task. - role: tool tool_call_id: toolcall_1 - content: 1. This file is attached + content: This file is attached - role: assistant content: |- The attached file contains a single line of text that says: "This file is attached" diff --git a/test/snapshots/session_config/should_apply_workingdirectory_on_session_resume.yaml b/test/snapshots/session_config/should_apply_workingdirectory_on_session_resume.yaml index 52cc114f94..9d3dd78ff1 100644 --- a/test/snapshots/session_config/should_apply_workingdirectory_on_session_resume.yaml +++ b/test/snapshots/session_config/should_apply_workingdirectory_on_session_resume.yaml @@ -25,7 +25,7 @@ conversations: task. - role: tool tool_call_id: toolcall_1 - content: 1. I am in the resume working directory + content: I am in the resume working directory - role: assistant content: |- The file `resume-marker.txt` says: diff --git a/test/snapshots/session_config/should_use_workingdirectory_for_tool_execution.yaml b/test/snapshots/session_config/should_use_workingdirectory_for_tool_execution.yaml index 18dfab04e6..40000d491b 100644 --- a/test/snapshots/session_config/should_use_workingdirectory_for_tool_execution.yaml +++ b/test/snapshots/session_config/should_use_workingdirectory_for_tool_execution.yaml @@ -44,6 +44,6 @@ conversations: task. - role: tool tool_call_id: toolcall_1 - content: 1. I am in the subdirectory + content: I am in the subdirectory - role: assistant content: 'The file marker.txt says: "I am in the subdirectory"' diff --git a/test/snapshots/subagent_hooks/should_invoke_pretooluse_and_posttooluse_hooks_for_sub_agent_tool_calls.yaml b/test/snapshots/subagent_hooks/should_invoke_pretooluse_and_posttooluse_hooks_for_sub_agent_tool_calls.yaml index c905726ee3..f920f8705b 100644 --- a/test/snapshots/subagent_hooks/should_invoke_pretooluse_and_posttooluse_hooks_for_sub_agent_tool_calls.yaml +++ b/test/snapshots/subagent_hooks/should_invoke_pretooluse_and_posttooluse_hooks_for_sub_agent_tool_calls.yaml @@ -56,7 +56,7 @@ conversations: arguments: '{"path":"${workdir}/subagent-test.txt"}' - role: tool tool_call_id: toolcall_0 - content: 1. Hello from subagent test! + content: Hello from subagent test! - role: assistant content: |- The complete contents of the file "subagent-test.txt" are: diff --git a/test/snapshots/system_message_transform/should_apply_transform_modifications_to_section_content.yaml b/test/snapshots/system_message_transform/should_apply_transform_modifications_to_section_content.yaml index 4b7c058b27..98e57919c6 100644 --- a/test/snapshots/system_message_transform/should_apply_transform_modifications_to_section_content.yaml +++ b/test/snapshots/system_message_transform/should_apply_transform_modifications_to_section_content.yaml @@ -26,7 +26,7 @@ conversations: task. - role: tool tool_call_id: toolcall_1 - content: 1. Hello! + content: Hello! - role: assistant content: |- The file **hello.txt** contains: diff --git a/test/snapshots/system_message_transform/should_invoke_transform_callbacks_with_section_content.yaml b/test/snapshots/system_message_transform/should_invoke_transform_callbacks_with_section_content.yaml index 0b1d9755f0..c54f25e2aa 100644 --- a/test/snapshots/system_message_transform/should_invoke_transform_callbacks_with_section_content.yaml +++ b/test/snapshots/system_message_transform/should_invoke_transform_callbacks_with_section_content.yaml @@ -47,6 +47,6 @@ conversations: task. - role: tool tool_call_id: toolcall_1 - content: 1. Hello transform! + content: Hello transform! - role: assistant content: 'The file `test.txt` contains: **"Hello transform!"**' diff --git a/test/snapshots/system_message_transform/should_work_with_static_overrides_and_transforms_together.yaml b/test/snapshots/system_message_transform/should_work_with_static_overrides_and_transforms_together.yaml index 0681b569dd..32d6367390 100644 --- a/test/snapshots/system_message_transform/should_work_with_static_overrides_and_transforms_together.yaml +++ b/test/snapshots/system_message_transform/should_work_with_static_overrides_and_transforms_together.yaml @@ -47,7 +47,7 @@ conversations: task. - role: tool tool_call_id: toolcall_1 - content: 1. Combo test! + content: Combo test! - role: assistant content: |- The file `combo.txt` contains: diff --git a/test/snapshots/tools/invokes_built_in_tools.yaml b/test/snapshots/tools/invokes_built_in_tools.yaml index 068cc4accf..0fba134424 100644 --- a/test/snapshots/tools/invokes_built_in_tools.yaml +++ b/test/snapshots/tools/invokes_built_in_tools.yaml @@ -15,6 +15,6 @@ conversations: arguments: '{"path":"${workdir}/README.md"}' - role: tool tool_call_id: toolcall_0 - content: "1. # ELIZA, the only chatbot you'll ever need" + content: "# ELIZA, the only chatbot you'll ever need" - role: assistant content: "The first line of README.md is: `# ELIZA, the only chatbot you'll ever need`" From 8715c1372abcf0135415710b6b04f3743f372a70 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 28 Aug 2026 03:40:54 +0000 Subject: [PATCH 28/32] Update @github/copilot to 1.0.82-0 (#2434) * Update @github/copilot to 1.0.82-0 - Updated nodejs and test harness dependencies - Re-ran code generators - Formatted generated code * Fix telemetry trace assertions Scope trace consistency checks to the agent turn so unrelated process telemetry may use separate traces. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Dmytro Struk <13853051+dmytrostruk@users.noreply.github.com> Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- dotnet/src/Generated/Rpc.cs | 2 +- dotnet/src/Generated/SessionEvents.cs | 5 ++ dotnet/test/E2E/TelemetryExportE2ETests.cs | 7 +- go/internal/e2e/telemetry_e2e_test.go | 21 +++--- go/rpc/zrpc.go | 24 +++---- go/rpc/zsession_events.go | 2 + java/pom.xml | 2 +- java/scripts/codegen/package-lock.json | 72 +++++++++---------- java/scripts/codegen/package.json | 2 +- .../SessionCompactionCompleteEvent.java | 2 + .../copilot/generated/rpc/SandboxConfig.java | 2 +- nodejs/package-lock.json | 54 +++++++------- nodejs/package.json | 2 +- nodejs/samples/package-lock.json | 2 +- nodejs/src/generated/rpc.ts | 2 +- nodejs/src/generated/session-events.ts | 4 ++ nodejs/test/e2e/telemetry.e2e.test.ts | 9 ++- python/copilot/generated/rpc.py | 24 +++---- python/copilot/generated/session_events.py | 5 ++ python/e2e/test_telemetry_e2e.py | 7 +- rust/src/generated/api_types.rs | 2 +- rust/src/generated/session_events.rs | 3 + rust/tests/e2e/telemetry.rs | 12 ++-- test/harness/package-lock.json | 54 +++++++------- test/harness/package.json | 2 +- 25 files changed, 169 insertions(+), 154 deletions(-) diff --git a/dotnet/src/Generated/Rpc.cs b/dotnet/src/Generated/Rpc.cs index 5e658452ae..f3c1a57e6e 100644 --- a/dotnet/src/Generated/Rpc.cs +++ b/dotnet/src/Generated/Rpc.cs @@ -11172,7 +11172,7 @@ public sealed class SandboxConfig [JsonPropertyName("addCurrentWorkingDirectory")] public bool? AddCurrentWorkingDirectory { get; set; } - /// Whether to auto-grant read access to the tool directories discovered on PATH and in toolchain environment variables (GOROOT, CARGO_HOME, JAVA_HOME, VIRTUAL_ENV, and similar), and to common developer-tool caches, registries, and toolchains in their default home locations (cargo, go, npm, Maven, and more), plus read-write access to (and up-front creation of) the scratch caches builds write on every run (go-build, ccache, sccache, Gradle caches, Cargo lock/tracker files), so builds work without extra configuration; a relocated CARGO_HOME additionally gets its Cargo lock files granted read-write. Set to false to disable every grant listed above: user-installed toolchains (rustup, nvm, pyenv, conda, pipx) then need explicit userPolicy.filesystem entries — readonlyPaths to read them, plus readwriteFiles for a relocated CARGO_HOME's .package-cache and .global-cache, which Cargo locks on every build. Only these developer-tool grants are affected: the working directory (see addCurrentWorkingDirectory), temporary storage, session log paths, and system locations follow their own rules and stay granted, so commands still run. Default: true (enabled by default; set to false to opt out). + /// Whether to auto-grant read access to tool directories discovered on PATH and in toolchain environment variables (GOROOT, JAVA_HOME, VIRTUAL_ENV, and similar), and to common developer-tool caches, config, and toolchains. Writable grants cover scratch caches, the Unix GitHub CLI cache, and Cargo's registry, git store, and lock/tracker files. A relocated CARGO_HOME gets the same narrow split: registry and git are read-write; bin is read-only; the home root, config.toml, and credentials.toml stay ungranted. Set to false to disable every grant listed above; user-installed toolchains and caches then need explicit userPolicy.filesystem readonlyPaths and readwritePaths entries. The working directory (see addCurrentWorkingDirectory), temporary storage, session log paths, and system locations follow their own rules and stay granted. Default: true (enabled by default; set to false to opt out). [JsonPropertyName("allowDevToolAccess")] public bool? AllowDevToolAccess { get; set; } diff --git a/dotnet/src/Generated/SessionEvents.cs b/dotnet/src/Generated/SessionEvents.cs index d7eb108ed3..b4351c8f2e 100644 --- a/dotnet/src/Generated/SessionEvents.cs +++ b/dotnet/src/Generated/SessionEvents.cs @@ -2722,6 +2722,11 @@ public sealed partial class SessionCompactionStartData /// Conversation compaction results including success status, metrics, and optional error details. public sealed partial class SessionCompactionCompleteData { + /// Canonical model identifier used for model-specific behavior when replaying compaction. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("behaviorModelId")] + public string? BehaviorModelId { get; set; } + /// Checkpoint snapshot number created for recovery. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("checkpointNumber")] diff --git a/dotnet/test/E2E/TelemetryExportE2ETests.cs b/dotnet/test/E2E/TelemetryExportE2ETests.cs index 48e3b53ad6..e2ad447e26 100644 --- a/dotnet/test/E2E/TelemetryExportE2ETests.cs +++ b/dotnet/test/E2E/TelemetryExportE2ETests.cs @@ -54,9 +54,6 @@ public async Task Should_Export_File_Telemetry_For_Sdk_Interactions() Assert.NotEmpty(spans); Assert.All(spans, span => Assert.Equal(sourceName, GetInstrumentationScopeName(span))); - // All spans for one SDK turn must share the same trace id and must not be in error state. - var traceIds = spans.Select(GetTraceId).Where(id => !string.IsNullOrEmpty(id)).Distinct().ToList(); - Assert.Single(traceIds); Assert.All(spans, span => Assert.NotEqual(2, GetStatusCode(span))); var invokeAgentSpan = AssertSpanWithOperation(spans, "invoke_agent"); @@ -65,10 +62,13 @@ public async Task Should_Export_File_Telemetry_For_Sdk_Interactions() "invoke_agent should be the root of the SDK turn trace."); var invokeAgentSpanId = GetSpanId(invokeAgentSpan); Assert.False(string.IsNullOrEmpty(invokeAgentSpanId)); + var invokeAgentTraceId = GetTraceId(invokeAgentSpan); + Assert.False(string.IsNullOrEmpty(invokeAgentTraceId)); var chatSpans = spans.Where(span => IsSpanWithOperation(span, "chat")).ToList(); Assert.NotEmpty(chatSpans); Assert.All(chatSpans, chat => Assert.Equal(invokeAgentSpanId, GetParentSpanId(chat))); + Assert.All(chatSpans, chat => Assert.Equal(invokeAgentTraceId, GetTraceId(chat))); Assert.Contains( chatSpans, span => (GetStringAttribute(span, "gen_ai.input.messages") ?? string.Empty).Contains(prompt, StringComparison.Ordinal)); @@ -78,6 +78,7 @@ public async Task Should_Export_File_Telemetry_For_Sdk_Interactions() var toolSpan = AssertSpanWithOperation(spans, "execute_tool"); Assert.Equal(invokeAgentSpanId, GetParentSpanId(toolSpan)); + Assert.Equal(invokeAgentTraceId, GetTraceId(toolSpan)); Assert.Equal(toolName, GetStringAttribute(toolSpan, "gen_ai.tool.name")); Assert.False(string.IsNullOrWhiteSpace(GetStringAttribute(toolSpan, "gen_ai.tool.call.id")), "execute_tool span should carry gen_ai.tool.call.id."); diff --git a/go/internal/e2e/telemetry_e2e_test.go b/go/internal/e2e/telemetry_e2e_test.go index 4567817fd9..77f8bec8ea 100644 --- a/go/internal/e2e/telemetry_e2e_test.go +++ b/go/internal/e2e/telemetry_e2e_test.go @@ -96,17 +96,6 @@ func TestTelemetryE2E(t *testing.T) { } } - traceIDs := map[string]struct{}{} - for _, span := range spans { - id := stringProp(span, "traceId") - if id != "" { - traceIDs[id] = struct{}{} - } - } - if len(traceIDs) != 1 { - t.Errorf("Expected exactly 1 trace id across spans, got %d (%v)", len(traceIDs), traceIDs) - } - invokeAgent := findSpanWithOperation(spans, "invoke_agent") if invokeAgent == nil { t.Fatal("Expected an invoke_agent span") @@ -121,6 +110,10 @@ func TestTelemetryE2E(t *testing.T) { if invokeAgentSpanID == "" { t.Fatal("invoke_agent span has empty spanId") } + invokeAgentTraceID := stringProp(invokeAgent, "traceId") + if invokeAgentTraceID == "" { + t.Fatal("invoke_agent span has empty traceId") + } var chatSpans []map[string]any for _, span := range spans { @@ -135,6 +128,9 @@ func TestTelemetryE2E(t *testing.T) { if got := stringProp(chat, "parentSpanId"); got != invokeAgentSpanID { t.Errorf("Expected chat span parentSpanId=%q, got %q", invokeAgentSpanID, got) } + if got := stringProp(chat, "traceId"); got != invokeAgentTraceID { + t.Errorf("Expected chat span traceId=%q, got %q", invokeAgentTraceID, got) + } } var sawPromptInput, sawDoneOutput bool for _, chat := range chatSpans { @@ -159,6 +155,9 @@ func TestTelemetryE2E(t *testing.T) { if got := stringProp(toolSpan, "parentSpanId"); got != invokeAgentSpanID { t.Errorf("Expected execute_tool parentSpanId=%q, got %q", invokeAgentSpanID, got) } + if got := stringProp(toolSpan, "traceId"); got != invokeAgentTraceID { + t.Errorf("Expected execute_tool traceId=%q, got %q", invokeAgentTraceID, got) + } if got := stringAttr(toolSpan, "gen_ai.tool.name"); got != toolName { t.Errorf("Expected gen_ai.tool.name=%q, got %q", toolName, got) } diff --git a/go/rpc/zrpc.go b/go/rpc/zrpc.go index 25da3dea4d..edd5f925fe 100644 --- a/go/rpc/zrpc.go +++ b/go/rpc/zrpc.go @@ -10213,20 +10213,16 @@ type RuntimeShutdownResult struct { type SandboxConfig struct { // Whether to auto-add the current working directory to readwritePaths. Default: true. AddCurrentWorkingDirectory *bool `json:"addCurrentWorkingDirectory,omitempty"` - // Whether to auto-grant read access to the tool directories discovered on PATH and in - // toolchain environment variables (GOROOT, CARGO_HOME, JAVA_HOME, VIRTUAL_ENV, and - // similar), and to common developer-tool caches, registries, and toolchains in their - // default home locations (cargo, go, npm, Maven, and more), plus read-write access to (and - // up-front creation of) the scratch caches builds write on every run (go-build, ccache, - // sccache, Gradle caches, Cargo lock/tracker files), so builds work without extra - // configuration; a relocated CARGO_HOME additionally gets its Cargo lock files granted - // read-write. Set to false to disable every grant listed above: user-installed toolchains - // (rustup, nvm, pyenv, conda, pipx) then need explicit userPolicy.filesystem entries — - // readonlyPaths to read them, plus readwriteFiles for a relocated CARGO_HOME's - // .package-cache and .global-cache, which Cargo locks on every build. Only these - // developer-tool grants are affected: the working directory (see - // addCurrentWorkingDirectory), temporary storage, session log paths, and system locations - // follow their own rules and stay granted, so commands still run. Default: true (enabled by + // Whether to auto-grant read access to tool directories discovered on PATH and in toolchain + // environment variables (GOROOT, JAVA_HOME, VIRTUAL_ENV, and similar), and to common + // developer-tool caches, config, and toolchains. Writable grants cover scratch caches, the + // Unix GitHub CLI cache, and Cargo's registry, git store, and lock/tracker files. A + // relocated CARGO_HOME gets the same narrow split: registry and git are read-write; bin is + // read-only; the home root, config.toml, and credentials.toml stay ungranted. Set to false + // to disable every grant listed above; user-installed toolchains and caches then need + // explicit userPolicy.filesystem readonlyPaths and readwritePaths entries. The working + // directory (see addCurrentWorkingDirectory), temporary storage, session log paths, and + // system locations follow their own rules and stay granted. Default: true (enabled by // default; set to false to opt out). AllowDevToolAccess *bool `json:"allowDevToolAccess,omitempty"` // Credential-injection capability flags. diff --git a/go/rpc/zsession_events.go b/go/rpc/zsession_events.go index 1d0110b7a0..7f24ab6283 100644 --- a/go/rpc/zsession_events.go +++ b/go/rpc/zsession_events.go @@ -532,6 +532,8 @@ func (*SessionContextClearedData) Type() SessionEventType { // Conversation compaction results including success status, metrics, and optional error details type SessionCompactionCompleteData struct { + // Canonical model identifier used for model-specific behavior when replaying compaction + BehaviorModelID *string `json:"behaviorModelId,omitempty"` // Checkpoint snapshot number created for recovery CheckpointNumber *int64 `json:"checkpointNumber,omitempty"` // File path where the checkpoint was stored diff --git a/java/pom.xml b/java/pom.xml index bb7a7bc0ae..c5a17d66d8 100644 --- a/java/pom.xml +++ b/java/pom.xml @@ -63,7 +63,7 @@ DO NOT EDIT MANUALLY. Updated by the update-copilot-dependency workflow. --> - ^1.0.81 + ^1.0.82-0 true diff --git a/java/scripts/codegen/package-lock.json b/java/scripts/codegen/package-lock.json index e40b3b2907..985ae5d5db 100644 --- a/java/scripts/codegen/package-lock.json +++ b/java/scripts/codegen/package-lock.json @@ -6,7 +6,7 @@ "": { "name": "copilot-sdk-java-codegen", "dependencies": { - "@github/copilot": "^1.0.81", + "@github/copilot": "^1.0.82-0", "json-schema": "^0.4.0", "tsx": "^4.23.12" } @@ -428,9 +428,9 @@ } }, "node_modules/@github/copilot": { - "version": "1.0.81", - "resolved": "https://registry.npmjs.org/@github/copilot/-/copilot-1.0.81.tgz", - "integrity": "sha512-Yif+wnRY1rT6FMmxr+SMZCq60mBTTPvbAHGd42Jty9wf1ZmeTsJYAcaGDXC9oyNm3RYRc3wkL0MSscNiEZADAA==", + "version": "1.0.82-0", + "resolved": "https://registry.npmjs.org/@github/copilot/-/copilot-1.0.82-0.tgz", + "integrity": "sha512-fSZVNAzFFYaS6btYD0+cKF7SrrtOklhpkPs/cIMZY7Fgxoa6rfZrlTWXlQNgNIdwLp51XxwTfxnZO+D9+MQ5yg==", "license": "SEE LICENSE IN LICENSE.md", "dependencies": { "detect-libc": "^2.1.2" @@ -439,20 +439,20 @@ "copilot": "npm-loader.js" }, "optionalDependencies": { - "@github/copilot-darwin-arm64": "1.0.81", - "@github/copilot-darwin-x64": "1.0.81", - "@github/copilot-linux-arm64": "1.0.81", - "@github/copilot-linux-x64": "1.0.81", - "@github/copilot-linuxmusl-arm64": "1.0.81", - "@github/copilot-linuxmusl-x64": "1.0.81", - "@github/copilot-win32-arm64": "1.0.81", - "@github/copilot-win32-x64": "1.0.81" + "@github/copilot-darwin-arm64": "1.0.82-0", + "@github/copilot-darwin-x64": "1.0.82-0", + "@github/copilot-linux-arm64": "1.0.82-0", + "@github/copilot-linux-x64": "1.0.82-0", + "@github/copilot-linuxmusl-arm64": "1.0.82-0", + "@github/copilot-linuxmusl-x64": "1.0.82-0", + "@github/copilot-win32-arm64": "1.0.82-0", + "@github/copilot-win32-x64": "1.0.82-0" } }, "node_modules/@github/copilot-darwin-arm64": { - "version": "1.0.81", - "resolved": "https://registry.npmjs.org/@github/copilot-darwin-arm64/-/copilot-darwin-arm64-1.0.81.tgz", - "integrity": "sha512-VKHJTwRVaNXOmSkMjuFAotZxWNsNLSz3ZEiB1vpUqOYT3AsGMPrj8MIwh64AGfoLa91n4GyotGVbxwnsW8+K4g==", + "version": "1.0.82-0", + "resolved": "https://registry.npmjs.org/@github/copilot-darwin-arm64/-/copilot-darwin-arm64-1.0.82-0.tgz", + "integrity": "sha512-TzBYfyvxcw3z9Mu7U8TsFo/Nq7m5XS6ahT71aPL+gx/YId0kmenI27b9daXsK6LA1D0gsFGwNBDKINddqntt1g==", "cpu": [ "arm64" ], @@ -466,9 +466,9 @@ } }, "node_modules/@github/copilot-darwin-x64": { - "version": "1.0.81", - "resolved": "https://registry.npmjs.org/@github/copilot-darwin-x64/-/copilot-darwin-x64-1.0.81.tgz", - "integrity": "sha512-O8BHh9d9j86RokqSYhgX3D1mA4t6MZp5OOneL1n3TPR7J+bEq+Catp+UBVGsJdhsuJ1Oasfk/z59Wit9wnc22w==", + "version": "1.0.82-0", + "resolved": "https://registry.npmjs.org/@github/copilot-darwin-x64/-/copilot-darwin-x64-1.0.82-0.tgz", + "integrity": "sha512-Lm/U5Q8kN8yEeBTWKTfIxXAgXaT6zqdBzAAO7lA4DWHZ92AEeZZiVTs6jEWsQ2aWB2uMs6zbn0vi6t+/jIqCGw==", "cpu": [ "x64" ], @@ -482,9 +482,9 @@ } }, "node_modules/@github/copilot-linux-arm64": { - "version": "1.0.81", - "resolved": "https://registry.npmjs.org/@github/copilot-linux-arm64/-/copilot-linux-arm64-1.0.81.tgz", - "integrity": "sha512-GhHDhRkeWM3IfuouVGrU8UuXF26kPlV6aINSZyLIkrjY2AG/rro7NXNPeMKYT07HksoAWiMUrjEel8R/bpPplg==", + "version": "1.0.82-0", + "resolved": "https://registry.npmjs.org/@github/copilot-linux-arm64/-/copilot-linux-arm64-1.0.82-0.tgz", + "integrity": "sha512-YERVMC1Q4p6l6KQHL5rVOI52rWbvgp9IwyzUBaVSGrfFuqu5BEvZ9bgHPsxTYi3Npkt5KVOXEyPMU5rAY09qQQ==", "cpu": [ "arm64" ], @@ -498,9 +498,9 @@ } }, "node_modules/@github/copilot-linux-x64": { - "version": "1.0.81", - "resolved": "https://registry.npmjs.org/@github/copilot-linux-x64/-/copilot-linux-x64-1.0.81.tgz", - "integrity": "sha512-qhoiWIqfpvHcajJ8AVKa+ibKjD5k8Nccd9sfAJt5AMYN+Rv/aSCZojmnUazYv5UJAwStaGzep+rmVbTiN+sWUQ==", + "version": "1.0.82-0", + "resolved": "https://registry.npmjs.org/@github/copilot-linux-x64/-/copilot-linux-x64-1.0.82-0.tgz", + "integrity": "sha512-z2hxMVjqt4+xDRFTZv3/0K3X+aqcJhd6zPO2JxCpOVTh5CNZFaWk+XIa2iXAPWxFqdKJsQ4muXMl3zInaAOkRw==", "cpu": [ "x64" ], @@ -514,9 +514,9 @@ } }, "node_modules/@github/copilot-linuxmusl-arm64": { - "version": "1.0.81", - "resolved": "https://registry.npmjs.org/@github/copilot-linuxmusl-arm64/-/copilot-linuxmusl-arm64-1.0.81.tgz", - "integrity": "sha512-sOwSiqIM5H3AeYCbFW3f36qvm+YWyPinHwNiJC2DzuwpX/ujg8Warwm1zuokOJV5iyHC8AQxbwQRQ7LRQx19sQ==", + "version": "1.0.82-0", + "resolved": "https://registry.npmjs.org/@github/copilot-linuxmusl-arm64/-/copilot-linuxmusl-arm64-1.0.82-0.tgz", + "integrity": "sha512-EcUCv2PKhBzCCvpTaS511VYTDWyhudyIRPvBpc9gFNO3hjlgiNDusf4k9vP6+E3/lHenwGYzMsyRHcYIOy4vcQ==", "cpu": [ "arm64" ], @@ -530,9 +530,9 @@ } }, "node_modules/@github/copilot-linuxmusl-x64": { - "version": "1.0.81", - "resolved": "https://registry.npmjs.org/@github/copilot-linuxmusl-x64/-/copilot-linuxmusl-x64-1.0.81.tgz", - "integrity": "sha512-9lbAC0jDtlGNagKz9DycgzovHk35lS1lP5xV+EW1dTChep9uQt7n00d7Ru7ErvHS79QMRJw9PkPhKL9Jyz9jiA==", + "version": "1.0.82-0", + "resolved": "https://registry.npmjs.org/@github/copilot-linuxmusl-x64/-/copilot-linuxmusl-x64-1.0.82-0.tgz", + "integrity": "sha512-1fKVjUiZ1tdb0/d/re90EpFGXhlIPfjENp2Wo/2Kj592dWO3+IwM2qV/AOdMQ4pacW5iYHII7nibx1/EYq3LGQ==", "cpu": [ "x64" ], @@ -546,9 +546,9 @@ } }, "node_modules/@github/copilot-win32-arm64": { - "version": "1.0.81", - "resolved": "https://registry.npmjs.org/@github/copilot-win32-arm64/-/copilot-win32-arm64-1.0.81.tgz", - "integrity": "sha512-LBUennWqLDcAuYP3HrO9iwhxCjNA97g9jJplte8bEGFWoYrtOEs2UlEscorbHCQHeBpXXSn1slsUBpLl1CrDBQ==", + "version": "1.0.82-0", + "resolved": "https://registry.npmjs.org/@github/copilot-win32-arm64/-/copilot-win32-arm64-1.0.82-0.tgz", + "integrity": "sha512-H341wuxQHhwe/yLmORHzwC3DGzFZgGzh+TpwfyK2zjeYVbwDZ3Bax8M+9CzIED9jIBdEfAmfGzxKUHFugpXTtQ==", "cpu": [ "arm64" ], @@ -562,9 +562,9 @@ } }, "node_modules/@github/copilot-win32-x64": { - "version": "1.0.81", - "resolved": "https://registry.npmjs.org/@github/copilot-win32-x64/-/copilot-win32-x64-1.0.81.tgz", - "integrity": "sha512-1O1F1OdMO5Z/S9IjD3dQVA8QAKCuPgOlfhSvI19QfTp5zeDRZb2vCAERosY9UrURinawCHEoWmrg3EalXc4zaQ==", + "version": "1.0.82-0", + "resolved": "https://registry.npmjs.org/@github/copilot-win32-x64/-/copilot-win32-x64-1.0.82-0.tgz", + "integrity": "sha512-f1ba3gG8NaoYWFHtHaHcLN4It7mclkWdCOXvwFPqPEwqCEIx/+Zh6VHiOeIcNWRS0elRP6QYDCKaTDy1TW27uQ==", "cpu": [ "x64" ], diff --git a/java/scripts/codegen/package.json b/java/scripts/codegen/package.json index d7f775c798..f5d6bf66da 100644 --- a/java/scripts/codegen/package.json +++ b/java/scripts/codegen/package.json @@ -7,7 +7,7 @@ "generate:java": "tsx java.ts" }, "dependencies": { - "@github/copilot": "^1.0.81", + "@github/copilot": "^1.0.82-0", "json-schema": "^0.4.0", "tsx": "^4.23.12" } 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 d05110abce..1925f6d893 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 @@ -52,6 +52,8 @@ public record SessionCompactionCompleteEventData( @JsonProperty("customInstructions") String customInstructions, /** LLM-generated summary of the compacted conversation history */ @JsonProperty("summaryContent") String summaryContent, + /** Canonical model identifier used for model-specific behavior when replaying compaction */ + @JsonProperty("behaviorModelId") String behaviorModelId, /** Checkpoint snapshot number created for recovery */ @JsonProperty("checkpointNumber") Long checkpointNumber, /** File path where the checkpoint was stored */ diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SandboxConfig.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SandboxConfig.java index 9194ea9661..beda6b20a2 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SandboxConfig.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SandboxConfig.java @@ -29,7 +29,7 @@ public record SandboxConfig( @JsonProperty("addCurrentWorkingDirectory") Boolean addCurrentWorkingDirectory, /** Credential-injection capability flags. */ @JsonProperty("auth") SandboxConfigAuth auth, - /** Whether to auto-grant read access to the tool directories discovered on PATH and in toolchain environment variables (GOROOT, CARGO_HOME, JAVA_HOME, VIRTUAL_ENV, and similar), and to common developer-tool caches, registries, and toolchains in their default home locations (cargo, go, npm, Maven, and more), plus read-write access to (and up-front creation of) the scratch caches builds write on every run (go-build, ccache, sccache, Gradle caches, Cargo lock/tracker files), so builds work without extra configuration; a relocated CARGO_HOME additionally gets its Cargo lock files granted read-write. Set to false to disable every grant listed above: user-installed toolchains (rustup, nvm, pyenv, conda, pipx) then need explicit userPolicy.filesystem entries — readonlyPaths to read them, plus readwriteFiles for a relocated CARGO_HOME's .package-cache and .global-cache, which Cargo locks on every build. Only these developer-tool grants are affected: the working directory (see addCurrentWorkingDirectory), temporary storage, session log paths, and system locations follow their own rules and stay granted, so commands still run. Default: true (enabled by default; set to false to opt out). */ + /** Whether to auto-grant read access to tool directories discovered on PATH and in toolchain environment variables (GOROOT, JAVA_HOME, VIRTUAL_ENV, and similar), and to common developer-tool caches, config, and toolchains. Writable grants cover scratch caches, the Unix GitHub CLI cache, and Cargo's registry, git store, and lock/tracker files. A relocated CARGO_HOME gets the same narrow split: registry and git are read-write; bin is read-only; the home root, config.toml, and credentials.toml stay ungranted. Set to false to disable every grant listed above; user-installed toolchains and caches then need explicit userPolicy.filesystem readonlyPaths and readwritePaths entries. The working directory (see addCurrentWorkingDirectory), temporary storage, session log paths, and system locations follow their own rules and stay granted. Default: true (enabled by default; set to false to opt out). */ @JsonProperty("allowDevToolAccess") Boolean allowDevToolAccess ) { } diff --git a/nodejs/package-lock.json b/nodejs/package-lock.json index 6a3e01adb0..3b8e6cb861 100644 --- a/nodejs/package-lock.json +++ b/nodejs/package-lock.json @@ -9,7 +9,7 @@ "version": "0.0.0-dev", "license": "MIT", "dependencies": { - "@github/copilot": "^1.0.81", + "@github/copilot": "^1.0.82-0", "koffi": "^3.1.0", "vscode-jsonrpc": "^8.2.1", "zod": "^4.3.6" @@ -658,8 +658,8 @@ } }, "node_modules/@github/copilot": { - "version": "1.0.81", - "integrity": "sha512-Yif+wnRY1rT6FMmxr+SMZCq60mBTTPvbAHGd42Jty9wf1ZmeTsJYAcaGDXC9oyNm3RYRc3wkL0MSscNiEZADAA==", + "version": "1.0.82-0", + "integrity": "sha512-fSZVNAzFFYaS6btYD0+cKF7SrrtOklhpkPs/cIMZY7Fgxoa6rfZrlTWXlQNgNIdwLp51XxwTfxnZO+D9+MQ5yg==", "license": "SEE LICENSE IN LICENSE.md", "dependencies": { "detect-libc": "^2.1.2" @@ -668,19 +668,19 @@ "copilot": "npm-loader.js" }, "optionalDependencies": { - "@github/copilot-darwin-arm64": "1.0.81", - "@github/copilot-darwin-x64": "1.0.81", - "@github/copilot-linux-arm64": "1.0.81", - "@github/copilot-linux-x64": "1.0.81", - "@github/copilot-linuxmusl-arm64": "1.0.81", - "@github/copilot-linuxmusl-x64": "1.0.81", - "@github/copilot-win32-arm64": "1.0.81", - "@github/copilot-win32-x64": "1.0.81" + "@github/copilot-darwin-arm64": "1.0.82-0", + "@github/copilot-darwin-x64": "1.0.82-0", + "@github/copilot-linux-arm64": "1.0.82-0", + "@github/copilot-linux-x64": "1.0.82-0", + "@github/copilot-linuxmusl-arm64": "1.0.82-0", + "@github/copilot-linuxmusl-x64": "1.0.82-0", + "@github/copilot-win32-arm64": "1.0.82-0", + "@github/copilot-win32-x64": "1.0.82-0" } }, "node_modules/@github/copilot-darwin-arm64": { - "version": "1.0.81", - "integrity": "sha512-VKHJTwRVaNXOmSkMjuFAotZxWNsNLSz3ZEiB1vpUqOYT3AsGMPrj8MIwh64AGfoLa91n4GyotGVbxwnsW8+K4g==", + "version": "1.0.82-0", + "integrity": "sha512-TzBYfyvxcw3z9Mu7U8TsFo/Nq7m5XS6ahT71aPL+gx/YId0kmenI27b9daXsK6LA1D0gsFGwNBDKINddqntt1g==", "cpu": [ "arm64" ], @@ -694,8 +694,8 @@ } }, "node_modules/@github/copilot-darwin-x64": { - "version": "1.0.81", - "integrity": "sha512-O8BHh9d9j86RokqSYhgX3D1mA4t6MZp5OOneL1n3TPR7J+bEq+Catp+UBVGsJdhsuJ1Oasfk/z59Wit9wnc22w==", + "version": "1.0.82-0", + "integrity": "sha512-Lm/U5Q8kN8yEeBTWKTfIxXAgXaT6zqdBzAAO7lA4DWHZ92AEeZZiVTs6jEWsQ2aWB2uMs6zbn0vi6t+/jIqCGw==", "cpu": [ "x64" ], @@ -709,8 +709,8 @@ } }, "node_modules/@github/copilot-linux-arm64": { - "version": "1.0.81", - "integrity": "sha512-GhHDhRkeWM3IfuouVGrU8UuXF26kPlV6aINSZyLIkrjY2AG/rro7NXNPeMKYT07HksoAWiMUrjEel8R/bpPplg==", + "version": "1.0.82-0", + "integrity": "sha512-YERVMC1Q4p6l6KQHL5rVOI52rWbvgp9IwyzUBaVSGrfFuqu5BEvZ9bgHPsxTYi3Npkt5KVOXEyPMU5rAY09qQQ==", "cpu": [ "arm64" ], @@ -724,8 +724,8 @@ } }, "node_modules/@github/copilot-linux-x64": { - "version": "1.0.81", - "integrity": "sha512-qhoiWIqfpvHcajJ8AVKa+ibKjD5k8Nccd9sfAJt5AMYN+Rv/aSCZojmnUazYv5UJAwStaGzep+rmVbTiN+sWUQ==", + "version": "1.0.82-0", + "integrity": "sha512-z2hxMVjqt4+xDRFTZv3/0K3X+aqcJhd6zPO2JxCpOVTh5CNZFaWk+XIa2iXAPWxFqdKJsQ4muXMl3zInaAOkRw==", "cpu": [ "x64" ], @@ -739,8 +739,8 @@ } }, "node_modules/@github/copilot-linuxmusl-arm64": { - "version": "1.0.81", - "integrity": "sha512-sOwSiqIM5H3AeYCbFW3f36qvm+YWyPinHwNiJC2DzuwpX/ujg8Warwm1zuokOJV5iyHC8AQxbwQRQ7LRQx19sQ==", + "version": "1.0.82-0", + "integrity": "sha512-EcUCv2PKhBzCCvpTaS511VYTDWyhudyIRPvBpc9gFNO3hjlgiNDusf4k9vP6+E3/lHenwGYzMsyRHcYIOy4vcQ==", "cpu": [ "arm64" ], @@ -754,8 +754,8 @@ } }, "node_modules/@github/copilot-linuxmusl-x64": { - "version": "1.0.81", - "integrity": "sha512-9lbAC0jDtlGNagKz9DycgzovHk35lS1lP5xV+EW1dTChep9uQt7n00d7Ru7ErvHS79QMRJw9PkPhKL9Jyz9jiA==", + "version": "1.0.82-0", + "integrity": "sha512-1fKVjUiZ1tdb0/d/re90EpFGXhlIPfjENp2Wo/2Kj592dWO3+IwM2qV/AOdMQ4pacW5iYHII7nibx1/EYq3LGQ==", "cpu": [ "x64" ], @@ -769,8 +769,8 @@ } }, "node_modules/@github/copilot-win32-arm64": { - "version": "1.0.81", - "integrity": "sha512-LBUennWqLDcAuYP3HrO9iwhxCjNA97g9jJplte8bEGFWoYrtOEs2UlEscorbHCQHeBpXXSn1slsUBpLl1CrDBQ==", + "version": "1.0.82-0", + "integrity": "sha512-H341wuxQHhwe/yLmORHzwC3DGzFZgGzh+TpwfyK2zjeYVbwDZ3Bax8M+9CzIED9jIBdEfAmfGzxKUHFugpXTtQ==", "cpu": [ "arm64" ], @@ -784,8 +784,8 @@ } }, "node_modules/@github/copilot-win32-x64": { - "version": "1.0.81", - "integrity": "sha512-1O1F1OdMO5Z/S9IjD3dQVA8QAKCuPgOlfhSvI19QfTp5zeDRZb2vCAERosY9UrURinawCHEoWmrg3EalXc4zaQ==", + "version": "1.0.82-0", + "integrity": "sha512-f1ba3gG8NaoYWFHtHaHcLN4It7mclkWdCOXvwFPqPEwqCEIx/+Zh6VHiOeIcNWRS0elRP6QYDCKaTDy1TW27uQ==", "cpu": [ "x64" ], diff --git a/nodejs/package.json b/nodejs/package.json index 5298004835..89863520e0 100644 --- a/nodejs/package.json +++ b/nodejs/package.json @@ -56,7 +56,7 @@ "author": "GitHub", "license": "MIT", "dependencies": { - "@github/copilot": "^1.0.81", + "@github/copilot": "^1.0.82-0", "koffi": "^3.1.0", "vscode-jsonrpc": "^8.2.1", "zod": "^4.3.6" diff --git a/nodejs/samples/package-lock.json b/nodejs/samples/package-lock.json index 6bf74301ea..ad675a88c7 100644 --- a/nodejs/samples/package-lock.json +++ b/nodejs/samples/package-lock.json @@ -18,7 +18,7 @@ "version": "0.0.0-dev", "license": "MIT", "dependencies": { - "@github/copilot": "^1.0.81", + "@github/copilot": "^1.0.82-0", "koffi": "^3.1.0", "vscode-jsonrpc": "^8.2.1", "zod": "^4.3.6" diff --git a/nodejs/src/generated/rpc.ts b/nodejs/src/generated/rpc.ts index 520426660a..db0ea63dcc 100644 --- a/nodejs/src/generated/rpc.ts +++ b/nodejs/src/generated/rpc.ts @@ -16454,7 +16454,7 @@ export interface SandboxConfig { addCurrentWorkingDirectory?: boolean; auth?: SandboxConfigAuth; /** - * Whether to auto-grant read access to the tool directories discovered on PATH and in toolchain environment variables (GOROOT, CARGO_HOME, JAVA_HOME, VIRTUAL_ENV, and similar), and to common developer-tool caches, registries, and toolchains in their default home locations (cargo, go, npm, Maven, and more), plus read-write access to (and up-front creation of) the scratch caches builds write on every run (go-build, ccache, sccache, Gradle caches, Cargo lock/tracker files), so builds work without extra configuration; a relocated CARGO_HOME additionally gets its Cargo lock files granted read-write. Set to false to disable every grant listed above: user-installed toolchains (rustup, nvm, pyenv, conda, pipx) then need explicit userPolicy.filesystem entries — readonlyPaths to read them, plus readwriteFiles for a relocated CARGO_HOME's .package-cache and .global-cache, which Cargo locks on every build. Only these developer-tool grants are affected: the working directory (see addCurrentWorkingDirectory), temporary storage, session log paths, and system locations follow their own rules and stay granted, so commands still run. Default: true (enabled by default; set to false to opt out). + * Whether to auto-grant read access to tool directories discovered on PATH and in toolchain environment variables (GOROOT, JAVA_HOME, VIRTUAL_ENV, and similar), and to common developer-tool caches, config, and toolchains. Writable grants cover scratch caches, the Unix GitHub CLI cache, and Cargo's registry, git store, and lock/tracker files. A relocated CARGO_HOME gets the same narrow split: registry and git are read-write; bin is read-only; the home root, config.toml, and credentials.toml stay ungranted. Set to false to disable every grant listed above; user-installed toolchains and caches then need explicit userPolicy.filesystem readonlyPaths and readwritePaths entries. The working directory (see addCurrentWorkingDirectory), temporary storage, session log paths, and system locations follow their own rules and stay granted. Default: true (enabled by default; set to false to opt out). */ allowDevToolAccess?: boolean; } diff --git a/nodejs/src/generated/session-events.ts b/nodejs/src/generated/session-events.ts index 512ffb398b..9075379982 100644 --- a/nodejs/src/generated/session-events.ts +++ b/nodejs/src/generated/session-events.ts @@ -2806,6 +2806,10 @@ export interface CompactionCompleteEvent { * Conversation compaction results including success status, metrics, and optional error details */ export interface CompactionCompleteData { + /** + * Canonical model identifier used for model-specific behavior when replaying compaction + */ + behaviorModelId?: string; /** * Checkpoint snapshot number created for recovery */ diff --git a/nodejs/test/e2e/telemetry.e2e.test.ts b/nodejs/test/e2e/telemetry.e2e.test.ts index c0f71ebfc6..66a0bb8cef 100644 --- a/nodejs/test/e2e/telemetry.e2e.test.ts +++ b/nodejs/test/e2e/telemetry.e2e.test.ts @@ -103,11 +103,6 @@ describe("Telemetry export", async () => { expect(span.instrumentationScope?.name).toBe(sourceName); } - // All spans for one SDK turn must share the same trace id and must not be in error state. - const traceIds = Array.from( - new Set(spans.map((span) => span.traceId).filter((id): id is string => Boolean(id))) - ); - expect(traceIds).toHaveLength(1); for (const span of spans) { expect(span.status?.code).not.toBe(2); } @@ -122,6 +117,8 @@ describe("Telemetry export", async () => { expect(isRootSpan(invokeAgentSpan!)).toBe(true); const invokeAgentSpanId = invokeAgentSpan!.spanId; expect(invokeAgentSpanId).toBeTruthy(); + const invokeAgentTraceId = invokeAgentSpan!.traceId; + expect(invokeAgentTraceId).toBeTruthy(); const chatSpans = spans.filter( (span) => getStringAttribute(span, "gen_ai.operation.name") === "chat" @@ -129,6 +126,7 @@ describe("Telemetry export", async () => { expect(chatSpans.length).toBeGreaterThan(0); for (const chat of chatSpans) { expect(chat.parentSpanId).toBe(invokeAgentSpanId); + expect(chat.traceId).toBe(invokeAgentTraceId); } expect( chatSpans.some((span) => @@ -148,6 +146,7 @@ describe("Telemetry export", async () => { ); expect(toolSpan).toBeDefined(); expect(toolSpan!.parentSpanId).toBe(invokeAgentSpanId); + expect(toolSpan!.traceId).toBe(invokeAgentTraceId); expect(getStringAttribute(toolSpan!, "gen_ai.tool.name")).toBe(toolName); expect(getStringAttribute(toolSpan!, "gen_ai.tool.call.id")).toBeTruthy(); expect(getStringAttribute(toolSpan!, "gen_ai.tool.call.arguments")).toBe( diff --git a/python/copilot/generated/rpc.py b/python/copilot/generated/rpc.py index 1f28ce3585..1d59a55f4a 100644 --- a/python/copilot/generated/rpc.py +++ b/python/copilot/generated/rpc.py @@ -30297,20 +30297,16 @@ class SandboxConfig: """Whether to auto-add the current working directory to readwritePaths. Default: true.""" allow_dev_tool_access: bool | None = None - """Whether to auto-grant read access to the tool directories discovered on PATH and in - toolchain environment variables (GOROOT, CARGO_HOME, JAVA_HOME, VIRTUAL_ENV, and - similar), and to common developer-tool caches, registries, and toolchains in their - default home locations (cargo, go, npm, Maven, and more), plus read-write access to (and - up-front creation of) the scratch caches builds write on every run (go-build, ccache, - sccache, Gradle caches, Cargo lock/tracker files), so builds work without extra - configuration; a relocated CARGO_HOME additionally gets its Cargo lock files granted - read-write. Set to false to disable every grant listed above: user-installed toolchains - (rustup, nvm, pyenv, conda, pipx) then need explicit userPolicy.filesystem entries — - readonlyPaths to read them, plus readwriteFiles for a relocated CARGO_HOME's - .package-cache and .global-cache, which Cargo locks on every build. Only these - developer-tool grants are affected: the working directory (see - addCurrentWorkingDirectory), temporary storage, session log paths, and system locations - follow their own rules and stay granted, so commands still run. Default: true (enabled by + """Whether to auto-grant read access to tool directories discovered on PATH and in toolchain + environment variables (GOROOT, JAVA_HOME, VIRTUAL_ENV, and similar), and to common + developer-tool caches, config, and toolchains. Writable grants cover scratch caches, the + Unix GitHub CLI cache, and Cargo's registry, git store, and lock/tracker files. A + relocated CARGO_HOME gets the same narrow split: registry and git are read-write; bin is + read-only; the home root, config.toml, and credentials.toml stay ungranted. Set to false + to disable every grant listed above; user-installed toolchains and caches then need + explicit userPolicy.filesystem readonlyPaths and readwritePaths entries. The working + directory (see addCurrentWorkingDirectory), temporary storage, session log paths, and + system locations follow their own rules and stay granted. Default: true (enabled by default; set to false to opt out). """ auth: SandboxConfigAuth | None = None diff --git a/python/copilot/generated/session_events.py b/python/copilot/generated/session_events.py index e6c5084861..c22f9f24fc 100644 --- a/python/copilot/generated/session_events.py +++ b/python/copilot/generated/session_events.py @@ -7426,6 +7426,7 @@ def to_dict(self) -> dict: class SessionCompactionCompleteData: "Conversation compaction results including success status, metrics, and optional error details" success: bool + behavior_model_id: str | None = None checkpoint_number: int | None = None checkpoint_path: str | None = None compaction_tokens_used: CompactionCompleteCompactionTokensUsed | None = None @@ -7450,6 +7451,7 @@ class SessionCompactionCompleteData: def from_dict(obj: Any) -> "SessionCompactionCompleteData": assert isinstance(obj, dict) success = from_bool(obj.get("success")) + behavior_model_id = from_union([from_none, from_str], obj.get("behaviorModelId")) checkpoint_number = from_union([from_none, from_int], obj.get("checkpointNumber")) checkpoint_path = from_union([from_none, from_str], obj.get("checkpointPath")) compaction_tokens_used = from_union([from_none, CompactionCompleteCompactionTokensUsed.from_dict], obj.get("compactionTokensUsed")) @@ -7471,6 +7473,7 @@ def from_dict(obj: Any) -> "SessionCompactionCompleteData": trigger = from_union([from_none, lambda x: parse_enum(CompactionTrigger, x)], obj.get("trigger")) return SessionCompactionCompleteData( success=success, + behavior_model_id=behavior_model_id, checkpoint_number=checkpoint_number, checkpoint_path=checkpoint_path, compaction_tokens_used=compaction_tokens_used, @@ -7495,6 +7498,8 @@ def from_dict(obj: Any) -> "SessionCompactionCompleteData": def to_dict(self) -> dict: result: dict = {} result["success"] = from_bool(self.success) + if self.behavior_model_id is not None: + result["behaviorModelId"] = from_union([from_none, from_str], self.behavior_model_id) if self.checkpoint_number is not None: result["checkpointNumber"] = from_union([from_none, to_int], self.checkpoint_number) if self.checkpoint_path is not None: diff --git a/python/e2e/test_telemetry_e2e.py b/python/e2e/test_telemetry_e2e.py index 14c03ada30..8b9c82abef 100644 --- a/python/e2e/test_telemetry_e2e.py +++ b/python/e2e/test_telemetry_e2e.py @@ -118,9 +118,6 @@ def echo(invocation: ToolInvocation) -> ToolResult: scope = span.get("instrumentationScope") or {} assert scope.get("name") == source_name - trace_ids = {s.get("traceId") for s in spans if s.get("traceId")} - assert len(trace_ids) == 1 - for span in spans: status = (span.get("status") or {}).get("code", 0) assert status != 2, f"span in error state: {span}" @@ -132,11 +129,14 @@ def echo(invocation: ToolInvocation) -> ToolResult: assert _is_root_span(invoke_agent) invoke_agent_span_id = invoke_agent.get("spanId") assert invoke_agent_span_id + invoke_agent_trace_id = invoke_agent.get("traceId") + assert invoke_agent_trace_id chat_spans = [s for s in spans if _string_attribute(s, "gen_ai.operation.name") == "chat"] assert chat_spans for chat in chat_spans: assert chat.get("parentSpanId") == invoke_agent_span_id + assert chat.get("traceId") == invoke_agent_trace_id assert any( prompt in (_string_attribute(c, "gen_ai.input.messages") or "") for c in chat_spans ) @@ -149,6 +149,7 @@ def echo(invocation: ToolInvocation) -> ToolResult: s for s in spans if _string_attribute(s, "gen_ai.operation.name") == "execute_tool" ) assert tool_span.get("parentSpanId") == invoke_agent_span_id + assert tool_span.get("traceId") == invoke_agent_trace_id assert _string_attribute(tool_span, "gen_ai.tool.name") == tool_name assert (_string_attribute(tool_span, "gen_ai.tool.call.id") or "").strip() assert ( diff --git a/rust/src/generated/api_types.rs b/rust/src/generated/api_types.rs index 9bf64ac404..e22c888f23 100644 --- a/rust/src/generated/api_types.rs +++ b/rust/src/generated/api_types.rs @@ -14743,7 +14743,7 @@ pub struct SandboxConfig { /// Whether to auto-add the current working directory to readwritePaths. Default: true. #[serde(skip_serializing_if = "Option::is_none")] pub add_current_working_directory: Option, - /// Whether to auto-grant read access to the tool directories discovered on PATH and in toolchain environment variables (GOROOT, CARGO_HOME, JAVA_HOME, VIRTUAL_ENV, and similar), and to common developer-tool caches, registries, and toolchains in their default home locations (cargo, go, npm, Maven, and more), plus read-write access to (and up-front creation of) the scratch caches builds write on every run (go-build, ccache, sccache, Gradle caches, Cargo lock/tracker files), so builds work without extra configuration; a relocated CARGO_HOME additionally gets its Cargo lock files granted read-write. Set to false to disable every grant listed above: user-installed toolchains (rustup, nvm, pyenv, conda, pipx) then need explicit userPolicy.filesystem entries — readonlyPaths to read them, plus readwriteFiles for a relocated CARGO_HOME's .package-cache and .global-cache, which Cargo locks on every build. Only these developer-tool grants are affected: the working directory (see addCurrentWorkingDirectory), temporary storage, session log paths, and system locations follow their own rules and stay granted, so commands still run. Default: true (enabled by default; set to false to opt out). + /// Whether to auto-grant read access to tool directories discovered on PATH and in toolchain environment variables (GOROOT, JAVA_HOME, VIRTUAL_ENV, and similar), and to common developer-tool caches, config, and toolchains. Writable grants cover scratch caches, the Unix GitHub CLI cache, and Cargo's registry, git store, and lock/tracker files. A relocated CARGO_HOME gets the same narrow split: registry and git are read-write; bin is read-only; the home root, config.toml, and credentials.toml stay ungranted. Set to false to disable every grant listed above; user-installed toolchains and caches then need explicit userPolicy.filesystem readonlyPaths and readwritePaths entries. The working directory (see addCurrentWorkingDirectory), temporary storage, session log paths, and system locations follow their own rules and stay granted. Default: true (enabled by default; set to false to opt out). #[serde(skip_serializing_if = "Option::is_none")] pub allow_dev_tool_access: Option, /// Credential-injection capability flags. diff --git a/rust/src/generated/session_events.rs b/rust/src/generated/session_events.rs index a14a5eab26..79284b1671 100644 --- a/rust/src/generated/session_events.rs +++ b/rust/src/generated/session_events.rs @@ -1726,6 +1726,9 @@ pub struct CompactionCompleteCompactionTokensUsed { #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct SessionCompactionCompleteData { + /// Canonical model identifier used for model-specific behavior when replaying compaction + #[serde(skip_serializing_if = "Option::is_none")] + pub behavior_model_id: Option, /// Checkpoint snapshot number created for recovery #[serde(skip_serializing_if = "Option::is_none")] pub checkpoint_number: Option, diff --git a/rust/tests/e2e/telemetry.rs b/rust/tests/e2e/telemetry.rs index f6905427ae..386cc2f560 100644 --- a/rust/tests/e2e/telemetry.rs +++ b/rust/tests/e2e/telemetry.rs @@ -83,11 +83,6 @@ async fn should_export_file_telemetry_for_sdk_interactions() { == Some(source_name) })); - let trace_ids: std::collections::HashSet<_> = spans - .iter() - .filter_map(|span| string_property(span, "traceId")) - .collect(); - assert_eq!(trace_ids.len(), 1); assert!(spans.iter().all(|span| status_code(span) != Some(2))); let invoke_agent = find_span(&spans, "invoke_agent"); @@ -97,6 +92,8 @@ async fn should_export_file_telemetry_for_sdk_interactions() { ); let invoke_agent_span_id = string_property(invoke_agent, "spanId").expect("invoke_agent span id"); + let invoke_agent_trace_id = + string_property(invoke_agent, "traceId").expect("invoke_agent trace id"); assert!(is_root_span(invoke_agent)); let chat_spans: Vec<_> = spans @@ -109,6 +106,7 @@ async fn should_export_file_telemetry_for_sdk_interactions() { assert!(!chat_spans.is_empty()); assert!(chat_spans.iter().all(|span| { string_property(span, "parentSpanId") == Some(invoke_agent_span_id) + && string_property(span, "traceId") == Some(invoke_agent_trace_id) })); assert!(chat_spans.iter().any(|span| string_attribute( span, @@ -126,6 +124,10 @@ async fn should_export_file_telemetry_for_sdk_interactions() { string_property(tool_span, "parentSpanId"), Some(invoke_agent_span_id) ); + assert_eq!( + string_property(tool_span, "traceId"), + Some(invoke_agent_trace_id) + ); assert_eq!( string_attribute(tool_span, "gen_ai.tool.name").as_deref(), Some(tool_name) diff --git a/test/harness/package-lock.json b/test/harness/package-lock.json index 9f29dc7c15..710d725318 100644 --- a/test/harness/package-lock.json +++ b/test/harness/package-lock.json @@ -9,7 +9,7 @@ "version": "1.0.0", "license": "ISC", "devDependencies": { - "@github/copilot": "^1.0.81", + "@github/copilot": "^1.0.82-0", "@modelcontextprotocol/sdk": "^1.26.0", "@types/node": "^25.3.3", "@types/node-forge": "^1.3.14", @@ -472,8 +472,8 @@ } }, "node_modules/@github/copilot": { - "version": "1.0.81", - "integrity": "sha512-Yif+wnRY1rT6FMmxr+SMZCq60mBTTPvbAHGd42Jty9wf1ZmeTsJYAcaGDXC9oyNm3RYRc3wkL0MSscNiEZADAA==", + "version": "1.0.82-0", + "integrity": "sha512-fSZVNAzFFYaS6btYD0+cKF7SrrtOklhpkPs/cIMZY7Fgxoa6rfZrlTWXlQNgNIdwLp51XxwTfxnZO+D9+MQ5yg==", "dev": true, "license": "SEE LICENSE IN LICENSE.md", "dependencies": { @@ -483,19 +483,19 @@ "copilot": "npm-loader.js" }, "optionalDependencies": { - "@github/copilot-darwin-arm64": "1.0.81", - "@github/copilot-darwin-x64": "1.0.81", - "@github/copilot-linux-arm64": "1.0.81", - "@github/copilot-linux-x64": "1.0.81", - "@github/copilot-linuxmusl-arm64": "1.0.81", - "@github/copilot-linuxmusl-x64": "1.0.81", - "@github/copilot-win32-arm64": "1.0.81", - "@github/copilot-win32-x64": "1.0.81" + "@github/copilot-darwin-arm64": "1.0.82-0", + "@github/copilot-darwin-x64": "1.0.82-0", + "@github/copilot-linux-arm64": "1.0.82-0", + "@github/copilot-linux-x64": "1.0.82-0", + "@github/copilot-linuxmusl-arm64": "1.0.82-0", + "@github/copilot-linuxmusl-x64": "1.0.82-0", + "@github/copilot-win32-arm64": "1.0.82-0", + "@github/copilot-win32-x64": "1.0.82-0" } }, "node_modules/@github/copilot-darwin-arm64": { - "version": "1.0.81", - "integrity": "sha512-VKHJTwRVaNXOmSkMjuFAotZxWNsNLSz3ZEiB1vpUqOYT3AsGMPrj8MIwh64AGfoLa91n4GyotGVbxwnsW8+K4g==", + "version": "1.0.82-0", + "integrity": "sha512-TzBYfyvxcw3z9Mu7U8TsFo/Nq7m5XS6ahT71aPL+gx/YId0kmenI27b9daXsK6LA1D0gsFGwNBDKINddqntt1g==", "cpu": [ "arm64" ], @@ -510,8 +510,8 @@ } }, "node_modules/@github/copilot-darwin-x64": { - "version": "1.0.81", - "integrity": "sha512-O8BHh9d9j86RokqSYhgX3D1mA4t6MZp5OOneL1n3TPR7J+bEq+Catp+UBVGsJdhsuJ1Oasfk/z59Wit9wnc22w==", + "version": "1.0.82-0", + "integrity": "sha512-Lm/U5Q8kN8yEeBTWKTfIxXAgXaT6zqdBzAAO7lA4DWHZ92AEeZZiVTs6jEWsQ2aWB2uMs6zbn0vi6t+/jIqCGw==", "cpu": [ "x64" ], @@ -526,8 +526,8 @@ } }, "node_modules/@github/copilot-linux-arm64": { - "version": "1.0.81", - "integrity": "sha512-GhHDhRkeWM3IfuouVGrU8UuXF26kPlV6aINSZyLIkrjY2AG/rro7NXNPeMKYT07HksoAWiMUrjEel8R/bpPplg==", + "version": "1.0.82-0", + "integrity": "sha512-YERVMC1Q4p6l6KQHL5rVOI52rWbvgp9IwyzUBaVSGrfFuqu5BEvZ9bgHPsxTYi3Npkt5KVOXEyPMU5rAY09qQQ==", "cpu": [ "arm64" ], @@ -542,8 +542,8 @@ } }, "node_modules/@github/copilot-linux-x64": { - "version": "1.0.81", - "integrity": "sha512-qhoiWIqfpvHcajJ8AVKa+ibKjD5k8Nccd9sfAJt5AMYN+Rv/aSCZojmnUazYv5UJAwStaGzep+rmVbTiN+sWUQ==", + "version": "1.0.82-0", + "integrity": "sha512-z2hxMVjqt4+xDRFTZv3/0K3X+aqcJhd6zPO2JxCpOVTh5CNZFaWk+XIa2iXAPWxFqdKJsQ4muXMl3zInaAOkRw==", "cpu": [ "x64" ], @@ -558,8 +558,8 @@ } }, "node_modules/@github/copilot-linuxmusl-arm64": { - "version": "1.0.81", - "integrity": "sha512-sOwSiqIM5H3AeYCbFW3f36qvm+YWyPinHwNiJC2DzuwpX/ujg8Warwm1zuokOJV5iyHC8AQxbwQRQ7LRQx19sQ==", + "version": "1.0.82-0", + "integrity": "sha512-EcUCv2PKhBzCCvpTaS511VYTDWyhudyIRPvBpc9gFNO3hjlgiNDusf4k9vP6+E3/lHenwGYzMsyRHcYIOy4vcQ==", "cpu": [ "arm64" ], @@ -574,8 +574,8 @@ } }, "node_modules/@github/copilot-linuxmusl-x64": { - "version": "1.0.81", - "integrity": "sha512-9lbAC0jDtlGNagKz9DycgzovHk35lS1lP5xV+EW1dTChep9uQt7n00d7Ru7ErvHS79QMRJw9PkPhKL9Jyz9jiA==", + "version": "1.0.82-0", + "integrity": "sha512-1fKVjUiZ1tdb0/d/re90EpFGXhlIPfjENp2Wo/2Kj592dWO3+IwM2qV/AOdMQ4pacW5iYHII7nibx1/EYq3LGQ==", "cpu": [ "x64" ], @@ -590,8 +590,8 @@ } }, "node_modules/@github/copilot-win32-arm64": { - "version": "1.0.81", - "integrity": "sha512-LBUennWqLDcAuYP3HrO9iwhxCjNA97g9jJplte8bEGFWoYrtOEs2UlEscorbHCQHeBpXXSn1slsUBpLl1CrDBQ==", + "version": "1.0.82-0", + "integrity": "sha512-H341wuxQHhwe/yLmORHzwC3DGzFZgGzh+TpwfyK2zjeYVbwDZ3Bax8M+9CzIED9jIBdEfAmfGzxKUHFugpXTtQ==", "cpu": [ "arm64" ], @@ -606,8 +606,8 @@ } }, "node_modules/@github/copilot-win32-x64": { - "version": "1.0.81", - "integrity": "sha512-1O1F1OdMO5Z/S9IjD3dQVA8QAKCuPgOlfhSvI19QfTp5zeDRZb2vCAERosY9UrURinawCHEoWmrg3EalXc4zaQ==", + "version": "1.0.82-0", + "integrity": "sha512-f1ba3gG8NaoYWFHtHaHcLN4It7mclkWdCOXvwFPqPEwqCEIx/+Zh6VHiOeIcNWRS0elRP6QYDCKaTDy1TW27uQ==", "cpu": [ "x64" ], diff --git a/test/harness/package.json b/test/harness/package.json index c048ec5f5b..cceca0b959 100644 --- a/test/harness/package.json +++ b/test/harness/package.json @@ -14,7 +14,7 @@ "node": "^20.19.0 || >=22.12.0" }, "devDependencies": { - "@github/copilot": "^1.0.81", + "@github/copilot": "^1.0.82-0", "@modelcontextprotocol/sdk": "^1.26.0", "@types/node": "^25.3.3", "@types/node-forge": "^1.3.14", From c2f08ff199d6a04908158a66803a6854b8de224d Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 28 Aug 2026 09:25:10 +0000 Subject: [PATCH 29/32] docs: update version references to 1.0.13-preview.2 --- java/README.md | 8 ++++---- java/sdk/jbang-example.java | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/java/README.md b/java/README.md index 10eb72ce50..2e1ba51812 100644 --- a/java/README.md +++ b/java/README.md @@ -32,14 +32,14 @@ Replace `${copilot.sdk.version}` with the latest release from Maven Central. com.github copilot-sdk-java - 1.0.13-preview.1 + 1.0.13-preview.2 ``` ### Gradle ```groovy -implementation 'com.github:copilot-sdk-java:1.0.13-preview.1' +implementation 'com.github:copilot-sdk-java:1.0.13-preview.2' ``` #### Snapshot Builds @@ -58,7 +58,7 @@ Snapshot builds of the next development version are published to Maven Central S com.github copilot-sdk-java - 1.0.14-preview.1-SNAPSHOT + 1.0.14-preview.2-SNAPSHOT ``` @@ -67,7 +67,7 @@ Snapshot builds of the next development version are published to Maven Central S Replace `${copilot.sdk.version}` with the latest release from Maven Central. ```groovy -implementation 'com.github:copilot-sdk-java:1.0.14-preview.1-SNAPSHOT' +implementation 'com.github:copilot-sdk-java:1.0.14-preview.2-SNAPSHOT' ``` ## In-process mode (experimental) diff --git a/java/sdk/jbang-example.java b/java/sdk/jbang-example.java index cf091d3c12..6cb04215ac 100644 --- a/java/sdk/jbang-example.java +++ b/java/sdk/jbang-example.java @@ -1,5 +1,5 @@ ///usr/bin/env jbang "$0" "$@" ; exit $? -//DEPS com.github:copilot-sdk-java:1.0.13-preview.1 +//DEPS com.github:copilot-sdk-java:1.0.13-preview.2 import com.github.copilot.CopilotClient; import com.github.copilot.generated.AssistantMessageEvent; import com.github.copilot.generated.SessionUsageInfoEvent; From a94a1f2ddd7e015b3d3f7bbc94c7c2b1a28219e0 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 28 Aug 2026 09:25:41 +0000 Subject: [PATCH 30/32] [maven-release-plugin] prepare release java/v1.0.13-preview.2 --- java/copilot-native/pom.xml | 4 ++-- java/pom.xml | 4 ++-- java/sdk/pom.xml | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/java/copilot-native/pom.xml b/java/copilot-native/pom.xml index f3694457d8..e0db057863 100644 --- a/java/copilot-native/pom.xml +++ b/java/copilot-native/pom.xml @@ -8,7 +8,7 @@ com.github copilot-sdk-java-parent - 1.0.14-preview.1-SNAPSHOT + 1.0.13-preview.2 ../pom.xml @@ -24,7 +24,7 @@ scm:git:https://github.com/github/copilot-sdk.git scm:git:https://github.com/github/copilot-sdk.git https://github.com/github/copilot-sdk - HEAD + java/v1.0.13-preview.2 diff --git a/java/pom.xml b/java/pom.xml index c5a17d66d8..dfa6bb7484 100644 --- a/java/pom.xml +++ b/java/pom.xml @@ -7,7 +7,7 @@ com.github copilot-sdk-java-parent - 1.0.14-preview.1-SNAPSHOT + 1.0.13-preview.2 pom GitHub Copilot SDK :: Java :: Parent @@ -33,7 +33,7 @@ scm:git:https://github.com/github/copilot-sdk.git scm:git:https://github.com/github/copilot-sdk.git https://github.com/github/copilot-sdk - HEAD + java/v1.0.13-preview.2 diff --git a/java/sdk/pom.xml b/java/sdk/pom.xml index 4f9ece6354..e0265813c5 100644 --- a/java/sdk/pom.xml +++ b/java/sdk/pom.xml @@ -8,7 +8,7 @@ com.github copilot-sdk-java-parent - 1.0.14-preview.1-SNAPSHOT + 1.0.13-preview.2 ../pom.xml @@ -24,7 +24,7 @@ scm:git:https://github.com/github/copilot-sdk.git scm:git:https://github.com/github/copilot-sdk.git https://github.com/github/copilot-sdk - HEAD + java/v1.0.13-preview.2 From 50ce37e19258524c6de82651652971e96d7ae5f3 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 28 Aug 2026 09:25:45 +0000 Subject: [PATCH 31/32] [maven-release-plugin] prepare for next development iteration --- java/copilot-native/pom.xml | 4 ++-- java/pom.xml | 4 ++-- java/sdk/pom.xml | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/java/copilot-native/pom.xml b/java/copilot-native/pom.xml index e0db057863..d138a0e56b 100644 --- a/java/copilot-native/pom.xml +++ b/java/copilot-native/pom.xml @@ -8,7 +8,7 @@ com.github copilot-sdk-java-parent - 1.0.13-preview.2 + 1.0.14-preview.2-SNAPSHOT ../pom.xml @@ -24,7 +24,7 @@ scm:git:https://github.com/github/copilot-sdk.git scm:git:https://github.com/github/copilot-sdk.git https://github.com/github/copilot-sdk - java/v1.0.13-preview.2 + HEAD diff --git a/java/pom.xml b/java/pom.xml index dfa6bb7484..3e5e1645a5 100644 --- a/java/pom.xml +++ b/java/pom.xml @@ -7,7 +7,7 @@ com.github copilot-sdk-java-parent - 1.0.13-preview.2 + 1.0.14-preview.2-SNAPSHOT pom GitHub Copilot SDK :: Java :: Parent @@ -33,7 +33,7 @@ scm:git:https://github.com/github/copilot-sdk.git scm:git:https://github.com/github/copilot-sdk.git https://github.com/github/copilot-sdk - java/v1.0.13-preview.2 + HEAD diff --git a/java/sdk/pom.xml b/java/sdk/pom.xml index e0265813c5..811a05db67 100644 --- a/java/sdk/pom.xml +++ b/java/sdk/pom.xml @@ -8,7 +8,7 @@ com.github copilot-sdk-java-parent - 1.0.13-preview.2 + 1.0.14-preview.2-SNAPSHOT ../pom.xml @@ -24,7 +24,7 @@ scm:git:https://github.com/github/copilot-sdk.git scm:git:https://github.com/github/copilot-sdk.git https://github.com/github/copilot-sdk - java/v1.0.13-preview.2 + HEAD From 53efb3593e65e5f04474099ffd3e00efa7777482 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 30 Aug 2026 04:06:49 +0000 Subject: [PATCH 32/32] Update @github/copilot to 1.0.82 (#2442) * Update @github/copilot to 1.0.82 - Updated nodejs and test harness dependencies - Re-ran code generators - Formatted generated code * Fix generated schema compatibility regressions Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Stephen Toub Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- dotnet/src/Generated/Rpc.cs | 73 ++++++++- dotnet/src/Generated/SessionEvents.cs | 151 ++++++++++++++++++ go/rpc/zrpc.go | 58 +++++++ go/rpc/zsession_events.go | 21 +++ go/zsession_events.go | 7 + java/pom.xml | 2 +- java/scripts/codegen/package-lock.json | 72 ++++----- java/scripts/codegen/package.json | 2 +- .../AssistantMessageToolRequest.java | 4 +- .../AssistantMessageToolRequestCaller.java | 29 ++++ ...AssistantMessageToolRequestCallerType.java | 33 ++++ .../github/copilot/generated/AutoTier.java | 37 +++++ .../copilot/generated/SessionResumeEvent.java | 2 + .../copilot/generated/SessionStartEvent.java | 2 + .../copilot/generated/rpc/AutoTier.java | 37 +++++ .../generated/rpc/CapiSessionOptions.java | 2 + .../rpc/SessionCommandsEnqueueParams.java | 4 +- .../copilot/generated/rpc/SessionRpc.java | 3 + .../generated/rpc/SessionSandboxApi.java | 42 +++++ ...sionSandboxGetEnforcementStatusParams.java | 30 ++++ ...sionSandboxGetEnforcementStatusResult.java | 34 ++++ .../copilot/SessionEventHandlingTest.java | 4 +- nodejs/package-lock.json | 54 +++---- nodejs/package.json | 2 +- nodejs/samples/package-lock.json | 2 +- nodejs/src/generated/rpc.ts | 38 ++++- nodejs/src/generated/session-events.ts | 27 ++++ nodejs/test/session-event-codegen.test.ts | 29 +++- python/copilot/generated/rpc.py | 97 +++++++---- python/copilot/generated/session_events.py | 56 +++++++ rust/src/generated/api_types.rs | 72 ++++++++- rust/src/generated/rpc.rs | 43 +++++ rust/src/generated/session_events.rs | 48 ++++++ rust/tests/e2e/commands.rs | 1 + rust/tests/e2e/rpc_queue.rs | 3 + scripts/codegen/python.ts | 12 +- test/harness/package-lock.json | 54 +++---- test/harness/package.json | 2 +- 38 files changed, 1051 insertions(+), 138 deletions(-) create mode 100644 java/sdk/src/generated/java/com/github/copilot/generated/AssistantMessageToolRequestCaller.java create mode 100644 java/sdk/src/generated/java/com/github/copilot/generated/AssistantMessageToolRequestCallerType.java create mode 100644 java/sdk/src/generated/java/com/github/copilot/generated/AutoTier.java create mode 100644 java/sdk/src/generated/java/com/github/copilot/generated/rpc/AutoTier.java create mode 100644 java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionSandboxApi.java create mode 100644 java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionSandboxGetEnforcementStatusParams.java create mode 100644 java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionSandboxGetEnforcementStatusResult.java diff --git a/dotnet/src/Generated/Rpc.cs b/dotnet/src/Generated/Rpc.cs index f3c1a57e6e..c97660f369 100644 --- a/dotnet/src/Generated/Rpc.cs +++ b/dotnet/src/Generated/Rpc.cs @@ -5393,6 +5393,32 @@ internal sealed class LogRequest public string? Url { get; set; } } +/// Managed sandbox enforcement state for a session. +[Experimental(Diagnostics.Experimental)] +public sealed class SandboxEnforcementStatus +{ + /// 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; } + + /// Whether the effective managed policy requires an available sandbox backend. + [JsonPropertyName("required")] + public bool Required { get; set; } +} + +/// Identifies the target session. +[Experimental(Diagnostics.Experimental)] +internal sealed class SessionSandboxGetEnforcementStatusRequest +{ + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + /// Authentication status and account metadata for the session. [Experimental(Diagnostics.Experimental)] public sealed class SessionAuthStatus @@ -10933,6 +10959,10 @@ public sealed class OptionsUpdateAdditionalContentExclusionPolicy [Experimental(Diagnostics.Experimental)] public sealed class CapiSessionOptions { + /// Routing preference used when the session model is `auto`. The runtime persists the preference across cold resume. When omitted, the default routing behavior is used. Resuming an already-resident session cannot change its preference. + [JsonPropertyName("autoTier")] + public AutoTier? AutoTier { get; set; } + /// Whether to use WebSocket transport for the CAPI Responses API. Enabled by default when the model advertises `ws:/responses` support; set to `false` to force the HTTP Responses transport in environments where WebSockets are blocked (e.g. behind a proxy). Setting this to `false` is equivalent to the `COPILOT_CLI_DISABLE_WEBSOCKET_RESPONSES` environment variable. [JsonPropertyName("enableWebSocketResponses")] public bool? EnableWebSocketResponses { get; set; } @@ -13395,6 +13425,10 @@ internal sealed class EnqueueCommandParams [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; @@ -30941,6 +30975,12 @@ internal SessionRpc(CopilotSession session) internal CopilotSession Session => _session; + /// Sandbox APIs. + public SandboxApi Sandbox => + field ?? + Interlocked.CompareExchange(ref field, new(_session), null) ?? + field; + /// GitHubAuth APIs. public GitHubAuthApi GitHubAuth => field ?? @@ -31313,6 +31353,29 @@ public async Task LogAsync(string message, SessionLogLevel? level = n } } +/// Provides session-scoped Sandbox APIs. +[Experimental(Diagnostics.Experimental)] +public sealed class SandboxApi +{ + private readonly CopilotSession _session; + + internal SandboxApi(CopilotSession session) + { + _session = session; + } + + /// Returns whether managed policy requires sandbox enforcement and whether an enforcement failure has permanently blocked the session. + /// The to monitor for cancellation requests. The default is . + /// Managed sandbox enforcement state for a session. + public async Task GetEnforcementStatusAsync(CancellationToken cancellationToken = default) + { + _session.ThrowIfDisposed(); + + var request = new SessionSandboxGetEnforcementStatusRequest { SessionId = _session.SessionId }; + return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.sandbox.getEnforcementStatus", [request], cancellationToken); + } +} + /// Provides session-scoped GitHubAuth APIs. [Experimental(Diagnostics.Experimental)] public sealed class GitHubAuthApi @@ -33687,14 +33750,15 @@ public async Task ExecuteAsync(string commandName, string /// Enqueues a slash command for FIFO processing on the local session. /// Slash-prefixed command string to enqueue, e.g. '/compact' or '/model gpt-4'. Queued FIFO with any in-flight items; if the session is idle, processing kicks off immediately. + /// Optional user-facing text for the queue row. The command string is shown when omitted. /// The to monitor for cancellation requests. The default is . /// Indicates whether the command was accepted into the local execution queue. - public async Task EnqueueAsync(string command, CancellationToken cancellationToken = default) + public async Task EnqueueAsync(string command, string? displayText = null, CancellationToken cancellationToken = default) { ArgumentNullException.ThrowIfNull(command); _session.ThrowIfDisposed(); - var request = new EnqueueCommandParams { SessionId = _session.SessionId, Command = command }; + var request = new EnqueueCommandParams { SessionId = _session.SessionId, Command = command, DisplayText = displayText }; return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.commands.enqueue", [request], cancellationToken); } @@ -35592,6 +35656,8 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH [JsonSerializable(typeof(GitHub.Copilot.AssistantMessageStartData), TypeInfoPropertyName = "SessionEventsAssistantMessageStartData")] [JsonSerializable(typeof(GitHub.Copilot.AssistantMessageStartEvent), TypeInfoPropertyName = "SessionEventsAssistantMessageStartEvent")] [JsonSerializable(typeof(GitHub.Copilot.AssistantMessageToolRequest), TypeInfoPropertyName = "SessionEventsAssistantMessageToolRequest")] +[JsonSerializable(typeof(GitHub.Copilot.AssistantMessageToolRequestCaller), TypeInfoPropertyName = "SessionEventsAssistantMessageToolRequestCaller")] +[JsonSerializable(typeof(GitHub.Copilot.AssistantMessageToolRequestCallerType), TypeInfoPropertyName = "SessionEventsAssistantMessageToolRequestCallerType")] [JsonSerializable(typeof(GitHub.Copilot.AssistantMessageToolRequestType), TypeInfoPropertyName = "SessionEventsAssistantMessageToolRequestType")] [JsonSerializable(typeof(GitHub.Copilot.AssistantReasoningData), TypeInfoPropertyName = "SessionEventsAssistantReasoningData")] [JsonSerializable(typeof(GitHub.Copilot.AssistantReasoningDeltaData), TypeInfoPropertyName = "SessionEventsAssistantReasoningDeltaData")] @@ -35646,6 +35712,7 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH [JsonSerializable(typeof(GitHub.Copilot.AutoModeSwitchRequestedData), TypeInfoPropertyName = "SessionEventsAutoModeSwitchRequestedData")] [JsonSerializable(typeof(GitHub.Copilot.AutoModeSwitchRequestedEvent), TypeInfoPropertyName = "SessionEventsAutoModeSwitchRequestedEvent")] [JsonSerializable(typeof(GitHub.Copilot.AutoModeSwitchResponse), TypeInfoPropertyName = "SessionEventsAutoModeSwitchResponse")] +[JsonSerializable(typeof(GitHub.Copilot.AutoTier), TypeInfoPropertyName = "SessionEventsAutoTier")] [JsonSerializable(typeof(GitHub.Copilot.AutopilotObjectiveChangedOperation), TypeInfoPropertyName = "SessionEventsAutopilotObjectiveChangedOperation")] [JsonSerializable(typeof(GitHub.Copilot.AutopilotObjectiveChangedStatus), TypeInfoPropertyName = "SessionEventsAutopilotObjectiveChangedStatus")] [JsonSerializable(typeof(GitHub.Copilot.BinaryAssetReference), TypeInfoPropertyName = "SessionEventsBinaryAssetReference")] @@ -36484,6 +36551,7 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH [JsonSerializable(typeof(SandboxConfigUserPolicyNetwork))] [JsonSerializable(typeof(SandboxConfigUserPolicyNetworkProxy))] [JsonSerializable(typeof(SandboxConfigUserPolicySeatbelt))] +[JsonSerializable(typeof(SandboxEnforcementStatus))] [JsonSerializable(typeof(ScheduleAddAtRequest))] [JsonSerializable(typeof(ScheduleAddCronRequest))] [JsonSerializable(typeof(ScheduleAddRequest))] @@ -36620,6 +36688,7 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH [JsonSerializable(typeof(SessionQueueRemoveMostRecentRequest))] [JsonSerializable(typeof(SessionQueueSnapshotRequest))] [JsonSerializable(typeof(SessionRemoteDisableRequest))] +[JsonSerializable(typeof(SessionSandboxGetEnforcementStatusRequest))] [JsonSerializable(typeof(SessionScheduleHasSelfPacedRequest))] [JsonSerializable(typeof(SessionScheduleHydrateRequest))] [JsonSerializable(typeof(SessionScheduleListRequest))] diff --git a/dotnet/src/Generated/SessionEvents.cs b/dotnet/src/Generated/SessionEvents.cs index b4351c8f2e..4c7ebc6ffa 100644 --- a/dotnet/src/Generated/SessionEvents.cs +++ b/dotnet/src/Generated/SessionEvents.cs @@ -1916,6 +1916,11 @@ public sealed partial class SessionStartData [JsonPropertyName("alreadyInUse")] public bool? AlreadyInUse { get; set; } + /// Auto routing preference selected at session creation time. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("autoTier")] + public AutoTier? AutoTier { get; set; } + /// Working directory and git context at session start. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("context")] @@ -1995,6 +2000,11 @@ public sealed partial class SessionResumeData [JsonPropertyName("alreadyInUse")] public bool? AlreadyInUse { get; set; } + /// Auto routing preference active at resume time. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("autoTier")] + public AutoTier? AutoTier { get; set; } + /// Updated working directory and git context at resume time. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("context")] @@ -7098,6 +7108,19 @@ public sealed partial class AssistantMessageServerTools public JsonElement[]? RawContentBlocks { get; set; } } +/// Hosted program that requested this client tool call. +/// Nested data type for AssistantMessageToolRequestCaller. +public sealed partial class AssistantMessageToolRequestCaller +{ + /// Provider-assigned identifier for the hosted caller. + [JsonPropertyName("callerId")] + public required string CallerId { get; set; } + + /// Kind of hosted caller that requested the client tool call. + [JsonPropertyName("type")] + public required AssistantMessageToolRequestCallerType Type { get; set; } +} + /// A tool invocation request from the assistant. /// Nested data type for AssistantMessageToolRequest. public sealed partial class AssistantMessageToolRequest @@ -7107,6 +7130,11 @@ public sealed partial class AssistantMessageToolRequest [JsonPropertyName("arguments")] public JsonElement? Arguments { get; set; } + /// Hosted program that requested this client tool call. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("caller")] + public AssistantMessageToolRequestCaller? Caller { get; set; } + /// Resolved intention summary describing what this specific call does. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("intentionSummary")] @@ -10305,6 +10333,70 @@ public sealed partial class McpAppToolCallCompleteToolMeta public McpAppToolCallCompleteToolMetaUI? Ui { get; set; } } +/// Routing preference used when the session model is `auto`. +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct AutoTier : IEquatable +{ + private readonly string? _value; + + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public AutoTier(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } + + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; + + /// Optimize for efficiency. + public static AutoTier Efficiency { get; } = new("efficiency"); + + /// Balance efficiency and intelligence. + public static AutoTier Balance { get; } = new("balance"); + + /// Optimize for intelligence. + public static AutoTier Intelligence { get; } = new("intelligence"); + + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(AutoTier left, AutoTier right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(AutoTier left, AutoTier right) => !(left == right); + + /// + public override bool Equals(object? obj) => obj is AutoTier other && Equals(other); + + /// + public bool Equals(AutoTier other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + + /// + public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); + + /// + public override string ToString() => Value; + + /// Provides a for serializing instances. + [EditorBrowsable(EditorBrowsableState.Never)] + public sealed class Converter : JsonConverter + { + /// + public override AutoTier Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } + + /// + public override void Write(Utf8JsonWriter writer, AutoTier value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(AutoTier)); + } + } +} + /// Hosting platform type of the repository (github or ado). [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] @@ -12374,6 +12466,64 @@ public override void Write(Utf8JsonWriter writer, CitationProvider value, JsonSe } } +/// Hosted program caller type. +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct AssistantMessageToolRequestCallerType : IEquatable +{ + private readonly string? _value; + + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public AssistantMessageToolRequestCallerType(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } + + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; + + /// Gets the program value. + public static AssistantMessageToolRequestCallerType Program { get; } = new("program"); + + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(AssistantMessageToolRequestCallerType left, AssistantMessageToolRequestCallerType right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(AssistantMessageToolRequestCallerType left, AssistantMessageToolRequestCallerType right) => !(left == right); + + /// + public override bool Equals(object? obj) => obj is AssistantMessageToolRequestCallerType other && Equals(other); + + /// + public bool Equals(AssistantMessageToolRequestCallerType 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 AssistantMessageToolRequestCallerType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } + + /// + public override void Write(Utf8JsonWriter writer, AssistantMessageToolRequestCallerType value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(AssistantMessageToolRequestCallerType)); + } + } +} + /// API endpoint used for this model call, matching CAPI supported_endpoints vocabulary. [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] @@ -15438,6 +15588,7 @@ public override void Write(Utf8JsonWriter writer, ExtensionsLoadedExtensionStatu [JsonSerializable(typeof(AssistantMessageStartData))] [JsonSerializable(typeof(AssistantMessageStartEvent))] [JsonSerializable(typeof(AssistantMessageToolRequest))] +[JsonSerializable(typeof(AssistantMessageToolRequestCaller))] [JsonSerializable(typeof(AssistantReasoningData))] [JsonSerializable(typeof(AssistantReasoningDeltaData))] [JsonSerializable(typeof(AssistantReasoningDeltaEvent))] diff --git a/go/rpc/zrpc.go b/go/rpc/zrpc.go index edd5f925fe..706f32bf5f 100644 --- a/go/rpc/zrpc.go +++ b/go/rpc/zrpc.go @@ -1361,6 +1361,10 @@ type CanvasSessionContext struct { // Experimental: CapiSessionOptions is part of an experimental API and may change or be // removed. type CapiSessionOptions struct { + // Routing preference used when the session model is `auto`. The runtime persists the + // preference across cold resume. When omitted, the default routing behavior is used. + // Resuming an already-resident session cannot change its preference. + AutoTier *AutoTier `json:"autoTier,omitempty"` // Whether to use WebSocket transport for the CAPI Responses API. Enabled by default when // the model advertises `ws:/responses` support; set to `false` to force the HTTP Responses // transport in environments where WebSockets are blocked (e.g. behind a proxy). Setting @@ -2599,6 +2603,8 @@ type EnqueueCommandParams struct { // Slash-prefixed command string to enqueue, e.g. '/compact' or '/model gpt-4'. Queued FIFO // with any in-flight items; if the session is idle, processing kicks off immediately. Command string `json:"command"` + // Optional user-facing text for the queue row. The command string is shown when omitted. + DisplayText *string `json:"displayText,omitempty"` } // Indicates whether the command was accepted into the local execution queue. @@ -10343,6 +10349,18 @@ type SandboxConfigUserPolicySeatbelt struct { KeychainAccess *bool `json:"keychainAccess,omitempty"` } +// Managed sandbox enforcement state for a session. +// Experimental: SandboxEnforcementStatus is part of an experimental API and may change or +// be removed. +type SandboxEnforcementStatus struct { + // Whether an enforcement failure has permanently blocked the session. + Blocked bool `json:"blocked"` + // The first sandbox enforcement failure that blocked the session. + Reason *string `json:"reason,omitempty"` + // Whether the effective managed policy requires an available sandbox backend. + Required bool `json:"required"` +} + // Register an absolute-time scheduled prompt. // Experimental: ScheduleAddAtRequest is part of an experimental API and may change or be // removed. @@ -15858,6 +15876,19 @@ const ( AuthInfoTypeUser AuthInfoType = "user" ) +// Routing preference used when the session model is `auto`. +// Experimental: AutoTier is part of an experimental API and may change or be removed. +type AutoTier string + +const ( + // Balance efficiency and intelligence. + AutoTierBalance AutoTier = "balance" + // Optimize for efficiency. + AutoTierEfficiency AutoTier = "efficiency" + // Optimize for intelligence. + AutoTierIntelligence AutoTier = "intelligence" +) + // Custom input-format kind. // Experimental: BuiltinToolFormatType is part of an experimental API and may change or be // removed. @@ -21250,6 +21281,9 @@ func (a *CommandsAPI) Enqueue(ctx context.Context, params *EnqueueCommandParams) req := map[string]any{"sessionId": a.sessionID} if params != nil { req["command"] = params.Command + if params.DisplayText != nil { + req["displayText"] = *params.DisplayText + } } raw, err := a.client.Request(ctx, "session.commands.enqueue", req) if err != nil { @@ -25037,6 +25071,28 @@ func (a *RemoteAPI) NotifySteerableChanged(ctx context.Context, params *RemoteNo return &result, nil } +// Experimental: SandboxAPI contains experimental APIs that may change or be removed. +type SandboxAPI sessionAPI + +// GetEnforcementStatus returns whether managed policy requires sandbox enforcement and +// whether an enforcement failure has permanently blocked the session. +// +// RPC method: session.sandbox.getEnforcementStatus. +// +// Returns: Managed sandbox enforcement state for a session. +func (a *SandboxAPI) GetEnforcementStatus(ctx context.Context) (*SandboxEnforcementStatus, error) { + req := map[string]any{"sessionId": a.sessionID} + raw, err := a.client.Request(ctx, "session.sandbox.getEnforcementStatus", req) + if err != nil { + return nil, err + } + var result SandboxEnforcementStatus + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + // Experimental: ScheduleAPI contains experimental APIs that may change or be removed. type ScheduleAPI sessionAPI @@ -26586,6 +26642,7 @@ type SessionRPC struct { Provider *ProviderAPI Queue *QueueAPI Remote *RemoteAPI + Sandbox *SandboxAPI Schedule *ScheduleAPI Shell *ShellAPI Skills *SkillsAPI @@ -26907,6 +26964,7 @@ func NewSessionRPC(client *jsonrpc2.Client, sessionID string) *SessionRPC { r.Provider = (*ProviderAPI)(&r.common) r.Queue = (*QueueAPI)(&r.common) r.Remote = (*RemoteAPI)(&r.common) + r.Sandbox = (*SandboxAPI)(&r.common) r.Schedule = (*ScheduleAPI)(&r.common) r.Shell = (*ShellAPI)(&r.common) r.Skills = (*SkillsAPI)(&r.common) diff --git a/go/rpc/zsession_events.go b/go/rpc/zsession_events.go index 7f24ab6283..07504e8815 100644 --- a/go/rpc/zsession_events.go +++ b/go/rpc/zsession_events.go @@ -2128,6 +2128,8 @@ func (*SessionHandoffData) Type() SessionEventType { return SessionEventTypeSess type SessionStartData struct { // Whether the session was already in use by another client at start time AlreadyInUse *bool `json:"alreadyInUse,omitempty"` + // Auto routing preference selected at session creation time + AutoTier *AutoTier `json:"autoTier,omitempty"` // Working directory and git context at session start Context *WorkingDirectoryContext `json:"context,omitempty"` // Context tier selected at session creation time for models with tiered context pricing; null when no tier is selected (e.g., non-tiered model) @@ -2206,6 +2208,8 @@ func (*SessionSessionLimitsChangedData) Type() SessionEventType { type SessionResumeData struct { // Whether the session was already in use by another client at resume time AlreadyInUse *bool `json:"alreadyInUse,omitempty"` + // Auto routing preference active at resume time + AutoTier *AutoTier `json:"autoTier,omitempty"` // Updated working directory and git context at resume time Context *WorkingDirectoryContext `json:"context,omitempty"` // Context tier currently selected at resume time; null when no tier is active @@ -2808,6 +2812,8 @@ type AssistantMessageServerTools struct { type AssistantMessageToolRequest struct { // Arguments to pass to the tool, format depends on the tool Arguments any `json:"arguments,omitempty"` + // Hosted program that requested this client tool call + Caller *AssistantMessageToolRequestCaller `json:"caller,omitempty"` // Resolved intention summary describing what this specific call does IntentionSummary *string `json:"intentionSummary,omitempty"` // Name of the MCP server hosting this tool, when the tool is an MCP tool @@ -2824,6 +2830,14 @@ type AssistantMessageToolRequest struct { Type *AssistantMessageToolRequestType `json:"type,omitempty"` } +// Hosted program that requested this client tool call +type AssistantMessageToolRequestCaller struct { + // Provider-assigned identifier for the hosted caller. + CallerID string `json:"callerId"` + // Kind of hosted caller that requested the client tool call. + Type AssistantMessageToolRequestCallerType `json:"type"` +} + // Per-request cost and usage data from the CAPI copilot_usage response field type AssistantUsageCopilotUsage struct { // Itemized token usage breakdown @@ -4856,6 +4870,13 @@ const ( AgentInterruptedCancelPhasePreFirstToken AgentInterruptedCancelPhase = "pre_first_token" ) +// Hosted program caller type +type AssistantMessageToolRequestCallerType string + +const ( + AssistantMessageToolRequestCallerTypeProgram AssistantMessageToolRequestCallerType = "program" +) + // Tool call type: "function" for standard tool calls, "custom" for grammar-based tool calls. Defaults to "function" when absent. type AssistantMessageToolRequestType string diff --git a/go/zsession_events.go b/go/zsession_events.go index 38ea78087b..8731009064 100644 --- a/go/zsession_events.go +++ b/go/zsession_events.go @@ -23,6 +23,8 @@ type ( AssistantMessageServerTools = rpc.AssistantMessageServerTools AssistantMessageStartData = rpc.AssistantMessageStartData AssistantMessageToolRequest = rpc.AssistantMessageToolRequest + AssistantMessageToolRequestCaller = rpc.AssistantMessageToolRequestCaller + AssistantMessageToolRequestCallerType = rpc.AssistantMessageToolRequestCallerType AssistantMessageToolRequestType = rpc.AssistantMessageToolRequestType AssistantReasoningData = rpc.AssistantReasoningData AssistantReasoningDeltaData = rpc.AssistantReasoningDeltaData @@ -69,6 +71,7 @@ type ( AutoModeSwitchResponse = rpc.AutoModeSwitchResponse AutopilotObjectiveChangedOperation = rpc.AutopilotObjectiveChangedOperation AutopilotObjectiveChangedStatus = rpc.AutopilotObjectiveChangedStatus + AutoTier = rpc.AutoTier BinaryAssetReference = rpc.BinaryAssetReference BinaryAssetReferenceType = rpc.BinaryAssetReferenceType BinaryAssetType = rpc.BinaryAssetType @@ -424,6 +427,7 @@ const ( AgentInterruptedActivityToolCall = rpc.AgentInterruptedActivityToolCall AgentInterruptedCancelPhaseMidStream = rpc.AgentInterruptedCancelPhaseMidStream AgentInterruptedCancelPhasePreFirstToken = rpc.AgentInterruptedCancelPhasePreFirstToken + AssistantMessageToolRequestCallerTypeProgram = rpc.AssistantMessageToolRequestCallerTypeProgram AssistantMessageToolRequestTypeCustom = rpc.AssistantMessageToolRequestTypeCustom AssistantMessageToolRequestTypeFunction = rpc.AssistantMessageToolRequestTypeFunction AssistantUsageAPIEndpointChatCompletions = rpc.AssistantUsageAPIEndpointChatCompletions @@ -472,6 +476,9 @@ const ( AutopilotObjectiveChangedStatusCapReached = rpc.AutopilotObjectiveChangedStatusCapReached AutopilotObjectiveChangedStatusCompleted = rpc.AutopilotObjectiveChangedStatusCompleted AutopilotObjectiveChangedStatusPaused = rpc.AutopilotObjectiveChangedStatusPaused + AutoTierBalance = rpc.AutoTierBalance + AutoTierEfficiency = rpc.AutoTierEfficiency + AutoTierIntelligence = rpc.AutoTierIntelligence BinaryAssetReferenceTypeImage = rpc.BinaryAssetReferenceTypeImage BinaryAssetReferenceTypeResource = rpc.BinaryAssetReferenceTypeResource BinaryAssetTypeImage = rpc.BinaryAssetTypeImage diff --git a/java/pom.xml b/java/pom.xml index 3e5e1645a5..5b493fcc55 100644 --- a/java/pom.xml +++ b/java/pom.xml @@ -63,7 +63,7 @@ DO NOT EDIT MANUALLY. Updated by the update-copilot-dependency workflow. --> - ^1.0.82-0 + ^1.0.82 true diff --git a/java/scripts/codegen/package-lock.json b/java/scripts/codegen/package-lock.json index 985ae5d5db..71abcd72dc 100644 --- a/java/scripts/codegen/package-lock.json +++ b/java/scripts/codegen/package-lock.json @@ -6,7 +6,7 @@ "": { "name": "copilot-sdk-java-codegen", "dependencies": { - "@github/copilot": "^1.0.82-0", + "@github/copilot": "^1.0.82", "json-schema": "^0.4.0", "tsx": "^4.23.12" } @@ -428,9 +428,9 @@ } }, "node_modules/@github/copilot": { - "version": "1.0.82-0", - "resolved": "https://registry.npmjs.org/@github/copilot/-/copilot-1.0.82-0.tgz", - "integrity": "sha512-fSZVNAzFFYaS6btYD0+cKF7SrrtOklhpkPs/cIMZY7Fgxoa6rfZrlTWXlQNgNIdwLp51XxwTfxnZO+D9+MQ5yg==", + "version": "1.0.82", + "resolved": "https://registry.npmjs.org/@github/copilot/-/copilot-1.0.82.tgz", + "integrity": "sha512-+mDIwBO3dCpL3k2rVLc4+1tFzqcVBTJNA/0co+okEpmCgcHjaz91Cqq7++pJKN5yJQuGC6bKangm7PSoheI1Xw==", "license": "SEE LICENSE IN LICENSE.md", "dependencies": { "detect-libc": "^2.1.2" @@ -439,20 +439,20 @@ "copilot": "npm-loader.js" }, "optionalDependencies": { - "@github/copilot-darwin-arm64": "1.0.82-0", - "@github/copilot-darwin-x64": "1.0.82-0", - "@github/copilot-linux-arm64": "1.0.82-0", - "@github/copilot-linux-x64": "1.0.82-0", - "@github/copilot-linuxmusl-arm64": "1.0.82-0", - "@github/copilot-linuxmusl-x64": "1.0.82-0", - "@github/copilot-win32-arm64": "1.0.82-0", - "@github/copilot-win32-x64": "1.0.82-0" + "@github/copilot-darwin-arm64": "1.0.82", + "@github/copilot-darwin-x64": "1.0.82", + "@github/copilot-linux-arm64": "1.0.82", + "@github/copilot-linux-x64": "1.0.82", + "@github/copilot-linuxmusl-arm64": "1.0.82", + "@github/copilot-linuxmusl-x64": "1.0.82", + "@github/copilot-win32-arm64": "1.0.82", + "@github/copilot-win32-x64": "1.0.82" } }, "node_modules/@github/copilot-darwin-arm64": { - "version": "1.0.82-0", - "resolved": "https://registry.npmjs.org/@github/copilot-darwin-arm64/-/copilot-darwin-arm64-1.0.82-0.tgz", - "integrity": "sha512-TzBYfyvxcw3z9Mu7U8TsFo/Nq7m5XS6ahT71aPL+gx/YId0kmenI27b9daXsK6LA1D0gsFGwNBDKINddqntt1g==", + "version": "1.0.82", + "resolved": "https://registry.npmjs.org/@github/copilot-darwin-arm64/-/copilot-darwin-arm64-1.0.82.tgz", + "integrity": "sha512-UpVSFA0COmlIakAr7/6WJqJlabtKgY8y5en6La3IxGxXsLlbkGoeevSqyfXvqJyHxaGn0YGpOC1oEPL379JfCw==", "cpu": [ "arm64" ], @@ -466,9 +466,9 @@ } }, "node_modules/@github/copilot-darwin-x64": { - "version": "1.0.82-0", - "resolved": "https://registry.npmjs.org/@github/copilot-darwin-x64/-/copilot-darwin-x64-1.0.82-0.tgz", - "integrity": "sha512-Lm/U5Q8kN8yEeBTWKTfIxXAgXaT6zqdBzAAO7lA4DWHZ92AEeZZiVTs6jEWsQ2aWB2uMs6zbn0vi6t+/jIqCGw==", + "version": "1.0.82", + "resolved": "https://registry.npmjs.org/@github/copilot-darwin-x64/-/copilot-darwin-x64-1.0.82.tgz", + "integrity": "sha512-wMKbxK8fpKsbATjn9dE31Pae67H1xu6dmaCuyXpXjXLsNPVjeLFszJZ30MAOwxRWz4dmA8VvEJZmLgJekojo2Q==", "cpu": [ "x64" ], @@ -482,9 +482,9 @@ } }, "node_modules/@github/copilot-linux-arm64": { - "version": "1.0.82-0", - "resolved": "https://registry.npmjs.org/@github/copilot-linux-arm64/-/copilot-linux-arm64-1.0.82-0.tgz", - "integrity": "sha512-YERVMC1Q4p6l6KQHL5rVOI52rWbvgp9IwyzUBaVSGrfFuqu5BEvZ9bgHPsxTYi3Npkt5KVOXEyPMU5rAY09qQQ==", + "version": "1.0.82", + "resolved": "https://registry.npmjs.org/@github/copilot-linux-arm64/-/copilot-linux-arm64-1.0.82.tgz", + "integrity": "sha512-YDQmh0F+GzWORPmNNxQdcjHcXHxcy08dhMhbAhu4cqtMeA2sZWQqhfk9mJhHppMm9U0uR7h0KAB6nsgw/8Bsuw==", "cpu": [ "arm64" ], @@ -498,9 +498,9 @@ } }, "node_modules/@github/copilot-linux-x64": { - "version": "1.0.82-0", - "resolved": "https://registry.npmjs.org/@github/copilot-linux-x64/-/copilot-linux-x64-1.0.82-0.tgz", - "integrity": "sha512-z2hxMVjqt4+xDRFTZv3/0K3X+aqcJhd6zPO2JxCpOVTh5CNZFaWk+XIa2iXAPWxFqdKJsQ4muXMl3zInaAOkRw==", + "version": "1.0.82", + "resolved": "https://registry.npmjs.org/@github/copilot-linux-x64/-/copilot-linux-x64-1.0.82.tgz", + "integrity": "sha512-vqmG9665ktgLHAkGKMjp4uVMdHqLxi8uS9zL+g2pMwZUPoM7JxkaSfOoeyYr99caF7J6VIJTWv4LdRNQyG5jrQ==", "cpu": [ "x64" ], @@ -514,9 +514,9 @@ } }, "node_modules/@github/copilot-linuxmusl-arm64": { - "version": "1.0.82-0", - "resolved": "https://registry.npmjs.org/@github/copilot-linuxmusl-arm64/-/copilot-linuxmusl-arm64-1.0.82-0.tgz", - "integrity": "sha512-EcUCv2PKhBzCCvpTaS511VYTDWyhudyIRPvBpc9gFNO3hjlgiNDusf4k9vP6+E3/lHenwGYzMsyRHcYIOy4vcQ==", + "version": "1.0.82", + "resolved": "https://registry.npmjs.org/@github/copilot-linuxmusl-arm64/-/copilot-linuxmusl-arm64-1.0.82.tgz", + "integrity": "sha512-14dWfa4rBME55bKmF+Z8V+WzZNgPQZKFFBQX+EOiAgLVV/arQCnQ1m7wwa5oXjQeCIgkS/L9J8uM8jNm6cZbOA==", "cpu": [ "arm64" ], @@ -530,9 +530,9 @@ } }, "node_modules/@github/copilot-linuxmusl-x64": { - "version": "1.0.82-0", - "resolved": "https://registry.npmjs.org/@github/copilot-linuxmusl-x64/-/copilot-linuxmusl-x64-1.0.82-0.tgz", - "integrity": "sha512-1fKVjUiZ1tdb0/d/re90EpFGXhlIPfjENp2Wo/2Kj592dWO3+IwM2qV/AOdMQ4pacW5iYHII7nibx1/EYq3LGQ==", + "version": "1.0.82", + "resolved": "https://registry.npmjs.org/@github/copilot-linuxmusl-x64/-/copilot-linuxmusl-x64-1.0.82.tgz", + "integrity": "sha512-Mauqa2TBjtB8W/1KXF/jJ4MbtwnLvoEQNHYoSyoX+s+nIpttmixmYBJDwKV89ZlQRdjOCuxOgraRLQ9hjjXvNQ==", "cpu": [ "x64" ], @@ -546,9 +546,9 @@ } }, "node_modules/@github/copilot-win32-arm64": { - "version": "1.0.82-0", - "resolved": "https://registry.npmjs.org/@github/copilot-win32-arm64/-/copilot-win32-arm64-1.0.82-0.tgz", - "integrity": "sha512-H341wuxQHhwe/yLmORHzwC3DGzFZgGzh+TpwfyK2zjeYVbwDZ3Bax8M+9CzIED9jIBdEfAmfGzxKUHFugpXTtQ==", + "version": "1.0.82", + "resolved": "https://registry.npmjs.org/@github/copilot-win32-arm64/-/copilot-win32-arm64-1.0.82.tgz", + "integrity": "sha512-PaW9s0GTgM+svtDkB583dZRrhLrO4o10y2ozMmQ0Hi5eB11C24UdC5U81nFlvKPED7+GlnBJIFQ+oHaxHrnp3A==", "cpu": [ "arm64" ], @@ -562,9 +562,9 @@ } }, "node_modules/@github/copilot-win32-x64": { - "version": "1.0.82-0", - "resolved": "https://registry.npmjs.org/@github/copilot-win32-x64/-/copilot-win32-x64-1.0.82-0.tgz", - "integrity": "sha512-f1ba3gG8NaoYWFHtHaHcLN4It7mclkWdCOXvwFPqPEwqCEIx/+Zh6VHiOeIcNWRS0elRP6QYDCKaTDy1TW27uQ==", + "version": "1.0.82", + "resolved": "https://registry.npmjs.org/@github/copilot-win32-x64/-/copilot-win32-x64-1.0.82.tgz", + "integrity": "sha512-m4iSROOMEPp1yRIQQwbnO0CDfwB4syESyHoeSLLFuynMR4/ApghAdyBrBQQcTKGsUu3R5rOE64RYlVmyKmuA9A==", "cpu": [ "x64" ], diff --git a/java/scripts/codegen/package.json b/java/scripts/codegen/package.json index f5d6bf66da..1b33a27904 100644 --- a/java/scripts/codegen/package.json +++ b/java/scripts/codegen/package.json @@ -7,7 +7,7 @@ "generate:java": "tsx java.ts" }, "dependencies": { - "@github/copilot": "^1.0.82-0", + "@github/copilot": "^1.0.82", "json-schema": "^0.4.0", "tsx": "^4.23.12" } diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/AssistantMessageToolRequest.java b/java/sdk/src/generated/java/com/github/copilot/generated/AssistantMessageToolRequest.java index 2013734012..bcc7c8206d 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/AssistantMessageToolRequest.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/AssistantMessageToolRequest.java @@ -36,6 +36,8 @@ public record AssistantMessageToolRequest( /** Original tool name on the MCP server, when the tool is an MCP tool */ @JsonProperty("mcpToolName") String mcpToolName, /** Resolved intention summary describing what this specific call does */ - @JsonProperty("intentionSummary") String intentionSummary + @JsonProperty("intentionSummary") String intentionSummary, + /** Hosted program that requested this client tool call */ + @JsonProperty("caller") AssistantMessageToolRequestCaller caller ) { } diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/AssistantMessageToolRequestCaller.java b/java/sdk/src/generated/java/com/github/copilot/generated/AssistantMessageToolRequestCaller.java new file mode 100644 index 0000000000..cb3d05a649 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/AssistantMessageToolRequestCaller.java @@ -0,0 +1,29 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: session-events.schema.json + +package com.github.copilot.generated; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import javax.annotation.processing.Generated; + +/** + * Hosted program that requested this client tool call + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record AssistantMessageToolRequestCaller( + /** Kind of hosted caller that requested the client tool call. */ + @JsonProperty("type") AssistantMessageToolRequestCallerType type, + /** Provider-assigned identifier for the hosted caller. */ + @JsonProperty("callerId") String callerId +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/AssistantMessageToolRequestCallerType.java b/java/sdk/src/generated/java/com/github/copilot/generated/AssistantMessageToolRequestCallerType.java new file mode 100644 index 0000000000..05be5068d9 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/AssistantMessageToolRequestCallerType.java @@ -0,0 +1,33 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: session-events.schema.json + +package com.github.copilot.generated; + +import javax.annotation.processing.Generated; + +/** + * Hosted program caller type + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public enum AssistantMessageToolRequestCallerType { + /** The {@code program} variant. */ + PROGRAM("program"); + + private final String value; + AssistantMessageToolRequestCallerType(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static AssistantMessageToolRequestCallerType fromValue(String value) { + for (AssistantMessageToolRequestCallerType v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown AssistantMessageToolRequestCallerType value: " + value); + } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/AutoTier.java b/java/sdk/src/generated/java/com/github/copilot/generated/AutoTier.java new file mode 100644 index 0000000000..254543160a --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/AutoTier.java @@ -0,0 +1,37 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: session-events.schema.json + +package com.github.copilot.generated; + +import javax.annotation.processing.Generated; + +/** + * Routing preference used when the session model is `auto`. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public enum AutoTier { + /** The {@code efficiency} variant. */ + EFFICIENCY("efficiency"), + /** The {@code balance} variant. */ + BALANCE("balance"), + /** The {@code intelligence} variant. */ + INTELLIGENCE("intelligence"); + + private final String value; + AutoTier(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static AutoTier fromValue(String value) { + for (AutoTier v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown AutoTier value: " + value); + } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/SessionResumeEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/SessionResumeEvent.java index a3f39d7696..de54f8a3ce 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/SessionResumeEvent.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/SessionResumeEvent.java @@ -51,6 +51,8 @@ public record SessionResumeEventData( @JsonProperty("verbosity") Verbosity verbosity, /** Context tier currently selected at resume time; null when no tier is active */ @JsonProperty("contextTier") ContextTier contextTier, + /** Auto routing preference active at resume time */ + @JsonProperty("autoTier") AutoTier autoTier, /** Session limits currently configured at resume time; null when no limits are active */ @JsonProperty("sessionLimits") SessionLimitsConfig sessionLimits, /** Updated working directory and git context at resume time */ diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/SessionStartEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/SessionStartEvent.java index bf8b4e91cf..b977ae036f 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/SessionStartEvent.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/SessionStartEvent.java @@ -55,6 +55,8 @@ public record SessionStartEventData( @JsonProperty("verbosity") Verbosity verbosity, /** Context tier selected at session creation time for models with tiered context pricing; null when no tier is selected (e.g., non-tiered model) */ @JsonProperty("contextTier") ContextTier contextTier, + /** Auto routing preference selected at session creation time */ + @JsonProperty("autoTier") AutoTier autoTier, /** Session limits configured at session creation time, if any */ @JsonProperty("sessionLimits") SessionLimitsConfig sessionLimits, /** Working directory and git context at session start */ diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/AutoTier.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/AutoTier.java new file mode 100644 index 0000000000..a4433e1ea9 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/AutoTier.java @@ -0,0 +1,37 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import javax.annotation.processing.Generated; + +/** + * Routing preference used when the session model is `auto`. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public enum AutoTier { + /** The {@code efficiency} variant. */ + EFFICIENCY("efficiency"), + /** The {@code balance} variant. */ + BALANCE("balance"), + /** The {@code intelligence} variant. */ + INTELLIGENCE("intelligence"); + + private final String value; + AutoTier(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static AutoTier fromValue(String value) { + for (AutoTier v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown AutoTier value: " + value); + } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CapiSessionOptions.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CapiSessionOptions.java index 27fd29128d..e77117b2cb 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CapiSessionOptions.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CapiSessionOptions.java @@ -21,6 +21,8 @@ @JsonInclude(JsonInclude.Include.NON_NULL) @JsonIgnoreProperties(ignoreUnknown = true) public record CapiSessionOptions( + /** Routing preference used when the session model is `auto`. The runtime persists the preference across cold resume. When omitted, the default routing behavior is used. Resuming an already-resident session cannot change its preference. */ + @JsonProperty("autoTier") AutoTier autoTier, /** Whether to use WebSocket transport for the CAPI Responses API. Enabled by default when the model advertises `ws:/responses` support; set to `false` to force the HTTP Responses transport in environments where WebSockets are blocked (e.g. behind a proxy). Setting this to `false` is equivalent to the `COPILOT_CLI_DISABLE_WEBSOCKET_RESPONSES` environment variable. */ @JsonProperty("enableWebSocketResponses") Boolean enableWebSocketResponses ) { diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionCommandsEnqueueParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionCommandsEnqueueParams.java index d7725bc9cf..073038d2fc 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionCommandsEnqueueParams.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionCommandsEnqueueParams.java @@ -27,6 +27,8 @@ public record SessionCommandsEnqueueParams( /** Target session identifier */ @JsonProperty("sessionId") String sessionId, /** Slash-prefixed command string to enqueue, e.g. '/compact' or '/model gpt-4'. Queued FIFO with any in-flight items; if the session is idle, processing kicks off immediately. */ - @JsonProperty("command") String command + @JsonProperty("command") String command, + /** Optional user-facing text for the queue row. The command string is shown when omitted. */ + @JsonProperty("displayText") String displayText ) { } diff --git a/java/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 05cfd396d4..b9bd6b8226 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 @@ -29,6 +29,8 @@ public final class SessionRpc { private final RpcCaller caller; private final String sessionId; + /** API methods for the {@code sandbox} namespace. */ + public final SessionSandboxApi sandbox; /** API methods for the {@code gitHubAuth} namespace. */ public final SessionGitHubAuthApi gitHubAuth; /** API methods for the {@code debug} namespace. */ @@ -115,6 +117,7 @@ public final class SessionRpc { public SessionRpc(RpcCaller caller, String sessionId) { this.caller = caller; this.sessionId = sessionId; + this.sandbox = new SessionSandboxApi(caller, sessionId); this.gitHubAuth = new SessionGitHubAuthApi(caller, sessionId); this.debug = new SessionDebugApi(caller, sessionId); this.canvas = new SessionCanvasApi(caller, sessionId); diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionSandboxApi.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionSandboxApi.java new file mode 100644 index 0000000000..55efb9da78 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionSandboxApi.java @@ -0,0 +1,42 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.github.copilot.CopilotExperimental; +import java.util.concurrent.CompletableFuture; +import javax.annotation.processing.Generated; + +/** + * API methods for the {@code sandbox} namespace. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public final class SessionSandboxApi { + + private final RpcCaller caller; + private final String sessionId; + + /** @param caller the RPC transport function */ + SessionSandboxApi(RpcCaller caller, String sessionId) { + this.caller = caller; + this.sessionId = sessionId; + } + + /** + * Identifies the target session. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + public CompletableFuture getEnforcementStatus() { + return caller.invoke("session.sandbox.getEnforcementStatus", java.util.Map.of("sessionId", this.sessionId), SessionSandboxGetEnforcementStatusResult.class); + } + +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionSandboxGetEnforcementStatusParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionSandboxGetEnforcementStatusParams.java new file mode 100644 index 0000000000..ec92981e04 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionSandboxGetEnforcementStatusParams.java @@ -0,0 +1,30 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import javax.annotation.processing.Generated; + +/** + * Identifies the target session. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ +@CopilotExperimental +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record SessionSandboxGetEnforcementStatusParams( + /** Target session identifier */ + @JsonProperty("sessionId") String sessionId +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionSandboxGetEnforcementStatusResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionSandboxGetEnforcementStatusResult.java new file mode 100644 index 0000000000..c1a0e77a1a --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionSandboxGetEnforcementStatusResult.java @@ -0,0 +1,34 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import javax.annotation.processing.Generated; + +/** + * Managed sandbox enforcement state for a session. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ +@CopilotExperimental +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record SessionSandboxGetEnforcementStatusResult( + /** Whether the effective managed policy requires an available sandbox backend. */ + @JsonProperty("required") Boolean required, + /** Whether an enforcement failure has permanently blocked the session. */ + @JsonProperty("blocked") Boolean blocked, + /** The first sandbox enforcement failure that blocked the session. */ + @JsonProperty("reason") String reason +) { +} diff --git a/java/sdk/src/test/java/com/github/copilot/SessionEventHandlingTest.java b/java/sdk/src/test/java/com/github/copilot/SessionEventHandlingTest.java index 529c42f2bb..b75e710720 100644 --- a/java/sdk/src/test/java/com/github/copilot/SessionEventHandlingTest.java +++ b/java/sdk/src/test/java/com/github/copilot/SessionEventHandlingTest.java @@ -213,7 +213,7 @@ void testHandlerReceivesCorrectEventData() { SessionStartEvent startEvent = createSessionStartEvent(); startEvent.setData(new SessionStartEvent.SessionStartEventData("my-session-123", null, null, null, null, null, - null, null, null, null, null, null, null, null, null, null)); + null, null, null, null, null, null, null, null, null, null, null)); dispatchEvent(startEvent); AssistantMessageEvent msgEvent = createAssistantMessageEvent("Test content"); @@ -890,7 +890,7 @@ private SessionStartEvent createSessionStartEvent() { private SessionStartEvent createSessionStartEvent(String sessionId) { var event = new SessionStartEvent(); var data = new SessionStartEvent.SessionStartEventData(sessionId, null, null, null, null, null, null, null, - null, null, null, null, null, null, null, null); + null, null, null, null, null, null, null, null, null); event.setData(data); return event; } diff --git a/nodejs/package-lock.json b/nodejs/package-lock.json index 3b8e6cb861..6cefab6031 100644 --- a/nodejs/package-lock.json +++ b/nodejs/package-lock.json @@ -9,7 +9,7 @@ "version": "0.0.0-dev", "license": "MIT", "dependencies": { - "@github/copilot": "^1.0.82-0", + "@github/copilot": "^1.0.82", "koffi": "^3.1.0", "vscode-jsonrpc": "^8.2.1", "zod": "^4.3.6" @@ -658,8 +658,8 @@ } }, "node_modules/@github/copilot": { - "version": "1.0.82-0", - "integrity": "sha512-fSZVNAzFFYaS6btYD0+cKF7SrrtOklhpkPs/cIMZY7Fgxoa6rfZrlTWXlQNgNIdwLp51XxwTfxnZO+D9+MQ5yg==", + "version": "1.0.82", + "integrity": "sha512-+mDIwBO3dCpL3k2rVLc4+1tFzqcVBTJNA/0co+okEpmCgcHjaz91Cqq7++pJKN5yJQuGC6bKangm7PSoheI1Xw==", "license": "SEE LICENSE IN LICENSE.md", "dependencies": { "detect-libc": "^2.1.2" @@ -668,19 +668,19 @@ "copilot": "npm-loader.js" }, "optionalDependencies": { - "@github/copilot-darwin-arm64": "1.0.82-0", - "@github/copilot-darwin-x64": "1.0.82-0", - "@github/copilot-linux-arm64": "1.0.82-0", - "@github/copilot-linux-x64": "1.0.82-0", - "@github/copilot-linuxmusl-arm64": "1.0.82-0", - "@github/copilot-linuxmusl-x64": "1.0.82-0", - "@github/copilot-win32-arm64": "1.0.82-0", - "@github/copilot-win32-x64": "1.0.82-0" + "@github/copilot-darwin-arm64": "1.0.82", + "@github/copilot-darwin-x64": "1.0.82", + "@github/copilot-linux-arm64": "1.0.82", + "@github/copilot-linux-x64": "1.0.82", + "@github/copilot-linuxmusl-arm64": "1.0.82", + "@github/copilot-linuxmusl-x64": "1.0.82", + "@github/copilot-win32-arm64": "1.0.82", + "@github/copilot-win32-x64": "1.0.82" } }, "node_modules/@github/copilot-darwin-arm64": { - "version": "1.0.82-0", - "integrity": "sha512-TzBYfyvxcw3z9Mu7U8TsFo/Nq7m5XS6ahT71aPL+gx/YId0kmenI27b9daXsK6LA1D0gsFGwNBDKINddqntt1g==", + "version": "1.0.82", + "integrity": "sha512-UpVSFA0COmlIakAr7/6WJqJlabtKgY8y5en6La3IxGxXsLlbkGoeevSqyfXvqJyHxaGn0YGpOC1oEPL379JfCw==", "cpu": [ "arm64" ], @@ -694,8 +694,8 @@ } }, "node_modules/@github/copilot-darwin-x64": { - "version": "1.0.82-0", - "integrity": "sha512-Lm/U5Q8kN8yEeBTWKTfIxXAgXaT6zqdBzAAO7lA4DWHZ92AEeZZiVTs6jEWsQ2aWB2uMs6zbn0vi6t+/jIqCGw==", + "version": "1.0.82", + "integrity": "sha512-wMKbxK8fpKsbATjn9dE31Pae67H1xu6dmaCuyXpXjXLsNPVjeLFszJZ30MAOwxRWz4dmA8VvEJZmLgJekojo2Q==", "cpu": [ "x64" ], @@ -709,8 +709,8 @@ } }, "node_modules/@github/copilot-linux-arm64": { - "version": "1.0.82-0", - "integrity": "sha512-YERVMC1Q4p6l6KQHL5rVOI52rWbvgp9IwyzUBaVSGrfFuqu5BEvZ9bgHPsxTYi3Npkt5KVOXEyPMU5rAY09qQQ==", + "version": "1.0.82", + "integrity": "sha512-YDQmh0F+GzWORPmNNxQdcjHcXHxcy08dhMhbAhu4cqtMeA2sZWQqhfk9mJhHppMm9U0uR7h0KAB6nsgw/8Bsuw==", "cpu": [ "arm64" ], @@ -724,8 +724,8 @@ } }, "node_modules/@github/copilot-linux-x64": { - "version": "1.0.82-0", - "integrity": "sha512-z2hxMVjqt4+xDRFTZv3/0K3X+aqcJhd6zPO2JxCpOVTh5CNZFaWk+XIa2iXAPWxFqdKJsQ4muXMl3zInaAOkRw==", + "version": "1.0.82", + "integrity": "sha512-vqmG9665ktgLHAkGKMjp4uVMdHqLxi8uS9zL+g2pMwZUPoM7JxkaSfOoeyYr99caF7J6VIJTWv4LdRNQyG5jrQ==", "cpu": [ "x64" ], @@ -739,8 +739,8 @@ } }, "node_modules/@github/copilot-linuxmusl-arm64": { - "version": "1.0.82-0", - "integrity": "sha512-EcUCv2PKhBzCCvpTaS511VYTDWyhudyIRPvBpc9gFNO3hjlgiNDusf4k9vP6+E3/lHenwGYzMsyRHcYIOy4vcQ==", + "version": "1.0.82", + "integrity": "sha512-14dWfa4rBME55bKmF+Z8V+WzZNgPQZKFFBQX+EOiAgLVV/arQCnQ1m7wwa5oXjQeCIgkS/L9J8uM8jNm6cZbOA==", "cpu": [ "arm64" ], @@ -754,8 +754,8 @@ } }, "node_modules/@github/copilot-linuxmusl-x64": { - "version": "1.0.82-0", - "integrity": "sha512-1fKVjUiZ1tdb0/d/re90EpFGXhlIPfjENp2Wo/2Kj592dWO3+IwM2qV/AOdMQ4pacW5iYHII7nibx1/EYq3LGQ==", + "version": "1.0.82", + "integrity": "sha512-Mauqa2TBjtB8W/1KXF/jJ4MbtwnLvoEQNHYoSyoX+s+nIpttmixmYBJDwKV89ZlQRdjOCuxOgraRLQ9hjjXvNQ==", "cpu": [ "x64" ], @@ -769,8 +769,8 @@ } }, "node_modules/@github/copilot-win32-arm64": { - "version": "1.0.82-0", - "integrity": "sha512-H341wuxQHhwe/yLmORHzwC3DGzFZgGzh+TpwfyK2zjeYVbwDZ3Bax8M+9CzIED9jIBdEfAmfGzxKUHFugpXTtQ==", + "version": "1.0.82", + "integrity": "sha512-PaW9s0GTgM+svtDkB583dZRrhLrO4o10y2ozMmQ0Hi5eB11C24UdC5U81nFlvKPED7+GlnBJIFQ+oHaxHrnp3A==", "cpu": [ "arm64" ], @@ -784,8 +784,8 @@ } }, "node_modules/@github/copilot-win32-x64": { - "version": "1.0.82-0", - "integrity": "sha512-f1ba3gG8NaoYWFHtHaHcLN4It7mclkWdCOXvwFPqPEwqCEIx/+Zh6VHiOeIcNWRS0elRP6QYDCKaTDy1TW27uQ==", + "version": "1.0.82", + "integrity": "sha512-m4iSROOMEPp1yRIQQwbnO0CDfwB4syESyHoeSLLFuynMR4/ApghAdyBrBQQcTKGsUu3R5rOE64RYlVmyKmuA9A==", "cpu": [ "x64" ], diff --git a/nodejs/package.json b/nodejs/package.json index 89863520e0..ff54fb3682 100644 --- a/nodejs/package.json +++ b/nodejs/package.json @@ -56,7 +56,7 @@ "author": "GitHub", "license": "MIT", "dependencies": { - "@github/copilot": "^1.0.82-0", + "@github/copilot": "^1.0.82", "koffi": "^3.1.0", "vscode-jsonrpc": "^8.2.1", "zod": "^4.3.6" diff --git a/nodejs/samples/package-lock.json b/nodejs/samples/package-lock.json index ad675a88c7..16a17c7d21 100644 --- a/nodejs/samples/package-lock.json +++ b/nodejs/samples/package-lock.json @@ -18,7 +18,7 @@ "version": "0.0.0-dev", "license": "MIT", "dependencies": { - "@github/copilot": "^1.0.82-0", + "@github/copilot": "^1.0.82", "koffi": "^3.1.0", "vscode-jsonrpc": "^8.2.1", "zod": "^4.3.6" diff --git a/nodejs/src/generated/rpc.ts b/nodejs/src/generated/rpc.ts index db0ea63dcc..024dd3a5bb 100644 --- a/nodejs/src/generated/rpc.ts +++ b/nodejs/src/generated/rpc.ts @@ -5,7 +5,7 @@ import type { MessageConnection } from "vscode-jsonrpc/node.js"; -import type { AbortReason, Attachment, ContextTier, EmbeddedBlobResourceContents, EmbeddedTextResourceContents, McpOauthHttpResponse, McpOauthWWWAuthenticateParams, McpServerSource, McpServerStatus, ModelChangeSource, PermissionMode, PermissionPromptRequest, PermissionRule, ReasoningSummary, SessionEvent, SessionLimitsConfig, SessionMode, ShutdownType, SkillSource, TaskCompleteData, TaskCompletionOutcome, UserToolSessionApproval, Verbosity } from "./session-events.js"; +import type { AbortReason, Attachment, AutoTier, ContextTier, EmbeddedBlobResourceContents, EmbeddedTextResourceContents, McpOauthHttpResponse, McpOauthWWWAuthenticateParams, McpServerSource, McpServerStatus, ModelChangeSource, PermissionMode, PermissionPromptRequest, PermissionRule, ReasoningSummary, SessionEvent, SessionLimitsConfig, SessionMode, ShutdownType, SkillSource, TaskCompleteData, TaskCompletionOutcome, UserToolSessionApproval, Verbosity } from "./session-events.js"; /** A value that can be represented losslessly on the SDK JSON wire. */ export type JsonValue = null | boolean | number | string | JsonValue[] | { [key: string]: JsonValue }; @@ -5487,6 +5487,7 @@ export interface CanvasProviderUnregisterRequest { */ /** @experimental */ export interface CapiSessionOptions { + autoTier?: AutoTier; /** * Whether to use WebSocket transport for the CAPI Responses API. Enabled by default when the model advertises `ws:/responses` support; set to `false` to force the HTTP Responses transport in environments where WebSockets are blocked (e.g. behind a proxy). Setting this to `false` is equivalent to the `COPILOT_CLI_DISABLE_WEBSOCKET_RESPONSES` environment variable. */ @@ -6859,6 +6860,10 @@ export interface EnqueueCommandParams { * Slash-prefixed command string to enqueue, e.g. '/compact' or '/model gpt-4'. Queued FIFO with any in-flight items; if the session is idle, processing kicks off immediately. */ command: string; + /** + * Optional user-facing text for the queue row. The command string is shown when omitted. + */ + displayText?: string | null; } /** * Indicates whether the command was accepted into the local execution queue. @@ -16588,6 +16593,27 @@ export interface SandboxConfigAuth { */ gh?: boolean; } +/** + * Managed sandbox enforcement state for a session. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "SandboxEnforcementStatus". + */ +/** @experimental */ +export interface SandboxEnforcementStatus { + /** + * Whether the effective managed policy requires an available sandbox backend. + */ + required: boolean; + /** + * Whether an enforcement failure has permanently blocked the session. + */ + blocked: boolean; + /** + * The first sandbox enforcement failure that blocked the session. + */ + reason?: string; +} /** * Register an absolute-time scheduled prompt. * @@ -23625,6 +23651,16 @@ export function createSessionRpc(connection: MessageConnection, sessionId: strin */ sendMessages: async (params: SendMessagesRequest): Promise => connection.sendRequest("session.sendMessages", { sessionId, ...params }), + /** @experimental */ + sandbox: { + /** + * Returns whether managed policy requires sandbox enforcement and whether an enforcement failure has permanently blocked the session. + * + * @returns Managed sandbox enforcement state for a session. + */ + getEnforcementStatus: async (): Promise => + connection.sendRequest("session.sandbox.getEnforcementStatus", { sessionId }), + }, /** * Aborts the current agent turn. * diff --git a/nodejs/src/generated/session-events.ts b/nodejs/src/generated/session-events.ts index 9075379982..cb845443aa 100644 --- a/nodejs/src/generated/session-events.ts +++ b/nodejs/src/generated/session-events.ts @@ -135,6 +135,16 @@ export type SessionEvent = | CanvasRemovedEvent | ExtensionsAttachmentsPushedEvent | McpAppToolCallCompleteEvent; +/** + * Routing preference used when the session model is `auto`. + */ +export type AutoTier = + /** Optimize for efficiency. */ + | "efficiency" + /** Balance efficiency and intelligence. */ + | "balance" + /** Optimize for intelligence. */ + | "intelligence"; /** * Hosting platform type of the repository (github or ado) */ @@ -469,6 +479,10 @@ export type CitationProvider = */ /** @experimental */ export type CitationLocation = CitationLocationChar | CitationLocationPage | CitationLocationBlock; +/** + * Hosted program caller type + */ +export type AssistantMessageToolRequestCallerType = "program"; /** * API endpoint used for this model call, matching CAPI supported_endpoints vocabulary */ @@ -1106,6 +1120,7 @@ export interface StartData { * Whether the session was already in use by another client at start time */ alreadyInUse?: boolean; + autoTier?: AutoTier; context?: WorkingDirectoryContext; /** * Context tier selected at session creation time for models with tiered context pricing; null when no tier is selected (e.g., non-tiered model) @@ -1258,6 +1273,7 @@ export interface ResumeData { * Whether the session was already in use by another client at resume time */ alreadyInUse?: boolean; + autoTier?: AutoTier; context?: WorkingDirectoryContext; /** * Context tier currently selected at resume time; null when no tier is active @@ -4843,6 +4859,7 @@ export interface AssistantMessageToolRequest { * Arguments to pass to the tool, format depends on the tool */ arguments?: JsonValue; + caller?: AssistantMessageToolRequestCaller; /** * Resolved intention summary describing what this specific call does */ @@ -4869,6 +4886,16 @@ export interface AssistantMessageToolRequest { toolTitle?: string; type?: AssistantMessageToolRequestType; } +/** + * Hosted program that requested this client tool call + */ +export interface AssistantMessageToolRequestCaller { + /** + * Provider-assigned identifier for the hosted caller. + */ + callerId: string; + type: AssistantMessageToolRequestCallerType; +} /** * Session event "assistant.message_start". Streaming assistant message start metadata */ diff --git a/nodejs/test/session-event-codegen.test.ts b/nodejs/test/session-event-codegen.test.ts index c84d9bc082..2dad8f4f18 100644 --- a/nodejs/test/session-event-codegen.test.ts +++ b/nodejs/test/session-event-codegen.test.ts @@ -3,10 +3,37 @@ import { describe, expect, it } from "vitest"; import { generateSessionEventsCode as generateCSharpSessionEventsCode } from "../../scripts/codegen/csharp.ts"; import { generateGoSessionEventsCode } from "../../scripts/codegen/go.ts"; -import { generatePythonSessionEventsCode } from "../../scripts/codegen/python.ts"; +import { + generatePythonSessionEventsCode, + postProcessExternalRefsForPython, +} from "../../scripts/codegen/python.ts"; import { generateSessionEventsCode as generateRustSessionEventsCode } from "../../scripts/codegen/rust.ts"; describe("session event codegen", () => { + it("replaces external reference placeholders regardless of acronym casing", () => { + const code = `@dataclass +class ExternalRefMCPOauthHTTPResponse: + external_ref_marker_external_ref_mcp_oauth_http_response: str + + @staticmethod + def from_dict(obj: Any) -> 'ExternalRefMCPOauthHTTPResponse': + value = obj.get("__externalRefMarker___ExternalRef_McpOauthHttpResponse") + return ExternalRefMCPOauthHTTPResponse(value) + +@dataclass +class ProbeResult: + response: ExternalRefMCPOauthHTTPResponse +`; + + const processed = postProcessExternalRefsForPython( + code, + new Map([["__ExternalRef_McpOauthHttpResponse", "McpOauthHttpResponse"]]) + ); + + expect(processed).toContain("response: McpOauthHttpResponse"); + expect(processed).not.toContain("class ExternalRefMCPOauthHTTPResponse"); + }); + it("maps special schema formats to the expected Python types", () => { const schema: JSONSchema7 = { definitions: { diff --git a/python/copilot/generated/rpc.py b/python/copilot/generated/rpc.py index 1d59a55f4a..84f3718b16 100644 --- a/python/copilot/generated/rpc.py +++ b/python/copilot/generated/rpc.py @@ -6,7 +6,7 @@ from typing import ClassVar, TYPE_CHECKING -from .session_events import AbortReason, Attachment, ContextTier, EmbeddedBlobResourceContents, EmbeddedTextResourceContents, McpOauthHttpResponse, McpOauthWWWAuthenticateParams, McpServerSource, McpServerStatus, ModelChangeSource, PermissionMode, PermissionPromptRequest, PermissionRule, ReasoningSummary, SessionEvent, SessionLimitsConfig, SessionMode, ShutdownType, SkillSource, TaskCompletionOutcome, UserToolSessionApproval, Verbosity +from .session_events import AbortReason, Attachment, AutoTier, ContextTier, EmbeddedBlobResourceContents, EmbeddedTextResourceContents, McpOauthHttpResponse, McpOauthWWWAuthenticateParams, McpServerSource, McpServerStatus, ModelChangeSource, PermissionMode, PermissionPromptRequest, PermissionRule, ReasoningSummary, SessionEvent, SessionLimitsConfig, SessionMode, ShutdownType, SkillSource, TaskCompletionOutcome, UserToolSessionApproval, Verbosity if TYPE_CHECKING: from .._jsonrpc import JsonRpcClient @@ -1051,6 +1051,11 @@ def to_dict(self) -> dict: class CapiSessionOptions: """Options scoped to the built-in CAPI (Copilot API) provider.""" + auto_tier: AutoTier | None = None + """Routing preference used when the session model is `auto`. The runtime persists the + preference across cold resume. When omitted, the default routing behavior is used. + Resuming an already-resident session cannot change its preference. + """ enable_web_socket_responses: bool | None = None """Whether to use WebSocket transport for the CAPI Responses API. Enabled by default when the model advertises `ws:/responses` support; set to `false` to force the HTTP Responses @@ -1062,11 +1067,14 @@ class CapiSessionOptions: @staticmethod def from_dict(obj: Any) -> 'CapiSessionOptions': assert isinstance(obj, dict) + auto_tier = from_union([AutoTier, from_none], obj.get("autoTier")) enable_web_socket_responses = from_union([from_bool, from_none], obj.get("enableWebSocketResponses")) - return CapiSessionOptions(enable_web_socket_responses) + return CapiSessionOptions(auto_tier, enable_web_socket_responses) def to_dict(self) -> dict: result: dict = {} + if self.auto_tier is not None: + result["autoTier"] = from_union([lambda x: to_enum(AutoTier, x), from_none], self.auto_tier) if self.enable_web_socket_responses is not None: result["enableWebSocketResponses"] = from_union([from_bool, from_none], self.enable_web_socket_responses) return result @@ -2502,16 +2510,21 @@ class EnqueueCommandParams: """Slash-prefixed command string to enqueue, e.g. '/compact' or '/model gpt-4'. Queued FIFO with any in-flight items; if the session is idle, processing kicks off immediately. """ + display_text: str | None = None + """Optional user-facing text for the queue row. The command string is shown when omitted.""" @staticmethod def from_dict(obj: Any) -> 'EnqueueCommandParams': assert isinstance(obj, dict) command = from_str(obj.get("command")) - return EnqueueCommandParams(command) + display_text = from_union([from_none, from_str], obj.get("displayText")) + return EnqueueCommandParams(command, display_text) def to_dict(self) -> dict: result: dict = {} result["command"] = from_str(self.command) + if self.display_text is not None: + result["displayText"] = from_union([from_none, from_str], self.display_text) return result # Experimental: this type is part of an experimental API and may change or be removed. @@ -5576,28 +5589,6 @@ def to_dict(self) -> dict: result["serverName"] = from_str(self.server_name) return result -@dataclass -class ExternalRefMCPOauthHTTPResponse: - """HTTP response returned by the server. - - HTTP 401 or 403 response returned by the server. - - HTTP response returned by the server, when the probe reached the server and captured the - complete response. - """ - external_ref_marker_external_ref_mcp_oauth_http_response: str - - @staticmethod - def from_dict(obj: Any) -> 'ExternalRefMCPOauthHTTPResponse': - assert isinstance(obj, dict) - external_ref_marker_external_ref_mcp_oauth_http_response = from_str(obj.get("__externalRefMarker___ExternalRef_McpOauthHttpResponse")) - return ExternalRefMCPOauthHTTPResponse(external_ref_marker_external_ref_mcp_oauth_http_response) - - def to_dict(self) -> dict: - result: dict = {} - result["__externalRefMarker___ExternalRef_McpOauthHttpResponse"] = from_str(self.external_ref_marker_external_ref_mcp_oauth_http_response) - return result - class Status(Enum): AUTHENTICATED = "authenticated" FAILED = "failed" @@ -9664,6 +9655,36 @@ 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 SandboxEnforcementStatus: + """Managed sandbox enforcement state for a session.""" + + blocked: bool + """Whether an enforcement failure has permanently blocked the session.""" + + required: bool + """Whether the effective managed policy requires an available sandbox backend.""" + + reason: str | None = None + """The first sandbox enforcement failure that blocked the session.""" + + @staticmethod + def from_dict(obj: Any) -> 'SandboxEnforcementStatus': + assert isinstance(obj, dict) + blocked = from_bool(obj.get("blocked")) + required = from_bool(obj.get("required")) + reason = from_union([from_str, from_none], obj.get("reason")) + return SandboxEnforcementStatus(blocked, required, reason) + + def to_dict(self) -> dict: + result: dict = {} + result["blocked"] = from_bool(self.blocked) + result["required"] = from_bool(self.required) + if self.reason is not None: + result["reason"] = from_union([from_str, from_none], self.reason) + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class ScheduleAddAtRequest: @@ -32942,7 +32963,7 @@ class MCPOauthProbeResult: status: Status """Probe outcome variant discriminator.""" - http_response: ExternalRefMCPOauthHTTPResponse | None = None + http_response: McpOauthHttpResponse | None = None """HTTP response returned by the server. HTTP 401 or 403 response returned by the server. @@ -32963,7 +32984,7 @@ class MCPOauthProbeResult: def from_dict(obj: Any) -> 'MCPOauthProbeResult': assert isinstance(obj, dict) status = Status(obj.get("status")) - http_response = from_union([ExternalRefMCPOauthHTTPResponse.from_dict, from_none], obj.get("httpResponse")) + http_response = from_union([McpOauthHttpResponse.from_dict, from_none], obj.get("httpResponse")) reason = from_union([MCPOauthProbeNeedsAuthReason, from_none], obj.get("reason")) www_authenticate_params = from_union([McpOauthWWWAuthenticateParams.from_dict, from_none], obj.get("wwwAuthenticateParams")) error = from_union([from_str, from_none], obj.get("error")) @@ -32973,7 +32994,7 @@ def to_dict(self) -> dict: result: dict = {} result["status"] = to_enum(Status, self.status) if self.http_response is not None: - result["httpResponse"] = from_union([lambda x: to_class(ExternalRefMCPOauthHTTPResponse, x), from_none], self.http_response) + result["httpResponse"] = from_union([lambda x: to_class(McpOauthHttpResponse, x), from_none], self.http_response) if self.reason is not None: result["reason"] = from_union([lambda x: to_enum(MCPOauthProbeNeedsAuthReason, x), from_none], self.reason) if self.www_authenticate_params is not None: @@ -35849,6 +35870,7 @@ class RPC: sandbox_config_user_policy_network: SandboxConfigUserPolicyNetwork sandbox_config_user_policy_network_proxy: SandboxConfigUserPolicyNetworkProxy sandbox_config_user_policy_seatbelt: SandboxConfigUserPolicySeatbelt + sandbox_enforcement_status: SandboxEnforcementStatus schedule_add_at_request: ScheduleAddAtRequest schedule_add_cron_request: ScheduleAddCronRequest schedule_add_request: ScheduleAddRequest @@ -37032,6 +37054,7 @@ def from_dict(obj: Any) -> 'RPC': sandbox_config_user_policy_network = SandboxConfigUserPolicyNetwork.from_dict(obj.get("SandboxConfigUserPolicyNetwork")) sandbox_config_user_policy_network_proxy = SandboxConfigUserPolicyNetworkProxy.from_dict(obj.get("SandboxConfigUserPolicyNetworkProxy")) sandbox_config_user_policy_seatbelt = SandboxConfigUserPolicySeatbelt.from_dict(obj.get("SandboxConfigUserPolicySeatbelt")) + sandbox_enforcement_status = SandboxEnforcementStatus.from_dict(obj.get("SandboxEnforcementStatus")) schedule_add_at_request = ScheduleAddAtRequest.from_dict(obj.get("ScheduleAddAtRequest")) schedule_add_cron_request = ScheduleAddCronRequest.from_dict(obj.get("ScheduleAddCronRequest")) schedule_add_request = ScheduleAddRequest.from_dict(obj.get("ScheduleAddRequest")) @@ -37410,7 +37433,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, account_all_users, account_get_all_users_result, account_get_current_auth_result, account_get_quota_request, account_get_quota_result, account_login_request, account_login_result, account_logout_request, account_logout_result, account_quota_snapshot, adaptive_thinking_support, agent_discovery_path, agent_discovery_path_list, agent_discovery_path_scope, agent_get_current_result, agent_info, agent_info_source, agent_list, agent_list_request, agent_registry_live_target_entry, agent_registry_live_target_entry_attention_kind, agent_registry_live_target_entry_kind, agent_registry_live_target_entry_last_terminal_event, agent_registry_live_target_entry_status, agent_registry_log_capture, agent_registry_log_capture_open_error_reason, agent_registry_spawn_error, agent_registry_spawn_permission_mode, agent_registry_spawn_registry_timeout, agent_registry_spawn_request, agent_registry_spawn_result, agent_registry_spawn_spawned, agent_registry_spawn_validation_error, agent_registry_spawn_validation_error_field, agent_registry_spawn_validation_error_reason, agent_reload_result, agents_discover_request, agent_select_request, agent_select_result, agent_set_prompt_request, agents_get_discovery_paths_request, api_key_auth_info, auth_identity, auth_info, auth_info_type, auth_validation_error, auth_validation_errors, built_in_model_catalog, built_in_model_catalog_entry, builtin_tool_descriptor, builtin_tool_format, builtin_tool_format_type, builtin_tool_input_schema, builtin_tool_input_schema_type, builtin_tool_safe_for_telemetry, builtin_tool_safe_telemetry_fields, cancel_user_requested_shell_command_result, canvas_action, canvas_action_invoke_request, canvas_action_invoke_result, canvas_close_request, canvas_host_context, canvas_host_context_capabilities, canvas_json_schema, canvas_list, canvas_list_open_result, canvas_open_request, canvas_provider_close_request, canvas_provider_invoke_action_request, canvas_provider_open_request, canvas_provider_open_result, canvas_provider_register_request, canvas_provider_unregister_request, canvas_session_context, capi_session_options, card_digest, card_digest_algorithm, card_digest_value, catalog_ai_skill_candidate, catalog_ai_skill_candidate_provenance, catalog_authentication_required_error, catalog_authentication_required_reason, catalog_candidate, catalog_candidate_kind, catalog_candidate_source, catalog_candidate_source_embedded, catalog_candidate_source_url, catalog_capability, catalog_capability_id, catalog_client_contract, catalog_contract_violation_error, catalog_contract_violation_reason, catalog_handle_rejected_error, catalog_handle_rejection_reason, catalog_handle_type, catalog_invalid_request_error, catalog_invalid_request_field, catalog_malformed_card_error, catalog_malformed_card_reason, catalog_mcp_server_candidate, catalog_mcp_server_candidate_provenance, catalog_mcp_server_installability, catalog_media_type, catalog_negotiated_contract, catalog_negotiation_refused_error, catalog_negotiation_refused_reason, catalog_network_failure_error, catalog_network_failure_reason, catalog_not_installable_error, catalog_not_installable_reason, catalog_policy_rejected_error, catalog_search_request, catalog_search_result, catalog_search_succeeded, catalog_unavailable_error, catalog_unavailable_reason, catalog_unavailable_transport_error, catalog_unavailable_transport_reason, catalog_unsafe_retrieval_error, catalog_unsafe_retrieval_reason, catalog_unsupported_kind_error, command_list, commands_finalize_invocation_effect_request, commands_finalize_invocation_effect_result, commands_handle_pending_command_request, commands_handle_pending_command_result, commands_invocation_effect_outcome, commands_invocation_origin, commands_invoke_request, commands_list_request, commands_respond_to_queued_command_request, commands_respond_to_queued_command_result, completions_get_trigger_characters_result, completions_request_request, completions_request_result, configure_session_extensions_params, connect_client_info, connected_remote_session_metadata, connected_remote_session_metadata_kind, connected_remote_session_metadata_repository, connect_remote_session_params, connect_request, connect_result, content_exclusion_check_paths_request, content_exclusion_check_paths_result, content_exclusion_path_check, content_filter_mode, context_heaviest_message, copilot_api_token_auth_info, copilot_user_response, copilot_user_response_endpoints, copilot_user_response_quota_snapshots, copilot_user_response_quota_snapshots_chat, copilot_user_response_quota_snapshots_completions, copilot_user_response_quota_snapshots_premium_interactions, current_model, current_tool_metadata, debug_collect_logs_collected_entry, debug_collect_logs_destination, debug_collect_logs_entry, debug_collect_logs_entry_kind, debug_collect_logs_include, debug_collect_logs_redaction, debug_collect_logs_request, debug_collect_logs_result, debug_collect_logs_result_kind, debug_collect_logs_skipped_entry, debug_collect_logs_source, discovered_canvas, discovered_extension, discovered_extension_mode, discovered_extension_plugin, discovered_extensions, discovered_extensions_disable_request, discovered_extensions_enable_request, discovered_extension_source, discovered_mcp_server, discovered_mcp_server_type, enqueue_command_params, enqueue_command_result, env_auth_info, event_log_read_request, event_log_release_interest_result, event_log_tail_result, event_log_types, events_agent_scope, events_cursor_status, events_read_direction, events_read_result, execute_command_params, execute_command_result, extension, extension_context_push_input, extension_launch_profile, extension_launch_provider_resolve_request, extension_launch_provider_resolve_result, extension_list, extensions_disable_request, extensions_enable_request, extension_source, extension_status, external_tool_result, external_tool_text_result_for_llm, external_tool_text_result_for_llm_binary_results_for_llm, external_tool_text_result_for_llm_binary_results_for_llm_type, external_tool_text_result_for_llm_content, external_tool_text_result_for_llm_content_audio, external_tool_text_result_for_llm_content_image, external_tool_text_result_for_llm_content_resource, external_tool_text_result_for_llm_content_resource_details, external_tool_text_result_for_llm_content_resource_link, external_tool_text_result_for_llm_content_resource_link_icon, external_tool_text_result_for_llm_content_resource_link_icon_theme, external_tool_text_result_for_llm_content_shell_exit, external_tool_text_result_for_llm_content_terminal, external_tool_text_result_for_llm_content_text, factory_abort_request, factory_ack_result, factory_agent_options, factory_agent_request, factory_agent_result, factory_agent_summary, factory_cancel_request, factory_current_phase, factory_declared_limits, factory_durable_operation, factory_execute_request, factory_execute_result, factory_get_run_progress_request, factory_get_run_request, factory_journal_get_request, factory_journal_get_result, factory_journal_put_request, factory_list_runs_request, factory_list_runs_result, factory_log_line, factory_log_line_kind, factory_log_request, factory_phase_observation, factory_phase_status, factory_progress_line, factory_progress_page, factory_resume_request, factory_resume_result, factory_run_consumed, factory_run_detail, factory_run_failure, factory_run_failure_kind, factory_run_limits, factory_run_request, factory_run_result, factory_run_status, factory_run_summary, factory_run_terminal, factory_tool_resume_request, factory_tool_run_options, factory_tool_run_request, filter_mapping, fleet_start_request, fleet_start_result, folder_trust_add_params, folder_trust_check_params, folder_trust_check_result, gh_cli_auth_info, git_hub_telemetry_client_info, git_hub_telemetry_event, git_hub_telemetry_notification, git_hub_token_acquire_reason, git_hub_token_acquire_request, git_hub_token_acquire_result, handle_pending_tool_call_request, handle_pending_tool_call_result, history_abort_manual_compaction_result, history_cancel_background_compaction_result, history_clear_context_request, history_clear_context_result, history_compact_context_window, history_compact_request, history_compact_result, history_file_restore_skip_reason, history_list_rewind_points_result, history_preview_rewind_request, history_preview_rewind_result, history_rewind_change_type, history_rewind_file_preview, history_rewind_mode, history_rewind_outcome, history_rewind_point, history_rewind_request, history_rewind_result, history_rewind_unavailable_reason, history_skipped_file_restore, history_summarize_for_handoff_result, history_truncate_request, history_truncate_result, hmac_auth_info, hook_invoke_request, hook_invoke_response, hook_type, installed_plugin, installed_plugin_info, installed_plugin_source, installed_plugin_source_git_hub, installed_plugin_source_local, installed_plugin_source_url, instruction_discovery_path, instruction_discovery_path_kind, instruction_discovery_path_list, instruction_discovery_path_location, instructions_discover_request, instructions_get_discovery_paths_request, instructions_get_sources_result, instruction_source, instruction_source_location, instruction_source_type, interrupt_main_turn_request, interrupt_main_turn_result, llm_inference_headers, llm_inference_http_request_chunk_request, llm_inference_http_request_chunk_result, llm_inference_http_request_start_request, llm_inference_http_request_start_result, llm_inference_http_request_start_transport, llm_inference_http_response_chunk_error, llm_inference_http_response_chunk_request, llm_inference_http_response_chunk_result, llm_inference_http_response_start_request, llm_inference_http_response_start_result, llm_inference_set_provider_result, local_session_metadata_value, log_request, log_result, lsp_initialize_request, managed_settings_read_result, marketplace_add_result, marketplace_browse_result, marketplace_info, marketplace_list_result, marketplace_plugin_info, marketplace_refresh_entry, marketplace_refresh_result, marketplace_remove_result, mcp_allowed_server, mcp_apps_call_tool_request, mcp_apps_diagnose_capability, mcp_apps_diagnose_request, mcp_apps_diagnose_result, mcp_apps_diagnose_server, mcp_apps_host_context, mcp_apps_host_context_details, mcp_apps_host_context_details_available_display_mode, mcp_apps_host_context_details_display_mode, mcp_apps_host_context_details_platform, mcp_apps_host_context_details_theme, mcp_apps_list_tools_request, mcp_apps_list_tools_result, mcp_apps_read_resource_request, mcp_apps_read_resource_result, mcp_apps_resource_content, mcp_apps_set_host_context_details, mcp_apps_set_host_context_details_available_display_mode, mcp_apps_set_host_context_details_display_mode, mcp_apps_set_host_context_details_platform, mcp_apps_set_host_context_details_theme, mcp_apps_set_host_context_request, mcp_cancel_sampling_execution_params, mcp_cancel_sampling_execution_result, mcp_config_add_request, mcp_config_disable_request, mcp_config_enable_request, mcp_config_list, mcp_config_remove_request, mcp_config_update_request, mcp_configure_git_hub_request, mcp_configure_git_hub_result, mcp_disable_request, mcp_discover_request, mcp_discover_result, mcp_elicitation_form_mode, mcp_enable_request, mcp_execute_sampling_params, mcp_execute_sampling_request, mcp_execute_sampling_result, mcp_failed_server, mcp_filtered_server, mcp_headers_handle_pending_headers_refresh_request, mcp_headers_handle_pending_headers_refresh_request_request, mcp_headers_handle_pending_headers_refresh_request_result, mcp_host_state, mcp_install_plan, mcp_is_server_running_request, mcp_is_server_running_result, mcp_list_tools_request, mcp_list_tools_result, mcp_oauth_authentication_state_changed_request, mcp_oauth_handle_pending_request, mcp_oauth_handle_pending_result, mcp_oauth_login_grant_type, mcp_oauth_login_request, mcp_oauth_login_result, mcp_oauth_pending_request_response, mcp_oauth_probe_needs_auth_reason, mcp_oauth_probe_request, mcp_oauth_probe_result, mcp_oauth_respond_request, mcp_oauth_respond_result, mcp_plan_configuration_change, mcp_plan_configuration_operation, mcp_plan_enum_value_type, mcp_plan_install_planned, mcp_plan_install_request, mcp_plan_install_result, mcp_plan_install_source, mcp_plan_install_source_candidate, mcp_plan_install_source_candidate_kind, mcp_plan_install_source_card, mcp_plan_install_source_card_kind, mcp_plan_package_install_method, mcp_plan_package_transport, mcp_plan_policy_decision, mcp_plan_policy_result, mcp_plan_policy_source, mcp_plan_provenance, mcp_plan_remote_install_method, mcp_plan_remote_transport, mcp_plan_required_value, mcp_plan_required_value_enum, mcp_plan_required_value_enum_kind, mcp_plan_required_value_scalar, mcp_plan_required_value_scalar_kind, mcp_plan_resource_identity, mcp_plan_scalar_value_type, mcp_plan_scope, mcp_plan_secret_placeholder, mcp_plan_secret_reference, mcp_plan_target, mcp_plan_transport_choice, mcp_plan_transport_choice_package, mcp_plan_transport_choice_remote, mcp_plan_value_category, mcp_register_external_client_request, mcp_reload_config, mcp_reload_with_config_request, mcp_remove_git_hub_result, mcp_resource, mcp_resource_annotations, mcp_resource_content, mcp_resource_icon, mcp_resources_list_request, mcp_resources_list_result, mcp_resources_list_templates_request, mcp_resources_list_templates_result, mcp_resources_read_request, mcp_resources_read_result, mcp_resource_template, mcp_restart_server_request, mcp_safe_for_telemetry, mcp_safe_for_telemetry_fields, mcp_sampling_execution_action, mcp_sampling_execution_result, mcp_serializable_server_config, mcp_server, mcp_server_auth_config, mcp_server_auth_config_redirect_port, mcp_server_card_embedded, mcp_server_card_embedded_kind, mcp_server_card_media_type, mcp_server_card_reference, mcp_server_card_url, mcp_server_card_url_kind, mcp_server_config, mcp_server_config_defer_tools, mcp_server_config_http, mcp_server_config_http_oauth_grant_type, mcp_server_config_http_type, mcp_server_config_memory, mcp_server_config_memory_type, mcp_server_config_stdio, mcp_server_config_stdio_type, mcp_server_failure_info, mcp_server_list, mcp_server_needs_auth_info, mcp_set_env_value_mode_details, mcp_set_env_value_mode_params, mcp_set_env_value_mode_result, mcp_start_server_request, mcp_start_servers_result, mcp_stop_server_request, mcp_task_metadata, mcp_tools, mcp_tool_ui, mcp_tool_ui_visibility, mcp_unregister_external_client_request, memory_configuration, metadata_context_attribution_result, metadata_context_heaviest_messages_request, metadata_context_heaviest_messages_result, metadata_context_info_request, metadata_context_info_result, metadata_is_processing_result, metadata_recompute_context_tokens_request, metadata_recompute_context_tokens_result, metadata_record_context_change_request, metadata_record_context_change_result, metadata_set_working_directory_request, metadata_set_working_directory_result, metadata_snapshot_current_mode, metadata_snapshot_remote_metadata, metadata_snapshot_remote_metadata_repository, metadata_snapshot_remote_metadata_task_type, model, model_apply_startup_overlay_request, model_billing, model_billing_promo, model_billing_token_prices, model_billing_token_prices_long_context, model_capabilities, model_capabilities_limits, model_capabilities_limits_vision, model_capabilities_override, model_capabilities_override_limits, model_capabilities_override_limits_vision, model_capabilities_override_supports, model_capabilities_supports, model_list, model_list_request, model_message, model_picker_category, model_picker_persistence_request, model_picker_price_category, model_picker_settings_context, model_policy, model_policy_state, model_set_reasoning_effort_request, model_set_reasoning_effort_result, models_list_request, model_switch_confirmation, model_switch_to_request, model_switch_to_result, model_warning_text, mode_set_request, mode_set_result, move_mcp_loading_to_background_result, named_provider_config, name_get_result, name_set_auto_request, name_set_auto_result, name_set_request, open_canvas_instance, options_update_additional_content_exclusion_policy, options_update_additional_content_exclusion_policy_rule, options_update_additional_content_exclusion_policy_rule_source, options_update_additional_content_exclusion_policy_scope, options_update_context_tier, options_update_env_value_mode, options_update_reasoning_summary, options_update_tool_filter_precedence, pending_permission_request, pending_permission_request_list, permission_decision, permission_decision_approved, permission_decision_approved_for_location, permission_decision_approved_for_session, permission_decision_approve_for_location, permission_decision_approve_for_location_approval, permission_decision_approve_for_location_approval_commands, permission_decision_approve_for_location_approval_custom_tool, permission_decision_approve_for_location_approval_extension_env_access, permission_decision_approve_for_location_approval_extension_management, permission_decision_approve_for_location_approval_extension_permission_access, permission_decision_approve_for_location_approval_factory, permission_decision_approve_for_location_approval_mcp, permission_decision_approve_for_location_approval_mcp_sampling, permission_decision_approve_for_location_approval_memory, permission_decision_approve_for_location_approval_read, permission_decision_approve_for_location_approval_write, permission_decision_approve_for_session, permission_decision_approve_for_session_approval, permission_decision_approve_for_session_approval_commands, permission_decision_approve_for_session_approval_custom_tool, permission_decision_approve_for_session_approval_extension_env_access, permission_decision_approve_for_session_approval_extension_management, permission_decision_approve_for_session_approval_extension_permission_access, permission_decision_approve_for_session_approval_factory, permission_decision_approve_for_session_approval_mcp, permission_decision_approve_for_session_approval_mcp_sampling, permission_decision_approve_for_session_approval_memory, permission_decision_approve_for_session_approval_read, permission_decision_approve_for_session_approval_write, permission_decision_approve_once, permission_decision_approve_permanently, permission_decision_cancelled, permission_decision_context, permission_decision_denied_by_content_exclusion_policy, permission_decision_denied_by_permission_request_hook, permission_decision_denied_by_rules, permission_decision_denied_interactively_by_user, permission_decision_denied_no_approval_rule_and_could_not_request_from_user, permission_decision_outcome, permission_decision_reject, permission_decision_request, permission_decision_source, permission_decision_surface, permission_decision_user_not_available, permission_location_add_tool_approval_params, permission_location_apply_params, permission_location_apply_result, permission_location_resolve_params, permission_location_resolve_result, permission_location_type, permission_mode_source, permission_paths_add_params, permission_paths_allowed_check_params, permission_paths_allowed_check_result, permission_paths_config, permission_paths_list, permission_paths_update_primary_params, permission_paths_workspace_check_params, permission_paths_workspace_check_result, permission_prompt_shown_notification, permission_request_result, permission_response_capability, permission_rules_set, permissions_configure_additional_content_exclusion_policy, permissions_configure_additional_content_exclusion_policy_rule, permissions_configure_additional_content_exclusion_policy_rule_source, permissions_configure_additional_content_exclusion_policy_scope, permissions_configure_params, permissions_configure_result, permissions_folder_trust_add_trusted_result, permissions_get_mode_request, permissions_get_mode_result, permissions_locations_add_tool_approval_details, permissions_locations_add_tool_approval_details_commands, permissions_locations_add_tool_approval_details_custom_tool, permissions_locations_add_tool_approval_details_extension_env_access, permissions_locations_add_tool_approval_details_extension_management, permissions_locations_add_tool_approval_details_extension_permission_access, permissions_locations_add_tool_approval_details_factory, permissions_locations_add_tool_approval_details_mcp, permissions_locations_add_tool_approval_details_mcp_sampling, permissions_locations_add_tool_approval_details_memory, permissions_locations_add_tool_approval_details_read, permissions_locations_add_tool_approval_details_write, permissions_locations_add_tool_approval_result, permissions_modify_rules_params, permissions_modify_rules_result, permissions_modify_rules_scope, permissions_notify_prompt_shown_result, permissions_paths_add_result, permissions_paths_list_request, permissions_paths_update_primary_result, permissions_pending_requests_request, permissions_reset_session_approvals_request, permissions_reset_session_approvals_result, permissions_set_approve_all_request, permissions_set_approve_all_result, permissions_set_approve_all_source, permissions_set_mode_request, permissions_set_mode_result, permissions_set_required_request, permissions_set_required_result, permissions_urls_set_unrestricted_mode_result, permission_urls_config, permission_urls_set_unrestricted_mode_params, ping_request, ping_result, plan_read_result, plan_read_sql_todos_result, plan_read_sql_todos_with_dependencies_result, plan_sql_todo_dependency, plan_sql_todos_row, plan_update_request, plugin, plugin_install_result, plugin_list, plugin_list_result, plugins_builtin_set_request, plugins_disable_request, plugins_enable_request, plugins_install_request, plugins_marketplaces_add_request, plugins_marketplaces_browse_request, plugins_marketplaces_refresh_request, plugins_marketplaces_remove_request, plugins_reload_request, plugins_uninstall_request, plugins_update_request, plugin_update_all_entry, plugin_update_all_result, plugin_update_result, protocol_external_tool_defer, protocol_external_tool_definition, provider_add_request, provider_add_result, provider_config, provider_config_azure, provider_config_transport, provider_config_type, provider_config_wire_api, provider_endpoint, provider_endpoint_transport, provider_endpoint_type, provider_endpoint_wire_api, provider_get_endpoint_request, provider_model_config, provider_session_token, provider_token_acquire_request, provider_token_acquire_result, push_attachment, push_attachment_blob, push_attachment_directory, push_attachment_file, push_attachment_file_line_range, push_attachment_git_hub_actions_job, push_attachment_git_hub_commit, push_attachment_git_hub_file, push_attachment_git_hub_file_diff, push_attachment_git_hub_file_diff_side, push_attachment_git_hub_reference, push_attachment_git_hub_reference_type, push_attachment_git_hub_release, push_attachment_git_hub_repository, push_attachment_git_hub_snippet, push_attachment_git_hub_tree_comparison, push_attachment_git_hub_tree_comparison_side, push_attachment_git_hub_url, push_attachment_selection, push_attachment_selection_details, push_attachment_selection_details_end, push_attachment_selection_details_start, push_git_hub_repo_ref, queue_begin_deferred_idle_drain_request, queue_begin_deferred_idle_drain_result, queue_consume_system_notifications_request, queued_command_handled, queued_command_not_handled, queued_command_result, queue_defer_session_idle_request, queue_duplicate_at_request, queue_duplicate_at_result, queue_enqueue_resume_pending_result, queue_finish_deferred_idle_drain_request, queue_finish_deferred_idle_drain_result, queue_has_pending_result, queue_insert_at_request, queue_insert_at_result, queue_insert_message, queue_move_item_request, queue_move_item_result, queue_pending_items, queue_pending_items_kind, queue_pending_items_result, queue_remove_at_request, queue_remove_at_result, queue_remove_most_recent_result, queue_send_now_request, queue_send_now_result, queue_set_drain_paused_request, queue_snapshot_result, queue_update_text_request, queue_update_text_result, register_event_interest_params, register_event_interest_result, register_extension_tools_params, register_extension_tools_result, release_event_interest_params, remote_control_config, remote_control_config_existing_mc_session, remote_control_status, remote_control_status_active, remote_control_status_connecting, remote_control_status_error, remote_control_status_off, remote_control_status_result, remote_control_stop_result, remote_control_transfer_result, remote_enable_request, remote_enable_result, remote_notify_steerable_changed_request, remote_notify_steerable_changed_result, remote_session_connection_result, remote_session_host_status, remote_session_metadata_repository, remote_session_metadata_task_type, remote_session_metadata_value, remote_session_mode, remote_session_repository, run_options, sandbox_config, sandbox_config_auth, sandbox_config_source, sandbox_config_user_policy, sandbox_config_user_policy_experimental, sandbox_config_user_policy_experimental_seatbelt, sandbox_config_user_policy_filesystem, sandbox_config_user_policy_network, sandbox_config_user_policy_network_proxy, sandbox_config_user_policy_seatbelt, schedule_add_at_request, schedule_add_cron_request, schedule_add_request, schedule_add_result, schedule_add_self_paced_request, schedule_entry, schedule_has_self_paced_result, schedule_list, schedule_rearm_self_paced_request, schedule_stop_request, schedule_stop_result, secrets_add_filter_values_request, secrets_add_filter_values_result, send_agent_mode, send_attachments_to_message_params, send_message_item, send_messages_request, send_messages_result, send_mode, send_request, send_result, send_system_notification_request, server_agent_list, server_instruction_source_list, server_skill, server_skill_list, session_activity, session_agent_list_request, session_auth_login_request, session_auth_logout_user_request, session_auth_status, session_auth_switch_request, session_bulk_delete_result, session_cancel_all_background_agents_result, session_capability, session_commands_list_request, session_completion_item, session_context, session_context_host_type, session_enrich_metadata_result, session_fs_append_file_request, session_fs_error, session_fs_error_code, session_fs_exists_request, session_fs_exists_result, session_fs_mkdir_request, session_fs_readdir_request, session_fs_readdir_result, session_fs_readdir_with_types_entry, session_fs_readdir_with_types_entry_type, session_fs_readdir_with_types_request, session_fs_readdir_with_types_result, session_fs_read_file_request, session_fs_read_file_result, session_fs_rename_request, session_fs_rm_request, session_fs_set_provider_capabilities, session_fs_set_provider_conventions, session_fs_set_provider_request, session_fs_set_provider_result, session_fs_sqlite_exists_request, session_fs_sqlite_exists_result, session_fs_sqlite_query_request, session_fs_sqlite_query_result, session_fs_sqlite_query_type, session_fs_sqlite_transaction_error, session_fs_sqlite_transaction_error_class, session_fs_sqlite_transaction_request, session_fs_sqlite_transaction_result, session_fs_sqlite_transaction_statement, session_fs_stat_request, session_fs_stat_result, session_fs_write_file_request, session_git_hub_auth_get_all_auth_available_result, session_git_hub_auth_logout_result, session_git_hub_auth_logout_user_result, session_history_compact_request, session_installed_plugin, session_installed_plugin_source, session_installed_plugin_source_git_hub, session_installed_plugin_source_local, session_installed_plugin_source_url, session_limit_prediction_baseline_data, session_limit_prediction_client_type, session_limit_prediction_details, session_limit_prediction_predict_request, session_limit_prediction_request, session_limit_prediction_result, session_limit_prediction_source, session_limit_prediction_tier, session_limit_prediction_tier_option, session_limit_prediction_unavailable_reason, session_list, session_list_entry, session_list_filter, session_load_deferred_repo_hooks_result, session_log_level, session_managed_permissions, session_managed_settings, session_mcp_apps_call_tool_result, session_metadata_snapshot, session_mode, session_model_list, session_model_list_request, session_model_price_category, session_open_options, session_open_options_additional_content_exclusion_policy, session_open_options_additional_content_exclusion_policy_rule, session_open_options_additional_content_exclusion_policy_rule_source, session_open_options_additional_content_exclusion_policy_scope, session_open_options_env_value_mode, session_open_options_reasoning_summary, session_open_params, session_open_result, session_plugins_reload_request, session_provider_get_endpoint_request, session_prune_result, sessions_bulk_delete_request, sessions_check_in_use_request, sessions_check_in_use_result, sessions_close_request, sessions_close_result, sessions_delete_request, sessions_enrich_metadata_request, session_set_credentials_params, session_set_credentials_result, session_settings_built_in_tool_availability_snapshot, session_settings_evaluate_predicate_request, session_settings_evaluate_predicate_result, session_settings_job_snapshot, session_settings_model_snapshot, session_settings_online_evaluation_snapshot, session_settings_predicate_name, session_settings_repo_snapshot, session_settings_snapshot, session_settings_validation_snapshot, sessions_find_by_prefix_request, sessions_find_by_prefix_result, sessions_find_by_task_id_request, sessions_find_by_task_id_result, sessions_fork_request, sessions_fork_result, sessions_get_board_entry_count_request, sessions_get_board_entry_count_result, sessions_get_event_file_path_request, sessions_get_event_file_path_result, sessions_get_last_for_context_request, sessions_get_last_for_context_result, sessions_get_metadata_request, sessions_get_metadata_result, sessions_get_persisted_remote_steerable_request, sessions_get_persisted_remote_steerable_result, session_sizes, sessions_list_non_empty_session_ids_request, sessions_list_non_empty_session_ids_result, sessions_list_request, sessions_load_deferred_repo_hooks_request, sessions_open_attach, sessions_open_cloud, sessions_open_create, sessions_open_handoff, sessions_open_handoff_task_type, sessions_open_progress, sessions_open_progress_status, sessions_open_progress_step, sessions_open_remote, sessions_open_resume, sessions_open_resume_last, sessions_open_status, session_source, sessions_prune_old_request, sessions_register_extension_tools_on_session_options, sessions_release_lock_request, sessions_release_lock_result, sessions_reload_plugin_hooks_request, sessions_reload_plugin_hooks_result, sessions_save_request, sessions_save_result, sessions_set_additional_plugins_request, sessions_set_additional_plugins_result, sessions_set_remote_control_steering_request, sessions_start_remote_control_request, sessions_stop_remote_control_request, sessions_transfer_remote_control_request, session_telemetry_engagement, session_update_options_params, session_update_options_result, session_visibility_status, session_working_directory_context, session_working_directory_context_host_type, settable_auth_info, settable_token_auth_info, shell_cancel_user_requested_request, shell_credentials, shell_exec_request, shell_exec_result, shell_execute_user_requested_request, shell_init_profile, shell_init_script, shell_init_script_shell, shell_kill_request, shell_kill_result, shell_kill_signal, shell_options, shutdown_request, skill, skill_discovery_path, skill_discovery_path_list, skill_discovery_scope, skill_list, skills_config_set_disabled_skills_request, skills_config_set_skill_disabled_request, skills_disable_request, skills_discover_request, skills_enable_request, skills_get_discovery_paths_request, skills_get_invoked_result, skills_invoked_skill, skills_load_diagnostics, slash_command_add_timeline_entry_result, slash_command_agent_prompt_result, slash_command_completed_result, slash_command_info, slash_command_input, slash_command_input_choice, slash_command_input_completion, slash_command_invocation_result, slash_command_kind, slash_command_model_picker_dialog, slash_command_select_subcommand_option, slash_command_select_subcommand_result, slash_command_set_model_result, slash_command_set_plan_model_result, slash_command_show_dialog_result, slash_command_text_result, slash_command_timeline_entry, subagent_settings_entry, subagent_settings_entry_context_tier, task_agent_info, task_agent_progress, task_complete_data, task_completion_decision, task_execution_mode, task_info, task_list, task_progress_line, tasks_cancel_request, tasks_cancel_result, tasks_get_current_promotable_result, tasks_get_progress_request, tasks_get_progress_result, task_shell_info, task_shell_info_attachment_mode, task_shell_progress, tasks_promote_current_to_background_result, tasks_promote_to_background_request, tasks_promote_to_background_result, tasks_refresh_result, tasks_remove_request, tasks_remove_result, tasks_send_message_request, tasks_send_message_result, tasks_start_agent_request, tasks_start_agent_result, task_status, tasks_wait_for_pending_result, telemetry_set_feature_overrides_request, token_auth_info, token_provider_auth_info, tool, tool_list, tool_result, tool_result_expanded, tool_result_new_message, tool_result_type, tools_execute_request, tools_get_builtin_descriptors_request, tools_get_builtin_descriptors_result, tools_get_current_metadata_result, tools_initialize_and_validate_result, tools_list_request, tools_set_request, tools_set_result, tools_shell_descriptor_config, tools_task_complete_event_data_request, tools_update_subagent_settings_result, ui_auto_mode_switch_response, ui_elicitation_array_any_of_field, ui_elicitation_array_any_of_field_items, ui_elicitation_array_any_of_field_items_any_of, ui_elicitation_array_enum_field, ui_elicitation_array_enum_field_items, ui_elicitation_field_value, ui_elicitation_request, ui_elicitation_response, ui_elicitation_response_action, ui_elicitation_response_content, ui_elicitation_result, ui_elicitation_schema, ui_elicitation_schema_property, ui_elicitation_schema_property_boolean, ui_elicitation_schema_property_number, ui_elicitation_schema_property_number_type, ui_elicitation_schema_property_string, ui_elicitation_schema_property_string_format, ui_elicitation_string_enum_field, ui_elicitation_string_one_of_field, ui_elicitation_string_one_of_field_one_of, ui_ephemeral_query_request, ui_ephemeral_query_result, ui_exit_plan_mode_action, ui_exit_plan_mode_response, ui_handle_pending_auto_mode_switch_request, ui_handle_pending_elicitation_request, ui_handle_pending_exit_plan_mode_request, ui_handle_pending_result, ui_handle_pending_sampling_request, ui_handle_pending_sampling_response, ui_handle_pending_session_limits_exhausted_request, ui_handle_pending_user_input_request, ui_register_direct_auto_mode_switch_handler_result, ui_session_limits_exhausted_response, ui_session_limits_exhausted_response_action, ui_unregister_direct_auto_mode_switch_handler_request, ui_unregister_direct_auto_mode_switch_handler_result, ui_user_input_response, update_subagent_settings_request, usage_get_metrics_result, usage_metrics_agent_metric, usage_metrics_code_changes, usage_metrics_model_metric, usage_metrics_model_metric_requests, usage_metrics_model_metric_token_detail, usage_metrics_model_metric_usage, usage_metrics_token_detail, user_auth_info, user_requested_shell_command_result, user_setting_metadata, user_settings_get_result, user_settings_set_request, user_settings_set_result, visibility_get_result, visibility_set_request, visibility_set_result, workspace_diff_file_change, workspace_diff_file_change_type, workspace_diff_mode, workspace_diff_result, workspaces_add_summary_request, workspaces_add_summary_result, workspaces_autopilot_objective_exists_result, workspaces_checkpoints, workspaces_create_file_request, workspaces_delete_autopilot_objective_result, workspaces_diff_request, workspaces_ensure_request, workspaces_get_workspace_result, workspaces_list_checkpoints_result, workspaces_list_files_result, workspaces_read_autopilot_objective_result, workspaces_read_checkpoint_request, workspaces_read_checkpoint_result, workspaces_read_file_request, workspaces_read_file_result, workspaces_save_large_paste_request, workspaces_save_large_paste_result, workspaces_truncate_summaries_request, workspace_summary_host_type, workspaces_update_metadata_request, workspaces_workspace_details_host_type, workspaces_write_autopilot_objective_request, workspaces_write_autopilot_objective_result, session_auth_info_result, session_context_attribution, session_context_info, subagent_settings, task_progress, workspace_summary) + return RPC(abort_request, abort_result, account_all_users, account_get_all_users_result, account_get_current_auth_result, account_get_quota_request, account_get_quota_result, account_login_request, account_login_result, account_logout_request, account_logout_result, account_quota_snapshot, adaptive_thinking_support, agent_discovery_path, agent_discovery_path_list, agent_discovery_path_scope, agent_get_current_result, agent_info, agent_info_source, agent_list, agent_list_request, agent_registry_live_target_entry, agent_registry_live_target_entry_attention_kind, agent_registry_live_target_entry_kind, agent_registry_live_target_entry_last_terminal_event, agent_registry_live_target_entry_status, agent_registry_log_capture, agent_registry_log_capture_open_error_reason, agent_registry_spawn_error, agent_registry_spawn_permission_mode, agent_registry_spawn_registry_timeout, agent_registry_spawn_request, agent_registry_spawn_result, agent_registry_spawn_spawned, agent_registry_spawn_validation_error, agent_registry_spawn_validation_error_field, agent_registry_spawn_validation_error_reason, agent_reload_result, agents_discover_request, agent_select_request, agent_select_result, agent_set_prompt_request, agents_get_discovery_paths_request, api_key_auth_info, auth_identity, auth_info, auth_info_type, auth_validation_error, auth_validation_errors, built_in_model_catalog, built_in_model_catalog_entry, builtin_tool_descriptor, builtin_tool_format, builtin_tool_format_type, builtin_tool_input_schema, builtin_tool_input_schema_type, builtin_tool_safe_for_telemetry, builtin_tool_safe_telemetry_fields, cancel_user_requested_shell_command_result, canvas_action, canvas_action_invoke_request, canvas_action_invoke_result, canvas_close_request, canvas_host_context, canvas_host_context_capabilities, canvas_json_schema, canvas_list, canvas_list_open_result, canvas_open_request, canvas_provider_close_request, canvas_provider_invoke_action_request, canvas_provider_open_request, canvas_provider_open_result, canvas_provider_register_request, canvas_provider_unregister_request, canvas_session_context, capi_session_options, card_digest, card_digest_algorithm, card_digest_value, catalog_ai_skill_candidate, catalog_ai_skill_candidate_provenance, catalog_authentication_required_error, catalog_authentication_required_reason, catalog_candidate, catalog_candidate_kind, catalog_candidate_source, catalog_candidate_source_embedded, catalog_candidate_source_url, catalog_capability, catalog_capability_id, catalog_client_contract, catalog_contract_violation_error, catalog_contract_violation_reason, catalog_handle_rejected_error, catalog_handle_rejection_reason, catalog_handle_type, catalog_invalid_request_error, catalog_invalid_request_field, catalog_malformed_card_error, catalog_malformed_card_reason, catalog_mcp_server_candidate, catalog_mcp_server_candidate_provenance, catalog_mcp_server_installability, catalog_media_type, catalog_negotiated_contract, catalog_negotiation_refused_error, catalog_negotiation_refused_reason, catalog_network_failure_error, catalog_network_failure_reason, catalog_not_installable_error, catalog_not_installable_reason, catalog_policy_rejected_error, catalog_search_request, catalog_search_result, catalog_search_succeeded, catalog_unavailable_error, catalog_unavailable_reason, catalog_unavailable_transport_error, catalog_unavailable_transport_reason, catalog_unsafe_retrieval_error, catalog_unsafe_retrieval_reason, catalog_unsupported_kind_error, command_list, commands_finalize_invocation_effect_request, commands_finalize_invocation_effect_result, commands_handle_pending_command_request, commands_handle_pending_command_result, commands_invocation_effect_outcome, commands_invocation_origin, commands_invoke_request, commands_list_request, commands_respond_to_queued_command_request, commands_respond_to_queued_command_result, completions_get_trigger_characters_result, completions_request_request, completions_request_result, configure_session_extensions_params, connect_client_info, connected_remote_session_metadata, connected_remote_session_metadata_kind, connected_remote_session_metadata_repository, connect_remote_session_params, connect_request, connect_result, content_exclusion_check_paths_request, content_exclusion_check_paths_result, content_exclusion_path_check, content_filter_mode, context_heaviest_message, copilot_api_token_auth_info, copilot_user_response, copilot_user_response_endpoints, copilot_user_response_quota_snapshots, copilot_user_response_quota_snapshots_chat, copilot_user_response_quota_snapshots_completions, copilot_user_response_quota_snapshots_premium_interactions, current_model, current_tool_metadata, debug_collect_logs_collected_entry, debug_collect_logs_destination, debug_collect_logs_entry, debug_collect_logs_entry_kind, debug_collect_logs_include, debug_collect_logs_redaction, debug_collect_logs_request, debug_collect_logs_result, debug_collect_logs_result_kind, debug_collect_logs_skipped_entry, debug_collect_logs_source, discovered_canvas, discovered_extension, discovered_extension_mode, discovered_extension_plugin, discovered_extensions, discovered_extensions_disable_request, discovered_extensions_enable_request, discovered_extension_source, discovered_mcp_server, discovered_mcp_server_type, enqueue_command_params, enqueue_command_result, env_auth_info, event_log_read_request, event_log_release_interest_result, event_log_tail_result, event_log_types, events_agent_scope, events_cursor_status, events_read_direction, events_read_result, execute_command_params, execute_command_result, extension, extension_context_push_input, extension_launch_profile, extension_launch_provider_resolve_request, extension_launch_provider_resolve_result, extension_list, extensions_disable_request, extensions_enable_request, extension_source, extension_status, external_tool_result, external_tool_text_result_for_llm, external_tool_text_result_for_llm_binary_results_for_llm, external_tool_text_result_for_llm_binary_results_for_llm_type, external_tool_text_result_for_llm_content, external_tool_text_result_for_llm_content_audio, external_tool_text_result_for_llm_content_image, external_tool_text_result_for_llm_content_resource, external_tool_text_result_for_llm_content_resource_details, external_tool_text_result_for_llm_content_resource_link, external_tool_text_result_for_llm_content_resource_link_icon, external_tool_text_result_for_llm_content_resource_link_icon_theme, external_tool_text_result_for_llm_content_shell_exit, external_tool_text_result_for_llm_content_terminal, external_tool_text_result_for_llm_content_text, factory_abort_request, factory_ack_result, factory_agent_options, factory_agent_request, factory_agent_result, factory_agent_summary, factory_cancel_request, factory_current_phase, factory_declared_limits, factory_durable_operation, factory_execute_request, factory_execute_result, factory_get_run_progress_request, factory_get_run_request, factory_journal_get_request, factory_journal_get_result, factory_journal_put_request, factory_list_runs_request, factory_list_runs_result, factory_log_line, factory_log_line_kind, factory_log_request, factory_phase_observation, factory_phase_status, factory_progress_line, factory_progress_page, factory_resume_request, factory_resume_result, factory_run_consumed, factory_run_detail, factory_run_failure, factory_run_failure_kind, factory_run_limits, factory_run_request, factory_run_result, factory_run_status, factory_run_summary, factory_run_terminal, factory_tool_resume_request, factory_tool_run_options, factory_tool_run_request, filter_mapping, fleet_start_request, fleet_start_result, folder_trust_add_params, folder_trust_check_params, folder_trust_check_result, gh_cli_auth_info, git_hub_telemetry_client_info, git_hub_telemetry_event, git_hub_telemetry_notification, git_hub_token_acquire_reason, git_hub_token_acquire_request, git_hub_token_acquire_result, handle_pending_tool_call_request, handle_pending_tool_call_result, history_abort_manual_compaction_result, history_cancel_background_compaction_result, history_clear_context_request, history_clear_context_result, history_compact_context_window, history_compact_request, history_compact_result, history_file_restore_skip_reason, history_list_rewind_points_result, history_preview_rewind_request, history_preview_rewind_result, history_rewind_change_type, history_rewind_file_preview, history_rewind_mode, history_rewind_outcome, history_rewind_point, history_rewind_request, history_rewind_result, history_rewind_unavailable_reason, history_skipped_file_restore, history_summarize_for_handoff_result, history_truncate_request, history_truncate_result, hmac_auth_info, hook_invoke_request, hook_invoke_response, hook_type, installed_plugin, installed_plugin_info, installed_plugin_source, installed_plugin_source_git_hub, installed_plugin_source_local, installed_plugin_source_url, instruction_discovery_path, instruction_discovery_path_kind, instruction_discovery_path_list, instruction_discovery_path_location, instructions_discover_request, instructions_get_discovery_paths_request, instructions_get_sources_result, instruction_source, instruction_source_location, instruction_source_type, interrupt_main_turn_request, interrupt_main_turn_result, llm_inference_headers, llm_inference_http_request_chunk_request, llm_inference_http_request_chunk_result, llm_inference_http_request_start_request, llm_inference_http_request_start_result, llm_inference_http_request_start_transport, llm_inference_http_response_chunk_error, llm_inference_http_response_chunk_request, llm_inference_http_response_chunk_result, llm_inference_http_response_start_request, llm_inference_http_response_start_result, llm_inference_set_provider_result, local_session_metadata_value, log_request, log_result, lsp_initialize_request, managed_settings_read_result, marketplace_add_result, marketplace_browse_result, marketplace_info, marketplace_list_result, marketplace_plugin_info, marketplace_refresh_entry, marketplace_refresh_result, marketplace_remove_result, mcp_allowed_server, mcp_apps_call_tool_request, mcp_apps_diagnose_capability, mcp_apps_diagnose_request, mcp_apps_diagnose_result, mcp_apps_diagnose_server, mcp_apps_host_context, mcp_apps_host_context_details, mcp_apps_host_context_details_available_display_mode, mcp_apps_host_context_details_display_mode, mcp_apps_host_context_details_platform, mcp_apps_host_context_details_theme, mcp_apps_list_tools_request, mcp_apps_list_tools_result, mcp_apps_read_resource_request, mcp_apps_read_resource_result, mcp_apps_resource_content, mcp_apps_set_host_context_details, mcp_apps_set_host_context_details_available_display_mode, mcp_apps_set_host_context_details_display_mode, mcp_apps_set_host_context_details_platform, mcp_apps_set_host_context_details_theme, mcp_apps_set_host_context_request, mcp_cancel_sampling_execution_params, mcp_cancel_sampling_execution_result, mcp_config_add_request, mcp_config_disable_request, mcp_config_enable_request, mcp_config_list, mcp_config_remove_request, mcp_config_update_request, mcp_configure_git_hub_request, mcp_configure_git_hub_result, mcp_disable_request, mcp_discover_request, mcp_discover_result, mcp_elicitation_form_mode, mcp_enable_request, mcp_execute_sampling_params, mcp_execute_sampling_request, mcp_execute_sampling_result, mcp_failed_server, mcp_filtered_server, mcp_headers_handle_pending_headers_refresh_request, mcp_headers_handle_pending_headers_refresh_request_request, mcp_headers_handle_pending_headers_refresh_request_result, mcp_host_state, mcp_install_plan, mcp_is_server_running_request, mcp_is_server_running_result, mcp_list_tools_request, mcp_list_tools_result, mcp_oauth_authentication_state_changed_request, mcp_oauth_handle_pending_request, mcp_oauth_handle_pending_result, mcp_oauth_login_grant_type, mcp_oauth_login_request, mcp_oauth_login_result, mcp_oauth_pending_request_response, mcp_oauth_probe_needs_auth_reason, mcp_oauth_probe_request, mcp_oauth_probe_result, mcp_oauth_respond_request, mcp_oauth_respond_result, mcp_plan_configuration_change, mcp_plan_configuration_operation, mcp_plan_enum_value_type, mcp_plan_install_planned, mcp_plan_install_request, mcp_plan_install_result, mcp_plan_install_source, mcp_plan_install_source_candidate, mcp_plan_install_source_candidate_kind, mcp_plan_install_source_card, mcp_plan_install_source_card_kind, mcp_plan_package_install_method, mcp_plan_package_transport, mcp_plan_policy_decision, mcp_plan_policy_result, mcp_plan_policy_source, mcp_plan_provenance, mcp_plan_remote_install_method, mcp_plan_remote_transport, mcp_plan_required_value, mcp_plan_required_value_enum, mcp_plan_required_value_enum_kind, mcp_plan_required_value_scalar, mcp_plan_required_value_scalar_kind, mcp_plan_resource_identity, mcp_plan_scalar_value_type, mcp_plan_scope, mcp_plan_secret_placeholder, mcp_plan_secret_reference, mcp_plan_target, mcp_plan_transport_choice, mcp_plan_transport_choice_package, mcp_plan_transport_choice_remote, mcp_plan_value_category, mcp_register_external_client_request, mcp_reload_config, mcp_reload_with_config_request, mcp_remove_git_hub_result, mcp_resource, mcp_resource_annotations, mcp_resource_content, mcp_resource_icon, mcp_resources_list_request, mcp_resources_list_result, mcp_resources_list_templates_request, mcp_resources_list_templates_result, mcp_resources_read_request, mcp_resources_read_result, mcp_resource_template, mcp_restart_server_request, mcp_safe_for_telemetry, mcp_safe_for_telemetry_fields, mcp_sampling_execution_action, mcp_sampling_execution_result, mcp_serializable_server_config, mcp_server, mcp_server_auth_config, mcp_server_auth_config_redirect_port, mcp_server_card_embedded, mcp_server_card_embedded_kind, mcp_server_card_media_type, mcp_server_card_reference, mcp_server_card_url, mcp_server_card_url_kind, mcp_server_config, mcp_server_config_defer_tools, mcp_server_config_http, mcp_server_config_http_oauth_grant_type, mcp_server_config_http_type, mcp_server_config_memory, mcp_server_config_memory_type, mcp_server_config_stdio, mcp_server_config_stdio_type, mcp_server_failure_info, mcp_server_list, mcp_server_needs_auth_info, mcp_set_env_value_mode_details, mcp_set_env_value_mode_params, mcp_set_env_value_mode_result, mcp_start_server_request, mcp_start_servers_result, mcp_stop_server_request, mcp_task_metadata, mcp_tools, mcp_tool_ui, mcp_tool_ui_visibility, mcp_unregister_external_client_request, memory_configuration, metadata_context_attribution_result, metadata_context_heaviest_messages_request, metadata_context_heaviest_messages_result, metadata_context_info_request, metadata_context_info_result, metadata_is_processing_result, metadata_recompute_context_tokens_request, metadata_recompute_context_tokens_result, metadata_record_context_change_request, metadata_record_context_change_result, metadata_set_working_directory_request, metadata_set_working_directory_result, metadata_snapshot_current_mode, metadata_snapshot_remote_metadata, metadata_snapshot_remote_metadata_repository, metadata_snapshot_remote_metadata_task_type, model, model_apply_startup_overlay_request, model_billing, model_billing_promo, model_billing_token_prices, model_billing_token_prices_long_context, model_capabilities, model_capabilities_limits, model_capabilities_limits_vision, model_capabilities_override, model_capabilities_override_limits, model_capabilities_override_limits_vision, model_capabilities_override_supports, model_capabilities_supports, model_list, model_list_request, model_message, model_picker_category, model_picker_persistence_request, model_picker_price_category, model_picker_settings_context, model_policy, model_policy_state, model_set_reasoning_effort_request, model_set_reasoning_effort_result, models_list_request, model_switch_confirmation, model_switch_to_request, model_switch_to_result, model_warning_text, mode_set_request, mode_set_result, move_mcp_loading_to_background_result, named_provider_config, name_get_result, name_set_auto_request, name_set_auto_result, name_set_request, open_canvas_instance, options_update_additional_content_exclusion_policy, options_update_additional_content_exclusion_policy_rule, options_update_additional_content_exclusion_policy_rule_source, options_update_additional_content_exclusion_policy_scope, options_update_context_tier, options_update_env_value_mode, options_update_reasoning_summary, options_update_tool_filter_precedence, pending_permission_request, pending_permission_request_list, permission_decision, permission_decision_approved, permission_decision_approved_for_location, permission_decision_approved_for_session, permission_decision_approve_for_location, permission_decision_approve_for_location_approval, permission_decision_approve_for_location_approval_commands, permission_decision_approve_for_location_approval_custom_tool, permission_decision_approve_for_location_approval_extension_env_access, permission_decision_approve_for_location_approval_extension_management, permission_decision_approve_for_location_approval_extension_permission_access, permission_decision_approve_for_location_approval_factory, permission_decision_approve_for_location_approval_mcp, permission_decision_approve_for_location_approval_mcp_sampling, permission_decision_approve_for_location_approval_memory, permission_decision_approve_for_location_approval_read, permission_decision_approve_for_location_approval_write, permission_decision_approve_for_session, permission_decision_approve_for_session_approval, permission_decision_approve_for_session_approval_commands, permission_decision_approve_for_session_approval_custom_tool, permission_decision_approve_for_session_approval_extension_env_access, permission_decision_approve_for_session_approval_extension_management, permission_decision_approve_for_session_approval_extension_permission_access, permission_decision_approve_for_session_approval_factory, permission_decision_approve_for_session_approval_mcp, permission_decision_approve_for_session_approval_mcp_sampling, permission_decision_approve_for_session_approval_memory, permission_decision_approve_for_session_approval_read, permission_decision_approve_for_session_approval_write, permission_decision_approve_once, permission_decision_approve_permanently, permission_decision_cancelled, permission_decision_context, permission_decision_denied_by_content_exclusion_policy, permission_decision_denied_by_permission_request_hook, permission_decision_denied_by_rules, permission_decision_denied_interactively_by_user, permission_decision_denied_no_approval_rule_and_could_not_request_from_user, permission_decision_outcome, permission_decision_reject, permission_decision_request, permission_decision_source, permission_decision_surface, permission_decision_user_not_available, permission_location_add_tool_approval_params, permission_location_apply_params, permission_location_apply_result, permission_location_resolve_params, permission_location_resolve_result, permission_location_type, permission_mode_source, permission_paths_add_params, permission_paths_allowed_check_params, permission_paths_allowed_check_result, permission_paths_config, permission_paths_list, permission_paths_update_primary_params, permission_paths_workspace_check_params, permission_paths_workspace_check_result, permission_prompt_shown_notification, permission_request_result, permission_response_capability, permission_rules_set, permissions_configure_additional_content_exclusion_policy, permissions_configure_additional_content_exclusion_policy_rule, permissions_configure_additional_content_exclusion_policy_rule_source, permissions_configure_additional_content_exclusion_policy_scope, permissions_configure_params, permissions_configure_result, permissions_folder_trust_add_trusted_result, permissions_get_mode_request, permissions_get_mode_result, permissions_locations_add_tool_approval_details, permissions_locations_add_tool_approval_details_commands, permissions_locations_add_tool_approval_details_custom_tool, permissions_locations_add_tool_approval_details_extension_env_access, permissions_locations_add_tool_approval_details_extension_management, permissions_locations_add_tool_approval_details_extension_permission_access, permissions_locations_add_tool_approval_details_factory, permissions_locations_add_tool_approval_details_mcp, permissions_locations_add_tool_approval_details_mcp_sampling, permissions_locations_add_tool_approval_details_memory, permissions_locations_add_tool_approval_details_read, permissions_locations_add_tool_approval_details_write, permissions_locations_add_tool_approval_result, permissions_modify_rules_params, permissions_modify_rules_result, permissions_modify_rules_scope, permissions_notify_prompt_shown_result, permissions_paths_add_result, permissions_paths_list_request, permissions_paths_update_primary_result, permissions_pending_requests_request, permissions_reset_session_approvals_request, permissions_reset_session_approvals_result, permissions_set_approve_all_request, permissions_set_approve_all_result, permissions_set_approve_all_source, permissions_set_mode_request, permissions_set_mode_result, permissions_set_required_request, permissions_set_required_result, permissions_urls_set_unrestricted_mode_result, permission_urls_config, permission_urls_set_unrestricted_mode_params, ping_request, ping_result, plan_read_result, plan_read_sql_todos_result, plan_read_sql_todos_with_dependencies_result, plan_sql_todo_dependency, plan_sql_todos_row, plan_update_request, plugin, plugin_install_result, plugin_list, plugin_list_result, plugins_builtin_set_request, plugins_disable_request, plugins_enable_request, plugins_install_request, plugins_marketplaces_add_request, plugins_marketplaces_browse_request, plugins_marketplaces_refresh_request, plugins_marketplaces_remove_request, plugins_reload_request, plugins_uninstall_request, plugins_update_request, plugin_update_all_entry, plugin_update_all_result, plugin_update_result, protocol_external_tool_defer, protocol_external_tool_definition, provider_add_request, provider_add_result, provider_config, provider_config_azure, provider_config_transport, provider_config_type, provider_config_wire_api, provider_endpoint, provider_endpoint_transport, provider_endpoint_type, provider_endpoint_wire_api, provider_get_endpoint_request, provider_model_config, provider_session_token, provider_token_acquire_request, provider_token_acquire_result, push_attachment, push_attachment_blob, push_attachment_directory, push_attachment_file, push_attachment_file_line_range, push_attachment_git_hub_actions_job, push_attachment_git_hub_commit, push_attachment_git_hub_file, push_attachment_git_hub_file_diff, push_attachment_git_hub_file_diff_side, push_attachment_git_hub_reference, push_attachment_git_hub_reference_type, push_attachment_git_hub_release, push_attachment_git_hub_repository, push_attachment_git_hub_snippet, push_attachment_git_hub_tree_comparison, push_attachment_git_hub_tree_comparison_side, push_attachment_git_hub_url, push_attachment_selection, push_attachment_selection_details, push_attachment_selection_details_end, push_attachment_selection_details_start, push_git_hub_repo_ref, queue_begin_deferred_idle_drain_request, queue_begin_deferred_idle_drain_result, queue_consume_system_notifications_request, queued_command_handled, queued_command_not_handled, queued_command_result, queue_defer_session_idle_request, queue_duplicate_at_request, queue_duplicate_at_result, queue_enqueue_resume_pending_result, queue_finish_deferred_idle_drain_request, queue_finish_deferred_idle_drain_result, queue_has_pending_result, queue_insert_at_request, queue_insert_at_result, queue_insert_message, queue_move_item_request, queue_move_item_result, queue_pending_items, queue_pending_items_kind, queue_pending_items_result, queue_remove_at_request, queue_remove_at_result, queue_remove_most_recent_result, queue_send_now_request, queue_send_now_result, queue_set_drain_paused_request, queue_snapshot_result, queue_update_text_request, queue_update_text_result, register_event_interest_params, register_event_interest_result, register_extension_tools_params, register_extension_tools_result, release_event_interest_params, remote_control_config, remote_control_config_existing_mc_session, remote_control_status, remote_control_status_active, remote_control_status_connecting, remote_control_status_error, remote_control_status_off, remote_control_status_result, remote_control_stop_result, remote_control_transfer_result, remote_enable_request, remote_enable_result, remote_notify_steerable_changed_request, remote_notify_steerable_changed_result, remote_session_connection_result, remote_session_host_status, remote_session_metadata_repository, remote_session_metadata_task_type, remote_session_metadata_value, remote_session_mode, remote_session_repository, run_options, sandbox_config, sandbox_config_auth, sandbox_config_source, sandbox_config_user_policy, sandbox_config_user_policy_experimental, sandbox_config_user_policy_experimental_seatbelt, sandbox_config_user_policy_filesystem, sandbox_config_user_policy_network, sandbox_config_user_policy_network_proxy, sandbox_config_user_policy_seatbelt, sandbox_enforcement_status, schedule_add_at_request, schedule_add_cron_request, schedule_add_request, schedule_add_result, schedule_add_self_paced_request, schedule_entry, schedule_has_self_paced_result, schedule_list, schedule_rearm_self_paced_request, schedule_stop_request, schedule_stop_result, secrets_add_filter_values_request, secrets_add_filter_values_result, send_agent_mode, send_attachments_to_message_params, send_message_item, send_messages_request, send_messages_result, send_mode, send_request, send_result, send_system_notification_request, server_agent_list, server_instruction_source_list, server_skill, server_skill_list, session_activity, session_agent_list_request, session_auth_login_request, session_auth_logout_user_request, session_auth_status, session_auth_switch_request, session_bulk_delete_result, session_cancel_all_background_agents_result, session_capability, session_commands_list_request, session_completion_item, session_context, session_context_host_type, session_enrich_metadata_result, session_fs_append_file_request, session_fs_error, session_fs_error_code, session_fs_exists_request, session_fs_exists_result, session_fs_mkdir_request, session_fs_readdir_request, session_fs_readdir_result, session_fs_readdir_with_types_entry, session_fs_readdir_with_types_entry_type, session_fs_readdir_with_types_request, session_fs_readdir_with_types_result, session_fs_read_file_request, session_fs_read_file_result, session_fs_rename_request, session_fs_rm_request, session_fs_set_provider_capabilities, session_fs_set_provider_conventions, session_fs_set_provider_request, session_fs_set_provider_result, session_fs_sqlite_exists_request, session_fs_sqlite_exists_result, session_fs_sqlite_query_request, session_fs_sqlite_query_result, session_fs_sqlite_query_type, session_fs_sqlite_transaction_error, session_fs_sqlite_transaction_error_class, session_fs_sqlite_transaction_request, session_fs_sqlite_transaction_result, session_fs_sqlite_transaction_statement, session_fs_stat_request, session_fs_stat_result, session_fs_write_file_request, session_git_hub_auth_get_all_auth_available_result, session_git_hub_auth_logout_result, session_git_hub_auth_logout_user_result, session_history_compact_request, session_installed_plugin, session_installed_plugin_source, session_installed_plugin_source_git_hub, session_installed_plugin_source_local, session_installed_plugin_source_url, session_limit_prediction_baseline_data, session_limit_prediction_client_type, session_limit_prediction_details, session_limit_prediction_predict_request, session_limit_prediction_request, session_limit_prediction_result, session_limit_prediction_source, session_limit_prediction_tier, session_limit_prediction_tier_option, session_limit_prediction_unavailable_reason, session_list, session_list_entry, session_list_filter, session_load_deferred_repo_hooks_result, session_log_level, session_managed_permissions, session_managed_settings, session_mcp_apps_call_tool_result, session_metadata_snapshot, session_mode, session_model_list, session_model_list_request, session_model_price_category, session_open_options, session_open_options_additional_content_exclusion_policy, session_open_options_additional_content_exclusion_policy_rule, session_open_options_additional_content_exclusion_policy_rule_source, session_open_options_additional_content_exclusion_policy_scope, session_open_options_env_value_mode, session_open_options_reasoning_summary, session_open_params, session_open_result, session_plugins_reload_request, session_provider_get_endpoint_request, session_prune_result, sessions_bulk_delete_request, sessions_check_in_use_request, sessions_check_in_use_result, sessions_close_request, sessions_close_result, sessions_delete_request, sessions_enrich_metadata_request, session_set_credentials_params, session_set_credentials_result, session_settings_built_in_tool_availability_snapshot, session_settings_evaluate_predicate_request, session_settings_evaluate_predicate_result, session_settings_job_snapshot, session_settings_model_snapshot, session_settings_online_evaluation_snapshot, session_settings_predicate_name, session_settings_repo_snapshot, session_settings_snapshot, session_settings_validation_snapshot, sessions_find_by_prefix_request, sessions_find_by_prefix_result, sessions_find_by_task_id_request, sessions_find_by_task_id_result, sessions_fork_request, sessions_fork_result, sessions_get_board_entry_count_request, sessions_get_board_entry_count_result, sessions_get_event_file_path_request, sessions_get_event_file_path_result, sessions_get_last_for_context_request, sessions_get_last_for_context_result, sessions_get_metadata_request, sessions_get_metadata_result, sessions_get_persisted_remote_steerable_request, sessions_get_persisted_remote_steerable_result, session_sizes, sessions_list_non_empty_session_ids_request, sessions_list_non_empty_session_ids_result, sessions_list_request, sessions_load_deferred_repo_hooks_request, sessions_open_attach, sessions_open_cloud, sessions_open_create, sessions_open_handoff, sessions_open_handoff_task_type, sessions_open_progress, sessions_open_progress_status, sessions_open_progress_step, sessions_open_remote, sessions_open_resume, sessions_open_resume_last, sessions_open_status, session_source, sessions_prune_old_request, sessions_register_extension_tools_on_session_options, sessions_release_lock_request, sessions_release_lock_result, sessions_reload_plugin_hooks_request, sessions_reload_plugin_hooks_result, sessions_save_request, sessions_save_result, sessions_set_additional_plugins_request, sessions_set_additional_plugins_result, sessions_set_remote_control_steering_request, sessions_start_remote_control_request, sessions_stop_remote_control_request, sessions_transfer_remote_control_request, session_telemetry_engagement, session_update_options_params, session_update_options_result, session_visibility_status, session_working_directory_context, session_working_directory_context_host_type, settable_auth_info, settable_token_auth_info, shell_cancel_user_requested_request, shell_credentials, shell_exec_request, shell_exec_result, shell_execute_user_requested_request, shell_init_profile, shell_init_script, shell_init_script_shell, shell_kill_request, shell_kill_result, shell_kill_signal, shell_options, shutdown_request, skill, skill_discovery_path, skill_discovery_path_list, skill_discovery_scope, skill_list, skills_config_set_disabled_skills_request, skills_config_set_skill_disabled_request, skills_disable_request, skills_discover_request, skills_enable_request, skills_get_discovery_paths_request, skills_get_invoked_result, skills_invoked_skill, skills_load_diagnostics, slash_command_add_timeline_entry_result, slash_command_agent_prompt_result, slash_command_completed_result, slash_command_info, slash_command_input, slash_command_input_choice, slash_command_input_completion, slash_command_invocation_result, slash_command_kind, slash_command_model_picker_dialog, slash_command_select_subcommand_option, slash_command_select_subcommand_result, slash_command_set_model_result, slash_command_set_plan_model_result, slash_command_show_dialog_result, slash_command_text_result, slash_command_timeline_entry, subagent_settings_entry, subagent_settings_entry_context_tier, task_agent_info, task_agent_progress, task_complete_data, task_completion_decision, task_execution_mode, task_info, task_list, task_progress_line, tasks_cancel_request, tasks_cancel_result, tasks_get_current_promotable_result, tasks_get_progress_request, tasks_get_progress_result, task_shell_info, task_shell_info_attachment_mode, task_shell_progress, tasks_promote_current_to_background_result, tasks_promote_to_background_request, tasks_promote_to_background_result, tasks_refresh_result, tasks_remove_request, tasks_remove_result, tasks_send_message_request, tasks_send_message_result, tasks_start_agent_request, tasks_start_agent_result, task_status, tasks_wait_for_pending_result, telemetry_set_feature_overrides_request, token_auth_info, token_provider_auth_info, tool, tool_list, tool_result, tool_result_expanded, tool_result_new_message, tool_result_type, tools_execute_request, tools_get_builtin_descriptors_request, tools_get_builtin_descriptors_result, tools_get_current_metadata_result, tools_initialize_and_validate_result, tools_list_request, tools_set_request, tools_set_result, tools_shell_descriptor_config, tools_task_complete_event_data_request, tools_update_subagent_settings_result, ui_auto_mode_switch_response, ui_elicitation_array_any_of_field, ui_elicitation_array_any_of_field_items, ui_elicitation_array_any_of_field_items_any_of, ui_elicitation_array_enum_field, ui_elicitation_array_enum_field_items, ui_elicitation_field_value, ui_elicitation_request, ui_elicitation_response, ui_elicitation_response_action, ui_elicitation_response_content, ui_elicitation_result, ui_elicitation_schema, ui_elicitation_schema_property, ui_elicitation_schema_property_boolean, ui_elicitation_schema_property_number, ui_elicitation_schema_property_number_type, ui_elicitation_schema_property_string, ui_elicitation_schema_property_string_format, ui_elicitation_string_enum_field, ui_elicitation_string_one_of_field, ui_elicitation_string_one_of_field_one_of, ui_ephemeral_query_request, ui_ephemeral_query_result, ui_exit_plan_mode_action, ui_exit_plan_mode_response, ui_handle_pending_auto_mode_switch_request, ui_handle_pending_elicitation_request, ui_handle_pending_exit_plan_mode_request, ui_handle_pending_result, ui_handle_pending_sampling_request, ui_handle_pending_sampling_response, ui_handle_pending_session_limits_exhausted_request, ui_handle_pending_user_input_request, ui_register_direct_auto_mode_switch_handler_result, ui_session_limits_exhausted_response, ui_session_limits_exhausted_response_action, ui_unregister_direct_auto_mode_switch_handler_request, ui_unregister_direct_auto_mode_switch_handler_result, ui_user_input_response, update_subagent_settings_request, usage_get_metrics_result, usage_metrics_agent_metric, usage_metrics_code_changes, usage_metrics_model_metric, usage_metrics_model_metric_requests, usage_metrics_model_metric_token_detail, usage_metrics_model_metric_usage, usage_metrics_token_detail, user_auth_info, user_requested_shell_command_result, user_setting_metadata, user_settings_get_result, user_settings_set_request, user_settings_set_result, visibility_get_result, visibility_set_request, visibility_set_result, workspace_diff_file_change, workspace_diff_file_change_type, workspace_diff_mode, workspace_diff_result, workspaces_add_summary_request, workspaces_add_summary_result, workspaces_autopilot_objective_exists_result, workspaces_checkpoints, workspaces_create_file_request, workspaces_delete_autopilot_objective_result, workspaces_diff_request, workspaces_ensure_request, workspaces_get_workspace_result, workspaces_list_checkpoints_result, workspaces_list_files_result, workspaces_read_autopilot_objective_result, workspaces_read_checkpoint_request, workspaces_read_checkpoint_result, workspaces_read_file_request, workspaces_read_file_result, workspaces_save_large_paste_request, workspaces_save_large_paste_result, workspaces_truncate_summaries_request, workspace_summary_host_type, workspaces_update_metadata_request, workspaces_workspace_details_host_type, workspaces_write_autopilot_objective_request, workspaces_write_autopilot_objective_result, session_auth_info_result, session_context_attribution, session_context_info, subagent_settings, task_progress, workspace_summary) def to_dict(self) -> dict: result: dict = {} @@ -38215,6 +38238,7 @@ def to_dict(self) -> dict: result["SandboxConfigUserPolicyNetwork"] = to_class(SandboxConfigUserPolicyNetwork, self.sandbox_config_user_policy_network) result["SandboxConfigUserPolicyNetworkProxy"] = to_class(SandboxConfigUserPolicyNetworkProxy, self.sandbox_config_user_policy_network_proxy) result["SandboxConfigUserPolicySeatbelt"] = to_class(SandboxConfigUserPolicySeatbelt, self.sandbox_config_user_policy_seatbelt) + result["SandboxEnforcementStatus"] = to_class(SandboxEnforcementStatus, self.sandbox_enforcement_status) result["ScheduleAddAtRequest"] = to_class(ScheduleAddAtRequest, self.schedule_add_at_request) result["ScheduleAddCronRequest"] = to_class(ScheduleAddCronRequest, self.schedule_add_cron_request) result["ScheduleAddRequest"] = to_class(ScheduleAddRequest, self.schedule_add_request) @@ -39663,6 +39687,17 @@ async def _connect(self, params: _ConnectRequest, *, timeout: float | None = Non return _ConnectResult.from_dict(await self._client.request("connect", params_dict, **_timeout_kwargs(timeout))) +# Experimental: this API group is experimental and may change or be removed. +class SandboxApi: + def __init__(self, client: "JsonRpcClient", session_id: str): + self._client = client + self._session_id = session_id + + async def get_enforcement_status(self, *, timeout: float | None = None) -> SandboxEnforcementStatus: + "Returns whether managed policy requires sandbox enforcement and whether an enforcement failure has permanently blocked the session.\n\nReturns:\n Managed sandbox enforcement state for a session." + return SandboxEnforcementStatus.from_dict(await self._client.request("session.sandbox.getEnforcementStatus", {"sessionId": self._session_id}, **_timeout_kwargs(timeout))) + + # Experimental: this API group is experimental and may change or be removed. class GitHubAuthApi: def __init__(self, client: "JsonRpcClient", session_id: str): @@ -41139,6 +41174,7 @@ class SessionRpc: def __init__(self, client: "JsonRpcClient", session_id: str): self._client = client self._session_id = session_id + self.sandbox = SandboxApi(client, session_id) self.git_hub_auth = GitHubAuthApi(client, session_id) self.debug = DebugApi(client, session_id) self.canvas = CanvasApi(client, session_id) @@ -42061,7 +42097,6 @@ async def handle_git_hub_token_get_token(params: dict) -> dict | None: "ExtensionsApi", "ExtensionsDisableRequest", "ExtensionsEnableRequest", - "ExternalRefMCPOauthHTTPResponse", "ExternalToolResult", "ExternalToolTextResultForLlm", "ExternalToolTextResultForLlmBinaryResultsForLlm", @@ -42766,6 +42801,7 @@ async def handle_git_hub_token_get_token(params: dict) -> dict | None: "RemoteSessionMode", "RemoteSessionRepository", "RunOptions", + "SandboxApi", "SandboxConfig", "SandboxConfigAuth", "SandboxConfigUserPolicy", @@ -42775,6 +42811,7 @@ async def handle_git_hub_token_get_token(params: dict) -> dict | None: "SandboxConfigUserPolicyNetwork", "SandboxConfigUserPolicyNetworkProxy", "SandboxConfigUserPolicySeatbelt", + "SandboxEnforcementStatus", "Saved", "ScheduleAddAtRequest", "ScheduleAddCronRequest", diff --git a/python/copilot/generated/session_events.py b/python/copilot/generated/session_events.py index c22f9f24fc..fd41b6c6c6 100644 --- a/python/copilot/generated/session_events.py +++ b/python/copilot/generated/session_events.py @@ -2450,6 +2450,7 @@ class AssistantMessageToolRequest: name: str tool_call_id: str arguments: Any = None + caller: AssistantMessageToolRequestCaller | None = None intention_summary: str | None = None mcp_server_name: str | None = None mcp_tool_name: str | None = None @@ -2462,6 +2463,7 @@ def from_dict(obj: Any) -> "AssistantMessageToolRequest": name = from_str(obj.get("name")) tool_call_id = from_str(obj.get("toolCallId")) arguments = obj.get("arguments") + caller = from_union([from_none, AssistantMessageToolRequestCaller.from_dict], obj.get("caller")) intention_summary = from_union([from_none, from_str], obj.get("intentionSummary")) mcp_server_name = from_union([from_none, from_str], obj.get("mcpServerName")) mcp_tool_name = from_union([from_none, from_str], obj.get("mcpToolName")) @@ -2471,6 +2473,7 @@ def from_dict(obj: Any) -> "AssistantMessageToolRequest": name=name, tool_call_id=tool_call_id, arguments=arguments, + caller=caller, intention_summary=intention_summary, mcp_server_name=mcp_server_name, mcp_tool_name=mcp_tool_name, @@ -2484,6 +2487,8 @@ def to_dict(self) -> dict: result["toolCallId"] = from_str(self.tool_call_id) if self.arguments is not None: result["arguments"] = self.arguments + if self.caller is not None: + result["caller"] = from_union([from_none, lambda x: to_class(AssistantMessageToolRequestCaller, x)], self.caller) if self.intention_summary is not None: result["intentionSummary"] = from_union([from_none, from_str], self.intention_summary) if self.mcp_server_name is not None: @@ -2497,6 +2502,29 @@ def to_dict(self) -> dict: return result +@dataclass +class AssistantMessageToolRequestCaller: + "Hosted program that requested this client tool call" + caller_id: str + type: AssistantMessageToolRequestCallerType + + @staticmethod + def from_dict(obj: Any) -> "AssistantMessageToolRequestCaller": + assert isinstance(obj, dict) + caller_id = from_str(obj.get("callerId")) + type = parse_enum(AssistantMessageToolRequestCallerType, obj.get("type")) + return AssistantMessageToolRequestCaller( + caller_id=caller_id, + type=type, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["callerId"] = from_str(self.caller_id) + result["type"] = to_enum(AssistantMessageToolRequestCallerType, self.type) + return result + + @dataclass class AssistantReasoningData: "Assistant reasoning content for timeline display with complete thinking text" @@ -8222,6 +8250,7 @@ class SessionResumeData: event_count: int resume_time: datetime already_in_use: bool | None = None + auto_tier: AutoTier | None = None context: WorkingDirectoryContext | None = None context_tier: ContextTier | None = None continue_pending_work: bool | None = None @@ -8240,6 +8269,7 @@ def from_dict(obj: Any) -> "SessionResumeData": event_count = from_int(obj.get("eventCount")) resume_time = from_datetime(obj.get("resumeTime")) already_in_use = from_union([from_none, from_bool], obj.get("alreadyInUse")) + auto_tier = from_union([from_none, lambda x: parse_enum(AutoTier, x)], obj.get("autoTier")) context = from_union([from_none, WorkingDirectoryContext.from_dict], obj.get("context")) context_tier = from_union([from_none, lambda x: parse_enum(ContextTier, x)], obj.get("contextTier")) continue_pending_work = from_union([from_none, from_bool], obj.get("continuePendingWork")) @@ -8255,6 +8285,7 @@ def from_dict(obj: Any) -> "SessionResumeData": event_count=event_count, resume_time=resume_time, already_in_use=already_in_use, + auto_tier=auto_tier, context=context, context_tier=context_tier, continue_pending_work=continue_pending_work, @@ -8274,6 +8305,8 @@ def to_dict(self) -> dict: result["resumeTime"] = to_datetime(self.resume_time) if self.already_in_use is not None: result["alreadyInUse"] = from_union([from_none, from_bool], self.already_in_use) + if self.auto_tier is not None: + result["autoTier"] = from_union([from_none, lambda x: to_enum(AutoTier, x)], self.auto_tier) if self.context is not None: result["context"] = from_union([from_none, lambda x: to_class(WorkingDirectoryContext, x)], self.context) if self.context_tier is not None: @@ -8566,6 +8599,7 @@ class SessionStartData: start_time: datetime version: int already_in_use: bool | None = None + auto_tier: AutoTier | None = None context: WorkingDirectoryContext | None = None context_tier: ContextTier | None = None detached_from_spawning_parent_session_id: str | None = None @@ -8586,6 +8620,7 @@ def from_dict(obj: Any) -> "SessionStartData": start_time = from_datetime(obj.get("startTime")) version = from_int(obj.get("version")) already_in_use = from_union([from_none, from_bool], obj.get("alreadyInUse")) + auto_tier = from_union([from_none, lambda x: parse_enum(AutoTier, x)], obj.get("autoTier")) context = from_union([from_none, WorkingDirectoryContext.from_dict], obj.get("context")) context_tier = from_union([from_none, lambda x: parse_enum(ContextTier, x)], obj.get("contextTier")) detached_from_spawning_parent_session_id = from_union([from_none, from_str], obj.get("detachedFromSpawningParentSessionId")) @@ -8603,6 +8638,7 @@ def from_dict(obj: Any) -> "SessionStartData": start_time=start_time, version=version, already_in_use=already_in_use, + auto_tier=auto_tier, context=context, context_tier=context_tier, detached_from_spawning_parent_session_id=detached_from_spawning_parent_session_id, @@ -8624,6 +8660,8 @@ def to_dict(self) -> dict: result["version"] = to_int(self.version) if self.already_in_use is not None: result["alreadyInUse"] = from_union([from_none, from_bool], self.already_in_use) + if self.auto_tier is not None: + result["autoTier"] = from_union([from_none, lambda x: to_enum(AutoTier, x)], self.auto_tier) if self.context is not None: result["context"] = from_union([from_none, lambda x: to_class(WorkingDirectoryContext, x)], self.context) if self.context_tier is not None: @@ -11637,6 +11675,11 @@ class AgentInterruptedCancelPhase(Enum): MID_STREAM = "mid_stream" +class AssistantMessageToolRequestCallerType(Enum): + "Hosted program caller type" + PROGRAM = "program" + + class AssistantMessageToolRequestType(Enum): "Tool call type: \"function\" for standard tool calls, \"custom\" for grammar-based tool calls. Defaults to \"function\" when absent." # Standard function-style tool call. @@ -11695,6 +11738,16 @@ class AutoModeSwitchResponse(Enum): NO = "no" +class AutoTier(Enum): + "Routing preference used when the session model is `auto`." + # Optimize for efficiency. + EFFICIENCY = "efficiency" + # Balance efficiency and intelligence. + BALANCE = "balance" + # Optimize for intelligence. + INTELLIGENCE = "intelligence" + + class AutopilotObjectiveChangedOperation(Enum): "The type of operation performed on the autopilot objective state file" # Autopilot objective state file was created for a new objective. @@ -12487,6 +12540,8 @@ def session_event_to_dict(x: SessionEvent) -> Any: "AssistantMessageServerTools", "AssistantMessageStartData", "AssistantMessageToolRequest", + "AssistantMessageToolRequestCaller", + "AssistantMessageToolRequestCallerType", "AssistantMessageToolRequestType", "AssistantReasoningData", "AssistantReasoningDeltaData", @@ -12530,6 +12585,7 @@ def session_event_to_dict(x: SessionEvent) -> Any: "AutoModeSwitchCompletedData", "AutoModeSwitchRequestedData", "AutoModeSwitchResponse", + "AutoTier", "AutopilotObjectiveChangedOperation", "AutopilotObjectiveChangedStatus", "BinaryAssetReference", diff --git a/rust/src/generated/api_types.rs b/rust/src/generated/api_types.rs index e22c888f23..6c2f9c8eaf 100644 --- a/rust/src/generated/api_types.rs +++ b/rust/src/generated/api_types.rs @@ -10,10 +10,11 @@ use std::collections::HashMap; use serde::{Deserialize, Serialize}; use super::session_events::{ - AbortReason, ContextTier, McpOauthHttpResponse, McpOauthWWWAuthenticateParams, McpServerSource, - McpServerStatus, ModelChangeSource, OmittedBinaryOmittedReason, PermissionMode, - PermissionPromptRequest, PermissionRule, ReasoningSummary, SessionLimitsConfig, SessionMode, - ShutdownType, SkillSource, TaskCompletionOutcome, UserToolSessionApproval, Verbosity, + AbortReason, AutoTier, ContextTier, McpOauthHttpResponse, McpOauthWWWAuthenticateParams, + McpServerSource, McpServerStatus, ModelChangeSource, OmittedBinaryOmittedReason, + PermissionMode, PermissionPromptRequest, PermissionRule, ReasoningSummary, SessionLimitsConfig, + SessionMode, ShutdownType, SkillSource, TaskCompletionOutcome, UserToolSessionApproval, + Verbosity, }; use crate::types::{RequestId, SessionEvent, SessionId}; @@ -202,6 +203,8 @@ pub mod rpc_methods { pub const SESSION_SEND: &str = "session.send"; /// `session.sendMessages` pub const SESSION_SENDMESSAGES: &str = "session.sendMessages"; + /// `session.sandbox.getEnforcementStatus` + pub const SESSION_SANDBOX_GETENFORCEMENTSTATUS: &str = "session.sandbox.getEnforcementStatus"; /// `session.sendSystemNotification` pub const SESSION_SENDSYSTEMNOTIFICATION: &str = "session.sendSystemNotification"; /// `session.abort` @@ -3019,6 +3022,9 @@ pub struct CanvasProviderUnregisterRequest { #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct CapiSessionOptions { + /// Routing preference used when the session model is `auto`. The runtime persists the preference across cold resume. When omitted, the default routing behavior is used. Resuming an already-resident session cannot change its preference. + #[serde(skip_serializing_if = "Option::is_none")] + pub auto_tier: Option, /// Whether to use WebSocket transport for the CAPI Responses API. Enabled by default when the model advertises `ws:/responses` support; set to `false` to force the HTTP Responses transport in environments where WebSockets are blocked (e.g. behind a proxy). Setting this to `false` is equivalent to the `COPILOT_CLI_DISABLE_WEBSOCKET_RESPONSES` environment variable. #[serde(skip_serializing_if = "Option::is_none")] pub enable_web_socket_responses: Option, @@ -4411,6 +4417,9 @@ pub struct DiscoveredMcpServer { pub struct EnqueueCommandParams { /// Slash-prefixed command string to enqueue, e.g. '/compact' or '/model gpt-4'. Queued FIFO with any in-flight items; if the session is idle, processing kicks off immediately. pub command: String, + /// Optional user-facing text for the queue row. The command string is shown when omitted. + #[serde(skip_serializing_if = "Option::is_none")] + pub display_text: Option, } /// Indicates whether the command was accepted into the local execution queue. @@ -14756,6 +14765,26 @@ pub struct SandboxConfig { pub user_policy: Option, } +/// Managed sandbox enforcement state for a session. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SandboxEnforcementStatus { + /// Whether an enforcement failure has permanently blocked the session. + pub blocked: bool, + /// The first sandbox enforcement failure that blocked the session. + #[serde(skip_serializing_if = "Option::is_none")] + pub reason: Option, + /// Whether the effective managed policy requires an available sandbox backend. + pub required: bool, +} + /// Register an absolute-time scheduled prompt. /// ///
@@ -21992,6 +22021,41 @@ pub struct SessionSendMessagesResult { pub message_ids: Vec, } +/// Identifies the target session. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionSandboxGetEnforcementStatusParams { + /// Target session identifier + pub session_id: SessionId, +} + +/// Managed sandbox enforcement state for a session. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionSandboxGetEnforcementStatusResult { + /// Whether an enforcement failure has permanently blocked the session. + pub blocked: bool, + /// The first sandbox enforcement failure that blocked the session. + #[serde(skip_serializing_if = "Option::is_none")] + pub reason: Option, + /// Whether the effective managed policy requires an available sandbox backend. + pub required: bool, +} + /// Result of aborting the current turn /// ///
diff --git a/rust/src/generated/rpc.rs b/rust/src/generated/rpc.rs index 4d5b7f1538..99fbd099ae 100644 --- a/rust/src/generated/rpc.rs +++ b/rust/src/generated/rpc.rs @@ -3185,6 +3185,13 @@ impl<'a> SessionRpc<'a> { } } + /// `session.sandbox.*` sub-namespace. + pub fn sandbox(&self) -> SessionRpcSandbox<'a> { + SessionRpcSandbox { + session: self.session, + } + } + /// `session.schedule.*` sub-namespace. pub fn schedule(&self) -> SessionRpcSchedule<'a> { SessionRpcSchedule { @@ -9413,6 +9420,42 @@ impl<'a> SessionRpcRemote<'a> { } } +/// `session.sandbox.*` RPCs. +#[derive(Clone, Copy)] +pub struct SessionRpcSandbox<'a> { + pub(crate) session: &'a Session, +} + +impl<'a> SessionRpcSandbox<'a> { + /// Returns whether managed policy requires sandbox enforcement and whether an enforcement failure has permanently blocked the session. + /// + /// Wire method: `session.sandbox.getEnforcementStatus`. + /// + /// # Returns + /// + /// Managed sandbox enforcement state for a session. + /// + ///
+ /// + /// **Experimental.** This API is part of an experimental wire-protocol surface + /// and may change or be removed in future SDK or CLI releases. Pin both the + /// SDK and CLI versions if your code depends on it. + /// + ///
+ pub async fn get_enforcement_status(&self) -> Result { + let wire_params = serde_json::json!({ "sessionId": self.session.id() }); + let _value = self + .session + .client() + .call( + rpc_methods::SESSION_SANDBOX_GETENFORCEMENTSTATUS, + Some(wire_params), + ) + .await?; + Ok(serde_json::from_value(_value)?) + } +} + /// `session.schedule.*` RPCs. #[derive(Clone, Copy)] pub struct SessionRpcSchedule<'a> { diff --git a/rust/src/generated/session_events.rs b/rust/src/generated/session_events.rs index 79284b1671..266be4895a 100644 --- a/rust/src/generated/session_events.rs +++ b/rust/src/generated/session_events.rs @@ -939,6 +939,9 @@ pub struct SessionStartData { /// Whether the session was already in use by another client at start time #[serde(skip_serializing_if = "Option::is_none")] pub already_in_use: Option, + /// Auto routing preference selected at session creation time + #[serde(skip_serializing_if = "Option::is_none")] + pub auto_tier: Option, /// Working directory and git context at session start #[serde(skip_serializing_if = "Option::is_none")] pub context: Option, @@ -988,6 +991,9 @@ pub struct SessionResumeData { /// Whether the session was already in use by another client at resume time #[serde(skip_serializing_if = "Option::is_none")] pub already_in_use: Option, + /// Auto routing preference active at resume time + #[serde(skip_serializing_if = "Option::is_none")] + pub auto_tier: Option, /// Updated working directory and git context at resume time #[serde(skip_serializing_if = "Option::is_none")] pub context: Option, @@ -2527,6 +2533,16 @@ pub struct AssistantMessageServerTools { pub raw_content_blocks: Option>, } +/// Hosted program that requested this client tool call +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct AssistantMessageToolRequestCaller { + /// Provider-assigned identifier for the hosted caller. + pub caller_id: String, + /// Kind of hosted caller that requested the client tool call. + pub r#type: AssistantMessageToolRequestCallerType, +} + /// A tool invocation request from the assistant #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] @@ -2534,6 +2550,9 @@ pub struct AssistantMessageToolRequest { /// Arguments to pass to the tool, format depends on the tool #[serde(skip_serializing_if = "Option::is_none")] pub arguments: Option, + /// Hosted program that requested this client tool call + #[serde(skip_serializing_if = "Option::is_none")] + pub caller: Option, /// Resolved intention summary describing what this specific call does #[serde(skip_serializing_if = "Option::is_none")] pub intention_summary: Option, @@ -6321,6 +6340,24 @@ pub struct McpAppToolCallCompleteData { pub tool_name: String, } +/// Routing preference used when the session model is `auto`. +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum AutoTier { + /// Optimize for efficiency. + #[serde(rename = "efficiency")] + Efficiency, + /// Balance efficiency and intelligence. + #[serde(rename = "balance")] + Balance, + /// Optimize for intelligence. + #[serde(rename = "intelligence")] + Intelligence, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, +} + /// Hosting platform type of the repository (github or ado) #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] pub enum WorkingDirectoryContextHostType { @@ -6939,6 +6976,17 @@ pub enum CitationProvider { Unknown, } +/// Hosted program caller type +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum AssistantMessageToolRequestCallerType { + #[serde(rename = "program")] + Program, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, +} + /// API endpoint used for this model call, matching CAPI supported_endpoints vocabulary #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] pub enum AssistantUsageApiEndpoint { diff --git a/rust/tests/e2e/commands.rs b/rust/tests/e2e/commands.rs index 2e15c24a0f..78c8e07616 100644 --- a/rust/tests/e2e/commands.rs +++ b/rust/tests/e2e/commands.rs @@ -210,6 +210,7 @@ async fn session_commands_enqueue_and_respond_to_queued_command() { .commands() .enqueue(EnqueueCommandParams { command: "/help".to_string(), + display_text: None, }) .await .expect("enqueue command"); diff --git a/rust/tests/e2e/rpc_queue.rs b/rust/tests/e2e/rpc_queue.rs index 6f4f881659..d5f1228302 100644 --- a/rust/tests/e2e/rpc_queue.rs +++ b/rust/tests/e2e/rpc_queue.rs @@ -153,6 +153,7 @@ async fn pendingitems_reports_queued_command_and_remove_and_clear_update_queue() .commands() .enqueue(EnqueueCommandParams { command: first_command, + display_text: None, }) .await .expect("enqueue command"); @@ -167,6 +168,7 @@ async fn pendingitems_reports_queued_command_and_remove_and_clear_update_queue() .commands() .enqueue(EnqueueCommandParams { command: second_command.clone(), + display_text: None, }) .await .expect("enqueue second command"); @@ -187,6 +189,7 @@ async fn pendingitems_reports_queued_command_and_remove_and_clear_update_queue() .commands() .enqueue(EnqueueCommandParams { command: third_command.clone(), + display_text: None, }) .await .expect("enqueue third command"); diff --git a/scripts/codegen/python.ts b/scripts/codegen/python.ts index bdb4d095b7..d4be520fe7 100644 --- a/scripts/codegen/python.ts +++ b/scripts/codegen/python.ts @@ -143,12 +143,22 @@ function placeholderToQuicktypeIdentifiers(placeholder: string): string[] { return [...new Set([basic, basic.replace(/Mcp/g, "MCP")])]; } -function postProcessExternalRefsForPython( +export function postProcessExternalRefsForPython( code: string, placeholderToReal: Map, externalEnumNames: Set = new Set() ): string { for (const [placeholder, realName] of placeholderToReal) { + const markerProperty = `__externalRefMarker_${placeholder}`; + const markerClass = [ + ...code.matchAll( + /(?:^|\n)(@dataclass\r?\nclass (\w+)\b[\s\S]*?)(?=\n@dataclass\b|\nclass\s+\w|\ndef\s+\w|$)/g + ), + ].find((match) => match[1].includes(`"${markerProperty}"`)); + if (markerClass) { + code = code.replace(markerClass[0], "\n"); + code = code.replace(new RegExp(`\\b${escapeRegExp(markerClass[2])}\\b`, "g"), realName); + } for (const quicktypeName of placeholderToQuicktypeIdentifiers(placeholder)) { code = code.replace( new RegExp( diff --git a/test/harness/package-lock.json b/test/harness/package-lock.json index 710d725318..8e1ab03a06 100644 --- a/test/harness/package-lock.json +++ b/test/harness/package-lock.json @@ -9,7 +9,7 @@ "version": "1.0.0", "license": "ISC", "devDependencies": { - "@github/copilot": "^1.0.82-0", + "@github/copilot": "^1.0.82", "@modelcontextprotocol/sdk": "^1.26.0", "@types/node": "^25.3.3", "@types/node-forge": "^1.3.14", @@ -472,8 +472,8 @@ } }, "node_modules/@github/copilot": { - "version": "1.0.82-0", - "integrity": "sha512-fSZVNAzFFYaS6btYD0+cKF7SrrtOklhpkPs/cIMZY7Fgxoa6rfZrlTWXlQNgNIdwLp51XxwTfxnZO+D9+MQ5yg==", + "version": "1.0.82", + "integrity": "sha512-+mDIwBO3dCpL3k2rVLc4+1tFzqcVBTJNA/0co+okEpmCgcHjaz91Cqq7++pJKN5yJQuGC6bKangm7PSoheI1Xw==", "dev": true, "license": "SEE LICENSE IN LICENSE.md", "dependencies": { @@ -483,19 +483,19 @@ "copilot": "npm-loader.js" }, "optionalDependencies": { - "@github/copilot-darwin-arm64": "1.0.82-0", - "@github/copilot-darwin-x64": "1.0.82-0", - "@github/copilot-linux-arm64": "1.0.82-0", - "@github/copilot-linux-x64": "1.0.82-0", - "@github/copilot-linuxmusl-arm64": "1.0.82-0", - "@github/copilot-linuxmusl-x64": "1.0.82-0", - "@github/copilot-win32-arm64": "1.0.82-0", - "@github/copilot-win32-x64": "1.0.82-0" + "@github/copilot-darwin-arm64": "1.0.82", + "@github/copilot-darwin-x64": "1.0.82", + "@github/copilot-linux-arm64": "1.0.82", + "@github/copilot-linux-x64": "1.0.82", + "@github/copilot-linuxmusl-arm64": "1.0.82", + "@github/copilot-linuxmusl-x64": "1.0.82", + "@github/copilot-win32-arm64": "1.0.82", + "@github/copilot-win32-x64": "1.0.82" } }, "node_modules/@github/copilot-darwin-arm64": { - "version": "1.0.82-0", - "integrity": "sha512-TzBYfyvxcw3z9Mu7U8TsFo/Nq7m5XS6ahT71aPL+gx/YId0kmenI27b9daXsK6LA1D0gsFGwNBDKINddqntt1g==", + "version": "1.0.82", + "integrity": "sha512-UpVSFA0COmlIakAr7/6WJqJlabtKgY8y5en6La3IxGxXsLlbkGoeevSqyfXvqJyHxaGn0YGpOC1oEPL379JfCw==", "cpu": [ "arm64" ], @@ -510,8 +510,8 @@ } }, "node_modules/@github/copilot-darwin-x64": { - "version": "1.0.82-0", - "integrity": "sha512-Lm/U5Q8kN8yEeBTWKTfIxXAgXaT6zqdBzAAO7lA4DWHZ92AEeZZiVTs6jEWsQ2aWB2uMs6zbn0vi6t+/jIqCGw==", + "version": "1.0.82", + "integrity": "sha512-wMKbxK8fpKsbATjn9dE31Pae67H1xu6dmaCuyXpXjXLsNPVjeLFszJZ30MAOwxRWz4dmA8VvEJZmLgJekojo2Q==", "cpu": [ "x64" ], @@ -526,8 +526,8 @@ } }, "node_modules/@github/copilot-linux-arm64": { - "version": "1.0.82-0", - "integrity": "sha512-YERVMC1Q4p6l6KQHL5rVOI52rWbvgp9IwyzUBaVSGrfFuqu5BEvZ9bgHPsxTYi3Npkt5KVOXEyPMU5rAY09qQQ==", + "version": "1.0.82", + "integrity": "sha512-YDQmh0F+GzWORPmNNxQdcjHcXHxcy08dhMhbAhu4cqtMeA2sZWQqhfk9mJhHppMm9U0uR7h0KAB6nsgw/8Bsuw==", "cpu": [ "arm64" ], @@ -542,8 +542,8 @@ } }, "node_modules/@github/copilot-linux-x64": { - "version": "1.0.82-0", - "integrity": "sha512-z2hxMVjqt4+xDRFTZv3/0K3X+aqcJhd6zPO2JxCpOVTh5CNZFaWk+XIa2iXAPWxFqdKJsQ4muXMl3zInaAOkRw==", + "version": "1.0.82", + "integrity": "sha512-vqmG9665ktgLHAkGKMjp4uVMdHqLxi8uS9zL+g2pMwZUPoM7JxkaSfOoeyYr99caF7J6VIJTWv4LdRNQyG5jrQ==", "cpu": [ "x64" ], @@ -558,8 +558,8 @@ } }, "node_modules/@github/copilot-linuxmusl-arm64": { - "version": "1.0.82-0", - "integrity": "sha512-EcUCv2PKhBzCCvpTaS511VYTDWyhudyIRPvBpc9gFNO3hjlgiNDusf4k9vP6+E3/lHenwGYzMsyRHcYIOy4vcQ==", + "version": "1.0.82", + "integrity": "sha512-14dWfa4rBME55bKmF+Z8V+WzZNgPQZKFFBQX+EOiAgLVV/arQCnQ1m7wwa5oXjQeCIgkS/L9J8uM8jNm6cZbOA==", "cpu": [ "arm64" ], @@ -574,8 +574,8 @@ } }, "node_modules/@github/copilot-linuxmusl-x64": { - "version": "1.0.82-0", - "integrity": "sha512-1fKVjUiZ1tdb0/d/re90EpFGXhlIPfjENp2Wo/2Kj592dWO3+IwM2qV/AOdMQ4pacW5iYHII7nibx1/EYq3LGQ==", + "version": "1.0.82", + "integrity": "sha512-Mauqa2TBjtB8W/1KXF/jJ4MbtwnLvoEQNHYoSyoX+s+nIpttmixmYBJDwKV89ZlQRdjOCuxOgraRLQ9hjjXvNQ==", "cpu": [ "x64" ], @@ -590,8 +590,8 @@ } }, "node_modules/@github/copilot-win32-arm64": { - "version": "1.0.82-0", - "integrity": "sha512-H341wuxQHhwe/yLmORHzwC3DGzFZgGzh+TpwfyK2zjeYVbwDZ3Bax8M+9CzIED9jIBdEfAmfGzxKUHFugpXTtQ==", + "version": "1.0.82", + "integrity": "sha512-PaW9s0GTgM+svtDkB583dZRrhLrO4o10y2ozMmQ0Hi5eB11C24UdC5U81nFlvKPED7+GlnBJIFQ+oHaxHrnp3A==", "cpu": [ "arm64" ], @@ -606,8 +606,8 @@ } }, "node_modules/@github/copilot-win32-x64": { - "version": "1.0.82-0", - "integrity": "sha512-f1ba3gG8NaoYWFHtHaHcLN4It7mclkWdCOXvwFPqPEwqCEIx/+Zh6VHiOeIcNWRS0elRP6QYDCKaTDy1TW27uQ==", + "version": "1.0.82", + "integrity": "sha512-m4iSROOMEPp1yRIQQwbnO0CDfwB4syESyHoeSLLFuynMR4/ApghAdyBrBQQcTKGsUu3R5rOE64RYlVmyKmuA9A==", "cpu": [ "x64" ], diff --git a/test/harness/package.json b/test/harness/package.json index cceca0b959..c7e7647ac3 100644 --- a/test/harness/package.json +++ b/test/harness/package.json @@ -14,7 +14,7 @@ "node": "^20.19.0 || >=22.12.0" }, "devDependencies": { - "@github/copilot": "^1.0.82-0", + "@github/copilot": "^1.0.82", "@modelcontextprotocol/sdk": "^1.26.0", "@types/node": "^25.3.3", "@types/node-forge": "^1.3.14",