diff --git a/.github/badges/jacoco-generated.svg b/.github/badges/jacoco-generated.svg deleted file mode 100644 index bd7223a65f..0000000000 --- a/.github/badges/jacoco-generated.svg +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - coverage generated - coverage generated - 72.7% - 72.7% - - diff --git a/.github/badges/jacoco-handwritten.svg b/.github/badges/jacoco-handwritten.svg deleted file mode 100644 index bfb6196f68..0000000000 --- a/.github/badges/jacoco-handwritten.svg +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - coverage handwritten - coverage handwritten - 78.4% - 78.4% - - diff --git a/.github/scripts/generate-java-coverage-badge.sh b/.github/scripts/generate-java-coverage-badge.sh deleted file mode 100755 index 3c68d830d5..0000000000 --- a/.github/scripts/generate-java-coverage-badge.sh +++ /dev/null @@ -1,104 +0,0 @@ -#!/usr/bin/env bash -# Generates SVG coverage badges from a JaCoCo CSV report. -# -# Usage: generate-coverage-badge.sh [jacoco.csv] [output-dir] -# jacoco.csv - Path to JaCoCo CSV report (default: target/site/jacoco-coverage/jacoco.csv) -# output-dir - Directory for the badge SVG (default: .github/badges) -set -euo pipefail - -CSV="${1:-target/site/jacoco-coverage/jacoco.csv}" -BADGES_DIR="${2:-.github/badges}" -GENERATED_PREFIX="com.github.copilot.generated" - -if [ ! -f "$CSV" ]; then - echo "⚠️ No JaCoCo CSV report found at $CSV" - exit 0 -fi - -calc_totals() { - local scope=$1 - awk -F',' -v scope="$scope" -v generated_prefix="$GENERATED_PREFIX" ' - NR > 1 { - is_generated = index($2, generated_prefix) == 1 - if (scope == "overall" || - (scope == "generated" && is_generated) || - (scope == "handwritten" && !is_generated)) { - missed += $4 - covered += $5 - } - } - END { print missed + 0, covered + 0 } - ' "$CSV" -} - -format_pct() { - local missed=$1 - local covered=$2 - local total=$((missed + covered)) - if [ "$total" -eq 0 ]; then - echo "0" - else - awk "BEGIN { printf \"%.1f\", ($covered / $total) * 100 }" | sed 's/\.0$//' - fi -} - -pick_color() { - local pct=$1 - local color="#e05d44" # red <60 - if awk "BEGIN{exit!($pct>=100)}"; then color="#4c1" # bright green - elif awk "BEGIN{exit!($pct>=90)}"; then color="#97ca00" # green - elif awk "BEGIN{exit!($pct>=80)}"; then color="#a4a61d" # yellow-green - elif awk "BEGIN{exit!($pct>=70)}"; then color="#dfb317" # yellow - elif awk "BEGIN{exit!($pct>=60)}"; then color="#fe7d37" # orange - fi - echo "$color" -} - -generate_badge() { - local label=$1 - local value=$2 - local output=$3 - local pct=${value%\%} - local color - color=$(pick_color "$pct") - local lw=$(( ${#label} * 7 + 12 )) - local vw=$(( ${#value} * 7 + 16 )) - local tw=$((lw + vw)) - - cat > "$output" < - - - - - - - - - - - - ${label} - ${label} - ${value} - ${value} - - -EOF -} - -mkdir -p "$BADGES_DIR" - -read -r handwritten_missed handwritten_covered <<< "$(calc_totals handwritten)" -read -r generated_missed generated_covered <<< "$(calc_totals generated)" - -handwritten_pct=$(format_pct "$handwritten_missed" "$handwritten_covered") -generated_pct=$(format_pct "$generated_missed" "$generated_covered") - -echo "Handwritten coverage: ${handwritten_pct}%" -echo "Generated coverage: ${generated_pct}%" - -generate_badge "coverage handwritten" "${handwritten_pct}%" "${BADGES_DIR}/jacoco-handwritten.svg" -generate_badge "coverage generated" "${generated_pct}%" "${BADGES_DIR}/jacoco-generated.svg" - -echo "Badges generated in ${BADGES_DIR}" diff --git a/.github/workflows/block-remove-before-merge.yml b/.github/workflows/block-remove-before-merge.yml index f9df22ebe9..0b491ea817 100644 --- a/.github/workflows/block-remove-before-merge.yml +++ b/.github/workflows/block-remove-before-merge.yml @@ -11,7 +11,7 @@ permissions: jobs: check-paths: name: "No remove-before-merge directories" - if: github.event_name == 'pull_request' + if: github.event_name == 'pull_request' && github.base_ref == 'main' runs-on: ubuntu-latest steps: - name: Check for remove-before-merge paths in PR diff --git a/.github/workflows/dotnet-sdk-tests.yml b/.github/workflows/dotnet-sdk-tests.yml index d3b2ef162a..dcf559228c 100644 --- a/.github/workflows/dotnet-sdk-tests.yml +++ b/.github/workflows/dotnet-sdk-tests.yml @@ -28,7 +28,7 @@ permissions: jobs: test: - name: ".NET SDK Tests" + name: ".NET SDK Tests (${{ matrix.os }}, ${{ matrix.transport }})" if: github.event.repository.fork == false env: POWERSHELL_UPDATECHECK: Off @@ -36,6 +36,11 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest, macos-latest, windows-latest] + transport: ["default", "inprocess"] + # TODO: Re-enable after fixing in-process sqlite file locking on shutdown on Windows + exclude: + - os: windows-latest + transport: "inprocess" runs-on: ${{ matrix.os }} defaults: run: @@ -80,6 +85,10 @@ jobs: if: runner.os == 'Windows' run: pwsh.exe -Command "Write-Host 'PowerShell ready'" + - name: Select inprocess transport + if: matrix.transport == 'inprocess' + run: echo "COPILOT_SDK_DEFAULT_CONNECTION=inprocess" >> "$GITHUB_ENV" + - name: Run .NET SDK tests env: COPILOT_HMAC_KEY: ${{ secrets.COPILOT_DEVELOPER_CLI_INTEGRATION_HMAC_KEY }} diff --git a/.github/workflows/java-sdk-tests.yml b/.github/workflows/java-sdk-tests.yml index e310fa8ba1..948a986e5a 100644 --- a/.github/workflows/java-sdk-tests.yml +++ b/.github/workflows/java-sdk-tests.yml @@ -31,9 +31,7 @@ on: merge_group: permissions: - contents: write - checks: write - pull-requests: write + contents: read jobs: java-sdk: @@ -77,12 +75,19 @@ jobs: - name: Run spotless check if: matrix.test-jdk == '25' run: | - mvn spotless:check - if [ $? -ne 0 ]; then - echo "❌ spotless:check failed. Please run 'mvn spotless:apply' in java" - exit 1 - fi - echo "✅ spotless:check passed" + max_attempts=3 + for ((attempt=1; attempt<=max_attempts; attempt++)); do + if mvn spotless:check; then + echo "✅ spotless:check passed" + exit 0 + fi + if [ "$attempt" -lt "$max_attempts" ]; then + echo "⚠️ spotless:check failed (attempt $attempt/$max_attempts), retrying in 10s..." + sleep 10 + fi + done + echo "❌ spotless:check failed after $max_attempts attempts. Please run 'mvn spotless:apply' in java/" + exit 1 - name: Run Java SDK tests (JDK 25) if: matrix.test-jdk == '25' @@ -117,22 +122,6 @@ jobs: java/target/surefire-reports-isolated/ retention-days: 1 - - name: Generate JaCoCo badge - if: success() && github.ref == 'refs/heads/main' && matrix.test-jdk == '25' - working-directory: . - run: bash .github/scripts/generate-java-coverage-badge.sh java/target/site/jacoco-coverage/jacoco.csv .github/badges - - - name: Create PR for JaCoCo badge update - if: success() && github.ref == 'refs/heads/main' && matrix.test-jdk == '25' - uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v7 - with: - commit-message: "Update Java JaCoCo coverage badge" - title: "Update Java JaCoCo coverage badge" - body: "Automated Java JaCoCo coverage badge update from CI." - branch: auto/update-java-jacoco-badge - add-paths: .github/badges/ - delete-branch: true - - name: Generate Test Report Summary if: always() uses: ./.github/actions/java-test-report diff --git a/CHANGELOG.md b/CHANGELOG.md index 06461a734a..acf014c538 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,82 @@ All notable changes to the Copilot SDK are documented in this file. This changelog is automatically generated by an AI agent when stable releases are published. See [GitHub Releases](https://github.com/github/copilot-sdk/releases) for the full list. +## [java/v1.0.5-01](https://github.com/github/copilot-sdk/releases/tag/java/v1.0.5-01) (2026-07-01) + +### Feature: new session options — citations, agent exclusions, and credit limits + +Three new options are available on `SessionConfig` and `ResumeSessionConfig`. `enableCitations` (experimental) enables native model citations for supported providers; `excludedBuiltInAgents` hides named built-in agents from discovery; and `sessionLimits` sets a per-session AI-credit budget. ([#1865](https://github.com/github/copilot-sdk/pull/1865)) + +```java +SessionConfig config = new SessionConfig() + .setEnableCitations(true) + .setExcludedBuiltInAgents(List.of("copilot")) + .setSessionLimits(new SessionLimitsConfig(100.0)); +``` + +### New contributors + +- @coleflennikenmsft made their first contribution in [#1854](https://github.com/github/copilot-sdk/pull/1854) +- @szabta89 made their first contribution in [#1856](https://github.com/github/copilot-sdk/pull/1856) + +## [v1.0.5](https://github.com/github/copilot-sdk/releases/tag/v1.0.5) (2026-07-01) + +### Feature: MCP OAuth host token handlers + +SDK applications can now handle OAuth challenges from MCP servers that require host-provided authentication. Register an `onMcpAuthRequest` callback on the session config and the SDK will invoke it whenever an MCP server responds with a `401 WWW-Authenticate` challenge; return an access token (or cancel the request). Supports initial auth, refresh, reauth, and upscope flows across all SDKs. ([#1669](https://github.com/github/copilot-sdk/pull/1669)) + +```ts +const session = await client.createSession({ + onMcpAuthRequest: async (request) => ({ + accessToken: await myIdentityProvider.getToken(request.serverUrl), + }), +}); +``` + +```cs +var session = await client.CreateSessionAsync(new SessionConfig +{ + OnMcpAuthRequest = async ctx => + McpAuthResult.FromToken(new McpAuthToken + { + AccessToken = await myIdentityProvider.GetTokenAsync(ctx.ServerUrl) + }), +}); +``` + +### Feature: session options for citations, excluded agents, and spending limits + +Three additional session configuration options are now available across all SDKs. ([#1865](https://github.com/github/copilot-sdk/pull/1865)) + +```ts +const session = await client.createSession({ + enableCitations: true, + excludedBuiltinAgents: ["github-search"], + sessionLimits: { maxAiCredits: 10 }, +}); +``` + +```cs +var session = await client.CreateSessionAsync(new SessionConfig +{ + EnableCitations = true, + ExcludedBuiltInAgents = ["github-search"], + SessionLimits = new SessionLimitsConfig { MaxAiCredits = 10 }, +}); +``` + +### Other changes + +- improvement: **[All SDKs]** rename BYOK callback field `getBearerToken` → `bearerTokenProvider`; add `sessionId` to `ProviderTokenArgs` for per-session token scoping ([#1796](https://github.com/github/copilot-sdk/pull/1796)) +- bugfix: **[Node]** fix MCP OAuth `registerInterest` sent before `session.resume`, causing "Session not found" errors when resuming a session with `onMcpAuthRequest` ([#1861](https://github.com/github/copilot-sdk/pull/1861)) +- feature: **[Java]** `@CopilotTool` and `@CopilotToolParam` annotations with compile-time annotation processor for ergonomic tool registration via `ToolDefinition.fromObject()` ([#1792](https://github.com/github/copilot-sdk/pull/1792), [#1838](https://github.com/github/copilot-sdk/pull/1838)) +- feature: **[Java]** `ToolInvocation` parameter injection in `@CopilotTool` methods for accessing session context without exposing it to the LLM schema ([#1832](https://github.com/github/copilot-sdk/pull/1832)) +- feature: **[Rust]** add 9 GitHub-anchored variants to `Attachment` enum (`GitHubCommit`, `GitHubRelease`, `GitHubActionsJob`, `GitHubRepository`, `GitHubFileDiff`, `GitHubTreeComparison`, `GitHubUrl`, `GitHubFile`, `GitHubSnippet`) ([#1823](https://github.com/github/copilot-sdk/pull/1823)) + +### New contributors + +- @pallaviraiturkar0 made their first contribution in [#1823](https://github.com/github/copilot-sdk/pull/1823) +- @roji made their first contribution in [#1827](https://github.com/github/copilot-sdk/pull/1827) ## [java/v1.0.4](https://github.com/github/copilot-sdk/releases/tag/java/v1.0.4) (2026-06-25) ### Feature: HTTP request callback support diff --git a/dotnet/src/Client.cs b/dotnet/src/Client.cs index 71c7c8795f..94b0921995 100644 --- a/dotnet/src/Client.cs +++ b/dotnet/src/Client.cs @@ -8,6 +8,7 @@ using Microsoft.Extensions.Logging.Abstractions; using System.Collections.Concurrent; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Net.Sockets; using System.Runtime.ExceptionServices; @@ -78,6 +79,7 @@ public sealed partial class CopilotClient : IDisposable, IAsyncDisposable private readonly List _lifecycleHandlers = []; private Task? _connectionTask; + private FfiRuntimeHost? _ffiHost; private bool _disposed; private int? _actualPort; private int? _negotiatedProtocolVersion; @@ -134,13 +136,16 @@ private sealed record LifecycleSubscription(Type EventType, Action + /// Validates environment-variable options against the resolved transport. + /// Per-client environment is only representable for child-process transports + /// (each client owns its own OS process). The in-process (FFI) transport + /// loads the native runtime into the shared host process, whose single + /// environment block cannot carry per-client values, so environment and + /// telemetry options that lower to environment variables are rejected there. + /// + private static void ValidateEnvironmentOptions(CopilotClientOptions options, RuntimeConnection connection) + { + if (connection is InProcessRuntimeConnection) + { + if (options.Environment is not null) + { + throw new ArgumentException( + $"{nameof(CopilotClientOptions)}.{nameof(CopilotClientOptions.Environment)} is not supported with " + + $"{nameof(RuntimeConnection)}.{nameof(RuntimeConnection.ForInProcess)}(): the in-process transport " + + "loads the native runtime into the shared host process, whose single environment block cannot carry " + + "per-client values. Set the variables on the host process environment instead.", + nameof(options)); + } + + if (options.Telemetry is not null) + { + throw new ArgumentException( + $"{nameof(CopilotClientOptions)}.{nameof(CopilotClientOptions.Telemetry)} is not supported with " + + $"{nameof(RuntimeConnection)}.{nameof(RuntimeConnection.ForInProcess)}(): telemetry configuration is " + + "lowered to environment variables read by native runtime code running in the shared host process, so " + + "per-client telemetry cannot be honored in-process. Configure telemetry via the host process " + + "environment, or use a child-process transport.", + nameof(options)); + } + + return; + } + + if (connection is ChildProcessRuntimeConnection { Environment: not null } && options.Environment is not null) + { + throw new ArgumentException( + $"Set environment variables via either {nameof(CopilotClientOptions)}.{nameof(CopilotClientOptions.Environment)} " + + $"or {nameof(ChildProcessRuntimeConnection)}.{nameof(ChildProcessRuntimeConnection.Environment)}, not both. " + + $"Prefer {nameof(ChildProcessRuntimeConnection)}.{nameof(ChildProcessRuntimeConnection.Environment)} for " + + "child-process transports.", + nameof(options)); + } + } + + /// + /// Environment variable that overrides the transport used when the caller does not + /// specify . Accepts "inprocess" + /// or "stdio" (case-insensitive); unset preserves the default stdio transport. + /// Any other value is an error. Ignored when a is set + /// explicitly. + /// + internal const string DefaultConnectionEnvVar = "COPILOT_SDK_DEFAULT_CONNECTION"; + + /// + /// Resolves the default for the no-Connection case, + /// honoring . + /// + private static RuntimeConnection ResolveDefaultConnection(CopilotClientOptions options) + { + var value = options.Environment is not null + && options.Environment.TryGetValue(DefaultConnectionEnvVar, out var fromOptions) + ? fromOptions + : Environment.GetEnvironmentVariable(DefaultConnectionEnvVar); + + if (string.IsNullOrEmpty(value) || string.Equals(value, "stdio", StringComparison.OrdinalIgnoreCase)) + { + return RuntimeConnection.ForStdio(); + } + if (string.Equals(value, "inprocess", StringComparison.OrdinalIgnoreCase)) + { + return RuntimeConnection.ForInProcess(); + } + throw new ArgumentException( + $"Invalid {DefaultConnectionEnvVar} value '{value}'. Expected 'inprocess', 'stdio', or unset."); + } + /// /// Parses a runtime URL into a URI with host and port. /// @@ -250,7 +336,23 @@ async Task StartCoreAsync(CancellationToken ct) try { - if (_connection is UriRuntimeConnection) + if (_connection is InProcessRuntimeConnection) + { + // In-process FFI hosting: load the Rust cdylib and let it spawn + // the CLI worker, instead of the SDK launching a CLI child process. + // The worker reads its configuration (telemetry export, etc.) from + // the environment passed here, so apply the same telemetry-derived + // vars the child-process path sets on its startInfo.Environment. + var ffiEnvironment = _options.Environment?.ToDictionary(kvp => kvp.Key, kvp => (string?)kvp.Value) + ?? new Dictionary(); + ApplyTelemetryEnvironment(ffiEnvironment, _options.Telemetry); + var resolvedFfiEnvironment = ffiEnvironment.ToDictionary(kvp => kvp.Key, kvp => kvp.Value!); + var ffiHost = FfiRuntimeHost.Create(ResolveCliPathForFfi(), GetNapiPrebuildsFolderOrThrow(), resolvedFfiEnvironment, _logger); + _ffiHost = ffiHost; + await ffiHost.StartAsync(ct); + connection = await ConnectToServerAsync(null, null, null, null, ct, ffiHost); + } + else if (_connection is UriRuntimeConnection) { // External runtime _actualPort = _optionsPort; @@ -437,7 +539,7 @@ private async Task CleanupConnectionAsync(List? errors, bool graceful private async Task CleanupConnectionAsync(Connection ctx, List? errors, bool gracefulRuntimeShutdown) { - if (gracefulRuntimeShutdown && ctx.CliProcess is not null) + if (gracefulRuntimeShutdown && (ctx.CliProcess is not null || ctx.FfiHost is not null)) { var runtimeShutdownTimestamp = Stopwatch.GetTimestamp(); try @@ -477,6 +579,13 @@ or IOException { await CleanupCliProcessAsync(childProcess, ctx.StderrPump, errors, _logger); } + + if (ctx.FfiHost is { } ffiHost) + { + try { ffiHost.Dispose(); } + catch (Exception ex) { AddCleanupError(errors, ex, _logger); } + _ffiHost = null; + } } private static async Task CleanupCliProcessAsync(Process childProcess, ProcessStderrPump? stderrPump, List? errors, ILogger? logger) @@ -1037,7 +1146,8 @@ public async Task CreateSessionAsync(SessionConfig config, Cance Providers: config.Providers, Models: config.Models, ToolFilterPrecedence: toolFilter.ToolFilterPrecedence, - ExpAssignments: config.ExpAssignments); + ExpAssignments: config.ExpAssignments, + EnableGitHubTelemetryForwarding: _options.OnGitHubTelemetry != null ? true : null); var rpcTimestamp = Stopwatch.GetTimestamp(); @@ -1246,7 +1356,8 @@ public async Task ResumeSessionAsync(string sessionId, ResumeSes Providers: config.Providers, Models: config.Models, ToolFilterPrecedence: toolFilter.ToolFilterPrecedence, - ExpAssignments: config.ExpAssignments); + ExpAssignments: config.ExpAssignments, + EnableGitHubTelemetryForwarding: _options.OnGitHubTelemetry != null ? true : null); var rpcTimestamp = Stopwatch.GetTimestamp(); var response = await InvokeRpcAsync( @@ -1724,21 +1835,24 @@ await Rpc.SessionFs.SetProviderAsync( } /// - /// Builds the client-global RPC handler bag at construction time. Currently - /// only the LLM inference provider adapter is registered; returns null when no + /// 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. /// private ClientGlobalApiHandlers? BuildClientGlobalApis() { var handler = _options.RequestHandler; - if (handler is null) + var onGitHubTelemetry = _options.OnGitHubTelemetry; + if (handler is null && onGitHubTelemetry is null) { return null; } return new ClientGlobalApiHandlers { - LlmInference = new LlmInferenceAdapter(handler, () => _serverRpc), + LlmInference = handler is null ? null : new LlmInferenceAdapter(handler, () => _serverRpc), + GitHubTelemetry = onGitHubTelemetry is null ? null : new GitHubTelemetryAdapter(onGitHubTelemetry, _logger), }; } @@ -1789,14 +1903,26 @@ private async Task VerifyProtocolVersionAsync(Connection connection, Cancellatio int? serverVersion; try { - var token = _connection switch - { - TcpRuntimeConnection tcp => tcp.ConnectionToken, - UriRuntimeConnection uri => uri.ConnectionToken, - _ => null, - }; + var token = _ffiHost is not null + ? null // FFI hosting is an ungated in-process connection; no token. + : _connection switch + { + TcpRuntimeConnection tcp => tcp.ConnectionToken, + UriRuntimeConnection uri => uri.ConnectionToken, + _ => null, + }; var connectResponse = await InvokeRpcAsync( - connection.Rpc, "connect", [new ConnectRequest { Token = token }], connection.StderrBuffer, cancellationToken); + connection.Rpc, + "connect", + [new ConnectHandshakeRequest( + token, + // Opt in to GitHub telemetry forwarding at the connection level when a + // handler is registered (mirrors the runtime, which reads this flag on the + // `connect` handshake so the first session's un-replayable `session.start` + // event is forwarded). Also sent on session.create/resume for older CLIs. + _options.OnGitHubTelemetry != null ? true : null)], + connection.StderrBuffer, + cancellationToken); serverVersion = (int)connectResponse.ProtocolVersion; } catch (IOException ex) when (ex.InnerException is RemoteRpcException remoteEx && IsUnsupportedConnectMethod(remoteEx)) @@ -1839,6 +1965,25 @@ private static bool IsUnsupportedConnectMethod(RemoteRpcException ex) || string.Equals(ex.Message, "Unhandled method connect", StringComparison.Ordinal); } + // Applies the telemetry-derived environment variables the runtime reads to + // enable OTLP export. Shared by the stdio/tcp child-process path and the + // in-process FFI path so telemetry behaves identically across transports. + private static void ApplyTelemetryEnvironment(IDictionary environment, TelemetryConfig? telemetry) + { + if (telemetry is null) + { + return; + } + + environment["COPILOT_OTEL_ENABLED"] = "true"; + if (telemetry.OtlpEndpoint is not null) environment["OTEL_EXPORTER_OTLP_ENDPOINT"] = telemetry.OtlpEndpoint; + if (telemetry.OtlpProtocol is not null) environment["OTEL_EXPORTER_OTLP_PROTOCOL"] = telemetry.OtlpProtocol; + if (telemetry.FilePath is not null) environment["COPILOT_OTEL_FILE_EXPORTER_PATH"] = telemetry.FilePath; + if (telemetry.ExporterType is not null) environment["COPILOT_OTEL_EXPORTER_TYPE"] = telemetry.ExporterType; + if (telemetry.SourceName is not null) environment["COPILOT_OTEL_SOURCE_NAME"] = telemetry.SourceName; + if (telemetry.CaptureContent is { } capture) environment["OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT"] = capture ? "true" : "false"; + } + private async Task<(Process Process, int? DetectedLocalhostTcpPort, ProcessStderrPump StderrPump)> StartCliServerAsync(CancellationToken cancellationToken) { var options = _options; @@ -1847,9 +1992,12 @@ private static bool IsUnsupportedConnectMethod(RemoteRpcException ex) var tcpConnection = _connection as TcpRuntimeConnection; var useStdio = _connection is StdioRuntimeConnection; - // Use explicit path, COPILOT_CLI_PATH env var (from options.Environment or process env), or bundled runtime - no PATH fallback - var envCliPath = options.Environment is not null && options.Environment.TryGetValue("COPILOT_CLI_PATH", out var envValue) ? envValue - : System.Environment.GetEnvironmentVariable("COPILOT_CLI_PATH"); + // Use explicit path, COPILOT_CLI_PATH env var (from the connection's + // Environment, options.Environment, or process env), or bundled runtime - no PATH fallback + var envCliPath = + (childProcessConnection.Environment is not null && childProcessConnection.Environment.TryGetValue("COPILOT_CLI_PATH", out var connEnvValue) ? connEnvValue : null) + ?? (options.Environment is not null && options.Environment.TryGetValue("COPILOT_CLI_PATH", out var envValue) ? envValue : null) + ?? System.Environment.GetEnvironmentVariable("COPILOT_CLI_PATH"); var cliPath = childProcessConnection.Path ?? envCliPath ?? GetBundledCliPath(out var searchedPath) @@ -1916,10 +2064,11 @@ private static bool IsUnsupportedConnectMethod(RemoteRpcException ex) CreateNoWindow = true }; - if (options.Environment != null) + var childEnvironment = options.Environment ?? childProcessConnection.Environment; + if (childEnvironment != null) { startInfo.Environment.Clear(); - foreach (var (key, value) in options.Environment) + foreach (var (key, value) in childEnvironment) { startInfo.Environment[key] = value; } @@ -1953,16 +2102,7 @@ private static bool IsUnsupportedConnectMethod(RemoteRpcException ex) } // Set telemetry environment variables if configured - if (options.Telemetry is { } telemetry) - { - startInfo.Environment["COPILOT_OTEL_ENABLED"] = "true"; - if (telemetry.OtlpEndpoint is not null) startInfo.Environment["OTEL_EXPORTER_OTLP_ENDPOINT"] = telemetry.OtlpEndpoint; - if (telemetry.OtlpProtocol is not null) startInfo.Environment["OTEL_EXPORTER_OTLP_PROTOCOL"] = telemetry.OtlpProtocol; - if (telemetry.FilePath is not null) startInfo.Environment["COPILOT_OTEL_FILE_EXPORTER_PATH"] = telemetry.FilePath; - if (telemetry.ExporterType is not null) startInfo.Environment["COPILOT_OTEL_EXPORTER_TYPE"] = telemetry.ExporterType; - if (telemetry.SourceName is not null) startInfo.Environment["COPILOT_OTEL_SOURCE_NAME"] = telemetry.SourceName; - if (telemetry.CaptureContent is { } capture) startInfo.Environment["OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT"] = capture ? "true" : "false"; - } + ApplyTelemetryEnvironment(startInfo.Environment, options.Telemetry); var cliProcess = new Process { StartInfo = startInfo }; try @@ -2071,6 +2211,57 @@ private static bool IsUnsupportedConnectMethod(RemoteRpcException ex) return arch != null ? $"{os}-{arch}" : null; } + private string ResolveCliPathForFfi() + { + var envCliPath = _options.Environment is not null && _options.Environment.TryGetValue("COPILOT_CLI_PATH", out var envValue) + ? envValue + : System.Environment.GetEnvironmentVariable("COPILOT_CLI_PATH"); + if (!string.IsNullOrEmpty(envCliPath)) + { + return envCliPath; + } + + // Fall back to the bundled single-file CLI the same way stdio discovers it. + // It embeds its own Node and is spawned directly as `copilot --embedded-host`, + // with the sibling cdylib loaded in-process (FfiRuntimeHost.Create prefers the + // flat `libcopilot_runtime.so`/`copilot_runtime.dll` next to the CLI, falling + // back to the dev `prebuilds//runtime.node` layout). + var bundled = GetBundledCliPath(out var searchedPath); + return bundled + ?? throw new InvalidOperationException( + "In-process FFI hosting requires the Copilot CLI. Set the COPILOT_CLI_PATH " + + $"environment variable, or ensure the bundled CLI is present (looked in '{searchedPath}')."); + } + + /// + /// Returns the napi-rs prebuilds folder name for the current host — the + /// <node-platform>-<arch> convention (e.g. win32-x64, + /// darwin-arm64, linux-x64) under which the runtime ships + /// prebuilds/<folder>/runtime.node. This differs from the .NET RID + /// (win-x64/osx-x64) for Windows and macOS. + /// + private static string? GetNapiPrebuildsFolder() + { + string platform; + if (OperatingSystem.IsWindows()) platform = "win32"; + else if (OperatingSystem.IsLinux()) platform = "linux"; + else if (OperatingSystem.IsMacOS()) platform = "darwin"; + else return null; + + var arch = System.Runtime.InteropServices.RuntimeInformation.OSArchitecture switch + { + System.Runtime.InteropServices.Architecture.X64 => "x64", + System.Runtime.InteropServices.Architecture.Arm64 => "arm64", + _ => null, + }; + + return arch != null ? $"{platform}-{arch}" : null; + } + + private static string GetNapiPrebuildsFolderOrThrow() => + GetNapiPrebuildsFolder() + ?? throw new InvalidOperationException("Could not determine a napi-rs prebuilds folder for FFI hosting."); + private static (string FileName, IEnumerable Args) ResolveCliCommand(string cliPath, IEnumerable args) { var isJsFile = cliPath.EndsWith(".js", StringComparison.OrdinalIgnoreCase); @@ -2083,7 +2274,7 @@ private static (string FileName, IEnumerable Args) ResolveCliCommand(str return (cliPath, args); } - private async Task ConnectToServerAsync(Process? cliProcess, string? tcpHost, int? tcpPort, ProcessStderrPump? stderrPump, CancellationToken cancellationToken) + private async Task ConnectToServerAsync(Process? cliProcess, string? tcpHost, int? tcpPort, ProcessStderrPump? stderrPump, CancellationToken cancellationToken, FfiRuntimeHost? ffiHost = null) { var setupTimestamp = Stopwatch.GetTimestamp(); NetworkStream? networkStream = null; @@ -2093,7 +2284,12 @@ private async Task ConnectToServerAsync(Process? cliProcess, string? { Stream inputStream, outputStream; - if (_connection is StdioRuntimeConnection) + if (ffiHost is not null) + { + inputStream = ffiHost.ReceiveStream; + outputStream = ffiHost.SendStream; + } + else if (_connection is StdioRuntimeConnection) { if (cliProcess == null) { @@ -2159,7 +2355,7 @@ private async Task ConnectToServerAsync(Process? cliProcess, string? "CopilotClient.ConnectToServerAsync transport setup complete. Elapsed={Elapsed}", setupTimestamp); - var connection = new Connection(rpc, cliProcess, networkStream, stderrPump); + var connection = new Connection(rpc, cliProcess, networkStream, stderrPump, ffiHost); _serverRpc = connection.Server; return connection; @@ -2248,13 +2444,15 @@ public void Dispose() /// /// A representing the asynchronous dispose operation. /// - /// This method calls to immediately release all resources. + /// This method calls to gracefully shut down the runtime and + /// release all resources. Use for an immediate hard stop + /// that skips graceful runtime shutdown. /// public async ValueTask DisposeAsync() { if (_disposed) return; _disposed = true; - await ForceStopAsync(); + await StopAsync(); } private class RpcHandler(CopilotClient client) @@ -2362,7 +2560,8 @@ private class Connection( JsonRpc rpc, Process? cliProcess, // Set if we created the child process NetworkStream? networkStream, // Set if using TCP - ProcessStderrPump? stderrPump = null) // Captures stderr for error messages + ProcessStderrPump? stderrPump = null, // Captures stderr for error messages + FfiRuntimeHost? ffiHost = null) // Set if using in-process FFI hosting { public Process? CliProcess => cliProcess; public JsonRpc Rpc => rpc; @@ -2370,6 +2569,7 @@ private class Connection( public NetworkStream? NetworkStream => networkStream; public ProcessStderrPump? StderrPump => stderrPump; public StringBuilder? StderrBuffer => stderrPump?.Buffer; + public FfiRuntimeHost? FfiHost => ffiHost; } private sealed class ProcessStderrPump @@ -2495,7 +2695,8 @@ internal record CreateSessionRequest( IList? Providers = null, IList? Models = null, OptionsUpdateToolFilterPrecedence? ToolFilterPrecedence = null, - [property: JsonPropertyName("expAssignments")] JsonElement? ExpAssignments = null); + [property: JsonPropertyName("expAssignments")] JsonElement? ExpAssignments = null, + bool? EnableGitHubTelemetryForwarding = null); #pragma warning restore GHCP001 internal record ToolDefinition( @@ -2594,7 +2795,8 @@ internal record ResumeSessionRequest( IList? Providers = null, IList? Models = null, OptionsUpdateToolFilterPrecedence? ToolFilterPrecedence = null, - [property: JsonPropertyName("expAssignments")] JsonElement? ExpAssignments = null); + [property: JsonPropertyName("expAssignments")] JsonElement? ExpAssignments = null, + bool? EnableGitHubTelemetryForwarding = null); #pragma warning restore GHCP001 internal record ResumeSessionResponse( @@ -2631,6 +2833,10 @@ internal record GetSessionMetadataRequest( internal record GetSessionMetadataResponse( SessionMetadata? Session); + internal record ConnectHandshakeRequest( + string? Token, + [property: JsonPropertyName("enableGitHubTelemetryForwarding")] bool? EnableGitHubTelemetryForwarding = null); + internal record SetForegroundSessionRequest( string SessionId); @@ -2665,6 +2871,7 @@ internal record HooksInvokeResponse( [JsonSerializable(typeof(ListSessionsResponse))] [JsonSerializable(typeof(GetSessionMetadataRequest))] [JsonSerializable(typeof(GetSessionMetadataResponse))] + [JsonSerializable(typeof(ConnectHandshakeRequest))] [JsonSerializable(typeof(McpOAuthTokenStorageMode))] [JsonSerializable(typeof(EmbeddingCacheStorageMode))] [JsonSerializable(typeof(ModelCapabilitiesOverride))] @@ -2713,3 +2920,28 @@ public sealed class ToolResultAIContent(ToolResultObject toolResult) : AIContent /// public ToolResultObject Result => toolResult; } + +/// +/// Bridges the generated client-global handler to +/// the public OnGitHubTelemetry callback, forwarding the generated +/// payload unchanged. +/// +[Experimental(Diagnostics.Experimental)] +internal sealed class GitHubTelemetryAdapter(Func callback, ILogger logger) : Rpc.IGitHubTelemetryHandler +{ + private readonly Func _callback = callback ?? throw new ArgumentNullException(nameof(callback)); + private readonly ILogger _logger = logger ?? NullLogger.Instance; + + public async Task EventAsync(Rpc.GitHubTelemetryNotification request, CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(request); + try + { + await _callback(request).ConfigureAwait(false); + } + catch (Exception ex) + { + _logger.LogWarning(ex, "Error handling gitHubTelemetry.event notification"); + } + } +} diff --git a/dotnet/src/FfiRuntimeHost.cs b/dotnet/src/FfiRuntimeHost.cs new file mode 100644 index 0000000000..210819de67 --- /dev/null +++ b/dotnet/src/FfiRuntimeHost.cs @@ -0,0 +1,674 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +using Microsoft.Extensions.Logging; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Text.Json; +using System.Threading.Channels; + +namespace GitHub.Copilot; + +/// +/// Hosts the Copilot runtime in-process by loading the Rust cdylib (runtime.node) +/// and speaking JSON-RPC over its C ABI (FFI) instead of spawning a CLI child process +/// and communicating over stdio/TCP. +/// +/// +/// The Rust host_start export spawns the residual TypeScript worker itself — +/// typically the packaged single-file CLI (copilot --embedded-host, which embeds +/// its own Node) or, for dev, node dist-cli/index.js --embedded-host — so the .NET +/// host never launches Node directly. JSON-RPC frames are pumped across the ABI: writes go +/// to connection_write; inbound frames arrive on a native callback that feeds +/// . +/// +/// The native interop layer has two implementations selected by target framework. On +/// modern .NET it uses source-generated LibraryImport P/Invoke with an +/// UnmanagedCallersOnly function-pointer callback, which is trim- and +/// NativeAOT-compatible. On netstandard2.0 (which has neither LibraryImport +/// nor NativeLibrary) it falls back to classic delegate-based P/Invoke over a +/// hand-rolled dlopen/LoadLibrary loader. Because the library lives at a +/// runtime-resolved absolute path, the modern path maps the logical +/// via a resolver and the legacy path loads the absolute path +/// directly. +/// +/// +internal sealed partial class FfiRuntimeHost : IDisposable +{ + /// Logical name the native interop layer binds the cdylib to. + private const string LibraryName = "copilot_runtime"; + + private readonly ILogger _logger; + private readonly string _cliEntrypoint; + private readonly string _libraryPath; + private readonly IReadOnlyDictionary? _environment; + + private readonly CallbackReceiveStream _receiveStream = new(); + private CallbackSendStream? _sendStream; + + private uint _serverId; + private uint _connectionId; + private bool _disposed; + + private FfiRuntimeHost(string libraryPath, string cliEntrypoint, IReadOnlyDictionary? environment, ILogger logger) + { + _libraryPath = libraryPath; + _cliEntrypoint = cliEntrypoint; + _environment = environment; + _logger = logger; + } + + /// The stream JSON-RPC reads server→client frames from. + public Stream ReceiveStream => _receiveStream; + + /// The stream JSON-RPC writes client→server frames to. + public Stream SendStream => _sendStream + ?? throw new InvalidOperationException("FfiRuntimeHost has not been started."); + + /// + /// Loads the cdylib next to the given CLI entrypoint and prepares the FFI host. + /// The entrypoint is either the packaged single-file CLI binary (e.g. + /// runtimes/<rid>/native/copilot) or, for dev, a .js file (e.g. + /// dist-cli/index.js) launched via node. The cdylib is resolved + /// relative to the entrypoint directory, preferring the flat, natural + /// shared-library name the .NET build emits (e.g. libcopilot_runtime.so) + /// and falling back to the dev tarball layout + /// prebuilds/<prebuildsFolder>/runtime.node, where + /// is the napi-rs + /// <node-platform>-<arch> folder name (e.g. win32-x64). + /// + public static FfiRuntimeHost Create(string cliEntrypoint, string prebuildsFolder, IReadOnlyDictionary? environment, ILogger logger) + { + var fullEntrypoint = Path.GetFullPath(cliEntrypoint); + var distDir = Path.GetDirectoryName(fullEntrypoint) + ?? throw new InvalidOperationException($"Could not determine directory for '{cliEntrypoint}'."); + + // Bundled .NET layout: flat, natural shared-library name next to the CLI. + var flatLibraryPath = Path.Combine(distDir, GetRuntimeLibraryFileName()); + // Dev/tarball layout: dist-cli/prebuilds/-/runtime.node. + var prebuildsLibraryPath = Path.Combine(distDir, "prebuilds", prebuildsFolder, "runtime.node"); + + var libraryPath = File.Exists(flatLibraryPath) ? flatLibraryPath + : File.Exists(prebuildsLibraryPath) ? prebuildsLibraryPath + : throw new InvalidOperationException( + $"FFI runtime library not found. Looked for '{flatLibraryPath}' and '{prebuildsLibraryPath}'."); + + PrepareNativeLibrary(libraryPath); + return new FfiRuntimeHost(libraryPath, fullEntrypoint, environment, logger); + } + + /// + /// The natural platform shared-library file name for the runtime cdylib, as + /// emitted by the .NET build (the .node file renamed to what the Rust cdylib + /// would be called on this OS). + /// + private static string GetRuntimeLibraryFileName() + { + if (OperatingSystem.IsWindows()) return "copilot_runtime.dll"; + if (OperatingSystem.IsMacOS()) return "libcopilot_runtime.dylib"; + return "libcopilot_runtime.so"; + } + + /// + /// Starts the in-process runtime: spawns the CLI worker via the Rust host, + /// waits for readiness, and opens the FFI JSON-RPC connection. + /// + public async Task StartAsync(CancellationToken cancellationToken) + { + // host_start blocks until the worker connects back and signals readiness + // (up to ~30s), and connection_open must run outside any async runtime, so + // perform the blocking FFI handshake on a background thread. + await Task.Run(() => + { + var argvJson = BuildArgvJson(_cliEntrypoint); + var envJson = BuildEnvJson(_environment); + + _serverId = NativeHostStart(argvJson, envJson); + if (_serverId == 0) + { + throw new InvalidOperationException( + $"copilot_runtime_host_start failed (library '{_libraryPath}', entrypoint '{_cliEntrypoint}')."); + } + + _connectionId = NativeOpenConnection(_serverId); + if (_connectionId == 0) + { + DisposeNativeCallback(); + NativeHostShutdown(_serverId); + _serverId = 0; + throw new InvalidOperationException("copilot_runtime_connection_open failed."); + } + + _sendStream = new CallbackSendStream(SendFrame); + }, cancellationToken).ConfigureAwait(false); + + if (_logger.IsEnabled(LogLevel.Debug)) + { + _logger.LogDebug( + "FfiRuntimeHost started. Library={Library}, ServerId={ServerId}, ConnectionId={ConnectionId}", + _libraryPath, _serverId, _connectionId); + } + } + + private static byte[] BuildArgvJson(string cliEntrypoint) + { + // A .js entrypoint (dev / dist-cli) is launched via node; the packaged + // single-file CLI binary embeds its own Node and is invoked directly. + var isJsFile = cliEntrypoint.EndsWith(".js", StringComparison.OrdinalIgnoreCase); + using var stream = new MemoryStream(); + using (var writer = new Utf8JsonWriter(stream)) + { + writer.WriteStartArray(); + if (isJsFile) + { + writer.WriteStringValue("node"); + } + writer.WriteStringValue(cliEntrypoint); + writer.WriteStringValue("--embedded-host"); + writer.WriteEndArray(); + } + return stream.ToArray(); + } + + private static byte[]? BuildEnvJson(IReadOnlyDictionary? environment) + { + if (environment is null || environment.Count == 0) + { + return null; + } + using var stream = new MemoryStream(); + using (var writer = new Utf8JsonWriter(stream)) + { + writer.WriteStartObject(); + foreach (var kvp in environment) + { + writer.WriteString(kvp.Key, kvp.Value); + } + writer.WriteEndObject(); + } + return stream.ToArray(); + } + + /// + /// Writes one framed message to the native connection. The bytes are read + /// synchronously by the native side (it copies before returning), so the + /// span does not need to outlive the call — no allocation or copy on our side. + /// + private delegate bool FrameWriter(ReadOnlySpan frame); + + private bool SendFrame(ReadOnlySpan frame) + { + if (_disposed || _connectionId == 0) + { + return false; + } + return NativeConnectionWrite(_connectionId, frame); + } + + private void FeedInbound(IntPtr bytesPtr, UIntPtr bytesLen) + { + var length = checked((int)bytesLen.ToUInt64()); + var buffer = new byte[length]; + Marshal.Copy(bytesPtr, buffer, 0, length); + _receiveStream.Feed(buffer); + } + + public void Dispose() + { + if (_disposed) + { + return; + } + _disposed = true; + + try + { + if (_connectionId != 0) + { + NativeConnectionClose(_connectionId); + _connectionId = 0; + } + } + catch (Exception ex) + { + _logger.LogDebug(ex, "FfiRuntimeHost: connection_close failed"); + } + + try + { + if (_serverId != 0) + { + NativeHostShutdown(_serverId); + _serverId = 0; + } + } + catch (Exception ex) + { + _logger.LogDebug(ex, "FfiRuntimeHost: host_shutdown failed"); + } + + _receiveStream.Complete(); + DisposeNativeCallback(); + } + + /// Length as the native pointer-sized unsigned integer the ABI expects. + private static UIntPtr Len(int value) => new((uint)value); + +#if NET + // ---- Modern interop: source-generated LibraryImport P/Invoke (trim/AOT-safe) ---- + + private static readonly object ResolverLock = new(); + private static bool s_resolverRegistered; + private static string? s_resolvedLibraryPath; + + // A normal (non-pinned) handle to this instance, passed to the native side as + // the callback's user_data so the static outbound callback can route back here. + private GCHandle _selfHandle; + + /// + /// Registers (once) a process-wide + /// that maps to the absolute runtime.node path so the + /// stubs resolve. The resolved handle is cached by + /// the runtime after first use, so all in-process hosts share a single loaded library. + /// + private static void PrepareNativeLibrary(string libraryPath) + { + lock (ResolverLock) + { + if (s_resolvedLibraryPath is not null && s_resolvedLibraryPath != libraryPath) + { + throw new InvalidOperationException( + $"An in-process FFI runtime library is already loaded from '{s_resolvedLibraryPath}'; " + + $"loading a different library from '{libraryPath}' in the same process is not supported."); + } + s_resolvedLibraryPath = libraryPath; + if (!s_resolverRegistered) + { + NativeLibrary.SetDllImportResolver(typeof(FfiRuntimeHost).Assembly, Resolve); + s_resolverRegistered = true; + } + } + } + + private static IntPtr Resolve(string libraryName, Assembly assembly, DllImportSearchPath? searchPath) + { + if (libraryName == LibraryName && s_resolvedLibraryPath is not null) + { + return NativeLibrary.Load(s_resolvedLibraryPath); + } + return IntPtr.Zero; + } + + private static uint NativeHostStart(byte[] argvJson, byte[]? env) => + HostStart(argvJson, Len(argvJson.Length), env, env is null ? UIntPtr.Zero : Len(env.Length)); + + private uint NativeOpenConnection(uint serverId) + { + _selfHandle = GCHandle.Alloc(this); + unsafe + { + return ConnectionOpen( + serverId, + &OnOutboundStatic, + GCHandle.ToIntPtr(_selfHandle), + null, UIntPtr.Zero, + null, UIntPtr.Zero, + null, UIntPtr.Zero); + } + } + + private static bool NativeHostShutdown(uint serverId) => HostShutdown(serverId); + + private static bool NativeConnectionWrite(uint connectionId, ReadOnlySpan frame) => ConnectionWrite(connectionId, frame, Len(frame.Length)); + + private static bool NativeConnectionClose(uint connectionId) => ConnectionClose(connectionId); + + private void DisposeNativeCallback() + { + if (_selfHandle.IsAllocated) + { + _selfHandle.Free(); + } + } + + [UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })] + private static void OnOutboundStatic(IntPtr userData, IntPtr bytesPtr, nuint bytesLen) + { + if (userData == IntPtr.Zero || bytesPtr == IntPtr.Zero || bytesLen == 0) + { + return; + } + if (GCHandle.FromIntPtr(userData).Target is FfiRuntimeHost self) + { + self.FeedInbound(bytesPtr, bytesLen); + } + } + + [LibraryImport(LibraryName, EntryPoint = "copilot_runtime_host_start")] + [UnmanagedCallConv(CallConvs = new[] { typeof(CallConvCdecl) })] + private static partial uint HostStart( + byte[] argvJson, nuint argvJsonLen, + byte[]? env, nuint envLen); + + [LibraryImport(LibraryName, EntryPoint = "copilot_runtime_host_shutdown")] + [UnmanagedCallConv(CallConvs = new[] { typeof(CallConvCdecl) })] + [return: MarshalAs(UnmanagedType.U1)] + private static partial bool HostShutdown(uint serverId); + + [LibraryImport(LibraryName, EntryPoint = "copilot_runtime_connection_open")] + [UnmanagedCallConv(CallConvs = new[] { typeof(CallConvCdecl) })] + private static unsafe partial uint ConnectionOpen( + uint serverId, + delegate* unmanaged[Cdecl] onOutbound, + IntPtr userData, + byte[]? extSource, nuint extSourceLen, + byte[]? extName, nuint extNameLen, + byte[]? connToken, nuint connTokenLen); + + [LibraryImport(LibraryName, EntryPoint = "copilot_runtime_connection_write")] + [UnmanagedCallConv(CallConvs = new[] { typeof(CallConvCdecl) })] + [return: MarshalAs(UnmanagedType.U1)] + private static partial bool ConnectionWrite(uint connectionId, ReadOnlySpan bytes, nuint bytesLen); + + [LibraryImport(LibraryName, EntryPoint = "copilot_runtime_connection_close")] + [UnmanagedCallConv(CallConvs = new[] { typeof(CallConvCdecl) })] + [return: MarshalAs(UnmanagedType.U1)] + private static partial bool ConnectionClose(uint connectionId); +#else + // ---- Legacy interop: delegate-based P/Invoke for netstandard2.0 ---- + // netstandard2.0 has neither LibraryImport, NativeLibrary, nor UnmanagedCallersOnly, + // so the cdylib is loaded through a hand-rolled dlopen/LoadLibrary shim and each + // export is bound to a [UnmanagedFunctionPointer] delegate. The outbound callback is + // an instance delegate kept alive in a field for the connection's lifetime. + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + private delegate uint HostStartDelegate( + byte[] argvJson, UIntPtr argvJsonLen, + byte[]? env, UIntPtr envLen); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + [return: MarshalAs(UnmanagedType.U1)] + private delegate bool HostShutdownDelegate(uint serverId); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + private delegate uint ConnectionOpenDelegate( + uint serverId, + OutboundCallbackDelegate onOutbound, + IntPtr userData, + byte[]? extSource, UIntPtr extSourceLen, + byte[]? extName, UIntPtr extNameLen, + byte[]? connToken, UIntPtr connTokenLen); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + [return: MarshalAs(UnmanagedType.U1)] + private delegate bool ConnectionWriteDelegate(uint connectionId, IntPtr bytes, UIntPtr bytesLen); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + [return: MarshalAs(UnmanagedType.U1)] + private delegate bool ConnectionCloseDelegate(uint connectionId); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + private delegate void OutboundCallbackDelegate(IntPtr userData, IntPtr bytesPtr, UIntPtr bytesLen); + + private static readonly object NativeLock = new(); + private static bool s_loaded; + private static string? s_loadedPath; + private static HostStartDelegate? s_hostStart; + private static HostShutdownDelegate? s_hostShutdown; + private static ConnectionOpenDelegate? s_connectionOpen; + private static ConnectionWriteDelegate? s_connectionWrite; + private static ConnectionCloseDelegate? s_connectionClose; + + // Held for the connection's lifetime so the marshaled function pointer handed to the + // native side is not collected while Rust may still invoke it. + private OutboundCallbackDelegate? _outboundDelegate; + + private static void PrepareNativeLibrary(string libraryPath) + { + lock (NativeLock) + { + if (s_loaded) + { + if (s_loadedPath != libraryPath) + { + throw new InvalidOperationException( + $"An in-process FFI runtime library is already loaded from '{s_loadedPath}'; " + + $"loading a different library from '{libraryPath}' in the same process is not supported."); + } + return; + } + + var handle = NativeLoader.Load(libraryPath); + if (handle == IntPtr.Zero) + { + throw new InvalidOperationException($"Failed to load FFI runtime library '{libraryPath}'."); + } + + s_hostStart = Bind(handle, "copilot_runtime_host_start"); + s_hostShutdown = Bind(handle, "copilot_runtime_host_shutdown"); + s_connectionOpen = Bind(handle, "copilot_runtime_connection_open"); + s_connectionWrite = Bind(handle, "copilot_runtime_connection_write"); + s_connectionClose = Bind(handle, "copilot_runtime_connection_close"); + s_loaded = true; + s_loadedPath = libraryPath; + } + } + + private static T Bind(IntPtr handle, string export) where T : Delegate + { + var symbol = NativeLoader.GetSymbol(handle, export); + if (symbol == IntPtr.Zero) + { + throw new InvalidOperationException($"FFI runtime library is missing the '{export}' export."); + } + return Marshal.GetDelegateForFunctionPointer(symbol); + } + + private static uint NativeHostStart(byte[] argvJson, byte[]? env) => + s_hostStart!(argvJson, Len(argvJson.Length), env, env is null ? UIntPtr.Zero : Len(env.Length)); + + private uint NativeOpenConnection(uint serverId) + { + _outboundDelegate = OnOutbound; + return s_connectionOpen!( + serverId, + _outboundDelegate, + IntPtr.Zero, + null, UIntPtr.Zero, + null, UIntPtr.Zero, + null, UIntPtr.Zero); + } + + private static bool NativeHostShutdown(uint serverId) => s_hostShutdown!(serverId); + + private static unsafe bool NativeConnectionWrite(uint connectionId, ReadOnlySpan frame) + { + fixed (byte* ptr = frame) + { + return s_connectionWrite!(connectionId, (IntPtr)ptr, Len(frame.Length)); + } + } + + private static bool NativeConnectionClose(uint connectionId) => s_connectionClose!(connectionId); + + private void DisposeNativeCallback() => _outboundDelegate = null; + + private void OnOutbound(IntPtr userData, IntPtr bytesPtr, UIntPtr bytesLen) + { + if (bytesPtr == IntPtr.Zero || bytesLen == UIntPtr.Zero) + { + return; + } + FeedInbound(bytesPtr, bytesLen); + } + + /// + /// Minimal cross-platform native library loader for netstandard2.0, which lacks + /// NativeLibrary. Uses LoadLibrary/GetProcAddress on Windows + /// and dlopen/dlsym elsewhere (trying libdl.so.2 first, then + /// libdl for older Linux and macOS). + /// + private static class NativeLoader + { + public static IntPtr Load(string path) => + RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? Windows.LoadLibrary(path) : Unix.Open(path); + + public static IntPtr GetSymbol(IntPtr handle, string name) => + RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? Windows.GetProcAddress(handle, name) : Unix.Sym(handle, name); + + private static class Windows + { + [DllImport("kernel32", SetLastError = true, CharSet = CharSet.Unicode, BestFitMapping = false, ThrowOnUnmappableChar = true)] + public static extern IntPtr LoadLibrary([MarshalAs(UnmanagedType.LPWStr)] string path); + + [DllImport("kernel32", SetLastError = true, BestFitMapping = false, ThrowOnUnmappableChar = true)] + public static extern IntPtr GetProcAddress(IntPtr module, [MarshalAs(UnmanagedType.LPStr)] string name); + } + + private static class Unix + { + private const int RtldNow = 2; + + public static IntPtr Open(string path) + { + try { return Libdl2.dlopen(path, RtldNow); } + catch (DllNotFoundException) { return Libdl1.dlopen(path, RtldNow); } + } + + public static IntPtr Sym(IntPtr handle, string name) + { + try { return Libdl2.dlsym(handle, name); } + catch (DllNotFoundException) { return Libdl1.dlsym(handle, name); } + } + + private static class Libdl2 + { + [DllImport("libdl.so.2", EntryPoint = "dlopen", CharSet = CharSet.Ansi, BestFitMapping = false, ThrowOnUnmappableChar = true)] + public static extern IntPtr dlopen([MarshalAs(UnmanagedType.LPStr)] string fileName, int flags); + + [DllImport("libdl.so.2", EntryPoint = "dlsym", CharSet = CharSet.Ansi, BestFitMapping = false, ThrowOnUnmappableChar = true)] + public static extern IntPtr dlsym(IntPtr handle, [MarshalAs(UnmanagedType.LPStr)] string symbol); + } + + private static class Libdl1 + { + [DllImport("libdl", EntryPoint = "dlopen", CharSet = CharSet.Ansi, BestFitMapping = false, ThrowOnUnmappableChar = true)] + public static extern IntPtr dlopen([MarshalAs(UnmanagedType.LPStr)] string fileName, int flags); + + [DllImport("libdl", EntryPoint = "dlsym", CharSet = CharSet.Ansi, BestFitMapping = false, ThrowOnUnmappableChar = true)] + public static extern IntPtr dlsym(IntPtr handle, [MarshalAs(UnmanagedType.LPStr)] string symbol); + } + } + } +#endif + + /// + /// A read-only stream fed by the native outbound callback. Chunks are queued on + /// an unbounded channel and drained in order by the JSON-RPC read loop. + /// + private sealed class CallbackReceiveStream : Stream + { + private readonly Channel _channel = Channel.CreateUnbounded( + new UnboundedChannelOptions { SingleReader = true, SingleWriter = false }); + private ReadOnlyMemory _leftover; + + public void Feed(byte[] data) => _channel.Writer.TryWrite(data); + + public void Complete() => _channel.Writer.TryComplete(); + +#if !NETSTANDARD2_0 + public override async ValueTask ReadAsync(Memory buffer, CancellationToken cancellationToken = default) + { + return await ReadCoreAsync(buffer, cancellationToken).ConfigureAwait(false); + } +#endif + + private async ValueTask ReadCoreAsync(Memory buffer, CancellationToken cancellationToken) + { + if (_leftover.IsEmpty) + { + while (true) + { + if (!await _channel.Reader.WaitToReadAsync(cancellationToken).ConfigureAwait(false)) + { + return 0; // EOF: channel completed. + } + if (_channel.Reader.TryRead(out var chunk)) + { + _leftover = chunk; + break; + } + // Data was signalled but lost a race for it; wait again rather + // than reporting a spurious EOF. + } + } + + var n = Math.Min(buffer.Length, _leftover.Length); + _leftover.Span.Slice(0, n).CopyTo(buffer.Span); + _leftover = _leftover.Slice(n); + return n; + } + + public override int Read(byte[] buffer, int offset, int count) => + ReadCoreAsync(buffer.AsMemory(offset, count), CancellationToken.None).AsTask().GetAwaiter().GetResult(); + + public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) => + ReadCoreAsync(buffer.AsMemory(offset, count), cancellationToken).AsTask(); + + public override bool CanRead => true; + public override bool CanSeek => false; + public override bool CanWrite => false; + public override long Length => throw new NotSupportedException(); + public override long Position { get => throw new NotSupportedException(); set => throw new NotSupportedException(); } + public override void Flush() { } + public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException(); + public override void SetLength(long value) => throw new NotSupportedException(); + public override void Write(byte[] buffer, int offset, int count) => throw new NotSupportedException(); + } + + /// + /// A write-only stream that forwards each frame to the native + /// connection_write export. + /// + private sealed class CallbackSendStream(FrameWriter write) : Stream + { + private void WriteFrame(ReadOnlySpan frame) + { + if (!write(frame)) + { + throw new IOException("Failed to write a frame to the in-process runtime connection."); + } + } + + public override void Write(byte[] buffer, int offset, int count) => WriteFrame(buffer.AsSpan(offset, count)); + +#if !NETSTANDARD2_0 + public override void Write(ReadOnlySpan buffer) => WriteFrame(buffer); + + public override ValueTask WriteAsync(ReadOnlyMemory buffer, CancellationToken cancellationToken = default) + { + WriteFrame(buffer.Span); + return ValueTask.CompletedTask; + } +#endif + + public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { + WriteFrame(buffer.AsSpan(offset, count)); + return Task.CompletedTask; + } + + public override bool CanRead => false; + public override bool CanSeek => false; + public override bool CanWrite => true; + public override long Length => throw new NotSupportedException(); + public override long Position { get => throw new NotSupportedException(); set => throw new NotSupportedException(); } + public override void Flush() { } + public override Task FlushAsync(CancellationToken cancellationToken) => Task.CompletedTask; + public override int Read(byte[] buffer, int offset, int count) => throw new NotSupportedException(); + public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException(); + public override void SetLength(long value) => throw new NotSupportedException(); + } +} diff --git a/dotnet/src/Generated/Rpc.cs b/dotnet/src/Generated/Rpc.cs index 065b64ecf0..308d9bc0d2 100644 --- a/dotnet/src/Generated/Rpc.cs +++ b/dotnet/src/Generated/Rpc.cs @@ -61,10 +61,14 @@ internal sealed class ConnectResult public string Version { get; set; } = string.Empty; } -/// Optional connection token presented by the SDK client during the handshake. +/// Parameters for the `server.connect` handshake: an optional connection token and optional connection-level opt-ins (e.g. GitHub telemetry forwarding). [Experimental(Diagnostics.Experimental)] internal sealed class ConnectRequest { + /// Opt this connection in to GitHub telemetry forwarding for its lifetime. When set, the runtime forwards every internal telemetry event it emits — across all sessions, plus sessionless events — to this connection over the `gitHubTelemetry.event` notification, in addition to the runtime's normal GitHub/CTS emission (dual-write). Intended for first-party hosts that re-emit the events into their own telemetry stores. Both unrestricted and restricted events are forwarded, each tagged with a `restricted` discriminator; a backstop drops restricted events when restricted telemetry is disabled. + [JsonPropertyName("enableGitHubTelemetryForwarding")] + public bool? EnableGitHubTelemetryForwarding { get; set; } + /// Connection token; required when the server was started with COPILOT_CONNECTION_TOKEN. [JsonPropertyName("token")] public string? Token { get; set; } @@ -258,7 +262,7 @@ public sealed class ModelPolicy public string? Terms { get; set; } } -/// Schema for the `Model` type. +/// Copilot model metadata, including identifier, display name, capabilities, policy, billing, reasoning efforts, and picker categories. [Experimental(Diagnostics.Experimental)] public sealed class Model { @@ -317,7 +321,7 @@ internal sealed class ModelsListRequest public string? GitHubToken { get; set; } } -/// Schema for the `Tool` type. +/// Built-in tool metadata with identifier, optional namespaced name, description, input-parameter schema, and usage instructions. [Experimental(Diagnostics.Experimental)] public sealed class Tool { @@ -360,7 +364,7 @@ internal sealed class ToolsListRequest public string? Model { get; set; } } -/// Schema for the `AccountQuotaSnapshot` type. +/// Quota usage snapshot for a Copilot quota type, including entitlement, used requests, overage, reset date, and remaining percentage. [Experimental(Diagnostics.Experimental)] public sealed class AccountQuotaSnapshot { @@ -436,7 +440,7 @@ public partial class AuthInfo } -/// Schema for the `CopilotUserResponseEndpoints` type. +/// Endpoint URLs from the raw Copilot `/copilot_internal/v2/token` user-response passthrough. [Experimental(Diagnostics.Experimental)] public sealed class CopilotUserResponseEndpoints { @@ -469,178 +473,178 @@ public sealed class CopilotUserResponseOrganizationListItem public string? Name { get; set; } } -/// Schema for the `CopilotUserResponseQuotaSnapshotsChat` type. +/// Chat quota snapshot from the raw Copilot user-response passthrough, with entitlement, overage, remaining quota, reset, and billing fields. [Experimental(Diagnostics.Experimental)] public sealed class CopilotUserResponseQuotaSnapshotsChat { - /// Gets or sets the entitlement value. + /// Number of requests/units included in the entitlement for this period; `-1` denotes an unlimited entitlement. [JsonPropertyName("entitlement")] public double? Entitlement { get; set; } - /// Gets or sets the has_quota value. + /// Whether the user currently has quota available; when `false` and not unlimited, further requests are blocked until the quota resets. [JsonPropertyName("has_quota")] public bool? HasQuota { get; set; } - /// Gets or sets the overage_count value. + /// Count of additional pay-per-request usage consumed this period beyond the entitlement. [JsonPropertyName("overage_count")] public double? OverageCount { get; set; } - /// Gets or sets the overage_permitted value. + /// Whether usage may continue at pay-per-request rates once the entitlement is exhausted. [JsonPropertyName("overage_permitted")] public bool? OveragePermitted { get; set; } - /// Gets or sets the percent_remaining value. + /// Percentage of the entitlement remaining at the snapshot timestamp. [JsonPropertyName("percent_remaining")] public double? PercentRemaining { get; set; } - /// Gets or sets the quota_id value. + /// Identifier of the quota bucket this snapshot describes. [JsonPropertyName("quota_id")] public string? QuotaId { get; set; } - /// Gets or sets the quota_remaining value. + /// Amount of quota remaining at the snapshot timestamp. [JsonPropertyName("quota_remaining")] public double? QuotaRemaining { get; set; } - /// Gets or sets the quota_reset_at value. + /// Unix epoch time, in seconds, when this quota next resets. [JsonPropertyName("quota_reset_at")] public double? QuotaResetAt { get; set; } - /// Gets or sets the remaining value. + /// Remaining entitlement/quota amount at the snapshot timestamp. [JsonPropertyName("remaining")] public double? Remaining { get; set; } - /// Gets or sets the timestamp_utc value. + /// UTC timestamp when this snapshot was captured. [JsonPropertyName("timestamp_utc")] public string? TimestampUtc { get; set; } - /// Gets or sets the token_based_billing value. + /// Whether this category uses usage-based (token/AI-credit) billing rather than a fixed premium-request count. [JsonPropertyName("token_based_billing")] public bool? TokenBasedBilling { get; set; } - /// Gets or sets the unlimited value. + /// Whether the entitlement for this category is unlimited. [JsonPropertyName("unlimited")] public bool? Unlimited { get; set; } } -/// Schema for the `CopilotUserResponseQuotaSnapshotsCompletions` type. +/// Completions quota snapshot from the raw Copilot user-response passthrough, with entitlement, overage, remaining quota, reset, and billing fields. [Experimental(Diagnostics.Experimental)] public sealed class CopilotUserResponseQuotaSnapshotsCompletions { - /// Gets or sets the entitlement value. + /// Number of requests/units included in the entitlement for this period; `-1` denotes an unlimited entitlement. [JsonPropertyName("entitlement")] public double? Entitlement { get; set; } - /// Gets or sets the has_quota value. + /// Whether the user currently has quota available; when `false` and not unlimited, further requests are blocked until the quota resets. [JsonPropertyName("has_quota")] public bool? HasQuota { get; set; } - /// Gets or sets the overage_count value. + /// Count of additional pay-per-request usage consumed this period beyond the entitlement. [JsonPropertyName("overage_count")] public double? OverageCount { get; set; } - /// Gets or sets the overage_permitted value. + /// Whether usage may continue at pay-per-request rates once the entitlement is exhausted. [JsonPropertyName("overage_permitted")] public bool? OveragePermitted { get; set; } - /// Gets or sets the percent_remaining value. + /// Percentage of the entitlement remaining at the snapshot timestamp. [JsonPropertyName("percent_remaining")] public double? PercentRemaining { get; set; } - /// Gets or sets the quota_id value. + /// Identifier of the quota bucket this snapshot describes. [JsonPropertyName("quota_id")] public string? QuotaId { get; set; } - /// Gets or sets the quota_remaining value. + /// Amount of quota remaining at the snapshot timestamp. [JsonPropertyName("quota_remaining")] public double? QuotaRemaining { get; set; } - /// Gets or sets the quota_reset_at value. + /// Unix epoch time, in seconds, when this quota next resets. [JsonPropertyName("quota_reset_at")] public double? QuotaResetAt { get; set; } - /// Gets or sets the remaining value. + /// Remaining entitlement/quota amount at the snapshot timestamp. [JsonPropertyName("remaining")] public double? Remaining { get; set; } - /// Gets or sets the timestamp_utc value. + /// UTC timestamp when this snapshot was captured. [JsonPropertyName("timestamp_utc")] public string? TimestampUtc { get; set; } - /// Gets or sets the token_based_billing value. + /// Whether this category uses usage-based (token/AI-credit) billing rather than a fixed premium-request count. [JsonPropertyName("token_based_billing")] public bool? TokenBasedBilling { get; set; } - /// Gets or sets the unlimited value. + /// Whether the entitlement for this category is unlimited. [JsonPropertyName("unlimited")] public bool? Unlimited { get; set; } } -/// Schema for the `CopilotUserResponseQuotaSnapshotsPremiumInteractions` type. +/// Premium-interactions quota snapshot from the raw Copilot user-response passthrough, with entitlement, overage, remaining quota, reset, and billing fields. [Experimental(Diagnostics.Experimental)] public sealed class CopilotUserResponseQuotaSnapshotsPremiumInteractions { - /// Gets or sets the entitlement value. + /// Number of requests/units included in the entitlement for this period; `-1` denotes an unlimited entitlement. [JsonPropertyName("entitlement")] public double? Entitlement { get; set; } - /// Gets or sets the has_quota value. + /// Whether the user currently has quota available; when `false` and not unlimited, further requests are blocked until the quota resets. [JsonPropertyName("has_quota")] public bool? HasQuota { get; set; } - /// Gets or sets the overage_count value. + /// Count of additional pay-per-request usage consumed this period beyond the entitlement. [JsonPropertyName("overage_count")] public double? OverageCount { get; set; } - /// Gets or sets the overage_permitted value. + /// Whether usage may continue at pay-per-request rates once the entitlement is exhausted. [JsonPropertyName("overage_permitted")] public bool? OveragePermitted { get; set; } - /// Gets or sets the percent_remaining value. + /// Percentage of the entitlement remaining at the snapshot timestamp. [JsonPropertyName("percent_remaining")] public double? PercentRemaining { get; set; } - /// Gets or sets the quota_id value. + /// Identifier of the quota bucket this snapshot describes. [JsonPropertyName("quota_id")] public string? QuotaId { get; set; } - /// Gets or sets the quota_remaining value. + /// Amount of quota remaining at the snapshot timestamp. [JsonPropertyName("quota_remaining")] public double? QuotaRemaining { get; set; } - /// Gets or sets the quota_reset_at value. + /// Unix epoch time, in seconds, when this quota next resets. [JsonPropertyName("quota_reset_at")] public double? QuotaResetAt { get; set; } - /// Gets or sets the remaining value. + /// Remaining entitlement/quota amount at the snapshot timestamp. [JsonPropertyName("remaining")] public double? Remaining { get; set; } - /// Gets or sets the timestamp_utc value. + /// UTC timestamp when this snapshot was captured. [JsonPropertyName("timestamp_utc")] public string? TimestampUtc { get; set; } - /// Gets or sets the token_based_billing value. + /// Whether this category uses usage-based (token/AI-credit) billing rather than a fixed premium-request count. [JsonPropertyName("token_based_billing")] public bool? TokenBasedBilling { get; set; } - /// Gets or sets the unlimited value. + /// Whether the entitlement for this category is unlimited. [JsonPropertyName("unlimited")] public bool? Unlimited { get; set; } } -/// Schema for the `CopilotUserResponseQuotaSnapshots` type. +/// Quota snapshot map from the raw Copilot user-response passthrough, with chat, completions, premium-interactions, and other entries. [Experimental(Diagnostics.Experimental)] public sealed class CopilotUserResponseQuotaSnapshots { - /// Schema for the `CopilotUserResponseQuotaSnapshotsChat` type. + /// Chat quota snapshot from the raw Copilot user-response passthrough, with entitlement, overage, remaining quota, reset, and billing fields. [JsonPropertyName("chat")] public CopilotUserResponseQuotaSnapshotsChat? Chat { get; set; } - /// Schema for the `CopilotUserResponseQuotaSnapshotsCompletions` type. + /// Completions quota snapshot from the raw Copilot user-response passthrough, with entitlement, overage, remaining quota, reset, and billing fields. [JsonPropertyName("completions")] public CopilotUserResponseQuotaSnapshotsCompletions? Completions { get; set; } - /// Schema for the `CopilotUserResponseQuotaSnapshotsPremiumInteractions` type. + /// Premium-interactions quota snapshot from the raw Copilot user-response passthrough, with entitlement, overage, remaining quota, reset, and billing fields. [JsonPropertyName("premium_interactions")] public CopilotUserResponseQuotaSnapshotsPremiumInteractions? PremiumInteractions { get; set; } } @@ -649,112 +653,112 @@ public sealed class CopilotUserResponseQuotaSnapshots [Experimental(Diagnostics.Experimental)] public sealed class CopilotUserResponse { - /// Gets or sets the access_type_sku value. + /// Copilot access SKU identifier (e.g. `free_limited_copilot`, `copilot_for_business_seat_quota`) used to gate model and feature access. [JsonPropertyName("access_type_sku")] public string? AccessTypeSku { get; set; } - /// Gets or sets the analytics_tracking_id value. + /// Opaque analytics tracking identifier for the user, forwarded from the Copilot API. [JsonPropertyName("analytics_tracking_id")] public string? AnalyticsTrackingId { get; set; } - /// Gets or sets the assigned_date value. + /// Date the Copilot seat was assigned to the user, if applicable. [JsonPropertyName("assigned_date")] public string? AssignedDate { get; set; } - /// Gets or sets the can_signup_for_limited value. + /// Whether the user is eligible to sign up for the free/limited Copilot tier. [JsonPropertyName("can_signup_for_limited")] public bool? CanSignupForLimited { get; set; } - /// Gets or sets the can_upgrade_plan value. + /// Whether the user is able to upgrade their Copilot plan. [JsonPropertyName("can_upgrade_plan")] public bool? CanUpgradePlan { get; set; } - /// Gets or sets the chat_enabled value. + /// Whether Copilot chat is enabled for the user. [JsonPropertyName("chat_enabled")] public bool? ChatEnabled { get; set; } - /// Gets or sets the cli_remote_control_enabled value. + /// Whether CLI remote control is enabled for the user. [JsonPropertyName("cli_remote_control_enabled")] public bool? CliRemoteControlEnabled { get; set; } - /// Gets or sets the cloud_session_storage_enabled value. + /// Whether cloud session storage is enabled for the user. [JsonPropertyName("cloud_session_storage_enabled")] public bool? CloudSessionStorageEnabled { get; set; } - /// Gets or sets the codex_agent_enabled value. + /// Whether the Codex agent is enabled for the user. [JsonPropertyName("codex_agent_enabled")] public bool? CodexAgentEnabled { get; set; } - /// Gets or sets the copilot_plan value. + /// Copilot plan name for the user (e.g. `individual`, `business`, `enterprise`). [JsonPropertyName("copilot_plan")] public string? CopilotPlan { get; set; } - /// Gets or sets the copilotignore_enabled value. + /// Whether `.copilotignore` content-exclusion support is enabled for the user. [JsonPropertyName("copilotignore_enabled")] public bool? CopilotignoreEnabled { get; set; } - /// Schema for the `CopilotUserResponseEndpoints` type. + /// Endpoint URLs from the raw Copilot `/copilot_internal/v2/token` user-response passthrough. [JsonPropertyName("endpoints")] public CopilotUserResponseEndpoints? Endpoints { get; set; } - /// Gets or sets the is_mcp_enabled value. + /// Whether MCP (Model Context Protocol) support is enabled for the user. [JsonPropertyName("is_mcp_enabled")] public bool? IsMcpEnabled { get; set; } - /// Gets or sets the is_staff value. + /// Whether the user is a GitHub/Microsoft staff member. [JsonPropertyName("is_staff")] public bool? IsStaff { get; set; } - /// Gets or sets the limited_user_quotas value. + /// Per-category quota allotments for free/limited-tier users, keyed by quota category. [JsonPropertyName("limited_user_quotas")] public IDictionary? LimitedUserQuotas { get; set; } - /// Gets or sets the limited_user_reset_date value. + /// Date the free/limited-tier user's quotas next reset, as a raw string from the Copilot API. [JsonPropertyName("limited_user_reset_date")] public string? LimitedUserResetDate { get; set; } - /// Gets or sets the login value. + /// GitHub login of the authenticated user. [JsonPropertyName("login")] public string? Login { get; set; } - /// Gets or sets the monthly_quotas value. + /// Per-category monthly quota allotments, keyed by quota category. [JsonPropertyName("monthly_quotas")] public IDictionary? MonthlyQuotas { get; set; } - /// Gets or sets the organization_list value. + /// Organizations the user belongs to, each with an optional login and display name. [JsonPropertyName("organization_list")] public IList? OrganizationList { get; set; } - /// Gets or sets the organization_login_list value. + /// Logins of the organizations the user belongs to. [JsonPropertyName("organization_login_list")] public IList? OrganizationLoginList { get; set; } - /// Gets or sets the quota_reset_date value. + /// Date the user's usage quota next resets, as a raw string from the Copilot API; see `quota_reset_date_utc` for the UTC-normalized value. [JsonPropertyName("quota_reset_date")] public string? QuotaResetDate { get; set; } - /// Gets or sets the quota_reset_date_utc value. + /// UTC-normalized form of `quota_reset_date` (the date the user's usage quota next resets). [JsonPropertyName("quota_reset_date_utc")] public string? QuotaResetDateUtc { get; set; } - /// Schema for the `CopilotUserResponseQuotaSnapshots` type. + /// Quota snapshot map from the raw Copilot user-response passthrough, with chat, completions, premium-interactions, and other entries. [JsonPropertyName("quota_snapshots")] public CopilotUserResponseQuotaSnapshots? QuotaSnapshots { get; set; } - /// Gets or sets the restricted_telemetry value. + /// Whether the user's telemetry is subject to restricted-data handling. [JsonPropertyName("restricted_telemetry")] public bool? RestrictedTelemetry { get; set; } - /// Gets or sets the te value. + /// Raw passthrough of the Copilot API `te` flag for the user (an opaque server-side eligibility signal surfaced in telemetry); not otherwise interpreted by the runtime. [JsonPropertyName("te")] public bool? Te { get; set; } - /// Gets or sets the token_based_billing value. + /// Whether the account is on usage-based (token/AI-credit) billing rather than a fixed premium-request quota. [JsonPropertyName("token_based_billing")] public bool? TokenBasedBilling { get; set; } } -/// Schema for the `HMACAuthInfo` type. +/// Authentication-info variant for GitHub-internal HMAC auth, carrying the public GitHub host and HMAC secret. /// The hmac variant of . [Experimental(Diagnostics.Experimental)] public partial class AuthInfoHmac : AuthInfo @@ -777,7 +781,7 @@ public partial class AuthInfoHmac : AuthInfo public required string Host { get; set; } } -/// Schema for the `EnvAuthInfo` type. +/// Authentication-info variant for a token sourced from an environment variable, with host, optional login, token, and env var name. /// The env variant of . [Experimental(Diagnostics.Experimental)] public partial class AuthInfoEnv : AuthInfo @@ -809,7 +813,7 @@ public partial class AuthInfoEnv : AuthInfo public required string Token { get; set; } } -/// Schema for the `TokenAuthInfo` type. +/// Authentication-info variant for SDK-configured token authentication, carrying host and the secret token value. /// The token variant of . [Experimental(Diagnostics.Experimental)] public partial class AuthInfoToken : AuthInfo @@ -832,7 +836,7 @@ public partial class AuthInfoToken : AuthInfo public required string Token { get; set; } } -/// Schema for the `CopilotApiTokenAuthInfo` type. +/// Authentication-info variant for direct Copilot API token auth sourced from environment variables, with public GitHub host. /// The copilot-api-token variant of . [Experimental(Diagnostics.Experimental)] public partial class AuthInfoCopilotApiToken : AuthInfo @@ -851,7 +855,7 @@ public partial class AuthInfoCopilotApiToken : AuthInfo public required string Host { get; set; } } -/// Schema for the `UserAuthInfo` type. +/// Authentication-info variant for OAuth user auth, with host and login; the token remains in the runtime secret store. /// The user variant of . [Experimental(Diagnostics.Experimental)] public partial class AuthInfoUser : AuthInfo @@ -874,7 +878,7 @@ public partial class AuthInfoUser : AuthInfo public required string Login { get; set; } } -/// Schema for the `GhCliAuthInfo` type. +/// Authentication-info variant for GitHub CLI credentials, carrying host, login, and the `gh auth token` value. /// The gh-cli variant of . [Experimental(Diagnostics.Experimental)] public partial class AuthInfoGhCli : AuthInfo @@ -901,7 +905,7 @@ public partial class AuthInfoGhCli : AuthInfo public required string Token { get; set; } } -/// Schema for the `ApiKeyAuthInfo` type. +/// Authentication-info variant for API-key authentication to a non-GitHub LLM provider, carrying the secret `apiKey` and host. /// The api-key variant of . [Experimental(Diagnostics.Experimental)] public partial class AuthInfoApiKey : AuthInfo @@ -937,7 +941,7 @@ public sealed class AccountGetCurrentAuthResult public AuthInfo? AuthInfo { get; set; } } -/// Schema for the `AccountAllUsers` type. +/// Authenticated account entry returned by `account.getAllUsers`, with auth info and an optional associated token. [Experimental(Diagnostics.Experimental)] public sealed class AccountAllUsers { @@ -1012,7 +1016,7 @@ internal sealed class SecretsAddFilterValuesRequest public IList Values { get => field ??= []; set; } } -/// Schema for the `DiscoveredMcpServer` type. +/// MCP server discovered by `mcp.discover`, with config source, optional plugin source, transport type, and enabled state. [Experimental(Diagnostics.Experimental)] public sealed class DiscoveredMcpServer { @@ -1240,7 +1244,7 @@ internal sealed class PluginsUpdateRequest public string Name { get; set; } = string.Empty; } -/// Schema for the `PluginUpdateAllEntry` type. +/// Per-plugin result from updating all plugins, with versions, skills installed, success flag, and optional error. [Experimental(Diagnostics.Experimental)] public sealed class PluginUpdateAllEntry { @@ -1401,7 +1405,7 @@ internal sealed class PluginsMarketplacesBrowseRequest public string Name { get; set; } = string.Empty; } -/// Schema for the `MarketplaceRefreshEntry` type. +/// Per-marketplace refresh result, including marketplace name, success flag, and optional failure error. [Experimental(Diagnostics.Experimental)] public sealed class MarketplaceRefreshEntry { @@ -1436,7 +1440,7 @@ internal sealed class PluginsMarketplacesRefreshRequest public string? Name { get; set; } } -/// Schema for the `ServerSkill` type. +/// Server-side skill metadata, including name, description, source, enabled/invocable state, path, project path, and argument hint. [Experimental(Diagnostics.Experimental)] public sealed class ServerSkill { @@ -1499,7 +1503,7 @@ internal sealed class SkillsDiscoverRequest public IList? SkillDirectories { get; set; } } -/// Schema for the `SkillDiscoveryPath` type. +/// Canonical directory where skills can be discovered or created, with scope, preference, and optional project path. [Experimental(Diagnostics.Experimental)] public sealed class SkillDiscoveryPath { @@ -1551,7 +1555,7 @@ internal sealed class SkillsConfigSetDisabledSkillsRequest public IList DisabledSkills { get => field ??= []; set; } } -/// Schema for the `AgentInfo` type. +/// Custom agent metadata, including identifiers, display details, source, tools, model, MCP servers, skills, and file path. [Experimental(Diagnostics.Experimental)] public sealed class AgentInfo { @@ -1623,7 +1627,7 @@ internal sealed class AgentsDiscoverRequest public IList? ProjectPaths { get; set; } } -/// Schema for the `AgentDiscoveryPath` type. +/// Canonical directory where custom agents can be discovered or created, with scope, preference, and optional project path. [Experimental(Diagnostics.Experimental)] public sealed class AgentDiscoveryPath { @@ -1666,7 +1670,7 @@ internal sealed class AgentsGetDiscoveryPathsRequest public IList? ProjectPaths { get; set; } } -/// Schema for the `InstructionSource` type. +/// Loaded instruction source for a session, including path, content, category, location, applicability, and optional description. [Experimental(Diagnostics.Experimental)] public sealed class InstructionSource { @@ -1733,7 +1737,7 @@ internal sealed class InstructionsDiscoverRequest public IList? ProjectPaths { get; set; } } -/// Schema for the `InstructionDiscoveryPath` type. +/// Canonical file or directory where custom instructions can be discovered or created, with location, kind, preference, and project path. [Experimental(Diagnostics.Experimental)] public sealed class InstructionDiscoveryPath { @@ -2052,7 +2056,7 @@ public sealed class RemoteSessionMetadataValue public RemoteSessionMetadataTaskType? TaskType { get; set; } } -/// Schema for the `SessionsOpenProgress` type. +/// `sessions.open` handoff progress update with step, status, and optional message. [Experimental(Diagnostics.Experimental)] public sealed class SessionsOpenProgress { @@ -2589,7 +2593,7 @@ internal sealed class SessionsReleaseLockRequest public string SessionId { get; set; } = string.Empty; } -/// Schema for the `LocalSessionMetadataValue` type. +/// Persisted local session metadata, including identifiers, timestamps, summary/name, client, context, detached state, and task ID. [Experimental(Diagnostics.Experimental)] public sealed class LocalSessionMetadataValue { @@ -2699,7 +2703,7 @@ public sealed class SessionsSetAdditionalPluginsResult { } -/// Schema for the `InstalledPlugin` type. +/// Installed plugin record from global state, with marketplace, version, install time, enabled state, cache path, and source. [Experimental(Diagnostics.Experimental)] public sealed class InstalledPlugin { @@ -2814,6 +2818,12 @@ public partial class RemoteControlStatusActive : RemoteControlStatus [JsonPropertyName("attachedSessionId")] public required string AttachedSessionId { get; set; } + /// True while a read-only/session-sync export is deferred, awaiting the first `user.message` before its MC session exists. Marked internal: this field is excluded from the public SDK surface and is populated only on the CLI in-process path. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonInclude] + [JsonPropertyName("awaitingFirstMessage")] + internal bool? AwaitingFirstMessage { get; set; } + /// MC frontend URL for this session, when known. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("frontendUrl")] @@ -2974,42 +2984,6 @@ internal sealed class SessionsStopRemoteControlRequest public bool? Force { get; set; } } -/// Schema for the `SessionsPollSpawnedSessionsEvent` type. -[Experimental(Diagnostics.Experimental)] -public sealed class SessionsPollSpawnedSessionsEvent -{ - /// Session id of the newly-spawned session. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; -} - -/// Batch of spawn events plus a cursor for follow-up polls. -[Experimental(Diagnostics.Experimental)] -internal sealed class PollSpawnedSessionsResult -{ - /// Opaque cursor to pass back to receive only events after this batch. - [JsonPropertyName("cursor")] - public string Cursor { get; set; } = string.Empty; - - /// Spawn events emitted since the supplied cursor. - [JsonPropertyName("events")] - public IList Events { get => field ??= []; set; } -} - -/// RPC data type for SessionsPollSpawnedSessions operations. -[Experimental(Diagnostics.Experimental)] -internal sealed class SessionsPollSpawnedSessionsRequest -{ - /// Opaque cursor returned by a previous poll. Omit on the first call to receive any spawn events buffered since the runtime started. - [JsonPropertyName("cursor")] - public string? Cursor { get; set; } - - /// Milliseconds to wait for new spawn events when the cursor is at the tail. 0 (default) returns immediately even if no events are buffered. Capped at 60000ms. - [JsonConverter(typeof(MillisecondsTimeSpanConverter))] - [JsonPropertyName("waitMs")] - public TimeSpan? Wait { get; set; } -} - /// Handle for releasing the extension tool registration. [Experimental(Diagnostics.Experimental)] internal sealed class RegisterExtensionToolsResult @@ -3532,6 +3506,187 @@ internal sealed class SessionSetCredentialsParams public string SessionId { get; set; } = string.Empty; } +/// A file included in the redacted debug bundle. +[Experimental(Diagnostics.Experimental)] +public sealed class DebugCollectLogsCollectedEntry +{ + /// Relative path of the file in the staged bundle/archive. + [JsonPropertyName("bundlePath")] + public string BundlePath { get; set; } = string.Empty; + + /// Redacted output size in bytes. + [JsonPropertyName("sizeBytes")] + public long SizeBytes { get; set; } + + /// Source category for this entry. + [JsonPropertyName("source")] + public DebugCollectLogsSource Source { get; set; } +} + +/// An optional debug bundle entry that could not be included. +[Experimental(Diagnostics.Experimental)] +public sealed class DebugCollectLogsSkippedEntry +{ + /// Relative path requested for this bundle entry. + [JsonPropertyName("bundlePath")] + public string BundlePath { get; set; } = string.Empty; + + /// Server-local source path that could not be read. + [JsonPropertyName("path")] + public string? Path { get; set; } + + /// Reason the entry was skipped. + [JsonPropertyName("reason")] + public string Reason { get; set; } = string.Empty; +} + +/// Result of collecting a redacted debug bundle. +[Experimental(Diagnostics.Experimental)] +public sealed class DebugCollectLogsResult +{ + /// Files included in the redacted bundle. + [JsonPropertyName("entries")] + public IList Entries { get => field ??= []; set; } + + /// Destination kind that was written. + [JsonPropertyName("kind")] + public DebugCollectLogsResultKind Kind { get; set; } + + /// Actual archive path or staging directory path written. This may differ from the requested path when no-overwrite suffixing or fallback-to-temp-directory was needed. + [JsonPropertyName("path")] + public string Path { get; set; } = string.Empty; + + /// Optional files or directories that could not be included. + [JsonPropertyName("skippedEntries")] + public IList? SkippedEntries { get; set; } +} + +/// A caller-provided server-local file or directory to include in the debug bundle. +[Experimental(Diagnostics.Experimental)] +public sealed class DebugCollectLogsEntry +{ + /// Relative path to use inside the staged bundle/archive. + [JsonPropertyName("bundlePath")] + public string BundlePath { get; set; } = string.Empty; + + /// Kind of source path to include. + [JsonPropertyName("kind")] + public DebugCollectLogsEntryKind Kind { get; set; } + + /// Server-local source path to read. + [JsonPropertyName("path")] + public string Path { get; set; } = string.Empty; + + /// How text content from this entry should be redacted. Defaults to plain-text. + [JsonPropertyName("redaction")] + public DebugCollectLogsRedaction? Redaction { get; set; } + + /// When true, collection fails if this entry cannot be read. Defaults to false, which records the entry in `skippedEntries`. + [JsonPropertyName("required")] + public bool? Required { get; set; } +} + +/// Destination for the redacted debug bundle. +/// Polymorphic base type discriminated by kind. +[Experimental(Diagnostics.Experimental)] +[JsonPolymorphic( + TypeDiscriminatorPropertyName = "kind", + UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] +[JsonDerivedType(typeof(DebugCollectLogsDestinationArchive), "archive")] +[JsonDerivedType(typeof(DebugCollectLogsDestinationDirectory), "directory")] +public partial class DebugCollectLogsDestination +{ + /// The type discriminator. + [JsonPropertyName("kind")] + public virtual string Kind { get; set; } = string.Empty; +} + + +/// The archive variant of . +[Experimental(Diagnostics.Experimental)] +public partial class DebugCollectLogsDestinationArchive : DebugCollectLogsDestination +{ + /// + [JsonIgnore] + public override string Kind => "archive"; + + /// When true, create the archive atomically without overwriting an existing file by appending ` (N)` before the extension as needed. Defaults to false. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("noOverwrite")] + public bool? NoOverwrite { get; set; } + + /// Absolute or server-relative path for the .tgz archive to create. + [JsonPropertyName("outputPath")] + public required string OutputPath { get; set; } +} + +/// The directory variant of . +[Experimental(Diagnostics.Experimental)] +public partial class DebugCollectLogsDestinationDirectory : DebugCollectLogsDestination +{ + /// + [JsonIgnore] + public override string Kind => "directory"; + + /// Directory where redacted files should be staged. The directory is created if needed. + [JsonPropertyName("outputDirectory")] + public required string OutputDirectory { get; set; } +} + +/// Built-in session diagnostics to include in the bundle. Omitted fields default to true. +[Experimental(Diagnostics.Experimental)] +public sealed class DebugCollectLogsInclude +{ + /// Server-local path to the current process log. When set, it is included as `process.log` and its directory is searched for prior logs from the same session. + [JsonPropertyName("currentProcessLogPath")] + public string? CurrentProcessLogPath { get; set; } + + /// Include the session event log (`events.jsonl`). Defaults to true. + [JsonPropertyName("events")] + public bool? Events { get; set; } + + /// Server-local path to the session's events.jsonl file. Internal callers normally omit this and let the runtime derive it from the session. + [JsonPropertyName("eventsPath")] + public string? EventsPath { get; set; } + + /// Maximum number of previous process logs to include. Defaults to 5. + [JsonPropertyName("previousProcessLogLimit")] + public long? PreviousProcessLogLimit { get; set; } + + /// Server-local process log directory to search when `currentProcessLogPath` is unavailable, useful for collecting logs for inactive sessions. + [JsonPropertyName("processLogDirectory")] + public string? ProcessLogDirectory { get; set; } + + /// Include process logs for the session. Defaults to true. + [JsonPropertyName("processLogs")] + public bool? ProcessLogs { get; set; } + + /// Include interactive shell logs written under the session's `shell-logs` directory. Defaults to true. + [JsonPropertyName("shellLogs")] + public bool? ShellLogs { get; set; } +} + +/// Options for collecting a redacted session debug bundle. +[Experimental(Diagnostics.Experimental)] +internal sealed class DebugCollectLogsRequest +{ + /// Caller-provided server-local files or directories to include in addition to the runtime's built-in session diagnostics. This lets host applications add their own diagnostics without changing the API shape. + [JsonPropertyName("additionalEntries")] + public IList? AdditionalEntries { get; set; } + + /// Where the redacted bundle should be written. Use `archive` to produce a .tgz, or `directory` to stage redacted files for caller-managed upload/post-processing. + [JsonPropertyName("destination")] + public DebugCollectLogsDestination Destination { get => field ??= new(); set; } + + /// Which built-in session diagnostics to include. Omitted fields default to true. + [JsonPropertyName("include")] + public DebugCollectLogsInclude? Include { get; set; } + + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + /// Canvas action that the agent or host can invoke. To discover the input schema for a particular action, call the list_canvas_capabilities tool. [Experimental(Diagnostics.Experimental)] public sealed class CanvasAction @@ -3855,6 +4010,10 @@ internal sealed class ModelSwitchToRequest /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; + + /// Output verbosity level to request for supported models. + [JsonPropertyName("verbosity")] + public Verbosity? Verbosity { get; set; } } /// Update the session's reasoning effort without changing the selected model. Use `switchTo` instead when you also need to change the model. The runtime stores the effort on the session and applies it to subsequent turns. @@ -4267,7 +4426,7 @@ internal sealed class WorkspacesCreateFileRequest public string SessionId { get; set; } = string.Empty; } -/// Schema for the `WorkspacesCheckpoints` type. +/// Workspace checkpoint metadata with assigned number, human-readable title, and checkpoint filename. [Experimental(Diagnostics.Experimental)] public sealed class WorkspacesCheckpoints { @@ -4661,7 +4820,7 @@ internal sealed class TasksStartAgentRequest public string SessionId { get; set; } = string.Empty; } -/// Schema for the `TaskInfo` type. +/// Tracked task union returned by task APIs, containing either an agent task or a shell task. /// Polymorphic base type discriminated by type. [Experimental(Diagnostics.Experimental)] [JsonPolymorphic( @@ -4677,7 +4836,7 @@ public partial class TaskInfo } -/// Schema for the `TaskAgentInfo` type. +/// Tracked background agent task metadata, including IDs, status, timing, agent type, prompt, model, result, and latest response. /// The agent variant of . [Experimental(Diagnostics.Experimental)] public partial class TaskInfoAgent : TaskInfo @@ -4771,7 +4930,7 @@ public partial class TaskInfoAgent : TaskInfo public required string ToolCallId { get; set; } } -/// Schema for the `TaskShellInfo` type. +/// Tracked shell task metadata, including ID, command, status, timing, attachment/execution mode, log path, and PID. /// The shell variant of . [Experimental(Diagnostics.Experimental)] public partial class TaskInfoShell : TaskInfo @@ -4892,7 +5051,7 @@ public partial class TasksGetProgressResultProgress } -/// Schema for the `TaskProgressLine` type. +/// Timestamped display line for task progress output or recent agent activity. [Experimental(Diagnostics.Experimental)] public sealed class TaskProgressLine { @@ -4905,7 +5064,7 @@ public sealed class TaskProgressLine public DateTimeOffset Timestamp { get; set; } } -/// Schema for the `TaskAgentProgress` type. +/// Progress snapshot for an agent task, with recent activity lines and optional latest intent. /// The agent variant of . public partial class TasksGetProgressResultProgressAgent : TasksGetProgressResultProgress { @@ -4923,7 +5082,7 @@ public partial class TasksGetProgressResultProgressAgent : TasksGetProgressResul public required IList RecentActivity { get; set; } } -/// Schema for the `TaskShellProgress` type. +/// Progress snapshot for a shell task, with recent stdout/stderr output and optional process ID. /// The shell variant of . public partial class TasksGetProgressResultProgressShell : TasksGetProgressResultProgress { @@ -5099,7 +5258,7 @@ internal sealed class TasksSendMessageRequest public string SessionId { get; set; } = string.Empty; } -/// Schema for the `Skill` type. +/// Skill metadata available to a session, with name, description, source, enabled/invocable state, path, plugin, and argument hint. [Experimental(Diagnostics.Experimental)] public sealed class Skill { @@ -5154,7 +5313,7 @@ internal sealed class SessionSkillsListRequest public string SessionId { get; set; } = string.Empty; } -/// Schema for the `SkillsInvokedSkill` type. +/// Skill invocation record with name, path, content, allowed tools, and turn number. [Experimental(Diagnostics.Experimental)] public sealed class SkillsInvokedSkill { @@ -5309,7 +5468,7 @@ public sealed class McpHostState public IList PendingConnections { get => field ??= []; set; } } -/// Schema for the `McpServer` type. +/// MCP server status entry, including config source/plugin source and any connection error. [Experimental(Diagnostics.Experimental)] public sealed class McpServer { @@ -5363,7 +5522,7 @@ internal sealed class SessionMcpListRequest public string SessionId { get; set; } = string.Empty; } -/// Schema for the `McpTools` type. +/// MCP tool metadata with tool name and optional description. [Experimental(Diagnostics.Experimental)] public sealed class McpTools { @@ -5442,7 +5601,7 @@ internal sealed class SessionMcpReloadRequest public string SessionId { get; set; } = string.Empty; } -/// Schema for the `McpAllowedServer` type. +/// MCP server allowed by policy, with server name and optional PII-free explanatory note. [Experimental(Diagnostics.Experimental)] public sealed class McpAllowedServer { @@ -5455,7 +5614,7 @@ public sealed class McpAllowedServer public string? RedactedNote { get; set; } } -/// Schema for the `McpFilteredServer` type. +/// MCP server filtered by policy, with name, reason, optional redacted reason, and enterprise login. [Experimental(Diagnostics.Experimental)] public sealed class McpFilteredServer { @@ -5754,30 +5913,6 @@ internal sealed class McpIsServerRunningRequest public string SessionId { get; set; } = string.Empty; } -/// Empty result after recording the MCP OAuth response. -[Experimental(Diagnostics.Experimental)] -internal sealed class McpOauthRespondResult -{ -} - -/// MCP OAuth request id and optional provider response. -[Experimental(Diagnostics.Experimental)] -internal sealed class McpOauthRespondRequest -{ - /// In-process OAuthClientProvider instance, or omitted to deny. Marked internal: cannot be serialized across the JSON-RPC boundary. - [JsonInclude] - [JsonPropertyName("provider")] - internal JsonElement? Provider { get; set; } - - /// OAuth request identifier from mcp.oauth_required. - [JsonPropertyName("requestId")] - public string RequestId { get; set; } = string.Empty; - - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; -} - /// Indicates whether the pending MCP OAuth response was accepted. [Experimental(Diagnostics.Experimental)] public sealed class McpOauthHandlePendingResult @@ -5971,7 +6106,7 @@ internal sealed class McpHeadersHandlePendingHeadersRefreshRequestRequest public string SessionId { get; set; } = string.Empty; } -/// Schema for the `McpAppsResourceContent` type. +/// MCP Apps resource content with URI, optional MIME type, text or base64 blob, and resource metadata. [Experimental(Diagnostics.Experimental)] public sealed class McpAppsResourceContent { @@ -6252,7 +6387,7 @@ internal sealed class McpAppsDiagnoseRequest public string SessionId { get; set; } = string.Empty; } -/// Schema for the `Plugin` type. +/// Session plugin metadata, with name, marketplace, optional version, and enabled state. [Experimental(Diagnostics.Experimental)] public sealed class Plugin { @@ -6303,6 +6438,10 @@ public sealed class PluginsReloadRequest [JsonPropertyName("reloadCustomAgents")] public bool? ReloadCustomAgents { get; set; } + /// Re-discover and relaunch subprocess extensions (including plugin-shipped extensions) after refreshing plugins. Defaults to true. Has no effect when the session has no active extension controller (e.g. extensions were not requested for the session). + [JsonPropertyName("reloadExtensions")] + public bool? ReloadExtensions { get; set; } + /// Re-load user, plugin, and (subject to `deferRepoHooks`) repo hooks. Defaults to true. Has no effect when the host has not registered a hook reloader (e.g. remote sessions). [JsonPropertyName("reloadHooks")] public bool? ReloadHooks { get; set; } @@ -6324,6 +6463,10 @@ internal sealed class PluginsReloadRequestWithSession [JsonPropertyName("reloadCustomAgents")] public bool? ReloadCustomAgents { get; set; } + /// Re-discover and relaunch subprocess extensions (including plugin-shipped extensions) after refreshing plugins. Defaults to true. Has no effect when the session has no active extension controller (e.g. extensions were not requested for the session). + [JsonPropertyName("reloadExtensions")] + public bool? ReloadExtensions { get; set; } + /// Re-load user, plugin, and (subject to `deferRepoHooks`) repo hooks. Defaults to true. Has no effect when the host has not registered a hook reloader (e.g. remote sessions). [JsonPropertyName("reloadHooks")] public bool? ReloadHooks { get; set; } @@ -6545,7 +6688,7 @@ public sealed class SessionUpdateOptionsResult public bool Success { get; set; } } -/// Schema for the `OptionsUpdateAdditionalContentExclusionPolicyRuleSource` type. +/// Source descriptor for a `session.options.update` content-exclusion rule, with source name and type. [Experimental(Diagnostics.Experimental)] public sealed class OptionsUpdateAdditionalContentExclusionPolicyRuleSource { @@ -6558,7 +6701,7 @@ public sealed class OptionsUpdateAdditionalContentExclusionPolicyRuleSource public string Type { get; set; } = string.Empty; } -/// Schema for the `OptionsUpdateAdditionalContentExclusionPolicyRule` type. +/// Single content-exclusion rule supplied to `session.options.update`, with paths, match conditions, and source. [Experimental(Diagnostics.Experimental)] public sealed class OptionsUpdateAdditionalContentExclusionPolicyRule { @@ -6574,12 +6717,12 @@ public sealed class OptionsUpdateAdditionalContentExclusionPolicyRule [JsonPropertyName("paths")] public IList Paths { get => field ??= []; set; } - /// Schema for the `OptionsUpdateAdditionalContentExclusionPolicyRuleSource` type. + /// Source descriptor for a `session.options.update` content-exclusion rule, with source name and type. [JsonPropertyName("source")] public OptionsUpdateAdditionalContentExclusionPolicyRuleSource Source { get => field ??= new(); set; } } -/// Schema for the `OptionsUpdateAdditionalContentExclusionPolicy` type. +/// Content-exclusion policy supplied to `session.options.update`, with rules, last-updated data, and scope. [Experimental(Diagnostics.Experimental)] public sealed class OptionsUpdateAdditionalContentExclusionPolicy { @@ -6605,7 +6748,7 @@ public sealed class CapiSessionOptions public bool? EnableWebSocketResponses { get; set; } } -/// Schema for the `SessionInstalledPlugin` type. +/// Installed plugin record for a session, with marketplace, version, install time, enabled state, cache path, and source. [Experimental(Diagnostics.Experimental)] public sealed class SessionInstalledPlugin { @@ -6742,10 +6885,6 @@ public sealed class SandboxConfigUserPolicyFilesystem [Experimental(Diagnostics.Experimental)] public sealed class SandboxConfigUserPolicyNetwork { - /// Hosts allowed in addition to the base policy. - [JsonPropertyName("allowedHosts")] - public IList? AllowedHosts { get; set; } - /// Whether traffic to local/loopback addresses is allowed. [JsonPropertyName("allowLocalNetwork")] public bool? AllowLocalNetwork { get; set; } @@ -6753,10 +6892,6 @@ public sealed class SandboxConfigUserPolicyNetwork /// Whether outbound network traffic is allowed at all. [JsonPropertyName("allowOutbound")] public bool? AllowOutbound { get; set; } - - /// Hosts explicitly blocked. - [JsonPropertyName("blockedHosts")] - public IList? BlockedHosts { get; set; } } /// macOS seatbelt-specific options. @@ -7023,6 +7158,10 @@ internal sealed class SessionUpdateOptionsParams [JsonPropertyName("trajectoryFile")] public string? TrajectoryFile { get; set; } + /// Output verbosity level for supported models. + [JsonPropertyName("verbosity")] + public Verbosity? Verbosity { get; set; } + /// Absolute working-directory path for shell tools. [JsonPropertyName("workingDirectory")] public string? WorkingDirectory { get; set; } @@ -7049,7 +7188,7 @@ internal sealed class LspInitializeRequest public string? WorkingDirectory { get; set; } } -/// Schema for the `Extension` type. +/// Discovered extension metadata, including source-qualified ID, name, discovery source, status, and optional process ID. [Experimental(Diagnostics.Experimental)] public sealed class Extension { @@ -7127,7 +7266,7 @@ internal sealed class SessionExtensionsReloadRequest public string SessionId { get; set; } = string.Empty; } -/// Schema for the `PushAttachment` type. +/// Attachment union accepted by push input, covering files, directories, GitHub objects, blobs, snippets, and extension context. /// Polymorphic base type discriminated by type. [Experimental(Diagnostics.Experimental)] [JsonPolymorphic( @@ -7796,10 +7935,27 @@ internal sealed class UpdateSubagentSettingsRequest public UpdateSubagentSettingsRequestSubagents? Subagents { get; set; } } +/// A literal choice the command input accepts, with a human-facing description. +[Experimental(Diagnostics.Experimental)] +public sealed class SlashCommandInputChoice +{ + /// Human-readable description shown alongside the choice. + [JsonPropertyName("description")] + public string Description { get; set; } = string.Empty; + + /// The literal choice value (e.g. 'on', 'off', 'show'). + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; +} + /// Optional unstructured input hint. [Experimental(Diagnostics.Experimental)] public sealed class SlashCommandInput { + /// Optional literal choices the input accepts, each with a human-facing description; clients may render these as selectable options. + [JsonPropertyName("choices")] + public IList? Choices { get; set; } + /// Optional completion hint for the input (e.g. 'directory' for filesystem path completion). [JsonPropertyName("completion")] public SlashCommandInputCompletion? Completion { get; set; } @@ -7817,7 +7973,7 @@ public sealed class SlashCommandInput public bool? Required { get; set; } } -/// Schema for the `SlashCommandInfo` type. +/// Slash-command metadata with name, aliases, description, kind, input hint, execution allowance, and schedulability. [Experimental(Diagnostics.Experimental)] public sealed class SlashCommandInfo { @@ -7919,7 +8075,7 @@ public partial class SlashCommandInvocationResult } -/// Schema for the `SlashCommandTextResult` type. +/// Slash-command invocation result containing text output plus Markdown/ANSI rendering flags. /// The text variant of . [Experimental(Diagnostics.Experimental)] public partial class SlashCommandInvocationResultText : SlashCommandInvocationResult @@ -7948,7 +8104,7 @@ public partial class SlashCommandInvocationResultText : SlashCommandInvocationRe public required string Text { get; set; } } -/// Schema for the `SlashCommandAgentPromptResult` type. +/// Slash-command invocation result that submits an agent prompt, with display prompt, optional mode, and settings-change flag. /// The agent-prompt variant of . [Experimental(Diagnostics.Experimental)] public partial class SlashCommandInvocationResultAgentPrompt : SlashCommandInvocationResult @@ -7976,7 +8132,7 @@ public partial class SlashCommandInvocationResultAgentPrompt : SlashCommandInvoc public bool? RuntimeSettingsChanged { get; set; } } -/// Schema for the `SlashCommandCompletedResult` type. +/// Slash-command invocation result indicating completion, with optional message and settings-change flag. /// The completed variant of . [Experimental(Diagnostics.Experimental)] public partial class SlashCommandInvocationResultCompleted : SlashCommandInvocationResult @@ -7996,7 +8152,7 @@ public partial class SlashCommandInvocationResultCompleted : SlashCommandInvocat public bool? RuntimeSettingsChanged { get; set; } } -/// Schema for the `SlashCommandSelectSubcommandOption` type. +/// Selectable slash-command subcommand option with name, description, and optional group label. [Experimental(Diagnostics.Experimental)] public sealed class SlashCommandSelectSubcommandOption { @@ -8013,7 +8169,7 @@ public sealed class SlashCommandSelectSubcommandOption public string Name { get; set; } = string.Empty; } -/// Schema for the `SlashCommandSelectSubcommandResult` type. +/// Slash-command invocation result asking the client to present subcommand options for a parent command. /// The select-subcommand variant of . [Experimental(Diagnostics.Experimental)] public partial class SlashCommandInvocationResultSelectSubcommand : SlashCommandInvocationResult @@ -8317,7 +8473,7 @@ public sealed class UIHandlePendingResult public bool Success { get; set; } } -/// Schema for the `UIUserInputResponse` type. +/// User response for a pending user-input request, with answer text and whether it was typed freeform. [Experimental(Diagnostics.Experimental)] public sealed class UIUserInputResponse { @@ -8338,7 +8494,7 @@ internal sealed class UIHandlePendingUserInputRequest [JsonPropertyName("requestId")] public string RequestId { get; set; } = string.Empty; - /// Schema for the `UIUserInputResponse` type. + /// User response for a pending user-input request, with answer text and whether it was typed freeform. [JsonPropertyName("response")] public UIUserInputResponse Response { get => field ??= new(); set; } @@ -8421,7 +8577,7 @@ internal sealed class UIHandlePendingSessionLimitsExhaustedRequest public string SessionId { get; set; } = string.Empty; } -/// Schema for the `UIExitPlanModeResponse` type. +/// User response for a pending exit-plan-mode request, with approval state, selected action, auto-approve flag, and feedback. [Experimental(Diagnostics.Experimental)] public sealed class UIExitPlanModeResponse { @@ -8450,7 +8606,7 @@ internal sealed class UIHandlePendingExitPlanModeRequest [JsonPropertyName("requestId")] public string RequestId { get; set; } = string.Empty; - /// Schema for the `UIExitPlanModeResponse` type. + /// User response for a pending exit-plan-mode request, with approval state, selected action, auto-approve flag, and feedback. [JsonPropertyName("response")] public UIExitPlanModeResponse Response { get => field ??= new(); set; } @@ -8508,7 +8664,7 @@ public sealed class PermissionsConfigureResult public bool Success { get; set; } } -/// Schema for the `PermissionsConfigureAdditionalContentExclusionPolicyRuleSource` type. +/// Source descriptor for a `session.permissions.configure` content-exclusion rule, with source name and type. [Experimental(Diagnostics.Experimental)] public sealed class PermissionsConfigureAdditionalContentExclusionPolicyRuleSource { @@ -8521,7 +8677,7 @@ public sealed class PermissionsConfigureAdditionalContentExclusionPolicyRuleSour public string Type { get; set; } = string.Empty; } -/// Schema for the `PermissionsConfigureAdditionalContentExclusionPolicyRule` type. +/// Single content-exclusion rule supplied to `session.permissions.configure`, with paths, match conditions, and source. [Experimental(Diagnostics.Experimental)] public sealed class PermissionsConfigureAdditionalContentExclusionPolicyRule { @@ -8537,12 +8693,12 @@ public sealed class PermissionsConfigureAdditionalContentExclusionPolicyRule [JsonPropertyName("paths")] public IList Paths { get => field ??= []; set; } - /// Schema for the `PermissionsConfigureAdditionalContentExclusionPolicyRuleSource` type. + /// Source descriptor for a `session.permissions.configure` content-exclusion rule, with source name and type. [JsonPropertyName("source")] public PermissionsConfigureAdditionalContentExclusionPolicyRuleSource Source { get => field ??= new(); set; } } -/// Schema for the `PermissionsConfigureAdditionalContentExclusionPolicy` type. +/// Content-exclusion policy supplied to `session.permissions.configure`, with rules, last-updated data, and scope. [Experimental(Diagnostics.Experimental)] public sealed class PermissionsConfigureAdditionalContentExclusionPolicy { @@ -8677,7 +8833,7 @@ public partial class PermissionDecision } -/// Schema for the `PermissionDecisionApproveOnce` type. +/// Permission-decision request variant to approve only the current permission request. /// The approve-once variant of . [Experimental(Diagnostics.Experimental)] public partial class PermissionDecisionApproveOnce : PermissionDecision @@ -8710,7 +8866,7 @@ public partial class PermissionDecisionApproveForSessionApproval } -/// Schema for the `PermissionDecisionApproveForSessionApprovalCommands` type. +/// Session-scoped approval details for specific command identifiers. /// The commands variant of . [Experimental(Diagnostics.Experimental)] public partial class PermissionDecisionApproveForSessionApprovalCommands : PermissionDecisionApproveForSessionApproval @@ -8724,7 +8880,7 @@ public partial class PermissionDecisionApproveForSessionApprovalCommands : Permi public required IList CommandIdentifiers { get; set; } } -/// Schema for the `PermissionDecisionApproveForSessionApprovalRead` type. +/// Session-scoped approval details for read-only filesystem operations. /// The read variant of . [Experimental(Diagnostics.Experimental)] public partial class PermissionDecisionApproveForSessionApprovalRead : PermissionDecisionApproveForSessionApproval @@ -8734,7 +8890,7 @@ public partial class PermissionDecisionApproveForSessionApprovalRead : Permissio public override string Kind => "read"; } -/// Schema for the `PermissionDecisionApproveForSessionApprovalWrite` type. +/// Session-scoped approval details for filesystem write operations. /// The write variant of . [Experimental(Diagnostics.Experimental)] public partial class PermissionDecisionApproveForSessionApprovalWrite : PermissionDecisionApproveForSessionApproval @@ -8744,7 +8900,7 @@ public partial class PermissionDecisionApproveForSessionApprovalWrite : Permissi public override string Kind => "write"; } -/// Schema for the `PermissionDecisionApproveForSessionApprovalMcp` type. +/// Session-scoped approval details for an MCP server tool, or all tools on the server when `toolName` is null. /// The mcp variant of . [Experimental(Diagnostics.Experimental)] public partial class PermissionDecisionApproveForSessionApprovalMcp : PermissionDecisionApproveForSessionApproval @@ -8762,7 +8918,7 @@ public partial class PermissionDecisionApproveForSessionApprovalMcp : Permission public string? ToolName { get; set; } } -/// Schema for the `PermissionDecisionApproveForSessionApprovalMcpSampling` type. +/// Session-scoped approval details for MCP sampling requests from a server. /// The mcp-sampling variant of . [Experimental(Diagnostics.Experimental)] public partial class PermissionDecisionApproveForSessionApprovalMcpSampling : PermissionDecisionApproveForSessionApproval @@ -8776,7 +8932,7 @@ public partial class PermissionDecisionApproveForSessionApprovalMcpSampling : Pe public required string ServerName { get; set; } } -/// Schema for the `PermissionDecisionApproveForSessionApprovalMemory` type. +/// Session-scoped approval details for writes to long-term memory. /// The memory variant of . [Experimental(Diagnostics.Experimental)] public partial class PermissionDecisionApproveForSessionApprovalMemory : PermissionDecisionApproveForSessionApproval @@ -8786,7 +8942,7 @@ public partial class PermissionDecisionApproveForSessionApprovalMemory : Permiss public override string Kind => "memory"; } -/// Schema for the `PermissionDecisionApproveForSessionApprovalCustomTool` type. +/// Session-scoped approval details for a custom tool, keyed by tool name. /// The custom-tool variant of . [Experimental(Diagnostics.Experimental)] public partial class PermissionDecisionApproveForSessionApprovalCustomTool : PermissionDecisionApproveForSessionApproval @@ -8800,7 +8956,7 @@ public partial class PermissionDecisionApproveForSessionApprovalCustomTool : Per public required string ToolName { get; set; } } -/// Schema for the `PermissionDecisionApproveForSessionApprovalExtensionManagement` type. +/// Session-scoped approval details for extension-management operations, optionally narrowed by operation. /// The extension-management variant of . [Experimental(Diagnostics.Experimental)] public partial class PermissionDecisionApproveForSessionApprovalExtensionManagement : PermissionDecisionApproveForSessionApproval @@ -8815,7 +8971,7 @@ public partial class PermissionDecisionApproveForSessionApprovalExtensionManagem public string? Operation { get; set; } } -/// Schema for the `PermissionDecisionApproveForSessionApprovalExtensionPermissionAccess` type. +/// Session-scoped approval details for an extension's permission-gated capability access, keyed by extension name. /// The extension-permission-access variant of . [Experimental(Diagnostics.Experimental)] public partial class PermissionDecisionApproveForSessionApprovalExtensionPermissionAccess : PermissionDecisionApproveForSessionApproval @@ -8829,7 +8985,7 @@ public partial class PermissionDecisionApproveForSessionApprovalExtensionPermiss public required string ExtensionName { get; set; } } -/// Schema for the `PermissionDecisionApproveForSession` type. +/// Permission-decision request variant to approve for the rest of the session, with optional tool approval or URL domain. /// The approve-for-session variant of . [Experimental(Diagnostics.Experimental)] public partial class PermissionDecisionApproveForSession : PermissionDecision @@ -8872,7 +9028,7 @@ public partial class PermissionDecisionApproveForLocationApproval } -/// Schema for the `PermissionDecisionApproveForLocationApprovalCommands` type. +/// Location-scoped approval details for specific command identifiers. /// The commands variant of . [Experimental(Diagnostics.Experimental)] public partial class PermissionDecisionApproveForLocationApprovalCommands : PermissionDecisionApproveForLocationApproval @@ -8886,7 +9042,7 @@ public partial class PermissionDecisionApproveForLocationApprovalCommands : Perm public required IList CommandIdentifiers { get; set; } } -/// Schema for the `PermissionDecisionApproveForLocationApprovalRead` type. +/// Location-scoped approval details for read-only filesystem operations. /// The read variant of . [Experimental(Diagnostics.Experimental)] public partial class PermissionDecisionApproveForLocationApprovalRead : PermissionDecisionApproveForLocationApproval @@ -8896,7 +9052,7 @@ public partial class PermissionDecisionApproveForLocationApprovalRead : Permissi public override string Kind => "read"; } -/// Schema for the `PermissionDecisionApproveForLocationApprovalWrite` type. +/// Location-scoped approval details for filesystem write operations. /// The write variant of . [Experimental(Diagnostics.Experimental)] public partial class PermissionDecisionApproveForLocationApprovalWrite : PermissionDecisionApproveForLocationApproval @@ -8906,7 +9062,7 @@ public partial class PermissionDecisionApproveForLocationApprovalWrite : Permiss public override string Kind => "write"; } -/// Schema for the `PermissionDecisionApproveForLocationApprovalMcp` type. +/// Location-scoped approval details for an MCP server tool, or all tools on the server when `toolName` is null. /// The mcp variant of . [Experimental(Diagnostics.Experimental)] public partial class PermissionDecisionApproveForLocationApprovalMcp : PermissionDecisionApproveForLocationApproval @@ -8924,7 +9080,7 @@ public partial class PermissionDecisionApproveForLocationApprovalMcp : Permissio public string? ToolName { get; set; } } -/// Schema for the `PermissionDecisionApproveForLocationApprovalMcpSampling` type. +/// Location-scoped approval details for MCP sampling requests from a server. /// The mcp-sampling variant of . [Experimental(Diagnostics.Experimental)] public partial class PermissionDecisionApproveForLocationApprovalMcpSampling : PermissionDecisionApproveForLocationApproval @@ -8938,7 +9094,7 @@ public partial class PermissionDecisionApproveForLocationApprovalMcpSampling : P public required string ServerName { get; set; } } -/// Schema for the `PermissionDecisionApproveForLocationApprovalMemory` type. +/// Location-scoped approval details for writes to long-term memory. /// The memory variant of . [Experimental(Diagnostics.Experimental)] public partial class PermissionDecisionApproveForLocationApprovalMemory : PermissionDecisionApproveForLocationApproval @@ -8948,7 +9104,7 @@ public partial class PermissionDecisionApproveForLocationApprovalMemory : Permis public override string Kind => "memory"; } -/// Schema for the `PermissionDecisionApproveForLocationApprovalCustomTool` type. +/// Location-scoped approval details for a custom tool, keyed by tool name. /// The custom-tool variant of . [Experimental(Diagnostics.Experimental)] public partial class PermissionDecisionApproveForLocationApprovalCustomTool : PermissionDecisionApproveForLocationApproval @@ -8962,7 +9118,7 @@ public partial class PermissionDecisionApproveForLocationApprovalCustomTool : Pe public required string ToolName { get; set; } } -/// Schema for the `PermissionDecisionApproveForLocationApprovalExtensionManagement` type. +/// Location-scoped approval details for extension-management operations, optionally narrowed by operation. /// The extension-management variant of . [Experimental(Diagnostics.Experimental)] public partial class PermissionDecisionApproveForLocationApprovalExtensionManagement : PermissionDecisionApproveForLocationApproval @@ -8977,7 +9133,7 @@ public partial class PermissionDecisionApproveForLocationApprovalExtensionManage public string? Operation { get; set; } } -/// Schema for the `PermissionDecisionApproveForLocationApprovalExtensionPermissionAccess` type. +/// Location-scoped approval details for an extension's permission-gated capability access, keyed by extension name. /// The extension-permission-access variant of . [Experimental(Diagnostics.Experimental)] public partial class PermissionDecisionApproveForLocationApprovalExtensionPermissionAccess : PermissionDecisionApproveForLocationApproval @@ -8991,7 +9147,7 @@ public partial class PermissionDecisionApproveForLocationApprovalExtensionPermis public required string ExtensionName { get; set; } } -/// Schema for the `PermissionDecisionApproveForLocation` type. +/// Permission-decision request variant to approve and persist a permission for a project location, with approval details and location key. /// The approve-for-location variant of . [Experimental(Diagnostics.Experimental)] public partial class PermissionDecisionApproveForLocation : PermissionDecision @@ -9009,7 +9165,7 @@ public partial class PermissionDecisionApproveForLocation : PermissionDecision public required string LocationKey { get; set; } } -/// Schema for the `PermissionDecisionApprovePermanently` type. +/// Permission-decision request variant to permanently approve a URL domain across sessions. /// The approve-permanently variant of . [Experimental(Diagnostics.Experimental)] public partial class PermissionDecisionApprovePermanently : PermissionDecision @@ -9023,7 +9179,7 @@ public partial class PermissionDecisionApprovePermanently : PermissionDecision public required string Domain { get; set; } } -/// Schema for the `PermissionDecisionReject` type. +/// Permission-decision request variant to reject a pending permission request, with optional feedback. /// The reject variant of . [Experimental(Diagnostics.Experimental)] public partial class PermissionDecisionReject : PermissionDecision @@ -9038,7 +9194,7 @@ public partial class PermissionDecisionReject : PermissionDecision public string? Feedback { get; set; } } -/// Schema for the `PermissionDecisionUserNotAvailable` type. +/// Permission-decision variant indicating no user was available to confirm the request. /// The user-not-available variant of . [Experimental(Diagnostics.Experimental)] public partial class PermissionDecisionUserNotAvailable : PermissionDecision @@ -9048,7 +9204,7 @@ public partial class PermissionDecisionUserNotAvailable : PermissionDecision public override string Kind => "user-not-available"; } -/// Schema for the `PermissionDecisionApproved` type. +/// Permission-decision variant indicating the request was approved. /// The approved variant of . [Experimental(Diagnostics.Experimental)] public partial class PermissionDecisionApproved : PermissionDecision @@ -9058,7 +9214,7 @@ public partial class PermissionDecisionApproved : PermissionDecision public override string Kind => "approved"; } -/// Schema for the `PermissionDecisionApprovedForSession` type. +/// Permission-decision variant indicating approval was remembered for the session, with approval details. /// The approved-for-session variant of . [Experimental(Diagnostics.Experimental)] public partial class PermissionDecisionApprovedForSession : PermissionDecision @@ -9072,7 +9228,7 @@ public partial class PermissionDecisionApprovedForSession : PermissionDecision public required UserToolSessionApproval Approval { get; set; } } -/// Schema for the `PermissionDecisionApprovedForLocation` type. +/// Permission-decision variant indicating approval was persisted for a project location, with approval details and location key. /// The approved-for-location variant of . [Experimental(Diagnostics.Experimental)] public partial class PermissionDecisionApprovedForLocation : PermissionDecision @@ -9090,7 +9246,7 @@ public partial class PermissionDecisionApprovedForLocation : PermissionDecision public required string LocationKey { get; set; } } -/// Schema for the `PermissionDecisionCancelled` type. +/// Permission-decision variant indicating the request was cancelled before use, with an optional reason. /// The cancelled variant of . [Experimental(Diagnostics.Experimental)] public partial class PermissionDecisionCancelled : PermissionDecision @@ -9105,7 +9261,7 @@ public partial class PermissionDecisionCancelled : PermissionDecision public string? Reason { get; set; } } -/// Schema for the `PermissionDecisionDeniedByRules` type. +/// Permission-decision variant indicating explicit denial by permission rules, with the matching rules. /// The denied-by-rules variant of . [Experimental(Diagnostics.Experimental)] public partial class PermissionDecisionDeniedByRules : PermissionDecision @@ -9119,7 +9275,7 @@ public partial class PermissionDecisionDeniedByRules : PermissionDecision public required IList Rules { get; set; } } -/// Schema for the `PermissionDecisionDeniedNoApprovalRuleAndCouldNotRequestFromUser` type. +/// Permission-decision variant indicating no approval rule matched and user confirmation was unavailable. /// The denied-no-approval-rule-and-could-not-request-from-user variant of . [Experimental(Diagnostics.Experimental)] public partial class PermissionDecisionDeniedNoApprovalRuleAndCouldNotRequestFromUser : PermissionDecision @@ -9129,7 +9285,7 @@ public partial class PermissionDecisionDeniedNoApprovalRuleAndCouldNotRequestFro public override string Kind => "denied-no-approval-rule-and-could-not-request-from-user"; } -/// Schema for the `PermissionDecisionDeniedInteractivelyByUser` type. +/// Permission-decision variant indicating the user denied an interactive prompt, with optional feedback and force-reject flag. /// The denied-interactively-by-user variant of . [Experimental(Diagnostics.Experimental)] public partial class PermissionDecisionDeniedInteractivelyByUser : PermissionDecision @@ -9149,7 +9305,7 @@ public partial class PermissionDecisionDeniedInteractivelyByUser : PermissionDec public bool? ForceReject { get; set; } } -/// Schema for the `PermissionDecisionDeniedByContentExclusionPolicy` type. +/// Permission-decision variant indicating denial by content-exclusion policy, with path and message. /// The denied-by-content-exclusion-policy variant of . [Experimental(Diagnostics.Experimental)] public partial class PermissionDecisionDeniedByContentExclusionPolicy : PermissionDecision @@ -9167,7 +9323,7 @@ public partial class PermissionDecisionDeniedByContentExclusionPolicy : Permissi public required string Path { get; set; } } -/// Schema for the `PermissionDecisionDeniedByPermissionRequestHook` type. +/// Permission-decision variant indicating denial by a permission request hook, with optional message and interrupt flag. /// The denied-by-permission-request-hook variant of . [Experimental(Diagnostics.Experimental)] public partial class PermissionDecisionDeniedByPermissionRequestHook : PermissionDecision @@ -9204,7 +9360,7 @@ internal sealed class PermissionDecisionRequest public string SessionId { get; set; } = string.Empty; } -/// Schema for the `PendingPermissionRequest` type. +/// Pending permission prompt reconstructed from event history, with request ID and user-facing prompt details. [Experimental(Diagnostics.Experimental)] public sealed class PendingPermissionRequest { @@ -9265,22 +9421,34 @@ internal sealed class PermissionsSetApproveAllRequest [Experimental(Diagnostics.Experimental)] public sealed class AllowAllPermissionSetResult { - /// Authoritative allow-all state after the mutation. + /// Authoritative full allow-all state after the mutation. [JsonPropertyName("enabled")] public bool Enabled { get; set; } + /// Authoritative allow-all mode after the mutation. + [JsonPropertyName("mode")] + public PermissionsAllowAllMode? Mode { get; set; } + /// Whether the operation succeeded. [JsonPropertyName("success")] public bool Success { get; set; } } -/// Whether to enable full allow-all permissions for the session. +/// Allow-all mode to apply for the session. [Experimental(Diagnostics.Experimental)] internal sealed class PermissionsSetAllowAllRequest { - /// Whether to enable full allow-all permissions. + /// Legacy full allow-all toggle. Prefer `mode`; when `mode` is omitted, `enabled: true` is treated as `mode: "on"` and any other value is treated as `mode: "off"`. [JsonPropertyName("enabled")] - public bool Enabled { get; set; } + public bool? Enabled { get; set; } + + /// Allow-all mode to apply. `on` enables full allow-all; `auto` enables advisory LLM auto-approval; `off` disables both. + [JsonPropertyName("mode")] + public PermissionsAllowAllMode? Mode { get; set; } + + /// Optional model id for the `auto` mode auto-approval LLM judging. Only meaningful when `mode` is `auto`; ignored otherwise. When omitted, the session's active model is used. + [JsonPropertyName("model")] + public string? Model { get; set; } /// Target session identifier. [JsonPropertyName("sessionId")] @@ -9291,13 +9459,17 @@ internal sealed class PermissionsSetAllowAllRequest public PermissionsSetAllowAllSource? Source { get; set; } } -/// Current full allow-all permission state. +/// Current allow-all permission mode. [Experimental(Diagnostics.Experimental)] public sealed class AllowAllPermissionState { /// Whether full allow-all permissions are currently active. [JsonPropertyName("enabled")] public bool Enabled { get; set; } + + /// Current allow-all mode. + [JsonPropertyName("mode")] + public PermissionsAllowAllMode? Mode { get; set; } } /// No parameters. @@ -9615,7 +9787,7 @@ public partial class PermissionsLocationsAddToolApprovalDetails } -/// Schema for the `PermissionsLocationsAddToolApprovalDetailsCommands` type. +/// Location-persisted tool approval details for specific command identifiers. /// The commands variant of . [Experimental(Diagnostics.Experimental)] public partial class PermissionsLocationsAddToolApprovalDetailsCommands : PermissionsLocationsAddToolApprovalDetails @@ -9629,7 +9801,7 @@ public partial class PermissionsLocationsAddToolApprovalDetailsCommands : Permis public required IList CommandIdentifiers { get; set; } } -/// Schema for the `PermissionsLocationsAddToolApprovalDetailsRead` type. +/// Location-persisted tool approval details for read-only filesystem operations. /// The read variant of . [Experimental(Diagnostics.Experimental)] public partial class PermissionsLocationsAddToolApprovalDetailsRead : PermissionsLocationsAddToolApprovalDetails @@ -9639,7 +9811,7 @@ public partial class PermissionsLocationsAddToolApprovalDetailsRead : Permission public override string Kind => "read"; } -/// Schema for the `PermissionsLocationsAddToolApprovalDetailsWrite` type. +/// Location-persisted tool approval details for filesystem write operations. /// The write variant of . [Experimental(Diagnostics.Experimental)] public partial class PermissionsLocationsAddToolApprovalDetailsWrite : PermissionsLocationsAddToolApprovalDetails @@ -9649,7 +9821,7 @@ public partial class PermissionsLocationsAddToolApprovalDetailsWrite : Permissio public override string Kind => "write"; } -/// Schema for the `PermissionsLocationsAddToolApprovalDetailsMcp` type. +/// Location-persisted tool approval details for an MCP server tool, or all tools when `toolName` is null. /// The mcp variant of . [Experimental(Diagnostics.Experimental)] public partial class PermissionsLocationsAddToolApprovalDetailsMcp : PermissionsLocationsAddToolApprovalDetails @@ -9667,7 +9839,7 @@ public partial class PermissionsLocationsAddToolApprovalDetailsMcp : Permissions public string? ToolName { get; set; } } -/// Schema for the `PermissionsLocationsAddToolApprovalDetailsMcpSampling` type. +/// Location-persisted tool approval details for MCP sampling requests from a server. /// The mcp-sampling variant of . [Experimental(Diagnostics.Experimental)] public partial class PermissionsLocationsAddToolApprovalDetailsMcpSampling : PermissionsLocationsAddToolApprovalDetails @@ -9681,7 +9853,7 @@ public partial class PermissionsLocationsAddToolApprovalDetailsMcpSampling : Per public required string ServerName { get; set; } } -/// Schema for the `PermissionsLocationsAddToolApprovalDetailsMemory` type. +/// Location-persisted tool approval details for writes to long-term memory. /// The memory variant of . [Experimental(Diagnostics.Experimental)] public partial class PermissionsLocationsAddToolApprovalDetailsMemory : PermissionsLocationsAddToolApprovalDetails @@ -9691,7 +9863,7 @@ public partial class PermissionsLocationsAddToolApprovalDetailsMemory : Permissi public override string Kind => "memory"; } -/// Schema for the `PermissionsLocationsAddToolApprovalDetailsCustomTool` type. +/// Location-persisted tool approval details for a custom tool, keyed by tool name. /// The custom-tool variant of . [Experimental(Diagnostics.Experimental)] public partial class PermissionsLocationsAddToolApprovalDetailsCustomTool : PermissionsLocationsAddToolApprovalDetails @@ -9705,7 +9877,7 @@ public partial class PermissionsLocationsAddToolApprovalDetailsCustomTool : Perm public required string ToolName { get; set; } } -/// Schema for the `PermissionsLocationsAddToolApprovalDetailsExtensionManagement` type. +/// Location-persisted tool approval details for extension-management operations, optionally narrowed by operation. /// The extension-management variant of . [Experimental(Diagnostics.Experimental)] public partial class PermissionsLocationsAddToolApprovalDetailsExtensionManagement : PermissionsLocationsAddToolApprovalDetails @@ -9720,7 +9892,7 @@ public partial class PermissionsLocationsAddToolApprovalDetailsExtensionManageme public string? Operation { get; set; } } -/// Schema for the `PermissionsLocationsAddToolApprovalDetailsExtensionPermissionAccess` type. +/// Location-persisted tool approval details for an extension's permission-gated capability access, keyed by extension name. /// The extension-permission-access variant of . [Experimental(Diagnostics.Experimental)] public partial class PermissionsLocationsAddToolApprovalDetailsExtensionPermissionAccess : PermissionsLocationsAddToolApprovalDetails @@ -10089,6 +10261,123 @@ internal sealed class MetadataContextInfoRequest public string SessionId { get; set; } = string.Empty; } +/// Successful compaction history for the session. +public sealed class MetadataContextAttributionResultContextAttributionCompactions +{ + /// Number of successful compactions in this session. + [JsonPropertyName("count")] + public long Count { get; set; } +} + +/// RPC data type for MetadataContextAttributionResultContextAttributionEntry operations. +public sealed class MetadataContextAttributionResultContextAttributionEntry +{ + /// Supplementary per-entry metadata (e.g. `messageCount`, `role`, `evictable`, `pluginSource`). Values are stringified; parse as needed and ignore unrecognized keys. + [JsonPropertyName("attributes")] + public IDictionary? Attributes { get; set; } + + /// Identifier for this entry, formed by joining its `kind` and source name (e.g. `tool:bash`, `skill:tmux`, `toolDefinition:bash`); unique within the snapshot. Use it to match the same entry across snapshots, to correlate with other APIs (skill/agent/MCP registries), and as the `parentId` target for nesting. Distinct from the human-facing `label`. + [JsonPropertyName("id")] + public string Id { get; set; } = string.Empty; + + /// Source category for this entry. Not a closed set — tolerate unknown values. Known values today: `skill`, `subagent`, `mcpServer`, `tool`, `system`, `toolDefinition`, `plugin`. + [JsonPropertyName("kind")] + public string Kind { get; set; } = string.Empty; + + /// Human-readable display label, e.g. `bash` or `skill: tmux`. Presentation-only; may be localized/reformatted without notice — do not key off it. + [JsonPropertyName("label")] + public string Label { get; set; } = string.Empty; + + /// Optional `id` of the parent entry: e.g. a `plugin` entry parenting its `skill`/`mcpServer` entries, or the `system` entry parenting `toolDefinition` entries. Omitted for top-level entries. + [JsonPropertyName("parentId")] + public string? ParentId { get; set; } + + /// Token count currently in context attributable to this entry. + [JsonPropertyName("tokens")] + public long Tokens { get; set; } +} + +/// Per-source token attribution snapshot for the current context window. The heaviest individual messages are available separately via `metadata.getContextHeaviestMessages`. +public sealed class MetadataContextAttributionResultContextAttribution +{ + /// Successful compaction history for the session. + [JsonPropertyName("compactions")] + public MetadataContextAttributionResultContextAttributionCompactions Compactions { get => field ??= new(); set; } + + /// Flat list of per-source attribution entries. Group by `kind` and render unrecognized kinds generically. Nesting and rollups are expressed via `parentId`. + [JsonPropertyName("entries")] + public IList Entries { get => field ??= []; set; } + + /// Total token count of the current context window the entries are measured against (system message + conversation messages + tool definitions — the same total reported by /context). Divide an entry's `tokens` by this to derive its share. + [JsonPropertyName("totalTokens")] + public long TotalTokens { get; set; } +} + +/// Per-source attribution breakdown for the session's current context window, or null if uninitialized. +[Experimental(Diagnostics.Experimental)] +public sealed class MetadataContextAttributionResult +{ + /// Per-source context-window attribution, or null if the session has not yet been initialized (no system prompt or tool metadata cached). + [JsonPropertyName("contextAttribution")] + public MetadataContextAttributionResultContextAttribution? ContextAttribution { get; set; } +} + +/// Identifies the target session. +[Experimental(Diagnostics.Experimental)] +internal sealed class SessionMetadataGetContextAttributionRequest +{ + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + +/// A single large message currently in context. +[Experimental(Diagnostics.Experimental)] +public sealed class ContextHeaviestMessage +{ + /// Stable identifier for this message within the snapshot. + [JsonPropertyName("id")] + public string Id { get; set; } = string.Empty; + + /// Human-readable source label, e.g. `tool: bash` or `skill: tmux`. Presentation-only. + [JsonPropertyName("label")] + public string Label { get; set; } = string.Empty; + + /// Role of the chat message (`user`, `assistant`, or `tool`). + [JsonPropertyName("role")] + public string Role { get; set; } = string.Empty; + + /// Token count currently in context for this individual message. + [JsonPropertyName("tokens")] + public long Tokens { get; set; } +} + +/// The heaviest individual messages in the session's context window, most-expensive first. +[Experimental(Diagnostics.Experimental)] +public sealed class MetadataContextHeaviestMessagesResult +{ + /// Heaviest messages, most-expensive first. + [JsonPropertyName("messages")] + public IList Messages { get => field ??= []; set; } + + /// Total token count of the current context window, so callers can compute each message's share without a second call. + [JsonPropertyName("totalTokens")] + public long TotalTokens { get; set; } +} + +/// Parameters for the heaviest-messages query. +[Experimental(Diagnostics.Experimental)] +internal sealed class MetadataContextHeaviestMessagesRequest +{ + /// Maximum number of messages to return, most-expensive first. Omit for the server default. + [JsonPropertyName("limit")] + public long? Limit { get; set; } + + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + /// Notify the session that its working directory context has changed. Emits a `session.context_changed` event so consumers (telemetry, OTel tracker, ACP, the timeline UI) can react. Use this when the host has detected a cwd/branch/repo change outside the session's normal lifecycle (e.g., after a shell command in interactive mode). [Experimental(Diagnostics.Experimental)] public sealed class MetadataRecordContextChangeResult @@ -10197,40 +10486,274 @@ internal sealed class MetadataRecomputeContextTokensRequest public string SessionId { get; set; } = string.Empty; } -/// Identifier of the spawned process, used to correlate streamed output and exit notifications. +/// Availability of built-in job tools surfaced to boundary consumers. [Experimental(Diagnostics.Experimental)] -public sealed class ShellExecResult +public sealed class SessionSettingsBuiltInToolAvailabilitySnapshot { - /// Unique identifier for tracking streamed output. - [JsonPropertyName("processId")] - public string ProcessId { get; set; } = string.Empty; + /// Gets or sets the createPullRequest value. + [JsonPropertyName("createPullRequest")] + public bool? CreatePullRequest { get; set; } + + /// Gets or sets the reportProgress value. + [JsonPropertyName("reportProgress")] + public bool? ReportProgress { get; set; } } -/// Shell command to run, with optional working directory and timeout in milliseconds. +/// Redacted job settings for a session. The job nonce is excluded. [Experimental(Diagnostics.Experimental)] -internal sealed class ShellExecRequest +public sealed class SessionSettingsJobSnapshot { - /// Shell command to execute. - [JsonPropertyName("command")] - public string Command { get; set; } = string.Empty; - - /// Working directory (defaults to session working directory). - [JsonPropertyName("cwd")] - public string? Cwd { get; set; } + /// Gets or sets the builtInToolAvailability value. + [JsonPropertyName("builtInToolAvailability")] + public SessionSettingsBuiltInToolAvailabilitySnapshot? BuiltInToolAvailability { get; set; } - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Gets or sets the eventType value. + [JsonPropertyName("eventType")] + public string? EventType { get; set; } - /// Timeout in milliseconds (default: 30000). - [JsonConverter(typeof(MillisecondsTimeSpanConverter))] - [JsonPropertyName("timeout")] - public TimeSpan? Timeout { get; set; } + /// Gets or sets the isTriggerJob value. + [JsonPropertyName("isTriggerJob")] + public bool? IsTriggerJob { get; set; } } -/// Indicates whether the signal was delivered; false if the process was unknown or already exited. +/// Redacted model routing settings for a session. [Experimental(Diagnostics.Experimental)] -public sealed class ShellKillResult +public sealed class SessionSettingsModelSnapshot +{ + /// Gets or sets the callbackUrl value. + [JsonPropertyName("callbackUrl")] + public string? CallbackUrl { get; set; } + + /// Gets or sets the defaultReasoningEffort value. + [JsonPropertyName("defaultReasoningEffort")] + public string? DefaultReasoningEffort { get; set; } + + /// Gets or sets the instanceId value. + [JsonPropertyName("instanceId")] + public string? InstanceId { get; set; } + + /// Gets or sets the model value. + [JsonPropertyName("model")] + public string? Model { get; set; } +} + +/// Online-evaluation settings safe to expose across the SDK boundary. +[Experimental(Diagnostics.Experimental)] +public sealed class SessionSettingsOnlineEvaluationSnapshot +{ + /// Gets or sets the disableOnlineEvaluation value. + [JsonPropertyName("disableOnlineEvaluation")] + public bool? DisableOnlineEvaluation { get; set; } + + /// Gets or sets the enableOnlineEvaluationOutputFile value. + [JsonPropertyName("enableOnlineEvaluationOutputFile")] + public bool? EnableOnlineEvaluationOutputFile { get; set; } +} + +/// Redacted repository and GitHub host settings for a session. +[Experimental(Diagnostics.Experimental)] +public sealed class SessionSettingsRepoSnapshot +{ + /// Gets or sets the branch value. + [JsonPropertyName("branch")] + public string? Branch { get; set; } + + /// Gets or sets the commit value. + [JsonPropertyName("commit")] + public string? Commit { get; set; } + + /// Gets or sets the host value. + [JsonPropertyName("host")] + public string? Host { get; set; } + + /// Gets or sets the hostProtocol value. + [JsonPropertyName("hostProtocol")] + public string? HostProtocol { get; set; } + + /// Gets or sets the id value. + [JsonPropertyName("id")] + public double? Id { get; set; } + + /// Gets or sets the name value. + [JsonPropertyName("name")] + public string? Name { get; set; } + + /// Gets or sets the ownerId value. + [JsonPropertyName("ownerId")] + public double? OwnerId { get; set; } + + /// Gets or sets the ownerName value. + [JsonPropertyName("ownerName")] + public string? OwnerName { get; set; } + + /// Gets or sets the prCommitCount value. + [JsonPropertyName("prCommitCount")] + public double? PrCommitCount { get; set; } + + /// Gets or sets the readWrite value. + [JsonPropertyName("readWrite")] + public bool? ReadWrite { get; set; } + + /// Gets or sets the secretScanningUrl value. + [JsonPropertyName("secretScanningUrl")] + public string? SecretScanningUrl { get; set; } + + /// Gets or sets the serverUrl value. + [JsonPropertyName("serverUrl")] + public string? ServerUrl { get; set; } +} + +/// Redacted validation and memory-tool settings for a session. +[Experimental(Diagnostics.Experimental)] +public sealed class SessionSettingsValidationSnapshot +{ + /// Gets or sets the advisoryEnabled value. + [JsonPropertyName("advisoryEnabled")] + public bool? AdvisoryEnabled { get; set; } + + /// Gets or sets the codeqlEnabled value. + [JsonPropertyName("codeqlEnabled")] + public bool? CodeqlEnabled { get; set; } + + /// Gets or sets the codeReviewEnabled value. + [JsonPropertyName("codeReviewEnabled")] + public bool? CodeReviewEnabled { get; set; } + + /// Gets or sets the codeReviewModel value. + [JsonPropertyName("codeReviewModel")] + public string? CodeReviewModel { get; set; } + + /// Gets or sets the dependabotTimeout value. + [JsonPropertyName("dependabotTimeout")] + public double? DependabotTimeout { get; set; } + + /// Gets or sets the memoryStoreEnabled value. + [JsonPropertyName("memoryStoreEnabled")] + public bool? MemoryStoreEnabled { get; set; } + + /// Gets or sets the memoryVoteEnabled value. + [JsonPropertyName("memoryVoteEnabled")] + public bool? MemoryVoteEnabled { get; set; } + + /// Gets or sets the secretScanningEnabled value. + [JsonPropertyName("secretScanningEnabled")] + public bool? SecretScanningEnabled { get; set; } + + /// Gets or sets the timeout value. + [JsonPropertyName("timeout")] + public double? Timeout { get; set; } +} + +/// Redacted, serializable view of session runtime settings for SDK boundary consumers. Secrets and raw feature flags are intentionally excluded. +[Experimental(Diagnostics.Experimental)] +internal sealed class SessionSettingsSnapshot +{ + /// Gets or sets the clientName value. + [JsonPropertyName("clientName")] + public string? ClientName { get; set; } + + /// Gets or sets the job value. + [JsonPropertyName("job")] + public SessionSettingsJobSnapshot Job { get => field ??= new(); set; } + + /// Gets or sets the model value. + [JsonPropertyName("model")] + public SessionSettingsModelSnapshot Model { get => field ??= new(); set; } + + /// Gets or sets the onlineEvaluation value. + [JsonPropertyName("onlineEvaluation")] + public SessionSettingsOnlineEvaluationSnapshot OnlineEvaluation { get => field ??= new(); set; } + + /// Gets or sets the repo value. + [JsonPropertyName("repo")] + public SessionSettingsRepoSnapshot Repo { get => field ??= new(); set; } + + /// Gets or sets the startTimeMs value. + [JsonPropertyName("startTimeMs")] + public double? StartTimeMs { get; set; } + + /// Gets or sets the timeoutMs value. + [JsonPropertyName("timeoutMs")] + public double? TimeoutMs { get; set; } + + /// Gets or sets the validation value. + [JsonPropertyName("validation")] + public SessionSettingsValidationSnapshot Validation { get => field ??= new(); set; } + + /// Gets or sets the version value. + [JsonPropertyName("version")] + public string? Version { get; set; } +} + +/// Identifies the target session. +[Experimental(Diagnostics.Experimental)] +internal sealed class SessionSettingsSnapshotRequest +{ + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + +/// Result of evaluating a Rust-owned settings predicate. +[Experimental(Diagnostics.Experimental)] +internal sealed class SessionSettingsEvaluatePredicateResult +{ + /// Gets or sets the enabled value. + [JsonPropertyName("enabled")] + public bool Enabled { get; set; } +} + +/// Named Rust-owned settings predicate to evaluate for this session. +[Experimental(Diagnostics.Experimental)] +internal sealed class SessionSettingsEvaluatePredicateRequest +{ + /// Predicate name. The runtime owns the raw feature-flag names and composition logic. + [JsonPropertyName("name")] + public SessionSettingsPredicateName Name { get; set; } + + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; + + /// Tool name for tool-scoped predicates such as trivial-change handling. + [JsonPropertyName("toolName")] + public string? ToolName { get; set; } +} + +/// Identifier of the spawned process, used to correlate streamed output and exit notifications. +[Experimental(Diagnostics.Experimental)] +public sealed class ShellExecResult +{ + /// Unique identifier for tracking streamed output. + [JsonPropertyName("processId")] + public string ProcessId { get; set; } = string.Empty; +} + +/// Shell command to run, with optional working directory and timeout in milliseconds. +[Experimental(Diagnostics.Experimental)] +internal sealed class ShellExecRequest +{ + /// Shell command to execute. + [JsonPropertyName("command")] + public string Command { get; set; } = string.Empty; + + /// Working directory (defaults to session working directory). + [JsonPropertyName("cwd")] + public string? Cwd { get; set; } + + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; + + /// Timeout in milliseconds (default: 30000). + [JsonConverter(typeof(MillisecondsTimeSpanConverter))] + [JsonPropertyName("timeout")] + public TimeSpan? Timeout { get; set; } +} + +/// Indicates whether the signal was delivered; false if the process was unknown or already exited. +[Experimental(Diagnostics.Experimental)] +public sealed class ShellKillResult { /// Whether the signal was sent successfully. [JsonPropertyName("killed")] @@ -10474,7 +10997,7 @@ internal sealed class SessionHistorySummarizeForHandoffRequest public string SessionId { get; set; } = string.Empty; } -/// Schema for the `QueuePendingItems` type. +/// User-facing pending queue entry, with kind and display text for a queued message, slash command, or model change. [Experimental(Diagnostics.Experimental)] public sealed class QueuePendingItems { @@ -10618,7 +11141,7 @@ public sealed class RegisterEventInterestResult [Experimental(Diagnostics.Experimental)] internal sealed class RegisterEventInterestParams { - /// The event type the consumer wants the runtime to treat as 'observed' for behavior-switching gating. Some runtime code paths inspect whether any consumer is interested in a specific event type and choose a different implementation accordingly (e.g. `mcp.oauth_required`: when interest is registered the runtime delegates the full interactive OAuth flow to the consumer; when no interest is registered the runtime installs a browserless fallback that silently reuses cached tokens). SDK clients that long-poll events do NOT automatically appear as listeners to these gating checks — they must explicitly call `registerInterest` for each event type they want the runtime to count as having a consumer. Multiple registrations for the same event type from the same or different consumers are tracked independently and must each be released. See: `mcp.oauth_required`, `sampling.requested`, `auto_mode_switch.requested`, `session_limits_exhausted.requested`, `user_input.requested`, `elicitation.requested`, `command.queued`, `exit_plan_mode.requested`. + /// The event type the consumer wants the runtime to treat as 'observed' for behavior-switching gating. Some runtime code paths inspect whether any consumer is interested in a specific event type and choose a different implementation accordingly (e.g. `mcp.oauth_required`: when interest is registered the runtime delegates OAuth token acquisition to the consumer; when no interest is registered OAuth-required servers become needs-auth). SDK clients that long-poll events do NOT automatically appear as listeners to these gating checks — they must explicitly call `registerInterest` for each event type they want the runtime to count as having a consumer. Multiple registrations for the same event type from the same or different consumers are tracked independently and must each be released. See: `mcp.oauth_required`, `sampling.requested`, `auto_mode_switch.requested`, `session_limits_exhausted.requested`, `user_input.requested`, `elicitation.requested`, `command.queued`, `exit_plan_mode.requested`. [JsonPropertyName("eventType")] public string EventType { get; set; } = string.Empty; @@ -10683,7 +11206,7 @@ public sealed class UsageMetricsModelMetricRequests public long Count { get; set; } } -/// Schema for the `UsageMetricsModelMetricTokenDetail` type. +/// Per-model token-detail entry containing the accumulated token count for one token type. [Experimental(Diagnostics.Experimental)] public sealed class UsageMetricsModelMetricTokenDetail { @@ -10717,7 +11240,7 @@ public sealed class UsageMetricsModelMetricUsage public long? ReasoningTokens { get; set; } } -/// Schema for the `UsageMetricsModelMetric` type. +/// Per-model usage metrics, including request counts/costs, token usage, nano-AI units, and per-token-type details. [Experimental(Diagnostics.Experimental)] public sealed class UsageMetricsModelMetric { @@ -10738,7 +11261,7 @@ public sealed class UsageMetricsModelMetric public UsageMetricsModelMetricUsage Usage { get => field ??= new(); set; } } -/// Schema for the `UsageMetricsTokenDetail` type. +/// Session-wide token-detail entry containing the accumulated token count for one token type. [Experimental(Diagnostics.Experimental)] public sealed class UsageMetricsTokenDetail { @@ -10922,7 +11445,7 @@ internal sealed class VisibilitySetRequest public SessionVisibilityStatus Status { get; set; } } -/// Schema for the `ScheduleEntry` type. +/// Scheduled prompt entry with ID, timing (`intervalMs`, `cron`, or `at`), prompt text, recurrence, and next run time. [Experimental(Diagnostics.Experimental)] public sealed class ScheduleEntry { @@ -11222,7 +11745,7 @@ public sealed class SessionFsReaddirRequest public string SessionId { get; set; } = string.Empty; } -/// Schema for the `SessionFsReaddirWithTypesEntry` type. +/// Directory entry returned by session filesystem `readdirWithTypes`, with name and entry type. [Experimental(Diagnostics.Experimental)] public sealed class SessionFsReaddirWithTypesEntry { @@ -11515,14 +12038,26 @@ public sealed class LlmInferenceHttpRequestStartResult [Experimental(Diagnostics.Experimental)] public sealed class LlmInferenceHttpRequestStartRequest { + /// Stable per-agent-instance id attributing this request to a specific agent trajectory. Present when the request originates from an agent turn; absent for requests issued outside any agent context (e.g. some SDK callers). A request with an `agentId` but no `parentAgentId` is a root-agent request; one carrying both is a subagent request. Sourced from the runtime's per-request agent context and surfaced on the envelope independently of transport, so it is available for both first-party (CAPI) and BYOK/custom-provider requests; on the CAPI transport the runtime derives the upstream `X-Agent-Task-Id` header from this same context. Consumers routing each provider call to a training trajectory should key on this rather than on lifecycle events, since it is available on the request path before sampling. + [JsonPropertyName("agentId")] + public string? AgentId { get; set; } + /// Gets or sets the headers value. [JsonPropertyName("headers")] public IDictionary> Headers { get => field ??= new Dictionary>(); set; } + /// Coarse classification of the interaction that produced this request. Open string for forward-compatibility; known values include `conversation-agent`, `conversation-subagent`, `conversation-sampling`, `conversation-background`, `conversation-compaction`, and `conversation-user`. Absent when the runtime did not classify the request. Comes from the runtime's per-request agent context independently of transport; on the CAPI transport the runtime derives the upstream `X-Interaction-Type` header from this same context. + [JsonPropertyName("interactionType")] + public string? InteractionType { get; set; } + /// HTTP method, e.g. GET, POST. [JsonPropertyName("method")] public string Method { get; set; } = string.Empty; + /// Id of the parent agent that spawned the agent issuing this request. Present only for subagent requests; absent for root-agent requests and non-agent requests. Combined with `agentId`, this lets consumers attribute a call to a child trajectory versus the root. Like `agentId`, it comes from the runtime's per-request agent context independently of transport; on the CAPI transport the runtime derives the upstream `X-Parent-Agent-Id` header from this same context. + [JsonPropertyName("parentAgentId")] + public string? ParentAgentId { get; set; } + /// Opaque runtime-minted id, unique per in-flight request. The SDK uses this to correlate httpRequestChunk frames and to address its httpResponseStart / httpResponseChunk replies back to the runtime. [JsonPropertyName("requestId")] public string RequestId { get; set; } = string.Empty; @@ -11665,7 +12200,7 @@ public sealed class GitHubTelemetryEvent public string? SessionId { get; set; } } -/// Payload for a `gitHubTelemetry.event` notification: a single GitHub telemetry event the runtime forwards to a host connection that opted into telemetry forwarding for the session. +/// Payload for a `gitHubTelemetry.event` notification: a single GitHub telemetry event the runtime forwards to a host connection that opted into telemetry forwarding during the `server.connect` handshake. [Experimental(Diagnostics.Experimental)] public sealed class GitHubTelemetryNotification { @@ -11677,9 +12212,9 @@ public sealed class GitHubTelemetryNotification [JsonPropertyName("restricted")] public bool Restricted { get; set; } - /// Session the telemetry event belongs to. + /// Session the telemetry event belongs to, when it is session-scoped. Omitted for sessionless events (for example, `server.sendTelemetry` calls with no session id), which are still forwarded to opted-in connections. [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + public string? SessionId { get; set; } } /// Resolved Anthropic adaptive-thinking capability for a model. @@ -13851,6 +14386,264 @@ public override void Write(Utf8JsonWriter writer, AuthInfoType value, JsonSerial } +/// Source category for a collected debug bundle entry. +[Experimental(Diagnostics.Experimental)] +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct DebugCollectLogsSource : IEquatable +{ + private readonly string? _value; + + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public DebugCollectLogsSource(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } + + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; + + /// Session event log. + public static DebugCollectLogsSource Events { get; } = new("events"); + + /// Process log for the session. + public static DebugCollectLogsSource ProcessLog { get; } = new("process-log"); + + /// Interactive shell log for the session. + public static DebugCollectLogsSource ShellLog { get; } = new("shell-log"); + + /// Caller-provided diagnostic entry. + public static DebugCollectLogsSource Additional { get; } = new("additional"); + + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(DebugCollectLogsSource left, DebugCollectLogsSource right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(DebugCollectLogsSource left, DebugCollectLogsSource right) => !(left == right); + + /// + public override bool Equals(object? obj) => obj is DebugCollectLogsSource other && Equals(other); + + /// + public bool Equals(DebugCollectLogsSource 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 DebugCollectLogsSource Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } + + /// + public override void Write(Utf8JsonWriter writer, DebugCollectLogsSource value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(DebugCollectLogsSource)); + } + } +} + + +/// Destination kind that was written. +[Experimental(Diagnostics.Experimental)] +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct DebugCollectLogsResultKind : IEquatable +{ + private readonly string? _value; + + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public DebugCollectLogsResultKind(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } + + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; + + /// A .tgz archive was written. + public static DebugCollectLogsResultKind Archive { get; } = new("archive"); + + /// A directory containing redacted files was written. + public static DebugCollectLogsResultKind Directory { get; } = new("directory"); + + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(DebugCollectLogsResultKind left, DebugCollectLogsResultKind right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(DebugCollectLogsResultKind left, DebugCollectLogsResultKind right) => !(left == right); + + /// + public override bool Equals(object? obj) => obj is DebugCollectLogsResultKind other && Equals(other); + + /// + public bool Equals(DebugCollectLogsResultKind 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 DebugCollectLogsResultKind Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } + + /// + public override void Write(Utf8JsonWriter writer, DebugCollectLogsResultKind value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(DebugCollectLogsResultKind)); + } + } +} + + +/// Kind of caller-provided debug log entry. +[Experimental(Diagnostics.Experimental)] +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct DebugCollectLogsEntryKind : IEquatable +{ + private readonly string? _value; + + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public DebugCollectLogsEntryKind(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } + + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; + + /// Include a single server-local file. + public static DebugCollectLogsEntryKind File { get; } = new("file"); + + /// Include files from a server-local directory recursively. + public static DebugCollectLogsEntryKind Directory { get; } = new("directory"); + + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(DebugCollectLogsEntryKind left, DebugCollectLogsEntryKind right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(DebugCollectLogsEntryKind left, DebugCollectLogsEntryKind right) => !(left == right); + + /// + public override bool Equals(object? obj) => obj is DebugCollectLogsEntryKind other && Equals(other); + + /// + public bool Equals(DebugCollectLogsEntryKind 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 DebugCollectLogsEntryKind Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } + + /// + public override void Write(Utf8JsonWriter writer, DebugCollectLogsEntryKind value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(DebugCollectLogsEntryKind)); + } + } +} + + +/// How a collected debug entry should be redacted before being staged. +[Experimental(Diagnostics.Experimental)] +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct DebugCollectLogsRedaction : IEquatable +{ + private readonly string? _value; + + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public DebugCollectLogsRedaction(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } + + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; + + /// Redact the file as plain UTF-8 log text. + public static DebugCollectLogsRedaction PlainText { get; } = new("plain-text"); + + /// Redact each non-empty line as a session event JSON object, falling back to plain-text redaction for malformed lines. + public static DebugCollectLogsRedaction EventsJsonl { get; } = new("events-jsonl"); + + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(DebugCollectLogsRedaction left, DebugCollectLogsRedaction right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(DebugCollectLogsRedaction left, DebugCollectLogsRedaction right) => !(left == right); + + /// + public override bool Equals(object? obj) => obj is DebugCollectLogsRedaction other && Equals(other); + + /// + public bool Equals(DebugCollectLogsRedaction 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 DebugCollectLogsRedaction Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } + + /// + public override void Write(Utf8JsonWriter writer, DebugCollectLogsRedaction value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(DebugCollectLogsRedaction)); + } + } +} + + /// Allowed values for the `WorkspacesWorkspaceDetailsHostType` enumeration. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] @@ -16551,6 +17344,72 @@ public override void Write(Utf8JsonWriter writer, PermissionsSetApproveAllSource } +/// Current or requested allow-all mode. +[Experimental(Diagnostics.Experimental)] +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct PermissionsAllowAllMode : IEquatable +{ + private readonly string? _value; + + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public PermissionsAllowAllMode(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } + + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; + + /// Permission requests follow the normal approval flow. + public static PermissionsAllowAllMode Off { get; } = new("off"); + + /// Tool, path, and URL permission requests are automatically approved. + public static PermissionsAllowAllMode On { get; } = new("on"); + + /// Permission requests follow the normal approval flow with an LLM advisory recommendation attached; clients may choose to auto-approve requests the judge evaluated as acceptable. + public static PermissionsAllowAllMode Auto { get; } = new("auto"); + + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(PermissionsAllowAllMode left, PermissionsAllowAllMode right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(PermissionsAllowAllMode left, PermissionsAllowAllMode right) => !(left == right); + + /// + public override bool Equals(object? obj) => obj is PermissionsAllowAllMode other && Equals(other); + + /// + public bool Equals(PermissionsAllowAllMode 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 PermissionsAllowAllMode Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } + + /// + public override void Write(Utf8JsonWriter writer, PermissionsAllowAllMode value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(PermissionsAllowAllMode)); + } + } +} + + /// Optional source for allow-all telemetry. Defaults to `rpc` when omitted for SDK callers. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] @@ -17001,6 +17860,120 @@ public override void Write(Utf8JsonWriter writer, SessionWorkingDirectoryContext } +/// Rust-owned settings predicates exposed across the SDK boundary. Raw feature-flag names are intentionally not part of the contract. +[Experimental(Diagnostics.Experimental)] +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct SessionSettingsPredicateName : IEquatable +{ + private readonly string? _value; + + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public SessionSettingsPredicateName(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } + + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; + + /// Whether the security-tools feature flag enables security tool wiring. + public static SessionSettingsPredicateName SecurityToolsEnabled { get; } = new("securityToolsEnabled"); + + /// Whether third-party security tools should receive the security prompt. + public static SessionSettingsPredicateName ThirdPartySecurityPromptEnabled { get; } = new("thirdPartySecurityPromptEnabled"); + + /// Whether validation may run in parallel. + public static SessionSettingsPredicateName ParallelValidationEnabled { get; } = new("parallelValidationEnabled"); + + /// Whether runtime timing telemetry is enabled. + public static SessionSettingsPredicateName RuntimeTimingTelemetryEnabled { get; } = new("runtimeTimingTelemetryEnabled"); + + /// Whether the co-author hook is enabled. + public static SessionSettingsPredicateName CoAuthorHookEnabled { get; } = new("coAuthorHookEnabled"); + + /// Whether Chronicle integration is enabled. + public static SessionSettingsPredicateName ChronicleEnabled { get; } = new("chronicleEnabled"); + + /// Whether content-exclusion policy may self-fetch data. + public static SessionSettingsPredicateName ContentExclusionSelfFetchEnabled { get; } = new("contentExclusionSelfFetchEnabled"); + + /// Whether Claude Opus token-limit caps should be applied. + public static SessionSettingsPredicateName CapClaudeOpusTokenLimitsEnabled { get; } = new("capClaudeOpusTokenLimitsEnabled"); + + /// Whether code-review behavior is enabled. + public static SessionSettingsPredicateName CodeReviewFeatureEnabled { get; } = new("codeReviewFeatureEnabled"); + + /// Whether CCA should use the TypeScript autofind behavior. + public static SessionSettingsPredicateName CcaUseTsAutofindEnabled { get; } = new("ccaUseTsAutofindEnabled"); + + /// Whether the dependency checker is enabled. + public static SessionSettingsPredicateName DependencyCheckerEnabled { get; } = new("dependencyCheckerEnabled"); + + /// Whether the Dependabot checker is enabled. + public static SessionSettingsPredicateName DependabotCheckerEnabled { get; } = new("dependabotCheckerEnabled"); + + /// Whether the CodeQL checker is enabled. + public static SessionSettingsPredicateName CodeqlCheckerEnabled { get; } = new("codeqlCheckerEnabled"); + + /// Whether trivial-change handling is enabled. + public static SessionSettingsPredicateName TrivialChangeEnabled { get; } = new("trivialChangeEnabled"); + + /// Whether trivial-change skip behavior is enabled. + public static SessionSettingsPredicateName TrivialChangeSkipEnabled { get; } = new("trivialChangeSkipEnabled"); + + /// Whether trivial-change handling is enabled for code review. + public static SessionSettingsPredicateName TrivialChangeEnabledForCodeReview { get; } = new("trivialChangeEnabledForCodeReview"); + + /// Whether trivial-change skip behavior is enabled for code review. + public static SessionSettingsPredicateName TrivialChangeSkipEnabledForCodeReview { get; } = new("trivialChangeSkipEnabledForCodeReview"); + + /// Whether trivial-change handling is enabled for a specific tool. + public static SessionSettingsPredicateName TrivialChangeEnabledForTool { get; } = new("trivialChangeEnabledForTool"); + + /// Whether trivial-change skip behavior is enabled for a specific tool. + public static SessionSettingsPredicateName TrivialChangeSkipEnabledForTool { get; } = new("trivialChangeSkipEnabledForTool"); + + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(SessionSettingsPredicateName left, SessionSettingsPredicateName right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(SessionSettingsPredicateName left, SessionSettingsPredicateName right) => !(left == right); + + /// + public override bool Equals(object? obj) => obj is SessionSettingsPredicateName other && Equals(other); + + /// + public bool Equals(SessionSettingsPredicateName 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 SessionSettingsPredicateName Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } + + /// + public override void Write(Utf8JsonWriter writer, SessionSettingsPredicateName value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(SessionSettingsPredicateName)); + } + } +} + + /// Signal to send (default: SIGTERM). [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] @@ -17663,12 +18636,13 @@ public async Task PingAsync(string? message = null, CancellationToke /// Performs the SDK server connection handshake and validates the optional connection token. Marked internal because this is JSON-RPC transport plumbing invoked automatically by an SDK client's own `connect()` wrapper, not a user-facing method. Stays internal as long as the SDK client owns the handshake; would only become public if the SDK ever exposed the raw schema surface to consumers without a connection wrapper. /// Connection token; required when the server was started with COPILOT_CONNECTION_TOKEN. + /// Opt this connection in to GitHub telemetry forwarding for its lifetime. When set, the runtime forwards every internal telemetry event it emits — across all sessions, plus sessionless events — to this connection over the `gitHubTelemetry.event` notification, in addition to the runtime's normal GitHub/CTS emission (dual-write). Intended for first-party hosts that re-emit the events into their own telemetry stores. Both unrestricted and restricted events are forwarded, each tagged with a `restricted` discriminator; a backstop drops restricted events when restricted telemetry is disabled. /// The to monitor for cancellation requests. The default is . /// Handshake result reporting the server's protocol version and package version on success. [Experimental(Diagnostics.Experimental)] - internal async Task ConnectAsync(string? token = null, CancellationToken cancellationToken = default) + internal async Task ConnectAsync(string? token = null, bool? enableGitHubTelemetryForwarding = null, CancellationToken cancellationToken = default) { - var request = new ConnectRequest { Token = token }; + var request = new ConnectRequest { Token = token, EnableGitHubTelemetryForwarding = enableGitHubTelemetryForwarding }; return await CopilotClient.InvokeRpcAsync(_rpc, "connect", [request], cancellationToken); } @@ -18775,17 +19749,6 @@ public async Task GetRemoteControlStatusAsync(Cancell return await CopilotClient.InvokeRpcAsync(_rpc, "sessions.getRemoteControlStatus", [], cancellationToken); } - /// Cursor-based long-poll for sessions spawned by the runtime (e.g. in response to a Mission Control `start_session` command). The cursor is an opaque token; pass it back to receive only spawn events that occurred AFTER the cursor was issued. Omit the cursor on the first call to receive any events buffered since the runtime started. Internal: this is a CLI background-daemon plumbing primitive. SDK consumers that need to react to runtime-spawned sessions should subscribe to a higher-level event stream rather than driving a long-poll loop. - /// Opaque cursor returned by a previous poll. Omit on the first call to receive any spawn events buffered since the runtime started. - /// Milliseconds to wait for new spawn events when the cursor is at the tail. 0 (default) returns immediately even if no events are buffered. Capped at 60000ms. - /// The to monitor for cancellation requests. The default is . - /// Batch of spawn events plus a cursor for follow-up polls. - internal async Task PollSpawnedSessionsAsync(string? cursor = null, TimeSpan? waitMs = null, CancellationToken cancellationToken = default) - { - var request = new SessionsPollSpawnedSessionsRequest { Cursor = cursor, Wait = waitMs }; - return await CopilotClient.InvokeRpcAsync(_rpc, "sessions.pollSpawnedSessions", [request], cancellationToken); - } - /// Registers extension-provided tools on the given session, gated by an optional `enabled` callback. Returns an opaque unsubscribe function the caller must invoke to deregister the tools when the extension is torn down. Marked internal because `loader`, `enabled`, and the returned `unsubscribe` are in-process handles that cannot cross the JSON-RPC boundary. Disappears once extension discovery / launch / tool registration are owned by the runtime: SDK consumers will pass pure config (search paths, disabled ids) via `SessionOptions` and the runtime will resolve, launch, register, and tear down extensions itself. /// Session to register extension tools on. /// In-process ExtensionLoader handle (CLI-only optimization). Marked internal: this field is excluded from the public SDK surface. When the CLI migrates to a process-separated SDK, extension discovery/launch moves entirely into the runtime — the CLI passes pure config (search paths, disabled ids) via SessionOptions instead. @@ -18861,6 +19824,12 @@ internal SessionRpc(CopilotSession session) Interlocked.CompareExchange(ref field, new(_session), null) ?? field; + /// Debug APIs. + public DebugApi Debug => + field ?? + Interlocked.CompareExchange(ref field, new(_session), null) ?? + field; + /// Canvas APIs. public CanvasApi Canvas => field ?? @@ -19005,6 +19974,12 @@ internal SessionRpc(CopilotSession session) Interlocked.CompareExchange(ref field, new(_session), null) ?? field; + /// Settings APIs. + public SettingsApi Settings => + field ?? + Interlocked.CompareExchange(ref field, new(_session), null) ?? + field; + /// Shell APIs. public ShellApi Shell => field ?? @@ -19171,6 +20146,33 @@ public async Task SetCredentialsAsync(AuthInfo? cre } } +/// Provides session-scoped Debug APIs. +[Experimental(Diagnostics.Experimental)] +public sealed class DebugApi +{ + private readonly CopilotSession _session; + + internal DebugApi(CopilotSession session) + { + _session = session; + } + + /// Collects a redacted session debug log bundle into a local archive or staging directory. The runtime includes session-owned logs by default and accepts caller-provided diagnostic entries so host applications can add their own files without changing this API shape. + /// Where the redacted bundle should be written. Use `archive` to produce a .tgz, or `directory` to stage redacted files for caller-managed upload/post-processing. + /// Which built-in session diagnostics to include. Omitted fields default to true. + /// Caller-provided server-local files or directories to include in addition to the runtime's built-in session diagnostics. This lets host applications add their own diagnostics without changing the API shape. + /// The to monitor for cancellation requests. The default is . + /// Result of collecting a redacted debug bundle. + public async Task CollectLogsAsync(DebugCollectLogsDestination destination, DebugCollectLogsInclude? include = null, IList? additionalEntries = null, CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(destination); + _session.ThrowIfDisposed(); + + var request = new DebugCollectLogsRequest { SessionId = _session.SessionId, Destination = destination, Include = include, AdditionalEntries = additionalEntries }; + return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.debug.collectLogs", [request], cancellationToken); + } +} + /// Provides session-scoped Canvas APIs. [Experimental(Diagnostics.Experimental)] public sealed class CanvasApi @@ -19294,16 +20296,17 @@ public async Task GetCurrentAsync(CancellationToken cancellationTo /// Model selection id to switch to, as returned by `list`. A bare id (e.g. `claude-sonnet-4.6`) names a Copilot (CAPI) model; a provider-qualified id (`provider/id`, e.g. `acme/claude-sonnet`) targets a registry BYOK model. /// Reasoning effort level to use for the model. "none" disables reasoning. /// Reasoning summary mode to request for supported model clients. + /// Output verbosity level to request for supported models. /// Override individual model capabilities resolved by the runtime. /// Explicit context tier for the selected model. `"default"` / `"long_context"` apply the requested tier; omit this field to use normal model behavior with no explicit tier. /// The to monitor for cancellation requests. The default is . /// The model identifier active on the session after the switch. - public async Task SwitchToAsync(string modelId, string? reasoningEffort = null, ReasoningSummary? reasoningSummary = null, ModelCapabilitiesOverride? modelCapabilities = null, ContextTier? contextTier = null, CancellationToken cancellationToken = default) + public async Task SwitchToAsync(string modelId, string? reasoningEffort = null, ReasoningSummary? reasoningSummary = null, Verbosity? verbosity = null, ModelCapabilitiesOverride? modelCapabilities = null, ContextTier? contextTier = null, CancellationToken cancellationToken = default) { ArgumentNullException.ThrowIfNull(modelId); _session.ThrowIfDisposed(); - var request = new ModelSwitchToRequest { SessionId = _session.SessionId, ModelId = modelId, ReasoningEffort = reasoningEffort, ReasoningSummary = reasoningSummary, ModelCapabilities = modelCapabilities, ContextTier = contextTier }; + var request = new ModelSwitchToRequest { SessionId = _session.SessionId, ModelId = modelId, ReasoningEffort = reasoningEffort, ReasoningSummary = reasoningSummary, Verbosity = verbosity, ModelCapabilities = modelCapabilities, ContextTier = contextTier }; return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.model.switchTo", [request], cancellationToken); } @@ -20240,20 +21243,6 @@ internal McpOauthApi(CopilotSession session) _session = session; } - /// Responds to a pending MCP OAuth request with an in-process provider. This internal CLI-only API accepts a live OAuthClientProvider instance and cannot be used over the SDK JSON-RPC boundary. Use session.mcp.oauth.handlePendingRequest instead for the public SDK-safe response path. - /// OAuth request identifier from mcp.oauth_required. - /// In-process OAuthClientProvider instance, or omitted to deny. Marked internal: cannot be serialized across the JSON-RPC boundary. - /// The to monitor for cancellation requests. The default is . - /// Empty result after recording the MCP OAuth response. - internal async Task RespondAsync(string requestId, object? provider = null, CancellationToken cancellationToken = default) - { - ArgumentNullException.ThrowIfNull(requestId); - _session.ThrowIfDisposed(); - - var request = new McpOauthRespondRequest { SessionId = _session.SessionId, RequestId = requestId, Provider = CopilotClient.ToJsonElementForWire(provider) }; - return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.mcp.oauth.respond", [request], cancellationToken); - } - /// Resolves a pending MCP OAuth request with a host-provided token or cancellation. The pending request is emitted as mcp.oauth_required with the data necessary to authorize the request. /// OAuth request identifier from the mcp.oauth_required event. /// Host response to the pending OAuth request. @@ -20442,7 +21431,7 @@ public async Task ReloadAsync(PluginsReloadRequest? request = null, Cancellation { _session.ThrowIfDisposed(); - var rpcRequest = new PluginsReloadRequestWithSession { SessionId = _session.SessionId, ReloadMcp = request?.ReloadMcp, ReloadCustomAgents = request?.ReloadCustomAgents, ReloadHooks = request?.ReloadHooks, DeferRepoHooks = request?.DeferRepoHooks }; + var rpcRequest = new PluginsReloadRequestWithSession { SessionId = _session.SessionId, ReloadMcp = request?.ReloadMcp, ReloadCustomAgents = request?.ReloadCustomAgents, ReloadHooks = request?.ReloadHooks, ReloadExtensions = request?.ReloadExtensions, DeferRepoHooks = request?.DeferRepoHooks }; await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.plugins.reload", [rpcRequest], cancellationToken); } } @@ -20500,6 +21489,7 @@ internal OptionsApi(CopilotSession session) /// Per-property model capability overrides for the selected model. /// Reasoning effort for the selected model (model-defined enum). /// Reasoning summary mode for supported model clients. + /// Output verbosity level for supported models. /// Identifier of the client driving the session. /// Identifier sent to LSP-style integrations. /// Stable integration identifier used for analytics and rate-limit attribution. @@ -20551,11 +21541,11 @@ internal OptionsApi(CopilotSession session) /// Optional session limits. Pass null to clear the session limits. /// The to monitor for cancellation requests. The default is . /// Indicates whether the session options patch was applied successfully. - public async Task UpdateAsync(string? model = null, ModelCapabilitiesOverride? modelCapabilitiesOverrides = null, string? reasoningEffort = null, OptionsUpdateReasoningSummary? reasoningSummary = null, string? clientName = null, string? lspClientName = null, string? integrationId = null, IDictionary? featureFlags = null, bool? isExperimentalMode = null, ProviderConfig? provider = null, CapiSessionOptions? capi = null, string? workingDirectory = null, IList? availableTools = null, IList? excludedTools = null, IList? excludedBuiltinAgents = null, OptionsUpdateToolFilterPrecedence? toolFilterPrecedence = null, bool? enableScriptSafety = null, string? shellInitProfile = null, IList? shellProcessFlags = null, SandboxConfig? sandboxConfig = null, bool? logInteractiveShells = null, OptionsUpdateEnvValueMode? envValueMode = null, bool? allowAllMcpServerInstructions = null, IList? skillDirectories = null, IList? disabledSkills = null, bool? enableOnDemandInstructionDiscovery = null, long? maxInlineBinaryBytes = null, IList? installedPlugins = null, bool? customAgentsLocalOnly = null, bool? suppressCustomAgentPrompt = null, bool? skipCustomInstructions = null, IList? disabledInstructionSources = null, bool? coauthorEnabled = null, string? trajectoryFile = null, bool? enableStreaming = null, string? copilotUrl = null, bool? askUserDisabled = null, bool? continueOnAutoMode = null, bool? runningInInteractiveMode = null, bool? enableReasoningSummaries = null, string? agentContext = null, string? eventsLogDirectory = null, IList? additionalContentExclusionPolicies = null, bool? manageScheduleEnabled = null, IList? sessionCapabilities = null, bool? skipEmbeddingRetrieval = null, string? organizationCustomInstructions = null, bool? enableFileHooks = null, bool? enableHostGitOperations = null, bool? enableSessionStore = null, bool? enableSkills = null, OptionsUpdateContextTier? contextTier = null, SessionLimitsConfig? sessionLimits = null, CancellationToken cancellationToken = default) + public async Task UpdateAsync(string? model = null, ModelCapabilitiesOverride? modelCapabilitiesOverrides = null, string? reasoningEffort = null, OptionsUpdateReasoningSummary? reasoningSummary = null, Verbosity? verbosity = null, string? clientName = null, string? lspClientName = null, string? integrationId = null, IDictionary? featureFlags = null, bool? isExperimentalMode = null, ProviderConfig? provider = null, CapiSessionOptions? capi = null, string? workingDirectory = null, IList? availableTools = null, IList? excludedTools = null, IList? excludedBuiltinAgents = null, OptionsUpdateToolFilterPrecedence? toolFilterPrecedence = null, bool? enableScriptSafety = null, string? shellInitProfile = null, IList? shellProcessFlags = null, SandboxConfig? sandboxConfig = null, bool? logInteractiveShells = null, OptionsUpdateEnvValueMode? envValueMode = null, bool? allowAllMcpServerInstructions = null, IList? skillDirectories = null, IList? disabledSkills = null, bool? enableOnDemandInstructionDiscovery = null, long? maxInlineBinaryBytes = null, IList? installedPlugins = null, bool? customAgentsLocalOnly = null, bool? suppressCustomAgentPrompt = null, bool? skipCustomInstructions = null, IList? disabledInstructionSources = null, bool? coauthorEnabled = null, string? trajectoryFile = null, bool? enableStreaming = null, string? copilotUrl = null, bool? askUserDisabled = null, bool? continueOnAutoMode = null, bool? runningInInteractiveMode = null, bool? enableReasoningSummaries = null, string? agentContext = null, string? eventsLogDirectory = null, IList? additionalContentExclusionPolicies = null, bool? manageScheduleEnabled = null, IList? sessionCapabilities = null, bool? skipEmbeddingRetrieval = null, string? organizationCustomInstructions = null, bool? enableFileHooks = null, bool? enableHostGitOperations = null, bool? enableSessionStore = null, bool? enableSkills = null, OptionsUpdateContextTier? contextTier = null, SessionLimitsConfig? sessionLimits = null, CancellationToken cancellationToken = default) { _session.ThrowIfDisposed(); - var request = new SessionUpdateOptionsParams { SessionId = _session.SessionId, Model = model, ModelCapabilitiesOverrides = modelCapabilitiesOverrides, ReasoningEffort = reasoningEffort, ReasoningSummary = reasoningSummary, ClientName = clientName, LspClientName = lspClientName, IntegrationId = integrationId, FeatureFlags = featureFlags, IsExperimentalMode = isExperimentalMode, Provider = provider, Capi = capi, WorkingDirectory = workingDirectory, AvailableTools = availableTools, ExcludedTools = excludedTools, ExcludedBuiltinAgents = excludedBuiltinAgents, ToolFilterPrecedence = toolFilterPrecedence, EnableScriptSafety = enableScriptSafety, ShellInitProfile = shellInitProfile, ShellProcessFlags = shellProcessFlags, SandboxConfig = sandboxConfig, LogInteractiveShells = logInteractiveShells, EnvValueMode = envValueMode, AllowAllMcpServerInstructions = allowAllMcpServerInstructions, SkillDirectories = skillDirectories, DisabledSkills = disabledSkills, EnableOnDemandInstructionDiscovery = enableOnDemandInstructionDiscovery, MaxInlineBinaryBytes = maxInlineBinaryBytes, InstalledPlugins = installedPlugins, CustomAgentsLocalOnly = customAgentsLocalOnly, SuppressCustomAgentPrompt = suppressCustomAgentPrompt, SkipCustomInstructions = skipCustomInstructions, DisabledInstructionSources = disabledInstructionSources, CoauthorEnabled = coauthorEnabled, TrajectoryFile = trajectoryFile, EnableStreaming = enableStreaming, CopilotUrl = copilotUrl, AskUserDisabled = askUserDisabled, ContinueOnAutoMode = continueOnAutoMode, RunningInInteractiveMode = runningInInteractiveMode, EnableReasoningSummaries = enableReasoningSummaries, AgentContext = agentContext, EventsLogDirectory = eventsLogDirectory, AdditionalContentExclusionPolicies = additionalContentExclusionPolicies, ManageScheduleEnabled = manageScheduleEnabled, SessionCapabilities = sessionCapabilities, SkipEmbeddingRetrieval = skipEmbeddingRetrieval, OrganizationCustomInstructions = organizationCustomInstructions, EnableFileHooks = enableFileHooks, EnableHostGitOperations = enableHostGitOperations, EnableSessionStore = enableSessionStore, EnableSkills = enableSkills, ContextTier = contextTier, SessionLimits = sessionLimits }; + var request = new SessionUpdateOptionsParams { SessionId = _session.SessionId, Model = model, ModelCapabilitiesOverrides = modelCapabilitiesOverrides, ReasoningEffort = reasoningEffort, ReasoningSummary = reasoningSummary, Verbosity = verbosity, ClientName = clientName, LspClientName = lspClientName, IntegrationId = integrationId, FeatureFlags = featureFlags, IsExperimentalMode = isExperimentalMode, Provider = provider, Capi = capi, WorkingDirectory = workingDirectory, AvailableTools = availableTools, ExcludedTools = excludedTools, ExcludedBuiltinAgents = excludedBuiltinAgents, ToolFilterPrecedence = toolFilterPrecedence, EnableScriptSafety = enableScriptSafety, ShellInitProfile = shellInitProfile, ShellProcessFlags = shellProcessFlags, SandboxConfig = sandboxConfig, LogInteractiveShells = logInteractiveShells, EnvValueMode = envValueMode, AllowAllMcpServerInstructions = allowAllMcpServerInstructions, SkillDirectories = skillDirectories, DisabledSkills = disabledSkills, EnableOnDemandInstructionDiscovery = enableOnDemandInstructionDiscovery, MaxInlineBinaryBytes = maxInlineBinaryBytes, InstalledPlugins = installedPlugins, CustomAgentsLocalOnly = customAgentsLocalOnly, SuppressCustomAgentPrompt = suppressCustomAgentPrompt, SkipCustomInstructions = skipCustomInstructions, DisabledInstructionSources = disabledInstructionSources, CoauthorEnabled = coauthorEnabled, TrajectoryFile = trajectoryFile, EnableStreaming = enableStreaming, CopilotUrl = copilotUrl, AskUserDisabled = askUserDisabled, ContinueOnAutoMode = continueOnAutoMode, RunningInInteractiveMode = runningInInteractiveMode, EnableReasoningSummaries = enableReasoningSummaries, AgentContext = agentContext, EventsLogDirectory = eventsLogDirectory, AdditionalContentExclusionPolicies = additionalContentExclusionPolicies, ManageScheduleEnabled = manageScheduleEnabled, SessionCapabilities = sessionCapabilities, SkipEmbeddingRetrieval = skipEmbeddingRetrieval, OrganizationCustomInstructions = organizationCustomInstructions, EnableFileHooks = enableFileHooks, EnableHostGitOperations = enableHostGitOperations, EnableSessionStore = enableSessionStore, EnableSkills = enableSkills, ContextTier = contextTier, SessionLimits = sessionLimits }; return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.options.update", [request], cancellationToken); } } @@ -20904,7 +21894,7 @@ public async Task HandlePendingElicitationAsync(string requ /// Resolves a pending `user_input.requested` event with the user's response. /// The unique request ID from the user_input.requested event. - /// Schema for the `UIUserInputResponse` type. + /// User response for a pending user-input request, with answer text and whether it was typed freeform. /// The to monitor for cancellation requests. The default is . /// Indicates whether the pending UI request was resolved by this call. public async Task HandlePendingUserInputAsync(string requestId, UIUserInputResponse response, CancellationToken cancellationToken = default) @@ -20962,7 +21952,7 @@ public async Task HandlePendingSessionLimitsExhaustedAsyn /// Resolves a pending `exit_plan_mode.requested` event with the user's response. /// The unique request ID from the exit_plan_mode.requested event. - /// Schema for the `UIExitPlanModeResponse` type. + /// User response for a pending exit-plan-mode request, with approval state, selected action, auto-approve flag, and feedback. /// The to monitor for cancellation requests. The default is . /// Indicates whether the pending UI request was resolved by this call. public async Task HandlePendingExitPlanModeAsync(string requestId, UIExitPlanModeResponse response, CancellationToken cancellationToken = default) @@ -21067,22 +22057,24 @@ public async Task SetApproveAllAsync(bool enable return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.permissions.setApproveAll", [request], cancellationToken); } - /// Enables or disables full allow-all permissions (tools, paths, and URLs) for the session. Used by attach-mode clients (e.g. LocalRpcSession's `/allow-all` forwarder) to flip the target session's permission state. Unlike `setApproveAll`, this swaps in the unrestricted path and URL managers and emits `session.permissions_changed` on transition. The result returns the authoritative post-mutation state so callers can update their local mirrors without racing the `session.permissions_changed` notification on the same wire. - /// Whether to enable full allow-all permissions. + /// Sets the allow-all permission mode for the session. Used by attach-mode clients (e.g. LocalRpcSession's `/allow-all` forwarder) to flip the target session's permission state. The `on` mode swaps in unrestricted path and URL managers and emits `session.permissions_changed` on transition; the `auto` mode keeps normal prompt paths active while attaching LLM safety recommendations. The result returns the authoritative post-mutation state so callers can update their local mirrors without racing the `session.permissions_changed` notification on the same wire. + /// Allow-all mode to apply. `on` enables full allow-all; `auto` enables advisory LLM auto-approval; `off` disables both. + /// Legacy full allow-all toggle. Prefer `mode`; when `mode` is omitted, `enabled: true` is treated as `mode: "on"` and any other value is treated as `mode: "off"`. + /// Optional model id for the `auto` mode auto-approval LLM judging. Only meaningful when `mode` is `auto`; ignored otherwise. When omitted, the session's active model is used. /// Optional source for allow-all telemetry. Defaults to `rpc` when omitted for SDK callers. /// The to monitor for cancellation requests. The default is . /// Indicates whether the operation succeeded and reports the post-mutation state. - public async Task SetAllowAllAsync(bool enabled, PermissionsSetAllowAllSource? source = null, CancellationToken cancellationToken = default) + public async Task SetAllowAllAsync(PermissionsAllowAllMode? mode = null, bool? enabled = null, string? model = null, PermissionsSetAllowAllSource? source = null, CancellationToken cancellationToken = default) { _session.ThrowIfDisposed(); - var request = new PermissionsSetAllowAllRequest { SessionId = _session.SessionId, Enabled = enabled, Source = source }; + var request = new PermissionsSetAllowAllRequest { SessionId = _session.SessionId, Mode = mode, Enabled = enabled, Model = model, Source = source }; return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.permissions.setAllowAll", [request], cancellationToken); } - /// Returns whether full allow-all permissions are currently active for the session. + /// Returns the current allow-all permission mode for the session. /// The to monitor for cancellation requests. The default is . - /// Current full allow-all permission state. + /// Current allow-all permission mode. public async Task GetAllowAllAsync(CancellationToken cancellationToken = default) { _session.ThrowIfDisposed(); @@ -21415,6 +22407,29 @@ public async Task ContextInfoAsync(long promptTokenLi return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.metadata.contextInfo", [request], cancellationToken); } + /// Returns the experimental per-source attribution breakdown of the session's current context window as a flat list of entries (skills, subagents, MCP servers, built-in tools, plugin rollups, system/tool-definition costs, with nesting via parentId), plus the successful compaction count. The heaviest individual messages are available separately via `metadata.getContextHeaviestMessages`. Returns null until the session has initialized its system prompt and tool metadata. + /// The to monitor for cancellation requests. The default is . + /// Per-source attribution breakdown for the session's current context window, or null if uninitialized. + public async Task GetContextAttributionAsync(CancellationToken cancellationToken = default) + { + _session.ThrowIfDisposed(); + + var request = new SessionMetadataGetContextAttributionRequest { SessionId = _session.SessionId }; + return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.metadata.getContextAttribution", [request], cancellationToken); + } + + /// Returns the largest individual messages currently in the session's context window, most-expensive first. Companion to `metadata.getContextAttribution`. Returns an empty list until the session has initialized. + /// Maximum number of messages to return, most-expensive first. Omit for the server default. + /// The to monitor for cancellation requests. The default is . + /// The heaviest individual messages in the session's context window, most-expensive first. + public async Task GetContextHeaviestMessagesAsync(long? limit = null, CancellationToken cancellationToken = default) + { + _session.ThrowIfDisposed(); + + var request = new MetadataContextHeaviestMessagesRequest { SessionId = _session.SessionId, Limit = limit }; + return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.metadata.getContextHeaviestMessages", [request], cancellationToken); + } + /// Records a working-directory/git context change and emits a `session.context_changed` event. /// Updated working directory and git context. Emitted as the new payload of `session.context_changed`. /// The to monitor for cancellation requests. The default is . @@ -21455,6 +22470,42 @@ public async Task RecomputeContextTokensAs } } +/// Provides session-scoped Settings APIs. +[Experimental(Diagnostics.Experimental)] +public sealed class SettingsApi +{ + private readonly CopilotSession _session; + + internal SettingsApi(CopilotSession session) + { + _session = session; + } + + /// Returns a redacted snapshot of session runtime settings, with secrets and raw feature flags excluded. Internal: the runtime settings shape is a runtime-internal surface and is deliberately kept out of the public SDK, because consumers should not depend on the runtime's internal settings layout. It remains callable in-process and is expected to be reworked as the runtime internals are consolidated. + /// The to monitor for cancellation requests. The default is . + /// Redacted, serializable view of session runtime settings for SDK boundary consumers. Secrets and raw feature flags are intentionally excluded. + internal async Task SnapshotAsync(CancellationToken cancellationToken = default) + { + _session.ThrowIfDisposed(); + + var request = new SessionSettingsSnapshotRequest { SessionId = _session.SessionId }; + return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.settings.snapshot", [request], cancellationToken); + } + + /// Evaluates a named Rust-owned settings predicate without exposing raw feature flags. Internal: the raw feature-flag names and composition are runtime-internal, so this predicate-evaluation helper is kept out of the public SDK surface and is callable in-process only. + /// Predicate name. The runtime owns the raw feature-flag names and composition logic. + /// Tool name for tool-scoped predicates such as trivial-change handling. + /// The to monitor for cancellation requests. The default is . + /// Result of evaluating a Rust-owned settings predicate. + internal async Task EvaluatePredicateAsync(SessionSettingsPredicateName name, string? toolName = null, CancellationToken cancellationToken = default) + { + _session.ThrowIfDisposed(); + + var request = new SessionSettingsEvaluatePredicateRequest { SessionId = _session.SessionId, Name = name, ToolName = toolName }; + return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.settings.evaluatePredicate", [request], cancellationToken); + } +} + /// Provides session-scoped Shell APIs. [Experimental(Diagnostics.Experimental)] public sealed class ShellApi @@ -21677,7 +22728,7 @@ public async Task TailAsync(CancellationToken cancellationTo } /// Registers consumer interest in an event type for runtime gating purposes. - /// The event type the consumer wants the runtime to treat as 'observed' for behavior-switching gating. Some runtime code paths inspect whether any consumer is interested in a specific event type and choose a different implementation accordingly (e.g. `mcp.oauth_required`: when interest is registered the runtime delegates the full interactive OAuth flow to the consumer; when no interest is registered the runtime installs a browserless fallback that silently reuses cached tokens). SDK clients that long-poll events do NOT automatically appear as listeners to these gating checks — they must explicitly call `registerInterest` for each event type they want the runtime to count as having a consumer. Multiple registrations for the same event type from the same or different consumers are tracked independently and must each be released. See: `mcp.oauth_required`, `sampling.requested`, `auto_mode_switch.requested`, `session_limits_exhausted.requested`, `user_input.requested`, `elicitation.requested`, `command.queued`, `exit_plan_mode.requested`. + /// The event type the consumer wants the runtime to treat as 'observed' for behavior-switching gating. Some runtime code paths inspect whether any consumer is interested in a specific event type and choose a different implementation accordingly (e.g. `mcp.oauth_required`: when interest is registered the runtime delegates OAuth token acquisition to the consumer; when no interest is registered OAuth-required servers become needs-auth). SDK clients that long-poll events do NOT automatically appear as listeners to these gating checks — they must explicitly call `registerInterest` for each event type they want the runtime to count as having a consumer. Multiple registrations for the same event type from the same or different consumers are tracked independently and must each be released. See: `mcp.oauth_required`, `sampling.requested`, `auto_mode_switch.requested`, `session_limits_exhausted.requested`, `user_input.requested`, `elicitation.requested`, `command.queued`, `exit_plan_mode.requested`. /// The to monitor for cancellation requests. The default is . /// Opaque handle representing an event-type interest registration. public async Task RegisterInterestAsync(string eventType, CancellationToken cancellationToken = default) @@ -22081,8 +23132,8 @@ public interface ILlmInferenceHandler [Experimental(Diagnostics.Experimental)] public interface IGitHubTelemetryHandler { - /// Forwards a single GitHub telemetry event to a host connection that opted into telemetry forwarding for the session. - /// Payload for a `gitHubTelemetry.event` notification: a single GitHub telemetry event the runtime forwards to a host connection that opted into telemetry forwarding for the session. + /// 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). + /// 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. /// The to monitor for cancellation requests. The default is . Task EventAsync(GitHubTelemetryNotification request, CancellationToken cancellationToken = default); } @@ -22157,6 +23208,8 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH [JsonSerializable(typeof(GitHub.Copilot.AssistantReasoningEvent), TypeInfoPropertyName = "SessionEventsAssistantReasoningEvent")] [JsonSerializable(typeof(GitHub.Copilot.AssistantStreamingDeltaData), TypeInfoPropertyName = "SessionEventsAssistantStreamingDeltaData")] [JsonSerializable(typeof(GitHub.Copilot.AssistantStreamingDeltaEvent), TypeInfoPropertyName = "SessionEventsAssistantStreamingDeltaEvent")] +[JsonSerializable(typeof(GitHub.Copilot.AssistantToolCallDeltaData), TypeInfoPropertyName = "SessionEventsAssistantToolCallDeltaData")] +[JsonSerializable(typeof(GitHub.Copilot.AssistantToolCallDeltaEvent), TypeInfoPropertyName = "SessionEventsAssistantToolCallDeltaEvent")] [JsonSerializable(typeof(GitHub.Copilot.AssistantTurnEndData), TypeInfoPropertyName = "SessionEventsAssistantTurnEndData")] [JsonSerializable(typeof(GitHub.Copilot.AssistantTurnEndEvent), TypeInfoPropertyName = "SessionEventsAssistantTurnEndEvent")] [JsonSerializable(typeof(GitHub.Copilot.AssistantTurnStartData), TypeInfoPropertyName = "SessionEventsAssistantTurnStartData")] @@ -22189,6 +23242,7 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH [JsonSerializable(typeof(GitHub.Copilot.AttachmentSelectionDetails), TypeInfoPropertyName = "SessionEventsAttachmentSelectionDetails")] [JsonSerializable(typeof(GitHub.Copilot.AttachmentSelectionDetailsEnd), TypeInfoPropertyName = "SessionEventsAttachmentSelectionDetailsEnd")] [JsonSerializable(typeof(GitHub.Copilot.AttachmentSelectionDetailsStart), TypeInfoPropertyName = "SessionEventsAttachmentSelectionDetailsStart")] +[JsonSerializable(typeof(GitHub.Copilot.AutoApprovalRecommendation), TypeInfoPropertyName = "SessionEventsAutoApprovalRecommendation")] [JsonSerializable(typeof(GitHub.Copilot.AutoModeSwitchCompletedData), TypeInfoPropertyName = "SessionEventsAutoModeSwitchCompletedData")] [JsonSerializable(typeof(GitHub.Copilot.AutoModeSwitchCompletedEvent), TypeInfoPropertyName = "SessionEventsAutoModeSwitchCompletedEvent")] [JsonSerializable(typeof(GitHub.Copilot.AutoModeSwitchRequestedData), TypeInfoPropertyName = "SessionEventsAutoModeSwitchRequestedData")] @@ -22291,6 +23345,8 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH [JsonSerializable(typeof(GitHub.Copilot.OmittedBinaryType), TypeInfoPropertyName = "SessionEventsOmittedBinaryType")] [JsonSerializable(typeof(GitHub.Copilot.PendingMessagesModifiedData), TypeInfoPropertyName = "SessionEventsPendingMessagesModifiedData")] [JsonSerializable(typeof(GitHub.Copilot.PendingMessagesModifiedEvent), TypeInfoPropertyName = "SessionEventsPendingMessagesModifiedEvent")] +[JsonSerializable(typeof(GitHub.Copilot.PermissionAllowAllMode), TypeInfoPropertyName = "SessionEventsPermissionAllowAllMode")] +[JsonSerializable(typeof(GitHub.Copilot.PermissionAutoApproval), TypeInfoPropertyName = "SessionEventsPermissionAutoApproval")] [JsonSerializable(typeof(GitHub.Copilot.PermissionCompletedData), TypeInfoPropertyName = "SessionEventsPermissionCompletedData")] [JsonSerializable(typeof(GitHub.Copilot.PermissionCompletedEvent), TypeInfoPropertyName = "SessionEventsPermissionCompletedEvent")] [JsonSerializable(typeof(GitHub.Copilot.PermissionPromptRequest), TypeInfoPropertyName = "SessionEventsPermissionPromptRequest")] @@ -22436,6 +23492,7 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH [JsonSerializable(typeof(GitHub.Copilot.UserToolSessionApprovalMemory), TypeInfoPropertyName = "SessionEventsUserToolSessionApprovalMemory")] [JsonSerializable(typeof(GitHub.Copilot.UserToolSessionApprovalRead), TypeInfoPropertyName = "SessionEventsUserToolSessionApprovalRead")] [JsonSerializable(typeof(GitHub.Copilot.UserToolSessionApprovalWrite), TypeInfoPropertyName = "SessionEventsUserToolSessionApprovalWrite")] +[JsonSerializable(typeof(GitHub.Copilot.Verbosity), TypeInfoPropertyName = "SessionEventsVerbosity")] [JsonSerializable(typeof(GitHub.Copilot.WorkingDirectoryContext), TypeInfoPropertyName = "SessionEventsWorkingDirectoryContext")] [JsonSerializable(typeof(GitHub.Copilot.WorkingDirectoryContextHostType), TypeInfoPropertyName = "SessionEventsWorkingDirectoryContextHostType")] [JsonSerializable(typeof(GitHub.Copilot.WorkspaceFileChangedOperation), TypeInfoPropertyName = "SessionEventsWorkspaceFileChangedOperation")] @@ -22500,6 +23557,7 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH [JsonSerializable(typeof(ConnectResult))] [JsonSerializable(typeof(ConnectedRemoteSessionMetadata))] [JsonSerializable(typeof(ConnectedRemoteSessionMetadataRepository))] +[JsonSerializable(typeof(ContextHeaviestMessage))] [JsonSerializable(typeof(CopilotUserResponse))] [JsonSerializable(typeof(CopilotUserResponseEndpoints))] [JsonSerializable(typeof(CopilotUserResponseOrganizationListItem))] @@ -22509,6 +23567,13 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH [JsonSerializable(typeof(CopilotUserResponseQuotaSnapshotsPremiumInteractions))] [JsonSerializable(typeof(CurrentModel))] [JsonSerializable(typeof(CurrentToolMetadata))] +[JsonSerializable(typeof(DebugCollectLogsCollectedEntry))] +[JsonSerializable(typeof(DebugCollectLogsDestination))] +[JsonSerializable(typeof(DebugCollectLogsEntry))] +[JsonSerializable(typeof(DebugCollectLogsInclude))] +[JsonSerializable(typeof(DebugCollectLogsRequest))] +[JsonSerializable(typeof(DebugCollectLogsResult))] +[JsonSerializable(typeof(DebugCollectLogsSkippedEntry))] [JsonSerializable(typeof(DiscoveredCanvas))] [JsonSerializable(typeof(DiscoveredMcpServer))] [JsonSerializable(typeof(EnqueueCommandParams))] @@ -22542,6 +23607,8 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH [JsonSerializable(typeof(HistorySummarizeForHandoffResult))] [JsonSerializable(typeof(HistoryTruncateRequest))] [JsonSerializable(typeof(HistoryTruncateResult))] +[JsonSerializable(typeof(IDictionary))] +[JsonSerializable(typeof(IList))] [JsonSerializable(typeof(InstalledPlugin))] [JsonSerializable(typeof(InstalledPluginInfo))] [JsonSerializable(typeof(InstructionDiscoveryPath))] @@ -22618,8 +23685,6 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH [JsonSerializable(typeof(McpOauthLoginRequest))] [JsonSerializable(typeof(McpOauthLoginResult))] [JsonSerializable(typeof(McpOauthPendingRequestResponse))] -[JsonSerializable(typeof(McpOauthRespondRequest))] -[JsonSerializable(typeof(McpOauthRespondResult))] [JsonSerializable(typeof(McpRegisterExternalClientRequest))] [JsonSerializable(typeof(McpReloadWithConfigRequest))] [JsonSerializable(typeof(McpRemoveGitHubResult))] @@ -22636,6 +23701,12 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH [JsonSerializable(typeof(McpStopServerRequest))] [JsonSerializable(typeof(McpTools))] [JsonSerializable(typeof(McpUnregisterExternalClientRequest))] +[JsonSerializable(typeof(MetadataContextAttributionResult))] +[JsonSerializable(typeof(MetadataContextAttributionResultContextAttribution))] +[JsonSerializable(typeof(MetadataContextAttributionResultContextAttributionCompactions))] +[JsonSerializable(typeof(MetadataContextAttributionResultContextAttributionEntry))] +[JsonSerializable(typeof(MetadataContextHeaviestMessagesRequest))] +[JsonSerializable(typeof(MetadataContextHeaviestMessagesResult))] [JsonSerializable(typeof(MetadataContextInfoRequest))] [JsonSerializable(typeof(MetadataContextInfoResult))] [JsonSerializable(typeof(MetadataContextInfoResultContextInfo))] @@ -22753,7 +23824,6 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH [JsonSerializable(typeof(PluginsReloadRequestWithSession))] [JsonSerializable(typeof(PluginsUninstallRequest))] [JsonSerializable(typeof(PluginsUpdateRequest))] -[JsonSerializable(typeof(PollSpawnedSessionsResult))] [JsonSerializable(typeof(ProviderAddRequest))] [JsonSerializable(typeof(ProviderAddResult))] [JsonSerializable(typeof(ProviderConfig))] @@ -22870,6 +23940,7 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH [JsonSerializable(typeof(SessionMcpReloadRequest))] [JsonSerializable(typeof(SessionMcpRemoveGitHubRequest))] [JsonSerializable(typeof(SessionMetadataActivityRequest))] +[JsonSerializable(typeof(SessionMetadataGetContextAttributionRequest))] [JsonSerializable(typeof(SessionMetadataIsProcessingRequest))] [JsonSerializable(typeof(SessionMetadataSnapshot))] [JsonSerializable(typeof(SessionMetadataSnapshotRequest))] @@ -22892,6 +23963,16 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH [JsonSerializable(typeof(SessionScheduleListRequest))] [JsonSerializable(typeof(SessionSetCredentialsParams))] [JsonSerializable(typeof(SessionSetCredentialsResult))] +[JsonSerializable(typeof(SessionSettingsBuiltInToolAvailabilitySnapshot))] +[JsonSerializable(typeof(SessionSettingsEvaluatePredicateRequest))] +[JsonSerializable(typeof(SessionSettingsEvaluatePredicateResult))] +[JsonSerializable(typeof(SessionSettingsJobSnapshot))] +[JsonSerializable(typeof(SessionSettingsModelSnapshot))] +[JsonSerializable(typeof(SessionSettingsOnlineEvaluationSnapshot))] +[JsonSerializable(typeof(SessionSettingsRepoSnapshot))] +[JsonSerializable(typeof(SessionSettingsSnapshot))] +[JsonSerializable(typeof(SessionSettingsSnapshotRequest))] +[JsonSerializable(typeof(SessionSettingsValidationSnapshot))] [JsonSerializable(typeof(SessionSizes))] [JsonSerializable(typeof(SessionSkillsEnsureLoadedRequest))] [JsonSerializable(typeof(SessionSkillsGetInvokedRequest))] @@ -22939,8 +24020,6 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH [JsonSerializable(typeof(SessionsListRequest))] [JsonSerializable(typeof(SessionsLoadDeferredRepoHooksRequest))] [JsonSerializable(typeof(SessionsOpenProgress))] -[JsonSerializable(typeof(SessionsPollSpawnedSessionsEvent))] -[JsonSerializable(typeof(SessionsPollSpawnedSessionsRequest))] [JsonSerializable(typeof(SessionsPruneOldRequest))] [JsonSerializable(typeof(SessionsRegisterExtensionToolsOnSessionOptions))] [JsonSerializable(typeof(SessionsReleaseLockRequest))] @@ -22976,6 +24055,7 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH [JsonSerializable(typeof(SkillsLoadDiagnostics))] [JsonSerializable(typeof(SlashCommandInfo))] [JsonSerializable(typeof(SlashCommandInput))] +[JsonSerializable(typeof(SlashCommandInputChoice))] [JsonSerializable(typeof(SlashCommandInvocationResult))] [JsonSerializable(typeof(SlashCommandSelectSubcommandOption))] [JsonSerializable(typeof(SubagentSettingsEntry))] diff --git a/dotnet/src/Generated/SessionEvents.cs b/dotnet/src/Generated/SessionEvents.cs index 1d9dfdceca..c3f827dec0 100644 --- a/dotnet/src/Generated/SessionEvents.cs +++ b/dotnet/src/Generated/SessionEvents.cs @@ -33,6 +33,7 @@ namespace GitHub.Copilot; [JsonDerivedType(typeof(AssistantReasoningEvent), "assistant.reasoning")] [JsonDerivedType(typeof(AssistantReasoningDeltaEvent), "assistant.reasoning_delta")] [JsonDerivedType(typeof(AssistantStreamingDeltaEvent), "assistant.streaming_delta")] +[JsonDerivedType(typeof(AssistantToolCallDeltaEvent), "assistant.tool_call_delta")] [JsonDerivedType(typeof(AssistantTurnEndEvent), "assistant.turn_end")] [JsonDerivedType(typeof(AssistantTurnStartEvent), "assistant.turn_start")] [JsonDerivedType(typeof(AssistantUsageEvent), "assistant.usage")] @@ -363,7 +364,7 @@ public sealed partial class SessionSessionLimitsChangedEvent : SessionEvent public required SessionSessionLimitsChangedData Data { get; set; } } -/// Permissions change details carrying the aggregate allow-all boolean transition. +/// Permissions change details carrying the aggregate allow-all transition. /// Represents the session.permissions_changed event. public sealed partial class SessionPermissionsChangedEvent : SessionEvent { @@ -545,7 +546,7 @@ public sealed partial class SessionTaskCompleteEvent : SessionEvent public required SessionTaskCompleteData Data { get; set; } } -/// Schema for the `UserMessageData` type. +/// Payload of `user.message` with displayed and model-transformed content, attachments, source/delivery metadata, mode, and telemetry IDs. /// Represents the user.message event. public sealed partial class UserMessageEvent : SessionEvent { @@ -623,6 +624,19 @@ public sealed partial class AssistantReasoningDeltaEvent : SessionEvent public required AssistantReasoningDeltaData Data { get; set; } } +/// Streaming tool-call input delta for incremental tool-call updates. +/// Represents the assistant.tool_call_delta event. +public sealed partial class AssistantToolCallDeltaEvent : SessionEvent +{ + /// + [JsonIgnore] + public override string Type => "assistant.tool_call_delta"; + + /// The assistant.tool_call_delta event payload. + [JsonPropertyName("data")] + public required AssistantToolCallDeltaData Data { get; set; } +} + /// Streaming response progress with cumulative byte count. /// Represents the assistant.streaming_delta event. public sealed partial class AssistantStreamingDeltaEvent : SessionEvent @@ -1300,7 +1314,7 @@ public sealed partial class ExitPlanModeCompletedEvent : SessionEvent public required ExitPlanModeCompletedData Data { get; set; } } -/// Schema for the `ToolsUpdatedData` type. +/// Payload of `session.tools_updated` identifying the model whose resolved tools were updated. /// Represents the session.tools_updated event. public sealed partial class SessionToolsUpdatedEvent : SessionEvent { @@ -1313,7 +1327,7 @@ public sealed partial class SessionToolsUpdatedEvent : SessionEvent public required SessionToolsUpdatedData Data { get; set; } } -/// Schema for the `BackgroundTasksChangedData` type. +/// Empty payload for `session.background_tasks_changed`, indicating background task state changed. /// Represents the session.background_tasks_changed event. public sealed partial class SessionBackgroundTasksChangedEvent : SessionEvent { @@ -1326,7 +1340,7 @@ public sealed partial class SessionBackgroundTasksChangedEvent : SessionEvent public required SessionBackgroundTasksChangedData Data { get; set; } } -/// Schema for the `SkillsLoadedData` type. +/// Payload of `session.skills_loaded` listing resolved skill metadata. /// Represents the session.skills_loaded event. public sealed partial class SessionSkillsLoadedEvent : SessionEvent { @@ -1339,7 +1353,7 @@ public sealed partial class SessionSkillsLoadedEvent : SessionEvent public required SessionSkillsLoadedData Data { get; set; } } -/// Schema for the `CustomAgentsUpdatedData` type. +/// Payload of `session.custom_agents_updated` with loaded custom agents plus non-fatal warnings and fatal errors. /// Represents the session.custom_agents_updated event. public sealed partial class SessionCustomAgentsUpdatedEvent : SessionEvent { @@ -1352,7 +1366,7 @@ public sealed partial class SessionCustomAgentsUpdatedEvent : SessionEvent public required SessionCustomAgentsUpdatedData Data { get; set; } } -/// Schema for the `McpServersLoadedData` type. +/// Payload of `session.mcp_servers_loaded` listing MCP server status summaries. /// Represents the session.mcp_servers_loaded event. public sealed partial class SessionMcpServersLoadedEvent : SessionEvent { @@ -1365,7 +1379,7 @@ public sealed partial class SessionMcpServersLoadedEvent : SessionEvent public required SessionMcpServersLoadedData Data { get; set; } } -/// Schema for the `McpServerStatusChangedData` type. +/// Payload of `session.mcp_server_status_changed` for one MCP server's status and optional failure error. /// Represents the session.mcp_server_status_changed event. public sealed partial class SessionMcpServerStatusChangedEvent : SessionEvent { @@ -1378,7 +1392,7 @@ public sealed partial class SessionMcpServerStatusChangedEvent : SessionEvent public required SessionMcpServerStatusChangedData Data { get; set; } } -/// Schema for the `ExtensionsLoadedData` type. +/// Payload of `session.extensions_loaded` listing discovered extensions and their statuses. /// Represents the session.extensions_loaded event. public sealed partial class SessionExtensionsLoadedEvent : SessionEvent { @@ -1391,7 +1405,7 @@ public sealed partial class SessionExtensionsLoadedEvent : SessionEvent public required SessionExtensionsLoadedData Data { get; set; } } -/// Schema for the `CanvasOpenedData` type. +/// Payload of `session.canvas.opened` with canvas instance and provider IDs plus optional title, status, URL, and input. /// Represents the session.canvas.opened event. [Experimental(Diagnostics.Experimental)] public sealed partial class SessionCanvasOpenedEvent : SessionEvent @@ -1405,7 +1419,7 @@ public sealed partial class SessionCanvasOpenedEvent : SessionEvent public required SessionCanvasOpenedData Data { get; set; } } -/// Schema for the `CanvasRegistryChangedData` type. +/// Payload of `session.canvas.registry_changed` listing the canvas declarations currently available. /// Represents the session.canvas.registry_changed event. [Experimental(Diagnostics.Experimental)] public sealed partial class SessionCanvasRegistryChangedEvent : SessionEvent @@ -1419,7 +1433,7 @@ public sealed partial class SessionCanvasRegistryChangedEvent : SessionEvent public required SessionCanvasRegistryChangedData Data { get; set; } } -/// Schema for the `CanvasClosedData` type. +/// Payload of `session.canvas.closed` with the closed canvas instance ID, provider ID, and canvas ID. /// Represents the session.canvas.closed event. [Experimental(Diagnostics.Experimental)] public sealed partial class SessionCanvasClosedEvent : SessionEvent @@ -1475,7 +1489,7 @@ public sealed partial class SessionCanvasRemovedEvent : SessionEvent public required SessionCanvasRemovedData Data { get; set; } } -/// Schema for the `ExtensionsAttachmentsPushedData` type. +/// Payload of `session.extensions.attachments_pushed` with extension-contributed attachments for the next send. /// Represents the session.extensions.attachments_pushed event. public sealed partial class SessionExtensionsAttachmentsPushedEvent : SessionEvent { @@ -1565,6 +1579,11 @@ public sealed partial class SessionStartData [JsonPropertyName("startTime")] public required DateTimeOffset StartTime { get; set; } + /// Output verbosity level used for model calls, if applicable (e.g. "low", "medium", "high"). + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("verbosity")] + public Verbosity? Verbosity { get; set; } + /// Schema version number for the session event format. [JsonPropertyName("version")] public required long Version { get; set; } @@ -1635,6 +1654,11 @@ public sealed partial class SessionResumeData [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("sessionWasActive")] public bool? SessionWasActive { get; set; } + + /// Output verbosity level used for model calls, if applicable (e.g. "low", "medium", "high"). + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("verbosity")] + public Verbosity? Verbosity { get; set; } } /// Notifies that the session's remote steering capability has changed. @@ -1866,6 +1890,11 @@ public sealed partial class SessionModelChangeData [JsonPropertyName("previousReasoningSummary")] public ReasoningSummary? PreviousReasoningSummary { get; set; } + /// Output verbosity level before the model change, if applicable. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("previousVerbosity")] + public Verbosity? PreviousVerbosity { get; set; } + /// Reasoning effort level after the model change, if applicable. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("reasoningEffort")] @@ -1875,6 +1904,11 @@ public sealed partial class SessionModelChangeData [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("reasoningSummary")] public ReasoningSummary? ReasoningSummary { get; set; } + + /// Output verbosity level after the model change, if applicable. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("verbosity")] + public Verbosity? Verbosity { get; set; } } /// Agent mode change details including previous and new modes. @@ -1897,13 +1931,25 @@ public sealed partial class SessionSessionLimitsChangedData public SessionLimitsConfig? SessionLimits { get; set; } } -/// Permissions change details carrying the aggregate allow-all boolean transition. +/// Permissions change details carrying the aggregate allow-all transition. public sealed partial class SessionPermissionsChangedData { + /// Allow-all mode after the change. + [Experimental(Diagnostics.Experimental)] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("allowAllPermissionMode")] + public PermissionAllowAllMode? AllowAllPermissionMode { get; set; } + /// Aggregate allow-all flag after the change. [JsonPropertyName("allowAllPermissions")] public required bool AllowAllPermissions { get; set; } + /// Allow-all mode before the change. + [Experimental(Diagnostics.Experimental)] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("previousAllowAllPermissionMode")] + public PermissionAllowAllMode? PreviousAllowAllPermissionMode { get; set; } + /// Aggregate allow-all flag before the change. [JsonPropertyName("previousAllowAllPermissions")] public required bool PreviousAllowAllPermissions { get; set; } @@ -2197,6 +2243,11 @@ public sealed partial class SessionCompactionStartData [JsonPropertyName("conversationTokens")] public long? ConversationTokens { get; set; } + /// Model identifier used for compaction, when known. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("model")] + public string? Model { get; set; } + /// Token count from system message(s) at compaction start. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("systemTokens")] @@ -2315,7 +2366,7 @@ public sealed partial class SessionTaskCompleteData public string? Summary { get; set; } } -/// Schema for the `UserMessageData` type. +/// Payload of `user.message` with displayed and model-transformed content, attachments, source/delivery metadata, mode, and telemetry IDs. public sealed partial class UserMessageData { /// The agent mode that was active when this message was sent. @@ -2386,6 +2437,11 @@ public sealed partial class AssistantTurnStartData [JsonPropertyName("interactionId")] public string? InteractionId { get; set; } + /// Model identifier used for this turn, when known. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("model")] + public string? Model { get; set; } + /// Identifier for this turn within the agentic loop, typically a stringified turn number. [JsonPropertyName("turnId")] public required string TurnId { get; set; } @@ -2423,6 +2479,28 @@ public sealed partial class AssistantReasoningDeltaData public required string ReasoningId { get; set; } } +/// Streaming tool-call input delta for incremental tool-call updates. +public sealed partial class AssistantToolCallDeltaData +{ + /// Raw provider tool input fragment to append for this tool call. Function/tool-use providers stream serialized JSON argument text (so newlines inside JSON string values may appear as escaped `\n` until the accumulated JSON is parsed); custom tool calls stream raw custom input. + [JsonPropertyName("inputDelta")] + public required string InputDelta { get; set; } + + /// Tool call ID this delta belongs to, matching the corresponding assistant.message tool request. + [JsonPropertyName("toolCallId")] + public required string ToolCallId { get; set; } + + /// Name of the tool being invoked, when known from the stream. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("toolName")] + public string? ToolName { get; set; } + + /// Tool call type, when known from the stream. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("toolType")] + public AssistantMessageToolRequestType? ToolType { get; set; } +} + /// Streaming response progress with cumulative byte count. public sealed partial class AssistantStreamingDeltaData { @@ -2565,6 +2643,11 @@ public sealed partial class AssistantMessageDeltaData /// Turn completion metadata including the turn identifier. public sealed partial class AssistantTurnEndData { + /// Model identifier used for this turn, when known. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("model")] + public string? Model { get; set; } + /// Identifier of the turn that has ended, matching the corresponding assistant.turn_start event. [JsonPropertyName("turnId")] public required string TurnId { get; set; } @@ -2964,6 +3047,11 @@ public sealed partial class SkillInvokedData [JsonPropertyName("description")] public string? Description { get; set; } + /// Model identifier active when the skill was invoked, when known. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("model")] + public string? Model { get; set; } + /// Name of the invoked skill. [JsonPropertyName("name")] public required string Name { get; set; } @@ -3729,7 +3817,7 @@ public sealed partial class ExitPlanModeCompletedData public ExitPlanModeAction? SelectedAction { get; set; } } -/// Schema for the `ToolsUpdatedData` type. +/// Payload of `session.tools_updated` identifying the model whose resolved tools were updated. public sealed partial class SessionToolsUpdatedData { /// Identifier of the model the resolved tools apply to. @@ -3737,12 +3825,12 @@ public sealed partial class SessionToolsUpdatedData public required string Model { get; set; } } -/// Schema for the `BackgroundTasksChangedData` type. +/// Empty payload for `session.background_tasks_changed`, indicating background task state changed. public sealed partial class SessionBackgroundTasksChangedData { } -/// Schema for the `SkillsLoadedData` type. +/// Payload of `session.skills_loaded` listing resolved skill metadata. public sealed partial class SessionSkillsLoadedData { /// Array of resolved skill metadata. @@ -3750,7 +3838,7 @@ public sealed partial class SessionSkillsLoadedData public required SkillsLoadedSkill[] Skills { get; set; } } -/// Schema for the `CustomAgentsUpdatedData` type. +/// Payload of `session.custom_agents_updated` with loaded custom agents plus non-fatal warnings and fatal errors. public sealed partial class SessionCustomAgentsUpdatedData { /// Array of loaded custom agent metadata. @@ -3766,7 +3854,7 @@ public sealed partial class SessionCustomAgentsUpdatedData public required string[] Warnings { get; set; } } -/// Schema for the `McpServersLoadedData` type. +/// Payload of `session.mcp_servers_loaded` listing MCP server status summaries. public sealed partial class SessionMcpServersLoadedData { /// Array of MCP server status summaries. @@ -3774,7 +3862,7 @@ public sealed partial class SessionMcpServersLoadedData public required McpServersLoadedServer[] Servers { get; set; } } -/// Schema for the `McpServerStatusChangedData` type. +/// Payload of `session.mcp_server_status_changed` for one MCP server's status and optional failure error. public sealed partial class SessionMcpServerStatusChangedData { /// Error message if the server entered a failed state. @@ -3791,7 +3879,7 @@ public sealed partial class SessionMcpServerStatusChangedData public required McpServerStatus Status { get; set; } } -/// Schema for the `ExtensionsLoadedData` type. +/// Payload of `session.extensions_loaded` listing discovered extensions and their statuses. public sealed partial class SessionExtensionsLoadedData { /// Array of discovered extensions and their status. @@ -3799,7 +3887,7 @@ public sealed partial class SessionExtensionsLoadedData public required ExtensionsLoadedExtension[] Extensions { get; set; } } -/// Schema for the `CanvasOpenedData` type. +/// Payload of `session.canvas.opened` with canvas instance and provider IDs plus optional title, status, URL, and input. [Experimental(Diagnostics.Experimental)] public sealed partial class SessionCanvasOpenedData { @@ -3841,7 +3929,7 @@ public sealed partial class SessionCanvasOpenedData public string? Url { get; set; } } -/// Schema for the `CanvasRegistryChangedData` type. +/// Payload of `session.canvas.registry_changed` listing the canvas declarations currently available. [Experimental(Diagnostics.Experimental)] public sealed partial class SessionCanvasRegistryChangedData { @@ -3850,7 +3938,7 @@ public sealed partial class SessionCanvasRegistryChangedData public required CanvasRegistryChangedCanvas[] Canvases { get; set; } } -/// Schema for the `CanvasClosedData` type. +/// Payload of `session.canvas.closed` with the closed canvas instance ID, provider ID, and canvas ID. [Experimental(Diagnostics.Experimental)] public sealed partial class SessionCanvasClosedData { @@ -3936,7 +4024,7 @@ public sealed partial class SessionCanvasRemovedData public required string InstanceId { get; set; } } -/// Schema for the `ExtensionsAttachmentsPushedData` type. +/// Payload of `session.extensions.attachments_pushed` with extension-contributed attachments for the next send. public sealed partial class SessionExtensionsAttachmentsPushedData { /// Attachments contributed by an extension; the host should surface these as composer pills and forward them via the next session.send call. @@ -4090,7 +4178,7 @@ public sealed partial class ShutdownModelMetricRequests public long? Count { get; set; } } -/// Schema for the `ShutdownModelMetricTokenDetail` type. +/// A token-type entry in a shutdown model metric, storing the accumulated token count. /// Nested data type for ShutdownModelMetricTokenDetail. public sealed partial class ShutdownModelMetricTokenDetail { @@ -4125,7 +4213,7 @@ public sealed partial class ShutdownModelMetricUsage public long? ReasoningTokens { get; set; } } -/// Schema for the `ShutdownModelMetric` type. +/// Per-model shutdown metrics with request counts, token usage, nano-AI units, and token details. /// Nested data type for ShutdownModelMetric. public sealed partial class ShutdownModelMetric { @@ -4149,7 +4237,7 @@ public sealed partial class ShutdownModelMetric public required ShutdownModelMetricUsage Usage { get; set; } } -/// Schema for the `ShutdownTokenDetail` type. +/// A session-wide shutdown token-type entry storing the accumulated token count. /// Nested data type for ShutdownTokenDetail. public sealed partial class ShutdownTokenDetail { @@ -5052,7 +5140,7 @@ public sealed partial class AssistantUsageCopilotUsage public required double TotalNanoAiu { get; set; } } -/// Schema for the `AssistantUsageQuotaSnapshot` type. +/// Internal per-quota snapshot for assistant usage, including entitlement, consumed requests, overage, reset date, and remaining quota. /// Nested data type for AssistantUsageQuotaSnapshot. internal sealed partial class AssistantUsageQuotaSnapshot { @@ -5163,7 +5251,7 @@ public sealed partial class ToolExecutionStartShellToolInfo public required string[] PossiblePaths { get; set; } } -/// Schema for the `ToolExecutionStartToolDescriptionMetaUI` type. +/// MCP Apps tool `_meta.ui` resource URI and visibility captured on `tool.execution_start`. /// Nested data type for ToolExecutionStartToolDescriptionMetaUI. public sealed partial class ToolExecutionStartToolDescriptionMetaUI { @@ -5182,7 +5270,7 @@ public sealed partial class ToolExecutionStartToolDescriptionMetaUI /// Nested data type for ToolExecutionStartToolDescriptionMeta. public sealed partial class ToolExecutionStartToolDescriptionMeta { - /// Schema for the `ToolExecutionStartToolDescriptionMetaUI` type. + /// MCP Apps tool `_meta.ui` resource URI and visibility captured on `tool.execution_start`. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("ui")] public ToolExecutionStartToolDescriptionMetaUI? Ui { get; set; } @@ -5617,7 +5705,7 @@ public sealed partial class ToolExecutionCompleteContentResourceLink : ToolExecu public required string Uri { get; set; } } -/// Schema for the `EmbeddedTextResourceContents` type. +/// Embedded text resource contents identified by a URI, with an optional MIME type and a text payload. /// Nested data type for EmbeddedTextResourceContents. public sealed partial class EmbeddedTextResourceContents { @@ -5635,7 +5723,7 @@ public sealed partial class EmbeddedTextResourceContents public required string Uri { get; set; } } -/// Schema for the `EmbeddedBlobResourceContents` type. +/// Embedded binary resource contents identified by a URI, with an optional MIME type and a base64-encoded blob. /// Nested data type for EmbeddedBlobResourceContents. public sealed partial class EmbeddedBlobResourceContents { @@ -5765,7 +5853,7 @@ public partial class ToolExecutionCompleteContent } -/// Schema for the `ToolExecutionCompleteUIResourceMetaUICsp` type. +/// CSP domain allowlists for an MCP Apps UI resource, including connect, resource, frame, and base URI domains. /// Nested data type for ToolExecutionCompleteUIResourceMetaUICsp. public sealed partial class ToolExecutionCompleteUIResourceMetaUICsp { @@ -5790,60 +5878,60 @@ public sealed partial class ToolExecutionCompleteUIResourceMetaUICsp public string[]? ResourceDomains { get; set; } } -/// Schema for the `ToolExecutionCompleteUIResourceMetaUIPermissionsCamera` type. +/// Marker object for camera permission on an MCP Apps UI resource. /// Nested data type for ToolExecutionCompleteUIResourceMetaUIPermissionsCamera. public sealed partial class ToolExecutionCompleteUIResourceMetaUIPermissionsCamera { } -/// Schema for the `ToolExecutionCompleteUIResourceMetaUIPermissionsClipboardWrite` type. +/// Marker object for clipboard-write permission on an MCP Apps UI resource. /// Nested data type for ToolExecutionCompleteUIResourceMetaUIPermissionsClipboardWrite. public sealed partial class ToolExecutionCompleteUIResourceMetaUIPermissionsClipboardWrite { } -/// Schema for the `ToolExecutionCompleteUIResourceMetaUIPermissionsGeolocation` type. +/// Marker object for geolocation permission on an MCP Apps UI resource. /// Nested data type for ToolExecutionCompleteUIResourceMetaUIPermissionsGeolocation. public sealed partial class ToolExecutionCompleteUIResourceMetaUIPermissionsGeolocation { } -/// Schema for the `ToolExecutionCompleteUIResourceMetaUIPermissionsMicrophone` type. +/// Marker object for microphone permission on an MCP Apps UI resource. /// Nested data type for ToolExecutionCompleteUIResourceMetaUIPermissionsMicrophone. public sealed partial class ToolExecutionCompleteUIResourceMetaUIPermissionsMicrophone { } -/// Schema for the `ToolExecutionCompleteUIResourceMetaUIPermissions` type. +/// Browser permission metadata for an MCP Apps UI resource, including camera, microphone, geolocation, and clipboard-write. /// Nested data type for ToolExecutionCompleteUIResourceMetaUIPermissions. public sealed partial class ToolExecutionCompleteUIResourceMetaUIPermissions { - /// Schema for the `ToolExecutionCompleteUIResourceMetaUIPermissionsCamera` type. + /// Marker object for camera permission on an MCP Apps UI resource. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("camera")] public ToolExecutionCompleteUIResourceMetaUIPermissionsCamera? Camera { get; set; } - /// Schema for the `ToolExecutionCompleteUIResourceMetaUIPermissionsClipboardWrite` type. + /// Marker object for clipboard-write permission on an MCP Apps UI resource. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("clipboardWrite")] public ToolExecutionCompleteUIResourceMetaUIPermissionsClipboardWrite? ClipboardWrite { get; set; } - /// Schema for the `ToolExecutionCompleteUIResourceMetaUIPermissionsGeolocation` type. + /// Marker object for geolocation permission on an MCP Apps UI resource. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("geolocation")] public ToolExecutionCompleteUIResourceMetaUIPermissionsGeolocation? Geolocation { get; set; } - /// Schema for the `ToolExecutionCompleteUIResourceMetaUIPermissionsMicrophone` type. + /// Marker object for microphone permission on an MCP Apps UI resource. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("microphone")] public ToolExecutionCompleteUIResourceMetaUIPermissionsMicrophone? Microphone { get; set; } } -/// Schema for the `ToolExecutionCompleteUIResourceMetaUI` type. +/// MCP Apps UI resource metadata for a completed tool result, including CSP, permissions, domain, and border preference. /// Nested data type for ToolExecutionCompleteUIResourceMetaUI. public sealed partial class ToolExecutionCompleteUIResourceMetaUI { - /// Schema for the `ToolExecutionCompleteUIResourceMetaUICsp` type. + /// CSP domain allowlists for an MCP Apps UI resource, including connect, resource, frame, and base URI domains. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("csp")] public ToolExecutionCompleteUIResourceMetaUICsp? Csp { get; set; } @@ -5853,7 +5941,7 @@ public sealed partial class ToolExecutionCompleteUIResourceMetaUI [JsonPropertyName("domain")] public string? Domain { get; set; } - /// Schema for the `ToolExecutionCompleteUIResourceMetaUIPermissions` type. + /// Browser permission metadata for an MCP Apps UI resource, including camera, microphone, geolocation, and clipboard-write. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("permissions")] public ToolExecutionCompleteUIResourceMetaUIPermissions? Permissions { get; set; } @@ -5868,7 +5956,7 @@ public sealed partial class ToolExecutionCompleteUIResourceMetaUI /// Nested data type for ToolExecutionCompleteUIResourceMeta. public sealed partial class ToolExecutionCompleteUIResourceMeta { - /// Schema for the `ToolExecutionCompleteUIResourceMetaUI` type. + /// MCP Apps UI resource metadata for a completed tool result, including CSP, permissions, domain, and border preference. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("ui")] public ToolExecutionCompleteUIResourceMetaUI? Ui { get; set; } @@ -5943,7 +6031,7 @@ public sealed partial class ToolExecutionCompleteResult public ToolExecutionCompleteUIResource? UiResource { get; set; } } -/// Schema for the `ToolExecutionCompleteToolDescriptionMetaUI` type. +/// MCP Apps tool `_meta.ui` resource URI and visibility captured on `tool.execution_complete`. /// Nested data type for ToolExecutionCompleteToolDescriptionMetaUI. public sealed partial class ToolExecutionCompleteToolDescriptionMetaUI { @@ -5962,7 +6050,7 @@ public sealed partial class ToolExecutionCompleteToolDescriptionMetaUI /// Nested data type for ToolExecutionCompleteToolDescriptionMeta. public sealed partial class ToolExecutionCompleteToolDescriptionMeta { - /// Schema for the `ToolExecutionCompleteToolDescriptionMetaUI` type. + /// MCP Apps tool `_meta.ui` resource URI and visibility captured on `tool.execution_complete`. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("ui")] public ToolExecutionCompleteToolDescriptionMetaUI? Ui { get; set; } @@ -6021,7 +6109,7 @@ public sealed partial class SystemMessageMetadata public IDictionary? Variables { get; set; } } -/// Schema for the `SystemNotificationAgentCompleted` type. +/// System notification metadata for a background agent that completed or failed, including agent ID, type, status, description, and prompt. /// The agent_completed variant of . public sealed partial class SystemNotificationAgentCompleted : SystemNotification { @@ -6052,7 +6140,7 @@ public sealed partial class SystemNotificationAgentCompleted : SystemNotificatio public required SystemNotificationAgentCompletedStatus Status { get; set; } } -/// Schema for the `SystemNotificationAgentIdle` type. +/// System notification metadata for a background agent that became idle, including agent ID, type, and description. /// The agent_idle variant of . public sealed partial class SystemNotificationAgentIdle : SystemNotification { @@ -6074,7 +6162,7 @@ public sealed partial class SystemNotificationAgentIdle : SystemNotification public string? Description { get; set; } } -/// Schema for the `SystemNotificationNewInboxMessage` type. +/// System notification metadata for a new inbox message, including entry ID, sender details, and summary. /// The new_inbox_message variant of . public sealed partial class SystemNotificationNewInboxMessage : SystemNotification { @@ -6099,7 +6187,7 @@ public sealed partial class SystemNotificationNewInboxMessage : SystemNotificati public required string Summary { get; set; } } -/// Schema for the `SystemNotificationShellCompleted` type. +/// System notification metadata for a shell session that completed, including shell ID, optional exit code, and description. /// The shell_completed variant of . public sealed partial class SystemNotificationShellCompleted : SystemNotification { @@ -6122,7 +6210,7 @@ public sealed partial class SystemNotificationShellCompleted : SystemNotificatio public required string ShellId { get; set; } } -/// Schema for the `SystemNotificationShellDetachedCompleted` type. +/// System notification metadata for a detached shell session that completed, including shell ID and description. /// The shell_detached_completed variant of . public sealed partial class SystemNotificationShellDetachedCompleted : SystemNotification { @@ -6140,7 +6228,7 @@ public sealed partial class SystemNotificationShellDetachedCompleted : SystemNot public required string ShellId { get; set; } } -/// Schema for the `SystemNotificationInstructionDiscovered` type. +/// System notification metadata for an instruction file discovered during tool access, including source, trigger file, and tool. /// The instruction_discovered variant of . public sealed partial class SystemNotificationInstructionDiscovered : SystemNotification { @@ -6185,7 +6273,7 @@ public partial class SystemNotification } -/// Schema for the `PermissionRequestShellCommand` type. +/// A parsed command identifier in a shell permission request, including whether it is read-only. /// Nested data type for PermissionRequestShellCommand. public sealed partial class PermissionRequestShellCommand { @@ -6198,7 +6286,7 @@ public sealed partial class PermissionRequestShellCommand public required bool ReadOnly { get; set; } } -/// Schema for the `PermissionRequestShellPossibleUrl` type. +/// A URL that may be accessed by a command in a shell permission request. /// Nested data type for PermissionRequestShellPossibleUrl. public sealed partial class PermissionRequestShellPossibleUrl { @@ -6293,6 +6381,16 @@ public sealed partial class PermissionRequestWrite : PermissionRequest [JsonPropertyName("newFileContents")] public string? NewFileContents { get; set; } + /// True when a built-in file tool (apply_patch / str_replace_editor) asked to write a path the sandbox filesystem policy would block, and the host opted in via sandbox.allowBypass. This is a request, not a grant: the write happens unsandboxed only if the user approves this permission request. Hosts should highlight the elevated risk in the approval UI. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("requestSandboxBypass")] + public bool? RequestSandboxBypass { get; set; } + + /// Justification for the sandbox-bypass request. Only meaningful when requestSandboxBypass is true. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("requestSandboxBypassReason")] + public string? RequestSandboxBypassReason { get; set; } + /// Tool call ID that triggered this permission request. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("toolCallId")] @@ -6378,6 +6476,16 @@ public sealed partial class PermissionRequestUrl : PermissionRequest [JsonPropertyName("intention")] public required string Intention { get; set; } + /// True when this URL fetch is requesting to bypass the sandbox network policy: either the model set requestSandboxBypass: true, or the tool re-issued the request as an interactive bypass after the network policy denied the approved URL (host opted in via sandbox.allowBypass). This is a request, not a grant: the fetch runs only if the user approves this permission request. Hosts should highlight the elevated risk in the approval UI. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("requestSandboxBypass")] + public bool? RequestSandboxBypass { get; set; } + + /// Model-provided justification for the sandbox-bypass request. Only meaningful when requestSandboxBypass is true. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("requestSandboxBypassReason")] + public string? RequestSandboxBypassReason { get; set; } + /// Tool call ID that triggered this permission request. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("toolCallId")] @@ -6554,6 +6662,21 @@ public partial class PermissionRequest } +/// Auto-approval judge information attached to a permission request. Present (non-null) only when the session's allow-all mode is "auto"; its absence means auto mode was off and the judge did not evaluate the request. The `recommendation` conveys the judge's disposition for this request. +/// Nested data type for PermissionAutoApproval. +[Experimental(Diagnostics.Experimental)] +public sealed partial class PermissionAutoApproval +{ + /// Human-readable reason for the judge's recommendation, when available. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("reason")] + public string? Reason { get; set; } + + /// The auto-approval safety judge's outcome for this request. + [JsonPropertyName("recommendation")] + public required AutoApprovalRecommendation Recommendation { get; set; } +} + /// Shell command permission prompt. /// The commands variant of . public sealed partial class PermissionPromptRequestCommands : PermissionPromptRequest @@ -6562,6 +6685,12 @@ public sealed partial class PermissionPromptRequestCommands : PermissionPromptRe [JsonIgnore] public override string Kind => "commands"; + /// Auto-approval judge information for this request; present only when auto mode is enabled. + [Experimental(Diagnostics.Experimental)] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("autoApproval")] + public PermissionAutoApproval? AutoApproval { get; set; } + /// Whether the UI can offer session-wide approval for this command pattern. [JsonPropertyName("canOfferSessionApproval")] public required bool CanOfferSessionApproval { get; set; } @@ -6597,6 +6726,12 @@ public sealed partial class PermissionPromptRequestWrite : PermissionPromptReque [JsonIgnore] public override string Kind => "write"; + /// Auto-approval judge information for this request; present only when auto mode is enabled. + [Experimental(Diagnostics.Experimental)] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("autoApproval")] + public PermissionAutoApproval? AutoApproval { get; set; } + /// Whether the UI can offer session-wide approval for file write operations. [JsonPropertyName("canOfferSessionApproval")] public required bool CanOfferSessionApproval { get; set; } @@ -6632,6 +6767,12 @@ public sealed partial class PermissionPromptRequestRead : PermissionPromptReques [JsonIgnore] public override string Kind => "read"; + /// Auto-approval judge information for this request; present only when auto mode is enabled. + [Experimental(Diagnostics.Experimental)] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("autoApproval")] + public PermissionAutoApproval? AutoApproval { get; set; } + /// Human-readable description of why the file is being read. [JsonPropertyName("intention")] public required string Intention { get; set; } @@ -6659,6 +6800,12 @@ public sealed partial class PermissionPromptRequestMcp : PermissionPromptRequest [JsonPropertyName("args")] public JsonElement? Args { get; set; } + /// Auto-approval judge information for this request; present only when auto mode is enabled. + [Experimental(Diagnostics.Experimental)] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("autoApproval")] + public PermissionAutoApproval? AutoApproval { get; set; } + /// Name of the MCP server providing the tool. [JsonPropertyName("serverName")] public required string ServerName { get; set; } @@ -6685,10 +6832,26 @@ public sealed partial class PermissionPromptRequestUrl : PermissionPromptRequest [JsonIgnore] public override string Kind => "url"; + /// Auto-approval judge information for this request; present only when auto mode is enabled. + [Experimental(Diagnostics.Experimental)] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("autoApproval")] + public PermissionAutoApproval? AutoApproval { get; set; } + /// Human-readable description of why the URL is being accessed. [JsonPropertyName("intention")] public required string Intention { get; set; } + /// True when this URL fetch is requesting to bypass the sandbox network policy: either the model set requestSandboxBypass: true, or the tool re-issued the request as an interactive bypass after the network policy denied the approved URL (host opted in via sandbox.allowBypass). This is a request, not a grant: the fetch runs only if the user approves this permission request. Hosts should highlight the elevated risk in the approval UI. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("requestSandboxBypass")] + public bool? RequestSandboxBypass { get; set; } + + /// Model-provided justification for the sandbox-bypass request. Only meaningful when requestSandboxBypass is true. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("requestSandboxBypassReason")] + public string? RequestSandboxBypassReason { get; set; } + /// Tool call ID that triggered this permission request. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("toolCallId")] @@ -6712,6 +6875,12 @@ public sealed partial class PermissionPromptRequestMemory : PermissionPromptRequ [JsonPropertyName("action")] public PermissionRequestMemoryAction? Action { get; set; } + /// Auto-approval judge information for this request; present only when auto mode is enabled. + [Experimental(Diagnostics.Experimental)] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("autoApproval")] + public PermissionAutoApproval? AutoApproval { get; set; } + /// Source references for the stored fact (store only). [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("citations")] @@ -6755,6 +6924,12 @@ public sealed partial class PermissionPromptRequestCustomTool : PermissionPrompt [JsonPropertyName("args")] public JsonElement? Args { get; set; } + /// Auto-approval judge information for this request; present only when auto mode is enabled. + [Experimental(Diagnostics.Experimental)] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("autoApproval")] + public PermissionAutoApproval? AutoApproval { get; set; } + /// Tool call ID that triggered this permission request. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("toolCallId")] @@ -6781,6 +6956,12 @@ public sealed partial class PermissionPromptRequestPath : PermissionPromptReques [JsonPropertyName("accessKind")] public required PermissionPromptRequestPathAccessKind AccessKind { get; set; } + /// Auto-approval judge information for this request; present only when auto mode is enabled. + [Experimental(Diagnostics.Experimental)] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("autoApproval")] + public PermissionAutoApproval? AutoApproval { get; set; } + /// File paths that require explicit approval. [JsonPropertyName("paths")] public required string[] Paths { get; set; } @@ -6799,6 +6980,12 @@ public sealed partial class PermissionPromptRequestHook : PermissionPromptReques [JsonIgnore] public override string Kind => "hook"; + /// Auto-approval judge information for this request; present only when auto mode is enabled. + [Experimental(Diagnostics.Experimental)] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("autoApproval")] + public PermissionAutoApproval? AutoApproval { get; set; } + /// Optional message from the hook explaining why confirmation is needed. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("hookMessage")] @@ -6827,6 +7014,12 @@ public sealed partial class PermissionPromptRequestExtensionManagement : Permiss [JsonIgnore] public override string Kind => "extension-management"; + /// Auto-approval judge information for this request; present only when auto mode is enabled. + [Experimental(Diagnostics.Experimental)] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("autoApproval")] + public PermissionAutoApproval? AutoApproval { get; set; } + /// Name of the extension being managed. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("extensionName")] @@ -6850,6 +7043,12 @@ public sealed partial class PermissionPromptRequestExtensionPermissionAccess : P [JsonIgnore] public override string Kind => "extension-permission-access"; + /// Auto-approval judge information for this request; present only when auto mode is enabled. + [Experimental(Diagnostics.Experimental)] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("autoApproval")] + public PermissionAutoApproval? AutoApproval { get; set; } + /// Capabilities the extension is requesting. [JsonPropertyName("capabilities")] public required string[] Capabilities { get; set; } @@ -6888,7 +7087,7 @@ public partial class PermissionPromptRequest } -/// Schema for the `PermissionApproved` type. +/// Permission response variant indicating the request was approved without persisting an approval rule. /// The approved variant of . public sealed partial class PermissionResultApproved : PermissionResult { @@ -6897,7 +7096,7 @@ public sealed partial class PermissionResultApproved : PermissionResult public override string Kind => "approved"; } -/// Schema for the `UserToolSessionApprovalCommands` type. +/// Session-scoped tool-approval rule for specific shell command identifiers. /// The commands variant of . public sealed partial class UserToolSessionApprovalCommands : UserToolSessionApproval { @@ -6910,7 +7109,7 @@ public sealed partial class UserToolSessionApprovalCommands : UserToolSessionApp public required string[] CommandIdentifiers { get; set; } } -/// Schema for the `UserToolSessionApprovalRead` type. +/// Session-scoped tool-approval rule for read-only filesystem operations. /// The read variant of . public sealed partial class UserToolSessionApprovalRead : UserToolSessionApproval { @@ -6919,7 +7118,7 @@ public sealed partial class UserToolSessionApprovalRead : UserToolSessionApprova public override string Kind => "read"; } -/// Schema for the `UserToolSessionApprovalWrite` type. +/// Session-scoped tool-approval rule for filesystem write operations. /// The write variant of . public sealed partial class UserToolSessionApprovalWrite : UserToolSessionApproval { @@ -6928,7 +7127,7 @@ public sealed partial class UserToolSessionApprovalWrite : UserToolSessionApprov public override string Kind => "write"; } -/// Schema for the `UserToolSessionApprovalMcp` type. +/// Session-scoped tool-approval rule for an MCP server tool, or all tools on the server when `toolName` is null. /// The mcp variant of . public sealed partial class UserToolSessionApprovalMcp : UserToolSessionApproval { @@ -6945,7 +7144,7 @@ public sealed partial class UserToolSessionApprovalMcp : UserToolSessionApproval public string? ToolName { get; set; } } -/// Schema for the `UserToolSessionApprovalMemory` type. +/// Session-scoped tool-approval rule for writes to long-term memory. /// The memory variant of . public sealed partial class UserToolSessionApprovalMemory : UserToolSessionApproval { @@ -6954,7 +7153,7 @@ public sealed partial class UserToolSessionApprovalMemory : UserToolSessionAppro public override string Kind => "memory"; } -/// Schema for the `UserToolSessionApprovalCustomTool` type. +/// Session-scoped tool-approval rule for a custom tool, keyed by tool name. /// The custom-tool variant of . public sealed partial class UserToolSessionApprovalCustomTool : UserToolSessionApproval { @@ -6967,7 +7166,7 @@ public sealed partial class UserToolSessionApprovalCustomTool : UserToolSessionA public required string ToolName { get; set; } } -/// Schema for the `UserToolSessionApprovalExtensionManagement` type. +/// Session-scoped tool-approval rule for extension-management operations, optionally narrowed by operation. /// The extension-management variant of . public sealed partial class UserToolSessionApprovalExtensionManagement : UserToolSessionApproval { @@ -6981,7 +7180,7 @@ public sealed partial class UserToolSessionApprovalExtensionManagement : UserToo public string? Operation { get; set; } } -/// Schema for the `UserToolSessionApprovalExtensionPermissionAccess` type. +/// Session-scoped tool-approval rule for an extension's permission-gated capability access, keyed by extension name. /// The extension-permission-access variant of . public sealed partial class UserToolSessionApprovalExtensionPermissionAccess : UserToolSessionApproval { @@ -7015,7 +7214,7 @@ public partial class UserToolSessionApproval } -/// Schema for the `PermissionApprovedForSession` type. +/// Permission response variant that approves a request and remembers the provided approval for the rest of the session. /// The approved-for-session variant of . public sealed partial class PermissionResultApprovedForSession : PermissionResult { @@ -7028,7 +7227,7 @@ public sealed partial class PermissionResultApprovedForSession : PermissionResul public required UserToolSessionApproval Approval { get; set; } } -/// Schema for the `PermissionApprovedForLocation` type. +/// Permission response variant that approves a request and persists the provided approval to a project location key. /// The approved-for-location variant of . public sealed partial class PermissionResultApprovedForLocation : PermissionResult { @@ -7045,7 +7244,7 @@ public sealed partial class PermissionResultApprovedForLocation : PermissionResu public required string LocationKey { get; set; } } -/// Schema for the `PermissionCancelled` type. +/// Permission response variant indicating the request was cancelled before use, with an optional reason. /// The cancelled variant of . public sealed partial class PermissionResultCancelled : PermissionResult { @@ -7059,7 +7258,7 @@ public sealed partial class PermissionResultCancelled : PermissionResult public string? Reason { get; set; } } -/// Schema for the `PermissionRule` type. +/// A permission approval or denial rule matched against a tool request, identified by a rule kind with an optional argument value. /// Nested data type for PermissionRule. public sealed partial class PermissionRule { @@ -7072,7 +7271,7 @@ public sealed partial class PermissionRule public required string Kind { get; set; } } -/// Schema for the `PermissionDeniedByRules` type. +/// Permission response variant denied because matching approval rules explicitly blocked the request. /// The denied-by-rules variant of . public sealed partial class PermissionResultDeniedByRules : PermissionResult { @@ -7085,7 +7284,7 @@ public sealed partial class PermissionResultDeniedByRules : PermissionResult public required PermissionRule[] Rules { get; set; } } -/// Schema for the `PermissionDeniedNoApprovalRuleAndCouldNotRequestFromUser` type. +/// Permission response variant denied because no approval rule matched and user confirmation was unavailable. /// The denied-no-approval-rule-and-could-not-request-from-user variant of . public sealed partial class PermissionResultDeniedNoApprovalRuleAndCouldNotRequestFromUser : PermissionResult { @@ -7094,7 +7293,7 @@ public sealed partial class PermissionResultDeniedNoApprovalRuleAndCouldNotReque public override string Kind => "denied-no-approval-rule-and-could-not-request-from-user"; } -/// Schema for the `PermissionDeniedInteractivelyByUser` type. +/// Permission response variant denied in an interactive user prompt, with optional feedback and force-reject flag. /// The denied-interactively-by-user variant of . public sealed partial class PermissionResultDeniedInteractivelyByUser : PermissionResult { @@ -7113,7 +7312,7 @@ public sealed partial class PermissionResultDeniedInteractivelyByUser : Permissi public bool? ForceReject { get; set; } } -/// Schema for the `PermissionDeniedByContentExclusionPolicy` type. +/// Permission response variant denying a path under content exclusion policy, with the path and message. /// The denied-by-content-exclusion-policy variant of . public sealed partial class PermissionResultDeniedByContentExclusionPolicy : PermissionResult { @@ -7130,7 +7329,7 @@ public sealed partial class PermissionResultDeniedByContentExclusionPolicy : Per public required string Path { get; set; } } -/// Schema for the `PermissionDeniedByPermissionRequestHook` type. +/// Permission response variant denied by a permission-request hook, with optional message and interrupt flag. /// The denied-by-permission-request-hook variant of . public sealed partial class PermissionResultDeniedByPermissionRequestHook : PermissionResult { @@ -7252,7 +7451,7 @@ public sealed partial class SessionLimitsExhaustedResponse public double? MaxAiCredits { get; set; } } -/// Schema for the `CommandsChangedCommand` type. +/// A single slash command available in the session, as listed by the `commands.changed` event. /// Nested data type for CommandsChangedCommand. public sealed partial class CommandsChangedCommand { @@ -7286,7 +7485,7 @@ public sealed partial class CapabilitiesChangedUI public bool? McpApps { get; set; } } -/// Schema for the `SkillsLoadedSkill` type. +/// A single resolved skill in `session.skills_loaded`, including source, invocability, enabled state, path, and argument hint. /// Nested data type for SkillsLoadedSkill. public sealed partial class SkillsLoadedSkill { @@ -7321,7 +7520,7 @@ public sealed partial class SkillsLoadedSkill public required bool UserInvocable { get; set; } } -/// Schema for the `CustomAgentsUpdatedAgent` type. +/// A single loaded custom agent in `session.custom_agents_updated`, with identity, source, tools, invocability, and model override. /// Nested data type for CustomAgentsUpdatedAgent. public sealed partial class CustomAgentsUpdatedAgent { @@ -7359,7 +7558,7 @@ public sealed partial class CustomAgentsUpdatedAgent public required bool UserInvocable { get; set; } } -/// Schema for the `McpServersLoadedServer` type. +/// A single MCP server status summary in `session.mcp_servers_loaded`, including name, status, source, transport, and plugin metadata. /// Nested data type for McpServersLoadedServer. public sealed partial class McpServersLoadedServer { @@ -7397,7 +7596,7 @@ public sealed partial class McpServersLoadedServer public McpServerTransport? Transport { get; set; } } -/// Schema for the `ExtensionsLoadedExtension` type. +/// A single extension discovered by `session.extensions_loaded`, including qualified ID, source, and current status. /// Nested data type for ExtensionsLoadedExtension. public sealed partial class ExtensionsLoadedExtension { @@ -7418,7 +7617,7 @@ public sealed partial class ExtensionsLoadedExtension public required ExtensionsLoadedExtensionStatus Status { get; set; } } -/// Schema for the `CanvasRegistryChangedCanvasAction` type. +/// A single action within a canvas declaration, with its name, optional description, and optional input schema. /// Nested data type for CanvasRegistryChangedCanvasAction. [Experimental(Diagnostics.Experimental)] public sealed partial class CanvasRegistryChangedCanvasAction @@ -7438,7 +7637,7 @@ public sealed partial class CanvasRegistryChangedCanvasAction public required string Name { get; set; } } -/// Schema for the `CanvasRegistryChangedCanvas` type. +/// A single canvas declaration in `session.canvas.registry_changed`, including provider IDs, display metadata, input schema, and actions. /// Nested data type for CanvasRegistryChangedCanvas. [Experimental(Diagnostics.Experimental)] public sealed partial class CanvasRegistryChangedCanvas @@ -7486,7 +7685,7 @@ public sealed partial class McpAppToolCallCompleteError public required string Message { get; set; } } -/// Schema for the `McpAppToolCallCompleteToolMetaUI` type. +/// MCP App tool `_meta.ui` resource URI and SEP-1865 visibility captured with an `mcp_app.tool_call_complete` result. /// Nested data type for McpAppToolCallCompleteToolMetaUI. public sealed partial class McpAppToolCallCompleteToolMetaUI { @@ -7505,7 +7704,7 @@ public sealed partial class McpAppToolCallCompleteToolMetaUI /// Nested data type for McpAppToolCallCompleteToolMeta. public sealed partial class McpAppToolCallCompleteToolMeta { - /// Schema for the `McpAppToolCallCompleteToolMetaUI` type. + /// MCP App tool `_meta.ui` resource URI and SEP-1865 visibility captured with an `mcp_app.tool_call_complete` result. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("ui")] public McpAppToolCallCompleteToolMetaUI? Ui { get; set; } @@ -7697,6 +7896,70 @@ public override void Write(Utf8JsonWriter writer, ReasoningSummary value, JsonSe } } +/// Output verbosity level used for supported model calls (e.g. "low", "medium", "high"). +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct Verbosity : IEquatable +{ + private readonly string? _value; + + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public Verbosity(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } + + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; + + /// A terse response was requested. + public static Verbosity Low { get; } = new("low"); + + /// A medium amount of response detail was requested. + public static Verbosity Medium { get; } = new("medium"); + + /// A more detailed response was requested. + public static Verbosity High { get; } = new("high"); + + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(Verbosity left, Verbosity right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(Verbosity left, Verbosity right) => !(left == right); + + /// + public override bool Equals(object? obj) => obj is Verbosity other && Equals(other); + + /// + public bool Equals(Verbosity other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + + /// + public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); + + /// + public override string ToString() => Value; + + /// Provides a for serializing instances. + [EditorBrowsable(EditorBrowsableState.Never)] + public sealed class Converter : JsonConverter + { + /// + public override Verbosity Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } + + /// + public override void Write(Utf8JsonWriter writer, Verbosity value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(Verbosity)); + } + } +} + /// The type of operation performed on the autopilot objective state file. [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] @@ -7892,6 +8155,71 @@ public override void Write(Utf8JsonWriter writer, SessionMode value, JsonSeriali } } +/// Allow-all mode for the session. +[Experimental(Diagnostics.Experimental)] +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct PermissionAllowAllMode : IEquatable +{ + private readonly string? _value; + + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public PermissionAllowAllMode(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } + + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; + + /// Permission requests follow the normal approval flow. + public static PermissionAllowAllMode Off { get; } = new("off"); + + /// Tool, path, and URL permission requests are automatically approved. + public static PermissionAllowAllMode On { get; } = new("on"); + + /// Permission requests follow the normal approval flow with an LLM advisory recommendation attached; clients may choose to auto-approve requests the judge evaluated as acceptable. + public static PermissionAllowAllMode Auto { get; } = new("auto"); + + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(PermissionAllowAllMode left, PermissionAllowAllMode right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(PermissionAllowAllMode left, PermissionAllowAllMode right) => !(left == right); + + /// + public override bool Equals(object? obj) => obj is PermissionAllowAllMode other && Equals(other); + + /// + public bool Equals(PermissionAllowAllMode 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 PermissionAllowAllMode Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } + + /// + public override void Write(Utf8JsonWriter writer, PermissionAllowAllMode value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(PermissionAllowAllMode)); + } + } +} + /// The type of operation performed on the plan file. [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] @@ -8395,46 +8723,42 @@ public override void Write(Utf8JsonWriter writer, UserMessageDelivery value, Jso } } -/// The system that produced a citation. -[Experimental(Diagnostics.Experimental)] +/// Tool call type: "function" for standard tool calls, "custom" for grammar-based tool calls. Defaults to "function" when absent. [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct CitationProvider : IEquatable +public readonly struct AssistantMessageToolRequestType : IEquatable { private readonly string? _value; - /// Initializes a new instance of the struct. - /// The value to associate with this . + /// Initializes a new instance of the struct. + /// The value to associate with this . [JsonConstructor] - public CitationProvider(string value) + public AssistantMessageToolRequestType(string value) { ArgumentException.ThrowIfNullOrWhiteSpace(value); _value = value; } - /// Gets the value associated with this . + /// Gets the value associated with this . public string Value => _value ?? string.Empty; - /// Citation produced by an Anthropic (Claude) model response. - public static CitationProvider Anthropic { get; } = new("anthropic"); - - /// Citation produced by an OpenAI model response. - public static CitationProvider Openai { get; } = new("openai"); + /// Standard function-style tool call. + public static AssistantMessageToolRequestType Function { get; } = new("function"); - /// Citation synthesized client-side by the runtime from tool output. - public static CitationProvider Client { get; } = new("client"); + /// Custom grammar-based tool call. + public static AssistantMessageToolRequestType Custom { get; } = new("custom"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(CitationProvider left, CitationProvider right) => left.Equals(right); + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(AssistantMessageToolRequestType left, AssistantMessageToolRequestType right) => left.Equals(right); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(CitationProvider left, CitationProvider right) => !(left == right); + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(AssistantMessageToolRequestType left, AssistantMessageToolRequestType right) => !(left == right); /// - public override bool Equals(object? obj) => obj is CitationProvider other && Equals(other); + public override bool Equals(object? obj) => obj is AssistantMessageToolRequestType other && Equals(other); /// - public bool Equals(CitationProvider other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(AssistantMessageToolRequestType other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -8442,60 +8766,64 @@ public CitationProvider(string value) /// public override string ToString() => Value; - /// Provides a for serializing instances. + /// Provides a for serializing instances. [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class Converter : JsonConverter + public sealed class Converter : JsonConverter { /// - public override CitationProvider Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override AssistantMessageToolRequestType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, CitationProvider value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, AssistantMessageToolRequestType value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(CitationProvider)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(AssistantMessageToolRequestType)); } } } -/// Tool call type: "function" for standard tool calls, "custom" for grammar-based tool calls. Defaults to "function" when absent. +/// The system that produced a citation. +[Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct AssistantMessageToolRequestType : IEquatable +public readonly struct CitationProvider : IEquatable { private readonly string? _value; - /// Initializes a new instance of the struct. - /// The value to associate with this . + /// Initializes a new instance of the struct. + /// The value to associate with this . [JsonConstructor] - public AssistantMessageToolRequestType(string value) + public CitationProvider(string value) { ArgumentException.ThrowIfNullOrWhiteSpace(value); _value = value; } - /// Gets the value associated with this . + /// Gets the value associated with this . public string Value => _value ?? string.Empty; - /// Standard function-style tool call. - public static AssistantMessageToolRequestType Function { get; } = new("function"); + /// Citation produced by an Anthropic (Claude) model response. + public static CitationProvider Anthropic { get; } = new("anthropic"); - /// Custom grammar-based tool call. - public static AssistantMessageToolRequestType Custom { get; } = new("custom"); + /// Citation produced by an OpenAI model response. + public static CitationProvider Openai { get; } = new("openai"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(AssistantMessageToolRequestType left, AssistantMessageToolRequestType right) => left.Equals(right); + /// Citation synthesized client-side by the runtime from tool output. + public static CitationProvider Client { get; } = new("client"); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(AssistantMessageToolRequestType left, AssistantMessageToolRequestType right) => !(left == right); + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(CitationProvider left, CitationProvider right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(CitationProvider left, CitationProvider right) => !(left == right); /// - public override bool Equals(object? obj) => obj is AssistantMessageToolRequestType other && Equals(other); + public override bool Equals(object? obj) => obj is CitationProvider other && Equals(other); /// - public bool Equals(AssistantMessageToolRequestType other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(CitationProvider other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -8503,20 +8831,20 @@ public AssistantMessageToolRequestType(string value) /// public override string ToString() => Value; - /// Provides a for serializing instances. + /// Provides a for serializing instances. [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class Converter : JsonConverter + public sealed class Converter : JsonConverter { /// - public override AssistantMessageToolRequestType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override CitationProvider Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, AssistantMessageToolRequestType value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, CitationProvider value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(AssistantMessageToolRequestType)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(CitationProvider)); } } } @@ -9512,6 +9840,74 @@ public override void Write(Utf8JsonWriter writer, PermissionRequestMemoryDirecti } } +/// Outcome of the auto-approval safety judge for a permission request. Present only when auto mode is enabled; its absence means the judge did not evaluate the request (auto mode was off). +[Experimental(Diagnostics.Experimental)] +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct AutoApprovalRecommendation : IEquatable +{ + private readonly string? _value; + + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public AutoApprovalRecommendation(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } + + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; + + /// The judge evaluated the request and recommends automatically approving it. + public static AutoApprovalRecommendation Approve { get; } = new("approve"); + + /// The judge evaluated the request and does not recommend auto-approving it; explicit approval is required. Whether that means prompting, denying, or something else is the consumer's decision. + public static AutoApprovalRecommendation RequireApproval { get; } = new("requireApproval"); + + /// Auto mode is enabled, but this request category is never auto-approvable (for example, sandbox-bypass requests), so the judge was not consulted. + public static AutoApprovalRecommendation Excluded { get; } = new("excluded"); + + /// The judge was consulted but did not return a usable recommendation, so the request requires explicit approval. + public static AutoApprovalRecommendation Error { get; } = new("error"); + + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(AutoApprovalRecommendation left, AutoApprovalRecommendation right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(AutoApprovalRecommendation left, AutoApprovalRecommendation right) => !(left == right); + + /// + public override bool Equals(object? obj) => obj is AutoApprovalRecommendation other && Equals(other); + + /// + public bool Equals(AutoApprovalRecommendation 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 AutoApprovalRecommendation Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } + + /// + public override void Write(Utf8JsonWriter writer, AutoApprovalRecommendation value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(AutoApprovalRecommendation)); + } + } +} + /// Underlying permission kind that needs path approval. [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] @@ -10597,6 +10993,8 @@ public override void Write(Utf8JsonWriter writer, ExtensionsLoadedExtensionStatu [JsonSerializable(typeof(AssistantReasoningEvent))] [JsonSerializable(typeof(AssistantStreamingDeltaData))] [JsonSerializable(typeof(AssistantStreamingDeltaEvent))] +[JsonSerializable(typeof(AssistantToolCallDeltaData))] +[JsonSerializable(typeof(AssistantToolCallDeltaEvent))] [JsonSerializable(typeof(AssistantTurnEndData))] [JsonSerializable(typeof(AssistantTurnEndEvent))] [JsonSerializable(typeof(AssistantTurnStartData))] @@ -10707,6 +11105,7 @@ public override void Write(Utf8JsonWriter writer, ExtensionsLoadedExtensionStatu [JsonSerializable(typeof(OmittedBinaryResult))] [JsonSerializable(typeof(PendingMessagesModifiedData))] [JsonSerializable(typeof(PendingMessagesModifiedEvent))] +[JsonSerializable(typeof(PermissionAutoApproval))] [JsonSerializable(typeof(PermissionCompletedData))] [JsonSerializable(typeof(PermissionCompletedEvent))] [JsonSerializable(typeof(PermissionPromptRequest))] diff --git a/dotnet/src/GitHub.Copilot.SDK.csproj b/dotnet/src/GitHub.Copilot.SDK.csproj index 7a9fa2bdca..f48fb802d7 100644 --- a/dotnet/src/GitHub.Copilot.SDK.csproj +++ b/dotnet/src/GitHub.Copilot.SDK.csproj @@ -16,6 +16,7 @@ copilot.png github;copilot;sdk;jsonrpc;agent true + true true snupkg true diff --git a/dotnet/src/JsonRpc.cs b/dotnet/src/JsonRpc.cs index edd0534ab8..bf1684f17b 100644 --- a/dotnet/src/JsonRpc.cs +++ b/dotnet/src/JsonRpc.cs @@ -206,14 +206,12 @@ public void Dispose() private async Task SendMessageAsync(T message, JsonTypeInfo typeInfo, CancellationToken cancellationToken) { - // "Content-Length: " (16) + max int digits (10) + "\r\n\r\n" (4) - const int MaxHeaderLength = 30; - var json = JsonSerializer.SerializeToUtf8Bytes(message, typeInfo); - var headerBuf = ArrayPool.Shared.Rent(MaxHeaderLength); - bool wrote = Utf8.TryWrite(headerBuf, $"Content-Length: {json.Length}\r\n\r\n", out int headerLen); - Debug.Assert(wrote && headerLen > 0); + // Format the LSP header and body into a single pooled buffer so the framed + // message is written in one call — over the FFI transport that is one native + // boundary crossing per message instead of two. + var frame = BuildFrame(json, out int frameLen); // Cancellation only applies to *waiting* for the write lock. Once we hold the lock // and start writing a framed message, we must finish it — cancelling between the @@ -223,15 +221,40 @@ private async Task SendMessageAsync(T message, JsonTypeInfo typeInfo, Canc await _writeLock.WaitAsync(cancellationToken).ConfigureAwait(false); try { - await _sendStream.WriteAsync(headerBuf.AsMemory(0, headerLen), CancellationToken.None).ConfigureAwait(false); - await _sendStream.WriteAsync(json, CancellationToken.None).ConfigureAwait(false); + await _sendStream.WriteAsync(frame.AsMemory(0, frameLen), CancellationToken.None).ConfigureAwait(false); await _sendStream.FlushAsync(CancellationToken.None).ConfigureAwait(false); } finally { _writeLock.Release(); - ArrayPool.Shared.Return(headerBuf); + ArrayPool.Shared.Return(frame); + } + } + + /// + /// Writes Content-Length: N\r\n\r\n followed by into a + /// single buffer rented from . The caller owns the returned + /// buffer and must return it to the shared pool. + /// + private static byte[] BuildFrame(ReadOnlySpan json, out int frameLen) + { + // "Content-Length: " (16) + max int digits (10) + "\r\n\r\n" (4) + const int MaxHeaderLength = 30; + + // Over-rent by the (fixed, tiny) header bound so the header can be written + // straight into the frame — no scratch buffer or header copy. The JSON is + // already UTF-8, so the only copy is placing it after the header, which is + // unavoidable since Content-Length needs its length up front. + var frame = ArrayPool.Shared.Rent(MaxHeaderLength + json.Length); + if (!Utf8.TryWrite(frame, $"Content-Length: {json.Length}\r\n\r\n", out int headerLen)) + { + ArrayPool.Shared.Return(frame); + throw new InvalidOperationException("Failed to write JSON-RPC frame header."); } + + json.CopyTo(frame.AsSpan(headerLen)); + frameLen = headerLen + json.Length; + return frame; } private async Task ReadLoopAsync(CancellationToken cancellationToken) diff --git a/dotnet/src/Session.cs b/dotnet/src/Session.cs index f009faa0bd..ae010a97f3 100644 --- a/dotnet/src/Session.cs +++ b/dotnet/src/Session.cs @@ -1787,6 +1787,7 @@ await Rpc.Model.SwitchToAsync( model, options.ReasoningEffort, options.ReasoningSummary, + null, options.ModelCapabilities, options.ContextTier, cancellationToken); diff --git a/dotnet/src/Types.cs b/dotnet/src/Types.cs index 172d9305f1..65295d3d24 100644 --- a/dotnet/src/Types.cs +++ b/dotnet/src/Types.cs @@ -145,6 +145,20 @@ public static TcpRuntimeConnection ForTcp(int port = 0, string? connectionToken /// Optional shared secret to authenticate the connection. public static UriRuntimeConnection ForUri(string url, string? connectionToken = null) => new() { Url = url, ConnectionToken = connectionToken }; + + /// + /// Host the runtime in-process by loading its native library and communicating + /// over the C ABI (FFI) — no child process is spawned by the SDK for JSON-RPC + /// transport. The bundled runtime is used; to point at a non-default runtime + /// entrypoint, set the COPILOT_CLI_PATH environment variable. + /// + /// + /// Works across the SDK's target frameworks: modern .NET uses NativeLibrary, + /// while netstandard2.0 consumers use a built-in fallback native loader. + /// + [Experimental(Diagnostics.Experimental)] + public static InProcessRuntimeConnection ForInProcess() + => new(); } /// @@ -159,6 +173,16 @@ internal ChildProcessRuntimeConnection() { } /// Extra command-line arguments to pass to the runtime process. public IList? Args { get; set; } + + /// + /// Gets or sets the environment variables passed to the spawned runtime process, + /// replacing the inherited environment. + /// + /// + /// Cannot be combined with ; setting both throws + /// an when the client is constructed. + /// + public IReadOnlyDictionary? Environment { get; set; } } /// @@ -170,6 +194,19 @@ public sealed class StdioRuntimeConnection : ChildProcessRuntimeConnection internal StdioRuntimeConnection() { } } +/// +/// Hosts the runtime in-process by loading its native library and communicating +/// over the C ABI (FFI). Construct via . +/// Works across the SDK's target frameworks (modern .NET and netstandard2.0). +/// To point at a non-default runtime entrypoint, set the COPILOT_CLI_PATH +/// environment variable. +/// +[Experimental(Diagnostics.Experimental)] +public sealed class InProcessRuntimeConnection : RuntimeConnection +{ + internal InProcessRuntimeConnection() { } +} + /// /// Spawns a runtime child process listening on a TCP socket. Construct via /// . @@ -281,6 +318,7 @@ private CopilotClientOptions(CopilotClientOptions? other) OnListModels = other.OnListModels; SessionFs = other.SessionFs; RequestHandler = other.RequestHandler; + OnGitHubTelemetry = other.OnGitHubTelemetry; SessionIdleTimeoutSeconds = other.SessionIdleTimeoutSeconds; EnableRemoteSessions = other.EnableRemoteSessions; Mode = other.Mode; @@ -330,7 +368,15 @@ private CopilotClientOptions(CopilotClientOptions? other) /// public CopilotLogLevel? LogLevel { get; set; } - /// Environment variables to pass to the runtime process. + /// + /// Gets or sets environment variables passed to the runtime process. + /// + /// + /// Not supported with the in-process transport (), + /// which runs the runtime in the host process; setting this option there throws an + /// . For child-process transports, prefer + /// ; setting both throws. + /// public IReadOnlyDictionary? Environment { get; set; } /// Logger instance for SDK diagnostic output. @@ -378,6 +424,15 @@ private CopilotClientOptions(CopilotClientOptions? other) [Experimental(Diagnostics.Experimental)] public CopilotRequestHandler? RequestHandler { get; set; } + /// + /// Experimental. Receives GitHub telemetry events the runtime forwards to this + /// connection; setting a handler opts created/resumed sessions into forwarding. + /// The SDK awaits the handler task so it may perform asynchronous work. + /// + [Experimental(Diagnostics.Experimental)] + [EditorBrowsable(EditorBrowsableState.Never)] + public Func? OnGitHubTelemetry { get; set; } + /// /// OpenTelemetry configuration for the runtime. /// When set to a non- instance, the runtime is started with OpenTelemetry instrumentation enabled. diff --git a/dotnet/src/build/GitHub.Copilot.SDK.targets b/dotnet/src/build/GitHub.Copilot.SDK.targets index 94b6515ea7..5a5e511811 100644 --- a/dotnet/src/build/GitHub.Copilot.SDK.targets +++ b/dotnet/src/build/GitHub.Copilot.SDK.targets @@ -35,6 +35,13 @@ <_CopilotPlatform Condition="'$(_CopilotRid)' == 'osx-arm64'">darwin-arm64 <_CopilotBinary Condition="$(_CopilotRid.StartsWith('win-'))">copilot.exe <_CopilotBinary Condition="'$(_CopilotBinary)' == ''">copilot + + <_CopilotRuntimeLib Condition="$(_CopilotRid.StartsWith('win-'))">copilot_runtime.dll + <_CopilotRuntimeLib Condition="$(_CopilotRid.StartsWith('osx-'))">libcopilot_runtime.dylib + <_CopilotRuntimeLib Condition="'$(_CopilotRuntimeLib)' == ''">libcopilot_runtime.so + <_CopilotRuntimeNodePath>$(_CopilotCacheDir)\prebuilds\$(_CopilotPlatform)\runtime.node + + diff --git a/dotnet/test/AssemblyInfo.cs b/dotnet/test/AssemblyInfo.cs index e34f0e255b..9380ca48cd 100644 --- a/dotnet/test/AssemblyInfo.cs +++ b/dotnet/test/AssemblyInfo.cs @@ -3,6 +3,7 @@ *--------------------------------------------------------------------------------------------*/ using Xunit; +using GitHub.Copilot.Test.Harness; // Each E2E test class fixture spins up its own Copilot CLI subprocess plus a CapiProxy // (replaying HTTP proxy) Node.js subprocess. With ~25 test classes, running them in parallel @@ -13,3 +14,5 @@ // (a) sharing a single CLI subprocess across classes, or (b) gating concurrency with a // semaphore that limits concurrent fixtures to a small number (e.g. 2-3). [assembly: CollectionBehavior(DisableTestParallelization = true)] + +[assembly: InProcessEnvIsolation] diff --git a/dotnet/test/ConnectionTokenTests.cs b/dotnet/test/ConnectionTokenTests.cs index 524ff25861..3192bada6b 100644 --- a/dotnet/test/ConnectionTokenTests.cs +++ b/dotnet/test/ConnectionTokenTests.cs @@ -113,7 +113,7 @@ public class ConnectionTokenAutoGeneratedTests : IAsyncLifetime public async Task InitializeAsync() { _ctx = await E2ETestContext.CreateAsync(); - _client = _ctx.CreateClient(useStdio: false); + _client = _ctx.CreateClient(options: new CopilotClientOptions { Connection = RuntimeConnection.ForTcp() }); } public async Task DisposeAsync() diff --git a/dotnet/test/E2E/ClientE2ETests.cs b/dotnet/test/E2E/ClientE2ETests.cs index 9972e3b336..5de6ce159a 100644 --- a/dotnet/test/E2E/ClientE2ETests.cs +++ b/dotnet/test/E2E/ClientE2ETests.cs @@ -33,6 +33,32 @@ public async Task Should_Start_And_Connect_To_Server(bool useStdio) } } + [Fact] + public async Task Should_Start_And_Connect_Over_InProcess_Ffi() + { + // In-process FFI hosting resolves the CLI entrypoint (COPILOT_CLI_PATH or the + // bundled CLI binary) and its sibling native runtime library itself; if neither + // is available, StartAsync throws and the test fails hard. + using var client = new CopilotClient(new CopilotClientOptions + { + Connection = RuntimeConnection.ForInProcess(), + }); + + try + { + await client.StartAsync(); + var pong = await client.PingAsync("ffi message"); + Assert.Equal("pong: ffi message", pong.Message); + Assert.NotEqual(default, pong.Timestamp); + + await client.StopAsync(); + } + finally + { + await client.ForceStopAsync(); + } + } + [Theory] [InlineData(true)] // stdio transport [InlineData(false)] // TCP transport diff --git a/dotnet/test/E2E/ClientOptionsE2ETests.cs b/dotnet/test/E2E/ClientOptionsE2ETests.cs index c2f16a042b..a8ceda5b08 100644 --- a/dotnet/test/E2E/ClientOptionsE2ETests.cs +++ b/dotnet/test/E2E/ClientOptionsE2ETests.cs @@ -71,7 +71,6 @@ public async Task Should_Propagate_Process_Options_To_Spawned_Cli() { Connection = RuntimeConnection.ForStdio(path: cliPath, args: ["--capture-file", capturePath]), BaseDirectory = copilotHomeFromOption, - Environment = clientEnv, GitHubToken = "process-option-token", LogLevel = CopilotLogLevel.Debug, SessionIdleTimeoutSeconds = 17, @@ -85,7 +84,7 @@ public async Task Should_Propagate_Process_Options_To_Spawned_Cli() CaptureContent = true, }, UseLoggedInUser = false, - }); + }, environment: clientEnv); await client.StartAsync(); @@ -219,6 +218,205 @@ public async Task Should_Forward_Granular_Multitenancy_Fields_In_Create_Wire_Req await session.DisposeAsync(); } + [Fact] + public async Task Should_Forward_Advanced_Session_Options_In_Create_Wire_Request() + { + var (cliPath, capturePath) = await CreateFakeCliCaptureAsync(); + var outputDirectory = Path.Join(Ctx.WorkDir, "large-output-create"); + + await using var client = Ctx.CreateClient(options: new CopilotClientOptions + { + Connection = RuntimeConnection.ForStdio(path: cliPath, args: ["--capture-file", capturePath]), + UseLoggedInUser = false, + }); + + await client.StartAsync(); + + var session = await client.CreateSessionAsync(new SessionConfig + { + ClientName = "advanced-create-client", + Model = "claude-sonnet-4.5", + ReasoningEffort = "medium", + ReasoningSummary = ReasoningSummary.Detailed, + ContextTier = ContextTier.LongContext, + EnableCitations = true, + Capi = new CapiSessionOptions { EnableWebSocketResponses = false }, + McpOAuthTokenStorage = McpOAuthTokenStorageMode.Persistent, + CustomAgents = + [ + new CustomAgentConfig + { + Name = "agent-one", + DisplayName = "Agent One", + Description = "Handles agent-one tasks.", + Prompt = "Be agent one.", + Tools = ["view"], + Infer = true, + Skills = ["create-skill"], + Model = "claude-haiku-4.5", + }, + ], + DefaultAgent = new DefaultAgentConfig { ExcludedTools = ["edit"] }, + Agent = "agent-one", + SkillDirectories = ["skills-create"], + DisabledSkills = ["disabled-create-skill"], + PluginDirectories = ["plugins-create"], + InfiniteSessions = new InfiniteSessionConfig + { + Enabled = false, + BackgroundCompactionThreshold = 0.5, + BufferExhaustionThreshold = 0.9, + }, + LargeOutput = new LargeToolOutputConfig + { + Enabled = true, + MaxSizeBytes = 4096, + OutputDirectory = outputDirectory, + }, + Memory = new MemoryConfiguration { Enabled = true }, + GitHubToken = "session-create-token", + RemoteSession = GitHub.Copilot.Rpc.RemoteSessionMode.Export, + Cloud = new CloudSessionOptions + { + Repository = new CloudSessionRepository + { + Owner = "github", + Name = "copilot-sdk", + Branch = "main", + }, + }, + EnableMcpApps = true, + RequestCanvasRenderer = true, + RequestExtensions = true, + ExtensionSdkPath = "custom-extension-sdk", + ExtensionInfo = new ExtensionInfo { Source = "dotnet-sdk-tests", Name = "advanced-create-extension" }, + Canvases = + [ + new CanvasDeclaration + { + Id = "advanced-create-canvas", + DisplayName = "Advanced Create Canvas", + Description = "Covers create-time canvas options.", + }, + ], + Providers = + [ + new NamedProviderConfig + { + Name = "create-provider", + Type = "openai", + WireApi = "responses", + BaseUrl = "https://create-provider.example.test/v1", + ApiKey = "create-provider-key", + Headers = new Dictionary { ["X-Create-Provider"] = "yes" }, + }, + ], + Models = + [ + new ProviderModelConfig + { + Provider = "create-provider", + Id = "create-model", + Name = "Create Model", + ModelId = "claude-sonnet-4.5", + WireModel = "create-wire-model", + MaxContextWindowTokens = 12_000, + MaxPromptTokens = 10_000, + MaxOutputTokens = 2_000, + }, + ], + OnPermissionRequest = PermissionHandler.ApproveAll, + }); + + using var capture = JsonDocument.Parse(await File.ReadAllTextAsync(capturePath)); + var createRequest = GetCapturedRequestParams(capture.RootElement, "session.create"); + Assert.Equal("advanced-create-client", createRequest.GetProperty("clientName").GetString()); + Assert.Equal("claude-sonnet-4.5", createRequest.GetProperty("model").GetString()); + Assert.Equal("medium", createRequest.GetProperty("reasoningEffort").GetString()); + Assert.Equal("detailed", createRequest.GetProperty("reasoningSummary").GetString()); + Assert.Equal("long_context", createRequest.GetProperty("contextTier").GetString()); + Assert.True(createRequest.GetProperty("enableCitations").GetBoolean()); + Assert.False(createRequest.GetProperty("capi").GetProperty("enableWebSocketResponses").GetBoolean()); + Assert.Equal("persistent", createRequest.GetProperty("mcpOAuthTokenStorage").GetString()); + Assert.Equal("agent-one", createRequest.GetProperty("agent").GetString()); + Assert.Equal("edit", createRequest.GetProperty("defaultAgent").GetProperty("excludedTools")[0].GetString()); + Assert.Equal("agent-one", createRequest.GetProperty("customAgents")[0].GetProperty("name").GetString()); + Assert.Equal("plugins-create", createRequest.GetProperty("pluginDirectories")[0].GetString()); + Assert.Equal("disabled-create-skill", createRequest.GetProperty("disabledSkills")[0].GetString()); + Assert.False(createRequest.GetProperty("infiniteSessions").GetProperty("enabled").GetBoolean()); + Assert.True(createRequest.GetProperty("largeOutput").GetProperty("enabled").GetBoolean()); + Assert.Equal(4096, createRequest.GetProperty("largeOutput").GetProperty("maxSizeBytes").GetInt64()); + Assert.Equal(outputDirectory, createRequest.GetProperty("largeOutput").GetProperty("outputDir").GetString()); + Assert.True(createRequest.GetProperty("memory").GetProperty("enabled").GetBoolean()); + Assert.Equal("session-create-token", createRequest.GetProperty("gitHubToken").GetString()); + Assert.Equal("export", createRequest.GetProperty("remoteSession").GetString()); + Assert.Equal("github", createRequest.GetProperty("cloud").GetProperty("repository").GetProperty("owner").GetString()); + Assert.True(createRequest.GetProperty("requestMcpApps").GetBoolean()); + Assert.True(createRequest.GetProperty("requestCanvasRenderer").GetBoolean()); + Assert.True(createRequest.GetProperty("requestExtensions").GetBoolean()); + Assert.Equal("custom-extension-sdk", createRequest.GetProperty("extensionSdkPath").GetString()); + Assert.Equal("advanced-create-extension", createRequest.GetProperty("extensionInfo").GetProperty("name").GetString()); + Assert.Equal("advanced-create-canvas", createRequest.GetProperty("canvases")[0].GetProperty("id").GetString()); + Assert.Equal("create-provider", createRequest.GetProperty("providers")[0].GetProperty("name").GetString()); + Assert.Equal("responses", createRequest.GetProperty("providers")[0].GetProperty("wireApi").GetString()); + Assert.Equal("create-model", createRequest.GetProperty("models")[0].GetProperty("id").GetString()); + Assert.Equal(12000, createRequest.GetProperty("models")[0].GetProperty("maxContextWindowTokens").GetInt32()); + + await session.DisposeAsync(); + } + + [Fact] + public async Task Should_Forward_Singular_Provider_Options_In_Create_Wire_Request() + { + var (cliPath, capturePath) = await CreateFakeCliCaptureAsync(); + + await using var client = Ctx.CreateClient(options: new CopilotClientOptions + { + Connection = RuntimeConnection.ForStdio(path: cliPath, args: ["--capture-file", capturePath]), + UseLoggedInUser = false, + }); + + await client.StartAsync(); + + var session = await client.CreateSessionAsync(new SessionConfig + { + Model = "claude-sonnet-4.5", + Provider = new ProviderConfig + { + Type = "azure", + WireApi = "responses", + Transport = "http", + BaseUrl = "https://azure-provider.example.test/openai", + ApiKey = "provider-api-key", + BearerToken = "provider-bearer-token", + Azure = new AzureOptions { ApiVersion = "2024-02-15-preview" }, + Headers = new Dictionary { ["X-Provider-Wire"] = "yes" }, + ModelId = "claude-sonnet-4.5", + WireModel = "azure-deployment", + MaxPromptTokens = 8192, + MaxOutputTokens = 1024, + }, + OnPermissionRequest = PermissionHandler.ApproveAll, + }); + + using var capture = JsonDocument.Parse(await File.ReadAllTextAsync(capturePath)); + var provider = GetCapturedRequestParams(capture.RootElement, "session.create").GetProperty("provider"); + Assert.Equal("azure", provider.GetProperty("type").GetString()); + Assert.Equal("responses", provider.GetProperty("wireApi").GetString()); + Assert.Equal("http", provider.GetProperty("transport").GetString()); + Assert.Equal("https://azure-provider.example.test/openai", provider.GetProperty("baseUrl").GetString()); + Assert.Equal("provider-api-key", provider.GetProperty("apiKey").GetString()); + Assert.Equal("provider-bearer-token", provider.GetProperty("bearerToken").GetString()); + Assert.Equal("2024-02-15-preview", provider.GetProperty("azure").GetProperty("apiVersion").GetString()); + Assert.Equal("yes", provider.GetProperty("headers").GetProperty("X-Provider-Wire").GetString()); + Assert.Equal("claude-sonnet-4.5", provider.GetProperty("modelId").GetString()); + Assert.Equal("azure-deployment", provider.GetProperty("wireModel").GetString()); + Assert.Equal(8192, provider.GetProperty("maxPromptTokens").GetInt32()); + Assert.Equal(1024, provider.GetProperty("maxOutputTokens").GetInt32()); + + await session.DisposeAsync(); + } + [Fact] public async Task Should_Apply_Empty_Mode_Defaults_To_CreateSession_Wire_Request() { @@ -411,6 +609,88 @@ public async Task Should_Forward_Granular_Multitenancy_Fields_In_Resume_Wire_Req await session.DisposeAsync(); } + [Fact] + public async Task Should_Forward_Advanced_Session_Options_In_Resume_Wire_Request() + { + var (cliPath, capturePath) = await CreateFakeCliCaptureAsync(); + var outputDirectory = Path.Join(Ctx.WorkDir, "large-output-resume"); + using var canvasInput = JsonDocument.Parse("{\"start\":41}"); + + await using var client = Ctx.CreateClient(options: new CopilotClientOptions + { + Connection = RuntimeConnection.ForStdio(path: cliPath, args: ["--capture-file", capturePath]), + UseLoggedInUser = false, + }); + + await client.StartAsync(); + + var session = await client.ResumeSessionAsync("advanced-resume-session", new ResumeSessionConfig + { + ClientName = "advanced-resume-client", + Model = "claude-haiku-4.5", + ReasoningEffort = "low", + ReasoningSummary = ReasoningSummary.None, + ContextTier = ContextTier.Default, + SuppressResumeEvent = true, + ContinuePendingWork = true, + McpOAuthTokenStorage = McpOAuthTokenStorageMode.Persistent, + PluginDirectories = ["plugins-resume"], + LargeOutput = new LargeToolOutputConfig + { + Enabled = false, + MaxSizeBytes = 2048, + OutputDirectory = outputDirectory, + }, + Memory = new MemoryConfiguration { Enabled = false }, + RemoteSession = GitHub.Copilot.Rpc.RemoteSessionMode.On, + OpenCanvases = + [ + new GitHub.Copilot.Rpc.OpenCanvasInstance + { + CanvasId = "resume-canvas", + ExtensionId = "dotnet-sdk-tests/resume-extension", + ExtensionName = "Resume Extension", + InstanceId = "resume-canvas-1", + Input = canvasInput.RootElement.Clone(), + Status = "ready", + Title = "Resume Canvas", + Url = "https://example.com/resume-canvas", + }, + ], + OnPermissionRequest = PermissionHandler.ApproveAll, + }); + + using var capture = JsonDocument.Parse(await File.ReadAllTextAsync(capturePath)); + var resumeRequest = GetCapturedRequestParams(capture.RootElement, "session.resume"); + Assert.Equal("advanced-resume-session", resumeRequest.GetProperty("sessionId").GetString()); + Assert.Equal("advanced-resume-client", resumeRequest.GetProperty("clientName").GetString()); + Assert.Equal("claude-haiku-4.5", resumeRequest.GetProperty("model").GetString()); + Assert.Equal("low", resumeRequest.GetProperty("reasoningEffort").GetString()); + Assert.Equal("none", resumeRequest.GetProperty("reasoningSummary").GetString()); + Assert.Equal("default", resumeRequest.GetProperty("contextTier").GetString()); + Assert.True(resumeRequest.GetProperty("disableResume").GetBoolean()); + Assert.True(resumeRequest.GetProperty("continuePendingWork").GetBoolean()); + Assert.Equal("persistent", resumeRequest.GetProperty("mcpOAuthTokenStorage").GetString()); + Assert.Equal("plugins-resume", resumeRequest.GetProperty("pluginDirectories")[0].GetString()); + Assert.False(resumeRequest.GetProperty("largeOutput").GetProperty("enabled").GetBoolean()); + Assert.Equal(2048, resumeRequest.GetProperty("largeOutput").GetProperty("maxSizeBytes").GetInt64()); + Assert.Equal(outputDirectory, resumeRequest.GetProperty("largeOutput").GetProperty("outputDir").GetString()); + Assert.False(resumeRequest.GetProperty("memory").GetProperty("enabled").GetBoolean()); + Assert.Equal("on", resumeRequest.GetProperty("remoteSession").GetString()); + + var openCanvas = resumeRequest.GetProperty("openCanvases")[0]; + Assert.Equal("resume-canvas", openCanvas.GetProperty("canvasId").GetString()); + Assert.Equal("dotnet-sdk-tests/resume-extension", openCanvas.GetProperty("extensionId").GetString()); + Assert.Equal("Resume Extension", openCanvas.GetProperty("extensionName").GetString()); + Assert.Equal("resume-canvas-1", openCanvas.GetProperty("instanceId").GetString()); + Assert.Equal(41, openCanvas.GetProperty("input").GetProperty("start").GetInt32()); + Assert.Equal("ready", openCanvas.GetProperty("status").GetString()); + Assert.Equal("Resume Canvas", openCanvas.GetProperty("title").GetString()); + Assert.Equal("https://example.com/resume-canvas", openCanvas.GetProperty("url").GetString()); + + await session.DisposeAsync(); + } + [Fact] public async Task Should_Apply_Empty_Mode_Defaults_To_ResumeSession_Wire_Request() { diff --git a/dotnet/test/E2E/CopilotRequestE2EProvider.cs b/dotnet/test/E2E/CopilotRequestE2EProvider.cs index e8e483556c..bade5c6e5d 100644 --- a/dotnet/test/E2E/CopilotRequestE2EProvider.cs +++ b/dotnet/test/E2E/CopilotRequestE2EProvider.cs @@ -96,7 +96,9 @@ private static HttpResponseMessage BuildInferenceResponse(string url, string bod if (u.EndsWith("/messages", StringComparison.Ordinal)) { - return Json(BufferedAnthropicMessageJson); + return wantsStream + ? Sse(string.Concat(AnthropicStreamEvents)) + : Json(BufferedAnthropicMessageJson); } // /chat/completions non-streaming (and any other inference url) — buffered JSON. @@ -158,6 +160,20 @@ internal static HttpResponseMessage BuildNonInferenceResponse(string url) "data: [DONE]\n\n", ]; + // Anthropic Messages streaming (SSE) sequence. Emitted when the runtime issues a + // streaming /messages request (stream: true); the buffered JSON below is only valid + // for non-streaming requests, and returning it for a streaming request makes the + // runtime's Anthropic client fail with "stream ended without producing a Message". + private static readonly string[] AnthropicStreamEvents = + [ + "event: message_start\ndata: {\"type\":\"message_start\",\"message\":{\"id\":\"msg_stub_1\",\"type\":\"message\",\"role\":\"assistant\",\"model\":\"claude-sonnet-4.5\",\"content\":[],\"stop_reason\":null,\"stop_sequence\":null,\"usage\":{\"input_tokens\":5,\"output_tokens\":1}}}\n\n", + "event: content_block_start\ndata: {\"type\":\"content_block_start\",\"index\":0,\"content_block\":{\"type\":\"text\",\"text\":\"\"}}\n\n", + "event: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\"" + SyntheticText + "\"}}\n\n", + "event: content_block_stop\ndata: {\"type\":\"content_block_stop\",\"index\":0}\n\n", + "event: message_delta\ndata: {\"type\":\"message_delta\",\"delta\":{\"stop_reason\":\"end_turn\",\"stop_sequence\":null},\"usage\":{\"output_tokens\":7}}\n\n", + "event: message_stop\ndata: {\"type\":\"message_stop\"}\n\n", + ]; + private static readonly string BufferedResponseJson = "{\"id\":\"resp_stub_1\",\"object\":\"response\",\"status\":\"completed\",\"output\":[{\"id\":\"msg_1\",\"type\":\"message\",\"role\":\"assistant\",\"content\":[{\"type\":\"output_text\",\"text\":\"" + SyntheticText + "\"}]}],\"usage\":{\"input_tokens\":5,\"output_tokens\":7,\"total_tokens\":12}}"; diff --git a/dotnet/test/E2E/CopilotRequestWebSocketE2ETests.cs b/dotnet/test/E2E/CopilotRequestWebSocketE2ETests.cs index 464aadb66a..9bb19df7d5 100644 --- a/dotnet/test/E2E/CopilotRequestWebSocketE2ETests.cs +++ b/dotnet/test/E2E/CopilotRequestWebSocketE2ETests.cs @@ -51,8 +51,7 @@ public async Task Services_A_WebSocket_Turn_End_To_End_Via_The_Request_Handler() { Connection = RuntimeConnection.ForStdio(), RequestHandler = handler, - Environment = env, - }); + }, environment: env); await client.StartAsync(); var session = await client.CreateSessionAsync(new SessionConfig diff --git a/dotnet/test/E2E/GitHubTelemetryForwardingE2ETests.cs b/dotnet/test/E2E/GitHubTelemetryForwardingE2ETests.cs new file mode 100644 index 0000000000..343c4815b7 --- /dev/null +++ b/dotnet/test/E2E/GitHubTelemetryForwardingE2ETests.cs @@ -0,0 +1,63 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +using System.Collections.Concurrent; +using GitHub.Copilot.Rpc; +using GitHub.Copilot.Test.Harness; +using Xunit; +using Xunit.Abstractions; + +namespace GitHub.Copilot.Test.E2E; + +#pragma warning disable GHCP001 // GitHub telemetry forwarding is experimental. + +public class GitHubTelemetryForwardingE2ETests(E2ETestFixture fixture, ITestOutputHelper output) + : E2ETestBase(fixture, "github_telemetry", output) +{ + [Fact] + public async Task Should_Forward_GitHub_Telemetry_For_A_Live_Session() + { + var notifications = new ConcurrentQueue(); + + await using var client = Ctx.CreateClient(options: new CopilotClientOptions + { + OnGitHubTelemetry = notification => + { + notifications.Enqueue(notification); + return Task.CompletedTask; + }, + }); + + CopilotSession? session = null; + try + { + session = await client.CreateSessionAsync(new SessionConfig + { + OnPermissionRequest = PermissionHandler.ApproveAll, + }); + + await TestHelper.WaitForConditionAsync( + () => !notifications.IsEmpty, + timeout: TimeSpan.FromSeconds(30), + timeoutMessage: "Timed out waiting for GitHub telemetry notification."); + + Assert.True(notifications.TryPeek(out var notification)); + Assert.False(string.IsNullOrEmpty(notification.SessionId)); + Assert.NotNull(notification.Event); + Assert.NotEmpty(notification.Event.Kind); + Assert.IsType(notification.Restricted); + } + finally + { + if (session is not null) + { + await session.DisposeAsync(); + } + + await client.StopAsync(); + } + } +} + +#pragma warning restore GHCP001 diff --git a/dotnet/test/E2E/McpOAuthE2ETests.cs b/dotnet/test/E2E/McpOAuthE2ETests.cs index 417b7ad1bd..f101bbc31b 100644 --- a/dotnet/test/E2E/McpOAuthE2ETests.cs +++ b/dotnet/test/E2E/McpOAuthE2ETests.cs @@ -70,6 +70,57 @@ public async Task Should_Satisfy_MCP_OAuth_Using_Host_Provided_Token() Assert.Contains(requests, request => request.Authorization == $"Bearer {ExpectedToken}"); } + [Fact] + public async Task Should_Resolve_Pending_MCP_OAuth_Request_With_Direct_Rpc() + { + await using var oauthServer = await OAuthMcpServer.StartAsync(ExpectedToken); + var serverName = "oauth-direct-rpc-mcp"; + var authRequest = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var releaseHandler = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + + await using var session = await CreateSessionAsync(new SessionConfig + { + OnMcpAuthRequest = request => + { + authRequest.TrySetResult(request); + return releaseHandler.Task; + }, + McpServers = new Dictionary + { + [serverName] = new McpHttpServerConfig + { + Url = $"{oauthServer.Url}/mcp", + Tools = ["*"], + }, + }, + }); + + var connected = WaitForMcpServerStatusAsync(session, serverName, McpServerStatus.Connected); + var request = await authRequest.Task.WaitAsync(TimeSpan.FromSeconds(30)); + Assert.NotEmpty(request.RequestId); + Assert.Equal(serverName, request.ServerName); + Assert.Equal($"{oauthServer.Url}/mcp", request.ServerUrl); + Assert.Equal(McpOauthRequestReason.Initial, request.Reason); + Assert.NotNull(request.WwwAuthenticateParams); + Assert.Equal("mcp.read", request.WwwAuthenticateParams!.Scope); + + var handled = await session.Rpc.Mcp.Oauth.HandlePendingRequestAsync( + request.RequestId, + new McpOauthPendingRequestResponseToken + { + AccessToken = ExpectedToken, + TokenType = "Bearer", + ExpiresIn = 3600, + }); + Assert.True(handled.Success); + + await connected; + var tools = await session.Rpc.Mcp.ListToolsAsync(serverName); + Assert.Contains(tools.Tools, tool => tool.Name == "whoami"); + + releaseHandler.SetResult(McpAuthResult.FromToken(new McpAuthToken { AccessToken = ExpectedToken })); + } + [Fact] public async Task Should_Request_Replacement_Tokens_Across_MCP_OAuth_Lifecycle() { @@ -172,7 +223,7 @@ public async Task Should_Cancel_Pending_MCP_OAuth_Request() } }); - await WaitForMcpServerStatusAsync(session, serverName, McpServerStatus.Failed); + await WaitForMcpServerStatusAsync(session, serverName, McpServerStatus.NeedsAuth); Assert.NotNull(observedRequest); Assert.NotEmpty(observedRequest!.RequestId); diff --git a/dotnet/test/E2E/ModeHandlersE2ETests.cs b/dotnet/test/E2E/ModeHandlersE2ETests.cs index 40552fa9f7..a397999f73 100644 --- a/dotnet/test/E2E/ModeHandlersE2ETests.cs +++ b/dotnet/test/E2E/ModeHandlersE2ETests.cs @@ -155,7 +155,7 @@ private CopilotClient CreateAuthenticatedClient() ["COPILOT_DEBUG_GITHUB_API_URL"] = Ctx.ProxyUrl, }; - return Ctx.CreateClient(options: new CopilotClientOptions { Environment = env }); + return Ctx.CreateClient(environment: env); } private Task ConfigureAuthenticatedUserAsync() diff --git a/dotnet/test/E2E/PerSessionAuthE2ETests.cs b/dotnet/test/E2E/PerSessionAuthE2ETests.cs index 7e104b33de..d1226f3737 100644 --- a/dotnet/test/E2E/PerSessionAuthE2ETests.cs +++ b/dotnet/test/E2E/PerSessionAuthE2ETests.cs @@ -22,7 +22,7 @@ private CopilotClient CreateAuthTestClient() }; // Disable the harness's auto-injected client token so the per-session // auth tests validate only session-scoped tokens. - return Ctx.CreateClient(options: new CopilotClientOptions { Environment = env }, autoInjectGitHubToken: false); + return Ctx.CreateClient(environment: env, autoInjectGitHubToken: false); } private CopilotClient CreateNoAuthTestClient() @@ -32,9 +32,8 @@ private CopilotClient CreateNoAuthTestClient() return Ctx.CreateClient(options: new CopilotClientOptions { - Environment = env, UseLoggedInUser = false, - }, autoInjectGitHubToken: false); + }, autoInjectGitHubToken: false, environment: env); } private static Dictionary WithoutAuthEnv(Dictionary env) diff --git a/dotnet/test/E2E/ProviderEndpointE2ETests.cs b/dotnet/test/E2E/ProviderEndpointE2ETests.cs index f7e9a78856..7ea4eecf39 100644 --- a/dotnet/test/E2E/ProviderEndpointE2ETests.cs +++ b/dotnet/test/E2E/ProviderEndpointE2ETests.cs @@ -23,7 +23,7 @@ private CopilotClient CreateProviderEndpointClient() { ["COPILOT_ALLOW_GET_PROVIDER_ENDPOINT"] = "true", }; - return Ctx.CreateClient(options: new CopilotClientOptions { Environment = env }); + return Ctx.CreateClient(environment: env); } [Fact] diff --git a/dotnet/test/E2E/RpcExtensionsLoadedE2ETests.cs b/dotnet/test/E2E/RpcExtensionsLoadedE2ETests.cs index c1e43b09ec..af959bd7eb 100644 --- a/dotnet/test/E2E/RpcExtensionsLoadedE2ETests.cs +++ b/dotnet/test/E2E/RpcExtensionsLoadedE2ETests.cs @@ -58,8 +58,7 @@ private CopilotClient CreateExtensionsClient() return Ctx.CreateClient(options: new CopilotClientOptions { Connection = RuntimeConnection.ForStdio(args: ["--yolo"]), - Environment = ExtensionsEnabledEnvironment(), - }); + }, environment: ExtensionsEnabledEnvironment()); } /// diff --git a/dotnet/test/E2E/RpcMcpAndSkillsE2ETests.cs b/dotnet/test/E2E/RpcMcpAndSkillsE2ETests.cs index d53f93c9a1..350aac4274 100644 --- a/dotnet/test/E2E/RpcMcpAndSkillsE2ETests.cs +++ b/dotnet/test/E2E/RpcMcpAndSkillsE2ETests.cs @@ -398,10 +398,7 @@ private CopilotClient CreateMcpAppsClient() environment["COPILOT_MCP_APPS"] = "true"; environment["MCP_APPS"] = "true"; - return Ctx.CreateClient(options: new CopilotClientOptions - { - Environment = environment, - }); + return Ctx.CreateClient(environment: environment); } private static void CreateSkill(string skillsDir, string skillName, string description) diff --git a/dotnet/test/E2E/RpcServerE2ETests.cs b/dotnet/test/E2E/RpcServerE2ETests.cs index da4a360ddc..47df256157 100644 --- a/dotnet/test/E2E/RpcServerE2ETests.cs +++ b/dotnet/test/E2E/RpcServerE2ETests.cs @@ -37,9 +37,8 @@ private CopilotClient CreateAuthenticatedClient(string token) return Ctx.CreateClient(options: new CopilotClientOptions { - Environment = env, GitHubToken = token, - }); + }, environment: env); } private async Task ConfigureAuthenticatedUserAsync( @@ -126,6 +125,40 @@ public async Task Should_Call_Rpc_Ping_With_Typed_Params_And_Result() Assert.NotEqual(default, result.Timestamp); } + [Fact] + public async Task Should_Reject_Llm_Inference_Response_Frames_For_Missing_Request() + { + await Client.StartAsync(); + + var start = await Client.Rpc.LlmInference.HttpResponseStartAsync( + requestId: "missing-llm-inference-request", + status: 200, + headers: new Dictionary> + { + ["content-type"] = ["text/event-stream"], + }, + statusText: "OK"); + Assert.False(start.Accepted); + + var chunk = await Client.Rpc.LlmInference.HttpResponseChunkAsync( + requestId: "missing-llm-inference-request", + data: "data: {}\n\n", + binary: false, + end: false); + Assert.False(chunk.Accepted); + + var error = await Client.Rpc.LlmInference.HttpResponseChunkAsync( + requestId: "missing-llm-inference-request", + data: string.Empty, + end: true, + error: new GitHub.Copilot.Rpc.LlmInferenceHttpResponseChunkError + { + Code = "missing_request", + Message = "No pending LLM inference request.", + }); + Assert.False(error.Accepted); + } + [Fact] public async Task Should_Call_Rpc_Models_List_With_Typed_Result() { @@ -203,10 +236,7 @@ public async Task Should_Add_Secret_Filter_Values() { var environment = Ctx.GetEnvironment(); environment["COPILOT_ENABLE_SECRET_FILTERING"] = "true"; - await using var client = Ctx.CreateClient(options: new CopilotClientOptions - { - Environment = environment, - }); + await using var client = Ctx.CreateClient(environment: environment); await client.StartAsync(); var secret = $"rpc-secret-{Guid.NewGuid():N}"; @@ -347,7 +377,7 @@ public async Task Should_Check_In_Use_Session_From_Another_Runtime_And_Release_L { var sessionId = Guid.NewGuid().ToString(); var workingDirectory = CreateUniqueWorkDirectory("server-rpc-in-use"); - await using var otherClient = Ctx.CreateClient(useStdio: true); + await using var otherClient = Ctx.CreateClient(options: new CopilotClientOptions { Connection = RuntimeConnection.ForStdio() }); await using var otherSession = await otherClient.CreateSessionAsync(new SessionConfig { SessionId = sessionId, @@ -493,6 +523,44 @@ public async Task Should_Discover_Server_Mcp_And_Skills() Assert.True(discoveredSkill.Enabled); Assert.EndsWith(Path.Join(skillName, "SKILL.md"), discoveredSkill.Path); + var skillPaths = await Client.Rpc.Skills.GetDiscoveryPathsAsync( + projectPaths: [Ctx.WorkDir], + excludeHostSkills: true); + var projectSkillPath = Assert.Single(skillPaths.Paths, path => + PathEquals(Ctx.WorkDir, path.ProjectPath) && path.PreferredForCreation); + Assert.False(string.IsNullOrWhiteSpace(projectSkillPath.Path)); + + var agents = await Client.Rpc.Agents.DiscoverAsync( + projectPaths: [Ctx.WorkDir], + excludeHostAgents: true); + Assert.NotNull(agents.Agents); + Assert.All(agents.Agents, agent => Assert.False(string.IsNullOrWhiteSpace(agent.Name))); + + var agentPaths = await Client.Rpc.Agents.GetDiscoveryPathsAsync( + projectPaths: [Ctx.WorkDir], + excludeHostAgents: true); + var projectAgentPath = Assert.Single(agentPaths.Paths, path => + PathEquals(Ctx.WorkDir, path.ProjectPath) && path.PreferredForCreation); + Assert.False(string.IsNullOrWhiteSpace(projectAgentPath.Path)); + + var instructions = await Client.Rpc.Instructions.DiscoverAsync( + projectPaths: [Ctx.WorkDir], + excludeHostInstructions: true); + Assert.NotNull(instructions.Sources); + Assert.All(instructions.Sources, source => + { + Assert.False(string.IsNullOrWhiteSpace(source.Id)); + Assert.False(string.IsNullOrWhiteSpace(source.Label)); + Assert.False(string.IsNullOrWhiteSpace(source.SourcePath)); + }); + + var instructionPaths = await Client.Rpc.Instructions.GetDiscoveryPathsAsync( + projectPaths: [Ctx.WorkDir], + excludeHostInstructions: true); + Assert.NotEmpty(instructionPaths.Paths); + Assert.Contains(instructionPaths.Paths, path => PathEquals(Ctx.WorkDir, path.ProjectPath)); + Assert.All(instructionPaths.Paths, path => Assert.False(string.IsNullOrWhiteSpace(path.Path))); + try { await Client.Rpc.Skills.Config.SetDisabledSkillsAsync([skillName]); diff --git a/dotnet/test/E2E/RpcServerMiscE2ETests.cs b/dotnet/test/E2E/RpcServerMiscE2ETests.cs index 6d04a35f84..29e560100e 100644 --- a/dotnet/test/E2E/RpcServerMiscE2ETests.cs +++ b/dotnet/test/E2E/RpcServerMiscE2ETests.cs @@ -4,14 +4,15 @@ using GitHub.Copilot; using GitHub.Copilot.Rpc; +using GitHub.Copilot.Test.Harness; using Xunit; using Xunit.Abstractions; namespace GitHub.Copilot.Test.E2E; /// -/// E2E coverage for the remaining miscellaneous server-scoped RPC methods that were previously -/// untested: user.settings.reload, agentRegistry.spawn, runtime.shutdown, sessions.open, and the +/// E2E coverage for miscellaneous server-scoped RPC methods, including account auth state, +/// user.settings get/set/reload, agentRegistry.spawn, runtime.shutdown, sessions.open, and the /// session-scoped session.extensions.sendAttachmentsToMessage. /// /// Several of these are intentionally exercised at the wiring/guard boundary because the meaningful @@ -32,6 +33,97 @@ public async Task Should_Reload_User_Settings() await Client.Rpc.User.Settings.ReloadAsync(); } + [Fact] + public async Task Should_Get_Set_And_Clear_User_Settings() + { + await Client.StartAsync(); + + var before = await Client.Rpc.User.Settings.GetAsync(); + Assert.NotNull(before.Settings); + Assert.NotEmpty(before.Settings); + Assert.All(before.Settings, setting => + { + Assert.False(string.IsNullOrWhiteSpace(setting.Key)); + Assert.True( + setting.Value.Value.ValueKind != System.Text.Json.JsonValueKind.Undefined + || setting.Value.Default.ValueKind != System.Text.Json.JsonValueKind.Undefined, + $"Setting '{setting.Key}' should expose either a value or a default."); + }); + + var settingToToggle = before.Settings.First(setting => + setting.Value.Value.ValueKind is System.Text.Json.JsonValueKind.True or System.Text.Json.JsonValueKind.False); + var settingKey = settingToToggle.Key; + var toggledValue = settingToToggle.Value.Value.ValueKind != System.Text.Json.JsonValueKind.True; + + var set = await Client.Rpc.User.Settings.SetAsync(ParseSettingJson(settingKey, toggledValue ? "true" : "false")); + Assert.NotNull(set.ShadowedKeys); + Assert.DoesNotContain(settingKey, set.ShadowedKeys); + + await Client.Rpc.User.Settings.ReloadAsync(); + var afterSet = await Client.Rpc.User.Settings.GetAsync(); + var updatedSetting = Assert.Contains(settingKey, afterSet.Settings); + Assert.False(updatedSetting.IsDefault); + Assert.Equal(toggledValue, updatedSetting.Value.GetBoolean()); + + var clear = await Client.Rpc.User.Settings.SetAsync(ParseSettingJson(settingKey, "null")); + Assert.NotNull(clear.ShadowedKeys); + + await Client.Rpc.User.Settings.ReloadAsync(); + var afterClear = await Client.Rpc.User.Settings.GetAsync(); + var clearedSetting = Assert.Contains(settingKey, afterClear.Settings); + Assert.True(clearedSetting.IsDefault); + } + + [Fact] + public async Task Should_Login_List_GetCurrentAuth_And_Logout_Account() + { + var (client, home) = await CreateIsolatedClientAsync(autoInjectGitHubToken: false); + var login = $"rpc-account-{Guid.NewGuid():N}"; + var token = $"rpc-account-token-{Guid.NewGuid():N}"; + + try + { + await Ctx.SetCopilotUserByTokenAsync(token, new CopilotUserConfig( + Login: login, + CopilotPlan: "individual_pro", + Endpoints: new CopilotUserEndpoints(Api: Ctx.ProxyUrl, Telemetry: "https://localhost:1/telemetry"), + AnalyticsTrackingId: "rpc-account-tracking-id")); + + var initial = await client.Rpc.Account.GetCurrentAuthAsync(); + Assert.Null(initial.AuthInfo); + + var loginResult = await client.Rpc.Account.LoginAsync("https://github.com", login, token); + Assert.NotNull(loginResult); + + var current = await client.Rpc.Account.GetCurrentAuthAsync(); + Assert.Null(current.AuthErrors); + var authInfo = Assert.IsType(current.AuthInfo); + Assert.Equal("https://github.com", authInfo.Host); + Assert.Equal(login, authInfo.Login); + + var users = await client.Rpc.Account.GetAllUsersAsync(); + Assert.All(users, user => Assert.False(string.IsNullOrWhiteSpace(user.AuthInfo.Type))); + var account = users.FirstOrDefault(user => + user.AuthInfo is AuthInfoUser userAuth + && string.Equals(userAuth.Login, login, StringComparison.Ordinal)); + if (account is not null) + { + Assert.Equal(token, account.Token); + } + + var logout = await client.Rpc.Account.LogoutAsync(authInfo); + Assert.False(logout.HasMoreUsers); + + var afterLogout = await client.Rpc.Account.GetCurrentAuthAsync(); + Assert.Null(afterLogout.AuthInfo); + } + finally + { + await client.DisposeAsync(); + TryDeleteDirectory(home); + } + } + [Fact] public async Task Should_Report_Agent_Registry_Spawn_Gate_Closed() { @@ -74,7 +166,7 @@ await Harness.TestHelper.WaitForConditionAsync( async () => { try { await client.Rpc.User.Settings.ReloadAsync(); return false; } - catch { return true; } + catch (Exception ex) when (IsExpectedShutdownException(ex)) { return true; } }, timeout: TimeSpan.FromSeconds(15), pollInterval: TimeSpan.FromMilliseconds(100), @@ -82,8 +174,7 @@ await Harness.TestHelper.WaitForConditionAsync( } finally { - try { await client.DisposeAsync(); } - catch { /* process is already gone after shutdown */ } + await DisposeStoppedRuntimeClientAsync(client); } } @@ -103,7 +194,7 @@ public async Task Should_Report_Not_Found_When_Opening_Session_Without_Context() } finally { - try { await client.DisposeAsync(); } catch { /* best-effort */ } + await client.DisposeAsync(); TryDeleteDirectory(home); } } @@ -127,7 +218,8 @@ public async Task Should_Reject_Send_Attachments_From_Non_Extension_Connection() /// Creates a started client backed by a throwaway COPILOT_HOME so its session store is empty and /// independent of every other test and of the shared fixture client. /// - private async Task<(CopilotClient Client, string Home)> CreateIsolatedClientAsync() + private async Task<(CopilotClient Client, string Home)> CreateIsolatedClientAsync( + bool autoInjectGitHubToken = true) { var home = Path.Combine(Path.GetTempPath(), "copilot-e2e-misc-home-" + Guid.NewGuid().ToString("N")); Directory.CreateDirectory(home); @@ -137,8 +229,22 @@ public async Task Should_Reject_Send_Attachments_From_Non_Extension_Connection() env["GH_CONFIG_DIR"] = home; env["XDG_CONFIG_HOME"] = home; env["XDG_STATE_HOME"] = home; + if (!autoInjectGitHubToken) + { + env["GH_TOKEN"] = ""; + env["GITHUB_TOKEN"] = ""; + } + + var options = new CopilotClientOptions(); + if (!autoInjectGitHubToken) + { + options.UseLoggedInUser = false; + } - var client = Ctx.CreateClient(options: new CopilotClientOptions { Environment = env }); + var client = Ctx.CreateClient( + options: options, + autoInjectGitHubToken: autoInjectGitHubToken, + environment: env); await client.StartAsync(); return (client, home); } @@ -152,9 +258,36 @@ private static void TryDeleteDirectory(string path) Directory.Delete(path, recursive: true); } } - catch + catch (Exception ex) when (ex is IOException or UnauthorizedAccessException) { // Temp directories are reclaimed by the OS; ignore transient locks on cleanup. } } + + private static async Task DisposeStoppedRuntimeClientAsync(CopilotClient client) + { + try + { + await client.DisposeAsync(); + } + catch (Exception ex) when (IsExpectedShutdownException(ex)) + { + // The runtime.shutdown test intentionally stops the process before disposal. + } + } + + private static bool IsExpectedShutdownException(Exception ex) => + ex is OperationCanceledException + or InvalidOperationException + or ObjectDisposedException + or IOException; + + private static System.Text.Json.JsonElement ParseJsonElement(string json) + { + using var document = System.Text.Json.JsonDocument.Parse(json); + return document.RootElement.Clone(); + } + + private static System.Text.Json.JsonElement ParseSettingJson(string key, string valueLiteral) + => ParseJsonElement("{\"" + System.Text.Json.JsonEncodedText.Encode(key) + "\":" + valueLiteral + "}"); } diff --git a/dotnet/test/E2E/RpcServerPluginsE2ETests.cs b/dotnet/test/E2E/RpcServerPluginsE2ETests.cs index eea4095931..64a0f1c261 100644 --- a/dotnet/test/E2E/RpcServerPluginsE2ETests.cs +++ b/dotnet/test/E2E/RpcServerPluginsE2ETests.cs @@ -302,7 +302,7 @@ This skill exists so the plugin reports at least one installed skill. env["XDG_CONFIG_HOME"] = home; env["XDG_STATE_HOME"] = home; - var client = Ctx.CreateClient(options: new CopilotClientOptions { Environment = env }); + var client = Ctx.CreateClient(environment: env); await client.StartAsync(); return (client, home); } diff --git a/dotnet/test/E2E/RpcSessionStateExtrasE2ETests.cs b/dotnet/test/E2E/RpcSessionStateExtrasE2ETests.cs index 5b1ce14843..f3f3d5d5de 100644 --- a/dotnet/test/E2E/RpcSessionStateExtrasE2ETests.cs +++ b/dotnet/test/E2E/RpcSessionStateExtrasE2ETests.cs @@ -11,8 +11,10 @@ namespace GitHub.Copilot.Test.E2E; /// /// E2E coverage for session-scoped RPC methods that were previously untested: -/// model.list, metadata.activity, permissions.getAllowAll/setAllowAll, plan.readSqlTodos, -/// telemetry.getEngagementId, tools.getCurrentMetadata, and the session-scoped plugins.reload. +/// completions, model.list, metadata.activity/context attribution/heaviest messages, +/// permissions.getAllowAll/setAllowAll, plan.readSqlTodos, provider.add, +/// telemetry.getEngagementId, tools.getCurrentMetadata/updateSubagentSettings, +/// session visibility, and the session-scoped plugins.reload. /// public class RpcSessionStateExtrasE2ETests(E2ETestFixture fixture, ITestOutputHelper output) : E2ETestBase(fixture, "rpc_session_state_extras", output) @@ -41,6 +43,69 @@ public async Task Should_List_Models_For_Session() Assert.Contains(result.List, model => model.GetRawText().Contains("claude-sonnet-4.5", StringComparison.Ordinal)); } + [Fact] + public async Task Should_Add_Byok_Provider_And_Model_At_Runtime() + { + await using var session = await CreateSessionAsync(); + var providerName = $"sdk-runtime-provider-{Guid.NewGuid():N}"; + var modelId = "sdk-runtime-model"; + var selectionId = $"{providerName}/{modelId}"; + + var added = await session.Rpc.Provider.AddAsync( + providers: + [ + new GitHub.Copilot.Rpc.NamedProviderConfig + { + Name = providerName, + Type = ProviderConfigType.Openai, + WireApi = ProviderConfigWireApi.Completions, + BaseUrl = "https://api.example.test/v1", + ApiKey = "runtime-provider-secret", + Headers = new Dictionary { ["X-SDK-Provider"] = "runtime" }, + }, + ], + models: + [ + new GitHub.Copilot.Rpc.ProviderModelConfig + { + Provider = providerName, + Id = modelId, + Name = "SDK Runtime Model", + ModelId = "claude-sonnet-4.5", + WireModel = "wire-sdk-runtime-model", + MaxContextWindowTokens = 4_096, + MaxPromptTokens = 3_072, + MaxOutputTokens = 1_024, + Capabilities = new GitHub.Copilot.Rpc.ModelCapabilitiesOverride + { + Limits = new GitHub.Copilot.Rpc.ModelCapabilitiesOverrideLimits + { + MaxContextWindowTokens = 4_096, + MaxPromptTokens = 3_072, + MaxOutputTokens = 1_024, + }, + Supports = new GitHub.Copilot.Rpc.ModelCapabilitiesOverrideSupports + { + ReasoningEffort = false, + Vision = false, + }, + }, + }, + ]); + + var addedModel = Assert.Single(added.Models); + var addedModelJson = addedModel.GetRawText(); + Assert.Contains(selectionId, addedModelJson, StringComparison.Ordinal); + Assert.Contains("SDK Runtime Model", addedModelJson, StringComparison.Ordinal); + + var listed = await session.Rpc.Model.ListAsync(); + Assert.Contains(listed.List, model => model.GetRawText().Contains(selectionId, StringComparison.Ordinal)); + + var switched = await session.Rpc.Model.SwitchToAsync(selectionId); + Assert.Equal(selectionId, switched.ModelId); + Assert.Equal(selectionId, (await session.Rpc.Model.GetCurrentAsync()).ModelId); + } + [Fact] public async Task Should_Report_Session_Activity_When_Idle() { @@ -54,6 +119,36 @@ public async Task Should_Report_Session_Activity_When_Idle() Assert.False(activity.Abortable, "Expected a freshly created session to have nothing abortable."); } + [Fact] + public async Task Should_Return_Empty_Completions_When_Host_Does_Not_Provide_Them() + { + await using var session = await CreateSessionAsync(); + + var triggers = await session.Rpc.Completions.GetTriggerCharactersAsync(); + Assert.NotNull(triggers.TriggerCharacters); + Assert.Empty(triggers.TriggerCharacters); + + var completions = await session.Rpc.Completions.RequestAsync("Use @", offset: 5); + Assert.NotNull(completions.Items); + Assert.Empty(completions.Items); + } + + [Fact] + public async Task Should_Report_Visibility_As_Unsynced_For_Local_Session() + { + await using var session = await CreateSessionAsync(); + + var initial = await session.Rpc.Visibility.GetAsync(); + Assert.False(initial.Synced); + Assert.Null(initial.Status); + Assert.Null(initial.ShareUrl); + + var set = await session.Rpc.Visibility.SetAsync(SessionVisibilityStatus.Repo); + Assert.False(set.Synced); + Assert.Null(set.Status); + Assert.Null(set.ShareUrl); + } + [Fact] public async Task Should_Get_And_Set_AllowAll_Permissions() { @@ -64,19 +159,19 @@ public async Task Should_Get_And_Set_AllowAll_Permissions() var initial = await session.Rpc.Permissions.GetAllowAllAsync(); Assert.False(initial.Enabled, "Allow-all should be disabled on a fresh session."); - var enable = await session.Rpc.Permissions.SetAllowAllAsync(true); + var enable = await session.Rpc.Permissions.SetAllowAllAsync(enabled: true); Assert.True(enable.Success); Assert.True(enable.Enabled); Assert.True((await session.Rpc.Permissions.GetAllowAllAsync()).Enabled); - var disable = await session.Rpc.Permissions.SetAllowAllAsync(false); + var disable = await session.Rpc.Permissions.SetAllowAllAsync(enabled: false); Assert.True(disable.Success); Assert.False(disable.Enabled); Assert.False((await session.Rpc.Permissions.GetAllowAllAsync()).Enabled); } finally { - await session.Rpc.Permissions.SetAllowAllAsync(false); + await session.Rpc.Permissions.SetAllowAllAsync(enabled: false); } } @@ -127,6 +222,74 @@ public async Task Should_Get_Current_Tool_Metadata_After_Initialization() }); } + [Fact] + public async Task Should_Get_Context_Attribution_And_Heaviest_Messages_After_Turn() + { + await using var session = await CreateSessionAsync(); + + var answer = await session.SendAndWaitAsync(new MessageOptions + { + Prompt = "Say CONTEXT_METADATA_OK exactly.", + }); + Assert.Contains("CONTEXT_METADATA_OK", answer?.Data.Content ?? string.Empty, StringComparison.Ordinal); + + var attribution = await session.Rpc.Metadata.GetContextAttributionAsync(); + var contextAttribution = Assert.IsType( + attribution.ContextAttribution); + Assert.True(contextAttribution.TotalTokens > 0); + Assert.True(contextAttribution.Compactions.Count >= 0); + Assert.NotEmpty(contextAttribution.Entries); + Assert.All(contextAttribution.Entries, entry => + { + Assert.False(string.IsNullOrWhiteSpace(entry.Id)); + Assert.False(string.IsNullOrWhiteSpace(entry.Kind)); + Assert.False(string.IsNullOrWhiteSpace(entry.Label)); + Assert.True(entry.Tokens >= 0); + if (entry.Attributes is not null) + { + Assert.All(entry.Attributes, attribute => Assert.False(string.IsNullOrWhiteSpace(attribute.Key))); + } + }); + + var heaviest = await session.Rpc.Metadata.GetContextHeaviestMessagesAsync(limit: 2); + Assert.True(heaviest.TotalTokens > 0); + Assert.NotNull(heaviest.Messages); + Assert.True(heaviest.Messages.Count <= 2); + Assert.All(heaviest.Messages, message => + { + Assert.False(string.IsNullOrWhiteSpace(message.Id)); + Assert.False(string.IsNullOrWhiteSpace(message.Label)); + Assert.False(string.IsNullOrWhiteSpace(message.Role)); + Assert.True(message.Tokens > 0); + }); + } + + [Fact] + public async Task Should_Update_And_Clear_Live_Subagent_Settings() + { + await using var session = await CreateSessionAsync(); + + var update = await session.Rpc.Tools.UpdateSubagentSettingsAsync(new UpdateSubagentSettingsRequestSubagents + { + Agents = new Dictionary + { + ["general-purpose"] = new() + { + Model = "claude-sonnet-4.5", + EffortLevel = "high", + ContextTier = SubagentSettingsEntryContextTier.Default, + }, + }, + DisabledSubagents = ["explore"], + MaxConcurrency = 2, + MaxDepth = 1, + }); + Assert.NotNull(update); + + var clear = await session.Rpc.Tools.UpdateSubagentSettingsAsync(); + Assert.NotNull(clear); + } + [Fact] public async Task Should_Reload_Session_Plugins() { @@ -150,9 +313,8 @@ private CopilotClient CreateAuthenticatedClient(string token) return Ctx.CreateClient(options: new CopilotClientOptions { - Environment = env, GitHubToken = token, - }); + }, environment: env); } private async Task ConfigureAuthenticatedUserAsync(string token) diff --git a/dotnet/test/E2E/RpcTasksAndHandlersE2ETests.cs b/dotnet/test/E2E/RpcTasksAndHandlersE2ETests.cs index fbb2892974..9b9738a2ee 100644 --- a/dotnet/test/E2E/RpcTasksAndHandlersE2ETests.cs +++ b/dotnet/test/E2E/RpcTasksAndHandlersE2ETests.cs @@ -209,6 +209,14 @@ public async Task Should_Return_Expected_Results_For_Missing_Pending_Handler_Req response: UIAutoModeSwitchResponse.No); Assert.False(autoModeSwitch.Success); + var sessionLimits = await session.Rpc.Ui.HandlePendingSessionLimitsExhaustedAsync( + requestId: "missing-session-limits-exhausted-request", + response: new UISessionLimitsExhaustedResponse + { + Action = UISessionLimitsExhaustedResponseAction.Cancel, + }); + Assert.False(sessionLimits.Success); + var exitPlanMode = await session.Rpc.Ui.HandlePendingExitPlanModeAsync( requestId: "missing-exit-plan-mode-request", response: new UIExitPlanModeResponse @@ -251,6 +259,19 @@ public async Task Should_Return_Expected_Results_For_Missing_Pending_Handler_Req LocationKey = "missing-location", }); Assert.False(locationApproval.Success); + + var missingHeaders = await session.Rpc.Mcp.Headers.HandlePendingHeadersRefreshRequestAsync( + requestId: "missing-headers-refresh-request", + result: new McpHeadersHandlePendingHeadersRefreshRequestHeaders + { + Headers = new Dictionary { ["X-SDK-Test"] = "missing" }, + }); + Assert.False(missingHeaders.Success); + + var missingNoHeaders = await session.Rpc.Mcp.Headers.HandlePendingHeadersRefreshRequestAsync( + requestId: "missing-headers-refresh-none-request", + result: new McpHeadersHandlePendingHeadersRefreshRequestNone()); + Assert.False(missingNoHeaders.Success); } [Fact] diff --git a/dotnet/test/E2E/SessionFsSqliteE2ETests.cs b/dotnet/test/E2E/SessionFsSqliteE2ETests.cs index 1e6175f9c4..bf7bdf3b5e 100644 --- a/dotnet/test/E2E/SessionFsSqliteE2ETests.cs +++ b/dotnet/test/E2E/SessionFsSqliteE2ETests.cs @@ -117,9 +117,9 @@ await TestHelper.WaitForConditionAsync( private CopilotClient CreateSessionFsClient() { return Ctx.CreateClient( - useStdio: true, options: new CopilotClientOptions { + Connection = RuntimeConnection.ForStdio(), SessionFs = SessionFsConfig, }); } diff --git a/dotnet/test/E2E/SubagentHooksE2ETests.cs b/dotnet/test/E2E/SubagentHooksE2ETests.cs index 23ba8ed3ac..4723c19b78 100644 --- a/dotnet/test/E2E/SubagentHooksE2ETests.cs +++ b/dotnet/test/E2E/SubagentHooksE2ETests.cs @@ -20,7 +20,7 @@ public async Task Should_Invoke_PreToolUse_And_PostToolUse_Hooks_For_Sub_Agent_T // Create a client with the session-based subagents feature flag var env = new Dictionary(Ctx.GetEnvironment()); env["COPILOT_EXP_COPILOT_CLI_SESSION_BASED_SUBAGENTS"] = "true"; - var client = Ctx.CreateClient(options: new CopilotClientOptions { Environment = env }); + var client = Ctx.CreateClient(environment: env); var session = await client.CreateSessionAsync(new SessionConfig { diff --git a/dotnet/test/E2E/TelemetryExportE2ETests.cs b/dotnet/test/E2E/TelemetryExportE2ETests.cs index 22ed5663d0..2dd9d591a4 100644 --- a/dotnet/test/E2E/TelemetryExportE2ETests.cs +++ b/dotnet/test/E2E/TelemetryExportE2ETests.cs @@ -24,6 +24,7 @@ public async Task Should_Export_File_Telemetry_For_Sdk_Interactions() await using var client = Ctx.CreateClient(options: new CopilotClientOptions { + Connection = RuntimeConnection.ForStdio(), Telemetry = new TelemetryConfig { FilePath = telemetryPath, diff --git a/dotnet/test/Harness/E2ETestContext.cs b/dotnet/test/Harness/E2ETestContext.cs index 6e26299a49..f4df1749f0 100644 --- a/dotnet/test/Harness/E2ETestContext.cs +++ b/dotnet/test/Harness/E2ETestContext.cs @@ -216,6 +216,16 @@ public Dictionary GetEnvironment() env["GITHUB_TOKEN"] = env["GH_TOKEN"] = DefaultGitHubToken; + // Disable HMAC auth for E2E runs. CI sets COPILOT_HMAC_KEY at the job + // level as an ambient credential, but the replay snapshots are captured + // against Bearer/OAuth (SDK-token) requests. In stdio the SDK token + // outranks HMAC so this is a no-op, but in-process auth resolution runs + // host-side in this process and would otherwise pick HMAC (which ranks + // above the GitHub token) and fail provider.getEndpoint. An empty value + // disables the method (runtime filters out empty HMAC keys). + env["COPILOT_HMAC_KEY"] = ""; + env["CAPI_HMAC_KEY"] = ""; + return env!; } @@ -227,41 +237,77 @@ public Dictionary GetEnvironment() } public CopilotClient CreateClient( - bool? useStdio = null, CopilotClientOptions? options = null, bool autoInjectGitHubToken = true, - bool persistent = false) + bool persistent = false, + IReadOnlyDictionary? environment = null) { options ??= new CopilotClientOptions(); options.WorkingDirectory ??= WorkDir; - options.Environment ??= GetEnvironment(); options.Logger ??= Logger; - // Build the connection. If the caller supplied one, just ensure the runtime path is set; - // otherwise default to Stdio with the bundled runtime (matches CopilotClient's own default). - // useStdio is a convenience shortcut for the no-Connection case; passing both is ambiguous. - if (useStdio is not null && options.Connection is not null) + // Tests must supply environment via the 'environment' parameter, which the + // harness routes to the right place per transport (the connection for + // child-process transports, the host process for in-process). Setting + // options.Environment directly bypasses that routing and is unsupported + // in-process, so reject it here. + if (options.Environment is not null) { throw new ArgumentException( - "Specify either useStdio or options.Connection, not both. " + - "Use options.Connection (e.g. RuntimeConnection.ForStdio() / RuntimeConnection.ForTcp()) to control transport when supplying a Connection.", - nameof(useStdio)); + "Do not set options.Environment in E2E tests; pass the 'environment' parameter to CreateClient instead.", + nameof(options)); } + // The full environment the client runs with: harness defaults (proxy + // redirect, isolated home, cleared HMAC/tokens, etc.) unless the test + // supplied a complete replacement. + var env = environment is not null + ? environment.ToDictionary(kvp => kvp.Key, kvp => kvp.Value) + : GetEnvironment(); + + // When the test doesn't pin a transport, leave Connection null so + // CopilotClient honors COPILOT_SDK_DEFAULT_CONNECTION (stdio by default, + // or in-process); the CI matrix uses this to run the suite under both. + // Tests that need a specific transport set options.Connection directly. var cliPath = GetCliPath(_repoRoot); switch (options.Connection) { + case null when !IsInProcess(null): + // No explicit connection and not the in-process default: the + // default resolves to stdio, so materialize it here so the + // environment can be attached to the connection below. + options.Connection = RuntimeConnection.ForStdio(path: cliPath); + break; case null: - options.Connection = useStdio == false - ? RuntimeConnection.ForTcp(path: cliPath) - : RuntimeConnection.ForStdio(path: cliPath); + // In-process default: leave Connection unset so CopilotClient's + // ResolveDefaultConnection honors COPILOT_SDK_DEFAULT_CONNECTION. break; case ChildProcessRuntimeConnection child when child.Path is null: child.Path = cliPath; break; } + if (IsInProcess(options.Connection)) + { + // In-process hosting: runtime code runs host-side in this process (the + // loaded cdylib) and reads the ambient process environment rather than + // the environment passed to copilot_runtime_host_start, so the per-test + // redirects, cleared tokens/HMAC, and isolated home must be mirrored + // onto this process's real environment. Restored after each test by + // InProcessEnvIsolationAttribute. + foreach (var (name, value) in env) + { + InProcessEnvIsolation.Apply(name, value); + } + } + else if (options.Connection is ChildProcessRuntimeConnection child) + { + // Child-process transport: hand the environment to the spawned child + // via the connection, where per-client environment is coherent. + child.Environment = env; + } + // Auto-inject auth token unless connecting to an existing runtime via URI. var isExistingRuntime = options.Connection is UriRuntimeConnection; if (autoInjectGitHubToken @@ -312,7 +358,7 @@ public async Task CleanupAfterTestAsync() { try { - await client.ForceStopAsync(); + await StopClientForCleanupAsync(client); } catch (Exception ex) when (IsTransientCleanupException(ex)) { @@ -346,7 +392,7 @@ public async ValueTask DisposeAsync() { try { - await client.ForceStopAsync(); + await StopClientForCleanupAsync(client); } catch (Exception ex) when (IsTransientCleanupException(ex)) { @@ -408,6 +454,45 @@ private static async Task DeleteDirectoryAsync(string path) } } + /// + /// Determines whether the resolved transport is the in-process (FFI) host, + /// mirroring 's own default-connection resolution: + /// an explicit , or (when no connection + /// is given) the COPILOT_SDK_DEFAULT_CONNECTION=inprocess default. + /// + private static bool IsInProcess(RuntimeConnection? connection) + { + if (connection is InProcessRuntimeConnection) + { + return true; + } + if (connection is null) + { + return string.Equals( + Environment.GetEnvironmentVariable("COPILOT_SDK_DEFAULT_CONNECTION"), + "inprocess", + StringComparison.OrdinalIgnoreCase); + } + return false; + } + + // Inproc holds the session-store SQLite handle in-process; graceful StopAsync releases it so the temp-dir delete succeeds on Windows. + private static async Task StopClientForCleanupAsync(CopilotClient client) + { + var isInProcess = string.Equals( + Environment.GetEnvironmentVariable("COPILOT_SDK_DEFAULT_CONNECTION"), + "inprocess", + StringComparison.OrdinalIgnoreCase); + if (isInProcess) + { + await client.StopAsync(); + } + else + { + await client.ForceStopAsync(); + } + } + private static bool IsTransientCleanupException(Exception exception) => exception is IOException or UnauthorizedAccessException; } diff --git a/dotnet/test/Harness/InProcessEnvIsolation.cs b/dotnet/test/Harness/InProcessEnvIsolation.cs new file mode 100644 index 0000000000..14b065204f --- /dev/null +++ b/dotnet/test/Harness/InProcessEnvIsolation.cs @@ -0,0 +1,93 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +using System.Collections; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using Xunit.Sdk; + +namespace GitHub.Copilot.Test.Harness; + +// Because many of the tests mutate global environment variables, we have to snapshot the original +// state and restore it after each test. Otherwise tests influence each other depending on run order. +// This is especially important for the in-process transport because the runtime is inside the test +// host process and will be reading/writing its environment variables directly. +internal static class InProcessEnvIsolation +{ + // Unset because CI sets them but the replay snapshots expect Bearer/OAuth. + private static readonly string[] SuppressEnvVars = ["COPILOT_HMAC_KEY", "CAPI_HMAC_KEY"]; + + // Captured at load, before any fixture/test mutates env. + private static readonly Dictionary s_ambient = CaptureEnvironment(); + + // Runs at assembly load so the ambient env is snapshotted before the shared + // fixture mirrors per-test env onto the process. Justifies suppressing CA2255. +#pragma warning disable CA2255 // ModuleInitializer discouraged in libraries; intentional in this test harness. + [ModuleInitializer] + internal static void CaptureAtLoad() => _ = s_ambient; +#pragma warning restore CA2255 + + [DllImport("libc", EntryPoint = "setenv", CharSet = CharSet.Ansi, + BestFitMapping = false, ThrowOnUnmappableChar = true)] + private static extern int NativeSetEnv(string name, string value, int overwrite); + + [DllImport("libc", EntryPoint = "unsetenv", CharSet = CharSet.Ansi, + BestFitMapping = false, ThrowOnUnmappableChar = true)] + private static extern int NativeUnsetEnv(string name); + + // Sets/unsets on the managed cache and, on Unix, the libc block so native + // readers in the loaded cdylib observe it. + public static void Apply(string name, string? value) + { + Environment.SetEnvironmentVariable(name, value); + if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + _ = value is null ? NativeUnsetEnv(name) : NativeSetEnv(name, value, 1); + } + } + + public static void NeutralizeAmbientCredentials() + { + foreach (var name in SuppressEnvVars) + { + Apply(name, null); + } + } + + public static void RestoreAmbient() + { + foreach (DictionaryEntry entry in Environment.GetEnvironmentVariables()) + { + var name = (string)entry.Key; + if (!s_ambient.ContainsKey(name)) + { + Apply(name, null); + } + } + + foreach (var (name, value) in s_ambient) + { + if (!string.Equals(Environment.GetEnvironmentVariable(name), value, StringComparison.Ordinal)) + { + Apply(name, value); + } + } + } + + private static Dictionary CaptureEnvironment() => + Environment.GetEnvironmentVariables() + .Cast() + .ToDictionary(e => (string)e.Key, e => e.Value?.ToString(), StringComparer.Ordinal); +} + +[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = false)] +public sealed class InProcessEnvIsolationAttribute : BeforeAfterTestAttribute +{ + public override void Before(MethodInfo methodUnderTest) => + InProcessEnvIsolation.NeutralizeAmbientCredentials(); + + public override void After(MethodInfo methodUnderTest) => + InProcessEnvIsolation.RestoreAmbient(); +} diff --git a/dotnet/test/Harness/ModuleInitializerAttribute.cs b/dotnet/test/Harness/ModuleInitializerAttribute.cs new file mode 100644 index 0000000000..fd95287335 --- /dev/null +++ b/dotnet/test/Harness/ModuleInitializerAttribute.cs @@ -0,0 +1,13 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +#if !NET5_0_OR_GREATER +namespace System.Runtime.CompilerServices; + +// Polyfill so [ModuleInitializer] compiles on net472; recognized by the compiler. +[AttributeUsage(AttributeTargets.Method, Inherited = false)] +internal sealed class ModuleInitializerAttribute : Attribute +{ +} +#endif diff --git a/dotnet/test/Unit/ClientSessionLifetimeTests.cs b/dotnet/test/Unit/ClientSessionLifetimeTests.cs index a028a6c7ec..f736e8dee4 100644 --- a/dotnet/test/Unit/ClientSessionLifetimeTests.cs +++ b/dotnet/test/Unit/ClientSessionLifetimeTests.cs @@ -32,6 +32,20 @@ public async Task StopAsync_Requests_Runtime_Shutdown_For_Owned_Process() Assert.Equal(1, server.RuntimeShutdownCount); } + [Fact] + public async Task DisposeAsync_Requests_Runtime_Shutdown_For_Owned_Process() + { + await using var server = await FakeCopilotServer.StartAsync(); + var client = new CopilotClient(new CopilotClientOptions { Connection = RuntimeConnection.ForUri(server.Url) }); + await client.StartAsync(); + using var process = StartExitedProcess(); + await ReplaceConnectionCliProcessAsync(client, process); + + await client.DisposeAsync(); + + Assert.Equal(1, server.RuntimeShutdownCount); + } + [Fact] public async Task StopAsync_Does_Not_Throw_When_Runtime_Shutdown_Fails() { @@ -423,7 +437,7 @@ private static async Task ReplaceConnectionCliProcessAsync(CopilotClient client, var rpc = connectionType.GetProperty("Rpc")!.GetValue(connection); var networkStream = connectionType.GetProperty("NetworkStream")!.GetValue(connection); var constructor = connectionType.GetConstructors(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public).Single(); - var updatedConnection = constructor.Invoke([rpc, process, networkStream, null]); + var updatedConnection = constructor.Invoke([rpc, process, networkStream, null, null]); var fromResult = typeof(Task).GetMethod(nameof(Task.FromResult))!.MakeGenericMethod(connectionType); field.SetValue(client, fromResult.Invoke(null, [updatedConnection])); } diff --git a/dotnet/test/Unit/GitHubTelemetryTests.cs b/dotnet/test/Unit/GitHubTelemetryTests.cs new file mode 100644 index 0000000000..f82e0db6e0 --- /dev/null +++ b/dotnet/test/Unit/GitHubTelemetryTests.cs @@ -0,0 +1,482 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +#if NET8_0_OR_GREATER +using System.Net; +using System.Net.Sockets; +using System.Text; +using System.Text.Json; +using Xunit; + +using GitHub.Copilot.Rpc; + +namespace GitHub.Copilot.Test.Unit; + +#pragma warning disable GHCP001 // GitHub telemetry forwarding is experimental. + +public sealed class GitHubTelemetryTests +{ + [Fact] + public async Task CreateSession_Opts_Into_Forwarding_When_Handler_Provided() + { + await using var server = await FakeTelemetryServer.StartAsync(); + await using var client = new CopilotClient(new CopilotClientOptions + { + Connection = RuntimeConnection.ForUri(server.Url), + OnGitHubTelemetry = _ => Task.CompletedTask, + }); + await client.StartAsync(); + + await client.CreateSessionAsync(new SessionConfig { OnPermissionRequest = PermissionHandler.ApproveAll }); + + var createParams = server.LastCreateParams ?? throw new InvalidOperationException("session.create was not captured."); + Assert.True(createParams.TryGetProperty("enableGitHubTelemetryForwarding", out var flag)); + Assert.True(flag.GetBoolean()); + } + + [Fact] + public async Task ResumeSession_Opts_Into_Forwarding_When_Handler_Provided() + { + await using var server = await FakeTelemetryServer.StartAsync(); + await using var client = new CopilotClient(new CopilotClientOptions + { + Connection = RuntimeConnection.ForUri(server.Url), + OnGitHubTelemetry = _ => Task.CompletedTask, + }); + await client.StartAsync(); + + await client.ResumeSessionAsync("session-1", new ResumeSessionConfig { OnPermissionRequest = PermissionHandler.ApproveAll }); + + var resumeParams = server.LastResumeParams ?? throw new InvalidOperationException("session.resume was not captured."); + Assert.True(resumeParams.TryGetProperty("enableGitHubTelemetryForwarding", out var flag)); + Assert.True(flag.GetBoolean()); + } + + [Fact] + public async Task Connect_Opts_Into_Forwarding_When_Handler_Provided() + { + await using var server = await FakeTelemetryServer.StartAsync(); + await using var client = new CopilotClient(new CopilotClientOptions + { + Connection = RuntimeConnection.ForUri(server.Url), + OnGitHubTelemetry = _ => Task.CompletedTask, + }); + await client.StartAsync(); + + var connectParams = server.LastConnectParams ?? throw new InvalidOperationException("connect was not captured."); + Assert.True(connectParams.TryGetProperty("enableGitHubTelemetryForwarding", out var flag)); + Assert.True(flag.GetBoolean()); + } + + [Fact] + public async Task Connect_Does_Not_Opt_In_Without_Handler() + { + await using var server = await FakeTelemetryServer.StartAsync(); + await using var client = new CopilotClient(new CopilotClientOptions + { + Connection = RuntimeConnection.ForUri(server.Url), + }); + await client.StartAsync(); + + var connectParams = server.LastConnectParams ?? throw new InvalidOperationException("connect was not captured."); + var present = connectParams.TryGetProperty("enableGitHubTelemetryForwarding", out var flag); + Assert.True( + !present || flag.ValueKind == JsonValueKind.Null, + "connect request should omit enableGitHubTelemetryForwarding (or send null) when no handler is registered"); + } + + [Fact] + public async Task CreateSession_Does_Not_Opt_In_Without_Handler() + { + await using var server = await FakeTelemetryServer.StartAsync(); + await using var client = new CopilotClient(new CopilotClientOptions + { + Connection = RuntimeConnection.ForUri(server.Url), + }); + await client.StartAsync(); + + await client.CreateSessionAsync(new SessionConfig { OnPermissionRequest = PermissionHandler.ApproveAll }); + + var createParams = server.LastCreateParams ?? throw new InvalidOperationException("session.create was not captured."); + var optedIn = createParams.TryGetProperty("enableGitHubTelemetryForwarding", out var flag) + && flag.ValueKind == JsonValueKind.True; + Assert.False(optedIn); + } + + [Fact] + public async Task GitHubTelemetry_Event_Is_Forwarded_To_OnGitHubTelemetry() + { + var received = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + + await using var server = await FakeTelemetryServer.StartAsync(); + await using var client = new CopilotClient(new CopilotClientOptions + { + Connection = RuntimeConnection.ForUri(server.Url), + OnGitHubTelemetry = notification => + { + received.TrySetResult(notification); + return Task.CompletedTask; + }, + }); + await client.StartAsync(); + + await server.SendGitHubTelemetryEventAsync(new Dictionary + { + ["sessionId"] = "session-1", + ["restricted"] = false, + ["event"] = new Dictionary + { + ["kind"] = "tool_call_executed", + ["properties"] = new Dictionary { ["tool"] = "shell" }, + ["metrics"] = new Dictionary { ["duration_ms"] = 42 }, + ["session_id"] = "session-1", + }, + }); + + var notification = await received.Task.WaitAsync(TimeSpan.FromSeconds(10)); + Assert.Equal("session-1", notification.SessionId); + Assert.False(notification.Restricted); + Assert.Equal("tool_call_executed", notification.Event.Kind); + Assert.Equal("shell", notification.Event.Properties["tool"]); + Assert.Equal(42, notification.Event.Metrics["duration_ms"]); + Assert.Equal("session-1", notification.Event.SessionId); + } + + [Fact] + public async Task GitHubTelemetry_Event_Maps_Restricted_And_ClientInfo() + { + var received = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + + await using var server = await FakeTelemetryServer.StartAsync(); + await using var client = new CopilotClient(new CopilotClientOptions + { + Connection = RuntimeConnection.ForUri(server.Url), + OnGitHubTelemetry = notification => + { + received.TrySetResult(notification); + return Task.CompletedTask; + }, + }); + await client.StartAsync(); + + await server.SendGitHubTelemetryEventAsync(new Dictionary + { + ["sessionId"] = "session-2", + ["restricted"] = true, + ["event"] = new Dictionary + { + ["kind"] = "model_call", + ["properties"] = new Dictionary { ["model"] = "gpt-5" }, + ["metrics"] = new Dictionary { ["tokens"] = 128 }, + ["session_id"] = "session-2", + ["client"] = new Dictionary + { + ["cli_version"] = "1.2.3", + ["os_platform"] = "win32", + ["os_arch"] = "x64", + ["node_version"] = "20.0.0", + ["is_staff"] = false, + }, + }, + }); + + var notification = await received.Task.WaitAsync(TimeSpan.FromSeconds(10)); + Assert.True(notification.Restricted); + + var clientInfo = notification.Event.Client; + Assert.NotNull(clientInfo); + Assert.Equal("1.2.3", clientInfo!.CliVersion); + Assert.Equal("win32", clientInfo.OsPlatform); + Assert.Equal("x64", clientInfo.OsArch); + Assert.Equal("20.0.0", clientInfo.NodeVersion); + Assert.Equal(false, clientInfo.IsStaff); + } + + private sealed class FakeTelemetryServer : IAsyncDisposable + { + private readonly TcpListener _listener; + private readonly CancellationTokenSource _cts = new(); + private readonly SemaphoreSlim _writeLock = new(1, 1); + private readonly TaskCompletionSource _connected = new(TaskCreationOptions.RunContinuationsAsynchronously); + private readonly Task _serverTask; + + private FakeTelemetryServer(TcpListener listener) + { + _listener = listener; + _serverTask = RunAsync(); + } + + public string Url + { + get + { + var endpoint = (IPEndPoint)_listener.LocalEndpoint; + return $"http://127.0.0.1:{endpoint.Port}"; + } + } + + public JsonElement? LastCreateParams { get; private set; } + + public JsonElement? LastResumeParams { get; private set; } + + public JsonElement? LastConnectParams { get; private set; } + + public static Task StartAsync() + { + var listener = new TcpListener(IPAddress.Loopback, 0); + listener.Start(); + return Task.FromResult(new FakeTelemetryServer(listener)); + } + + public async Task SendGitHubTelemetryEventAsync(Dictionary notificationParams) + { + var stream = await _connected.Task.WaitAsync(_cts.Token); + + // Send a genuine JSON-RPC notification (no "id"), exactly as the runtime + // does via sendNotification. This exercises the real notification dispatch + // path rather than masking it behind a request that carries an id. + await WriteMessageAsync(stream, new Dictionary + { + ["jsonrpc"] = "2.0", + ["method"] = "gitHubTelemetry.event", + ["params"] = notificationParams, + }, _cts.Token); + } + + public async ValueTask DisposeAsync() + { + _cts.Cancel(); + _listener.Stop(); + + try + { + await _serverTask; + } + catch (Exception ex) when (ex is OperationCanceledException or ObjectDisposedException or IOException or SocketException) + { + // Expected during teardown: the listener/socket is torn down while the + // server loop is still awaiting I/O. Observe the exception and move on. + _ = ex; + } + + _cts.Dispose(); + _writeLock.Dispose(); + } + + private async Task RunAsync() + { + using var tcpClient = await _listener.AcceptTcpClientAsync(_cts.Token); + using var stream = tcpClient.GetStream(); + _connected.TrySetResult(stream); + + while (!_cts.Token.IsCancellationRequested) + { + using var message = await ReadMessageAsync(stream, _cts.Token); + if (message is null) + { + return; + } + + // Inbound messages without a "method" are responses to our own + // server-initiated requests (e.g. session.* the SDK answers); the + // SDK never replies to the gitHubTelemetry.event notification. + if (!message.RootElement.TryGetProperty("method", out _)) + { + continue; + } + + await HandleRequestAsync(stream, message.RootElement, _cts.Token); + } + } + + private async Task HandleRequestAsync(Stream stream, JsonElement request, CancellationToken cancellationToken) + { + if (!request.TryGetProperty("id", out var idElement)) + { + return; + } + + var id = idElement.Clone(); + var method = request.GetProperty("method").GetString(); + + object? result = method switch + { + "connect" => CaptureConnect(request), + "session.create" => CaptureCreate(request), + "session.resume" => CaptureResume(request), + "session.send" => new Dictionary { ["messageId"] = "message-1" }, + "session.destroy" => new Dictionary(), + "runtime.shutdown" => new Dictionary(), + _ => throw new InvalidOperationException($"Unexpected RPC method '{method}'."), + }; + + await WriteMessageAsync(stream, new Dictionary + { + ["jsonrpc"] = "2.0", + ["id"] = id, + ["result"] = result, + }, cancellationToken); + } + + private Dictionary CaptureConnect(JsonElement request) + { + LastConnectParams = request.TryGetProperty("params", out var p) ? p.Clone() : null; + return new Dictionary + { + ["ok"] = true, + ["protocolVersion"] = 3, + ["version"] = "test", + }; + } + + private Dictionary CaptureCreate(JsonElement request) + { + LastCreateParams = request.TryGetProperty("params", out var p) ? p.Clone() : null; + return SessionResult(LastCreateParams); + } + + private Dictionary CaptureResume(JsonElement request) + { + LastResumeParams = request.TryGetProperty("params", out var p) ? p.Clone() : null; + return SessionResult(LastResumeParams); + } + + private static Dictionary SessionResult(JsonElement? paramsElement) + { + string sessionId = "session-1"; + if (paramsElement is { ValueKind: JsonValueKind.Object } p + && p.TryGetProperty("sessionId", out var sidProp) + && sidProp.ValueKind == JsonValueKind.String + && sidProp.GetString() is string sid + && !string.IsNullOrEmpty(sid)) + { + sessionId = sid; + } + + return new Dictionary + { + ["sessionId"] = sessionId, + ["workspacePath"] = null, + ["capabilities"] = null, + }; + } + + private async Task WriteMessageAsync(Stream stream, object payload, CancellationToken cancellationToken) + { + using var bodyStream = new MemoryStream(); + using (var writer = new Utf8JsonWriter(bodyStream)) + { + WriteJsonValue(writer, payload); + } + + var body = bodyStream.ToArray(); + var header = Encoding.ASCII.GetBytes($"Content-Length: {body.Length}\r\n\r\n"); + + await _writeLock.WaitAsync(cancellationToken); + try + { + await stream.WriteAsync(header, cancellationToken); + await stream.WriteAsync(body, cancellationToken); + await stream.FlushAsync(cancellationToken); + } + finally + { + _writeLock.Release(); + } + } + + private static void WriteJsonValue(Utf8JsonWriter writer, object? value) + { + switch (value) + { + case null: + writer.WriteNullValue(); + break; + case string stringValue: + writer.WriteStringValue(stringValue); + break; + case bool boolValue: + writer.WriteBooleanValue(boolValue); + break; + case int intValue: + writer.WriteNumberValue(intValue); + break; + case long longValue: + writer.WriteNumberValue(longValue); + break; + case JsonElement jsonElement: + jsonElement.WriteTo(writer); + break; + case Dictionary dictionary: + writer.WriteStartObject(); + foreach (var (propertyName, propertyValue) in dictionary) + { + writer.WritePropertyName(propertyName); + WriteJsonValue(writer, propertyValue); + } + writer.WriteEndObject(); + break; + default: + throw new InvalidOperationException($"Unexpected JSON value type '{value.GetType().Name}'."); + } + } + + private static async Task ReadMessageAsync(Stream stream, CancellationToken cancellationToken) + { + var headerBytes = new List(); + while (true) + { + var value = await ReadByteAsync(stream, cancellationToken); + if (value < 0) + { + return null; + } + + headerBytes.Add((byte)value); + var count = headerBytes.Count; + if (count >= 4 && + headerBytes[count - 4] == '\r' && + headerBytes[count - 3] == '\n' && + headerBytes[count - 2] == '\r' && + headerBytes[count - 1] == '\n') + { + break; + } + } + + var header = Encoding.ASCII.GetString([.. headerBytes]); + var contentLength = header + .Split(["\r\n"], StringSplitOptions.RemoveEmptyEntries) + .Select(line => line.Split(':', 2)) + .Where(parts => parts.Length == 2 && parts[0].Equals("Content-Length", StringComparison.OrdinalIgnoreCase)) + .Select(parts => int.Parse(parts[1].Trim(), System.Globalization.CultureInfo.InvariantCulture)) + .Single(); + + var body = new byte[contentLength]; + var offset = 0; + while (offset < body.Length) + { + var read = await stream.ReadAsync(body.AsMemory(offset, body.Length - offset), cancellationToken); + if (read == 0) + { + return null; + } + + offset += read; + } + + return JsonDocument.Parse(body); + } + + private static async Task ReadByteAsync(Stream stream, CancellationToken cancellationToken) + { + var buffer = new byte[1]; + var read = await stream.ReadAsync(buffer, cancellationToken); + return read == 0 ? -1 : buffer[0]; + } + } +} + +#pragma warning restore GHCP001 +#endif diff --git a/go/client.go b/go/client.go index 4e7f1f549a..5b8dabf8f2 100644 --- a/go/client.go +++ b/go/client.go @@ -730,6 +730,7 @@ func (c *Client) CreateSession(ctx context.Context, config *SessionConfig) (*Ses req.RequestCanvasRenderer = config.RequestCanvasRenderer req.RequestExtensions = config.RequestExtensions req.ExtensionSDKPath = config.ExtensionSDKPath + req.ExtensionInfo = config.ExtensionInfo req.ExpAssignments = config.ExpAssignments if len(config.Commands) > 0 { @@ -760,6 +761,9 @@ func (c *Client) CreateSession(ctx context.Context, config *SessionConfig) (*Ses } else { req.IncludeSubAgentStreamingEvents = Bool(true) } + if c.options.OnGitHubTelemetry != nil { + req.EnableGitHubTelemetryForwarding = Bool(true) + } if config.OnUserInputRequest != nil { req.RequestUserInput = Bool(true) } @@ -1038,6 +1042,9 @@ func (c *Client) ResumeSessionWithOptions(ctx context.Context, sessionID string, } else { req.IncludeSubAgentStreamingEvents = Bool(true) } + if c.options.OnGitHubTelemetry != nil { + req.EnableGitHubTelemetryForwarding = Bool(true) + } if config.OnUserInputRequest != nil { req.RequestUserInput = Bool(true) } @@ -1086,6 +1093,7 @@ func (c *Client) ResumeSessionWithOptions(ctx context.Context, sessionID string, req.RequestCanvasRenderer = config.RequestCanvasRenderer req.RequestExtensions = config.RequestExtensions req.ExtensionSDKPath = config.ExtensionSDKPath + req.ExtensionInfo = config.ExtensionInfo req.ExpAssignments = config.ExpAssignments if config.OnPermissionRequest != nil { req.RequestPermission = Bool(true) @@ -1679,7 +1687,15 @@ func (c *Client) verifyProtocolVersion(ctx context.Context) error { t := c.effectiveConnectionToken tokenPtr = &t } - connectResult, err := c.internalRPC.Connect(ctx, &rpc.ConnectRequest{Token: tokenPtr}) + connectReq := &connectHandshakeRequest{Token: tokenPtr} + // Opt in to GitHub telemetry forwarding at the connection level when a handler is + // registered (mirrors the runtime, which reads this flag on the `connect` handshake + // so the first session's un-replayable `session.start` event is forwarded). Also + // sent on session.create/resume for older CLIs. + if c.options.OnGitHubTelemetry != nil { + connectReq.EnableGitHubTelemetryForwarding = Bool(true) + } + rawConnectResult, err := c.client.Request(ctx, "connect", connectReq) if err != nil { var rpcErr *jsonrpc2.Error if errors.As(err, &rpcErr) && (rpcErr.Code == jsonrpc2.ErrMethodNotFound.Code || rpcErr.Message == "Unhandled method connect") { @@ -1694,6 +1710,10 @@ func (c *Client) verifyProtocolVersion(ctx context.Context) error { return err } } else { + var connectResult rpc.ConnectResult + if err := json.Unmarshal(rawConnectResult, &connectResult); err != nil { + return err + } v := int(connectResult.ProtocolVersion) serverVersion = &v } @@ -1710,6 +1730,11 @@ func (c *Client) verifyProtocolVersion(ctx context.Context) error { return nil } +type connectHandshakeRequest struct { + Token *string `json:"token,omitempty"` + EnableGitHubTelemetryForwarding *bool `json:"enableGitHubTelemetryForwarding,omitempty"` +} + // stderrBufferSize is the maximum number of bytes kept from the CLI process's // stderr. Only the tail is retained so that memory stays bounded even when the // process produces a large amount of diagnostic output. @@ -2057,17 +2082,35 @@ func (c *Client) setupNotificationHandler() { } return session.clientSessionAPIs }) - if c.options.RequestHandler != nil { - adapter := newCopilotRequestAdapter(c.options.RequestHandler, func() *rpc.ServerLlmInferenceAPI { - if c.RPC == nil { - return nil - } - return c.RPC.LlmInference - }) - rpc.RegisterClientGlobalAPIHandlers(c.client, &rpc.ClientGlobalAPIHandlers{LlmInference: adapter}) + if c.options.RequestHandler != nil || c.options.OnGitHubTelemetry != nil { + handlers := &rpc.ClientGlobalAPIHandlers{} + if c.options.RequestHandler != nil { + handlers.LlmInference = newCopilotRequestAdapter(c.options.RequestHandler, func() *rpc.ServerLlmInferenceAPI { + if c.RPC == nil { + return nil + } + return c.RPC.LlmInference + }) + } + if c.options.OnGitHubTelemetry != nil { + handlers.GitHubTelemetry = &gitHubTelemetryAdapter{callback: c.options.OnGitHubTelemetry} + } + rpc.RegisterClientGlobalAPIHandlers(c.client, handlers) } } +// gitHubTelemetryAdapter adapts the OnGitHubTelemetry option to the generated +// rpc.GitHubTelemetryHandler interface. +type gitHubTelemetryAdapter struct { + callback func(notification *rpc.GitHubTelemetryNotification) +} + +func (a *gitHubTelemetryAdapter) Event(request *rpc.GitHubTelemetryNotification) error { + defer func() { recover() }() // Ignore handler panics + a.callback(request) + return nil +} + func (c *Client) handleSessionEvent(req sessionEventRequest) { if req.SessionID == "" { return diff --git a/go/client_test.go b/go/client_test.go index 059f098a0c..bd48baacd7 100644 --- a/go/client_test.go +++ b/go/client_test.go @@ -15,6 +15,7 @@ import ( "strings" "sync" "testing" + "time" "github.com/github/copilot-sdk/go/internal/jsonrpc2" "github.com/github/copilot-sdk/go/internal/truncbuffer" @@ -2337,6 +2338,285 @@ func TestResumeSessionRequest_IncludeSubAgentStreamingEvents(t *testing.T) { }) } +func TestCreateSessionRequest_EnableGitHubTelemetryForwarding(t *testing.T) { + t.Run("forwards explicit true", func(t *testing.T) { + req := createSessionRequest{ + EnableGitHubTelemetryForwarding: Bool(true), + } + data, err := json.Marshal(req) + if err != nil { + t.Fatalf("Failed to marshal: %v", err) + } + var m map[string]any + if err := json.Unmarshal(data, &m); err != nil { + t.Fatalf("Failed to unmarshal: %v", err) + } + if m["enableGitHubTelemetryForwarding"] != true { + t.Errorf("Expected enableGitHubTelemetryForwarding to be true, got %v", m["enableGitHubTelemetryForwarding"]) + } + }) + + t.Run("omits when not set", func(t *testing.T) { + req := createSessionRequest{} + data, _ := json.Marshal(req) + var m map[string]any + json.Unmarshal(data, &m) + if _, ok := m["enableGitHubTelemetryForwarding"]; ok { + t.Error("Expected enableGitHubTelemetryForwarding to be omitted when not set") + } + }) +} + +func TestResumeSessionRequest_EnableGitHubTelemetryForwarding(t *testing.T) { + t.Run("forwards explicit true", func(t *testing.T) { + req := resumeSessionRequest{ + SessionID: "s1", + EnableGitHubTelemetryForwarding: Bool(true), + } + data, err := json.Marshal(req) + if err != nil { + t.Fatalf("Failed to marshal: %v", err) + } + var m map[string]any + if err := json.Unmarshal(data, &m); err != nil { + t.Fatalf("Failed to unmarshal: %v", err) + } + if m["enableGitHubTelemetryForwarding"] != true { + t.Errorf("Expected enableGitHubTelemetryForwarding to be true, got %v", m["enableGitHubTelemetryForwarding"]) + } + }) + + t.Run("omits when not set", func(t *testing.T) { + req := resumeSessionRequest{SessionID: "s1"} + data, _ := json.Marshal(req) + var m map[string]any + json.Unmarshal(data, &m) + if _, ok := m["enableGitHubTelemetryForwarding"]; ok { + t.Error("Expected enableGitHubTelemetryForwarding to be omitted when not set") + } + }) +} + +func TestClient_ForwardsGitHubTelemetryForwardingToSessionRequests(t *testing.T) { + rpcClient, server, _ := newRuntimeShutdownRpcPair(t) + t.Cleanup(server.Stop) + client := &Client{ + client: rpcClient, + RPC: rpc.NewServerRPC(rpcClient), + sessions: make(map[string]*Session), + options: ClientOptions{OnGitHubTelemetry: func(*rpc.GitHubTelemetryNotification) {}}, + } + + createParams := make(chan json.RawMessage, 1) + server.SetRequestHandler("session.create", func(params json.RawMessage) (json.RawMessage, *jsonrpc2.Error) { + createParams <- append(json.RawMessage(nil), params...) + sessionID := sessionIDFromParams(t, params) + return []byte(`{"sessionId":"` + sessionID + `","workspacePath":"/workspace"}`), nil + }) + + if _, err := client.CreateSession(t.Context(), &SessionConfig{}); err != nil { + t.Fatalf("CreateSession failed: %v", err) + } + assertForwardingFlagTrue(t, <-createParams) + + resumeParams := make(chan json.RawMessage, 1) + server.SetRequestHandler("session.resume", func(params json.RawMessage) (json.RawMessage, *jsonrpc2.Error) { + resumeParams <- append(json.RawMessage(nil), params...) + return []byte(`{"sessionId":"resumed","workspacePath":"/workspace"}`), nil + }) + + if _, err := client.ResumeSessionWithOptions(t.Context(), "resumed", &ResumeSessionConfig{}); err != nil { + t.Fatalf("ResumeSessionWithOptions failed: %v", err) + } + assertForwardingFlagTrue(t, <-resumeParams) +} + +func assertForwardingFlagTrue(t *testing.T, params json.RawMessage) { + t.Helper() + var decoded map[string]any + if err := json.Unmarshal(params, &decoded); err != nil { + t.Fatalf("failed to unmarshal request params: %v", err) + } + if decoded["enableGitHubTelemetryForwarding"] != true { + t.Fatalf("expected enableGitHubTelemetryForwarding=true, got %v", decoded["enableGitHubTelemetryForwarding"]) + } +} + +func TestClient_OmitsGitHubTelemetryForwardingWhenNoHandler(t *testing.T) { + rpcClient, server, _ := newRuntimeShutdownRpcPair(t) + t.Cleanup(server.Stop) + client := &Client{ + client: rpcClient, + RPC: rpc.NewServerRPC(rpcClient), + sessions: make(map[string]*Session), + options: ClientOptions{}, + } + + createParams := make(chan json.RawMessage, 1) + server.SetRequestHandler("session.create", func(params json.RawMessage) (json.RawMessage, *jsonrpc2.Error) { + createParams <- append(json.RawMessage(nil), params...) + sessionID := sessionIDFromParams(t, params) + return []byte(`{"sessionId":"` + sessionID + `","workspacePath":"/workspace"}`), nil + }) + + if _, err := client.CreateSession(t.Context(), &SessionConfig{}); err != nil { + t.Fatalf("CreateSession failed: %v", err) + } + assertForwardingFlagAbsent(t, <-createParams) + + resumeParams := make(chan json.RawMessage, 1) + server.SetRequestHandler("session.resume", func(params json.RawMessage) (json.RawMessage, *jsonrpc2.Error) { + resumeParams <- append(json.RawMessage(nil), params...) + return []byte(`{"sessionId":"resumed","workspacePath":"/workspace"}`), nil + }) + + if _, err := client.ResumeSessionWithOptions(t.Context(), "resumed", &ResumeSessionConfig{}); err != nil { + t.Fatalf("ResumeSessionWithOptions failed: %v", err) + } + assertForwardingFlagAbsent(t, <-resumeParams) +} + +func assertForwardingFlagAbsent(t *testing.T, params json.RawMessage) { + t.Helper() + var decoded map[string]any + if err := json.Unmarshal(params, &decoded); err != nil { + t.Fatalf("failed to unmarshal request params: %v", err) + } + if _, ok := decoded["enableGitHubTelemetryForwarding"]; ok { + t.Fatalf("expected enableGitHubTelemetryForwarding to be omitted, got %v", decoded["enableGitHubTelemetryForwarding"]) + } +} + +func TestClient_ForwardsGitHubTelemetryForwardingOnConnect(t *testing.T) { + rpcClient, server, _ := newRuntimeShutdownRpcPair(t) + t.Cleanup(server.Stop) + client := &Client{ + client: rpcClient, + RPC: rpc.NewServerRPC(rpcClient), + internalRPC: rpc.NewInternalServerRPC(rpcClient), + sessions: make(map[string]*Session), + options: ClientOptions{OnGitHubTelemetry: func(*rpc.GitHubTelemetryNotification) {}}, + } + + connectParams := make(chan json.RawMessage, 1) + server.SetRequestHandler("connect", func(params json.RawMessage) (json.RawMessage, *jsonrpc2.Error) { + connectParams <- append(json.RawMessage(nil), params...) + return []byte(`{"ok":true,"protocolVersion":3,"version":"test"}`), nil + }) + + if err := client.verifyProtocolVersion(t.Context()); err != nil { + t.Fatalf("verifyProtocolVersion failed: %v", err) + } + assertForwardingFlagTrue(t, <-connectParams) +} + +func TestClient_OmitsGitHubTelemetryForwardingOnConnectWhenNoHandler(t *testing.T) { + rpcClient, server, _ := newRuntimeShutdownRpcPair(t) + t.Cleanup(server.Stop) + client := &Client{ + client: rpcClient, + RPC: rpc.NewServerRPC(rpcClient), + internalRPC: rpc.NewInternalServerRPC(rpcClient), + sessions: make(map[string]*Session), + options: ClientOptions{}, + } + + connectParams := make(chan json.RawMessage, 1) + server.SetRequestHandler("connect", func(params json.RawMessage) (json.RawMessage, *jsonrpc2.Error) { + connectParams <- append(json.RawMessage(nil), params...) + return []byte(`{"ok":true,"protocolVersion":3,"version":"test"}`), nil + }) + + if err := client.verifyProtocolVersion(t.Context()); err != nil { + t.Fatalf("verifyProtocolVersion failed: %v", err) + } + assertForwardingFlagAbsent(t, <-connectParams) +} + +func TestGitHubTelemetryNotificationRoutesToCallback(t *testing.T) { + // The runtime forwards telemetry via a JSON-RPC *notification* (no id). + // Drive a real Content-Length-framed notification through the transport and + // verify that a real Client wired with OnGitHubTelemetry routes it to the + // callback through the client's own client-global handler registration + // (setupNotificationHandler), rather than registering the adapter by hand. + clientConn, serverConn := net.Pipe() + defer clientConn.Close() + defer serverConn.Close() + + rpcClient := jsonrpc2.NewClient(clientConn, clientConn) + rpcClient.Start() + defer rpcClient.Stop() + + // Drain the client->server direction so net.Pipe writes never block. + go func() { + buf := make([]byte, 4096) + for { + if _, err := serverConn.Read(buf); err != nil { + return + } + } + }() + + received := make(chan *rpc.GitHubTelemetryNotification, 1) + client := &Client{ + client: rpcClient, + RPC: rpc.NewServerRPC(rpcClient), + sessions: make(map[string]*Session), + options: ClientOptions{ + OnGitHubTelemetry: func(n *rpc.GitHubTelemetryNotification) { received <- n }, + }, + } + // setupNotificationHandler is what registers the gitHubTelemetryAdapter when + // OnGitHubTelemetry is set; exercising it here covers the real client wiring. + client.setupNotificationHandler() + + notification := map[string]any{ + "jsonrpc": "2.0", + "method": "gitHubTelemetry.event", + "params": map[string]any{ + "sessionId": "sess-telemetry", + "restricted": true, + "event": map[string]any{ + "kind": "tool_call_executed", + "metrics": map[string]any{"duration_ms": 12.5}, + "properties": map[string]any{"tool": "shell"}, + }, + }, + } + data, err := json.Marshal(notification) + if err != nil { + t.Fatalf("marshal notification: %v", err) + } + go func() { + _, _ = fmt.Fprintf(serverConn, "Content-Length: %d\r\n\r\n%s", len(data), data) + }() + + select { + case n := <-received: + sessionID := "" + if n.SessionID != nil { + sessionID = *n.SessionID + } + if sessionID != "sess-telemetry" { + t.Errorf("session id = %q, want sess-telemetry", sessionID) + } + if !n.Restricted { + t.Error("expected restricted to be true") + } + if n.Event.Kind != "tool_call_executed" { + t.Errorf("kind = %q, want tool_call_executed", n.Event.Kind) + } + if n.Event.Metrics["duration_ms"] != 12.5 { + t.Errorf("metrics[duration_ms] = %v, want 12.5", n.Event.Metrics["duration_ms"]) + } + if n.Event.Properties["tool"] != "shell" { + t.Errorf("properties[tool] = %q, want shell", n.Event.Properties["tool"]) + } + case <-time.After(2 * time.Second): + t.Fatal("timed out waiting for telemetry notification") + } +} + func TestCreateSessionRequest_EnableOnDemandInstructionDiscovery(t *testing.T) { t.Run("forwards explicit true", func(t *testing.T) { req := createSessionRequest{ diff --git a/go/internal/e2e/client_options_e2e_test.go b/go/internal/e2e/client_options_e2e_test.go index aa42be1f3a..0d3c802b06 100644 --- a/go/internal/e2e/client_options_e2e_test.go +++ b/go/internal/e2e/client_options_e2e_test.go @@ -10,6 +10,7 @@ import ( copilot "github.com/github/copilot-sdk/go" "github.com/github/copilot-sdk/go/internal/e2e/testharness" + "github.com/github/copilot-sdk/go/rpc" ) // Mirrors the E2E portions of dotnet/test/ClientOptionsTests.cs (snapshot category "client_options"). @@ -198,6 +199,315 @@ func TestClientOptionsE2E(t *testing.T) { } }) + t.Run("should forward advanced session creation options to the CLI", func(t *testing.T) { + ctx := testharness.NewTestContext(t) + cliPath := filepath.Join(ctx.WorkDir, "fake-cli-"+randomHex(t)+".js") + capturePath := filepath.Join(ctx.WorkDir, "fake-cli-capture-"+randomHex(t)+".json") + if err := os.WriteFile(cliPath, []byte(fakeStdioCliScript), 0644); err != nil { + t.Fatalf("Failed to write fake CLI script: %v", err) + } + + client := ctx.NewClient(func(opts *copilot.ClientOptions) { + opts.Connection = copilot.StdioConnection{Path: cliPath, Args: []string{"--capture-file", capturePath}} + opts.GitHubToken = "advanced-create-client-token" + opts.UseLoggedInUser = copilot.Bool(false) + }) + t.Cleanup(func() { client.ForceStop() }) + + if err := client.Start(t.Context()); err != nil { + t.Fatalf("Start failed: %v", err) + } + + sessionID := "advanced-session-id" + workingDirectory := t.TempDir() + configDirectory := t.TempDir() + embeddingCacheStorage := "in-memory" + organizationCustomInstructions := "organization guidance" + maxAiCredits := float64(42) + extensionSDKPath := filepath.Join(ctx.WorkDir, "extension-sdk") + session, err := client.CreateSession(t.Context(), &copilot.SessionConfig{ + SessionID: sessionID, + ClientName: "go-sdk-e2e-client", + Model: "claude-sonnet-4.5", + ReasoningEffort: "low", + ReasoningSummary: copilot.ReasoningSummaryNone, + ContextTier: copilot.ContextTierLongContext, + ConfigDirectory: configDirectory, + EnableConfigDiscovery: copilot.Bool(true), + SkipEmbeddingRetrieval: copilot.Bool(true), + EmbeddingCacheStorage: &embeddingCacheStorage, + OrganizationCustomInstructions: &organizationCustomInstructions, + EnableOnDemandInstructionDiscovery: copilot.Bool(true), + EnableFileHooks: copilot.Bool(false), + EnableHostGitOperations: copilot.Bool(false), + EnableSessionStore: copilot.Bool(false), + EnableSkills: copilot.Bool(false), + WorkingDirectory: workingDirectory, + Streaming: copilot.Bool(true), + IncludeSubAgentStreamingEvents: copilot.Bool(false), + AvailableTools: []string{"read_file"}, + ExcludedTools: []string{"bash"}, + ExcludedBuiltInAgents: []string{"legacy-agent"}, + EnableSessionTelemetry: copilot.Bool(false), + EnableCitations: copilot.Bool(true), + SessionLimits: &rpc.SessionLimitsConfig{MaxAiCredits: &maxAiCredits}, + SkipCustomInstructions: copilot.Bool(true), + CustomAgentsLocalOnly: copilot.Bool(true), + CoauthorEnabled: copilot.Bool(false), + ManageScheduleEnabled: copilot.Bool(false), + GitHubToken: "advanced-create-session-token", + RemoteSession: rpc.RemoteSessionModeExport, + SkillDirectories: []string{"skills"}, + PluginDirectories: []string{"plugins"}, + InstructionDirectories: []string{"instructions"}, + DisabledSkills: []string{"disabled-skill"}, + EnableMCPApps: true, + Canvases: []copilot.CanvasDeclaration{{ + ID: "canvas", + DisplayName: "Canvas", + Description: "Canvas description", + InputSchema: map[string]any{"type": "object"}, + }}, + RequestCanvasRenderer: copilot.Bool(true), + RequestExtensions: copilot.Bool(true), + ExtensionSDKPath: &extensionSDKPath, + ExtensionInfo: &copilot.ExtensionInfo{Source: "github-app", Name: "go-e2e-extension"}, + ExpAssignments: map[string]any{"feature": "enabled"}, + }) + if err != nil { + t.Fatalf("CreateSession failed: %v", err) + } + session.Disconnect() + + createReq := getCapturedRequest(t, capturePath, "session.create") + params, ok := createReq.Params.(map[string]any) + if !ok { + t.Fatalf("Expected session.create params object, got %T", createReq.Params) + } + expectedValues := map[string]any{ + "sessionId": sessionID, + "clientName": "go-sdk-e2e-client", + "model": "claude-sonnet-4.5", + "reasoningEffort": "low", + "reasoningSummary": "none", + "contextTier": "long_context", + "configDir": configDirectory, + "enableConfigDiscovery": true, + "skipEmbeddingRetrieval": true, + "embeddingCacheStorage": embeddingCacheStorage, + "organizationCustomInstructions": organizationCustomInstructions, + "enableOnDemandInstructionDiscovery": true, + "enableFileHooks": false, + "enableHostGitOperations": false, + "enableSessionStore": false, + "enableSkills": false, + "workingDirectory": workingDirectory, + "streaming": true, + "includeSubAgentStreamingEvents": false, + "enableSessionTelemetry": false, + "enableCitations": true, + "skipCustomInstructions": true, + "customAgentsLocalOnly": true, + "coauthorEnabled": false, + "manageScheduleEnabled": false, + "gitHubToken": "advanced-create-session-token", + "remoteSession": "export", + "requestMcpApps": true, + "requestCanvasRenderer": true, + "requestExtensions": true, + "extensionSdkPath": extensionSDKPath, + "envValueMode": "direct", + } + for key, expected := range expectedValues { + if params[key] != expected { + t.Fatalf("Expected %s=%#v, got %#v in %#v", key, expected, params[key], params) + } + } + assertStringArray(t, params["availableTools"], []string{"read_file"}) + assertStringArray(t, params["excludedTools"], []string{"bash"}) + assertStringArray(t, params["excludedBuiltinAgents"], []string{"legacy-agent"}) + assertStringArray(t, params["skillDirectories"], []string{"skills"}) + assertStringArray(t, params["pluginDirectories"], []string{"plugins"}) + assertStringArray(t, params["instructionDirectories"], []string{"instructions"}) + assertStringArray(t, params["disabledSkills"], []string{"disabled-skill"}) + if params["sessionLimits"].(map[string]any)["maxAiCredits"] != maxAiCredits { + t.Fatalf("Expected sessionLimits to be forwarded, got %#v", params["sessionLimits"]) + } + extensionInfo := params["extensionInfo"].(map[string]any) + if extensionInfo["source"] != "github-app" || extensionInfo["name"] != "go-e2e-extension" { + t.Fatalf("Expected extensionInfo to be forwarded, got %#v", extensionInfo) + } + canvases := params["canvases"].([]any) + canvas := canvases[0].(map[string]any) + if canvas["id"] != "canvas" || canvas["displayName"] != "Canvas" || canvas["description"] != "Canvas description" { + t.Fatalf("Expected canvas declaration to be forwarded, got %#v", canvas) + } + if params["expAssignments"].(map[string]any)["feature"] != "enabled" { + t.Fatalf("Expected expAssignments to be forwarded, got %#v", params["expAssignments"]) + } + }) + + t.Run("should forward singular provider configuration on session creation", func(t *testing.T) { + ctx := testharness.NewTestContext(t) + cliPath := filepath.Join(ctx.WorkDir, "fake-cli-"+randomHex(t)+".js") + capturePath := filepath.Join(ctx.WorkDir, "fake-cli-capture-"+randomHex(t)+".json") + if err := os.WriteFile(cliPath, []byte(fakeStdioCliScript), 0644); err != nil { + t.Fatalf("Failed to write fake CLI script: %v", err) + } + + client := ctx.NewClient(func(opts *copilot.ClientOptions) { + opts.Connection = copilot.StdioConnection{Path: cliPath, Args: []string{"--capture-file", capturePath}} + opts.GitHubToken = "provider-client-token" + opts.UseLoggedInUser = copilot.Bool(false) + }) + t.Cleanup(func() { client.ForceStop() }) + + if err := client.Start(t.Context()); err != nil { + t.Fatalf("Start failed: %v", err) + } + + session, err := client.CreateSession(t.Context(), &copilot.SessionConfig{ + Provider: &copilot.ProviderConfig{ + Type: "openai", + WireAPI: "responses", + Transport: "websockets", + BaseURL: "https://models.example.test/v1", + APIKey: "provider-key", + ModelID: "base-model", + WireModel: "wire-model", + MaxPromptTokens: 1000, + MaxOutputTokens: 2000, + Headers: map[string]string{"x-provider": "go"}, + }, + }) + if err != nil { + t.Fatalf("CreateSession failed: %v", err) + } + session.Disconnect() + + createReq := getCapturedRequest(t, capturePath, "session.create") + params := createReq.Params.(map[string]any) + provider := params["provider"].(map[string]any) + for key, expected := range map[string]any{ + "type": "openai", + "wireApi": "responses", + "transport": "websockets", + "baseUrl": "https://models.example.test/v1", + "apiKey": "provider-key", + "modelId": "base-model", + "wireModel": "wire-model", + "maxPromptTokens": float64(1000), + "maxOutputTokens": float64(2000), + } { + if provider[key] != expected { + t.Fatalf("Expected provider.%s=%#v, got %#v in %#v", key, expected, provider[key], provider) + } + } + if provider["headers"].(map[string]any)["x-provider"] != "go" { + t.Fatalf("Expected provider headers to be forwarded, got %#v", provider["headers"]) + } + }) + + t.Run("should forward advanced session resume options to the CLI", func(t *testing.T) { + ctx := testharness.NewTestContext(t) + cliPath := filepath.Join(ctx.WorkDir, "fake-cli-"+randomHex(t)+".js") + capturePath := filepath.Join(ctx.WorkDir, "fake-cli-capture-"+randomHex(t)+".json") + if err := os.WriteFile(cliPath, []byte(fakeStdioCliScript), 0644); err != nil { + t.Fatalf("Failed to write fake CLI script: %v", err) + } + + client := ctx.NewClient(func(opts *copilot.ClientOptions) { + opts.Connection = copilot.StdioConnection{Path: cliPath, Args: []string{"--capture-file", capturePath}} + opts.GitHubToken = "advanced-resume-client-token" + opts.UseLoggedInUser = copilot.Bool(false) + }) + t.Cleanup(func() { client.ForceStop() }) + + if err := client.Start(t.Context()); err != nil { + t.Fatalf("Start failed: %v", err) + } + + workingDirectory := t.TempDir() + configDirectory := t.TempDir() + continuePendingWork := false + extensionSDKPath := filepath.Join(ctx.WorkDir, "resume-extension-sdk") + session, err := client.ResumeSession(t.Context(), "resume-session-id", &copilot.ResumeSessionConfig{ + Model: "gpt-5-mini", + ReasoningEffort: "low", + ReasoningSummary: copilot.ReasoningSummaryNone, + ContextTier: copilot.ContextTierLongContext, + WorkingDirectory: workingDirectory, + ConfigDirectory: configDirectory, + EnableConfigDiscovery: copilot.Bool(false), + SuppressResumeEvent: true, + ContinuePendingWork: &continuePendingWork, + Streaming: copilot.Bool(true), + IncludeSubAgentStreamingEvents: copilot.Bool(false), + GitHubToken: "advanced-resume-session-token", + Canvases: []copilot.CanvasDeclaration{{ + ID: "resume-canvas", + DisplayName: "Resume Canvas", + Description: "Resume canvas description", + InputSchema: map[string]any{"type": "object"}, + }}, + OpenCanvases: []rpc.OpenCanvasInstance{{ + CanvasID: "resume-canvas", + ExtensionID: "github-app/go-e2e-extension", + InstanceID: "resume-instance", + Input: map[string]any{"value": "from-resume"}, + }}, + RequestCanvasRenderer: copilot.Bool(true), + RequestExtensions: copilot.Bool(true), + ExtensionSDKPath: &extensionSDKPath, + ExtensionInfo: &copilot.ExtensionInfo{Source: "github-app", Name: "go-e2e-extension"}, + ExpAssignments: map[string]any{"resumeFeature": "enabled"}, + }) + if err != nil { + t.Fatalf("ResumeSession failed: %v", err) + } + session.Disconnect() + + resumeReq := getCapturedRequest(t, capturePath, "session.resume") + params := resumeReq.Params.(map[string]any) + expectedValues := map[string]any{ + "sessionId": "resume-session-id", + "model": "gpt-5-mini", + "reasoningEffort": "low", + "reasoningSummary": "none", + "contextTier": "long_context", + "workingDirectory": workingDirectory, + "configDir": configDirectory, + "enableConfigDiscovery": false, + "disableResume": true, + "continuePendingWork": false, + "streaming": true, + "includeSubAgentStreamingEvents": false, + "gitHubToken": "advanced-resume-session-token", + "requestCanvasRenderer": true, + "requestExtensions": true, + "extensionSdkPath": extensionSDKPath, + "envValueMode": "direct", + } + for key, expected := range expectedValues { + if params[key] != expected { + t.Fatalf("Expected resume %s=%#v, got %#v in %#v", key, expected, params[key], params) + } + } + openCanvases := params["openCanvases"].([]any) + openCanvas := openCanvases[0].(map[string]any) + if openCanvas["canvasId"] != "resume-canvas" || openCanvas["extensionId"] != "github-app/go-e2e-extension" || + openCanvas["instanceId"] != "resume-instance" { + t.Fatalf("Expected open canvas state to be forwarded, got %#v", openCanvas) + } + extensionInfo := params["extensionInfo"].(map[string]any) + if extensionInfo["source"] != "github-app" || extensionInfo["name"] != "go-e2e-extension" { + t.Fatalf("Expected extensionInfo on resume, got %#v", extensionInfo) + } + if params["expAssignments"].(map[string]any)["resumeFeature"] != "enabled" { + t.Fatalf("Expected resume expAssignments to be forwarded, got %#v", params["expAssignments"]) + } + }) + } // --------------------------------------------------------------------------- @@ -353,8 +663,36 @@ func readCapture(t *testing.T, path string) capturedCli { return c } -// fakeStdioCliScript is identical to the one used by the .NET / Python -// equivalents (dotnet/test/ClientOptionsTests.cs and python/e2e/test_client_options.py). +func getCapturedRequest(t *testing.T, path, method string) capturedRequest { + t.Helper() + capture := readCapture(t, path) + for _, request := range capture.Requests { + if request.Method == method { + return request + } + } + t.Fatalf("Expected %s request in capture, got %+v", method, capture.Requests) + return capturedRequest{} +} + +func assertStringArray(t *testing.T, value any, expected []string) { + t.Helper() + items, ok := value.([]any) + if !ok { + t.Fatalf("Expected string array %v, got %#v", expected, value) + } + if len(items) != len(expected) { + t.Fatalf("Expected string array %v, got %#v", expected, items) + } + for i, expectedValue := range expected { + if items[i] != expectedValue { + t.Fatalf("Expected string array %v, got %#v", expected, items) + } + } +} + +// fakeStdioCliScript is intentionally kept close to the fake CLIs used by the +// other SDK client-options E2E tests, while still matching Go's request capture shape. const fakeStdioCliScript = ` const fs = require("fs"); @@ -430,6 +768,11 @@ function handleMessage(message) { writeResponse(message.id, { sessionId, workspacePath: null, capabilities: null }); return; } + if (message.method === "session.resume") { + const sessionId = (message.params && message.params.sessionId) || "fake-session"; + writeResponse(message.id, { sessionId, workspacePath: null, capabilities: null }); + return; + } writeResponse(message.id, {}); } diff --git a/go/internal/e2e/copilot_request_helpers_test.go b/go/internal/e2e/copilot_request_helpers_test.go index 7c6058207a..81d14f4d94 100644 --- a/go/internal/e2e/copilot_request_helpers_test.go +++ b/go/internal/e2e/copilot_request_helpers_test.go @@ -128,6 +128,47 @@ func buildResponsesSSEBody(text, respID string) string { return sb.String() } +// buildAnthropicMessageSSEBody returns a complete Anthropic Messages SSE body for a +// streaming /messages response (message_start … message_stop). The buffered JSON +// message is only valid for a non-streaming request; a streaming request expects +// named SSE events or the runtime fails to finalize the message. +func buildAnthropicMessageSSEBody(text string) string { + events := []struct { + name string + data map[string]any + }{ + {"message_start", map[string]any{ + "type": "message_start", + "message": map[string]any{ + "id": "msg_stub_1", "type": "message", "role": "assistant", + "model": "claude-sonnet-4.5", "content": []any{}, + "stop_reason": nil, "stop_sequence": nil, + "usage": map[string]any{"input_tokens": 5, "output_tokens": 1}, + }, + }}, + {"content_block_start", map[string]any{ + "type": "content_block_start", "index": 0, + "content_block": map[string]any{"type": "text", "text": ""}, + }}, + {"content_block_delta", map[string]any{ + "type": "content_block_delta", "index": 0, + "delta": map[string]any{"type": "text_delta", "text": text}, + }}, + {"content_block_stop", map[string]any{"type": "content_block_stop", "index": 0}}, + {"message_delta", map[string]any{ + "type": "message_delta", + "delta": map[string]any{"stop_reason": "end_turn", "stop_sequence": nil}, + "usage": map[string]any{"output_tokens": 7}, + }}, + {"message_stop", map[string]any{"type": "message_stop"}}, + } + var sb strings.Builder + for _, event := range events { + sb.WriteString(sseFrame(event.name, event.data)) + } + return sb.String() +} + // buildInferenceResponse synthesizes a well-formed inference HTTP response. func buildInferenceResponse(url string, bodyText string) *http.Response { wantsStream := isStreamingRequest(bodyText) @@ -167,6 +208,9 @@ func buildInferenceResponse(url string, bodyText string) *http.Response { } if strings.HasSuffix(u, "/messages") { + if wantsStream { + return buildSSEResponse(buildAnthropicMessageSSEBody(syntheticResponseText)) + } raw, _ := json.Marshal(map[string]any{ "id": "msg_stub_1", "type": "message", diff --git a/go/internal/e2e/github_telemetry_e2e_test.go b/go/internal/e2e/github_telemetry_e2e_test.go new file mode 100644 index 0000000000..aa26ba31f2 --- /dev/null +++ b/go/internal/e2e/github_telemetry_e2e_test.go @@ -0,0 +1,68 @@ +package e2e + +import ( + "sync" + "testing" + "time" + + copilot "github.com/github/copilot-sdk/go" + "github.com/github/copilot-sdk/go/internal/e2e/testharness" + "github.com/github/copilot-sdk/go/rpc" +) + +func TestGitHubTelemetryE2E(t *testing.T) { + t.Run("should forward github telemetry for a live session", func(t *testing.T) { + ctx := testharness.NewTestContext(t) + ctx.ConfigureForTest(t) + + var mu sync.Mutex + var notifications []*rpc.GitHubTelemetryNotification + client := ctx.NewClient(func(opts *copilot.ClientOptions) { + opts.OnGitHubTelemetry = func(notification *rpc.GitHubTelemetryNotification) { + mu.Lock() + notifications = append(notifications, notification) + mu.Unlock() + } + }) + t.Cleanup(func() { client.ForceStop() }) + + session, err := client.CreateSession(t.Context(), &copilot.SessionConfig{ + OnPermissionRequest: copilot.PermissionHandler.ApproveAll, + }) + if err != nil { + t.Fatalf("CreateSession failed: %v", err) + } + t.Cleanup(func() { session.Disconnect() }) + + notification := waitForGitHubTelemetryNotification(t, &mu, ¬ifications, 30*time.Second) + if notification.SessionID == nil || *notification.SessionID == "" { + t.Fatal("Expected a non-empty SessionID") + } + if notification.Event.Kind == "" { + t.Fatal("Expected a non-empty Event.Kind") + } + }) +} + +func waitForGitHubTelemetryNotification(t *testing.T, mu *sync.Mutex, notifications *[]*rpc.GitHubTelemetryNotification, timeout time.Duration) *rpc.GitHubTelemetryNotification { + t.Helper() + + deadline := time.Now().Add(timeout) + for time.Now().Before(deadline) { + mu.Lock() + if len(*notifications) > 0 { + notification := (*notifications)[0] + mu.Unlock() + if notification != nil { + return notification + } + t.Fatal("Received nil GitHub telemetry notification") + } + mu.Unlock() + + time.Sleep(50 * time.Millisecond) + } + + t.Fatalf("Timed out waiting for GitHub telemetry notification after %s", timeout) + return nil +} diff --git a/go/internal/e2e/mcp_oauth_e2e_test.go b/go/internal/e2e/mcp_oauth_e2e_test.go index e423f12d12..7959043063 100644 --- a/go/internal/e2e/mcp_oauth_e2e_test.go +++ b/go/internal/e2e/mcp_oauth_e2e_test.go @@ -218,7 +218,7 @@ func TestMCPOAuthE2E(t *testing.T) { } t.Cleanup(func() { session.Disconnect() }) - waitForMCPServerStatus(t, session, serverName, rpc.MCPServerStatusFailed) + waitForMCPServerStatus(t, session, serverName, rpc.MCPServerStatusNeedsAuth) if observedRequest.ServerName != serverName { t.Fatalf("Expected serverName %q, got %q", serverName, observedRequest.ServerName) } @@ -226,6 +226,77 @@ func TestMCPOAuthE2E(t *testing.T) { t.Fatalf("Unexpected auth request reason: %q", observedRequest.Reason) } }) + + t.Run("resolve pending MCP OAuth request through RPC", func(t *testing.T) { + ctx := testharness.NewTestContext(t) + ctx.ConfigureWithoutSnapshot(t) + client := ctx.NewClient() + defer client.ForceStop() + + baseURL := startOAuthMCPServer(t) + serverName := "oauth-direct-rpc-mcp" + requests := make(chan copilot.MCPAuthRequest, 1) + releaseHandler := make(chan struct{}) + + session, err := client.CreateSession(t.Context(), &copilot.SessionConfig{ + OnPermissionRequest: copilot.PermissionHandler.ApproveAll, + EnableMCPApps: true, + OnMCPAuthRequest: func(request copilot.MCPAuthRequest, _ copilot.MCPAuthInvocation) (*copilot.MCPAuthResult, error) { + requests <- request + <-releaseHandler + return copilot.MCPAuthResultCancelled(), nil + }, + MCPServers: map[string]copilot.MCPServerConfig{ + serverName: copilot.MCPHTTPServerConfig{ + URL: baseURL + "/mcp", + Tools: []string{"*"}, + }, + }, + }) + if err != nil { + t.Fatalf("Failed to create session: %v", err) + } + t.Cleanup(func() { session.Disconnect() }) + + connected := make(chan error, 1) + go func() { + connected <- waitForMCPServerStatusResult(t.Context(), session, serverName, rpc.MCPServerStatusConnected, 60*time.Second) + }() + + var request copilot.MCPAuthRequest + select { + case request = <-requests: + case <-time.After(30 * time.Second): + t.Fatal("Timed out waiting for MCP OAuth request") + } + + tokenType := "Bearer" + expiresIn := int64(3600) + result, err := session.RPC.MCP.Oauth().HandlePendingRequest(t.Context(), &rpc.MCPOauthHandlePendingRequest{ + RequestID: request.RequestID, + Result: rpc.MCPOauthPendingRequestResponseToken{ + AccessToken: expectedMCPOAuthToken, + TokenType: &tokenType, + ExpiresIn: &expiresIn, + }, + }) + if err != nil { + close(releaseHandler) + t.Fatalf("HandlePendingRequest failed: %v", err) + } + close(releaseHandler) + if !result.Success { + t.Fatal("Expected direct MCP OAuth pending request resolution to succeed") + } + + if err := <-connected; err != nil { + t.Fatal(err) + } + requestLog := fetchOAuthMCPRequests(t, baseURL) + if !hasAuthorization(requestLog, "Bearer "+expectedMCPOAuthToken) { + t.Fatal("Expected MCP request with token supplied through direct RPC") + } + }) } type oauthMCPRequest struct { diff --git a/go/internal/e2e/mcp_server_helpers_test.go b/go/internal/e2e/mcp_server_helpers_test.go index 1860067eda..e7269b1e26 100644 --- a/go/internal/e2e/mcp_server_helpers_test.go +++ b/go/internal/e2e/mcp_server_helpers_test.go @@ -1,6 +1,8 @@ package e2e import ( + "context" + "fmt" "path/filepath" "testing" "time" @@ -33,10 +35,16 @@ func testMCPServers(t *testing.T, serverNames ...string) map[string]copilot.MCPS func waitForMCPServerStatus(t *testing.T, session *copilot.Session, serverName string, expectedStatus rpc.MCPServerStatus) { t.Helper() + if err := waitForMCPServerStatusResult(t.Context(), session, serverName, expectedStatus, 60*time.Second); err != nil { + t.Fatal(err) + } +} + +func waitForMCPServerStatusResult(ctx context.Context, session *copilot.Session, serverName string, expectedStatus rpc.MCPServerStatus, timeout time.Duration) error { var lastStatus string - deadline := time.Now().Add(60 * time.Second) + deadline := time.Now().Add(timeout) for time.Now().Before(deadline) { - result, err := session.RPC.MCP.List(t.Context()) + result, err := session.RPC.MCP.List(ctx) if err != nil { lastStatus = err.Error() } else { @@ -46,7 +54,7 @@ func waitForMCPServerStatus(t *testing.T, session *copilot.Session, serverName s continue } if server.Status == expectedStatus { - return + return nil } lastStatus = string(server.Status) break @@ -55,5 +63,5 @@ func waitForMCPServerStatus(t *testing.T, session *copilot.Session, serverName s time.Sleep(200 * time.Millisecond) } - t.Fatalf("%s did not reach %s; last status was %s", serverName, expectedStatus, lastStatus) + return fmt.Errorf("%s did not reach %s; last status was %s", serverName, expectedStatus, lastStatus) } diff --git a/go/internal/e2e/rpc_server_e2e_test.go b/go/internal/e2e/rpc_server_e2e_test.go index ba661acdba..2510eb1d9c 100644 --- a/go/internal/e2e/rpc_server_e2e_test.go +++ b/go/internal/e2e/rpc_server_e2e_test.go @@ -3,6 +3,7 @@ package e2e import ( "fmt" "path/filepath" + "runtime" "strings" "testing" "time" @@ -196,6 +197,44 @@ func TestRPCServerE2E(t *testing.T) { } }) + t.Run("should return false for missing LLM response frames", func(t *testing.T) { + ctx := testharness.NewTestContext(t) + client := ctx.NewClient() + t.Cleanup(func() { client.ForceStop() }) + + if err := client.Start(t.Context()); err != nil { + t.Fatalf("Start failed: %v", err) + } + + start, err := client.RPC.LlmInference.HttpResponseStart(t.Context(), &rpc.LlmInferenceHTTPResponseStartRequest{ + RequestID: "missing-response-start-request", + Status: 200, + StatusText: rpcPtr("OK"), + Headers: map[string][]string{ + "content-type": {"application/json"}, + }, + }) + if err != nil { + t.Fatalf("LlmInference.HttpResponseStart failed: %v", err) + } + if start.Accepted { + t.Fatal("Expected Accepted=false for missing LLM response start request id") + } + + end := true + chunk, err := client.RPC.LlmInference.HttpResponseChunk(t.Context(), &rpc.LlmInferenceHTTPResponseChunkRequest{ + RequestID: "missing-response-chunk-request", + Data: "{}", + End: &end, + }) + if err != nil { + t.Fatalf("LlmInference.HttpResponseChunk failed: %v", err) + } + if chunk.Accepted { + t.Fatal("Expected Accepted=false for missing LLM response chunk request id") + } + }) + t.Run("should list find and inspect persisted session state", func(t *testing.T) { ctx := testharness.NewTestContext(t) token := "rpc-server-list-token-" + randomHex(t) @@ -554,6 +593,82 @@ func TestRPCServerE2E(t *testing.T) { t.Errorf("Expected skill path to end with %q, got %v", expectedSuffix, discovered.Path) } + excludeHost := true + skillPaths, err := client.RPC.Skills.GetDiscoveryPaths(t.Context(), &rpc.SkillsGetDiscoveryPathsRequest{ + ProjectPaths: []string{ctx.WorkDir}, + ExcludeHostSkills: &excludeHost, + }) + if err != nil { + t.Fatalf("Skills.GetDiscoveryPaths failed: %v", err) + } + projectSkillPath := findSkillDiscoveryPath(skillPaths.Paths, ctx.WorkDir) + if projectSkillPath == nil { + t.Fatalf("Expected skill discovery paths to include %q", ctx.WorkDir) + } + if strings.TrimSpace(projectSkillPath.Path) == "" { + t.Fatal("Expected non-empty skill discovery path") + } + + agents, err := client.RPC.Agents.Discover(t.Context(), &rpc.AgentsDiscoverRequest{ + ProjectPaths: []string{ctx.WorkDir}, + ExcludeHostAgents: &excludeHost, + }) + if err != nil { + t.Fatalf("Agents.Discover failed: %v", err) + } + for _, agent := range agents.Agents { + if strings.TrimSpace(agent.Name) == "" { + t.Fatalf("Expected discovered agent to have a name: %+v", agent) + } + } + + agentPaths, err := client.RPC.Agents.GetDiscoveryPaths(t.Context(), &rpc.AgentsGetDiscoveryPathsRequest{ + ProjectPaths: []string{ctx.WorkDir}, + ExcludeHostAgents: &excludeHost, + }) + if err != nil { + t.Fatalf("Agents.GetDiscoveryPaths failed: %v", err) + } + projectAgentPath := findAgentDiscoveryPath(agentPaths.Paths, ctx.WorkDir) + if projectAgentPath == nil { + t.Fatalf("Expected agent discovery paths to include %q", ctx.WorkDir) + } + if strings.TrimSpace(projectAgentPath.Path) == "" { + t.Fatal("Expected non-empty agent discovery path") + } + + instructions, err := client.RPC.Instructions.Discover(t.Context(), &rpc.InstructionsDiscoverRequest{ + ProjectPaths: []string{ctx.WorkDir}, + ExcludeHostInstructions: &excludeHost, + }) + if err != nil { + t.Fatalf("Instructions.Discover failed: %v", err) + } + for _, source := range instructions.Sources { + if strings.TrimSpace(source.ID) == "" || strings.TrimSpace(source.Label) == "" || strings.TrimSpace(source.SourcePath) == "" { + t.Fatalf("Expected discovered instruction source fields to be populated: %+v", source) + } + } + + instructionPaths, err := client.RPC.Instructions.GetDiscoveryPaths(t.Context(), &rpc.InstructionsGetDiscoveryPathsRequest{ + ProjectPaths: []string{ctx.WorkDir}, + ExcludeHostInstructions: &excludeHost, + }) + if err != nil { + t.Fatalf("Instructions.GetDiscoveryPaths failed: %v", err) + } + if len(instructionPaths.Paths) == 0 { + t.Fatal("Expected instruction discovery paths") + } + if !hasInstructionDiscoveryPath(instructionPaths.Paths, ctx.WorkDir) { + t.Fatalf("Expected instruction discovery paths to include %q", ctx.WorkDir) + } + for _, path := range instructionPaths.Paths { + if strings.TrimSpace(path.Path) == "" { + t.Fatalf("Expected non-empty instruction discovery path: %+v", path) + } + } + // Disable the skill globally and re-discover. if _, err := client.RPC.Skills.Config().SetDisabledSkills(t.Context(), &rpc.SkillsConfigSetDisabledSkillsRequest{ DisabledSkills: []string{skillName}, @@ -615,6 +730,42 @@ func findServerSkill(skills []rpc.ServerSkill, name string) *rpc.ServerSkill { return nil } +func findSkillDiscoveryPath(paths []rpc.SkillDiscoveryPath, projectPath string) *rpc.SkillDiscoveryPath { + for i, path := range paths { + if path.ProjectPath != nil && path.PreferredForCreation && pathsEqual(*path.ProjectPath, projectPath) { + return &paths[i] + } + } + return nil +} + +func findAgentDiscoveryPath(paths []rpc.AgentDiscoveryPath, projectPath string) *rpc.AgentDiscoveryPath { + for i, path := range paths { + if path.ProjectPath != nil && path.PreferredForCreation && pathsEqual(*path.ProjectPath, projectPath) { + return &paths[i] + } + } + return nil +} + +func hasInstructionDiscoveryPath(paths []rpc.InstructionDiscoveryPath, projectPath string) bool { + for _, path := range paths { + if path.ProjectPath != nil && pathsEqual(*path.ProjectPath, projectPath) { + return true + } + } + return false +} + +func pathsEqual(left, right string) bool { + left = filepath.Clean(left) + right = filepath.Clean(right) + if runtime.GOOS == "windows" { + return strings.EqualFold(left, right) + } + return left == right +} + func saveSession(t *testing.T, client *copilot.Client, sessionID string) { t.Helper() if _, err := client.RPC.Sessions.Save(t.Context(), &rpc.SessionsSaveRequest{SessionID: sessionID}); err != nil { diff --git a/go/internal/e2e/rpc_server_misc_e2e_test.go b/go/internal/e2e/rpc_server_misc_e2e_test.go index 34b48b5063..38b569798c 100644 --- a/go/internal/e2e/rpc_server_misc_e2e_test.go +++ b/go/internal/e2e/rpc_server_misc_e2e_test.go @@ -5,6 +5,7 @@ import ( "testing" "time" + copilot "github.com/github/copilot-sdk/go" "github.com/github/copilot-sdk/go/internal/e2e/testharness" "github.com/github/copilot-sdk/go/rpc" ) @@ -25,6 +26,168 @@ func TestRpcServerMisc(t *testing.T) { } }) + t.Run("should_get_set_and_clear_user_settings", func(t *testing.T) { + ctx.ConfigureForTest(t) + client := newStartedIsolatedPortedClient(t, ctx) + defer client.ForceStop() + + initial, err := client.RPC.User.Settings().Get(t.Context()) + if err != nil { + t.Fatalf("User.Settings.Get initial failed: %v", err) + } + if initial.Settings == nil { + t.Fatal("Expected settings map") + } + var key string + var value bool + for candidateKey, setting := range initial.Settings { + if candidateValue, ok := setting.Value.(bool); ok { + key = candidateKey + value = candidateValue + break + } + } + if key == "" { + t.Fatalf("Expected at least one boolean setting, got %+v", initial.Settings) + } + toggledValue := !value + + set, err := client.RPC.User.Settings().Set(t.Context(), &rpc.UserSettingsSetRequest{ + Settings: map[string]any{key: toggledValue}, + }) + if err != nil { + t.Fatalf("User.Settings.Set(toggle) failed: %v", err) + } + if len(set.ShadowedKeys) != 0 { + t.Fatalf("Expected no shadowed settings keys, got %+v", set.ShadowedKeys) + } + if _, err := client.RPC.User.Settings().Reload(t.Context()); err != nil { + t.Fatalf("User.Settings.Reload after set failed: %v", err) + } + afterSet, err := client.RPC.User.Settings().Get(t.Context()) + if err != nil { + t.Fatalf("User.Settings.Get after set failed: %v", err) + } + metadata, ok := afterSet.Settings[key] + if !ok { + t.Fatalf("Expected setting %q in %+v", key, afterSet.Settings) + } + if metadata.Value != toggledValue || metadata.IsDefault { + t.Fatalf("Expected explicit true setting, got %+v", metadata) + } + + clear, err := client.RPC.User.Settings().Set(t.Context(), &rpc.UserSettingsSetRequest{ + Settings: map[string]any{key: nil}, + }) + if err != nil { + t.Fatalf("User.Settings.Set(null) failed: %v", err) + } + if len(clear.ShadowedKeys) != 0 { + t.Fatalf("Expected no shadowed settings keys from clear, got %+v", clear.ShadowedKeys) + } + if _, err := client.RPC.User.Settings().Reload(t.Context()); err != nil { + t.Fatalf("User.Settings.Reload after clear failed: %v", err) + } + afterClear, err := client.RPC.User.Settings().Get(t.Context()) + if err != nil { + t.Fatalf("User.Settings.Get after clear failed: %v", err) + } + metadata, ok = afterClear.Settings[key] + if !ok { + t.Fatalf("Expected setting %q after clear in %+v", key, afterClear.Settings) + } + if !metadata.IsDefault { + t.Fatalf("Expected cleared setting to be default, got %+v", metadata) + } + }) + + t.Run("should_login_list_getcurrentauth_and_logout_account", func(t *testing.T) { + ctx.ConfigureForTest(t) + if err := ctx.SetCopilotUserByToken("go-account-token", map[string]interface{}{ + "login": "go-account-user", + "copilot_plan": "individual_pro", + "endpoints": map[string]interface{}{ + "api": ctx.ProxyURL, + "telemetry": "https://localhost:1/telemetry", + }, + "analytics_tracking_id": "go-account-user-tracking-id", + }); err != nil { + t.Fatalf("SetCopilotUserByToken failed: %v", err) + } + client := newNoTokenClient(t, ctx) + defer client.ForceStop() + + if err := client.Start(t.Context()); err != nil { + t.Fatalf("Start failed: %v", err) + } + + initial, err := client.RPC.Account.GetCurrentAuth(t.Context()) + if err != nil { + t.Fatalf("Account.GetCurrentAuth initial failed: %v", err) + } + if initial.AuthInfo != nil { + t.Fatalf("Expected no initial auth info, got %+v", initial.AuthInfo) + } + + login, err := client.RPC.Account.Login(t.Context(), &rpc.AccountLoginRequest{ + Host: "https://github.com", + Login: "go-account-user", + Token: "go-account-token", + }) + if err != nil { + t.Fatalf("Account.Login failed: %v", err) + } + if login == nil { + t.Fatal("Expected login result") + } + + current, err := client.RPC.Account.GetCurrentAuth(t.Context()) + if err != nil { + t.Fatalf("Account.GetCurrentAuth after login failed: %v", err) + } + authInfo, ok := current.AuthInfo.(*rpc.UserAuthInfo) + if !ok { + t.Fatalf("Expected user auth info after login, got %#v", current.AuthInfo) + } + if authInfo.Login != "go-account-user" || authInfo.Host != "https://github.com" { + t.Fatalf("Unexpected current auth info: %+v", authInfo) + } + + users, err := client.RPC.Account.GetAllUsers(t.Context()) + if err != nil { + t.Fatalf("Account.GetAllUsers failed: %v", err) + } + if users == nil { + t.Fatal("Expected non-nil users result") + } + for _, user := range *users { + userInfo, ok := user.AuthInfo.(*rpc.UserAuthInfo) + if !ok { + t.Fatalf("Expected user auth info in all users, got %#v", user.AuthInfo) + } + if userInfo.Login == "go-account-user" && (user.Token == nil || *user.Token != "go-account-token") { + t.Fatalf("Expected logged-in user's token to round trip, got %+v", user) + } + } + + logout, err := client.RPC.Account.Logout(t.Context(), &rpc.AccountLogoutRequest{ + AuthInfo: authInfo, + }) + if err != nil { + t.Fatalf("Account.Logout failed: %v", err) + } + if logout.HasMoreUsers { + t.Fatalf("Expected no users after isolated logout, got %+v", logout) + } + afterLogout, err := client.RPC.Account.GetCurrentAuth(t.Context()) + if err != nil { + t.Fatalf("Account.GetCurrentAuth after logout failed: %v", err) + } + if afterLogout.AuthInfo != nil { + t.Fatalf("Expected no auth after logout, got %+v", afterLogout.AuthInfo) + } + }) + t.Run("should_report_agent_registry_spawn_gate_closed", func(t *testing.T) { ctx.ConfigureForTest(t) client := newStartedIsolatedPortedClient(t, ctx) @@ -91,3 +254,22 @@ func TestRpcServerMisc(t *testing.T) { assertPortedContainsFold(t, message, "extension") }) } + +func newNoTokenClient(t *testing.T, ctx *testharness.TestContext) *copilot.Client { + t.Helper() + env := append([]string{}, ctx.Env()...) + env = append(env, + "COPILOT_HOME="+t.TempDir(), + "GH_CONFIG_DIR="+t.TempDir(), + "GH_TOKEN=", + "GITHUB_TOKEN=", + "COPILOT_SDK_AUTH_TOKEN=", + ) + useLoggedInUser := false + return copilot.NewClient(&copilot.ClientOptions{ + Connection: copilot.StdioConnection{Path: ctx.CLIPath}, + WorkingDirectory: ctx.WorkDir, + Env: env, + UseLoggedInUser: &useLoggedInUser, + }) +} diff --git a/go/internal/e2e/rpc_session_state_extras_e2e_test.go b/go/internal/e2e/rpc_session_state_extras_e2e_test.go index f4de1c1867..1e33e8a8bc 100644 --- a/go/internal/e2e/rpc_session_state_extras_e2e_test.go +++ b/go/internal/e2e/rpc_session_state_extras_e2e_test.go @@ -70,7 +70,7 @@ func TestRpcSessionStateExtras(t *testing.T) { session := createPortedSession(t, client, nil) defer session.Disconnect() defer func() { - _, _ = session.RPC.Permissions.SetAllowAll(t.Context(), &rpc.PermissionsSetAllowAllRequest{Enabled: false}) + _, _ = session.RPC.Permissions.SetAllowAll(t.Context(), &rpc.PermissionsSetAllowAllRequest{Enabled: copilot.Bool(false)}) }() initial, err := session.RPC.Permissions.GetAllowAll(t.Context()) @@ -81,7 +81,7 @@ func TestRpcSessionStateExtras(t *testing.T) { t.Fatal("Allow-all should be disabled on a fresh session") } - enable, err := session.RPC.Permissions.SetAllowAll(t.Context(), &rpc.PermissionsSetAllowAllRequest{Enabled: true}) + enable, err := session.RPC.Permissions.SetAllowAll(t.Context(), &rpc.PermissionsSetAllowAllRequest{Enabled: copilot.Bool(true)}) if err != nil { t.Fatalf("Permissions.SetAllowAll(true) failed: %v", err) } @@ -96,7 +96,7 @@ func TestRpcSessionStateExtras(t *testing.T) { t.Fatal("Expected allow-all to be enabled") } - disable, err := session.RPC.Permissions.SetAllowAll(t.Context(), &rpc.PermissionsSetAllowAllRequest{Enabled: false}) + disable, err := session.RPC.Permissions.SetAllowAll(t.Context(), &rpc.PermissionsSetAllowAllRequest{Enabled: copilot.Bool(false)}) if err != nil { t.Fatalf("Permissions.SetAllowAll(false) failed: %v", err) } @@ -176,6 +176,157 @@ func TestRpcSessionStateExtras(t *testing.T) { } }) + t.Run("should_add_byok_provider_and_model_at_runtime", func(t *testing.T) { + ctx.ConfigureForTest(t) + session := createPortedSession(t, client, nil) + defer session.Disconnect() + + apiKey := "provider-key" + providerType := rpc.ProviderConfigTypeOpenai + wireAPI := rpc.ProviderConfigWireAPICompletions + modelName := "Go Added Model" + maxPromptTokens := float64(4096) + result, err := session.RPC.Provider.Add(t.Context(), &rpc.ProviderAddRequest{ + Providers: []rpc.NamedProviderConfig{{ + Name: "go-e2e-provider", + Type: &providerType, + BaseURL: "https://models.example.test/v1", + APIKey: &apiKey, + Headers: map[string]string{"x-provider": "go"}, + WireAPI: &wireAPI, + }}, + Models: []rpc.ProviderModelConfig{{ + ID: "small", + Provider: "go-e2e-provider", + Name: &modelName, + MaxPromptTokens: &maxPromptTokens, + }}, + }) + if err != nil { + t.Fatalf("Provider.Add failed: %v", err) + } + if len(result.Models) != 1 { + t.Fatalf("Expected one added provider model, got %+v", result.Models) + } + + selectionID := "go-e2e-provider/small" + if _, err := session.RPC.Model.SwitchTo(t.Context(), &rpc.ModelSwitchToRequest{ModelID: selectionID}); err != nil { + t.Fatalf("Model.SwitchTo added model failed: %v", err) + } + current, err := session.RPC.Model.GetCurrent(t.Context()) + if err != nil { + t.Fatalf("Model.GetCurrent after provider add failed: %v", err) + } + if current.ModelID == nil || *current.ModelID != selectionID { + t.Fatalf("Expected current model %q, got %+v", selectionID, current) + } + }) + + t.Run("should_return_empty_completions_when_host_does_not_provide_them", func(t *testing.T) { + ctx.ConfigureForTest(t) + session := createPortedSession(t, client, nil) + defer session.Disconnect() + + result, err := session.RPC.Completions.Request(t.Context(), &rpc.CompletionsRequestRequest{ + Text: "Use @ to mention context", + Offset: 5, + }) + if err != nil { + t.Fatalf("Completions.Request failed: %v", err) + } + if result.Items == nil { + t.Fatal("Expected non-nil completion items list") + } + }) + + t.Run("should_report_visibility_as_unsynced_for_local_session", func(t *testing.T) { + ctx.ConfigureForTest(t) + session := createPortedSession(t, client, nil) + defer session.Disconnect() + + status := rpc.SessionVisibilityStatusUnshared + set, err := session.RPC.Visibility.Set(t.Context(), &rpc.VisibilitySetRequest{Status: status}) + if err != nil { + t.Fatalf("Visibility.Set failed: %v", err) + } + if set.Synced || set.Status != nil || set.ShareURL != nil { + t.Fatalf("Expected unsynced visibility set result, got %+v", set) + } + get, err := session.RPC.Visibility.Get(t.Context()) + if err != nil { + t.Fatalf("Visibility.Get failed: %v", err) + } + if get.Synced || get.Status != nil || get.ShareURL != nil { + t.Fatalf("Expected unsynced visibility get result, got %+v", get) + } + }) + + t.Run("should_get_context_attribution_and_heaviest_messages_after_turn", func(t *testing.T) { + ctx.ConfigureForTest(t) + session := createPortedSession(t, client, nil) + defer session.Disconnect() + + answer, err := session.SendAndWait(t.Context(), copilot.MessageOptions{Prompt: "Say CONTEXT_METADATA_OK exactly."}) + if err != nil { + t.Fatalf("SendAndWait failed: %v", err) + } + if answer == nil { + t.Fatal("Expected final assistant message") + } + + attribution, err := session.RPC.Metadata.GetContextAttribution(t.Context()) + if err != nil { + t.Fatalf("Metadata.GetContextAttribution failed: %v", err) + } + if attribution == nil { + t.Fatal("Expected attribution result") + } + limit := int64(5) + heaviest, err := session.RPC.Metadata.GetContextHeaviestMessages(t.Context(), &rpc.MetadataContextHeaviestMessagesRequest{Limit: &limit}) + if err != nil { + t.Fatalf("Metadata.GetContextHeaviestMessages failed: %v", err) + } + if heaviest.Messages == nil { + t.Fatal("Expected non-nil heaviest messages list") + } + }) + + t.Run("should_update_and_clear_live_subagent_settings", func(t *testing.T) { + ctx.ConfigureForTest(t) + session := createPortedSession(t, client, nil) + defer session.Disconnect() + + contextTier := rpc.SubagentSettingsEntryContextTierLongContext + model := "gpt-5-mini" + reasoningEffort := "low" + update, err := session.RPC.Tools.UpdateSubagentSettings(t.Context(), &rpc.UpdateSubagentSettingsRequest{ + Subagents: &rpc.SubagentSettings{ + DisabledSubagents: []string{"legacy-agent"}, + Agents: map[string]rpc.SubagentSettingsEntry{ + "general-purpose": { + ContextTier: &contextTier, + Model: &model, + EffortLevel: &reasoningEffort, + }, + }, + }, + }) + if err != nil { + t.Fatalf("Tools.UpdateSubagentSettings failed: %v", err) + } + if update == nil { + t.Fatal("Expected update result") + } + + clear, err := session.RPC.Tools.UpdateSubagentSettings(t.Context(), &rpc.UpdateSubagentSettingsRequest{}) + if err != nil { + t.Fatalf("Tools.UpdateSubagentSettings clear failed: %v", err) + } + if clear == nil { + t.Fatal("Expected clear result") + } + }) + t.Run("should_reload_session_plugins", func(t *testing.T) { ctx.ConfigureForTest(t) session := createPortedSession(t, client, nil) diff --git a/go/internal/e2e/rpc_tasks_and_handlers_e2e_test.go b/go/internal/e2e/rpc_tasks_and_handlers_e2e_test.go index 60ae3f1fd3..0267f8d042 100644 --- a/go/internal/e2e/rpc_tasks_and_handlers_e2e_test.go +++ b/go/internal/e2e/rpc_tasks_and_handlers_e2e_test.go @@ -302,6 +302,39 @@ func TestRPCTasksAndHandlersE2E(t *testing.T) { if locationApproval.Success { t.Error("Expected Success=false for missing location approval request id") } + + sessionLimits, err := session.RPC.UI.HandlePendingSessionLimitsExhausted(t.Context(), &rpc.UIHandlePendingSessionLimitsExhaustedRequest{ + RequestID: "missing-session-limits-request", + Response: rpc.UISessionLimitsExhaustedResponse{Action: rpc.UISessionLimitsExhaustedResponseActionCancel}, + }) + if err != nil { + t.Fatalf("UI.HandlePendingSessionLimitsExhausted failed: %v", err) + } + if sessionLimits.Success { + t.Error("Expected Success=false for missing session limits request id") + } + + headers, err := session.RPC.MCP.Headers().HandlePendingHeadersRefreshRequest(t.Context(), &rpc.MCPHeadersHandlePendingHeadersRefreshRequestRequest{ + RequestID: "missing-headers-refresh-request", + Result: rpc.MCPHeadersHandlePendingHeadersRefreshRequestHeaders{Headers: map[string]string{"authorization": "Bearer refreshed"}}, + }) + if err != nil { + t.Fatalf("MCP.Headers.HandlePendingHeadersRefreshRequest failed: %v", err) + } + if headers.Success { + t.Error("Expected Success=false for missing MCP headers refresh request id") + } + + noHeaders, err := session.RPC.MCP.Headers().HandlePendingHeadersRefreshRequest(t.Context(), &rpc.MCPHeadersHandlePendingHeadersRefreshRequestRequest{ + RequestID: "missing-headers-refresh-none-request", + Result: rpc.MCPHeadersHandlePendingHeadersRefreshRequestNone{}, + }) + if err != nil { + t.Fatalf("MCP.Headers.HandlePendingHeadersRefreshRequest none failed: %v", err) + } + if noHeaders.Success { + t.Error("Expected Success=false for missing MCP headers refresh none request id") + } }) t.Run("should round trip rpc elicitation through config handler", func(t *testing.T) { diff --git a/go/internal/e2e/testharness/context.go b/go/internal/e2e/testharness/context.go index 2643980b75..6b6463749f 100644 --- a/go/internal/e2e/testharness/context.go +++ b/go/internal/e2e/testharness/context.go @@ -158,6 +158,18 @@ func (c *TestContext) ConfigureForTest(t *testing.T) { } } +// ConfigureWithoutSnapshot initializes the replay proxy without loading a recorded CAPI +// exchange file. Use this for tests that serve all model-layer behavior locally but +// still need proxy-backed auth and GitHub API endpoints. +func (c *TestContext) ConfigureWithoutSnapshot(t *testing.T) { + t.Helper() + + dummySnapshotPath := filepath.Join(c.WorkDir, "__no_snapshot__.yaml") + if err := c.proxy.Configure(dummySnapshotPath, c.WorkDir); err != nil { + t.Fatalf("Failed to configure proxy without snapshot: %v", err) + } +} + // Close cleans up the test context resources. func (c *TestContext) Close(testFailed bool) { if c.proxy != nil { diff --git a/go/rpc/zrpc.go b/go/rpc/zrpc.go index 03cec0dc3c..1a1ec84ddb 100644 --- a/go/rpc/zrpc.go +++ b/go/rpc/zrpc.go @@ -28,7 +28,8 @@ type AbortResult struct { Success bool `json:"success"` } -// Schema for the `AccountAllUsers` type. +// Authenticated account entry returned by `account.getAllUsers`, with auth info and an +// optional associated token. // Experimental: AccountAllUsers is part of an experimental API and may change or be removed. type AccountAllUsers struct { // Authentication information for this user @@ -106,7 +107,8 @@ type AccountLogoutResult struct { HasMoreUsers bool `json:"hasMoreUsers"` } -// Schema for the `AccountQuotaSnapshot` type. +// Quota usage snapshot for a Copilot quota type, including entitlement, used requests, +// overage, reset date, and remaining percentage. // Experimental: AccountQuotaSnapshot is part of an experimental API and may change or be // removed. type AccountQuotaSnapshot struct { @@ -128,7 +130,8 @@ type AccountQuotaSnapshot struct { UsedRequests int64 `json:"usedRequests"` } -// Schema for the `AgentDiscoveryPath` type. +// Canonical directory where custom agents can be discovered or created, with scope, +// preference, and optional project path. // Experimental: AgentDiscoveryPath is part of an experimental API and may change or be // removed. type AgentDiscoveryPath struct { @@ -159,7 +162,8 @@ type AgentGetCurrentResult struct { Agent *AgentInfo `json:"agent,omitempty"` } -// Schema for the `AgentInfo` type. +// Custom agent metadata, including identifiers, display details, source, tools, model, MCP +// servers, skills, and file path. // Experimental: AgentInfo is part of an experimental API and may change or be removed. type AgentInfo struct { // Description of the agent's purpose @@ -429,18 +433,22 @@ type AgentsGetDiscoveryPathsRequest struct { // Experimental: AllowAllPermissionSetResult is part of an experimental API and may change // or be removed. type AllowAllPermissionSetResult struct { - // Authoritative allow-all state after the mutation + // Authoritative full allow-all state after the mutation Enabled bool `json:"enabled"` + // Authoritative allow-all mode after the mutation + Mode *PermissionsAllowAllMode `json:"mode,omitempty"` // Whether the operation succeeded Success bool `json:"success"` } -// Current full allow-all permission state. +// Current allow-all permission mode. // Experimental: AllowAllPermissionState is part of an experimental API and may change or be // removed. type AllowAllPermissionState struct { // Whether full allow-all permissions are currently active Enabled bool `json:"enabled"` + // Current allow-all mode + Mode *PermissionsAllowAllMode `json:"mode,omitempty"` } // A user message attachment — a file, directory, code selection, blob, GitHub-anchored @@ -852,7 +860,8 @@ func (r RawAuthInfoData) Type() AuthInfoType { return r.Discriminator } -// Schema for the `ApiKeyAuthInfo` type. +// Authentication-info variant for API-key authentication to a non-GitHub LLM provider, +// carrying the secret `apiKey` and host. // Experimental: APIKeyAuthInfo is part of an experimental API and may change or be removed. type APIKeyAuthInfo struct { // The API key. Treat as a secret. @@ -870,7 +879,8 @@ func (APIKeyAuthInfo) Type() AuthInfoType { return AuthInfoTypeAPIKey } -// Schema for the `CopilotApiTokenAuthInfo` type. +// Authentication-info variant for direct Copilot API token auth sourced from environment +// variables, with public GitHub host. // Experimental: CopilotAPITokenAuthInfo is part of an experimental API and may change or be // removed. type CopilotAPITokenAuthInfo struct { @@ -887,7 +897,8 @@ func (CopilotAPITokenAuthInfo) Type() AuthInfoType { return AuthInfoTypeCopilotAPIToken } -// Schema for the `EnvAuthInfo` type. +// Authentication-info variant for a token sourced from an environment variable, with host, +// optional login, token, and env var name. // Experimental: EnvAuthInfo is part of an experimental API and may change or be removed. type EnvAuthInfo struct { // Snapshot of the authenticated user's Copilot subscription info, if known. Mirrors the @@ -910,7 +921,8 @@ func (EnvAuthInfo) Type() AuthInfoType { return AuthInfoTypeEnv } -// Schema for the `GhCliAuthInfo` type. +// Authentication-info variant for GitHub CLI credentials, carrying host, login, and the `gh +// auth token` value. // Experimental: GhCLIAuthInfo is part of an experimental API and may change or be removed. type GhCLIAuthInfo struct { // Snapshot of the authenticated user's Copilot subscription info, if known. Mirrors the @@ -930,7 +942,8 @@ func (GhCLIAuthInfo) Type() AuthInfoType { return AuthInfoTypeGhCLI } -// Schema for the `HMACAuthInfo` type. +// Authentication-info variant for GitHub-internal HMAC auth, carrying the public GitHub +// host and HMAC secret. // Experimental: HMACAuthInfo is part of an experimental API and may change or be removed. type HMACAuthInfo struct { // Snapshot of the authenticated user's Copilot subscription info, if known. Mirrors the @@ -948,7 +961,8 @@ func (HMACAuthInfo) Type() AuthInfoType { return AuthInfoTypeHMAC } -// Schema for the `TokenAuthInfo` type. +// Authentication-info variant for SDK-configured token authentication, carrying host and +// the secret token value. // Experimental: TokenAuthInfo is part of an experimental API and may change or be removed. type TokenAuthInfo struct { // Snapshot of the authenticated user's Copilot subscription info, if known. Mirrors the @@ -966,7 +980,8 @@ func (TokenAuthInfo) Type() AuthInfoType { return AuthInfoTypeToken } -// Schema for the `UserAuthInfo` type. +// Authentication-info variant for OAuth user auth, with host and login; the token remains +// in the runtime secret store. // Experimental: UserAuthInfo is part of an experimental API and may change or be removed. type UserAuthInfo struct { // Snapshot of the authenticated user's Copilot subscription info, if known. Mirrors the @@ -1341,10 +1356,19 @@ type ConnectRemoteSessionParams struct { SessionID string `json:"sessionId"` } -// Optional connection token presented by the SDK client during the handshake. +// Parameters for the `server.connect` handshake: an optional connection token and optional +// connection-level opt-ins (e.g. GitHub telemetry forwarding). // Experimental: ConnectRequest is part of an experimental API and may change or be removed. // Internal: ConnectRequest is an internal SDK API and is not part of the public surface. type ConnectRequest struct { + // Opt this connection in to GitHub telemetry forwarding for its lifetime. When set, the + // runtime forwards every internal telemetry event it emits — across all sessions, plus + // sessionless events — to this connection over the `gitHubTelemetry.event` notification, in + // addition to the runtime's normal GitHub/CTS emission (dual-write). Intended for + // first-party hosts that re-emit the events into their own telemetry stores. Both + // unrestricted and restricted events are forwarded, each tagged with a `restricted` + // discriminator; a backstop drops restricted events when restricted telemetry is disabled. + EnableGitHubTelemetryForwarding *bool `json:"enableGitHubTelemetryForwarding,omitempty"` // Connection token; required when the server was started with COPILOT_CONNECTION_TOKEN Token *string `json:"token,omitempty"` } @@ -1361,43 +1385,86 @@ type ConnectResult struct { Version string `json:"version"` } +// A single large message currently in context. +// Experimental: ContextHeaviestMessage is part of an experimental API and may change or be +// removed. +type ContextHeaviestMessage struct { + // Stable identifier for this message within the snapshot. + ID string `json:"id"` + // Human-readable source label, e.g. `tool: bash` or `skill: tmux`. Presentation-only. + Label string `json:"label"` + // Role of the chat message (`user`, `assistant`, or `tool`). + Role string `json:"role"` + // Token count currently in context for this individual message. + Tokens int64 `json:"tokens"` +} + // Snapshot of the authenticated user's Copilot subscription info, if known. Mirrors the // GitHub API `/copilot_internal/v2/token` user response shape — the runtime trusts this // verbatim and does not re-fetch when set. // Experimental: CopilotUserResponse is part of an experimental API and may change or be // removed. type CopilotUserResponse struct { - AccessTypeSku *string `json:"access_type_sku,omitempty"` - AnalyticsTrackingID *string `json:"analytics_tracking_id,omitempty"` - AssignedDate *string `json:"assigned_date,omitempty"` - CanSignupForLimited *bool `json:"can_signup_for_limited,omitempty"` - CanUpgradePlan *bool `json:"can_upgrade_plan,omitempty"` - ChatEnabled *bool `json:"chat_enabled,omitempty"` - CLIRemoteControlEnabled *bool `json:"cli_remote_control_enabled,omitempty"` - CloudSessionStorageEnabled *bool `json:"cloud_session_storage_enabled,omitempty"` - CodexAgentEnabled *bool `json:"codex_agent_enabled,omitempty"` - CopilotignoreEnabled *bool `json:"copilotignore_enabled,omitempty"` - CopilotPlan *string `json:"copilot_plan,omitempty"` - // Schema for the `CopilotUserResponseEndpoints` type. - Endpoints *CopilotUserResponseEndpoints `json:"endpoints,omitempty"` - IsMCPEnabled *bool `json:"is_mcp_enabled,omitempty"` - IsStaff *bool `json:"is_staff,omitempty"` - LimitedUserQuotas map[string]float64 `json:"limited_user_quotas,omitzero"` - LimitedUserResetDate *string `json:"limited_user_reset_date,omitempty"` - Login *string `json:"login,omitempty"` - MonthlyQuotas map[string]float64 `json:"monthly_quotas,omitzero"` - OrganizationList []CopilotUserResponseOrganizationListItem `json:"organization_list,omitzero"` - OrganizationLoginList []string `json:"organization_login_list,omitzero"` - QuotaResetDate *string `json:"quota_reset_date,omitempty"` - QuotaResetDateUTC *string `json:"quota_reset_date_utc,omitempty"` - // Schema for the `CopilotUserResponseQuotaSnapshots` type. - QuotaSnapshots *CopilotUserResponseQuotaSnapshots `json:"quota_snapshots,omitempty"` - RestrictedTelemetry *bool `json:"restricted_telemetry,omitempty"` - Te *bool `json:"te,omitempty"` - TokenBasedBilling *bool `json:"token_based_billing,omitempty"` -} - -// Schema for the `CopilotUserResponseEndpoints` type. + // Copilot access SKU identifier (e.g. `free_limited_copilot`, + // `copilot_for_business_seat_quota`) used to gate model and feature access. + AccessTypeSku *string `json:"access_type_sku,omitempty"` + // Opaque analytics tracking identifier for the user, forwarded from the Copilot API. + AnalyticsTrackingID *string `json:"analytics_tracking_id,omitempty"` + // Date the Copilot seat was assigned to the user, if applicable. + AssignedDate *string `json:"assigned_date,omitempty"` + // Whether the user is eligible to sign up for the free/limited Copilot tier. + CanSignupForLimited *bool `json:"can_signup_for_limited,omitempty"` + // Whether the user is able to upgrade their Copilot plan. + CanUpgradePlan *bool `json:"can_upgrade_plan,omitempty"` + // Whether Copilot chat is enabled for the user. + ChatEnabled *bool `json:"chat_enabled,omitempty"` + // Whether CLI remote control is enabled for the user. + CLIRemoteControlEnabled *bool `json:"cli_remote_control_enabled,omitempty"` + // Whether cloud session storage is enabled for the user. + CloudSessionStorageEnabled *bool `json:"cloud_session_storage_enabled,omitempty"` + // Whether the Codex agent is enabled for the user. + CodexAgentEnabled *bool `json:"codex_agent_enabled,omitempty"` + // Whether `.copilotignore` content-exclusion support is enabled for the user. + CopilotignoreEnabled *bool `json:"copilotignore_enabled,omitempty"` + // Copilot plan name for the user (e.g. `individual`, `business`, `enterprise`). + CopilotPlan *string `json:"copilot_plan,omitempty"` + // Endpoint URLs from the raw Copilot `/copilot_internal/v2/token` user-response passthrough. + Endpoints *CopilotUserResponseEndpoints `json:"endpoints,omitempty"` + // Whether MCP (Model Context Protocol) support is enabled for the user. + IsMCPEnabled *bool `json:"is_mcp_enabled,omitempty"` + // Whether the user is a GitHub/Microsoft staff member. + IsStaff *bool `json:"is_staff,omitempty"` + // Per-category quota allotments for free/limited-tier users, keyed by quota category. + LimitedUserQuotas map[string]float64 `json:"limited_user_quotas,omitzero"` + // Date the free/limited-tier user's quotas next reset, as a raw string from the Copilot API. + LimitedUserResetDate *string `json:"limited_user_reset_date,omitempty"` + // GitHub login of the authenticated user. + Login *string `json:"login,omitempty"` + // Per-category monthly quota allotments, keyed by quota category. + MonthlyQuotas map[string]float64 `json:"monthly_quotas,omitzero"` + // Organizations the user belongs to, each with an optional login and display name. + OrganizationList []CopilotUserResponseOrganizationListItem `json:"organization_list,omitzero"` + // Logins of the organizations the user belongs to. + OrganizationLoginList []string `json:"organization_login_list,omitzero"` + // Date the user's usage quota next resets, as a raw string from the Copilot API; see + // `quota_reset_date_utc` for the UTC-normalized value. + QuotaResetDate *string `json:"quota_reset_date,omitempty"` + // UTC-normalized form of `quota_reset_date` (the date the user's usage quota next resets). + QuotaResetDateUTC *string `json:"quota_reset_date_utc,omitempty"` + // Quota snapshot map from the raw Copilot user-response passthrough, with chat, + // completions, premium-interactions, and other entries. + QuotaSnapshots *CopilotUserResponseQuotaSnapshots `json:"quota_snapshots,omitempty"` + // Whether the user's telemetry is subject to restricted-data handling. + RestrictedTelemetry *bool `json:"restricted_telemetry,omitempty"` + // Raw passthrough of the Copilot API `te` flag for the user (an opaque server-side + // eligibility signal surfaced in telemetry); not otherwise interpreted by the runtime. + Te *bool `json:"te,omitempty"` + // Whether the account is on usage-based (token/AI-credit) billing rather than a fixed + // premium-request quota. + TokenBasedBilling *bool `json:"token_based_billing,omitempty"` +} + +// Endpoint URLs from the raw Copilot `/copilot_internal/v2/token` user-response passthrough. // Experimental: CopilotUserResponseEndpoints is part of an experimental API and may change // or be removed. type CopilotUserResponseEndpoints struct { @@ -1412,70 +1479,122 @@ type CopilotUserResponseOrganizationListItem struct { Name *string `json:"name,omitempty"` } -// Schema for the `CopilotUserResponseQuotaSnapshots` type. +// Quota snapshot map from the raw Copilot user-response passthrough, with chat, +// completions, premium-interactions, and other entries. // Experimental: CopilotUserResponseQuotaSnapshots is part of an experimental API and may // change or be removed. type CopilotUserResponseQuotaSnapshots struct { - // Schema for the `CopilotUserResponseQuotaSnapshotsChat` type. + // Chat quota snapshot from the raw Copilot user-response passthrough, with entitlement, + // overage, remaining quota, reset, and billing fields. Chat *CopilotUserResponseQuotaSnapshotsChat `json:"chat,omitempty"` - // Schema for the `CopilotUserResponseQuotaSnapshotsCompletions` type. + // Completions quota snapshot from the raw Copilot user-response passthrough, with + // entitlement, overage, remaining quota, reset, and billing fields. Completions *CopilotUserResponseQuotaSnapshotsCompletions `json:"completions,omitempty"` - // Schema for the `CopilotUserResponseQuotaSnapshotsPremiumInteractions` type. + // Premium-interactions quota snapshot from the raw Copilot user-response passthrough, with + // entitlement, overage, remaining quota, reset, and billing fields. PremiumInteractions *CopilotUserResponseQuotaSnapshotsPremiumInteractions `json:"premium_interactions,omitempty"` } -// Schema for the `CopilotUserResponseQuotaSnapshotsChat` type. +// Chat quota snapshot from the raw Copilot user-response passthrough, with entitlement, +// overage, remaining quota, reset, and billing fields. // Experimental: CopilotUserResponseQuotaSnapshotsChat is part of an experimental API and // may change or be removed. type CopilotUserResponseQuotaSnapshotsChat struct { - Entitlement *float64 `json:"entitlement,omitempty"` - HasQuota *bool `json:"has_quota,omitempty"` - OverageCount *float64 `json:"overage_count,omitempty"` - OveragePermitted *bool `json:"overage_permitted,omitempty"` - PercentRemaining *float64 `json:"percent_remaining,omitempty"` - QuotaID *string `json:"quota_id,omitempty"` - QuotaRemaining *float64 `json:"quota_remaining,omitempty"` - QuotaResetAt *float64 `json:"quota_reset_at,omitempty"` - Remaining *float64 `json:"remaining,omitempty"` - TimestampUTC *string `json:"timestamp_utc,omitempty"` - TokenBasedBilling *bool `json:"token_based_billing,omitempty"` - Unlimited *bool `json:"unlimited,omitempty"` -} - -// Schema for the `CopilotUserResponseQuotaSnapshotsCompletions` type. + // Number of requests/units included in the entitlement for this period; `-1` denotes an + // unlimited entitlement. + Entitlement *float64 `json:"entitlement,omitempty"` + // Whether the user currently has quota available; when `false` and not unlimited, further + // requests are blocked until the quota resets. + HasQuota *bool `json:"has_quota,omitempty"` + // Count of additional pay-per-request usage consumed this period beyond the entitlement. + OverageCount *float64 `json:"overage_count,omitempty"` + // Whether usage may continue at pay-per-request rates once the entitlement is exhausted. + OveragePermitted *bool `json:"overage_permitted,omitempty"` + // Percentage of the entitlement remaining at the snapshot timestamp. + PercentRemaining *float64 `json:"percent_remaining,omitempty"` + // Identifier of the quota bucket this snapshot describes. + QuotaID *string `json:"quota_id,omitempty"` + // Amount of quota remaining at the snapshot timestamp. + QuotaRemaining *float64 `json:"quota_remaining,omitempty"` + // Unix epoch time, in seconds, when this quota next resets. + QuotaResetAt *float64 `json:"quota_reset_at,omitempty"` + // Remaining entitlement/quota amount at the snapshot timestamp. + Remaining *float64 `json:"remaining,omitempty"` + // UTC timestamp when this snapshot was captured. + TimestampUTC *string `json:"timestamp_utc,omitempty"` + // Whether this category uses usage-based (token/AI-credit) billing rather than a fixed + // premium-request count. + TokenBasedBilling *bool `json:"token_based_billing,omitempty"` + // Whether the entitlement for this category is unlimited. + Unlimited *bool `json:"unlimited,omitempty"` +} + +// Completions quota snapshot from the raw Copilot user-response passthrough, with +// entitlement, overage, remaining quota, reset, and billing fields. // Experimental: CopilotUserResponseQuotaSnapshotsCompletions is part of an experimental API // and may change or be removed. type CopilotUserResponseQuotaSnapshotsCompletions struct { - Entitlement *float64 `json:"entitlement,omitempty"` - HasQuota *bool `json:"has_quota,omitempty"` - OverageCount *float64 `json:"overage_count,omitempty"` - OveragePermitted *bool `json:"overage_permitted,omitempty"` - PercentRemaining *float64 `json:"percent_remaining,omitempty"` - QuotaID *string `json:"quota_id,omitempty"` - QuotaRemaining *float64 `json:"quota_remaining,omitempty"` - QuotaResetAt *float64 `json:"quota_reset_at,omitempty"` - Remaining *float64 `json:"remaining,omitempty"` - TimestampUTC *string `json:"timestamp_utc,omitempty"` - TokenBasedBilling *bool `json:"token_based_billing,omitempty"` - Unlimited *bool `json:"unlimited,omitempty"` -} - -// Schema for the `CopilotUserResponseQuotaSnapshotsPremiumInteractions` type. + // Number of requests/units included in the entitlement for this period; `-1` denotes an + // unlimited entitlement. + Entitlement *float64 `json:"entitlement,omitempty"` + // Whether the user currently has quota available; when `false` and not unlimited, further + // requests are blocked until the quota resets. + HasQuota *bool `json:"has_quota,omitempty"` + // Count of additional pay-per-request usage consumed this period beyond the entitlement. + OverageCount *float64 `json:"overage_count,omitempty"` + // Whether usage may continue at pay-per-request rates once the entitlement is exhausted. + OveragePermitted *bool `json:"overage_permitted,omitempty"` + // Percentage of the entitlement remaining at the snapshot timestamp. + PercentRemaining *float64 `json:"percent_remaining,omitempty"` + // Identifier of the quota bucket this snapshot describes. + QuotaID *string `json:"quota_id,omitempty"` + // Amount of quota remaining at the snapshot timestamp. + QuotaRemaining *float64 `json:"quota_remaining,omitempty"` + // Unix epoch time, in seconds, when this quota next resets. + QuotaResetAt *float64 `json:"quota_reset_at,omitempty"` + // Remaining entitlement/quota amount at the snapshot timestamp. + Remaining *float64 `json:"remaining,omitempty"` + // UTC timestamp when this snapshot was captured. + TimestampUTC *string `json:"timestamp_utc,omitempty"` + // Whether this category uses usage-based (token/AI-credit) billing rather than a fixed + // premium-request count. + TokenBasedBilling *bool `json:"token_based_billing,omitempty"` + // Whether the entitlement for this category is unlimited. + Unlimited *bool `json:"unlimited,omitempty"` +} + +// Premium-interactions quota snapshot from the raw Copilot user-response passthrough, with +// entitlement, overage, remaining quota, reset, and billing fields. // Experimental: CopilotUserResponseQuotaSnapshotsPremiumInteractions is part of an // experimental API and may change or be removed. type CopilotUserResponseQuotaSnapshotsPremiumInteractions struct { - Entitlement *float64 `json:"entitlement,omitempty"` - HasQuota *bool `json:"has_quota,omitempty"` - OverageCount *float64 `json:"overage_count,omitempty"` - OveragePermitted *bool `json:"overage_permitted,omitempty"` - PercentRemaining *float64 `json:"percent_remaining,omitempty"` - QuotaID *string `json:"quota_id,omitempty"` - QuotaRemaining *float64 `json:"quota_remaining,omitempty"` - QuotaResetAt *float64 `json:"quota_reset_at,omitempty"` - Remaining *float64 `json:"remaining,omitempty"` - TimestampUTC *string `json:"timestamp_utc,omitempty"` - TokenBasedBilling *bool `json:"token_based_billing,omitempty"` - Unlimited *bool `json:"unlimited,omitempty"` + // Number of requests/units included in the entitlement for this period; `-1` denotes an + // unlimited entitlement. + Entitlement *float64 `json:"entitlement,omitempty"` + // Whether the user currently has quota available; when `false` and not unlimited, further + // requests are blocked until the quota resets. + HasQuota *bool `json:"has_quota,omitempty"` + // Count of additional pay-per-request usage consumed this period beyond the entitlement. + OverageCount *float64 `json:"overage_count,omitempty"` + // Whether usage may continue at pay-per-request rates once the entitlement is exhausted. + OveragePermitted *bool `json:"overage_permitted,omitempty"` + // Percentage of the entitlement remaining at the snapshot timestamp. + PercentRemaining *float64 `json:"percent_remaining,omitempty"` + // Identifier of the quota bucket this snapshot describes. + QuotaID *string `json:"quota_id,omitempty"` + // Amount of quota remaining at the snapshot timestamp. + QuotaRemaining *float64 `json:"quota_remaining,omitempty"` + // Unix epoch time, in seconds, when this quota next resets. + QuotaResetAt *float64 `json:"quota_reset_at,omitempty"` + // Remaining entitlement/quota amount at the snapshot timestamp. + Remaining *float64 `json:"remaining,omitempty"` + // UTC timestamp when this snapshot was captured. + TimestampUTC *string `json:"timestamp_utc,omitempty"` + // Whether this category uses usage-based (token/AI-credit) billing rather than a fixed + // premium-request count. + TokenBasedBilling *bool `json:"token_based_billing,omitempty"` + // Whether the entitlement for this category is unlimited. + Unlimited *bool `json:"unlimited,omitempty"` } // The currently selected model, reasoning effort, and context tier for the session. The @@ -1513,6 +1632,142 @@ type CurrentToolMetadata struct { NamespacedName *string `json:"namespacedName,omitempty"` } +// A file included in the redacted debug bundle. +// Experimental: DebugCollectLogsCollectedEntry is part of an experimental API and may +// change or be removed. +type DebugCollectLogsCollectedEntry struct { + // Relative path of the file in the staged bundle/archive. + BundlePath string `json:"bundlePath"` + // Redacted output size in bytes. + SizeBytes int64 `json:"sizeBytes"` + // Source category for this entry. + Source DebugCollectLogsSource `json:"source"` +} + +// Destination for the redacted debug bundle. +// Experimental: DebugCollectLogsDestination is part of an experimental API and may change +// or be removed. +type DebugCollectLogsDestination interface { + debugCollectLogsDestination() + Kind() DebugCollectLogsDestinationKind +} + +type RawDebugCollectLogsDestinationData struct { + Discriminator DebugCollectLogsDestinationKind + Raw json.RawMessage +} + +func (RawDebugCollectLogsDestinationData) debugCollectLogsDestination() {} +func (r RawDebugCollectLogsDestinationData) Kind() DebugCollectLogsDestinationKind { + return r.Discriminator +} + +type DebugCollectLogsDestinationArchive struct { + // When true, create the archive atomically without overwriting an existing file by + // appending ` (N)` before the extension as needed. Defaults to false. + NoOverwrite *bool `json:"noOverwrite,omitempty"` + // Absolute or server-relative path for the .tgz archive to create. + OutputPath string `json:"outputPath"` +} + +func (DebugCollectLogsDestinationArchive) debugCollectLogsDestination() {} +func (DebugCollectLogsDestinationArchive) Kind() DebugCollectLogsDestinationKind { + return DebugCollectLogsDestinationKindArchive +} + +type DebugCollectLogsDestinationDirectory struct { + // Directory where redacted files should be staged. The directory is created if needed. + OutputDirectory string `json:"outputDirectory"` +} + +func (DebugCollectLogsDestinationDirectory) debugCollectLogsDestination() {} +func (DebugCollectLogsDestinationDirectory) Kind() DebugCollectLogsDestinationKind { + return DebugCollectLogsDestinationKindDirectory +} + +// A caller-provided server-local file or directory to include in the debug bundle. +// Experimental: DebugCollectLogsEntry is part of an experimental API and may change or be +// removed. +type DebugCollectLogsEntry struct { + // Relative path to use inside the staged bundle/archive. + BundlePath string `json:"bundlePath"` + // Kind of source path to include. + Kind DebugCollectLogsEntryKind `json:"kind"` + // Server-local source path to read. + Path string `json:"path"` + // How text content from this entry should be redacted. Defaults to plain-text. + Redaction *DebugCollectLogsRedaction `json:"redaction,omitempty"` + // When true, collection fails if this entry cannot be read. Defaults to false, which + // records the entry in `skippedEntries`. + Required *bool `json:"required,omitempty"` +} + +// Built-in session diagnostics to include in the bundle. Omitted fields default to true. +// Experimental: DebugCollectLogsInclude is part of an experimental API and may change or be +// removed. +type DebugCollectLogsInclude struct { + // Server-local path to the current process log. When set, it is included as `process.log` + // and its directory is searched for prior logs from the same session. + CurrentProcessLogPath *string `json:"currentProcessLogPath,omitempty"` + // Include the session event log (`events.jsonl`). Defaults to true. + Events *bool `json:"events,omitempty"` + // Server-local path to the session's events.jsonl file. Internal callers normally omit this + // and let the runtime derive it from the session. + EventsPath *string `json:"eventsPath,omitempty"` + // Maximum number of previous process logs to include. Defaults to 5. + PreviousProcessLogLimit *int64 `json:"previousProcessLogLimit,omitempty"` + // Server-local process log directory to search when `currentProcessLogPath` is unavailable, + // useful for collecting logs for inactive sessions. + ProcessLogDirectory *string `json:"processLogDirectory,omitempty"` + // Include process logs for the session. Defaults to true. + ProcessLogs *bool `json:"processLogs,omitempty"` + // Include interactive shell logs written under the session's `shell-logs` directory. + // Defaults to true. + ShellLogs *bool `json:"shellLogs,omitempty"` +} + +// Options for collecting a redacted session debug bundle. +// Experimental: DebugCollectLogsRequest is part of an experimental API and may change or be +// removed. +type DebugCollectLogsRequest struct { + // Caller-provided server-local files or directories to include in addition to the runtime's + // built-in session diagnostics. This lets host applications add their own diagnostics + // without changing the API shape. + AdditionalEntries []DebugCollectLogsEntry `json:"additionalEntries,omitzero"` + // Where the redacted bundle should be written. Use `archive` to produce a .tgz, or + // `directory` to stage redacted files for caller-managed upload/post-processing. + Destination DebugCollectLogsDestination `json:"destination"` + // Which built-in session diagnostics to include. Omitted fields default to true. + Include *DebugCollectLogsInclude `json:"include,omitempty"` +} + +// Result of collecting a redacted debug bundle. +// Experimental: DebugCollectLogsResult is part of an experimental API and may change or be +// removed. +type DebugCollectLogsResult struct { + // Files included in the redacted bundle. + Entries []DebugCollectLogsCollectedEntry `json:"entries"` + // Destination kind that was written. + Kind DebugCollectLogsResultKind `json:"kind"` + // Actual archive path or staging directory path written. This may differ from the requested + // path when no-overwrite suffixing or fallback-to-temp-directory was needed. + Path string `json:"path"` + // Optional files or directories that could not be included. + SkippedEntries []DebugCollectLogsSkippedEntry `json:"skippedEntries,omitzero"` +} + +// An optional debug bundle entry that could not be included. +// Experimental: DebugCollectLogsSkippedEntry is part of an experimental API and may change +// or be removed. +type DebugCollectLogsSkippedEntry struct { + // Relative path requested for this bundle entry. + BundlePath string `json:"bundlePath"` + // Server-local source path that could not be read. + Path *string `json:"path,omitempty"` + // Reason the entry was skipped. + Reason string `json:"reason"` +} + // Canvas available in the current session. // Experimental: DiscoveredCanvas is part of an experimental API and may change or be // removed. @@ -1533,7 +1788,8 @@ type DiscoveredCanvas struct { InputSchema any `json:"inputSchema,omitempty"` } -// Schema for the `DiscoveredMcpServer` type. +// MCP server discovered by `mcp.discover`, with config source, optional plugin source, +// transport type, and enabled state. // Experimental: DiscoveredMCPServer is part of an experimental API and may change or be // removed. type DiscoveredMCPServer struct { @@ -1663,7 +1919,8 @@ type ExecuteCommandResult struct { Error *string `json:"error,omitempty"` } -// Schema for the `Extension` type. +// Discovered extension metadata, including source-qualified ID, name, discovery source, +// status, and optional process ID. // Experimental: Extension is part of an experimental API and may change or be removed. type Extension struct { // Source-qualified ID (e.g., 'project:my-ext', 'user:auth-helper', @@ -1733,6 +1990,10 @@ type ExternalToolTextResultForLlm struct { SessionLog *string `json:"sessionLog,omitempty"` // Text result returned to the model TextResultForLlm string `json:"textResultForLlm"` + // Tool references returned by a tool-search override: names of deferred tools to surface to + // the model. When set, the tool result is materialized as `tool_reference` content blocks + // (rather than plain text) so the model knows which deferred tools are now available. + ToolReferences []string `json:"toolReferences,omitzero"` // Optional tool-specific telemetry ToolTelemetry map[string]any `json:"toolTelemetry,omitzero"` } @@ -1907,7 +2168,8 @@ type RawExternalToolTextResultForLlmContentResourceDetailsData struct { func (RawExternalToolTextResultForLlmContentResourceDetailsData) externalToolTextResultForLlmContentResourceDetails() { } -// Schema for the `EmbeddedBlobResourceContents` type. +// Embedded binary resource contents identified by a URI, with an optional MIME type and a +// base64-encoded blob. // Experimental: EmbeddedBlobResourceContents is part of an experimental API and may change // or be removed. type EmbeddedBlobResourceContents struct { @@ -1921,7 +2183,8 @@ type EmbeddedBlobResourceContents struct { func (EmbeddedBlobResourceContents) externalToolTextResultForLlmContentResourceDetails() {} -// Schema for the `EmbeddedTextResourceContents` type. +// Embedded text resource contents identified by a URI, with an optional MIME type and a +// text payload. // Experimental: EmbeddedTextResourceContents is part of an experimental API and may change // or be removed. type EmbeddedTextResourceContents struct { @@ -2074,8 +2337,8 @@ type GitHubTelemetryEventResult struct { } // Payload for a `gitHubTelemetry.event` notification: a single GitHub telemetry event the -// runtime forwards to a host connection that opted into telemetry forwarding for the -// session. +// runtime forwards to a host connection that opted into telemetry forwarding during the +// `server.connect` handshake. // Experimental: GitHubTelemetryNotification is part of an experimental API and may change // or be removed. type GitHubTelemetryNotification struct { @@ -2084,8 +2347,10 @@ type GitHubTelemetryNotification struct { // Whether this is a restricted telemetry event (cli.restricted_telemetry). Hosts must route // restricted events to first-party Microsoft stores only. Restricted bool `json:"restricted"` - // Session the telemetry event belongs to. - SessionID string `json:"sessionId"` + // Session the telemetry event belongs to, when it is session-scoped. Omitted for + // sessionless events (for example, `server.sendTelemetry` calls with no session id), which + // are still forwarded to opted-in connections. + SessionID *string `json:"sessionId,omitempty"` } // Pending external tool call request ID, with the tool result or an error describing why it @@ -2196,7 +2461,8 @@ type HistoryTruncateResult struct { EventsRemoved int64 `json:"eventsRemoved"` } -// Schema for the `InstalledPlugin` type. +// Installed plugin record from global state, with marketplace, version, install time, +// enabled state, cache path, and source. // Experimental: InstalledPlugin is part of an experimental API and may change or be removed. type InstalledPlugin struct { // Path where the plugin is cached locally @@ -2244,7 +2510,8 @@ type InstalledPluginSource struct { String *string } -// Schema for the `InstalledPluginSourceGitHub` type. +// Source descriptor for a direct GitHub plugin install, with `owner/repo`, optional ref, +// and optional subpath. // Experimental: InstalledPluginSourceGitHub is part of an experimental API and may change // or be removed. type InstalledPluginSourceGitHub struct { @@ -2255,7 +2522,7 @@ type InstalledPluginSourceGitHub struct { Source InstalledPluginSourceGitHubSource `json:"source"` } -// Schema for the `InstalledPluginSourceLocal` type. +// Source descriptor for a direct local plugin install, with a local filesystem path. // Experimental: InstalledPluginSourceLocal is part of an experimental API and may change or // be removed. type InstalledPluginSourceLocal struct { @@ -2264,7 +2531,8 @@ type InstalledPluginSourceLocal struct { Source InstalledPluginSourceLocalSource `json:"source"` } -// Schema for the `InstalledPluginSourceUrl` type. +// Source descriptor for a direct URL plugin install, with URL, optional ref, and optional +// subpath. // Experimental: InstalledPluginSourceURL is part of an experimental API and may change or // be removed. type InstalledPluginSourceURL struct { @@ -2275,7 +2543,8 @@ type InstalledPluginSourceURL struct { URL string `json:"url"` } -// Schema for the `InstructionDiscoveryPath` type. +// Canonical file or directory where custom instructions can be discovered or created, with +// location, kind, preference, and project path. // Experimental: InstructionDiscoveryPath is part of an experimental API and may change or // be removed. type InstructionDiscoveryPath struct { @@ -2334,7 +2603,8 @@ type InstructionsGetSourcesResult struct { Sources []InstructionSource `json:"sources"` } -// Schema for the `InstructionSource` type. +// Loaded instruction source for a session, including path, content, category, location, +// applicability, and optional description. // Experimental: InstructionSource is part of an experimental API and may change or be // removed. type InstructionSource struct { @@ -2402,9 +2672,35 @@ type LlmInferenceHTTPRequestChunkResult struct { // Experimental: LlmInferenceHTTPRequestStartRequest is part of an experimental API and may // change or be removed. type LlmInferenceHTTPRequestStartRequest struct { + // Stable per-agent-instance id attributing this request to a specific agent trajectory. + // Present when the request originates from an agent turn; absent for requests issued + // outside any agent context (e.g. some SDK callers). A request with an `agentId` but no + // `parentAgentId` is a root-agent request; one carrying both is a subagent request. Sourced + // from the runtime's per-request agent context and surfaced on the envelope independently + // of transport, so it is available for both first-party (CAPI) and BYOK/custom-provider + // requests; on the CAPI transport the runtime derives the upstream `X-Agent-Task-Id` header + // from this same context. Consumers routing each provider call to a training trajectory + // should key on this rather than on lifecycle events, since it is available on the request + // path before sampling. + AgentID *string `json:"agentId,omitempty"` Headers map[string][]string `json:"headers"` + // Coarse classification of the interaction that produced this request. Open string for + // forward-compatibility; known values include `conversation-agent`, + // `conversation-subagent`, `conversation-sampling`, `conversation-background`, + // `conversation-compaction`, and `conversation-user`. Absent when the runtime did not + // classify the request. Comes from the runtime's per-request agent context independently of + // transport; on the CAPI transport the runtime derives the upstream `X-Interaction-Type` + // header from this same context. + InteractionType *string `json:"interactionType,omitempty"` // HTTP method, e.g. GET, POST. Method string `json:"method"` + // Id of the parent agent that spawned the agent issuing this request. Present only for + // subagent requests; absent for root-agent requests and non-agent requests. Combined with + // `agentId`, this lets consumers attribute a call to a child trajectory versus the root. + // Like `agentId`, it comes from the runtime's per-request agent context independently of + // transport; on the CAPI transport the runtime derives the upstream `X-Parent-Agent-Id` + // header from this same context. + ParentAgentID *string `json:"parentAgentId,omitempty"` // Opaque runtime-minted id, unique per in-flight request. The SDK uses this to correlate // httpRequestChunk frames and to address its httpResponseStart / httpResponseChunk replies // back to the runtime. @@ -2500,7 +2796,8 @@ type LlmInferenceSetProviderResult struct { Success bool `json:"success"` } -// Schema for the `LocalSessionMetadataValue` type. +// Persisted local session metadata, including identifiers, timestamps, summary/name, +// client, context, detached state, and task ID. // Experimental: LocalSessionMetadataValue is part of an experimental API and may change or // be removed. type LocalSessionMetadataValue struct { @@ -2616,7 +2913,8 @@ type MarketplacePluginInfo struct { Name string `json:"name"` } -// Schema for the `MarketplaceRefreshEntry` type. +// Per-marketplace refresh result, including marketplace name, success flag, and optional +// failure error. // Experimental: MarketplaceRefreshEntry is part of an experimental API and may change or be // removed. type MarketplaceRefreshEntry struct { @@ -2647,7 +2945,7 @@ type MarketplaceRemoveResult struct { Removed bool `json:"removed"` } -// Schema for the `McpAllowedServer` type. +// MCP server allowed by policy, with server name and optional PII-free explanatory note. // Experimental: MCPAllowedServer is part of an experimental API and may change or be // removed. type MCPAllowedServer struct { @@ -2783,7 +3081,8 @@ type MCPAppsReadResourceResult struct { Contents []MCPAppsResourceContent `json:"contents"` } -// Schema for the `McpAppsResourceContent` type. +// MCP Apps resource content with URI, optional MIME type, text or base64 blob, and resource +// metadata. // Experimental: MCPAppsResourceContent is part of an experimental API and may change or be // removed. type MCPAppsResourceContent struct { @@ -3017,7 +3316,8 @@ type MCPExecuteSamplingRequest struct { type MCPExecuteSamplingResult struct { } -// Schema for the `McpFilteredServer` type. +// MCP server filtered by policy, with name, reason, optional redacted reason, and +// enterprise login. // Experimental: MCPFilteredServer is part of an experimental API and may change or be // removed. type MCPFilteredServer struct { @@ -3250,25 +3550,6 @@ func (MCPOauthPendingRequestResponseToken) Kind() MCPOauthPendingRequestResponse return MCPOauthPendingRequestResponseKindToken } -// MCP OAuth request id and optional provider response. -// Experimental: MCPOauthRespondRequest is part of an experimental API and may change or be -// removed. -type MCPOauthRespondRequest struct { - // In-process OAuthClientProvider instance, or omitted to deny. Marked internal: cannot be - // serialized across the JSON-RPC boundary. - // Internal: Provider is part of the SDK's internal API surface and is not intended for - // external use. - Provider any `json:"provider,omitempty"` - // OAuth request identifier from mcp.oauth_required - RequestID string `json:"requestId"` -} - -// Empty result after recording the MCP OAuth response. -// Experimental: MCPOauthRespondResult is part of an experimental API and may change or be -// removed. -type MCPOauthRespondResult struct { -} - // Registration parameters for an external MCP client. // Experimental: MCPRegisterExternalClientRequest is part of an experimental API and may // change or be removed. @@ -3343,7 +3624,7 @@ type MCPSamplingExecutionResult struct { Result *MCPExecuteSamplingResult `json:"result,omitempty"` } -// Schema for the `McpServer` type. +// MCP server status entry, including config source/plugin source and any connection error. // Experimental: MCPServer is part of an experimental API and may change or be removed. type MCPServer struct { // Error message if the server failed to connect @@ -3541,7 +3822,7 @@ type MCPStopServerRequest struct { ServerName string `json:"serverName"` } -// Schema for the `McpTools` type. +// MCP tool metadata with tool name and optional description. // Experimental: MCPTools is part of an experimental API and may change or be removed. type MCPTools struct { // Tool description, when provided. @@ -3566,6 +3847,35 @@ type MemoryConfiguration struct { Enabled bool `json:"enabled"` } +// Per-source attribution breakdown for the session's current context window, or null if +// uninitialized. +// Experimental: MetadataContextAttributionResult is part of an experimental API and may +// change or be removed. +type MetadataContextAttributionResult struct { + // Per-source context-window attribution, or null if the session has not yet been + // initialized (no system prompt or tool metadata cached). + ContextAttribution *SessionContextAttribution `json:"contextAttribution,omitempty"` +} + +// Parameters for the heaviest-messages query. +// Experimental: MetadataContextHeaviestMessagesRequest is part of an experimental API and +// may change or be removed. +type MetadataContextHeaviestMessagesRequest struct { + // Maximum number of messages to return, most-expensive first. Omit for the server default. + Limit *int64 `json:"limit,omitempty"` +} + +// The heaviest individual messages in the session's context window, most-expensive first. +// Experimental: MetadataContextHeaviestMessagesResult is part of an experimental API and +// may change or be removed. +type MetadataContextHeaviestMessagesResult struct { + // Heaviest messages, most-expensive first. + Messages []ContextHeaviestMessage `json:"messages"` + // Total token count of the current context window, so callers can compute each message's + // share without a second call. + TotalTokens int64 `json:"totalTokens"` +} + // Model identifier and token limits used to compute the context-info breakdown. // Experimental: MetadataContextInfoRequest is part of an experimental API and may change or // be removed. @@ -3692,7 +4002,8 @@ type MetadataSnapshotRemoteMetadataRepository struct { Owner string `json:"owner"` } -// Schema for the `Model` type. +// Copilot model metadata, including identifier, display name, capabilities, policy, +// billing, reasoning efforts, and picker categories. // Experimental: Model is part of an experimental API and may change or be removed. type Model struct { // Billing information @@ -3948,6 +4259,8 @@ type ModelSwitchToRequest struct { ReasoningEffort *string `json:"reasoningEffort,omitempty"` // Reasoning summary mode to request for supported model clients ReasoningSummary *ReasoningSummary `json:"reasoningSummary,omitempty"` + // Output verbosity level to request for supported models + Verbosity *Verbosity `json:"verbosity,omitempty"` } // The model identifier active on the session after the switch. @@ -4056,7 +4369,8 @@ type OpenCanvasInstance struct { URL *string `json:"url,omitempty"` } -// Schema for the `OptionsUpdateAdditionalContentExclusionPolicy` type. +// Content-exclusion policy supplied to `session.options.update`, with rules, last-updated +// data, and scope. // Experimental: OptionsUpdateAdditionalContentExclusionPolicy is part of an experimental // API and may change or be removed. type OptionsUpdateAdditionalContentExclusionPolicy struct { @@ -4066,18 +4380,21 @@ type OptionsUpdateAdditionalContentExclusionPolicy struct { Scope OptionsUpdateAdditionalContentExclusionPolicyScope `json:"scope"` } -// Schema for the `OptionsUpdateAdditionalContentExclusionPolicyRule` type. +// Single content-exclusion rule supplied to `session.options.update`, with paths, match +// conditions, and source. // Experimental: OptionsUpdateAdditionalContentExclusionPolicyRule is part of an // experimental API and may change or be removed. type OptionsUpdateAdditionalContentExclusionPolicyRule struct { IfAnyMatch []string `json:"ifAnyMatch,omitzero"` IfNoneMatch []string `json:"ifNoneMatch,omitzero"` Paths []string `json:"paths"` - // Schema for the `OptionsUpdateAdditionalContentExclusionPolicyRuleSource` type. + // Source descriptor for a `session.options.update` content-exclusion rule, with source name + // and type. Source OptionsUpdateAdditionalContentExclusionPolicyRuleSource `json:"source"` } -// Schema for the `OptionsUpdateAdditionalContentExclusionPolicyRuleSource` type. +// Source descriptor for a `session.options.update` content-exclusion rule, with source name +// and type. // Experimental: OptionsUpdateAdditionalContentExclusionPolicyRuleSource is part of an // experimental API and may change or be removed. type OptionsUpdateAdditionalContentExclusionPolicyRuleSource struct { @@ -4085,7 +4402,8 @@ type OptionsUpdateAdditionalContentExclusionPolicyRuleSource struct { Type string `json:"type"` } -// Schema for the `PendingPermissionRequest` type. +// Pending permission prompt reconstructed from event history, with request ID and +// user-facing prompt details. // Experimental: PendingPermissionRequest is part of an experimental API and may change or // be removed. type PendingPermissionRequest struct { @@ -4125,7 +4443,7 @@ func (r RawPermissionDecisionData) Kind() PermissionDecisionKind { return r.Discriminator } -// Schema for the `PermissionDecisionApproved` type. +// Permission-decision variant indicating the request was approved. // Experimental: PermissionDecisionApproved is part of an experimental API and may change or // be removed. type PermissionDecisionApproved struct { @@ -4136,7 +4454,8 @@ func (PermissionDecisionApproved) Kind() PermissionDecisionKind { return PermissionDecisionKindApproved } -// Schema for the `PermissionDecisionApprovedForLocation` type. +// Permission-decision variant indicating approval was persisted for a project location, +// with approval details and location key. // Experimental: PermissionDecisionApprovedForLocation is part of an experimental API and // may change or be removed. type PermissionDecisionApprovedForLocation struct { @@ -4151,7 +4470,8 @@ func (PermissionDecisionApprovedForLocation) Kind() PermissionDecisionKind { return PermissionDecisionKindApprovedForLocation } -// Schema for the `PermissionDecisionApprovedForSession` type. +// Permission-decision variant indicating approval was remembered for the session, with +// approval details. // Experimental: PermissionDecisionApprovedForSession is part of an experimental API and may // change or be removed. type PermissionDecisionApprovedForSession struct { @@ -4164,7 +4484,8 @@ func (PermissionDecisionApprovedForSession) Kind() PermissionDecisionKind { return PermissionDecisionKindApprovedForSession } -// Schema for the `PermissionDecisionApproveForLocation` type. +// Permission-decision request variant to approve and persist a permission for a project +// location, with approval details and location key. // Experimental: PermissionDecisionApproveForLocation is part of an experimental API and may // change or be removed. type PermissionDecisionApproveForLocation struct { @@ -4179,7 +4500,8 @@ func (PermissionDecisionApproveForLocation) Kind() PermissionDecisionKind { return PermissionDecisionKindApproveForLocation } -// Schema for the `PermissionDecisionApproveForSession` type. +// Permission-decision request variant to approve for the rest of the session, with optional +// tool approval or URL domain. // Experimental: PermissionDecisionApproveForSession is part of an experimental API and may // change or be removed. type PermissionDecisionApproveForSession struct { @@ -4194,7 +4516,7 @@ func (PermissionDecisionApproveForSession) Kind() PermissionDecisionKind { return PermissionDecisionKindApproveForSession } -// Schema for the `PermissionDecisionApproveOnce` type. +// Permission-decision request variant to approve only the current permission request. // Experimental: PermissionDecisionApproveOnce is part of an experimental API and may change // or be removed. type PermissionDecisionApproveOnce struct { @@ -4205,7 +4527,7 @@ func (PermissionDecisionApproveOnce) Kind() PermissionDecisionKind { return PermissionDecisionKindApproveOnce } -// Schema for the `PermissionDecisionApprovePermanently` type. +// Permission-decision request variant to permanently approve a URL domain across sessions. // Experimental: PermissionDecisionApprovePermanently is part of an experimental API and may // change or be removed. type PermissionDecisionApprovePermanently struct { @@ -4218,7 +4540,8 @@ func (PermissionDecisionApprovePermanently) Kind() PermissionDecisionKind { return PermissionDecisionKindApprovePermanently } -// Schema for the `PermissionDecisionCancelled` type. +// Permission-decision variant indicating the request was cancelled before use, with an +// optional reason. // Experimental: PermissionDecisionCancelled is part of an experimental API and may change // or be removed. type PermissionDecisionCancelled struct { @@ -4231,7 +4554,8 @@ func (PermissionDecisionCancelled) Kind() PermissionDecisionKind { return PermissionDecisionKindCancelled } -// Schema for the `PermissionDecisionDeniedByContentExclusionPolicy` type. +// Permission-decision variant indicating denial by content-exclusion policy, with path and +// message. // Experimental: PermissionDecisionDeniedByContentExclusionPolicy is part of an experimental // API and may change or be removed. type PermissionDecisionDeniedByContentExclusionPolicy struct { @@ -4246,7 +4570,8 @@ func (PermissionDecisionDeniedByContentExclusionPolicy) Kind() PermissionDecisio return PermissionDecisionKindDeniedByContentExclusionPolicy } -// Schema for the `PermissionDecisionDeniedByPermissionRequestHook` type. +// Permission-decision variant indicating denial by a permission request hook, with optional +// message and interrupt flag. // Experimental: PermissionDecisionDeniedByPermissionRequestHook is part of an experimental // API and may change or be removed. type PermissionDecisionDeniedByPermissionRequestHook struct { @@ -4261,7 +4586,8 @@ func (PermissionDecisionDeniedByPermissionRequestHook) Kind() PermissionDecision return PermissionDecisionKindDeniedByPermissionRequestHook } -// Schema for the `PermissionDecisionDeniedByRules` type. +// Permission-decision variant indicating explicit denial by permission rules, with the +// matching rules. // Experimental: PermissionDecisionDeniedByRules is part of an experimental API and may // change or be removed. type PermissionDecisionDeniedByRules struct { @@ -4274,7 +4600,8 @@ func (PermissionDecisionDeniedByRules) Kind() PermissionDecisionKind { return PermissionDecisionKindDeniedByRules } -// Schema for the `PermissionDecisionDeniedInteractivelyByUser` type. +// Permission-decision variant indicating the user denied an interactive prompt, with +// optional feedback and force-reject flag. // Experimental: PermissionDecisionDeniedInteractivelyByUser is part of an experimental API // and may change or be removed. type PermissionDecisionDeniedInteractivelyByUser struct { @@ -4289,7 +4616,8 @@ func (PermissionDecisionDeniedInteractivelyByUser) Kind() PermissionDecisionKind return PermissionDecisionKindDeniedInteractivelyByUser } -// Schema for the `PermissionDecisionDeniedNoApprovalRuleAndCouldNotRequestFromUser` type. +// Permission-decision variant indicating no approval rule matched and user confirmation was +// unavailable. // Experimental: PermissionDecisionDeniedNoApprovalRuleAndCouldNotRequestFromUser is part of // an experimental API and may change or be removed. type PermissionDecisionDeniedNoApprovalRuleAndCouldNotRequestFromUser struct { @@ -4300,7 +4628,8 @@ func (PermissionDecisionDeniedNoApprovalRuleAndCouldNotRequestFromUser) Kind() P return PermissionDecisionKindDeniedNoApprovalRuleAndCouldNotRequestFromUser } -// Schema for the `PermissionDecisionReject` type. +// Permission-decision request variant to reject a pending permission request, with optional +// feedback. // Experimental: PermissionDecisionReject is part of an experimental API and may change or // be removed. type PermissionDecisionReject struct { @@ -4313,7 +4642,7 @@ func (PermissionDecisionReject) Kind() PermissionDecisionKind { return PermissionDecisionKindReject } -// Schema for the `PermissionDecisionUserNotAvailable` type. +// Permission-decision variant indicating no user was available to confirm the request. // Experimental: PermissionDecisionUserNotAvailable is part of an experimental API and may // change or be removed. type PermissionDecisionUserNotAvailable struct { @@ -4343,7 +4672,7 @@ func (r RawPermissionDecisionApproveForLocationApprovalData) Kind() PermissionDe return r.Discriminator } -// Schema for the `PermissionDecisionApproveForLocationApprovalCommands` type. +// Location-scoped approval details for specific command identifiers. // Experimental: PermissionDecisionApproveForLocationApprovalCommands is part of an // experimental API and may change or be removed. type PermissionDecisionApproveForLocationApprovalCommands struct { @@ -4357,7 +4686,7 @@ func (PermissionDecisionApproveForLocationApprovalCommands) Kind() PermissionDec return PermissionDecisionApproveForLocationApprovalKindCommands } -// Schema for the `PermissionDecisionApproveForLocationApprovalCustomTool` type. +// Location-scoped approval details for a custom tool, keyed by tool name. // Experimental: PermissionDecisionApproveForLocationApprovalCustomTool is part of an // experimental API and may change or be removed. type PermissionDecisionApproveForLocationApprovalCustomTool struct { @@ -4371,7 +4700,8 @@ func (PermissionDecisionApproveForLocationApprovalCustomTool) Kind() PermissionD return PermissionDecisionApproveForLocationApprovalKindCustomTool } -// Schema for the `PermissionDecisionApproveForLocationApprovalExtensionManagement` type. +// Location-scoped approval details for extension-management operations, optionally narrowed +// by operation. // Experimental: PermissionDecisionApproveForLocationApprovalExtensionManagement is part of // an experimental API and may change or be removed. type PermissionDecisionApproveForLocationApprovalExtensionManagement struct { @@ -4386,8 +4716,8 @@ func (PermissionDecisionApproveForLocationApprovalExtensionManagement) Kind() Pe return PermissionDecisionApproveForLocationApprovalKindExtensionManagement } -// Schema for the `PermissionDecisionApproveForLocationApprovalExtensionPermissionAccess` -// type. +// Location-scoped approval details for an extension's permission-gated capability access, +// keyed by extension name. // Experimental: PermissionDecisionApproveForLocationApprovalExtensionPermissionAccess is // part of an experimental API and may change or be removed. type PermissionDecisionApproveForLocationApprovalExtensionPermissionAccess struct { @@ -4401,7 +4731,8 @@ func (PermissionDecisionApproveForLocationApprovalExtensionPermissionAccess) Kin return PermissionDecisionApproveForLocationApprovalKindExtensionPermissionAccess } -// Schema for the `PermissionDecisionApproveForLocationApprovalMcp` type. +// Location-scoped approval details for an MCP server tool, or all tools on the server when +// `toolName` is null. // Experimental: PermissionDecisionApproveForLocationApprovalMCP is part of an experimental // API and may change or be removed. type PermissionDecisionApproveForLocationApprovalMCP struct { @@ -4417,7 +4748,7 @@ func (PermissionDecisionApproveForLocationApprovalMCP) Kind() PermissionDecision return PermissionDecisionApproveForLocationApprovalKindMCP } -// Schema for the `PermissionDecisionApproveForLocationApprovalMcpSampling` type. +// Location-scoped approval details for MCP sampling requests from a server. // Experimental: PermissionDecisionApproveForLocationApprovalMCPSampling is part of an // experimental API and may change or be removed. type PermissionDecisionApproveForLocationApprovalMCPSampling struct { @@ -4431,7 +4762,7 @@ func (PermissionDecisionApproveForLocationApprovalMCPSampling) Kind() Permission return PermissionDecisionApproveForLocationApprovalKindMCPSampling } -// Schema for the `PermissionDecisionApproveForLocationApprovalMemory` type. +// Location-scoped approval details for writes to long-term memory. // Experimental: PermissionDecisionApproveForLocationApprovalMemory is part of an // experimental API and may change or be removed. type PermissionDecisionApproveForLocationApprovalMemory struct { @@ -4443,7 +4774,7 @@ func (PermissionDecisionApproveForLocationApprovalMemory) Kind() PermissionDecis return PermissionDecisionApproveForLocationApprovalKindMemory } -// Schema for the `PermissionDecisionApproveForLocationApprovalRead` type. +// Location-scoped approval details for read-only filesystem operations. // Experimental: PermissionDecisionApproveForLocationApprovalRead is part of an experimental // API and may change or be removed. type PermissionDecisionApproveForLocationApprovalRead struct { @@ -4455,7 +4786,7 @@ func (PermissionDecisionApproveForLocationApprovalRead) Kind() PermissionDecisio return PermissionDecisionApproveForLocationApprovalKindRead } -// Schema for the `PermissionDecisionApproveForLocationApprovalWrite` type. +// Location-scoped approval details for filesystem write operations. // Experimental: PermissionDecisionApproveForLocationApprovalWrite is part of an // experimental API and may change or be removed. type PermissionDecisionApproveForLocationApprovalWrite struct { @@ -4486,7 +4817,7 @@ func (r RawPermissionDecisionApproveForSessionApprovalData) Kind() PermissionDec return r.Discriminator } -// Schema for the `PermissionDecisionApproveForSessionApprovalCommands` type. +// Session-scoped approval details for specific command identifiers. // Experimental: PermissionDecisionApproveForSessionApprovalCommands is part of an // experimental API and may change or be removed. type PermissionDecisionApproveForSessionApprovalCommands struct { @@ -4500,7 +4831,7 @@ func (PermissionDecisionApproveForSessionApprovalCommands) Kind() PermissionDeci return PermissionDecisionApproveForSessionApprovalKindCommands } -// Schema for the `PermissionDecisionApproveForSessionApprovalCustomTool` type. +// Session-scoped approval details for a custom tool, keyed by tool name. // Experimental: PermissionDecisionApproveForSessionApprovalCustomTool is part of an // experimental API and may change or be removed. type PermissionDecisionApproveForSessionApprovalCustomTool struct { @@ -4514,7 +4845,8 @@ func (PermissionDecisionApproveForSessionApprovalCustomTool) Kind() PermissionDe return PermissionDecisionApproveForSessionApprovalKindCustomTool } -// Schema for the `PermissionDecisionApproveForSessionApprovalExtensionManagement` type. +// Session-scoped approval details for extension-management operations, optionally narrowed +// by operation. // Experimental: PermissionDecisionApproveForSessionApprovalExtensionManagement is part of // an experimental API and may change or be removed. type PermissionDecisionApproveForSessionApprovalExtensionManagement struct { @@ -4529,8 +4861,8 @@ func (PermissionDecisionApproveForSessionApprovalExtensionManagement) Kind() Per return PermissionDecisionApproveForSessionApprovalKindExtensionManagement } -// Schema for the `PermissionDecisionApproveForSessionApprovalExtensionPermissionAccess` -// type. +// Session-scoped approval details for an extension's permission-gated capability access, +// keyed by extension name. // Experimental: PermissionDecisionApproveForSessionApprovalExtensionPermissionAccess is // part of an experimental API and may change or be removed. type PermissionDecisionApproveForSessionApprovalExtensionPermissionAccess struct { @@ -4544,7 +4876,8 @@ func (PermissionDecisionApproveForSessionApprovalExtensionPermissionAccess) Kind return PermissionDecisionApproveForSessionApprovalKindExtensionPermissionAccess } -// Schema for the `PermissionDecisionApproveForSessionApprovalMcp` type. +// Session-scoped approval details for an MCP server tool, or all tools on the server when +// `toolName` is null. // Experimental: PermissionDecisionApproveForSessionApprovalMCP is part of an experimental // API and may change or be removed. type PermissionDecisionApproveForSessionApprovalMCP struct { @@ -4559,7 +4892,7 @@ func (PermissionDecisionApproveForSessionApprovalMCP) Kind() PermissionDecisionA return PermissionDecisionApproveForSessionApprovalKindMCP } -// Schema for the `PermissionDecisionApproveForSessionApprovalMcpSampling` type. +// Session-scoped approval details for MCP sampling requests from a server. // Experimental: PermissionDecisionApproveForSessionApprovalMCPSampling is part of an // experimental API and may change or be removed. type PermissionDecisionApproveForSessionApprovalMCPSampling struct { @@ -4573,7 +4906,7 @@ func (PermissionDecisionApproveForSessionApprovalMCPSampling) Kind() PermissionD return PermissionDecisionApproveForSessionApprovalKindMCPSampling } -// Schema for the `PermissionDecisionApproveForSessionApprovalMemory` type. +// Session-scoped approval details for writes to long-term memory. // Experimental: PermissionDecisionApproveForSessionApprovalMemory is part of an // experimental API and may change or be removed. type PermissionDecisionApproveForSessionApprovalMemory struct { @@ -4585,7 +4918,7 @@ func (PermissionDecisionApproveForSessionApprovalMemory) Kind() PermissionDecisi return PermissionDecisionApproveForSessionApprovalKindMemory } -// Schema for the `PermissionDecisionApproveForSessionApprovalRead` type. +// Session-scoped approval details for read-only filesystem operations. // Experimental: PermissionDecisionApproveForSessionApprovalRead is part of an experimental // API and may change or be removed. type PermissionDecisionApproveForSessionApprovalRead struct { @@ -4597,7 +4930,7 @@ func (PermissionDecisionApproveForSessionApprovalRead) Kind() PermissionDecision return PermissionDecisionApproveForSessionApprovalKindRead } -// Schema for the `PermissionDecisionApproveForSessionApprovalWrite` type. +// Session-scoped approval details for filesystem write operations. // Experimental: PermissionDecisionApproveForSessionApprovalWrite is part of an experimental // API and may change or be removed. type PermissionDecisionApproveForSessionApprovalWrite struct { @@ -4773,7 +5106,8 @@ type PermissionRequestResult struct { Success bool `json:"success"` } -// Schema for the `PermissionRule` type. +// A permission approval or denial rule matched against a tool request, identified by a rule +// kind with an optional argument value. // Experimental: PermissionRule is part of an experimental API and may change or be removed. type PermissionRule struct { // Argument value matched against the request, or null when the rule kind has no argument @@ -4794,7 +5128,8 @@ type PermissionRulesSet struct { Denied []PermissionRule `json:"denied"` } -// Schema for the `PermissionsConfigureAdditionalContentExclusionPolicy` type. +// Content-exclusion policy supplied to `session.permissions.configure`, with rules, +// last-updated data, and scope. // Experimental: PermissionsConfigureAdditionalContentExclusionPolicy is part of an // experimental API and may change or be removed. type PermissionsConfigureAdditionalContentExclusionPolicy struct { @@ -4805,18 +5140,21 @@ type PermissionsConfigureAdditionalContentExclusionPolicy struct { Scope PermissionsConfigureAdditionalContentExclusionPolicyScope `json:"scope"` } -// Schema for the `PermissionsConfigureAdditionalContentExclusionPolicyRule` type. +// Single content-exclusion rule supplied to `session.permissions.configure`, with paths, +// match conditions, and source. // Experimental: PermissionsConfigureAdditionalContentExclusionPolicyRule is part of an // experimental API and may change or be removed. type PermissionsConfigureAdditionalContentExclusionPolicyRule struct { IfAnyMatch []string `json:"ifAnyMatch,omitzero"` IfNoneMatch []string `json:"ifNoneMatch,omitzero"` Paths []string `json:"paths"` - // Schema for the `PermissionsConfigureAdditionalContentExclusionPolicyRuleSource` type. + // Source descriptor for a `session.permissions.configure` content-exclusion rule, with + // source name and type. Source PermissionsConfigureAdditionalContentExclusionPolicyRuleSource `json:"source"` } -// Schema for the `PermissionsConfigureAdditionalContentExclusionPolicyRuleSource` type. +// Source descriptor for a `session.permissions.configure` content-exclusion rule, with +// source name and type. // Experimental: PermissionsConfigureAdditionalContentExclusionPolicyRuleSource is part of // an experimental API and may change or be removed. type PermissionsConfigureAdditionalContentExclusionPolicyRuleSource struct { @@ -4892,7 +5230,7 @@ func (r RawPermissionsLocationsAddToolApprovalDetailsData) Kind() PermissionsLoc return r.Discriminator } -// Schema for the `PermissionsLocationsAddToolApprovalDetailsCommands` type. +// Location-persisted tool approval details for specific command identifiers. // Experimental: PermissionsLocationsAddToolApprovalDetailsCommands is part of an // experimental API and may change or be removed. type PermissionsLocationsAddToolApprovalDetailsCommands struct { @@ -4906,7 +5244,7 @@ func (PermissionsLocationsAddToolApprovalDetailsCommands) Kind() PermissionsLoca return PermissionsLocationsAddToolApprovalDetailsKindCommands } -// Schema for the `PermissionsLocationsAddToolApprovalDetailsCustomTool` type. +// Location-persisted tool approval details for a custom tool, keyed by tool name. // Experimental: PermissionsLocationsAddToolApprovalDetailsCustomTool is part of an // experimental API and may change or be removed. type PermissionsLocationsAddToolApprovalDetailsCustomTool struct { @@ -4920,7 +5258,8 @@ func (PermissionsLocationsAddToolApprovalDetailsCustomTool) Kind() PermissionsLo return PermissionsLocationsAddToolApprovalDetailsKindCustomTool } -// Schema for the `PermissionsLocationsAddToolApprovalDetailsExtensionManagement` type. +// Location-persisted tool approval details for extension-management operations, optionally +// narrowed by operation. // Experimental: PermissionsLocationsAddToolApprovalDetailsExtensionManagement is part of an // experimental API and may change or be removed. type PermissionsLocationsAddToolApprovalDetailsExtensionManagement struct { @@ -4935,7 +5274,8 @@ func (PermissionsLocationsAddToolApprovalDetailsExtensionManagement) Kind() Perm return PermissionsLocationsAddToolApprovalDetailsKindExtensionManagement } -// Schema for the `PermissionsLocationsAddToolApprovalDetailsExtensionPermissionAccess` type. +// Location-persisted tool approval details for an extension's permission-gated capability +// access, keyed by extension name. // Experimental: PermissionsLocationsAddToolApprovalDetailsExtensionPermissionAccess is part // of an experimental API and may change or be removed. type PermissionsLocationsAddToolApprovalDetailsExtensionPermissionAccess struct { @@ -4949,7 +5289,8 @@ func (PermissionsLocationsAddToolApprovalDetailsExtensionPermissionAccess) Kind( return PermissionsLocationsAddToolApprovalDetailsKindExtensionPermissionAccess } -// Schema for the `PermissionsLocationsAddToolApprovalDetailsMcp` type. +// Location-persisted tool approval details for an MCP server tool, or all tools when +// `toolName` is null. // Experimental: PermissionsLocationsAddToolApprovalDetailsMCP is part of an experimental // API and may change or be removed. type PermissionsLocationsAddToolApprovalDetailsMCP struct { @@ -4964,7 +5305,7 @@ func (PermissionsLocationsAddToolApprovalDetailsMCP) Kind() PermissionsLocations return PermissionsLocationsAddToolApprovalDetailsKindMCP } -// Schema for the `PermissionsLocationsAddToolApprovalDetailsMcpSampling` type. +// Location-persisted tool approval details for MCP sampling requests from a server. // Experimental: PermissionsLocationsAddToolApprovalDetailsMCPSampling is part of an // experimental API and may change or be removed. type PermissionsLocationsAddToolApprovalDetailsMCPSampling struct { @@ -4978,7 +5319,7 @@ func (PermissionsLocationsAddToolApprovalDetailsMCPSampling) Kind() PermissionsL return PermissionsLocationsAddToolApprovalDetailsKindMCPSampling } -// Schema for the `PermissionsLocationsAddToolApprovalDetailsMemory` type. +// Location-persisted tool approval details for writes to long-term memory. // Experimental: PermissionsLocationsAddToolApprovalDetailsMemory is part of an experimental // API and may change or be removed. type PermissionsLocationsAddToolApprovalDetailsMemory struct { @@ -4990,7 +5331,7 @@ func (PermissionsLocationsAddToolApprovalDetailsMemory) Kind() PermissionsLocati return PermissionsLocationsAddToolApprovalDetailsKindMemory } -// Schema for the `PermissionsLocationsAddToolApprovalDetailsRead` type. +// Location-persisted tool approval details for read-only filesystem operations. // Experimental: PermissionsLocationsAddToolApprovalDetailsRead is part of an experimental // API and may change or be removed. type PermissionsLocationsAddToolApprovalDetailsRead struct { @@ -5001,7 +5342,7 @@ func (PermissionsLocationsAddToolApprovalDetailsRead) Kind() PermissionsLocation return PermissionsLocationsAddToolApprovalDetailsKindRead } -// Schema for the `PermissionsLocationsAddToolApprovalDetailsWrite` type. +// Location-persisted tool approval details for filesystem write operations. // Experimental: PermissionsLocationsAddToolApprovalDetailsWrite is part of an experimental // API and may change or be removed. type PermissionsLocationsAddToolApprovalDetailsWrite struct { @@ -5095,12 +5436,19 @@ type PermissionsResetSessionApprovalsResult struct { Success bool `json:"success"` } -// Whether to enable full allow-all permissions for the session. +// Allow-all mode to apply for the session. // Experimental: PermissionsSetAllowAllRequest is part of an experimental API and may change // or be removed. type PermissionsSetAllowAllRequest struct { - // Whether to enable full allow-all permissions - Enabled bool `json:"enabled"` + // Legacy full allow-all toggle. Prefer `mode`; when `mode` is omitted, `enabled: true` is + // treated as `mode: "on"` and any other value is treated as `mode: "off"`. + Enabled *bool `json:"enabled,omitempty"` + // Allow-all mode to apply. `on` enables full allow-all; `auto` enables advisory LLM + // auto-approval; `off` disables both. + Mode *PermissionsAllowAllMode `json:"mode,omitempty"` + // Optional model id for the `auto` mode auto-approval LLM judging. Only meaningful when + // `mode` is `auto`; ignored otherwise. When omitted, the session's active model is used. + Model *string `json:"model,omitempty"` // Optional source for allow-all telemetry. Defaults to `rpc` when omitted for SDK callers. Source *PermissionsSetAllowAllSource `json:"source,omitempty"` } @@ -5256,7 +5604,7 @@ type PlanUpdateRequest struct { Content string `json:"content"` } -// Schema for the `Plugin` type. +// Session plugin metadata, with name, marketplace, optional version, and enabled state. // Experimental: Plugin is part of an experimental API and may change or be removed. type Plugin struct { // Whether the plugin is currently enabled @@ -5390,6 +5738,10 @@ type PluginsReloadRequest struct { DeferRepoHooks *bool `json:"deferRepoHooks,omitempty"` // Re-run custom-agent discovery after refreshing plugins. Defaults to true. ReloadCustomAgents *bool `json:"reloadCustomAgents,omitempty"` + // Re-discover and relaunch subprocess extensions (including plugin-shipped extensions) + // after refreshing plugins. Defaults to true. Has no effect when the session has no active + // extension controller (e.g. extensions were not requested for the session). + ReloadExtensions *bool `json:"reloadExtensions,omitempty"` // Re-load user, plugin, and (subject to `deferRepoHooks`) repo hooks. Defaults to true. Has // no effect when the host has not registered a hook reloader (e.g. remote sessions). ReloadHooks *bool `json:"reloadHooks,omitempty"` @@ -5422,7 +5774,8 @@ type PluginsUpdateRequest struct { Name string `json:"name"` } -// Schema for the `PluginUpdateAllEntry` type. +// Per-plugin result from updating all plugins, with versions, skills installed, success +// flag, and optional error. // Experimental: PluginUpdateAllEntry is part of an experimental API and may change or be // removed. type PluginUpdateAllEntry struct { @@ -5462,16 +5815,6 @@ type PluginUpdateResult struct { SkillsInstalled int64 `json:"skillsInstalled"` } -// Batch of spawn events plus a cursor for follow-up polls. -// Experimental: PollSpawnedSessionsResult is part of an experimental API and may change or -// be removed. -type PollSpawnedSessionsResult struct { - // Opaque cursor to pass back to receive only events after this batch. - Cursor string `json:"cursor"` - // Spawn events emitted since the supplied cursor. - Events []SessionsPollSpawnedSessionsEvent `json:"events"` -} - // BYOK providers and/or models to add to the session's registry at runtime. Both fields are // optional; provide providers, models, or both. // Experimental: ProviderAddRequest is part of an experimental API and may change or be @@ -5646,7 +5989,8 @@ type ProviderTokenAcquireResult struct { Token string `json:"token"` } -// Schema for the `PushAttachment` type. +// Attachment union accepted by push input, covering files, directories, GitHub objects, +// blobs, snippets, and extension context. // Experimental: PushAttachment is part of an experimental API and may change or be removed. type PushAttachment interface { pushAttachment() @@ -6018,7 +6362,8 @@ type QueuedCommandResult interface { Handled() bool } -// Schema for the `QueuedCommandHandled` type. +// Queued-command response indicating the host executed the command, with an optional flag +// to stop queue processing. // Experimental: QueuedCommandHandled is part of an experimental API and may change or be // removed. type QueuedCommandHandled struct { @@ -6032,7 +6377,8 @@ func (QueuedCommandHandled) Handled() bool { return true } -// Schema for the `QueuedCommandNotHandled` type. +// Queued-command response indicating the host did not execute the command and the queue may +// continue. // Experimental: QueuedCommandNotHandled is part of an experimental API and may change or be // removed. type QueuedCommandNotHandled struct { @@ -6043,7 +6389,8 @@ func (QueuedCommandNotHandled) Handled() bool { return false } -// Schema for the `QueuePendingItems` type. +// User-facing pending queue entry, with kind and display text for a queued message, slash +// command, or model change. // Experimental: QueuePendingItems is part of an experimental API and may change or be // removed. type QueuePendingItems struct { @@ -6081,16 +6428,16 @@ type RegisterEventInterestParams struct { // The event type the consumer wants the runtime to treat as 'observed' for // behavior-switching gating. Some runtime code paths inspect whether any consumer is // interested in a specific event type and choose a different implementation accordingly - // (e.g. `mcp.oauth_required`: when interest is registered the runtime delegates the full - // interactive OAuth flow to the consumer; when no interest is registered the runtime - // installs a browserless fallback that silently reuses cached tokens). SDK clients that - // long-poll events do NOT automatically appear as listeners to these gating checks — they - // must explicitly call `registerInterest` for each event type they want the runtime to - // count as having a consumer. Multiple registrations for the same event type from the same - // or different consumers are tracked independently and must each be released. See: - // `mcp.oauth_required`, `sampling.requested`, `auto_mode_switch.requested`, - // `session_limits_exhausted.requested`, `user_input.requested`, `elicitation.requested`, - // `command.queued`, `exit_plan_mode.requested`. + // (e.g. `mcp.oauth_required`: when interest is registered the runtime delegates OAuth token + // acquisition to the consumer; when no interest is registered OAuth-required servers become + // needs-auth). SDK clients that long-poll events do NOT automatically appear as listeners + // to these gating checks — they must explicitly call `registerInterest` for each event type + // they want the runtime to count as having a consumer. Multiple registrations for the same + // event type from the same or different consumers are tracked independently and must each + // be released. See: `mcp.oauth_required`, `sampling.requested`, + // `auto_mode_switch.requested`, `session_limits_exhausted.requested`, + // `user_input.requested`, `elicitation.requested`, `command.queued`, + // `exit_plan_mode.requested`. EventType string `json:"eventType"` } @@ -6200,6 +6547,12 @@ func (r RawRemoteControlStatusData) State() RemoteControlStatusState { type RemoteControlStatusActive struct { // Session id remote control is pointed at. AttachedSessionID string `json:"attachedSessionId"` + // True while a read-only/session-sync export is deferred, awaiting the first `user.message` + // before its MC session exists. Marked internal: this field is excluded from the public SDK + // surface and is populated only on the CLI in-process path. + // Internal: AwaitingFirstMessage is part of the SDK's internal API surface and is not + // intended for external use. + AwaitingFirstMessage *bool `json:"awaitingFirstMessage,omitempty"` // MC frontend URL for this session, when known. FrontendURL *string `json:"frontendUrl,omitempty"` // Whether the MC session may steer this session. @@ -6422,14 +6775,10 @@ type SandboxConfigUserPolicyFilesystem struct { // Experimental: SandboxConfigUserPolicyNetwork is part of an experimental API and may // change or be removed. type SandboxConfigUserPolicyNetwork struct { - // Hosts allowed in addition to the base policy. - AllowedHosts []string `json:"allowedHosts,omitzero"` // Whether traffic to local/loopback addresses is allowed. AllowLocalNetwork *bool `json:"allowLocalNetwork,omitempty"` // Whether outbound network traffic is allowed at all. AllowOutbound *bool `json:"allowOutbound,omitempty"` - // Hosts explicitly blocked. - BlockedHosts []string `json:"blockedHosts,omitzero"` } // macOS seatbelt-specific options. @@ -6440,7 +6789,8 @@ type SandboxConfigUserPolicySeatbelt struct { KeychainAccess *bool `json:"keychainAccess,omitempty"` } -// Schema for the `ScheduleEntry` type. +// Scheduled prompt entry with ID, timing (`intervalMs`, `cron`, or `at`), prompt text, +// recurrence, and next run time. // Experimental: ScheduleEntry is part of an experimental API and may change or be removed. type ScheduleEntry struct { // Absolute fire time (epoch milliseconds) for a one-shot calendar schedule. @@ -6588,7 +6938,8 @@ type ServerInstructionSourceList struct { Sources []InstructionSource `json:"sources"` } -// Schema for the `ServerSkill` type. +// Server-side skill metadata, including name, description, source, enabled/invocable state, +// path, project path, and argument hint. // Experimental: ServerSkill is part of an experimental API and may change or be removed. type ServerSkill struct { // Optional freeform hint describing the skill's expected arguments, from the @@ -6698,6 +7049,52 @@ type SessionContext struct { Repository *string `json:"repository,omitempty"` } +// Per-source token attribution snapshot for the current context window. The heaviest +// individual messages are available separately via `metadata.getContextHeaviestMessages`. +// Experimental: SessionContextAttribution is part of an experimental API and may change or +// be removed. +type SessionContextAttribution struct { + // Successful compaction history for the session. + Compactions SessionContextAttributionCompactions `json:"compactions"` + // Flat list of per-source attribution entries. Group by `kind` and render unrecognized + // kinds generically. Nesting and rollups are expressed via `parentId`. + Entries []SessionContextAttributionEntriesItem `json:"entries"` + // Total token count of the current context window the entries are measured against (system + // message + conversation messages + tool definitions — the same total reported by + // /context). Divide an entry's `tokens` by this to derive its share. + TotalTokens int64 `json:"totalTokens"` +} + +// Successful compaction history for the session. +type SessionContextAttributionCompactions struct { + // Number of successful compactions in this session. + Count int64 `json:"count"` +} + +type SessionContextAttributionEntriesItem struct { + // Supplementary per-entry metadata (e.g. `messageCount`, `role`, `evictable`, + // `pluginSource`). Values are stringified; parse as needed and ignore unrecognized keys. + Attributes map[string]string `json:"attributes,omitzero"` + // Identifier for this entry, formed by joining its `kind` and source name (e.g. + // `tool:bash`, `skill:tmux`, `toolDefinition:bash`); unique within the snapshot. Use it to + // match the same entry across snapshots, to correlate with other APIs (skill/agent/MCP + // registries), and as the `parentId` target for nesting. Distinct from the human-facing + // `label`. + ID string `json:"id"` + // Source category for this entry. Not a closed set — tolerate unknown values. Known values + // today: `skill`, `subagent`, `mcpServer`, `tool`, `system`, `toolDefinition`, `plugin`. + Kind string `json:"kind"` + // Human-readable display label, e.g. `bash` or `skill: tmux`. Presentation-only; may be + // localized/reformatted without notice — do not key off it. + Label string `json:"label"` + // Optional `id` of the parent entry: e.g. a `plugin` entry parenting its + // `skill`/`mcpServer` entries, or the `system` entry parenting `toolDefinition` entries. + // Omitted for top-level entries. + ParentID *string `json:"parentId,omitempty"` + // Token count currently in context attributable to this entry. + Tokens int64 `json:"tokens"` +} + // Token-usage breakdown for the session's current context window // Experimental: SessionContextInfo is part of an experimental API and may change or be // removed. @@ -6833,7 +7230,8 @@ type SessionFSReaddirResult struct { Error *SessionFSError `json:"error,omitempty"` } -// Schema for the `SessionFsReaddirWithTypesEntry` type. +// Directory entry returned by session filesystem `readdirWithTypes`, with name and entry +// type. // Experimental: SessionFSReaddirWithTypesEntry is part of an experimental API and may // change or be removed. type SessionFSReaddirWithTypesEntry struct { @@ -7035,7 +7433,8 @@ type SessionFSWriteFileRequest struct { SessionID string `json:"sessionId"` } -// Schema for the `SessionInstalledPlugin` type. +// Installed plugin record for a session, with marketplace, version, install time, enabled +// state, cache path, and source. // Experimental: SessionInstalledPlugin is part of an experimental API and may change or be // removed. type SessionInstalledPlugin struct { @@ -7065,7 +7464,8 @@ type SessionInstalledPluginSource struct { String *string } -// Schema for the `SessionInstalledPluginSourceGitHub` type. +// Source descriptor for a direct GitHub plugin install, with `owner/repo`, optional ref, +// and optional subpath. // Experimental: SessionInstalledPluginSourceGitHub is part of an experimental API and may // change or be removed. type SessionInstalledPluginSourceGitHub struct { @@ -7076,7 +7476,7 @@ type SessionInstalledPluginSourceGitHub struct { Source SessionInstalledPluginSourceGitHubSource `json:"source"` } -// Schema for the `SessionInstalledPluginSourceLocal` type. +// Source descriptor for a direct local plugin install, with a local filesystem path. // Experimental: SessionInstalledPluginSourceLocal is part of an experimental API and may // change or be removed. type SessionInstalledPluginSourceLocal struct { @@ -7085,7 +7485,8 @@ type SessionInstalledPluginSourceLocal struct { Source SessionInstalledPluginSourceLocalSource `json:"source"` } -// Schema for the `SessionInstalledPluginSourceUrl` type. +// Source descriptor for a direct URL plugin install, with URL, optional ref, and optional +// subpath. // Experimental: SessionInstalledPluginSourceURL is part of an experimental API and may // change or be removed. type SessionInstalledPluginSourceURL struct { @@ -7362,6 +7763,8 @@ type SessionOpenOptions struct { // surface is experimental. // Experimental: EnableCitations is part of an experimental API and may change or be removed. EnableCitations *bool `json:"enableCitations,omitempty"` + // Opt-in: self-fetch and enforce enterprise managed settings at session bootstrap. + EnableManagedSettings *bool `json:"enableManagedSettings,omitempty"` // Whether on-demand custom instruction discovery is enabled. EnableOnDemandInstructionDiscovery *bool `json:"enableOnDemandInstructionDiscovery,omitempty"` // Whether shell-script safety heuristics are enabled. @@ -7448,13 +7851,16 @@ type SessionOpenOptions struct { SkipCustomInstructions *bool `json:"skipCustomInstructions,omitempty"` // Optional trajectory output file path. TrajectoryFile *string `json:"trajectoryFile,omitempty"` + // Initial output verbosity level for supported models. + Verbosity *Verbosity `json:"verbosity,omitempty"` // Working directory to anchor the session. WorkingDirectory *string `json:"workingDirectory,omitempty"` // Pre-resolved working-directory context for session startup. WorkingDirectoryContext *SessionContext `json:"workingDirectoryContext,omitempty"` } -// Schema for the `SessionOpenOptionsAdditionalContentExclusionPolicy` type. +// Content-exclusion policy supplied to `sessions.open` options, with rules, last-updated +// data, and scope. // Experimental: SessionOpenOptionsAdditionalContentExclusionPolicy is part of an // experimental API and may change or be removed. type SessionOpenOptionsAdditionalContentExclusionPolicy struct { @@ -7465,18 +7871,19 @@ type SessionOpenOptionsAdditionalContentExclusionPolicy struct { Scope SessionOpenOptionsAdditionalContentExclusionPolicyScope `json:"scope"` } -// Schema for the `SessionOpenOptionsAdditionalContentExclusionPolicyRule` type. +// Single content-exclusion rule supplied to `sessions.open` options, with paths, match +// conditions, and source. // Experimental: SessionOpenOptionsAdditionalContentExclusionPolicyRule is part of an // experimental API and may change or be removed. type SessionOpenOptionsAdditionalContentExclusionPolicyRule struct { IfAnyMatch []string `json:"ifAnyMatch,omitzero"` IfNoneMatch []string `json:"ifNoneMatch,omitzero"` Paths []string `json:"paths"` - // Schema for the `SessionOpenOptionsAdditionalContentExclusionPolicyRuleSource` type. + // Source descriptor for a `sessions.open` content-exclusion rule, with source name and type. Source SessionOpenOptionsAdditionalContentExclusionPolicyRuleSource `json:"source"` } -// Schema for the `SessionOpenOptionsAdditionalContentExclusionPolicyRuleSource` type. +// Source descriptor for a `sessions.open` content-exclusion rule, with source name and type. // Experimental: SessionOpenOptionsAdditionalContentExclusionPolicyRuleSource is part of an // experimental API and may change or be removed. type SessionOpenOptionsAdditionalContentExclusionPolicyRuleSource struct { @@ -7563,6 +7970,15 @@ type SessionsOpenHandoff struct { // Remote session metadata for the session to hand off (typically obtained from // `sessions.list` with `source: "remote"`). Metadata RemoteSessionMetadataValue `json:"metadata"` + // In-process confirmation callback `(request) => boolean | Promise` invoked when + // the handoff needs the caller to confirm a non-fatal blocker (e.g. a repository mismatch + // between the current working directory and the remote session). Returning `true` proceeds + // with the handoff; returning `false` (or omitting the callback) aborts it. Marked internal + // because a function reference cannot cross the JSON-RPC boundary, for the same reasons as + // `onProgress`. + // Internal: OnConfirm is part of the SDK's internal API surface and is not intended for + // external use. + OnConfirm any `json:"onConfirm,omitempty"` // In-process progress callback `(update) => void` invoked for each handoff step. Marked // internal because a function reference cannot cross the JSON-RPC boundary. The host-side // `handoffSession` is already declared as `AsyncGenerator`; @@ -7792,6 +8208,107 @@ type SessionSetCredentialsResult struct { Success bool `json:"success"` } +// Availability of built-in job tools surfaced to boundary consumers. +// Experimental: SessionSettingsBuiltInToolAvailabilitySnapshot is part of an experimental +// API and may change or be removed. +type SessionSettingsBuiltInToolAvailabilitySnapshot struct { + CreatePullRequest *bool `json:"createPullRequest,omitempty"` + ReportProgress *bool `json:"reportProgress,omitempty"` +} + +// Named Rust-owned settings predicate to evaluate for this session. +// Experimental: SessionSettingsEvaluatePredicateRequest is part of an experimental API and +// may change or be removed. +type SessionSettingsEvaluatePredicateRequest struct { + // Predicate name. The runtime owns the raw feature-flag names and composition logic. + Name SessionSettingsPredicateName `json:"name"` + // Tool name for tool-scoped predicates such as trivial-change handling. + ToolName *string `json:"toolName,omitempty"` +} + +// Result of evaluating a Rust-owned settings predicate. +// Experimental: SessionSettingsEvaluatePredicateResult is part of an experimental API and +// may change or be removed. +type SessionSettingsEvaluatePredicateResult struct { + Enabled bool `json:"enabled"` +} + +// Redacted job settings for a session. The job nonce is excluded. +// Experimental: SessionSettingsJobSnapshot is part of an experimental API and may change or +// be removed. +type SessionSettingsJobSnapshot struct { + BuiltInToolAvailability *SessionSettingsBuiltInToolAvailabilitySnapshot `json:"builtInToolAvailability,omitempty"` + EventType *string `json:"eventType,omitempty"` + IsTriggerJob *bool `json:"isTriggerJob,omitempty"` +} + +// Redacted model routing settings for a session. +// Experimental: SessionSettingsModelSnapshot is part of an experimental API and may change +// or be removed. +type SessionSettingsModelSnapshot struct { + CallbackURL *string `json:"callbackUrl,omitempty"` + DefaultReasoningEffort *string `json:"defaultReasoningEffort,omitempty"` + InstanceID *string `json:"instanceId,omitempty"` + Model *string `json:"model,omitempty"` +} + +// Online-evaluation settings safe to expose across the SDK boundary. +// Experimental: SessionSettingsOnlineEvaluationSnapshot is part of an experimental API and +// may change or be removed. +type SessionSettingsOnlineEvaluationSnapshot struct { + DisableOnlineEvaluation *bool `json:"disableOnlineEvaluation,omitempty"` + EnableOnlineEvaluationOutputFile *bool `json:"enableOnlineEvaluationOutputFile,omitempty"` +} + +// Redacted repository and GitHub host settings for a session. +// Experimental: SessionSettingsRepoSnapshot is part of an experimental API and may change +// or be removed. +type SessionSettingsRepoSnapshot struct { + Branch *string `json:"branch,omitempty"` + Commit *string `json:"commit,omitempty"` + Host *string `json:"host,omitempty"` + HostProtocol *string `json:"hostProtocol,omitempty"` + ID *float64 `json:"id,omitempty"` + Name *string `json:"name,omitempty"` + OwnerID *float64 `json:"ownerId,omitempty"` + OwnerName *string `json:"ownerName,omitempty"` + PrCommitCount *float64 `json:"prCommitCount,omitempty"` + ReadWrite *bool `json:"readWrite,omitempty"` + SecretScanningURL *string `json:"secretScanningUrl,omitempty"` + ServerURL *string `json:"serverUrl,omitempty"` +} + +// Redacted, serializable view of session runtime settings for SDK boundary consumers. +// Secrets and raw feature flags are intentionally excluded. +// Experimental: SessionSettingsSnapshot is part of an experimental API and may change or be +// removed. +type SessionSettingsSnapshot struct { + ClientName *string `json:"clientName,omitempty"` + Job SessionSettingsJobSnapshot `json:"job"` + Model SessionSettingsModelSnapshot `json:"model"` + OnlineEvaluation SessionSettingsOnlineEvaluationSnapshot `json:"onlineEvaluation"` + Repo SessionSettingsRepoSnapshot `json:"repo"` + StartTimeMs *float64 `json:"startTimeMs,omitempty"` + TimeoutMs *float64 `json:"timeoutMs,omitempty"` + Validation SessionSettingsValidationSnapshot `json:"validation"` + Version *string `json:"version,omitempty"` +} + +// Redacted validation and memory-tool settings for a session. +// Experimental: SessionSettingsValidationSnapshot is part of an experimental API and may +// change or be removed. +type SessionSettingsValidationSnapshot struct { + AdvisoryEnabled *bool `json:"advisoryEnabled,omitempty"` + CodeqlEnabled *bool `json:"codeqlEnabled,omitempty"` + CodeReviewEnabled *bool `json:"codeReviewEnabled,omitempty"` + CodeReviewModel *string `json:"codeReviewModel,omitempty"` + DependabotTimeout *float64 `json:"dependabotTimeout,omitempty"` + MemoryStoreEnabled *bool `json:"memoryStoreEnabled,omitempty"` + MemoryVoteEnabled *bool `json:"memoryVoteEnabled,omitempty"` + SecretScanningEnabled *bool `json:"secretScanningEnabled,omitempty"` + Timeout *float64 `json:"timeout,omitempty"` +} + // UUID prefix to resolve to a unique session ID. // Experimental: SessionsFindByPrefixRequest is part of an experimental API and may change // or be removed. @@ -7973,7 +8490,7 @@ type SessionsLoadDeferredRepoHooksRequest struct { SessionID string `json:"sessionId"` } -// Schema for the `SessionsOpenProgress` type. +// `sessions.open` handoff progress update with step, status, and optional message. // Experimental: SessionsOpenProgress is part of an experimental API and may change or be // removed. type SessionsOpenProgress struct { @@ -7985,25 +8502,6 @@ type SessionsOpenProgress struct { Step SessionsOpenProgressStep `json:"step"` } -// Schema for the `SessionsPollSpawnedSessionsEvent` type. -// Experimental: SessionsPollSpawnedSessionsEvent is part of an experimental API and may -// change or be removed. -type SessionsPollSpawnedSessionsEvent struct { - // Session id of the newly-spawned session. - SessionID string `json:"sessionId"` -} - -// Experimental: SessionsPollSpawnedSessionsRequest is part of an experimental API and may -// change or be removed. -type SessionsPollSpawnedSessionsRequest struct { - // Opaque cursor returned by a previous poll. Omit on the first call to receive any spawn - // events buffered since the runtime started. - Cursor *string `json:"cursor,omitempty"` - // Milliseconds to wait for new spawn events when the cursor is at the tail. 0 (default) - // returns immediately even if no events are buffered. Capped at 60000ms. - WaitMs *int32 `json:"waitMs,omitempty"` -} - // Age threshold and optional flags controlling which old sessions are pruned (or simulated // when dryRun is true). // Experimental: SessionsPruneOldRequest is part of an experimental API and may change or be @@ -8288,6 +8786,8 @@ type SessionUpdateOptionsParams struct { ToolFilterPrecedence *OptionsUpdateToolFilterPrecedence `json:"toolFilterPrecedence,omitempty"` // Optional path for trajectory output. TrajectoryFile *string `json:"trajectoryFile,omitempty"` + // Output verbosity level for supported models. + Verbosity *Verbosity `json:"verbosity,omitempty"` // Absolute working-directory path for shell tools. WorkingDirectory *string `json:"workingDirectory,omitempty"` } @@ -8395,7 +8895,8 @@ type ShutdownRequest struct { Type *ShutdownType `json:"type,omitempty"` } -// Schema for the `Skill` type. +// Skill metadata available to a session, with name, description, source, enabled/invocable +// state, path, plugin, and argument hint. // Experimental: Skill is part of an experimental API and may change or be removed. type Skill struct { // Optional freeform hint describing the skill's expected arguments, from the @@ -8417,7 +8918,8 @@ type Skill struct { UserInvocable bool `json:"userInvocable"` } -// Schema for the `SkillDiscoveryPath` type. +// Canonical directory where skills can be discovered or created, with scope, preference, +// and optional project path. // Experimental: SkillDiscoveryPath is part of an experimental API and may change or be // removed. type SkillDiscoveryPath struct { @@ -8510,7 +9012,7 @@ type SkillsGetInvokedResult struct { Skills []SkillsInvokedSkill `json:"skills"` } -// Schema for the `SkillsInvokedSkill` type. +// Skill invocation record with name, path, content, allowed tools, and turn number. // Experimental: SkillsInvokedSkill is part of an experimental API and may change or be // removed. type SkillsInvokedSkill struct { @@ -8536,7 +9038,8 @@ type SkillsLoadDiagnostics struct { Warnings []string `json:"warnings"` } -// Schema for the `SlashCommandInfo` type. +// Slash-command metadata with name, aliases, description, kind, input hint, execution +// allowance, and schedulability. // Experimental: SlashCommandInfo is part of an experimental API and may change or be // removed. type SlashCommandInfo struct { @@ -8565,6 +9068,9 @@ type SlashCommandInfo struct { // Experimental: SlashCommandInput is part of an experimental API and may change or be // removed. type SlashCommandInput struct { + // Optional literal choices the input accepts, each with a human-facing description; clients + // may render these as selectable options + Choices []SlashCommandInputChoice `json:"choices,omitzero"` // Optional completion hint for the input (e.g. 'directory' for filesystem path completion) Completion *SlashCommandInputCompletion `json:"completion,omitempty"` // Hint to display when command input has not been provided @@ -8577,6 +9083,16 @@ type SlashCommandInput struct { Required *bool `json:"required,omitempty"` } +// A literal choice the command input accepts, with a human-facing description +// Experimental: SlashCommandInputChoice is part of an experimental API and may change or be +// removed. +type SlashCommandInputChoice struct { + // Human-readable description shown alongside the choice + Description string `json:"description"` + // The literal choice value (e.g. 'on', 'off', 'show') + Name string `json:"name"` +} + // Result of invoking the slash command (text output, prompt to send to the agent, // completion, or subcommand selection). // Experimental: SlashCommandInvocationResult is part of an experimental API and may change @@ -8596,7 +9112,8 @@ func (r RawSlashCommandInvocationResultData) Kind() SlashCommandInvocationResult return r.Discriminator } -// Schema for the `SlashCommandAgentPromptResult` type. +// Slash-command invocation result that submits an agent prompt, with display prompt, +// optional mode, and settings-change flag. // Experimental: SlashCommandAgentPromptResult is part of an experimental API and may change // or be removed. type SlashCommandAgentPromptResult struct { @@ -8616,7 +9133,8 @@ func (SlashCommandAgentPromptResult) Kind() SlashCommandInvocationResultKind { return SlashCommandInvocationResultKindAgentPrompt } -// Schema for the `SlashCommandCompletedResult` type. +// Slash-command invocation result indicating completion, with optional message and +// settings-change flag. // Experimental: SlashCommandCompletedResult is part of an experimental API and may change // or be removed. type SlashCommandCompletedResult struct { @@ -8632,7 +9150,8 @@ func (SlashCommandCompletedResult) Kind() SlashCommandInvocationResultKind { return SlashCommandInvocationResultKindCompleted } -// Schema for the `SlashCommandSelectSubcommandResult` type. +// Slash-command invocation result asking the client to present subcommand options for a +// parent command. // Experimental: SlashCommandSelectSubcommandResult is part of an experimental API and may // change or be removed. type SlashCommandSelectSubcommandResult struct { @@ -8652,7 +9171,7 @@ func (SlashCommandSelectSubcommandResult) Kind() SlashCommandInvocationResultKin return SlashCommandInvocationResultKindSelectSubcommand } -// Schema for the `SlashCommandTextResult` type. +// Slash-command invocation result containing text output plus Markdown/ANSI rendering flags. // Experimental: SlashCommandTextResult is part of an experimental API and may change or be // removed. type SlashCommandTextResult struct { @@ -8672,7 +9191,8 @@ func (SlashCommandTextResult) Kind() SlashCommandInvocationResultKind { return SlashCommandInvocationResultKindText } -// Schema for the `SlashCommandSelectSubcommandOption` type. +// Selectable slash-command subcommand option with name, description, and optional group +// label. // Experimental: SlashCommandSelectSubcommandOption is part of an experimental API and may // change or be removed. type SlashCommandSelectSubcommandOption struct { @@ -8711,7 +9231,7 @@ type SubagentSettingsEntry struct { Model *string `json:"model,omitempty"` } -// Schema for the `TaskInfo` type. +// Tracked task union returned by task APIs, containing either an agent task or a shell task. // Experimental: TaskInfo is part of an experimental API and may change or be removed. type TaskInfo interface { taskInfo() @@ -8728,7 +9248,8 @@ func (r RawTaskInfoData) Type() TaskInfoType { return r.Discriminator } -// Schema for the `TaskAgentInfo` type. +// Tracked background agent task metadata, including IDs, status, timing, agent type, +// prompt, model, result, and latest response. // Experimental: TaskAgentInfo is part of an experimental API and may change or be removed. type TaskAgentInfo struct { // ISO 8601 timestamp when the current active period began @@ -8777,7 +9298,8 @@ func (TaskAgentInfo) Type() TaskInfoType { return TaskInfoTypeAgent } -// Schema for the `TaskShellInfo` type. +// Tracked shell task metadata, including ID, command, status, timing, attachment/execution +// mode, log path, and PID. // Experimental: TaskShellInfo is part of an experimental API and may change or be removed. type TaskShellInfo struct { // Whether the shell runs inside a managed PTY session or as an independent background @@ -8833,7 +9355,8 @@ func (r RawTaskProgressData) Type() TaskProgressType { return r.Discriminator } -// Schema for the `TaskAgentProgress` type. +// Progress snapshot for an agent task, with recent activity lines and optional latest +// intent. // Experimental: TaskAgentProgress is part of an experimental API and may change or be // removed. type TaskAgentProgress struct { @@ -8848,7 +9371,8 @@ func (TaskAgentProgress) Type() TaskProgressType { return TaskProgressTypeAgent } -// Schema for the `TaskShellProgress` type. +// Progress snapshot for a shell task, with recent stdout/stderr output and optional process +// ID. // Experimental: TaskShellProgress is part of an experimental API and may change or be // removed. type TaskShellProgress struct { @@ -8863,7 +9387,7 @@ func (TaskShellProgress) Type() TaskProgressType { return TaskProgressTypeShell } -// Schema for the `TaskProgressLine` type. +// Timestamped display line for task progress output or recent agent activity. // Experimental: TaskProgressLine is part of an experimental API and may change or be // removed. type TaskProgressLine struct { @@ -9033,7 +9557,8 @@ type TelemetrySetFeatureOverridesRequest struct { Features map[string]string `json:"features"` } -// Schema for the `Tool` type. +// Built-in tool metadata with identifier, optional namespaced name, description, +// input-parameter schema, and usage instructions. // Experimental: Tool is part of an experimental API and may change or be removed. type Tool struct { // Description of what the tool does @@ -9095,7 +9620,8 @@ type UIElicitationArrayAnyOfFieldItems struct { AnyOf []UIElicitationArrayAnyOfFieldItemsAnyOf `json:"anyOf"` } -// Schema for the `UIElicitationArrayAnyOfFieldItemsAnyOf` type. +// Selectable option for a UI elicitation multi-select array item, with submitted value and +// display label. // Experimental: UIElicitationArrayAnyOfFieldItemsAnyOf is part of an experimental API and // may change or be removed. type UIElicitationArrayAnyOfFieldItemsAnyOf struct { @@ -9115,7 +9641,7 @@ type UIElicitationArrayEnumFieldItems struct { Type UIElicitationArrayEnumFieldItemsType `json:"type"` } -// Schema for the `UIElicitationFieldValue` type. +// Submitted UI elicitation field value: string, number, boolean, or an array of strings. // Experimental: UIElicitationFieldValue is part of an experimental API and may change or be // removed. type UIElicitationFieldValue interface { @@ -9354,7 +9880,8 @@ func (UIElicitationStringOneOfField) Type() UIElicitationSchemaPropertyType { return UIElicitationSchemaPropertyTypeString } -// Schema for the `UIElicitationStringOneOfFieldOneOf` type. +// Selectable option for a UI elicitation single-select string field, with submitted value +// and display label. // Experimental: UIElicitationStringOneOfFieldOneOf is part of an experimental API and may // change or be removed. type UIElicitationStringOneOfFieldOneOf struct { @@ -9392,7 +9919,8 @@ type UIEphemeralQueryResult struct { Answer string `json:"answer"` } -// Schema for the `UIExitPlanModeResponse` type. +// User response for a pending exit-plan-mode request, with approval state, selected action, +// auto-approve flag, and feedback. // Experimental: UIExitPlanModeResponse is part of an experimental API and may change or be // removed. type UIExitPlanModeResponse struct { @@ -9435,7 +9963,8 @@ type UIHandlePendingElicitationRequest struct { type UIHandlePendingExitPlanModeRequest struct { // The unique request ID from the exit_plan_mode.requested event RequestID string `json:"requestId"` - // Schema for the `UIExitPlanModeResponse` type. + // User response for a pending exit-plan-mode request, with approval state, selected action, + // auto-approve flag, and feedback. Response UIExitPlanModeResponse `json:"response"` } @@ -9485,7 +10014,8 @@ type UIHandlePendingSessionLimitsExhaustedRequest struct { type UIHandlePendingUserInputRequest struct { // The unique request ID from the user_input.requested event RequestID string `json:"requestId"` - // Schema for the `UIUserInputResponse` type. + // User response for a pending user-input request, with answer text and whether it was typed + // freeform. Response UIUserInputResponse `json:"response"` } @@ -9532,7 +10062,8 @@ type UIUnregisterDirectAutoModeSwitchHandlerResult struct { Unregistered bool `json:"unregistered"` } -// Schema for the `UIUserInputResponse` type. +// User response for a pending user-input request, with answer text and whether it was typed +// freeform. // Experimental: UIUserInputResponse is part of an experimental API and may change or be // removed. type UIUserInputResponse struct { @@ -9595,7 +10126,8 @@ type UsageMetricsCodeChanges struct { LinesRemoved int64 `json:"linesRemoved"` } -// Schema for the `UsageMetricsModelMetric` type. +// Per-model usage metrics, including request counts/costs, token usage, nano-AI units, and +// per-token-type details. // Experimental: UsageMetricsModelMetric is part of an experimental API and may change or be // removed. type UsageMetricsModelMetric struct { @@ -9619,7 +10151,7 @@ type UsageMetricsModelMetricRequests struct { Count int64 `json:"count"` } -// Schema for the `UsageMetricsModelMetricTokenDetail` type. +// Per-model token-detail entry containing the accumulated token count for one token type. // Experimental: UsageMetricsModelMetricTokenDetail is part of an experimental API and may // change or be removed. type UsageMetricsModelMetricTokenDetail struct { @@ -9643,7 +10175,7 @@ type UsageMetricsModelMetricUsage struct { ReasoningTokens *int64 `json:"reasoningTokens,omitempty"` } -// Schema for the `UsageMetricsTokenDetail` type. +// Session-wide token-detail entry containing the accumulated token count for one token type. // Experimental: UsageMetricsTokenDetail is part of an experimental API and may change or be // removed. type UsageMetricsTokenDetail struct { @@ -9736,7 +10268,7 @@ func (r RawUserToolSessionApprovalData) Kind() UserToolSessionApprovalKind { return r.Discriminator } -// Schema for the `UserToolSessionApprovalCommands` type. +// Session-scoped tool-approval rule for specific shell command identifiers. // Experimental: UserToolSessionApprovalCommands is part of an experimental API and may // change or be removed. type UserToolSessionApprovalCommands struct { @@ -9749,7 +10281,7 @@ func (UserToolSessionApprovalCommands) Kind() UserToolSessionApprovalKind { return UserToolSessionApprovalKindCommands } -// Schema for the `UserToolSessionApprovalCustomTool` type. +// Session-scoped tool-approval rule for a custom tool, keyed by tool name. // Experimental: UserToolSessionApprovalCustomTool is part of an experimental API and may // change or be removed. type UserToolSessionApprovalCustomTool struct { @@ -9762,7 +10294,8 @@ func (UserToolSessionApprovalCustomTool) Kind() UserToolSessionApprovalKind { return UserToolSessionApprovalKindCustomTool } -// Schema for the `UserToolSessionApprovalExtensionManagement` type. +// Session-scoped tool-approval rule for extension-management operations, optionally +// narrowed by operation. // Experimental: UserToolSessionApprovalExtensionManagement is part of an experimental API // and may change or be removed. type UserToolSessionApprovalExtensionManagement struct { @@ -9775,7 +10308,8 @@ func (UserToolSessionApprovalExtensionManagement) Kind() UserToolSessionApproval return UserToolSessionApprovalKindExtensionManagement } -// Schema for the `UserToolSessionApprovalExtensionPermissionAccess` type. +// Session-scoped tool-approval rule for an extension's permission-gated capability access, +// keyed by extension name. // Experimental: UserToolSessionApprovalExtensionPermissionAccess is part of an experimental // API and may change or be removed. type UserToolSessionApprovalExtensionPermissionAccess struct { @@ -9788,7 +10322,8 @@ func (UserToolSessionApprovalExtensionPermissionAccess) Kind() UserToolSessionAp return UserToolSessionApprovalKindExtensionPermissionAccess } -// Schema for the `UserToolSessionApprovalMcp` type. +// Session-scoped tool-approval rule for an MCP server tool, or all tools on the server when +// `toolName` is null. // Experimental: UserToolSessionApprovalMCP is part of an experimental API and may change or // be removed. type UserToolSessionApprovalMCP struct { @@ -9803,7 +10338,7 @@ func (UserToolSessionApprovalMCP) Kind() UserToolSessionApprovalKind { return UserToolSessionApprovalKindMCP } -// Schema for the `UserToolSessionApprovalMemory` type. +// Session-scoped tool-approval rule for writes to long-term memory. // Experimental: UserToolSessionApprovalMemory is part of an experimental API and may change // or be removed. type UserToolSessionApprovalMemory struct { @@ -9814,7 +10349,7 @@ func (UserToolSessionApprovalMemory) Kind() UserToolSessionApprovalKind { return UserToolSessionApprovalKindMemory } -// Schema for the `UserToolSessionApprovalRead` type. +// Session-scoped tool-approval rule for read-only filesystem operations. // Experimental: UserToolSessionApprovalRead is part of an experimental API and may change // or be removed. type UserToolSessionApprovalRead struct { @@ -9825,7 +10360,7 @@ func (UserToolSessionApprovalRead) Kind() UserToolSessionApprovalKind { return UserToolSessionApprovalKindRead } -// Schema for the `UserToolSessionApprovalWrite` type. +// Session-scoped tool-approval rule for filesystem write operations. // Experimental: UserToolSessionApprovalWrite is part of an experimental API and may change // or be removed. type UserToolSessionApprovalWrite struct { @@ -9908,7 +10443,8 @@ type WorkspaceDiffResult struct { RequestedMode WorkspaceDiffMode `json:"requestedMode"` } -// Schema for the `WorkspacesCheckpoints` type. +// Workspace checkpoint metadata with assigned number, human-readable title, and checkpoint +// filename. // Experimental: WorkspacesCheckpoints is part of an experimental API and may change or be // removed. type WorkspacesCheckpoints struct { @@ -10364,6 +10900,67 @@ const ( CopilotAPITokenAuthInfoHostHTTPSGitHubCom CopilotAPITokenAuthInfoHost = "https://github.com" ) +// Kind discriminator for DebugCollectLogsDestination. +type DebugCollectLogsDestinationKind string + +const ( + DebugCollectLogsDestinationKindArchive DebugCollectLogsDestinationKind = "archive" + DebugCollectLogsDestinationKindDirectory DebugCollectLogsDestinationKind = "directory" +) + +// Kind of caller-provided debug log entry. +// Experimental: DebugCollectLogsEntryKind is part of an experimental API and may change or +// be removed. +type DebugCollectLogsEntryKind string + +const ( + // Include files from a server-local directory recursively. + DebugCollectLogsEntryKindDirectory DebugCollectLogsEntryKind = "directory" + // Include a single server-local file. + DebugCollectLogsEntryKindFile DebugCollectLogsEntryKind = "file" +) + +// How a collected debug entry should be redacted before being staged. +// Experimental: DebugCollectLogsRedaction is part of an experimental API and may change or +// be removed. +type DebugCollectLogsRedaction string + +const ( + // Redact each non-empty line as a session event JSON object, falling back to plain-text + // redaction for malformed lines. + DebugCollectLogsRedactionEventsJsonl DebugCollectLogsRedaction = "events-jsonl" + // Redact the file as plain UTF-8 log text. + DebugCollectLogsRedactionPlainText DebugCollectLogsRedaction = "plain-text" +) + +// Destination kind that was written. +// Experimental: DebugCollectLogsResultKind is part of an experimental API and may change or +// be removed. +type DebugCollectLogsResultKind string + +const ( + // A .tgz archive was written. + DebugCollectLogsResultKindArchive DebugCollectLogsResultKind = "archive" + // A directory containing redacted files was written. + DebugCollectLogsResultKindDirectory DebugCollectLogsResultKind = "directory" +) + +// Source category for a collected debug bundle entry. +// Experimental: DebugCollectLogsSource is part of an experimental API and may change or be +// removed. +type DebugCollectLogsSource string + +const ( + // Caller-provided diagnostic entry. + DebugCollectLogsSourceAdditional DebugCollectLogsSource = "additional" + // Session event log. + DebugCollectLogsSourceEvents DebugCollectLogsSource = "events" + // Process log for the session. + DebugCollectLogsSourceProcessLog DebugCollectLogsSource = "process-log" + // Interactive shell log for the session. + DebugCollectLogsSourceShellLog DebugCollectLogsSource = "shell-log" +) + // Server transport type: stdio, http, sse (deprecated), or memory // Experimental: DiscoveredMCPServerType is part of an experimental API and may change or be // removed. @@ -11056,6 +11653,21 @@ const ( PermissionLocationTypeRepo PermissionLocationType = "repo" ) +// Current or requested allow-all mode. +// Experimental: PermissionsAllowAllMode is part of an experimental API and may change or be +// removed. +type PermissionsAllowAllMode string + +const ( + // Permission requests follow the normal approval flow with an LLM advisory recommendation + // attached; clients may choose to auto-approve requests the judge evaluated as acceptable. + PermissionsAllowAllModeAuto PermissionsAllowAllMode = "auto" + // Permission requests follow the normal approval flow. + PermissionsAllowAllModeOff PermissionsAllowAllMode = "off" + // Tool, path, and URL permission requests are automatically approved. + PermissionsAllowAllModeOn PermissionsAllowAllMode = "on" +) + // Allowed values for the `PermissionsConfigureAdditionalContentExclusionPolicyScope` // enumeration. // Experimental: PermissionsConfigureAdditionalContentExclusionPolicyScope is part of an @@ -11524,6 +12136,53 @@ const ( SessionOpenParamsKindResumeLast SessionOpenParamsKind = "resumeLast" ) +// Rust-owned settings predicates exposed across the SDK boundary. Raw feature-flag names +// are intentionally not part of the contract. +// Experimental: SessionSettingsPredicateName is part of an experimental API and may change +// or be removed. +type SessionSettingsPredicateName string + +const ( + // Whether Claude Opus token-limit caps should be applied. + SessionSettingsPredicateNameCapClaudeOpusTokenLimitsEnabled SessionSettingsPredicateName = "capClaudeOpusTokenLimitsEnabled" + // Whether CCA should use the TypeScript autofind behavior. + SessionSettingsPredicateNameCcaUseTsAutofindEnabled SessionSettingsPredicateName = "ccaUseTsAutofindEnabled" + // Whether Chronicle integration is enabled. + SessionSettingsPredicateNameChronicleEnabled SessionSettingsPredicateName = "chronicleEnabled" + // Whether the co-author hook is enabled. + SessionSettingsPredicateNameCoAuthorHookEnabled SessionSettingsPredicateName = "coAuthorHookEnabled" + // Whether the CodeQL checker is enabled. + SessionSettingsPredicateNameCodeqlCheckerEnabled SessionSettingsPredicateName = "codeqlCheckerEnabled" + // Whether code-review behavior is enabled. + SessionSettingsPredicateNameCodeReviewFeatureEnabled SessionSettingsPredicateName = "codeReviewFeatureEnabled" + // Whether content-exclusion policy may self-fetch data. + SessionSettingsPredicateNameContentExclusionSelfFetchEnabled SessionSettingsPredicateName = "contentExclusionSelfFetchEnabled" + // Whether the Dependabot checker is enabled. + SessionSettingsPredicateNameDependabotCheckerEnabled SessionSettingsPredicateName = "dependabotCheckerEnabled" + // Whether the dependency checker is enabled. + SessionSettingsPredicateNameDependencyCheckerEnabled SessionSettingsPredicateName = "dependencyCheckerEnabled" + // Whether validation may run in parallel. + SessionSettingsPredicateNameParallelValidationEnabled SessionSettingsPredicateName = "parallelValidationEnabled" + // Whether runtime timing telemetry is enabled. + SessionSettingsPredicateNameRuntimeTimingTelemetryEnabled SessionSettingsPredicateName = "runtimeTimingTelemetryEnabled" + // Whether the security-tools feature flag enables security tool wiring. + SessionSettingsPredicateNameSecurityToolsEnabled SessionSettingsPredicateName = "securityToolsEnabled" + // Whether third-party security tools should receive the security prompt. + SessionSettingsPredicateNameThirdPartySecurityPromptEnabled SessionSettingsPredicateName = "thirdPartySecurityPromptEnabled" + // Whether trivial-change handling is enabled. + SessionSettingsPredicateNameTrivialChangeEnabled SessionSettingsPredicateName = "trivialChangeEnabled" + // Whether trivial-change handling is enabled for code review. + SessionSettingsPredicateNameTrivialChangeEnabledForCodeReview SessionSettingsPredicateName = "trivialChangeEnabledForCodeReview" + // Whether trivial-change handling is enabled for a specific tool. + SessionSettingsPredicateNameTrivialChangeEnabledForTool SessionSettingsPredicateName = "trivialChangeEnabledForTool" + // Whether trivial-change skip behavior is enabled. + SessionSettingsPredicateNameTrivialChangeSkipEnabled SessionSettingsPredicateName = "trivialChangeSkipEnabled" + // Whether trivial-change skip behavior is enabled for code review. + SessionSettingsPredicateNameTrivialChangeSkipEnabledForCodeReview SessionSettingsPredicateName = "trivialChangeSkipEnabledForCodeReview" + // Whether trivial-change skip behavior is enabled for a specific tool. + SessionSettingsPredicateNameTrivialChangeSkipEnabledForTool SessionSettingsPredicateName = "trivialChangeSkipEnabledForTool" +) + // Task type determines the handoff strategy (CCA fetches events; CLI prepares a transient // session). // Experimental: SessionsOpenHandoffTaskType is part of an experimental API and may change @@ -11922,6 +12581,19 @@ const ( UserToolSessionApprovalKindWrite UserToolSessionApprovalKind = "write" ) +// Output verbosity level for supported models +// Experimental: Verbosity is part of an experimental API and may change or be removed. +type Verbosity string + +const ( + // Request a more detailed response. + VerbosityHigh Verbosity = "high" + // Request a terse response. + VerbosityLow Verbosity = "low" + // Request a medium amount of response detail. + VerbosityMedium Verbosity = "medium" +) + // Type of change represented by this file diff. // Experimental: WorkspaceDiffFileChangeType is part of an experimental API and may change // or be removed. @@ -13550,33 +14222,6 @@ func (a *InternalServerSessionsAPI) GetPersistedRemoteSteerable(ctx context.Cont return &result, nil } -// PollSpawnedSessions cursor-based long-poll for sessions spawned by the runtime (e.g. in -// response to a Mission Control `start_session` command). The cursor is an opaque token; -// pass it back to receive only spawn events that occurred AFTER the cursor was issued. Omit -// the cursor on the first call to receive any events buffered since the runtime started. -// Internal: this is a CLI background-daemon plumbing primitive. SDK consumers that need to -// react to runtime-spawned sessions should subscribe to a higher-level event stream rather -// than driving a long-poll loop. -// -// RPC method: sessions.pollSpawnedSessions. -// -// Parameters: Cursor and optional long-poll wait for polling runtime-spawned sessions. -// -// Returns: Batch of spawn events plus a cursor for follow-up polls. -// Internal: PollSpawnedSessions is part of the SDK's internal handshake/plumbing; external -// callers should not use it. -func (a *InternalServerSessionsAPI) PollSpawnedSessions(ctx context.Context, params *SessionsPollSpawnedSessionsRequest) (*PollSpawnedSessionsResult, error) { - raw, err := a.client.Request(ctx, "sessions.pollSpawnedSessions", params) - if err != nil { - return nil, err - } - var result PollSpawnedSessionsResult - if err := json.Unmarshal(raw, &result); err != nil { - return nil, err - } - return &result, nil -} - // RegisterExtensionToolsOnSession registers extension-provided tools on the given session, // gated by an optional `enabled` callback. Returns an opaque unsubscribe function the // caller must invoke to deregister the tools when the extension is torn down. Marked @@ -13622,7 +14267,8 @@ type InternalServerRPC struct { // // RPC method: connect. // -// Parameters: Optional connection token presented by the SDK client during the handshake. +// Parameters: Parameters for the `server.connect` handshake: an optional connection token +// and optional connection-level opt-ins (e.g. GitHub telemetry forwarding). // // Returns: Handshake result reporting the server's protocol version and package version on // success. @@ -14091,6 +14737,41 @@ func (a *CompletionsAPI) Request(ctx context.Context, params *CompletionsRequest return &result, nil } +// Experimental: DebugAPI contains experimental APIs that may change or be removed. +type DebugAPI sessionAPI + +// CollectLogs collects a redacted session debug log bundle into a local archive or staging +// directory. The runtime includes session-owned logs by default and accepts caller-provided +// diagnostic entries so host applications can add their own files without changing this API +// shape. +// +// RPC method: session.debug.collectLogs. +// +// Parameters: Options for collecting a redacted session debug bundle. +// +// Returns: Result of collecting a redacted debug bundle. +func (a *DebugAPI) CollectLogs(ctx context.Context, params *DebugCollectLogsRequest) (*DebugCollectLogsResult, error) { + req := map[string]any{"sessionId": a.sessionID} + if params != nil { + if params.AdditionalEntries != nil { + req["additionalEntries"] = params.AdditionalEntries + } + req["destination"] = params.Destination + if params.Include != nil { + req["include"] = *params.Include + } + } + raw, err := a.client.Request(ctx, "session.debug.collectLogs", req) + if err != nil { + return nil, err + } + var result DebugCollectLogsResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + // Experimental: EventLogAPI contains experimental APIs that may change or be removed. type EventLogAPI sessionAPI @@ -15113,6 +15794,58 @@ func (a *MetadataAPI) ContextInfo(ctx context.Context, params *MetadataContextIn return &result, nil } +// GetContextAttribution returns the experimental per-source attribution breakdown of the +// session's current context window as a flat list of entries (skills, subagents, MCP +// servers, built-in tools, plugin rollups, system/tool-definition costs, with nesting via +// parentId), plus the successful compaction count. The heaviest individual messages are +// available separately via `metadata.getContextHeaviestMessages`. Returns null until the +// session has initialized its system prompt and tool metadata. +// +// RPC method: session.metadata.getContextAttribution. +// +// Returns: Per-source attribution breakdown for the session's current context window, or +// null if uninitialized. +func (a *MetadataAPI) GetContextAttribution(ctx context.Context) (*MetadataContextAttributionResult, error) { + req := map[string]any{"sessionId": a.sessionID} + raw, err := a.client.Request(ctx, "session.metadata.getContextAttribution", req) + if err != nil { + return nil, err + } + var result MetadataContextAttributionResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +// GetContextHeaviestMessages returns the largest individual messages currently in the +// session's context window, most-expensive first. Companion to +// `metadata.getContextAttribution`. Returns an empty list until the session has initialized. +// +// RPC method: session.metadata.getContextHeaviestMessages. +// +// Parameters: Parameters for the heaviest-messages query. +// +// Returns: The heaviest individual messages in the session's context window, most-expensive +// first. +func (a *MetadataAPI) GetContextHeaviestMessages(ctx context.Context, params *MetadataContextHeaviestMessagesRequest) (*MetadataContextHeaviestMessagesResult, error) { + req := map[string]any{"sessionId": a.sessionID} + if params != nil { + if params.Limit != nil { + req["limit"] = *params.Limit + } + } + raw, err := a.client.Request(ctx, "session.metadata.getContextHeaviestMessages", req) + if err != nil { + return nil, err + } + var result MetadataContextHeaviestMessagesResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + // IsProcessing reports whether the local session is currently processing user/agent // messages. // @@ -15378,6 +16111,9 @@ func (a *ModelAPI) SwitchTo(ctx context.Context, params *ModelSwitchToRequest) ( if params.ReasoningSummary != nil { req["reasoningSummary"] = *params.ReasoningSummary } + if params.Verbosity != nil { + req["verbosity"] = *params.Verbosity + } } raw, err := a.client.Request(ctx, "session.model.switchTo", req) if err != nil { @@ -15626,6 +16362,9 @@ func (a *OptionsAPI) Update(ctx context.Context, params *SessionUpdateOptionsPar if params.TrajectoryFile != nil { req["trajectoryFile"] = *params.TrajectoryFile } + if params.Verbosity != nil { + req["verbosity"] = *params.Verbosity + } if params.WorkingDirectory != nil { req["workingDirectory"] = *params.WorkingDirectory } @@ -15686,12 +16425,11 @@ func (a *PermissionsAPI) Configure(ctx context.Context, params *PermissionsConfi return &result, nil } -// GetAllowAll returns whether full allow-all permissions are currently active for the -// session. +// GetAllowAll returns the current allow-all permission mode for the session. // // RPC method: session.permissions.getAllowAll. // -// Returns: Current full allow-all permission state. +// Returns: Current allow-all permission mode. func (a *PermissionsAPI) GetAllowAll(ctx context.Context) (*AllowAllPermissionState, error) { req := map[string]any{"sessionId": a.sessionID} raw, err := a.client.Request(ctx, "session.permissions.getAllowAll", req) @@ -15826,23 +16564,31 @@ func (a *PermissionsAPI) ResetSessionApprovals(ctx context.Context) (*Permission return &result, nil } -// SetAllowAll enables or disables full allow-all permissions (tools, paths, and URLs) for -// the session. Used by attach-mode clients (e.g. LocalRpcSession's `/allow-all` forwarder) -// to flip the target session's permission state. Unlike `setApproveAll`, this swaps in the -// unrestricted path and URL managers and emits `session.permissions_changed` on transition. -// The result returns the authoritative post-mutation state so callers can update their -// local mirrors without racing the `session.permissions_changed` notification on the same -// wire. +// SetAllowAll sets the allow-all permission mode for the session. Used by attach-mode +// clients (e.g. LocalRpcSession's `/allow-all` forwarder) to flip the target session's +// permission state. The `on` mode swaps in unrestricted path and URL managers and emits +// `session.permissions_changed` on transition; the `auto` mode keeps normal prompt paths +// active while attaching LLM safety recommendations. The result returns the authoritative +// post-mutation state so callers can update their local mirrors without racing the +// `session.permissions_changed` notification on the same wire. // // RPC method: session.permissions.setAllowAll. // -// Parameters: Whether to enable full allow-all permissions for the session. +// Parameters: Allow-all mode to apply for the session. // // Returns: Indicates whether the operation succeeded and reports the post-mutation state. func (a *PermissionsAPI) SetAllowAll(ctx context.Context, params *PermissionsSetAllowAllRequest) (*AllowAllPermissionSetResult, error) { req := map[string]any{"sessionId": a.sessionID} if params != nil { - req["enabled"] = params.Enabled + if params.Enabled != nil { + req["enabled"] = *params.Enabled + } + if params.Mode != nil { + req["mode"] = *params.Mode + } + if params.Model != nil { + req["model"] = *params.Model + } if params.Source != nil { req["source"] = *params.Source } @@ -16339,6 +17085,9 @@ func (a *PluginsAPI) Reload(ctx context.Context, params ...*PluginsReloadRequest if requestParams.ReloadCustomAgents != nil { req["reloadCustomAgents"] = *requestParams.ReloadCustomAgents } + if requestParams.ReloadExtensions != nil { + req["reloadExtensions"] = *requestParams.ReloadExtensions + } if requestParams.ReloadHooks != nil { req["reloadHooks"] = *requestParams.ReloadHooks } @@ -17749,6 +18498,7 @@ type SessionRPC struct { Canvas *CanvasAPI Commands *CommandsAPI Completions *CompletionsAPI + Debug *DebugAPI EventLog *EventLogAPI Extensions *ExtensionsAPI Fleet *FleetAPI @@ -17962,6 +18712,7 @@ func NewSessionRPC(client *jsonrpc2.Client, sessionID string) *SessionRPC { r.Canvas = (*CanvasAPI)(&r.common) r.Commands = (*CommandsAPI)(&r.common) r.Completions = (*CompletionsAPI)(&r.common) + r.Debug = (*DebugAPI)(&r.common) r.EventLog = (*EventLogAPI)(&r.common) r.Extensions = (*ExtensionsAPI)(&r.common) r.Fleet = (*FleetAPI)(&r.common) @@ -18161,44 +18912,64 @@ func (a *InternalMCPAPI) UnregisterExternalClient(ctx context.Context, params *M return &result, nil } -// Experimental: InternalMCPOauthAPI contains experimental APIs that may change or be +// Experimental: InternalSettingsAPI contains experimental APIs that may change or be // removed. -type InternalMCPOauthAPI internalSessionAPI +type InternalSettingsAPI internalSessionAPI -// Responds to a pending MCP OAuth request with an in-process provider. This internal -// CLI-only API accepts a live OAuthClientProvider instance and cannot be used over the SDK -// JSON-RPC boundary. Use session.mcp.oauth.handlePendingRequest instead for the public -// SDK-safe response path. +// EvaluatePredicate evaluates a named Rust-owned settings predicate without exposing raw +// feature flags. Internal: the raw feature-flag names and composition are runtime-internal, +// so this predicate-evaluation helper is kept out of the public SDK surface and is callable +// in-process only. // -// RPC method: session.mcp.oauth.respond. +// RPC method: session.settings.evaluatePredicate. // -// Parameters: MCP OAuth request id and optional provider response. +// Parameters: Named Rust-owned settings predicate to evaluate for this session. // -// Returns: Empty result after recording the MCP OAuth response. -// Internal: Respond is part of the SDK's internal handshake/plumbing; external callers -// should not use it. -func (a *InternalMCPOauthAPI) Respond(ctx context.Context, params *MCPOauthRespondRequest) (*MCPOauthRespondResult, error) { +// Returns: Result of evaluating a Rust-owned settings predicate. +// Internal: EvaluatePredicate is part of the SDK's internal handshake/plumbing; external +// callers should not use it. +func (a *InternalSettingsAPI) EvaluatePredicate(ctx context.Context, params *SessionSettingsEvaluatePredicateRequest) (*SessionSettingsEvaluatePredicateResult, error) { req := map[string]any{"sessionId": a.sessionID} if params != nil { - if params.Provider != nil { - req["provider"] = params.Provider + req["name"] = params.Name + if params.ToolName != nil { + req["toolName"] = *params.ToolName } - req["requestId"] = params.RequestID } - raw, err := a.client.Request(ctx, "session.mcp.oauth.respond", req) + raw, err := a.client.Request(ctx, "session.settings.evaluatePredicate", req) if err != nil { return nil, err } - var result MCPOauthRespondResult + var result SessionSettingsEvaluatePredicateResult if err := json.Unmarshal(raw, &result); err != nil { return nil, err } return &result, nil } -// Experimental: Oauth returns experimental APIs that may change or be removed. -func (s *InternalMCPAPI) Oauth() *InternalMCPOauthAPI { - return (*InternalMCPOauthAPI)(s) +// Snapshot returns a redacted snapshot of session runtime settings, with secrets and raw +// feature flags excluded. Internal: the runtime settings shape is a runtime-internal +// surface and is deliberately kept out of the public SDK, because consumers should not +// depend on the runtime's internal settings layout. It remains callable in-process and is +// expected to be reworked as the runtime internals are consolidated. +// +// RPC method: session.settings.snapshot. +// +// Returns: Redacted, serializable view of session runtime settings for SDK boundary +// consumers. Secrets and raw feature flags are intentionally excluded. +// Internal: Snapshot is part of the SDK's internal handshake/plumbing; external callers +// should not use it. +func (a *InternalSettingsAPI) Snapshot(ctx context.Context) (*SessionSettingsSnapshot, error) { + req := map[string]any{"sessionId": a.sessionID} + raw, err := a.client.Request(ctx, "session.settings.snapshot", req) + if err != nil { + return nil, err + } + var result SessionSettingsSnapshot + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil } // InternalSessionRPC provides internal SDK session-scoped RPC methods (handshake helpers @@ -18207,13 +18978,15 @@ type InternalSessionRPC struct { // Reuse a single struct instead of allocating one for each service on the heap. common internalSessionAPI - MCP *InternalMCPAPI + MCP *InternalMCPAPI + Settings *InternalSettingsAPI } func NewInternalSessionRPC(client *jsonrpc2.Client, sessionID string) *InternalSessionRPC { r := &InternalSessionRPC{} r.common = internalSessionAPI{client: client, sessionID: sessionID} r.MCP = (*InternalMCPAPI)(&r.common) + r.Settings = (*InternalSettingsAPI)(&r.common) return r } @@ -18714,14 +19487,16 @@ func RegisterClientSessionAPIHandlers(client *jsonrpc2.Client, getHandlers func( // removed. type GitHubTelemetryHandler interface { // Event forwards a single GitHub telemetry event to a host connection that opted into - // telemetry forwarding for the session. + // 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). // // RPC method: gitHubTelemetry.event. // // Parameters: Payload for a `gitHubTelemetry.event` notification: a single GitHub telemetry - // event the runtime forwards to a host connection that opted into telemetry forwarding for - // the session. - Event(request *GitHubTelemetryNotification) (*GitHubTelemetryEventResult, error) + // event the runtime forwards to a host connection that opted into telemetry forwarding + // during the `server.connect` handshake. + Event(request *GitHubTelemetryNotification) error } // Experimental: LlmInferenceHandler contains experimental APIs that may change or be @@ -18784,17 +19559,12 @@ func RegisterClientGlobalAPIHandlers(client *jsonrpc2.Client, handlers *ClientGl return nil, &jsonrpc2.Error{Code: -32602, Message: fmt.Sprintf("Invalid params: %v", err)} } if handlers == nil || handlers.GitHubTelemetry == nil { - return nil, &jsonrpc2.Error{Code: -32603, Message: "No gitHubTelemetry client-global handler registered"} + return nil, nil } - result, err := handlers.GitHubTelemetry.Event(&request) - if err != nil { + if err := handlers.GitHubTelemetry.Event(&request); err != nil { return nil, clientGlobalHandlerError(err) } - raw, err := json.Marshal(result) - if err != nil { - return nil, &jsonrpc2.Error{Code: -32603, Message: fmt.Sprintf("Failed to marshal response: %v", err)} - } - return raw, nil + return nil, nil }) client.SetRequestHandler("llmInference.httpRequestChunk", func(params json.RawMessage) (json.RawMessage, *jsonrpc2.Error) { var request LlmInferenceHTTPRequestChunkRequest diff --git a/go/rpc/zrpc_encoding.go b/go/rpc/zrpc_encoding.go index aeccd99805..89bd609a65 100644 --- a/go/rpc/zrpc_encoding.go +++ b/go/rpc/zrpc_encoding.go @@ -669,6 +669,91 @@ func (r *CommandsRespondToQueuedCommandRequest) UnmarshalJSON(data []byte) error return nil } +func unmarshalDebugCollectLogsDestination(data []byte) (DebugCollectLogsDestination, error) { + if string(data) == "null" { + return nil, nil + } + type rawUnion struct { + Kind DebugCollectLogsDestinationKind `json:"kind"` + } + var raw rawUnion + if err := json.Unmarshal(data, &raw); err != nil { + return nil, err + } + + switch raw.Kind { + case DebugCollectLogsDestinationKindArchive: + var d DebugCollectLogsDestinationArchive + if err := json.Unmarshal(data, &d); err != nil { + return nil, err + } + return &d, nil + case DebugCollectLogsDestinationKindDirectory: + var d DebugCollectLogsDestinationDirectory + if err := json.Unmarshal(data, &d); err != nil { + return nil, err + } + return &d, nil + default: + return &RawDebugCollectLogsDestinationData{Discriminator: raw.Kind, Raw: data}, nil + } +} + +func (r RawDebugCollectLogsDestinationData) MarshalJSON() ([]byte, error) { + if r.Raw != nil { + return r.Raw, nil + } + return json.Marshal(struct { + Kind DebugCollectLogsDestinationKind `json:"kind"` + }{ + Kind: r.Discriminator, + }) +} + +func (r DebugCollectLogsDestinationArchive) MarshalJSON() ([]byte, error) { + type alias DebugCollectLogsDestinationArchive + return json.Marshal(struct { + Kind DebugCollectLogsDestinationKind `json:"kind"` + alias + }{ + Kind: r.Kind(), + alias: alias(r), + }) +} + +func (r DebugCollectLogsDestinationDirectory) MarshalJSON() ([]byte, error) { + type alias DebugCollectLogsDestinationDirectory + return json.Marshal(struct { + Kind DebugCollectLogsDestinationKind `json:"kind"` + alias + }{ + Kind: r.Kind(), + alias: alias(r), + }) +} + +func (r *DebugCollectLogsRequest) UnmarshalJSON(data []byte) error { + type rawDebugCollectLogsRequest struct { + AdditionalEntries []DebugCollectLogsEntry `json:"additionalEntries,omitzero"` + Destination json.RawMessage `json:"destination"` + Include *DebugCollectLogsInclude `json:"include,omitempty"` + } + var raw rawDebugCollectLogsRequest + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + r.AdditionalEntries = raw.AdditionalEntries + if raw.Destination != nil { + value, err := unmarshalDebugCollectLogsDestination(raw.Destination) + if err != nil { + return err + } + r.Destination = value + } + r.Include = raw.Include + return nil +} + func (r EventLogTypes) MarshalJSON() ([]byte, error) { if r.String != nil { return json.Marshal(r.String) @@ -931,6 +1016,7 @@ func (r *ExternalToolTextResultForLlm) UnmarshalJSON(data []byte) error { ResultType *string `json:"resultType,omitempty"` SessionLog *string `json:"sessionLog,omitempty"` TextResultForLlm string `json:"textResultForLlm"` + ToolReferences []string `json:"toolReferences,omitzero"` ToolTelemetry map[string]any `json:"toolTelemetry,omitzero"` } var raw rawExternalToolTextResultForLlm @@ -952,6 +1038,7 @@ func (r *ExternalToolTextResultForLlm) UnmarshalJSON(data []byte) error { r.ResultType = raw.ResultType r.SessionLog = raw.SessionLog r.TextResultForLlm = raw.TextResultForLlm + r.ToolReferences = raw.ToolReferences r.ToolTelemetry = raw.ToolTelemetry return nil } @@ -3242,6 +3329,7 @@ func (r *SessionOpenOptions) UnmarshalJSON(data []byte) error { DisabledInstructionSources []string `json:"disabledInstructionSources,omitzero"` DisabledSkills []string `json:"disabledSkills,omitzero"` EnableCitations *bool `json:"enableCitations,omitempty"` + EnableManagedSettings *bool `json:"enableManagedSettings,omitempty"` EnableOnDemandInstructionDiscovery *bool `json:"enableOnDemandInstructionDiscovery,omitempty"` EnableScriptSafety *bool `json:"enableScriptSafety,omitempty"` EnableStreaming *bool `json:"enableStreaming,omitempty"` @@ -3279,6 +3367,7 @@ func (r *SessionOpenOptions) UnmarshalJSON(data []byte) error { SkillDirectories []string `json:"skillDirectories,omitzero"` SkipCustomInstructions *bool `json:"skipCustomInstructions,omitempty"` TrajectoryFile *string `json:"trajectoryFile,omitempty"` + Verbosity *Verbosity `json:"verbosity,omitempty"` WorkingDirectory *string `json:"workingDirectory,omitempty"` WorkingDirectoryContext *SessionContext `json:"workingDirectoryContext,omitempty"` } @@ -3311,6 +3400,7 @@ func (r *SessionOpenOptions) UnmarshalJSON(data []byte) error { r.DisabledInstructionSources = raw.DisabledInstructionSources r.DisabledSkills = raw.DisabledSkills r.EnableCitations = raw.EnableCitations + r.EnableManagedSettings = raw.EnableManagedSettings r.EnableOnDemandInstructionDiscovery = raw.EnableOnDemandInstructionDiscovery r.EnableScriptSafety = raw.EnableScriptSafety r.EnableStreaming = raw.EnableStreaming @@ -3348,6 +3438,7 @@ func (r *SessionOpenOptions) UnmarshalJSON(data []byte) error { r.SkillDirectories = raw.SkillDirectories r.SkipCustomInstructions = raw.SkipCustomInstructions r.TrajectoryFile = raw.TrajectoryFile + r.Verbosity = raw.Verbosity r.WorkingDirectory = raw.WorkingDirectory r.WorkingDirectoryContext = raw.WorkingDirectoryContext return nil diff --git a/go/rpc/zsession_encoding.go b/go/rpc/zsession_encoding.go index 85c1bd4497..1102e9bdae 100644 --- a/go/rpc/zsession_encoding.go +++ b/go/rpc/zsession_encoding.go @@ -89,6 +89,12 @@ func (e *SessionEvent) UnmarshalJSON(data []byte) error { return err } e.Data = &d + case SessionEventTypeAssistantToolCallDelta: + var d AssistantToolCallDeltaData + if err := json.Unmarshal(raw.Data, &d); err != nil { + return err + } + e.Data = &d case SessionEventTypeAssistantTurnEnd: var d AssistantTurnEndData if err := json.Unmarshal(raw.Data, &d); err != nil { diff --git a/go/rpc/zsession_events.go b/go/rpc/zsession_events.go index 7d2a230a66..758b90b7d8 100644 --- a/go/rpc/zsession_events.go +++ b/go/rpc/zsession_events.go @@ -62,6 +62,7 @@ const ( SessionEventTypeAssistantReasoning SessionEventType = "assistant.reasoning" SessionEventTypeAssistantReasoningDelta SessionEventType = "assistant.reasoning_delta" SessionEventTypeAssistantStreamingDelta SessionEventType = "assistant.streaming_delta" + SessionEventTypeAssistantToolCallDelta SessionEventType = "assistant.tool_call_delta" SessionEventTypeAssistantTurnEnd SessionEventType = "assistant.turn_end" SessionEventTypeAssistantTurnStart SessionEventType = "assistant.turn_start" SessionEventTypeAssistantUsage SessionEventType = "assistant.usage" @@ -315,6 +316,8 @@ func (*SessionBinaryAssetData) Type() SessionEventType { return SessionEventType type SessionCompactionStartData struct { // Token count from non-system messages (user, assistant, tool) at compaction start ConversationTokens *int64 `json:"conversationTokens,omitempty"` + // Model identifier used for compaction, when known + Model *string `json:"model,omitempty"` // Token count from system message(s) at compaction start SystemTokens *int64 `json:"systemTokens,omitempty"` // Token count from tool definitions at compaction start @@ -527,6 +530,15 @@ type ElicitationRequestedData struct { func (*ElicitationRequestedData) sessionEventData() {} func (*ElicitationRequestedData) Type() SessionEventType { return SessionEventTypeElicitationRequested } +// Empty payload for `session.background_tasks_changed`, indicating background task state changed. +type SessionBackgroundTasksChangedData struct { +} + +func (*SessionBackgroundTasksChangedData) sessionEventData() {} +func (*SessionBackgroundTasksChangedData) Type() SessionEventType { + return SessionEventTypeSessionBackgroundTasksChanged +} + // Empty payload; the event signals that the custom agent was deselected, returning to the default agent type SubagentDeselectedData struct { } @@ -742,7 +754,7 @@ type AssistantUsageData struct { // Copilot service request ID (x-copilot-service-request-id header) for CAPI log correlation ServiceRequestID *string `json:"serviceRequestId,omitempty"` // Time to first token in milliseconds. Only available for streaming requests - TimeToFirstTokenMs *int64 `json:"timeToFirstTokenMs,omitempty"` + TimeToFirstTokenMs *float64 `json:"timeToFirstTokenMs,omitempty"` } func (*AssistantUsageData) sessionEventData() {} @@ -811,10 +823,14 @@ type SessionModelChangeData struct { PreviousReasoningEffort *string `json:"previousReasoningEffort,omitempty"` // Reasoning summary mode before the model change, if applicable PreviousReasoningSummary *ReasoningSummary `json:"previousReasoningSummary,omitempty"` + // Output verbosity level before the model change, if applicable + PreviousVerbosity *Verbosity `json:"previousVerbosity,omitempty"` // Reasoning effort level after the model change, if applicable ReasoningEffort *string `json:"reasoningEffort,omitempty"` // Reasoning summary mode after the model change, if applicable ReasoningSummary *ReasoningSummary `json:"reasoningSummary,omitempty"` + // Output verbosity level after the model change, if applicable + Verbosity *Verbosity `json:"verbosity,omitempty"` } func (*SessionModelChangeData) sessionEventData() {} @@ -889,6 +905,166 @@ type SessionIdleData struct { func (*SessionIdleData) sessionEventData() {} func (*SessionIdleData) Type() SessionEventType { return SessionEventTypeSessionIdle } +// Payload of `session.canvas.closed` with the closed canvas instance ID, provider ID, and canvas ID. +// Experimental: SessionCanvasClosedData is part of an experimental API and may change or be removed. +type SessionCanvasClosedData struct { + // Provider-local canvas identifier + CanvasID string `json:"canvasId"` + // Owning provider identifier + ExtensionID string `json:"extensionId"` + // Stable caller-supplied identifier of the canvas instance that was closed + InstanceID string `json:"instanceId"` +} + +func (*SessionCanvasClosedData) sessionEventData() {} +func (*SessionCanvasClosedData) Type() SessionEventType { return SessionEventTypeSessionCanvasClosed } + +// Payload of `session.canvas.opened` with canvas instance and provider IDs plus optional title, status, URL, and input. +// Experimental: SessionCanvasOpenedData is part of an experimental API and may change or be removed. +type SessionCanvasOpenedData struct { + // Provider-local canvas identifier + CanvasID string `json:"canvasId"` + // Owning provider identifier + ExtensionID string `json:"extensionId"` + // Owning extension display name, when available + ExtensionName *string `json:"extensionName,omitempty"` + // Input supplied when the instance was opened + Input any `json:"input,omitempty"` + // Stable caller-supplied canvas instance identifier + InstanceID string `json:"instanceId"` + // Provider-supplied status text + Status *string `json:"status,omitempty"` + // Rendered title + Title *string `json:"title,omitempty"` + // URL for web-rendered canvases + URL *string `json:"url,omitempty"` +} + +func (*SessionCanvasOpenedData) sessionEventData() {} +func (*SessionCanvasOpenedData) Type() SessionEventType { return SessionEventTypeSessionCanvasOpened } + +// Payload of `session.canvas.registry_changed` listing the canvas declarations currently available. +// Experimental: SessionCanvasRegistryChangedData is part of an experimental API and may change or be removed. +type SessionCanvasRegistryChangedData struct { + // Canvas declarations currently available + Canvases []CanvasRegistryChangedCanvas `json:"canvases"` +} + +func (*SessionCanvasRegistryChangedData) sessionEventData() {} +func (*SessionCanvasRegistryChangedData) Type() SessionEventType { + return SessionEventTypeSessionCanvasRegistryChanged +} + +// Payload of `session.custom_agents_updated` with loaded custom agents plus non-fatal warnings and fatal errors. +type SessionCustomAgentsUpdatedData struct { + // Array of loaded custom agent metadata + Agents []CustomAgentsUpdatedAgent `json:"agents"` + // Fatal errors from agent loading + Errors []string `json:"errors"` + // Non-fatal warnings from agent loading + Warnings []string `json:"warnings"` +} + +func (*SessionCustomAgentsUpdatedData) sessionEventData() {} +func (*SessionCustomAgentsUpdatedData) Type() SessionEventType { + return SessionEventTypeSessionCustomAgentsUpdated +} + +// Payload of `session.extensions.attachments_pushed` with extension-contributed attachments for the next send. +type SessionExtensionsAttachmentsPushedData struct { + // Attachments contributed by an extension; the host should surface these as composer pills and forward them via the next session.send call. + Attachments []Attachment `json:"attachments"` +} + +func (*SessionExtensionsAttachmentsPushedData) sessionEventData() {} +func (*SessionExtensionsAttachmentsPushedData) Type() SessionEventType { + return SessionEventTypeSessionExtensionsAttachmentsPushed +} + +// Payload of `session.extensions_loaded` listing discovered extensions and their statuses. +type SessionExtensionsLoadedData struct { + // Array of discovered extensions and their status + Extensions []ExtensionsLoadedExtension `json:"extensions"` +} + +func (*SessionExtensionsLoadedData) sessionEventData() {} +func (*SessionExtensionsLoadedData) Type() SessionEventType { + return SessionEventTypeSessionExtensionsLoaded +} + +// Payload of `session.mcp_server_status_changed` for one MCP server's status and optional failure error. +type SessionMCPServerStatusChangedData struct { + // Error message if the server entered a failed state + Error *string `json:"error,omitempty"` + // Name of the MCP server whose status changed + ServerName string `json:"serverName"` + // Connection status: connected, failed, needs-auth, pending, disabled, or not_configured + Status MCPServerStatus `json:"status"` +} + +func (*SessionMCPServerStatusChangedData) sessionEventData() {} +func (*SessionMCPServerStatusChangedData) Type() SessionEventType { + return SessionEventTypeSessionMCPServerStatusChanged +} + +// Payload of `session.mcp_servers_loaded` listing MCP server status summaries. +type SessionMCPServersLoadedData struct { + // Array of MCP server status summaries + Servers []MCPServersLoadedServer `json:"servers"` +} + +func (*SessionMCPServersLoadedData) sessionEventData() {} +func (*SessionMCPServersLoadedData) Type() SessionEventType { + return SessionEventTypeSessionMCPServersLoaded +} + +// Payload of `session.skills_loaded` listing resolved skill metadata. +type SessionSkillsLoadedData struct { + // Array of resolved skill metadata + Skills []SkillsLoadedSkill `json:"skills"` +} + +func (*SessionSkillsLoadedData) sessionEventData() {} +func (*SessionSkillsLoadedData) Type() SessionEventType { return SessionEventTypeSessionSkillsLoaded } + +// Payload of `session.tools_updated` identifying the model whose resolved tools were updated. +type SessionToolsUpdatedData struct { + // Identifier of the model the resolved tools apply to. + Model string `json:"model"` +} + +func (*SessionToolsUpdatedData) sessionEventData() {} +func (*SessionToolsUpdatedData) Type() SessionEventType { return SessionEventTypeSessionToolsUpdated } + +// Payload of `user.message` with displayed and model-transformed content, attachments, source/delivery metadata, mode, and telemetry IDs. +type UserMessageData struct { + // The agent mode that was active when this message was sent + AgentMode *UserMessageAgentMode `json:"agentMode,omitempty"` + // Files, selections, or GitHub references attached to the message + Attachments []Attachment `json:"attachments,omitzero"` + // The user's message text as displayed in the timeline + Content string `json:"content"` + // How this message was delivered to the agentic loop relative to loop state (idle-start vs. steering/queued while busy). The timing axis; combine with `source` (origin) for the full picture. Used for telemetry attribution. + Delivery *UserMessageDelivery `json:"delivery,omitempty"` + // CAPI interaction ID for correlating this user message with its turn + InteractionID *string `json:"interactionId,omitempty"` + // True when this user message was auto-injected by autopilot's continuation loop rather than typed by the user; used to distinguish autopilot-driven turns in telemetry. + IsAutopilotContinuation *bool `json:"isAutopilotContinuation,omitempty"` + // Path-backed native document attachments that stayed on the tagged_files path flow because native upload could not read them or would exceed the request size limit + NativeDocumentPathFallbackPaths []string `json:"nativeDocumentPathFallbackPaths,omitzero"` + // Parent agent task ID for background telemetry correlated to this user turn + ParentAgentTaskID *string `json:"parentAgentTaskId,omitempty"` + // Origin of this message, used for timeline filtering (e.g., "skill-pdf" for skill-injected messages that should be hidden from the user) + Source *string `json:"source,omitempty"` + // Normalized document MIME types that were sent natively instead of through tagged_files XML + SupportedNativeDocumentMIMETypes []string `json:"supportedNativeDocumentMimeTypes,omitzero"` + // Transformed version of the message sent to the model, with XML wrapping, timestamps, and other augmentations for prompt caching + TransformedContent *string `json:"transformedContent,omitempty"` +} + +func (*UserMessageData) sessionEventData() {} +func (*UserMessageData) Type() SessionEventType { return SessionEventTypeUserMessage } + // Permission request completion notification signaling UI dismissal type PermissionCompletedData struct { // Request ID of the resolved permission request; clients should dismiss any UI for this request @@ -917,10 +1093,16 @@ type PermissionRequestedData struct { func (*PermissionRequestedData) sessionEventData() {} func (*PermissionRequestedData) Type() SessionEventType { return SessionEventTypePermissionRequested } -// Permissions change details carrying the aggregate allow-all boolean transition. +// Permissions change details carrying the aggregate allow-all transition. type SessionPermissionsChangedData struct { + // Allow-all mode after the change + // Experimental: AllowAllPermissionMode is part of an experimental API and may change or be removed. + AllowAllPermissionMode *PermissionAllowAllMode `json:"allowAllPermissionMode,omitempty"` // Aggregate allow-all flag after the change AllowAllPermissions bool `json:"allowAllPermissions"` + // Allow-all mode before the change + // Experimental: PreviousAllowAllPermissionMode is part of an experimental API and may change or be removed. + PreviousAllowAllPermissionMode *PermissionAllowAllMode `json:"previousAllowAllPermissionMode,omitempty"` // Aggregate allow-all flag before the change PreviousAllowAllPermissions bool `json:"previousAllowAllPermissions"` } @@ -1081,175 +1263,6 @@ func (*SessionScheduleCreatedData) Type() SessionEventType { return SessionEventTypeSessionScheduleCreated } -// Schema for the `BackgroundTasksChangedData` type. -type SessionBackgroundTasksChangedData struct { -} - -func (*SessionBackgroundTasksChangedData) sessionEventData() {} -func (*SessionBackgroundTasksChangedData) Type() SessionEventType { - return SessionEventTypeSessionBackgroundTasksChanged -} - -// Schema for the `CanvasClosedData` type. -// Experimental: SessionCanvasClosedData is part of an experimental API and may change or be removed. -type SessionCanvasClosedData struct { - // Provider-local canvas identifier - CanvasID string `json:"canvasId"` - // Owning provider identifier - ExtensionID string `json:"extensionId"` - // Stable caller-supplied identifier of the canvas instance that was closed - InstanceID string `json:"instanceId"` -} - -func (*SessionCanvasClosedData) sessionEventData() {} -func (*SessionCanvasClosedData) Type() SessionEventType { return SessionEventTypeSessionCanvasClosed } - -// Schema for the `CanvasOpenedData` type. -// Experimental: SessionCanvasOpenedData is part of an experimental API and may change or be removed. -type SessionCanvasOpenedData struct { - // Provider-local canvas identifier - CanvasID string `json:"canvasId"` - // Owning provider identifier - ExtensionID string `json:"extensionId"` - // Owning extension display name, when available - ExtensionName *string `json:"extensionName,omitempty"` - // Input supplied when the instance was opened - Input any `json:"input,omitempty"` - // Stable caller-supplied canvas instance identifier - InstanceID string `json:"instanceId"` - // Provider-supplied status text - Status *string `json:"status,omitempty"` - // Rendered title - Title *string `json:"title,omitempty"` - // URL for web-rendered canvases - URL *string `json:"url,omitempty"` -} - -func (*SessionCanvasOpenedData) sessionEventData() {} -func (*SessionCanvasOpenedData) Type() SessionEventType { return SessionEventTypeSessionCanvasOpened } - -// Schema for the `CanvasRegistryChangedData` type. -// Experimental: SessionCanvasRegistryChangedData is part of an experimental API and may change or be removed. -type SessionCanvasRegistryChangedData struct { - // Canvas declarations currently available - Canvases []CanvasRegistryChangedCanvas `json:"canvases"` -} - -func (*SessionCanvasRegistryChangedData) sessionEventData() {} -func (*SessionCanvasRegistryChangedData) Type() SessionEventType { - return SessionEventTypeSessionCanvasRegistryChanged -} - -// Schema for the `CustomAgentsUpdatedData` type. -type SessionCustomAgentsUpdatedData struct { - // Array of loaded custom agent metadata - Agents []CustomAgentsUpdatedAgent `json:"agents"` - // Fatal errors from agent loading - Errors []string `json:"errors"` - // Non-fatal warnings from agent loading - Warnings []string `json:"warnings"` -} - -func (*SessionCustomAgentsUpdatedData) sessionEventData() {} -func (*SessionCustomAgentsUpdatedData) Type() SessionEventType { - return SessionEventTypeSessionCustomAgentsUpdated -} - -// Schema for the `ExtensionsAttachmentsPushedData` type. -type SessionExtensionsAttachmentsPushedData struct { - // Attachments contributed by an extension; the host should surface these as composer pills and forward them via the next session.send call. - Attachments []Attachment `json:"attachments"` -} - -func (*SessionExtensionsAttachmentsPushedData) sessionEventData() {} -func (*SessionExtensionsAttachmentsPushedData) Type() SessionEventType { - return SessionEventTypeSessionExtensionsAttachmentsPushed -} - -// Schema for the `ExtensionsLoadedData` type. -type SessionExtensionsLoadedData struct { - // Array of discovered extensions and their status - Extensions []ExtensionsLoadedExtension `json:"extensions"` -} - -func (*SessionExtensionsLoadedData) sessionEventData() {} -func (*SessionExtensionsLoadedData) Type() SessionEventType { - return SessionEventTypeSessionExtensionsLoaded -} - -// Schema for the `McpServerStatusChangedData` type. -type SessionMCPServerStatusChangedData struct { - // Error message if the server entered a failed state - Error *string `json:"error,omitempty"` - // Name of the MCP server whose status changed - ServerName string `json:"serverName"` - // Connection status: connected, failed, needs-auth, pending, disabled, or not_configured - Status MCPServerStatus `json:"status"` -} - -func (*SessionMCPServerStatusChangedData) sessionEventData() {} -func (*SessionMCPServerStatusChangedData) Type() SessionEventType { - return SessionEventTypeSessionMCPServerStatusChanged -} - -// Schema for the `McpServersLoadedData` type. -type SessionMCPServersLoadedData struct { - // Array of MCP server status summaries - Servers []MCPServersLoadedServer `json:"servers"` -} - -func (*SessionMCPServersLoadedData) sessionEventData() {} -func (*SessionMCPServersLoadedData) Type() SessionEventType { - return SessionEventTypeSessionMCPServersLoaded -} - -// Schema for the `SkillsLoadedData` type. -type SessionSkillsLoadedData struct { - // Array of resolved skill metadata - Skills []SkillsLoadedSkill `json:"skills"` -} - -func (*SessionSkillsLoadedData) sessionEventData() {} -func (*SessionSkillsLoadedData) Type() SessionEventType { return SessionEventTypeSessionSkillsLoaded } - -// Schema for the `ToolsUpdatedData` type. -type SessionToolsUpdatedData struct { - // Identifier of the model the resolved tools apply to. - Model string `json:"model"` -} - -func (*SessionToolsUpdatedData) sessionEventData() {} -func (*SessionToolsUpdatedData) Type() SessionEventType { return SessionEventTypeSessionToolsUpdated } - -// Schema for the `UserMessageData` type. -type UserMessageData struct { - // The agent mode that was active when this message was sent - AgentMode *UserMessageAgentMode `json:"agentMode,omitempty"` - // Files, selections, or GitHub references attached to the message - Attachments []Attachment `json:"attachments,omitzero"` - // The user's message text as displayed in the timeline - Content string `json:"content"` - // How this message was delivered to the agentic loop relative to loop state (idle-start vs. steering/queued while busy). The timing axis; combine with `source` (origin) for the full picture. Used for telemetry attribution. - Delivery *UserMessageDelivery `json:"delivery,omitempty"` - // CAPI interaction ID for correlating this user message with its turn - InteractionID *string `json:"interactionId,omitempty"` - // True when this user message was auto-injected by autopilot's continuation loop rather than typed by the user; used to distinguish autopilot-driven turns in telemetry. - IsAutopilotContinuation *bool `json:"isAutopilotContinuation,omitempty"` - // Path-backed native document attachments that stayed on the tagged_files path flow because native upload could not read them or would exceed the request size limit - NativeDocumentPathFallbackPaths []string `json:"nativeDocumentPathFallbackPaths,omitzero"` - // Parent agent task ID for background telemetry correlated to this user turn - ParentAgentTaskID *string `json:"parentAgentTaskId,omitempty"` - // Origin of this message, used for timeline filtering (e.g., "skill-pdf" for skill-injected messages that should be hidden from the user) - Source *string `json:"source,omitempty"` - // Normalized document MIME types that were sent natively instead of through tagged_files XML - SupportedNativeDocumentMIMETypes []string `json:"supportedNativeDocumentMimeTypes,omitzero"` - // Transformed version of the message sent to the model, with XML wrapping, timestamps, and other augmentations for prompt caching - TransformedContent *string `json:"transformedContent,omitempty"` -} - -func (*UserMessageData) sessionEventData() {} -func (*UserMessageData) Type() SessionEventType { return SessionEventTypeUserMessage } - // Self-paced schedule re-armed for its next run type SessionScheduleRearmedData struct { // Id of the self-paced schedule that was re-armed @@ -1321,6 +1334,8 @@ type SessionStartData struct { SessionLimits *SessionLimitsConfig `json:"sessionLimits,omitempty"` // ISO 8601 timestamp when the session was created StartTime time.Time `json:"startTime"` + // Output verbosity level used for model calls, if applicable (e.g. "low", "medium", "high") + Verbosity *Verbosity `json:"verbosity,omitempty"` // Schema version number for the session event format Version int64 `json:"version"` } @@ -1395,6 +1410,8 @@ type SessionResumeData struct { SessionLimits *SessionLimitsConfig `json:"sessionLimits,omitempty"` // True when this resume attached to a session that the runtime already had running in-memory (for example, an extension joining a session another client was actively driving). False (or omitted) for cold resumes — the runtime had to reconstitute the session from its persisted event log. SessionWasActive *bool `json:"sessionWasActive,omitempty"` + // Output verbosity level used for model calls, if applicable (e.g. "low", "medium", "high") + Verbosity *Verbosity `json:"verbosity,omitempty"` } func (*SessionResumeData) sessionEventData() {} @@ -1476,6 +1493,8 @@ type SkillInvokedData struct { Content string `json:"content"` // Description of the skill from its SKILL.md frontmatter Description *string `json:"description,omitempty"` + // Model identifier active when the skill was invoked, when known + Model *string `json:"model,omitempty"` // Name of the invoked skill Name string `json:"name"` // File path to the SKILL.md definition @@ -1559,6 +1578,23 @@ func (*ToolExecutionPartialResultData) Type() SessionEventType { return SessionEventTypeToolExecutionPartialResult } +// Streaming tool-call input delta for incremental tool-call updates +type AssistantToolCallDeltaData struct { + // Raw provider tool input fragment to append for this tool call. Function/tool-use providers stream serialized JSON argument text (so newlines inside JSON string values may appear as escaped `\n` until the accumulated JSON is parsed); custom tool calls stream raw custom input. + InputDelta string `json:"inputDelta"` + // Tool call ID this delta belongs to, matching the corresponding assistant.message tool request + ToolCallID string `json:"toolCallId"` + // Name of the tool being invoked, when known from the stream + ToolName *string `json:"toolName,omitempty"` + // Tool call type, when known from the stream + ToolType *AssistantMessageToolRequestType `json:"toolType,omitempty"` +} + +func (*AssistantToolCallDeltaData) sessionEventData() {} +func (*AssistantToolCallDeltaData) Type() SessionEventType { + return SessionEventTypeAssistantToolCallDelta +} + // Sub-agent completion details for successful execution type SubagentCompletedData struct { // Human-readable display name of the sub-agent @@ -1761,6 +1797,8 @@ func (*AbortData) Type() SessionEventType { return SessionEventTypeAbort } // Turn completion metadata including the turn identifier type AssistantTurnEndData struct { + // Model identifier used for this turn, when known + Model *string `json:"model,omitempty"` // Identifier of the turn that has ended, matching the corresponding assistant.turn_start event TurnID string `json:"turnId"` } @@ -1772,6 +1810,8 @@ func (*AssistantTurnEndData) Type() SessionEventType { return SessionEventTypeAs type AssistantTurnStartData struct { // CAPI interaction ID for correlating this turn with upstream telemetry InteractionID *string `json:"interactionId,omitempty"` + // Model identifier used for this turn, when known + Model *string `json:"model,omitempty"` // Identifier for this turn within the agentic loop, typically a stringified turn number TurnID string `json:"turnId"` } @@ -1924,7 +1964,7 @@ type AssistantUsageCopilotUsageTokenDetail struct { TokenType string `json:"tokenType"` } -// Schema for the `AssistantUsageQuotaSnapshot` type. +// Internal per-quota snapshot for assistant usage, including entitlement, consumed requests, overage, reset date, and remaining quota. // Internal: AssistantUsageQuotaSnapshot is an internal SDK API and is not part of the public surface. type AssistantUsageQuotaSnapshot struct { // Total requests allowed by the entitlement @@ -1962,7 +2002,7 @@ type AssistantUsageQuotaSnapshot struct { UsedRequests int64 `json:"usedRequests"` } -// Schema for the `CanvasRegistryChangedCanvas` type. +// A single canvas declaration in `session.canvas.registry_changed`, including provider IDs, display metadata, input schema, and actions. // Experimental: CanvasRegistryChangedCanvas is part of an experimental API and may change or be removed. type CanvasRegistryChangedCanvas struct { // Actions the agent or host may invoke @@ -1981,7 +2021,7 @@ type CanvasRegistryChangedCanvas struct { InputSchema any `json:"inputSchema,omitempty"` } -// Schema for the `CanvasRegistryChangedCanvasAction` type. +// A single action within a canvas declaration, with its name, optional description, and optional input schema. // Experimental: CanvasRegistryChangedCanvasAction is part of an experimental API and may change or be removed. type CanvasRegistryChangedCanvasAction struct { // Action description @@ -2121,7 +2161,7 @@ type CitationSpan struct { StartIndex int64 `json:"startIndex"` } -// Schema for the `CommandsChangedCommand` type. +// A single slash command available in the session, as listed by the `commands.changed` event. type CommandsChangedCommand struct { // Optional human-readable command description. Description *string `json:"description,omitempty"` @@ -2170,7 +2210,7 @@ type CompactionCompleteCompactionTokensUsedCopilotUsageTokenDetail struct { TokenType string `json:"tokenType"` } -// Schema for the `CustomAgentsUpdatedAgent` type. +// A single loaded custom agent in `session.custom_agents_updated`, with identity, source, tools, invocability, and model override. type CustomAgentsUpdatedAgent struct { // Description of what the agent does Description string `json:"description"` @@ -2200,7 +2240,7 @@ type ElicitationRequestedSchema struct { Type ElicitationRequestedSchemaType `json:"type"` } -// Schema for the `ExtensionsLoadedExtension` type. +// A single extension discovered by `session.extensions_loaded`, including qualified ID, source, and current status. type ExtensionsLoadedExtension struct { // Source-qualified extension ID (e.g., 'project:my-ext', 'user:auth-helper', 'plugin:my-plugin:my-ext') ID string `json:"id"` @@ -2240,11 +2280,11 @@ type MCPAppToolCallCompleteError struct { // The tool's `_meta.ui` block at the time of the call, so consumers can decide whether to forward the result to the model without re-listing tools. type MCPAppToolCallCompleteToolMeta struct { - // Schema for the `McpAppToolCallCompleteToolMetaUI` type. + // MCP App tool `_meta.ui` resource URI and SEP-1865 visibility captured with an `mcp_app.tool_call_complete` result. UI *MCPAppToolCallCompleteToolMetaUI `json:"ui,omitempty"` } -// Schema for the `McpAppToolCallCompleteToolMetaUI` type. +// MCP App tool `_meta.ui` resource URI and SEP-1865 visibility captured with an `mcp_app.tool_call_complete` result. type MCPAppToolCallCompleteToolMetaUI struct { // `ui://` URI declared by the tool's `_meta.ui.resourceUri` ResourceURI *string `json:"resourceUri,omitempty"` @@ -2274,7 +2314,7 @@ type MCPOauthWwwAuthenticateParams struct { Scope *string `json:"scope,omitempty"` } -// Schema for the `McpServersLoadedServer` type. +// A single MCP server status summary in `session.mcp_servers_loaded`, including name, status, source, transport, and plugin metadata. type MCPServersLoadedServer struct { // Error message if the server failed to connect Error *string `json:"error,omitempty"` @@ -2310,6 +2350,15 @@ type ModelCallFailureRequestFingerprint struct { ToolResultMessageCount int64 `json:"toolResultMessageCount"` } +// Auto-approval judge information attached to a permission request. Present (non-null) only when the session's allow-all mode is "auto"; its absence means auto mode was off and the judge did not evaluate the request. The `recommendation` conveys the judge's disposition for this request. +// Experimental: PermissionAutoApproval is part of an experimental API and may change or be removed. +type PermissionAutoApproval struct { + // Human-readable reason for the judge's recommendation, when available. + Reason *string `json:"reason,omitempty"` + // The auto-approval safety judge's outcome for this request. + Recommendation AutoApprovalRecommendation `json:"recommendation"` +} + // Derived user-facing permission prompt details for UI consumers type PermissionPromptRequest interface { permissionPromptRequest() @@ -2328,6 +2377,9 @@ func (r RawPermissionPromptRequest) Kind() PermissionPromptRequestKind { // Shell command permission prompt type PermissionPromptRequestCommands struct { + // Auto-approval judge information for this request; present only when auto mode is enabled. + // Experimental: AutoApproval is part of an experimental API and may change or be removed. + AutoApproval *PermissionAutoApproval `json:"autoApproval,omitempty"` // Whether the UI can offer session-wide approval for this command pattern CanOfferSessionApproval bool `json:"canOfferSessionApproval"` // Command identifiers covered by this approval prompt @@ -2351,6 +2403,9 @@ func (PermissionPromptRequestCommands) Kind() PermissionPromptRequestKind { type PermissionPromptRequestCustomTool struct { // Arguments to pass to the custom tool Args any `json:"args,omitempty"` + // Auto-approval judge information for this request; present only when auto mode is enabled. + // Experimental: AutoApproval is part of an experimental API and may change or be removed. + AutoApproval *PermissionAutoApproval `json:"autoApproval,omitempty"` // Tool call ID that triggered this permission request ToolCallID *string `json:"toolCallId,omitempty"` // Description of what the custom tool does @@ -2366,6 +2421,9 @@ func (PermissionPromptRequestCustomTool) Kind() PermissionPromptRequestKind { // Extension management permission prompt type PermissionPromptRequestExtensionManagement struct { + // Auto-approval judge information for this request; present only when auto mode is enabled. + // Experimental: AutoApproval is part of an experimental API and may change or be removed. + AutoApproval *PermissionAutoApproval `json:"autoApproval,omitempty"` // Name of the extension being managed ExtensionName *string `json:"extensionName,omitempty"` // The extension management operation (scaffold, reload) @@ -2381,6 +2439,9 @@ func (PermissionPromptRequestExtensionManagement) Kind() PermissionPromptRequest // Extension permission access prompt type PermissionPromptRequestExtensionPermissionAccess struct { + // Auto-approval judge information for this request; present only when auto mode is enabled. + // Experimental: AutoApproval is part of an experimental API and may change or be removed. + AutoApproval *PermissionAutoApproval `json:"autoApproval,omitempty"` // Capabilities the extension is requesting Capabilities []string `json:"capabilities"` // Name of the extension requesting permission access @@ -2396,6 +2457,9 @@ func (PermissionPromptRequestExtensionPermissionAccess) Kind() PermissionPromptR // Hook confirmation permission prompt type PermissionPromptRequestHook struct { + // Auto-approval judge information for this request; present only when auto mode is enabled. + // Experimental: AutoApproval is part of an experimental API and may change or be removed. + AutoApproval *PermissionAutoApproval `json:"autoApproval,omitempty"` // Optional message from the hook explaining why confirmation is needed HookMessage *string `json:"hookMessage,omitempty"` // Arguments of the tool call being gated @@ -2415,6 +2479,9 @@ func (PermissionPromptRequestHook) Kind() PermissionPromptRequestKind { type PermissionPromptRequestMCP struct { // Arguments to pass to the MCP tool Args any `json:"args,omitempty"` + // Auto-approval judge information for this request; present only when auto mode is enabled. + // Experimental: AutoApproval is part of an experimental API and may change or be removed. + AutoApproval *PermissionAutoApproval `json:"autoApproval,omitempty"` // Name of the MCP server providing the tool ServerName string `json:"serverName"` // Tool call ID that triggered this permission request @@ -2434,6 +2501,9 @@ func (PermissionPromptRequestMCP) Kind() PermissionPromptRequestKind { type PermissionPromptRequestMemory struct { // Whether this is a store or vote memory operation Action *PermissionRequestMemoryAction `json:"action,omitempty"` + // Auto-approval judge information for this request; present only when auto mode is enabled. + // Experimental: AutoApproval is part of an experimental API and may change or be removed. + AutoApproval *PermissionAutoApproval `json:"autoApproval,omitempty"` // Source references for the stored fact (store only) Citations *string `json:"citations,omitempty"` // Vote direction (vote only) @@ -2457,6 +2527,9 @@ func (PermissionPromptRequestMemory) Kind() PermissionPromptRequestKind { type PermissionPromptRequestPath struct { // Underlying permission kind that needs path approval AccessKind PermissionPromptRequestPathAccessKind `json:"accessKind"` + // Auto-approval judge information for this request; present only when auto mode is enabled. + // Experimental: AutoApproval is part of an experimental API and may change or be removed. + AutoApproval *PermissionAutoApproval `json:"autoApproval,omitempty"` // File paths that require explicit approval Paths []string `json:"paths"` // Tool call ID that triggered this permission request @@ -2470,6 +2543,9 @@ func (PermissionPromptRequestPath) Kind() PermissionPromptRequestKind { // File read permission prompt type PermissionPromptRequestRead struct { + // Auto-approval judge information for this request; present only when auto mode is enabled. + // Experimental: AutoApproval is part of an experimental API and may change or be removed. + AutoApproval *PermissionAutoApproval `json:"autoApproval,omitempty"` // Human-readable description of why the file is being read Intention string `json:"intention"` // Path of the file or directory being read @@ -2485,8 +2561,15 @@ func (PermissionPromptRequestRead) Kind() PermissionPromptRequestKind { // URL access permission prompt type PermissionPromptRequestURL struct { + // Auto-approval judge information for this request; present only when auto mode is enabled. + // Experimental: AutoApproval is part of an experimental API and may change or be removed. + AutoApproval *PermissionAutoApproval `json:"autoApproval,omitempty"` // Human-readable description of why the URL is being accessed Intention string `json:"intention"` + // True when this URL fetch is requesting to bypass the sandbox network policy: either the model set requestSandboxBypass: true, or the tool re-issued the request as an interactive bypass after the network policy denied the approved URL (host opted in via sandbox.allowBypass). This is a request, not a grant: the fetch runs only if the user approves this permission request. Hosts should highlight the elevated risk in the approval UI. + RequestSandboxBypass *bool `json:"requestSandboxBypass,omitempty"` + // Model-provided justification for the sandbox-bypass request. Only meaningful when requestSandboxBypass is true. + RequestSandboxBypassReason *string `json:"requestSandboxBypassReason,omitempty"` // Tool call ID that triggered this permission request ToolCallID *string `json:"toolCallId,omitempty"` // URL to be fetched @@ -2500,6 +2583,9 @@ func (PermissionPromptRequestURL) Kind() PermissionPromptRequestKind { // File write permission prompt type PermissionPromptRequestWrite struct { + // Auto-approval judge information for this request; present only when auto mode is enabled. + // Experimental: AutoApproval is part of an experimental API and may change or be removed. + AutoApproval *PermissionAutoApproval `json:"autoApproval,omitempty"` // Whether the UI can offer session-wide approval for file write operations CanOfferSessionApproval bool `json:"canOfferSessionApproval"` // Unified diff showing the proposed changes @@ -2697,6 +2783,10 @@ func (PermissionRequestShell) Kind() PermissionRequestKind { type PermissionRequestURL struct { // Human-readable description of why the URL is being accessed Intention string `json:"intention"` + // True when this URL fetch is requesting to bypass the sandbox network policy: either the model set requestSandboxBypass: true, or the tool re-issued the request as an interactive bypass after the network policy denied the approved URL (host opted in via sandbox.allowBypass). This is a request, not a grant: the fetch runs only if the user approves this permission request. Hosts should highlight the elevated risk in the approval UI. + RequestSandboxBypass *bool `json:"requestSandboxBypass,omitempty"` + // Model-provided justification for the sandbox-bypass request. Only meaningful when requestSandboxBypass is true. + RequestSandboxBypassReason *string `json:"requestSandboxBypassReason,omitempty"` // Tool call ID that triggered this permission request ToolCallID *string `json:"toolCallId,omitempty"` // URL to be fetched @@ -2720,6 +2810,10 @@ type PermissionRequestWrite struct { Intention string `json:"intention"` // Complete new file contents for newly created files NewFileContents *string `json:"newFileContents,omitempty"` + // True when a built-in file tool (apply_patch / str_replace_editor) asked to write a path the sandbox filesystem policy would block, and the host opted in via sandbox.allowBypass. This is a request, not a grant: the write happens unsandboxed only if the user approves this permission request. Hosts should highlight the elevated risk in the approval UI. + RequestSandboxBypass *bool `json:"requestSandboxBypass,omitempty"` + // Justification for the sandbox-bypass request. Only meaningful when requestSandboxBypass is true. + RequestSandboxBypassReason *string `json:"requestSandboxBypassReason,omitempty"` // Tool call ID that triggered this permission request ToolCallID *string `json:"toolCallId,omitempty"` } @@ -2729,7 +2823,7 @@ func (PermissionRequestWrite) Kind() PermissionRequestKind { return PermissionRequestKindWrite } -// Schema for the `PermissionRequestShellCommand` type. +// A parsed command identifier in a shell permission request, including whether it is read-only. type PermissionRequestShellCommand struct { // Command identifier (e.g., executable name) Identifier string `json:"identifier"` @@ -2737,7 +2831,7 @@ type PermissionRequestShellCommand struct { ReadOnly bool `json:"readOnly"` } -// Schema for the `PermissionRequestShellPossibleUrl` type. +// A URL that may be accessed by a command in a shell permission request. type PermissionRequestShellPossibleURL struct { // URL that may be accessed by the command URL string `json:"url"` @@ -2759,7 +2853,7 @@ func (r RawPermissionResult) Kind() PermissionResultKind { return r.Discriminator } -// Schema for the `PermissionApproved` type. +// Permission response variant indicating the request was approved without persisting an approval rule. type PermissionApproved struct { } @@ -2768,7 +2862,7 @@ func (PermissionApproved) Kind() PermissionResultKind { return PermissionResultKindApproved } -// Schema for the `PermissionApprovedForLocation` type. +// Permission response variant that approves a request and persists the provided approval to a project location key. type PermissionApprovedForLocation struct { // The approval to persist for this location Approval UserToolSessionApproval `json:"approval"` @@ -2781,7 +2875,7 @@ func (PermissionApprovedForLocation) Kind() PermissionResultKind { return PermissionResultKindApprovedForLocation } -// Schema for the `PermissionApprovedForSession` type. +// Permission response variant that approves a request and remembers the provided approval for the rest of the session. type PermissionApprovedForSession struct { // The approval to add as a session-scoped rule Approval UserToolSessionApproval `json:"approval"` @@ -2792,7 +2886,7 @@ func (PermissionApprovedForSession) Kind() PermissionResultKind { return PermissionResultKindApprovedForSession } -// Schema for the `PermissionCancelled` type. +// Permission response variant indicating the request was cancelled before use, with an optional reason. type PermissionCancelled struct { // Optional explanation of why the request was cancelled Reason *string `json:"reason,omitempty"` @@ -2803,7 +2897,7 @@ func (PermissionCancelled) Kind() PermissionResultKind { return PermissionResultKindCancelled } -// Schema for the `PermissionDeniedByContentExclusionPolicy` type. +// Permission response variant denying a path under content exclusion policy, with the path and message. type PermissionDeniedByContentExclusionPolicy struct { // Human-readable explanation of why the path was excluded Message string `json:"message"` @@ -2816,7 +2910,7 @@ func (PermissionDeniedByContentExclusionPolicy) Kind() PermissionResultKind { return PermissionResultKindDeniedByContentExclusionPolicy } -// Schema for the `PermissionDeniedByPermissionRequestHook` type. +// Permission response variant denied by a permission-request hook, with optional message and interrupt flag. type PermissionDeniedByPermissionRequestHook struct { // Whether to interrupt the current agent turn Interrupt *bool `json:"interrupt,omitempty"` @@ -2829,7 +2923,7 @@ func (PermissionDeniedByPermissionRequestHook) Kind() PermissionResultKind { return PermissionResultKindDeniedByPermissionRequestHook } -// Schema for the `PermissionDeniedByRules` type. +// Permission response variant denied because matching approval rules explicitly blocked the request. type PermissionDeniedByRules struct { // Rules that denied the request Rules []PermissionRule `json:"rules"` @@ -2840,7 +2934,7 @@ func (PermissionDeniedByRules) Kind() PermissionResultKind { return PermissionResultKindDeniedByRules } -// Schema for the `PermissionDeniedInteractivelyByUser` type. +// Permission response variant denied in an interactive user prompt, with optional feedback and force-reject flag. type PermissionDeniedInteractivelyByUser struct { // Optional feedback from the user explaining the denial Feedback *string `json:"feedback,omitempty"` @@ -2853,7 +2947,7 @@ func (PermissionDeniedInteractivelyByUser) Kind() PermissionResultKind { return PermissionResultKindDeniedInteractivelyByUser } -// Schema for the `PermissionDeniedNoApprovalRuleAndCouldNotRequestFromUser` type. +// Permission response variant denied because no approval rule matched and user confirmation was unavailable. type PermissionDeniedNoApprovalRuleAndCouldNotRequestFromUser struct { } @@ -2966,7 +3060,7 @@ type ShutdownCodeChanges struct { LinesRemoved int64 `json:"linesRemoved"` } -// Schema for the `ShutdownModelMetric` type. +// Per-model shutdown metrics with request counts, token usage, nano-AI units, and token details. type ShutdownModelMetric struct { // Request count and cost metrics Requests ShutdownModelMetricRequests `json:"requests"` @@ -2989,7 +3083,7 @@ type ShutdownModelMetricRequests struct { Count *int64 `json:"count,omitempty"` } -// Schema for the `ShutdownModelMetricTokenDetail` type. +// A token-type entry in a shutdown model metric, storing the accumulated token count. type ShutdownModelMetricTokenDetail struct { // Accumulated token count for this token type TokenCount int64 `json:"tokenCount"` @@ -3009,13 +3103,13 @@ type ShutdownModelMetricUsage struct { ReasoningTokens *int64 `json:"reasoningTokens,omitempty"` } -// Schema for the `ShutdownTokenDetail` type. +// A session-wide shutdown token-type entry storing the accumulated token count. type ShutdownTokenDetail struct { // Accumulated token count for this token type TokenCount int64 `json:"tokenCount"` } -// Schema for the `SkillsLoadedSkill` type. +// A single resolved skill in `session.skills_loaded`, including source, invocability, enabled state, path, and argument hint. type SkillsLoadedSkill struct { // Optional freeform hint describing the skill's expected arguments, from the `argument-hint` frontmatter field ArgumentHint *string `json:"argumentHint,omitempty"` @@ -3057,7 +3151,7 @@ func (r RawSystemNotification) Type() SystemNotificationType { return r.Discriminator } -// Schema for the `SystemNotificationAgentCompleted` type. +// System notification metadata for a background agent that completed or failed, including agent ID, type, status, description, and prompt. type SystemNotificationAgentCompleted struct { // Unique identifier of the background agent AgentID string `json:"agentId"` @@ -3076,7 +3170,7 @@ func (SystemNotificationAgentCompleted) Type() SystemNotificationType { return SystemNotificationTypeAgentCompleted } -// Schema for the `SystemNotificationAgentIdle` type. +// System notification metadata for a background agent that became idle, including agent ID, type, and description. type SystemNotificationAgentIdle struct { // Unique identifier of the background agent AgentID string `json:"agentId"` @@ -3091,7 +3185,7 @@ func (SystemNotificationAgentIdle) Type() SystemNotificationType { return SystemNotificationTypeAgentIdle } -// Schema for the `SystemNotificationInstructionDiscovered` type. +// System notification metadata for an instruction file discovered during tool access, including source, trigger file, and tool. type SystemNotificationInstructionDiscovered struct { // Human-readable label for the timeline (e.g., 'AGENTS.md from packages/billing/') Description *string `json:"description,omitempty"` @@ -3108,7 +3202,7 @@ func (SystemNotificationInstructionDiscovered) Type() SystemNotificationType { return SystemNotificationTypeInstructionDiscovered } -// Schema for the `SystemNotificationNewInboxMessage` type. +// System notification metadata for a new inbox message, including entry ID, sender details, and summary. type SystemNotificationNewInboxMessage struct { // Unique identifier of the inbox entry EntryID string `json:"entryId"` @@ -3125,7 +3219,7 @@ func (SystemNotificationNewInboxMessage) Type() SystemNotificationType { return SystemNotificationTypeNewInboxMessage } -// Schema for the `SystemNotificationShellCompleted` type. +// System notification metadata for a shell session that completed, including shell ID, optional exit code, and description. type SystemNotificationShellCompleted struct { // Human-readable description of the command Description *string `json:"description,omitempty"` @@ -3140,7 +3234,7 @@ func (SystemNotificationShellCompleted) Type() SystemNotificationType { return SystemNotificationTypeShellCompleted } -// Schema for the `SystemNotificationShellDetachedCompleted` type. +// System notification metadata for a detached shell session that completed, including shell ID and description. type SystemNotificationShellDetachedCompleted struct { // Human-readable description of the command Description *string `json:"description,omitempty"` @@ -3332,11 +3426,11 @@ type ToolExecutionCompleteToolDescription struct { // MCP Apps metadata for UI resource association type ToolExecutionCompleteToolDescriptionMeta struct { - // Schema for the `ToolExecutionCompleteToolDescriptionMetaUI` type. + // MCP Apps tool `_meta.ui` resource URI and visibility captured on `tool.execution_complete`. UI *ToolExecutionCompleteToolDescriptionMetaUI `json:"ui,omitempty"` } -// Schema for the `ToolExecutionCompleteToolDescriptionMetaUI` type. +// MCP Apps tool `_meta.ui` resource URI and visibility captured on `tool.execution_complete`. type ToolExecutionCompleteToolDescriptionMetaUI struct { // URI of the UI resource ResourceURI *string `json:"resourceUri,omitempty"` @@ -3360,21 +3454,21 @@ type ToolExecutionCompleteUIResource struct { // Resource-level UI metadata (CSP, permissions, visual preferences) type ToolExecutionCompleteUIResourceMeta struct { - // Schema for the `ToolExecutionCompleteUIResourceMetaUI` type. + // MCP Apps UI resource metadata for a completed tool result, including CSP, permissions, domain, and border preference. UI *ToolExecutionCompleteUIResourceMetaUI `json:"ui,omitempty"` } -// Schema for the `ToolExecutionCompleteUIResourceMetaUI` type. +// MCP Apps UI resource metadata for a completed tool result, including CSP, permissions, domain, and border preference. type ToolExecutionCompleteUIResourceMetaUI struct { - // Schema for the `ToolExecutionCompleteUIResourceMetaUICsp` type. + // CSP domain allowlists for an MCP Apps UI resource, including connect, resource, frame, and base URI domains. Csp *ToolExecutionCompleteUIResourceMetaUICsp `json:"csp,omitempty"` Domain *string `json:"domain,omitempty"` - // Schema for the `ToolExecutionCompleteUIResourceMetaUIPermissions` type. + // Browser permission metadata for an MCP Apps UI resource, including camera, microphone, geolocation, and clipboard-write. Permissions *ToolExecutionCompleteUIResourceMetaUIPermissions `json:"permissions,omitempty"` PrefersBorder *bool `json:"prefersBorder,omitempty"` } -// Schema for the `ToolExecutionCompleteUIResourceMetaUICsp` type. +// CSP domain allowlists for an MCP Apps UI resource, including connect, resource, frame, and base URI domains. type ToolExecutionCompleteUIResourceMetaUICsp struct { BaseURIDomains []string `json:"baseUriDomains,omitzero"` ConnectDomains []string `json:"connectDomains,omitzero"` @@ -3382,31 +3476,31 @@ type ToolExecutionCompleteUIResourceMetaUICsp struct { ResourceDomains []string `json:"resourceDomains,omitzero"` } -// Schema for the `ToolExecutionCompleteUIResourceMetaUIPermissions` type. +// Browser permission metadata for an MCP Apps UI resource, including camera, microphone, geolocation, and clipboard-write. type ToolExecutionCompleteUIResourceMetaUIPermissions struct { - // Schema for the `ToolExecutionCompleteUIResourceMetaUIPermissionsCamera` type. + // Marker object for camera permission on an MCP Apps UI resource. Camera *ToolExecutionCompleteUIResourceMetaUIPermissionsCamera `json:"camera,omitempty"` - // Schema for the `ToolExecutionCompleteUIResourceMetaUIPermissionsClipboardWrite` type. + // Marker object for clipboard-write permission on an MCP Apps UI resource. ClipboardWrite *ToolExecutionCompleteUIResourceMetaUIPermissionsClipboardWrite `json:"clipboardWrite,omitempty"` - // Schema for the `ToolExecutionCompleteUIResourceMetaUIPermissionsGeolocation` type. + // Marker object for geolocation permission on an MCP Apps UI resource. Geolocation *ToolExecutionCompleteUIResourceMetaUIPermissionsGeolocation `json:"geolocation,omitempty"` - // Schema for the `ToolExecutionCompleteUIResourceMetaUIPermissionsMicrophone` type. + // Marker object for microphone permission on an MCP Apps UI resource. Microphone *ToolExecutionCompleteUIResourceMetaUIPermissionsMicrophone `json:"microphone,omitempty"` } -// Schema for the `ToolExecutionCompleteUIResourceMetaUIPermissionsCamera` type. +// Marker object for camera permission on an MCP Apps UI resource. type ToolExecutionCompleteUIResourceMetaUIPermissionsCamera struct { } -// Schema for the `ToolExecutionCompleteUIResourceMetaUIPermissionsClipboardWrite` type. +// Marker object for clipboard-write permission on an MCP Apps UI resource. type ToolExecutionCompleteUIResourceMetaUIPermissionsClipboardWrite struct { } -// Schema for the `ToolExecutionCompleteUIResourceMetaUIPermissionsGeolocation` type. +// Marker object for geolocation permission on an MCP Apps UI resource. type ToolExecutionCompleteUIResourceMetaUIPermissionsGeolocation struct { } -// Schema for the `ToolExecutionCompleteUIResourceMetaUIPermissionsMicrophone` type. +// Marker object for microphone permission on an MCP Apps UI resource. type ToolExecutionCompleteUIResourceMetaUIPermissionsMicrophone struct { } @@ -3430,11 +3524,11 @@ type ToolExecutionStartToolDescription struct { // MCP Apps metadata for UI resource association type ToolExecutionStartToolDescriptionMeta struct { - // Schema for the `ToolExecutionStartToolDescriptionMetaUI` type. + // MCP Apps tool `_meta.ui` resource URI and visibility captured on `tool.execution_start`. UI *ToolExecutionStartToolDescriptionMetaUI `json:"ui,omitempty"` } -// Schema for the `ToolExecutionStartToolDescriptionMetaUI` type. +// MCP Apps tool `_meta.ui` resource URI and visibility captured on `tool.execution_start`. type ToolExecutionStartToolDescriptionMetaUI struct { // URI of the UI resource ResourceURI *string `json:"resourceUri,omitempty"` @@ -3486,6 +3580,21 @@ const ( AssistantUsageAPIEndpointWsResponses AssistantUsageAPIEndpoint = "ws:/responses" ) +// Outcome of the auto-approval safety judge for a permission request. Present only when auto mode is enabled; its absence means the judge did not evaluate the request (auto mode was off). +// Experimental: AutoApprovalRecommendation is part of an experimental API and may change or be removed. +type AutoApprovalRecommendation string + +const ( + // The judge evaluated the request and recommends automatically approving it. + AutoApprovalRecommendationApprove AutoApprovalRecommendation = "approve" + // The judge was consulted but did not return a usable recommendation, so the request requires explicit approval. + AutoApprovalRecommendationError AutoApprovalRecommendation = "error" + // Auto mode is enabled, but this request category is never auto-approvable (for example, sandbox-bypass requests), so the judge was not consulted. + AutoApprovalRecommendationExcluded AutoApprovalRecommendation = "excluded" + // The judge evaluated the request and does not recommend auto-approving it; explicit approval is required. Whether that means prompting, denying, or something else is the consumer's decision. + AutoApprovalRecommendationRequireApproval AutoApprovalRecommendation = "requireApproval" +) + // The user's auto-mode-switch choice type AutoModeSwitchResponse string @@ -3749,6 +3858,19 @@ const ( OmittedBinaryTypeResource OmittedBinaryType = "resource" ) +// Allow-all mode for the session. +// Experimental: PermissionAllowAllMode is part of an experimental API and may change or be removed. +type PermissionAllowAllMode string + +const ( + // Permission requests follow the normal approval flow with an LLM advisory recommendation attached; clients may choose to auto-approve requests the judge evaluated as acceptable. + PermissionAllowAllModeAuto PermissionAllowAllMode = "auto" + // Permission requests follow the normal approval flow. + PermissionAllowAllModeOff PermissionAllowAllMode = "off" + // Tool, path, and URL permission requests are automatically approved. + PermissionAllowAllModeOn PermissionAllowAllMode = "on" +) + // Kind discriminator for PermissionPromptRequest. type PermissionPromptRequestKind string diff --git a/go/types.go b/go/types.go index 3586a2a916..1e424514eb 100644 --- a/go/types.go +++ b/go/types.go @@ -122,6 +122,11 @@ type ClientOptions struct { // this handler instead of issuing the calls itself. Works for both CAPI // and BYOK sessions. RequestHandler *CopilotRequestHandler + // OnGitHubTelemetry registers a connection-level callback (experimental) + // that receives GitHub telemetry events the runtime forwards for sessions + // opened by this client. When non-nil, every session created or resumed by + // this client opts into telemetry forwarding (enableGitHubTelemetryForwarding). + OnGitHubTelemetry func(notification *rpc.GitHubTelemetryNotification) // Telemetry configures OpenTelemetry integration for the runtime. // When non-nil, COPILOT_OTEL_ENABLED=true is set and any populated // fields are mapped to the corresponding environment variables. @@ -2081,6 +2086,7 @@ type createSessionRequest struct { WorkingDirectory string `json:"workingDirectory,omitempty"` Streaming *bool `json:"streaming,omitempty"` IncludeSubAgentStreamingEvents *bool `json:"includeSubAgentStreamingEvents,omitempty"` + EnableGitHubTelemetryForwarding *bool `json:"enableGitHubTelemetryForwarding,omitempty"` MCPServers map[string]MCPServerConfig `json:"mcpServers,omitempty"` MCPOAuthTokenStorage string `json:"mcpOAuthTokenStorage,omitempty"` EnvValueMode string `json:"envValueMode,omitempty"` @@ -2179,6 +2185,7 @@ type resumeSessionRequest struct { ContinuePendingWork *bool `json:"continuePendingWork,omitempty"` Streaming *bool `json:"streaming,omitempty"` IncludeSubAgentStreamingEvents *bool `json:"includeSubAgentStreamingEvents,omitempty"` + EnableGitHubTelemetryForwarding *bool `json:"enableGitHubTelemetryForwarding,omitempty"` MCPServers map[string]MCPServerConfig `json:"mcpServers,omitempty"` MCPOAuthTokenStorage string `json:"mcpOAuthTokenStorage,omitempty"` EnvValueMode string `json:"envValueMode,omitempty"` diff --git a/go/zsession_events.go b/go/zsession_events.go index 8d11d4da54..24e726f7ad 100644 --- a/go/zsession_events.go +++ b/go/zsession_events.go @@ -20,6 +20,7 @@ type ( AssistantReasoningData = rpc.AssistantReasoningData AssistantReasoningDeltaData = rpc.AssistantReasoningDeltaData AssistantStreamingDeltaData = rpc.AssistantStreamingDeltaData + AssistantToolCallDeltaData = rpc.AssistantToolCallDeltaData AssistantTurnEndData = rpc.AssistantTurnEndData AssistantTurnStartData = rpc.AssistantTurnStartData AssistantUsageAPIEndpoint = rpc.AssistantUsageAPIEndpoint @@ -50,6 +51,7 @@ type ( AttachmentSelectionDetailsEnd = rpc.AttachmentSelectionDetailsEnd AttachmentSelectionDetailsStart = rpc.AttachmentSelectionDetailsStart AttachmentType = rpc.AttachmentType + AutoApprovalRecommendation = rpc.AutoApprovalRecommendation AutoModeSwitchCompletedData = rpc.AutoModeSwitchCompletedData AutoModeSwitchRequestedData = rpc.AutoModeSwitchRequestedData AutoModeSwitchResponse = rpc.AutoModeSwitchResponse @@ -132,9 +134,11 @@ type ( OmittedBinaryResult = rpc.OmittedBinaryResult OmittedBinaryType = rpc.OmittedBinaryType PendingMessagesModifiedData = rpc.PendingMessagesModifiedData + PermissionAllowAllMode = rpc.PermissionAllowAllMode PermissionApproved = rpc.PermissionApproved PermissionApprovedForLocation = rpc.PermissionApprovedForLocation PermissionApprovedForSession = rpc.PermissionApprovedForSession + PermissionAutoApproval = rpc.PermissionAutoApproval PermissionCancelled = rpc.PermissionCancelled PermissionCompletedData = rpc.PermissionCompletedData PermissionDeniedByContentExclusionPolicy = rpc.PermissionDeniedByContentExclusionPolicy @@ -329,6 +333,7 @@ type ( UserToolSessionApprovalMemory = rpc.UserToolSessionApprovalMemory UserToolSessionApprovalRead = rpc.UserToolSessionApprovalRead UserToolSessionApprovalWrite = rpc.UserToolSessionApprovalWrite + Verbosity = rpc.Verbosity WorkingDirectoryContext = rpc.WorkingDirectoryContext WorkingDirectoryContextHostType = rpc.WorkingDirectoryContextHostType WorkspaceFileChangedOperation = rpc.WorkspaceFileChangedOperation @@ -363,6 +368,10 @@ const ( AttachmentTypeGitHubTreeComparison = rpc.AttachmentTypeGitHubTreeComparison AttachmentTypeGitHubURL = rpc.AttachmentTypeGitHubURL AttachmentTypeSelection = rpc.AttachmentTypeSelection + AutoApprovalRecommendationApprove = rpc.AutoApprovalRecommendationApprove + AutoApprovalRecommendationError = rpc.AutoApprovalRecommendationError + AutoApprovalRecommendationExcluded = rpc.AutoApprovalRecommendationExcluded + AutoApprovalRecommendationRequireApproval = rpc.AutoApprovalRecommendationRequireApproval AutoModeSwitchResponseNo = rpc.AutoModeSwitchResponseNo AutoModeSwitchResponseYes = rpc.AutoModeSwitchResponseYes AutoModeSwitchResponseYesAlways = rpc.AutoModeSwitchResponseYesAlways @@ -441,6 +450,9 @@ const ( OmittedBinaryOmittedReasonTooLarge = rpc.OmittedBinaryOmittedReasonTooLarge OmittedBinaryTypeImage = rpc.OmittedBinaryTypeImage OmittedBinaryTypeResource = rpc.OmittedBinaryTypeResource + PermissionAllowAllModeAuto = rpc.PermissionAllowAllModeAuto + PermissionAllowAllModeOff = rpc.PermissionAllowAllModeOff + PermissionAllowAllModeOn = rpc.PermissionAllowAllModeOn PermissionPromptRequestKindCommands = rpc.PermissionPromptRequestKindCommands PermissionPromptRequestKindCustomTool = rpc.PermissionPromptRequestKindCustomTool PermissionPromptRequestKindExtensionManagement = rpc.PermissionPromptRequestKindExtensionManagement @@ -497,6 +509,7 @@ const ( SessionEventTypeAssistantReasoning = rpc.SessionEventTypeAssistantReasoning SessionEventTypeAssistantReasoningDelta = rpc.SessionEventTypeAssistantReasoningDelta SessionEventTypeAssistantStreamingDelta = rpc.SessionEventTypeAssistantStreamingDelta + SessionEventTypeAssistantToolCallDelta = rpc.SessionEventTypeAssistantToolCallDelta SessionEventTypeAssistantTurnEnd = rpc.SessionEventTypeAssistantTurnEnd SessionEventTypeAssistantTurnStart = rpc.SessionEventTypeAssistantTurnStart SessionEventTypeAssistantUsage = rpc.SessionEventTypeAssistantUsage @@ -647,6 +660,9 @@ const ( UserToolSessionApprovalKindMemory = rpc.UserToolSessionApprovalKindMemory UserToolSessionApprovalKindRead = rpc.UserToolSessionApprovalKindRead UserToolSessionApprovalKindWrite = rpc.UserToolSessionApprovalKindWrite + VerbosityHigh = rpc.VerbosityHigh + VerbosityLow = rpc.VerbosityLow + VerbosityMedium = rpc.VerbosityMedium WorkingDirectoryContextHostTypeADO = rpc.WorkingDirectoryContextHostTypeADO WorkingDirectoryContextHostTypeGitHub = rpc.WorkingDirectoryContextHostTypeGitHub WorkspaceFileChangedOperationCreate = rpc.WorkspaceFileChangedOperationCreate diff --git a/java/README.md b/java/README.md index 2498df1f5a..334db459f5 100644 --- a/java/README.md +++ b/java/README.md @@ -32,14 +32,14 @@ Replace `${copilot.sdk.version}` with the latest release from Maven Central. com.github copilot-sdk-java - 1.0.5 + 1.0.5-01 ``` ### Gradle ```groovy -implementation 'com.github:copilot-sdk-java:1.0.5' +implementation 'com.github:copilot-sdk-java:1.0.6-preview.1-01' ``` #### 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.6-SNAPSHOT + 1.0.7-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.6-SNAPSHOT' +implementation 'com.github:copilot-sdk-java:1.0.6-preview.1-01-SNAPSHOT' ``` ## Quick Start @@ -165,6 +165,73 @@ public String onlyContext(ToolInvocation invocation) { ... } public String report(@CopilotToolParam("Phase") String phase, ToolInvocation invocation, @CopilotToolParam("Limit") int limit) { ... } ``` +## Inline lambda tool definitions (experimental) + +For inline tool authoring at the session construction site, use `ToolDefinition.from(...)` with explicit parameter metadata: + +```java +import com.github.copilot.rpc.ToolDefinition; +import com.github.copilot.rpc.ToolDefer; +import com.github.copilot.tool.Param; + +ToolDefinition search = ToolDefinition + .from( + "search_items", + "Searches indexed items by keyword", + Param.of(String.class, "keyword", "Search keyword"), + keyword -> "Searching for: " + keyword) + .skipPermission(true) + .defer(ToolDefer.AUTO); +``` + +### Parameter metadata with `Param.of(...)` + +`Param.of(type, name, description)` creates a required parameter. For optional parameters with defaults: + +```java +Param limit = Param.of(Integer.class, "limit", "Max results", false, "10"); +``` + +### Async handlers + +Use `fromAsync` for asynchronous tool handlers: + +```java +import java.util.concurrent.CompletableFuture; + +ToolDefinition fetchData = ToolDefinition.fromAsync( + "fetch_data", + "Fetches data from remote source", + Param.of(String.class, "url", "Data source URL"), + url -> CompletableFuture.supplyAsync(() -> fetchRemote(url)) +); +``` + +### ToolInvocation context injection + +Inline tools can access `ToolInvocation` runtime context using `fromWithToolInvocation`: + +```java +ToolDefinition reportPhase = ToolDefinition.fromWithToolInvocation( + "report_phase", + "Reports the current phase with invocation context", + Param.of(String.class, "phase", "The current phase"), + (phase, invocation) -> "phase=" + phase + ", toolCallId=" + invocation.getToolCallId() +); +``` + +For async with `ToolInvocation`, use `fromAsyncWithToolInvocation`. + +### Fluent option modifiers + +Chain fluent modifiers to set tool options: + +- `.skipPermission(boolean)` — bypass permission prompts +- `.defer(ToolDefer)` — control deferred execution (`AUTO`, `NEVER`) +- `.overridesBuiltInTool(boolean)` — shadow built-in tools + +For design context and decision rationale, see [ADR-006](docs/adr/adr-006-tool-definition-inline.md). + ## Memory Sessions can opt into persistent memory, allowing the agent to read and write memory across turns. Memory is configured per session and applies to both `createSession` and `resumeSession`. diff --git a/java/docs/adr/adr-006-tool-definition-inline.md b/java/docs/adr/adr-006-tool-definition-inline.md new file mode 100644 index 0000000000..ad48527c10 --- /dev/null +++ b/java/docs/adr/adr-006-tool-definition-inline.md @@ -0,0 +1,118 @@ +# ADR-006: Inline tool definition with lambdas + +## Context and problem statement + +[ADR-005](adr-005-tool-definition.md) introduced an ergonomic Java tools API based on `@CopilotTool` method annotations, `@CopilotToolParam` parameter annotations, and `ToolDefinition.fromObject(...)` for reflection-based tool registration. That model works well when teams define tools as methods on a class. + +The next ergonomics goal is an inline style comparable to C# `CopilotTool.DefineTool(...)`, where developers can define a tool at the call site without creating a separate tool container class. + +For this decision, we evaluated two alternatives: + +* Method-reference registration (`ToolDefinition.from(tools::setCurrentPhase)`) +* Inline lambda registration (`ToolDefinition.from(..., phase -> ...)`) + +The key factor is metadata quality: tool name, description, parameter names, parameter descriptions, required/default semantics, and schema stability. + +## Considered options + +### Option 1: Method-reference API + +Example: + +```java +ToolDefinition setPhase = ToolDefinition.from(tools::setCurrentPhase); +``` + +In this model, metadata is sourced from existing method-level annotations (`@CopilotTool`, `@Param`) on the referenced method. + +Advantages: + +* Closest Java analog to C# method-group ergonomics +* High-quality metadata with minimal additional API surface +* Reuses ADR-005 metadata and invocation behavior directly + +Drawbacks: + +* Not truly inline: still requires a declared method (and usually annotations) elsewhere +* Does not solve the "define the whole tool at the call site" use case +* Method-reference resolution adds runtime/reflection complexity + +### Option 2: Inline lambda API with explicit metadata + +Example: + +```java +ToolDefinition setPhase = ToolDefinition.from( + "set_current_phase", + "Sets the current phase of the agent", + Param.of(String.class, "phase", "The phase to transition to"), + (String phase) -> { + currentPhase = phase; + return "Phase set to " + phase; + }); +``` + +In this model, handler logic is inline, and metadata is provided explicitly through `Param.of(...)` parameter definitions. + +Advantages: + +* True inline authoring at the session construction site +* No dependence on lambda parameter-name reflection or `-parameters` +* Deterministic metadata and schema generation +* Independent from annotation processing and generated companion classes + +Drawbacks: + +* Slightly more verbose than method-reference style because metadata is explicit +* Introduces new public API types for parameter definitions and typed lambda overloads +* Requires careful API design to stay concise for common one-parameter tools + +## Decision outcome + +Chosen: **Option 2 for ADR-006 scope** — inline lambda API with explicit metadata. + +Rationale: + +1. The primary requirement for this ADR is inline definition. Option 2 satisfies it directly; Option 1 does not. +1. Metadata quality is the critical requirement. Option 2 keeps metadata explicit and stable, instead of relying on fragile lambda introspection. +1. Option 2 can ship independently of method-reference support and without changes to annotation processing. +1. Option 2 preserves behavior parity with existing tool execution by delegating to `ToolDefinition` construction and current invocation semantics. + +Option 1 remains valuable and can be added independently as a separate ergonomic layer. It is not blocked by this decision. + +## Design constraints and non-goals + +Constraints for the inline lambda API: + +* Require explicit tool name and description. +* Require explicit parameter metadata (at minimum name and type, with optional description/required/default). +* Support both sync and async handlers (`R` and `CompletableFuture`). +* Keep result semantics aligned with existing behavior (`String` passthrough, `void` maps to `"Success"`, non-string objects serialized to JSON). +* Keep override/permission/defer flags available through options, consistent with existing `ToolDefinition` fields. + +Non-goals for this ADR: + +* Replacing `@CopilotTool`/`fromObject` APIs. +* Defining method-reference registration behavior in detail. +* Introducing compile-time code generation for lambda metadata. + +## Consequences + +The SDK now provides an explicit inline path for developers who prefer to keep tool declarations at session creation while preserving high-quality schema metadata. Implemented API families include: + +- `ToolDefinition.from(name, description, [params...], handler)` — sync handlers +- `ToolDefinition.fromAsync(name, description, [params...], asyncHandler)` — async handlers returning `CompletableFuture` +- `ToolDefinition.fromWithToolInvocation(...)` — sync with `ToolInvocation` context injection +- `ToolDefinition.fromAsyncWithToolInvocation(...)` — async with `ToolInvocation` context injection + +Parameter metadata is defined using `Param.of(type, name, description)` for required parameters and `Param.of(type, name, description, required, defaultValue)` for optional parameters with defaults. + +Fluent option modifiers (`.skipPermission(boolean)`, `.defer(ToolDefer)`, `.overridesBuiltInTool(boolean)`) allow post-construction customization. + +The annotation-driven API from [ADR-005](adr-005-tool-definition.md) remains the recommended path for larger tool surfaces where co-locating metadata with method implementations improves maintainability. For usage examples and complete API coverage, see the Java SDK README. + +## Related work items + +* #1682 +* #1792 +* #1810 diff --git a/java/docs/adr/adr-007-native-bundling-strategy.md b/java/docs/adr/adr-007-native-bundling-strategy.md new file mode 100644 index 0000000000..d2a76d161f --- /dev/null +++ b/java/docs/adr/adr-007-native-bundling-strategy.md @@ -0,0 +1,199 @@ +# ADR-007: DRAFT: Native runtime bundling strategy — per-platform classifier JARs + +## Context and Problem Statement + +The Copilot SDK for Java currently has no embedded runtime. It depends on an externally provided runtime process (see epic [#1917](https://github.com/github/copilot-sdk/issues/1917)). The ongoing Rust port of the `copilot-agent-runtime` repository is reaching the point where the runtime can be consumed as a native shared library without requiring a Node.js process, making it practical to embed the runtime directly in the SDK JAR. + +### The runtime artifact + +The artifact to be embedded is `runtime.node`, a Rust [`cdylib`](#references) produced by the `src/runtime` crate in `github/copilot-agent-runtime` using the [napi-rs](#references) build toolchain. Despite the `.node` file extension (a naming convention of napi-rs), this is an ordinary platform-specific shared library (`.so` on Linux, `.dylib` on macOS, `.dll` on Windows). It exposes two front doors built over the same internal engine: + +- **[napi](#references) front door** — loaded by a Node.js process as a native addon (current CLI path). +- **[C ABI](#references) front door** — a fixed set of approximately 12 `extern "C"` lifecycle and transport entry points (`copilot_runtime_server_create`, `copilot_runtime_connection_open`, etc.) that any language can call in-process via [FFI](#references) ([JNA](#references) for Java, Python/cffi, C#/`DllImport`, Go/purego) **without a Node.js process**. All API methods travel as JSON-RPC data through this fixed transport; the export list never changes as the method set grows. + +The `cli-native.node` addon — a separate, smaller artifact that provides ICU4X text segmentation, Win32 API wrappers, and terminal UI helpers — is a CLI-only artifact used by the Ink/React terminal interface. It is **not needed** by the Java SDK. + +### Note on the active Rust migration + +As of 2026-07, the `runtime.node` binary is being built up iteratively as TypeScript runtime code is ported into it. It is **not** being reduced; it is growing with each port PR. The `embedded_host.rs` module in the runtime currently spawns a short-lived child process to service method bodies not yet ported to Rust. This internal Node.js dependency shrinks with each port PR and is expected to disappear entirely when the migration completes. The C ABI surface and loading mechanism described in this ADR are stable regardless of migration progress. + +### Platform dimensions + +The runtime must be built for each unique combination of OS, CPU architecture, and (on Linux) C runtime variant. The build system in `github/copilot-agent-runtime` produces eight Rust target triples: + +| Platform label | Rust triple | Constraint | +|---------------|-------------|------------| +| `linux-x64` | `x86_64-unknown-linux-gnu` | [glibc](#references) ≥ 2.28 (Debian 10+, Ubuntu 20.04+, RHEL 8+) | +| `linux-arm64` | `aarch64-unknown-linux-gnu` | glibc ≥ 2.28 | +| `linuxmusl-x64` | `x86_64-unknown-linux-musl` | dynamically links [musl libc](#references) (Alpine Linux) | +| `linuxmusl-arm64` | `aarch64-unknown-linux-musl` | dynamically links musl libc | +| `darwin-x64` | `x86_64-apple-darwin` | macOS, Intel | +| `darwin-arm64` | `aarch64-apple-darwin` | macOS, Apple Silicon | +| `win32-x64` | `x86_64-pc-windows-msvc` | [MSVC CRT](#references) statically linked (`+crt-static`) | +| `win32-arm64` | `aarch64-pc-windows-msvc` | MSVC CRT statically linked (`+crt-static`) | + +The GNU/Linux glibc minimum of 2.28 is enforced at build time via a Microsoft/vscode-linux-build-agent sysroot and verified post-build by `script/linux/verify-glibc-requirements.sh`. The musl binaries are **not** fully statically linked; they dynamically link musl libc (`-C target-feature=-crt-static` is explicitly set at build time). + +The **common case** (Windows × 2 + macOS × 2 + GNU/Linux × 2) requires **6 binaries**. Supporting Alpine Linux adds 2 more musl binaries for a total of **8**. + +### Platform selection is 100% deterministic + +The correct binary can be selected at runtime without any heuristics, using only standard Java and OS APIs: + +1. **OS**: `System.getProperty("os.name")` — distinguishes Windows, macOS, and Linux unambiguously. +2. **Architecture**: `System.getProperty("os.arch")` — `"amd64"` and `"x86_64"` both map to `x64`; `"aarch64"` and `"arm64"` both map to `arm64`. +3. **Linux libc variant**: Read the first 2 KB of `/proc/self/exe` and parse the [ELF](#references) PT_INTERP segment (the dynamic linker path). If the interpreter path contains `/ld-musl-` → musl; if it contains `/ld-linux-` → glibc. This requires no subprocess, no PATH lookup, and works inside containers. This is the same approach used by the `detect-libc` npm package (its primary, most reliable detection method). + +### Size baseline + +Measured from `github/copilot-agent-runtime` release `cli-1.0.69-2` (2026-07-06): + +| Platform | `runtime.node` (uncompressed) | Compressed (~40% deflate) | +|----------|------------------------------|--------------------------| +| `linux-x64` | 64.7 MB | ~25.9 MB | +| `linux-arm64` | 55.5 MB | ~22.2 MB | +| `linuxmusl-x64` | 64.4 MB | ~25.8 MB | +| `linuxmusl-arm64` | 55.3 MB | ~22.1 MB | +| `darwin-x64` | 57.3 MB | ~22.9 MB | +| `darwin-arm64` | 48.1 MB | ~19.2 MB | +| `win32-x64` | 55.9 MB | ~22.4 MB | +| `win32-arm64` | 48.4 MB | ~19.4 MB | + +The published Java SDK JAR (`copilot-sdk-java-1.0.6-preview.1.jar`) is currently **1.53 MB**. A monolithic JAR containing all 6 common-case native binaries would be approximately **132 MB** compressed; all 8 including musl would be approximately **180 MB** compressed. + +All native dependencies within the runtime (`rustls`/`aws-lc-rs` for TLS, `rusqlite` with `bundled` feature for SQLite, `zlib-rs` for compression) are statically compiled into the binary. There are no dependencies on system OpenSSL, libgit2, or libz. + +## Considered Options + +### Option 1: Monolithic JAR — all platform binaries in one artifact + +All 6 (or 8) `runtime.node` binaries are bundled inside the single `copilot-sdk-java` artifact. At runtime the SDK extracts and loads the one matching the current platform; the remaining 5–7 are carried silently. + +**Advantages:** +- Single `` in `pom.xml`; zero extra configuration for users. +- Familiar pattern: [ONNX Runtime](#references) (`onnxruntime-1.21.0.jar`, **130 MB**, all platforms) demonstrates this is an accepted norm in the Java ML ecosystem. + +**Drawbacks:** +- Every user downloads every platform regardless of their target. A developer on Apple Silicon downloads 105+ MB of Linux and Windows binaries they will never use. +- Build tooling (thin Docker layers, incremental CI caches, artifact registries) penalises large JARs. A single 132–180 MB JAR invalidates the entire cache whenever any platform's binary changes. +- Maven's dependency resolution has no mechanism to supply platform-appropriate variants automatically; platform selection must happen entirely at runtime inside the JAR. +- Conflicts with the principle that Maven artifacts should be reproducible and minimal. + +### Option 2: Per-platform classifier JARs ([DJL](#references) style) + +A small, pure-Java coordination artifact (`copilot-sdk-java`, ~1.5 MB) is published alongside separate per-platform native artifacts differentiated by Maven classifier: + +``` +com.github:copilot-sdk-java-runtime:VERSION:linux-x64 +com.github:copilot-sdk-java-runtime:VERSION:linux-arm64 +com.github:copilot-sdk-java-runtime:VERSION:linuxmusl-x64 +com.github:copilot-sdk-java-runtime:VERSION:linuxmusl-arm64 +com.github:copilot-sdk-java-runtime:VERSION:darwin-x64 +com.github:copilot-sdk-java-runtime:VERSION:darwin-arm64 +com.github:copilot-sdk-java-runtime:VERSION:win32-x64 +com.github:copilot-sdk-java-runtime:VERSION:win32-arm64 +``` + +Each classifier JAR contains only the `runtime.node` binary for that platform (~19–26 MB compressed) plus a small `.properties` metadata file. The coordination artifact selects and loads the matching native at startup. + +This is the same pattern used by DJL's PyTorch native artifacts (`pytorch-native-cpu-2.5.1-linux-x86_64.jar`, `pytorch-native-cpu-2.5.1-osx-aarch64.jar`, etc.), Netty's `netty-tcnative-boringssl-static` per-platform JARs, and others. + +Build tools can be configured to resolve the correct classifier automatically: + +- **Maven**: `${os.detected.classifier}` via [os-maven-plugin](#references). +- **Gradle**: variant-aware dependency resolution with attribute matching. +- **Uber-jar builds**: include all classifiers; the coordination artifact picks the right one at runtime. + +**Advantages:** +- Default download is the tiny coordination artifact (~1.5 MB) plus one platform JAR (~20–26 MB compressed) — approximately **22–28 MB total** vs. 132–180 MB for a monolithic JAR. +- Each platform JAR changes independently; CI caches and Docker layers for unchanged platforms are preserved across releases. +- Users building for a single known platform (most production deployments) pay exactly the cost of that platform. +- Follows well-established Maven ecosystem conventions; standard tooling ([os-maven-plugin](#references), Gradle variant resolution) handles classifier selection. +- Aligns with DJL's proven distribution strategy for large native ML runtimes. + +**Drawbacks:** +- Requires publishing 6–8 additional Maven artifacts per release. +- Users building portable über-JARs must explicitly include all classifiers they wish to support. +- Slightly more complex `pom.xml` / `build.gradle` for users who need cross-platform packaging. + +### Option 3: Download-on-demand (DJL thin placeholder style) + +The SDK ships a minimal placeholder that detects the current platform at runtime and downloads the correct `runtime.node` binary from a distribution endpoint (GitHub Releases or a CDN) on first use, caching it locally (e.g., `~/.copilot/runtime-cache/`). + +**Advantages:** +- Zero native binary content in any published Maven artifact; total download at `mvn install` is negligible. +- Identical user experience to the current "externally provided runtime" model during the download, which most CLI users already accept. + +**Drawbacks:** +- Requires internet access on first run. Offline environments (air-gapped enterprise, CI without outbound HTTP) break silently or require manual pre-seeding. +- Introduces a network dependency into an otherwise pure library artifact, which violates Maven Central's expectations for reproducible builds. +- Adds an operational concern: distribution endpoint availability, CDN costs, URL stability across versions. +- Makes JVM startup non-deterministic in latency (first run downloads 20–26 MB). +- Cannot be pre-warmed by dependency management tooling; no `mvn dependency:resolve` analogue works for a runtime download. + +## Decision Outcome + +**Chosen: Option 2 — per-platform classifier JARs.** + +### Rationale + +1. **User download cost matches actual need.** Most users run on one OS and architecture. Option 2 makes their download approximately 22–28 MB (coordination JAR + one platform JAR), versus 132–180 MB for Option 1 and an unbounded deferred network cost for Option 3. + +2. **Proven ecosystem pattern.** DJL, Netty, and others have established the per-classifier pattern as the correct Maven idiom for large native binaries. Build tooling already knows how to handle it; users and framework integrations (Spring Boot, Quarkus, Micronaut) are familiar with it. + +3. **Cache efficiency.** Individual platform JARs change only when that platform's binary changes. Unchanged platform JARs are never re-downloaded or re-cached by CI or developer machines. + +4. **No operational dependencies.** Unlike Option 3, no external download service is required at runtime. The artifact is self-contained once resolved by Maven/Gradle. + +5. **Size per platform is acceptable.** At ~20–26 MB compressed per platform, each classifier JAR is well within the range of routinely used native JARs in the Java ecosystem (DJL PyTorch osx-aarch64: 37 MB; ONNX Runtime per platform: ~20–30 MB before bundling). + +6. **Option 3 remains composable.** A download-on-demand fallback can be layered on top of Option 2 for users who prefer it without changing the primary distribution model. The coordination artifact can attempt classpath lookup first, then fall back to a cached download if no matching classifier JAR is present. + +## Consequences + +- A new Maven module (`copilot-sdk-java-runtime` or similar) is introduced to hold the per-platform native JARs. The existing `copilot-sdk-java` coordination artifact depends on it. +- The coordination artifact gains a platform detection and native loading component that: + 1. Detects OS, architecture, and Linux libc variant deterministically as described above. + 2. Locates the matching `runtime.node` binary on the classpath (via `getResourceAsStream` from the classifier JAR). + 3. Extracts the binary to a temporary or cached location (e.g., `~/.copilot/runtime-cache/`) if not already present. + 4. Loads it via [JNA](#references) using the C ABI entry points. +- The release pipeline for `github/copilot-agent-runtime` must produce the per-platform `runtime.node` binaries as inputs to the Java SDK publish workflow. The per-platform `pkg-tarballs-` artifacts from the `publish-cli.yml` workflow are the authoritative source. +- Each release of `copilot-sdk-java` publishes 6 (or 8) classifier JARs to Maven Central alongside the coordination JAR. +- The version of the bundled `runtime.node` is recorded in the coordination JAR's manifest and queryable at runtime, enabling diagnostics and mismatch detection. +- `cli-native.node` is not bundled. It provides only terminal UI features (ICU4X text segmentation, Win32 APIs, OS desktop notifications) that are irrelevant to the Java SDK's programmatic API surface. + +## Related work items + +- https://github.com/github/copilot-sdk/issues/1917 — Epic: Embed Rust-based Copilot CLI Runtime and cease requiring Node.js +- https://devdiv.visualstudio.com/DevDiv/_workitems/edit/3028097 +- https://github.com/github/copilot-sdk/pull/1901 dotnet: in-process FFI runtime hosting (InProcess transport) +- https://github.com/github/copilot-sdk/pull/1915 Add in-process FFI transport for Rust and TypeScript SDKs + +### References + +| Term | Definition | Link | +|------|------------|------| +| **FFI** (Foreign Function Interface) | A mechanism by which code written in one language can call functions defined in another. In this ADR, Java calls into the Rust runtime shared library via JNA's FFI layer. | https://en.wikipedia.org/wiki/Foreign_function_interface | +| **JNA** (Java Native Access) | A Java library that provides easy access to native shared libraries without requiring the JNI boilerplate. Used here to call the `extern "C"` C ABI entry points exported by `runtime.node`. | https://github.com/java-native-access/jna | +| **napi-rs** | A Rust framework for building native Node.js addons using the Node-API (napi) stable ABI. Produces the `.node` file and generates TypeScript type declarations automatically. | https://napi.rs/ | +| **cdylib** | A Rust `crate-type` that produces a C-compatible dynamic shared library (`.so` / `.dylib` / `.dll`). Distinct from `dylib` (Rust-to-Rust only) and `staticlib`. | https://doc.rust-lang.org/reference/linkage.html | +| **napi (Node-API)** | A stable C ABI provided by Node.js for building native addons that remain binary-compatible across Node.js versions. `napi-rs` generates Rust code against this interface. | https://nodejs.org/api/n-api.html | +| **C ABI** (Application Binary Interface) | The low-level contract between a compiled binary and its callers: calling conventions, data type layouts, symbol naming. An `extern "C"` ABI uses C's conventions, making a library callable from any language that speaks C FFI. | https://en.wikipedia.org/wiki/Application_binary_interface | +| **ELF PT_INTERP** | A segment in an [ELF](https://man7.org/linux/man-pages/man5/elf.5.html) binary (the Linux/Unix executable format) that records the path of the dynamic linker/interpreter. On glibc systems this path is `/lib64/ld-linux-x86-64.so.2`; on musl systems it is `/lib/ld-musl-x86_64.so.1`. Inspecting it is the most reliable way to detect glibc vs. musl at runtime without executing a subprocess. | https://man7.org/linux/man-pages/man5/elf.5.html | +| **glibc** (GNU C Library) | The standard C runtime library on most mainstream Linux distributions (Debian, Ubuntu, RHEL, Fedora, SLES). Binaries linked against glibc require the same version or newer to be present at runtime. The `runtime.node` glibc build requires glibc ≥ 2.28. | https://www.gnu.org/software/libc/ | +| **musl libc** | An alternative C standard library optimised for static linking and used as the default libc on Alpine Linux. Not binary-compatible with glibc; a separate `runtime.node` build is required. | https://musl.libc.org/ | +| **MSVC CRT** (Microsoft Visual C++ Runtime) | The C runtime library shipped with Visual Studio. When compiled with `+crt-static` (as `runtime.node` is on Windows), it is statically linked into the binary and the end-user does not need to install the Visual C++ Redistributable. | https://learn.microsoft.com/en-us/cpp/c-runtime-library/c-run-time-library-reference | +| **DJL** (Deep Java Library) | Amazon's open-source Java framework for ML inference, used here as a reference for the per-platform classifier JAR distribution pattern. Its PyTorch native artifacts (`pytorch-native-cpu-*-.jar`) are the direct model for the proposed `copilot-sdk-java-runtime:VERSION:` artifacts. | https://djl.ai/ | +| **os-maven-plugin** | A Maven extension that detects the current OS and architecture and exposes them as properties (e.g., `${os.detected.classifier}`) so that `` values can be resolved at build time rather than hardcoded. | https://github.com/trustin/os-maven-plugin | +| **ONNX Runtime** | Microsoft's cross-platform ML inference runtime, used in this ADR as the size comparable for a monolithic all-platform JAR (~130 MB, Option 1). | https://onnxruntime.ai/ | + +Additional source references: + +- DJL native distribution pattern: https://github.com/deepjavalibrary/djl/tree/master/engines/pytorch/pytorch-native +- DJL `Platform.fromSystem()` (OS/arch detection): https://github.com/deepjavalibrary/djl/blob/master/api/src/main/java/ai/djl/util/Platform.java +- `detect-libc` npm package (ELF PT_INTERP libc detection): https://github.com/lovell/detect-libc +- `github/copilot-agent-runtime` C ABI front door (`cabi.rs`): `src/runtime/src/interop/cabi.rs` +- `github/copilot-agent-runtime` build target definitions: `script/build-runtime.ts` +- `github/copilot-agent-runtime` glibc sysroot and verification: `script/linux/install-sysroot.cjs`, `script/linux/verify-glibc-requirements.sh` +- ONNX Runtime Java on Maven Central (size comparable): https://repo1.maven.org/maven2/com/microsoft/onnxruntime/onnxruntime/1.21.0/ + diff --git a/java/jbang-example.java b/java/jbang-example.java index 51a3df1a76..9bb83457ad 100644 --- a/java/jbang-example.java +++ b/java/jbang-example.java @@ -1,5 +1,5 @@ ///usr/bin/env jbang "$0" "$@" ; exit $? -//DEPS com.github:copilot-sdk-java:1.0.5 +//DEPS com.github:copilot-sdk-java:1.0.6-preview.1-01 import com.github.copilot.CopilotClient; import com.github.copilot.generated.AssistantMessageEvent; import com.github.copilot.generated.SessionUsageInfoEvent; diff --git a/java/pom.xml b/java/pom.xml index 35f66287f2..30be279b0e 100644 --- a/java/pom.xml +++ b/java/pom.xml @@ -7,7 +7,7 @@ com.github copilot-sdk-java - 1.0.6-SNAPSHOT + 1.0.7-SNAPSHOT jar GitHub Copilot SDK :: Java @@ -86,7 +86,7 @@ DO NOT EDIT MANUALLY. Updated by the update-copilot-dependency workflow. --> - ^1.0.67 + ^1.0.69 diff --git a/java/scripts/codegen/java.ts b/java/scripts/codegen/java.ts index ac65c8003d..7c2a5cebea 100644 --- a/java/scripts/codegen/java.ts +++ b/java/scripts/codegen/java.ts @@ -1651,15 +1651,16 @@ function addWrapperResultImports(resultType: string, allImports: Set, pa } /** - * Return the params class name if the method has a params schema with properties - * other than sessionId (i.e. there are user-supplied parameters). + * Return the params class name if the method has a params schema with user-supplied properties. + * Session-scoped wrappers inject sessionId automatically, but server-scoped wrappers must let + * callers supply it explicitly. */ -function wrapperParamsClassName(method: RpcMethodNode): string | null { +function wrapperParamsClassName(method: RpcMethodNode, isSession: boolean): string | null { let params = method.params; if (params?.$ref) params = resolveRef(params) as JSONSchema7; if (!params || typeof params !== "object") return null; const props = params.properties ?? {}; - const userProps = Object.keys(props).filter((k) => k !== "sessionId"); + const userProps = Object.keys(props).filter((k) => !isSession || k !== "sessionId"); if (userProps.length === 0) return null; return rpcMethodToClassName(method.rpcMethod) + "Params"; } @@ -1682,7 +1683,7 @@ function generateApiMethod( sessionIdExpr: string ): { lines: string[]; needsMapper: boolean; needsExperimentalImport: boolean } { const resultClass = wrapperResultClassName(method); - const paramsClass = wrapperParamsClassName(method); + const paramsClass = wrapperParamsClassName(method, isSession); const hasSessionId = methodHasSessionId(method); const hasExtraParams = paramsClass !== null; let needsMapper = false; @@ -1790,7 +1791,7 @@ async function generateNamespaceApiFile( const methodLines: string[] = []; for (const [key, method] of tree.methods) { const resultClass = wrapperResultClassName(method); - const paramsClass = wrapperParamsClassName(method); + const paramsClass = wrapperParamsClassName(method, isSession); addWrapperResultImports(resultClass, allImports, packageName); if (paramsClass) allImports.add(`${packageName}.${paramsClass}`); @@ -1910,7 +1911,7 @@ async function generateRpcRootFile( const methodLines: string[] = []; for (const [key, method] of tree.methods) { const resultClass = wrapperResultClassName(method); - const paramsClass = wrapperParamsClassName(method); + const paramsClass = wrapperParamsClassName(method, isSession); addWrapperResultImports(resultClass, allImports, packageName); if (paramsClass) allImports.add(`${packageName}.${paramsClass}`); diff --git a/java/scripts/codegen/package-lock.json b/java/scripts/codegen/package-lock.json index 27689da9a6..bb3df20f6b 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.67", + "@github/copilot": "^1.0.69", "json-schema": "^0.4.0", "tsx": "^4.22.4" } @@ -428,9 +428,9 @@ } }, "node_modules/@github/copilot": { - "version": "1.0.67", - "resolved": "https://registry.npmjs.org/@github/copilot/-/copilot-1.0.67.tgz", - "integrity": "sha512-5YEY9LNXBT9Q8uShjCdYcornJJJhGtdIzSYla2+pjfXYpHsDVibqYubzYjfgffOUKFChyzOpH7n/868+t56iIg==", + "version": "1.0.69", + "resolved": "https://registry.npmjs.org/@github/copilot/-/copilot-1.0.69.tgz", + "integrity": "sha512-sri6PKtRQu7mfok3eV505fmYo6M6PGZpB1vd+QbMEm2rEZaKE2+wJGCAtkpQNKfAWBvvoW/C94IaeFyyulUvqQ==", "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.67", - "@github/copilot-darwin-x64": "1.0.67", - "@github/copilot-linux-arm64": "1.0.67", - "@github/copilot-linux-x64": "1.0.67", - "@github/copilot-linuxmusl-arm64": "1.0.67", - "@github/copilot-linuxmusl-x64": "1.0.67", - "@github/copilot-win32-arm64": "1.0.67", - "@github/copilot-win32-x64": "1.0.67" + "@github/copilot-darwin-arm64": "1.0.69", + "@github/copilot-darwin-x64": "1.0.69", + "@github/copilot-linux-arm64": "1.0.69", + "@github/copilot-linux-x64": "1.0.69", + "@github/copilot-linuxmusl-arm64": "1.0.69", + "@github/copilot-linuxmusl-x64": "1.0.69", + "@github/copilot-win32-arm64": "1.0.69", + "@github/copilot-win32-x64": "1.0.69" } }, "node_modules/@github/copilot-darwin-arm64": { - "version": "1.0.67", - "resolved": "https://registry.npmjs.org/@github/copilot-darwin-arm64/-/copilot-darwin-arm64-1.0.67.tgz", - "integrity": "sha512-CO3mpgFXcN6e7ZsSmjMkt1AKxMfb1+mjdn3yrf2DRnnWIURSK9kGvw+E+E1+YE37D1MBiUn/VOBmhRad5+vl0A==", + "version": "1.0.69", + "resolved": "https://registry.npmjs.org/@github/copilot-darwin-arm64/-/copilot-darwin-arm64-1.0.69.tgz", + "integrity": "sha512-LpaVS2o21BbYTFUlkqdwUJXqE0l1Lp50Cb/EHru3z+5n5x7zNUU5IlPxdMg5gHMVw78clnMn2zSJbe5hYFyDDQ==", "cpu": [ "arm64" ], @@ -466,9 +466,9 @@ } }, "node_modules/@github/copilot-darwin-x64": { - "version": "1.0.67", - "resolved": "https://registry.npmjs.org/@github/copilot-darwin-x64/-/copilot-darwin-x64-1.0.67.tgz", - "integrity": "sha512-M20Hpn3bOJRkVwAIVRK4ZlX66AqtmGfXZRxZBRFQC045QIwcfmVUP45sTSgXDb4uHWeK0cZgdTdniHwKGtMplw==", + "version": "1.0.69", + "resolved": "https://registry.npmjs.org/@github/copilot-darwin-x64/-/copilot-darwin-x64-1.0.69.tgz", + "integrity": "sha512-QL5bsmnYqliBVIEE8RY91e6yAr39c2TNvJc/5IzoaKMlQ9MCrD9xakBJwd7LOS0SiOwRE4fqn8pUA4L1+UU5fg==", "cpu": [ "x64" ], @@ -482,9 +482,9 @@ } }, "node_modules/@github/copilot-linux-arm64": { - "version": "1.0.67", - "resolved": "https://registry.npmjs.org/@github/copilot-linux-arm64/-/copilot-linux-arm64-1.0.67.tgz", - "integrity": "sha512-b4ePtFBow+Ior+aVLKA1hHxhR5wF+ql5CD7TSg/NHGYgc1kwD+3a9uKSENy05J5Lit/G/DZ9C6JwowvvdMWSKg==", + "version": "1.0.69", + "resolved": "https://registry.npmjs.org/@github/copilot-linux-arm64/-/copilot-linux-arm64-1.0.69.tgz", + "integrity": "sha512-SOibp0I6APLoY2KkccbNOISpbhE3lp41s9fM3eANqfdoHQLsgBopfk4ruBP8k/DzjQA6t4MrbV8v6dLixAqv3w==", "cpu": [ "arm64" ], @@ -498,9 +498,9 @@ } }, "node_modules/@github/copilot-linux-x64": { - "version": "1.0.67", - "resolved": "https://registry.npmjs.org/@github/copilot-linux-x64/-/copilot-linux-x64-1.0.67.tgz", - "integrity": "sha512-4ynZyfKnWAdvEPAFDDBIz1wpFttcOTJu4Y8Mlz5oXCBA0NM/rwr8K4l7Adp8UzwbfmdrMJ9y+zivqRBMDbPInA==", + "version": "1.0.69", + "resolved": "https://registry.npmjs.org/@github/copilot-linux-x64/-/copilot-linux-x64-1.0.69.tgz", + "integrity": "sha512-URT7UaR3l1VAAtEpbKjybup2vxHTMSfDTVlu0jinu1O2nWxFOeQ9N4BQgRdcCB7wDu3N8Yz47o3fV1Gob6Z1Yw==", "cpu": [ "x64" ], @@ -514,9 +514,9 @@ } }, "node_modules/@github/copilot-linuxmusl-arm64": { - "version": "1.0.67", - "resolved": "https://registry.npmjs.org/@github/copilot-linuxmusl-arm64/-/copilot-linuxmusl-arm64-1.0.67.tgz", - "integrity": "sha512-IjezxBU8fYUr/b5hEiniXqzwoOrJ4egrQSBbG96M+roLTqd9txP0MgxZtcRtKV7phRIdIGE109wwrn4H6hSqmA==", + "version": "1.0.69", + "resolved": "https://registry.npmjs.org/@github/copilot-linuxmusl-arm64/-/copilot-linuxmusl-arm64-1.0.69.tgz", + "integrity": "sha512-Y6DY9RkyWV6l+S0cNz4d+AkUa38goNV5StQH3j8OoVrndK3+RgQeUjG1XpJ//PzXEXi4vQ0cZuc9XzFj/TY7tw==", "cpu": [ "arm64" ], @@ -530,9 +530,9 @@ } }, "node_modules/@github/copilot-linuxmusl-x64": { - "version": "1.0.67", - "resolved": "https://registry.npmjs.org/@github/copilot-linuxmusl-x64/-/copilot-linuxmusl-x64-1.0.67.tgz", - "integrity": "sha512-Zy/rbja1lnhzDoNfn051H0EybCseCvjvH7WmbcHCayjXUjzXeKF6OmAt4hvqFZH87ttT3KbKtQ8/6oDUhhM2YQ==", + "version": "1.0.69", + "resolved": "https://registry.npmjs.org/@github/copilot-linuxmusl-x64/-/copilot-linuxmusl-x64-1.0.69.tgz", + "integrity": "sha512-h0aNgUWRu70Tgp4/7Vdqa/4XKJi96wP32R1PoKeXkz1QNxb6i/IQxhoCjjS+xFon9e2kefKdF8pDrDtUJjsiBQ==", "cpu": [ "x64" ], @@ -546,9 +546,9 @@ } }, "node_modules/@github/copilot-win32-arm64": { - "version": "1.0.67", - "resolved": "https://registry.npmjs.org/@github/copilot-win32-arm64/-/copilot-win32-arm64-1.0.67.tgz", - "integrity": "sha512-O3VFRS5v9NXRP8o+N1SvcFbBqECDzZP7XQBeBj2Vcrma80gdJc5GQub/w2mwmr1w5UbwgzJkRasm0Ec/jxbcoA==", + "version": "1.0.69", + "resolved": "https://registry.npmjs.org/@github/copilot-win32-arm64/-/copilot-win32-arm64-1.0.69.tgz", + "integrity": "sha512-7DMoC1uwt01yZf48xq8MgKdnHacXabfsGsOizbgmrlZMfvQQofrF55x1Efu3BKsEKFGRl4fCLjJHJYCz/nWsHg==", "cpu": [ "arm64" ], @@ -562,9 +562,9 @@ } }, "node_modules/@github/copilot-win32-x64": { - "version": "1.0.67", - "resolved": "https://registry.npmjs.org/@github/copilot-win32-x64/-/copilot-win32-x64-1.0.67.tgz", - "integrity": "sha512-td5tQ/nve5dB7RPvNglBZwa/6DJqiOBgacXXa1GpYcohqpCzoI8gONNkeaeyr6oF4iu5wXJ9krUNr6QXL4yB5Q==", + "version": "1.0.69", + "resolved": "https://registry.npmjs.org/@github/copilot-win32-x64/-/copilot-win32-x64-1.0.69.tgz", + "integrity": "sha512-5uD1/k6oVzqRhiS9jLrySGa20HjItqUJsaMe+bV6eYT7zQPF/74xihEjxMm3/bJcATW2SesiYQxyNzTcKm3qRA==", "cpu": [ "x64" ], diff --git a/java/scripts/codegen/package.json b/java/scripts/codegen/package.json index 57de91c91f..a8e592d969 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.67", + "@github/copilot": "^1.0.69", "json-schema": "^0.4.0", "tsx": "^4.22.4" } diff --git a/java/src/generated/java/com/github/copilot/generated/AssistantToolCallDeltaEvent.java b/java/src/generated/java/com/github/copilot/generated/AssistantToolCallDeltaEvent.java new file mode 100644 index 0000000000..72b629c0c6 --- /dev/null +++ b/java/src/generated/java/com/github/copilot/generated/AssistantToolCallDeltaEvent.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 "assistant.tool_call_delta". Streaming tool-call input delta for incremental tool-call updates + * @since 1.0.0 + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public final class AssistantToolCallDeltaEvent extends SessionEvent { + + @Override + public String getType() { return "assistant.tool_call_delta"; } + + @JsonProperty("data") + private AssistantToolCallDeltaEventData data; + + public AssistantToolCallDeltaEventData getData() { return data; } + public void setData(AssistantToolCallDeltaEventData data) { this.data = data; } + + /** Data payload for {@link AssistantToolCallDeltaEvent}. */ + @JsonIgnoreProperties(ignoreUnknown = true) + @JsonInclude(JsonInclude.Include.NON_NULL) + public record AssistantToolCallDeltaEventData( + /** Tool call ID this delta belongs to, matching the corresponding assistant.message tool request */ + @JsonProperty("toolCallId") String toolCallId, + /** Name of the tool being invoked, when known from the stream */ + @JsonProperty("toolName") String toolName, + /** Tool call type, when known from the stream */ + @JsonProperty("toolType") AssistantMessageToolRequestType toolType, + /** Raw provider tool input fragment to append for this tool call. Function/tool-use providers stream serialized JSON argument text (so newlines inside JSON string values may appear as escaped `\n` until the accumulated JSON is parsed); custom tool calls stream raw custom input. */ + @JsonProperty("inputDelta") String inputDelta + ) { + } +} diff --git a/java/src/generated/java/com/github/copilot/generated/AssistantTurnEndEvent.java b/java/src/generated/java/com/github/copilot/generated/AssistantTurnEndEvent.java index 28146b05bb..082f62b476 100644 --- a/java/src/generated/java/com/github/copilot/generated/AssistantTurnEndEvent.java +++ b/java/src/generated/java/com/github/copilot/generated/AssistantTurnEndEvent.java @@ -35,7 +35,9 @@ public final class AssistantTurnEndEvent extends SessionEvent { @JsonInclude(JsonInclude.Include.NON_NULL) public record AssistantTurnEndEventData( /** Identifier of the turn that has ended, matching the corresponding assistant.turn_start event */ - @JsonProperty("turnId") String turnId + @JsonProperty("turnId") String turnId, + /** Model identifier used for this turn, when known */ + @JsonProperty("model") String model ) { } } diff --git a/java/src/generated/java/com/github/copilot/generated/AssistantTurnStartEvent.java b/java/src/generated/java/com/github/copilot/generated/AssistantTurnStartEvent.java index 639ee013f4..a9c6b2932d 100644 --- a/java/src/generated/java/com/github/copilot/generated/AssistantTurnStartEvent.java +++ b/java/src/generated/java/com/github/copilot/generated/AssistantTurnStartEvent.java @@ -36,6 +36,8 @@ public final class AssistantTurnStartEvent extends SessionEvent { public record AssistantTurnStartEventData( /** Identifier for this turn within the agentic loop, typically a stringified turn number */ @JsonProperty("turnId") String turnId, + /** Model identifier used for this turn, when known */ + @JsonProperty("model") String model, /** CAPI interaction ID for correlating this turn with upstream telemetry */ @JsonProperty("interactionId") String interactionId ) { diff --git a/java/src/generated/java/com/github/copilot/generated/AssistantUsageEvent.java b/java/src/generated/java/com/github/copilot/generated/AssistantUsageEvent.java index 72953adc49..62cf9cfe85 100644 --- a/java/src/generated/java/com/github/copilot/generated/AssistantUsageEvent.java +++ b/java/src/generated/java/com/github/copilot/generated/AssistantUsageEvent.java @@ -52,7 +52,7 @@ public record AssistantUsageEventData( /** Duration of the API call in milliseconds */ @JsonProperty("duration") Long duration, /** Time to first token in milliseconds. Only available for streaming requests */ - @JsonProperty("timeToFirstTokenMs") Long timeToFirstTokenMs, + @JsonProperty("timeToFirstTokenMs") Double timeToFirstTokenMs, /** 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/src/generated/java/com/github/copilot/generated/AssistantUsageQuotaSnapshot.java b/java/src/generated/java/com/github/copilot/generated/AssistantUsageQuotaSnapshot.java index 974a7f272c..f32dacdee8 100644 --- a/java/src/generated/java/com/github/copilot/generated/AssistantUsageQuotaSnapshot.java +++ b/java/src/generated/java/com/github/copilot/generated/AssistantUsageQuotaSnapshot.java @@ -14,7 +14,7 @@ import javax.annotation.processing.Generated; /** - * Schema for the `AssistantUsageQuotaSnapshot` type. + * Internal per-quota snapshot for assistant usage, including entitlement, consumed requests, overage, reset date, and remaining quota. * * @since 1.0.0 */ diff --git a/java/src/generated/java/com/github/copilot/generated/CanvasRegistryChangedCanvas.java b/java/src/generated/java/com/github/copilot/generated/CanvasRegistryChangedCanvas.java index b4805b0bbb..dbafc5d626 100644 --- a/java/src/generated/java/com/github/copilot/generated/CanvasRegistryChangedCanvas.java +++ b/java/src/generated/java/com/github/copilot/generated/CanvasRegistryChangedCanvas.java @@ -14,7 +14,7 @@ import javax.annotation.processing.Generated; /** - * Schema for the `CanvasRegistryChangedCanvas` type. + * A single canvas declaration in `session.canvas.registry_changed`, including provider IDs, display metadata, input schema, and actions. * * @since 1.0.0 */ diff --git a/java/src/generated/java/com/github/copilot/generated/CanvasRegistryChangedCanvasAction.java b/java/src/generated/java/com/github/copilot/generated/CanvasRegistryChangedCanvasAction.java index b8e474e62c..99c390efb2 100644 --- a/java/src/generated/java/com/github/copilot/generated/CanvasRegistryChangedCanvasAction.java +++ b/java/src/generated/java/com/github/copilot/generated/CanvasRegistryChangedCanvasAction.java @@ -13,7 +13,7 @@ import javax.annotation.processing.Generated; /** - * Schema for the `CanvasRegistryChangedCanvasAction` type. + * A single action within a canvas declaration, with its name, optional description, and optional input schema. * * @since 1.0.0 */ diff --git a/java/src/generated/java/com/github/copilot/generated/CommandsChangedCommand.java b/java/src/generated/java/com/github/copilot/generated/CommandsChangedCommand.java index 383f141fcb..76a30b920b 100644 --- a/java/src/generated/java/com/github/copilot/generated/CommandsChangedCommand.java +++ b/java/src/generated/java/com/github/copilot/generated/CommandsChangedCommand.java @@ -13,7 +13,7 @@ import javax.annotation.processing.Generated; /** - * Schema for the `CommandsChangedCommand` type. + * A single slash command available in the session, as listed by the `commands.changed` event. * * @since 1.0.0 */ diff --git a/java/src/generated/java/com/github/copilot/generated/CustomAgentsUpdatedAgent.java b/java/src/generated/java/com/github/copilot/generated/CustomAgentsUpdatedAgent.java index 642be86944..c2f195e486 100644 --- a/java/src/generated/java/com/github/copilot/generated/CustomAgentsUpdatedAgent.java +++ b/java/src/generated/java/com/github/copilot/generated/CustomAgentsUpdatedAgent.java @@ -14,7 +14,7 @@ import javax.annotation.processing.Generated; /** - * Schema for the `CustomAgentsUpdatedAgent` type. + * A single loaded custom agent in `session.custom_agents_updated`, with identity, source, tools, invocability, and model override. * * @since 1.0.0 */ diff --git a/java/src/generated/java/com/github/copilot/generated/ExtensionsLoadedExtension.java b/java/src/generated/java/com/github/copilot/generated/ExtensionsLoadedExtension.java index b45c72139d..d8c65f4555 100644 --- a/java/src/generated/java/com/github/copilot/generated/ExtensionsLoadedExtension.java +++ b/java/src/generated/java/com/github/copilot/generated/ExtensionsLoadedExtension.java @@ -13,7 +13,7 @@ import javax.annotation.processing.Generated; /** - * Schema for the `ExtensionsLoadedExtension` type. + * A single extension discovered by `session.extensions_loaded`, including qualified ID, source, and current status. * * @since 1.0.0 */ diff --git a/java/src/generated/java/com/github/copilot/generated/McpAppToolCallCompleteToolMeta.java b/java/src/generated/java/com/github/copilot/generated/McpAppToolCallCompleteToolMeta.java index 33b9a3725b..335f3694ad 100644 --- a/java/src/generated/java/com/github/copilot/generated/McpAppToolCallCompleteToolMeta.java +++ b/java/src/generated/java/com/github/copilot/generated/McpAppToolCallCompleteToolMeta.java @@ -21,7 +21,7 @@ @JsonInclude(JsonInclude.Include.NON_NULL) @JsonIgnoreProperties(ignoreUnknown = true) public record McpAppToolCallCompleteToolMeta( - /** Schema for the `McpAppToolCallCompleteToolMetaUI` type. */ + /** MCP App tool `_meta.ui` resource URI and SEP-1865 visibility captured with an `mcp_app.tool_call_complete` result. */ @JsonProperty("ui") McpAppToolCallCompleteToolMetaUI ui ) { } diff --git a/java/src/generated/java/com/github/copilot/generated/McpAppToolCallCompleteToolMetaUI.java b/java/src/generated/java/com/github/copilot/generated/McpAppToolCallCompleteToolMetaUI.java index eb960434ad..47708eaaa2 100644 --- a/java/src/generated/java/com/github/copilot/generated/McpAppToolCallCompleteToolMetaUI.java +++ b/java/src/generated/java/com/github/copilot/generated/McpAppToolCallCompleteToolMetaUI.java @@ -14,7 +14,7 @@ import javax.annotation.processing.Generated; /** - * Schema for the `McpAppToolCallCompleteToolMetaUI` type. + * MCP App tool `_meta.ui` resource URI and SEP-1865 visibility captured with an `mcp_app.tool_call_complete` result. * * @since 1.0.0 */ diff --git a/java/src/generated/java/com/github/copilot/generated/McpServersLoadedServer.java b/java/src/generated/java/com/github/copilot/generated/McpServersLoadedServer.java index 9c5d520813..1a2f05023e 100644 --- a/java/src/generated/java/com/github/copilot/generated/McpServersLoadedServer.java +++ b/java/src/generated/java/com/github/copilot/generated/McpServersLoadedServer.java @@ -13,7 +13,7 @@ import javax.annotation.processing.Generated; /** - * Schema for the `McpServersLoadedServer` type. + * A single MCP server status summary in `session.mcp_servers_loaded`, including name, status, source, transport, and plugin metadata. * * @since 1.0.0 */ diff --git a/java/src/generated/java/com/github/copilot/generated/PermissionAllowAllMode.java b/java/src/generated/java/com/github/copilot/generated/PermissionAllowAllMode.java new file mode 100644 index 0000000000..d05b936e6a --- /dev/null +++ b/java/src/generated/java/com/github/copilot/generated/PermissionAllowAllMode.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; + +/** + * Allow-all mode for the session. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public enum PermissionAllowAllMode { + /** The {@code off} variant. */ + OFF("off"), + /** The {@code on} variant. */ + ON("on"), + /** The {@code auto} variant. */ + AUTO("auto"); + + private final String value; + PermissionAllowAllMode(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static PermissionAllowAllMode fromValue(String value) { + for (PermissionAllowAllMode v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown PermissionAllowAllMode value: " + value); + } +} diff --git a/java/src/generated/java/com/github/copilot/generated/SessionBackgroundTasksChangedEvent.java b/java/src/generated/java/com/github/copilot/generated/SessionBackgroundTasksChangedEvent.java index dddb44b843..6058e18c34 100644 --- a/java/src/generated/java/com/github/copilot/generated/SessionBackgroundTasksChangedEvent.java +++ b/java/src/generated/java/com/github/copilot/generated/SessionBackgroundTasksChangedEvent.java @@ -13,7 +13,7 @@ import javax.annotation.processing.Generated; /** - * Session event "session.background_tasks_changed". + * Session event "session.background_tasks_changed". Empty payload for `session.background_tasks_changed`, indicating background task state changed. * @since 1.0.0 */ @JsonIgnoreProperties(ignoreUnknown = true) diff --git a/java/src/generated/java/com/github/copilot/generated/SessionCanvasClosedEvent.java b/java/src/generated/java/com/github/copilot/generated/SessionCanvasClosedEvent.java index 79fd365c2b..b660c0be66 100644 --- a/java/src/generated/java/com/github/copilot/generated/SessionCanvasClosedEvent.java +++ b/java/src/generated/java/com/github/copilot/generated/SessionCanvasClosedEvent.java @@ -13,7 +13,7 @@ import javax.annotation.processing.Generated; /** - * Session event "session.canvas.closed". + * Session event "session.canvas.closed". Payload of `session.canvas.closed` with the closed canvas instance ID, provider ID, and canvas ID. * @since 1.0.0 */ @JsonIgnoreProperties(ignoreUnknown = true) diff --git a/java/src/generated/java/com/github/copilot/generated/SessionCanvasOpenedEvent.java b/java/src/generated/java/com/github/copilot/generated/SessionCanvasOpenedEvent.java index 84f87ffbc0..975737b2f8 100644 --- a/java/src/generated/java/com/github/copilot/generated/SessionCanvasOpenedEvent.java +++ b/java/src/generated/java/com/github/copilot/generated/SessionCanvasOpenedEvent.java @@ -13,7 +13,7 @@ import javax.annotation.processing.Generated; /** - * Session event "session.canvas.opened". + * Session event "session.canvas.opened". Payload of `session.canvas.opened` with canvas instance and provider IDs plus optional title, status, URL, and input. * @since 1.0.0 */ @JsonIgnoreProperties(ignoreUnknown = true) diff --git a/java/src/generated/java/com/github/copilot/generated/SessionCanvasRegistryChangedEvent.java b/java/src/generated/java/com/github/copilot/generated/SessionCanvasRegistryChangedEvent.java index 95ba017630..0a6a9b62de 100644 --- a/java/src/generated/java/com/github/copilot/generated/SessionCanvasRegistryChangedEvent.java +++ b/java/src/generated/java/com/github/copilot/generated/SessionCanvasRegistryChangedEvent.java @@ -14,7 +14,7 @@ import javax.annotation.processing.Generated; /** - * Session event "session.canvas.registry_changed". + * Session event "session.canvas.registry_changed". Payload of `session.canvas.registry_changed` listing the canvas declarations currently available. * @since 1.0.0 */ @JsonIgnoreProperties(ignoreUnknown = true) diff --git a/java/src/generated/java/com/github/copilot/generated/SessionCompactionStartEvent.java b/java/src/generated/java/com/github/copilot/generated/SessionCompactionStartEvent.java index f09e75f4cc..e92a3ac50f 100644 --- a/java/src/generated/java/com/github/copilot/generated/SessionCompactionStartEvent.java +++ b/java/src/generated/java/com/github/copilot/generated/SessionCompactionStartEvent.java @@ -34,6 +34,8 @@ public final class SessionCompactionStartEvent extends SessionEvent { @JsonIgnoreProperties(ignoreUnknown = true) @JsonInclude(JsonInclude.Include.NON_NULL) public record SessionCompactionStartEventData( + /** Model identifier used for compaction, when known */ + @JsonProperty("model") String model, /** Token count from system message(s) at compaction start */ @JsonProperty("systemTokens") Long systemTokens, /** Token count from non-system messages (user, assistant, tool) at compaction start */ diff --git a/java/src/generated/java/com/github/copilot/generated/SessionCustomAgentsUpdatedEvent.java b/java/src/generated/java/com/github/copilot/generated/SessionCustomAgentsUpdatedEvent.java index e7bbad3580..6d7ed6611a 100644 --- a/java/src/generated/java/com/github/copilot/generated/SessionCustomAgentsUpdatedEvent.java +++ b/java/src/generated/java/com/github/copilot/generated/SessionCustomAgentsUpdatedEvent.java @@ -14,7 +14,7 @@ import javax.annotation.processing.Generated; /** - * Session event "session.custom_agents_updated". + * Session event "session.custom_agents_updated". Payload of `session.custom_agents_updated` with loaded custom agents plus non-fatal warnings and fatal errors. * @since 1.0.0 */ @JsonIgnoreProperties(ignoreUnknown = true) diff --git a/java/src/generated/java/com/github/copilot/generated/SessionEvent.java b/java/src/generated/java/com/github/copilot/generated/SessionEvent.java index 7bf0660f6c..d4aed5cd4d 100644 --- a/java/src/generated/java/com/github/copilot/generated/SessionEvent.java +++ b/java/src/generated/java/com/github/copilot/generated/SessionEvent.java @@ -60,6 +60,7 @@ @JsonSubTypes.Type(value = AssistantIntentEvent.class, name = "assistant.intent"), @JsonSubTypes.Type(value = AssistantReasoningEvent.class, name = "assistant.reasoning"), @JsonSubTypes.Type(value = AssistantReasoningDeltaEvent.class, name = "assistant.reasoning_delta"), + @JsonSubTypes.Type(value = AssistantToolCallDeltaEvent.class, name = "assistant.tool_call_delta"), @JsonSubTypes.Type(value = AssistantStreamingDeltaEvent.class, name = "assistant.streaming_delta"), @JsonSubTypes.Type(value = AssistantMessageEvent.class, name = "assistant.message"), @JsonSubTypes.Type(value = AssistantMessageStartEvent.class, name = "assistant.message_start"), @@ -165,6 +166,7 @@ public abstract sealed class SessionEvent permits AssistantIntentEvent, AssistantReasoningEvent, AssistantReasoningDeltaEvent, + AssistantToolCallDeltaEvent, AssistantStreamingDeltaEvent, AssistantMessageEvent, AssistantMessageStartEvent, diff --git a/java/src/generated/java/com/github/copilot/generated/SessionExtensionsAttachmentsPushedEvent.java b/java/src/generated/java/com/github/copilot/generated/SessionExtensionsAttachmentsPushedEvent.java index 55a9f64577..72d0a9deff 100644 --- a/java/src/generated/java/com/github/copilot/generated/SessionExtensionsAttachmentsPushedEvent.java +++ b/java/src/generated/java/com/github/copilot/generated/SessionExtensionsAttachmentsPushedEvent.java @@ -14,7 +14,7 @@ import javax.annotation.processing.Generated; /** - * Session event "session.extensions.attachments_pushed". + * Session event "session.extensions.attachments_pushed". Payload of `session.extensions.attachments_pushed` with extension-contributed attachments for the next send. * @since 1.0.0 */ @JsonIgnoreProperties(ignoreUnknown = true) diff --git a/java/src/generated/java/com/github/copilot/generated/SessionExtensionsLoadedEvent.java b/java/src/generated/java/com/github/copilot/generated/SessionExtensionsLoadedEvent.java index 978162f03e..6ec3ec2746 100644 --- a/java/src/generated/java/com/github/copilot/generated/SessionExtensionsLoadedEvent.java +++ b/java/src/generated/java/com/github/copilot/generated/SessionExtensionsLoadedEvent.java @@ -14,7 +14,7 @@ import javax.annotation.processing.Generated; /** - * Session event "session.extensions_loaded". + * Session event "session.extensions_loaded". Payload of `session.extensions_loaded` listing discovered extensions and their statuses. * @since 1.0.0 */ @JsonIgnoreProperties(ignoreUnknown = true) diff --git a/java/src/generated/java/com/github/copilot/generated/SessionMcpServerStatusChangedEvent.java b/java/src/generated/java/com/github/copilot/generated/SessionMcpServerStatusChangedEvent.java index 6cbcca29fd..cb15f1d9e9 100644 --- a/java/src/generated/java/com/github/copilot/generated/SessionMcpServerStatusChangedEvent.java +++ b/java/src/generated/java/com/github/copilot/generated/SessionMcpServerStatusChangedEvent.java @@ -13,7 +13,7 @@ import javax.annotation.processing.Generated; /** - * Session event "session.mcp_server_status_changed". + * Session event "session.mcp_server_status_changed". Payload of `session.mcp_server_status_changed` for one MCP server's status and optional failure error. * @since 1.0.0 */ @JsonIgnoreProperties(ignoreUnknown = true) diff --git a/java/src/generated/java/com/github/copilot/generated/SessionMcpServersLoadedEvent.java b/java/src/generated/java/com/github/copilot/generated/SessionMcpServersLoadedEvent.java index 59ff2a1aaf..98ddc5b191 100644 --- a/java/src/generated/java/com/github/copilot/generated/SessionMcpServersLoadedEvent.java +++ b/java/src/generated/java/com/github/copilot/generated/SessionMcpServersLoadedEvent.java @@ -14,7 +14,7 @@ import javax.annotation.processing.Generated; /** - * Session event "session.mcp_servers_loaded". + * Session event "session.mcp_servers_loaded". Payload of `session.mcp_servers_loaded` listing MCP server status summaries. * @since 1.0.0 */ @JsonIgnoreProperties(ignoreUnknown = true) diff --git a/java/src/generated/java/com/github/copilot/generated/SessionModelChangeEvent.java b/java/src/generated/java/com/github/copilot/generated/SessionModelChangeEvent.java index da0279757d..f12b86d088 100644 --- a/java/src/generated/java/com/github/copilot/generated/SessionModelChangeEvent.java +++ b/java/src/generated/java/com/github/copilot/generated/SessionModelChangeEvent.java @@ -46,6 +46,10 @@ public record SessionModelChangeEventData( @JsonProperty("previousReasoningSummary") ReasoningSummary previousReasoningSummary, /** Reasoning summary mode after the model change, if applicable */ @JsonProperty("reasoningSummary") ReasoningSummary reasoningSummary, + /** Output verbosity level before the model change, if applicable */ + @JsonProperty("previousVerbosity") Verbosity previousVerbosity, + /** Output verbosity level after the model change, if applicable */ + @JsonProperty("verbosity") Verbosity verbosity, /** Context tier after the model change; null explicitly clears a previously selected tier */ @JsonProperty("contextTier") ContextTier contextTier, /** Reason the change happened, when not user-initiated. Currently `"rate_limit_auto_switch"` for changes triggered by the auto-mode-switch rate-limit recovery path. UI clients can use this to render contextual copy. */ diff --git a/java/src/generated/java/com/github/copilot/generated/SessionPermissionsChangedEvent.java b/java/src/generated/java/com/github/copilot/generated/SessionPermissionsChangedEvent.java index fe91df9d35..c1f82f5af7 100644 --- a/java/src/generated/java/com/github/copilot/generated/SessionPermissionsChangedEvent.java +++ b/java/src/generated/java/com/github/copilot/generated/SessionPermissionsChangedEvent.java @@ -13,7 +13,7 @@ import javax.annotation.processing.Generated; /** - * Session event "session.permissions_changed". Permissions change details carrying the aggregate allow-all boolean transition. + * Session event "session.permissions_changed". Permissions change details carrying the aggregate allow-all transition. * @since 1.0.0 */ @JsonIgnoreProperties(ignoreUnknown = true) @@ -37,7 +37,11 @@ public record SessionPermissionsChangedEventData( /** Aggregate allow-all flag before the change */ @JsonProperty("previousAllowAllPermissions") Boolean previousAllowAllPermissions, /** Aggregate allow-all flag after the change */ - @JsonProperty("allowAllPermissions") Boolean allowAllPermissions + @JsonProperty("allowAllPermissions") Boolean allowAllPermissions, + /** Allow-all mode before the change */ + @JsonProperty("previousAllowAllPermissionMode") PermissionAllowAllMode previousAllowAllPermissionMode, + /** Allow-all mode after the change */ + @JsonProperty("allowAllPermissionMode") PermissionAllowAllMode allowAllPermissionMode ) { } } diff --git a/java/src/generated/java/com/github/copilot/generated/SessionResumeEvent.java b/java/src/generated/java/com/github/copilot/generated/SessionResumeEvent.java index b70e24ed34..6efac46f27 100644 --- a/java/src/generated/java/com/github/copilot/generated/SessionResumeEvent.java +++ b/java/src/generated/java/com/github/copilot/generated/SessionResumeEvent.java @@ -47,6 +47,8 @@ public record SessionResumeEventData( @JsonProperty("reasoningEffort") String reasoningEffort, /** Reasoning summary mode used for model calls, if applicable (e.g. "none", "concise", "detailed") */ @JsonProperty("reasoningSummary") ReasoningSummary reasoningSummary, + /** Output verbosity level used for model calls, if applicable (e.g. "low", "medium", "high") */ + @JsonProperty("verbosity") Verbosity verbosity, /** Context tier currently selected at resume time; null when no tier is active */ @JsonProperty("contextTier") ContextTier contextTier, /** Session limits currently configured at resume time; null when no limits are active */ diff --git a/java/src/generated/java/com/github/copilot/generated/SessionSkillsLoadedEvent.java b/java/src/generated/java/com/github/copilot/generated/SessionSkillsLoadedEvent.java index b2ddd18ed3..efe356670a 100644 --- a/java/src/generated/java/com/github/copilot/generated/SessionSkillsLoadedEvent.java +++ b/java/src/generated/java/com/github/copilot/generated/SessionSkillsLoadedEvent.java @@ -14,7 +14,7 @@ import javax.annotation.processing.Generated; /** - * Session event "session.skills_loaded". + * Session event "session.skills_loaded". Payload of `session.skills_loaded` listing resolved skill metadata. * @since 1.0.0 */ @JsonIgnoreProperties(ignoreUnknown = true) diff --git a/java/src/generated/java/com/github/copilot/generated/SessionStartEvent.java b/java/src/generated/java/com/github/copilot/generated/SessionStartEvent.java index fcd1928874..4beb487c31 100644 --- a/java/src/generated/java/com/github/copilot/generated/SessionStartEvent.java +++ b/java/src/generated/java/com/github/copilot/generated/SessionStartEvent.java @@ -51,6 +51,8 @@ public record SessionStartEventData( @JsonProperty("reasoningEffort") String reasoningEffort, /** Reasoning summary mode used for model calls, if applicable (e.g. "none", "concise", "detailed") */ @JsonProperty("reasoningSummary") ReasoningSummary reasoningSummary, + /** Output verbosity level used for model calls, if applicable (e.g. "low", "medium", "high") */ + @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, /** Session limits configured at session creation time, if any */ diff --git a/java/src/generated/java/com/github/copilot/generated/SessionToolsUpdatedEvent.java b/java/src/generated/java/com/github/copilot/generated/SessionToolsUpdatedEvent.java index 2b0d94c260..f69954ee5e 100644 --- a/java/src/generated/java/com/github/copilot/generated/SessionToolsUpdatedEvent.java +++ b/java/src/generated/java/com/github/copilot/generated/SessionToolsUpdatedEvent.java @@ -13,7 +13,7 @@ import javax.annotation.processing.Generated; /** - * Session event "session.tools_updated". + * Session event "session.tools_updated". Payload of `session.tools_updated` identifying the model whose resolved tools were updated. * @since 1.0.0 */ @JsonIgnoreProperties(ignoreUnknown = true) diff --git a/java/src/generated/java/com/github/copilot/generated/ShutdownModelMetric.java b/java/src/generated/java/com/github/copilot/generated/ShutdownModelMetric.java index b7eb37fd90..1ba45d90b6 100644 --- a/java/src/generated/java/com/github/copilot/generated/ShutdownModelMetric.java +++ b/java/src/generated/java/com/github/copilot/generated/ShutdownModelMetric.java @@ -14,7 +14,7 @@ import javax.annotation.processing.Generated; /** - * Schema for the `ShutdownModelMetric` type. + * Per-model shutdown metrics with request counts, token usage, nano-AI units, and token details. * * @since 1.0.0 */ diff --git a/java/src/generated/java/com/github/copilot/generated/ShutdownModelMetricTokenDetail.java b/java/src/generated/java/com/github/copilot/generated/ShutdownModelMetricTokenDetail.java index fe18de6c69..cd0e67d702 100644 --- a/java/src/generated/java/com/github/copilot/generated/ShutdownModelMetricTokenDetail.java +++ b/java/src/generated/java/com/github/copilot/generated/ShutdownModelMetricTokenDetail.java @@ -13,7 +13,7 @@ import javax.annotation.processing.Generated; /** - * Schema for the `ShutdownModelMetricTokenDetail` type. + * A token-type entry in a shutdown model metric, storing the accumulated token count. * * @since 1.0.0 */ diff --git a/java/src/generated/java/com/github/copilot/generated/ShutdownTokenDetail.java b/java/src/generated/java/com/github/copilot/generated/ShutdownTokenDetail.java index 75db095c5f..dfc986e834 100644 --- a/java/src/generated/java/com/github/copilot/generated/ShutdownTokenDetail.java +++ b/java/src/generated/java/com/github/copilot/generated/ShutdownTokenDetail.java @@ -13,7 +13,7 @@ import javax.annotation.processing.Generated; /** - * Schema for the `ShutdownTokenDetail` type. + * A session-wide shutdown token-type entry storing the accumulated token count. * * @since 1.0.0 */ diff --git a/java/src/generated/java/com/github/copilot/generated/SkillInvokedEvent.java b/java/src/generated/java/com/github/copilot/generated/SkillInvokedEvent.java index a3bac49bc2..6ad04f9699 100644 --- a/java/src/generated/java/com/github/copilot/generated/SkillInvokedEvent.java +++ b/java/src/generated/java/com/github/copilot/generated/SkillInvokedEvent.java @@ -37,6 +37,8 @@ public final class SkillInvokedEvent extends SessionEvent { public record SkillInvokedEventData( /** Name of the invoked skill */ @JsonProperty("name") String name, + /** Model identifier active when the skill was invoked, when known */ + @JsonProperty("model") String model, /** File path to the SKILL.md definition */ @JsonProperty("path") String path, /** Full content of the skill file, injected into the conversation for the model */ diff --git a/java/src/generated/java/com/github/copilot/generated/SkillsLoadedSkill.java b/java/src/generated/java/com/github/copilot/generated/SkillsLoadedSkill.java index a3db9697fe..d3196c6bbf 100644 --- a/java/src/generated/java/com/github/copilot/generated/SkillsLoadedSkill.java +++ b/java/src/generated/java/com/github/copilot/generated/SkillsLoadedSkill.java @@ -13,7 +13,7 @@ import javax.annotation.processing.Generated; /** - * Schema for the `SkillsLoadedSkill` type. + * A single resolved skill in `session.skills_loaded`, including source, invocability, enabled state, path, and argument hint. * * @since 1.0.0 */ diff --git a/java/src/generated/java/com/github/copilot/generated/ToolExecutionCompleteToolDescriptionMeta.java b/java/src/generated/java/com/github/copilot/generated/ToolExecutionCompleteToolDescriptionMeta.java index 563358cfa3..f9af397a79 100644 --- a/java/src/generated/java/com/github/copilot/generated/ToolExecutionCompleteToolDescriptionMeta.java +++ b/java/src/generated/java/com/github/copilot/generated/ToolExecutionCompleteToolDescriptionMeta.java @@ -21,7 +21,7 @@ @JsonInclude(JsonInclude.Include.NON_NULL) @JsonIgnoreProperties(ignoreUnknown = true) public record ToolExecutionCompleteToolDescriptionMeta( - /** Schema for the `ToolExecutionCompleteToolDescriptionMetaUI` type. */ + /** MCP Apps tool `_meta.ui` resource URI and visibility captured on `tool.execution_complete`. */ @JsonProperty("ui") ToolExecutionCompleteToolDescriptionMetaUI ui ) { } diff --git a/java/src/generated/java/com/github/copilot/generated/ToolExecutionCompleteToolDescriptionMetaUI.java b/java/src/generated/java/com/github/copilot/generated/ToolExecutionCompleteToolDescriptionMetaUI.java index 9acf435bc7..1cbe17d498 100644 --- a/java/src/generated/java/com/github/copilot/generated/ToolExecutionCompleteToolDescriptionMetaUI.java +++ b/java/src/generated/java/com/github/copilot/generated/ToolExecutionCompleteToolDescriptionMetaUI.java @@ -14,7 +14,7 @@ import javax.annotation.processing.Generated; /** - * Schema for the `ToolExecutionCompleteToolDescriptionMetaUI` type. + * MCP Apps tool `_meta.ui` resource URI and visibility captured on `tool.execution_complete`. * * @since 1.0.0 */ diff --git a/java/src/generated/java/com/github/copilot/generated/ToolExecutionCompleteUIResourceMeta.java b/java/src/generated/java/com/github/copilot/generated/ToolExecutionCompleteUIResourceMeta.java index 6375222cc3..897f0ff397 100644 --- a/java/src/generated/java/com/github/copilot/generated/ToolExecutionCompleteUIResourceMeta.java +++ b/java/src/generated/java/com/github/copilot/generated/ToolExecutionCompleteUIResourceMeta.java @@ -21,7 +21,7 @@ @JsonInclude(JsonInclude.Include.NON_NULL) @JsonIgnoreProperties(ignoreUnknown = true) public record ToolExecutionCompleteUIResourceMeta( - /** Schema for the `ToolExecutionCompleteUIResourceMetaUI` type. */ + /** MCP Apps UI resource metadata for a completed tool result, including CSP, permissions, domain, and border preference. */ @JsonProperty("ui") ToolExecutionCompleteUIResourceMetaUI ui ) { } diff --git a/java/src/generated/java/com/github/copilot/generated/ToolExecutionCompleteUIResourceMetaUI.java b/java/src/generated/java/com/github/copilot/generated/ToolExecutionCompleteUIResourceMetaUI.java index 3b9548a8ce..6679b85aec 100644 --- a/java/src/generated/java/com/github/copilot/generated/ToolExecutionCompleteUIResourceMetaUI.java +++ b/java/src/generated/java/com/github/copilot/generated/ToolExecutionCompleteUIResourceMetaUI.java @@ -13,7 +13,7 @@ import javax.annotation.processing.Generated; /** - * Schema for the `ToolExecutionCompleteUIResourceMetaUI` type. + * MCP Apps UI resource metadata for a completed tool result, including CSP, permissions, domain, and border preference. * * @since 1.0.0 */ @@ -21,9 +21,9 @@ @JsonInclude(JsonInclude.Include.NON_NULL) @JsonIgnoreProperties(ignoreUnknown = true) public record ToolExecutionCompleteUIResourceMetaUI( - /** Schema for the `ToolExecutionCompleteUIResourceMetaUICsp` type. */ + /** CSP domain allowlists for an MCP Apps UI resource, including connect, resource, frame, and base URI domains. */ @JsonProperty("csp") ToolExecutionCompleteUIResourceMetaUICsp csp, - /** Schema for the `ToolExecutionCompleteUIResourceMetaUIPermissions` type. */ + /** Browser permission metadata for an MCP Apps UI resource, including camera, microphone, geolocation, and clipboard-write. */ @JsonProperty("permissions") ToolExecutionCompleteUIResourceMetaUIPermissions permissions, @JsonProperty("domain") String domain, @JsonProperty("prefersBorder") Boolean prefersBorder diff --git a/java/src/generated/java/com/github/copilot/generated/ToolExecutionCompleteUIResourceMetaUICsp.java b/java/src/generated/java/com/github/copilot/generated/ToolExecutionCompleteUIResourceMetaUICsp.java index 0ccb8a3dbc..41e799cf03 100644 --- a/java/src/generated/java/com/github/copilot/generated/ToolExecutionCompleteUIResourceMetaUICsp.java +++ b/java/src/generated/java/com/github/copilot/generated/ToolExecutionCompleteUIResourceMetaUICsp.java @@ -14,7 +14,7 @@ import javax.annotation.processing.Generated; /** - * Schema for the `ToolExecutionCompleteUIResourceMetaUICsp` type. + * CSP domain allowlists for an MCP Apps UI resource, including connect, resource, frame, and base URI domains. * * @since 1.0.0 */ diff --git a/java/src/generated/java/com/github/copilot/generated/ToolExecutionCompleteUIResourceMetaUIPermissions.java b/java/src/generated/java/com/github/copilot/generated/ToolExecutionCompleteUIResourceMetaUIPermissions.java index 8b1fc5dc9a..d9adf5579d 100644 --- a/java/src/generated/java/com/github/copilot/generated/ToolExecutionCompleteUIResourceMetaUIPermissions.java +++ b/java/src/generated/java/com/github/copilot/generated/ToolExecutionCompleteUIResourceMetaUIPermissions.java @@ -13,7 +13,7 @@ import javax.annotation.processing.Generated; /** - * Schema for the `ToolExecutionCompleteUIResourceMetaUIPermissions` type. + * Browser permission metadata for an MCP Apps UI resource, including camera, microphone, geolocation, and clipboard-write. * * @since 1.0.0 */ @@ -21,13 +21,13 @@ @JsonInclude(JsonInclude.Include.NON_NULL) @JsonIgnoreProperties(ignoreUnknown = true) public record ToolExecutionCompleteUIResourceMetaUIPermissions( - /** Schema for the `ToolExecutionCompleteUIResourceMetaUIPermissionsCamera` type. */ + /** Marker object for camera permission on an MCP Apps UI resource. */ @JsonProperty("camera") ToolExecutionCompleteUIResourceMetaUIPermissionsCamera camera, - /** Schema for the `ToolExecutionCompleteUIResourceMetaUIPermissionsMicrophone` type. */ + /** Marker object for microphone permission on an MCP Apps UI resource. */ @JsonProperty("microphone") ToolExecutionCompleteUIResourceMetaUIPermissionsMicrophone microphone, - /** Schema for the `ToolExecutionCompleteUIResourceMetaUIPermissionsGeolocation` type. */ + /** Marker object for geolocation permission on an MCP Apps UI resource. */ @JsonProperty("geolocation") ToolExecutionCompleteUIResourceMetaUIPermissionsGeolocation geolocation, - /** Schema for the `ToolExecutionCompleteUIResourceMetaUIPermissionsClipboardWrite` type. */ + /** Marker object for clipboard-write permission on an MCP Apps UI resource. */ @JsonProperty("clipboardWrite") ToolExecutionCompleteUIResourceMetaUIPermissionsClipboardWrite clipboardWrite ) { } diff --git a/java/src/generated/java/com/github/copilot/generated/ToolExecutionCompleteUIResourceMetaUIPermissionsCamera.java b/java/src/generated/java/com/github/copilot/generated/ToolExecutionCompleteUIResourceMetaUIPermissionsCamera.java index 300967e8ce..9a02355350 100644 --- a/java/src/generated/java/com/github/copilot/generated/ToolExecutionCompleteUIResourceMetaUIPermissionsCamera.java +++ b/java/src/generated/java/com/github/copilot/generated/ToolExecutionCompleteUIResourceMetaUIPermissionsCamera.java @@ -13,7 +13,7 @@ import javax.annotation.processing.Generated; /** - * Schema for the `ToolExecutionCompleteUIResourceMetaUIPermissionsCamera` type. + * Marker object for camera permission on an MCP Apps UI resource. * * @since 1.0.0 */ diff --git a/java/src/generated/java/com/github/copilot/generated/ToolExecutionCompleteUIResourceMetaUIPermissionsClipboardWrite.java b/java/src/generated/java/com/github/copilot/generated/ToolExecutionCompleteUIResourceMetaUIPermissionsClipboardWrite.java index 485a6946ce..0c4e8dad1f 100644 --- a/java/src/generated/java/com/github/copilot/generated/ToolExecutionCompleteUIResourceMetaUIPermissionsClipboardWrite.java +++ b/java/src/generated/java/com/github/copilot/generated/ToolExecutionCompleteUIResourceMetaUIPermissionsClipboardWrite.java @@ -13,7 +13,7 @@ import javax.annotation.processing.Generated; /** - * Schema for the `ToolExecutionCompleteUIResourceMetaUIPermissionsClipboardWrite` type. + * Marker object for clipboard-write permission on an MCP Apps UI resource. * * @since 1.0.0 */ diff --git a/java/src/generated/java/com/github/copilot/generated/ToolExecutionCompleteUIResourceMetaUIPermissionsGeolocation.java b/java/src/generated/java/com/github/copilot/generated/ToolExecutionCompleteUIResourceMetaUIPermissionsGeolocation.java index 30ed9bb545..d681474737 100644 --- a/java/src/generated/java/com/github/copilot/generated/ToolExecutionCompleteUIResourceMetaUIPermissionsGeolocation.java +++ b/java/src/generated/java/com/github/copilot/generated/ToolExecutionCompleteUIResourceMetaUIPermissionsGeolocation.java @@ -13,7 +13,7 @@ import javax.annotation.processing.Generated; /** - * Schema for the `ToolExecutionCompleteUIResourceMetaUIPermissionsGeolocation` type. + * Marker object for geolocation permission on an MCP Apps UI resource. * * @since 1.0.0 */ diff --git a/java/src/generated/java/com/github/copilot/generated/ToolExecutionCompleteUIResourceMetaUIPermissionsMicrophone.java b/java/src/generated/java/com/github/copilot/generated/ToolExecutionCompleteUIResourceMetaUIPermissionsMicrophone.java index 1748ccb487..4caa88edeb 100644 --- a/java/src/generated/java/com/github/copilot/generated/ToolExecutionCompleteUIResourceMetaUIPermissionsMicrophone.java +++ b/java/src/generated/java/com/github/copilot/generated/ToolExecutionCompleteUIResourceMetaUIPermissionsMicrophone.java @@ -13,7 +13,7 @@ import javax.annotation.processing.Generated; /** - * Schema for the `ToolExecutionCompleteUIResourceMetaUIPermissionsMicrophone` type. + * Marker object for microphone permission on an MCP Apps UI resource. * * @since 1.0.0 */ diff --git a/java/src/generated/java/com/github/copilot/generated/ToolExecutionStartToolDescriptionMeta.java b/java/src/generated/java/com/github/copilot/generated/ToolExecutionStartToolDescriptionMeta.java index 25296d1d3d..e93bf998a2 100644 --- a/java/src/generated/java/com/github/copilot/generated/ToolExecutionStartToolDescriptionMeta.java +++ b/java/src/generated/java/com/github/copilot/generated/ToolExecutionStartToolDescriptionMeta.java @@ -21,7 +21,7 @@ @JsonInclude(JsonInclude.Include.NON_NULL) @JsonIgnoreProperties(ignoreUnknown = true) public record ToolExecutionStartToolDescriptionMeta( - /** Schema for the `ToolExecutionStartToolDescriptionMetaUI` type. */ + /** MCP Apps tool `_meta.ui` resource URI and visibility captured on `tool.execution_start`. */ @JsonProperty("ui") ToolExecutionStartToolDescriptionMetaUI ui ) { } diff --git a/java/src/generated/java/com/github/copilot/generated/ToolExecutionStartToolDescriptionMetaUI.java b/java/src/generated/java/com/github/copilot/generated/ToolExecutionStartToolDescriptionMetaUI.java index c928a03f61..954edf2105 100644 --- a/java/src/generated/java/com/github/copilot/generated/ToolExecutionStartToolDescriptionMetaUI.java +++ b/java/src/generated/java/com/github/copilot/generated/ToolExecutionStartToolDescriptionMetaUI.java @@ -14,7 +14,7 @@ import javax.annotation.processing.Generated; /** - * Schema for the `ToolExecutionStartToolDescriptionMetaUI` type. + * MCP Apps tool `_meta.ui` resource URI and visibility captured on `tool.execution_start`. * * @since 1.0.0 */ diff --git a/java/src/generated/java/com/github/copilot/generated/UserMessageEvent.java b/java/src/generated/java/com/github/copilot/generated/UserMessageEvent.java index 57f64b6527..5af4842430 100644 --- a/java/src/generated/java/com/github/copilot/generated/UserMessageEvent.java +++ b/java/src/generated/java/com/github/copilot/generated/UserMessageEvent.java @@ -14,7 +14,7 @@ import javax.annotation.processing.Generated; /** - * Session event "user.message". + * Session event "user.message". Payload of `user.message` with displayed and model-transformed content, attachments, source/delivery metadata, mode, and telemetry IDs. * @since 1.0.0 */ @JsonIgnoreProperties(ignoreUnknown = true) diff --git a/java/src/generated/java/com/github/copilot/generated/Verbosity.java b/java/src/generated/java/com/github/copilot/generated/Verbosity.java new file mode 100644 index 0000000000..9db84f1857 --- /dev/null +++ b/java/src/generated/java/com/github/copilot/generated/Verbosity.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; + +/** + * Output verbosity level used for supported model calls (e.g. "low", "medium", "high") + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public enum Verbosity { + /** The {@code low} variant. */ + LOW("low"), + /** The {@code medium} variant. */ + MEDIUM("medium"), + /** The {@code high} variant. */ + HIGH("high"); + + private final String value; + Verbosity(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static Verbosity fromValue(String value) { + for (Verbosity v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown Verbosity value: " + value); + } +} diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/AccountAllUsers.java b/java/src/generated/java/com/github/copilot/generated/rpc/AccountAllUsers.java index a544b8a307..eecdad01ee 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/AccountAllUsers.java +++ b/java/src/generated/java/com/github/copilot/generated/rpc/AccountAllUsers.java @@ -13,7 +13,7 @@ import javax.annotation.processing.Generated; /** - * Schema for the `AccountAllUsers` type. + * Authenticated account entry returned by `account.getAllUsers`, with auth info and an optional associated token. * * @since 1.0.0 */ diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/AccountQuotaSnapshot.java b/java/src/generated/java/com/github/copilot/generated/rpc/AccountQuotaSnapshot.java index 88e7ba9c64..fe4baf3fd6 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/AccountQuotaSnapshot.java +++ b/java/src/generated/java/com/github/copilot/generated/rpc/AccountQuotaSnapshot.java @@ -14,7 +14,7 @@ import javax.annotation.processing.Generated; /** - * Schema for the `AccountQuotaSnapshot` type. + * Quota usage snapshot for a Copilot quota type, including entitlement, used requests, overage, reset date, and remaining percentage. * * @since 1.0.0 */ diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/AgentDiscoveryPath.java b/java/src/generated/java/com/github/copilot/generated/rpc/AgentDiscoveryPath.java index 93a4bb1f6a..53d48904ff 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/AgentDiscoveryPath.java +++ b/java/src/generated/java/com/github/copilot/generated/rpc/AgentDiscoveryPath.java @@ -13,7 +13,7 @@ import javax.annotation.processing.Generated; /** - * Schema for the `AgentDiscoveryPath` type. + * Canonical directory where custom agents can be discovered or created, with scope, preference, and optional project path. * * @since 1.0.0 */ diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/AgentInfo.java b/java/src/generated/java/com/github/copilot/generated/rpc/AgentInfo.java index ce80893730..29813e30a0 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/AgentInfo.java +++ b/java/src/generated/java/com/github/copilot/generated/rpc/AgentInfo.java @@ -15,7 +15,7 @@ import javax.annotation.processing.Generated; /** - * Schema for the `AgentInfo` type. + * Custom agent metadata, including identifiers, display details, source, tools, model, MCP servers, skills, and file path. * * @since 1.0.0 */ diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/ConnectParams.java b/java/src/generated/java/com/github/copilot/generated/rpc/ConnectParams.java index 5fa87b3af6..491dadad37 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/ConnectParams.java +++ b/java/src/generated/java/com/github/copilot/generated/rpc/ConnectParams.java @@ -14,7 +14,7 @@ import javax.annotation.processing.Generated; /** - * Optional connection token presented by the SDK client during the handshake. + * Parameters for the `server.connect` handshake: an optional connection token and optional connection-level opt-ins (e.g. GitHub telemetry forwarding). * * @apiNote This method is experimental and may change in a future version. * @since 1.0.0 @@ -25,6 +25,8 @@ @JsonIgnoreProperties(ignoreUnknown = true) public record ConnectParams( /** Connection token; required when the server was started with COPILOT_CONNECTION_TOKEN */ - @JsonProperty("token") String token + @JsonProperty("token") String token, + /** Opt this connection in to GitHub telemetry forwarding for its lifetime. When set, the runtime forwards every internal telemetry event it emits — across all sessions, plus sessionless events — to this connection over the `gitHubTelemetry.event` notification, in addition to the runtime's normal GitHub/CTS emission (dual-write). Intended for first-party hosts that re-emit the events into their own telemetry stores. Both unrestricted and restricted events are forwarded, each tagged with a `restricted` discriminator; a backstop drops restricted events when restricted telemetry is disabled. */ + @JsonProperty("enableGitHubTelemetryForwarding") Boolean enableGitHubTelemetryForwarding ) { } diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/ContextHeaviestMessage.java b/java/src/generated/java/com/github/copilot/generated/rpc/ContextHeaviestMessage.java new file mode 100644 index 0000000000..818841a3c7 --- /dev/null +++ b/java/src/generated/java/com/github/copilot/generated/rpc/ContextHeaviestMessage.java @@ -0,0 +1,33 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import javax.annotation.processing.Generated; + +/** + * A single large message currently in context. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record ContextHeaviestMessage( + /** Stable identifier for this message within the snapshot. */ + @JsonProperty("id") String id, + /** Human-readable source label, e.g. `tool: bash` or `skill: tmux`. Presentation-only. */ + @JsonProperty("label") String label, + /** Role of the chat message (`user`, `assistant`, or `tool`). */ + @JsonProperty("role") String role, + /** Token count currently in context for this individual message. */ + @JsonProperty("tokens") Long tokens +) { +} diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/DebugCollectLogsCollectedEntry.java b/java/src/generated/java/com/github/copilot/generated/rpc/DebugCollectLogsCollectedEntry.java new file mode 100644 index 0000000000..9d8592220d --- /dev/null +++ b/java/src/generated/java/com/github/copilot/generated/rpc/DebugCollectLogsCollectedEntry.java @@ -0,0 +1,31 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import javax.annotation.processing.Generated; + +/** + * A file included in the redacted debug bundle. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record DebugCollectLogsCollectedEntry( + /** Relative path of the file in the staged bundle/archive. */ + @JsonProperty("bundlePath") String bundlePath, + /** Source category for this entry. */ + @JsonProperty("source") DebugCollectLogsSource source, + /** Redacted output size in bytes. */ + @JsonProperty("sizeBytes") Long sizeBytes +) { +} diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/DebugCollectLogsEntry.java b/java/src/generated/java/com/github/copilot/generated/rpc/DebugCollectLogsEntry.java new file mode 100644 index 0000000000..285b1ef6e0 --- /dev/null +++ b/java/src/generated/java/com/github/copilot/generated/rpc/DebugCollectLogsEntry.java @@ -0,0 +1,35 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import javax.annotation.processing.Generated; + +/** + * A caller-provided server-local file or directory to include in the debug bundle. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record DebugCollectLogsEntry( + /** Kind of source path to include. */ + @JsonProperty("kind") DebugCollectLogsEntryKind kind, + /** Server-local source path to read. */ + @JsonProperty("path") String path, + /** Relative path to use inside the staged bundle/archive. */ + @JsonProperty("bundlePath") String bundlePath, + /** How text content from this entry should be redacted. Defaults to plain-text. */ + @JsonProperty("redaction") DebugCollectLogsRedaction redaction, + /** When true, collection fails if this entry cannot be read. Defaults to false, which records the entry in `skippedEntries`. */ + @JsonProperty("required") Boolean required +) { +} diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/DebugCollectLogsEntryKind.java b/java/src/generated/java/com/github/copilot/generated/rpc/DebugCollectLogsEntryKind.java new file mode 100644 index 0000000000..316b6dcd5a --- /dev/null +++ b/java/src/generated/java/com/github/copilot/generated/rpc/DebugCollectLogsEntryKind.java @@ -0,0 +1,35 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import javax.annotation.processing.Generated; + +/** + * Kind of caller-provided debug log entry. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public enum DebugCollectLogsEntryKind { + /** The {@code file} variant. */ + FILE("file"), + /** The {@code directory} variant. */ + DIRECTORY("directory"); + + private final String value; + DebugCollectLogsEntryKind(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static DebugCollectLogsEntryKind fromValue(String value) { + for (DebugCollectLogsEntryKind v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown DebugCollectLogsEntryKind value: " + value); + } +} diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/DebugCollectLogsInclude.java b/java/src/generated/java/com/github/copilot/generated/rpc/DebugCollectLogsInclude.java new file mode 100644 index 0000000000..0cab63830b --- /dev/null +++ b/java/src/generated/java/com/github/copilot/generated/rpc/DebugCollectLogsInclude.java @@ -0,0 +1,39 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import javax.annotation.processing.Generated; + +/** + * Built-in session diagnostics to include in the bundle. Omitted fields default to true. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record DebugCollectLogsInclude( + /** Include the session event log (`events.jsonl`). Defaults to true. */ + @JsonProperty("events") Boolean events, + /** Include process logs for the session. Defaults to true. */ + @JsonProperty("processLogs") Boolean processLogs, + /** Include interactive shell logs written under the session's `shell-logs` directory. Defaults to true. */ + @JsonProperty("shellLogs") Boolean shellLogs, + /** Server-local path to the session's events.jsonl file. Internal callers normally omit this and let the runtime derive it from the session. */ + @JsonProperty("eventsPath") String eventsPath, + /** Server-local path to the current process log. When set, it is included as `process.log` and its directory is searched for prior logs from the same session. */ + @JsonProperty("currentProcessLogPath") String currentProcessLogPath, + /** Server-local process log directory to search when `currentProcessLogPath` is unavailable, useful for collecting logs for inactive sessions. */ + @JsonProperty("processLogDirectory") String processLogDirectory, + /** Maximum number of previous process logs to include. Defaults to 5. */ + @JsonProperty("previousProcessLogLimit") Long previousProcessLogLimit +) { +} diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/DebugCollectLogsRedaction.java b/java/src/generated/java/com/github/copilot/generated/rpc/DebugCollectLogsRedaction.java new file mode 100644 index 0000000000..5f57e37378 --- /dev/null +++ b/java/src/generated/java/com/github/copilot/generated/rpc/DebugCollectLogsRedaction.java @@ -0,0 +1,35 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import javax.annotation.processing.Generated; + +/** + * How a collected debug entry should be redacted before being staged. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public enum DebugCollectLogsRedaction { + /** The {@code plain-text} variant. */ + PLAIN_TEXT("plain-text"), + /** The {@code events-jsonl} variant. */ + EVENTS_JSONL("events-jsonl"); + + private final String value; + DebugCollectLogsRedaction(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static DebugCollectLogsRedaction fromValue(String value) { + for (DebugCollectLogsRedaction v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown DebugCollectLogsRedaction value: " + value); + } +} diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/DebugCollectLogsResultKind.java b/java/src/generated/java/com/github/copilot/generated/rpc/DebugCollectLogsResultKind.java new file mode 100644 index 0000000000..00986bd3fd --- /dev/null +++ b/java/src/generated/java/com/github/copilot/generated/rpc/DebugCollectLogsResultKind.java @@ -0,0 +1,35 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import javax.annotation.processing.Generated; + +/** + * Destination kind that was written. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public enum DebugCollectLogsResultKind { + /** The {@code archive} variant. */ + ARCHIVE("archive"), + /** The {@code directory} variant. */ + DIRECTORY("directory"); + + private final String value; + DebugCollectLogsResultKind(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static DebugCollectLogsResultKind fromValue(String value) { + for (DebugCollectLogsResultKind v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown DebugCollectLogsResultKind value: " + value); + } +} diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/DebugCollectLogsSkippedEntry.java b/java/src/generated/java/com/github/copilot/generated/rpc/DebugCollectLogsSkippedEntry.java new file mode 100644 index 0000000000..a5a702bcda --- /dev/null +++ b/java/src/generated/java/com/github/copilot/generated/rpc/DebugCollectLogsSkippedEntry.java @@ -0,0 +1,31 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import javax.annotation.processing.Generated; + +/** + * An optional debug bundle entry that could not be included. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record DebugCollectLogsSkippedEntry( + /** Relative path requested for this bundle entry. */ + @JsonProperty("bundlePath") String bundlePath, + /** Server-local source path that could not be read. */ + @JsonProperty("path") String path, + /** Reason the entry was skipped. */ + @JsonProperty("reason") String reason +) { +} diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/DebugCollectLogsSource.java b/java/src/generated/java/com/github/copilot/generated/rpc/DebugCollectLogsSource.java new file mode 100644 index 0000000000..989059f459 --- /dev/null +++ b/java/src/generated/java/com/github/copilot/generated/rpc/DebugCollectLogsSource.java @@ -0,0 +1,39 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import javax.annotation.processing.Generated; + +/** + * Source category for a collected debug bundle entry. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public enum DebugCollectLogsSource { + /** The {@code events} variant. */ + EVENTS("events"), + /** The {@code process-log} variant. */ + PROCESS_LOG("process-log"), + /** The {@code shell-log} variant. */ + SHELL_LOG("shell-log"), + /** The {@code additional} variant. */ + ADDITIONAL("additional"); + + private final String value; + DebugCollectLogsSource(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static DebugCollectLogsSource fromValue(String value) { + for (DebugCollectLogsSource v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown DebugCollectLogsSource value: " + value); + } +} diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/DiscoveredMcpServer.java b/java/src/generated/java/com/github/copilot/generated/rpc/DiscoveredMcpServer.java index 60fa5271de..3262994c1f 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/DiscoveredMcpServer.java +++ b/java/src/generated/java/com/github/copilot/generated/rpc/DiscoveredMcpServer.java @@ -13,7 +13,7 @@ import javax.annotation.processing.Generated; /** - * Schema for the `DiscoveredMcpServer` type. + * MCP server discovered by `mcp.discover`, with config source, optional plugin source, transport type, and enabled state. * * @since 1.0.0 */ diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/Extension.java b/java/src/generated/java/com/github/copilot/generated/rpc/Extension.java index 7bbdb5207a..4d3e357cd7 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/Extension.java +++ b/java/src/generated/java/com/github/copilot/generated/rpc/Extension.java @@ -13,7 +13,7 @@ import javax.annotation.processing.Generated; /** - * Schema for the `Extension` type. + * Discovered extension metadata, including source-qualified ID, name, discovery source, status, and optional process ID. * * @since 1.0.0 */ diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/GitHubTelemetryNotification.java b/java/src/generated/java/com/github/copilot/generated/rpc/GitHubTelemetryNotification.java index fddcdc70bc..6059f1ff6c 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/GitHubTelemetryNotification.java +++ b/java/src/generated/java/com/github/copilot/generated/rpc/GitHubTelemetryNotification.java @@ -13,7 +13,7 @@ import javax.annotation.processing.Generated; /** - * Payload for a `gitHubTelemetry.event` notification: a single GitHub telemetry event the runtime forwards to a host connection that opted into telemetry forwarding for the session. + * 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. * * @since 1.0.0 */ @@ -21,7 +21,7 @@ @JsonInclude(JsonInclude.Include.NON_NULL) @JsonIgnoreProperties(ignoreUnknown = true) public record GitHubTelemetryNotification( - /** Session the telemetry event belongs to. */ + /** Session the telemetry event belongs to, when it is session-scoped. Omitted for sessionless events (for example, `server.sendTelemetry` calls with no session id), which are still forwarded to opted-in connections. */ @JsonProperty("sessionId") String sessionId, /** Whether this is a restricted telemetry event (cli.restricted_telemetry). Hosts must route restricted events to first-party Microsoft stores only. */ @JsonProperty("restricted") Boolean restricted, diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/InstalledPlugin.java b/java/src/generated/java/com/github/copilot/generated/rpc/InstalledPlugin.java index c274dfb1a2..b3487e7e31 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/InstalledPlugin.java +++ b/java/src/generated/java/com/github/copilot/generated/rpc/InstalledPlugin.java @@ -13,7 +13,7 @@ import javax.annotation.processing.Generated; /** - * Schema for the `InstalledPlugin` type. + * Installed plugin record from global state, with marketplace, version, install time, enabled state, cache path, and source. * * @since 1.0.0 */ diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/InstructionDiscoveryPath.java b/java/src/generated/java/com/github/copilot/generated/rpc/InstructionDiscoveryPath.java index b47451bcd9..213b003c15 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/InstructionDiscoveryPath.java +++ b/java/src/generated/java/com/github/copilot/generated/rpc/InstructionDiscoveryPath.java @@ -13,7 +13,7 @@ import javax.annotation.processing.Generated; /** - * Schema for the `InstructionDiscoveryPath` type. + * Canonical file or directory where custom instructions can be discovered or created, with location, kind, preference, and project path. * * @since 1.0.0 */ diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/InstructionSource.java b/java/src/generated/java/com/github/copilot/generated/rpc/InstructionSource.java index 37d1ead1c6..2581375b33 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/InstructionSource.java +++ b/java/src/generated/java/com/github/copilot/generated/rpc/InstructionSource.java @@ -14,7 +14,7 @@ import javax.annotation.processing.Generated; /** - * Schema for the `InstructionSource` type. + * Loaded instruction source for a session, including path, content, category, location, applicability, and optional description. * * @since 1.0.0 */ diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/LlmInferenceHttpRequestStartRequest.java b/java/src/generated/java/com/github/copilot/generated/rpc/LlmInferenceHttpRequestStartRequest.java index 4c23404d3e..a625e4253e 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/LlmInferenceHttpRequestStartRequest.java +++ b/java/src/generated/java/com/github/copilot/generated/rpc/LlmInferenceHttpRequestStartRequest.java @@ -33,6 +33,12 @@ public record LlmInferenceHttpRequestStartRequest( @JsonProperty("url") String url, @JsonProperty("headers") Map> headers, /** Transport the runtime would otherwise use for this request. `http` (the default when absent) covers plain HTTP and SSE responses; `websocket` indicates a full-duplex message channel where each body chunk maps to one WebSocket message and the `binary` flag distinguishes text from binary frames. The SDK consumer uses this to decide whether to service the request with an HTTP client or a WebSocket client. It is the one piece of request metadata the consumer cannot reliably infer from the URL or headers alone. */ - @JsonProperty("transport") LlmInferenceHttpRequestStartTransport transport + @JsonProperty("transport") LlmInferenceHttpRequestStartTransport transport, + /** Stable per-agent-instance id attributing this request to a specific agent trajectory. Present when the request originates from an agent turn; absent for requests issued outside any agent context (e.g. some SDK callers). A request with an `agentId` but no `parentAgentId` is a root-agent request; one carrying both is a subagent request. Sourced from the runtime's per-request agent context and surfaced on the envelope independently of transport, so it is available for both first-party (CAPI) and BYOK/custom-provider requests; on the CAPI transport the runtime derives the upstream `X-Agent-Task-Id` header from this same context. Consumers routing each provider call to a training trajectory should key on this rather than on lifecycle events, since it is available on the request path before sampling. */ + @JsonProperty("agentId") String agentId, + /** Id of the parent agent that spawned the agent issuing this request. Present only for subagent requests; absent for root-agent requests and non-agent requests. Combined with `agentId`, this lets consumers attribute a call to a child trajectory versus the root. Like `agentId`, it comes from the runtime's per-request agent context independently of transport; on the CAPI transport the runtime derives the upstream `X-Parent-Agent-Id` header from this same context. */ + @JsonProperty("parentAgentId") String parentAgentId, + /** Coarse classification of the interaction that produced this request. Open string for forward-compatibility; known values include `conversation-agent`, `conversation-subagent`, `conversation-sampling`, `conversation-background`, `conversation-compaction`, and `conversation-user`. Absent when the runtime did not classify the request. Comes from the runtime's per-request agent context independently of transport; on the CAPI transport the runtime derives the upstream `X-Interaction-Type` header from this same context. */ + @JsonProperty("interactionType") String interactionType ) { } diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/LocalSessionMetadataValue.java b/java/src/generated/java/com/github/copilot/generated/rpc/LocalSessionMetadataValue.java index 80d0581990..c7970940a0 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/LocalSessionMetadataValue.java +++ b/java/src/generated/java/com/github/copilot/generated/rpc/LocalSessionMetadataValue.java @@ -13,7 +13,7 @@ import javax.annotation.processing.Generated; /** - * Schema for the `LocalSessionMetadataValue` type. + * Persisted local session metadata, including identifiers, timestamps, summary/name, client, context, detached state, and task ID. * * @since 1.0.0 */ diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/MarketplaceRefreshEntry.java b/java/src/generated/java/com/github/copilot/generated/rpc/MarketplaceRefreshEntry.java index c7b6819dcc..31d6310e14 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/MarketplaceRefreshEntry.java +++ b/java/src/generated/java/com/github/copilot/generated/rpc/MarketplaceRefreshEntry.java @@ -13,7 +13,7 @@ import javax.annotation.processing.Generated; /** - * Schema for the `MarketplaceRefreshEntry` type. + * Per-marketplace refresh result, including marketplace name, success flag, and optional failure error. * * @since 1.0.0 */ diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/McpAllowedServer.java b/java/src/generated/java/com/github/copilot/generated/rpc/McpAllowedServer.java index 8474a916c2..1d0a17cc9b 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/McpAllowedServer.java +++ b/java/src/generated/java/com/github/copilot/generated/rpc/McpAllowedServer.java @@ -13,7 +13,7 @@ import javax.annotation.processing.Generated; /** - * Schema for the `McpAllowedServer` type. + * MCP server allowed by policy, with server name and optional PII-free explanatory note. * * @since 1.0.0 */ diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/McpAppsResourceContent.java b/java/src/generated/java/com/github/copilot/generated/rpc/McpAppsResourceContent.java index 25850f372a..0a0f977ffd 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/McpAppsResourceContent.java +++ b/java/src/generated/java/com/github/copilot/generated/rpc/McpAppsResourceContent.java @@ -14,7 +14,7 @@ import javax.annotation.processing.Generated; /** - * Schema for the `McpAppsResourceContent` type. + * MCP Apps resource content with URI, optional MIME type, text or base64 blob, and resource metadata. * * @since 1.0.0 */ diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/McpFilteredServer.java b/java/src/generated/java/com/github/copilot/generated/rpc/McpFilteredServer.java index 9f5e919106..51dbd2bd14 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/McpFilteredServer.java +++ b/java/src/generated/java/com/github/copilot/generated/rpc/McpFilteredServer.java @@ -13,7 +13,7 @@ import javax.annotation.processing.Generated; /** - * Schema for the `McpFilteredServer` type. + * MCP server filtered by policy, with name, reason, optional redacted reason, and enterprise login. * * @since 1.0.0 */ diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/McpServer.java b/java/src/generated/java/com/github/copilot/generated/rpc/McpServer.java index e410cf5642..382c54c40f 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/McpServer.java +++ b/java/src/generated/java/com/github/copilot/generated/rpc/McpServer.java @@ -13,7 +13,7 @@ import javax.annotation.processing.Generated; /** - * Schema for the `McpServer` type. + * MCP server status entry, including config source/plugin source and any connection error. * * @since 1.0.0 */ diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/McpTools.java b/java/src/generated/java/com/github/copilot/generated/rpc/McpTools.java index 31373c1d18..04d6881266 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/McpTools.java +++ b/java/src/generated/java/com/github/copilot/generated/rpc/McpTools.java @@ -13,7 +13,7 @@ import javax.annotation.processing.Generated; /** - * Schema for the `McpTools` type. + * MCP tool metadata with tool name and optional description. * * @since 1.0.0 */ diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/Model.java b/java/src/generated/java/com/github/copilot/generated/rpc/Model.java index 0904519165..c55fc026f6 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/Model.java +++ b/java/src/generated/java/com/github/copilot/generated/rpc/Model.java @@ -14,7 +14,7 @@ import javax.annotation.processing.Generated; /** - * Schema for the `Model` type. + * Copilot model metadata, including identifier, display name, capabilities, policy, billing, reasoning efforts, and picker categories. * * @since 1.0.0 */ diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/OptionsUpdateAdditionalContentExclusionPolicy.java b/java/src/generated/java/com/github/copilot/generated/rpc/OptionsUpdateAdditionalContentExclusionPolicy.java index 13360e808d..2864bbd99b 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/OptionsUpdateAdditionalContentExclusionPolicy.java +++ b/java/src/generated/java/com/github/copilot/generated/rpc/OptionsUpdateAdditionalContentExclusionPolicy.java @@ -14,7 +14,7 @@ import javax.annotation.processing.Generated; /** - * Schema for the `OptionsUpdateAdditionalContentExclusionPolicy` type. + * Content-exclusion policy supplied to `session.options.update`, with rules, last-updated data, and scope. * * @since 1.0.0 */ diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/OptionsUpdateAdditionalContentExclusionPolicyRule.java b/java/src/generated/java/com/github/copilot/generated/rpc/OptionsUpdateAdditionalContentExclusionPolicyRule.java index af6917cb7d..135a7c7f85 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/OptionsUpdateAdditionalContentExclusionPolicyRule.java +++ b/java/src/generated/java/com/github/copilot/generated/rpc/OptionsUpdateAdditionalContentExclusionPolicyRule.java @@ -14,7 +14,7 @@ import javax.annotation.processing.Generated; /** - * Schema for the `OptionsUpdateAdditionalContentExclusionPolicyRule` type. + * Single content-exclusion rule supplied to `session.options.update`, with paths, match conditions, and source. * * @since 1.0.0 */ @@ -25,7 +25,7 @@ public record OptionsUpdateAdditionalContentExclusionPolicyRule( @JsonProperty("paths") List paths, @JsonProperty("ifAnyMatch") List ifAnyMatch, @JsonProperty("ifNoneMatch") List ifNoneMatch, - /** Schema for the `OptionsUpdateAdditionalContentExclusionPolicyRuleSource` type. */ + /** Source descriptor for a `session.options.update` content-exclusion rule, with source name and type. */ @JsonProperty("source") OptionsUpdateAdditionalContentExclusionPolicyRuleSource source ) { } diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/OptionsUpdateAdditionalContentExclusionPolicyRuleSource.java b/java/src/generated/java/com/github/copilot/generated/rpc/OptionsUpdateAdditionalContentExclusionPolicyRuleSource.java index 8befe476ec..a363722801 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/OptionsUpdateAdditionalContentExclusionPolicyRuleSource.java +++ b/java/src/generated/java/com/github/copilot/generated/rpc/OptionsUpdateAdditionalContentExclusionPolicyRuleSource.java @@ -13,7 +13,7 @@ import javax.annotation.processing.Generated; /** - * Schema for the `OptionsUpdateAdditionalContentExclusionPolicyRuleSource` type. + * Source descriptor for a `session.options.update` content-exclusion rule, with source name and type. * * @since 1.0.0 */ diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/PendingPermissionRequest.java b/java/src/generated/java/com/github/copilot/generated/rpc/PendingPermissionRequest.java index de370ca5d6..7042864b01 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/PendingPermissionRequest.java +++ b/java/src/generated/java/com/github/copilot/generated/rpc/PendingPermissionRequest.java @@ -13,7 +13,7 @@ import javax.annotation.processing.Generated; /** - * Schema for the `PendingPermissionRequest` type. + * Pending permission prompt reconstructed from event history, with request ID and user-facing prompt details. * * @since 1.0.0 */ diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/PermissionRule.java b/java/src/generated/java/com/github/copilot/generated/rpc/PermissionRule.java index 7980e0e832..8e7a6c769e 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/PermissionRule.java +++ b/java/src/generated/java/com/github/copilot/generated/rpc/PermissionRule.java @@ -13,7 +13,7 @@ import javax.annotation.processing.Generated; /** - * Schema for the `PermissionRule` type. + * A permission approval or denial rule matched against a tool request, identified by a rule kind with an optional argument value. * * @since 1.0.0 */ diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/PermissionsAllowAllMode.java b/java/src/generated/java/com/github/copilot/generated/rpc/PermissionsAllowAllMode.java new file mode 100644 index 0000000000..db24a2bad1 --- /dev/null +++ b/java/src/generated/java/com/github/copilot/generated/rpc/PermissionsAllowAllMode.java @@ -0,0 +1,37 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import javax.annotation.processing.Generated; + +/** + * Current or requested allow-all mode. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public enum PermissionsAllowAllMode { + /** The {@code off} variant. */ + OFF("off"), + /** The {@code on} variant. */ + ON("on"), + /** The {@code auto} variant. */ + AUTO("auto"); + + private final String value; + PermissionsAllowAllMode(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static PermissionsAllowAllMode fromValue(String value) { + for (PermissionsAllowAllMode v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown PermissionsAllowAllMode value: " + value); + } +} diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/PermissionsConfigureAdditionalContentExclusionPolicy.java b/java/src/generated/java/com/github/copilot/generated/rpc/PermissionsConfigureAdditionalContentExclusionPolicy.java index 61108c16bb..249c7598d9 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/PermissionsConfigureAdditionalContentExclusionPolicy.java +++ b/java/src/generated/java/com/github/copilot/generated/rpc/PermissionsConfigureAdditionalContentExclusionPolicy.java @@ -14,7 +14,7 @@ import javax.annotation.processing.Generated; /** - * Schema for the `PermissionsConfigureAdditionalContentExclusionPolicy` type. + * Content-exclusion policy supplied to `session.permissions.configure`, with rules, last-updated data, and scope. * * @since 1.0.0 */ diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/PermissionsConfigureAdditionalContentExclusionPolicyRule.java b/java/src/generated/java/com/github/copilot/generated/rpc/PermissionsConfigureAdditionalContentExclusionPolicyRule.java index c6c7f649a9..b1afc50fcd 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/PermissionsConfigureAdditionalContentExclusionPolicyRule.java +++ b/java/src/generated/java/com/github/copilot/generated/rpc/PermissionsConfigureAdditionalContentExclusionPolicyRule.java @@ -14,7 +14,7 @@ import javax.annotation.processing.Generated; /** - * Schema for the `PermissionsConfigureAdditionalContentExclusionPolicyRule` type. + * Single content-exclusion rule supplied to `session.permissions.configure`, with paths, match conditions, and source. * * @since 1.0.0 */ @@ -25,7 +25,7 @@ public record PermissionsConfigureAdditionalContentExclusionPolicyRule( @JsonProperty("paths") List paths, @JsonProperty("ifAnyMatch") List ifAnyMatch, @JsonProperty("ifNoneMatch") List ifNoneMatch, - /** Schema for the `PermissionsConfigureAdditionalContentExclusionPolicyRuleSource` type. */ + /** Source descriptor for a `session.permissions.configure` content-exclusion rule, with source name and type. */ @JsonProperty("source") PermissionsConfigureAdditionalContentExclusionPolicyRuleSource source ) { } diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/PermissionsConfigureAdditionalContentExclusionPolicyRuleSource.java b/java/src/generated/java/com/github/copilot/generated/rpc/PermissionsConfigureAdditionalContentExclusionPolicyRuleSource.java index a5d4a45f32..f592ae7991 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/PermissionsConfigureAdditionalContentExclusionPolicyRuleSource.java +++ b/java/src/generated/java/com/github/copilot/generated/rpc/PermissionsConfigureAdditionalContentExclusionPolicyRuleSource.java @@ -13,7 +13,7 @@ import javax.annotation.processing.Generated; /** - * Schema for the `PermissionsConfigureAdditionalContentExclusionPolicyRuleSource` type. + * Source descriptor for a `session.permissions.configure` content-exclusion rule, with source name and type. * * @since 1.0.0 */ diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/Plugin.java b/java/src/generated/java/com/github/copilot/generated/rpc/Plugin.java index b10cd31cf7..65268ab6e3 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/Plugin.java +++ b/java/src/generated/java/com/github/copilot/generated/rpc/Plugin.java @@ -13,7 +13,7 @@ import javax.annotation.processing.Generated; /** - * Schema for the `Plugin` type. + * Session plugin metadata, with name, marketplace, optional version, and enabled state. * * @since 1.0.0 */ diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/PluginUpdateAllEntry.java b/java/src/generated/java/com/github/copilot/generated/rpc/PluginUpdateAllEntry.java index 548ee39314..dab44f1688 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/PluginUpdateAllEntry.java +++ b/java/src/generated/java/com/github/copilot/generated/rpc/PluginUpdateAllEntry.java @@ -13,7 +13,7 @@ import javax.annotation.processing.Generated; /** - * Schema for the `PluginUpdateAllEntry` type. + * Per-plugin result from updating all plugins, with versions, skills installed, success flag, and optional error. * * @since 1.0.0 */ diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/QueuePendingItems.java b/java/src/generated/java/com/github/copilot/generated/rpc/QueuePendingItems.java index bfbc87f463..8767e91d0d 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/QueuePendingItems.java +++ b/java/src/generated/java/com/github/copilot/generated/rpc/QueuePendingItems.java @@ -13,7 +13,7 @@ import javax.annotation.processing.Generated; /** - * Schema for the `QueuePendingItems` type. + * User-facing pending queue entry, with kind and display text for a queued message, slash command, or model change. * * @since 1.0.0 */ diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SandboxConfigUserPolicyNetwork.java b/java/src/generated/java/com/github/copilot/generated/rpc/SandboxConfigUserPolicyNetwork.java index d3d7b901fa..9b8b53c816 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/SandboxConfigUserPolicyNetwork.java +++ b/java/src/generated/java/com/github/copilot/generated/rpc/SandboxConfigUserPolicyNetwork.java @@ -10,7 +10,6 @@ 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; /** @@ -25,10 +24,6 @@ public record SandboxConfigUserPolicyNetwork( /** Whether outbound network traffic is allowed at all. */ @JsonProperty("allowOutbound") Boolean allowOutbound, /** Whether traffic to local/loopback addresses is allowed. */ - @JsonProperty("allowLocalNetwork") Boolean allowLocalNetwork, - /** Hosts allowed in addition to the base policy. */ - @JsonProperty("allowedHosts") List allowedHosts, - /** Hosts explicitly blocked. */ - @JsonProperty("blockedHosts") List blockedHosts + @JsonProperty("allowLocalNetwork") Boolean allowLocalNetwork ) { } diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/ScheduleEntry.java b/java/src/generated/java/com/github/copilot/generated/rpc/ScheduleEntry.java index 5ffd0a7cdd..b88a79cca1 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/ScheduleEntry.java +++ b/java/src/generated/java/com/github/copilot/generated/rpc/ScheduleEntry.java @@ -14,7 +14,7 @@ import javax.annotation.processing.Generated; /** - * Schema for the `ScheduleEntry` type. + * Scheduled prompt entry with ID, timing (`intervalMs`, `cron`, or `at`), prompt text, recurrence, and next run time. * * @since 1.0.0 */ diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/ServerRpc.java b/java/src/generated/java/com/github/copilot/generated/rpc/ServerRpc.java index ce8c6c34f4..21907b7ab3 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/ServerRpc.java +++ b/java/src/generated/java/com/github/copilot/generated/rpc/ServerRpc.java @@ -92,7 +92,7 @@ public CompletableFuture ping(PingParams params) { } /** - * Optional connection token presented by the SDK client during the handshake. + * Parameters for the `server.connect` handshake: an optional connection token and optional connection-level opt-ins (e.g. GitHub telemetry forwarding). * * @apiNote This method is experimental and may change in a future version. * @since 1.0.0 diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/ServerSessionsApi.java b/java/src/generated/java/com/github/copilot/generated/rpc/ServerSessionsApi.java index 263c111d38..7e3b4d90d8 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/ServerSessionsApi.java +++ b/java/src/generated/java/com/github/copilot/generated/rpc/ServerSessionsApi.java @@ -55,8 +55,8 @@ public CompletableFuture fork(SessionsForkParams params) { * @since 1.0.0 */ @CopilotExperimental - public CompletableFuture connect() { - return caller.invoke("sessions.connect", java.util.Map.of(), SessionsConnectResult.class); + public CompletableFuture connect(SessionsConnectParams params) { + return caller.invoke("sessions.connect", params, SessionsConnectResult.class); } /** @@ -110,8 +110,8 @@ public CompletableFuture getLastForContext(Sess * @since 1.0.0 */ @CopilotExperimental - public CompletableFuture getEventFilePath() { - return caller.invoke("sessions.getEventFilePath", java.util.Map.of(), SessionsGetEventFilePathResult.class); + public CompletableFuture getEventFilePath(SessionsGetEventFilePathParams params) { + return caller.invoke("sessions.getEventFilePath", params, SessionsGetEventFilePathResult.class); } /** @@ -143,8 +143,8 @@ public CompletableFuture checkInUse(SessionsCheckInUse * @since 1.0.0 */ @CopilotExperimental - public CompletableFuture getPersistedRemoteSteerable() { - return caller.invoke("sessions.getPersistedRemoteSteerable", java.util.Map.of(), SessionsGetPersistedRemoteSteerableResult.class); + public CompletableFuture getPersistedRemoteSteerable(SessionsGetPersistedRemoteSteerableParams params) { + return caller.invoke("sessions.getPersistedRemoteSteerable", params, SessionsGetPersistedRemoteSteerableResult.class); } /** @@ -154,8 +154,8 @@ public CompletableFuture getPersisted * @since 1.0.0 */ @CopilotExperimental - public CompletableFuture close() { - return caller.invoke("sessions.close", java.util.Map.of(), Void.class); + public CompletableFuture close(SessionsCloseParams params) { + return caller.invoke("sessions.close", params, Void.class); } /** @@ -187,8 +187,8 @@ public CompletableFuture pruneOld(SessionsPruneOldParams * @since 1.0.0 */ @CopilotExperimental - public CompletableFuture save() { - return caller.invoke("sessions.save", java.util.Map.of(), Void.class); + public CompletableFuture save(SessionsSaveParams params) { + return caller.invoke("sessions.save", params, Void.class); } /** @@ -198,8 +198,8 @@ public CompletableFuture save() { * @since 1.0.0 */ @CopilotExperimental - public CompletableFuture releaseLock() { - return caller.invoke("sessions.releaseLock", java.util.Map.of(), Void.class); + public CompletableFuture releaseLock(SessionsReleaseLockParams params) { + return caller.invoke("sessions.releaseLock", params, Void.class); } /** @@ -231,8 +231,8 @@ public CompletableFuture reloadPluginHooks(SessionsReloadPluginHooksParams * @since 1.0.0 */ @CopilotExperimental - public CompletableFuture loadDeferredRepoHooks() { - return caller.invoke("sessions.loadDeferredRepoHooks", java.util.Map.of(), SessionsLoadDeferredRepoHooksResult.class); + public CompletableFuture loadDeferredRepoHooks(SessionsLoadDeferredRepoHooksParams params) { + return caller.invoke("sessions.loadDeferredRepoHooks", params, SessionsLoadDeferredRepoHooksResult.class); } /** @@ -253,8 +253,8 @@ public CompletableFuture setAdditionalPlugins(SessionsSetAdditionalPlugins * @since 1.0.0 */ @CopilotExperimental - public CompletableFuture getBoardEntryCount() { - return caller.invoke("sessions.getBoardEntryCount", java.util.Map.of(), SessionsGetBoardEntryCountResult.class); + public CompletableFuture getBoardEntryCount(SessionsGetBoardEntryCountParams params) { + return caller.invoke("sessions.getBoardEntryCount", params, SessionsGetBoardEntryCountResult.class); } /** @@ -312,17 +312,6 @@ public CompletableFuture getRemoteControlS return caller.invoke("sessions.getRemoteControlStatus", java.util.Map.of(), SessionsGetRemoteControlStatusResult.class); } - /** - * Cursor and optional long-poll wait for polling runtime-spawned sessions. - * - * @apiNote This method is experimental and may change in a future version. - * @since 1.0.0 - */ - @CopilotExperimental - public CompletableFuture pollSpawnedSessions() { - return caller.invoke("sessions.pollSpawnedSessions", java.util.Map.of(), SessionsPollSpawnedSessionsResult.class); - } - /** * Params to attach an extension loader's tools to a session. * diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/ServerSkill.java b/java/src/generated/java/com/github/copilot/generated/rpc/ServerSkill.java index 9752907e96..3498245262 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/ServerSkill.java +++ b/java/src/generated/java/com/github/copilot/generated/rpc/ServerSkill.java @@ -13,7 +13,7 @@ import javax.annotation.processing.Generated; /** - * Schema for the `ServerSkill` type. + * Server-side skill metadata, including name, description, source, enabled/invocable state, path, project path, and argument hint. * * @since 1.0.0 */ diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionDebugApi.java b/java/src/generated/java/com/github/copilot/generated/rpc/SessionDebugApi.java new file mode 100644 index 0000000000..e0ca94374a --- /dev/null +++ b/java/src/generated/java/com/github/copilot/generated/rpc/SessionDebugApi.java @@ -0,0 +1,49 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.github.copilot.CopilotExperimental; +import java.util.concurrent.CompletableFuture; +import javax.annotation.processing.Generated; + +/** + * API methods for the {@code debug} namespace. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public final class SessionDebugApi { + + private static final com.fasterxml.jackson.databind.ObjectMapper MAPPER = RpcMapper.INSTANCE; + + private final RpcCaller caller; + private final String sessionId; + + /** @param caller the RPC transport function */ + SessionDebugApi(RpcCaller caller, String sessionId) { + this.caller = caller; + this.sessionId = sessionId; + } + + /** + * Options for collecting a redacted session debug bundle. + *

+ * 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 collectLogs(SessionDebugCollectLogsParams params) { + com.fasterxml.jackson.databind.node.ObjectNode _p = MAPPER.valueToTree(params); + _p.put("sessionId", this.sessionId); + return caller.invoke("session.debug.collectLogs", _p, SessionDebugCollectLogsResult.class); + } + +} diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionDebugCollectLogsParams.java b/java/src/generated/java/com/github/copilot/generated/rpc/SessionDebugCollectLogsParams.java new file mode 100644 index 0000000000..2076e2ad7d --- /dev/null +++ b/java/src/generated/java/com/github/copilot/generated/rpc/SessionDebugCollectLogsParams.java @@ -0,0 +1,37 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import java.util.List; +import javax.annotation.processing.Generated; + +/** + * Options for collecting a redacted session debug bundle. + * + * @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 SessionDebugCollectLogsParams( + /** Target session identifier */ + @JsonProperty("sessionId") String sessionId, + /** Where the redacted bundle should be written. Use `archive` to produce a .tgz, or `directory` to stage redacted files for caller-managed upload/post-processing. */ + @JsonProperty("destination") Object destination, + /** Which built-in session diagnostics to include. Omitted fields default to true. */ + @JsonProperty("include") DebugCollectLogsInclude include, + /** Caller-provided server-local files or directories to include in addition to the runtime's built-in session diagnostics. This lets host applications add their own diagnostics without changing the API shape. */ + @JsonProperty("additionalEntries") List additionalEntries +) { +} diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionDebugCollectLogsResult.java b/java/src/generated/java/com/github/copilot/generated/rpc/SessionDebugCollectLogsResult.java new file mode 100644 index 0000000000..623792b02b --- /dev/null +++ b/java/src/generated/java/com/github/copilot/generated/rpc/SessionDebugCollectLogsResult.java @@ -0,0 +1,37 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import java.util.List; +import javax.annotation.processing.Generated; + +/** + * Result of collecting a redacted debug bundle. + * + * @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 SessionDebugCollectLogsResult( + /** Destination kind that was written. */ + @JsonProperty("kind") DebugCollectLogsResultKind kind, + /** Actual archive path or staging directory path written. This may differ from the requested path when no-overwrite suffixing or fallback-to-temp-directory was needed. */ + @JsonProperty("path") String path, + /** Files included in the redacted bundle. */ + @JsonProperty("entries") List entries, + /** Optional files or directories that could not be included. */ + @JsonProperty("skippedEntries") List skippedEntries +) { +} diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionEventLogRegisterInterestParams.java b/java/src/generated/java/com/github/copilot/generated/rpc/SessionEventLogRegisterInterestParams.java index d6f522ed12..6188858e01 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/SessionEventLogRegisterInterestParams.java +++ b/java/src/generated/java/com/github/copilot/generated/rpc/SessionEventLogRegisterInterestParams.java @@ -26,7 +26,7 @@ public record SessionEventLogRegisterInterestParams( /** Target session identifier */ @JsonProperty("sessionId") String sessionId, - /** The event type the consumer wants the runtime to treat as 'observed' for behavior-switching gating. Some runtime code paths inspect whether any consumer is interested in a specific event type and choose a different implementation accordingly (e.g. `mcp.oauth_required`: when interest is registered the runtime delegates the full interactive OAuth flow to the consumer; when no interest is registered the runtime installs a browserless fallback that silently reuses cached tokens). SDK clients that long-poll events do NOT automatically appear as listeners to these gating checks — they must explicitly call `registerInterest` for each event type they want the runtime to count as having a consumer. Multiple registrations for the same event type from the same or different consumers are tracked independently and must each be released. See: `mcp.oauth_required`, `sampling.requested`, `auto_mode_switch.requested`, `session_limits_exhausted.requested`, `user_input.requested`, `elicitation.requested`, `command.queued`, `exit_plan_mode.requested`. */ + /** The event type the consumer wants the runtime to treat as 'observed' for behavior-switching gating. Some runtime code paths inspect whether any consumer is interested in a specific event type and choose a different implementation accordingly (e.g. `mcp.oauth_required`: when interest is registered the runtime delegates OAuth token acquisition to the consumer; when no interest is registered OAuth-required servers become needs-auth). SDK clients that long-poll events do NOT automatically appear as listeners to these gating checks — they must explicitly call `registerInterest` for each event type they want the runtime to count as having a consumer. Multiple registrations for the same event type from the same or different consumers are tracked independently and must each be released. See: `mcp.oauth_required`, `sampling.requested`, `auto_mode_switch.requested`, `session_limits_exhausted.requested`, `user_input.requested`, `elicitation.requested`, `command.queued`, `exit_plan_mode.requested`. */ @JsonProperty("eventType") String eventType ) { } diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionFsReaddirWithTypesEntry.java b/java/src/generated/java/com/github/copilot/generated/rpc/SessionFsReaddirWithTypesEntry.java index f4c755951d..3afa7fe120 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/SessionFsReaddirWithTypesEntry.java +++ b/java/src/generated/java/com/github/copilot/generated/rpc/SessionFsReaddirWithTypesEntry.java @@ -13,7 +13,7 @@ import javax.annotation.processing.Generated; /** - * Schema for the `SessionFsReaddirWithTypesEntry` type. + * Directory entry returned by session filesystem `readdirWithTypes`, with name and entry type. * * @since 1.0.0 */ diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionInstalledPlugin.java b/java/src/generated/java/com/github/copilot/generated/rpc/SessionInstalledPlugin.java index 5d14285586..ee1bc71544 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/SessionInstalledPlugin.java +++ b/java/src/generated/java/com/github/copilot/generated/rpc/SessionInstalledPlugin.java @@ -13,7 +13,7 @@ import javax.annotation.processing.Generated; /** - * Schema for the `SessionInstalledPlugin` type. + * Installed plugin record for a session, with marketplace, version, install time, enabled state, cache path, and source. * * @since 1.0.0 */ diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpOauthApi.java b/java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpOauthApi.java index 7b5e93c416..7b7f7b82bd 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpOauthApi.java +++ b/java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpOauthApi.java @@ -30,22 +30,6 @@ public final class SessionMcpOauthApi { this.sessionId = sessionId; } - /** - * MCP OAuth request id and optional provider response. - *

- * Note: the {@code sessionId} field in the params record is overridden - * by the session-scoped wrapper; any value provided is ignored. - * - * @apiNote This method is experimental and may change in a future version. - * @since 1.0.0 - */ - @CopilotExperimental - public CompletableFuture respond(SessionMcpOauthRespondParams params) { - com.fasterxml.jackson.databind.node.ObjectNode _p = MAPPER.valueToTree(params); - _p.put("sessionId", this.sessionId); - return caller.invoke("session.mcp.oauth.respond", _p, Void.class); - } - /** * Pending MCP OAuth request ID and host-provided token or cancellation response. *

diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionMetadataApi.java b/java/src/generated/java/com/github/copilot/generated/rpc/SessionMetadataApi.java index 2223e56cd3..209fe8cac4 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/SessionMetadataApi.java +++ b/java/src/generated/java/com/github/copilot/generated/rpc/SessionMetadataApi.java @@ -79,6 +79,33 @@ public CompletableFuture contextInfo(SessionMe return caller.invoke("session.metadata.contextInfo", _p, SessionMetadataContextInfoResult.class); } + /** + * Identifies the target session. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + public CompletableFuture getContextAttribution() { + return caller.invoke("session.metadata.getContextAttribution", java.util.Map.of("sessionId", this.sessionId), SessionMetadataGetContextAttributionResult.class); + } + + /** + * Parameters for the heaviest-messages query. + *

+ * 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 getContextHeaviestMessages(SessionMetadataGetContextHeaviestMessagesParams params) { + com.fasterxml.jackson.databind.node.ObjectNode _p = MAPPER.valueToTree(params); + _p.put("sessionId", this.sessionId); + return caller.invoke("session.metadata.getContextHeaviestMessages", _p, SessionMetadataGetContextHeaviestMessagesResult.class); + } + /** * Updated working-directory/git context to record on the session. *

diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionsPollSpawnedSessionsResult.java b/java/src/generated/java/com/github/copilot/generated/rpc/SessionMetadataGetContextAttributionParams.java similarity index 70% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionsPollSpawnedSessionsResult.java rename to java/src/generated/java/com/github/copilot/generated/rpc/SessionMetadataGetContextAttributionParams.java index e5ca0c0857..c0fc0e9120 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/SessionsPollSpawnedSessionsResult.java +++ b/java/src/generated/java/com/github/copilot/generated/rpc/SessionMetadataGetContextAttributionParams.java @@ -11,11 +11,10 @@ import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import com.github.copilot.CopilotExperimental; -import java.util.List; import javax.annotation.processing.Generated; /** - * Batch of spawn events plus a cursor for follow-up polls. + * Identifies the target session. * * @apiNote This method is experimental and may change in a future version. * @since 1.0.0 @@ -24,10 +23,8 @@ @javax.annotation.processing.Generated("copilot-sdk-codegen") @JsonInclude(JsonInclude.Include.NON_NULL) @JsonIgnoreProperties(ignoreUnknown = true) -public record SessionsPollSpawnedSessionsResult( - /** Spawn events emitted since the supplied cursor. */ - @JsonProperty("events") List events, - /** Opaque cursor to pass back to receive only events after this batch. */ - @JsonProperty("cursor") String cursor +public record SessionMetadataGetContextAttributionParams( + /** Target session identifier */ + @JsonProperty("sessionId") String sessionId ) { } diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionMetadataGetContextAttributionResult.java b/java/src/generated/java/com/github/copilot/generated/rpc/SessionMetadataGetContextAttributionResult.java new file mode 100644 index 0000000000..ef7fba44d9 --- /dev/null +++ b/java/src/generated/java/com/github/copilot/generated/rpc/SessionMetadataGetContextAttributionResult.java @@ -0,0 +1,72 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import java.util.List; +import java.util.Map; +import javax.annotation.processing.Generated; + +/** + * Per-source attribution breakdown for the session's current context window, or null if uninitialized. + * + * @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 SessionMetadataGetContextAttributionResult( + /** Per-source context-window attribution, or null if the session has not yet been initialized (no system prompt or tool metadata cached). */ + @JsonProperty("contextAttribution") SessionMetadataGetContextAttributionResultContextAttribution contextAttribution +) { + + /** Per-source token attribution snapshot for the current context window. The heaviest individual messages are available separately via `metadata.getContextHeaviestMessages`. */ + @JsonIgnoreProperties(ignoreUnknown = true) + @JsonInclude(JsonInclude.Include.NON_NULL) + public record SessionMetadataGetContextAttributionResultContextAttribution( + /** Total token count of the current context window the entries are measured against (system message + conversation messages + tool definitions — the same total reported by /context). Divide an entry's `tokens` by this to derive its share. */ + @JsonProperty("totalTokens") Long totalTokens, + /** Flat list of per-source attribution entries. Group by `kind` and render unrecognized kinds generically. Nesting and rollups are expressed via `parentId`. */ + @JsonProperty("entries") List entries, + /** Successful compaction history for the session. */ + @JsonProperty("compactions") SessionMetadataGetContextAttributionResultContextAttributionCompactions compactions + ) { + + @JsonIgnoreProperties(ignoreUnknown = true) + @JsonInclude(JsonInclude.Include.NON_NULL) + public record SessionMetadataGetContextAttributionResultContextAttributionEntriesItem( + /** Source category for this entry. Not a closed set — tolerate unknown values. Known values today: `skill`, `subagent`, `mcpServer`, `tool`, `system`, `toolDefinition`, `plugin`. */ + @JsonProperty("kind") String kind, + /** Identifier for this entry, formed by joining its `kind` and source name (e.g. `tool:bash`, `skill:tmux`, `toolDefinition:bash`); unique within the snapshot. Use it to match the same entry across snapshots, to correlate with other APIs (skill/agent/MCP registries), and as the `parentId` target for nesting. Distinct from the human-facing `label`. */ + @JsonProperty("id") String id, + /** Human-readable display label, e.g. `bash` or `skill: tmux`. Presentation-only; may be localized/reformatted without notice — do not key off it. */ + @JsonProperty("label") String label, + /** Token count currently in context attributable to this entry. */ + @JsonProperty("tokens") Long tokens, + /** Optional `id` of the parent entry: e.g. a `plugin` entry parenting its `skill`/`mcpServer` entries, or the `system` entry parenting `toolDefinition` entries. Omitted for top-level entries. */ + @JsonProperty("parentId") String parentId, + /** Supplementary per-entry metadata (e.g. `messageCount`, `role`, `evictable`, `pluginSource`). Values are stringified; parse as needed and ignore unrecognized keys. */ + @JsonProperty("attributes") Map attributes + ) { + } + + /** Successful compaction history for the session. */ + @JsonIgnoreProperties(ignoreUnknown = true) + @JsonInclude(JsonInclude.Include.NON_NULL) + public record SessionMetadataGetContextAttributionResultContextAttributionCompactions( + /** Number of successful compactions in this session. */ + @JsonProperty("count") Long count + ) { + } + } +} diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpOauthRespondParams.java b/java/src/generated/java/com/github/copilot/generated/rpc/SessionMetadataGetContextHeaviestMessagesParams.java similarity index 71% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpOauthRespondParams.java rename to java/src/generated/java/com/github/copilot/generated/rpc/SessionMetadataGetContextHeaviestMessagesParams.java index 9757a95388..cacdd4ccb5 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpOauthRespondParams.java +++ b/java/src/generated/java/com/github/copilot/generated/rpc/SessionMetadataGetContextHeaviestMessagesParams.java @@ -14,7 +14,7 @@ import javax.annotation.processing.Generated; /** - * MCP OAuth request id and optional provider response. + * Parameters for the heaviest-messages query. * * @apiNote This method is experimental and may change in a future version. * @since 1.0.0 @@ -23,12 +23,10 @@ @javax.annotation.processing.Generated("copilot-sdk-codegen") @JsonInclude(JsonInclude.Include.NON_NULL) @JsonIgnoreProperties(ignoreUnknown = true) -public record SessionMcpOauthRespondParams( +public record SessionMetadataGetContextHeaviestMessagesParams( /** Target session identifier */ @JsonProperty("sessionId") String sessionId, - /** OAuth request identifier from mcp.oauth_required */ - @JsonProperty("requestId") String requestId, - /** In-process OAuthClientProvider instance, or omitted to deny. Marked internal: cannot be serialized across the JSON-RPC boundary. */ - @JsonProperty("provider") Object provider + /** Maximum number of messages to return, most-expensive first. Omit for the server default. */ + @JsonProperty("limit") Long limit ) { } diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionMetadataGetContextHeaviestMessagesResult.java b/java/src/generated/java/com/github/copilot/generated/rpc/SessionMetadataGetContextHeaviestMessagesResult.java new file mode 100644 index 0000000000..90b5c3160f --- /dev/null +++ b/java/src/generated/java/com/github/copilot/generated/rpc/SessionMetadataGetContextHeaviestMessagesResult.java @@ -0,0 +1,33 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import java.util.List; +import javax.annotation.processing.Generated; + +/** + * The heaviest individual messages in the session's context window, most-expensive first. + * + * @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 SessionMetadataGetContextHeaviestMessagesResult( + /** Total token count of the current context window, so callers can compute each message's share without a second call. */ + @JsonProperty("totalTokens") Long totalTokens, + /** Heaviest messages, most-expensive first. */ + @JsonProperty("messages") List messages +) { +} diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionModelSwitchToParams.java b/java/src/generated/java/com/github/copilot/generated/rpc/SessionModelSwitchToParams.java index b0b69ff25e..580ab3bab2 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/SessionModelSwitchToParams.java +++ b/java/src/generated/java/com/github/copilot/generated/rpc/SessionModelSwitchToParams.java @@ -32,6 +32,8 @@ public record SessionModelSwitchToParams( @JsonProperty("reasoningEffort") String reasoningEffort, /** Reasoning summary mode to request for supported model clients */ @JsonProperty("reasoningSummary") ReasoningSummary reasoningSummary, + /** Output verbosity level to request for supported models */ + @JsonProperty("verbosity") Verbosity verbosity, /** Override individual model capabilities resolved by the runtime */ @JsonProperty("modelCapabilities") ModelCapabilitiesOverride modelCapabilities, /** Explicit context tier for the selected model. `"default"` / `"long_context"` apply the requested tier; omit this field to use normal model behavior with no explicit tier. */ diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionOptionsUpdateParams.java b/java/src/generated/java/com/github/copilot/generated/rpc/SessionOptionsUpdateParams.java index 9cede57d35..ba012106a4 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/SessionOptionsUpdateParams.java +++ b/java/src/generated/java/com/github/copilot/generated/rpc/SessionOptionsUpdateParams.java @@ -36,6 +36,8 @@ public record SessionOptionsUpdateParams( @JsonProperty("reasoningEffort") String reasoningEffort, /** Reasoning summary mode for supported model clients. */ @JsonProperty("reasoningSummary") OptionsUpdateReasoningSummary reasoningSummary, + /** Output verbosity level for supported models. */ + @JsonProperty("verbosity") Verbosity verbosity, /** Identifier of the client driving the session. */ @JsonProperty("clientName") String clientName, /** Identifier sent to LSP-style integrations. */ diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsApi.java b/java/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsApi.java index bbac10accd..aba3177083 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsApi.java +++ b/java/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsApi.java @@ -103,7 +103,7 @@ public CompletableFuture setApproveAll(Se } /** - * Whether to enable full allow-all permissions for the session. + * Allow-all mode to apply for the session. *

* Note: the {@code sessionId} field in the params record is overridden * by the session-scoped wrapper; any value provided is ignored. diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsGetAllowAllResult.java b/java/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsGetAllowAllResult.java index 772d9a0deb..28d9915df2 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsGetAllowAllResult.java +++ b/java/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsGetAllowAllResult.java @@ -14,7 +14,7 @@ import javax.annotation.processing.Generated; /** - * Current full allow-all permission state. + * Current allow-all permission mode. * * @apiNote This method is experimental and may change in a future version. * @since 1.0.0 @@ -25,6 +25,8 @@ @JsonIgnoreProperties(ignoreUnknown = true) public record SessionPermissionsGetAllowAllResult( /** Whether full allow-all permissions are currently active */ - @JsonProperty("enabled") Boolean enabled + @JsonProperty("enabled") Boolean enabled, + /** Current allow-all mode */ + @JsonProperty("mode") PermissionsAllowAllMode mode ) { } diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsSetAllowAllParams.java b/java/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsSetAllowAllParams.java index 4a553ffcf0..7bde47bc81 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsSetAllowAllParams.java +++ b/java/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsSetAllowAllParams.java @@ -14,7 +14,7 @@ import javax.annotation.processing.Generated; /** - * Whether to enable full allow-all permissions for the session. + * Allow-all mode to apply for the session. * * @apiNote This method is experimental and may change in a future version. * @since 1.0.0 @@ -26,8 +26,12 @@ public record SessionPermissionsSetAllowAllParams( /** Target session identifier */ @JsonProperty("sessionId") String sessionId, - /** Whether to enable full allow-all permissions */ + /** Allow-all mode to apply. `on` enables full allow-all; `auto` enables advisory LLM auto-approval; `off` disables both. */ + @JsonProperty("mode") PermissionsAllowAllMode mode, + /** Legacy full allow-all toggle. Prefer `mode`; when `mode` is omitted, `enabled: true` is treated as `mode: "on"` and any other value is treated as `mode: "off"`. */ @JsonProperty("enabled") Boolean enabled, + /** Optional model id for the `auto` mode auto-approval LLM judging. Only meaningful when `mode` is `auto`; ignored otherwise. When omitted, the session's active model is used. */ + @JsonProperty("model") String model, /** Optional source for allow-all telemetry. Defaults to `rpc` when omitted for SDK callers. */ @JsonProperty("source") PermissionsSetAllowAllSource source ) { diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsSetAllowAllResult.java b/java/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsSetAllowAllResult.java index ed14e554ba..9b14d8f6ad 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsSetAllowAllResult.java +++ b/java/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsSetAllowAllResult.java @@ -26,7 +26,9 @@ public record SessionPermissionsSetAllowAllResult( /** Whether the operation succeeded */ @JsonProperty("success") Boolean success, - /** Authoritative allow-all state after the mutation */ - @JsonProperty("enabled") Boolean enabled + /** Authoritative full allow-all state after the mutation */ + @JsonProperty("enabled") Boolean enabled, + /** Authoritative allow-all mode after the mutation */ + @JsonProperty("mode") PermissionsAllowAllMode mode ) { } diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionRpc.java b/java/src/generated/java/com/github/copilot/generated/rpc/SessionRpc.java index 3881140bb1..1007c0db7b 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/SessionRpc.java +++ b/java/src/generated/java/com/github/copilot/generated/rpc/SessionRpc.java @@ -31,6 +31,8 @@ public final class SessionRpc { /** API methods for the {@code gitHubAuth} namespace. */ public final SessionGitHubAuthApi gitHubAuth; + /** API methods for the {@code debug} namespace. */ + public final SessionDebugApi debug; /** API methods for the {@code canvas} namespace. */ public final SessionCanvasApi canvas; /** API methods for the {@code model} namespace. */ @@ -79,6 +81,8 @@ public final class SessionRpc { public final SessionPermissionsApi permissions; /** API methods for the {@code metadata} namespace. */ public final SessionMetadataApi metadata; + /** API methods for the {@code settings} namespace. */ + public final SessionSettingsApi settings; /** API methods for the {@code shell} namespace. */ public final SessionShellApi shell; /** API methods for the {@code history} namespace. */ @@ -106,6 +110,7 @@ public SessionRpc(RpcCaller caller, String sessionId) { this.caller = caller; this.sessionId = sessionId; this.gitHubAuth = new SessionGitHubAuthApi(caller, sessionId); + this.debug = new SessionDebugApi(caller, sessionId); this.canvas = new SessionCanvasApi(caller, sessionId); this.model = new SessionModelApi(caller, sessionId); this.mode = new SessionModeApi(caller, sessionId); @@ -130,6 +135,7 @@ public SessionRpc(RpcCaller caller, String sessionId) { this.ui = new SessionUiApi(caller, sessionId); this.permissions = new SessionPermissionsApi(caller, sessionId); this.metadata = new SessionMetadataApi(caller, sessionId); + this.settings = new SessionSettingsApi(caller, sessionId); this.shell = new SessionShellApi(caller, sessionId); this.history = new SessionHistoryApi(caller, sessionId); this.queue = new SessionQueueApi(caller, sessionId); diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionSettingsApi.java b/java/src/generated/java/com/github/copilot/generated/rpc/SessionSettingsApi.java new file mode 100644 index 0000000000..dd4acfdb53 --- /dev/null +++ b/java/src/generated/java/com/github/copilot/generated/rpc/SessionSettingsApi.java @@ -0,0 +1,60 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.github.copilot.CopilotExperimental; +import java.util.concurrent.CompletableFuture; +import javax.annotation.processing.Generated; + +/** + * API methods for the {@code settings} namespace. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public final class SessionSettingsApi { + + private static final com.fasterxml.jackson.databind.ObjectMapper MAPPER = RpcMapper.INSTANCE; + + private final RpcCaller caller; + private final String sessionId; + + /** @param caller the RPC transport function */ + SessionSettingsApi(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 snapshot() { + return caller.invoke("session.settings.snapshot", java.util.Map.of("sessionId", this.sessionId), SessionSettingsSnapshotResult.class); + } + + /** + * Named Rust-owned settings predicate to evaluate for this session. + *

+ * Note: the {@code sessionId} field in the params record is overridden + * by the session-scoped wrapper; any value provided is ignored. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + public CompletableFuture evaluatePredicate(SessionSettingsEvaluatePredicateParams params) { + com.fasterxml.jackson.databind.node.ObjectNode _p = MAPPER.valueToTree(params); + _p.put("sessionId", this.sessionId); + return caller.invoke("session.settings.evaluatePredicate", _p, SessionSettingsEvaluatePredicateResult.class); + } + +} diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionSettingsBuiltInToolAvailabilitySnapshot.java b/java/src/generated/java/com/github/copilot/generated/rpc/SessionSettingsBuiltInToolAvailabilitySnapshot.java new file mode 100644 index 0000000000..98e6df29fa --- /dev/null +++ b/java/src/generated/java/com/github/copilot/generated/rpc/SessionSettingsBuiltInToolAvailabilitySnapshot.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; + +/** + * Availability of built-in job tools surfaced to boundary consumers. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record SessionSettingsBuiltInToolAvailabilitySnapshot( + @JsonProperty("reportProgress") Boolean reportProgress, + @JsonProperty("createPullRequest") Boolean createPullRequest +) { +} diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionSettingsEvaluatePredicateParams.java b/java/src/generated/java/com/github/copilot/generated/rpc/SessionSettingsEvaluatePredicateParams.java new file mode 100644 index 0000000000..a7c1663e28 --- /dev/null +++ b/java/src/generated/java/com/github/copilot/generated/rpc/SessionSettingsEvaluatePredicateParams.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; + +/** + * Named Rust-owned settings predicate to evaluate for this 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 SessionSettingsEvaluatePredicateParams( + /** Target session identifier */ + @JsonProperty("sessionId") String sessionId, + /** Predicate name. The runtime owns the raw feature-flag names and composition logic. */ + @JsonProperty("name") SessionSettingsPredicateName name, + /** Tool name for tool-scoped predicates such as trivial-change handling. */ + @JsonProperty("toolName") String toolName +) { +} diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionSettingsEvaluatePredicateResult.java b/java/src/generated/java/com/github/copilot/generated/rpc/SessionSettingsEvaluatePredicateResult.java new file mode 100644 index 0000000000..1f4a7ed320 --- /dev/null +++ b/java/src/generated/java/com/github/copilot/generated/rpc/SessionSettingsEvaluatePredicateResult.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 com.github.copilot.CopilotExperimental; +import javax.annotation.processing.Generated; + +/** + * Result of evaluating a Rust-owned settings predicate. + * + * @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 SessionSettingsEvaluatePredicateResult( + @JsonProperty("enabled") Boolean enabled +) { +} diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionSettingsJobSnapshot.java b/java/src/generated/java/com/github/copilot/generated/rpc/SessionSettingsJobSnapshot.java new file mode 100644 index 0000000000..463660d8a1 --- /dev/null +++ b/java/src/generated/java/com/github/copilot/generated/rpc/SessionSettingsJobSnapshot.java @@ -0,0 +1,28 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import javax.annotation.processing.Generated; + +/** + * Redacted job settings for a session. The job nonce is excluded. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record SessionSettingsJobSnapshot( + @JsonProperty("eventType") String eventType, + @JsonProperty("isTriggerJob") Boolean isTriggerJob, + @JsonProperty("builtInToolAvailability") SessionSettingsBuiltInToolAvailabilitySnapshot builtInToolAvailability +) { +} diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionSettingsModelSnapshot.java b/java/src/generated/java/com/github/copilot/generated/rpc/SessionSettingsModelSnapshot.java new file mode 100644 index 0000000000..ce515c74db --- /dev/null +++ b/java/src/generated/java/com/github/copilot/generated/rpc/SessionSettingsModelSnapshot.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; + +/** + * Redacted model routing settings for a session. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record SessionSettingsModelSnapshot( + @JsonProperty("model") String model, + @JsonProperty("defaultReasoningEffort") String defaultReasoningEffort, + @JsonProperty("instanceId") String instanceId, + @JsonProperty("callbackUrl") String callbackUrl +) { +} diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionSettingsOnlineEvaluationSnapshot.java b/java/src/generated/java/com/github/copilot/generated/rpc/SessionSettingsOnlineEvaluationSnapshot.java new file mode 100644 index 0000000000..b1a5be6f3a --- /dev/null +++ b/java/src/generated/java/com/github/copilot/generated/rpc/SessionSettingsOnlineEvaluationSnapshot.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; + +/** + * Online-evaluation settings safe to expose across the SDK boundary. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record SessionSettingsOnlineEvaluationSnapshot( + @JsonProperty("disableOnlineEvaluation") Boolean disableOnlineEvaluation, + @JsonProperty("enableOnlineEvaluationOutputFile") Boolean enableOnlineEvaluationOutputFile +) { +} diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionSettingsPredicateName.java b/java/src/generated/java/com/github/copilot/generated/rpc/SessionSettingsPredicateName.java new file mode 100644 index 0000000000..6c99c214a8 --- /dev/null +++ b/java/src/generated/java/com/github/copilot/generated/rpc/SessionSettingsPredicateName.java @@ -0,0 +1,69 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (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; + +/** + * Rust-owned settings predicates exposed across the SDK boundary. Raw feature-flag names are intentionally not part of the contract. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public enum SessionSettingsPredicateName { + /** The {@code securityToolsEnabled} variant. */ + SECURITYTOOLSENABLED("securityToolsEnabled"), + /** The {@code thirdPartySecurityPromptEnabled} variant. */ + THIRDPARTYSECURITYPROMPTENABLED("thirdPartySecurityPromptEnabled"), + /** The {@code parallelValidationEnabled} variant. */ + PARALLELVALIDATIONENABLED("parallelValidationEnabled"), + /** The {@code runtimeTimingTelemetryEnabled} variant. */ + RUNTIMETIMINGTELEMETRYENABLED("runtimeTimingTelemetryEnabled"), + /** The {@code coAuthorHookEnabled} variant. */ + COAUTHORHOOKENABLED("coAuthorHookEnabled"), + /** The {@code chronicleEnabled} variant. */ + CHRONICLEENABLED("chronicleEnabled"), + /** The {@code contentExclusionSelfFetchEnabled} variant. */ + CONTENTEXCLUSIONSELFFETCHENABLED("contentExclusionSelfFetchEnabled"), + /** The {@code capClaudeOpusTokenLimitsEnabled} variant. */ + CAPCLAUDEOPUSTOKENLIMITSENABLED("capClaudeOpusTokenLimitsEnabled"), + /** The {@code codeReviewFeatureEnabled} variant. */ + CODEREVIEWFEATUREENABLED("codeReviewFeatureEnabled"), + /** The {@code ccaUseTsAutofindEnabled} variant. */ + CCAUSETSAUTOFINDENABLED("ccaUseTsAutofindEnabled"), + /** The {@code dependencyCheckerEnabled} variant. */ + DEPENDENCYCHECKERENABLED("dependencyCheckerEnabled"), + /** The {@code dependabotCheckerEnabled} variant. */ + DEPENDABOTCHECKERENABLED("dependabotCheckerEnabled"), + /** The {@code codeqlCheckerEnabled} variant. */ + CODEQLCHECKERENABLED("codeqlCheckerEnabled"), + /** The {@code trivialChangeEnabled} variant. */ + TRIVIALCHANGEENABLED("trivialChangeEnabled"), + /** The {@code trivialChangeSkipEnabled} variant. */ + TRIVIALCHANGESKIPENABLED("trivialChangeSkipEnabled"), + /** The {@code trivialChangeEnabledForCodeReview} variant. */ + TRIVIALCHANGEENABLEDFORCODEREVIEW("trivialChangeEnabledForCodeReview"), + /** The {@code trivialChangeSkipEnabledForCodeReview} variant. */ + TRIVIALCHANGESKIPENABLEDFORCODEREVIEW("trivialChangeSkipEnabledForCodeReview"), + /** The {@code trivialChangeEnabledForTool} variant. */ + TRIVIALCHANGEENABLEDFORTOOL("trivialChangeEnabledForTool"), + /** The {@code trivialChangeSkipEnabledForTool} variant. */ + TRIVIALCHANGESKIPENABLEDFORTOOL("trivialChangeSkipEnabledForTool"); + + private final String value; + SessionSettingsPredicateName(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static SessionSettingsPredicateName fromValue(String value) { + for (SessionSettingsPredicateName v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown SessionSettingsPredicateName value: " + value); + } +} diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionSettingsRepoSnapshot.java b/java/src/generated/java/com/github/copilot/generated/rpc/SessionSettingsRepoSnapshot.java new file mode 100644 index 0000000000..960853c527 --- /dev/null +++ b/java/src/generated/java/com/github/copilot/generated/rpc/SessionSettingsRepoSnapshot.java @@ -0,0 +1,37 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import javax.annotation.processing.Generated; + +/** + * Redacted repository and GitHub host settings for a session. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record SessionSettingsRepoSnapshot( + @JsonProperty("name") String name, + @JsonProperty("id") Double id, + @JsonProperty("branch") String branch, + @JsonProperty("commit") String commit, + @JsonProperty("readWrite") Boolean readWrite, + @JsonProperty("ownerName") String ownerName, + @JsonProperty("ownerId") Double ownerId, + @JsonProperty("serverUrl") String serverUrl, + @JsonProperty("host") String host, + @JsonProperty("hostProtocol") String hostProtocol, + @JsonProperty("secretScanningUrl") String secretScanningUrl, + @JsonProperty("prCommitCount") Double prCommitCount +) { +} diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionsPollSpawnedSessionsEvent.java b/java/src/generated/java/com/github/copilot/generated/rpc/SessionSettingsSnapshotParams.java similarity index 75% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionsPollSpawnedSessionsEvent.java rename to java/src/generated/java/com/github/copilot/generated/rpc/SessionSettingsSnapshotParams.java index 09b6ed1134..8bad931529 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/SessionsPollSpawnedSessionsEvent.java +++ b/java/src/generated/java/com/github/copilot/generated/rpc/SessionSettingsSnapshotParams.java @@ -10,18 +10,21 @@ 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; /** - * Schema for the `SessionsPollSpawnedSessionsEvent` type. + * 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 SessionsPollSpawnedSessionsEvent( - /** Session id of the newly-spawned session. */ +public record SessionSettingsSnapshotParams( + /** Target session identifier */ @JsonProperty("sessionId") String sessionId ) { } diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionSettingsSnapshotResult.java b/java/src/generated/java/com/github/copilot/generated/rpc/SessionSettingsSnapshotResult.java new file mode 100644 index 0000000000..0851474d64 --- /dev/null +++ b/java/src/generated/java/com/github/copilot/generated/rpc/SessionSettingsSnapshotResult.java @@ -0,0 +1,37 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import javax.annotation.processing.Generated; + +/** + * Redacted, serializable view of session runtime settings for SDK boundary consumers. Secrets and raw feature flags are intentionally excluded. + * + * @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 SessionSettingsSnapshotResult( + @JsonProperty("version") String version, + @JsonProperty("clientName") String clientName, + @JsonProperty("timeoutMs") Double timeoutMs, + @JsonProperty("startTimeMs") Double startTimeMs, + @JsonProperty("repo") SessionSettingsRepoSnapshot repo, + @JsonProperty("model") SessionSettingsModelSnapshot model, + @JsonProperty("validation") SessionSettingsValidationSnapshot validation, + @JsonProperty("job") SessionSettingsJobSnapshot job, + @JsonProperty("onlineEvaluation") SessionSettingsOnlineEvaluationSnapshot onlineEvaluation +) { +} diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionSettingsValidationSnapshot.java b/java/src/generated/java/com/github/copilot/generated/rpc/SessionSettingsValidationSnapshot.java new file mode 100644 index 0000000000..375cfdfec1 --- /dev/null +++ b/java/src/generated/java/com/github/copilot/generated/rpc/SessionSettingsValidationSnapshot.java @@ -0,0 +1,34 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import javax.annotation.processing.Generated; + +/** + * Redacted validation and memory-tool settings for a session. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record SessionSettingsValidationSnapshot( + @JsonProperty("timeout") Double timeout, + @JsonProperty("dependabotTimeout") Double dependabotTimeout, + @JsonProperty("codeqlEnabled") Boolean codeqlEnabled, + @JsonProperty("codeReviewEnabled") Boolean codeReviewEnabled, + @JsonProperty("codeReviewModel") String codeReviewModel, + @JsonProperty("advisoryEnabled") Boolean advisoryEnabled, + @JsonProperty("secretScanningEnabled") Boolean secretScanningEnabled, + @JsonProperty("memoryStoreEnabled") Boolean memoryStoreEnabled, + @JsonProperty("memoryVoteEnabled") Boolean memoryVoteEnabled +) { +} diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionUiHandlePendingExitPlanModeParams.java b/java/src/generated/java/com/github/copilot/generated/rpc/SessionUiHandlePendingExitPlanModeParams.java index 87eccd1e24..2142a98f3b 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/SessionUiHandlePendingExitPlanModeParams.java +++ b/java/src/generated/java/com/github/copilot/generated/rpc/SessionUiHandlePendingExitPlanModeParams.java @@ -28,7 +28,7 @@ public record SessionUiHandlePendingExitPlanModeParams( @JsonProperty("sessionId") String sessionId, /** The unique request ID from the exit_plan_mode.requested event */ @JsonProperty("requestId") String requestId, - /** Schema for the `UIExitPlanModeResponse` type. */ + /** User response for a pending exit-plan-mode request, with approval state, selected action, auto-approve flag, and feedback. */ @JsonProperty("response") UIExitPlanModeResponse response ) { } diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionUiHandlePendingUserInputParams.java b/java/src/generated/java/com/github/copilot/generated/rpc/SessionUiHandlePendingUserInputParams.java index 2240354381..999a8738db 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/SessionUiHandlePendingUserInputParams.java +++ b/java/src/generated/java/com/github/copilot/generated/rpc/SessionUiHandlePendingUserInputParams.java @@ -28,7 +28,7 @@ public record SessionUiHandlePendingUserInputParams( @JsonProperty("sessionId") String sessionId, /** The unique request ID from the user_input.requested event */ @JsonProperty("requestId") String requestId, - /** Schema for the `UIUserInputResponse` type. */ + /** User response for a pending user-input request, with answer text and whether it was typed freeform. */ @JsonProperty("response") UIUserInputResponse response ) { } diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionsOpenProgress.java b/java/src/generated/java/com/github/copilot/generated/rpc/SessionsOpenProgress.java index 44d1aa0de7..8509295cbd 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/SessionsOpenProgress.java +++ b/java/src/generated/java/com/github/copilot/generated/rpc/SessionsOpenProgress.java @@ -13,7 +13,7 @@ import javax.annotation.processing.Generated; /** - * Schema for the `SessionsOpenProgress` type. + * `sessions.open` handoff progress update with step, status, and optional message. * * @since 1.0.0 */ diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/Skill.java b/java/src/generated/java/com/github/copilot/generated/rpc/Skill.java index 5b1cea9b76..4ad4116871 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/Skill.java +++ b/java/src/generated/java/com/github/copilot/generated/rpc/Skill.java @@ -13,7 +13,7 @@ import javax.annotation.processing.Generated; /** - * Schema for the `Skill` type. + * Skill metadata available to a session, with name, description, source, enabled/invocable state, path, plugin, and argument hint. * * @since 1.0.0 */ diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SkillDiscoveryPath.java b/java/src/generated/java/com/github/copilot/generated/rpc/SkillDiscoveryPath.java index 4d2e90a5d9..feea2794ff 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/SkillDiscoveryPath.java +++ b/java/src/generated/java/com/github/copilot/generated/rpc/SkillDiscoveryPath.java @@ -13,7 +13,7 @@ import javax.annotation.processing.Generated; /** - * Schema for the `SkillDiscoveryPath` type. + * Canonical directory where skills can be discovered or created, with scope, preference, and optional project path. * * @since 1.0.0 */ diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SkillsInvokedSkill.java b/java/src/generated/java/com/github/copilot/generated/rpc/SkillsInvokedSkill.java index 697e18fa4c..a020c89ecf 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/SkillsInvokedSkill.java +++ b/java/src/generated/java/com/github/copilot/generated/rpc/SkillsInvokedSkill.java @@ -14,7 +14,7 @@ import javax.annotation.processing.Generated; /** - * Schema for the `SkillsInvokedSkill` type. + * Skill invocation record with name, path, content, allowed tools, and turn number. * * @since 1.0.0 */ diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SlashCommandAgentPromptResult.java b/java/src/generated/java/com/github/copilot/generated/rpc/SlashCommandAgentPromptResult.java index 48a6c08a6f..a034458c98 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/SlashCommandAgentPromptResult.java +++ b/java/src/generated/java/com/github/copilot/generated/rpc/SlashCommandAgentPromptResult.java @@ -13,7 +13,7 @@ import javax.annotation.processing.Generated; /** - * Schema for the `SlashCommandAgentPromptResult` type. + * Slash-command invocation result that submits an agent prompt, with display prompt, optional mode, and settings-change flag. * * @since 1.0.0 */ diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SlashCommandCompletedResult.java b/java/src/generated/java/com/github/copilot/generated/rpc/SlashCommandCompletedResult.java index 8c6ef74be8..b2a1970a36 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/SlashCommandCompletedResult.java +++ b/java/src/generated/java/com/github/copilot/generated/rpc/SlashCommandCompletedResult.java @@ -13,7 +13,7 @@ import javax.annotation.processing.Generated; /** - * Schema for the `SlashCommandCompletedResult` type. + * Slash-command invocation result indicating completion, with optional message and settings-change flag. * * @since 1.0.0 */ diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SlashCommandInfo.java b/java/src/generated/java/com/github/copilot/generated/rpc/SlashCommandInfo.java index 61b2e933f3..9a725ececf 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/SlashCommandInfo.java +++ b/java/src/generated/java/com/github/copilot/generated/rpc/SlashCommandInfo.java @@ -14,7 +14,7 @@ import javax.annotation.processing.Generated; /** - * Schema for the `SlashCommandInfo` type. + * Slash-command metadata with name, aliases, description, kind, input hint, execution allowance, and schedulability. * * @since 1.0.0 */ diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SlashCommandInput.java b/java/src/generated/java/com/github/copilot/generated/rpc/SlashCommandInput.java index dcfc2a36e7..f0df784489 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/SlashCommandInput.java +++ b/java/src/generated/java/com/github/copilot/generated/rpc/SlashCommandInput.java @@ -10,6 +10,7 @@ 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; /** @@ -23,6 +24,8 @@ public record SlashCommandInput( /** Hint to display when command input has not been provided */ @JsonProperty("hint") String hint, + /** Optional literal choices the input accepts, each with a human-facing description; clients may render these as selectable options */ + @JsonProperty("choices") List choices, /** When true, the command requires non-empty input; clients should render the input hint as required */ @JsonProperty("required") Boolean required, /** Optional completion hint for the input (e.g. 'directory' for filesystem path completion) */ diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SlashCommandInputChoice.java b/java/src/generated/java/com/github/copilot/generated/rpc/SlashCommandInputChoice.java new file mode 100644 index 0000000000..2afc510159 --- /dev/null +++ b/java/src/generated/java/com/github/copilot/generated/rpc/SlashCommandInputChoice.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 literal choice the command input accepts, with a human-facing description + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record SlashCommandInputChoice( + /** The literal choice value (e.g. 'on', 'off', 'show') */ + @JsonProperty("name") String name, + /** Human-readable description shown alongside the choice */ + @JsonProperty("description") String description +) { +} diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SlashCommandSelectSubcommandOption.java b/java/src/generated/java/com/github/copilot/generated/rpc/SlashCommandSelectSubcommandOption.java index 317ec970f8..00d8b423e5 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/SlashCommandSelectSubcommandOption.java +++ b/java/src/generated/java/com/github/copilot/generated/rpc/SlashCommandSelectSubcommandOption.java @@ -13,7 +13,7 @@ import javax.annotation.processing.Generated; /** - * Schema for the `SlashCommandSelectSubcommandOption` type. + * Selectable slash-command subcommand option with name, description, and optional group label. * * @since 1.0.0 */ diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SlashCommandSelectSubcommandResult.java b/java/src/generated/java/com/github/copilot/generated/rpc/SlashCommandSelectSubcommandResult.java index 512c46355b..5c151954b2 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/SlashCommandSelectSubcommandResult.java +++ b/java/src/generated/java/com/github/copilot/generated/rpc/SlashCommandSelectSubcommandResult.java @@ -14,7 +14,7 @@ import javax.annotation.processing.Generated; /** - * Schema for the `SlashCommandSelectSubcommandResult` type. + * Slash-command invocation result asking the client to present subcommand options for a parent command. * * @since 1.0.0 */ diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SlashCommandTextResult.java b/java/src/generated/java/com/github/copilot/generated/rpc/SlashCommandTextResult.java index 4aaab6969b..5f232e8b9c 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/SlashCommandTextResult.java +++ b/java/src/generated/java/com/github/copilot/generated/rpc/SlashCommandTextResult.java @@ -13,7 +13,7 @@ import javax.annotation.processing.Generated; /** - * Schema for the `SlashCommandTextResult` type. + * Slash-command invocation result containing text output plus Markdown/ANSI rendering flags. * * @since 1.0.0 */ diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/Tool.java b/java/src/generated/java/com/github/copilot/generated/rpc/Tool.java index e4b1361c87..51fe65e6b0 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/Tool.java +++ b/java/src/generated/java/com/github/copilot/generated/rpc/Tool.java @@ -14,7 +14,7 @@ import javax.annotation.processing.Generated; /** - * Schema for the `Tool` type. + * Built-in tool metadata with identifier, optional namespaced name, description, input-parameter schema, and usage instructions. * * @since 1.0.0 */ diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/UIExitPlanModeResponse.java b/java/src/generated/java/com/github/copilot/generated/rpc/UIExitPlanModeResponse.java index 50e2011427..d74d1b5042 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/UIExitPlanModeResponse.java +++ b/java/src/generated/java/com/github/copilot/generated/rpc/UIExitPlanModeResponse.java @@ -13,7 +13,7 @@ import javax.annotation.processing.Generated; /** - * Schema for the `UIExitPlanModeResponse` type. + * User response for a pending exit-plan-mode request, with approval state, selected action, auto-approve flag, and feedback. * * @since 1.0.0 */ diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/UIUserInputResponse.java b/java/src/generated/java/com/github/copilot/generated/rpc/UIUserInputResponse.java index d4ce9899dd..9abc82505d 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/UIUserInputResponse.java +++ b/java/src/generated/java/com/github/copilot/generated/rpc/UIUserInputResponse.java @@ -13,7 +13,7 @@ import javax.annotation.processing.Generated; /** - * Schema for the `UIUserInputResponse` type. + * User response for a pending user-input request, with answer text and whether it was typed freeform. * * @since 1.0.0 */ diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/UsageMetricsModelMetric.java b/java/src/generated/java/com/github/copilot/generated/rpc/UsageMetricsModelMetric.java index 7e34f43b38..1b66120cf4 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/UsageMetricsModelMetric.java +++ b/java/src/generated/java/com/github/copilot/generated/rpc/UsageMetricsModelMetric.java @@ -14,7 +14,7 @@ import javax.annotation.processing.Generated; /** - * Schema for the `UsageMetricsModelMetric` type. + * Per-model usage metrics, including request counts/costs, token usage, nano-AI units, and per-token-type details. * * @since 1.0.0 */ diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/UsageMetricsModelMetricTokenDetail.java b/java/src/generated/java/com/github/copilot/generated/rpc/UsageMetricsModelMetricTokenDetail.java index e47a45fde9..90af60c84c 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/UsageMetricsModelMetricTokenDetail.java +++ b/java/src/generated/java/com/github/copilot/generated/rpc/UsageMetricsModelMetricTokenDetail.java @@ -13,7 +13,7 @@ import javax.annotation.processing.Generated; /** - * Schema for the `UsageMetricsModelMetricTokenDetail` type. + * Per-model token-detail entry containing the accumulated token count for one token type. * * @since 1.0.0 */ diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/UsageMetricsTokenDetail.java b/java/src/generated/java/com/github/copilot/generated/rpc/UsageMetricsTokenDetail.java index 55d0b81c8d..32149bfa79 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/UsageMetricsTokenDetail.java +++ b/java/src/generated/java/com/github/copilot/generated/rpc/UsageMetricsTokenDetail.java @@ -13,7 +13,7 @@ import javax.annotation.processing.Generated; /** - * Schema for the `UsageMetricsTokenDetail` type. + * Session-wide token-detail entry containing the accumulated token count for one token type. * * @since 1.0.0 */ diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/Verbosity.java b/java/src/generated/java/com/github/copilot/generated/rpc/Verbosity.java new file mode 100644 index 0000000000..188ce23b41 --- /dev/null +++ b/java/src/generated/java/com/github/copilot/generated/rpc/Verbosity.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; + +/** + * Output verbosity level for supported models + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public enum Verbosity { + /** The {@code low} variant. */ + LOW("low"), + /** The {@code medium} variant. */ + MEDIUM("medium"), + /** The {@code high} variant. */ + HIGH("high"); + + private final String value; + Verbosity(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static Verbosity fromValue(String value) { + for (Verbosity v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown Verbosity value: " + value); + } +} diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/WorkspacesCheckpoints.java b/java/src/generated/java/com/github/copilot/generated/rpc/WorkspacesCheckpoints.java index 07de034a96..c3696236c3 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/WorkspacesCheckpoints.java +++ b/java/src/generated/java/com/github/copilot/generated/rpc/WorkspacesCheckpoints.java @@ -13,7 +13,7 @@ import javax.annotation.processing.Generated; /** - * Schema for the `WorkspacesCheckpoints` type. + * Workspace checkpoint metadata with assigned number, human-readable title, and checkpoint filename. * * @since 1.0.0 */ diff --git a/java/src/main/java/com/github/copilot/CopilotClient.java b/java/src/main/java/com/github/copilot/CopilotClient.java index 52c0cfa486..01294fdaac 100644 --- a/java/src/main/java/com/github/copilot/CopilotClient.java +++ b/java/src/main/java/com/github/copilot/CopilotClient.java @@ -17,6 +17,7 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.TimeUnit; +import java.util.function.Function; import java.util.logging.Level; import java.util.logging.Logger; @@ -25,7 +26,8 @@ import com.github.copilot.rpc.CreateSessionResponse; import com.github.copilot.generated.rpc.SessionOptionsUpdateParams; import com.github.copilot.generated.rpc.SessionInstalledPlugin; -import com.github.copilot.generated.rpc.ConnectParams; +import com.github.copilot.generated.rpc.ConnectResult; +import com.github.copilot.generated.rpc.GitHubTelemetryNotification; import com.github.copilot.generated.rpc.ServerRpc; import com.github.copilot.generated.rpc.SessionEventLogRegisterInterestParams; import com.github.copilot.rpc.DeleteSessionResponse; @@ -258,6 +260,14 @@ private Connection startCoreBody() { llmAdapter.registerHandlers(rpc); } + // Register the GitHub telemetry forwarding handler when configured. + Function> onGitHubTelemetry = this.options + .getOnGitHubTelemetry(); + if (onGitHubTelemetry != null) { + GitHubTelemetryAdapter telemetryAdapter = new GitHubTelemetryAdapter(onGitHubTelemetry); + telemetryAdapter.registerHandlers(rpc); + } + // Verify protocol version verifyProtocolVersion(connection); LoggingHelpers.logTiming(LOG, Level.FINE, @@ -296,11 +306,20 @@ private void verifyProtocolVersion(Connection connection) throws Exception { Integer serverVersion; try { - // Try the new 'connect' RPC which supports connection tokens - var connectParams = new ConnectParams(effectiveConnectionToken); - var connectResponse = connection.rpc - .invoke("connect", connectParams, com.github.copilot.generated.rpc.ConnectResult.class) - .get(30, TimeUnit.SECONDS); + // Try the new 'connect' RPC which supports connection tokens. + var connectParams = new HashMap(); + if (effectiveConnectionToken != null) { + connectParams.put("token", effectiveConnectionToken); + } + // Opt into GitHub telemetry forwarding at the connection level when a handler + // is registered, so the runtime can forward the first session's un-replayable + // start event. Also sent on session create/resume for backward compatibility + // with servers that read the flag there instead. + if (this.options.getOnGitHubTelemetry() != null) { + connectParams.put("enableGitHubTelemetryForwarding", true); + } + var connectResponse = connection.rpc.invoke("connect", connectParams, ConnectResult.class).get(30, + TimeUnit.SECONDS); serverVersion = connectResponse.protocolVersion() != null ? connectResponse.protocolVersion().intValue() : null; @@ -579,6 +598,13 @@ public CompletableFuture createSession(SessionConfig config) { request.setSystemMessage(extracted.wireSystemMessage()); } + // Opt this session into GitHub telemetry forwarding when a + // connection-level handler is registered (mirrors the runtime's + // hand-written capability flag, not part of the codegen'd contract). + if (options.getOnGitHubTelemetry() != null) { + request.setEnableGitHubTelemetryForwarding(true); + } + // Empty mode: validate availableTools and set toolFilterPrecedence if (options.getMode() == CopilotClientMode.EMPTY) { if (config.getAvailableTools() == null) { @@ -733,6 +759,13 @@ public CompletableFuture resumeSession(String sessionId, ResumeS request.setSystemMessage(extracted.wireSystemMessage()); } + // Opt this session into GitHub telemetry forwarding when a + // connection-level handler is registered (mirrors the runtime's + // hand-written capability flag, not part of the codegen'd contract). + if (options.getOnGitHubTelemetry() != null) { + request.setEnableGitHubTelemetryForwarding(true); + } + // Empty mode: validate availableTools and set toolFilterPrecedence for resume // path if (options.getMode() == CopilotClientMode.EMPTY) { @@ -893,6 +926,7 @@ CompletableFuture updateSessionOptionsForMode(CopilotSession session, Bool null, // modelCapabilitiesOverrides null, // reasoningEffort null, // reasoningSummary + null, // verbosity null, // clientName null, // lspClientName null, // integrationId diff --git a/java/src/main/java/com/github/copilot/CopilotSession.java b/java/src/main/java/com/github/copilot/CopilotSession.java index 5a597dcd9a..3fe0de9889 100644 --- a/java/src/main/java/com/github/copilot/CopilotSession.java +++ b/java/src/main/java/com/github/copilot/CopilotSession.java @@ -1927,7 +1927,7 @@ public CompletableFuture abort() { public CompletableFuture setModel(String model, String reasoningEffort) { ensureNotTerminated(); return getRpc().model - .switchTo(new SessionModelSwitchToParams(sessionId, model, reasoningEffort, null, null, null)) + .switchTo(new SessionModelSwitchToParams(sessionId, model, reasoningEffort, null, null, null, null)) .thenApply(r -> null); } @@ -2008,7 +2008,7 @@ public CompletableFuture setModel(String model, String reasoningEffort, St ? null : com.github.copilot.generated.rpc.ReasoningSummary.fromValue(reasoningSummary); return getRpc().model.switchTo(new SessionModelSwitchToParams(sessionId, model, reasoningEffort, - generatedReasoningSummary, generatedCapabilities, null)).thenApply(r -> null); + generatedReasoningSummary, null, generatedCapabilities, null)).thenApply(r -> null); } /** diff --git a/java/src/main/java/com/github/copilot/GitHubTelemetryAdapter.java b/java/src/main/java/com/github/copilot/GitHubTelemetryAdapter.java new file mode 100644 index 0000000000..1fdb2a4737 --- /dev/null +++ b/java/src/main/java/com/github/copilot/GitHubTelemetryAdapter.java @@ -0,0 +1,54 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +package com.github.copilot; + +import java.util.concurrent.CompletableFuture; +import java.util.function.Function; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.github.copilot.generated.rpc.GitHubTelemetryNotification; + +/** + * Bridges the runtime's {@code gitHubTelemetry.event} client-global + * notification to a consumer's async {@code onGitHubTelemetry} callback. The + * notification carries per-session GitHub (hydro) telemetry the runtime + * forwards to connections that opted into telemetry forwarding. + */ +final class GitHubTelemetryAdapter { + + private static final Logger LOG = Logger.getLogger(GitHubTelemetryAdapter.class.getName()); + private static final ObjectMapper MAPPER = JsonRpcClient.getObjectMapper(); + + private final Function> callback; + + GitHubTelemetryAdapter(Function> callback) { + this.callback = callback; + } + + void registerHandlers(JsonRpcClient rpc) { + rpc.registerMethodHandler("gitHubTelemetry.event", (rpcId, params) -> handleEvent(params)); + } + + private void handleEvent(JsonNode params) { + try { + GitHubTelemetryNotification notification = MAPPER.treeToValue(params, GitHubTelemetryNotification.class); + if (notification != null) { + CompletableFuture result = callback.apply(notification); + if (result != null) { + result.whenComplete((unused, error) -> { + if (error != null) { + LOG.log(Level.WARNING, "Error handling gitHubTelemetry.event notification", error); + } + }); + } + } + } catch (Exception e) { + LOG.log(Level.WARNING, "Error handling gitHubTelemetry.event notification", e); + } + } +} diff --git a/java/src/main/java/com/github/copilot/JsonRpcClient.java b/java/src/main/java/com/github/copilot/JsonRpcClient.java index a7cd0e120d..5303c4f500 100644 --- a/java/src/main/java/com/github/copilot/JsonRpcClient.java +++ b/java/src/main/java/com/github/copilot/JsonRpcClient.java @@ -70,7 +70,8 @@ static ObjectMapper createObjectMapper() { mapper.registerModule(new JavaTimeModule()); mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false); - mapper.setDefaultPropertyInclusion(JsonInclude.Include.NON_NULL); + mapper.setDefaultPropertyInclusion( + JsonInclude.Value.construct(JsonInclude.Include.NON_NULL, JsonInclude.Include.ALWAYS)); return mapper; } diff --git a/java/src/main/java/com/github/copilot/rpc/CopilotClientOptions.java b/java/src/main/java/com/github/copilot/rpc/CopilotClientOptions.java index e9f59aa646..0d4494d738 100644 --- a/java/src/main/java/com/github/copilot/rpc/CopilotClientOptions.java +++ b/java/src/main/java/com/github/copilot/rpc/CopilotClientOptions.java @@ -11,11 +11,14 @@ import java.util.Objects; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executor; +import java.util.function.Function; import java.util.function.Supplier; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonIgnore; +import com.github.copilot.CopilotExperimental; import com.github.copilot.CopilotRequestHandler; +import com.github.copilot.generated.rpc.GitHubTelemetryNotification; import java.util.Optional; import java.util.OptionalInt; @@ -57,6 +60,7 @@ public class CopilotClientOptions { private CopilotClientMode mode = CopilotClientMode.COPILOT_CLI; private Supplier>> onListModels; private CopilotRequestHandler requestHandler; + private Function> onGitHubTelemetry; private int port; private TelemetryConfig telemetry; private Integer sessionIdleTimeoutSeconds; @@ -484,6 +488,44 @@ public CopilotClientOptions setRequestHandler(CopilotRequestHandler requestHandl return this; } + /** + * Gets the connection-level GitHub telemetry forwarding handler. + * + *

+ * Experimental: this option may change or be removed without notice. + * + * @return the async telemetry handler, or {@code null} if not set + */ + @JsonIgnore + @CopilotExperimental + public Function> getOnGitHubTelemetry() { + return onGitHubTelemetry; + } + + /** + * Sets a connection-level handler for GitHub telemetry forwarding + * (experimental). + * + *

+ * When provided, the client opts every session it creates or resumes into + * telemetry forwarding, and the runtime forwards each per-session telemetry + * event to this handler via the {@code gitHubTelemetry.event} notification. The + * handler returns a {@link CompletableFuture} that completes when asynchronous + * processing is finished. + * + * @param onGitHubTelemetry + * the async telemetry handler (must not be {@code null}) + * @return this options instance for method chaining + * @throws IllegalArgumentException + * if {@code onGitHubTelemetry} is {@code null} + */ + @CopilotExperimental + public CopilotClientOptions setOnGitHubTelemetry( + Function> onGitHubTelemetry) { + this.onGitHubTelemetry = Objects.requireNonNull(onGitHubTelemetry, "onGitHubTelemetry must not be null"); + return this; + } + /** * Gets the TCP port for the CLI server. * @@ -720,6 +762,7 @@ public CopilotClientOptions clone() { copy.logLevel = this.logLevel; copy.onListModels = this.onListModels; copy.requestHandler = this.requestHandler; + copy.onGitHubTelemetry = this.onGitHubTelemetry; copy.port = this.port; copy.remote = this.remote; copy.sessionIdleTimeoutSeconds = this.sessionIdleTimeoutSeconds; diff --git a/java/src/main/java/com/github/copilot/rpc/CreateSessionRequest.java b/java/src/main/java/com/github/copilot/rpc/CreateSessionRequest.java index 3ec3dc5698..2510b0bd6e 100644 --- a/java/src/main/java/com/github/copilot/rpc/CreateSessionRequest.java +++ b/java/src/main/java/com/github/copilot/rpc/CreateSessionRequest.java @@ -102,6 +102,9 @@ public final class CreateSessionRequest { @JsonProperty("includeSubAgentStreamingEvents") private Boolean includeSubAgentStreamingEvents; + @JsonProperty("enableGitHubTelemetryForwarding") + private Boolean enableGitHubTelemetryForwarding; + @JsonProperty("mcpServers") private Map mcpServers; @@ -820,6 +823,27 @@ public void clearIncludeSubAgentStreamingEvents() { this.includeSubAgentStreamingEvents = null; } + /** Gets the GitHub telemetry forwarding flag. @return the flag */ + public Boolean getEnableGitHubTelemetryForwarding() { + return enableGitHubTelemetryForwarding; + } + + /** + * Sets the GitHub telemetry forwarding flag. @param + * enableGitHubTelemetryForwarding the flag + */ + public void setEnableGitHubTelemetryForwarding(boolean enableGitHubTelemetryForwarding) { + this.enableGitHubTelemetryForwarding = enableGitHubTelemetryForwarding; + } + + /** + * Clears the enableGitHubTelemetryForwarding setting, reverting to the default + * behavior. + */ + public void clearEnableGitHubTelemetryForwarding() { + this.enableGitHubTelemetryForwarding = null; + } + /** Gets the commands wire definitions. @return the commands */ public List getCommands() { return commands == null ? null : Collections.unmodifiableList(commands); diff --git a/java/src/main/java/com/github/copilot/rpc/ParamCoercion.java b/java/src/main/java/com/github/copilot/rpc/ParamCoercion.java new file mode 100644 index 0000000000..fc82742546 --- /dev/null +++ b/java/src/main/java/com/github/copilot/rpc/ParamCoercion.java @@ -0,0 +1,197 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +package com.github.copilot.rpc; + +import java.util.Map; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.github.copilot.tool.Param; + +/** + * Internal runtime helper: coerces raw invocation arguments to the typed values + * declared by {@link Param} descriptors. + * + *

+ * Reuses the SDK-configured {@link ObjectMapper} for complex type conversions, + * matching the coercion policy applied by existing ergonomic tooling. No + * bespoke conversion paths are introduced. + * + *

+ * Package-private: not part of the public API. + */ +class ParamCoercion { + + /** Utility class; do not instantiate. */ + private ParamCoercion() { + } + + /** + * Coerces the named argument from an invocation argument map to the Java type + * declared by {@code param}. + * + *

+ * Resolution order: + *

    + *
  1. If the argument is present, convert it to {@code T} via + * {@link ObjectMapper#convertValue}.
  2. + *
  3. If absent and a default value is set, parse the string default via + * {@link #coerceDefault}.
  4. + *
  5. If absent and the parameter is optional ({@code required=false}), return + * an empty Optional variant or {@code null}.
  6. + *
  7. If absent and required, throw {@link IllegalArgumentException} with the + * parameter name.
  8. + *
+ * + * @param + * the target Java type + * @param args + * the invocation argument map; may be {@code null} for zero-argument + * tools + * @param param + * the parameter descriptor + * @param mapper + * the configured {@link ObjectMapper} for complex type conversion + * @return the coerced argument value + * @throws IllegalArgumentException + * if a required parameter is missing or coercion fails + */ + @SuppressWarnings("unchecked") + static T coerce(Map args, Param param, ObjectMapper mapper) { + Object raw = (args != null) ? args.get(param.name()) : null; + + if (raw == null) { + if (param.hasDefaultValue()) { + return coerceDefault(param, mapper); + } else if (!param.required()) { + return (T) emptyOptionalOrNull(param.type()); + } else { + throw new IllegalArgumentException( + "Required parameter '" + param.name() + "' is missing from tool invocation"); + } + } + + Class type = param.type(); + + // Handle Optional* types explicitly before delegating to ObjectMapper + if (type == java.util.OptionalInt.class) { + try { + return (T) java.util.OptionalInt.of(((Number) raw).intValue()); + } catch (ClassCastException ex) { + throw new IllegalArgumentException("Parameter '" + param.name() + + "' expected a numeric value for OptionalInt, got: " + raw.getClass().getSimpleName(), ex); + } + } + if (type == java.util.OptionalLong.class) { + try { + return (T) java.util.OptionalLong.of(((Number) raw).longValue()); + } catch (ClassCastException ex) { + throw new IllegalArgumentException("Parameter '" + param.name() + + "' expected a numeric value for OptionalLong, got: " + raw.getClass().getSimpleName(), ex); + } + } + if (type == java.util.OptionalDouble.class) { + try { + return (T) java.util.OptionalDouble.of(((Number) raw).doubleValue()); + } catch (ClassCastException ex) { + throw new IllegalArgumentException("Parameter '" + param.name() + + "' expected a numeric value for OptionalDouble, got: " + raw.getClass().getSimpleName(), ex); + } + } + + try { + return mapper.convertValue(raw, type); + } catch (IllegalArgumentException ex) { + throw new IllegalArgumentException( + "Failed to coerce parameter '" + param.name() + "' to type " + type.getSimpleName(), ex); + } + } + + /** + * Parses a {@link Param}'s string default value into the declared Java type. + * + *

+ * Handles primitives, boxed types, {@link String}, {@link Boolean}, and enums + * explicitly, mirroring the validation logic in {@link Param}. The + * {@link ObjectMapper#readValue} fallback exists as a safety net but is not + * expected to be reached in practice, since {@link Param} construction rejects + * defaults for non-primitive/boxed/String/Boolean/enum types. + * + * @param + * the target Java type + * @param param + * the parameter descriptor carrying the default value + * @param mapper + * the configured {@link ObjectMapper} used as fallback for complex + * types + * @return the parsed default value + * @throws IllegalArgumentException + * if parsing fails + */ + @SuppressWarnings({"rawtypes", "unchecked"}) + static T coerceDefault(Param param, ObjectMapper mapper) { + String defaultValue = param.defaultValue(); + Class type = param.type(); + try { + if (type == String.class) { + return type.cast(defaultValue); + } + if (type == Integer.class || type == int.class) { + return (T) Integer.valueOf(defaultValue); + } + if (type == Long.class || type == long.class) { + return (T) Long.valueOf(defaultValue); + } + if (type == Double.class || type == double.class) { + return (T) Double.valueOf(defaultValue); + } + if (type == Float.class || type == float.class) { + return (T) Float.valueOf(defaultValue); + } + if (type == Short.class || type == short.class) { + return (T) Short.valueOf(defaultValue); + } + if (type == Byte.class || type == byte.class) { + return (T) Byte.valueOf(defaultValue); + } + if (type == Boolean.class || type == boolean.class) { + return (T) Boolean.valueOf(defaultValue); + } + if (type.isEnum()) { + Class enumType = (Class) type; + return type.cast(Enum.valueOf(enumType, defaultValue)); + } + // Fallback: let ObjectMapper parse the JSON-encoded default string + return mapper.readValue(defaultValue, type); + } catch (IllegalArgumentException ex) { + throw ex; + } catch (Exception ex) { + throw new IllegalArgumentException("Failed to apply default value '" + defaultValue + "' for parameter '" + + param.name() + "' of type " + type.getSimpleName(), ex); + } + } + + /** + * Returns an empty Optional variant for Optional primitive types, or + * {@code null} for all other types. + * + * @param type + * the declared parameter type + * @return {@link java.util.OptionalInt#empty()}, + * {@link java.util.OptionalLong#empty()}, + * {@link java.util.OptionalDouble#empty()}, or {@code null} + */ + static Object emptyOptionalOrNull(Class type) { + if (type == java.util.OptionalInt.class) { + return java.util.OptionalInt.empty(); + } + if (type == java.util.OptionalLong.class) { + return java.util.OptionalLong.empty(); + } + if (type == java.util.OptionalDouble.class) { + return java.util.OptionalDouble.empty(); + } + return null; + } +} diff --git a/java/src/main/java/com/github/copilot/rpc/ParamSchema.java b/java/src/main/java/com/github/copilot/rpc/ParamSchema.java new file mode 100644 index 0000000000..ee025eb2cc --- /dev/null +++ b/java/src/main/java/com/github/copilot/rpc/ParamSchema.java @@ -0,0 +1,190 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +package com.github.copilot.rpc; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.github.copilot.tool.Param; + +/** + * Internal runtime helper: maps {@link Param} metadata to JSON Schema + * {@code Map} objects. + * + *

+ * This class is a simplified runtime counterpart to the compile-time + * {@code SchemaGenerator}. It operates on {@code java.lang.reflect.Class} + * values instead of {@code javax.lang.model} mirrors, and produces {@link Map} + * instances rather than Java source-code literals. Unlike + * {@code SchemaGenerator}, it does not inspect generics or object members + * (records/POJOs) and therefore produces flat type mappings only (no + * {@code additionalProperties} or nested object {@code properties}). It does + * produce {@code items} for plain Java arrays via component-type recursion. + * + *

+ * Package-private: not part of the public API. + */ +class ParamSchema { + + /** Utility class; do not instantiate. */ + private ParamSchema() { + } + + /** + * Builds a JSON Schema {@code Map} from zero or more {@link Param} descriptors. + * + *

+ * Validation applied: + *

    + *
  • Each {@link Param} must be non-null.
  • + *
  • Parameter names must be unique; duplicates throw + * {@link IllegalArgumentException} with the tool name and duplicate name.
  • + *
+ * + * @param toolName + * the tool name, included in exception messages for clarity + * @param mapper + * the configured {@link ObjectMapper} used to coerce default values + * into their typed form for the schema + * @param params + * zero or more parameter descriptors + * @return a JSON Schema object map with {@code type=object}, + * {@code properties}, and {@code required} keys + * @throws IllegalArgumentException + * if a null param or duplicate parameter names are found + */ + static Map buildSchema(String toolName, ObjectMapper mapper, Param... params) { + if (params == null || params.length == 0) { + return Map.of("type", "object", "properties", Map.of(), "required", List.of()); + } + + // Validate: no null params, no duplicate names + Set seen = new HashSet<>(); + for (Param param : params) { + if (param == null) { + throw new IllegalArgumentException("A Param descriptor is null for tool '" + toolName + "'"); + } + if (!seen.add(param.name())) { + throw new IllegalArgumentException( + "Duplicate parameter name '" + param.name() + "' in tool '" + toolName + "'"); + } + } + + List requiredNames = new ArrayList<>(); + Map properties = new LinkedHashMap<>(); + + for (Param param : params) { + Map typeSchema = forType(param.type()); + Map enriched = new LinkedHashMap<>(typeSchema); + enriched.put("description", param.description()); + if (param.hasDefaultValue()) { + enriched.put("default", ParamCoercion.coerceDefault(param, mapper)); + } + properties.put(param.name(), Collections.unmodifiableMap(enriched)); + if (param.required()) { + requiredNames.add(param.name()); + } + } + + return Map.of("type", "object", "properties", Collections.unmodifiableMap(properties), "required", + Collections.unmodifiableList(requiredNames)); + } + + /** + * Maps a Java {@link Class} to a flat JSON Schema type descriptor. + * + *

+ * Covers primitives, boxed types, strings, UUIDs, date-time types, enums, + * collections, arrays, and maps. Does not resolve generic type parameters (e.g. + * {@code List} item schemas or {@code Map} additionalProperties) — + * those require the compile-time {@code SchemaGenerator} which operates on + * {@code TypeMirror}. + * + * @param type + * the Java type to map + * @return a JSON Schema type map (e.g. {@code Map.of("type", "string")}) + */ + @SuppressWarnings({"rawtypes", "unchecked"}) + static Map forType(Class type) { + // Integer types + if (type == int.class || type == Integer.class || type == long.class || type == Long.class || type == byte.class + || type == Byte.class || type == short.class || type == Short.class) { + return Map.of("type", "integer"); + } + // Floating-point types + if (type == double.class || type == Double.class || type == float.class || type == Float.class) { + return Map.of("type", "number"); + } + // Boolean + if (type == boolean.class || type == Boolean.class) { + return Map.of("type", "boolean"); + } + // Char → string + if (type == char.class || type == Character.class) { + return Map.of("type", "string"); + } + // String + if (type == String.class) { + return Map.of("type", "string"); + } + // UUID + if (type == java.util.UUID.class) { + return Map.of("type", "string", "format", "uuid"); + } + // Optional primitive types + if (type == java.util.OptionalInt.class || type == java.util.OptionalLong.class) { + return Map.of("type", "integer"); + } + if (type == java.util.OptionalDouble.class) { + return Map.of("type", "number"); + } + // Date-time types + if (type == java.time.OffsetDateTime.class || type == java.time.LocalDateTime.class + || type == java.time.Instant.class || type == java.time.ZonedDateTime.class) { + return Map.of("type", "string", "format", "date-time"); + } + if (type == java.time.LocalDate.class) { + return Map.of("type", "string", "format", "date"); + } + if (type == java.time.LocalTime.class) { + return Map.of("type", "string", "format", "time"); + } + // JsonNode / Object → any (no type constraint) + if (type == com.fasterxml.jackson.databind.JsonNode.class || type == Object.class) { + return Map.of(); + } + // Enum types + if (type.isEnum()) { + Class enumType = (Class) type; + List constants = Arrays.stream(enumType.getEnumConstants()).map(Enum::name) + .collect(Collectors.toList()); + return Map.of("type", "string", "enum", Collections.unmodifiableList(constants)); + } + // List / Collection / Set → array (raw element type) + if (java.util.List.class.isAssignableFrom(type) || java.util.Collection.class.isAssignableFrom(type) + || java.util.Set.class.isAssignableFrom(type)) { + return Map.of("type", "array"); + } + // Plain array → array with items schema derived from component type + if (type.isArray()) { + Map itemsSchema = forType(type.getComponentType()); + return Map.of("type", "array", "items", itemsSchema); + } + // Map → object + if (java.util.Map.class.isAssignableFrom(type)) { + return Map.of("type", "object"); + } + // POJO / record → object + return Map.of("type", "object"); + } +} diff --git a/java/src/main/java/com/github/copilot/rpc/ResumeSessionRequest.java b/java/src/main/java/com/github/copilot/rpc/ResumeSessionRequest.java index 89776f86cf..4917f1d8cc 100644 --- a/java/src/main/java/com/github/copilot/rpc/ResumeSessionRequest.java +++ b/java/src/main/java/com/github/copilot/rpc/ResumeSessionRequest.java @@ -145,6 +145,9 @@ public final class ResumeSessionRequest { @JsonProperty("includeSubAgentStreamingEvents") private Boolean includeSubAgentStreamingEvents; + @JsonProperty("enableGitHubTelemetryForwarding") + private Boolean enableGitHubTelemetryForwarding; + @JsonProperty("mcpServers") private Map mcpServers; @@ -705,6 +708,27 @@ public void clearIncludeSubAgentStreamingEvents() { this.includeSubAgentStreamingEvents = null; } + /** Gets the GitHub telemetry forwarding flag. @return the flag */ + public Boolean getEnableGitHubTelemetryForwarding() { + return enableGitHubTelemetryForwarding; + } + + /** + * Sets the GitHub telemetry forwarding flag. @param + * enableGitHubTelemetryForwarding the flag + */ + public void setEnableGitHubTelemetryForwarding(boolean enableGitHubTelemetryForwarding) { + this.enableGitHubTelemetryForwarding = enableGitHubTelemetryForwarding; + } + + /** + * Clears the enableGitHubTelemetryForwarding setting, reverting to the default + * behavior. + */ + public void clearEnableGitHubTelemetryForwarding() { + this.enableGitHubTelemetryForwarding = null; + } + /** Gets MCP servers. @return the servers map */ public Map getMcpServers() { return mcpServers == null ? null : Collections.unmodifiableMap(mcpServers); diff --git a/java/src/main/java/com/github/copilot/rpc/ToolDefinition.java b/java/src/main/java/com/github/copilot/rpc/ToolDefinition.java index b3fa2bc53a..8a336c749a 100644 --- a/java/src/main/java/com/github/copilot/rpc/ToolDefinition.java +++ b/java/src/main/java/com/github/copilot/rpc/ToolDefinition.java @@ -9,6 +9,10 @@ import java.util.Arrays; import java.util.List; import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.function.BiFunction; +import java.util.function.Function; +import java.util.function.Supplier; import java.util.stream.Collectors; import com.fasterxml.jackson.annotation.JsonIgnore; @@ -19,6 +23,7 @@ import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import com.github.copilot.CopilotExperimental; +import com.github.copilot.tool.Param; /** * Defines a tool that can be invoked by the AI assistant. @@ -186,7 +191,7 @@ public static ToolDefinition createWithDefer(String name, String description, Ma * @throws IllegalStateException * if the generated {@code $$CopilotToolMeta} class is not found * (annotation processor did not run) - * @since 1.0.2 + * @since 1.0.6 */ @CopilotExperimental public static List fromObject(Object instance) { @@ -209,7 +214,7 @@ public static List fromObject(Object instance) { * @throws IllegalStateException * if the generated {@code $$CopilotToolMeta} class is not found * (annotation processor did not run) - * @since 1.0.2 + * @since 1.0.6 */ @CopilotExperimental public static List fromClass(Class clazz) { @@ -227,6 +232,570 @@ public static List fromClass(Class clazz) { return loadDefinitions(clazz, null); } + // ------------------------------------------------------------------ + // Fluent copy-style modifier methods for lambda-defined tools + // ------------------------------------------------------------------ + + /** + * Returns a copy with the {@code overridesBuiltInTool} flag set. + * + * @param value + * {@code true} to indicate this tool intentionally overrides a + * built-in CLI tool with the same name + * @return a new {@code ToolDefinition} with the flag applied + * @since 1.0.6 + */ + @CopilotExperimental + public ToolDefinition overridesBuiltInTool(boolean value) { + return new ToolDefinition(name, description, parameters, handler, value, skipPermission, defer); + } + + /** + * Returns a copy with the {@code skipPermission} flag set. + * + * @param value + * {@code true} to skip the permission request for this tool + * invocation + * @return a new {@code ToolDefinition} with the flag applied + * @since 1.0.6 + */ + @CopilotExperimental + public ToolDefinition skipPermission(boolean value) { + return new ToolDefinition(name, description, parameters, handler, overridesBuiltInTool, value, defer); + } + + /** + * Returns a copy with the {@code defer} mode set. + * + * @param value + * the deferral mode; use {@link ToolDefer#AUTO} to allow deferral or + * {@link ToolDefer#NEVER} to force the tool to always be pre-loaded + * @return a new {@code ToolDefinition} with the defer mode applied + * @since 1.0.6 + */ + @CopilotExperimental + public ToolDefinition defer(ToolDefer value) { + return new ToolDefinition(name, description, parameters, handler, overridesBuiltInTool, skipPermission, value); + } + + // ------------------------------------------------------------------ + // from(...) — sync, no ToolInvocation + // ------------------------------------------------------------------ + + /** + * Creates a tool definition with a zero-argument synchronous handler. + * + *

+ * The handler is a {@link Supplier} that returns the tool result. + * + *

Example

+ * + *
{@code
+     * ToolDefinition ping = ToolDefinition.from("ping", "Returns a simple pong response", () -> "pong");
+     * }
+ * + * @param + * the return type of the handler + * @param name + * the unique name of the tool (must not be blank) + * @param description + * a description of what the tool does (must not be blank) + * @param handler + * the zero-argument sync handler + * @return a new tool definition + * @throws IllegalArgumentException + * if {@code name} or {@code description} is blank, or if + * {@code handler} is null + * @since 1.0.6 + */ + @CopilotExperimental + public static ToolDefinition from(String name, String description, Supplier handler) { + requireNonBlankToolName(name); + requireNonBlankDescription(description); + requireNonNullHandler(handler, name); + final ObjectMapper mapper = getConfiguredMapper(); + Map schema = ParamSchema.buildSchema(name, mapper); + ToolHandler toolHandler = invocation -> { + R result = handler.get(); + return CompletableFuture.completedFuture(formatResult(result, mapper)); + }; + return new ToolDefinition(name, description, schema, toolHandler, null, null, null); + } + + /** + * Creates a tool definition with a one-argument synchronous handler. + * + *

Example

+ * + *
{@code
+     * ToolDefinition greet = ToolDefinition.from("greet", "Greets a user by name",
+     * 		Param.of(String.class, "name", "The user's name"), name -> "Hello, " + name + "!");
+     * }
+ * + * @param + * the type of the first parameter + * @param + * the return type of the handler + * @param name + * the unique name of the tool (must not be blank) + * @param description + * a description of what the tool does (must not be blank) + * @param p1 + * the first parameter descriptor + * @param handler + * the one-argument sync handler + * @return a new tool definition + * @throws IllegalArgumentException + * if validation fails + * @since 1.0.6 + */ + @CopilotExperimental + public static ToolDefinition from(String name, String description, Param p1, Function handler) { + requireNonBlankToolName(name); + requireNonBlankDescription(description); + requireNonNullHandler(handler, name); + final ObjectMapper mapper = getConfiguredMapper(); + Map schema = ParamSchema.buildSchema(name, mapper, p1); + ToolHandler toolHandler = invocation -> { + T1 arg1 = ParamCoercion.coerce(invocation.getArguments(), p1, mapper); + R result = handler.apply(arg1); + return CompletableFuture.completedFuture(formatResult(result, mapper)); + }; + return new ToolDefinition(name, description, schema, toolHandler, null, null, null); + } + + /** + * Creates a tool definition with a two-argument synchronous handler. + * + *

Example

+ * + *
{@code
+     * ToolDefinition add = ToolDefinition.from("add", "Adds two integers", Param.of(Integer.class, "a", "First number"),
+     * 		Param.of(Integer.class, "b", "Second number"), (a, b) -> a + b);
+     * }
+ * + * @param + * the type of the first parameter + * @param + * the type of the second parameter + * @param + * the return type of the handler + * @param name + * the unique name of the tool (must not be blank) + * @param description + * a description of what the tool does (must not be blank) + * @param p1 + * the first parameter descriptor + * @param p2 + * the second parameter descriptor + * @param handler + * the two-argument sync handler + * @return a new tool definition + * @throws IllegalArgumentException + * if validation fails + * @since 1.0.6 + */ + @CopilotExperimental + public static ToolDefinition from(String name, String description, Param p1, Param p2, + BiFunction handler) { + requireNonBlankToolName(name); + requireNonBlankDescription(description); + requireNonNullHandler(handler, name); + final ObjectMapper mapper = getConfiguredMapper(); + Map schema = ParamSchema.buildSchema(name, mapper, p1, p2); + ToolHandler toolHandler = invocation -> { + T1 arg1 = ParamCoercion.coerce(invocation.getArguments(), p1, mapper); + T2 arg2 = ParamCoercion.coerce(invocation.getArguments(), p2, mapper); + R result = handler.apply(arg1, arg2); + return CompletableFuture.completedFuture(formatResult(result, mapper)); + }; + return new ToolDefinition(name, description, schema, toolHandler, null, null, null); + } + + // ------------------------------------------------------------------ + // fromAsync(...) — async, no ToolInvocation + // ------------------------------------------------------------------ + + /** + * Creates a tool definition with a zero-argument asynchronous handler. + * + *

+ * The handler is a {@link Supplier} returning a {@link CompletableFuture}. + * + *

Example

+ * + *
{@code
+     * ToolDefinition ping = ToolDefinition.fromAsync("ping", "Returns a pong response asynchronously",
+     * 		() -> CompletableFuture.completedFuture("pong"));
+     * }
+ * + * @param + * the return type wrapped in {@link CompletableFuture} + * @param name + * the unique name of the tool (must not be blank) + * @param description + * a description of what the tool does (must not be blank) + * @param handler + * the zero-argument async handler + * @return a new tool definition + * @throws IllegalArgumentException + * if validation fails + * @since 1.0.6 + */ + @CopilotExperimental + public static ToolDefinition fromAsync(String name, String description, + Supplier> handler) { + requireNonBlankToolName(name); + requireNonBlankDescription(description); + requireNonNullHandler(handler, name); + final ObjectMapper mapper = getConfiguredMapper(); + Map schema = ParamSchema.buildSchema(name, mapper); + ToolHandler toolHandler = invocation -> { + CompletableFuture future = handler.get(); + if (future == null) { + return CompletableFuture.failedFuture( + new NullPointerException("Async handler for tool '" + name + "' returned a null future")); + } + return future.thenApply(result -> formatResult(result, mapper)); + }; + return new ToolDefinition(name, description, schema, toolHandler, null, null, null); + } + + /** + * Creates a tool definition with a one-argument asynchronous handler. + * + *

Example

+ * + *
{@code
+     * ToolDefinition greet = ToolDefinition.fromAsync("greet_async", "Greets a user by name asynchronously",
+     * 		Param.of(String.class, "name", "The user's name"),
+     * 		name -> CompletableFuture.completedFuture("Hello, " + name + "!"));
+     * }
+ * + * @param + * the type of the first parameter + * @param + * the return type wrapped in {@link CompletableFuture} + * @param name + * the unique name of the tool (must not be blank) + * @param description + * a description of what the tool does (must not be blank) + * @param p1 + * the first parameter descriptor + * @param handler + * the one-argument async handler + * @return a new tool definition + * @throws IllegalArgumentException + * if validation fails + * @since 1.0.6 + */ + @CopilotExperimental + public static ToolDefinition fromAsync(String name, String description, Param p1, + Function> handler) { + requireNonBlankToolName(name); + requireNonBlankDescription(description); + requireNonNullHandler(handler, name); + final ObjectMapper mapper = getConfiguredMapper(); + Map schema = ParamSchema.buildSchema(name, mapper, p1); + ToolHandler toolHandler = invocation -> { + T1 arg1 = ParamCoercion.coerce(invocation.getArguments(), p1, mapper); + CompletableFuture future = handler.apply(arg1); + if (future == null) { + return CompletableFuture.failedFuture( + new NullPointerException("Async handler for tool '" + name + "' returned a null future")); + } + return future.thenApply(result -> formatResult(result, mapper)); + }; + return new ToolDefinition(name, description, schema, toolHandler, null, null, null); + } + + /** + * Creates a tool definition with a two-argument asynchronous handler. + * + * @param + * the type of the first parameter + * @param + * the type of the second parameter + * @param + * the return type wrapped in {@link CompletableFuture} + * @param name + * the unique name of the tool (must not be blank) + * @param description + * a description of what the tool does (must not be blank) + * @param p1 + * the first parameter descriptor + * @param p2 + * the second parameter descriptor + * @param handler + * the two-argument async handler + * @return a new tool definition + * @throws IllegalArgumentException + * if validation fails + * @since 1.0.6 + */ + @CopilotExperimental + public static ToolDefinition fromAsync(String name, String description, Param p1, Param p2, + BiFunction> handler) { + requireNonBlankToolName(name); + requireNonBlankDescription(description); + requireNonNullHandler(handler, name); + final ObjectMapper mapper = getConfiguredMapper(); + Map schema = ParamSchema.buildSchema(name, mapper, p1, p2); + ToolHandler toolHandler = invocation -> { + T1 arg1 = ParamCoercion.coerce(invocation.getArguments(), p1, mapper); + T2 arg2 = ParamCoercion.coerce(invocation.getArguments(), p2, mapper); + CompletableFuture future = handler.apply(arg1, arg2); + if (future == null) { + return CompletableFuture.failedFuture( + new NullPointerException("Async handler for tool '" + name + "' returned a null future")); + } + return future.thenApply(result -> formatResult(result, mapper)); + }; + return new ToolDefinition(name, description, schema, toolHandler, null, null, null); + } + + // ------------------------------------------------------------------ + // fromWithToolInvocation(...) — sync, with ToolInvocation context + // ------------------------------------------------------------------ + + /** + * Creates a tool definition with a zero-argument synchronous handler that + * receives the {@link ToolInvocation} context. + * + *

Example

+ * + *
{@code
+     * ToolDefinition sessionInfo = ToolDefinition.fromWithToolInvocation("session_info", "Return the current session id",
+     * 		invocation -> "sessionId=" + invocation.getSessionId());
+     * }
+ * + * @param + * the return type of the handler + * @param name + * the unique name of the tool (must not be blank) + * @param description + * a description of what the tool does (must not be blank) + * @param handler + * a function accepting the {@link ToolInvocation} context + * @return a new tool definition + * @throws IllegalArgumentException + * if validation fails + * @since 1.0.6 + */ + @CopilotExperimental + public static ToolDefinition fromWithToolInvocation(String name, String description, + Function handler) { + requireNonBlankToolName(name); + requireNonBlankDescription(description); + requireNonNullHandler(handler, name); + final ObjectMapper mapper = getConfiguredMapper(); + Map schema = ParamSchema.buildSchema(name, mapper); + ToolHandler toolHandler = invocation -> { + R result = handler.apply(invocation); + return CompletableFuture.completedFuture(formatResult(result, mapper)); + }; + return new ToolDefinition(name, description, schema, toolHandler, null, null, null); + } + + /** + * Creates a tool definition with a one-argument synchronous handler that also + * receives the {@link ToolInvocation} context. + * + *

Example

+ * + *
{@code
+     * ToolDefinition reportPhase = ToolDefinition.fromWithToolInvocation("report_phase",
+     * 		"Report the current phase along with invocation context", Param.of(String.class, "phase", "Current phase"),
+     * 		(phase, invocation) -> "phase=" + phase + ", toolCallId=" + invocation.getToolCallId());
+     * }
+ * + * @param + * the type of the first parameter + * @param + * the return type of the handler + * @param name + * the unique name of the tool (must not be blank) + * @param description + * a description of what the tool does (must not be blank) + * @param p1 + * the first parameter descriptor + * @param handler + * a function accepting the typed argument and the + * {@link ToolInvocation} context + * @return a new tool definition + * @throws IllegalArgumentException + * if validation fails + * @since 1.0.6 + */ + @CopilotExperimental + public static ToolDefinition fromWithToolInvocation(String name, String description, Param p1, + BiFunction handler) { + requireNonBlankToolName(name); + requireNonBlankDescription(description); + requireNonNullHandler(handler, name); + final ObjectMapper mapper = getConfiguredMapper(); + Map schema = ParamSchema.buildSchema(name, mapper, p1); + ToolHandler toolHandler = invocation -> { + T1 arg1 = ParamCoercion.coerce(invocation.getArguments(), p1, mapper); + R result = handler.apply(arg1, invocation); + return CompletableFuture.completedFuture(formatResult(result, mapper)); + }; + return new ToolDefinition(name, description, schema, toolHandler, null, null, null); + } + + // ------------------------------------------------------------------ + // fromAsyncWithToolInvocation(...) — async, with ToolInvocation context + // ------------------------------------------------------------------ + + /** + * Creates a tool definition with a zero-argument asynchronous handler that + * receives the {@link ToolInvocation} context. + * + *

Example

+ * + *
{@code
+     * ToolDefinition sessionInfo = ToolDefinition.fromAsyncWithToolInvocation("session_info_async",
+     * 		"Return the current session id asynchronously",
+     * 		invocation -> CompletableFuture.completedFuture("sessionId=" + invocation.getSessionId()));
+     * }
+ * + * @param + * the return type wrapped in {@link CompletableFuture} + * @param name + * the unique name of the tool (must not be blank) + * @param description + * a description of what the tool does (must not be blank) + * @param handler + * a function accepting the {@link ToolInvocation} context, returning + * a {@link CompletableFuture} + * @return a new tool definition + * @throws IllegalArgumentException + * if validation fails + * @since 1.0.6 + */ + @CopilotExperimental + public static ToolDefinition fromAsyncWithToolInvocation(String name, String description, + Function> handler) { + requireNonBlankToolName(name); + requireNonBlankDescription(description); + requireNonNullHandler(handler, name); + final ObjectMapper mapper = getConfiguredMapper(); + Map schema = ParamSchema.buildSchema(name, mapper); + ToolHandler toolHandler = invocation -> { + CompletableFuture future = handler.apply(invocation); + if (future == null) { + return CompletableFuture.failedFuture( + new NullPointerException("Async handler for tool '" + name + "' returned a null future")); + } + return future.thenApply(result -> formatResult(result, mapper)); + }; + return new ToolDefinition(name, description, schema, toolHandler, null, null, null); + } + + /** + * Creates a tool definition with a one-argument asynchronous handler that also + * receives the {@link ToolInvocation} context. + * + *

Example

+ * + *
{@code
+     * ToolDefinition reportPhase = ToolDefinition.fromAsyncWithToolInvocation("report_phase_async",
+     * 		"Report the current phase with invocation context asynchronously",
+     * 		Param.of(String.class, "phase", "The current phase"), (phase, invocation) -> CompletableFuture
+     * 				.completedFuture("phase=" + phase + ", toolCallId=" + invocation.getToolCallId()));
+     * }
+ * + * @param + * the type of the first parameter + * @param + * the return type wrapped in {@link CompletableFuture} + * @param name + * the unique name of the tool (must not be blank) + * @param description + * a description of what the tool does (must not be blank) + * @param p1 + * the first parameter descriptor + * @param handler + * a function accepting the typed argument and the + * {@link ToolInvocation} context, returning a + * {@link CompletableFuture} + * @return a new tool definition + * @throws IllegalArgumentException + * if validation fails + * @since 1.0.6 + */ + @CopilotExperimental + public static ToolDefinition fromAsyncWithToolInvocation(String name, String description, Param p1, + BiFunction> handler) { + requireNonBlankToolName(name); + requireNonBlankDescription(description); + requireNonNullHandler(handler, name); + final ObjectMapper mapper = getConfiguredMapper(); + Map schema = ParamSchema.buildSchema(name, mapper, p1); + ToolHandler toolHandler = invocation -> { + T1 arg1 = ParamCoercion.coerce(invocation.getArguments(), p1, mapper); + CompletableFuture future = handler.apply(arg1, invocation); + if (future == null) { + return CompletableFuture.failedFuture( + new NullPointerException("Async handler for tool '" + name + "' returned a null future")); + } + return future.thenApply(result -> formatResult(result, mapper)); + }; + return new ToolDefinition(name, description, schema, toolHandler, null, null, null); + } + + // ------------------------------------------------------------------ + // Internal helpers: result formatting, validation + // ------------------------------------------------------------------ + + /** + * Formats a handler return value according to the tool result contract: + *
    + *
  • {@link String} — returned as-is
  • + *
  • {@code null} — mapped to {@code "Success"} (covers handlers that return + * null to indicate a successful no-value result)
  • + *
  • any other value — JSON-serialized via {@link ObjectMapper}
  • + *
+ */ + private static Object formatResult(Object result, ObjectMapper mapper) { + if (result == null) { + return "Success"; + } + if (result instanceof String) { + return result; + } + if (result instanceof ToolResultObject) { + return result; + } + try { + return mapper.writeValueAsString(result); + } catch (com.fasterxml.jackson.core.JsonProcessingException ex) { + throw new IllegalStateException("Failed to serialize tool result to JSON", ex); + } + } + + // ------------------------------------------------------------------ + // Validation helpers + // ------------------------------------------------------------------ + + private static void requireNonBlankToolName(String name) { + if (name == null || name.isBlank()) { + throw new IllegalArgumentException("Tool name must not be null or blank"); + } + } + + private static void requireNonBlankDescription(String description) { + if (description == null || description.isBlank()) { + throw new IllegalArgumentException("Tool description must not be null or blank"); + } + } + + private static void requireNonNullHandler(Object handler, String toolName) { + if (handler == null) { + throw new IllegalArgumentException("handler must not be null for tool '" + toolName + "'"); + } + } + @SuppressWarnings("unchecked") private static List loadDefinitions(Class clazz, Object instance) { String metaClassName = clazz.getName() + "$$CopilotToolMeta"; diff --git a/java/src/main/java/com/github/copilot/tool/Param.java b/java/src/main/java/com/github/copilot/tool/Param.java new file mode 100644 index 0000000000..bbe188ce05 --- /dev/null +++ b/java/src/main/java/com/github/copilot/tool/Param.java @@ -0,0 +1,261 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +package com.github.copilot.tool; + +import java.util.Objects; + +import com.github.copilot.CopilotExperimental; + +/** + * Runtime parameter metadata for lambda-defined tools. + * + *

+ * Each {@code Param} instance describes a single parameter that a tool accepts, + * including its Java type, wire name, description, whether it is required, and + * an optional default value. Instances are immutable; fluent mutators return + * new copies. + * + *

Example Usage

+ * + *
{@code
+ * Param query = Param.of(String.class, "query", "Search query text");
+ *
+ * Param limit = Param.of(Integer.class, "limit", "Max results", false, "10");
+ * }
+ * + * @param + * the Java type of the parameter value + * @since 1.0.6 + */ +@CopilotExperimental +public final class Param { + + private final Class type; + private final String name; + private final String description; + private final boolean required; + private final String defaultValue; + + private Param(Class type, String name, String description, boolean required, String defaultValue) { + this.type = Objects.requireNonNull(type, "type"); + this.name = requireNonBlank(name, "name"); + this.description = requireNonBlank(description, "description"); + this.defaultValue = defaultValue == null ? "" : defaultValue; + this.required = required; + + if (this.required && !this.defaultValue.isEmpty()) { + throw new IllegalArgumentException("required=true cannot be combined with a non-empty defaultValue"); + } + + validateDefaultValue(type, this.defaultValue); + } + + /** + * Creates a required parameter with no default value. + * + * @param + * the parameter type + * @param type + * the Java class of the parameter + * @param name + * the wire name sent to the model (must not be blank) + * @param description + * a human-readable description (must not be blank) + * @return a new {@code Param} instance + * @throws NullPointerException + * if {@code type} is null + * @throws IllegalArgumentException + * if {@code name} or {@code description} is blank + */ + public static Param of(Class type, String name, String description) { + return new Param<>(type, name, description, true, ""); + } + + /** + * Creates a parameter with explicit required/default settings. + * + * @param + * the parameter type + * @param type + * the Java class of the parameter + * @param name + * the wire name sent to the model (must not be blank) + * @param description + * a human-readable description (must not be blank) + * @param required + * whether the parameter is required + * @param defaultValue + * the default value as a string, or {@code null}/empty for none + * @return a new {@code Param} instance + * @throws NullPointerException + * if {@code type} is null + * @throws IllegalArgumentException + * if validation fails + */ + public static Param of(Class type, String name, String description, boolean required, + String defaultValue) { + return new Param<>(type, name, description, required, defaultValue); + } + + /** + * Returns a copy with a different name. + * + * @param name + * the new parameter name + * @return a new {@code Param} with the updated name + */ + public Param name(String name) { + return new Param<>(this.type, name, this.description, this.required, this.defaultValue); + } + + /** + * Returns a copy with a different description. + * + * @param description + * the new description + * @return a new {@code Param} with the updated description + */ + public Param description(String description) { + return new Param<>(this.type, this.name, description, this.required, this.defaultValue); + } + + /** + * Returns a copy with a different required flag. + * + * @param required + * whether the parameter is required + * @return a new {@code Param} with the updated required flag + */ + public Param required(boolean required) { + return new Param<>(this.type, this.name, this.description, required, this.defaultValue); + } + + /** + * Returns an optional copy with the given default value. Setting a default + * implicitly makes the parameter optional ({@code required=false}). + * + * @param defaultValue + * the default value as a string + * @return a new {@code Param} with the default applied and required set to + * false + */ + public Param defaultValue(String defaultValue) { + return new Param<>(this.type, this.name, this.description, false, defaultValue); + } + + /** Returns the Java type of this parameter. */ + public Class type() { + return type; + } + + /** Returns the wire name of this parameter. */ + public String name() { + return name; + } + + /** Returns the human-readable description. */ + public String description() { + return description; + } + + /** Returns whether this parameter is required. */ + public boolean required() { + return required; + } + + /** Returns the default value string, or empty if none. */ + public String defaultValue() { + return defaultValue; + } + + /** Returns {@code true} if a non-empty default value is set. */ + public boolean hasDefaultValue() { + return !defaultValue.isEmpty(); + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof Param other)) { + return false; + } + return required == other.required && Objects.equals(type, other.type) && Objects.equals(name, other.name) + && Objects.equals(description, other.description) && Objects.equals(defaultValue, other.defaultValue); + } + + @Override + public int hashCode() { + return Objects.hash(type, name, description, required, defaultValue); + } + + @Override + public String toString() { + return "Param[name=" + name + ", type=" + type.getSimpleName() + ", required=" + required + "]"; + } + + // ------------------------------------------------------------------ + // Internal validation helpers + // ------------------------------------------------------------------ + + private static String requireNonBlank(String value, String fieldName) { + if (value == null || value.isBlank()) { + throw new IllegalArgumentException(fieldName + " must not be null or blank"); + } + return value; + } + + @SuppressWarnings({"rawtypes", "unchecked"}) + private static void validateDefaultValue(Class type, String defaultValue) { + if (defaultValue == null || defaultValue.isEmpty()) { + return; + } + + try { + if (type == String.class) { + return; + } + if (type == Integer.class || type == int.class) { + Integer.parseInt(defaultValue); + return; + } + if (type == Long.class || type == long.class) { + Long.parseLong(defaultValue); + return; + } + if (type == Double.class || type == double.class) { + Double.parseDouble(defaultValue); + return; + } + if (type == Float.class || type == float.class) { + Float.parseFloat(defaultValue); + return; + } + if (type == Short.class || type == short.class) { + Short.parseShort(defaultValue); + return; + } + if (type == Byte.class || type == byte.class) { + Byte.parseByte(defaultValue); + return; + } + if (type == Boolean.class || type == boolean.class) { + if (!"true".equalsIgnoreCase(defaultValue) && !"false".equalsIgnoreCase(defaultValue)) { + throw new IllegalArgumentException("must be 'true' or 'false'"); + } + return; + } + if (type.isEnum()) { + Class enumType = (Class) type; + Enum.valueOf(enumType, defaultValue); + return; + } + } catch (RuntimeException ex) { + throw new IllegalArgumentException( + "defaultValue '" + defaultValue + "' is not valid for type " + type.getSimpleName(), ex); + } + + throw new IllegalArgumentException( + "defaultValue is not supported for type " + type.getName() + " without a custom coercion policy"); + } +} diff --git a/java/src/test/java/com/github/copilot/ClientOptionsE2ETest.java b/java/src/test/java/com/github/copilot/ClientOptionsE2ETest.java new file mode 100644 index 0000000000..45056afdb4 --- /dev/null +++ b/java/src/test/java/com/github/copilot/ClientOptionsE2ETest.java @@ -0,0 +1,302 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +package com.github.copilot; + +import static org.junit.jupiter.api.Assertions.*; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Comparator; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +import org.junit.jupiter.api.Test; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.github.copilot.generated.rpc.SessionLimitsConfig; +import com.github.copilot.rpc.CopilotClientOptions; +import com.github.copilot.rpc.PermissionHandler; +import com.github.copilot.rpc.ProviderConfig; +import com.github.copilot.rpc.ResumeSessionConfig; +import com.github.copilot.rpc.SessionConfig; + +class ClientOptionsE2ETest { + + private static final ObjectMapper MAPPER = new ObjectMapper(); + + @Test + void testShouldForwardAdvancedSessionCreationOptionsToTheCli() throws Exception { + try (var fake = FakeStdioCli.create()) { + var workDir = fake.path("create-work"); + var configDir = fake.path("create-config"); + + try (var client = fake.createClient()) { + var session = client.createSession(new SessionConfig().setSessionId("java-create-session") + .setClientName("java-e2e-client").setModel("gpt-5-mini").setReasoningEffort("low") + .setReasoningSummary("none").setContextTier("long_context") + .setAvailableTools(java.util.List.of("bash")).setExcludedTools(java.util.List.of("grep")) + .setExcludedBuiltInAgents(java.util.List.of("explore")).setEnableSessionTelemetry(true) + .setEnableCitations(true).setSessionLimits(new SessionLimitsConfig(42.0)) + .setWorkingDirectory(workDir.toString()).setStreaming(true) + .setIncludeSubAgentStreamingEvents(true).setConfigDirectory(configDir.toString()) + .setEnableConfigDiscovery(false).setSkipEmbeddingRetrieval(true) + .setOrganizationCustomInstructions("Use Java parity instructions.") + .setEnableOnDemandInstructionDiscovery(false).setEnableFileHooks(true) + .setEnableHostGitOperations(false).setEnableSessionStore(true).setEnableSkills(false) + .setEmbeddingCacheStorage("in-memory").setGitHubToken("java-session-token") + .setRemoteSession("export").setSkipCustomInstructions(true).setCustomAgentsLocalOnly(false) + .setCoauthorEnabled(true).setManageScheduleEnabled(true) + .setOnPermissionRequest(PermissionHandler.APPROVE_ALL)).get(30, TimeUnit.SECONDS); + session.close(); + } + + var create = fake.capturedRequest("session.create").path("params"); + assertEquals("java-create-session", create.path("sessionId").asText()); + assertEquals("java-e2e-client", create.path("clientName").asText()); + assertEquals("gpt-5-mini", create.path("model").asText()); + assertEquals("low", create.path("reasoningEffort").asText()); + assertEquals("none", create.path("reasoningSummary").asText()); + assertEquals("long_context", create.path("contextTier").asText()); + assertEquals("bash", create.path("availableTools").get(0).asText()); + assertEquals("grep", create.path("excludedTools").get(0).asText()); + assertEquals("explore", create.path("excludedBuiltinAgents").get(0).asText()); + assertTrue(create.path("enableSessionTelemetry").asBoolean()); + assertTrue(create.path("enableCitations").asBoolean()); + assertEquals(42.0, create.path("sessionLimits").path("maxAiCredits").asDouble()); + assertEquals(workDir.toString(), create.path("workingDirectory").asText()); + assertTrue(create.path("streaming").asBoolean()); + assertTrue(create.path("includeSubAgentStreamingEvents").asBoolean()); + assertEquals(configDir.toString(), create.path("configDir").asText()); + assertFalse(create.path("enableConfigDiscovery").asBoolean()); + assertTrue(create.path("skipEmbeddingRetrieval").asBoolean()); + assertEquals("Use Java parity instructions.", create.path("organizationCustomInstructions").asText()); + assertFalse(create.path("enableOnDemandInstructionDiscovery").asBoolean()); + assertTrue(create.path("enableFileHooks").asBoolean()); + assertFalse(create.path("enableHostGitOperations").asBoolean()); + assertTrue(create.path("enableSessionStore").asBoolean()); + assertFalse(create.path("enableSkills").asBoolean()); + assertEquals("in-memory", create.path("embeddingCacheStorage").asText()); + assertEquals("java-session-token", create.path("gitHubToken").asText()); + assertEquals("export", create.path("remoteSession").asText()); + assertEquals("direct", create.path("envValueMode").asText()); + assertTrue(create.path("requestPermission").asBoolean()); + + var update = fake.capturedRequest("session.options.update").path("params"); + assertEquals("java-create-session", update.path("sessionId").asText()); + assertTrue(update.path("skipCustomInstructions").asBoolean()); + assertFalse(update.path("customAgentsLocalOnly").asBoolean()); + assertTrue(update.path("coauthorEnabled").asBoolean()); + assertTrue(update.path("manageScheduleEnabled").asBoolean()); + } + } + + @Test + void testShouldForwardSingularProviderConfigurationOnSessionCreation() throws Exception { + try (var fake = FakeStdioCli.create()) { + try (var client = fake.createClient()) { + var session = client.createSession(new SessionConfig() + .setProvider(new ProviderConfig().setType("openai").setWireApi("responses") + .setTransport("websockets").setBaseUrl("https://models.example.test/v1") + .setApiKey("provider-key").setModelId("base-model").setWireModel("wire-model") + .setMaxPromptTokens(1000).setMaxOutputTokens(2000) + .setHeaders(Map.of("x-provider", "java"))) + .setOnPermissionRequest(PermissionHandler.APPROVE_ALL)).get(30, TimeUnit.SECONDS); + session.close(); + } + + var provider = fake.capturedRequest("session.create").path("params").path("provider"); + assertEquals("openai", provider.path("type").asText()); + assertEquals("responses", provider.path("wireApi").asText()); + assertEquals("websockets", provider.path("transport").asText()); + assertEquals("https://models.example.test/v1", provider.path("baseUrl").asText()); + assertEquals("provider-key", provider.path("apiKey").asText()); + assertEquals("base-model", provider.path("modelId").asText()); + assertEquals("wire-model", provider.path("wireModel").asText()); + assertEquals(1000, provider.path("maxPromptTokens").asInt()); + assertEquals(2000, provider.path("maxOutputTokens").asInt()); + assertEquals("java", provider.path("headers").path("x-provider").asText()); + } + } + + @Test + void testShouldForwardAdvancedSessionResumeOptionsToTheCli() throws Exception { + try (var fake = FakeStdioCli.create()) { + var workDir = fake.path("resume-work"); + var configDir = fake.path("resume-config"); + + try (var client = fake.createClient()) { + client.createSession(new SessionConfig().setSessionId("java-resume-session") + .setOnPermissionRequest(PermissionHandler.APPROVE_ALL)).get(30, TimeUnit.SECONDS); + var session = client.resumeSession("java-resume-session", + new ResumeSessionConfig().setClientName("java-resume-client").setModel("gpt-5-mini") + .setReasoningEffort("medium").setReasoningSummary("none").setContextTier("long_context") + .setEnableCitations(true).setSessionLimits(new SessionLimitsConfig(84.0)) + .setWorkingDirectory(workDir.toString()).setConfigDirectory(configDir.toString()) + .setEnableConfigDiscovery(false).setSkipEmbeddingRetrieval(true) + .setOrganizationCustomInstructions("Use resumed Java instructions.") + .setEnableOnDemandInstructionDiscovery(false).setEnableFileHooks(true) + .setEnableHostGitOperations(false).setEnableSessionStore(true).setEnableSkills(false) + .setEmbeddingCacheStorage("in-memory").setGitHubToken("java-resume-token") + .setRemoteSession("export").setSkipCustomInstructions(false) + .setCustomAgentsLocalOnly(true).setCoauthorEnabled(false).setManageScheduleEnabled(true) + .setOnPermissionRequest(PermissionHandler.APPROVE_ALL)) + .get(30, TimeUnit.SECONDS); + session.close(); + } + + var resume = fake.capturedRequest("session.resume").path("params"); + assertEquals("java-resume-session", resume.path("sessionId").asText()); + assertEquals("java-resume-client", resume.path("clientName").asText()); + assertEquals("gpt-5-mini", resume.path("model").asText()); + assertEquals("medium", resume.path("reasoningEffort").asText()); + assertEquals("none", resume.path("reasoningSummary").asText()); + assertEquals("long_context", resume.path("contextTier").asText()); + assertTrue(resume.path("enableCitations").asBoolean()); + assertEquals(84.0, resume.path("sessionLimits").path("maxAiCredits").asDouble()); + assertEquals(workDir.toString(), resume.path("workingDirectory").asText()); + assertEquals(configDir.toString(), resume.path("configDir").asText()); + assertFalse(resume.path("enableConfigDiscovery").asBoolean()); + assertTrue(resume.path("skipEmbeddingRetrieval").asBoolean()); + assertEquals("Use resumed Java instructions.", resume.path("organizationCustomInstructions").asText()); + assertFalse(resume.path("enableOnDemandInstructionDiscovery").asBoolean()); + assertTrue(resume.path("enableFileHooks").asBoolean()); + assertFalse(resume.path("enableHostGitOperations").asBoolean()); + assertTrue(resume.path("enableSessionStore").asBoolean()); + assertFalse(resume.path("enableSkills").asBoolean()); + assertEquals("in-memory", resume.path("embeddingCacheStorage").asText()); + assertEquals("java-resume-token", resume.path("gitHubToken").asText()); + assertEquals("export", resume.path("remoteSession").asText()); + assertEquals("direct", resume.path("envValueMode").asText()); + assertTrue(resume.path("requestPermission").asBoolean()); + + var update = fake.capturedRequest("session.options.update").path("params"); + assertEquals("java-resume-session", update.path("sessionId").asText()); + assertFalse(update.path("skipCustomInstructions").asBoolean()); + assertTrue(update.path("customAgentsLocalOnly").asBoolean()); + assertFalse(update.path("coauthorEnabled").asBoolean()); + assertTrue(update.path("manageScheduleEnabled").asBoolean()); + } + } + + private record FakeStdioCli(Path dir, Path script, Path capture, Path workDir) implements AutoCloseable { + + static FakeStdioCli create() throws IOException { + var dir = Files.createTempDirectory("java-fake-copilot-cli-"); + var script = dir.resolve("fake-copilot-cli.js"); + var capture = dir.resolve("capture.json"); + var workDir = dir.resolve("work"); + Files.createDirectories(workDir); + Files.writeString(capture, "{\"requests\":[]}"); + Files.writeString(script, FAKE_STDIO_CLI_SCRIPT); + return new FakeStdioCli(dir, script, capture, workDir); + } + + CopilotClient createClient() { + var options = new CopilotClientOptions().setCliPath(script.toString()) + .setCliArgs(new String[]{"--capture-file", capture.toString()}).setCwd(workDir.toString()) + .setUseLoggedInUser(false); + return new CopilotClient(options); + } + + Path path(String name) throws IOException { + var path = workDir.resolve(name); + Files.createDirectories(path); + return path; + } + + JsonNode capturedRequest(String method) throws IOException { + for (JsonNode request : MAPPER.readTree(Files.readString(capture)).path("requests")) { + if (method.equals(request.path("method").asText())) { + return request; + } + } + fail("Expected captured request for " + method + " in " + Files.readString(capture)); + return null; + } + + @Override + public void close() throws IOException { + if (Files.exists(dir)) { + try (var paths = Files.walk(dir)) { + paths.sorted(Comparator.reverseOrder()).forEach(path -> { + try { + Files.deleteIfExists(path); + } catch (IOException ignored) { + } + }); + } + } + } + } + + private static final String FAKE_STDIO_CLI_SCRIPT = """ + const fs = require('fs'); + + const captureFileIndex = process.argv.indexOf('--capture-file'); + const captureFile = process.argv[captureFileIndex + 1]; + const capture = { requests: [] }; + fs.writeFileSync(captureFile, JSON.stringify(capture)); + + let buffer = Buffer.alloc(0); + + function persist() { + fs.writeFileSync(captureFile, JSON.stringify(capture)); + } + + function send(message) { + const body = Buffer.from(JSON.stringify(message), 'utf8'); + process.stdout.write(`Content-Length: ${body.length}\\r\\n\\r\\n`); + process.stdout.write(body); + } + + function resultFor(message) { + switch (message.method) { + case 'connect': + return { ok: true, protocolVersion: 3, version: 'fake' }; + case 'llmInference.setProvider': + return {}; + case 'session.create': + return { sessionId: message.params?.sessionId ?? 'fake-session', openCanvases: [] }; + case 'session.resume': + return { sessionId: message.params?.sessionId ?? 'fake-session', openCanvases: [] }; + case 'session.options.update': + return { success: true }; + default: + return {}; + } + } + + function handle(message) { + capture.requests.push({ method: message.method, params: message.params ?? null }); + persist(); + send({ jsonrpc: '2.0', id: message.id, result: resultFor(message) }); + } + + process.stdin.on('data', chunk => { + buffer = Buffer.concat([buffer, chunk]); + while (true) { + const headerEnd = buffer.indexOf('\\r\\n\\r\\n'); + if (headerEnd < 0) { + return; + } + const header = buffer.subarray(0, headerEnd).toString('utf8'); + const match = /Content-Length:\\s*(\\d+)/i.exec(header); + if (!match) { + throw new Error(`Missing Content-Length in ${header}`); + } + const length = Number(match[1]); + const bodyStart = headerEnd + 4; + if (buffer.length < bodyStart + length) { + return; + } + const body = buffer.subarray(bodyStart, bodyStart + length).toString('utf8'); + buffer = buffer.subarray(bodyStart + length); + handle(JSON.parse(body)); + } + }); + """; +} diff --git a/java/src/test/java/com/github/copilot/CopilotRequestTestSupport.java b/java/src/test/java/com/github/copilot/CopilotRequestTestSupport.java index 7347724583..7a2e7f2f0f 100644 --- a/java/src/test/java/com/github/copilot/CopilotRequestTestSupport.java +++ b/java/src/test/java/com/github/copilot/CopilotRequestTestSupport.java @@ -122,6 +122,58 @@ static String sseBody(String text, String respId) { return sb.toString(); } + /** + * Builds a complete Anthropic Messages SSE body (message_start … message_stop) + * for a streaming {@code /messages} response. The buffered JSON message is only + * valid for a non-streaming request; a streaming request expects named SSE + * events or the runtime fails to finalize the message. + */ + static String anthropicMessageSseBody(String text) { + Map startMessage = new LinkedHashMap<>(); + startMessage.put("id", "msg_stub_1"); + startMessage.put("type", "message"); + startMessage.put("role", "assistant"); + startMessage.put("model", "claude-sonnet-4.5"); + startMessage.put("content", List.of()); + startMessage.put("stop_reason", null); + startMessage.put("stop_sequence", null); + startMessage.put("usage", Map.of("input_tokens", 5, "output_tokens", 1)); + Map messageStart = new LinkedHashMap<>(); + messageStart.put("type", "message_start"); + messageStart.put("message", startMessage); + + Map contentBlockStart = new LinkedHashMap<>(); + contentBlockStart.put("type", "content_block_start"); + contentBlockStart.put("index", 0); + contentBlockStart.put("content_block", Map.of("type", "text", "text", "")); + + Map contentBlockDelta = new LinkedHashMap<>(); + contentBlockDelta.put("type", "content_block_delta"); + contentBlockDelta.put("index", 0); + contentBlockDelta.put("delta", Map.of("type", "text_delta", "text", text)); + + Map contentBlockStop = new LinkedHashMap<>(); + contentBlockStop.put("type", "content_block_stop"); + contentBlockStop.put("index", 0); + + Map messageDeltaDelta = new LinkedHashMap<>(); + messageDeltaDelta.put("stop_reason", "end_turn"); + messageDeltaDelta.put("stop_sequence", null); + Map messageDelta = new LinkedHashMap<>(); + messageDelta.put("type", "message_delta"); + messageDelta.put("delta", messageDeltaDelta); + messageDelta.put("usage", Map.of("output_tokens", 7)); + + StringBuilder sb = new StringBuilder(); + sb.append(sse("message_start", messageStart)); + sb.append(sse("content_block_start", contentBlockStart)); + sb.append(sse("content_block_delta", contentBlockDelta)); + sb.append(sse("content_block_stop", contentBlockStop)); + sb.append(sse("message_delta", messageDelta)); + sb.append(sse("message_stop", Map.of("type", "message_stop"))); + return sb.toString(); + } + // --- Synthetic response builders for the CopilotRequestHandler send override // --- @@ -191,6 +243,9 @@ static HttpResponse buildInferenceResponse(String url, String bodyT } if (u.endsWith("/messages")) { + if (stream) { + return sseResponse(anthropicMessageSseBody(text)); + } Map body = new LinkedHashMap<>(); body.put("id", "msg_stub_1"); body.put("type", "message"); diff --git a/java/src/test/java/com/github/copilot/E2ETestContext.java b/java/src/test/java/com/github/copilot/E2ETestContext.java index 4a4da04229..f524b33dab 100644 --- a/java/src/test/java/com/github/copilot/E2ETestContext.java +++ b/java/src/test/java/com/github/copilot/E2ETestContext.java @@ -381,6 +381,24 @@ public void setCopilotUserByToken(String token, String login, String copilotPlan proxy.setCopilotUserByToken(token, login, copilotPlan, apiUrl, telemetryUrl, analyticsTrackingId); } + /** + * Configures the proxy to return a raw Copilot user response for a given token. + * + * @param token + * the GitHub token + * @param response + * the raw response object to return for the token + * @throws IOException + * if the request fails + * @throws InterruptedException + * if the request is interrupted + */ + public void setCopilotUserByToken(String token, Map response) + throws IOException, InterruptedException { + ensureProxyAlive(); + proxy.setCopilotUserByToken(token, response); + } + /** * Initializes the proxy state without loading a snapshot. *

diff --git a/java/src/test/java/com/github/copilot/GitHubTelemetryForwardingIT.java b/java/src/test/java/com/github/copilot/GitHubTelemetryForwardingIT.java new file mode 100644 index 0000000000..d41c5f97dc --- /dev/null +++ b/java/src/test/java/com/github/copilot/GitHubTelemetryForwardingIT.java @@ -0,0 +1,59 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +package com.github.copilot; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.TimeUnit; + +import org.junit.jupiter.api.Test; + +import com.github.copilot.generated.rpc.GitHubTelemetryNotification; +import com.github.copilot.rpc.CopilotClientOptions; +import com.github.copilot.rpc.PermissionHandler; +import com.github.copilot.rpc.SessionConfig; + +/** + * Failsafe integration test that verifies the live CLI forwards GitHub + * telemetry notifications during session creation. + */ +@AllowCopilotExperimental +class GitHubTelemetryForwardingIT { + + @Test + void forwardsGitHubTelemetryForALiveSession() throws Exception { + var notifications = new CopyOnWriteArrayList(); + var firstNotification = new CompletableFuture(); + + try (E2ETestContext ctx = E2ETestContext.create()) { + var options = new CopilotClientOptions().setOnGitHubTelemetry(notification -> { + notifications.add(notification); + firstNotification.complete(notification); + return CompletableFuture.completedFuture(null); + }); + + try (CopilotClient client = ctx.createClient(options); + CopilotSession session = client + .createSession(new SessionConfig().setOnPermissionRequest(PermissionHandler.APPROVE_ALL)) + .get(30, TimeUnit.SECONDS)) { + + GitHubTelemetryNotification notification = firstNotification.get(30, TimeUnit.SECONDS); + + assertFalse(notifications.isEmpty(), "Expected at least one GitHub telemetry notification"); + assertNotNull(notification, "Expected a GitHub telemetry notification"); + assertNotNull(notification.sessionId(), "Telemetry notification sessionId must be present"); + assertTrue(!notification.sessionId().isBlank(), "Telemetry notification sessionId must be non-empty"); + assertNotNull(notification.restricted(), "Telemetry notification restricted flag must be present"); + assertNotNull(notification.event(), "Telemetry notification event must be present"); + assertNotNull(notification.event().kind(), "Telemetry event kind must be present"); + assertTrue(!notification.event().kind().isBlank(), "Telemetry event kind must be non-empty"); + } + } + } +} diff --git a/java/src/test/java/com/github/copilot/GitHubTelemetryTest.java b/java/src/test/java/com/github/copilot/GitHubTelemetryTest.java new file mode 100644 index 0000000000..8e35bd9a92 --- /dev/null +++ b/java/src/test/java/com/github/copilot/GitHubTelemetryTest.java @@ -0,0 +1,308 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +package com.github.copilot; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.IOException; +import java.io.OutputStream; +import java.net.ServerSocket; +import java.net.Socket; +import java.nio.charset.StandardCharsets; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; +import java.util.function.Function; + +import org.junit.jupiter.api.Test; + +import com.fasterxml.jackson.databind.JsonNode; +import com.github.copilot.generated.rpc.GitHubTelemetryNotification; +import com.github.copilot.rpc.CopilotClientOptions; +import com.github.copilot.rpc.PermissionHandler; +import com.github.copilot.rpc.ResumeSessionConfig; +import com.github.copilot.rpc.SessionConfig; + +/** + * Exercises the hand-written GitHub telemetry forwarding surface: the + * {@code gitHubTelemetry.event} notification adapter, the + * {@code enableGitHubTelemetryForwarding} capability flag on the connect + * handshake and the create/resume requests, and the {@code onGitHubTelemetry} + * client option. + */ +@AllowCopilotExperimental +class GitHubTelemetryTest { + + private record SocketPair(JsonRpcClient client, Socket serverSide, + ServerSocket serverSocket) implements AutoCloseable { + + @Override + public void close() throws Exception { + client.close(); + serverSide.close(); + serverSocket.close(); + } + } + + private SocketPair createSocketPair() throws Exception { + var serverSocket = new ServerSocket(0); + var clientSocket = new Socket("localhost", serverSocket.getLocalPort()); + var serverSide = serverSocket.accept(); + var client = JsonRpcClient.fromSocket(clientSocket); + return new SocketPair(client, serverSide, serverSocket); + } + + private void writeRpcMessage(OutputStream out, String json) throws IOException { + byte[] content = json.getBytes(StandardCharsets.UTF_8); + String header = "Content-Length: " + content.length + "\r\n\r\n"; + out.write(header.getBytes(StandardCharsets.UTF_8)); + out.write(content); + out.flush(); + } + + @Test + void adapterDispatchesNotificationToHandlerWithTypedPayload() throws Exception { + try (var pair = createSocketPair()) { + var received = new CompletableFuture(); + Function> handler = notification -> { + received.complete(notification); + return CompletableFuture.completedFuture(null); + }; + new GitHubTelemetryAdapter(handler).registerHandlers(pair.client()); + + String notification = """ + { + "jsonrpc": "2.0", + "method": "gitHubTelemetry.event", + "params": { + "sessionId": "sess-123", + "restricted": true, + "event": { + "kind": "tool_call_executed", + "created_at": "2024-01-01T00:00:00Z", + "model_call_id": "call-9", + "properties": { "tool": "shell" }, + "metrics": { "duration_ms": 42.5 }, + "exp_assignment_context": "ctx", + "features": { "flag_a": "on" }, + "session_id": "sess-123", + "copilot_tracking_id": "track-1", + "client": { + "cli_version": "1.2.3", + "os_platform": "win32", + "os_version": "10", + "os_arch": "x64", + "node_version": "20.0.0", + "is_staff": false + } + } + } + } + """; + writeRpcMessage(pair.serverSide().getOutputStream(), notification); + + GitHubTelemetryNotification result = received.get(5, TimeUnit.SECONDS); + assertEquals("sess-123", result.sessionId()); + assertTrue(result.restricted()); + + var event = result.event(); + assertNotNull(event); + assertEquals("tool_call_executed", event.kind()); + assertEquals("2024-01-01T00:00:00Z", event.createdAt()); + assertEquals("call-9", event.modelCallId()); + assertEquals("shell", event.properties().get("tool")); + assertEquals(42.5, event.metrics().get("duration_ms")); + assertEquals("ctx", event.expAssignmentContext()); + assertEquals("on", event.features().get("flag_a")); + assertEquals("sess-123", event.sessionId()); + assertEquals("track-1", event.copilotTrackingId()); + + var client = event.client(); + assertNotNull(client); + assertEquals("1.2.3", client.cliVersion()); + assertEquals("win32", client.osPlatform()); + assertEquals("x64", client.osArch()); + assertEquals("20.0.0", client.nodeVersion()); + assertEquals(Boolean.FALSE, client.isStaff()); + } + } + + @Test + void clientOptsSessionsIntoForwardingAndReceivesEvents() throws Exception { + var received = new CompletableFuture(); + Function> handler = notification -> { + received.complete(notification); + return CompletableFuture.completedFuture(null); + }; + + try (var server = new FakeRuntimeServer(); + var client = new CopilotClient( + new CopilotClientOptions().setCliUrl(server.url()).setOnGitHubTelemetry(handler))) { + + client.start().get(15, TimeUnit.SECONDS); + + // Connecting must opt into telemetry forwarding at the connection level so + // the runtime can forward the first session's un-replayable start event. + JsonNode connectParams = server.awaitConnect(); + assertTrue(connectParams.path("enableGitHubTelemetryForwarding").asBoolean(), + "connect request should carry enableGitHubTelemetryForwarding=true"); + + // Creating a session must opt it into telemetry forwarding. + client.createSession(new SessionConfig().setOnPermissionRequest(PermissionHandler.APPROVE_ALL)).get(15, + TimeUnit.SECONDS); + JsonNode createParams = server.awaitCreate(); + assertTrue(createParams.path("enableGitHubTelemetryForwarding").asBoolean(), + "create request should carry enableGitHubTelemetryForwarding=true"); + + // The adapter registered on connect should forward server-pushed events. + server.sendTelemetry(Map.of("sessionId", "sess-xyz", "restricted", false, "event", + Map.of("kind", "session_started", "session_id", "sess-xyz"))); + GitHubTelemetryNotification event = received.get(5, TimeUnit.SECONDS); + assertEquals("sess-xyz", event.sessionId()); + assertFalse(event.restricted()); + assertEquals("session_started", event.event().kind()); + + // Resuming a session must opt it in as well. + client.resumeSession("resume-1", + new ResumeSessionConfig().setOnPermissionRequest(PermissionHandler.APPROVE_ALL)) + .get(15, TimeUnit.SECONDS); + JsonNode resumeParams = server.awaitResume(); + assertTrue(resumeParams.path("enableGitHubTelemetryForwarding").asBoolean(), + "resume request should carry enableGitHubTelemetryForwarding=true"); + } + } + + @Test + void clientOmitsForwardingWhenNoHandler() throws Exception { + try (var server = new FakeRuntimeServer(); + var client = new CopilotClient(new CopilotClientOptions().setCliUrl(server.url()))) { + + client.start().get(15, TimeUnit.SECONDS); + + JsonNode connectParams = server.awaitConnect(); + assertFalse(connectParams.has("enableGitHubTelemetryForwarding"), + "connect request should omit the flag when no handler is registered"); + + client.createSession(new SessionConfig().setOnPermissionRequest(PermissionHandler.APPROVE_ALL)).get(15, + TimeUnit.SECONDS); + JsonNode createParams = server.awaitCreate(); + assertFalse(createParams.has("enableGitHubTelemetryForwarding"), + "create request should omit the flag when no handler is registered"); + + client.resumeSession("resume-1", + new ResumeSessionConfig().setOnPermissionRequest(PermissionHandler.APPROVE_ALL)) + .get(15, TimeUnit.SECONDS); + JsonNode resumeParams = server.awaitResume(); + assertFalse(resumeParams.has("enableGitHubTelemetryForwarding"), + "resume request should omit the flag when no handler is registered"); + } + } + + @Test + void optionsRetainAndCloneTelemetryHandler() { + Function> handler = n -> CompletableFuture + .completedFuture(null); + var options = new CopilotClientOptions().setOnGitHubTelemetry(handler); + assertSame(handler, options.getOnGitHubTelemetry()); + + var copy = options.clone(); + assertSame(handler, copy.getOnGitHubTelemetry()); + } + + /** + * A minimal in-process JSON-RPC runtime that answers the connect/create/resume + * handshake so a real {@link CopilotClient} can be driven over a socket, and + * can push {@code gitHubTelemetry.event} notifications back to the client. + */ + private static final class FakeRuntimeServer implements AutoCloseable { + + private final ServerSocket serverSocket; + private final Thread acceptThread; + private final CompletableFuture ready = new CompletableFuture<>(); + private final CompletableFuture connectParams = new CompletableFuture<>(); + private final CompletableFuture createParams = new CompletableFuture<>(); + private final CompletableFuture resumeParams = new CompletableFuture<>(); + + FakeRuntimeServer() throws IOException { + serverSocket = new ServerSocket(0); + acceptThread = new Thread(this::acceptLoop, "fake-runtime-accept"); + acceptThread.setDaemon(true); + acceptThread.start(); + } + + String url() { + return "127.0.0.1:" + serverSocket.getLocalPort(); + } + + JsonNode awaitConnect() throws Exception { + return connectParams.get(15, TimeUnit.SECONDS); + } + + JsonNode awaitCreate() throws Exception { + return createParams.get(15, TimeUnit.SECONDS); + } + + JsonNode awaitResume() throws Exception { + return resumeParams.get(15, TimeUnit.SECONDS); + } + + void sendTelemetry(Object params) throws Exception { + ready.get(15, TimeUnit.SECONDS).notify("gitHubTelemetry.event", params); + } + + private void acceptLoop() { + try { + Socket socket = serverSocket.accept(); + JsonRpcClient server = JsonRpcClient.fromSocket(socket); + server.registerMethodHandler("connect", (id, params) -> { + connectParams.complete(params); + respond(server, id, Map.of("protocolVersion", 2)); + }); + server.registerMethodHandler("session.create", (id, params) -> { + createParams.complete(params); + respond(server, id, Map.of("sessionId", params.path("sessionId").asText("created"), "workspacePath", + "/workspace")); + }); + server.registerMethodHandler("session.resume", (id, params) -> { + resumeParams.complete(params); + respond(server, id, Map.of("sessionId", params.path("sessionId").asText("resume-1"), + "workspacePath", "/workspace")); + }); + server.registerMethodHandler("session.destroy", (id, params) -> respond(server, id, Map.of())); + server.registerMethodHandler("runtime.shutdown", (id, params) -> respond(server, id, Map.of())); + ready.complete(server); + } catch (IOException e) { + ready.completeExceptionally(e); + connectParams.completeExceptionally(e); + createParams.completeExceptionally(e); + resumeParams.completeExceptionally(e); + } + } + + private static void respond(JsonRpcClient server, String id, Object result) { + if (id == null) { + return; + } + try { + server.sendResponse(id, result); + } catch (IOException e) { + // Connection torn down (e.g. client closing); ignore. + } + } + + @Override + public void close() throws Exception { + JsonRpcClient server = ready.getNow(null); + if (server != null) { + server.close(); + } + serverSocket.close(); + } + } +} diff --git a/java/src/test/java/com/github/copilot/McpOAuthE2ETest.java b/java/src/test/java/com/github/copilot/McpOAuthE2ETest.java index 8da3b08b47..e721468c00 100644 --- a/java/src/test/java/com/github/copilot/McpOAuthE2ETest.java +++ b/java/src/test/java/com/github/copilot/McpOAuthE2ETest.java @@ -19,8 +19,11 @@ import java.time.Duration; import java.util.List; import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import org.junit.jupiter.api.AfterAll; @@ -33,7 +36,9 @@ import com.github.copilot.generated.rpc.SessionMcpAppsCallToolParams; import com.github.copilot.generated.rpc.McpServerStatus; import com.github.copilot.generated.rpc.SessionMcpListToolsParams; +import com.github.copilot.generated.rpc.SessionMcpOauthHandlePendingRequestParams; import com.github.copilot.rpc.McpAuthInvocation; +import com.github.copilot.rpc.McpAuthRequest; import com.github.copilot.rpc.McpAuthResult; import com.github.copilot.rpc.McpAuthToken; import com.github.copilot.rpc.McpHttpServerConfig; @@ -182,7 +187,7 @@ void testShouldCancelPendingMcpOauthRequest() throws Exception { }).setMcpServers(Map.of(serverName, new McpHttpServerConfig() .setUrl(oauthServer.url() + "/mcp").setTools(List.of("*"))))) .get()) { - waitForMcpServerStatus(session, serverName, McpServerStatus.FAILED, observedRequest); + waitForMcpServerStatus(session, serverName, McpServerStatus.NEEDS_AUTH, observedRequest); } var request = observedRequest.get(); @@ -192,6 +197,62 @@ void testShouldCancelPendingMcpOauthRequest() throws Exception { } } + @Test + void testShouldResolvePendingMcpOauthRequestThroughRpc() throws Exception { + try (var oauthServer = OAuthMcpServer.start(ctx.getRepoRoot())) { + var serverName = "oauth-direct-rpc-mcp"; + var observedRequest = new AtomicReference(); + var pendingHandlerResult = new CompletableFuture(); + + try (var client = ctx.createClient(); + var session = client.createSession(new SessionConfig().setEnableMcpApps(true) + .setOnPermissionRequest(PermissionHandler.APPROVE_ALL) + .setOnMcpAuthRequest((request, invocation) -> { + assertNotNull(invocation); + observedRequest.set(request); + return pendingHandlerResult; + }).setMcpServers(Map.of(serverName, new McpHttpServerConfig() + .setUrl(oauthServer.url() + "/mcp").setTools(List.of("*"))))) + .get()) { + var connected = CompletableFuture.runAsync(() -> { + try { + waitForMcpServerStatus(session, serverName, McpServerStatus.CONNECTED, observedRequest); + } catch (Exception ex) { + throw new CompletionException(ex); + } + }); + + var request = waitForAuthRequest(observedRequest); + assertEquals(serverName, request.serverName()); + assertEquals(oauthServer.url() + "/mcp", request.serverUrl()); + assertEquals(McpOauthRequestReason.INITIAL, request.reason()); + assertNotNull(request.wwwAuthenticateParams()); + assertEquals(oauthServer.url() + "/.well-known/oauth-protected-resource", + request.wwwAuthenticateParams().resourceMetadataUrl()); + assertEquals("mcp.read", request.wwwAuthenticateParams().scope()); + assertEquals("invalid_token", request.wwwAuthenticateParams().error()); + + var handled = session.getRpc().mcp.oauth.handlePendingRequest( + new SessionMcpOauthHandlePendingRequestParams(null, request.requestId(), Map.of("kind", "token", + "accessToken", EXPECTED_TOKEN, "tokenType", "Bearer", "expiresIn", 3600L))) + .get(30, TimeUnit.SECONDS); + assertTrue(handled.success()); + + pendingHandlerResult.complete(McpAuthResult.cancelled()); + connected.get(60, TimeUnit.SECONDS); + var tools = session.getRpc().mcp.listTools(new SessionMcpListToolsParams(null, serverName)).get(30, + TimeUnit.SECONDS); + assertTrue(tools.tools().stream().anyMatch(tool -> "whoami".equals(tool.name()))); + } finally { + pendingHandlerResult.complete(McpAuthResult.cancelled()); + } + + var requests = oauthServer.requests(); + assertTrue( + requests.stream().anyMatch(record -> ("Bearer " + EXPECTED_TOKEN).equals(record.authorization()))); + } + } + private static void callWhoami(CopilotSession session, String serverName, String scenario) throws Exception { var result = session.getRpc().mcp.apps.callTool( new SessionMcpAppsCallToolParams(null, serverName, "whoami", Map.of("scenario", scenario), serverName)) @@ -221,6 +282,18 @@ private static void waitForMcpServerStatus(CopilotSession session, String server + (observedRequest.get() != null)); } + private static McpAuthRequest waitForAuthRequest(AtomicReference observedRequest) throws Exception { + var deadline = System.nanoTime() + TimeUnit.SECONDS.toNanos(30); + while (System.nanoTime() < deadline) { + var request = observedRequest.get(); + if (request != null) { + return request; + } + Thread.sleep(100); + } + throw new AssertionError("Timed out waiting for MCP OAuth request"); + } + @JsonIgnoreProperties(ignoreUnknown = true) private record OAuthMcpRequest(String authorization) { } diff --git a/java/src/test/java/com/github/copilot/RpcServerE2ETest.java b/java/src/test/java/com/github/copilot/RpcServerE2ETest.java new file mode 100644 index 0000000000..2393f334b2 --- /dev/null +++ b/java/src/test/java/com/github/copilot/RpcServerE2ETest.java @@ -0,0 +1,596 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +package com.github.copilot; + +import static org.junit.jupiter.api.Assertions.*; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.time.OffsetDateTime; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.TimeUnit; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import com.github.copilot.generated.rpc.AccountQuotaSnapshot; +import com.github.copilot.generated.rpc.AgentsDiscoverParams; +import com.github.copilot.generated.rpc.AgentsGetDiscoveryPathsParams; +import com.github.copilot.generated.rpc.InstructionsDiscoverParams; +import com.github.copilot.generated.rpc.InstructionsGetDiscoveryPathsParams; +import com.github.copilot.generated.rpc.LlmInferenceHttpResponseChunkError; +import com.github.copilot.generated.rpc.LlmInferenceHttpResponseChunkParams; +import com.github.copilot.generated.rpc.LlmInferenceHttpResponseStartParams; +import com.github.copilot.generated.rpc.LocalSessionMetadataValue; +import com.github.copilot.generated.rpc.McpDiscoverParams; +import com.github.copilot.generated.rpc.PingParams; +import com.github.copilot.generated.rpc.SecretsAddFilterValuesParams; +import com.github.copilot.generated.rpc.ServerSkill; +import com.github.copilot.generated.rpc.SessionContext; +import com.github.copilot.generated.rpc.SessionFsSetProviderCapabilities; +import com.github.copilot.generated.rpc.SessionFsSetProviderConventions; +import com.github.copilot.generated.rpc.SessionFsSetProviderParams; +import com.github.copilot.generated.rpc.SessionsBulkDeleteParams; +import com.github.copilot.generated.rpc.SessionsCheckInUseParams; +import com.github.copilot.generated.rpc.SessionsCloseParams; +import com.github.copilot.generated.rpc.SessionsConnectParams; +import com.github.copilot.generated.rpc.SessionsEnrichMetadataParams; +import com.github.copilot.generated.rpc.SessionsFindByPrefixParams; +import com.github.copilot.generated.rpc.SessionsFindByTaskIdParams; +import com.github.copilot.generated.rpc.SessionsGetEventFilePathParams; +import com.github.copilot.generated.rpc.SessionsGetLastForContextParams; +import com.github.copilot.generated.rpc.SessionsGetPersistedRemoteSteerableParams; +import com.github.copilot.generated.rpc.SessionsLoadDeferredRepoHooksParams; +import com.github.copilot.generated.rpc.SessionsPruneOldParams; +import com.github.copilot.generated.rpc.SessionsReleaseLockParams; +import com.github.copilot.generated.rpc.SessionsReloadPluginHooksParams; +import com.github.copilot.generated.rpc.SessionsSaveParams; +import com.github.copilot.generated.rpc.SessionsSetAdditionalPluginsParams; +import com.github.copilot.generated.rpc.SkillsConfigSetDisabledSkillsParams; +import com.github.copilot.generated.rpc.SkillsDiscoverParams; +import com.github.copilot.generated.rpc.SkillsGetDiscoveryPathsParams; +import com.github.copilot.generated.rpc.ToolsListParams; +import com.github.copilot.rpc.CopilotClientOptions; +import com.github.copilot.rpc.InfiniteSessionConfig; +import com.github.copilot.rpc.PermissionHandler; +import com.github.copilot.rpc.SessionConfig; + +class RpcServerE2ETest { + + private static final long TIMEOUT_SECONDS = 30; + private static final long SESSION_PERSISTENCE_TIMEOUT_MILLIS = 30_000; + private static E2ETestContext ctx; + + @BeforeAll + static void setup() throws Exception { + ctx = E2ETestContext.create(); + } + + @AfterAll + static void teardown() throws Exception { + if (ctx != null) { + ctx.close(); + } + } + + @Test + void testShouldCallRpcPingWithTypedParamsAndResult() throws Exception { + ctx.configureForTest("rpc_server", "should_call_rpc_ping_with_typed_params_and_result"); + + try (var client = ctx.createClient()) { + client.start().get(TIMEOUT_SECONDS, TimeUnit.SECONDS); + + var result = client.getRpc().ping(new PingParams("typed rpc test")).get(TIMEOUT_SECONDS, TimeUnit.SECONDS); + + assertEquals("pong: typed rpc test", result.message()); + assertNotNull(result.timestamp()); + assertNotNull(result.protocolVersion()); + assertTrue(result.protocolVersion() >= 0); + } + } + + @Test + void testShouldRejectLlmInferenceResponseFramesForMissingRequest() throws Exception { + ctx.initializeProxy(); + + try (var client = ctx.createClient()) { + client.start().get(TIMEOUT_SECONDS, TimeUnit.SECONDS); + var requestId = "missing-llm-inference-request"; + + var start = client.getRpc().llmInference + .httpResponseStart(new LlmInferenceHttpResponseStartParams(requestId, 200L, "OK", + Map.of("content-type", List.of("text/event-stream")))) + .get(TIMEOUT_SECONDS, TimeUnit.SECONDS); + assertFalse(start.accepted()); + + var chunk = client.getRpc().llmInference + .httpResponseChunk( + new LlmInferenceHttpResponseChunkParams(requestId, "data: {}\n\n", false, false, null)) + .get(TIMEOUT_SECONDS, TimeUnit.SECONDS); + assertFalse(chunk.accepted()); + + var error = client.getRpc().llmInference.httpResponseChunk(new LlmInferenceHttpResponseChunkParams( + requestId, "", null, true, + new LlmInferenceHttpResponseChunkError("No pending LLM inference request.", "missing_request"))) + .get(TIMEOUT_SECONDS, TimeUnit.SECONDS); + assertFalse(error.accepted()); + } + } + + @Test + void testShouldCallRpcModelsListWithTypedResult() throws Exception { + ctx.configureForTest("rpc_server", "should_call_rpc_models_list_with_typed_result"); + var token = "rpc-models-token"; + configureAuthenticatedUser(token, null); + + try (var client = createAuthenticatedClient(token)) { + client.start().get(TIMEOUT_SECONDS, TimeUnit.SECONDS); + + var result = client.getRpc().models.list().get(TIMEOUT_SECONDS, TimeUnit.SECONDS); + + assertNotNull(result.models()); + assertTrue(result.models().stream().anyMatch(model -> "claude-sonnet-4.5".equals(model.id()))); + result.models().forEach(model -> { + assertFalse(model.id().isBlank()); + assertFalse(model.name().isBlank()); + }); + } + } + + @Test + void testShouldCallRpcAccountGetQuotaWhenAuthenticated() throws Exception { + ctx.configureForTest("rpc_server", "should_call_rpc_account_get_quota_when_authenticated"); + var token = "rpc-quota-token"; + configureAuthenticatedUser(token, Map.of("chat", Map.of("entitlement", 100, "overage_count", 2, + "overage_permitted", true, "percent_remaining", 75, "timestamp_utc", "2026-04-30T00:00:00Z"))); + + try (var client = createAuthenticatedClient(token)) { + client.start().get(TIMEOUT_SECONDS, TimeUnit.SECONDS); + + var result = client.getRpc().account.getQuota().get(TIMEOUT_SECONDS, TimeUnit.SECONDS); + + assertNotNull(result.quotaSnapshots()); + var chatQuota = result.quotaSnapshots().get("chat"); + assertNotNull(chatQuota); + assertQuota(chatQuota); + } + } + + @Test + void testShouldCallRpcToolsListWithTypedResult() throws Exception { + ctx.configureForTest("rpc_server", "should_call_rpc_tools_list_with_typed_result"); + + try (var client = ctx.createClient()) { + client.start().get(TIMEOUT_SECONDS, TimeUnit.SECONDS); + + var result = client.getRpc().tools.list(new ToolsListParams(null)).get(TIMEOUT_SECONDS, TimeUnit.SECONDS); + + assertNotNull(result.tools()); + assertFalse(result.tools().isEmpty()); + result.tools().forEach(tool -> assertFalse(tool.name().isBlank())); + } + } + + @Test + void testShouldCallRpcSessionFsSetProviderWithTypedResult() throws Exception { + ctx.initializeProxy(); + + try (var client = ctx.createClient()) { + client.start().get(TIMEOUT_SECONDS, TimeUnit.SECONDS); + + var result = client.getRpc().sessionFs + .setProvider(new SessionFsSetProviderParams(ctx.getWorkDir().toString(), + ctx.getWorkDir().resolve("session-state").toString(), currentPathConventions(), + new SessionFsSetProviderCapabilities(true))) + .get(TIMEOUT_SECONDS, TimeUnit.SECONDS); + + assertTrue(result.success()); + } + } + + @Test + void testShouldAddSecretFilterValues() throws Exception { + ctx.initializeProxy(); + var env = new HashMap<>(ctx.getEnvironment()); + env.put("COPILOT_ENABLE_SECRET_FILTERING", "true"); + + try (var client = ctx.createClient(new CopilotClientOptions().setEnvironment(env))) { + client.start().get(TIMEOUT_SECONDS, TimeUnit.SECONDS); + var secret = "rpc-secret-" + UUID.randomUUID().toString().replace("-", ""); + + var result = client.getRpc().secrets.addFilterValues(new SecretsAddFilterValuesParams(List.of(secret))) + .get(TIMEOUT_SECONDS, TimeUnit.SECONDS); + + assertTrue(result.ok()); + } + } + + @Test + void testShouldListFindAndInspectPersistedSessionState() throws Exception { + ctx.initializeProxy(); + + try (var client = ctx.createClient()) { + var requestedSessionId = UUID.randomUUID().toString(); + var workingDirectory = createUniqueWorkDirectory("server-rpc-list"); + var missingTaskId = "missing-task-" + UUID.randomUUID().toString().replace("-", ""); + var missingSessionId = UUID.randomUUID().toString(); + + try (var session = client.createSession(persistedSessionConfig(requestedSessionId, workingDirectory)) + .get(TIMEOUT_SECONDS, TimeUnit.SECONDS)) { + var sessionId = session.getSessionId(); + session.log("SERVER_RPC_LIST_READY").get(TIMEOUT_SECONDS, TimeUnit.SECONDS); + saveSession(client, sessionId); + assertNull(client.getRpc().sessions.close(new SessionsCloseParams(sessionId)).get(TIMEOUT_SECONDS, + TimeUnit.SECONDS)); + + var listed = client.getRpc().sessions.list().get(TIMEOUT_SECONDS, TimeUnit.SECONDS); + assertNotNull(listed.sessions()); + + var byPrefix = client.getRpc().sessions + .findByPrefix(new SessionsFindByPrefixParams(sessionId.substring(0, 8))) + .get(TIMEOUT_SECONDS, TimeUnit.SECONDS); + assertTrue(byPrefix.sessionId() == null || sessionId.equals(byPrefix.sessionId())); + + var byTaskId = client.getRpc().sessions.findByTaskId(new SessionsFindByTaskIdParams(missingTaskId)) + .get(TIMEOUT_SECONDS, TimeUnit.SECONDS); + assertNull(byTaskId.sessionId()); + + var lastForContext = client.getRpc().sessions + .getLastForContext(new SessionsGetLastForContextParams( + new SessionContext(workingDirectory.toString(), null, null, null, null))) + .get(TIMEOUT_SECONDS, TimeUnit.SECONDS); + assertTrue(lastForContext.sessionId() == null || sessionId.equals(lastForContext.sessionId())); + + var eventFile = client.getRpc().sessions.getEventFilePath(new SessionsGetEventFilePathParams(sessionId)) + .get(TIMEOUT_SECONDS, TimeUnit.SECONDS); + assertTrue(eventFile.filePath().endsWith("events.jsonl")); + + var remoteSteerable = client.getRpc().sessions + .getPersistedRemoteSteerable(new SessionsGetPersistedRemoteSteerableParams(sessionId)) + .get(TIMEOUT_SECONDS, TimeUnit.SECONDS); + assertNull(remoteSteerable.remoteSteerable()); + + var sizes = client.getRpc().sessions.getSizes().get(TIMEOUT_SECONDS, TimeUnit.SECONDS); + assertNotNull(sizes.sizes()); + if (sizes.sizes().containsKey(sessionId)) { + assertTrue(sizes.sizes().get(sessionId) >= 0); + } + + var inUse = client.getRpc().sessions + .checkInUse(new SessionsCheckInUseParams(List.of(sessionId, missingSessionId))) + .get(TIMEOUT_SECONDS, TimeUnit.SECONDS); + assertNotNull(inUse.inUse()); + assertFalse(inUse.inUse().contains(missingSessionId)); + } + } + } + + @Test + void testShouldEnrichBasicSessionMetadata() throws Exception { + ctx.initializeProxy(); + + try (var client = ctx.createClient()) { + var requestedSessionId = UUID.randomUUID().toString(); + var workingDirectory = createUniqueWorkDirectory("server-rpc-enrich"); + + try (var session = client.createSession(persistedSessionConfig(requestedSessionId, workingDirectory)) + .get(TIMEOUT_SECONDS, TimeUnit.SECONDS)) { + var sessionId = session.getSessionId(); + session.log("SERVER_RPC_ENRICH_READY").get(TIMEOUT_SECONDS, TimeUnit.SECONDS); + saveSession(client, sessionId); + + var now = OffsetDateTime.now().toString(); + var basic = new LocalSessionMetadataValue(sessionId, now, now, null, "Basic metadata", null, false, + null, new SessionContext(workingDirectory.toString(), null, null, null, null), null); + + var result = client.getRpc().sessions.enrichMetadata(new SessionsEnrichMetadataParams(List.of(basic))) + .get(TIMEOUT_SECONDS, TimeUnit.SECONDS); + + assertNotNull(result.sessions()); + assertEquals(1, result.sessions().size()); + var enriched = result.sessions().get(0); + assertEquals(sessionId, enriched.sessionId()); + assertNotNull(enriched.context()); + assertTrue(pathsEqual(workingDirectory.toString(), enriched.context().cwd())); + assertFalse(enriched.isRemote()); + } + } + } + + @Test + void testShouldCloseActiveSessionAndReleaseLock() throws Exception { + ctx.initializeProxy(); + + try (var client = ctx.createClient()) { + var requestedSessionId = UUID.randomUUID().toString(); + var workingDirectory = createUniqueWorkDirectory("server-rpc-close"); + + try (var session = client.createSession(persistedSessionConfig(requestedSessionId, workingDirectory)) + .get(TIMEOUT_SECONDS, TimeUnit.SECONDS)) { + var sessionId = session.getSessionId(); + session.log("SERVER_RPC_CLOSE_READY").get(TIMEOUT_SECONDS, TimeUnit.SECONDS); + saveSession(client, sessionId); + + var close = client.getRpc().sessions.close(new SessionsCloseParams(sessionId)).get(TIMEOUT_SECONDS, + TimeUnit.SECONDS); + assertNull(close); + + var release = client.getRpc().sessions.releaseLock(new SessionsReleaseLockParams(sessionId)) + .get(TIMEOUT_SECONDS, TimeUnit.SECONDS); + assertNull(release); + + var inUse = client.getRpc().sessions.checkInUse(new SessionsCheckInUseParams(List.of(sessionId))) + .get(TIMEOUT_SECONDS, TimeUnit.SECONDS); + assertFalse(inUse.inUse().contains(sessionId)); + } + } + } + + @Test + void testShouldPruneDryRunAndBulkDeletePersistedSession() throws Exception { + ctx.initializeProxy(); + + try (var client = ctx.createClient()) { + var requestedSessionId = UUID.randomUUID().toString(); + var missingSessionId = UUID.randomUUID().toString(); + var workingDirectory = createUniqueWorkDirectory("server-rpc-delete"); + + var session = client.createSession(persistedSessionConfig(requestedSessionId, workingDirectory)) + .get(TIMEOUT_SECONDS, TimeUnit.SECONDS); + try { + var sessionId = session.getSessionId(); + saveSession(client, sessionId); + client.getRpc().sessions.close(new SessionsCloseParams(sessionId)).get(TIMEOUT_SECONDS, + TimeUnit.SECONDS); + + var prune = client.getRpc().sessions.pruneOld(new SessionsPruneOldParams(0L, true, true, List.of())) + .get(TIMEOUT_SECONDS, TimeUnit.SECONDS); + assertTrue(prune.dryRun()); + assertNotNull(prune.candidates()); + assertNotNull(prune.deleted()); + assertFalse(prune.deleted().contains(sessionId)); + assertFalse(prune.candidates().contains(missingSessionId)); + assertNotNull(prune.freedBytes()); + assertTrue(prune.freedBytes() >= 0); + + var delete = client.getRpc().sessions + .bulkDelete(new SessionsBulkDeleteParams(List.of(sessionId, missingSessionId))) + .get(TIMEOUT_SECONDS, TimeUnit.SECONDS); + assertTrue(delete.freedBytes().containsKey(sessionId)); + assertTrue(delete.freedBytes().get(sessionId) >= 0); + if (delete.freedBytes().containsKey(missingSessionId)) { + assertEquals(0L, delete.freedBytes().get(missingSessionId)); + } + + waitForSessionAbsent(client, sessionId); + } finally { + session.close(); + } + } + } + + @Test + void testShouldSetAdditionalPluginsAndReloadDeferredHooks() throws Exception { + ctx.initializeProxy(); + + try (var client = ctx.createClient()) { + client.start().get(TIMEOUT_SECONDS, TimeUnit.SECONDS); + assertNull(client.getRpc().sessions.setAdditionalPlugins(new SessionsSetAdditionalPluginsParams(List.of())) + .get(TIMEOUT_SECONDS, TimeUnit.SECONDS)); + + var requestedSessionId = UUID.randomUUID().toString(); + var workingDirectory = createUniqueWorkDirectory("server-rpc-hooks"); + + try (var session = client.createSession( + persistedSessionConfig(requestedSessionId, workingDirectory).setEnableConfigDiscovery(false)) + .get(TIMEOUT_SECONDS, TimeUnit.SECONDS)) { + var sessionId = session.getSessionId(); + var reload = client.getRpc().sessions + .reloadPluginHooks(new SessionsReloadPluginHooksParams(sessionId, true)) + .get(TIMEOUT_SECONDS, TimeUnit.SECONDS); + assertNull(reload); + + var loaded = client.getRpc().sessions + .loadDeferredRepoHooks(new SessionsLoadDeferredRepoHooksParams(sessionId)) + .get(TIMEOUT_SECONDS, TimeUnit.SECONDS); + assertNotNull(loaded.startupPrompts()); + assertEquals(0L, loaded.hookCount()); + assertTrue(loaded.startupPrompts().isEmpty()); + } finally { + client.getRpc().sessions.setAdditionalPlugins(new SessionsSetAdditionalPluginsParams(List.of())) + .get(TIMEOUT_SECONDS, TimeUnit.SECONDS); + } + } + } + + @Test + void testShouldReportImplementedErrorWhenConnectingUnknownRemoteSession() throws Exception { + ctx.initializeProxy(); + + try (var client = ctx.createClient()) { + client.start().get(TIMEOUT_SECONDS, TimeUnit.SECONDS); + var remoteSessionId = "remote-" + UUID.randomUUID().toString().replace("-", ""); + + var ex = assertThrows(Exception.class, () -> client.getRpc().sessions + .connect(new SessionsConnectParams(remoteSessionId)).get(TIMEOUT_SECONDS, TimeUnit.SECONDS)); + var text = ex.toString(); + assertFalse(text.toLowerCase().contains("unhandled method sessions.connect")); + assertTrue(text.toLowerCase().contains("session")); + } + } + + @Test + void testShouldDiscoverServerMcpSkillsAgentsAndInstructions() throws Exception { + ctx.configureForTest("rpc_server", "should_discover_server_mcp_and_skills"); + + try (var client = ctx.createClient()) { + client.start().get(TIMEOUT_SECONDS, TimeUnit.SECONDS); + var workDir = ctx.getWorkDir().toString(); + var skillName = "server-rpc-skill-" + UUID.randomUUID().toString().replace("-", ""); + var skillDirectory = createSkillDirectory(skillName, "Skill discovered by server-scoped RPC tests."); + + var mcp = client.getRpc().mcp.discover(new McpDiscoverParams(workDir)).get(TIMEOUT_SECONDS, + TimeUnit.SECONDS); + assertNotNull(mcp.servers()); + + var skills = client.getRpc().skills + .discover(new SkillsDiscoverParams(null, List.of(skillDirectory.toString()), null)) + .get(TIMEOUT_SECONDS, TimeUnit.SECONDS); + var discoveredSkill = findSkill(skills.skills(), skillName); + assertEquals("Skill discovered by server-scoped RPC tests.", discoveredSkill.description()); + assertTrue(discoveredSkill.enabled()); + assertTrue(discoveredSkill.path().replace('\\', '/').endsWith(skillName + "/SKILL.md")); + + var skillPaths = client.getRpc().skills + .getDiscoveryPaths(new SkillsGetDiscoveryPathsParams(List.of(workDir), true)) + .get(TIMEOUT_SECONDS, TimeUnit.SECONDS); + var projectSkillPath = skillPaths.paths().stream().filter( + path -> pathsEqual(workDir, path.projectPath()) && Boolean.TRUE.equals(path.preferredForCreation())) + .findFirst().orElseThrow(() -> new AssertionError("Expected project skill discovery path")); + assertFalse(projectSkillPath.path().isBlank()); + + var agents = client.getRpc().agents.discover(new AgentsDiscoverParams(List.of(workDir), true)) + .get(TIMEOUT_SECONDS, TimeUnit.SECONDS); + assertNotNull(agents.agents()); + agents.agents().forEach(agent -> assertFalse(agent.name().isBlank())); + + var agentPaths = client.getRpc().agents + .getDiscoveryPaths(new AgentsGetDiscoveryPathsParams(List.of(workDir), true)) + .get(TIMEOUT_SECONDS, TimeUnit.SECONDS); + var projectAgentPath = agentPaths.paths().stream().filter( + path -> pathsEqual(workDir, path.projectPath()) && Boolean.TRUE.equals(path.preferredForCreation())) + .findFirst().orElseThrow(() -> new AssertionError("Expected project agent discovery path")); + assertFalse(projectAgentPath.path().isBlank()); + + var instructions = client.getRpc().instructions + .discover(new InstructionsDiscoverParams(List.of(workDir), true)) + .get(TIMEOUT_SECONDS, TimeUnit.SECONDS); + assertNotNull(instructions.sources()); + instructions.sources().forEach(source -> { + assertFalse(source.id().isBlank()); + assertFalse(source.label().isBlank()); + assertFalse(source.sourcePath().isBlank()); + }); + + var instructionPaths = client.getRpc().instructions + .getDiscoveryPaths(new InstructionsGetDiscoveryPathsParams(List.of(workDir), true)) + .get(TIMEOUT_SECONDS, TimeUnit.SECONDS); + assertFalse(instructionPaths.paths().isEmpty()); + assertTrue(instructionPaths.paths().stream().anyMatch(path -> pathsEqual(workDir, path.projectPath()))); + instructionPaths.paths().forEach(path -> assertFalse(path.path().isBlank())); + + try { + client.getRpc().skills.config + .setDisabledSkills(new SkillsConfigSetDisabledSkillsParams(List.of(skillName))) + .get(TIMEOUT_SECONDS, TimeUnit.SECONDS); + var disabledSkills = client.getRpc().skills + .discover(new SkillsDiscoverParams(null, List.of(skillDirectory.toString()), null)) + .get(TIMEOUT_SECONDS, TimeUnit.SECONDS); + var disabledSkill = findSkill(disabledSkills.skills(), skillName); + assertFalse(disabledSkill.enabled()); + } finally { + client.getRpc().skills.config.setDisabledSkills(new SkillsConfigSetDisabledSkillsParams(List.of())) + .get(TIMEOUT_SECONDS, TimeUnit.SECONDS); + } + } + } + + private static CopilotClient createAuthenticatedClient(String token) throws Exception { + return ctx.createClient(new CopilotClientOptions().setGitHubToken(token)); + } + + private static void configureAuthenticatedUser(String token, Map quotaSnapshots) throws Exception { + var user = new HashMap(); + user.put("login", "rpc-user"); + user.put("copilot_plan", "individual_pro"); + user.put("endpoints", Map.of("api", ctx.getProxyUrl(), "telemetry", "https://localhost:1/telemetry")); + user.put("analytics_tracking_id", "rpc-user-tracking-id"); + if (quotaSnapshots != null) { + user.put("quota_snapshots", quotaSnapshots); + } + ctx.setCopilotUserByToken(token, user); + } + + private static void assertQuota(AccountQuotaSnapshot chatQuota) { + assertEquals(100L, chatQuota.entitlementRequests()); + assertEquals(25L, chatQuota.usedRequests()); + assertEquals(75.0, chatQuota.remainingPercentage()); + assertEquals(2.0, chatQuota.overage()); + assertTrue(chatQuota.usageAllowedWithExhaustedQuota()); + assertTrue(chatQuota.overageAllowedWithExhaustedQuota()); + assertEquals(OffsetDateTime.parse("2026-04-30T00:00:00Z"), chatQuota.resetDate()); + } + + private static SessionFsSetProviderConventions currentPathConventions() { + return isWindows() ? SessionFsSetProviderConventions.WINDOWS : SessionFsSetProviderConventions.POSIX; + } + + private static Path createUniqueWorkDirectory(String prefix) throws Exception { + var directory = ctx.getWorkDir().resolve(prefix + "-" + UUID.randomUUID().toString().replace("-", "")); + Files.createDirectories(directory); + return directory; + } + + private static SessionConfig persistedSessionConfig(String sessionId, Path workingDirectory) { + return new SessionConfig().setSessionId(sessionId).setWorkingDirectory(workingDirectory.toString()) + .setInfiniteSessions(new InfiniteSessionConfig().setEnabled(true)) + .setOnPermissionRequest(PermissionHandler.APPROVE_ALL); + } + + private static void saveSession(CopilotClient client, String sessionId) throws Exception { + var save = client.getRpc().sessions.save(new SessionsSaveParams(sessionId)).get(TIMEOUT_SECONDS, + TimeUnit.SECONDS); + assertNull(save); + } + + private static void waitForSessionAbsent(CopilotClient client, String sessionId) throws Exception { + var deadline = System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(SESSION_PERSISTENCE_TIMEOUT_MILLIS); + do { + var list = client.getRpc().sessions.list().get(TIMEOUT_SECONDS, TimeUnit.SECONDS); + assertNotNull(list.sessions()); + var present = list.sessions().stream() + .anyMatch(session -> session instanceof Map map && sessionId.equals(map.get("sessionId"))); + if (!present) { + return; + } + Thread.sleep(100); + } while (System.nanoTime() < deadline); + + throw new AssertionError("Timed out waiting for session '" + sessionId + "' to be removed."); + } + + private static Path createSkillDirectory(String skillName, String description) throws Exception { + var skillsDir = ctx.getWorkDir().resolve("server-rpc-skills") + .resolve(UUID.randomUUID().toString().replace("-", "")); + var skillSubdir = skillsDir.resolve(skillName); + Files.createDirectories(skillSubdir); + Files.writeString(skillSubdir.resolve("SKILL.md"), "---\nname: " + skillName + "\ndescription: " + description + + "\n---\n\n# " + skillName + "\n\nThis skill is used by RPC E2E tests.\n"); + return skillsDir; + } + + private static ServerSkill findSkill(List skills, String name) { + return skills.stream().filter(skill -> name.equals(skill.name())).findFirst() + .orElseThrow(() -> new AssertionError("Expected to discover skill " + name)); + } + + private static boolean pathsEqual(String expected, String actual) { + if (actual == null) { + return false; + } + + var expectedPath = Path.of(expected).toAbsolutePath().normalize().toString(); + var actualPath = Path.of(actual).toAbsolutePath().normalize().toString(); + return isWindows() ? expectedPath.equalsIgnoreCase(actualPath) : expectedPath.equals(actualPath); + } + + private static boolean isWindows() { + return System.getProperty("os.name").toLowerCase().contains("win"); + } +} diff --git a/java/src/test/java/com/github/copilot/RpcServerMiscE2ETest.java b/java/src/test/java/com/github/copilot/RpcServerMiscE2ETest.java new file mode 100644 index 0000000000..1db801d841 --- /dev/null +++ b/java/src/test/java/com/github/copilot/RpcServerMiscE2ETest.java @@ -0,0 +1,132 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +package com.github.copilot; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import com.github.copilot.generated.rpc.AccountAllUsers; +import com.github.copilot.generated.rpc.AccountLoginParams; +import com.github.copilot.generated.rpc.AccountLogoutParams; +import com.github.copilot.generated.rpc.UserSettingMetadata; +import com.github.copilot.generated.rpc.UserSettingsSetParams; +import com.github.copilot.rpc.CopilotClientOptions; + +class RpcServerMiscE2ETest { + + private static E2ETestContext ctx; + + @BeforeAll + static void setup() throws Exception { + ctx = E2ETestContext.create(); + } + + @AfterAll + static void teardown() throws Exception { + if (ctx != null) { + ctx.close(); + } + } + + @Test + void testShouldGetSetAndClearUserSettings() throws Exception { + ctx.configureForTest("rpc_server_misc", "should_get_set_and_clear_user_settings"); + + try (var client = ctx.createClient()) { + client.start().get(30, TimeUnit.SECONDS); + var before = client.getRpc().user.settings.get().get(30, TimeUnit.SECONDS); + var entry = before.settings().entrySet().stream().filter(e -> isBooleanSetting(e.getValue())).findFirst() + .orElseThrow(() -> new AssertionError("Expected at least one boolean user setting")); + var key = entry.getKey(); + var original = settingBoolean(entry.getValue()); + var updated = !original; + + var set = client.getRpc().user.settings.set(new UserSettingsSetParams(Map.of(key, updated))).get(30, + TimeUnit.SECONDS); + assertTrue(set.shadowedKeys().isEmpty()); + client.getRpc().user.settings.reload().get(30, TimeUnit.SECONDS); + var afterSet = client.getRpc().user.settings.get().get(30, TimeUnit.SECONDS); + assertEquals(updated, settingBoolean(afterSet.settings().get(key))); + assertFalse(afterSet.settings().get(key).isDefault()); + + var clearSettings = new HashMap(); + clearSettings.put(key, null); + var clear = client.getRpc().user.settings.set(new UserSettingsSetParams(clearSettings)).get(30, + TimeUnit.SECONDS); + assertTrue(clear.shadowedKeys().isEmpty()); + client.getRpc().user.settings.reload().get(30, TimeUnit.SECONDS); + var afterClear = client.getRpc().user.settings.get().get(30, TimeUnit.SECONDS); + assertTrue(afterClear.settings().get(key).isDefault()); + } + } + + @Test + void testShouldLoginListGetCurrentAuthAndLogoutAccount() throws Exception { + ctx.configureForTest("rpc_server_misc", "should_login_list_getcurrentauth_and_logout_account"); + var token = "java-account-token"; + var login = "java-account-user"; + ctx.setCopilotUserByToken(token, login, "individual_pro", ctx.getProxyUrl(), "https://localhost:1/telemetry", + "java-account-tracking-id"); + + var env = new HashMap<>(ctx.getEnvironment()); + env.put("GH_TOKEN", ""); + env.put("GITHUB_TOKEN", ""); + env.put("COPILOT_SDK_AUTH_TOKEN", ""); + + try (var client = new CopilotClient( + new CopilotClientOptions().setCliPath(ctx.getCliPath()).setCwd(ctx.getWorkDir().toString()) + .setEnvironment(env).setGitHubToken("").setUseLoggedInUser(false))) { + client.start().get(30, TimeUnit.SECONDS); + + var initial = client.getRpc().account.getCurrentAuth().get(30, TimeUnit.SECONDS); + assertNull(initial.authInfo()); + + var loginResult = client.getRpc().account.login(new AccountLoginParams("https://github.com", login, token)) + .get(30, TimeUnit.SECONDS); + assertNotNull(loginResult); + + var current = client.getRpc().account.getCurrentAuth().get(30, TimeUnit.SECONDS); + assertNull(current.authErrors()); + assertInstanceOf(Map.class, current.authInfo()); + @SuppressWarnings("unchecked") + var authInfo = (Map) current.authInfo(); + assertEquals(login, authInfo.get("login")); + assertEquals("https://github.com", authInfo.get("host")); + + var users = client.getRpc().account.getAllUsers().get(30, TimeUnit.SECONDS); + users.stream().filter(user -> accountLogin(user).equals(login)).findFirst() + .ifPresent(user -> assertEquals(token, user.token())); + + var logout = client.getRpc().account.logout(new AccountLogoutParams(authInfo)).get(30, TimeUnit.SECONDS); + assertFalse(logout.hasMoreUsers()); + assertNull(client.getRpc().account.getCurrentAuth().get(30, TimeUnit.SECONDS).authInfo()); + } + } + + private static boolean isBooleanSetting(UserSettingMetadata metadata) { + return metadata.value() instanceof Boolean || metadata.default_() instanceof Boolean; + } + + private static boolean settingBoolean(UserSettingMetadata metadata) { + if (metadata.value() instanceof Boolean value) { + return value; + } + return (Boolean) metadata.default_(); + } + + private static String accountLogin(AccountAllUsers user) { + if (user.authInfo() instanceof Map authInfo) { + return String.valueOf(authInfo.get("login")); + } + return ""; + } +} diff --git a/java/src/test/java/com/github/copilot/RpcSessionStateExtrasE2ETest.java b/java/src/test/java/com/github/copilot/RpcSessionStateExtrasE2ETest.java new file mode 100644 index 0000000000..83365455e7 --- /dev/null +++ b/java/src/test/java/com/github/copilot/RpcSessionStateExtrasE2ETest.java @@ -0,0 +1,155 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +package com.github.copilot; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import com.github.copilot.generated.rpc.NamedProviderConfig; +import com.github.copilot.generated.rpc.ProviderConfigType; +import com.github.copilot.generated.rpc.ProviderConfigWireApi; +import com.github.copilot.generated.rpc.ProviderModelConfig; +import com.github.copilot.generated.rpc.SessionCompletionsRequestParams; +import com.github.copilot.generated.rpc.SessionMetadataGetContextHeaviestMessagesParams; +import com.github.copilot.generated.rpc.SessionModelSwitchToParams; +import com.github.copilot.generated.rpc.SessionProviderAddParams; +import com.github.copilot.generated.rpc.SessionToolsUpdateSubagentSettingsParams; +import com.github.copilot.generated.rpc.SessionVisibilitySetParams; +import com.github.copilot.generated.rpc.SessionVisibilityStatus; +import com.github.copilot.generated.rpc.SubagentSettingsEntry; +import com.github.copilot.generated.rpc.SubagentSettingsEntryContextTier; +import com.github.copilot.rpc.MessageOptions; +import com.github.copilot.rpc.PermissionHandler; +import com.github.copilot.rpc.SessionConfig; + +class RpcSessionStateExtrasE2ETest { + + private static E2ETestContext ctx; + + @BeforeAll + static void setup() throws Exception { + ctx = E2ETestContext.create(); + } + + @AfterAll + static void teardown() throws Exception { + if (ctx != null) { + ctx.close(); + } + } + + @Test + void testShouldAddByokProviderAndModelAtRuntime() throws Exception { + ctx.configureForTest("rpc_session_state_extras", "should_add_byok_provider_and_model_at_runtime"); + + try (var client = ctx.createClient()) { + try (var session = client + .createSession(new SessionConfig().setOnPermissionRequest(PermissionHandler.APPROVE_ALL)).get()) { + var result = session.getRpc().provider.add(new SessionProviderAddParams(null, + List.of(new NamedProviderConfig("java-e2e-provider", ProviderConfigType.OPENAI, + ProviderConfigWireApi.COMPLETIONS, null, "https://models.example.test/v1", + "provider-key", null, null, Map.of("x-provider", "java"), null)), + List.of(new ProviderModelConfig("small", "java-e2e-provider", null, null, "Java Added Model", + 4096.0, null, null, null)))) + .get(30, TimeUnit.SECONDS); + assertEquals(1, result.models().size()); + + var selectionId = "java-e2e-provider/small"; + session.getRpc().model + .switchTo(new SessionModelSwitchToParams(null, selectionId, null, null, null, null, null)) + .get(30, TimeUnit.SECONDS); + var current = session.getRpc().model.getCurrent().get(30, TimeUnit.SECONDS); + assertEquals(selectionId, current.modelId()); + } + } + } + + @Test + void testShouldReturnEmptyCompletionsWhenHostDoesNotProvideThem() throws Exception { + ctx.configureForTest("rpc_session_state_extras", + "should_return_empty_completions_when_host_does_not_provide_them"); + + try (var client = ctx.createClient()) { + try (var session = client + .createSession(new SessionConfig().setOnPermissionRequest(PermissionHandler.APPROVE_ALL)).get()) { + var result = session.getRpc().completions + .request(new SessionCompletionsRequestParams(null, "Use @ to mention context", 5L)) + .get(30, TimeUnit.SECONDS); + assertTrue(result.items().isEmpty()); + } + } + } + + @Test + void testShouldReportVisibilityAsUnsyncedForLocalSession() throws Exception { + ctx.configureForTest("rpc_session_state_extras", "should_report_visibility_as_unsynced_for_local_session"); + + try (var client = ctx.createClient()) { + try (var session = client + .createSession(new SessionConfig().setOnPermissionRequest(PermissionHandler.APPROVE_ALL)).get()) { + var set = session.getRpc().visibility + .set(new SessionVisibilitySetParams(null, SessionVisibilityStatus.UNSHARED)) + .get(30, TimeUnit.SECONDS); + assertFalse(set.synced()); + assertNull(set.status()); + assertNull(set.shareUrl()); + + var get = session.getRpc().visibility.get().get(30, TimeUnit.SECONDS); + assertFalse(get.synced()); + assertNull(get.status()); + assertNull(get.shareUrl()); + } + } + } + + @Test + void testShouldGetContextAttributionAndHeaviestMessagesAfterTurn() throws Exception { + ctx.configureForTest("rpc_session_state_extras", + "should_get_context_attribution_and_heaviest_messages_after_turn"); + + try (var client = ctx.createClient()) { + try (var session = client + .createSession(new SessionConfig().setOnPermissionRequest(PermissionHandler.APPROVE_ALL)).get()) { + var answer = session.sendAndWait(new MessageOptions().setPrompt("Say CONTEXT_METADATA_OK exactly.")) + .get(60, TimeUnit.SECONDS); + assertTrue(answer.getData().content().contains("CONTEXT_METADATA_OK")); + + var attribution = session.getRpc().metadata.getContextAttribution().get(30, TimeUnit.SECONDS); + assertNotNull(attribution.contextAttribution()); + var heaviest = session.getRpc().metadata + .getContextHeaviestMessages(new SessionMetadataGetContextHeaviestMessagesParams(null, 5L)) + .get(30, TimeUnit.SECONDS); + assertTrue(heaviest.totalTokens() >= 0); + } + } + } + + @Test + void testShouldUpdateAndClearLiveSubagentSettings() throws Exception { + ctx.configureForTest("rpc_session_state_extras", "should_update_and_clear_live_subagent_settings"); + + try (var client = ctx.createClient()) { + try (var session = client + .createSession(new SessionConfig().setOnPermissionRequest(PermissionHandler.APPROVE_ALL)).get()) { + session.getRpc().tools.updateSubagentSettings(new SessionToolsUpdateSubagentSettingsParams(null, + new SessionToolsUpdateSubagentSettingsParams.SessionToolsUpdateSubagentSettingsParamsSubagents( + Map.of("general-purpose", + new SubagentSettingsEntry("gpt-5-mini", "low", + SubagentSettingsEntryContextTier.LONG_CONTEXT)), + List.of("legacy-agent"), null, null))) + .get(30, TimeUnit.SECONDS); + session.getRpc().tools.updateSubagentSettings(new SessionToolsUpdateSubagentSettingsParams(null, null)) + .get(30, TimeUnit.SECONDS); + } + } + } +} diff --git a/java/src/test/java/com/github/copilot/RpcTasksAndHandlersE2ETest.java b/java/src/test/java/com/github/copilot/RpcTasksAndHandlersE2ETest.java new file mode 100644 index 0000000000..89b283339e --- /dev/null +++ b/java/src/test/java/com/github/copilot/RpcTasksAndHandlersE2ETest.java @@ -0,0 +1,72 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +package com.github.copilot; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.Map; +import java.util.concurrent.TimeUnit; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import com.github.copilot.generated.rpc.SessionMcpHeadersHandlePendingHeadersRefreshRequestParams; +import com.github.copilot.generated.rpc.SessionUiHandlePendingSessionLimitsExhaustedParams; +import com.github.copilot.generated.rpc.UISessionLimitsExhaustedResponse; +import com.github.copilot.generated.rpc.UISessionLimitsExhaustedResponseAction; +import com.github.copilot.rpc.PermissionHandler; +import com.github.copilot.rpc.SessionConfig; + +class RpcTasksAndHandlersE2ETest { + + private static E2ETestContext ctx; + + @BeforeAll + static void setup() throws Exception { + ctx = E2ETestContext.create(); + } + + @AfterAll + static void teardown() throws Exception { + if (ctx != null) { + ctx.close(); + } + } + + @Test + void testShouldReturnExpectedResultsForMissingPendingHandlerRequestIds() throws Exception { + ctx.initializeProxy(); + + try (var client = ctx.createClient()) { + try (var session = client + .createSession(new SessionConfig().setOnPermissionRequest(PermissionHandler.APPROVE_ALL)).get()) { + var sessionLimits = session.getRpc().ui + .handlePendingSessionLimitsExhausted( + new SessionUiHandlePendingSessionLimitsExhaustedParams(null, + "missing-session-limits-request", + new UISessionLimitsExhaustedResponse( + UISessionLimitsExhaustedResponseAction.UNSET, null, null))) + .get(30, TimeUnit.SECONDS); + assertFalse(sessionLimits.success()); + + var headersRefresh = session.getRpc().mcp.headers + .handlePendingHeadersRefreshRequest( + new SessionMcpHeadersHandlePendingHeadersRefreshRequestParams(null, + "missing-headers-refresh-request", + Map.of("kind", "headers", "headers", Map.of("x-refresh", "missing")))) + .get(30, TimeUnit.SECONDS); + assertFalse(headersRefresh.success()); + + var noHeadersRefresh = session.getRpc().mcp.headers + .handlePendingHeadersRefreshRequest( + new SessionMcpHeadersHandlePendingHeadersRefreshRequestParams(null, + "missing-headers-refresh-none-request", Map.of("kind", "none"))) + .get(30, TimeUnit.SECONDS); + assertFalse(noHeadersRefresh.success()); + } + } + } +} diff --git a/java/src/test/java/com/github/copilot/RpcWrappersTest.java b/java/src/test/java/com/github/copilot/RpcWrappersTest.java index 9a3559d66d..7493c6e470 100644 --- a/java/src/test/java/com/github/copilot/RpcWrappersTest.java +++ b/java/src/test/java/com/github/copilot/RpcWrappersTest.java @@ -205,7 +205,7 @@ void sessionRpc_model_switchTo_merges_sessionId_with_extra_params() { var session = new SessionRpc(stub, "sess-xyz"); // switchTo takes extra params beyond sessionId - var switchParams = new SessionModelSwitchToParams(null, "gpt-5", null, null, null, null); + var switchParams = new SessionModelSwitchToParams(null, "gpt-5", null, null, null, null, null); session.model.switchTo(switchParams); assertEquals(1, stub.calls.size()); diff --git a/java/src/test/java/com/github/copilot/SessionEventHandlingTest.java b/java/src/test/java/com/github/copilot/SessionEventHandlingTest.java index 2e54abf9d1..f075d57d5e 100644 --- a/java/src/test/java/com/github/copilot/SessionEventHandlingTest.java +++ b/java/src/test/java/com/github/copilot/SessionEventHandlingTest.java @@ -180,7 +180,7 @@ void testHandlerReceivesCorrectEventData() { SessionStartEvent startEvent = createSessionStartEvent(); startEvent.setData(new SessionStartEvent.SessionStartEventData("my-session-123", null, null, null, null, null, - null, null, null, null, null, null, null, null)); + null, null, null, null, null, null, null, null, null)); dispatchEvent(startEvent); AssistantMessageEvent msgEvent = createAssistantMessageEvent("Test content"); @@ -857,7 +857,7 @@ private SessionStartEvent createSessionStartEvent() { private SessionStartEvent createSessionStartEvent(String sessionId) { var event = new SessionStartEvent(); var data = new SessionStartEvent.SessionStartEventData(sessionId, null, null, null, null, null, null, null, - null, null, null, null, null, null); + null, null, null, null, null, null, null); event.setData(data); return event; } diff --git a/java/src/test/java/com/github/copilot/e2e/ErgonomicTestTools$$CopilotToolMeta.java b/java/src/test/java/com/github/copilot/e2e/ErgonomicTestTools$$CopilotToolMeta.java index 703a6b0102..3e6291984f 100644 --- a/java/src/test/java/com/github/copilot/e2e/ErgonomicTestTools$$CopilotToolMeta.java +++ b/java/src/test/java/com/github/copilot/e2e/ErgonomicTestTools$$CopilotToolMeta.java @@ -44,6 +44,25 @@ public List definitions(ErgonomicTestTools instance, ObjectMappe Map args = invocation.getArguments(); String keyword = (String) args.get("keyword"); return CompletableFuture.completedFuture(instance.searchItems(keyword)); + }, null, null, null), + new ToolDefinition("get_status", "Returns the current status", + Map.of("type", "object", "properties", Map.of(), "required", List.of()), invocation -> { + return CompletableFuture.completedFuture(instance.getStatus()); + }, null, null, null), + new ToolDefinition("combine_values", "Combines two values into a single string", Map.of( + "type", "object", "properties", Map + .ofEntries( + Map.entry("value1", + (Map) (Map) withMeta(Map.of("type", "string"), + "First value", null)), + Map.entry("value2", + (Map) (Map) withMeta(Map.of("type", "string"), + "Second value", null))), + "required", List.of("value1", "value2")), invocation -> { + Map args = invocation.getArguments(); + String value1 = (String) args.get("value1"); + String value2 = (String) args.get("value2"); + return CompletableFuture.completedFuture(instance.combineValues(value1, value2)); }, null, null, null)); } } diff --git a/java/src/test/java/com/github/copilot/e2e/ErgonomicTestTools.java b/java/src/test/java/com/github/copilot/e2e/ErgonomicTestTools.java index e70e9b4dc2..15b2c087ab 100644 --- a/java/src/test/java/com/github/copilot/e2e/ErgonomicTestTools.java +++ b/java/src/test/java/com/github/copilot/e2e/ErgonomicTestTools.java @@ -29,4 +29,15 @@ public String setCurrentPhase(@CopilotToolParam("The phase to transition to") St public String searchItems(@CopilotToolParam("Search keyword") String keyword) { return "Found: " + keyword + " -> item_alpha, item_beta"; } + + @CopilotTool("Returns the current status") + public String getStatus() { + return "Status: OK"; + } + + @CopilotTool("Combines two values into a single string") + public String combineValues(@CopilotToolParam("First value") String value1, + @CopilotToolParam("Second value") String value2) { + return "combined: " + value1 + " + " + value2; + } } diff --git a/java/src/test/java/com/github/copilot/e2e/ErgonomicToolDefinitionIT.java b/java/src/test/java/com/github/copilot/e2e/ErgonomicToolDefinitionIT.java index c74e945444..412acd4c46 100644 --- a/java/src/test/java/com/github/copilot/e2e/ErgonomicToolDefinitionIT.java +++ b/java/src/test/java/com/github/copilot/e2e/ErgonomicToolDefinitionIT.java @@ -23,6 +23,7 @@ import com.github.copilot.rpc.SessionConfig; import com.github.copilot.rpc.ToolDefinition; import com.github.copilot.rpc.ToolSet; +import com.github.copilot.tool.Param; /** * Failsafe integration test for the ergonomic {@code @CopilotTool} + @@ -82,4 +83,163 @@ void ergonomicToolDefinition() throws Exception { } } } + + @Test + void ergonomicToolArity0() throws Exception { + ctx.configureForTest("tools", "ergonomic_tool_arity0"); + + ErgonomicTestTools tools = new ErgonomicTestTools(); + List toolDefs = ToolDefinition.fromObject(tools); + + try (CopilotClient client = ctx.createClient()) { + CopilotSession session = client + .createSession(new SessionConfig().setOnPermissionRequest(PermissionHandler.APPROVE_ALL) + .setAvailableTools(new ToolSet().addCustom("*")).setTools(toolDefs)) + .get(30, TimeUnit.SECONDS); + + try { + AssistantMessageEvent response = session + .sendAndWait(new MessageOptions().setPrompt("Call get_status and tell me the result."), 60_000) + .get(90, TimeUnit.SECONDS); + + assertNotNull(response, "Expected a response from the assistant"); + String content = response.getData().content().toLowerCase(); + assertTrue(content.contains("ok"), + "Response should mention the status: " + response.getData().content()); + } finally { + session.close(); + } + } + } + + @Test + void ergonomicToolArity2() throws Exception { + ctx.configureForTest("tools", "ergonomic_tool_arity2"); + + ErgonomicTestTools tools = new ErgonomicTestTools(); + List toolDefs = ToolDefinition.fromObject(tools); + + try (CopilotClient client = ctx.createClient()) { + CopilotSession session = client + .createSession(new SessionConfig().setOnPermissionRequest(PermissionHandler.APPROVE_ALL) + .setAvailableTools(new ToolSet().addCustom("*")).setTools(toolDefs)) + .get(30, TimeUnit.SECONDS); + + try { + AssistantMessageEvent response = session.sendAndWait( + new MessageOptions().setPrompt( + "Call combine_values with 'alpha' and 'beta', then report the combined result."), + 60_000).get(90, TimeUnit.SECONDS); + + assertNotNull(response, "Expected a response from the assistant"); + String content = response.getData().content().toLowerCase(); + assertTrue(content.contains("alpha") && content.contains("beta"), + "Response should contain the combined values: " + response.getData().content()); + } finally { + session.close(); + } + } + } + + @Test + void lambdaToolArity0() throws Exception { + ctx.configureForTest("tools", "ergonomic_tool_arity0"); + + ToolDefinition getStatus = ToolDefinition.from("get_status", "Returns the current status", () -> "Status: OK"); + + try (CopilotClient client = ctx.createClient()) { + CopilotSession session = client + .createSession(new SessionConfig().setOnPermissionRequest(PermissionHandler.APPROVE_ALL) + .setAvailableTools(new ToolSet().addCustom("*")).setTools(List.of(getStatus))) + .get(30, TimeUnit.SECONDS); + + try { + AssistantMessageEvent response = session + .sendAndWait(new MessageOptions().setPrompt("Call get_status and tell me the result."), 60_000) + .get(90, TimeUnit.SECONDS); + + assertNotNull(response, "Expected a response from the assistant"); + String content = response.getData().content().toLowerCase(); + assertTrue(content.contains("ok"), + "Response should mention the status: " + response.getData().content()); + } finally { + session.close(); + } + } + } + + @Test + void lambdaToolArity2() throws Exception { + ctx.configureForTest("tools", "ergonomic_tool_arity2"); + + ToolDefinition combineValues = ToolDefinition.from("combine_values", "Combines two values into a single string", + Param.of(String.class, "value1", "First value"), Param.of(String.class, "value2", "Second value"), + (v1, v2) -> "combined: " + v1 + " + " + v2); + + try (CopilotClient client = ctx.createClient()) { + CopilotSession session = client + .createSession(new SessionConfig().setOnPermissionRequest(PermissionHandler.APPROVE_ALL) + .setAvailableTools(new ToolSet().addCustom("*")).setTools(List.of(combineValues))) + .get(30, TimeUnit.SECONDS); + + try { + AssistantMessageEvent response = session.sendAndWait( + new MessageOptions().setPrompt( + "Call combine_values with 'alpha' and 'beta', then report the combined result."), + 60_000).get(90, TimeUnit.SECONDS); + + assertNotNull(response, "Expected a response from the assistant"); + String content = response.getData().content().toLowerCase(); + assertTrue(content.contains("alpha") && content.contains("beta"), + "Response should contain the combined values: " + response.getData().content()); + } finally { + session.close(); + } + } + } + + @Test + void lambdaToolDefinition() throws Exception { + ctx.configureForTest("tools", "ergonomic_tool_definition"); + + class LambdaTools { + String currentPhase; + } + LambdaTools tools = new LambdaTools(); + + ToolDefinition setCurrentPhase = ToolDefinition.from("set_current_phase", "Sets the current phase of the agent", + Param.of(String.class, "phase", "The phase to transition to"), phase -> { + tools.currentPhase = phase; + return "Phase set to " + phase; + }); + + ToolDefinition searchItems = ToolDefinition.from("search_items", "Search for items by keyword", + Param.of(String.class, "keyword", "Search keyword"), + keyword -> "Found: " + keyword + " -> item_alpha, item_beta"); + + try (CopilotClient client = ctx.createClient()) { + CopilotSession session = client + .createSession(new SessionConfig().setOnPermissionRequest(PermissionHandler.APPROVE_ALL) + .setAvailableTools(new ToolSet().addCustom("*").addBuiltIn("web_fetch")) + .setTools(List.of(setCurrentPhase, searchItems))) + .get(30, TimeUnit.SECONDS); + + try { + AssistantMessageEvent response = session.sendAndWait(new MessageOptions().setPrompt( + "First, set the current phase to 'analyzing'. Then search for items with keyword 'copilot'. Report the phase and search results."), + 60_000).get(90, TimeUnit.SECONDS); + + assertNotNull(response, "Expected a response from the assistant"); + String content = response.getData().content().toLowerCase(); + assertTrue(content.contains("analyzing"), + "Response should contain the updated phase: " + response.getData().content()); + assertTrue(content.contains("item_alpha") || content.contains("item_beta"), + "Response should contain search results: " + response.getData().content()); + assertTrue("analyzing".equals(tools.currentPhase), + "Expected currentPhase to be 'analyzing' but was: " + tools.currentPhase); + } finally { + session.close(); + } + } + } } diff --git a/java/src/test/java/com/github/copilot/generated/rpc/GeneratedRpcRecordsCoverageTest.java b/java/src/test/java/com/github/copilot/generated/rpc/GeneratedRpcRecordsCoverageTest.java index 3fc22412e3..0a7a4f2541 100644 --- a/java/src/test/java/com/github/copilot/generated/rpc/GeneratedRpcRecordsCoverageTest.java +++ b/java/src/test/java/com/github/copilot/generated/rpc/GeneratedRpcRecordsCoverageTest.java @@ -321,11 +321,12 @@ void sessionModelGetCurrentParams_record() { @Test void sessionModelSwitchToParams_record() { - var params = new SessionModelSwitchToParams("sess-32", "claude-sonnet-4.5", "high", null, null, null); + var params = new SessionModelSwitchToParams("sess-32", "claude-sonnet-4.5", "high", null, null, null, null); assertEquals("sess-32", params.sessionId()); assertEquals("claude-sonnet-4.5", params.modelId()); assertEquals("high", params.reasoningEffort()); assertNull(params.reasoningSummary()); + assertNull(params.verbosity()); assertNull(params.modelCapabilities()); } @@ -836,7 +837,7 @@ void sessionModelSwitchToParams_nested_records() { var limits = new ModelCapabilitiesOverrideLimits(100000L, 8192L, 128000L, limitsVision); var supports = new ModelCapabilitiesOverrideSupports(true, true, null); var capabilities = new ModelCapabilitiesOverride(supports, limits); - var params = new SessionModelSwitchToParams("sess-m", "gpt-5", null, null, capabilities, null); + var params = new SessionModelSwitchToParams("sess-m", "gpt-5", null, null, null, capabilities, null); assertEquals("gpt-5", params.modelId()); assertNotNull(params.modelCapabilities()); diff --git a/java/src/test/java/com/github/copilot/rpc/ParamCoercionTest.java b/java/src/test/java/com/github/copilot/rpc/ParamCoercionTest.java new file mode 100644 index 0000000000..8ad4ee8306 --- /dev/null +++ b/java/src/test/java/com/github/copilot/rpc/ParamCoercionTest.java @@ -0,0 +1,362 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +package com.github.copilot.rpc; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Map; +import java.util.OptionalDouble; +import java.util.OptionalInt; +import java.util.OptionalLong; + +import org.junit.jupiter.api.Test; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.github.copilot.tool.Param; + +/** + * Unit tests for {@link ParamCoercion} — runtime argument coercion from raw + * invocation maps to typed Java values declared by {@link Param} descriptors. + */ +class ParamCoercionTest { + + private static final ObjectMapper MAPPER = new ObjectMapper(); + + // ── coerce: present argument, simple types ─────────────────────────────────── + + @Test + void coerce_stringArg_passedThrough() { + Param p = Param.of(String.class, "msg", "A message"); + String result = ParamCoercion.coerce(Map.of("msg", "hello"), p, MAPPER); + assertEquals("hello", result); + } + + @Test + void coerce_integerArgFromNumber() { + Param p = Param.of(Integer.class, "n", "A number"); + Integer result = ParamCoercion.coerce(Map.of("n", 42), p, MAPPER); + assertEquals(42, result); + } + + @Test + void coerce_longArgFromNumber() { + Param p = Param.of(Long.class, "id", "An identifier"); + Long result = ParamCoercion.coerce(Map.of("id", 123456789L), p, MAPPER); + assertEquals(123456789L, result); + } + + @Test + void coerce_doubleArgFromNumber() { + Param p = Param.of(Double.class, "price", "A price"); + Double result = ParamCoercion.coerce(Map.of("price", 19.99), p, MAPPER); + assertEquals(19.99, result, 0.001); + } + + @Test + void coerce_floatArgFromNumber() { + Param p = Param.of(Float.class, "rate", "A rate"); + Float result = ParamCoercion.coerce(Map.of("rate", 3.14), p, MAPPER); + assertEquals(3.14f, result, 0.01f); + } + + @Test + void coerce_booleanArgFromBoolean() { + Param p = Param.of(Boolean.class, "flag", "A flag"); + Boolean result = ParamCoercion.coerce(Map.of("flag", true), p, MAPPER); + assertEquals(true, result); + } + + // Note: enum coercion via mapper.convertValue requires the enum's package to be + // opened to com.fasterxml.jackson.databind. In the SDK module, + // com.github.copilot.tool + // is not opened to Jackson (only com.github.copilot.rpc is). User-defined enums + // will + // be outside the SDK module and fully accessible. Enum default coercion is + // tested via + // coerceDefault_enum which uses Enum.valueOf directly. + + @Test + void coerce_enumFromString_viaCoerceDefault() { + Param p = Param.of(TestMode.class, "mode", "Mode", false, "FAST"); + TestMode result = ParamCoercion.coerce(Map.of(), p, MAPPER); + assertEquals(TestMode.FAST, result); + } + + // ── coerce: Optional primitive types ───────────────────────────────────────── + + @Test + void coerce_optionalInt_fromNumber() { + Param p = Param.of(OptionalInt.class, "count", "Count", false, ""); + OptionalInt result = ParamCoercion.coerce(Map.of("count", 7), p, MAPPER); + assertEquals(OptionalInt.of(7), result); + } + + @Test + void coerce_optionalLong_fromNumber() { + Param p = Param.of(OptionalLong.class, "ts", "Timestamp", false, ""); + OptionalLong result = ParamCoercion.coerce(Map.of("ts", 999L), p, MAPPER); + assertEquals(OptionalLong.of(999L), result); + } + + @Test + void coerce_optionalDouble_fromNumber() { + Param p = Param.of(OptionalDouble.class, "ratio", "Ratio", false, ""); + OptionalDouble result = ParamCoercion.coerce(Map.of("ratio", 2.5), p, MAPPER); + assertEquals(OptionalDouble.of(2.5), result); + } + + @Test + void coerce_optionalInt_nonNumeric_throwsIllegalArgument() { + Param p = Param.of(OptionalInt.class, "count", "Count", false, ""); + assertThrows(IllegalArgumentException.class, + () -> ParamCoercion.coerce(Map.of("count", "not_a_number"), p, MAPPER)); + } + + @Test + void coerce_optionalLong_nonNumeric_throwsIllegalArgument() { + Param p = Param.of(OptionalLong.class, "ts", "Timestamp", false, ""); + assertThrows(IllegalArgumentException.class, () -> ParamCoercion.coerce(Map.of("ts", "abc"), p, MAPPER)); + } + + @Test + void coerce_optionalDouble_nonNumeric_throwsIllegalArgument() { + Param p = Param.of(OptionalDouble.class, "ratio", "Ratio", false, ""); + assertThrows(IllegalArgumentException.class, () -> ParamCoercion.coerce(Map.of("ratio", "xyz"), p, MAPPER)); + } + + // ── coerce: missing argument — required ────────────────────────────────────── + + @Test + void coerce_requiredMissing_throwsWithParamName() { + Param p = Param.of(String.class, "query", "Search query"); + var ex = assertThrows(IllegalArgumentException.class, () -> ParamCoercion.coerce(Map.of(), p, MAPPER)); + assertTrue(ex.getMessage().contains("query")); + } + + @Test + void coerce_requiredMissing_nullArgs_throws() { + Param p = Param.of(String.class, "name", "A name"); + var ex = assertThrows(IllegalArgumentException.class, () -> ParamCoercion.coerce(null, p, MAPPER)); + assertTrue(ex.getMessage().contains("name")); + } + + // ── coerce: missing argument — optional with default ───────────────────────── + + @Test + void coerce_optionalWithStringDefault_usesDefault() { + Param p = Param.of(String.class, "mode", "Mode", false, "normal"); + String result = ParamCoercion.coerce(Map.of(), p, MAPPER); + assertEquals("normal", result); + } + + @Test + void coerce_optionalWithIntegerDefault_usesDefault() { + Param p = Param.of(Integer.class, "limit", "Limit", false, "25"); + Integer result = ParamCoercion.coerce(Map.of(), p, MAPPER); + assertEquals(25, result); + } + + @Test + void coerce_optionalWithLongDefault_usesDefault() { + Param p = Param.of(Long.class, "offset", "Offset", false, "100"); + Long result = ParamCoercion.coerce(Map.of(), p, MAPPER); + assertEquals(100L, result); + } + + @Test + void coerce_optionalWithDoubleDefault_usesDefault() { + Param p = Param.of(Double.class, "threshold", "Threshold", false, "0.75"); + Double result = ParamCoercion.coerce(Map.of(), p, MAPPER); + assertEquals(0.75, result, 0.001); + } + + @Test + void coerce_optionalWithFloatDefault_usesDefault() { + Param p = Param.of(Float.class, "rate", "Rate", false, "1.5"); + Float result = ParamCoercion.coerce(Map.of(), p, MAPPER); + assertEquals(1.5f, result, 0.01f); + } + + @Test + void coerce_optionalWithShortDefault_usesDefault() { + Param p = Param.of(Short.class, "level", "Level", false, "3"); + Short result = ParamCoercion.coerce(Map.of(), p, MAPPER); + assertEquals((short) 3, result); + } + + @Test + void coerce_optionalWithByteDefault_usesDefault() { + Param p = Param.of(Byte.class, "code", "Code", false, "7"); + Byte result = ParamCoercion.coerce(Map.of(), p, MAPPER); + assertEquals((byte) 7, result); + } + + @Test + void coerce_optionalWithBooleanDefault_usesDefault() { + Param p = Param.of(Boolean.class, "verbose", "Verbose", false, "true"); + Boolean result = ParamCoercion.coerce(Map.of(), p, MAPPER); + assertEquals(true, result); + } + + @Test + void coerce_optionalWithEnumDefault_usesDefault() { + Param p = Param.of(TestMode.class, "mode", "Mode", false, "SLOW"); + TestMode result = ParamCoercion.coerce(Map.of(), p, MAPPER); + assertEquals(TestMode.SLOW, result); + } + + // ── coerce: missing argument — optional without default ────────────────────── + + @Test + void coerce_optionalNoDefault_returnsNull() { + Param p = Param.of(String.class, "title", "Title", false, ""); + String result = ParamCoercion.coerce(Map.of(), p, MAPPER); + assertNull(result); + } + + @Test + void coerce_optionalNoDefault_optionalInt_returnsEmpty() { + Param p = Param.of(OptionalInt.class, "n", "Number", false, ""); + OptionalInt result = ParamCoercion.coerce(Map.of(), p, MAPPER); + assertEquals(OptionalInt.empty(), result); + } + + @Test + void coerce_optionalNoDefault_optionalLong_returnsEmpty() { + Param p = Param.of(OptionalLong.class, "ts", "Timestamp", false, ""); + OptionalLong result = ParamCoercion.coerce(Map.of(), p, MAPPER); + assertEquals(OptionalLong.empty(), result); + } + + @Test + void coerce_optionalNoDefault_optionalDouble_returnsEmpty() { + Param p = Param.of(OptionalDouble.class, "ratio", "Ratio", false, ""); + OptionalDouble result = ParamCoercion.coerce(Map.of(), p, MAPPER); + assertEquals(OptionalDouble.empty(), result); + } + + // ── coerce: type conversion via ObjectMapper ───────────────────────────────── + + @Test + void coerce_integerFromStringViaMapper() { + // ObjectMapper can convert "42" string to Integer + Param p = Param.of(Integer.class, "n", "A number"); + Integer result = ParamCoercion.coerce(Map.of("n", "42"), p, MAPPER); + assertEquals(42, result); + } + + @Test + void coerce_booleanFromStringViaMapper() { + Param p = Param.of(Boolean.class, "flag", "A flag"); + Boolean result = ParamCoercion.coerce(Map.of("flag", "true"), p, MAPPER); + assertEquals(true, result); + } + + @Test + void coerce_incompatibleType_throwsWithParamName() { + Param p = Param.of(Integer.class, "count", "Count"); + var ex = assertThrows(IllegalArgumentException.class, + () -> ParamCoercion.coerce(Map.of("count", "not_a_number"), p, MAPPER)); + assertTrue(ex.getMessage().contains("count")); + } + + // ── coerceDefault: direct tests ────────────────────────────────────────────── + + @Test + void coerceDefault_string() { + Param p = Param.of(String.class, "s", "A string", false, "hello"); + assertEquals("hello", ParamCoercion.coerceDefault(p, MAPPER)); + } + + @Test + void coerceDefault_integer() { + Param p = Param.of(Integer.class, "n", "A num", false, "99"); + assertEquals(99, ParamCoercion.coerceDefault(p, MAPPER)); + } + + @Test + void coerceDefault_long() { + Param p = Param.of(Long.class, "id", "An id", false, "12345"); + assertEquals(12345L, ParamCoercion.coerceDefault(p, MAPPER)); + } + + @Test + void coerceDefault_double() { + Param p = Param.of(Double.class, "d", "A double", false, "3.14"); + assertEquals(3.14, ParamCoercion.coerceDefault(p, MAPPER), 0.001); + } + + @Test + void coerceDefault_float() { + Param p = Param.of(Float.class, "f", "A float", false, "2.5"); + assertEquals(2.5f, ParamCoercion.coerceDefault(p, MAPPER), 0.01f); + } + + @Test + void coerceDefault_short() { + Param p = Param.of(Short.class, "s", "A short", false, "10"); + assertEquals((short) 10, ParamCoercion.coerceDefault(p, MAPPER)); + } + + @Test + void coerceDefault_byte() { + Param p = Param.of(Byte.class, "b", "A byte", false, "5"); + assertEquals((byte) 5, ParamCoercion.coerceDefault(p, MAPPER)); + } + + @Test + void coerceDefault_booleanTrue() { + Param p = Param.of(Boolean.class, "v", "Verbose", false, "true"); + assertEquals(true, ParamCoercion.coerceDefault(p, MAPPER)); + } + + @Test + void coerceDefault_booleanFalse() { + Param p = Param.of(Boolean.class, "v", "Verbose", false, "false"); + assertEquals(false, ParamCoercion.coerceDefault(p, MAPPER)); + } + + @Test + void coerceDefault_enum() { + Param p = Param.of(TestMode.class, "m", "Mode", false, "FAST"); + assertEquals(TestMode.FAST, ParamCoercion.coerceDefault(p, MAPPER)); + } + + // ── emptyOptionalOrNull: direct tests ──────────────────────────────────────── + + @Test + void emptyOptionalOrNull_optionalInt_returnsEmpty() { + assertEquals(OptionalInt.empty(), ParamCoercion.emptyOptionalOrNull(OptionalInt.class)); + } + + @Test + void emptyOptionalOrNull_optionalLong_returnsEmpty() { + assertEquals(OptionalLong.empty(), ParamCoercion.emptyOptionalOrNull(OptionalLong.class)); + } + + @Test + void emptyOptionalOrNull_optionalDouble_returnsEmpty() { + assertEquals(OptionalDouble.empty(), ParamCoercion.emptyOptionalOrNull(OptionalDouble.class)); + } + + @Test + void emptyOptionalOrNull_string_returnsNull() { + assertNull(ParamCoercion.emptyOptionalOrNull(String.class)); + } + + @Test + void emptyOptionalOrNull_integer_returnsNull() { + assertNull(ParamCoercion.emptyOptionalOrNull(Integer.class)); + } + + // ── Test helper types ──────────────────────────────────────────────────────── + + enum TestMode { + FAST, SLOW, NORMAL + } +} diff --git a/java/src/test/java/com/github/copilot/rpc/ParamSchemaTest.java b/java/src/test/java/com/github/copilot/rpc/ParamSchemaTest.java new file mode 100644 index 0000000000..27d76a91a5 --- /dev/null +++ b/java/src/test/java/com/github/copilot/rpc/ParamSchemaTest.java @@ -0,0 +1,436 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +package com.github.copilot.rpc; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.OffsetDateTime; +import java.time.ZonedDateTime; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.OptionalDouble; +import java.util.OptionalInt; +import java.util.OptionalLong; +import java.util.Set; +import java.util.UUID; + +import org.junit.jupiter.api.Test; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.github.copilot.tool.Param; + +/** + * Unit tests for {@link ParamSchema} — runtime JSON Schema generation from + * {@link Param} descriptors. + */ +class ParamSchemaTest { + + private static final ObjectMapper MAPPER = new ObjectMapper(); + + // ── buildSchema: empty / zero params ───────────────────────────────────────── + + @Test + void buildSchema_nullParams_returnsEmptySchema() { + Map schema = ParamSchema.buildSchema("tool", MAPPER, (Param[]) null); + assertEquals("object", schema.get("type")); + assertTrue(((Map) schema.get("properties")).isEmpty()); + assertTrue(((List) schema.get("required")).isEmpty()); + } + + @Test + void buildSchema_emptyArray_returnsEmptySchema() { + Map schema = ParamSchema.buildSchema("tool", MAPPER); + assertEquals("object", schema.get("type")); + assertTrue(((Map) schema.get("properties")).isEmpty()); + assertTrue(((List) schema.get("required")).isEmpty()); + } + + // ── buildSchema: validation ────────────────────────────────────────────────── + + @Test + void buildSchema_nullParamElement_throwsWithToolName() { + Param p1 = Param.of(String.class, "a", "First"); + var ex = assertThrows(IllegalArgumentException.class, + () -> ParamSchema.buildSchema("my_tool", MAPPER, p1, null)); + assertTrue(ex.getMessage().contains("my_tool")); + } + + @Test + void buildSchema_duplicateNames_throwsWithToolNameAndParamName() { + Param p1 = Param.of(String.class, "name", "First name"); + Param p2 = Param.of(String.class, "name", "Second name"); + var ex = assertThrows(IllegalArgumentException.class, + () -> ParamSchema.buildSchema("greeting", MAPPER, p1, p2)); + assertTrue(ex.getMessage().contains("name")); + assertTrue(ex.getMessage().contains("greeting")); + } + + // ── buildSchema: required / optional semantics ─────────────────────────────── + + @Test + void buildSchema_requiredParam_appearsInRequiredList() { + Param p = Param.of(String.class, "query", "Search query"); + Map schema = ParamSchema.buildSchema("search", MAPPER, p); + @SuppressWarnings("unchecked") + List required = (List) schema.get("required"); + assertTrue(required.contains("query")); + } + + @Test + void buildSchema_optionalParam_notInRequiredList() { + Param p = Param.of(Integer.class, "limit", "Max results", false, "10"); + Map schema = ParamSchema.buildSchema("list", MAPPER, p); + @SuppressWarnings("unchecked") + List required = (List) schema.get("required"); + assertTrue(required.isEmpty()); + } + + @Test + void buildSchema_mixedRequiredAndOptional_onlyRequiredInList() { + Param pReq = Param.of(String.class, "query", "Search query"); + Param pOpt = Param.of(Integer.class, "limit", "Max", false, "20"); + Map schema = ParamSchema.buildSchema("search", MAPPER, pReq, pOpt); + @SuppressWarnings("unchecked") + List required = (List) schema.get("required"); + assertEquals(1, required.size()); + assertEquals("query", required.get(0)); + } + + // ── buildSchema: description and default in property ───────────────────────── + + @Test + void buildSchema_paramDescription_appearsInPropertySchema() { + Param p = Param.of(String.class, "msg", "A message to send"); + Map schema = ParamSchema.buildSchema("send", MAPPER, p); + @SuppressWarnings("unchecked") + Map props = (Map) schema.get("properties"); + @SuppressWarnings("unchecked") + Map msgSchema = (Map) props.get("msg"); + assertEquals("A message to send", msgSchema.get("description")); + } + + @Test + void buildSchema_paramDefault_appearsInPropertySchema() { + Param p = Param.of(Integer.class, "count", "Item count", false, "5"); + Map schema = ParamSchema.buildSchema("items", MAPPER, p); + @SuppressWarnings("unchecked") + Map props = (Map) schema.get("properties"); + @SuppressWarnings("unchecked") + Map countSchema = (Map) props.get("count"); + assertEquals(5, countSchema.get("default")); + } + + @Test + void buildSchema_stringDefault_appearsAsString() { + Param p = Param.of(String.class, "mode", "Operating mode", false, "fast"); + Map schema = ParamSchema.buildSchema("run", MAPPER, p); + @SuppressWarnings("unchecked") + Map props = (Map) schema.get("properties"); + @SuppressWarnings("unchecked") + Map modeSchema = (Map) props.get("mode"); + assertEquals("fast", modeSchema.get("default")); + } + + @Test + void buildSchema_booleanDefault_appearsAsBoolean() { + Param p = Param.of(Boolean.class, "verbose", "Verbose mode", false, "true"); + Map schema = ParamSchema.buildSchema("run", MAPPER, p); + @SuppressWarnings("unchecked") + Map props = (Map) schema.get("properties"); + @SuppressWarnings("unchecked") + Map verboseSchema = (Map) props.get("verbose"); + assertEquals(true, verboseSchema.get("default")); + } + + // ── buildSchema: multiple params preserve order ────────────────────────────── + + @Test + void buildSchema_multipleParams_orderPreservedInProperties() { + Param p1 = Param.of(String.class, "alpha", "First"); + Param p2 = Param.of(String.class, "beta", "Second"); + Param p3 = Param.of(String.class, "gamma", "Third"); + Map schema = ParamSchema.buildSchema("ordered", MAPPER, p1, p2, p3); + @SuppressWarnings("unchecked") + Map props = (Map) schema.get("properties"); + List keys = List.copyOf(props.keySet()); + assertEquals(List.of("alpha", "beta", "gamma"), keys); + } + + // ── forType: primitive and boxed integer types ─────────────────────────────── + + @Test + void forType_int_returnsInteger() { + assertEquals(Map.of("type", "integer"), ParamSchema.forType(int.class)); + } + + @Test + void forType_Integer_returnsInteger() { + assertEquals(Map.of("type", "integer"), ParamSchema.forType(Integer.class)); + } + + @Test + void forType_long_returnsInteger() { + assertEquals(Map.of("type", "integer"), ParamSchema.forType(long.class)); + } + + @Test + void forType_Long_returnsInteger() { + assertEquals(Map.of("type", "integer"), ParamSchema.forType(Long.class)); + } + + @Test + void forType_short_returnsInteger() { + assertEquals(Map.of("type", "integer"), ParamSchema.forType(short.class)); + } + + @Test + void forType_Short_returnsInteger() { + assertEquals(Map.of("type", "integer"), ParamSchema.forType(Short.class)); + } + + @Test + void forType_byte_returnsInteger() { + assertEquals(Map.of("type", "integer"), ParamSchema.forType(byte.class)); + } + + @Test + void forType_Byte_returnsInteger() { + assertEquals(Map.of("type", "integer"), ParamSchema.forType(Byte.class)); + } + + // ── forType: floating-point types ──────────────────────────────────────────── + + @Test + void forType_double_returnsNumber() { + assertEquals(Map.of("type", "number"), ParamSchema.forType(double.class)); + } + + @Test + void forType_Double_returnsNumber() { + assertEquals(Map.of("type", "number"), ParamSchema.forType(Double.class)); + } + + @Test + void forType_float_returnsNumber() { + assertEquals(Map.of("type", "number"), ParamSchema.forType(float.class)); + } + + @Test + void forType_Float_returnsNumber() { + assertEquals(Map.of("type", "number"), ParamSchema.forType(Float.class)); + } + + // ── forType: boolean ───────────────────────────────────────────────────────── + + @Test + void forType_boolean_returnsBoolean() { + assertEquals(Map.of("type", "boolean"), ParamSchema.forType(boolean.class)); + } + + @Test + void forType_Boolean_returnsBoolean() { + assertEquals(Map.of("type", "boolean"), ParamSchema.forType(Boolean.class)); + } + + // ── forType: char / Character ──────────────────────────────────────────────── + + @Test + void forType_char_returnsString() { + assertEquals(Map.of("type", "string"), ParamSchema.forType(char.class)); + } + + @Test + void forType_Character_returnsString() { + assertEquals(Map.of("type", "string"), ParamSchema.forType(Character.class)); + } + + // ── forType: String ────────────────────────────────────────────────────────── + + @Test + void forType_String_returnsString() { + assertEquals(Map.of("type", "string"), ParamSchema.forType(String.class)); + } + + // ── forType: UUID ──────────────────────────────────────────────────────────── + + @Test + void forType_UUID_returnsStringWithUuidFormat() { + Map schema = ParamSchema.forType(UUID.class); + assertEquals("string", schema.get("type")); + assertEquals("uuid", schema.get("format")); + } + + // ── forType: Optional primitive types ──────────────────────────────────────── + + @Test + void forType_OptionalInt_returnsInteger() { + assertEquals(Map.of("type", "integer"), ParamSchema.forType(OptionalInt.class)); + } + + @Test + void forType_OptionalLong_returnsInteger() { + assertEquals(Map.of("type", "integer"), ParamSchema.forType(OptionalLong.class)); + } + + @Test + void forType_OptionalDouble_returnsNumber() { + assertEquals(Map.of("type", "number"), ParamSchema.forType(OptionalDouble.class)); + } + + // ── forType: date-time types ───────────────────────────────────────────────── + + @Test + void forType_OffsetDateTime_returnsDateTimeFormat() { + Map schema = ParamSchema.forType(OffsetDateTime.class); + assertEquals("string", schema.get("type")); + assertEquals("date-time", schema.get("format")); + } + + @Test + void forType_LocalDateTime_returnsDateTimeFormat() { + Map schema = ParamSchema.forType(LocalDateTime.class); + assertEquals("string", schema.get("type")); + assertEquals("date-time", schema.get("format")); + } + + @Test + void forType_Instant_returnsDateTimeFormat() { + Map schema = ParamSchema.forType(Instant.class); + assertEquals("string", schema.get("type")); + assertEquals("date-time", schema.get("format")); + } + + @Test + void forType_ZonedDateTime_returnsDateTimeFormat() { + Map schema = ParamSchema.forType(ZonedDateTime.class); + assertEquals("string", schema.get("type")); + assertEquals("date-time", schema.get("format")); + } + + @Test + void forType_LocalDate_returnsDateFormat() { + Map schema = ParamSchema.forType(LocalDate.class); + assertEquals("string", schema.get("type")); + assertEquals("date", schema.get("format")); + } + + @Test + void forType_LocalTime_returnsTimeFormat() { + Map schema = ParamSchema.forType(LocalTime.class); + assertEquals("string", schema.get("type")); + assertEquals("time", schema.get("format")); + } + + // ── forType: JsonNode / Object → any ───────────────────────────────────────── + + @Test + void forType_JsonNode_returnsEmptySchema() { + assertTrue(ParamSchema.forType(JsonNode.class).isEmpty()); + } + + @Test + void forType_Object_returnsEmptySchema() { + assertTrue(ParamSchema.forType(Object.class).isEmpty()); + } + + // ── forType: enums ─────────────────────────────────────────────────────────── + + @Test + void forType_enum_returnsStringWithEnumValues() { + Map schema = ParamSchema.forType(TestColor.class); + assertEquals("string", schema.get("type")); + @SuppressWarnings("unchecked") + List values = (List) schema.get("enum"); + assertNotNull(values); + assertEquals(List.of("RED", "GREEN", "BLUE"), values); + } + + // ── forType: collections ───────────────────────────────────────────────────── + + @Test + void forType_List_returnsArray() { + assertEquals(Map.of("type", "array"), ParamSchema.forType(List.class)); + } + + @Test + void forType_Set_returnsArray() { + assertEquals(Map.of("type", "array"), ParamSchema.forType(Set.class)); + } + + @Test + void forType_Collection_returnsArray() { + assertEquals(Map.of("type", "array"), ParamSchema.forType(Collection.class)); + } + + // ── forType: arrays ────────────────────────────────────────────────────────── + + @Test + void forType_stringArray_returnsArrayWithStringItems() { + Map schema = ParamSchema.forType(String[].class); + assertEquals("array", schema.get("type")); + @SuppressWarnings("unchecked") + Map items = (Map) schema.get("items"); + assertEquals("string", items.get("type")); + } + + @Test + void forType_intArray_returnsArrayWithIntegerItems() { + Map schema = ParamSchema.forType(int[].class); + assertEquals("array", schema.get("type")); + @SuppressWarnings("unchecked") + Map items = (Map) schema.get("items"); + assertEquals("integer", items.get("type")); + } + + @Test + void forType_doubleArray_returnsArrayWithNumberItems() { + Map schema = ParamSchema.forType(double[].class); + assertEquals("array", schema.get("type")); + @SuppressWarnings("unchecked") + Map items = (Map) schema.get("items"); + assertEquals("number", items.get("type")); + } + + // ── forType: Map ───────────────────────────────────────────────────────────── + + @Test + void forType_Map_returnsObject() { + assertEquals(Map.of("type", "object"), ParamSchema.forType(Map.class)); + } + + // ── forType: POJO / record fallback ────────────────────────────────────────── + + @Test + void forType_record_returnsObject() { + assertEquals(Map.of("type", "object"), ParamSchema.forType(TestRecord.class)); + } + + @Test + void forType_pojo_returnsObject() { + assertEquals(Map.of("type", "object"), ParamSchema.forType(TestPojo.class)); + } + + // ── Test helper types ──────────────────────────────────────────────────────── + + enum TestColor { + RED, GREEN, BLUE + } + + record TestRecord(String name, int value) { + } + + static class TestPojo { + String field; + } +} diff --git a/java/src/test/java/com/github/copilot/rpc/ToolDefinitionLambdaTest.java b/java/src/test/java/com/github/copilot/rpc/ToolDefinitionLambdaTest.java new file mode 100644 index 0000000000..7f9ccaaba7 --- /dev/null +++ b/java/src/test/java/com/github/copilot/rpc/ToolDefinitionLambdaTest.java @@ -0,0 +1,613 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +package com.github.copilot.rpc; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; + +import org.junit.jupiter.api.Test; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.github.copilot.AllowCopilotExperimental; +import com.github.copilot.tool.Param; + +/** + * Unit tests for {@link ToolDefinition#from}, {@link ToolDefinition#fromAsync}, + * {@link ToolDefinition#fromWithToolInvocation}, and + * {@link ToolDefinition#fromAsyncWithToolInvocation} lambda-tool factories, + * plus the fluent option-modifier methods + * ({@link ToolDefinition#overridesBuiltInTool}, + * {@link ToolDefinition#skipPermission}, {@link ToolDefinition#defer}). + * + *

+ * Tests are grouped by the Phase 4.4 contract: + *

    + *
  1. Successful inline definitions for arities 0–2 (sync and async).
  2. + *
  3. ToolInvocation context injection (sync and async).
  4. + *
  5. Option flag propagation.
  6. + *
  7. Required/default semantics.
  8. + *
  9. Error and validation paths.
  10. + *
  11. Schema structure.
  12. + *
  13. Result formatting (String, null, non-String).
  14. + *
  15. Argument coercion.
  16. + *
+ */ +@AllowCopilotExperimental +class ToolDefinitionLambdaTest { + + // ── Helpers ────────────────────────────────────────────────────────────────── + + private static ToolInvocation invocationOf(Map args) { + ObjectNode argsNode = JsonNodeFactory.instance.objectNode(); + for (Map.Entry e : args.entrySet()) { + Object v = e.getValue(); + if (v instanceof String s) { + argsNode.put(e.getKey(), s); + } else if (v instanceof Integer i) { + argsNode.put(e.getKey(), i); + } else if (v instanceof Long l) { + argsNode.put(e.getKey(), l); + } else if (v instanceof Double d) { + argsNode.put(e.getKey(), d); + } else if (v instanceof Boolean b) { + argsNode.put(e.getKey(), b); + } else if (v != null) { + argsNode.put(e.getKey(), v.toString()); + } + } + return new ToolInvocation().setArguments(argsNode); + } + + private static ToolInvocation invocationWithContext(String sessionId, String toolCallId, Map args) { + return invocationOf(args).setSessionId(sessionId).setToolCallId(toolCallId); + } + + @SuppressWarnings("unchecked") + private static Map schemaOf(ToolDefinition tool) { + return (Map) tool.parameters(); + } + + @SuppressWarnings("unchecked") + private static Map propertiesOf(ToolDefinition tool) { + return (Map) schemaOf(tool).get("properties"); + } + + @SuppressWarnings("unchecked") + private static List requiredOf(ToolDefinition tool) { + return (List) schemaOf(tool).get("required"); + } + + // ── Group 1: Successful inline definitions – arity 0, sync ─────────────────── + + @Test + void from_zeroArg_returnsNameAndDescription() { + ToolDefinition tool = ToolDefinition.from("ping", "Returns pong", () -> "pong"); + assertEquals("ping", tool.name()); + assertEquals("Returns pong", tool.description()); + } + + @Test + void from_zeroArg_invokesHandler() throws Exception { + ToolDefinition tool = ToolDefinition.from("ping", "Returns pong", () -> "pong"); + Object result = tool.handler().invoke(invocationOf(Map.of())).get(); + assertEquals("pong", result); + } + + @Test + void from_zeroArg_emptySchema() { + ToolDefinition tool = ToolDefinition.from("ping", "Returns pong", () -> "pong"); + assertTrue(propertiesOf(tool).isEmpty()); + assertTrue(requiredOf(tool).isEmpty()); + } + + // ── Group 1: Successful inline definitions – arity 1, sync ─────────────────── + + @Test + void from_oneArg_returnsNameAndDescription() { + Param nameParam = Param.of(String.class, "name", "The user's name"); + ToolDefinition tool = ToolDefinition.from("greet", "Greets a user", nameParam, n -> "Hello, " + n + "!"); + assertEquals("greet", tool.name()); + assertEquals("Greets a user", tool.description()); + } + + @Test + void from_oneArg_invokesHandler() throws Exception { + Param nameParam = Param.of(String.class, "name", "The user's name"); + ToolDefinition tool = ToolDefinition.from("greet", "Greets a user", nameParam, n -> "Hello, " + n + "!"); + Object result = tool.handler().invoke(invocationOf(Map.of("name", "Alice"))).get(); + assertEquals("Hello, Alice!", result); + } + + @Test + void from_oneArg_schemaContainsParam() { + Param nameParam = Param.of(String.class, "name", "The user's name"); + ToolDefinition tool = ToolDefinition.from("greet", "Greets a user", nameParam, n -> "Hello, " + n + "!"); + assertTrue(propertiesOf(tool).containsKey("name")); + assertTrue(requiredOf(tool).contains("name")); + } + + // ── Group 1: Successful inline definitions – arity 2, sync ─────────────────── + + @Test + void from_twoArg_invokesHandler() throws Exception { + Param paramA = Param.of(Integer.class, "a", "First number"); + Param paramB = Param.of(Integer.class, "b", "Second number"); + ToolDefinition tool = ToolDefinition.from("add", "Adds two integers", paramA, paramB, + (a, b) -> String.valueOf(a + b)); + Object result = tool.handler().invoke(invocationOf(Map.of("a", 3, "b", 4))).get(); + assertEquals("7", result); + } + + @Test + void from_twoArg_schemaBothParamsPresent() { + Param paramA = Param.of(Integer.class, "a", "First"); + Param paramB = Param.of(Integer.class, "b", "Second"); + ToolDefinition tool = ToolDefinition.from("add", "Adds two integers", paramA, paramB, (a, b) -> a + b); + assertTrue(propertiesOf(tool).containsKey("a")); + assertTrue(propertiesOf(tool).containsKey("b")); + assertTrue(requiredOf(tool).contains("a")); + assertTrue(requiredOf(tool).contains("b")); + } + + // ── Group 2: Async handlers (fromAsync) ────────────────────────────────────── + + @Test + void fromAsync_zeroArg_invokesHandler() throws Exception { + ToolDefinition tool = ToolDefinition.fromAsync("ping_async", "Async ping", + () -> CompletableFuture.completedFuture("pong")); + Object result = tool.handler().invoke(invocationOf(Map.of())).get(); + assertEquals("pong", result); + } + + @Test + void fromAsync_oneArg_invokesHandler() throws Exception { + Param nameParam = Param.of(String.class, "name", "Name to greet"); + ToolDefinition tool = ToolDefinition.fromAsync("greet_async", "Async greet", nameParam, + n -> CompletableFuture.completedFuture("Hi, " + n + "!")); + Object result = tool.handler().invoke(invocationOf(Map.of("name", "Bob"))).get(); + assertEquals("Hi, Bob!", result); + } + + @Test + void fromAsync_twoArg_invokesHandler() throws Exception { + Param paramA = Param.of(Integer.class, "a", "Left operand"); + Param paramB = Param.of(Integer.class, "b", "Right operand"); + ToolDefinition tool = ToolDefinition.fromAsync("add_async", "Async add", paramA, paramB, + (a, b) -> CompletableFuture.completedFuture(String.valueOf(a + b))); + Object result = tool.handler().invoke(invocationOf(Map.of("a", 10, "b", 5))).get(); + assertEquals("15", result); + } + + // ── Group 3: ToolInvocation context injection (sync) ───────────────────────── + + @Test + void fromWithToolInvocation_zeroArg_receivesContext() throws Exception { + ToolDefinition tool = ToolDefinition.fromWithToolInvocation("ctx_sync", "Returns session id", + inv -> "session=" + inv.getSessionId()); + Object result = tool.handler().invoke(invocationWithContext("sess-1", "call-1", Map.of())).get(); + assertEquals("session=sess-1", result); + } + + @Test + void fromWithToolInvocation_zeroArg_emptySchema() { + ToolDefinition tool = ToolDefinition.fromWithToolInvocation("ctx_sync", "Returns session id", + inv -> "session=" + inv.getSessionId()); + assertTrue(propertiesOf(tool).isEmpty()); + assertTrue(requiredOf(tool).isEmpty()); + } + + @Test + void fromWithToolInvocation_oneArg_receivesArgAndContext() throws Exception { + Param phaseParam = Param.of(String.class, "phase", "Current phase"); + ToolDefinition tool = ToolDefinition.fromWithToolInvocation("report", "Report phase", phaseParam, + (phase, inv) -> "phase=" + phase + ",callId=" + inv.getToolCallId()); + Object result = tool.handler().invoke(invocationWithContext("sess-2", "call-42", Map.of("phase", "analysis"))) + .get(); + assertEquals("phase=analysis,callId=call-42", result); + } + + @Test + void fromWithToolInvocation_oneArg_schemaExcludesInvocationParam() { + Param phaseParam = Param.of(String.class, "phase", "Current phase"); + ToolDefinition tool = ToolDefinition.fromWithToolInvocation("report", "Report phase", phaseParam, + (phase, inv) -> phase); + assertTrue(propertiesOf(tool).containsKey("phase")); + assertFalse(propertiesOf(tool).containsKey("invocation")); + assertEquals(List.of("phase"), requiredOf(tool)); + } + + // ── Group 4: Async ToolInvocation context injection ────────────────────────── + + @Test + void fromAsyncWithToolInvocation_zeroArg_receivesContext() throws Exception { + ToolDefinition tool = ToolDefinition.fromAsyncWithToolInvocation("ctx_async", "Async ctx", + inv -> CompletableFuture.completedFuture("callId=" + inv.getToolCallId())); + Object result = tool.handler().invoke(invocationWithContext("sess-3", "call-99", Map.of())).get(); + assertEquals("callId=call-99", result); + } + + @Test + void fromAsyncWithToolInvocation_oneArg_receivesArgAndContext() throws Exception { + Param phaseParam = Param.of(String.class, "phase", "Phase name"); + ToolDefinition tool = ToolDefinition.fromAsyncWithToolInvocation("report_async", "Async report", phaseParam, + (phase, inv) -> CompletableFuture.completedFuture("phase=" + phase + ",sess=" + inv.getSessionId())); + Object result = tool.handler().invoke(invocationWithContext("sess-4", "call-7", Map.of("phase", "planning"))) + .get(); + assertEquals("phase=planning,sess=sess-4", result); + } + + // ── Group 5: Option flag propagation ───────────────────────────────────────── + + @Test + void overridesBuiltInTool_setsFlag() { + ToolDefinition base = ToolDefinition.from("grep", "Custom grep", () -> "ok"); + assertNull(base.overridesBuiltInTool()); + ToolDefinition withOverride = base.overridesBuiltInTool(true); + assertEquals(Boolean.TRUE, withOverride.overridesBuiltInTool()); + } + + @Test + void overridesBuiltInTool_doesNotMutateOriginal() { + ToolDefinition base = ToolDefinition.from("grep", "Custom grep", () -> "ok"); + base.overridesBuiltInTool(true); + assertNull(base.overridesBuiltInTool(), "original must remain unchanged"); + } + + @Test + void skipPermission_setsFlag() { + ToolDefinition base = ToolDefinition.from("read_file", "Reads a file", () -> "contents"); + assertNull(base.skipPermission()); + ToolDefinition withSkip = base.skipPermission(true); + assertEquals(Boolean.TRUE, withSkip.skipPermission()); + } + + @Test + void skipPermission_doesNotMutateOriginal() { + ToolDefinition base = ToolDefinition.from("read_file", "Reads a file", () -> "contents"); + base.skipPermission(true); + assertNull(base.skipPermission(), "original must remain unchanged"); + } + + @Test + void defer_setsAutoMode() { + ToolDefinition base = ToolDefinition.from("search", "Searches things", () -> "results"); + assertNull(base.defer()); + ToolDefinition deferred = base.defer(ToolDefer.AUTO); + assertEquals(ToolDefer.AUTO, deferred.defer()); + } + + @Test + void defer_setsNeverMode() { + ToolDefinition base = ToolDefinition.from("must_preload", "Always preloaded", () -> "ok"); + ToolDefinition neverDeferred = base.defer(ToolDefer.NEVER); + assertEquals(ToolDefer.NEVER, neverDeferred.defer()); + } + + @Test + void defer_doesNotMutateOriginal() { + ToolDefinition base = ToolDefinition.from("search", "Searches things", () -> "results"); + base.defer(ToolDefer.AUTO); + assertNull(base.defer(), "original must remain unchanged"); + } + + @Test + void fluentModifiers_canBeChained() { + ToolDefinition tool = ToolDefinition.from("override_tool", "Overrides built-in", () -> "ok") + .overridesBuiltInTool(true).skipPermission(true).defer(ToolDefer.AUTO); + assertEquals(Boolean.TRUE, tool.overridesBuiltInTool()); + assertEquals(Boolean.TRUE, tool.skipPermission()); + assertEquals(ToolDefer.AUTO, tool.defer()); + } + + @Test + void fluentModifiers_preserveHandlerAndSchema() throws Exception { + Param p = Param.of(String.class, "msg", "A message"); + ToolDefinition tool = ToolDefinition.from("echo", "Echoes message", p, msg -> msg).skipPermission(true) + .overridesBuiltInTool(false); + assertNotNull(tool.handler()); + Object result = tool.handler().invoke(invocationOf(Map.of("msg", "hello"))).get(); + assertEquals("hello", result); + } + + // ── Group 6: Required/default semantics ────────────────────────────────────── + + @Test + void requiredParam_passedValue_usesProvidedValue() throws Exception { + Param p = Param.of(String.class, "word", "A word"); + ToolDefinition tool = ToolDefinition.from("echo", "Echoes", p, w -> w); + Object result = tool.handler().invoke(invocationOf(Map.of("word", "hello"))).get(); + assertEquals("hello", result); + } + + @Test + void requiredParam_missingFromInvocation_throwsIllegalArgumentException() { + Param p = Param.of(String.class, "word", "A required word"); + ToolDefinition tool = ToolDefinition.from("echo", "Echoes", p, w -> w); + var ex = assertThrows(IllegalArgumentException.class, () -> tool.handler().invoke(invocationOf(Map.of()))); + assertTrue(ex.getMessage().contains("word"), "Exception message should mention the missing parameter name"); + } + + @Test + void optionalParamWithDefault_absent_usesDefault() throws Exception { + Param p = Param.of(Integer.class, "limit", "Max results", false, "10"); + ToolDefinition tool = ToolDefinition.from("list", "Lists items", p, lim -> "limit=" + lim); + Object result = tool.handler().invoke(invocationOf(Map.of())).get(); + assertEquals("limit=10", result); + } + + @Test + void optionalParamWithDefault_provided_usesProvidedValue() throws Exception { + Param p = Param.of(Integer.class, "limit", "Max results", false, "10"); + ToolDefinition tool = ToolDefinition.from("list", "Lists items", p, lim -> "limit=" + lim); + Object result = tool.handler().invoke(invocationOf(Map.of("limit", 25))).get(); + assertEquals("limit=25", result); + } + + @Test + void optionalParamWithDefault_schemaNotInRequired() { + Param p = Param.of(Integer.class, "limit", "Max results", false, "10"); + ToolDefinition tool = ToolDefinition.from("list", "Lists items", p, lim -> "limit=" + lim); + assertFalse(requiredOf(tool).contains("limit")); + assertTrue(propertiesOf(tool).containsKey("limit")); + } + + @Test + void optionalParam_absent_noDefaultYieldsNull() throws Exception { + Param p = Param.of(String.class, "title", "Optional title", false, ""); + ToolDefinition tool = ToolDefinition.from("greet", "Greets", p, t -> t == null ? "(no title)" : t); + Object result = tool.handler().invoke(invocationOf(Map.of())).get(); + assertEquals("(no title)", result); + } + + @Test + void defaultValueAppearsInSchema() { + Param p = Param.of(Integer.class, "limit", "Max results", false, "5"); + ToolDefinition tool = ToolDefinition.from("list", "Lists items", p, lim -> lim.toString()); + @SuppressWarnings("unchecked") + Map limitPropSchema = (Map) propertiesOf(tool).get("limit"); + assertNotNull(limitPropSchema, "Schema must include 'limit' property"); + assertEquals(5, limitPropSchema.get("default"), "Default value must appear in schema"); + } + + // ── Group 7: Error / validation paths ──────────────────────────────────────── + + @Test + void from_nullName_throwsIllegalArgumentException() { + assertThrows(IllegalArgumentException.class, () -> ToolDefinition.from(null, "desc", () -> "ok")); + } + + @Test + void from_blankName_throwsIllegalArgumentException() { + assertThrows(IllegalArgumentException.class, () -> ToolDefinition.from(" ", "desc", () -> "ok")); + } + + @Test + void from_nullDescription_throwsIllegalArgumentException() { + assertThrows(IllegalArgumentException.class, () -> ToolDefinition.from("tool", null, () -> "ok")); + } + + @Test + void from_blankDescription_throwsIllegalArgumentException() { + assertThrows(IllegalArgumentException.class, () -> ToolDefinition.from("tool", "", () -> "ok")); + } + + @Test + void from_nullHandler_throwsIllegalArgumentException() { + assertThrows(IllegalArgumentException.class, + () -> ToolDefinition.from("tool", "desc", (java.util.function.Supplier) null)); + } + + @Test + void from_oneArg_nullParam_throwsIllegalArgumentException() { + assertThrows(IllegalArgumentException.class, + () -> ToolDefinition.from("tool", "desc", (Param) null, s -> s)); + } + + @Test + void from_twoArg_nullFirstParam_throwsIllegalArgumentException() { + Param p2 = Param.of(String.class, "b", "B param"); + assertThrows(IllegalArgumentException.class, () -> ToolDefinition.from("tool", "desc", null, p2, (a, b) -> a)); + } + + @Test + void from_twoArg_nullSecondParam_throwsIllegalArgumentException() { + Param p1 = Param.of(String.class, "a", "A param"); + assertThrows(IllegalArgumentException.class, () -> ToolDefinition.from("tool", "desc", p1, null, (a, b) -> a)); + } + + @Test + void from_twoArg_duplicateParamNames_throwsIllegalArgumentException() { + Param p1 = Param.of(String.class, "name", "Name 1"); + Param p2 = Param.of(String.class, "name", "Name 2"); + var ex = assertThrows(IllegalArgumentException.class, + () -> ToolDefinition.from("tool", "desc", p1, p2, (a, b) -> a + b)); + assertTrue(ex.getMessage().contains("name"), "error must mention the duplicate param name"); + assertTrue(ex.getMessage().contains("tool"), "error must mention the tool name"); + } + + @Test + void fromAsync_nullName_throwsIllegalArgumentException() { + assertThrows(IllegalArgumentException.class, + () -> ToolDefinition.fromAsync(null, "desc", () -> CompletableFuture.completedFuture("ok"))); + } + + @Test + void fromAsync_nullHandler_throwsIllegalArgumentException() { + assertThrows(IllegalArgumentException.class, () -> ToolDefinition.fromAsync("tool", "desc", + (java.util.function.Supplier>) null)); + } + + @Test + void fromWithToolInvocation_nullName_throwsIllegalArgumentException() { + assertThrows(IllegalArgumentException.class, + () -> ToolDefinition.fromWithToolInvocation(null, "desc", inv -> "ok")); + } + + @Test + void fromAsyncWithToolInvocation_nullDescription_throwsIllegalArgumentException() { + assertThrows(IllegalArgumentException.class, () -> ToolDefinition.fromAsyncWithToolInvocation("tool", null, + inv -> CompletableFuture.completedFuture("ok"))); + } + + // ── Group 8: Schema structure + // ───────────────────────────────────────────────── + + @Test + void schema_zeroArg_hasTypeObjectAndEmptyMaps() { + ToolDefinition tool = ToolDefinition.from("noop", "No-op", () -> "done"); + Map schema = schemaOf(tool); + assertEquals("object", schema.get("type")); + assertTrue(((Map) schema.get("properties")).isEmpty()); + assertTrue(((List) schema.get("required")).isEmpty()); + } + + @Test + void schema_oneArg_hasCorrectTypeForString() { + Param p = Param.of(String.class, "query", "Search query"); + ToolDefinition tool = ToolDefinition.from("search", "Searches", p, q -> q); + @SuppressWarnings("unchecked") + Map querySchema = (Map) propertiesOf(tool).get("query"); + assertNotNull(querySchema); + assertEquals("string", querySchema.get("type")); + assertEquals("Search query", querySchema.get("description")); + } + + @Test + void schema_oneArg_hasCorrectTypeForInteger() { + Param p = Param.of(Integer.class, "count", "Item count"); + ToolDefinition tool = ToolDefinition.from("count_items", "Counts items", p, c -> c.toString()); + @SuppressWarnings("unchecked") + Map countSchema = (Map) propertiesOf(tool).get("count"); + assertNotNull(countSchema); + assertEquals("integer", countSchema.get("type")); + } + + @Test + void schema_oneArg_hasCorrectTypeForBoolean() { + Param p = Param.of(Boolean.class, "enabled", "Whether enabled"); + ToolDefinition tool = ToolDefinition.from("toggle", "Toggles", p, e -> e.toString()); + @SuppressWarnings("unchecked") + Map enabledSchema = (Map) propertiesOf(tool).get("enabled"); + assertNotNull(enabledSchema); + assertEquals("boolean", enabledSchema.get("type")); + } + + @Test + void schema_oneArg_enumTypeHasStringAndEnumValues() { + Param p = Param.of(Color.class, "color", "A color"); + ToolDefinition tool = ToolDefinition.from("paint", "Paints with a color", p, c -> c.name()); + @SuppressWarnings("unchecked") + Map colorSchema = (Map) propertiesOf(tool).get("color"); + assertNotNull(colorSchema); + assertEquals("string", colorSchema.get("type")); + @SuppressWarnings("unchecked") + List enumValues = (List) colorSchema.get("enum"); + assertNotNull(enumValues); + assertTrue(enumValues.contains("RED")); + assertTrue(enumValues.contains("GREEN")); + assertTrue(enumValues.contains("BLUE")); + } + + // ── Group 9: Result formatting + // ──────────────────────────────────────────────── + + @Test + void resultFormatting_stringReturnedAsIs() throws Exception { + ToolDefinition tool = ToolDefinition.from("echo", "Echoes", () -> "plain text"); + Object result = tool.handler().invoke(invocationOf(Map.of())).get(); + assertEquals("plain text", result); + } + + @Test + void resultFormatting_nullMappedToSuccess() throws Exception { + ToolDefinition tool = ToolDefinition.from("noop", "No-op", () -> null); + Object result = tool.handler().invoke(invocationOf(Map.of())).get(); + assertEquals("Success", result); + } + + @Test + void resultFormatting_nonStringSerializedToJson() throws Exception { + Param p = Param.of(String.class, "key", "Key name"); + ToolDefinition tool = ToolDefinition.from("to_map", "Wraps in map", p, k -> Map.of("key", k, "value", 42)); + Object result = tool.handler().invoke(invocationOf(Map.of("key", "x"))).get(); + assertNotNull(result); + assertTrue(result instanceof String, "Non-String should be JSON-serialized to String"); + String json = (String) result; + ObjectMapper mapper = new ObjectMapper(); + JsonNode node = mapper.readTree(json); + assertTrue(node.isObject(), "Result should be a JSON object"); + assertEquals("x", node.get("key").asText(), "JSON must contain key field with value 'x'"); + assertEquals(42, node.get("value").asInt(), "JSON must contain value field with value 42"); + } + + @Test + void resultFormatting_integerSerializedToJson() throws Exception { + ToolDefinition tool = ToolDefinition.from("forty_two", "Returns 42", () -> 42); + Object result = tool.handler().invoke(invocationOf(Map.of())).get(); + assertEquals("42", result); + } + + // ── Group 10: Argument coercion + // ─────────────────────────────────────────────── + + @Test + void coercion_stringArgPassedThrough() throws Exception { + Param p = Param.of(String.class, "msg", "A message"); + ToolDefinition tool = ToolDefinition.from("echo", "Echoes message", p, m -> m); + Object result = tool.handler().invoke(invocationOf(Map.of("msg", "hello world"))).get(); + assertEquals("hello world", result); + } + + @Test + void coercion_integerArgFromJsonNumber() throws Exception { + Param p = Param.of(Integer.class, "n", "An integer"); + ToolDefinition tool = ToolDefinition.from("double_it", "Doubles n", p, n -> String.valueOf(n * 2)); + Object result = tool.handler().invoke(invocationOf(Map.of("n", 7))).get(); + assertEquals("14", result); + } + + @Test + void coercion_booleanArg() throws Exception { + Param p = Param.of(Boolean.class, "flag", "A flag"); + ToolDefinition tool = ToolDefinition.from("flagged", "Reports flag", p, f -> f ? "yes" : "no"); + Object result = tool.handler().invoke(invocationOf(Map.of("flag", true))).get(); + assertEquals("yes", result); + } + + @Test + void coercion_enumArgFromString() throws Exception { + Param p = Param.of(Color.class, "color", "A color"); + ToolDefinition tool = ToolDefinition.from("paint", "Paints", p, c -> c.name().toLowerCase()); + Object result = tool.handler().invoke(invocationOf(Map.of("color", "GREEN"))).get(); + assertEquals("green", result); + } + + @Test + void coercion_defaultIntegerParsedCorrectly() throws Exception { + Param p = Param.of(Integer.class, "limit", "Max count", false, "99"); + ToolDefinition tool = ToolDefinition.from("bounded", "Bounded list", p, lim -> "got=" + lim); + // No argument provided — should use default 99 + Object result = tool.handler().invoke(invocationOf(Map.of())).get(); + assertEquals("got=99", result); + } + + // ── Inner types for test helpers + // ────────────────────────────────────────────── + + enum Color { + RED, GREEN, BLUE + } +} diff --git a/java/src/test/java/com/github/copilot/tool/ParamTest.java b/java/src/test/java/com/github/copilot/tool/ParamTest.java new file mode 100644 index 0000000000..75f6e44222 --- /dev/null +++ b/java/src/test/java/com/github/copilot/tool/ParamTest.java @@ -0,0 +1,262 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +package com.github.copilot.tool; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +/** + * Unit tests for {@link Param} runtime parameter metadata. + */ +public class ParamTest { + + // ------------------------------------------------------------------ + // Factory method: of(type, name, description) + // ------------------------------------------------------------------ + + @Test + void ofCreatesRequiredParamWithNoDefault() { + Param p = Param.of(String.class, "query", "Search query"); + assertEquals(String.class, p.type()); + assertEquals("query", p.name()); + assertEquals("Search query", p.description()); + assertTrue(p.required()); + assertEquals("", p.defaultValue()); + assertFalse(p.hasDefaultValue()); + } + + @Test + void ofFullFactoryCreatesOptionalParamWithDefault() { + Param p = Param.of(Integer.class, "limit", "Max results", false, "10"); + assertEquals(Integer.class, p.type()); + assertEquals("limit", p.name()); + assertEquals("Max results", p.description()); + assertFalse(p.required()); + assertEquals("10", p.defaultValue()); + assertTrue(p.hasDefaultValue()); + } + + // ------------------------------------------------------------------ + // Validation: blank name/description rejected + // ------------------------------------------------------------------ + + @Test + void rejectsNullName() { + var ex = assertThrows(IllegalArgumentException.class, () -> Param.of(String.class, null, "desc")); + assertTrue(ex.getMessage().contains("name")); + } + + @Test + void rejectsBlankName() { + var ex = assertThrows(IllegalArgumentException.class, () -> Param.of(String.class, " ", "desc")); + assertTrue(ex.getMessage().contains("name")); + } + + @Test + void rejectsNullDescription() { + var ex = assertThrows(IllegalArgumentException.class, () -> Param.of(String.class, "n", null)); + assertTrue(ex.getMessage().contains("description")); + } + + @Test + void rejectsBlankDescription() { + var ex = assertThrows(IllegalArgumentException.class, () -> Param.of(String.class, "n", "")); + assertTrue(ex.getMessage().contains("description")); + } + + // ------------------------------------------------------------------ + // Validation: required=true with non-empty default rejected + // ------------------------------------------------------------------ + + @Test + void rejectsRequiredWithNonEmptyDefault() { + var ex = assertThrows(IllegalArgumentException.class, () -> Param.of(String.class, "x", "desc", true, "val")); + assertTrue(ex.getMessage().contains("required=true")); + } + + @Test + void allowsRequiredWithEmptyDefault() { + Param p = Param.of(String.class, "x", "desc", true, ""); + assertTrue(p.required()); + assertFalse(p.hasDefaultValue()); + } + + @Test + void allowsRequiredWithNullDefault() { + Param p = Param.of(String.class, "x", "desc", true, null); + assertTrue(p.required()); + assertEquals("", p.defaultValue()); + } + + // ------------------------------------------------------------------ + // Validation: default value type checking + // ------------------------------------------------------------------ + + @Test + void validatesIntegerDefault() { + // valid + Param p = Param.of(Integer.class, "n", "num", false, "42"); + assertEquals("42", p.defaultValue()); + + // invalid + assertThrows(IllegalArgumentException.class, () -> Param.of(Integer.class, "n", "num", false, "abc")); + } + + @Test + void validatesLongDefault() { + Param p = Param.of(Long.class, "n", "num", false, "999999999999"); + assertEquals("999999999999", p.defaultValue()); + + assertThrows(IllegalArgumentException.class, () -> Param.of(Long.class, "n", "num", false, "notlong")); + } + + @Test + void validatesDoubleDefault() { + Param p = Param.of(Double.class, "d", "decimal", false, "3.14"); + assertEquals("3.14", p.defaultValue()); + + assertThrows(IllegalArgumentException.class, () -> Param.of(Double.class, "d", "decimal", false, "xyz")); + } + + @Test + void validatesFloatDefault() { + Param p = Param.of(Float.class, "f", "float val", false, "1.5"); + assertEquals("1.5", p.defaultValue()); + + assertThrows(IllegalArgumentException.class, () -> Param.of(Float.class, "f", "float val", false, "notfloat")); + } + + @Test + void validatesShortDefault() { + Param p = Param.of(Short.class, "s", "short val", false, "100"); + assertEquals("100", p.defaultValue()); + + assertThrows(IllegalArgumentException.class, () -> Param.of(Short.class, "s", "short val", false, "99999")); + } + + @Test + void validatesByteDefault() { + Param p = Param.of(Byte.class, "b", "byte val", false, "127"); + assertEquals("127", p.defaultValue()); + + assertThrows(IllegalArgumentException.class, () -> Param.of(Byte.class, "b", "byte val", false, "999")); + } + + @Test + void validatesBooleanDefault() { + Param p1 = Param.of(Boolean.class, "b", "flag", false, "true"); + assertEquals("true", p1.defaultValue()); + + Param p2 = Param.of(Boolean.class, "b", "flag", false, "FALSE"); + assertEquals("FALSE", p2.defaultValue()); + + assertThrows(IllegalArgumentException.class, () -> Param.of(Boolean.class, "b", "flag", false, "yes")); + } + + @Test + void validatesEnumDefault() { + Param p = Param.of(TestEnum.class, "e", "enum val", false, "ALPHA"); + assertEquals("ALPHA", p.defaultValue()); + + assertThrows(IllegalArgumentException.class, () -> Param.of(TestEnum.class, "e", "enum val", false, "INVALID")); + } + + @Test + void rejectsUnsupportedTypeWithDefault() { + assertThrows(IllegalArgumentException.class, () -> Param.of(Object.class, "o", "object", false, "something")); + } + + @Test + void allowsStringDefault() { + Param p = Param.of(String.class, "s", "string", false, "hello"); + assertEquals("hello", p.defaultValue()); + } + + // ------------------------------------------------------------------ + // Fluent mutators return new instances + // ------------------------------------------------------------------ + + @Test + void nameMutatorReturnsNewInstance() { + Param original = Param.of(String.class, "a", "desc"); + Param renamed = original.name("b"); + assertEquals("a", original.name()); + assertEquals("b", renamed.name()); + } + + @Test + void descriptionMutatorReturnsNewInstance() { + Param original = Param.of(String.class, "a", "desc1"); + Param updated = original.description("desc2"); + assertEquals("desc1", original.description()); + assertEquals("desc2", updated.description()); + } + + @Test + void requiredMutatorReturnsNewInstance() { + Param original = Param.of(String.class, "a", "desc"); + Param optional = original.required(false); + assertTrue(original.required()); + assertFalse(optional.required()); + } + + @Test + void defaultValueMutatorSetsOptional() { + Param original = Param.of(String.class, "a", "desc"); + Param withDefault = original.defaultValue("val"); + assertTrue(original.required()); + assertFalse(withDefault.required()); + assertEquals("val", withDefault.defaultValue()); + assertTrue(withDefault.hasDefaultValue()); + } + + // ------------------------------------------------------------------ + // equals / hashCode / toString + // ------------------------------------------------------------------ + + @Test + void equalParamsAreEqual() { + Param a = Param.of(String.class, "x", "desc"); + Param b = Param.of(String.class, "x", "desc"); + assertEquals(a, b); + assertEquals(a.hashCode(), b.hashCode()); + } + + @Test + void differentParamsAreNotEqual() { + Param a = Param.of(String.class, "x", "desc"); + Param b = Param.of(String.class, "y", "desc"); + assertNotEquals(a, b); + } + + @Test + void toStringContainsName() { + Param p = Param.of(String.class, "query", "Search"); + assertTrue(p.toString().contains("query")); + assertTrue(p.toString().contains("String")); + } + + // ------------------------------------------------------------------ + // Null type rejected + // ------------------------------------------------------------------ + + @Test + void rejectsNullType() { + assertThrows(NullPointerException.class, () -> Param.of(null, "n", "desc")); + } + + // ------------------------------------------------------------------ + // Test enum for validation tests + // ------------------------------------------------------------------ + + enum TestEnum { + ALPHA, BETA + } +} diff --git a/nodejs/package-lock.json b/nodejs/package-lock.json index 8ad03e8e7f..2275d9f59e 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.67", + "@github/copilot": "^1.0.69", "vscode-jsonrpc": "^8.2.1", "zod": "^4.3.6" }, @@ -699,9 +699,9 @@ } }, "node_modules/@github/copilot": { - "version": "1.0.67", - "resolved": "https://registry.npmjs.org/@github/copilot/-/copilot-1.0.67.tgz", - "integrity": "sha512-5YEY9LNXBT9Q8uShjCdYcornJJJhGtdIzSYla2+pjfXYpHsDVibqYubzYjfgffOUKFChyzOpH7n/868+t56iIg==", + "version": "1.0.69", + "resolved": "https://registry.npmjs.org/@github/copilot/-/copilot-1.0.69.tgz", + "integrity": "sha512-sri6PKtRQu7mfok3eV505fmYo6M6PGZpB1vd+QbMEm2rEZaKE2+wJGCAtkpQNKfAWBvvoW/C94IaeFyyulUvqQ==", "license": "SEE LICENSE IN LICENSE.md", "dependencies": { "detect-libc": "^2.1.2" @@ -710,20 +710,20 @@ "copilot": "npm-loader.js" }, "optionalDependencies": { - "@github/copilot-darwin-arm64": "1.0.67", - "@github/copilot-darwin-x64": "1.0.67", - "@github/copilot-linux-arm64": "1.0.67", - "@github/copilot-linux-x64": "1.0.67", - "@github/copilot-linuxmusl-arm64": "1.0.67", - "@github/copilot-linuxmusl-x64": "1.0.67", - "@github/copilot-win32-arm64": "1.0.67", - "@github/copilot-win32-x64": "1.0.67" + "@github/copilot-darwin-arm64": "1.0.69", + "@github/copilot-darwin-x64": "1.0.69", + "@github/copilot-linux-arm64": "1.0.69", + "@github/copilot-linux-x64": "1.0.69", + "@github/copilot-linuxmusl-arm64": "1.0.69", + "@github/copilot-linuxmusl-x64": "1.0.69", + "@github/copilot-win32-arm64": "1.0.69", + "@github/copilot-win32-x64": "1.0.69" } }, "node_modules/@github/copilot-darwin-arm64": { - "version": "1.0.67", - "resolved": "https://registry.npmjs.org/@github/copilot-darwin-arm64/-/copilot-darwin-arm64-1.0.67.tgz", - "integrity": "sha512-CO3mpgFXcN6e7ZsSmjMkt1AKxMfb1+mjdn3yrf2DRnnWIURSK9kGvw+E+E1+YE37D1MBiUn/VOBmhRad5+vl0A==", + "version": "1.0.69", + "resolved": "https://registry.npmjs.org/@github/copilot-darwin-arm64/-/copilot-darwin-arm64-1.0.69.tgz", + "integrity": "sha512-LpaVS2o21BbYTFUlkqdwUJXqE0l1Lp50Cb/EHru3z+5n5x7zNUU5IlPxdMg5gHMVw78clnMn2zSJbe5hYFyDDQ==", "cpu": [ "arm64" ], @@ -737,9 +737,9 @@ } }, "node_modules/@github/copilot-darwin-x64": { - "version": "1.0.67", - "resolved": "https://registry.npmjs.org/@github/copilot-darwin-x64/-/copilot-darwin-x64-1.0.67.tgz", - "integrity": "sha512-M20Hpn3bOJRkVwAIVRK4ZlX66AqtmGfXZRxZBRFQC045QIwcfmVUP45sTSgXDb4uHWeK0cZgdTdniHwKGtMplw==", + "version": "1.0.69", + "resolved": "https://registry.npmjs.org/@github/copilot-darwin-x64/-/copilot-darwin-x64-1.0.69.tgz", + "integrity": "sha512-QL5bsmnYqliBVIEE8RY91e6yAr39c2TNvJc/5IzoaKMlQ9MCrD9xakBJwd7LOS0SiOwRE4fqn8pUA4L1+UU5fg==", "cpu": [ "x64" ], @@ -753,9 +753,9 @@ } }, "node_modules/@github/copilot-linux-arm64": { - "version": "1.0.67", - "resolved": "https://registry.npmjs.org/@github/copilot-linux-arm64/-/copilot-linux-arm64-1.0.67.tgz", - "integrity": "sha512-b4ePtFBow+Ior+aVLKA1hHxhR5wF+ql5CD7TSg/NHGYgc1kwD+3a9uKSENy05J5Lit/G/DZ9C6JwowvvdMWSKg==", + "version": "1.0.69", + "resolved": "https://registry.npmjs.org/@github/copilot-linux-arm64/-/copilot-linux-arm64-1.0.69.tgz", + "integrity": "sha512-SOibp0I6APLoY2KkccbNOISpbhE3lp41s9fM3eANqfdoHQLsgBopfk4ruBP8k/DzjQA6t4MrbV8v6dLixAqv3w==", "cpu": [ "arm64" ], @@ -769,9 +769,9 @@ } }, "node_modules/@github/copilot-linux-x64": { - "version": "1.0.67", - "resolved": "https://registry.npmjs.org/@github/copilot-linux-x64/-/copilot-linux-x64-1.0.67.tgz", - "integrity": "sha512-4ynZyfKnWAdvEPAFDDBIz1wpFttcOTJu4Y8Mlz5oXCBA0NM/rwr8K4l7Adp8UzwbfmdrMJ9y+zivqRBMDbPInA==", + "version": "1.0.69", + "resolved": "https://registry.npmjs.org/@github/copilot-linux-x64/-/copilot-linux-x64-1.0.69.tgz", + "integrity": "sha512-URT7UaR3l1VAAtEpbKjybup2vxHTMSfDTVlu0jinu1O2nWxFOeQ9N4BQgRdcCB7wDu3N8Yz47o3fV1Gob6Z1Yw==", "cpu": [ "x64" ], @@ -785,9 +785,9 @@ } }, "node_modules/@github/copilot-linuxmusl-arm64": { - "version": "1.0.67", - "resolved": "https://registry.npmjs.org/@github/copilot-linuxmusl-arm64/-/copilot-linuxmusl-arm64-1.0.67.tgz", - "integrity": "sha512-IjezxBU8fYUr/b5hEiniXqzwoOrJ4egrQSBbG96M+roLTqd9txP0MgxZtcRtKV7phRIdIGE109wwrn4H6hSqmA==", + "version": "1.0.69", + "resolved": "https://registry.npmjs.org/@github/copilot-linuxmusl-arm64/-/copilot-linuxmusl-arm64-1.0.69.tgz", + "integrity": "sha512-Y6DY9RkyWV6l+S0cNz4d+AkUa38goNV5StQH3j8OoVrndK3+RgQeUjG1XpJ//PzXEXi4vQ0cZuc9XzFj/TY7tw==", "cpu": [ "arm64" ], @@ -801,9 +801,9 @@ } }, "node_modules/@github/copilot-linuxmusl-x64": { - "version": "1.0.67", - "resolved": "https://registry.npmjs.org/@github/copilot-linuxmusl-x64/-/copilot-linuxmusl-x64-1.0.67.tgz", - "integrity": "sha512-Zy/rbja1lnhzDoNfn051H0EybCseCvjvH7WmbcHCayjXUjzXeKF6OmAt4hvqFZH87ttT3KbKtQ8/6oDUhhM2YQ==", + "version": "1.0.69", + "resolved": "https://registry.npmjs.org/@github/copilot-linuxmusl-x64/-/copilot-linuxmusl-x64-1.0.69.tgz", + "integrity": "sha512-h0aNgUWRu70Tgp4/7Vdqa/4XKJi96wP32R1PoKeXkz1QNxb6i/IQxhoCjjS+xFon9e2kefKdF8pDrDtUJjsiBQ==", "cpu": [ "x64" ], @@ -817,9 +817,9 @@ } }, "node_modules/@github/copilot-win32-arm64": { - "version": "1.0.67", - "resolved": "https://registry.npmjs.org/@github/copilot-win32-arm64/-/copilot-win32-arm64-1.0.67.tgz", - "integrity": "sha512-O3VFRS5v9NXRP8o+N1SvcFbBqECDzZP7XQBeBj2Vcrma80gdJc5GQub/w2mwmr1w5UbwgzJkRasm0Ec/jxbcoA==", + "version": "1.0.69", + "resolved": "https://registry.npmjs.org/@github/copilot-win32-arm64/-/copilot-win32-arm64-1.0.69.tgz", + "integrity": "sha512-7DMoC1uwt01yZf48xq8MgKdnHacXabfsGsOizbgmrlZMfvQQofrF55x1Efu3BKsEKFGRl4fCLjJHJYCz/nWsHg==", "cpu": [ "arm64" ], @@ -833,9 +833,9 @@ } }, "node_modules/@github/copilot-win32-x64": { - "version": "1.0.67", - "resolved": "https://registry.npmjs.org/@github/copilot-win32-x64/-/copilot-win32-x64-1.0.67.tgz", - "integrity": "sha512-td5tQ/nve5dB7RPvNglBZwa/6DJqiOBgacXXa1GpYcohqpCzoI8gONNkeaeyr6oF4iu5wXJ9krUNr6QXL4yB5Q==", + "version": "1.0.69", + "resolved": "https://registry.npmjs.org/@github/copilot-win32-x64/-/copilot-win32-x64-1.0.69.tgz", + "integrity": "sha512-5uD1/k6oVzqRhiS9jLrySGa20HjItqUJsaMe+bV6eYT7zQPF/74xihEjxMm3/bJcATW2SesiYQxyNzTcKm3qRA==", "cpu": [ "x64" ], diff --git a/nodejs/package.json b/nodejs/package.json index 06c34b77f4..fc96f6ad59 100644 --- a/nodejs/package.json +++ b/nodejs/package.json @@ -56,7 +56,7 @@ "author": "GitHub", "license": "MIT", "dependencies": { - "@github/copilot": "^1.0.67", + "@github/copilot": "^1.0.69", "vscode-jsonrpc": "^8.2.1", "zod": "^4.3.6" }, diff --git a/nodejs/samples/package-lock.json b/nodejs/samples/package-lock.json index 9d24dafe90..9d57bb9fe7 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.67", + "@github/copilot": "^1.0.69", "vscode-jsonrpc": "^8.2.1", "zod": "^4.3.6" }, diff --git a/nodejs/src/client.ts b/nodejs/src/client.ts index 5d0f51ac1b..9f430600ca 100644 --- a/nodejs/src/client.ts +++ b/nodejs/src/client.ts @@ -33,7 +33,11 @@ import { registerClientGlobalApiHandlers, registerClientSessionApiHandlers, } from "./generated/rpc.js"; -import type { OpenCanvasInstance, SessionUpdateOptionsParams } from "./generated/rpc.js"; +import type { + GitHubTelemetryNotification, + OpenCanvasInstance, + SessionUpdateOptionsParams, +} from "./generated/rpc.js"; import { getSdkProtocolVersion } from "./sdkProtocolVersion.js"; import { CopilotSession } from "./session.js"; import { createSessionFsAdapter, type SessionFsProvider } from "./sessionFsProvider.js"; @@ -514,7 +518,8 @@ export class CopilotClient { /** Connection-level session filesystem config, set via constructor option. */ private sessionFsConfig: SessionFsConfig | null = null; private requestHandler: CopilotRequestHandler | null = null; - private llmInferenceHandlers: import("./generated/rpc.js").ClientGlobalApiHandlers = {}; + private onGitHubTelemetry?: (notification: GitHubTelemetryNotification) => void | Promise; + private clientGlobalHandlers: import("./generated/rpc.js").ClientGlobalApiHandlers = {}; /** * Typed server-scoped RPC methods. @@ -634,7 +639,8 @@ export class CopilotClient { this.onGetTraceContext = options.onGetTraceContext; this.sessionFsConfig = options.sessionFs ?? null; this.requestHandler = options.requestHandler ?? null; - this.setupLlmInference(); + this.onGitHubTelemetry = options.onGitHubTelemetry; + this.setupClientGlobalHandlers(); const effectiveEnv = options.env ?? process.env; this.resolvedEnv = effectiveEnv; @@ -751,19 +757,30 @@ export class CopilotClient { session.clientSessionApis.sessionFs = createSessionFsAdapter(provider); } - private setupLlmInference(): void { - if (!this.requestHandler) { - return; - } - this.llmInferenceHandlers = { - llmInference: createCopilotRequestAdapter(this.requestHandler, () => { + private setupClientGlobalHandlers(): void { + const handlers: import("./generated/rpc.js").ClientGlobalApiHandlers = {}; + if (this.requestHandler) { + handlers.llmInference = createCopilotRequestAdapter(this.requestHandler, () => { if (!this.connection) { return undefined; } this._rpc ??= createServerRpc(this.connection); return this._rpc; - }), - }; + }); + } + if (this.onGitHubTelemetry) { + const onGitHubTelemetry = this.onGitHubTelemetry; + handlers.gitHubTelemetry = { + event: async (notification) => { + try { + await onGitHubTelemetry(notification); + } catch { + // Ignore handler errors + } + }, + }; + } + this.clientGlobalHandlers = handlers; } /** @@ -1426,6 +1443,9 @@ export class CopilotClient { workingDirectory: config.workingDirectory, streaming: config.streaming, includeSubAgentStreamingEvents: config.includeSubAgentStreamingEvents ?? true, + ...(this.onGitHubTelemetry != null + ? { enableGitHubTelemetryForwarding: true } + : {}), mcpServers: toWireMcpServers(config.mcpServers), mcpOAuthTokenStorage: config.mcpOAuthTokenStorage, envValueMode: "direct", @@ -1642,6 +1662,9 @@ export class CopilotClient { enableSkills: config.enableSkills, streaming: config.streaming, includeSubAgentStreamingEvents: config.includeSubAgentStreamingEvents ?? true, + ...(this.onGitHubTelemetry != null + ? { enableGitHubTelemetryForwarding: true } + : {}), mcpServers: toWireMcpServers(config.mcpServers), mcpOAuthTokenStorage: config.mcpOAuthTokenStorage, envValueMode: "direct", @@ -1823,9 +1846,18 @@ export class CopilotClient { let serverVersion: number | undefined; try { - const result = await raceAgainstExit( - this.internalRpc.connect({ token: this.effectiveConnectionToken }) - ); + const connectParams: { + token?: string; + enableGitHubTelemetryForwarding?: boolean; + } = { token: this.effectiveConnectionToken }; + // Opt in to GitHub telemetry forwarding at the connection level when a + // handler is registered (mirrors the runtime, which reads this flag on the + // `connect` handshake so the first session's un-replayable `session.start` + // event is forwarded). Also sent on session.create/resume for older CLIs. + if (this.onGitHubTelemetry != null) { + connectParams.enableGitHubTelemetryForwarding = true; + } + const result = await raceAgainstExit(this.internalRpc.connect(connectParams)); serverVersion = result.protocolVersion; } catch (err) { if ( @@ -2565,7 +2597,7 @@ export class CopilotClient { // Register client *global* API handlers (e.g. LLM inference) on the // same connection. These methods carry no implicit sessionId dispatch // — the runtime calls into a single handler for the whole connection. - registerClientGlobalApiHandlers(this.connection, this.llmInferenceHandlers); + registerClientGlobalApiHandlers(this.connection, this.clientGlobalHandlers); this.connection.onClose(() => { this.state = "disconnected"; diff --git a/nodejs/src/generated/rpc.ts b/nodejs/src/generated/rpc.ts index 79991dae9e..4814ea191f 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, McpServerSource, McpServerStatus, PermissionPromptRequest, PermissionRule, ReasoningSummary, SessionEvent, SessionLimitsConfig, SessionMode, ShutdownType, SkillSource, UserToolSessionApproval } from "./session-events.js"; +import type { AbortReason, Attachment, ContextTier, EmbeddedBlobResourceContents, EmbeddedTextResourceContents, McpServerSource, McpServerStatus, PermissionPromptRequest, PermissionRule, ReasoningSummary, SessionEvent, SessionLimitsConfig, SessionMode, ShutdownType, SkillSource, UserToolSessionApproval, Verbosity } from "./session-events.js"; /** * Initial authentication info for the session. @@ -202,6 +202,20 @@ export type AgentRegistrySpawnValidationErrorField = | "model" /** The permissionMode parameter */ | "permissionMode"; +/** + * Current or requested allow-all mode. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "PermissionsAllowAllMode". + */ +/** @experimental */ +export type PermissionsAllowAllMode = + /** Permission requests follow the normal approval flow. */ + | "off" + /** Tool, path, and URL permission requests are automatically approved. */ + | "on" + /** Permission requests follow the normal approval flow with an LLM advisory recommendation attached; clients may choose to auto-approve requests the judge evaluated as acceptable. */ + | "auto"; /** * Authentication type * @@ -280,6 +294,84 @@ export type ContentFilterMode = | "markdown" /** Remove characters that can hide directives. */ | "hidden_characters"; +/** + * Source category for a collected debug bundle entry. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "DebugCollectLogsSource". + */ +/** @experimental */ +export type DebugCollectLogsSource = + /** Session event log. */ + | "events" + /** Process log for the session. */ + | "process-log" + /** Interactive shell log for the session. */ + | "shell-log" + /** Caller-provided diagnostic entry. */ + | "additional"; +/** + * Destination for the redacted debug bundle. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "DebugCollectLogsDestination". + */ +/** @experimental */ +export type DebugCollectLogsDestination = + | { + /** + * Absolute or server-relative path for the .tgz archive to create. + */ + outputPath: string; + /** + * When true, create the archive atomically without overwriting an existing file by appending ` (N)` before the extension as needed. Defaults to false. + */ + noOverwrite?: boolean; + kind: "archive"; + } + | { + /** + * Directory where redacted files should be staged. The directory is created if needed. + */ + outputDirectory: string; + kind: "directory"; + }; +/** + * Kind of caller-provided debug log entry. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "DebugCollectLogsEntryKind". + */ +/** @experimental */ +export type DebugCollectLogsEntryKind = + /** Include a single server-local file. */ + | "file" + /** Include files from a server-local directory recursively. */ + | "directory"; +/** + * How a collected debug entry should be redacted before being staged. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "DebugCollectLogsRedaction". + */ +/** @experimental */ +export type DebugCollectLogsRedaction = + /** Redact the file as plain UTF-8 log text. */ + | "plain-text" + /** Redact each non-empty line as a session event JSON object, falling back to plain-text redaction for malformed lines. */ + | "events-jsonl"; +/** + * Destination kind that was written. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "DebugCollectLogsResultKind". + */ +/** @experimental */ +export type DebugCollectLogsResultKind = + /** A .tgz archive was written. */ + | "archive" + /** A directory containing redacted files was written. */ + | "directory"; /** * Server transport type: stdio, http, sse (deprecated), or memory * @@ -789,6 +881,59 @@ export type McpSetEnvValueModeDetails = | "direct" /** Treat MCP server environment values as host-side references to resolve before launch. */ | "indirect"; +/** + * Per-source context-window attribution, or null if the session has not yet been initialized (no system prompt or tool metadata cached). + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "SessionContextAttribution". + */ +/** @experimental */ +export type SessionContextAttribution = { + /** + * Total token count of the current context window the entries are measured against (system message + conversation messages + tool definitions — the same total reported by /context). Divide an entry's `tokens` by this to derive its share. + */ + totalTokens: number; + /** + * Flat list of per-source attribution entries. Group by `kind` and render unrecognized kinds generically. Nesting and rollups are expressed via `parentId`. + */ + entries: { + /** + * Source category for this entry. Not a closed set — tolerate unknown values. Known values today: `skill`, `subagent`, `mcpServer`, `tool`, `system`, `toolDefinition`, `plugin`. + */ + kind: string; + /** + * Identifier for this entry, formed by joining its `kind` and source name (e.g. `tool:bash`, `skill:tmux`, `toolDefinition:bash`); unique within the snapshot. Use it to match the same entry across snapshots, to correlate with other APIs (skill/agent/MCP registries), and as the `parentId` target for nesting. Distinct from the human-facing `label`. + */ + id: string; + /** + * Human-readable display label, e.g. `bash` or `skill: tmux`. Presentation-only; may be localized/reformatted without notice — do not key off it. + */ + label: string; + /** + * Token count currently in context attributable to this entry. + */ + tokens: number; + /** + * Optional `id` of the parent entry: e.g. a `plugin` entry parenting its `skill`/`mcpServer` entries, or the `system` entry parenting `toolDefinition` entries. Omitted for top-level entries. + */ + parentId?: string; + /** + * Supplementary per-entry metadata (e.g. `messageCount`, `role`, `evictable`, `pluginSource`). Values are stringified; parse as needed and ignore unrecognized keys. + */ + attributes?: { + [k: string]: string | undefined; + }; + }[]; + /** + * Successful compaction history for the session. + */ + compactions: { + /** + * Number of successful compactions in this session. + */ + count: number; + }; +} | null; /** * Token breakdown for the current context window, or null if the session has not yet been initialized (no system prompt or tool metadata cached). * @@ -1201,7 +1346,7 @@ export type ProviderEndpointTransport = /** WebSocket transport. */ | "websockets"; /** - * Schema for the `PushAttachment` type. + * Attachment union accepted by push input, covering files, directories, GitHub objects, blobs, snippets, and extension context. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "PushAttachment". @@ -1588,6 +1733,52 @@ export type SessionsOpenProgressStatus = | "in-progress" /** The step has completed successfully. */ | "complete"; +/** + * Rust-owned settings predicates exposed across the SDK boundary. Raw feature-flag names are intentionally not part of the contract. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "SessionSettingsPredicateName". + */ +/** @experimental */ +export type SessionSettingsPredicateName = + /** Whether the security-tools feature flag enables security tool wiring. */ + | "securityToolsEnabled" + /** Whether third-party security tools should receive the security prompt. */ + | "thirdPartySecurityPromptEnabled" + /** Whether validation may run in parallel. */ + | "parallelValidationEnabled" + /** Whether runtime timing telemetry is enabled. */ + | "runtimeTimingTelemetryEnabled" + /** Whether the co-author hook is enabled. */ + | "coAuthorHookEnabled" + /** Whether Chronicle integration is enabled. */ + | "chronicleEnabled" + /** Whether content-exclusion policy may self-fetch data. */ + | "contentExclusionSelfFetchEnabled" + /** Whether Claude Opus token-limit caps should be applied. */ + | "capClaudeOpusTokenLimitsEnabled" + /** Whether code-review behavior is enabled. */ + | "codeReviewFeatureEnabled" + /** Whether CCA should use the TypeScript autofind behavior. */ + | "ccaUseTsAutofindEnabled" + /** Whether the dependency checker is enabled. */ + | "dependencyCheckerEnabled" + /** Whether the Dependabot checker is enabled. */ + | "dependabotCheckerEnabled" + /** Whether the CodeQL checker is enabled. */ + | "codeqlCheckerEnabled" + /** Whether trivial-change handling is enabled. */ + | "trivialChangeEnabled" + /** Whether trivial-change skip behavior is enabled. */ + | "trivialChangeSkipEnabled" + /** Whether trivial-change handling is enabled for code review. */ + | "trivialChangeEnabledForCodeReview" + /** Whether trivial-change skip behavior is enabled for code review. */ + | "trivialChangeSkipEnabledForCodeReview" + /** Whether trivial-change handling is enabled for a specific tool. */ + | "trivialChangeEnabledForTool" + /** Whether trivial-change skip behavior is enabled for a specific tool. */ + | "trivialChangeSkipEnabledForTool"; /** * Which session sources to include. Defaults to `local` for backward compatibility. * @@ -1728,7 +1919,7 @@ export type TaskExecutionMode = /** The task is managed in the background. */ | "background"; /** - * Schema for the `TaskInfo` type. + * Tracked task union returned by task APIs, containing either an agent task or a shell task. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "TaskInfo". @@ -1770,7 +1961,7 @@ export type UIAutoModeSwitchResponse = /** Decline the automatic mode switch. */ | "no"; /** - * Schema for the `UIElicitationFieldValue` type. + * Submitted UI elicitation field value: string, number, boolean, or an array of strings. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "UIElicitationFieldValue". @@ -1948,7 +2139,7 @@ export interface AbortResult { error?: string; } /** - * Schema for the `AccountAllUsers` type. + * Authenticated account entry returned by `account.getAllUsers`, with auth info and an optional associated token. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "AccountAllUsers". @@ -1962,7 +2153,7 @@ export interface AccountAllUsers { token?: string; } /** - * Schema for the `HMACAuthInfo` type. + * Authentication-info variant for GitHub-internal HMAC auth, carrying the public GitHub host and HMAC secret. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "HMACAuthInfo". @@ -1991,9 +2182,21 @@ export interface HMACAuthInfo { */ /** @experimental */ export interface CopilotUserResponse { + /** + * GitHub login of the authenticated user. + */ login?: string; + /** + * Copilot access SKU identifier (e.g. `free_limited_copilot`, `copilot_for_business_seat_quota`) used to gate model and feature access. + */ access_type_sku?: string; + /** + * Opaque analytics tracking identifier for the user, forwarded from the Copilot API. + */ analytics_tracking_id?: string; + /** + * Date the Copilot seat was assigned to the user, if applicable. + */ assigned_date?: | ( | { @@ -2002,12 +2205,30 @@ export interface CopilotUserResponse { | string ) | null; + /** + * Whether the user is eligible to sign up for the free/limited Copilot tier. + */ can_signup_for_limited?: boolean; + /** + * Whether Copilot chat is enabled for the user. + */ chat_enabled?: boolean; + /** + * Copilot plan name for the user (e.g. `individual`, `business`, `enterprise`). + */ copilot_plan?: string; + /** + * Whether `.copilotignore` content-exclusion support is enabled for the user. + */ copilotignore_enabled?: boolean; endpoints?: CopilotUserResponseEndpoints; + /** + * Logins of the organizations the user belongs to. + */ organization_login_list?: string[]; + /** + * Organizations the user belongs to, each with an optional login and display name. + */ organization_list?: | ( | { @@ -2033,7 +2254,13 @@ export interface CopilotUserResponse { } | null)[] ) | null; + /** + * Whether the Codex agent is enabled for the user. + */ codex_agent_enabled?: boolean; + /** + * Whether MCP (Model Context Protocol) support is enabled for the user. + */ is_mcp_enabled?: | ( | { @@ -2042,26 +2269,62 @@ export interface CopilotUserResponse { | boolean ) | null; + /** + * Date the user's usage quota next resets, as a raw string from the Copilot API; see `quota_reset_date_utc` for the UTC-normalized value. + */ quota_reset_date?: string; quota_snapshots?: CopilotUserResponseQuotaSnapshots; + /** + * Whether the user's telemetry is subject to restricted-data handling. + */ restricted_telemetry?: boolean; + /** + * Whether the user is a GitHub/Microsoft staff member. + */ is_staff?: boolean; + /** + * Raw passthrough of the Copilot API `te` flag for the user (an opaque server-side eligibility signal surfaced in telemetry); not otherwise interpreted by the runtime. + */ te?: boolean; + /** + * Whether the account is on usage-based (token/AI-credit) billing rather than a fixed premium-request quota. + */ token_based_billing?: boolean; + /** + * Whether the user is able to upgrade their Copilot plan. + */ can_upgrade_plan?: boolean; + /** + * UTC-normalized form of `quota_reset_date` (the date the user's usage quota next resets). + */ quota_reset_date_utc?: string; + /** + * Per-category quota allotments for free/limited-tier users, keyed by quota category. + */ limited_user_quotas?: { [k: string]: number | undefined; }; + /** + * Date the free/limited-tier user's quotas next reset, as a raw string from the Copilot API. + */ limited_user_reset_date?: string; + /** + * Per-category monthly quota allotments, keyed by quota category. + */ monthly_quotas?: { [k: string]: number | undefined; }; + /** + * Whether cloud session storage is enabled for the user. + */ cloud_session_storage_enabled?: boolean; + /** + * Whether CLI remote control is enabled for the user. + */ cli_remote_control_enabled?: boolean; } /** - * Schema for the `CopilotUserResponseEndpoints` type. + * Endpoint URLs from the raw Copilot `/copilot_internal/v2/token` user-response passthrough. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "CopilotUserResponseEndpoints". @@ -2074,7 +2337,7 @@ export interface CopilotUserResponseEndpoints { telemetry?: string; } /** - * Schema for the `CopilotUserResponseQuotaSnapshots` type. + * Quota snapshot map from the raw Copilot user-response passthrough, with chat, completions, premium-interactions, and other entries. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "CopilotUserResponseQuotaSnapshots". @@ -2102,70 +2365,178 @@ export interface CopilotUserResponseQuotaSnapshots { | undefined; } /** - * Schema for the `CopilotUserResponseQuotaSnapshotsChat` type. + * Chat quota snapshot from the raw Copilot user-response passthrough, with entitlement, overage, remaining quota, reset, and billing fields. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "CopilotUserResponseQuotaSnapshotsChat". */ /** @experimental */ export interface CopilotUserResponseQuotaSnapshotsChat { + /** + * Number of requests/units included in the entitlement for this period; `-1` denotes an unlimited entitlement. + */ entitlement?: number; + /** + * Count of additional pay-per-request usage consumed this period beyond the entitlement. + */ overage_count?: number; + /** + * Whether usage may continue at pay-per-request rates once the entitlement is exhausted. + */ overage_permitted?: boolean; + /** + * Percentage of the entitlement remaining at the snapshot timestamp. + */ percent_remaining?: number; + /** + * Identifier of the quota bucket this snapshot describes. + */ quota_id?: string; + /** + * Amount of quota remaining at the snapshot timestamp. + */ quota_remaining?: number; + /** + * Remaining entitlement/quota amount at the snapshot timestamp. + */ remaining?: number; + /** + * Whether the entitlement for this category is unlimited. + */ unlimited?: boolean; + /** + * UTC timestamp when this snapshot was captured. + */ timestamp_utc?: string; + /** + * Whether the user currently has quota available; when `false` and not unlimited, further requests are blocked until the quota resets. + */ has_quota?: boolean; + /** + * Unix epoch time, in seconds, when this quota next resets. + */ quota_reset_at?: number; + /** + * Whether this category uses usage-based (token/AI-credit) billing rather than a fixed premium-request count. + */ token_based_billing?: boolean; } /** - * Schema for the `CopilotUserResponseQuotaSnapshotsCompletions` type. + * Completions quota snapshot from the raw Copilot user-response passthrough, with entitlement, overage, remaining quota, reset, and billing fields. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "CopilotUserResponseQuotaSnapshotsCompletions". */ /** @experimental */ export interface CopilotUserResponseQuotaSnapshotsCompletions { + /** + * Number of requests/units included in the entitlement for this period; `-1` denotes an unlimited entitlement. + */ entitlement?: number; + /** + * Count of additional pay-per-request usage consumed this period beyond the entitlement. + */ overage_count?: number; + /** + * Whether usage may continue at pay-per-request rates once the entitlement is exhausted. + */ overage_permitted?: boolean; + /** + * Percentage of the entitlement remaining at the snapshot timestamp. + */ percent_remaining?: number; + /** + * Identifier of the quota bucket this snapshot describes. + */ quota_id?: string; + /** + * Amount of quota remaining at the snapshot timestamp. + */ quota_remaining?: number; + /** + * Remaining entitlement/quota amount at the snapshot timestamp. + */ remaining?: number; + /** + * Whether the entitlement for this category is unlimited. + */ unlimited?: boolean; + /** + * UTC timestamp when this snapshot was captured. + */ timestamp_utc?: string; + /** + * Whether the user currently has quota available; when `false` and not unlimited, further requests are blocked until the quota resets. + */ has_quota?: boolean; + /** + * Unix epoch time, in seconds, when this quota next resets. + */ quota_reset_at?: number; + /** + * Whether this category uses usage-based (token/AI-credit) billing rather than a fixed premium-request count. + */ token_based_billing?: boolean; } /** - * Schema for the `CopilotUserResponseQuotaSnapshotsPremiumInteractions` type. + * Premium-interactions quota snapshot from the raw Copilot user-response passthrough, with entitlement, overage, remaining quota, reset, and billing fields. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "CopilotUserResponseQuotaSnapshotsPremiumInteractions". */ /** @experimental */ export interface CopilotUserResponseQuotaSnapshotsPremiumInteractions { + /** + * Number of requests/units included in the entitlement for this period; `-1` denotes an unlimited entitlement. + */ entitlement?: number; + /** + * Count of additional pay-per-request usage consumed this period beyond the entitlement. + */ overage_count?: number; + /** + * Whether usage may continue at pay-per-request rates once the entitlement is exhausted. + */ overage_permitted?: boolean; + /** + * Percentage of the entitlement remaining at the snapshot timestamp. + */ percent_remaining?: number; + /** + * Identifier of the quota bucket this snapshot describes. + */ quota_id?: string; + /** + * Amount of quota remaining at the snapshot timestamp. + */ quota_remaining?: number; + /** + * Remaining entitlement/quota amount at the snapshot timestamp. + */ remaining?: number; + /** + * Whether the entitlement for this category is unlimited. + */ unlimited?: boolean; + /** + * UTC timestamp when this snapshot was captured. + */ timestamp_utc?: string; + /** + * Whether the user currently has quota available; when `false` and not unlimited, further requests are blocked until the quota resets. + */ has_quota?: boolean; + /** + * Unix epoch time, in seconds, when this quota next resets. + */ quota_reset_at?: number; + /** + * Whether this category uses usage-based (token/AI-credit) billing rather than a fixed premium-request count. + */ token_based_billing?: boolean; } /** - * Schema for the `EnvAuthInfo` type. + * Authentication-info variant for a token sourced from an environment variable, with host, optional login, token, and env var name. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "EnvAuthInfo". @@ -2195,7 +2566,7 @@ export interface EnvAuthInfo { copilotUser?: CopilotUserResponse; } /** - * Schema for the `TokenAuthInfo` type. + * Authentication-info variant for SDK-configured token authentication, carrying host and the secret token value. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "TokenAuthInfo". @@ -2217,7 +2588,7 @@ export interface TokenAuthInfo { copilotUser?: CopilotUserResponse; } /** - * Schema for the `CopilotApiTokenAuthInfo` type. + * Authentication-info variant for direct Copilot API token auth sourced from environment variables, with public GitHub host. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "CopilotApiTokenAuthInfo". @@ -2235,7 +2606,7 @@ export interface CopilotApiTokenAuthInfo { copilotUser?: CopilotUserResponse; } /** - * Schema for the `UserAuthInfo` type. + * Authentication-info variant for OAuth user auth, with host and login; the token remains in the runtime secret store. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "UserAuthInfo". @@ -2257,7 +2628,7 @@ export interface UserAuthInfo { copilotUser?: CopilotUserResponse; } /** - * Schema for the `GhCliAuthInfo` type. + * Authentication-info variant for GitHub CLI credentials, carrying host, login, and the `gh auth token` value. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "GhCliAuthInfo". @@ -2283,7 +2654,7 @@ export interface GhCliAuthInfo { copilotUser?: CopilotUserResponse; } /** - * Schema for the `ApiKeyAuthInfo` type. + * Authentication-info variant for API-key authentication to a non-GitHub LLM provider, carrying the secret `apiKey` and host. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "ApiKeyAuthInfo". @@ -2342,7 +2713,7 @@ export interface AccountGetQuotaResult { }; } /** - * Schema for the `AccountQuotaSnapshot` type. + * Quota usage snapshot for a Copilot quota type, including entitlement, used requests, overage, reset date, and remaining percentage. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "AccountQuotaSnapshot". @@ -2440,7 +2811,7 @@ export interface AccountLogoutResult { hasMoreUsers: boolean; } /** - * Schema for the `AgentDiscoveryPath` type. + * Canonical directory where custom agents can be discovered or created, with scope, preference, and optional project path. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "AgentDiscoveryPath". @@ -2488,7 +2859,7 @@ export interface AgentGetCurrentResult { agent?: AgentInfo | null; } /** - * Schema for the `AgentInfo` type. + * Custom agent metadata, including identifiers, display details, source, tools, model, MCP servers, skills, and file path. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "AgentInfo". @@ -2841,12 +3212,13 @@ export interface AllowAllPermissionSetResult { */ success: boolean; /** - * Authoritative allow-all state after the mutation + * Authoritative full allow-all state after the mutation */ enabled: boolean; + mode?: PermissionsAllowAllMode; } /** - * Current full allow-all permission state. + * Current allow-all permission mode. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "AllowAllPermissionState". @@ -2857,6 +3229,7 @@ export interface AllowAllPermissionState { * Whether full allow-all permissions are currently active */ enabled: boolean; + mode?: PermissionsAllowAllMode; } /** * Cancellation result for a user-requested shell command. @@ -3256,7 +3629,7 @@ export interface CommandList { commands: SlashCommandInfo[]; } /** - * Schema for the `SlashCommandInfo` type. + * Slash-command metadata with name, aliases, description, kind, input hint, execution allowance, and schedulability. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "SlashCommandInfo". @@ -3302,6 +3675,10 @@ export interface SlashCommandInput { * Hint to display when command input has not been provided */ hint: string; + /** + * Optional literal choices the input accepts, each with a human-facing description; clients may render these as selectable options + */ + choices?: SlashCommandInputChoice[]; /** * When true, the command requires non-empty input; clients should render the input hint as required */ @@ -3312,6 +3689,23 @@ export interface SlashCommandInput { */ preserveMultilineInput?: boolean; } +/** + * A literal choice the command input accepts, with a human-facing description + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "SlashCommandInputChoice". + */ +/** @experimental */ +export interface SlashCommandInputChoice { + /** + * The literal choice value (e.g. 'on', 'off', 'show') + */ + name: string; + /** + * Human-readable description shown alongside the choice + */ + description: string; +} /** * Pending command request ID and an optional error if the client handler failed. * @@ -3395,7 +3789,7 @@ export interface CommandsRespondToQueuedCommandRequest { result: QueuedCommandResult; } /** - * Schema for the `QueuedCommandHandled` type. + * Queued-command response indicating the host executed the command, with an optional flag to stop queue processing. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "QueuedCommandHandled". @@ -3412,7 +3806,7 @@ export interface QueuedCommandHandled { stopProcessingQueue?: boolean; } /** - * Schema for the `QueuedCommandNotHandled` type. + * Queued-command response indicating the host did not execute the command and the queue may continue. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "QueuedCommandNotHandled". @@ -3615,7 +4009,7 @@ export interface ConnectRemoteSessionParams { sessionId: string; } /** - * Optional connection token presented by the SDK client during the handshake. + * Parameters for the `server.connect` handshake: an optional connection token and optional connection-level opt-ins (e.g. GitHub telemetry forwarding). * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "ConnectRequest". @@ -3627,6 +4021,10 @@ export interface ConnectRequest { * Connection token; required when the server was started with COPILOT_CONNECTION_TOKEN */ token?: string; + /** + * Opt this connection in to GitHub telemetry forwarding for its lifetime. When set, the runtime forwards every internal telemetry event it emits — across all sessions, plus sessionless events — to this connection over the `gitHubTelemetry.event` notification, in addition to the runtime's normal GitHub/CTS emission (dual-write). Intended for first-party hosts that re-emit the events into their own telemetry stores. Both unrestricted and restricted events are forwarded, each tagged with a `restricted` discriminator; a backstop drops restricted events when restricted telemetry is disabled. + */ + enableGitHubTelemetryForwarding?: boolean; } /** * Handshake result reporting the server's protocol version and package version on success. @@ -3638,77 +4036,238 @@ export interface ConnectRequest { /** @internal */ export interface ConnectResult { /** - * Always true on success + * Always true on success + */ + ok: true; + /** + * Server protocol version number + */ + protocolVersion: number; + /** + * Server package version + */ + version: string; +} +/** + * A single large message currently in context. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "ContextHeaviestMessage". + */ +/** @experimental */ +export interface ContextHeaviestMessage { + /** + * Stable identifier for this message within the snapshot. + */ + id: string; + /** + * Human-readable source label, e.g. `tool: bash` or `skill: tmux`. Presentation-only. + */ + label: string; + /** + * Role of the chat message (`user`, `assistant`, or `tool`). + */ + role: string; + /** + * Token count currently in context for this individual message. + */ + tokens: number; +} +/** + * The currently selected model, reasoning effort, and context tier for the session. The context tier reflects `Session.getContextTier()`, restored from the session journal on resume. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "CurrentModel". + */ +/** @experimental */ +export interface CurrentModel { + /** + * Currently active model identifier + */ + modelId?: string; + /** + * Reasoning effort level currently applied to the active model, when one is set. Reads `Session.getReasoningEffort()` synchronously after `getSelectedModel()` resolves so the two values are reported as a snapshot. + */ + reasoningEffort?: string; + contextTier?: ContextTier; +} +/** + * Lightweight metadata for a currently initialized session tool + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "CurrentToolMetadata". + */ +/** @experimental */ +export interface CurrentToolMetadata { + /** + * Model-facing tool name + */ + name: string; + /** + * Optional MCP/config namespaced tool name + */ + namespacedName?: string; + /** + * MCP server name for MCP-backed tools + */ + mcpServerName?: string; + /** + * Raw MCP tool name for MCP-backed tools + */ + mcpToolName?: string; + /** + * Tool description + */ + description: string; + /** + * JSON Schema for tool input + */ + input_schema?: { + [k: string]: unknown | undefined; + }; + /** + * Whether the tool is loaded on demand via tool search + */ + deferLoading?: boolean; +} +/** + * A file included in the redacted debug bundle. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "DebugCollectLogsCollectedEntry". + */ +/** @experimental */ +export interface DebugCollectLogsCollectedEntry { + /** + * Relative path of the file in the staged bundle/archive. + */ + bundlePath: string; + source: DebugCollectLogsSource; + /** + * Redacted output size in bytes. + */ + sizeBytes: number; +} +/** + * A caller-provided server-local file or directory to include in the debug bundle. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "DebugCollectLogsEntry". + */ +/** @experimental */ +export interface DebugCollectLogsEntry { + kind: DebugCollectLogsEntryKind; + /** + * Server-local source path to read. + */ + path: string; + /** + * Relative path to use inside the staged bundle/archive. + */ + bundlePath: string; + redaction?: DebugCollectLogsRedaction; + /** + * When true, collection fails if this entry cannot be read. Defaults to false, which records the entry in `skippedEntries`. + */ + required?: boolean; +} +/** + * Built-in session diagnostics to include in the bundle. Omitted fields default to true. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "DebugCollectLogsInclude". + */ +/** @experimental */ +export interface DebugCollectLogsInclude { + /** + * Include the session event log (`events.jsonl`). Defaults to true. + */ + events?: boolean; + /** + * Include process logs for the session. Defaults to true. + */ + processLogs?: boolean; + /** + * Include interactive shell logs written under the session's `shell-logs` directory. Defaults to true. + */ + shellLogs?: boolean; + /** + * Server-local path to the session's events.jsonl file. Internal callers normally omit this and let the runtime derive it from the session. + */ + eventsPath?: string; + /** + * Server-local path to the current process log. When set, it is included as `process.log` and its directory is searched for prior logs from the same session. */ - ok: true; + currentProcessLogPath?: string; /** - * Server protocol version number + * Server-local process log directory to search when `currentProcessLogPath` is unavailable, useful for collecting logs for inactive sessions. */ - protocolVersion: number; + processLogDirectory?: string; /** - * Server package version + * Maximum number of previous process logs to include. Defaults to 5. */ - version: string; + previousProcessLogLimit?: number; } /** - * The currently selected model, reasoning effort, and context tier for the session. The context tier reflects `Session.getContextTier()`, restored from the session journal on resume. + * Options for collecting a redacted session debug bundle. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema - * via the `definition` "CurrentModel". + * via the `definition` "DebugCollectLogsRequest". */ /** @experimental */ -export interface CurrentModel { - /** - * Currently active model identifier - */ - modelId?: string; +export interface DebugCollectLogsRequest { + destination: DebugCollectLogsDestination; + include?: DebugCollectLogsInclude; /** - * Reasoning effort level currently applied to the active model, when one is set. Reads `Session.getReasoningEffort()` synchronously after `getSelectedModel()` resolves so the two values are reported as a snapshot. + * Caller-provided server-local files or directories to include in addition to the runtime's built-in session diagnostics. This lets host applications add their own diagnostics without changing the API shape. */ - reasoningEffort?: string; - contextTier?: ContextTier; + additionalEntries?: DebugCollectLogsEntry[]; } /** - * Lightweight metadata for a currently initialized session tool + * Result of collecting a redacted debug bundle. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema - * via the `definition` "CurrentToolMetadata". + * via the `definition` "DebugCollectLogsResult". */ /** @experimental */ -export interface CurrentToolMetadata { - /** - * Model-facing tool name - */ - name: string; +export interface DebugCollectLogsResult { + kind: DebugCollectLogsResultKind; /** - * Optional MCP/config namespaced tool name + * Actual archive path or staging directory path written. This may differ from the requested path when no-overwrite suffixing or fallback-to-temp-directory was needed. */ - namespacedName?: string; + path: string; /** - * MCP server name for MCP-backed tools + * Files included in the redacted bundle. */ - mcpServerName?: string; + entries: DebugCollectLogsCollectedEntry[]; /** - * Raw MCP tool name for MCP-backed tools + * Optional files or directories that could not be included. */ - mcpToolName?: string; + skippedEntries?: DebugCollectLogsSkippedEntry[]; +} +/** + * An optional debug bundle entry that could not be included. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "DebugCollectLogsSkippedEntry". + */ +/** @experimental */ +export interface DebugCollectLogsSkippedEntry { /** - * Tool description + * Relative path requested for this bundle entry. */ - description: string; + bundlePath: string; /** - * JSON Schema for tool input + * Server-local source path that could not be read. */ - input_schema?: { - [k: string]: unknown | undefined; - }; + path?: string; /** - * Whether the tool is loaded on demand via tool search + * Reason the entry was skipped. */ - deferLoading?: boolean; + reason: string; } /** - * Schema for the `DiscoveredMcpServer` type. + * MCP server discovered by `mcp.discover`, with config source, optional plugin source, transport type, and enabled state. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "DiscoveredMcpServer". @@ -3862,7 +4421,7 @@ export interface ExecuteCommandResult { error?: string; } /** - * Schema for the `Extension` type. + * Discovered extension metadata, including source-qualified ID, name, discovery source, status, and optional process ID. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "Extension". @@ -3984,6 +4543,10 @@ export interface ExternalToolTextResultForLlm { * Structured content blocks from the tool */ contents?: ExternalToolTextResultForLlmContent[]; + /** + * Tool references returned by a tool-search override: names of deferred tools to surface to the model. When set, the tool result is materialized as `tool_reference` content blocks (rather than plain text) so the model knows which deferred tools are now available. + */ + toolReferences?: string[]; } /** * Binary result returned by a tool for the model @@ -4374,7 +4937,7 @@ export interface GitHubTelemetryEvent { client?: GitHubTelemetryClientInfo; } /** - * Payload for a `gitHubTelemetry.event` notification: a single GitHub telemetry event the runtime forwards to a host connection that opted into telemetry forwarding for the session. + * 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. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "GitHubTelemetryNotification". @@ -4382,9 +4945,9 @@ export interface GitHubTelemetryEvent { /** @experimental */ export interface GitHubTelemetryNotification { /** - * Session the telemetry event belongs to. + * Session the telemetry event belongs to, when it is session-scoped. Omitted for sessionless events (for example, `server.sendTelemetry` calls with no session id), which are still forwarded to opted-in connections. */ - sessionId: string; + sessionId?: string; /** * Whether this is a restricted telemetry event (cli.restricted_telemetry). Hosts must route restricted events to first-party Microsoft stores only. */ @@ -4560,7 +5123,7 @@ export interface HistoryTruncateResult { eventsRemoved: number; } /** - * Schema for the `InstalledPlugin` type. + * Installed plugin record from global state, with marketplace, version, install time, enabled state, cache path, and source. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "InstalledPlugin". @@ -4594,7 +5157,7 @@ export interface InstalledPlugin { source?: InstalledPluginSource; } /** - * Schema for the `InstalledPluginSourceGitHub` type. + * Source descriptor for a direct GitHub plugin install, with `owner/repo`, optional ref, and optional subpath. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "InstalledPluginSourceGitHub". @@ -4610,7 +5173,7 @@ export interface InstalledPluginSourceGitHub { path?: string; } /** - * Schema for the `InstalledPluginSourceUrl` type. + * Source descriptor for a direct URL plugin install, with URL, optional ref, and optional subpath. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "InstalledPluginSourceUrl". @@ -4626,7 +5189,7 @@ export interface InstalledPluginSourceUrl { path?: string; } /** - * Schema for the `InstalledPluginSourceLocal` type. + * Source descriptor for a direct local plugin install, with a local filesystem path. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "InstalledPluginSourceLocal". @@ -4669,7 +5232,7 @@ export interface InstalledPluginInfo { enabled: boolean; } /** - * Schema for the `InstructionDiscoveryPath` type. + * Canonical file or directory where custom instructions can be discovered or created, with location, kind, preference, and project path. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "InstructionDiscoveryPath". @@ -4752,7 +5315,7 @@ export interface InstructionsGetSourcesResult { sources: InstructionSource[]; } /** - * Schema for the `InstructionSource` type. + * Loaded instruction source for a session, including path, content, category, location, applicability, and optional description. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "InstructionSource". @@ -4871,6 +5434,18 @@ export interface LlmInferenceHttpRequestStartRequest { url: string; headers: LlmInferenceHeaders; transport?: LlmInferenceHttpRequestStartTransport; + /** + * Stable per-agent-instance id attributing this request to a specific agent trajectory. Present when the request originates from an agent turn; absent for requests issued outside any agent context (e.g. some SDK callers). A request with an `agentId` but no `parentAgentId` is a root-agent request; one carrying both is a subagent request. Sourced from the runtime's per-request agent context and surfaced on the envelope independently of transport, so it is available for both first-party (CAPI) and BYOK/custom-provider requests; on the CAPI transport the runtime derives the upstream `X-Agent-Task-Id` header from this same context. Consumers routing each provider call to a training trajectory should key on this rather than on lifecycle events, since it is available on the request path before sampling. + */ + agentId?: string; + /** + * Id of the parent agent that spawned the agent issuing this request. Present only for subagent requests; absent for root-agent requests and non-agent requests. Combined with `agentId`, this lets consumers attribute a call to a child trajectory versus the root. Like `agentId`, it comes from the runtime's per-request agent context independently of transport; on the CAPI transport the runtime derives the upstream `X-Parent-Agent-Id` header from this same context. + */ + parentAgentId?: string; + /** + * Coarse classification of the interaction that produced this request. Open string for forward-compatibility; known values include `conversation-agent`, `conversation-subagent`, `conversation-sampling`, `conversation-background`, `conversation-compaction`, and `conversation-user`. Absent when the runtime did not classify the request. Comes from the runtime's per-request agent context independently of transport; on the CAPI transport the runtime derives the upstream `X-Interaction-Type` header from this same context. + */ + interactionType?: string; } /** * Acknowledgement. Returning successfully simply means the SDK accepted the start frame; it does not imply the request will succeed. @@ -4985,7 +5560,7 @@ export interface LlmInferenceSetProviderResult { success: boolean; } /** - * Schema for the `LocalSessionMetadataValue` type. + * Persisted local session metadata, including identifiers, timestamps, summary/name, client, context, detached state, and task ID. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "LocalSessionMetadataValue". @@ -5198,7 +5773,7 @@ export interface MarketplaceListResult { marketplaces: MarketplaceInfo[]; } /** - * Schema for the `MarketplaceRefreshEntry` type. + * Per-marketplace refresh result, including marketplace name, success flag, and optional failure error. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "MarketplaceRefreshEntry". @@ -5249,7 +5824,7 @@ export interface MarketplaceRemoveResult { dependentPlugins?: string[]; } /** - * Schema for the `McpAllowedServer` type. + * MCP server allowed by policy, with server name and optional PII-free explanatory note. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "McpAllowedServer". @@ -5464,7 +6039,7 @@ export interface McpAppsReadResourceResult { contents: McpAppsResourceContent[]; } /** - * Schema for the `McpAppsResourceContent` type. + * MCP Apps resource content with URI, optional MIME type, text or base64 blob, and resource metadata. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "McpAppsResourceContent". @@ -5870,7 +6445,7 @@ export interface McpExecuteSamplingResult { [k: string]: unknown | undefined; } /** - * Schema for the `McpFilteredServer` type. + * MCP server filtered by policy, with name, reason, optional redacted reason, and enterprise login. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "McpFilteredServer". @@ -6045,7 +6620,7 @@ export interface McpListToolsResult { tools: McpTools[]; } /** - * Schema for the `McpTools` type. + * MCP tool metadata with tool name and optional description. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "McpTools". @@ -6139,36 +6714,6 @@ export interface McpOauthLoginResult { */ authorizationUrl?: string; } -/** - * MCP OAuth request id and optional provider response. - * - * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema - * via the `definition` "McpOauthRespondRequest". - */ -/** @experimental */ -/** @internal */ -export interface McpOauthRespondRequest { - /** - * OAuth request identifier from mcp.oauth_required - */ - requestId: string; - /** - * In-process OAuthClientProvider instance, or omitted to deny. Marked internal: cannot be serialized across the JSON-RPC boundary. - * - * @internal - */ - provider?: { - [k: string]: unknown | undefined; - }; -} -/** - * Empty result after recording the MCP OAuth response. - * - * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema - * via the `definition` "McpOauthRespondResult". - */ -/** @experimental */ -export interface McpOauthRespondResult {} /** * Registration parameters for an external MCP client. * @@ -6276,7 +6821,7 @@ export interface McpSamplingExecutionResult { error?: string; } /** - * Schema for the `McpServer` type. + * MCP server status entry, including config source/plugin source and any connection error. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "McpServer". @@ -6415,6 +6960,49 @@ export interface MemoryConfiguration { */ enabled: boolean; } +/** + * Per-source attribution breakdown for the session's current context window, or null if uninitialized. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "MetadataContextAttributionResult". + */ +/** @experimental */ +export interface MetadataContextAttributionResult { + /** + * Per-source context-window attribution, or null if the session has not yet been initialized (no system prompt or tool metadata cached). + */ + contextAttribution?: SessionContextAttribution | null; +} +/** + * Parameters for the heaviest-messages query. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "MetadataContextHeaviestMessagesRequest". + */ +/** @experimental */ +export interface MetadataContextHeaviestMessagesRequest { + /** + * Maximum number of messages to return, most-expensive first. Omit for the server default. + */ + limit?: number; +} +/** + * The heaviest individual messages in the session's context window, most-expensive first. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "MetadataContextHeaviestMessagesResult". + */ +/** @experimental */ +export interface MetadataContextHeaviestMessagesResult { + /** + * Total token count of the current context window, so callers can compute each message's share without a second call. + */ + totalTokens: number; + /** + * Heaviest messages, most-expensive first. + */ + messages: ContextHeaviestMessage[]; +} /** * Model identifier and token limits used to compute the context-info breakdown. * @@ -6619,7 +7207,7 @@ export interface MetadataSnapshotRemoteMetadataRepository { branch: string; } /** - * Schema for the `Model` type. + * Copilot model metadata, including identifier, display name, capabilities, policy, billing, reasoning efforts, and picker categories. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "Model". @@ -6984,6 +7572,7 @@ export interface ModelSwitchToRequest { */ reasoningEffort?: string; reasoningSummary?: ReasoningSummary; + verbosity?: Verbosity; modelCapabilities?: ModelCapabilitiesOverride; contextTier?: ContextTier; } @@ -7115,7 +7704,7 @@ export interface NameSetRequest { name: string; } /** - * Schema for the `OptionsUpdateAdditionalContentExclusionPolicy` type. + * Content-exclusion policy supplied to `session.options.update`, with rules, last-updated data, and scope. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "OptionsUpdateAdditionalContentExclusionPolicy". @@ -7127,7 +7716,7 @@ export interface OptionsUpdateAdditionalContentExclusionPolicy { scope: OptionsUpdateAdditionalContentExclusionPolicyScope; } /** - * Schema for the `OptionsUpdateAdditionalContentExclusionPolicyRule` type. + * Single content-exclusion rule supplied to `session.options.update`, with paths, match conditions, and source. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "OptionsUpdateAdditionalContentExclusionPolicyRule". @@ -7140,7 +7729,7 @@ export interface OptionsUpdateAdditionalContentExclusionPolicyRule { source: OptionsUpdateAdditionalContentExclusionPolicyRuleSource; } /** - * Schema for the `OptionsUpdateAdditionalContentExclusionPolicyRuleSource` type. + * Source descriptor for a `session.options.update` content-exclusion rule, with source name and type. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "OptionsUpdateAdditionalContentExclusionPolicyRuleSource". @@ -7151,7 +7740,7 @@ export interface OptionsUpdateAdditionalContentExclusionPolicyRuleSource { type: string; } /** - * Schema for the `PendingPermissionRequest` type. + * Pending permission prompt reconstructed from event history, with request ID and user-facing prompt details. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "PendingPermissionRequest". @@ -7178,7 +7767,7 @@ export interface PendingPermissionRequestList { items: PendingPermissionRequest[]; } /** - * Schema for the `PermissionDecisionApproveOnce` type. + * Permission-decision request variant to approve only the current permission request. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "PermissionDecisionApproveOnce". @@ -7191,7 +7780,7 @@ export interface PermissionDecisionApproveOnce { kind: "approve-once"; } /** - * Schema for the `PermissionDecisionApproveForSession` type. + * Permission-decision request variant to approve for the rest of the session, with optional tool approval or URL domain. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "PermissionDecisionApproveForSession". @@ -7209,7 +7798,7 @@ export interface PermissionDecisionApproveForSession { domain?: string; } /** - * Schema for the `PermissionDecisionApproveForSessionApprovalCommands` type. + * Session-scoped approval details for specific command identifiers. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "PermissionDecisionApproveForSessionApprovalCommands". @@ -7226,7 +7815,7 @@ export interface PermissionDecisionApproveForSessionApprovalCommands { commandIdentifiers: string[]; } /** - * Schema for the `PermissionDecisionApproveForSessionApprovalRead` type. + * Session-scoped approval details for read-only filesystem operations. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "PermissionDecisionApproveForSessionApprovalRead". @@ -7239,7 +7828,7 @@ export interface PermissionDecisionApproveForSessionApprovalRead { kind: "read"; } /** - * Schema for the `PermissionDecisionApproveForSessionApprovalWrite` type. + * Session-scoped approval details for filesystem write operations. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "PermissionDecisionApproveForSessionApprovalWrite". @@ -7252,7 +7841,7 @@ export interface PermissionDecisionApproveForSessionApprovalWrite { kind: "write"; } /** - * Schema for the `PermissionDecisionApproveForSessionApprovalMcp` type. + * Session-scoped approval details for an MCP server tool, or all tools on the server when `toolName` is null. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "PermissionDecisionApproveForSessionApprovalMcp". @@ -7273,7 +7862,7 @@ export interface PermissionDecisionApproveForSessionApprovalMcp { toolName: string | null; } /** - * Schema for the `PermissionDecisionApproveForSessionApprovalMcpSampling` type. + * Session-scoped approval details for MCP sampling requests from a server. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "PermissionDecisionApproveForSessionApprovalMcpSampling". @@ -7290,7 +7879,7 @@ export interface PermissionDecisionApproveForSessionApprovalMcpSampling { serverName: string; } /** - * Schema for the `PermissionDecisionApproveForSessionApprovalMemory` type. + * Session-scoped approval details for writes to long-term memory. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "PermissionDecisionApproveForSessionApprovalMemory". @@ -7303,7 +7892,7 @@ export interface PermissionDecisionApproveForSessionApprovalMemory { kind: "memory"; } /** - * Schema for the `PermissionDecisionApproveForSessionApprovalCustomTool` type. + * Session-scoped approval details for a custom tool, keyed by tool name. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "PermissionDecisionApproveForSessionApprovalCustomTool". @@ -7320,7 +7909,7 @@ export interface PermissionDecisionApproveForSessionApprovalCustomTool { toolName: string; } /** - * Schema for the `PermissionDecisionApproveForSessionApprovalExtensionManagement` type. + * Session-scoped approval details for extension-management operations, optionally narrowed by operation. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "PermissionDecisionApproveForSessionApprovalExtensionManagement". @@ -7337,7 +7926,7 @@ export interface PermissionDecisionApproveForSessionApprovalExtensionManagement operation?: string; } /** - * Schema for the `PermissionDecisionApproveForSessionApprovalExtensionPermissionAccess` type. + * Session-scoped approval details for an extension's permission-gated capability access, keyed by extension name. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "PermissionDecisionApproveForSessionApprovalExtensionPermissionAccess". @@ -7354,7 +7943,7 @@ export interface PermissionDecisionApproveForSessionApprovalExtensionPermissionA extensionName: string; } /** - * Schema for the `PermissionDecisionApproveForLocation` type. + * Permission-decision request variant to approve and persist a permission for a project location, with approval details and location key. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "PermissionDecisionApproveForLocation". @@ -7372,7 +7961,7 @@ export interface PermissionDecisionApproveForLocation { locationKey: string; } /** - * Schema for the `PermissionDecisionApproveForLocationApprovalCommands` type. + * Location-scoped approval details for specific command identifiers. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "PermissionDecisionApproveForLocationApprovalCommands". @@ -7389,7 +7978,7 @@ export interface PermissionDecisionApproveForLocationApprovalCommands { commandIdentifiers: string[]; } /** - * Schema for the `PermissionDecisionApproveForLocationApprovalRead` type. + * Location-scoped approval details for read-only filesystem operations. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "PermissionDecisionApproveForLocationApprovalRead". @@ -7402,7 +7991,7 @@ export interface PermissionDecisionApproveForLocationApprovalRead { kind: "read"; } /** - * Schema for the `PermissionDecisionApproveForLocationApprovalWrite` type. + * Location-scoped approval details for filesystem write operations. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "PermissionDecisionApproveForLocationApprovalWrite". @@ -7415,7 +8004,7 @@ export interface PermissionDecisionApproveForLocationApprovalWrite { kind: "write"; } /** - * Schema for the `PermissionDecisionApproveForLocationApprovalMcp` type. + * Location-scoped approval details for an MCP server tool, or all tools on the server when `toolName` is null. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "PermissionDecisionApproveForLocationApprovalMcp". @@ -7436,7 +8025,7 @@ export interface PermissionDecisionApproveForLocationApprovalMcp { toolName: string | null; } /** - * Schema for the `PermissionDecisionApproveForLocationApprovalMcpSampling` type. + * Location-scoped approval details for MCP sampling requests from a server. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "PermissionDecisionApproveForLocationApprovalMcpSampling". @@ -7453,7 +8042,7 @@ export interface PermissionDecisionApproveForLocationApprovalMcpSampling { serverName: string; } /** - * Schema for the `PermissionDecisionApproveForLocationApprovalMemory` type. + * Location-scoped approval details for writes to long-term memory. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "PermissionDecisionApproveForLocationApprovalMemory". @@ -7466,7 +8055,7 @@ export interface PermissionDecisionApproveForLocationApprovalMemory { kind: "memory"; } /** - * Schema for the `PermissionDecisionApproveForLocationApprovalCustomTool` type. + * Location-scoped approval details for a custom tool, keyed by tool name. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "PermissionDecisionApproveForLocationApprovalCustomTool". @@ -7483,7 +8072,7 @@ export interface PermissionDecisionApproveForLocationApprovalCustomTool { toolName: string; } /** - * Schema for the `PermissionDecisionApproveForLocationApprovalExtensionManagement` type. + * Location-scoped approval details for extension-management operations, optionally narrowed by operation. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "PermissionDecisionApproveForLocationApprovalExtensionManagement". @@ -7500,7 +8089,7 @@ export interface PermissionDecisionApproveForLocationApprovalExtensionManagement operation?: string; } /** - * Schema for the `PermissionDecisionApproveForLocationApprovalExtensionPermissionAccess` type. + * Location-scoped approval details for an extension's permission-gated capability access, keyed by extension name. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "PermissionDecisionApproveForLocationApprovalExtensionPermissionAccess". @@ -7517,7 +8106,7 @@ export interface PermissionDecisionApproveForLocationApprovalExtensionPermission extensionName: string; } /** - * Schema for the `PermissionDecisionApprovePermanently` type. + * Permission-decision request variant to permanently approve a URL domain across sessions. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "PermissionDecisionApprovePermanently". @@ -7534,7 +8123,7 @@ export interface PermissionDecisionApprovePermanently { domain: string; } /** - * Schema for the `PermissionDecisionReject` type. + * Permission-decision request variant to reject a pending permission request, with optional feedback. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "PermissionDecisionReject". @@ -7551,7 +8140,7 @@ export interface PermissionDecisionReject { feedback?: string; } /** - * Schema for the `PermissionDecisionUserNotAvailable` type. + * Permission-decision variant indicating no user was available to confirm the request. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "PermissionDecisionUserNotAvailable". @@ -7564,7 +8153,7 @@ export interface PermissionDecisionUserNotAvailable { kind: "user-not-available"; } /** - * Schema for the `PermissionDecisionApproved` type. + * Permission-decision variant indicating the request was approved. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "PermissionDecisionApproved". @@ -7577,7 +8166,7 @@ export interface PermissionDecisionApproved { kind: "approved"; } /** - * Schema for the `PermissionDecisionApprovedForSession` type. + * Permission-decision variant indicating approval was remembered for the session, with approval details. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "PermissionDecisionApprovedForSession". @@ -7591,7 +8180,7 @@ export interface PermissionDecisionApprovedForSession { approval: UserToolSessionApproval; } /** - * Schema for the `PermissionDecisionApprovedForLocation` type. + * Permission-decision variant indicating approval was persisted for a project location, with approval details and location key. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "PermissionDecisionApprovedForLocation". @@ -7609,7 +8198,7 @@ export interface PermissionDecisionApprovedForLocation { locationKey: string; } /** - * Schema for the `PermissionDecisionCancelled` type. + * Permission-decision variant indicating the request was cancelled before use, with an optional reason. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "PermissionDecisionCancelled". @@ -7626,7 +8215,7 @@ export interface PermissionDecisionCancelled { reason?: string; } /** - * Schema for the `PermissionDecisionDeniedByRules` type. + * Permission-decision variant indicating explicit denial by permission rules, with the matching rules. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "PermissionDecisionDeniedByRules". @@ -7643,7 +8232,7 @@ export interface PermissionDecisionDeniedByRules { rules: PermissionRule[]; } /** - * Schema for the `PermissionDecisionDeniedNoApprovalRuleAndCouldNotRequestFromUser` type. + * Permission-decision variant indicating no approval rule matched and user confirmation was unavailable. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "PermissionDecisionDeniedNoApprovalRuleAndCouldNotRequestFromUser". @@ -7656,7 +8245,7 @@ export interface PermissionDecisionDeniedNoApprovalRuleAndCouldNotRequestFromUse kind: "denied-no-approval-rule-and-could-not-request-from-user"; } /** - * Schema for the `PermissionDecisionDeniedInteractivelyByUser` type. + * Permission-decision variant indicating the user denied an interactive prompt, with optional feedback and force-reject flag. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "PermissionDecisionDeniedInteractivelyByUser". @@ -7677,7 +8266,7 @@ export interface PermissionDecisionDeniedInteractivelyByUser { forceReject?: boolean; } /** - * Schema for the `PermissionDecisionDeniedByContentExclusionPolicy` type. + * Permission-decision variant indicating denial by content-exclusion policy, with path and message. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "PermissionDecisionDeniedByContentExclusionPolicy". @@ -7698,7 +8287,7 @@ export interface PermissionDecisionDeniedByContentExclusionPolicy { message: string; } /** - * Schema for the `PermissionDecisionDeniedByPermissionRequestHook` type. + * Permission-decision variant indicating denial by a permission request hook, with optional message and interrupt flag. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "PermissionDecisionDeniedByPermissionRequestHook". @@ -7747,7 +8336,7 @@ export interface PermissionLocationAddToolApprovalParams { approval: PermissionsLocationsAddToolApprovalDetails; } /** - * Schema for the `PermissionsLocationsAddToolApprovalDetailsCommands` type. + * Location-persisted tool approval details for specific command identifiers. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "PermissionsLocationsAddToolApprovalDetailsCommands". @@ -7764,7 +8353,7 @@ export interface PermissionsLocationsAddToolApprovalDetailsCommands { commandIdentifiers: string[]; } /** - * Schema for the `PermissionsLocationsAddToolApprovalDetailsRead` type. + * Location-persisted tool approval details for read-only filesystem operations. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "PermissionsLocationsAddToolApprovalDetailsRead". @@ -7777,7 +8366,7 @@ export interface PermissionsLocationsAddToolApprovalDetailsRead { kind: "read"; } /** - * Schema for the `PermissionsLocationsAddToolApprovalDetailsWrite` type. + * Location-persisted tool approval details for filesystem write operations. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "PermissionsLocationsAddToolApprovalDetailsWrite". @@ -7790,7 +8379,7 @@ export interface PermissionsLocationsAddToolApprovalDetailsWrite { kind: "write"; } /** - * Schema for the `PermissionsLocationsAddToolApprovalDetailsMcp` type. + * Location-persisted tool approval details for an MCP server tool, or all tools when `toolName` is null. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "PermissionsLocationsAddToolApprovalDetailsMcp". @@ -7811,7 +8400,7 @@ export interface PermissionsLocationsAddToolApprovalDetailsMcp { toolName: string | null; } /** - * Schema for the `PermissionsLocationsAddToolApprovalDetailsMcpSampling` type. + * Location-persisted tool approval details for MCP sampling requests from a server. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "PermissionsLocationsAddToolApprovalDetailsMcpSampling". @@ -7828,7 +8417,7 @@ export interface PermissionsLocationsAddToolApprovalDetailsMcpSampling { serverName: string; } /** - * Schema for the `PermissionsLocationsAddToolApprovalDetailsMemory` type. + * Location-persisted tool approval details for writes to long-term memory. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "PermissionsLocationsAddToolApprovalDetailsMemory". @@ -7841,7 +8430,7 @@ export interface PermissionsLocationsAddToolApprovalDetailsMemory { kind: "memory"; } /** - * Schema for the `PermissionsLocationsAddToolApprovalDetailsCustomTool` type. + * Location-persisted tool approval details for a custom tool, keyed by tool name. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "PermissionsLocationsAddToolApprovalDetailsCustomTool". @@ -7858,7 +8447,7 @@ export interface PermissionsLocationsAddToolApprovalDetailsCustomTool { toolName: string; } /** - * Schema for the `PermissionsLocationsAddToolApprovalDetailsExtensionManagement` type. + * Location-persisted tool approval details for extension-management operations, optionally narrowed by operation. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "PermissionsLocationsAddToolApprovalDetailsExtensionManagement". @@ -7875,7 +8464,7 @@ export interface PermissionsLocationsAddToolApprovalDetailsExtensionManagement { operation?: string; } /** - * Schema for the `PermissionsLocationsAddToolApprovalDetailsExtensionPermissionAccess` type. + * Location-persisted tool approval details for an extension's permission-gated capability access, keyed by extension name. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "PermissionsLocationsAddToolApprovalDetailsExtensionPermissionAccess". @@ -8125,7 +8714,7 @@ export interface PermissionRulesSet { denied: PermissionRule[]; } /** - * Schema for the `PermissionsConfigureAdditionalContentExclusionPolicy` type. + * Content-exclusion policy supplied to `session.permissions.configure`, with rules, last-updated data, and scope. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "PermissionsConfigureAdditionalContentExclusionPolicy". @@ -8137,7 +8726,7 @@ export interface PermissionsConfigureAdditionalContentExclusionPolicy { scope: PermissionsConfigureAdditionalContentExclusionPolicyScope; } /** - * Schema for the `PermissionsConfigureAdditionalContentExclusionPolicyRule` type. + * Single content-exclusion rule supplied to `session.permissions.configure`, with paths, match conditions, and source. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "PermissionsConfigureAdditionalContentExclusionPolicyRule". @@ -8150,7 +8739,7 @@ export interface PermissionsConfigureAdditionalContentExclusionPolicyRule { source: PermissionsConfigureAdditionalContentExclusionPolicyRuleSource; } /** - * Schema for the `PermissionsConfigureAdditionalContentExclusionPolicyRuleSource` type. + * Source descriptor for a `session.permissions.configure` content-exclusion rule, with source name and type. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "PermissionsConfigureAdditionalContentExclusionPolicyRuleSource". @@ -8360,17 +8949,22 @@ export interface PermissionsResetSessionApprovalsResult { success: boolean; } /** - * Whether to enable full allow-all permissions for the session. + * Allow-all mode to apply for the session. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "PermissionsSetAllowAllRequest". */ /** @experimental */ export interface PermissionsSetAllowAllRequest { + mode?: PermissionsAllowAllMode; /** - * Whether to enable full allow-all permissions + * Legacy full allow-all toggle. Prefer `mode`; when `mode` is omitted, `enabled: true` is treated as `mode: "on"` and any other value is treated as `mode: "off"`. */ - enabled: boolean; + enabled?: boolean; + /** + * Optional model id for the `auto` mode auto-approval LLM judging. Only meaningful when `mode` is `auto`; ignored otherwise. When omitted, the session's active model is used. + */ + model?: string; source?: PermissionsSetAllowAllSource; } /** @@ -8593,7 +9187,7 @@ export interface PlanUpdateRequest { content: string; } /** - * Schema for the `Plugin` type. + * Session plugin metadata, with name, marketplace, optional version, and enabled state. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "Plugin". @@ -8779,6 +9373,10 @@ export interface PluginsReloadRequest { * Re-load user, plugin, and (subject to `deferRepoHooks`) repo hooks. Defaults to true. Has no effect when the host has not registered a hook reloader (e.g. remote sessions). */ reloadHooks?: boolean; + /** + * Re-discover and relaunch subprocess extensions (including plugin-shipped extensions) after refreshing plugins. Defaults to true. Has no effect when the session has no active extension controller (e.g. extensions were not requested for the session). + */ + reloadExtensions?: boolean; /** * When true, skip repo-level hooks during the hook reload. Use before folder trust is confirmed; load them post-trust via `sessions.loadDeferredRepoHooks`. */ @@ -8815,7 +9413,7 @@ export interface PluginsUpdateRequest { name: string; } /** - * Schema for the `PluginUpdateAllEntry` type. + * Per-plugin result from updating all plugins, with versions, skills installed, success flag, and optional error. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "PluginUpdateAllEntry". @@ -8885,36 +9483,6 @@ export interface PluginUpdateResult { */ skillsInstalled: number; } -/** - * Batch of spawn events plus a cursor for follow-up polls. - * - * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema - * via the `definition` "PollSpawnedSessionsResult". - */ -/** @experimental */ -export interface PollSpawnedSessionsResult { - /** - * Spawn events emitted since the supplied cursor. - */ - events: SessionsPollSpawnedSessionsEvent[]; - /** - * Opaque cursor to pass back to receive only events after this batch. - */ - cursor: string; -} -/** - * Schema for the `SessionsPollSpawnedSessionsEvent` type. - * - * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema - * via the `definition` "SessionsPollSpawnedSessionsEvent". - */ -/** @experimental */ -export interface SessionsPollSpawnedSessionsEvent { - /** - * Session id of the newly-spawned session. - */ - sessionId: string; -} /** * BYOK providers and/or models to add to the session's registry at runtime. Both fields are optional; provide providers, models, or both. * @@ -9597,7 +10165,7 @@ export interface PushAttachmentBlob { displayName?: string; } /** - * Schema for the `QueuePendingItems` type. + * User-facing pending queue entry, with kind and display text for a queued message, slash command, or model change. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "QueuePendingItems". @@ -9649,7 +10217,7 @@ export interface QueueRemoveMostRecentResult { /** @experimental */ export interface RegisterEventInterestParams { /** - * The event type the consumer wants the runtime to treat as 'observed' for behavior-switching gating. Some runtime code paths inspect whether any consumer is interested in a specific event type and choose a different implementation accordingly (e.g. `mcp.oauth_required`: when interest is registered the runtime delegates the full interactive OAuth flow to the consumer; when no interest is registered the runtime installs a browserless fallback that silently reuses cached tokens). SDK clients that long-poll events do NOT automatically appear as listeners to these gating checks — they must explicitly call `registerInterest` for each event type they want the runtime to count as having a consumer. Multiple registrations for the same event type from the same or different consumers are tracked independently and must each be released. See: `mcp.oauth_required`, `sampling.requested`, `auto_mode_switch.requested`, `session_limits_exhausted.requested`, `user_input.requested`, `elicitation.requested`, `command.queued`, `exit_plan_mode.requested`. + * The event type the consumer wants the runtime to treat as 'observed' for behavior-switching gating. Some runtime code paths inspect whether any consumer is interested in a specific event type and choose a different implementation accordingly (e.g. `mcp.oauth_required`: when interest is registered the runtime delegates OAuth token acquisition to the consumer; when no interest is registered OAuth-required servers become needs-auth). SDK clients that long-poll events do NOT automatically appear as listeners to these gating checks — they must explicitly call `registerInterest` for each event type they want the runtime to count as having a consumer. Multiple registrations for the same event type from the same or different consumers are tracked independently and must each be released. See: `mcp.oauth_required`, `sampling.requested`, `auto_mode_switch.requested`, `session_limits_exhausted.requested`, `user_input.requested`, `elicitation.requested`, `command.queued`, `exit_plan_mode.requested`. */ eventType: string; } @@ -9850,6 +10418,12 @@ export interface RemoteControlStatusActive { promptManager?: { [k: string]: unknown | undefined; }; + /** + * True while a read-only/session-sync export is deferred, awaiting the first `user.message` before its MC session exists. Marked internal: this field is excluded from the public SDK surface and is populated only on the CLI in-process path. + * + * @internal + */ + awaitingFirstMessage?: boolean; } /** * The last setup attempt failed. The singleton is otherwise off. @@ -10142,14 +10716,6 @@ export interface SandboxConfigUserPolicyNetwork { * Whether traffic to local/loopback addresses is allowed. */ allowLocalNetwork?: boolean; - /** - * Hosts allowed in addition to the base policy. - */ - allowedHosts?: string[]; - /** - * Hosts explicitly blocked. - */ - blockedHosts?: string[]; } /** * macOS seatbelt-specific options. @@ -10188,7 +10754,7 @@ export interface SandboxConfigUserPolicyExperimentalSeatbelt { keychainAccess?: boolean; } /** - * Schema for the `ScheduleEntry` type. + * Scheduled prompt entry with ID, timing (`intervalMs`, `cron`, or `at`), prompt text, recurrence, and next run time. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "ScheduleEntry". @@ -10414,7 +10980,7 @@ export interface ServerInstructionSourceList { sources: InstructionSource[]; } /** - * Schema for the `ServerSkill` type. + * Server-side skill metadata, including name, description, source, enabled/invocable state, path, project path, and argument hint. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "ServerSkill". @@ -10665,7 +11231,7 @@ export interface SessionFsReaddirResult { error?: SessionFsError; } /** - * Schema for the `SessionFsReaddirWithTypesEntry` type. + * Directory entry returned by session filesystem `readdirWithTypes`, with name and entry type. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "SessionFsReaddirWithTypesEntry". @@ -10969,7 +11535,7 @@ export interface SessionFsWriteFileRequest { mode?: number; } /** - * Schema for the `SessionInstalledPlugin` type. + * Installed plugin record for a session, with marketplace, version, install time, enabled state, cache path, and source. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "SessionInstalledPlugin". @@ -11003,7 +11569,7 @@ export interface SessionInstalledPlugin { source?: SessionInstalledPluginSource; } /** - * Schema for the `SessionInstalledPluginSourceGitHub` type. + * Source descriptor for a direct GitHub plugin install, with `owner/repo`, optional ref, and optional subpath. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "SessionInstalledPluginSourceGitHub". @@ -11019,7 +11585,7 @@ export interface SessionInstalledPluginSourceGitHub { path?: string; } /** - * Schema for the `SessionInstalledPluginSourceUrl` type. + * Source descriptor for a direct URL plugin install, with URL, optional ref, and optional subpath. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "SessionInstalledPluginSourceUrl". @@ -11035,7 +11601,7 @@ export interface SessionInstalledPluginSourceUrl { path?: string; } /** - * Schema for the `SessionInstalledPluginSourceLocal` type. + * Source descriptor for a direct local plugin install, with a local filesystem path. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "SessionInstalledPluginSourceLocal". @@ -11210,6 +11776,7 @@ export interface SessionOpenOptions { */ reasoningEffort?: string; reasoningSummary?: SessionOpenOptionsReasoningSummary; + verbosity?: Verbosity; /** * Identifier of the client driving the session. */ @@ -11234,6 +11801,10 @@ export interface SessionOpenOptions { expAssignments?: { [k: string]: unknown | undefined; }; + /** + * Opt-in: self-fetch and enforce enterprise managed settings at session bootstrap. + */ + enableManagedSettings?: boolean; /** * Feature-flag values resolved by the host. */ @@ -11411,7 +11982,7 @@ export interface SessionOpenOptions { sessionCapabilities?: SessionCapability[]; } /** - * Schema for the `SessionOpenOptionsAdditionalContentExclusionPolicy` type. + * Content-exclusion policy supplied to `sessions.open` options, with rules, last-updated data, and scope. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "SessionOpenOptionsAdditionalContentExclusionPolicy". @@ -11423,7 +11994,7 @@ export interface SessionOpenOptionsAdditionalContentExclusionPolicy { scope: SessionOpenOptionsAdditionalContentExclusionPolicyScope; } /** - * Schema for the `SessionOpenOptionsAdditionalContentExclusionPolicyRule` type. + * Single content-exclusion rule supplied to `sessions.open` options, with paths, match conditions, and source. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "SessionOpenOptionsAdditionalContentExclusionPolicyRule". @@ -11436,7 +12007,7 @@ export interface SessionOpenOptionsAdditionalContentExclusionPolicyRule { source: SessionOpenOptionsAdditionalContentExclusionPolicyRuleSource; } /** - * Schema for the `SessionOpenOptionsAdditionalContentExclusionPolicyRuleSource` type. + * Source descriptor for a `sessions.open` content-exclusion rule, with source name and type. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "SessionOpenOptionsAdditionalContentExclusionPolicyRuleSource". @@ -11595,6 +12166,14 @@ export interface SessionsOpenHandoff { onProgress?: { [k: string]: unknown | undefined; }; + /** + * In-process confirmation callback `(request) => boolean | Promise` invoked when the handoff needs the caller to confirm a non-fatal blocker (e.g. a repository mismatch between the current working directory and the remote session). Returning `true` proceeds with the handoff; returning `false` (or omitting the callback) aborts it. Marked internal because a function reference cannot cross the JSON-RPC boundary, for the same reasons as `onProgress`. + * + * @internal + */ + onConfirm?: { + [k: string]: unknown | undefined; + }; } /** * Result of opening a session. @@ -11634,7 +12213,7 @@ export interface SessionOpenResult { progress?: SessionsOpenProgress[]; } /** - * Schema for the `SessionsOpenProgress` type. + * `sessions.open` handoff progress update with step, status, and optional message. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "SessionsOpenProgress". @@ -11777,6 +12356,134 @@ export interface SessionSetCredentialsResult { */ copilotUserResolved?: boolean; } +/** + * Availability of built-in job tools surfaced to boundary consumers. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "SessionSettingsBuiltInToolAvailabilitySnapshot". + */ +/** @experimental */ +export interface SessionSettingsBuiltInToolAvailabilitySnapshot { + reportProgress?: boolean; + createPullRequest?: boolean; +} +/** + * Named Rust-owned settings predicate to evaluate for this session. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "SessionSettingsEvaluatePredicateRequest". + */ +/** @experimental */ +export interface SessionSettingsEvaluatePredicateRequest { + name: SessionSettingsPredicateName; + /** + * Tool name for tool-scoped predicates such as trivial-change handling. + */ + toolName?: string; +} +/** + * Result of evaluating a Rust-owned settings predicate. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "SessionSettingsEvaluatePredicateResult". + */ +/** @experimental */ +export interface SessionSettingsEvaluatePredicateResult { + enabled: boolean; +} +/** + * Redacted job settings for a session. The job nonce is excluded. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "SessionSettingsJobSnapshot". + */ +/** @experimental */ +export interface SessionSettingsJobSnapshot { + eventType?: string; + isTriggerJob?: boolean; + builtInToolAvailability?: SessionSettingsBuiltInToolAvailabilitySnapshot; +} +/** + * Redacted model routing settings for a session. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "SessionSettingsModelSnapshot". + */ +/** @experimental */ +export interface SessionSettingsModelSnapshot { + model?: string; + defaultReasoningEffort?: string; + instanceId?: string; + callbackUrl?: string; +} +/** + * Online-evaluation settings safe to expose across the SDK boundary. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "SessionSettingsOnlineEvaluationSnapshot". + */ +/** @experimental */ +export interface SessionSettingsOnlineEvaluationSnapshot { + disableOnlineEvaluation?: boolean; + enableOnlineEvaluationOutputFile?: boolean; +} +/** + * Redacted repository and GitHub host settings for a session. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "SessionSettingsRepoSnapshot". + */ +/** @experimental */ +export interface SessionSettingsRepoSnapshot { + name?: string; + id?: number; + branch?: string; + commit?: string; + readWrite?: boolean; + ownerName?: string; + ownerId?: number; + serverUrl?: string; + host?: string; + hostProtocol?: string; + secretScanningUrl?: string; + prCommitCount?: number; +} +/** + * Redacted, serializable view of session runtime settings for SDK boundary consumers. Secrets and raw feature flags are intentionally excluded. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "SessionSettingsSnapshot". + */ +/** @experimental */ +export interface SessionSettingsSnapshot { + version?: string; + clientName?: string; + timeoutMs?: number; + startTimeMs?: number; + repo: SessionSettingsRepoSnapshot; + model: SessionSettingsModelSnapshot; + validation: SessionSettingsValidationSnapshot; + job: SessionSettingsJobSnapshot; + onlineEvaluation: SessionSettingsOnlineEvaluationSnapshot; +} +/** + * Redacted validation and memory-tool settings for a session. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "SessionSettingsValidationSnapshot". + */ +/** @experimental */ +export interface SessionSettingsValidationSnapshot { + timeout?: number; + dependabotTimeout?: number; + codeqlEnabled?: boolean; + codeReviewEnabled?: boolean; + codeReviewModel?: string; + advisoryEnabled?: boolean; + secretScanningEnabled?: boolean; + memoryStoreEnabled?: boolean; + memoryVoteEnabled?: boolean; +} /** * UUID prefix to resolve to a unique session ID. * @@ -12019,18 +12726,6 @@ export interface SessionsLoadDeferredRepoHooksRequest { */ sessionId: string; } - -/** @experimental */ -export interface SessionsPollSpawnedSessionsRequest { - /** - * Opaque cursor returned by a previous poll. Omit on the first call to receive any spawn events buffered since the runtime started. - */ - cursor?: string; - /** - * Milliseconds to wait for new spawn events when the cursor is at the tail. 0 (default) returns immediately even if no events are buffered. Capped at 60000ms. - */ - waitMs?: number; -} /** * Age threshold and optional flags controlling which old sessions are pruned (or simulated when dryRun is true). * @@ -12231,6 +12926,7 @@ export interface SessionUpdateOptionsParams { */ reasoningEffort?: string; reasoningSummary?: OptionsUpdateReasoningSummary; + verbosity?: Verbosity; /** * Identifier of the client driving the session. */ @@ -12533,7 +13229,7 @@ export interface ShutdownRequest { reason?: string; } /** - * Schema for the `Skill` type. + * Skill metadata available to a session, with name, description, source, enabled/invocable state, path, plugin, and argument hint. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "Skill". @@ -12571,7 +13267,7 @@ export interface Skill { argumentHint?: string; } /** - * Schema for the `SkillDiscoveryPath` type. + * Canonical directory where skills can be discovered or created, with scope, preference, and optional project path. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "SkillDiscoveryPath". @@ -12709,7 +13405,7 @@ export interface SkillsGetInvokedResult { skills: SkillsInvokedSkill[]; } /** - * Schema for the `SkillsInvokedSkill` type. + * Skill invocation record with name, path, content, allowed tools, and turn number. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "SkillsInvokedSkill". @@ -12755,7 +13451,7 @@ export interface SkillsLoadDiagnostics { errors: string[]; } /** - * Schema for the `SlashCommandAgentPromptResult` type. + * Slash-command invocation result that submits an agent prompt, with display prompt, optional mode, and settings-change flag. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "SlashCommandAgentPromptResult". @@ -12781,7 +13477,7 @@ export interface SlashCommandAgentPromptResult { runtimeSettingsChanged?: boolean; } /** - * Schema for the `SlashCommandCompletedResult` type. + * Slash-command invocation result indicating completion, with optional message and settings-change flag. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "SlashCommandCompletedResult". @@ -12802,7 +13498,7 @@ export interface SlashCommandCompletedResult { runtimeSettingsChanged?: boolean; } /** - * Schema for the `SlashCommandTextResult` type. + * Slash-command invocation result containing text output plus Markdown/ANSI rendering flags. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "SlashCommandTextResult". @@ -12831,7 +13527,7 @@ export interface SlashCommandTextResult { runtimeSettingsChanged?: boolean; } /** - * Schema for the `SlashCommandSelectSubcommandResult` type. + * Slash-command invocation result asking the client to present subcommand options for a parent command. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "SlashCommandSelectSubcommandResult". @@ -12860,7 +13556,7 @@ export interface SlashCommandSelectSubcommandResult { runtimeSettingsChanged?: boolean; } /** - * Schema for the `SlashCommandSelectSubcommandOption` type. + * Selectable slash-command subcommand option with name, description, and optional group label. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "SlashCommandSelectSubcommandOption". @@ -12899,7 +13595,7 @@ export interface SubagentSettingsEntry { contextTier?: SubagentSettingsEntryContextTier; } /** - * Schema for the `TaskAgentInfo` type. + * Tracked background agent task metadata, including IDs, status, timing, agent type, prompt, model, result, and latest response. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "TaskAgentInfo". @@ -12978,7 +13674,7 @@ export interface TaskAgentInfo { idleSince?: string; } /** - * Schema for the `TaskAgentProgress` type. + * Progress snapshot for an agent task, with recent activity lines and optional latest intent. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "TaskAgentProgress". @@ -12999,7 +13695,7 @@ export interface TaskAgentProgress { latestIntent?: string; } /** - * Schema for the `TaskProgressLine` type. + * Timestamped display line for task progress output or recent agent activity. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "TaskProgressLine". @@ -13016,7 +13712,7 @@ export interface TaskProgressLine { timestamp: string; } /** - * Schema for the `TaskShellInfo` type. + * Tracked shell task metadata, including ID, command, status, timing, attachment/execution mode, log path, and PID. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "TaskShellInfo". @@ -13077,7 +13773,7 @@ export interface TaskList { tasks: TaskInfo[]; } /** - * Schema for the `TaskShellProgress` type. + * Progress snapshot for a shell task, with recent stdout/stderr output and optional process ID. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "TaskShellProgress". @@ -13333,7 +14029,7 @@ export interface TelemetrySetFeatureOverridesRequest { }; } /** - * Schema for the `Tool` type. + * Built-in tool metadata with identifier, optional namespaced name, description, input-parameter schema, and usage instructions. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "Tool". @@ -13466,7 +14162,7 @@ export interface UIElicitationArrayAnyOfFieldItems { anyOf: UIElicitationArrayAnyOfFieldItemsAnyOf[]; } /** - * Schema for the `UIElicitationArrayAnyOfFieldItemsAnyOf` type. + * Selectable option for a UI elicitation multi-select array item, with submitted value and display label. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "UIElicitationArrayAnyOfFieldItemsAnyOf". @@ -13633,7 +14329,7 @@ export interface UIElicitationStringOneOfField { default?: string; } /** - * Schema for the `UIElicitationStringOneOfFieldOneOf` type. + * Selectable option for a UI elicitation single-select string field, with submitted value and display label. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "UIElicitationStringOneOfFieldOneOf". @@ -13815,7 +14511,7 @@ export interface UIEphemeralQueryResult { answer: string; } /** - * Schema for the `UIExitPlanModeResponse` type. + * User response for a pending exit-plan-mode request, with approval state, selected action, auto-approve flag, and feedback. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "UIExitPlanModeResponse". @@ -13962,7 +14658,7 @@ export interface UIHandlePendingUserInputRequest { response: UIUserInputResponse; } /** - * Schema for the `UIUserInputResponse` type. + * User response for a pending user-input request, with answer text and whether it was typed freeform. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "UIUserInputResponse". @@ -14085,7 +14781,7 @@ export interface UsageGetMetricsResult { lastCallOutputTokens: number; } /** - * Schema for the `UsageMetricsTokenDetail` type. + * Session-wide token-detail entry containing the accumulated token count for one token type. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "UsageMetricsTokenDetail". @@ -14123,7 +14819,7 @@ export interface UsageMetricsCodeChanges { filesModified: string[]; } /** - * Schema for the `UsageMetricsModelMetric` type. + * Per-model usage metrics, including request counts/costs, token usage, nano-AI units, and per-token-type details. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "UsageMetricsModelMetric". @@ -14190,7 +14886,7 @@ export interface UsageMetricsModelMetricUsage { reasoningTokens?: number; } /** - * Schema for the `UsageMetricsModelMetricTokenDetail` type. + * Per-model token-detail entry containing the accumulated token count for one token type. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "UsageMetricsModelMetricTokenDetail". @@ -14395,7 +15091,7 @@ export interface WorkspaceDiffResult { isFallback: boolean; } /** - * Schema for the `WorkspacesCheckpoints` type. + * Workspace checkpoint metadata with assigned number, human-readable title, and checkpoint filename. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "WorkspacesCheckpoints". @@ -15259,7 +15955,7 @@ export function createInternalServerRpc(connection: MessageConnection) { /** * Performs the SDK server connection handshake and validates the optional connection token. Marked internal because this is JSON-RPC transport plumbing invoked automatically by an SDK client's own `connect()` wrapper, not a user-facing method. Stays internal as long as the SDK client owns the handshake; would only become public if the SDK ever exposed the raw schema surface to consumers without a connection wrapper. * - * @param params Optional connection token presented by the SDK client during the handshake. + * @param params Parameters for the `server.connect` handshake: an optional connection token and optional connection-level opt-ins (e.g. GitHub telemetry forwarding). * * @returns Handshake result reporting the server's protocol version and package version on success. * @@ -15296,15 +15992,6 @@ export function createInternalServerRpc(connection: MessageConnection) { */ getBoardEntryCount: async (params: SessionsGetBoardEntryCountRequest): Promise => connection.sendRequest("sessions.getBoardEntryCount", params), - /** - * Cursor-based long-poll for sessions spawned by the runtime (e.g. in response to a Mission Control `start_session` command). The cursor is an opaque token; pass it back to receive only spawn events that occurred AFTER the cursor was issued. Omit the cursor on the first call to receive any events buffered since the runtime started. Internal: this is a CLI background-daemon plumbing primitive. SDK consumers that need to react to runtime-spawned sessions should subscribe to a higher-level event stream rather than driving a long-poll loop. - * - * @param params Cursor and optional long-poll wait for polling runtime-spawned sessions. - * - * @returns Batch of spawn events plus a cursor for follow-up polls. - */ - pollSpawnedSessions: async (params: SessionsPollSpawnedSessionsRequest): Promise => - connection.sendRequest("sessions.pollSpawnedSessions", params), /** * Registers extension-provided tools on the given session, gated by an optional `enabled` callback. Returns an opaque unsubscribe function the caller must invoke to deregister the tools when the extension is torn down. Marked internal because `loader`, `enabled`, and the returned `unsubscribe` are in-process handles that cannot cross the JSON-RPC boundary. Disappears once extension discovery / launch / tool registration are owned by the runtime: SDK consumers will pass pure config (search paths, disabled ids) via `SessionOptions` and the runtime will resolve, launch, register, and tear down extensions itself. * @@ -15386,6 +16073,18 @@ export function createSessionRpc(connection: MessageConnection, sessionId: strin connection.sendRequest("session.gitHubAuth.setCredentials", { sessionId, ...params }), }, /** @experimental */ + debug: { + /** + * Collects a redacted session debug log bundle into a local archive or staging directory. The runtime includes session-owned logs by default and accepts caller-provided diagnostic entries so host applications can add their own files without changing this API shape. + * + * @param params Options for collecting a redacted session debug bundle. + * + * @returns Result of collecting a redacted debug bundle. + */ + collectLogs: async (params: DebugCollectLogsRequest): Promise => + connection.sendRequest("session.debug.collectLogs", { sessionId, ...params }), + }, + /** @experimental */ canvas: { /** * Lists canvases declared for the session. @@ -16334,18 +17033,18 @@ export function createSessionRpc(connection: MessageConnection, sessionId: strin setApproveAll: async (params: PermissionsSetApproveAllRequest): Promise => connection.sendRequest("session.permissions.setApproveAll", { sessionId, ...params }), /** - * Enables or disables full allow-all permissions (tools, paths, and URLs) for the session. Used by attach-mode clients (e.g. LocalRpcSession's `/allow-all` forwarder) to flip the target session's permission state. Unlike `setApproveAll`, this swaps in the unrestricted path and URL managers and emits `session.permissions_changed` on transition. The result returns the authoritative post-mutation state so callers can update their local mirrors without racing the `session.permissions_changed` notification on the same wire. + * Sets the allow-all permission mode for the session. Used by attach-mode clients (e.g. LocalRpcSession's `/allow-all` forwarder) to flip the target session's permission state. The `on` mode swaps in unrestricted path and URL managers and emits `session.permissions_changed` on transition; the `auto` mode keeps normal prompt paths active while attaching LLM safety recommendations. The result returns the authoritative post-mutation state so callers can update their local mirrors without racing the `session.permissions_changed` notification on the same wire. * - * @param params Whether to enable full allow-all permissions for the session. + * @param params Allow-all mode to apply for the session. * * @returns Indicates whether the operation succeeded and reports the post-mutation state. */ setAllowAll: async (params: PermissionsSetAllowAllRequest): Promise => connection.sendRequest("session.permissions.setAllowAll", { sessionId, ...params }), /** - * Returns whether full allow-all permissions are currently active for the session. + * Returns the current allow-all permission mode for the session. * - * @returns Current full allow-all permission state. + * @returns Current allow-all permission mode. */ getAllowAll: async (): Promise => connection.sendRequest("session.permissions.getAllowAll", { sessionId }), @@ -16536,6 +17235,22 @@ export function createSessionRpc(connection: MessageConnection, sessionId: strin */ contextInfo: async (params: MetadataContextInfoRequest): Promise => connection.sendRequest("session.metadata.contextInfo", { sessionId, ...params }), + /** + * Returns the experimental per-source attribution breakdown of the session's current context window as a flat list of entries (skills, subagents, MCP servers, built-in tools, plugin rollups, system/tool-definition costs, with nesting via parentId), plus the successful compaction count. The heaviest individual messages are available separately via `metadata.getContextHeaviestMessages`. Returns null until the session has initialized its system prompt and tool metadata. + * + * @returns Per-source attribution breakdown for the session's current context window, or null if uninitialized. + */ + getContextAttribution: async (): Promise => + connection.sendRequest("session.metadata.getContextAttribution", { sessionId }), + /** + * Returns the largest individual messages currently in the session's context window, most-expensive first. Companion to `metadata.getContextAttribution`. Returns an empty list until the session has initialized. + * + * @param params Parameters for the heaviest-messages query. + * + * @returns The heaviest individual messages in the session's context window, most-expensive first. + */ + getContextHeaviestMessages: async (params: MetadataContextHeaviestMessagesRequest): Promise => + connection.sendRequest("session.metadata.getContextHeaviestMessages", { sessionId, ...params }), /** * Records a working-directory/git context change and emits a `session.context_changed` event. * @@ -16836,18 +17551,25 @@ export function createInternalSessionRpc(connection: MessageConnection, sessionI */ unregisterExternalClient: async (params: McpUnregisterExternalClientRequest): Promise => connection.sendRequest("session.mcp.unregisterExternalClient", { sessionId, ...params }), - /** @experimental */ - oauth: { - /** - * Responds to a pending MCP OAuth request with an in-process provider. This internal CLI-only API accepts a live OAuthClientProvider instance and cannot be used over the SDK JSON-RPC boundary. Use session.mcp.oauth.handlePendingRequest instead for the public SDK-safe response path. - * - * @param params MCP OAuth request id and optional provider response. - * - * @returns Empty result after recording the MCP OAuth response. - */ - respond: async (params: McpOauthRespondRequest): Promise => - connection.sendRequest("session.mcp.oauth.respond", { sessionId, ...params }), - }, + }, + /** @experimental */ + settings: { + /** + * Returns a redacted snapshot of session runtime settings, with secrets and raw feature flags excluded. Internal: the runtime settings shape is a runtime-internal surface and is deliberately kept out of the public SDK, because consumers should not depend on the runtime's internal settings layout. It remains callable in-process and is expected to be reworked as the runtime internals are consolidated. + * + * @returns Redacted, serializable view of session runtime settings for SDK boundary consumers. Secrets and raw feature flags are intentionally excluded. + */ + snapshot: async (): Promise => + connection.sendRequest("session.settings.snapshot", { sessionId }), + /** + * Evaluates a named Rust-owned settings predicate without exposing raw feature flags. Internal: the raw feature-flag names and composition are runtime-internal, so this predicate-evaluation helper is kept out of the public SDK surface and is callable in-process only. + * + * @param params Named Rust-owned settings predicate to evaluate for this session. + * + * @returns Result of evaluating a Rust-owned settings predicate. + */ + evaluatePredicate: async (params: SessionSettingsEvaluatePredicateRequest): Promise => + connection.sendRequest("session.settings.evaluatePredicate", { sessionId, ...params }), }, }; } @@ -17117,9 +17839,9 @@ export interface LlmInferenceHandler { /** @experimental */ export interface GitHubTelemetryHandler { /** - * Forwards a single GitHub telemetry event to a host connection that opted into telemetry forwarding for the session. + * 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). * - * @param params Payload for a `gitHubTelemetry.event` notification: a single GitHub telemetry event the runtime forwards to a host connection that opted into telemetry forwarding for the session. + * @param 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. */ event(params: GitHubTelemetryNotification): Promise; } @@ -17151,9 +17873,9 @@ export function registerClientGlobalApiHandlers( if (!handler) throw new Error("No llmInference client-global handler registered"); return handler.httpRequestChunk(params); }); - connection.onRequest("gitHubTelemetry.event", async (params: GitHubTelemetryNotification) => { + connection.onNotification("gitHubTelemetry.event", async (params: GitHubTelemetryNotification) => { const handler = handlers.gitHubTelemetry; - if (!handler) throw new Error("No gitHubTelemetry client-global handler registered"); - return handler.event(params); + if (!handler) return; + await handler.event(params); }); } diff --git a/nodejs/src/generated/session-events.ts b/nodejs/src/generated/session-events.ts index 5d7eac82da..c62044006c 100644 --- a/nodejs/src/generated/session-events.ts +++ b/nodejs/src/generated/session-events.ts @@ -42,6 +42,7 @@ export type SessionEvent = | AssistantIntentEvent | AssistantReasoningEvent | AssistantReasoningDeltaEvent + | AssistantToolCallDeltaEvent | AssistantStreamingDeltaEvent | AssistantMessageEvent | AssistantMessageStartEvent @@ -135,6 +136,16 @@ export type ReasoningSummary = | "concise" /** Request a detailed summary of the model's reasoning. */ | "detailed"; +/** + * Output verbosity level used for supported model calls (e.g. "low", "medium", "high") + */ +export type Verbosity = + /** A terse response was requested. */ + | "low" + /** A medium amount of response detail was requested. */ + | "medium" + /** A more detailed response was requested. */ + | "high"; /** * The type of operation performed on the autopilot objective state file */ @@ -167,6 +178,17 @@ export type SessionMode = | "plan" /** The agent is working autonomously toward task completion. */ | "autopilot"; +/** + * Allow-all mode for the session. + */ +/** @experimental */ +export type PermissionAllowAllMode = + /** Permission requests follow the normal approval flow. */ + | "off" + /** Tool, path, and URL permission requests are automatically approved. */ + | "on" + /** Permission requests follow the normal approval flow with an LLM advisory recommendation attached; clients may choose to auto-approve requests the judge evaluated as acceptable. */ + | "auto"; /** * The type of operation performed on the plan file */ @@ -260,6 +282,14 @@ export type UserMessageDelivery = | "steering" /** Enqueued while the agent was busy; processed as its own run afterward. */ | "queued"; +/** + * Tool call type: "function" for standard tool calls, "custom" for grammar-based tool calls. Defaults to "function" when absent. + */ +export type AssistantMessageToolRequestType = + /** Standard function-style tool call. */ + | "function" + /** Custom grammar-based tool call. */ + | "custom"; /** * The system that produced a citation. */ @@ -276,14 +306,6 @@ export type CitationProvider = */ /** @experimental */ export type CitationLocation = CitationLocationChar | CitationLocationPage | CitationLocationBlock; -/** - * Tool call type: "function" for standard tool calls, "custom" for grammar-based tool calls. Defaults to "function" when absent. - */ -export type AssistantMessageToolRequestType = - /** Standard function-style tool call. */ - | "function" - /** Custom grammar-based tool call. */ - | "custom"; /** * API endpoint used for this model call, matching CAPI supported_endpoints vocabulary */ @@ -481,6 +503,19 @@ export type PermissionPromptRequest = | PermissionPromptRequestHook | PermissionPromptRequestExtensionManagement | PermissionPromptRequestExtensionPermissionAccess; +/** + * Outcome of the auto-approval safety judge for a permission request. Present only when auto mode is enabled; its absence means the judge did not evaluate the request (auto mode was off). + */ +/** @experimental */ +export type AutoApprovalRecommendation = + /** The judge evaluated the request and recommends automatically approving it. */ + | "approve" + /** The judge evaluated the request and does not recommend auto-approving it; explicit approval is required. Whether that means prompting, denying, or something else is the consumer's decision. */ + | "requireApproval" + /** Auto mode is enabled, but this request category is never auto-approvable (for example, sandbox-bypass requests), so the judge was not consulted. */ + | "excluded" + /** The judge was consulted but did not return a usable recommendation, so the request requires explicit approval. */ + | "error"; /** * Underlying permission kind that needs path approval */ @@ -768,6 +803,7 @@ export interface StartData { * ISO 8601 timestamp when the session was created */ startTime: string; + verbosity?: Verbosity; /** * Schema version number for the session event format */ @@ -896,6 +932,7 @@ export interface ResumeData { * True when this resume attached to a session that the runtime already had running in-memory (for example, an extension joining a session another client was actively driving). False (or omitted) for cold resumes — the runtime had to reconstitute the session from its persisted event log. */ sessionWasActive?: boolean; + verbosity?: Verbosity; } /** * Session event "session.remote_steerable_changed". Notifies that the session's remote steering capability has changed @@ -1432,11 +1469,13 @@ export interface ModelChangeData { */ previousReasoningEffort?: string; previousReasoningSummary?: ReasoningSummary; + previousVerbosity?: Verbosity; /** * Reasoning effort level after the model change, if applicable */ reasoningEffort?: string | null; reasoningSummary?: ReasoningSummary; + verbosity?: Verbosity; } /** * Session event "session.mode_changed". Agent mode change details including previous and new modes @@ -1515,7 +1554,7 @@ export interface SessionLimitsChangedData { sessionLimits: SessionLimitsConfig | null; } /** - * Session event "session.permissions_changed". Permissions change details carrying the aggregate allow-all boolean transition. + * Session event "session.permissions_changed". Permissions change details carrying the aggregate allow-all transition. */ export interface PermissionsChangedEvent { /** @@ -1545,13 +1584,25 @@ export interface PermissionsChangedEvent { type: "session.permissions_changed"; } /** - * Permissions change details carrying the aggregate allow-all boolean transition. + * Permissions change details carrying the aggregate allow-all transition. */ export interface PermissionsChangedData { + /** + * Allow-all mode after the change + * + * @experimental + */ + allowAllPermissionMode?: PermissionAllowAllMode; /** * Aggregate allow-all flag after the change */ allowAllPermissions: boolean; + /** + * Allow-all mode before the change + * + * @experimental + */ + previousAllowAllPermissionMode?: PermissionAllowAllMode; /** * Aggregate allow-all flag before the change */ @@ -1966,7 +2017,7 @@ export interface ShutdownCodeChanges { linesRemoved: number; } /** - * Schema for the `ShutdownModelMetric` type. + * Per-model shutdown metrics with request counts, token usage, nano-AI units, and token details. */ export interface ShutdownModelMetric { requests: ShutdownModelMetricRequests; @@ -2002,7 +2053,7 @@ export interface ShutdownModelMetricRequests { count?: number; } /** - * Schema for the `ShutdownModelMetricTokenDetail` type. + * A token-type entry in a shutdown model metric, storing the accumulated token count. */ export interface ShutdownModelMetricTokenDetail { /** @@ -2036,7 +2087,7 @@ export interface ShutdownModelMetricUsage { reasoningTokens?: number; } /** - * Schema for the `ShutdownTokenDetail` type. + * A session-wide shutdown token-type entry storing the accumulated token count. */ export interface ShutdownTokenDetail { /** @@ -2220,6 +2271,10 @@ export interface CompactionStartData { * Token count from non-system messages (user, assistant, tool) at compaction start */ conversationTokens?: number; + /** + * Model identifier used for compaction, when known + */ + model?: string; /** * Token count from system message(s) at compaction start */ @@ -2449,7 +2504,7 @@ export interface TaskCompleteData { summary?: string; } /** - * Session event "user.message". + * 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 { /** @@ -2479,7 +2534,7 @@ export interface UserMessageEvent { type: "user.message"; } /** - * Schema for the `UserMessageData` type. + * Payload of `user.message` with displayed and model-transformed content, attachments, source/delivery metadata, mode, and telemetry IDs. */ export interface UserMessageData { agentMode?: UserMessageAgentMode; @@ -3033,6 +3088,10 @@ 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 */ @@ -3163,6 +3222,54 @@ export interface AssistantReasoningDeltaData { */ reasoningId: string; } +/** + * Session event "assistant.tool_call_delta". Streaming tool-call input delta for incremental tool-call updates + */ +export interface AssistantToolCallDeltaEvent { + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + data: AssistantToolCallDeltaData; + /** + * Always true for events that are transient and not persisted to the session event log on disk. + */ + ephemeral: true; + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + /** + * Type discriminator. Always "assistant.tool_call_delta". + */ + type: "assistant.tool_call_delta"; +} +/** + * Streaming tool-call input delta for incremental tool-call updates + */ +export interface AssistantToolCallDeltaData { + /** + * Raw provider tool input fragment to append for this tool call. Function/tool-use providers stream serialized JSON argument text (so newlines inside JSON string values may appear as escaped `\n` until the accumulated JSON is parsed); custom tool calls stream raw custom input. + */ + inputDelta: string; + /** + * Tool call ID this delta belongs to, matching the corresponding assistant.message tool request + */ + toolCallId: string; + /** + * Name of the tool being invoked, when known from the stream + */ + toolName?: string; + toolType?: AssistantMessageToolRequestType; +} /** * Session event "assistant.streaming_delta". Streaming response progress with cumulative byte count */ @@ -3613,6 +3720,10 @@ export interface AssistantTurnEndEvent { * Turn completion metadata including the turn identifier */ export interface AssistantTurnEndData { + /** + * Model identifier used for this turn, when known + */ + model?: string; /** * Identifier of the turn that has ended, matching the corresponding assistant.turn_start event */ @@ -3814,7 +3925,7 @@ export interface AssistantUsageCopilotUsageTokenDetail { tokenType: string; } /** - * Schema for the `AssistantUsageQuotaSnapshot` type. + * Internal per-quota snapshot for assistant usage, including entitlement, consumed requests, overage, reset date, and remaining quota. */ /** @internal */ export interface AssistantUsageQuotaSnapshot { @@ -4199,7 +4310,7 @@ export interface ToolExecutionStartToolDescriptionMeta { ui?: ToolExecutionStartToolDescriptionMetaUI; } /** - * Schema for the `ToolExecutionStartToolDescriptionMetaUI` type. + * MCP Apps tool `_meta.ui` resource URI and visibility captured on `tool.execution_start`. */ export interface ToolExecutionStartToolDescriptionMetaUI { /** @@ -4692,7 +4803,7 @@ export interface ToolExecutionCompleteContentResource { type: "resource"; } /** - * Schema for the `EmbeddedTextResourceContents` type. + * Embedded text resource contents identified by a URI, with an optional MIME type and a text payload. */ export interface EmbeddedTextResourceContents { /** @@ -4709,7 +4820,7 @@ export interface EmbeddedTextResourceContents { uri: string; } /** - * Schema for the `EmbeddedBlobResourceContents` type. + * Embedded binary resource contents identified by a URI, with an optional MIME type and a base64-encoded blob. */ export interface EmbeddedBlobResourceContents { /** @@ -4754,7 +4865,7 @@ export interface ToolExecutionCompleteUIResourceMeta { ui?: ToolExecutionCompleteUIResourceMetaUI; } /** - * Schema for the `ToolExecutionCompleteUIResourceMetaUI` type. + * MCP Apps UI resource metadata for a completed tool result, including CSP, permissions, domain, and border preference. */ export interface ToolExecutionCompleteUIResourceMetaUI { csp?: ToolExecutionCompleteUIResourceMetaUICsp; @@ -4763,7 +4874,7 @@ export interface ToolExecutionCompleteUIResourceMetaUI { prefersBorder?: boolean; } /** - * Schema for the `ToolExecutionCompleteUIResourceMetaUICsp` type. + * CSP domain allowlists for an MCP Apps UI resource, including connect, resource, frame, and base URI domains. */ export interface ToolExecutionCompleteUIResourceMetaUICsp { baseUriDomains?: string[]; @@ -4772,7 +4883,7 @@ export interface ToolExecutionCompleteUIResourceMetaUICsp { resourceDomains?: string[]; } /** - * Schema for the `ToolExecutionCompleteUIResourceMetaUIPermissions` type. + * Browser permission metadata for an MCP Apps UI resource, including camera, microphone, geolocation, and clipboard-write. */ export interface ToolExecutionCompleteUIResourceMetaUIPermissions { camera?: ToolExecutionCompleteUIResourceMetaUIPermissionsCamera; @@ -4781,19 +4892,19 @@ export interface ToolExecutionCompleteUIResourceMetaUIPermissions { microphone?: ToolExecutionCompleteUIResourceMetaUIPermissionsMicrophone; } /** - * Schema for the `ToolExecutionCompleteUIResourceMetaUIPermissionsCamera` type. + * Marker object for camera permission on an MCP Apps UI resource. */ export interface ToolExecutionCompleteUIResourceMetaUIPermissionsCamera {} /** - * Schema for the `ToolExecutionCompleteUIResourceMetaUIPermissionsClipboardWrite` type. + * Marker object for clipboard-write permission on an MCP Apps UI resource. */ export interface ToolExecutionCompleteUIResourceMetaUIPermissionsClipboardWrite {} /** - * Schema for the `ToolExecutionCompleteUIResourceMetaUIPermissionsGeolocation` type. + * Marker object for geolocation permission on an MCP Apps UI resource. */ export interface ToolExecutionCompleteUIResourceMetaUIPermissionsGeolocation {} /** - * Schema for the `ToolExecutionCompleteUIResourceMetaUIPermissionsMicrophone` type. + * Marker object for microphone permission on an MCP Apps UI resource. */ export interface ToolExecutionCompleteUIResourceMetaUIPermissionsMicrophone {} /** @@ -4817,7 +4928,7 @@ export interface ToolExecutionCompleteToolDescriptionMeta { ui?: ToolExecutionCompleteToolDescriptionMetaUI; } /** - * Schema for the `ToolExecutionCompleteToolDescriptionMetaUI` type. + * MCP Apps tool `_meta.ui` resource URI and visibility captured on `tool.execution_complete`. */ export interface ToolExecutionCompleteToolDescriptionMetaUI { /** @@ -4875,6 +4986,10 @@ export interface SkillInvokedData { * Description of the skill from its SKILL.md frontmatter */ description?: string; + /** + * Model identifier active when the skill was invoked, when known + */ + model?: string; /** * Name of the invoked skill */ @@ -5490,7 +5605,7 @@ export interface SystemNotificationData { kind: SystemNotification; } /** - * Schema for the `SystemNotificationAgentCompleted` type. + * System notification metadata for a background agent that completed or failed, including agent ID, type, status, description, and prompt. */ export interface SystemNotificationAgentCompleted { /** @@ -5516,7 +5631,7 @@ export interface SystemNotificationAgentCompleted { type: "agent_completed"; } /** - * Schema for the `SystemNotificationAgentIdle` type. + * System notification metadata for a background agent that became idle, including agent ID, type, and description. */ export interface SystemNotificationAgentIdle { /** @@ -5537,7 +5652,7 @@ export interface SystemNotificationAgentIdle { type: "agent_idle"; } /** - * Schema for the `SystemNotificationNewInboxMessage` type. + * System notification metadata for a new inbox message, including entry ID, sender details, and summary. */ export interface SystemNotificationNewInboxMessage { /** @@ -5562,7 +5677,7 @@ export interface SystemNotificationNewInboxMessage { type: "new_inbox_message"; } /** - * Schema for the `SystemNotificationShellCompleted` type. + * System notification metadata for a shell session that completed, including shell ID, optional exit code, and description. */ export interface SystemNotificationShellCompleted { /** @@ -5583,7 +5698,7 @@ export interface SystemNotificationShellCompleted { type: "shell_completed"; } /** - * Schema for the `SystemNotificationShellDetachedCompleted` type. + * System notification metadata for a detached shell session that completed, including shell ID and description. */ export interface SystemNotificationShellDetachedCompleted { /** @@ -5600,7 +5715,7 @@ export interface SystemNotificationShellDetachedCompleted { type: "shell_detached_completed"; } /** - * Schema for the `SystemNotificationInstructionDiscovered` type. + * System notification metadata for an instruction file discovered during tool access, including source, trigger file, and tool. */ export interface SystemNotificationInstructionDiscovered { /** @@ -5723,7 +5838,7 @@ export interface PermissionRequestShell { warning?: string; } /** - * Schema for the `PermissionRequestShellCommand` type. + * A parsed command identifier in a shell permission request, including whether it is read-only. */ export interface PermissionRequestShellCommand { /** @@ -5736,7 +5851,7 @@ export interface PermissionRequestShellCommand { readOnly: boolean; } /** - * Schema for the `PermissionRequestShellPossibleUrl` type. + * A URL that may be accessed by a command in a shell permission request. */ export interface PermissionRequestShellPossibleUrl { /** @@ -5772,6 +5887,14 @@ export interface PermissionRequestWrite { * Complete new file contents for newly created files */ newFileContents?: string; + /** + * True when a built-in file tool (apply_patch / str_replace_editor) asked to write a path the sandbox filesystem policy would block, and the host opted in via sandbox.allowBypass. This is a request, not a grant: the write happens unsandboxed only if the user approves this permission request. Hosts should highlight the elevated risk in the approval UI. + */ + requestSandboxBypass?: boolean; + /** + * Justification for the sandbox-bypass request. Only meaningful when requestSandboxBypass is true. + */ + requestSandboxBypassReason?: string; /** * Tool call ID that triggered this permission request */ @@ -5853,6 +5976,14 @@ export interface PermissionRequestUrl { * Permission kind discriminator */ kind: "url"; + /** + * True when this URL fetch is requesting to bypass the sandbox network policy: either the model set requestSandboxBypass: true, or the tool re-issued the request as an interactive bypass after the network policy denied the approved URL (host opted in via sandbox.allowBypass). This is a request, not a grant: the fetch runs only if the user approves this permission request. Hosts should highlight the elevated risk in the approval UI. + */ + requestSandboxBypass?: boolean; + /** + * Model-provided justification for the sandbox-bypass request. Only meaningful when requestSandboxBypass is true. + */ + requestSandboxBypassReason?: string; /** * Tool call ID that triggered this permission request */ @@ -5993,6 +6124,12 @@ export interface PermissionRequestExtensionPermissionAccess { * Shell command permission prompt */ export interface PermissionPromptRequestCommands { + /** + * Auto-approval judge information for this request; present only when auto mode is enabled. + * + * @experimental + */ + autoApproval?: PermissionAutoApproval; /** * Whether the UI can offer session-wide approval for this command pattern */ @@ -6022,10 +6159,27 @@ export interface PermissionPromptRequestCommands { */ warning?: string; } +/** + * Auto-approval judge information attached to a permission request. Present (non-null) only when the session's allow-all mode is "auto"; its absence means auto mode was off and the judge did not evaluate the request. The `recommendation` conveys the judge's disposition for this request. + */ +/** @experimental */ +export interface PermissionAutoApproval { + /** + * Human-readable reason for the judge's recommendation, when available. + */ + reason?: string; + recommendation: AutoApprovalRecommendation; +} /** * File write permission prompt */ export interface PermissionPromptRequestWrite { + /** + * Auto-approval judge information for this request; present only when auto mode is enabled. + * + * @experimental + */ + autoApproval?: PermissionAutoApproval; /** * Whether the UI can offer session-wide approval for file write operations */ @@ -6059,6 +6213,12 @@ export interface PermissionPromptRequestWrite { * File read permission prompt */ export interface PermissionPromptRequestRead { + /** + * Auto-approval judge information for this request; present only when auto mode is enabled. + * + * @experimental + */ + autoApproval?: PermissionAutoApproval; /** * Human-readable description of why the file is being read */ @@ -6086,6 +6246,12 @@ export interface PermissionPromptRequestMcp { args?: { [k: string]: unknown | undefined; }; + /** + * Auto-approval judge information for this request; present only when auto mode is enabled. + * + * @experimental + */ + autoApproval?: PermissionAutoApproval; /** * Prompt kind discriminator */ @@ -6111,6 +6277,12 @@ export interface PermissionPromptRequestMcp { * URL access permission prompt */ export interface PermissionPromptRequestUrl { + /** + * Auto-approval judge information for this request; present only when auto mode is enabled. + * + * @experimental + */ + autoApproval?: PermissionAutoApproval; /** * Human-readable description of why the URL is being accessed */ @@ -6119,6 +6291,14 @@ export interface PermissionPromptRequestUrl { * Prompt kind discriminator */ kind: "url"; + /** + * True when this URL fetch is requesting to bypass the sandbox network policy: either the model set requestSandboxBypass: true, or the tool re-issued the request as an interactive bypass after the network policy denied the approved URL (host opted in via sandbox.allowBypass). This is a request, not a grant: the fetch runs only if the user approves this permission request. Hosts should highlight the elevated risk in the approval UI. + */ + requestSandboxBypass?: boolean; + /** + * Model-provided justification for the sandbox-bypass request. Only meaningful when requestSandboxBypass is true. + */ + requestSandboxBypassReason?: string; /** * Tool call ID that triggered this permission request */ @@ -6133,6 +6313,12 @@ export interface PermissionPromptRequestUrl { */ export interface PermissionPromptRequestMemory { action?: PermissionRequestMemoryAction; + /** + * Auto-approval judge information for this request; present only when auto mode is enabled. + * + * @experimental + */ + autoApproval?: PermissionAutoApproval; /** * Source references for the stored fact (store only) */ @@ -6169,6 +6355,12 @@ export interface PermissionPromptRequestCustomTool { args?: { [k: string]: unknown | undefined; }; + /** + * Auto-approval judge information for this request; present only when auto mode is enabled. + * + * @experimental + */ + autoApproval?: PermissionAutoApproval; /** * Prompt kind discriminator */ @@ -6191,6 +6383,12 @@ export interface PermissionPromptRequestCustomTool { */ export interface PermissionPromptRequestPath { accessKind: PermissionPromptRequestPathAccessKind; + /** + * Auto-approval judge information for this request; present only when auto mode is enabled. + * + * @experimental + */ + autoApproval?: PermissionAutoApproval; /** * Prompt kind discriminator */ @@ -6208,6 +6406,12 @@ export interface PermissionPromptRequestPath { * Hook confirmation permission prompt */ export interface PermissionPromptRequestHook { + /** + * Auto-approval judge information for this request; present only when auto mode is enabled. + * + * @experimental + */ + autoApproval?: PermissionAutoApproval; /** * Optional message from the hook explaining why confirmation is needed */ @@ -6235,6 +6439,12 @@ export interface PermissionPromptRequestHook { * Extension management permission prompt */ export interface PermissionPromptRequestExtensionManagement { + /** + * Auto-approval judge information for this request; present only when auto mode is enabled. + * + * @experimental + */ + autoApproval?: PermissionAutoApproval; /** * Name of the extension being managed */ @@ -6256,6 +6466,12 @@ export interface PermissionPromptRequestExtensionManagement { * Extension permission access prompt */ export interface PermissionPromptRequestExtensionPermissionAccess { + /** + * Auto-approval judge information for this request; present only when auto mode is enabled. + * + * @experimental + */ + autoApproval?: PermissionAutoApproval; /** * Capabilities the extension is requesting */ @@ -6318,7 +6534,7 @@ export interface PermissionCompletedData { toolCallId?: string; } /** - * Schema for the `PermissionApproved` type. + * Permission response variant indicating the request was approved without persisting an approval rule. */ export interface PermissionApproved { /** @@ -6327,7 +6543,7 @@ export interface PermissionApproved { kind: "approved"; } /** - * Schema for the `PermissionApprovedForSession` type. + * Permission response variant that approves a request and remembers the provided approval for the rest of the session. */ export interface PermissionApprovedForSession { approval: UserToolSessionApproval; @@ -6337,7 +6553,7 @@ export interface PermissionApprovedForSession { kind: "approved-for-session"; } /** - * Schema for the `UserToolSessionApprovalCommands` type. + * Session-scoped tool-approval rule for specific shell command identifiers. */ export interface UserToolSessionApprovalCommands { /** @@ -6350,7 +6566,7 @@ export interface UserToolSessionApprovalCommands { kind: "commands"; } /** - * Schema for the `UserToolSessionApprovalRead` type. + * Session-scoped tool-approval rule for read-only filesystem operations. */ export interface UserToolSessionApprovalRead { /** @@ -6359,7 +6575,7 @@ export interface UserToolSessionApprovalRead { kind: "read"; } /** - * Schema for the `UserToolSessionApprovalWrite` type. + * Session-scoped tool-approval rule for filesystem write operations. */ export interface UserToolSessionApprovalWrite { /** @@ -6368,7 +6584,7 @@ export interface UserToolSessionApprovalWrite { kind: "write"; } /** - * Schema for the `UserToolSessionApprovalMcp` type. + * Session-scoped tool-approval rule for an MCP server tool, or all tools on the server when `toolName` is null. */ export interface UserToolSessionApprovalMcp { /** @@ -6385,7 +6601,7 @@ export interface UserToolSessionApprovalMcp { toolName: string | null; } /** - * Schema for the `UserToolSessionApprovalMemory` type. + * Session-scoped tool-approval rule for writes to long-term memory. */ export interface UserToolSessionApprovalMemory { /** @@ -6394,7 +6610,7 @@ export interface UserToolSessionApprovalMemory { kind: "memory"; } /** - * Schema for the `UserToolSessionApprovalCustomTool` type. + * Session-scoped tool-approval rule for a custom tool, keyed by tool name. */ export interface UserToolSessionApprovalCustomTool { /** @@ -6407,7 +6623,7 @@ export interface UserToolSessionApprovalCustomTool { toolName: string; } /** - * Schema for the `UserToolSessionApprovalExtensionManagement` type. + * Session-scoped tool-approval rule for extension-management operations, optionally narrowed by operation. */ export interface UserToolSessionApprovalExtensionManagement { /** @@ -6420,7 +6636,7 @@ export interface UserToolSessionApprovalExtensionManagement { operation?: string; } /** - * Schema for the `UserToolSessionApprovalExtensionPermissionAccess` type. + * Session-scoped tool-approval rule for an extension's permission-gated capability access, keyed by extension name. */ export interface UserToolSessionApprovalExtensionPermissionAccess { /** @@ -6433,7 +6649,7 @@ export interface UserToolSessionApprovalExtensionPermissionAccess { kind: "extension-permission-access"; } /** - * Schema for the `PermissionApprovedForLocation` type. + * Permission response variant that approves a request and persists the provided approval to a project location key. */ export interface PermissionApprovedForLocation { approval: UserToolSessionApproval; @@ -6447,7 +6663,7 @@ export interface PermissionApprovedForLocation { locationKey: string; } /** - * Schema for the `PermissionCancelled` type. + * Permission response variant indicating the request was cancelled before use, with an optional reason. */ export interface PermissionCancelled { /** @@ -6460,7 +6676,7 @@ export interface PermissionCancelled { reason?: string; } /** - * Schema for the `PermissionDeniedByRules` type. + * Permission response variant denied because matching approval rules explicitly blocked the request. */ export interface PermissionDeniedByRules { /** @@ -6473,7 +6689,7 @@ export interface PermissionDeniedByRules { rules: PermissionRule[]; } /** - * Schema for the `PermissionRule` type. + * A permission approval or denial rule matched against a tool request, identified by a rule kind with an optional argument value. */ export interface PermissionRule { /** @@ -6486,7 +6702,7 @@ export interface PermissionRule { kind: string; } /** - * Schema for the `PermissionDeniedNoApprovalRuleAndCouldNotRequestFromUser` type. + * Permission response variant denied because no approval rule matched and user confirmation was unavailable. */ export interface PermissionDeniedNoApprovalRuleAndCouldNotRequestFromUser { /** @@ -6495,7 +6711,7 @@ export interface PermissionDeniedNoApprovalRuleAndCouldNotRequestFromUser { kind: "denied-no-approval-rule-and-could-not-request-from-user"; } /** - * Schema for the `PermissionDeniedInteractivelyByUser` type. + * Permission response variant denied in an interactive user prompt, with optional feedback and force-reject flag. */ export interface PermissionDeniedInteractivelyByUser { /** @@ -6512,7 +6728,7 @@ export interface PermissionDeniedInteractivelyByUser { kind: "denied-interactively-by-user"; } /** - * Schema for the `PermissionDeniedByContentExclusionPolicy` type. + * Permission response variant denying a path under content exclusion policy, with the path and message. */ export interface PermissionDeniedByContentExclusionPolicy { /** @@ -6529,7 +6745,7 @@ export interface PermissionDeniedByContentExclusionPolicy { path: string; } /** - * Schema for the `PermissionDeniedByPermissionRequestHook` type. + * Permission response variant denied by a permission-request hook, with optional message and interrupt flag. */ export interface PermissionDeniedByPermissionRequestHook { /** @@ -6770,7 +6986,7 @@ export interface ElicitationCompletedData { requestId: string; } /** - * Schema for the `ElicitationCompletedContent` type. + * Opaque JSON value submitted for one field in accepted `elicitation.completed` form content. */ export interface ElicitationCompletedContent { [k: string]: unknown | undefined; @@ -7613,7 +7829,7 @@ export interface CommandsChangedData { commands: CommandsChangedCommand[]; } /** - * Schema for the `CommandsChangedCommand` type. + * A single slash command available in the session, as listed by the `commands.changed` event. */ export interface CommandsChangedCommand { /** @@ -7783,7 +7999,7 @@ export interface ExitPlanModeCompletedData { selectedAction?: ExitPlanModeAction; } /** - * Session event "session.tools_updated". + * Session event "session.tools_updated". Payload of `session.tools_updated` identifying the model whose resolved tools were updated. */ export interface ToolsUpdatedEvent { /** @@ -7813,7 +8029,7 @@ export interface ToolsUpdatedEvent { type: "session.tools_updated"; } /** - * Schema for the `ToolsUpdatedData` type. + * Payload of `session.tools_updated` identifying the model whose resolved tools were updated. */ export interface ToolsUpdatedData { /** @@ -7822,7 +8038,7 @@ export interface ToolsUpdatedData { model: string; } /** - * Session event "session.background_tasks_changed". + * Session event "session.background_tasks_changed". Empty payload for `session.background_tasks_changed`, indicating background task state changed. */ export interface BackgroundTasksChangedEvent { /** @@ -7852,11 +8068,11 @@ export interface BackgroundTasksChangedEvent { type: "session.background_tasks_changed"; } /** - * Schema for the `BackgroundTasksChangedData` type. + * Empty payload for `session.background_tasks_changed`, indicating background task state changed. */ export interface BackgroundTasksChangedData {} /** - * Session event "session.skills_loaded". + * Session event "session.skills_loaded". Payload of `session.skills_loaded` listing resolved skill metadata. */ export interface SkillsLoadedEvent { /** @@ -7886,7 +8102,7 @@ export interface SkillsLoadedEvent { type: "session.skills_loaded"; } /** - * Schema for the `SkillsLoadedData` type. + * Payload of `session.skills_loaded` listing resolved skill metadata. */ export interface SkillsLoadedData { /** @@ -7895,7 +8111,7 @@ export interface SkillsLoadedData { skills: SkillsLoadedSkill[]; } /** - * Schema for the `SkillsLoadedSkill` type. + * A single resolved skill in `session.skills_loaded`, including source, invocability, enabled state, path, and argument hint. */ export interface SkillsLoadedSkill { /** @@ -7925,7 +8141,7 @@ export interface SkillsLoadedSkill { userInvocable: boolean; } /** - * Session event "session.custom_agents_updated". + * Session event "session.custom_agents_updated". Payload of `session.custom_agents_updated` with loaded custom agents plus non-fatal warnings and fatal errors. */ export interface CustomAgentsUpdatedEvent { /** @@ -7955,7 +8171,7 @@ export interface CustomAgentsUpdatedEvent { type: "session.custom_agents_updated"; } /** - * Schema for the `CustomAgentsUpdatedData` type. + * Payload of `session.custom_agents_updated` with loaded custom agents plus non-fatal warnings and fatal errors. */ export interface CustomAgentsUpdatedData { /** @@ -7972,7 +8188,7 @@ export interface CustomAgentsUpdatedData { warnings: string[]; } /** - * Schema for the `CustomAgentsUpdatedAgent` type. + * A single loaded custom agent in `session.custom_agents_updated`, with identity, source, tools, invocability, and model override. */ export interface CustomAgentsUpdatedAgent { /** @@ -8009,7 +8225,7 @@ export interface CustomAgentsUpdatedAgent { userInvocable: boolean; } /** - * Session event "session.mcp_servers_loaded". + * Session event "session.mcp_servers_loaded". Payload of `session.mcp_servers_loaded` listing MCP server status summaries. */ export interface McpServersLoadedEvent { /** @@ -8039,7 +8255,7 @@ export interface McpServersLoadedEvent { type: "session.mcp_servers_loaded"; } /** - * Schema for the `McpServersLoadedData` type. + * Payload of `session.mcp_servers_loaded` listing MCP server status summaries. */ export interface McpServersLoadedData { /** @@ -8048,7 +8264,7 @@ export interface McpServersLoadedData { servers: McpServersLoadedServer[]; } /** - * Schema for the `McpServersLoadedServer` type. + * A single MCP server status summary in `session.mcp_servers_loaded`, including name, status, source, transport, and plugin metadata. */ export interface McpServersLoadedServer { /** @@ -8072,7 +8288,7 @@ export interface McpServersLoadedServer { transport?: McpServerTransport; } /** - * Session event "session.mcp_server_status_changed". + * Session event "session.mcp_server_status_changed". Payload of `session.mcp_server_status_changed` for one MCP server's status and optional failure error. */ export interface McpServerStatusChangedEvent { /** @@ -8102,7 +8318,7 @@ export interface McpServerStatusChangedEvent { type: "session.mcp_server_status_changed"; } /** - * Schema for the `McpServerStatusChangedData` type. + * Payload of `session.mcp_server_status_changed` for one MCP server's status and optional failure error. */ export interface McpServerStatusChangedData { /** @@ -8116,7 +8332,7 @@ export interface McpServerStatusChangedData { status: McpServerStatus; } /** - * Session event "session.extensions_loaded". + * Session event "session.extensions_loaded". Payload of `session.extensions_loaded` listing discovered extensions and their statuses. */ export interface ExtensionsLoadedEvent { /** @@ -8146,7 +8362,7 @@ export interface ExtensionsLoadedEvent { type: "session.extensions_loaded"; } /** - * Schema for the `ExtensionsLoadedData` type. + * Payload of `session.extensions_loaded` listing discovered extensions and their statuses. */ export interface ExtensionsLoadedData { /** @@ -8155,7 +8371,7 @@ export interface ExtensionsLoadedData { extensions: ExtensionsLoadedExtension[]; } /** - * Schema for the `ExtensionsLoadedExtension` type. + * A single extension discovered by `session.extensions_loaded`, including qualified ID, source, and current status. */ export interface ExtensionsLoadedExtension { /** @@ -8170,7 +8386,7 @@ export interface ExtensionsLoadedExtension { status: ExtensionsLoadedExtensionStatus; } /** - * Session event "session.canvas.opened". + * Session event "session.canvas.opened". Payload of `session.canvas.opened` with canvas instance and provider IDs plus optional title, status, URL, and input. */ /** @experimental */ export interface CanvasOpenedEvent { @@ -8201,7 +8417,7 @@ export interface CanvasOpenedEvent { type: "session.canvas.opened"; } /** - * Schema for the `CanvasOpenedData` type. + * Payload of `session.canvas.opened` with canvas instance and provider IDs plus optional title, status, URL, and input. */ /** @experimental */ export interface CanvasOpenedData { @@ -8241,7 +8457,7 @@ export interface CanvasOpenedData { url?: string; } /** - * Session event "session.canvas.registry_changed". + * Session event "session.canvas.registry_changed". Payload of `session.canvas.registry_changed` listing the canvas declarations currently available. */ /** @experimental */ export interface CanvasRegistryChangedEvent { @@ -8272,7 +8488,7 @@ export interface CanvasRegistryChangedEvent { type: "session.canvas.registry_changed"; } /** - * Schema for the `CanvasRegistryChangedData` type. + * Payload of `session.canvas.registry_changed` listing the canvas declarations currently available. */ /** @experimental */ export interface CanvasRegistryChangedData { @@ -8282,7 +8498,7 @@ export interface CanvasRegistryChangedData { canvases: CanvasRegistryChangedCanvas[]; } /** - * Schema for the `CanvasRegistryChangedCanvas` type. + * A single canvas declaration in `session.canvas.registry_changed`, including provider IDs, display metadata, input schema, and actions. */ /** @experimental */ export interface CanvasRegistryChangedCanvas { @@ -8318,7 +8534,7 @@ export interface CanvasRegistryChangedCanvas { }; } /** - * Schema for the `CanvasRegistryChangedCanvasAction` type. + * A single action within a canvas declaration, with its name, optional description, and optional input schema. */ /** @experimental */ export interface CanvasRegistryChangedCanvasAction { @@ -8338,7 +8554,7 @@ export interface CanvasRegistryChangedCanvasAction { name: string; } /** - * Session event "session.canvas.closed". + * Session event "session.canvas.closed". Payload of `session.canvas.closed` with the closed canvas instance ID, provider ID, and canvas ID. */ /** @experimental */ export interface CanvasClosedEvent { @@ -8369,7 +8585,7 @@ export interface CanvasClosedEvent { type: "session.canvas.closed"; } /** - * Schema for the `CanvasClosedData` type. + * Payload of `session.canvas.closed` with the closed canvas instance ID, provider ID, and canvas ID. */ /** @experimental */ export interface CanvasClosedData { @@ -8544,7 +8760,7 @@ export interface CanvasRemovedData { instanceId: string; } /** - * Session event "session.extensions.attachments_pushed". + * Session event "session.extensions.attachments_pushed". Payload of `session.extensions.attachments_pushed` with extension-contributed attachments for the next send. */ export interface ExtensionsAttachmentsPushedEvent { /** @@ -8574,7 +8790,7 @@ export interface ExtensionsAttachmentsPushedEvent { type: "session.extensions.attachments_pushed"; } /** - * Schema for the `ExtensionsAttachmentsPushedData` type. + * Payload of `session.extensions.attachments_pushed` with extension-contributed attachments for the next send. */ export interface ExtensionsAttachmentsPushedData { /** @@ -8663,7 +8879,7 @@ export interface McpAppToolCallCompleteToolMeta { ui?: McpAppToolCallCompleteToolMetaUI; } /** - * Schema for the `McpAppToolCallCompleteToolMetaUI` type. + * MCP App tool `_meta.ui` resource URI and SEP-1865 visibility captured with an `mcp_app.tool_call_complete` result. */ export interface McpAppToolCallCompleteToolMetaUI { /** diff --git a/nodejs/src/index.ts b/nodejs/src/index.ts index eebf9add5e..e05b33c158 100644 --- a/nodejs/src/index.ts +++ b/nodejs/src/index.ts @@ -76,6 +76,9 @@ export type { ForegroundSessionInfo, GetAuthStatusResponse, GetStatusResponse, + GitHubTelemetryNotification, + GitHubTelemetryEvent, + GitHubTelemetryClientInfo, InfiniteSessionConfig, LargeToolOutputConfig, MemoryConfiguration, diff --git a/nodejs/src/types.ts b/nodejs/src/types.ts index 898ca02569..97182b5f1c 100644 --- a/nodejs/src/types.ts +++ b/nodejs/src/types.ts @@ -17,12 +17,18 @@ import type { } from "./generated/session-events.js"; import type { CopilotSession } from "./session.js"; import type { + GitHubTelemetryNotification, ModelBillingTokenPrices, OpenCanvasInstance, RemoteSessionMode, } from "./generated/rpc.js"; import type { ToolSet } from "./toolSet.js"; export type { RemoteSessionMode } from "./generated/rpc.js"; +export type { + GitHubTelemetryNotification, + GitHubTelemetryEvent, + GitHubTelemetryClientInfo, +} from "./generated/rpc.js"; export type { ModelBillingTokenPrices, ModelBillingTokenPricesLongContext, @@ -339,6 +345,18 @@ export interface CopilotClientOptions { */ requestHandler?: CopilotRequestHandler; + /** + * Experimental. Receives GitHub telemetry events the runtime forwards to + * this connection. When set, the client opts each session it creates or + * resumes into telemetry forwarding and dispatches each + * `gitHubTelemetry.event` notification to this connection-global handler; + * each {@link GitHubTelemetryNotification} carries its originating + * `sessionId`. + * + * @experimental + */ + onGitHubTelemetry?: (notification: GitHubTelemetryNotification) => void | Promise; + /** * Server-wide idle timeout for sessions in seconds. * Sessions without activity for this duration are automatically cleaned up. diff --git a/nodejs/test/client.test.ts b/nodejs/test/client.test.ts index 8c52512fd9..96c32a5951 100644 --- a/nodejs/test/client.test.ts +++ b/nodejs/test/client.test.ts @@ -7,6 +7,7 @@ import { CopilotClient, createCanvas, RuntimeConnection, + type GitHubTelemetryNotification, type ModelInfo, } from "../src/index.js"; import { CopilotSession } from "../src/session.js"; @@ -461,6 +462,171 @@ describe("CopilotClient", () => { expect(resumePayload.sessionLimits).toEqual({ maxAiCredits: 15 }); }); + it("opts into GitHub telemetry forwarding when onGitHubTelemetry is provided", async () => { + const client = new CopilotClient({ onGitHubTelemetry: () => {} }); + await client.start(); + onTestFinished(() => client.forceStop()); + + const spy = vi + .spyOn((client as any).connection!, "sendRequest") + .mockImplementation(async (method: string, params: any) => { + if (method === "session.create") return { sessionId: params.sessionId }; + if (method === "session.resume") return { sessionId: params.sessionId }; + throw new Error(`Unexpected method: ${method}`); + }); + + const session = await client.createSession({ onPermissionRequest: approveAll }); + await client.resumeSession(session.sessionId, { onPermissionRequest: approveAll }); + + const createPayload = spy.mock.calls.find( + ([method]) => method === "session.create" + )![1] as any; + const resumePayload = spy.mock.calls.find( + ([method]) => method === "session.resume" + )![1] as any; + expect(createPayload.enableGitHubTelemetryForwarding).toBe(true); + expect(resumePayload.enableGitHubTelemetryForwarding).toBe(true); + }); + + it("opts into GitHub telemetry forwarding on the connect handshake when a handler is provided", async () => { + const client = new CopilotClient({ onGitHubTelemetry: () => {} }); + onTestFinished(() => client.forceStop()); + + const sendRequest = vi.fn(async (method: string) => { + if (method === "connect") return { ok: true, protocolVersion: 3, version: "test" }; + throw new Error(`Unexpected method: ${method}`); + }); + (client as any).connection = { sendRequest }; + + await (client as any).verifyProtocolVersion(); + + const connectCall = sendRequest.mock.calls.find(([method]) => method === "connect"); + expect(connectCall).toBeDefined(); + expect((connectCall![1] as any).enableGitHubTelemetryForwarding).toBe(true); + }); + + it("does not opt into GitHub telemetry forwarding on the connect handshake without a handler", async () => { + const client = new CopilotClient(); + onTestFinished(() => client.forceStop()); + + const sendRequest = vi.fn(async (method: string) => { + if (method === "connect") return { ok: true, protocolVersion: 3, version: "test" }; + throw new Error(`Unexpected method: ${method}`); + }); + (client as any).connection = { sendRequest }; + + await (client as any).verifyProtocolVersion(); + + const connectCall = sendRequest.mock.calls.find(([method]) => method === "connect"); + expect(connectCall).toBeDefined(); + expect((connectCall![1] as any).enableGitHubTelemetryForwarding).toBeUndefined(); + }); + + it("does not opt into GitHub telemetry forwarding without a handler", async () => { + const client = new CopilotClient(); + await client.start(); + onTestFinished(() => client.forceStop()); + + const spy = vi + .spyOn((client as any).connection!, "sendRequest") + .mockImplementation(async (method: string, params: any) => { + if (method === "session.create") return { sessionId: params.sessionId }; + throw new Error(`Unexpected method: ${method}`); + }); + + await client.createSession({ onPermissionRequest: approveAll }); + + const createPayload = spy.mock.calls.find( + ([method]) => method === "session.create" + )![1] as any; + expect(createPayload.enableGitHubTelemetryForwarding).toBeUndefined(); + }); + + it("dispatches a real gitHubTelemetry.event wire message to the handler", async () => { + const { createMessageConnection, StreamMessageReader, StreamMessageWriter } = + await import("vscode-jsonrpc/node.js"); + const { registerClientGlobalApiHandlers } = await import("../src/generated/rpc.js"); + + const clientToServer = new PassThrough(); + const serverToClient = new PassThrough(); + + const clientConn = createMessageConnection( + new StreamMessageReader(serverToClient), + new StreamMessageWriter(clientToServer) + ); + const serverConn = createMessageConnection( + new StreamMessageReader(clientToServer), + new StreamMessageWriter(serverToClient) + ); + onTestFinished(() => { + clientConn.dispose(); + serverConn.dispose(); + }); + + const received: GitHubTelemetryNotification[] = []; + let resolveReceived: () => void; + const got = new Promise((resolve) => { + resolveReceived = resolve; + }); + + registerClientGlobalApiHandlers(clientConn, { + gitHubTelemetry: { + event: async (notification) => { + received.push(notification); + resolveReceived(); + }, + }, + }); + + clientConn.listen(); + serverConn.listen(); + + const notification: GitHubTelemetryNotification = { + sessionId: "session-1", + restricted: false, + event: { + kind: "tool_call_executed", + properties: { tool: "shell" }, + metrics: { duration_ms: 42 }, + }, + }; + + // Deliver the event as a real JSON-RPC *notification* (no id) and confirm + // the generated dispatcher routes it to the registered handler. The runtime + // forwards telemetry via `sendNotification`, which only fires `onNotification` + // handlers — an `onRequest` registration would never be invoked, so sending a + // notification here guards against regressing back to request-style dispatch. + serverConn.sendNotification("gitHubTelemetry.event", notification); + await got; + + expect(received).toEqual([notification]); + }); + + it("registers no gitHubTelemetry handler when onGitHubTelemetry is omitted", () => { + const client = new CopilotClient(); + onTestFinished(() => client.forceStop()); + + const handlers = (client as any).clientGlobalHandlers; + expect(handlers.gitHubTelemetry).toBeUndefined(); + }); + + it("forwards gitHubTelemetry events to the onGitHubTelemetry handler", () => { + const received: GitHubTelemetryNotification[] = []; + const client = new CopilotClient({ onGitHubTelemetry: (n) => received.push(n) }); + onTestFinished(() => client.forceStop()); + + const handlers = (client as any).clientGlobalHandlers; + expect(handlers.gitHubTelemetry).toBeDefined(); + + const notification: GitHubTelemetryNotification = { + sessionId: "session-1", + restricted: false, + event: { kind: "tool_call_executed", properties: {}, metrics: {} }, + }; + handlers.gitHubTelemetry.event(notification); + expect(received).toEqual([notification]); + }); + it("forwards expAssignments in session.create and session.resume", async () => { const client = new CopilotClient(); await client.start(); diff --git a/nodejs/test/e2e/client_options.e2e.test.ts b/nodejs/test/e2e/client_options.e2e.test.ts index 2cfc69456f..2910b6fb9c 100644 --- a/nodejs/test/e2e/client_options.e2e.test.ts +++ b/nodejs/test/e2e/client_options.e2e.test.ts @@ -6,8 +6,8 @@ import * as fs from "fs"; import * as net from "net"; import * as path from "path"; import { describe, expect, it, onTestFinished } from "vitest"; -import { approveAll, CopilotClient, RuntimeConnection } from "../../src/index.js"; -import { createSdkTestContext } from "./harness/sdkTestContext.js"; +import { approveAll, CopilotClient, createCanvas, RuntimeConnection } from "../../src/index.js"; +import { createSdkTestContext, DEFAULT_GITHUB_TOKEN } from "./harness/sdkTestContext.js"; const FAKE_STDIO_CLI_SCRIPT = `const fs = require("fs"); @@ -99,6 +99,17 @@ function handleMessage(message) { return; } + if (message.method === "session.resume") { + const sessionId = message.params?.sessionId ?? message.params?.[0]?.sessionId ?? "fake-session"; + writeResponse(message.id, { + sessionId, + workspacePath: null, + capabilities: null, + openCanvases: message.params?.openCanvases ?? [] + }); + return; + } + writeResponse(message.id, {}); } @@ -138,6 +149,27 @@ function assertArgumentValue( expect(args[index + 1]).toBe(expectedValue); } +function getCapturedRequest(capturePath: string, method: string): Record { + const raw = fs.readFileSync(capturePath, "utf8"); + const capture = JSON.parse(raw) as { + requests: { method: string; params: Record }[]; + }; + const request = capture.requests.find((r) => r.method === method); + expect(request, `Expected ${method} request in capture`).toBeDefined(); + return request!.params; +} + +function getObject(value: unknown): Record { + expect(value).toBeTypeOf("object"); + expect(value).not.toBeNull(); + return value as Record; +} + +function getArray(value: unknown): unknown[] { + expect(Array.isArray(value)).toBe(true); + return value as unknown[]; +} + describe("Client options", async () => { const { copilotClient: defaultClient, env, workDir } = await createSdkTestContext(); @@ -146,6 +178,7 @@ describe("Client options", async () => { workingDirectory: workDir, env, connection: RuntimeConnection.forStdio({ path: process.env.COPILOT_CLI_PATH }), + gitHubToken: DEFAULT_GITHUB_TOKEN, }); onTestFinished(async () => { try { @@ -200,7 +233,7 @@ describe("Client options", async () => { workingDirectory: clientCwd, env, connection: RuntimeConnection.forStdio({ path: process.env.COPILOT_CLI_PATH }), - gitHubToken: process.env.CI ? "fake-token-for-e2e-tests" : undefined, + gitHubToken: DEFAULT_GITHUB_TOKEN, }); onTestFinished(async () => { try { @@ -318,6 +351,315 @@ describe("Client options", async () => { await session.disconnect(); }); + it("should forward advanced session options in create wire request", async () => { + const cliPath = path.join( + workDir, + `fake-cli-advanced-create-${Date.now()}-${Math.random().toString(36).slice(2)}.js` + ); + const capturePath = path.join( + workDir, + `fake-cli-advanced-create-capture-${Date.now()}-${Math.random().toString(36).slice(2)}.json` + ); + const outputDirectory = path.join(workDir, "large-output-create"); + fs.writeFileSync(cliPath, FAKE_STDIO_CLI_SCRIPT); + + const client = new CopilotClient({ + workingDirectory: workDir, + env, + connection: RuntimeConnection.forStdio({ + path: cliPath, + args: ["--capture-file", capturePath], + }), + useLoggedInUser: false, + }); + onTestFinished(async () => { + try { + await client.forceStop(); + } catch { + // Ignore cleanup errors + } + }); + + await client.start(); + + const canvas = createCanvas({ + id: "advanced-create-canvas", + displayName: "Advanced Create Canvas", + description: "Covers create-time canvas options.", + open: () => ({ url: "https://example.test/advanced-create-canvas" }), + }); + const session = await client.createSession({ + clientName: "advanced-create-client", + model: "claude-sonnet-4.5", + reasoningEffort: "medium", + reasoningSummary: "detailed", + contextTier: "long_context", + enableCitations: true, + capi: { enableWebSocketResponses: false }, + mcpOAuthTokenStorage: "persistent", + customAgents: [ + { + name: "agent-one", + displayName: "Agent One", + description: "Handles agent-one tasks.", + prompt: "Be agent one.", + tools: ["view"], + infer: true, + skills: ["create-skill"], + model: "claude-haiku-4.5", + }, + ], + defaultAgent: { excludedTools: ["edit"] }, + agent: "agent-one", + skillDirectories: ["skills-create"], + disabledSkills: ["disabled-create-skill"], + pluginDirectories: ["plugins-create"], + infiniteSessions: { + enabled: false, + backgroundCompactionThreshold: 0.5, + bufferExhaustionThreshold: 0.9, + }, + largeOutput: { + enabled: true, + maxSizeBytes: 4096, + outputDirectory, + }, + memory: { enabled: true }, + gitHubToken: "session-create-token", + remoteSession: "export", + cloud: { + repository: { + owner: "github", + name: "copilot-sdk", + branch: "main", + }, + }, + enableMcpApps: true, + requestCanvasRenderer: true, + requestExtensions: true, + extensionSdkPath: "custom-extension-sdk", + extensionInfo: { source: "typescript-sdk-tests", name: "advanced-create-extension" }, + canvases: [canvas], + providers: [ + { + name: "create-provider", + type: "openai", + wireApi: "responses", + baseUrl: "https://create-provider.example.test/v1", + apiKey: "create-provider-key", + headers: { "X-Create-Provider": "yes" }, + }, + ], + models: [ + { + provider: "create-provider", + id: "create-model", + name: "Create Model", + modelId: "claude-sonnet-4.5", + wireModel: "create-wire-model", + maxContextWindowTokens: 12_000, + maxPromptTokens: 10_000, + maxOutputTokens: 2_000, + }, + ], + onPermissionRequest: approveAll, + }); + + const createRequest = getCapturedRequest(capturePath, "session.create"); + expect(createRequest.clientName).toBe("advanced-create-client"); + expect(createRequest.model).toBe("claude-sonnet-4.5"); + expect(createRequest.reasoningEffort).toBe("medium"); + expect(createRequest.reasoningSummary).toBe("detailed"); + expect(createRequest.contextTier).toBe("long_context"); + expect(createRequest.enableCitations).toBe(true); + expect(getObject(createRequest.capi).enableWebSocketResponses).toBe(false); + expect(createRequest.mcpOAuthTokenStorage).toBe("persistent"); + expect(createRequest.agent).toBe("agent-one"); + expect(getArray(getObject(createRequest.defaultAgent).excludedTools)[0]).toBe("edit"); + expect(getObject(getArray(createRequest.customAgents)[0]).name).toBe("agent-one"); + expect(getArray(createRequest.pluginDirectories)[0]).toBe("plugins-create"); + expect(getArray(createRequest.disabledSkills)[0]).toBe("disabled-create-skill"); + expect(getObject(createRequest.infiniteSessions).enabled).toBe(false); + expect(getObject(createRequest.largeOutput).enabled).toBe(true); + expect(getObject(createRequest.largeOutput).maxSizeBytes).toBe(4096); + expect(getObject(createRequest.largeOutput).outputDir).toBe(outputDirectory); + expect(getObject(createRequest.memory).enabled).toBe(true); + expect(createRequest.gitHubToken).toBe("session-create-token"); + expect(createRequest.remoteSession).toBe("export"); + expect(getObject(getObject(createRequest.cloud).repository).owner).toBe("github"); + expect(createRequest.requestMcpApps).toBe(true); + expect(createRequest.requestCanvasRenderer).toBe(true); + expect(createRequest.requestExtensions).toBe(true); + expect(createRequest.extensionSdkPath).toBe("custom-extension-sdk"); + expect(getObject(createRequest.extensionInfo).name).toBe("advanced-create-extension"); + expect(getObject(getArray(createRequest.canvases)[0]).id).toBe("advanced-create-canvas"); + expect(getObject(getArray(createRequest.providers)[0]).name).toBe("create-provider"); + expect(getObject(getArray(createRequest.providers)[0]).wireApi).toBe("responses"); + expect(getObject(getArray(createRequest.models)[0]).id).toBe("create-model"); + expect(getObject(getArray(createRequest.models)[0]).maxContextWindowTokens).toBe(12_000); + + await session.disconnect(); + }); + + it("should forward singular provider options in create wire request", async () => { + const cliPath = path.join( + workDir, + `fake-cli-provider-create-${Date.now()}-${Math.random().toString(36).slice(2)}.js` + ); + const capturePath = path.join( + workDir, + `fake-cli-provider-create-capture-${Date.now()}-${Math.random().toString(36).slice(2)}.json` + ); + fs.writeFileSync(cliPath, FAKE_STDIO_CLI_SCRIPT); + + const client = new CopilotClient({ + workingDirectory: workDir, + env, + connection: RuntimeConnection.forStdio({ + path: cliPath, + args: ["--capture-file", capturePath], + }), + useLoggedInUser: false, + }); + onTestFinished(async () => { + try { + await client.forceStop(); + } catch { + // Ignore cleanup errors + } + }); + + await client.start(); + + const session = await client.createSession({ + model: "claude-sonnet-4.5", + provider: { + type: "azure", + wireApi: "responses", + transport: "http", + baseUrl: "https://azure-provider.example.test/openai", + apiKey: "provider-api-key", + bearerToken: "provider-bearer-token", + azure: { apiVersion: "2024-02-15-preview" }, + headers: { "X-Provider-Wire": "yes" }, + modelId: "claude-sonnet-4.5", + wireModel: "azure-deployment", + maxPromptTokens: 8192, + maxOutputTokens: 1024, + }, + onPermissionRequest: approveAll, + }); + + const provider = getObject(getCapturedRequest(capturePath, "session.create").provider); + expect(provider.type).toBe("azure"); + expect(provider.wireApi).toBe("responses"); + expect(provider.transport).toBe("http"); + expect(provider.baseUrl).toBe("https://azure-provider.example.test/openai"); + expect(provider.apiKey).toBe("provider-api-key"); + expect(provider.bearerToken).toBe("provider-bearer-token"); + expect(getObject(provider.azure).apiVersion).toBe("2024-02-15-preview"); + expect(getObject(provider.headers)["X-Provider-Wire"]).toBe("yes"); + expect(provider.modelId).toBe("claude-sonnet-4.5"); + expect(provider.wireModel).toBe("azure-deployment"); + expect(provider.maxPromptTokens).toBe(8192); + expect(provider.maxOutputTokens).toBe(1024); + + await session.disconnect(); + }); + + it("should forward advanced session options in resume wire request", async () => { + const cliPath = path.join( + workDir, + `fake-cli-advanced-resume-${Date.now()}-${Math.random().toString(36).slice(2)}.js` + ); + const capturePath = path.join( + workDir, + `fake-cli-advanced-resume-capture-${Date.now()}-${Math.random().toString(36).slice(2)}.json` + ); + const outputDirectory = path.join(workDir, "large-output-resume"); + fs.writeFileSync(cliPath, FAKE_STDIO_CLI_SCRIPT); + + const client = new CopilotClient({ + workingDirectory: workDir, + env, + connection: RuntimeConnection.forStdio({ + path: cliPath, + args: ["--capture-file", capturePath], + }), + useLoggedInUser: false, + }); + onTestFinished(async () => { + try { + await client.forceStop(); + } catch { + // Ignore cleanup errors + } + }); + + await client.start(); + + const session = await client.resumeSession("advanced-resume-session", { + clientName: "advanced-resume-client", + model: "claude-haiku-4.5", + reasoningEffort: "low", + reasoningSummary: "none", + contextTier: "default", + suppressResumeEvent: true, + continuePendingWork: true, + mcpOAuthTokenStorage: "persistent", + pluginDirectories: ["plugins-resume"], + largeOutput: { + enabled: false, + maxSizeBytes: 2048, + outputDirectory, + }, + memory: { enabled: false }, + remoteSession: "on", + openCanvases: [ + { + canvasId: "resume-canvas", + extensionId: "typescript-sdk-tests/resume-extension", + extensionName: "Resume Extension", + instanceId: "resume-canvas-1", + input: { start: 41 }, + status: "ready", + title: "Resume Canvas", + url: "https://example.com/resume-canvas", + }, + ], + onPermissionRequest: approveAll, + }); + + const resumeRequest = getCapturedRequest(capturePath, "session.resume"); + expect(resumeRequest.sessionId).toBe("advanced-resume-session"); + expect(resumeRequest.clientName).toBe("advanced-resume-client"); + expect(resumeRequest.model).toBe("claude-haiku-4.5"); + expect(resumeRequest.reasoningEffort).toBe("low"); + expect(resumeRequest.reasoningSummary).toBe("none"); + expect(resumeRequest.contextTier).toBe("default"); + expect(resumeRequest.disableResume).toBe(true); + expect(resumeRequest.continuePendingWork).toBe(true); + expect(resumeRequest.mcpOAuthTokenStorage).toBe("persistent"); + expect(getArray(resumeRequest.pluginDirectories)[0]).toBe("plugins-resume"); + expect(getObject(resumeRequest.largeOutput).enabled).toBe(false); + expect(getObject(resumeRequest.largeOutput).maxSizeBytes).toBe(2048); + expect(getObject(resumeRequest.largeOutput).outputDir).toBe(outputDirectory); + expect(getObject(resumeRequest.memory).enabled).toBe(false); + expect(resumeRequest.remoteSession).toBe("on"); + + const openCanvas = getObject(getArray(resumeRequest.openCanvases)[0]); + expect(openCanvas.canvasId).toBe("resume-canvas"); + expect(openCanvas.extensionId).toBe("typescript-sdk-tests/resume-extension"); + expect(openCanvas.extensionName).toBe("Resume Extension"); + expect(openCanvas.instanceId).toBe("resume-canvas-1"); + expect(getObject(openCanvas.input).start).toBe(41); + expect(openCanvas.status).toBe("ready"); + expect(openCanvas.title).toBe("Resume Canvas"); + expect(openCanvas.url).toBe("https://example.com/resume-canvas"); + + await session.disconnect(); + }); + it("should throw when gitHubToken used with forUri", () => { expect(() => { new CopilotClient({ diff --git a/nodejs/test/e2e/github_telemetry.e2e.test.ts b/nodejs/test/e2e/github_telemetry.e2e.test.ts new file mode 100644 index 0000000000..e33178f9d0 --- /dev/null +++ b/nodejs/test/e2e/github_telemetry.e2e.test.ts @@ -0,0 +1,57 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +import { describe, expect, it } from "vitest"; +import { approveAll, GitHubTelemetryNotification } from "../../src/index.js"; +import { createSdkTestContext } from "./harness/sdkTestContext.js"; +import { waitForCondition } from "./harness/sdkTestHelper.js"; + +// Experimental: exercises the end-to-end GitHub (hydro) telemetry forwarding +// path. The runtime forwards per-session telemetry to opted-in connections via +// the `gitHubTelemetry.event` JSON-RPC *notification*; the SDK opts in +// automatically whenever an `onGitHubTelemetry` handler is registered. Creating +// a session emits an early `session.start` hydro event, so no model round-trip +// (and therefore no recorded CAPI exchange) is needed to observe forwarding. +describe("GitHub telemetry forwarding", async () => { + const received: GitHubTelemetryNotification[] = []; + + const { copilotClient: client } = await createSdkTestContext({ + copilotClientOptions: { + onGitHubTelemetry: (notification) => { + received.push(notification); + }, + }, + }); + + it( + "forwards gitHubTelemetry.event notifications from a live session", + { timeout: 60_000 }, + async () => { + received.length = 0; + + const session = await client.createSession({ + onPermissionRequest: approveAll, + }); + + // The CLI forwards telemetry over the JSON-RPC connection + // asynchronously, so wait until at least one event arrives or we + // time out. + await waitForCondition(() => received.length > 0, { + timeoutMs: 30_000, + timeoutMessage: "Timed out waiting for a gitHubTelemetry.event notification.", + }); + + expect(received.length).toBeGreaterThan(0); + + const notification = received[0]; + expect(typeof notification.sessionId).toBe("string"); + expect(notification.sessionId.length).toBeGreaterThan(0); + expect(typeof notification.restricted).toBe("boolean"); + expect(notification.event).toBeDefined(); + expect(typeof notification.event.kind).toBe("string"); + + await session.disconnect(); + } + ); +}); diff --git a/nodejs/test/e2e/mcp_oauth.e2e.test.ts b/nodejs/test/e2e/mcp_oauth.e2e.test.ts index 29ed089edb..0556e857fc 100644 --- a/nodejs/test/e2e/mcp_oauth.e2e.test.ts +++ b/nodejs/test/e2e/mcp_oauth.e2e.test.ts @@ -89,6 +89,73 @@ describe("MCP OAuth host auth", async () => { ).toBe(true); }); + it( + "should resolve pending MCP OAuth request with direct RPC", + { timeout: 120_000 }, + async () => { + const oauthServer = await startOAuthMcpServer(); + const serverName = "oauth-direct-rpc-mcp"; + let resolveAuthRequest!: (request: McpAuthRequest) => void; + const authRequest = new Promise((resolve) => { + resolveAuthRequest = resolve; + }); + let releaseHandler!: (value: unknown) => void; + const handlerResult = new Promise((resolve) => { + releaseHandler = resolve; + }); + + const session = await client.createSession({ + onPermissionRequest: approveAll, + enableMcpApps: true, + onMcpAuthRequest: async (request) => { + resolveAuthRequest(request); + await handlerResult; + return { kind: "token", accessToken: EXPECTED_TOKEN }; + }, + mcpServers: { + [serverName]: { + type: "http", + url: `${oauthServer.url}/mcp`, + tools: ["*"], + oauthClientId: "sdk-e2e-client", + oauthPublicClient: true, + } as unknown as MCPServerConfig, + }, + }); + onTestFinished(() => disconnectSession(session)); + + const connected = waitForMcpServerStatus(session, serverName); + const request = await authRequest; + expect(request).toMatchObject({ + requestId: expect.any(String), + serverName, + serverUrl: `${oauthServer.url}/mcp`, + reason: "initial", + wwwAuthenticateParams: { + resourceMetadataUrl: `${oauthServer.url}/.well-known/oauth-protected-resource`, + scope: "mcp.read", + error: "invalid_token", + }, + }); + + const handled = await session.rpc.mcp.oauth.handlePendingRequest({ + requestId: request.requestId, + result: { + kind: "token", + accessToken: EXPECTED_TOKEN, + tokenType: "Bearer", + expiresIn: 3600, + }, + }); + expect(handled.success).toBe(true); + + await connected; + const tools = await session.rpc.mcp.listTools({ serverName }); + expect(tools.tools.map((tool) => tool.name)).toContain("whoami"); + releaseHandler(undefined); + } + ); + it( "should request host-owned replacement tokens across the MCP OAuth lifecycle", { timeout: 120_000 }, @@ -193,7 +260,7 @@ describe("MCP OAuth host auth", async () => { }); onTestFinished(() => disconnectSession(session)); - await waitForMcpServerStatus(session, serverName, "failed"); + await waitForMcpServerStatus(session, serverName, "needs-auth"); expect(authRequest).toMatchObject({ serverName, diff --git a/nodejs/test/e2e/pending_work_resume.e2e.test.ts b/nodejs/test/e2e/pending_work_resume.e2e.test.ts index 60bb2399e8..85abc3a900 100644 --- a/nodejs/test/e2e/pending_work_resume.e2e.test.ts +++ b/nodejs/test/e2e/pending_work_resume.e2e.test.ts @@ -57,6 +57,20 @@ async function waitWithTimeout( } } +async function waitForPendingPermissionRequestId(session: CopilotSession): Promise { + const deadline = Date.now() + PENDING_WORK_TIMEOUT_MS; + do { + const pending = await session.rpc.permissions.pendingRequests(); + const request = pending.items[0]; + if (request) { + return request.requestId; + } + await new Promise((resolve) => setTimeout(resolve, 100)); + } while (Date.now() < deadline); + + throw new Error("Timeout waiting for pending permission request"); +} + function waitForExternalToolRequests( session: CopilotSession, toolNames: string[] @@ -205,7 +219,7 @@ describe("Pending work resume", async () => { PENDING_WORK_TIMEOUT_MS, "originalPermissionRequest" ); - const permissionEvent = await permissionRequestedP; + await permissionRequestedP; expect(initialRequest.kind).toBe("custom-tool"); await suspendedClient.forceStop(); @@ -222,10 +236,11 @@ describe("Pending work resume", async () => { }), ], }); + const requestId = await waitForPendingPermissionRequestId(session2); const permissionResult = await session2.rpc.permissions.handlePendingPermissionRequest({ - requestId: permissionEvent.data.requestId, + requestId, result: { kind: "approve-once" }, }); expect(permissionResult.success).toBe(true); diff --git a/nodejs/test/e2e/rpc_server.e2e.test.ts b/nodejs/test/e2e/rpc_server.e2e.test.ts index 3d4b00c961..8913146b12 100644 --- a/nodejs/test/e2e/rpc_server.e2e.test.ts +++ b/nodejs/test/e2e/rpc_server.e2e.test.ts @@ -103,6 +103,39 @@ describe("Server-scoped RPC", async () => { expect(Date.parse(result.timestamp)).not.toBeNaN(); }); + it("should reject llm inference response frames for missing request", async () => { + await client.start(); + + const start = await client.rpc.llmInference.httpResponseStart({ + requestId: "missing-llm-inference-request", + status: 200, + headers: { + "content-type": ["text/event-stream"], + }, + statusText: "OK", + }); + expect(start.accepted).toBe(false); + + const chunk = await client.rpc.llmInference.httpResponseChunk({ + requestId: "missing-llm-inference-request", + data: "data: {}\n\n", + binary: false, + end: false, + }); + expect(chunk.accepted).toBe(false); + + const error = await client.rpc.llmInference.httpResponseChunk({ + requestId: "missing-llm-inference-request", + data: "", + end: true, + error: { + code: "missing_request", + message: "No pending LLM inference request.", + }, + }); + expect(error.accepted).toBe(false); + }); + it("should call rpc models list with typed result", async () => { const token = "rpc-models-token"; await configureAuthenticatedUser(token); @@ -401,6 +434,59 @@ describe("Server-scoped RPC", async () => { expect(discovered[0].enabled).toBe(true); expect(discovered[0].path.endsWith(path.join(skillName, "SKILL.md"))).toBe(true); + const skillPaths = await client.rpc.skills.getDiscoveryPaths({ + projectPaths: [workDir], + excludeHostSkills: true, + }); + const projectSkillPath = skillPaths.paths.find( + (p) => p.projectPath && pathsEqual(p.projectPath, workDir) && p.preferredForCreation + ); + if (!projectSkillPath) { + throw new Error(`Expected skill discovery paths to include ${workDir}`); + } + expect(projectSkillPath.path.trim()).not.toBe(""); + + const agents = await client.rpc.agents.discover({ + projectPaths: [workDir], + excludeHostAgents: true, + }); + expect(agents.agents.every((agent) => agent.name.trim() !== "")).toBe(true); + + const agentPaths = await client.rpc.agents.getDiscoveryPaths({ + projectPaths: [workDir], + excludeHostAgents: true, + }); + const projectAgentPath = agentPaths.paths.find( + (p) => p.projectPath && pathsEqual(p.projectPath, workDir) && p.preferredForCreation + ); + if (!projectAgentPath) { + throw new Error(`Expected agent discovery paths to include ${workDir}`); + } + expect(projectAgentPath.path.trim()).not.toBe(""); + + const instructions = await client.rpc.instructions.discover({ + projectPaths: [workDir], + excludeHostInstructions: true, + }); + expect( + instructions.sources.every( + (source) => + source.id.trim() !== "" && + source.label.trim() !== "" && + source.sourcePath.trim() !== "" + ) + ).toBe(true); + + const instructionPaths = await client.rpc.instructions.getDiscoveryPaths({ + projectPaths: [workDir], + excludeHostInstructions: true, + }); + expect(instructionPaths.paths.length).toBeGreaterThan(0); + expect( + instructionPaths.paths.some((p) => p.projectPath && pathsEqual(p.projectPath, workDir)) + ).toBe(true); + expect(instructionPaths.paths.every((p) => p.path.trim() !== "")).toBe(true); + try { await client.rpc.skills.config.setDisabledSkills({ disabledSkills: [skillName] }); const disabled = await client.rpc.skills.discover({ diff --git a/nodejs/test/e2e/rpc_server_misc.e2e.test.ts b/nodejs/test/e2e/rpc_server_misc.e2e.test.ts index d358c7d9d1..c38fccca17 100644 --- a/nodejs/test/e2e/rpc_server_misc.e2e.test.ts +++ b/nodejs/test/e2e/rpc_server_misc.e2e.test.ts @@ -11,7 +11,7 @@ import { createSdkTestContext, DEFAULT_GITHUB_TOKEN } from "./harness/sdkTestCon import { formatError, waitForCondition } from "./harness/sdkTestHelper.js"; describe("Miscellaneous server-scoped RPC", async () => { - const { copilotClient: client, env, workDir } = await createSdkTestContext(); + const { copilotClient: client, env, openAiEndpoint, workDir } = await createSdkTestContext(); function createUniqueDirectory(prefix: string): string { const directory = join(workDir, `${prefix}-${randomUUID()}`); @@ -19,7 +19,10 @@ describe("Miscellaneous server-scoped RPC", async () => { return directory; } - function createClient(extraEnv: Record = {}): CopilotClient { + function createClient( + extraEnv: Record, + gitHubToken: string | undefined + ): CopilotClient { return new CopilotClient({ workingDirectory: workDir, env: { @@ -28,21 +31,29 @@ describe("Miscellaneous server-scoped RPC", async () => { }, logLevel: "error", connection: RuntimeConnection.forStdio({ path: process.env.COPILOT_CLI_PATH }), - gitHubToken: DEFAULT_GITHUB_TOKEN, + gitHubToken, + useLoggedInUser: gitHubToken === undefined ? false : undefined, }); } - async function createIsolatedStartedClient(): Promise<{ + async function createIsolatedStartedClient( + gitHubToken: string | null = DEFAULT_GITHUB_TOKEN + ): Promise<{ client: CopilotClient; home: string; }> { const home = createUniqueDirectory("copilot-e2e-misc-home"); - const isolatedClient = createClient({ - COPILOT_HOME: home, - GH_CONFIG_DIR: home, - XDG_CONFIG_HOME: home, - XDG_STATE_HOME: home, - }); + const effectiveGitHubToken = gitHubToken === null ? undefined : gitHubToken; + const isolatedClient = createClient( + { + COPILOT_HOME: home, + GH_CONFIG_DIR: home, + XDG_CONFIG_HOME: home, + XDG_STATE_HOME: home, + COPILOT_DEBUG_GITHUB_API_URL: env.COPILOT_API_URL, + }, + effectiveGitHubToken + ); try { await isolatedClient.start(); return { client: isolatedClient, home }; @@ -83,6 +94,101 @@ describe("Miscellaneous server-scoped RPC", async () => { await client.rpc.user.settings.reload(); }); + it("should get set and clear user settings", { timeout: 120_000 }, async () => { + const { client: isolatedClient, home } = await createIsolatedStartedClient(); + try { + const before = await isolatedClient.rpc.user.settings.get(); + expect(Object.keys(before.settings).length).toBeGreaterThan(0); + for (const [key, setting] of Object.entries(before.settings)) { + expect(key.trim()).toBeTruthy(); + expect(setting.value !== undefined || setting.default !== undefined).toBe(true); + } + + const entry = Object.entries(before.settings).find( + ([, setting]) => typeof setting.value === "boolean" + ); + expect(entry).toBeDefined(); + const [settingKey, setting] = entry!; + const toggledValue = setting.value !== true; + + const set = await isolatedClient.rpc.user.settings.set({ + settings: { [settingKey]: toggledValue }, + }); + expect(set.shadowedKeys).not.toContain(settingKey); + + await isolatedClient.rpc.user.settings.reload(); + const afterSet = await isolatedClient.rpc.user.settings.get(); + expect(afterSet.settings[settingKey].isDefault).toBe(false); + expect(afterSet.settings[settingKey].value).toBe(toggledValue); + + await isolatedClient.rpc.user.settings.set({ + settings: { [settingKey]: null }, + }); + await isolatedClient.rpc.user.settings.reload(); + const afterClear = await isolatedClient.rpc.user.settings.get(); + expect(afterClear.settings[settingKey].isDefault).toBe(true); + } finally { + await disposeIsolated(isolatedClient, home); + } + }); + + it("should login list getCurrentAuth and logout account", { timeout: 120_000 }, async () => { + const login = `rpc-account-${randomUUID().replaceAll("-", "")}`; + const token = `rpc-account-token-${randomUUID().replaceAll("-", "")}`; + await openAiEndpoint.setCopilotUserByToken(token, { + login, + copilot_plan: "individual_pro", + endpoints: { + api: env.COPILOT_API_URL, + telemetry: "https://localhost:1/telemetry", + }, + analytics_tracking_id: "rpc-account-tracking-id", + }); + + const { client: isolatedClient, home } = await createIsolatedStartedClient(null); + try { + const initial = await isolatedClient.rpc.account.getCurrentAuth(); + expect(initial.authInfo).toBeUndefined(); + + const loginResult = await isolatedClient.rpc.account.login({ + host: "https://github.com", + login, + token, + }); + expect(typeof loginResult.storedInVault).toBe("boolean"); + + const current = await isolatedClient.rpc.account.getCurrentAuth(); + expect(current.authErrors).toBeUndefined(); + expect(current.authInfo).toMatchObject({ + type: "user", + host: "https://github.com", + login, + }); + + const users = await isolatedClient.rpc.account.getAllUsers(); + expect(Array.isArray(users)).toBe(true); + for (const user of users) { + expect(user.authInfo.type.trim()).toBeTruthy(); + } + const account = users.find( + (user) => user.authInfo.type === "user" && user.authInfo.login === login + ); + if (account) { + expect(account?.token).toBe(token); + } + + const logout = await isolatedClient.rpc.account.logout({ + authInfo: current.authInfo!, + }); + expect(logout.hasMoreUsers).toBe(false); + + const afterLogout = await isolatedClient.rpc.account.getCurrentAuth(); + expect(afterLogout.authInfo).toBeUndefined(); + } finally { + await disposeIsolated(isolatedClient, home); + } + }); + it("should report agent registry spawn gate closed", { timeout: 120_000 }, async () => { const { client: isolatedClient, home } = await createIsolatedStartedClient(); try { @@ -104,7 +210,7 @@ describe("Miscellaneous server-scoped RPC", async () => { }); it("should shut down owned runtime", { timeout: 120_000 }, async () => { - const dedicatedClient = createClient(); + const dedicatedClient = createClient({}, DEFAULT_GITHUB_TOKEN); try { await dedicatedClient.start(); await dedicatedClient.rpc.user.settings.reload(); diff --git a/nodejs/test/e2e/rpc_session_state_extras.e2e.test.ts b/nodejs/test/e2e/rpc_session_state_extras.e2e.test.ts index 4b0ff9ba17..6e84a6f669 100644 --- a/nodejs/test/e2e/rpc_session_state_extras.e2e.test.ts +++ b/nodejs/test/e2e/rpc_session_state_extras.e2e.test.ts @@ -103,6 +103,103 @@ describe("Session-scoped state extras RPC", async () => { } }); + it("should add byok provider and model at runtime", { timeout: 120_000 }, async () => { + const session = await createSession(); + try { + const providerName = `sdk-runtime-provider-${Date.now()}-${Math.random().toString(36).slice(2)}`; + const modelId = "sdk-runtime-model"; + const selectionId = `${providerName}/${modelId}`; + + const added = await session.rpc.provider.add({ + providers: [ + { + name: providerName, + type: "openai", + wireApi: "completions", + baseUrl: "https://api.example.test/v1", + apiKey: "runtime-provider-secret", + headers: { "X-SDK-Provider": "runtime" }, + }, + ], + models: [ + { + provider: providerName, + id: modelId, + name: "SDK Runtime Model", + modelId: "claude-sonnet-4.5", + wireModel: "wire-sdk-runtime-model", + maxContextWindowTokens: 4096, + maxPromptTokens: 3072, + maxOutputTokens: 1024, + capabilities: { + limits: { + maxContextWindowTokens: 4096, + maxPromptTokens: 3072, + maxOutputTokens: 1024, + }, + supports: { + reasoningEffort: false, + vision: false, + }, + }, + }, + ], + }); + + expect(added.models).toHaveLength(1); + expect(JSON.stringify(added.models[0])).toContain(selectionId); + expect(JSON.stringify(added.models[0])).toContain("SDK Runtime Model"); + + const listed = await session.rpc.model.list(); + expect(listed.list.some((model) => JSON.stringify(model).includes(selectionId))).toBe( + true + ); + + const switched = await session.rpc.model.switchTo({ modelId: selectionId }); + expect(switched.modelId).toBe(selectionId); + expect((await session.rpc.model.getCurrent()).modelId).toBe(selectionId); + } finally { + await session.disconnect(); + } + }); + + it( + "should return empty completions when host does not provide them", + { timeout: 120_000 }, + async () => { + const session = await createSession(); + try { + const triggers = await session.rpc.completions.getTriggerCharacters(); + expect(triggers.triggerCharacters).toEqual([]); + + const completions = await session.rpc.completions.request({ + text: "Use @", + offset: 5, + }); + expect(completions.items).toEqual([]); + } finally { + await session.disconnect(); + } + } + ); + + it("should report visibility as unsynced for local session", { timeout: 120_000 }, async () => { + const session = await createSession(); + try { + const initial = await session.rpc.visibility.get(); + expect(initial.synced).toBe(false); + expect(initial.status).toBeUndefined(); + expect(initial.shareUrl).toBeUndefined(); + + const set = await session.rpc.visibility.set({ status: "repo" }); + expect(set.synced).toBe(false); + expect(set.status).toBeUndefined(); + expect(set.shareUrl).toBeUndefined(); + } finally { + await session.disconnect(); + } + }); + it("should get and set allowall permissions", { timeout: 120_000 }, async () => { const session = await createSession(); try { @@ -128,6 +225,72 @@ describe("Session-scoped state extras RPC", async () => { } }); + it( + "should get context attribution and heaviest messages after turn", + { timeout: 120_000 }, + async () => { + const session = await createSession(); + try { + const answer = await session.sendAndWait({ + prompt: "Say CONTEXT_METADATA_OK exactly.", + }); + expect(answer?.data.content ?? "").toContain("CONTEXT_METADATA_OK"); + + const attribution = await session.rpc.metadata.getContextAttribution(); + expect(attribution.contextAttribution).not.toBeNull(); + const contextAttribution = attribution.contextAttribution!; + expect(contextAttribution.totalTokens).toBeGreaterThan(0); + expect(contextAttribution.entries.length).toBeGreaterThan(0); + for (const entry of contextAttribution.entries) { + expect(entry.id.trim()).toBeTruthy(); + expect(entry.kind.trim()).toBeTruthy(); + expect(entry.label.trim()).toBeTruthy(); + expect(entry.tokens).toBeGreaterThanOrEqual(0); + for (const attribute of entry.attributes ?? []) { + expect(attribute.key.trim()).toBeTruthy(); + } + } + + const heaviest = await session.rpc.metadata.getContextHeaviestMessages({ + limit: 2, + }); + expect(heaviest.totalTokens).toBeGreaterThan(0); + expect(heaviest.messages.length).toBeLessThanOrEqual(2); + for (const message of heaviest.messages) { + expect(message.id.trim()).toBeTruthy(); + expect(message.tokens).toBeGreaterThanOrEqual(0); + } + } finally { + await session.disconnect(); + } + } + ); + + it("should update and clear live subagent settings", { timeout: 120_000 }, async () => { + const session = await createSession(); + try { + await expect( + session.rpc.tools.updateSubagentSettings({ + subagents: { + "general-purpose": { + model: "claude-haiku-4.5", + effortLevel: "low", + contextTier: "default", + }, + }, + }) + ).resolves.toBeDefined(); + + await expect( + session.rpc.tools.updateSubagentSettings({ + subagents: null, + }) + ).resolves.toBeDefined(); + } finally { + await session.disconnect(); + } + }); + it("should read empty sql todos for fresh session", { timeout: 120_000 }, async () => { const session = await createSession(); try { diff --git a/nodejs/test/e2e/rpc_tasks_and_handlers.e2e.test.ts b/nodejs/test/e2e/rpc_tasks_and_handlers.e2e.test.ts index e7f7664c3c..cb41c69e68 100644 --- a/nodejs/test/e2e/rpc_tasks_and_handlers.e2e.test.ts +++ b/nodejs/test/e2e/rpc_tasks_and_handlers.e2e.test.ts @@ -173,6 +173,27 @@ describe("Session tasks RPC and pending handlers", async () => { }); expect(locationApproval.success).toBe(false); + const sessionLimits = await session.rpc.ui.handlePendingSessionLimitsExhausted({ + requestId: "missing-session-limits-request", + response: { action: "cancel" }, + }); + expect(sessionLimits.success).toBe(false); + + const headers = await session.rpc.mcp.headers.handlePendingHeadersRefreshRequest({ + requestId: "missing-headers-refresh-request", + result: { + kind: "headers", + headers: { "X-SDK-Test": "missing" }, + }, + }); + expect(headers.success).toBe(false); + + const noHeaders = await session.rpc.mcp.headers.handlePendingHeadersRefreshRequest({ + requestId: "missing-headers-refresh-none-request", + result: { kind: "none" }, + }); + expect(noHeaders.success).toBe(false); + await session.disconnect(); }); diff --git a/nodejs/test/e2e/session_config.e2e.test.ts b/nodejs/test/e2e/session_config.e2e.test.ts index 70ee6546e6..98b1a0bfaa 100644 --- a/nodejs/test/e2e/session_config.e2e.test.ts +++ b/nodejs/test/e2e/session_config.e2e.test.ts @@ -308,6 +308,59 @@ describe("Session Configuration", async () => { }); } + function sse(body: string): Response { + return new Response(body, { + status: 200, + headers: { "content-type": "text/event-stream" }, + }); + } + + function anthropicMessageStreamBody(text: string): string { + const events: Array<[string, unknown]> = [ + [ + "message_start", + { + type: "message_start", + message: { + id: "msg_stub_1", + type: "message", + role: "assistant", + model: "claude-sonnet-4.5", + content: [], + stop_reason: null, + stop_sequence: null, + usage: { input_tokens: 5, output_tokens: 1 }, + }, + }, + ], + [ + "content_block_start", + { + type: "content_block_start", + index: 0, + content_block: { type: "text", text: "" }, + }, + ], + [ + "content_block_delta", + { type: "content_block_delta", index: 0, delta: { type: "text_delta", text } }, + ], + ["content_block_stop", { type: "content_block_stop", index: 0 }], + [ + "message_delta", + { + type: "message_delta", + delta: { stop_reason: "end_turn", stop_sequence: null }, + usage: { output_tokens: 7 }, + }, + ], + ["message_stop", { type: "message_stop" }], + ]; + return events + .map(([event, data]) => `event: ${event}\ndata: ${JSON.stringify(data)}\n\n`) + .join(""); + } + function buildNonInferenceResponse(url: string): Response { const u = url.toLowerCase(); if (u.endsWith("/models")) { @@ -342,9 +395,13 @@ describe("Session Configuration", async () => { return json({}); } - function buildInferenceResponse(url: string, _body: string): Response { + function buildInferenceResponse(url: string, body: string): Response { const u = url.toLowerCase(); + const wantsStream = /"stream"\s*:\s*true/.test(body); if (u.endsWith("/messages")) { + if (wantsStream) { + return sse(anthropicMessageStreamBody("OK from the synthetic stream.")); + } return json({ id: "msg_stub_1", type: "message", diff --git a/python/copilot/__init__.py b/python/copilot/__init__.py index cdcfdc6ae6..fdf422d2aa 100644 --- a/python/copilot/__init__.py +++ b/python/copilot/__init__.py @@ -74,6 +74,9 @@ LlmInferenceHeaders, ) from .generated.rpc import ( + GitHubTelemetryClientInfo, + GitHubTelemetryEvent, + GitHubTelemetryNotification, ModelBillingTokenPrices, ModelBillingTokenPricesLongContext, ) @@ -226,6 +229,9 @@ "GetAuthStatusResponse", "BearerTokenProvider", "GetStatusResponse", + "GitHubTelemetryClientInfo", + "GitHubTelemetryEvent", + "GitHubTelemetryNotification", "InfiniteSessionConfig", "InputOptions", "LargeToolOutputConfig", diff --git a/python/copilot/_jsonrpc.py b/python/copilot/_jsonrpc.py index a58908d08d..ed70e4e8d0 100644 --- a/python/copilot/_jsonrpc.py +++ b/python/copilot/_jsonrpc.py @@ -80,6 +80,7 @@ def __init__(self, process): self.pending_requests: dict[str, asyncio.Future] = {} self._pending_inline_callbacks: dict[str, Callable[[Any], None]] = {} self.notification_handler: Callable[[str, dict], None] | None = None + self.notification_method_handlers: dict[str, Callable[[dict], Any]] = {} self.request_handlers: dict[str, RequestHandler] = {} self._running = False self._read_thread: threading.Thread | None = None @@ -232,6 +233,19 @@ def set_notification_handler(self, handler: Callable[[str, dict], None]): """Set the handler for incoming notifications from the server.""" self.notification_handler = handler + def set_notification_method_handler(self, method: str, handler: Callable[[dict], Any] | None): + """Register a handler for a specific server-to-client notification method. + + Notifications carry no ``id`` and expect no response, so they are + dispatched separately from request handlers. A registered method + handler takes precedence over the generic notification handler. The + handler may be a coroutine function; its result is awaited. + """ + if handler is None: + self.notification_method_handlers.pop(method, None) + else: + self.notification_method_handlers[method] = handler + def set_request_handler(self, method: str, handler: RequestHandler): if handler is None: self.request_handlers.pop(method, None) @@ -397,9 +411,14 @@ def _handle_message(self, message: dict): # Check if it's a notification from the server if "method" in message and "id" not in message: + method = message["method"] + params = message.get("params", {}) + handler = self.notification_method_handlers.get(method) + if handler is not None and self._loop: + # Method-specific notification handler takes precedence. + self._loop.call_soon_threadsafe(self._dispatch_notification, handler, params) + return if self.notification_handler and self._loop: - method = message["method"] - params = message.get("params", {}) # Schedule notification handler on the event loop for thread safety self._loop.call_soon_threadsafe(self.notification_handler, method, params) return @@ -427,6 +446,25 @@ def _handle_request(self, message: dict): self._loop, ) + def _dispatch_notification(self, handler: Callable[[dict], Any], params: dict): + """Invoke a method-specific notification handler. Runs on the event loop; + coroutine results are scheduled and any error is logged (notifications + carry no response, so failures never propagate to the server).""" + try: + outcome = handler(params) + except Exception: # pylint: disable=broad-except + logger.warning("Notification handler raised", exc_info=True) + return + if inspect.isawaitable(outcome): + + async def _await_outcome(): + try: + await outcome + except Exception: # pylint: disable=broad-except + logger.warning("Notification handler raised", exc_info=True) + + asyncio.create_task(_await_outcome()) + async def _dispatch_request(self, message: dict, handler: RequestHandler): try: params = message.get("params", {}) diff --git a/python/copilot/client.py b/python/copilot/client.py index 6bbdf91363..55d01c5b57 100644 --- a/python/copilot/client.py +++ b/python/copilot/client.py @@ -64,13 +64,13 @@ from .generated.rpc import ( ClientGlobalApiHandlers, ClientSessionApiHandlers, + GitHubTelemetryNotification, ModelBillingTokenPrices, ModelBillingTokenPricesLongContext, # noqa: F401 OpenCanvasInstance, RemoteSessionMode, ServerRpc, - _ConnectRequest, - _InternalServerRpc, + _ConnectResult, from_datetime, register_client_global_api_handlers, register_client_session_api_handlers, @@ -399,6 +399,26 @@ class UriRuntimeConnection(RuntimeConnection): """Shared secret to authenticate the connection.""" +class _GitHubTelemetryAdapter: + """Adapts a user-provided ``on_github_telemetry`` callback to the generated + ``GitHubTelemetryHandler`` protocol. + """ + + def __init__( + self, + callback: Callable[[GitHubTelemetryNotification], None | Awaitable[None]], + ) -> None: + self._callback = callback + + async def event(self, params: GitHubTelemetryNotification) -> None: + try: + result = self._callback(params) + if inspect.isawaitable(result): + await result + except Exception: + logger.warning("Error handling gitHubTelemetry.event notification", exc_info=True) + + @dataclass class _CopilotClientOptions: """Internal configuration carrier used by :class:`CopilotClient`. @@ -420,6 +440,9 @@ class _CopilotClientOptions: session_idle_timeout_seconds: int | None = None enable_remote_sessions: bool = False on_list_models: Callable[[], list[ModelInfo] | Awaitable[list[ModelInfo]]] | None = None + on_github_telemetry: Callable[[GitHubTelemetryNotification], None | Awaitable[None]] | None = ( + None + ) mode: CopilotClientMode = "copilot-cli" @@ -1109,6 +1132,8 @@ def __init__( session_idle_timeout_seconds: int | None = None, enable_remote_sessions: bool = False, on_list_models: Callable[[], list[ModelInfo] | Awaitable[list[ModelInfo]]] | None = None, + on_github_telemetry: Callable[[GitHubTelemetryNotification], None | Awaitable[None]] + | None = None, mode: CopilotClientMode = "copilot-cli", ): """ @@ -1153,6 +1178,10 @@ def __init__( on_list_models: Custom handler for :meth:`list_models`. When provided, the handler is called instead of querying the runtime server. + on_github_telemetry: Internal. Callback invoked when the runtime + forwards a GitHub telemetry event for a session. The callback + may be sync or async. Registering a handler opts every session + opened by this client into telemetry forwarding. Example: >>> # Default — spawns runtime using stdio with the bundled binary @@ -1183,6 +1212,7 @@ def __init__( session_idle_timeout_seconds=session_idle_timeout_seconds, enable_remote_sessions=enable_remote_sessions, on_list_models=on_list_models, + on_github_telemetry=on_github_telemetry, mode=mode, ) connection = ( @@ -1198,6 +1228,7 @@ def __init__( self._options: _CopilotClientOptions = options self._connection: RuntimeConnection = connection self._on_list_models = options.on_list_models + self._on_github_telemetry = options.on_github_telemetry # Resolve connection-mode-specific state. self._actual_host: str = "localhost" @@ -2002,6 +2033,11 @@ async def create_session( else True ) + # Opt this connection into gitHubTelemetry.event notifications when a + # telemetry handler was registered on the client. + if self._on_github_telemetry is not None: + payload["enableGitHubTelemetryForwarding"] = True + # Add provider configuration if provided if provider: payload["provider"] = self._convert_provider_to_wire_format(provider) @@ -2620,6 +2656,11 @@ async def resume_session( else True ) + # Opt this connection into gitHubTelemetry.event notifications when a + # telemetry handler was registered on the client. + if self._on_github_telemetry is not None: + payload["enableGitHubTelemetryForwarding"] = True + # Enable permission request callback if handler provided payload["requestPermission"] = bool(on_permission_request) @@ -3261,8 +3302,17 @@ async def _verify_protocol_version(self) -> None: server_version: int | None try: - connect_result = await _InternalServerRpc(self._client)._connect( - _ConnectRequest(token=self._effective_connection_token) + connect_params: dict[str, Any] = {} + if self._effective_connection_token is not None: + connect_params["token"] = self._effective_connection_token + # Opt in to GitHub telemetry forwarding at the connection level when a + # handler is registered (mirrors the runtime, which reads this flag on the + # `connect` handshake so the first session's un-replayable `session.start` + # event is forwarded). Also sent on session.create/resume for older CLIs. + if self._on_github_telemetry is not None: + connect_params["enableGitHubTelemetryForwarding"] = True + connect_result = _ConnectResult.from_dict( + await self._client.request("connect", connect_params) ) server_version = connect_result.protocol_version except JsonRpcError as err: @@ -3690,7 +3740,7 @@ def handle_notification(method: str, params: dict): "systemMessage.transform", self._handle_system_message_transform ) register_client_session_api_handlers(self._client, self._get_client_session_handlers) - self._register_llm_inference_handlers() + self._register_client_global_handlers() # Start listening for messages loop = asyncio.get_running_loop() @@ -3810,7 +3860,7 @@ def handle_notification(method: str, params: dict): "systemMessage.transform", self._handle_system_message_transform ) register_client_session_api_handlers(self._client, self._get_client_session_handlers) - self._register_llm_inference_handlers() + self._register_client_global_handlers() # Start listening for messages loop = asyncio.get_running_loop() @@ -3883,15 +3933,26 @@ async def _set_session_fs_provider(self) -> None: await self._client.request("sessionFs.setProvider", params) - def _register_llm_inference_handlers(self) -> None: - if self._request_handler is None or not self._client: + def _register_client_global_handlers(self) -> None: + if not self._client: + return + llm_inference_adapter = None + if self._request_handler is not None: + llm_inference_adapter = create_copilot_request_adapter( + self._request_handler, + lambda: self._rpc.llm_inference if self._rpc is not None else None, + ) + github_telemetry_adapter = None + if self._on_github_telemetry is not None: + github_telemetry_adapter = _GitHubTelemetryAdapter(self._on_github_telemetry) + if llm_inference_adapter is None and github_telemetry_adapter is None: return - adapter = create_copilot_request_adapter( - self._request_handler, - lambda: self._rpc.llm_inference if self._rpc is not None else None, - ) register_client_global_api_handlers( - self._client, ClientGlobalApiHandlers(llm_inference=adapter) + self._client, + ClientGlobalApiHandlers( + llm_inference=llm_inference_adapter, + git_hub_telemetry=github_telemetry_adapter, + ), ) async def _set_llm_inference_provider(self) -> None: diff --git a/python/copilot/generated/rpc.py b/python/copilot/generated/rpc.py index 89b724ce9c..75e186dc49 100644 --- a/python/copilot/generated/rpc.py +++ b/python/copilot/generated/rpc.py @@ -6,7 +6,7 @@ from typing import ClassVar, TYPE_CHECKING -from .session_events import AbortReason, Attachment, ContextTier, EmbeddedBlobResourceContents, EmbeddedTextResourceContents, McpServerSource, McpServerStatus, PermissionPromptRequest, PermissionRule, ReasoningSummary, SessionEvent, SessionLimitsConfig, SessionMode, ShutdownType, SkillSource, UserToolSessionApproval +from .session_events import AbortReason, Attachment, ContextTier, EmbeddedBlobResourceContents, EmbeddedTextResourceContents, McpServerSource, McpServerStatus, PermissionPromptRequest, PermissionRule, ReasoningSummary, SessionEvent, SessionLimitsConfig, SessionMode, ShutdownType, SkillSource, UserToolSessionApproval, Verbosity if TYPE_CHECKING: from .._jsonrpc import JsonRpcClient @@ -123,7 +123,7 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class CopilotUserResponseEndpoints: - """Schema for the `CopilotUserResponseEndpoints` type.""" + """Endpoint URLs from the raw Copilot `/copilot_internal/v2/token` user-response passthrough.""" api: str | None = None origin_tracker: str | None = None @@ -151,6 +151,102 @@ def to_dict(self) -> dict: result["telemetry"] = from_union([from_str, from_none], self.telemetry) return result +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class CopilotUserResponseQuotaSnapshots: + """Chat quota snapshot from the raw Copilot user-response passthrough, with entitlement, + overage, remaining quota, reset, and billing fields. + + Completions quota snapshot from the raw Copilot user-response passthrough, with + entitlement, overage, remaining quota, reset, and billing fields. + + Premium-interactions quota snapshot from the raw Copilot user-response passthrough, with + entitlement, overage, remaining quota, reset, and billing fields. + """ + entitlement: float | None = None + """Number of requests/units included in the entitlement for this period; `-1` denotes an + unlimited entitlement. + """ + has_quota: bool | None = None + """Whether the user currently has quota available; when `false` and not unlimited, further + requests are blocked until the quota resets. + """ + overage_count: float | None = None + """Count of additional pay-per-request usage consumed this period beyond the entitlement.""" + + overage_permitted: bool | None = None + """Whether usage may continue at pay-per-request rates once the entitlement is exhausted.""" + + percent_remaining: float | None = None + """Percentage of the entitlement remaining at the snapshot timestamp.""" + + quota_id: str | None = None + """Identifier of the quota bucket this snapshot describes.""" + + quota_remaining: float | None = None + """Amount of quota remaining at the snapshot timestamp.""" + + quota_reset_at: float | None = None + """Unix epoch time, in seconds, when this quota next resets.""" + + remaining: float | None = None + """Remaining entitlement/quota amount at the snapshot timestamp.""" + + timestamp_utc: str | None = None + """UTC timestamp when this snapshot was captured.""" + + token_based_billing: bool | None = None + """Whether this category uses usage-based (token/AI-credit) billing rather than a fixed + premium-request count. + """ + unlimited: bool | None = None + """Whether the entitlement for this category is unlimited.""" + + @staticmethod + def from_dict(obj: Any) -> 'CopilotUserResponseQuotaSnapshots': + assert isinstance(obj, dict) + entitlement = from_union([from_float, from_none], obj.get("entitlement")) + has_quota = from_union([from_bool, from_none], obj.get("has_quota")) + overage_count = from_union([from_float, from_none], obj.get("overage_count")) + overage_permitted = from_union([from_bool, from_none], obj.get("overage_permitted")) + percent_remaining = from_union([from_float, from_none], obj.get("percent_remaining")) + quota_id = from_union([from_str, from_none], obj.get("quota_id")) + quota_remaining = from_union([from_float, from_none], obj.get("quota_remaining")) + quota_reset_at = from_union([from_float, from_none], obj.get("quota_reset_at")) + remaining = from_union([from_float, from_none], obj.get("remaining")) + timestamp_utc = from_union([from_str, from_none], obj.get("timestamp_utc")) + token_based_billing = from_union([from_bool, from_none], obj.get("token_based_billing")) + unlimited = from_union([from_bool, from_none], obj.get("unlimited")) + return CopilotUserResponseQuotaSnapshots(entitlement, has_quota, overage_count, overage_permitted, percent_remaining, quota_id, quota_remaining, quota_reset_at, remaining, timestamp_utc, token_based_billing, unlimited) + + def to_dict(self) -> dict: + result: dict = {} + if self.entitlement is not None: + result["entitlement"] = from_union([to_float, from_none], self.entitlement) + if self.has_quota is not None: + result["has_quota"] = from_union([from_bool, from_none], self.has_quota) + if self.overage_count is not None: + result["overage_count"] = from_union([to_float, from_none], self.overage_count) + if self.overage_permitted is not None: + result["overage_permitted"] = from_union([from_bool, from_none], self.overage_permitted) + if self.percent_remaining is not None: + result["percent_remaining"] = from_union([to_float, from_none], self.percent_remaining) + if self.quota_id is not None: + result["quota_id"] = from_union([from_str, from_none], self.quota_id) + if self.quota_remaining is not None: + result["quota_remaining"] = from_union([to_float, from_none], self.quota_remaining) + if self.quota_reset_at is not None: + result["quota_reset_at"] = from_union([to_float, from_none], self.quota_reset_at) + if self.remaining is not None: + result["remaining"] = from_union([to_float, from_none], self.remaining) + if self.timestamp_utc is not None: + result["timestamp_utc"] = from_union([from_str, from_none], self.timestamp_utc) + if self.token_based_billing is not None: + result["token_based_billing"] = from_union([from_bool, from_none], self.token_based_billing) + if self.unlimited is not None: + result["unlimited"] = from_union([from_bool, from_none], self.unlimited) + return result + # Experimental: this type is part of an experimental API and may change or be removed. class AuthInfoType(Enum): """Authentication type""" @@ -166,7 +262,8 @@ class AuthInfoType(Enum): # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class AccountAllUsers: - """Schema for the `AccountAllUsers` type. + """Authenticated account entry returned by `account.getAllUsers`, with auth info and an + optional associated token. List of all authenticated users """ @@ -239,8 +336,9 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class AccountQuotaSnapshot: - """Schema for the `AccountQuotaSnapshot` type.""" - + """Quota usage snapshot for a Copilot quota type, including entitlement, used requests, + overage, reset date, and remaining percentage. + """ entitlement_requests: int """Number of requests included in the entitlement, or -1 for unlimited entitlements""" @@ -578,47 +676,19 @@ def to_dict(self) -> dict: return result # Experimental: this type is part of an experimental API and may change or be removed. -@dataclass -class AllowAllPermissionSetResult: - """Indicates whether the operation succeeded and reports the post-mutation state.""" - - enabled: bool - """Authoritative allow-all state after the mutation""" - - success: bool - """Whether the operation succeeded""" - - @staticmethod - def from_dict(obj: Any) -> 'AllowAllPermissionSetResult': - assert isinstance(obj, dict) - enabled = from_bool(obj.get("enabled")) - success = from_bool(obj.get("success")) - return AllowAllPermissionSetResult(enabled, success) - - def to_dict(self) -> dict: - result: dict = {} - result["enabled"] = from_bool(self.enabled) - result["success"] = from_bool(self.success) - return result - -# Experimental: this type is part of an experimental API and may change or be removed. -@dataclass -class AllowAllPermissionState: - """Current full allow-all permission state.""" +class PermissionsAllowAllMode(Enum): + """Authoritative allow-all mode after the mutation - enabled: bool - """Whether full allow-all permissions are currently active""" + Current or requested allow-all mode. - @staticmethod - def from_dict(obj: Any) -> 'AllowAllPermissionState': - assert isinstance(obj, dict) - enabled = from_bool(obj.get("enabled")) - return AllowAllPermissionState(enabled) + Current allow-all mode - def to_dict(self) -> dict: - result: dict = {} - result["enabled"] = from_bool(self.enabled) - return result + Allow-all mode to apply. `on` enables full allow-all; `auto` enables advisory LLM + auto-approval; `off` disables both. + """ + AUTO = "auto" + OFF = "off" + ON = "on" class APIKeyAuthInfoType(Enum): API_KEY = "api-key" @@ -896,6 +966,30 @@ def to_dict(self) -> dict: result["enableWebSocketResponses"] = from_union([from_bool, from_none], self.enable_web_socket_responses) return result +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class SlashCommandInputChoice: + """A literal choice the command input accepts, with a human-facing description""" + + description: str + """Human-readable description shown alongside the choice""" + + name: str + """The literal choice value (e.g. 'on', 'off', 'show')""" + + @staticmethod + def from_dict(obj: Any) -> 'SlashCommandInputChoice': + assert isinstance(obj, dict) + description = from_str(obj.get("description")) + name = from_str(obj.get("name")) + return SlashCommandInputChoice(description, name) + + def to_dict(self) -> dict: + result: dict = {} + result["description"] = from_str(self.description) + result["name"] = from_str(self.name) + return result + # Experimental: this type is part of an experimental API and may change or be removed. class SlashCommandInputCompletion(Enum): """Optional completion hint for the input (e.g. 'directory' for filesystem path completion)""" @@ -1203,19 +1297,32 @@ def to_dict(self) -> dict: # Internal: this type is an internal SDK API and is not part of the public surface. @dataclass class _ConnectRequest: - """Optional connection token presented by the SDK client during the handshake.""" - + """Parameters for the `server.connect` handshake: an optional connection token and optional + connection-level opt-ins (e.g. GitHub telemetry forwarding). + """ + enable_git_hub_telemetry_forwarding: bool | None = None + """Opt this connection in to GitHub telemetry forwarding for its lifetime. When set, the + runtime forwards every internal telemetry event it emits — across all sessions, plus + sessionless events — to this connection over the `gitHubTelemetry.event` notification, in + addition to the runtime's normal GitHub/CTS emission (dual-write). Intended for + first-party hosts that re-emit the events into their own telemetry stores. Both + unrestricted and restricted events are forwarded, each tagged with a `restricted` + discriminator; a backstop drops restricted events when restricted telemetry is disabled. + """ 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) + 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(token) + return _ConnectRequest(enable_git_hub_telemetry_forwarding, token) 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) return result @@ -1296,6 +1403,40 @@ class ContentFilterMode(Enum): MARKDOWN = "markdown" NONE = "none" +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class ContextHeaviestMessage: + """A single large message currently in context.""" + + id: str + """Stable identifier for this message within the snapshot.""" + + label: str + """Human-readable source label, e.g. `tool: bash` or `skill: tmux`. Presentation-only.""" + + role: str + """Role of the chat message (`user`, `assistant`, or `tool`).""" + + tokens: int + """Token count currently in context for this individual message.""" + + @staticmethod + def from_dict(obj: Any) -> 'ContextHeaviestMessage': + assert isinstance(obj, dict) + id = from_str(obj.get("id")) + label = from_str(obj.get("label")) + role = from_str(obj.get("role")) + tokens = from_int(obj.get("tokens")) + return ContextHeaviestMessage(id, label, role, tokens) + + def to_dict(self) -> dict: + result: dict = {} + result["id"] = from_str(self.id) + result["label"] = from_str(self.label) + result["role"] = from_str(self.role) + result["tokens"] = from_int(self.tokens) + return result + class Host(Enum): HTTPS_GITHUB_COM = "https://github.com" @@ -1305,20 +1446,47 @@ class CopilotAPITokenAuthInfoType(Enum): # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class CopilotUserResponseQuotaSnapshotsChat: - """Schema for the `CopilotUserResponseQuotaSnapshotsChat` type.""" - + """Chat quota snapshot from the raw Copilot user-response passthrough, with entitlement, + overage, remaining quota, reset, and billing fields. + """ entitlement: float | None = None + """Number of requests/units included in the entitlement for this period; `-1` denotes an + unlimited entitlement. + """ has_quota: bool | None = None + """Whether the user currently has quota available; when `false` and not unlimited, further + requests are blocked until the quota resets. + """ overage_count: float | None = None + """Count of additional pay-per-request usage consumed this period beyond the entitlement.""" + overage_permitted: bool | None = None + """Whether usage may continue at pay-per-request rates once the entitlement is exhausted.""" + percent_remaining: float | None = None + """Percentage of the entitlement remaining at the snapshot timestamp.""" + quota_id: str | None = None + """Identifier of the quota bucket this snapshot describes.""" + quota_remaining: float | None = None + """Amount of quota remaining at the snapshot timestamp.""" + quota_reset_at: float | None = None + """Unix epoch time, in seconds, when this quota next resets.""" + remaining: float | None = None + """Remaining entitlement/quota amount at the snapshot timestamp.""" + timestamp_utc: str | None = None + """UTC timestamp when this snapshot was captured.""" + token_based_billing: bool | None = None + """Whether this category uses usage-based (token/AI-credit) billing rather than a fixed + premium-request count. + """ unlimited: bool | None = None + """Whether the entitlement for this category is unlimited.""" @staticmethod def from_dict(obj: Any) -> 'CopilotUserResponseQuotaSnapshotsChat': @@ -1368,20 +1536,47 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class CopilotUserResponseQuotaSnapshotsCompletions: - """Schema for the `CopilotUserResponseQuotaSnapshotsCompletions` type.""" - + """Completions quota snapshot from the raw Copilot user-response passthrough, with + entitlement, overage, remaining quota, reset, and billing fields. + """ entitlement: float | None = None + """Number of requests/units included in the entitlement for this period; `-1` denotes an + unlimited entitlement. + """ has_quota: bool | None = None + """Whether the user currently has quota available; when `false` and not unlimited, further + requests are blocked until the quota resets. + """ overage_count: float | None = None + """Count of additional pay-per-request usage consumed this period beyond the entitlement.""" + overage_permitted: bool | None = None + """Whether usage may continue at pay-per-request rates once the entitlement is exhausted.""" + percent_remaining: float | None = None + """Percentage of the entitlement remaining at the snapshot timestamp.""" + quota_id: str | None = None + """Identifier of the quota bucket this snapshot describes.""" + quota_remaining: float | None = None + """Amount of quota remaining at the snapshot timestamp.""" + quota_reset_at: float | None = None + """Unix epoch time, in seconds, when this quota next resets.""" + remaining: float | None = None + """Remaining entitlement/quota amount at the snapshot timestamp.""" + timestamp_utc: str | None = None + """UTC timestamp when this snapshot was captured.""" + token_based_billing: bool | None = None + """Whether this category uses usage-based (token/AI-credit) billing rather than a fixed + premium-request count. + """ unlimited: bool | None = None + """Whether the entitlement for this category is unlimited.""" @staticmethod def from_dict(obj: Any) -> 'CopilotUserResponseQuotaSnapshotsCompletions': @@ -1431,20 +1626,47 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class CopilotUserResponseQuotaSnapshotsPremiumInteractions: - """Schema for the `CopilotUserResponseQuotaSnapshotsPremiumInteractions` type.""" - + """Premium-interactions quota snapshot from the raw Copilot user-response passthrough, with + entitlement, overage, remaining quota, reset, and billing fields. + """ entitlement: float | None = None + """Number of requests/units included in the entitlement for this period; `-1` denotes an + unlimited entitlement. + """ has_quota: bool | None = None + """Whether the user currently has quota available; when `false` and not unlimited, further + requests are blocked until the quota resets. + """ overage_count: float | None = None + """Count of additional pay-per-request usage consumed this period beyond the entitlement.""" + overage_permitted: bool | None = None + """Whether usage may continue at pay-per-request rates once the entitlement is exhausted.""" + percent_remaining: float | None = None + """Percentage of the entitlement remaining at the snapshot timestamp.""" + quota_id: str | None = None + """Identifier of the quota bucket this snapshot describes.""" + quota_remaining: float | None = None + """Amount of quota remaining at the snapshot timestamp.""" + quota_reset_at: float | None = None + """Unix epoch time, in seconds, when this quota next resets.""" + remaining: float | None = None + """Remaining entitlement/quota amount at the snapshot timestamp.""" + timestamp_utc: str | None = None + """UTC timestamp when this snapshot was captured.""" + token_based_billing: bool | None = None + """Whether this category uses usage-based (token/AI-credit) billing rather than a fixed + premium-request count. + """ unlimited: bool | None = None + """Whether the entitlement for this category is unlimited.""" @staticmethod def from_dict(obj: Any) -> 'CopilotUserResponseQuotaSnapshotsPremiumInteractions': @@ -1528,6 +1750,126 @@ def to_dict(self) -> dict: result["reasoningEffort"] = from_union([from_str, from_none], self.reasoning_effort) return result +# Experimental: this type is part of an experimental API and may change or be removed. +class DebugCollectLogsSource(Enum): + """Source category for this entry. + + Source category for a collected debug bundle entry. + """ + ADDITIONAL = "additional" + EVENTS = "events" + PROCESS_LOG = "process-log" + SHELL_LOG = "shell-log" + +# Experimental: this type is part of an experimental API and may change or be removed. +class DebugCollectLogsResultKind(Enum): + """Destination kind that was written.""" + + ARCHIVE = "archive" + DIRECTORY = "directory" + +# Experimental: this type is part of an experimental API and may change or be removed. +class DebugCollectLogsRedaction(Enum): + """How text content from this entry should be redacted. Defaults to plain-text. + + How a collected debug entry should be redacted before being staged. + """ + EVENTS_JSONL = "events-jsonl" + PLAIN_TEXT = "plain-text" + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class DebugCollectLogsInclude: + """Built-in session diagnostics to include in the bundle. Omitted fields default to true. + + Which built-in session diagnostics to include. Omitted fields default to true. + """ + current_process_log_path: str | None = None + """Server-local path to the current process log. When set, it is included as `process.log` + and its directory is searched for prior logs from the same session. + """ + events: bool | None = None + """Include the session event log (`events.jsonl`). Defaults to true.""" + + events_path: str | None = None + """Server-local path to the session's events.jsonl file. Internal callers normally omit this + and let the runtime derive it from the session. + """ + previous_process_log_limit: int | None = None + """Maximum number of previous process logs to include. Defaults to 5.""" + + process_log_directory: str | None = None + """Server-local process log directory to search when `currentProcessLogPath` is unavailable, + useful for collecting logs for inactive sessions. + """ + process_logs: bool | None = None + """Include process logs for the session. Defaults to true.""" + + shell_logs: bool | None = None + """Include interactive shell logs written under the session's `shell-logs` directory. + Defaults to true. + """ + + @staticmethod + def from_dict(obj: Any) -> 'DebugCollectLogsInclude': + assert isinstance(obj, dict) + current_process_log_path = from_union([from_str, from_none], obj.get("currentProcessLogPath")) + events = from_union([from_bool, from_none], obj.get("events")) + events_path = from_union([from_str, from_none], obj.get("eventsPath")) + previous_process_log_limit = from_union([from_int, from_none], obj.get("previousProcessLogLimit")) + process_log_directory = from_union([from_str, from_none], obj.get("processLogDirectory")) + process_logs = from_union([from_bool, from_none], obj.get("processLogs")) + shell_logs = from_union([from_bool, from_none], obj.get("shellLogs")) + return DebugCollectLogsInclude(current_process_log_path, events, events_path, previous_process_log_limit, process_log_directory, process_logs, shell_logs) + + def to_dict(self) -> dict: + result: dict = {} + if self.current_process_log_path is not None: + result["currentProcessLogPath"] = from_union([from_str, from_none], self.current_process_log_path) + if self.events is not None: + result["events"] = from_union([from_bool, from_none], self.events) + if self.events_path is not None: + result["eventsPath"] = from_union([from_str, from_none], self.events_path) + if self.previous_process_log_limit is not None: + result["previousProcessLogLimit"] = from_union([from_int, from_none], self.previous_process_log_limit) + if self.process_log_directory is not None: + result["processLogDirectory"] = from_union([from_str, from_none], self.process_log_directory) + if self.process_logs is not None: + result["processLogs"] = from_union([from_bool, from_none], self.process_logs) + if self.shell_logs is not None: + result["shellLogs"] = from_union([from_bool, from_none], self.shell_logs) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class DebugCollectLogsSkippedEntry: + """An optional debug bundle entry that could not be included.""" + + bundle_path: str + """Relative path requested for this bundle entry.""" + + reason: str + """Reason the entry was skipped.""" + + path: str | None = None + """Server-local source path that could not be read.""" + + @staticmethod + def from_dict(obj: Any) -> 'DebugCollectLogsSkippedEntry': + assert isinstance(obj, dict) + bundle_path = from_str(obj.get("bundlePath")) + reason = from_str(obj.get("reason")) + path = from_union([from_str, from_none], obj.get("path")) + return DebugCollectLogsSkippedEntry(bundle_path, reason, path) + + def to_dict(self) -> dict: + result: dict = {} + result["bundlePath"] = from_str(self.bundle_path) + result["reason"] = from_str(self.reason) + if self.path is not None: + 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 DiscoveredMCPServerType(Enum): """Server transport type: stdio, http, sse (deprecated), or memory""" @@ -2177,15 +2519,6 @@ class TentacledSource(Enum): class StickySource(Enum): URL = "url" -# Experimental: this type is part of an experimental API and may change or be removed. -class InstructionDiscoveryPathKind(Enum): - """Whether the target is a single file or a directory of instruction files - - Entry type - """ - DIRECTORY = "directory" - FILE = "file" - # Experimental: this type is part of an experimental API and may change or be removed. class InstructionLocation(Enum): """Which tier this target belongs to @@ -2606,8 +2939,9 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class MarketplaceRefreshEntry: - """Schema for the `MarketplaceRefreshEntry` type.""" - + """Per-marketplace refresh result, including marketplace name, success flag, and optional + failure error. + """ name: str """Marketplace name that was refreshed""" @@ -2662,7 +2996,7 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class MCPAllowedServer: - """Schema for the `McpAllowedServer` type.""" + """MCP server allowed by policy, with server name and optional PII-free explanatory note.""" name: str """Allowed server name""" @@ -2858,9 +3192,10 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class MCPAppsResourceContent: - """Schema for the `McpAppsResourceContent` type.""" - - uri: str + """MCP Apps resource content with URI, optional MIME type, text or base64 blob, and resource + metadata. + """ + uri: str """The resource URI (typically ui://...)""" meta: dict[str, Any] | None = None @@ -3151,8 +3486,9 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class MCPFilteredServer: - """Schema for the `McpFilteredServer` type.""" - + """MCP server filtered by policy, with name, reason, optional redacted reason, and + enterprise login. + """ name: str """Filtered server name""" @@ -3360,19 +3696,6 @@ def to_dict(self) -> dict: result["authorizationUrl"] = from_union([from_str, from_none], self.authorization_url) return result -# Experimental: this type is part of an experimental API and may change or be removed. -@dataclass -class MCPOauthRespondResult: - """Empty result after recording the MCP OAuth response.""" - @staticmethod - def from_dict(obj: Any) -> 'MCPOauthRespondResult': - assert isinstance(obj, dict) - return MCPOauthRespondResult() - - def to_dict(self) -> dict: - result: 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 @@ -3504,6 +3827,97 @@ def to_dict(self) -> dict: result["enabled"] = from_bool(self.enabled) return result +@dataclass +class Compactions: + """Successful compaction history for the session.""" + + count: int + """Number of successful compactions in this session.""" + + @staticmethod + def from_dict(obj: Any) -> 'Compactions': + assert isinstance(obj, dict) + count = from_int(obj.get("count")) + return Compactions(count) + + def to_dict(self) -> dict: + result: dict = {} + result["count"] = from_int(self.count) + return result + +@dataclass +class Entry: + id: str + """Identifier for this entry, formed by joining its `kind` and source name (e.g. + `tool:bash`, `skill:tmux`, `toolDefinition:bash`); unique within the snapshot. Use it to + match the same entry across snapshots, to correlate with other APIs (skill/agent/MCP + registries), and as the `parentId` target for nesting. Distinct from the human-facing + `label`. + """ + kind: str + """Source category for this entry. Not a closed set — tolerate unknown values. Known values + today: `skill`, `subagent`, `mcpServer`, `tool`, `system`, `toolDefinition`, `plugin`. + """ + label: str + """Human-readable display label, e.g. `bash` or `skill: tmux`. Presentation-only; may be + localized/reformatted without notice — do not key off it. + """ + tokens: int + """Token count currently in context attributable to this entry.""" + + attributes: dict[str, str] | None = None + """Supplementary per-entry metadata (e.g. `messageCount`, `role`, `evictable`, + `pluginSource`). Values are stringified; parse as needed and ignore unrecognized keys. + """ + parent_id: str | None = None + """Optional `id` of the parent entry: e.g. a `plugin` entry parenting its + `skill`/`mcpServer` entries, or the `system` entry parenting `toolDefinition` entries. + Omitted for top-level entries. + """ + + @staticmethod + def from_dict(obj: Any) -> 'Entry': + assert isinstance(obj, dict) + id = from_str(obj.get("id")) + kind = from_str(obj.get("kind")) + label = from_str(obj.get("label")) + tokens = from_int(obj.get("tokens")) + attributes = from_union([lambda x: from_dict(from_str, x), from_none], obj.get("attributes")) + parent_id = from_union([from_str, from_none], obj.get("parentId")) + return Entry(id, kind, label, tokens, attributes, parent_id) + + def to_dict(self) -> dict: + result: dict = {} + result["id"] = from_str(self.id) + result["kind"] = from_str(self.kind) + result["label"] = from_str(self.label) + result["tokens"] = from_int(self.tokens) + if self.attributes is not None: + result["attributes"] = from_union([lambda x: from_dict(from_str, x), from_none], self.attributes) + if self.parent_id is not None: + result["parentId"] = from_union([from_str, from_none], self.parent_id) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class MetadataContextHeaviestMessagesRequest: + """Parameters for the heaviest-messages query.""" + + limit: int | None = None + """Maximum number of messages to return, most-expensive first. Omit for the server default.""" + + @staticmethod + def from_dict(obj: Any) -> 'MetadataContextHeaviestMessagesRequest': + assert isinstance(obj, dict) + limit = from_union([from_int, from_none], obj.get("limit")) + return MetadataContextHeaviestMessagesRequest(limit) + + def to_dict(self) -> dict: + result: dict = {} + if self.limit is not None: + result["limit"] = from_union([from_int, from_none], self.limit) + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class SessionContextInfo: @@ -4129,8 +4543,9 @@ class ProviderWireAPI(Enum): # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class OptionsUpdateAdditionalContentExclusionPolicyRuleSource: - """Schema for the `OptionsUpdateAdditionalContentExclusionPolicyRuleSource` type.""" - + """Source descriptor for a `session.options.update` content-exclusion rule, with source name + and type. + """ name: str type: str @@ -4180,8 +4595,9 @@ class OptionsUpdateToolFilterPrecedence(Enum): # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class PendingPermissionRequest: - """Schema for the `PendingPermissionRequest` type.""" - + """Pending permission prompt reconstructed from event history, with request ID and + user-facing prompt details. + """ request: PermissionPromptRequest """The user-facing permission prompt details (commands, write, read, mcp, url, memory, custom-tool, path, hook) @@ -4633,8 +5049,9 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class PermissionsConfigureAdditionalContentExclusionPolicyRuleSource: - """Schema for the `PermissionsConfigureAdditionalContentExclusionPolicyRuleSource` type.""" - + """Source descriptor for a `session.permissions.configure` content-exclusion rule, with + source name and type. + """ name: str type: str @@ -5107,7 +5524,7 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class Plugin: - """Schema for the `Plugin` type.""" + """Session plugin metadata, with name, marketplace, optional version, and enabled state.""" enabled: bool """Whether the plugin is currently enabled""" @@ -5270,6 +5687,11 @@ class PluginsReloadRequest: reload_custom_agents: bool | None = None """Re-run custom-agent discovery after refreshing plugins. Defaults to true.""" + reload_extensions: bool | None = None + """Re-discover and relaunch subprocess extensions (including plugin-shipped extensions) + after refreshing plugins. Defaults to true. Has no effect when the session has no active + extension controller (e.g. extensions were not requested for the session). + """ reload_hooks: bool | None = None """Re-load user, plugin, and (subject to `deferRepoHooks`) repo hooks. Defaults to true. Has no effect when the host has not registered a hook reloader (e.g. remote sessions). @@ -5282,9 +5704,10 @@ def from_dict(obj: Any) -> 'PluginsReloadRequest': assert isinstance(obj, dict) defer_repo_hooks = from_union([from_bool, from_none], obj.get("deferRepoHooks")) reload_custom_agents = from_union([from_bool, from_none], obj.get("reloadCustomAgents")) + reload_extensions = from_union([from_bool, from_none], obj.get("reloadExtensions")) reload_hooks = from_union([from_bool, from_none], obj.get("reloadHooks")) reload_mcp = from_union([from_bool, from_none], obj.get("reloadMcp")) - return PluginsReloadRequest(defer_repo_hooks, reload_custom_agents, reload_hooks, reload_mcp) + return PluginsReloadRequest(defer_repo_hooks, reload_custom_agents, reload_extensions, reload_hooks, reload_mcp) def to_dict(self) -> dict: result: dict = {} @@ -5292,31 +5715,14 @@ def to_dict(self) -> dict: result["deferRepoHooks"] = from_union([from_bool, from_none], self.defer_repo_hooks) if self.reload_custom_agents is not None: result["reloadCustomAgents"] = from_union([from_bool, from_none], self.reload_custom_agents) + if self.reload_extensions is not None: + result["reloadExtensions"] = from_union([from_bool, from_none], self.reload_extensions) if self.reload_hooks is not None: result["reloadHooks"] = from_union([from_bool, from_none], self.reload_hooks) if self.reload_mcp is not None: result["reloadMcp"] = from_union([from_bool, from_none], self.reload_mcp) return result -# Experimental: this type is part of an experimental API and may change or be removed. -@dataclass -class SessionsPollSpawnedSessionsEvent: - """Schema for the `SessionsPollSpawnedSessionsEvent` type.""" - - session_id: str - """Session id of the newly-spawned session.""" - - @staticmethod - def from_dict(obj: Any) -> 'SessionsPollSpawnedSessionsEvent': - assert isinstance(obj, dict) - session_id = from_str(obj.get("sessionId")) - return SessionsPollSpawnedSessionsEvent(session_id) - - def to_dict(self) -> dict: - result: dict = {} - result["sessionId"] = from_str(self.session_id) - return result - # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class ProviderAddResult: @@ -5610,8 +6016,9 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class QueuedCommandHandled: - """Schema for the `QueuedCommandHandled` type.""" - + """Queued-command response indicating the host executed the command, with an optional flag + to stop queue processing. + """ handled: ClassVar[str] = "true" """The host actually executed the queued command.""" @@ -5636,8 +6043,9 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class QueuedCommandNotHandled: - """Schema for the `QueuedCommandNotHandled` type.""" - + """Queued-command response indicating the host did not execute the command and the queue may + continue. + """ handled: ClassVar[str] = "false" """The host did not execute the queued command. Unblocks the queue without claiming the command was processed (e.g. when the handler threw before completing). @@ -5662,16 +6070,16 @@ class RegisterEventInterestParams: """The event type the consumer wants the runtime to treat as 'observed' for behavior-switching gating. Some runtime code paths inspect whether any consumer is interested in a specific event type and choose a different implementation accordingly - (e.g. `mcp.oauth_required`: when interest is registered the runtime delegates the full - interactive OAuth flow to the consumer; when no interest is registered the runtime - installs a browserless fallback that silently reuses cached tokens). SDK clients that - long-poll events do NOT automatically appear as listeners to these gating checks — they - must explicitly call `registerInterest` for each event type they want the runtime to - count as having a consumer. Multiple registrations for the same event type from the same - or different consumers are tracked independently and must each be released. See: - `mcp.oauth_required`, `sampling.requested`, `auto_mode_switch.requested`, - `session_limits_exhausted.requested`, `user_input.requested`, `elicitation.requested`, - `command.queued`, `exit_plan_mode.requested`. + (e.g. `mcp.oauth_required`: when interest is registered the runtime delegates OAuth token + acquisition to the consumer; when no interest is registered OAuth-required servers become + needs-auth). SDK clients that long-poll events do NOT automatically appear as listeners + to these gating checks — they must explicitly call `registerInterest` for each event type + they want the runtime to count as having a consumer. Multiple registrations for the same + event type from the same or different consumers are tracked independently and must each + be released. See: `mcp.oauth_required`, `sampling.requested`, + `auto_mode_switch.requested`, `session_limits_exhausted.requested`, + `user_input.requested`, `elicitation.requested`, `command.queued`, + `exit_plan_mode.requested`. """ @staticmethod @@ -6058,37 +6466,25 @@ def to_dict(self) -> dict: class SandboxConfigUserPolicyNetwork: """Network rules to merge into the base policy.""" - allowed_hosts: list[str] | None = None - """Hosts allowed in addition to the base policy.""" - allow_local_network: bool | None = None """Whether traffic to local/loopback addresses is allowed.""" allow_outbound: bool | None = None """Whether outbound network traffic is allowed at all.""" - blocked_hosts: list[str] | None = None - """Hosts explicitly blocked.""" - @staticmethod def from_dict(obj: Any) -> 'SandboxConfigUserPolicyNetwork': assert isinstance(obj, dict) - allowed_hosts = from_union([lambda x: from_list(from_str, x), from_none], obj.get("allowedHosts")) allow_local_network = from_union([from_bool, from_none], obj.get("allowLocalNetwork")) allow_outbound = from_union([from_bool, from_none], obj.get("allowOutbound")) - blocked_hosts = from_union([lambda x: from_list(from_str, x), from_none], obj.get("blockedHosts")) - return SandboxConfigUserPolicyNetwork(allowed_hosts, allow_local_network, allow_outbound, blocked_hosts) + return SandboxConfigUserPolicyNetwork(allow_local_network, allow_outbound) def to_dict(self) -> dict: result: dict = {} - if self.allowed_hosts is not None: - result["allowedHosts"] = from_union([lambda x: from_list(from_str, x), from_none], self.allowed_hosts) if self.allow_local_network is not None: result["allowLocalNetwork"] = from_union([from_bool, from_none], self.allow_local_network) if self.allow_outbound is not None: result["allowOutbound"] = from_union([from_bool, from_none], self.allow_outbound) - if self.blocked_hosts is not None: - result["blockedHosts"] = from_union([lambda x: from_list(from_str, x), from_none], self.blocked_hosts) return result # Experimental: this type is part of an experimental API and may change or be removed. @@ -6116,7 +6512,8 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class ScheduleEntry: - """Schema for the `ScheduleEntry` type. + """Scheduled prompt entry with ID, timing (`intervalMs`, `cron`, or `at`), prompt text, + recurrence, and next run time. The removed entry, or omitted if no entry matched. """ @@ -6315,8 +6712,9 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class ServerSkill: - """Schema for the `ServerSkill` type.""" - + """Server-side skill metadata, including name, description, source, enabled/invocable state, + path, project path, and argument hint. + """ description: str """Description of what the skill does""" @@ -6963,7 +7361,7 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class SessionOpenOptionsAdditionalContentExclusionPolicyRuleSource: - """Schema for the `SessionOpenOptionsAdditionalContentExclusionPolicyRuleSource` type.""" + """Source descriptor for a `sessions.open` content-exclusion rule, with source name and type.""" name: str type: str @@ -7120,6 +7518,242 @@ def to_dict(self) -> dict: result["copilotUserResolved"] = from_union([from_bool, from_none], self.copilot_user_resolved) return result +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class SessionSettingsBuiltInToolAvailabilitySnapshot: + """Availability of built-in job tools surfaced to boundary consumers.""" + + create_pull_request: bool | None = None + report_progress: bool | None = None + + @staticmethod + def from_dict(obj: Any) -> 'SessionSettingsBuiltInToolAvailabilitySnapshot': + assert isinstance(obj, dict) + create_pull_request = from_union([from_bool, from_none], obj.get("createPullRequest")) + report_progress = from_union([from_bool, from_none], obj.get("reportProgress")) + return SessionSettingsBuiltInToolAvailabilitySnapshot(create_pull_request, report_progress) + + def to_dict(self) -> dict: + result: dict = {} + if self.create_pull_request is not None: + result["createPullRequest"] = from_union([from_bool, from_none], self.create_pull_request) + if self.report_progress is not None: + result["reportProgress"] = from_union([from_bool, from_none], self.report_progress) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +class SessionSettingsPredicateName(Enum): + """Predicate name. The runtime owns the raw feature-flag names and composition logic. + + Rust-owned settings predicates exposed across the SDK boundary. Raw feature-flag names + are intentionally not part of the contract. + """ + CAP_CLAUDE_OPUS_TOKEN_LIMITS_ENABLED = "capClaudeOpusTokenLimitsEnabled" + CCA_USE_TS_AUTOFIND_ENABLED = "ccaUseTsAutofindEnabled" + CHRONICLE_ENABLED = "chronicleEnabled" + CODEQL_CHECKER_ENABLED = "codeqlCheckerEnabled" + CODE_REVIEW_FEATURE_ENABLED = "codeReviewFeatureEnabled" + CONTENT_EXCLUSION_SELF_FETCH_ENABLED = "contentExclusionSelfFetchEnabled" + CO_AUTHOR_HOOK_ENABLED = "coAuthorHookEnabled" + DEPENDABOT_CHECKER_ENABLED = "dependabotCheckerEnabled" + DEPENDENCY_CHECKER_ENABLED = "dependencyCheckerEnabled" + PARALLEL_VALIDATION_ENABLED = "parallelValidationEnabled" + RUNTIME_TIMING_TELEMETRY_ENABLED = "runtimeTimingTelemetryEnabled" + SECURITY_TOOLS_ENABLED = "securityToolsEnabled" + THIRD_PARTY_SECURITY_PROMPT_ENABLED = "thirdPartySecurityPromptEnabled" + TRIVIAL_CHANGE_ENABLED = "trivialChangeEnabled" + TRIVIAL_CHANGE_ENABLED_FOR_CODE_REVIEW = "trivialChangeEnabledForCodeReview" + TRIVIAL_CHANGE_ENABLED_FOR_TOOL = "trivialChangeEnabledForTool" + TRIVIAL_CHANGE_SKIP_ENABLED = "trivialChangeSkipEnabled" + TRIVIAL_CHANGE_SKIP_ENABLED_FOR_CODE_REVIEW = "trivialChangeSkipEnabledForCodeReview" + TRIVIAL_CHANGE_SKIP_ENABLED_FOR_TOOL = "trivialChangeSkipEnabledForTool" + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class SessionSettingsEvaluatePredicateResult: + """Result of evaluating a Rust-owned settings predicate.""" + + enabled: bool + + @staticmethod + def from_dict(obj: Any) -> 'SessionSettingsEvaluatePredicateResult': + assert isinstance(obj, dict) + enabled = from_bool(obj.get("enabled")) + return SessionSettingsEvaluatePredicateResult(enabled) + + def to_dict(self) -> dict: + result: dict = {} + result["enabled"] = from_bool(self.enabled) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class SessionSettingsModelSnapshot: + """Redacted model routing settings for a session.""" + + callback_url: str | None = None + default_reasoning_effort: str | None = None + instance_id: str | None = None + model: str | None = None + + @staticmethod + def from_dict(obj: Any) -> 'SessionSettingsModelSnapshot': + assert isinstance(obj, dict) + callback_url = from_union([from_str, from_none], obj.get("callbackUrl")) + default_reasoning_effort = from_union([from_str, from_none], obj.get("defaultReasoningEffort")) + instance_id = from_union([from_str, from_none], obj.get("instanceId")) + model = from_union([from_str, from_none], obj.get("model")) + return SessionSettingsModelSnapshot(callback_url, default_reasoning_effort, instance_id, model) + + def to_dict(self) -> dict: + result: dict = {} + if self.callback_url is not None: + result["callbackUrl"] = from_union([from_str, from_none], self.callback_url) + if self.default_reasoning_effort is not None: + result["defaultReasoningEffort"] = from_union([from_str, from_none], self.default_reasoning_effort) + if self.instance_id is not None: + result["instanceId"] = from_union([from_str, from_none], self.instance_id) + if self.model is not None: + result["model"] = from_union([from_str, from_none], self.model) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class SessionSettingsOnlineEvaluationSnapshot: + """Online-evaluation settings safe to expose across the SDK boundary.""" + + disable_online_evaluation: bool | None = None + enable_online_evaluation_output_file: bool | None = None + + @staticmethod + def from_dict(obj: Any) -> 'SessionSettingsOnlineEvaluationSnapshot': + assert isinstance(obj, dict) + disable_online_evaluation = from_union([from_bool, from_none], obj.get("disableOnlineEvaluation")) + enable_online_evaluation_output_file = from_union([from_bool, from_none], obj.get("enableOnlineEvaluationOutputFile")) + return SessionSettingsOnlineEvaluationSnapshot(disable_online_evaluation, enable_online_evaluation_output_file) + + def to_dict(self) -> dict: + result: dict = {} + if self.disable_online_evaluation is not None: + result["disableOnlineEvaluation"] = from_union([from_bool, from_none], self.disable_online_evaluation) + if self.enable_online_evaluation_output_file is not None: + result["enableOnlineEvaluationOutputFile"] = from_union([from_bool, from_none], self.enable_online_evaluation_output_file) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class SessionSettingsRepoSnapshot: + """Redacted repository and GitHub host settings for a session.""" + + branch: str | None = None + commit: str | None = None + host: str | None = None + host_protocol: str | None = None + id: float | None = None + name: str | None = None + owner_id: float | None = None + owner_name: str | None = None + pr_commit_count: float | None = None + read_write: bool | None = None + secret_scanning_url: str | None = None + server_url: str | None = None + + @staticmethod + def from_dict(obj: Any) -> 'SessionSettingsRepoSnapshot': + assert isinstance(obj, dict) + branch = from_union([from_str, from_none], obj.get("branch")) + commit = from_union([from_str, from_none], obj.get("commit")) + host = from_union([from_str, from_none], obj.get("host")) + host_protocol = from_union([from_str, from_none], obj.get("hostProtocol")) + id = from_union([from_float, from_none], obj.get("id")) + name = from_union([from_str, from_none], obj.get("name")) + owner_id = from_union([from_float, from_none], obj.get("ownerId")) + owner_name = from_union([from_str, from_none], obj.get("ownerName")) + pr_commit_count = from_union([from_float, from_none], obj.get("prCommitCount")) + read_write = from_union([from_bool, from_none], obj.get("readWrite")) + secret_scanning_url = from_union([from_str, from_none], obj.get("secretScanningUrl")) + server_url = from_union([from_str, from_none], obj.get("serverUrl")) + return SessionSettingsRepoSnapshot(branch, commit, host, host_protocol, id, name, owner_id, owner_name, pr_commit_count, read_write, secret_scanning_url, server_url) + + def to_dict(self) -> dict: + result: dict = {} + if self.branch is not None: + result["branch"] = from_union([from_str, from_none], self.branch) + if self.commit is not None: + result["commit"] = from_union([from_str, from_none], self.commit) + if self.host is not None: + result["host"] = from_union([from_str, from_none], self.host) + if self.host_protocol is not None: + result["hostProtocol"] = from_union([from_str, from_none], self.host_protocol) + if self.id is not None: + result["id"] = from_union([to_float, from_none], self.id) + if self.name is not None: + result["name"] = from_union([from_str, from_none], self.name) + if self.owner_id is not None: + result["ownerId"] = from_union([to_float, from_none], self.owner_id) + if self.owner_name is not None: + result["ownerName"] = from_union([from_str, from_none], self.owner_name) + if self.pr_commit_count is not None: + result["prCommitCount"] = from_union([to_float, from_none], self.pr_commit_count) + if self.read_write is not None: + result["readWrite"] = from_union([from_bool, from_none], self.read_write) + if self.secret_scanning_url is not None: + result["secretScanningUrl"] = from_union([from_str, from_none], self.secret_scanning_url) + if self.server_url is not None: + result["serverUrl"] = from_union([from_str, from_none], self.server_url) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class SessionSettingsValidationSnapshot: + """Redacted validation and memory-tool settings for a session.""" + + advisory_enabled: bool | None = None + codeql_enabled: bool | None = None + code_review_enabled: bool | None = None + code_review_model: str | None = None + dependabot_timeout: float | None = None + memory_store_enabled: bool | None = None + memory_vote_enabled: bool | None = None + secret_scanning_enabled: bool | None = None + timeout: float | None = None + + @staticmethod + def from_dict(obj: Any) -> 'SessionSettingsValidationSnapshot': + assert isinstance(obj, dict) + advisory_enabled = from_union([from_bool, from_none], obj.get("advisoryEnabled")) + codeql_enabled = from_union([from_bool, from_none], obj.get("codeqlEnabled")) + code_review_enabled = from_union([from_bool, from_none], obj.get("codeReviewEnabled")) + code_review_model = from_union([from_str, from_none], obj.get("codeReviewModel")) + dependabot_timeout = from_union([from_float, from_none], obj.get("dependabotTimeout")) + memory_store_enabled = from_union([from_bool, from_none], obj.get("memoryStoreEnabled")) + memory_vote_enabled = from_union([from_bool, from_none], obj.get("memoryVoteEnabled")) + secret_scanning_enabled = from_union([from_bool, from_none], obj.get("secretScanningEnabled")) + timeout = from_union([from_float, from_none], obj.get("timeout")) + return SessionSettingsValidationSnapshot(advisory_enabled, codeql_enabled, code_review_enabled, code_review_model, dependabot_timeout, memory_store_enabled, memory_vote_enabled, secret_scanning_enabled, timeout) + + def to_dict(self) -> dict: + result: dict = {} + if self.advisory_enabled is not None: + result["advisoryEnabled"] = from_union([from_bool, from_none], self.advisory_enabled) + if self.codeql_enabled is not None: + result["codeqlEnabled"] = from_union([from_bool, from_none], self.codeql_enabled) + if self.code_review_enabled is not None: + result["codeReviewEnabled"] = from_union([from_bool, from_none], self.code_review_enabled) + if self.code_review_model is not None: + result["codeReviewModel"] = from_union([from_str, from_none], self.code_review_model) + if self.dependabot_timeout is not None: + result["dependabotTimeout"] = from_union([to_float, from_none], self.dependabot_timeout) + if self.memory_store_enabled is not None: + result["memoryStoreEnabled"] = from_union([from_bool, from_none], self.memory_store_enabled) + if self.memory_vote_enabled is not None: + result["memoryVoteEnabled"] = from_union([from_bool, from_none], self.memory_vote_enabled) + if self.secret_scanning_enabled is not None: + result["secretScanningEnabled"] = from_union([from_bool, from_none], self.secret_scanning_enabled) + if self.timeout is not None: + result["timeout"] = from_union([to_float, from_none], self.timeout) + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class SessionSizes: @@ -7616,33 +8250,6 @@ class SessionsOpenResumeKind(Enum): class SessionsOpenResumeLastKind(Enum): RESUME_LAST = "resumeLast" -# Experimental: this type is part of an experimental API and may change or be removed. -@dataclass -class SessionsPollSpawnedSessionsRequest: - cursor: str | None = None - """Opaque cursor returned by a previous poll. Omit on the first call to receive any spawn - events buffered since the runtime started. - """ - wait_ms: int | None = None - """Milliseconds to wait for new spawn events when the cursor is at the tail. 0 (default) - returns immediately even if no events are buffered. Capped at 60000ms. - """ - - @staticmethod - def from_dict(obj: Any) -> 'SessionsPollSpawnedSessionsRequest': - assert isinstance(obj, dict) - cursor = from_union([from_str, from_none], obj.get("cursor")) - wait_ms = from_union([from_int, from_none], obj.get("waitMs")) - return SessionsPollSpawnedSessionsRequest(cursor, wait_ms) - - def to_dict(self) -> dict: - result: dict = {} - if self.cursor is not None: - result["cursor"] = from_union([from_str, from_none], self.cursor) - if self.wait_ms is not None: - result["waitMs"] = from_union([from_int, from_none], self.wait_ms) - return result - # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class SessionsPruneOldRequest: @@ -8035,8 +8642,9 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class Skill: - """Schema for the `Skill` type.""" - + """Skill metadata available to a session, with name, description, source, enabled/invocable + state, path, plugin, and argument hint. + """ description: str """Description of what the skill does""" @@ -8199,46 +8807,6 @@ def to_dict(self) -> dict: result["projectPaths"] = from_union([lambda x: from_list(from_str, x), from_none], self.project_paths) return result -# Experimental: this type is part of an experimental API and may change or be removed. -@dataclass -class SkillsInvokedSkill: - """Schema for the `SkillsInvokedSkill` type.""" - - content: str - """Full content of the skill file""" - - invoked_at_turn: int - """Turn number when the skill was invoked""" - - name: str - """Unique identifier for the skill""" - - path: str - """Path to the SKILL.md file""" - - allowed_tools: list[str] | None = None - """Tools that should be auto-approved when this skill is active, captured at invocation time""" - - @staticmethod - def from_dict(obj: Any) -> 'SkillsInvokedSkill': - assert isinstance(obj, dict) - content = from_str(obj.get("content")) - invoked_at_turn = from_int(obj.get("invokedAtTurn")) - name = from_str(obj.get("name")) - path = from_str(obj.get("path")) - allowed_tools = from_union([lambda x: from_list(from_str, x), from_none], obj.get("allowedTools")) - return SkillsInvokedSkill(content, invoked_at_turn, name, path, allowed_tools) - - def to_dict(self) -> dict: - result: dict = {} - result["content"] = from_str(self.content) - result["invokedAtTurn"] = from_int(self.invoked_at_turn) - result["name"] = from_str(self.name) - result["path"] = from_str(self.path) - if self.allowed_tools is not None: - result["allowedTools"] = from_union([lambda x: from_list(from_str, x), from_none], self.allowed_tools) - return result - # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class SkillsLoadDiagnostics: @@ -8278,8 +8846,9 @@ class SlashCommandInvocationResultKind(Enum): # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class SlashCommandSelectSubcommandOption: - """Schema for the `SlashCommandSelectSubcommandOption` type.""" - + """Selectable slash-command subcommand option with name, description, and optional group + label. + """ description: str """Human-readable description of the subcommand""" @@ -8339,7 +8908,7 @@ class TaskAgentInfoType(Enum): # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class TaskProgressLine: - """Schema for the `TaskProgressLine` type.""" + """Timestamped display line for task progress output or recent agent activity.""" message: str """Display message, e.g., "▸ bash", "✓ edit src/foo.ts\"""" @@ -8752,8 +9321,9 @@ class TokenAuthInfoType(Enum): # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class Tool: - """Schema for the `Tool` type.""" - + """Built-in tool metadata with identifier, optional namespaced name, description, + input-parameter schema, and usage instructions. + """ description: str """Description of what the tool does""" @@ -8855,8 +9425,9 @@ class UIAutoModeSwitchResponse(Enum): # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class UIElicitationArrayAnyOfFieldItemsAnyOf: - """Schema for the `UIElicitationArrayAnyOfFieldItemsAnyOf` type.""" - + """Selectable option for a UI elicitation multi-select array item, with submitted value and + display label. + """ const: str """Value submitted when this option is selected.""" @@ -8894,8 +9465,9 @@ class UIElicitationSchemaPropertyStringFormat(Enum): # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class UIElicitationStringOneOfFieldOneOf: - """Schema for the `UIElicitationStringOneOfFieldOneOf` type.""" - + """Selectable option for a UI elicitation single-select string field, with submitted value + and display label. + """ const: str """Value submitted when this option is selected.""" @@ -9060,8 +9632,9 @@ class UISessionLimitsExhaustedResponseAction(Enum): # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class UIUserInputResponse: - """Schema for the `UIUserInputResponse` type.""" - + """User response for a pending user-input request, with answer text and whether it was typed + freeform. + """ answer: str """The user's answer text""" @@ -9210,7 +9783,7 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class UsageMetricsModelMetricTokenDetail: - """Schema for the `UsageMetricsModelMetricTokenDetail` type.""" + """Per-model token-detail entry containing the accumulated token count for one token type.""" token_count: int """Accumulated token count for this token type""" @@ -9269,7 +9842,7 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class UsageMetricsTokenDetail: - """Schema for the `UsageMetricsTokenDetail` type.""" + """Session-wide token-detail entry containing the accumulated token count for one token type.""" token_count: int """Accumulated token count for this token type""" @@ -9384,37 +9957,8 @@ class WorkspaceDiffMode(Enum): # Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class WorkspacesCheckpoints: - """Schema for the `WorkspacesCheckpoints` type.""" - - filename: str - """Filename of the checkpoint within the workspace checkpoints directory""" - - number: int - """Checkpoint number assigned by the workspace manager""" - - title: str - """Human-readable checkpoint title""" - - @staticmethod - def from_dict(obj: Any) -> 'WorkspacesCheckpoints': - assert isinstance(obj, dict) - filename = from_str(obj.get("filename")) - number = from_int(obj.get("number")) - title = from_str(obj.get("title")) - return WorkspacesCheckpoints(filename, number, title) - - def to_dict(self) -> dict: - result: dict = {} - result["filename"] = from_str(self.filename) - result["number"] = from_int(self.number) - result["title"] = from_str(self.title) - return result - -# Experimental: this type is part of an experimental API and may change or be removed. -@dataclass -class WorkspacesCreateFileRequest: - """Relative path and UTF-8 content for the workspace file to create or overwrite.""" +class WorkspacesCreateFileRequest: + """Relative path and UTF-8 content for the workspace file to create or overwrite.""" content: str """File content to write as a UTF-8 string""" @@ -9712,8 +10256,9 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class AgentDiscoveryPath: - """Schema for the `AgentDiscoveryPath` type.""" - + """Canonical directory where custom agents can be discovered or created, with scope, + preference, and optional project path. + """ path: str """Absolute path of the search/create directory (may not exist on disk yet)""" @@ -9848,6 +10393,61 @@ def to_dict(self) -> dict: result["field"] = from_union([lambda x: to_enum(AgentRegistrySpawnValidationErrorField, x), from_none], self.field) return result +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class AllowAllPermissionSetResult: + """Indicates whether the operation succeeded and reports the post-mutation state.""" + + enabled: bool + """Authoritative full allow-all state after the mutation""" + + success: bool + """Whether the operation succeeded""" + + mode: PermissionsAllowAllMode | None = None + """Authoritative allow-all mode after the mutation""" + + @staticmethod + def from_dict(obj: Any) -> 'AllowAllPermissionSetResult': + assert isinstance(obj, dict) + enabled = from_bool(obj.get("enabled")) + success = from_bool(obj.get("success")) + mode = from_union([PermissionsAllowAllMode, from_none], obj.get("mode")) + return AllowAllPermissionSetResult(enabled, success, mode) + + def to_dict(self) -> dict: + result: dict = {} + result["enabled"] = from_bool(self.enabled) + result["success"] = from_bool(self.success) + if self.mode is not None: + result["mode"] = from_union([lambda x: to_enum(PermissionsAllowAllMode, x), from_none], self.mode) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class AllowAllPermissionState: + """Current allow-all permission mode.""" + + enabled: bool + """Whether full allow-all permissions are currently active""" + + mode: PermissionsAllowAllMode | None = None + """Current allow-all mode""" + + @staticmethod + def from_dict(obj: Any) -> 'AllowAllPermissionState': + assert isinstance(obj, dict) + enabled = from_bool(obj.get("enabled")) + mode = from_union([PermissionsAllowAllMode, from_none], obj.get("mode")) + return AllowAllPermissionState(enabled, mode) + + def to_dict(self) -> dict: + result: dict = {} + result["enabled"] = from_bool(self.enabled) + if self.mode is not None: + result["mode"] = from_union([lambda x: to_enum(PermissionsAllowAllMode, x), from_none], self.mode) + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class DiscoveredCanvas: @@ -9927,6 +10527,10 @@ class SlashCommandInput: hint: str """Hint to display when command input has not been provided""" + choices: list[SlashCommandInputChoice] | None = None + """Optional literal choices the input accepts, each with a human-facing description; clients + may render these as selectable options + """ completion: SlashCommandInputCompletion | None = None """Optional completion hint for the input (e.g. 'directory' for filesystem path completion)""" @@ -9943,14 +10547,17 @@ class SlashCommandInput: def from_dict(obj: Any) -> 'SlashCommandInput': assert isinstance(obj, dict) hint = from_str(obj.get("hint")) + choices = from_union([lambda x: from_list(SlashCommandInputChoice.from_dict, x), from_none], obj.get("choices")) completion = from_union([SlashCommandInputCompletion, from_none], obj.get("completion")) preserve_multiline_input = from_union([from_bool, from_none], obj.get("preserveMultilineInput")) required = from_union([from_bool, from_none], obj.get("required")) - return SlashCommandInput(hint, completion, preserve_multiline_input, required) + return SlashCommandInput(hint, choices, completion, preserve_multiline_input, required) def to_dict(self) -> dict: result: dict = {} result["hint"] = from_str(self.hint) + if self.choices is not None: + result["choices"] = from_union([lambda x: from_list(lambda x: to_class(SlashCommandInputChoice, x), x), from_none], self.choices) if self.completion is not None: result["completion"] = from_union([lambda x: to_enum(SlashCommandInputCompletion, x), from_none], self.completion) if self.preserve_multiline_input is not None: @@ -10062,6 +10669,32 @@ def to_dict(self) -> dict: result["summary"] = from_union([from_str, from_none], self.summary) return result +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class MetadataContextHeaviestMessagesResult: + """The heaviest individual messages in the session's context window, most-expensive first.""" + + messages: list[ContextHeaviestMessage] + """Heaviest messages, most-expensive first.""" + + total_tokens: int + """Total token count of the current context window, so callers can compute each message's + share without a second call. + """ + + @staticmethod + def from_dict(obj: Any) -> 'MetadataContextHeaviestMessagesResult': + assert isinstance(obj, dict) + messages = from_list(ContextHeaviestMessage.from_dict, obj.get("messages")) + total_tokens = from_int(obj.get("totalTokens")) + return MetadataContextHeaviestMessagesResult(messages, total_tokens) + + def to_dict(self) -> dict: + result: dict = {} + result["messages"] = from_list(lambda x: to_class(ContextHeaviestMessage, x), self.messages) + result["totalTokens"] = from_int(self.total_tokens) + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class CanvasHostContextCapabilities: @@ -10104,69 +10737,70 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class CopilotUserResponseQuotaSnapshots: - """Schema for the `CopilotUserResponseQuotaSnapshotsChat` type. +class DebugCollectLogsCollectedEntry: + """A file included in the redacted debug bundle.""" + + bundle_path: str + """Relative path of the file in the staged bundle/archive.""" + + size_bytes: int + """Redacted output size in bytes.""" + + source: DebugCollectLogsSource + """Source category for this entry.""" + + @staticmethod + def from_dict(obj: Any) -> 'DebugCollectLogsCollectedEntry': + assert isinstance(obj, dict) + bundle_path = from_str(obj.get("bundlePath")) + size_bytes = from_int(obj.get("sizeBytes")) + source = DebugCollectLogsSource(obj.get("source")) + return DebugCollectLogsCollectedEntry(bundle_path, size_bytes, source) + + def to_dict(self) -> dict: + result: dict = {} + result["bundlePath"] = from_str(self.bundle_path) + result["sizeBytes"] = from_int(self.size_bytes) + result["source"] = to_enum(DebugCollectLogsSource, self.source) + return result - Schema for the `CopilotUserResponseQuotaSnapshotsCompletions` type. +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class DebugCollectLogsDestination: + """Destination for the redacted debug bundle. - Schema for the `CopilotUserResponseQuotaSnapshotsPremiumInteractions` type. + Where the redacted bundle should be written. Use `archive` to produce a .tgz, or + `directory` to stage redacted files for caller-managed upload/post-processing. """ - entitlement: float | None = None - has_quota: bool | None = None - overage_count: float | None = None - overage_permitted: bool | None = None - percent_remaining: float | None = None - quota_id: str | None = None - quota_remaining: float | None = None - quota_reset_at: float | None = None - remaining: float | None = None - timestamp_utc: str | None = None - token_based_billing: bool | None = None - unlimited: bool | None = None + kind: DebugCollectLogsResultKind + no_overwrite: bool | None = None + """When true, create the archive atomically without overwriting an existing file by + appending ` (N)` before the extension as needed. Defaults to false. + """ + output_path: str | None = None + """Absolute or server-relative path for the .tgz archive to create.""" + + output_directory: str | None = None + """Directory where redacted files should be staged. The directory is created if needed.""" @staticmethod - def from_dict(obj: Any) -> 'CopilotUserResponseQuotaSnapshots': + def from_dict(obj: Any) -> 'DebugCollectLogsDestination': assert isinstance(obj, dict) - entitlement = from_union([from_float, from_none], obj.get("entitlement")) - has_quota = from_union([from_bool, from_none], obj.get("has_quota")) - overage_count = from_union([from_float, from_none], obj.get("overage_count")) - overage_permitted = from_union([from_bool, from_none], obj.get("overage_permitted")) - percent_remaining = from_union([from_float, from_none], obj.get("percent_remaining")) - quota_id = from_union([from_str, from_none], obj.get("quota_id")) - quota_remaining = from_union([from_float, from_none], obj.get("quota_remaining")) - quota_reset_at = from_union([from_float, from_none], obj.get("quota_reset_at")) - remaining = from_union([from_float, from_none], obj.get("remaining")) - timestamp_utc = from_union([from_str, from_none], obj.get("timestamp_utc")) - token_based_billing = from_union([from_bool, from_none], obj.get("token_based_billing")) - unlimited = from_union([from_bool, from_none], obj.get("unlimited")) - return CopilotUserResponseQuotaSnapshots(entitlement, has_quota, overage_count, overage_permitted, percent_remaining, quota_id, quota_remaining, quota_reset_at, remaining, timestamp_utc, token_based_billing, unlimited) + kind = DebugCollectLogsResultKind(obj.get("kind")) + no_overwrite = from_union([from_bool, from_none], obj.get("noOverwrite")) + output_path = from_union([from_str, from_none], obj.get("outputPath")) + output_directory = from_union([from_str, from_none], obj.get("outputDirectory")) + return DebugCollectLogsDestination(kind, no_overwrite, output_path, output_directory) def to_dict(self) -> dict: result: dict = {} - if self.entitlement is not None: - result["entitlement"] = from_union([to_float, from_none], self.entitlement) - if self.has_quota is not None: - result["has_quota"] = from_union([from_bool, from_none], self.has_quota) - if self.overage_count is not None: - result["overage_count"] = from_union([to_float, from_none], self.overage_count) - if self.overage_permitted is not None: - result["overage_permitted"] = from_union([from_bool, from_none], self.overage_permitted) - if self.percent_remaining is not None: - result["percent_remaining"] = from_union([to_float, from_none], self.percent_remaining) - if self.quota_id is not None: - result["quota_id"] = from_union([from_str, from_none], self.quota_id) - if self.quota_remaining is not None: - result["quota_remaining"] = from_union([to_float, from_none], self.quota_remaining) - if self.quota_reset_at is not None: - result["quota_reset_at"] = from_union([to_float, from_none], self.quota_reset_at) - if self.remaining is not None: - result["remaining"] = from_union([to_float, from_none], self.remaining) - if self.timestamp_utc is not None: - result["timestamp_utc"] = from_union([from_str, from_none], self.timestamp_utc) - if self.token_based_billing is not None: - result["token_based_billing"] = from_union([from_bool, from_none], self.token_based_billing) - if self.unlimited is not None: - result["unlimited"] = from_union([from_bool, from_none], self.unlimited) + result["kind"] = to_enum(DebugCollectLogsResultKind, self.kind) + if self.no_overwrite is not None: + result["noOverwrite"] = from_union([from_bool, from_none], self.no_overwrite) + if self.output_path is not None: + result["outputPath"] = from_union([from_str, from_none], self.output_path) + if self.output_directory is not None: + 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. @@ -10268,8 +10902,9 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class Extension: - """Schema for the `Extension` type.""" - + """Discovered extension metadata, including source-qualified ID, name, discovery source, + status, and optional process ID. + """ id: str """Source-qualified ID (e.g., 'project:my-ext', 'user:auth-helper', 'plugin:my-plugin:my-ext') @@ -10603,7 +11238,7 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class SlashCommandTextResult: - """Schema for the `SlashCommandTextResult` type.""" + """Slash-command invocation result containing text output plus Markdown/ANSI rendering flags.""" kind: ClassVar[str] = "text" """Text result discriminator""" @@ -10689,9 +11324,102 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class InstalledPluginSourceGitHub: - """Schema for the `InstalledPluginSourceGitHub` type.""" +class InstalledPluginSource: + """Source descriptor for a direct GitHub plugin install, with `owner/repo`, optional ref, + and optional subpath. + + Source descriptor for a direct URL plugin install, with URL, optional ref, and optional + subpath. + + Source descriptor for a direct local plugin install, with a local filesystem path. + """ + source: PurpleSource + """Constant value. Always "github". + + Constant value. Always "url". + + Constant value. Always "local". + """ + path: str | None = None + ref: str | None = None + repo: str | None = None + url: str | None = None + + @staticmethod + def from_dict(obj: Any) -> 'InstalledPluginSource': + assert isinstance(obj, dict) + source = PurpleSource(obj.get("source")) + path = from_union([from_str, from_none], obj.get("path")) + ref = from_union([from_str, from_none], obj.get("ref")) + repo = from_union([from_str, from_none], obj.get("repo")) + url = from_union([from_str, from_none], obj.get("url")) + return InstalledPluginSource(source, path, ref, repo, url) + + def to_dict(self) -> dict: + result: dict = {} + result["source"] = to_enum(PurpleSource, self.source) + if self.path is not None: + result["path"] = from_union([from_str, from_none], self.path) + if self.ref is not None: + result["ref"] = from_union([from_str, from_none], self.ref) + if self.repo is not None: + result["repo"] = from_union([from_str, from_none], self.repo) + if self.url is not None: + result["url"] = from_union([from_str, from_none], self.url) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class SessionInstalledPluginSource: + """Source descriptor for a direct GitHub plugin install, with `owner/repo`, optional ref, + and optional subpath. + + Source descriptor for a direct URL plugin install, with URL, optional ref, and optional + subpath. + + Source descriptor for a direct local plugin install, with a local filesystem path. + """ + source: PurpleSource + """Constant value. Always "github". + + Constant value. Always "url". + + Constant value. Always "local". + """ + path: str | None = None + ref: str | None = None + repo: str | None = None + url: str | None = None + + @staticmethod + def from_dict(obj: Any) -> 'SessionInstalledPluginSource': + assert isinstance(obj, dict) + source = PurpleSource(obj.get("source")) + path = from_union([from_str, from_none], obj.get("path")) + ref = from_union([from_str, from_none], obj.get("ref")) + repo = from_union([from_str, from_none], obj.get("repo")) + url = from_union([from_str, from_none], obj.get("url")) + return SessionInstalledPluginSource(source, path, ref, repo, url) + + def to_dict(self) -> dict: + result: dict = {} + result["source"] = to_enum(PurpleSource, self.source) + if self.path is not None: + result["path"] = from_union([from_str, from_none], self.path) + if self.ref is not None: + result["ref"] = from_union([from_str, from_none], self.ref) + if self.repo is not None: + result["repo"] = from_union([from_str, from_none], self.repo) + if self.url is not None: + result["url"] = from_union([from_str, from_none], self.url) + return result +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class InstalledPluginSourceGitHub: + """Source descriptor for a direct GitHub plugin install, with `owner/repo`, optional ref, + and optional subpath. + """ repo: str source: FluffySource """Constant value. Always "github".""" @@ -10721,8 +11449,9 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class SessionInstalledPluginSourceGitHub: - """Schema for the `SessionInstalledPluginSourceGitHub` type.""" - + """Source descriptor for a direct GitHub plugin install, with `owner/repo`, optional ref, + and optional subpath. + """ repo: str source: FluffySource """Constant value. Always "github".""" @@ -10752,7 +11481,7 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class InstalledPluginSourceLocal: - """Schema for the `InstalledPluginSourceLocal` type.""" + """Source descriptor for a direct local plugin install, with a local filesystem path.""" path: str source: TentacledSource @@ -10774,7 +11503,7 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class SessionInstalledPluginSourceLocal: - """Schema for the `SessionInstalledPluginSourceLocal` type.""" + """Source descriptor for a direct local plugin install, with a local filesystem path.""" path: str source: TentacledSource @@ -10796,8 +11525,9 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class InstalledPluginSourceURL: - """Schema for the `InstalledPluginSourceUrl` type.""" - + """Source descriptor for a direct URL plugin install, with URL, optional ref, and optional + subpath. + """ source: StickySource """Constant value. Always "url".""" @@ -10827,8 +11557,9 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class SessionInstalledPluginSourceURL: - """Schema for the `SessionInstalledPluginSourceUrl` type.""" - + """Source descriptor for a direct URL plugin install, with URL, optional ref, and optional + subpath. + """ source: StickySource """Constant value. Always "url".""" @@ -10857,76 +11588,12 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class SessionFSReaddirWithTypesEntry: - """Schema for the `SessionFsReaddirWithTypesEntry` type.""" - - name: str - """Entry name""" - - type: InstructionDiscoveryPathKind - """Entry type""" - - @staticmethod - def from_dict(obj: Any) -> 'SessionFSReaddirWithTypesEntry': - assert isinstance(obj, dict) - name = from_str(obj.get("name")) - type = InstructionDiscoveryPathKind(obj.get("type")) - return SessionFSReaddirWithTypesEntry(name, type) - - def to_dict(self) -> dict: - result: dict = {} - result["name"] = from_str(self.name) - result["type"] = to_enum(InstructionDiscoveryPathKind, self.type) - return result - -# Experimental: this type is part of an experimental API and may change or be removed. -@dataclass -class InstructionDiscoveryPath: - """Schema for the `InstructionDiscoveryPath` type.""" - - kind: InstructionDiscoveryPathKind - """Whether the target is a single file or a directory of instruction files""" - - location: InstructionLocation - """Which tier this target belongs to""" - - path: str - """Absolute path of the file or directory (may not exist on disk yet)""" - - preferred_for_creation: bool - """Whether this is the canonical target to create new instructions in its tier. At most one - entry per tier is preferred. - """ - project_path: str | None = None - """The input project path this target was derived from (only for repository targets)""" - - @staticmethod - def from_dict(obj: Any) -> 'InstructionDiscoveryPath': - assert isinstance(obj, dict) - kind = InstructionDiscoveryPathKind(obj.get("kind")) - location = InstructionLocation(obj.get("location")) - path = from_str(obj.get("path")) - preferred_for_creation = from_bool(obj.get("preferredForCreation")) - project_path = from_union([from_str, from_none], obj.get("projectPath")) - return InstructionDiscoveryPath(kind, location, path, preferred_for_creation, project_path) - - def to_dict(self) -> dict: - result: dict = {} - result["kind"] = to_enum(InstructionDiscoveryPathKind, self.kind) - result["location"] = to_enum(InstructionLocation, self.location) - result["path"] = from_str(self.path) - result["preferredForCreation"] = from_bool(self.preferred_for_creation) - if self.project_path is not None: - result["projectPath"] = from_union([from_str, from_none], self.project_path) - return result - -# Experimental: this type is part of an experimental API and may change or be removed. -@dataclass -class InstructionSource: - """Schema for the `InstructionSource` type.""" - - content: str - """Raw content of the instruction file""" +class InstructionSource: + """Loaded instruction source for a session, including path, content, category, location, + applicability, and optional description. + """ + content: str + """Raw content of the instruction file""" id: str """Unique identifier for this source (used for toggling)""" @@ -11009,6 +11676,35 @@ class LlmInferenceHTTPRequestStartRequest: url: str """Absolute request URL.""" + agent_id: str | None = None + """Stable per-agent-instance id attributing this request to a specific agent trajectory. + Present when the request originates from an agent turn; absent for requests issued + outside any agent context (e.g. some SDK callers). A request with an `agentId` but no + `parentAgentId` is a root-agent request; one carrying both is a subagent request. Sourced + from the runtime's per-request agent context and surfaced on the envelope independently + of transport, so it is available for both first-party (CAPI) and BYOK/custom-provider + requests; on the CAPI transport the runtime derives the upstream `X-Agent-Task-Id` header + from this same context. Consumers routing each provider call to a training trajectory + should key on this rather than on lifecycle events, since it is available on the request + path before sampling. + """ + interaction_type: str | None = None + """Coarse classification of the interaction that produced this request. Open string for + forward-compatibility; known values include `conversation-agent`, + `conversation-subagent`, `conversation-sampling`, `conversation-background`, + `conversation-compaction`, and `conversation-user`. Absent when the runtime did not + classify the request. Comes from the runtime's per-request agent context independently of + transport; on the CAPI transport the runtime derives the upstream `X-Interaction-Type` + header from this same context. + """ + parent_agent_id: str | None = None + """Id of the parent agent that spawned the agent issuing this request. Present only for + subagent requests; absent for root-agent requests and non-agent requests. Combined with + `agentId`, this lets consumers attribute a call to a child trajectory versus the root. + Like `agentId`, it comes from the runtime's per-request agent context independently of + transport; on the CAPI transport the runtime derives the upstream `X-Parent-Agent-Id` + header from this same context. + """ session_id: str | None = None """Id of the runtime session that triggered this request, when one is in scope. Absent for requests issued outside any session (e.g. startup model-catalog or capability @@ -11031,9 +11727,12 @@ def from_dict(obj: Any) -> 'LlmInferenceHTTPRequestStartRequest': method = from_str(obj.get("method")) request_id = from_str(obj.get("requestId")) url = from_str(obj.get("url")) + agent_id = from_union([from_str, from_none], obj.get("agentId")) + interaction_type = from_union([from_str, from_none], obj.get("interactionType")) + parent_agent_id = from_union([from_str, from_none], obj.get("parentAgentId")) session_id = from_union([from_str, from_none], obj.get("sessionId")) transport = from_union([LlmInferenceHTTPRequestStartTransport, from_none], obj.get("transport")) - return LlmInferenceHTTPRequestStartRequest(headers, method, request_id, url, session_id, transport) + return LlmInferenceHTTPRequestStartRequest(headers, method, request_id, url, agent_id, interaction_type, parent_agent_id, session_id, transport) def to_dict(self) -> dict: result: dict = {} @@ -11041,6 +11740,12 @@ def to_dict(self) -> dict: result["method"] = from_str(self.method) result["requestId"] = from_str(self.request_id) result["url"] = from_str(self.url) + if self.agent_id is not None: + result["agentId"] = from_union([from_str, from_none], self.agent_id) + if self.interaction_type is not None: + result["interactionType"] = from_union([from_str, from_none], self.interaction_type) + if self.parent_agent_id is not None: + result["parentAgentId"] = from_union([from_str, from_none], self.parent_agent_id) if self.session_id is not None: result["sessionId"] = from_union([from_str, from_none], self.session_id) if self.transport is not None: @@ -12122,6 +12827,53 @@ def to_dict(self) -> dict: result["mode"] = to_enum(MCPSetEnvValueModeDetails, self.mode) return result +# Experimental: this type is part of an experimental API and may change or be removed. +class DebugCollectLogsEntryKind(Enum): + """Kind of source path to include. + + Kind of caller-provided debug log entry. + + Whether the target is a single file or a directory of instruction files + + Entry type + """ + DIRECTORY = "directory" + FILE = "file" + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class SessionContextAttribution: + """Per-source token attribution snapshot for the current context window. The heaviest + individual messages are available separately via `metadata.getContextHeaviestMessages`. + """ + compactions: Compactions + """Successful compaction history for the session.""" + + entries: list[Entry] + """Flat list of per-source attribution entries. Group by `kind` and render unrecognized + kinds generically. Nesting and rollups are expressed via `parentId`. + """ + total_tokens: int + """Total token count of the current context window the entries are measured against (system + message + conversation messages + tool definitions — the same total reported by + /context). Divide an entry's `tokens` by this to derive its share. + """ + + @staticmethod + def from_dict(obj: Any) -> 'SessionContextAttribution': + assert isinstance(obj, dict) + compactions = Compactions.from_dict(obj.get("compactions")) + entries = from_list(Entry.from_dict, obj.get("entries")) + total_tokens = from_int(obj.get("totalTokens")) + return SessionContextAttribution(compactions, entries, total_tokens) + + def to_dict(self) -> dict: + result: dict = {} + result["compactions"] = to_class(Compactions, self.compactions) + result["entries"] = from_list(lambda x: to_class(Entry, x), self.entries) + result["totalTokens"] = from_int(self.total_tokens) + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class MetadataContextInfoResult: @@ -12551,12 +13303,14 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class OptionsUpdateAdditionalContentExclusionPolicyRule: - """Schema for the `OptionsUpdateAdditionalContentExclusionPolicyRule` type.""" - + """Single content-exclusion rule supplied to `session.options.update`, with paths, match + conditions, and source. + """ paths: list[str] source: OptionsUpdateAdditionalContentExclusionPolicyRuleSource - """Schema for the `OptionsUpdateAdditionalContentExclusionPolicyRuleSource` type.""" - + """Source descriptor for a `session.options.update` content-exclusion rule, with source name + and type. + """ if_any_match: list[str] | None = None if_none_match: list[str] | None = None @@ -12605,8 +13359,9 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class PermissionDecisionApproveForLocation: - """Schema for the `PermissionDecisionApproveForLocation` type.""" - + """Permission-decision request variant to approve and persist a permission for a project + location, with approval details and location key. + """ approval: PermissionDecisionApproveForLocationApproval """Approval to persist for this location""" @@ -12633,7 +13388,7 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class PermissionDecisionApproveForLocationApprovalCommands: - """Schema for the `PermissionDecisionApproveForLocationApprovalCommands` type.""" + """Location-scoped approval details for specific command identifiers.""" command_identifiers: list[str] """Command identifiers covered by this approval.""" @@ -12656,7 +13411,7 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class PermissionDecisionApproveForSessionApprovalCommands: - """Schema for the `PermissionDecisionApproveForSessionApprovalCommands` type.""" + """Session-scoped approval details for specific command identifiers.""" command_identifiers: list[str] """Command identifiers covered by this approval.""" @@ -12679,7 +13434,7 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class PermissionsLocationsAddToolApprovalDetailsCommands: - """Schema for the `PermissionsLocationsAddToolApprovalDetailsCommands` type.""" + """Location-persisted tool approval details for specific command identifiers.""" command_identifiers: list[str] """Command identifiers covered by this approval.""" @@ -12702,7 +13457,7 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class PermissionDecisionApproveForLocationApprovalCustomTool: - """Schema for the `PermissionDecisionApproveForLocationApprovalCustomTool` type.""" + """Location-scoped approval details for a custom tool, keyed by tool name.""" kind: ClassVar[str] = "custom-tool" """Approval covering a custom tool.""" @@ -12725,7 +13480,7 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class PermissionDecisionApproveForSessionApprovalCustomTool: - """Schema for the `PermissionDecisionApproveForSessionApprovalCustomTool` type.""" + """Session-scoped approval details for a custom tool, keyed by tool name.""" kind: ClassVar[str] = "custom-tool" """Approval covering a custom tool.""" @@ -12748,7 +13503,7 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class PermissionsLocationsAddToolApprovalDetailsCustomTool: - """Schema for the `PermissionsLocationsAddToolApprovalDetailsCustomTool` type.""" + """Location-persisted tool approval details for a custom tool, keyed by tool name.""" kind: ClassVar[str] = "custom-tool" """Approval covering a custom tool.""" @@ -12771,8 +13526,9 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class PermissionDecisionApproveForLocationApprovalExtensionManagement: - """Schema for the `PermissionDecisionApproveForLocationApprovalExtensionManagement` type.""" - + """Location-scoped approval details for extension-management operations, optionally narrowed + by operation. + """ kind: ClassVar[str] = "extension-management" """Approval covering extension lifecycle operations such as enable, disable, or reload.""" @@ -12797,8 +13553,9 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class PermissionDecisionApproveForSessionApprovalExtensionManagement: - """Schema for the `PermissionDecisionApproveForSessionApprovalExtensionManagement` type.""" - + """Session-scoped approval details for extension-management operations, optionally narrowed + by operation. + """ kind: ClassVar[str] = "extension-management" """Approval covering extension lifecycle operations such as enable, disable, or reload.""" @@ -12823,8 +13580,9 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class PermissionsLocationsAddToolApprovalDetailsExtensionManagement: - """Schema for the `PermissionsLocationsAddToolApprovalDetailsExtensionManagement` type.""" - + """Location-persisted tool approval details for extension-management operations, optionally + narrowed by operation. + """ kind: ClassVar[str] = "extension-management" """Approval covering extension lifecycle operations such as enable, disable, or reload.""" @@ -12849,8 +13607,9 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class PermissionDecisionApproveForLocationApprovalMCP: - """Schema for the `PermissionDecisionApproveForLocationApprovalMcp` type.""" - + """Location-scoped approval details for an MCP server tool, or all tools on the server when + `toolName` is null. + """ kind: ClassVar[str] = "mcp" """Approval covering an MCP tool.""" @@ -12877,8 +13636,9 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class PermissionDecisionApproveForSessionApprovalMCP: - """Schema for the `PermissionDecisionApproveForSessionApprovalMcp` type.""" - + """Session-scoped approval details for an MCP server tool, or all tools on the server when + `toolName` is null. + """ kind: ClassVar[str] = "mcp" """Approval covering an MCP tool.""" @@ -12905,8 +13665,9 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class PermissionsLocationsAddToolApprovalDetailsMCP: - """Schema for the `PermissionsLocationsAddToolApprovalDetailsMcp` type.""" - + """Location-persisted tool approval details for an MCP server tool, or all tools when + `toolName` is null. + """ kind: ClassVar[str] = "mcp" """Approval covering an MCP tool.""" @@ -12933,7 +13694,7 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class PermissionDecisionApproveForLocationApprovalMCPSampling: - """Schema for the `PermissionDecisionApproveForLocationApprovalMcpSampling` type.""" + """Location-scoped approval details for MCP sampling requests from a server.""" kind: ClassVar[str] = "mcp-sampling" """Approval covering MCP sampling requests for a server.""" @@ -12956,7 +13717,7 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class PermissionDecisionApproveForSessionApprovalMCPSampling: - """Schema for the `PermissionDecisionApproveForSessionApprovalMcpSampling` type.""" + """Session-scoped approval details for MCP sampling requests from a server.""" kind: ClassVar[str] = "mcp-sampling" """Approval covering MCP sampling requests for a server.""" @@ -12979,7 +13740,7 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class PermissionsLocationsAddToolApprovalDetailsMCPSampling: - """Schema for the `PermissionsLocationsAddToolApprovalDetailsMcpSampling` type.""" + """Location-persisted tool approval details for MCP sampling requests from a server.""" kind: ClassVar[str] = "mcp-sampling" """Approval covering MCP sampling requests for a server.""" @@ -13002,7 +13763,7 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class PermissionDecisionApproveForLocationApprovalMemory: - """Schema for the `PermissionDecisionApproveForLocationApprovalMemory` type.""" + """Location-scoped approval details for writes to long-term memory.""" kind: ClassVar[str] = "memory" """Approval covering writes to long-term memory.""" @@ -13020,7 +13781,7 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class PermissionDecisionApproveForSessionApprovalMemory: - """Schema for the `PermissionDecisionApproveForSessionApprovalMemory` type.""" + """Session-scoped approval details for writes to long-term memory.""" kind: ClassVar[str] = "memory" """Approval covering writes to long-term memory.""" @@ -13038,7 +13799,7 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class PermissionsLocationsAddToolApprovalDetailsMemory: - """Schema for the `PermissionsLocationsAddToolApprovalDetailsMemory` type.""" + """Location-persisted tool approval details for writes to long-term memory.""" kind: ClassVar[str] = "memory" """Approval covering writes to long-term memory.""" @@ -13056,7 +13817,7 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class PermissionDecisionApproveForLocationApprovalRead: - """Schema for the `PermissionDecisionApproveForLocationApprovalRead` type.""" + """Location-scoped approval details for read-only filesystem operations.""" kind: ClassVar[str] = "read" """Approval covering read-only filesystem operations.""" @@ -13074,7 +13835,7 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class PermissionDecisionApproveForSessionApprovalRead: - """Schema for the `PermissionDecisionApproveForSessionApprovalRead` type.""" + """Session-scoped approval details for read-only filesystem operations.""" kind: ClassVar[str] = "read" """Approval covering read-only filesystem operations.""" @@ -13092,7 +13853,7 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class PermissionsLocationsAddToolApprovalDetailsRead: - """Schema for the `PermissionsLocationsAddToolApprovalDetailsRead` type.""" + """Location-persisted tool approval details for read-only filesystem operations.""" kind: ClassVar[str] = "read" """Approval covering read-only filesystem operations.""" @@ -13110,7 +13871,7 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class PermissionDecisionApproveForLocationApprovalWrite: - """Schema for the `PermissionDecisionApproveForLocationApprovalWrite` type.""" + """Location-scoped approval details for filesystem write operations.""" kind: ClassVar[str] = "write" """Approval covering filesystem write operations.""" @@ -13128,7 +13889,7 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class PermissionDecisionApproveForSessionApprovalWrite: - """Schema for the `PermissionDecisionApproveForSessionApprovalWrite` type.""" + """Session-scoped approval details for filesystem write operations.""" kind: ClassVar[str] = "write" """Approval covering filesystem write operations.""" @@ -13146,7 +13907,7 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class PermissionsLocationsAddToolApprovalDetailsWrite: - """Schema for the `PermissionsLocationsAddToolApprovalDetailsWrite` type.""" + """Location-persisted tool approval details for filesystem write operations.""" kind: ClassVar[str] = "write" """Approval covering filesystem write operations.""" @@ -13164,8 +13925,9 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class PermissionDecisionApproveForSession: - """Schema for the `PermissionDecisionApproveForSession` type.""" - + """Permission-decision request variant to approve for the rest of the session, with optional + tool approval or URL domain. + """ kind: ClassVar[str] = "approve-for-session" """Approve and remember for the rest of the session""" @@ -13194,7 +13956,7 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class PermissionDecisionApproveOnce: - """Schema for the `PermissionDecisionApproveOnce` type.""" + """Permission-decision request variant to approve only the current permission request.""" kind: ClassVar[str] = "approve-once" """Approve this single request only""" @@ -13212,7 +13974,7 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class PermissionDecisionApprovePermanently: - """Schema for the `PermissionDecisionApprovePermanently` type.""" + """Permission-decision request variant to permanently approve a URL domain across sessions.""" domain: str """URL domain to approve permanently""" @@ -13235,7 +13997,7 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class PermissionDecisionApproved: - """Schema for the `PermissionDecisionApproved` type.""" + """Permission-decision variant indicating the request was approved.""" kind: ClassVar[str] = "approved" """The permission request was approved""" @@ -13253,8 +14015,9 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class PermissionDecisionApprovedForLocation: - """Schema for the `PermissionDecisionApprovedForLocation` type.""" - + """Permission-decision variant indicating approval was persisted for a project location, + with approval details and location key. + """ approval: UserToolSessionApproval """The approval to persist for this location""" @@ -13281,8 +14044,9 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class PermissionDecisionApprovedForSession: - """Schema for the `PermissionDecisionApprovedForSession` type.""" - + """Permission-decision variant indicating approval was remembered for the session, with + approval details. + """ approval: UserToolSessionApproval """The approval to add as a session-scoped rule""" @@ -13304,8 +14068,9 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class PermissionDecisionCancelled: - """Schema for the `PermissionDecisionCancelled` type.""" - + """Permission-decision variant indicating the request was cancelled before use, with an + optional reason. + """ kind: ClassVar[str] = "cancelled" """The permission request was cancelled before a response was used""" @@ -13328,8 +14093,9 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class PermissionDecisionDeniedByContentExclusionPolicy: - """Schema for the `PermissionDecisionDeniedByContentExclusionPolicy` type.""" - + """Permission-decision variant indicating denial by content-exclusion policy, with path and + message. + """ kind: ClassVar[str] = "denied-by-content-exclusion-policy" """Denied by the organization's content exclusion policy""" @@ -13356,8 +14122,9 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class PermissionDecisionDeniedByPermissionRequestHook: - """Schema for the `PermissionDecisionDeniedByPermissionRequestHook` type.""" - + """Permission-decision variant indicating denial by a permission request hook, with optional + message and interrupt flag. + """ kind: ClassVar[str] = "denied-by-permission-request-hook" """Denied by a permission request hook registered by an extension or plugin""" @@ -13386,8 +14153,9 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class PermissionDecisionDeniedByRules: - """Schema for the `PermissionDecisionDeniedByRules` type.""" - + """Permission-decision variant indicating explicit denial by permission rules, with the + matching rules. + """ kind: ClassVar[str] = "denied-by-rules" """Denied because approval rules explicitly blocked it""" @@ -13409,8 +14177,9 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class PermissionDecisionDeniedInteractivelyByUser: - """Schema for the `PermissionDecisionDeniedInteractivelyByUser` type.""" - + """Permission-decision variant indicating the user denied an interactive prompt, with + optional feedback and force-reject flag. + """ kind: ClassVar[str] = "denied-interactively-by-user" """Denied by the user during an interactive prompt""" @@ -13439,8 +14208,9 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class PermissionDecisionDeniedNoApprovalRuleAndCouldNotRequestFromUser: - """Schema for the `PermissionDecisionDeniedNoApprovalRuleAndCouldNotRequestFromUser` type.""" - + """Permission-decision variant indicating no approval rule matched and user confirmation was + unavailable. + """ kind: ClassVar[str] = "denied-no-approval-rule-and-could-not-request-from-user" """Denied because no approval rule matched and user confirmation was unavailable""" @@ -13457,8 +14227,9 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class PermissionDecisionReject: - """Schema for the `PermissionDecisionReject` type.""" - + """Permission-decision request variant to reject a pending permission request, with optional + feedback. + """ kind: ClassVar[str] = "reject" """Reject the request""" @@ -13481,7 +14252,7 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class PermissionDecisionUserNotAvailable: - """Schema for the `PermissionDecisionUserNotAvailable` type.""" + """Permission-decision variant indicating no user was available to confirm the request.""" kind: ClassVar[str] = "user-not-available" """No user is available to confirm the request""" @@ -13567,12 +14338,14 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class PermissionsConfigureAdditionalContentExclusionPolicyRule: - """Schema for the `PermissionsConfigureAdditionalContentExclusionPolicyRule` type.""" - + """Single content-exclusion rule supplied to `session.permissions.configure`, with paths, + match conditions, and source. + """ paths: list[str] source: PermissionsConfigureAdditionalContentExclusionPolicyRuleSource - """Schema for the `PermissionsConfigureAdditionalContentExclusionPolicyRuleSource` type.""" - + """Source descriptor for a `session.permissions.configure` content-exclusion rule, with + source name and type. + """ if_any_match: list[str] | None = None if_none_match: list[str] | None = None @@ -13686,8 +14459,9 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class DiscoveredMCPServer: - """Schema for the `DiscoveredMcpServer` type.""" - + """MCP server discovered by `mcp.discover`, with config source, optional plugin source, + transport type, and enabled state. + """ enabled: bool """Whether the server is enabled (not in the disabled list)""" @@ -13804,7 +14578,7 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class MCPServer: - """Schema for the `McpServer` type.""" + """MCP server status entry, including config source/plugin source and any connection error.""" name: str """Server name (config key)""" @@ -13871,8 +14645,9 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class PluginUpdateAllEntry: - """Schema for the `PluginUpdateAllEntry` type.""" - + """Per-plugin result from updating all plugins, with versions, skills installed, success + flag, and optional error. + """ marketplace: str """Marketplace the plugin came from. Empty string ("") for direct installs.""" @@ -14042,30 +14817,6 @@ def to_dict(self) -> dict: result["name"] = from_str(self.name) return result -# Experimental: this type is part of an experimental API and may change or be removed. -@dataclass -class PollSpawnedSessionsResult: - """Batch of spawn events plus a cursor for follow-up polls.""" - - cursor: str - """Opaque cursor to pass back to receive only events after this batch.""" - - events: list[SessionsPollSpawnedSessionsEvent] - """Spawn events emitted since the supplied cursor.""" - - @staticmethod - def from_dict(obj: Any) -> 'PollSpawnedSessionsResult': - assert isinstance(obj, dict) - cursor = from_str(obj.get("cursor")) - events = from_list(SessionsPollSpawnedSessionsEvent.from_dict, obj.get("events")) - return PollSpawnedSessionsResult(cursor, events) - - def to_dict(self) -> dict: - result: dict = {} - result["cursor"] = from_str(self.cursor) - result["events"] = from_list(lambda x: to_class(SessionsPollSpawnedSessionsEvent, x), self.events) - return result - # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class ProviderEndpoint: @@ -14641,8 +15392,9 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class QueuePendingItems: - """Schema for the `QueuePendingItems` type.""" - + """User-facing pending queue entry, with kind and display text for a queued message, slash + command, or model change. + """ display_text: str """Human-readable text to display for this queue entry in the UI""" @@ -14757,6 +15509,12 @@ class RemoteControlStatusActive: state: ClassVar[str] = "active" """Remote control state tag: active.""" + # Internal: this field is an internal SDK API and is not part of the public surface. + awaiting_first_message: bool | None = None + """True while a read-only/session-sync export is deferred, awaiting the first `user.message` + before its MC session exists. Marked internal: this field is excluded from the public SDK + surface and is populated only on the CLI in-process path. + """ frontend_url: str | None = None """MC frontend URL for this session, when known.""" @@ -14773,15 +15531,18 @@ def from_dict(obj: Any) -> 'RemoteControlStatusActive': assert isinstance(obj, dict) attached_session_id = from_str(obj.get("attachedSessionId")) is_steerable = from_bool(obj.get("isSteerable")) + awaiting_first_message = from_union([from_bool, from_none], obj.get("awaitingFirstMessage")) frontend_url = from_union([from_str, from_none], obj.get("frontendUrl")) prompt_manager = obj.get("promptManager") - return RemoteControlStatusActive(attached_session_id, is_steerable, frontend_url, prompt_manager) + return RemoteControlStatusActive(attached_session_id, is_steerable, awaiting_first_message, frontend_url, prompt_manager) def to_dict(self) -> dict: result: dict = {} result["attachedSessionId"] = from_str(self.attached_session_id) result["isSteerable"] = from_bool(self.is_steerable) result["state"] = self.state + if self.awaiting_first_message is not None: + result["awaitingFirstMessage"] = from_union([from_bool, from_none], self.awaiting_first_message) if self.frontend_url is not None: result["frontendUrl"] = from_union([from_str, from_none], self.frontend_url) if self.prompt_manager is not None: @@ -15168,11 +15929,12 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class SessionOpenOptionsAdditionalContentExclusionPolicyRule: - """Schema for the `SessionOpenOptionsAdditionalContentExclusionPolicyRule` type.""" - + """Single content-exclusion rule supplied to `sessions.open` options, with paths, match + conditions, and source. + """ paths: list[str] source: SessionOpenOptionsAdditionalContentExclusionPolicyRuleSource - """Schema for the `SessionOpenOptionsAdditionalContentExclusionPolicyRuleSource` type.""" + """Source descriptor for a `sessions.open` content-exclusion rule, with source name and type.""" if_any_match: list[str] | None = None if_none_match: list[str] | None = None @@ -15199,7 +15961,7 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class SessionsOpenProgress: - """Schema for the `SessionsOpenProgress` type.""" + """`sessions.open` handoff progress update with step, status, and optional message.""" status: SessionsOpenProgressStatus """Step status.""" @@ -15226,6 +15988,33 @@ def to_dict(self) -> dict: result["message"] = from_union([from_str, from_none], self.message) return result +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class SessionSettingsJobSnapshot: + """Redacted job settings for a session. The job nonce is excluded.""" + + built_in_tool_availability: SessionSettingsBuiltInToolAvailabilitySnapshot | None = None + event_type: str | None = None + is_trigger_job: bool | None = None + + @staticmethod + def from_dict(obj: Any) -> 'SessionSettingsJobSnapshot': + assert isinstance(obj, dict) + built_in_tool_availability = from_union([SessionSettingsBuiltInToolAvailabilitySnapshot.from_dict, from_none], obj.get("builtInToolAvailability")) + event_type = from_union([from_str, from_none], obj.get("eventType")) + is_trigger_job = from_union([from_bool, from_none], obj.get("isTriggerJob")) + return SessionSettingsJobSnapshot(built_in_tool_availability, event_type, is_trigger_job) + + def to_dict(self) -> dict: + result: dict = {} + if self.built_in_tool_availability is not None: + result["builtInToolAvailability"] = from_union([lambda x: to_class(SessionSettingsBuiltInToolAvailabilitySnapshot, x), from_none], self.built_in_tool_availability) + if self.event_type is not None: + result["eventType"] = from_union([from_str, from_none], self.event_type) + if self.is_trigger_job is not None: + result["isTriggerJob"] = from_union([from_bool, from_none], self.is_trigger_job) + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class SessionsListRequest: @@ -15424,7 +16213,8 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class AgentInfo: - """Schema for the `AgentInfo` type. + """Custom agent metadata, including identifiers, display details, source, tools, model, MCP + servers, skills, and file path. The newly selected custom agent """ @@ -15545,9 +16335,50 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class SkillDiscoveryPath: - """Schema for the `SkillDiscoveryPath` type.""" +class SkillsInvokedSkill: + """Skill invocation record with name, path, content, allowed tools, and turn number.""" + + content: str + """Full content of the skill file""" + + invoked_at_turn: int + """Turn number when the skill was invoked""" + + name: str + """Unique identifier for the skill""" + + path: str + """Path to the SKILL.md file""" + + allowed_tools: list[str] | None = None + """Tools that should be auto-approved when this skill is active, captured at invocation time""" + + @staticmethod + def from_dict(obj: Any) -> 'SkillsInvokedSkill': + assert isinstance(obj, dict) + content = from_str(obj.get("content")) + invoked_at_turn = from_int(obj.get("invokedAtTurn")) + name = from_str(obj.get("name")) + path = from_str(obj.get("path")) + allowed_tools = from_union([lambda x: from_list(from_str, x), from_none], obj.get("allowedTools")) + return SkillsInvokedSkill(content, invoked_at_turn, name, path, allowed_tools) + + def to_dict(self) -> dict: + result: dict = {} + result["content"] = from_str(self.content) + result["invokedAtTurn"] = from_int(self.invoked_at_turn) + result["name"] = from_str(self.name) + result["path"] = from_str(self.path) + if self.allowed_tools is not None: + result["allowedTools"] = from_union([lambda x: from_list(from_str, x), from_none], self.allowed_tools) + return result +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class SkillDiscoveryPath: + """Canonical directory where skills can be discovered or created, with scope, preference, + and optional project path. + """ path: str """Absolute path of the create/discovery target (may not exist on disk yet)""" @@ -15580,30 +16411,12 @@ def to_dict(self) -> dict: result["projectPath"] = from_union([from_str, from_none], self.project_path) return result -# Experimental: this type is part of an experimental API and may change or be removed. -@dataclass -class SkillsGetInvokedResult: - """Skills invoked during this session, ordered by invocation time (most recent last).""" - - skills: list[SkillsInvokedSkill] - """Skills invoked during this session, ordered by invocation time (most recent last)""" - - @staticmethod - def from_dict(obj: Any) -> 'SkillsGetInvokedResult': - assert isinstance(obj, dict) - skills = from_list(SkillsInvokedSkill.from_dict, obj.get("skills")) - return SkillsGetInvokedResult(skills) - - def to_dict(self) -> dict: - result: dict = {} - result["skills"] = from_list(lambda x: to_class(SkillsInvokedSkill, x), self.skills) - return result - # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class SlashCommandAgentPromptResult: - """Schema for the `SlashCommandAgentPromptResult` type.""" - + """Slash-command invocation result that submits an agent prompt, with display prompt, + optional mode, and settings-change flag. + """ display_prompt: str """Prompt text to display to the user""" @@ -15644,8 +16457,9 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class SlashCommandCompletedResult: - """Schema for the `SlashCommandCompletedResult` type.""" - + """Slash-command invocation result indicating completion, with optional message and + settings-change flag. + """ kind: ClassVar[str] = "completed" """Completed result discriminator""" @@ -15676,8 +16490,9 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class SlashCommandSelectSubcommandResult: - """Schema for the `SlashCommandSelectSubcommandResult` type.""" - + """Slash-command invocation result asking the client to present subcommand options for a + parent command. + """ command: str """Parent command name that requires subcommand selection""" @@ -15717,8 +16532,9 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class TaskAgentProgress: - """Schema for the `TaskAgentProgress` type.""" - + """Progress snapshot for an agent task, with recent activity lines and optional latest + intent. + """ recent_activity: list[TaskProgressLine] """Recent tool execution events converted to display lines""" @@ -15746,27 +16562,75 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class TaskShellInfo: - """Schema for the `TaskShellInfo` type.""" +class TaskProgress: + """Progress snapshot for an agent task, with recent activity lines and optional latest + intent. - attachment_mode: TaskShellInfoAttachmentMode - """Whether the shell runs inside a managed PTY session or as an independent background - process + Progress snapshot for a shell task, with recent stdout/stderr output and optional process + ID. """ - command: str - """Command being executed""" + type: TaskInfoType + """Progress kind""" - description: str - """Short description of the task""" + latest_intent: str | None = None + """The most recent intent reported by the agent""" - id: str - """Unique task identifier""" + recent_activity: list[TaskProgressLine] | None = None + """Recent tool execution events converted to display lines""" - started_at: datetime - """ISO 8601 timestamp when the task was started""" + pid: int | None = None + """Process ID when available""" - status: TaskStatus - """Current lifecycle status of the task""" + recent_output: str | None = None + """Recent stdout/stderr lines from the running shell command""" + + @staticmethod + def from_dict(obj: Any) -> 'TaskProgress': + assert isinstance(obj, dict) + type = TaskInfoType(obj.get("type")) + latest_intent = from_union([from_str, from_none], obj.get("latestIntent")) + recent_activity = from_union([lambda x: from_list(TaskProgressLine.from_dict, x), from_none], obj.get("recentActivity")) + pid = from_union([from_int, from_none], obj.get("pid")) + recent_output = from_union([from_str, from_none], obj.get("recentOutput")) + return TaskProgress(type, latest_intent, recent_activity, pid, recent_output) + + def to_dict(self) -> dict: + result: dict = {} + result["type"] = to_enum(TaskInfoType, self.type) + if self.latest_intent is not None: + result["latestIntent"] = from_union([from_str, from_none], self.latest_intent) + if self.recent_activity is not None: + result["recentActivity"] = from_union([lambda x: from_list(lambda x: to_class(TaskProgressLine, x), x), from_none], self.recent_activity) + if self.pid is not None: + result["pid"] = from_union([from_int, from_none], self.pid) + if self.recent_output is not None: + result["recentOutput"] = from_union([from_str, from_none], self.recent_output) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class TaskShellInfo: + """Tracked shell task metadata, including ID, command, status, timing, attachment/execution + mode, log path, and PID. + """ + attachment_mode: TaskShellInfoAttachmentMode + """Whether the shell runs inside a managed PTY session or as an independent background + process + """ + command: str + """Command being executed""" + + description: str + """Short description of the task""" + + id: str + """Unique task identifier""" + + started_at: datetime + """ISO 8601 timestamp when the task was started""" + + status: TaskStatus + """Current lifecycle status of the task""" type: ClassVar[str] = "shell" """Task kind""" @@ -15826,8 +16690,9 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class TaskShellProgress: - """Schema for the `TaskShellProgress` type.""" - + """Progress snapshot for a shell task, with recent stdout/stderr output and optional process + ID. + """ recent_output: str """Recent stdout/stderr lines from the running shell command""" @@ -15893,7 +16758,7 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class MCPTools: - """Schema for the `McpTools` type.""" + """MCP tool metadata with tool name and optional description.""" name: str """Tool name.""" @@ -15941,9 +16806,35 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class TaskAgentInfo: - """Schema for the `TaskAgentInfo` type.""" +class SessionSettingsEvaluatePredicateRequest: + """Named Rust-owned settings predicate to evaluate for this session.""" + + name: SessionSettingsPredicateName + """Predicate name. The runtime owns the raw feature-flag names and composition logic.""" + + tool_name: str | None = None + """Tool name for tool-scoped predicates such as trivial-change handling.""" + + @staticmethod + def from_dict(obj: Any) -> 'SessionSettingsEvaluatePredicateRequest': + assert isinstance(obj, dict) + name = SessionSettingsPredicateName(obj.get("name")) + tool_name = from_union([from_str, from_none], obj.get("toolName")) + return SessionSettingsEvaluatePredicateRequest(name, tool_name) + + def to_dict(self) -> dict: + result: dict = {} + result["name"] = to_enum(SessionSettingsPredicateName, self.name) + if self.tool_name is not None: + result["toolName"] = from_union([from_str, from_none], self.tool_name) + return result +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class TaskAgentInfo: + """Tracked background agent task metadata, including IDs, status, timing, agent type, + prompt, model, result, and latest response. + """ agent_type: str """Type of agent running this task""" @@ -16480,8 +17371,9 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class UIExitPlanModeResponse: - """Schema for the `UIExitPlanModeResponse` type.""" - + """User response for a pending exit-plan-mode request, with approval state, selected action, + auto-approve flag, and feedback. + """ approved: bool """Whether the plan was approved.""" @@ -16558,7 +17450,9 @@ class UIHandlePendingUserInputRequest: """The unique request ID from the user_input.requested event""" response: UIUserInputResponse - """Schema for the `UIUserInputResponse` type.""" + """User response for a pending user-input request, with answer text and whether it was typed + freeform. + """ @staticmethod def from_dict(obj: Any) -> 'UIHandlePendingUserInputRequest': @@ -16576,8 +17470,9 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class UsageMetricsModelMetric: - """Schema for the `UsageMetricsModelMetric` type.""" - + """Per-model usage metrics, including request counts/costs, token usage, nano-AI units, and + per-token-type details. + """ requests: UsageMetricsModelMetricRequests """Request count and cost metrics for this model""" @@ -16790,8 +17685,9 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class SlashCommandInfo: - """Schema for the `SlashCommandInfo` type.""" - + """Slash-command metadata with name, aliases, description, kind, input hint, execution + allowance, and schedulability. + """ allow_during_agent_execution: bool """Whether the command may run while an agent turn is active""" @@ -16895,127 +17791,38 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class CopilotUserResponse: - """Snapshot of the authenticated user's Copilot subscription info, if known. Mirrors the - GitHub API `/copilot_internal/v2/token` user response shape — the runtime trusts this - verbatim and does not re-fetch when set. - """ - access_type_sku: str | None = None - analytics_tracking_id: str | None = None - assigned_date: Any = None - can_signup_for_limited: bool | None = None - can_upgrade_plan: bool | None = None - chat_enabled: bool | None = None - cli_remote_control_enabled: bool | None = None - cloud_session_storage_enabled: bool | None = None - codex_agent_enabled: bool | None = None - copilot_plan: str | None = None - copilotignore_enabled: bool | None = None - endpoints: CopilotUserResponseEndpoints | None = None - """Schema for the `CopilotUserResponseEndpoints` type.""" +class DebugCollectLogsResult: + """Result of collecting a redacted debug bundle.""" - is_mcp_enabled: Any = None - is_staff: bool | None = None - limited_user_quotas: dict[str, float] | None = None - limited_user_reset_date: str | None = None - login: str | None = None - monthly_quotas: dict[str, float] | None = None - organization_list: Any = None - organization_login_list: list[str] | None = None - quota_reset_date: str | None = None - quota_reset_date_utc: str | None = None - quota_snapshots: dict[str, CopilotUserResponseQuotaSnapshots | None] | None = None - """Schema for the `CopilotUserResponseQuotaSnapshots` type.""" + entries: list[DebugCollectLogsCollectedEntry] + """Files included in the redacted bundle.""" - restricted_telemetry: bool | None = None - te: bool | None = None - token_based_billing: bool | None = None + kind: DebugCollectLogsResultKind + """Destination kind that was written.""" + + path: str + """Actual archive path or staging directory path written. This may differ from the requested + path when no-overwrite suffixing or fallback-to-temp-directory was needed. + """ + skipped_entries: list[DebugCollectLogsSkippedEntry] | None = None + """Optional files or directories that could not be included.""" @staticmethod - def from_dict(obj: Any) -> 'CopilotUserResponse': + def from_dict(obj: Any) -> 'DebugCollectLogsResult': assert isinstance(obj, dict) - access_type_sku = from_union([from_str, from_none], obj.get("access_type_sku")) - analytics_tracking_id = from_union([from_str, from_none], obj.get("analytics_tracking_id")) - assigned_date = obj.get("assigned_date") - can_signup_for_limited = from_union([from_bool, from_none], obj.get("can_signup_for_limited")) - can_upgrade_plan = from_union([from_bool, from_none], obj.get("can_upgrade_plan")) - chat_enabled = from_union([from_bool, from_none], obj.get("chat_enabled")) - cli_remote_control_enabled = from_union([from_bool, from_none], obj.get("cli_remote_control_enabled")) - cloud_session_storage_enabled = from_union([from_bool, from_none], obj.get("cloud_session_storage_enabled")) - codex_agent_enabled = from_union([from_bool, from_none], obj.get("codex_agent_enabled")) - copilot_plan = from_union([from_str, from_none], obj.get("copilot_plan")) - copilotignore_enabled = from_union([from_bool, from_none], obj.get("copilotignore_enabled")) - endpoints = from_union([CopilotUserResponseEndpoints.from_dict, from_none], obj.get("endpoints")) - is_mcp_enabled = obj.get("is_mcp_enabled") - is_staff = from_union([from_bool, from_none], obj.get("is_staff")) - limited_user_quotas = from_union([lambda x: from_dict(from_float, x), from_none], obj.get("limited_user_quotas")) - limited_user_reset_date = from_union([from_str, from_none], obj.get("limited_user_reset_date")) - login = from_union([from_str, from_none], obj.get("login")) - monthly_quotas = from_union([lambda x: from_dict(from_float, x), from_none], obj.get("monthly_quotas")) - organization_list = obj.get("organization_list") - organization_login_list = from_union([lambda x: from_list(from_str, x), from_none], obj.get("organization_login_list")) - quota_reset_date = from_union([from_str, from_none], obj.get("quota_reset_date")) - quota_reset_date_utc = from_union([from_str, from_none], obj.get("quota_reset_date_utc")) - quota_snapshots = from_union([lambda x: from_dict(lambda x: from_union([CopilotUserResponseQuotaSnapshots.from_dict, from_none], x), x), from_none], obj.get("quota_snapshots")) - restricted_telemetry = from_union([from_bool, from_none], obj.get("restricted_telemetry")) - te = from_union([from_bool, from_none], obj.get("te")) - token_based_billing = from_union([from_bool, from_none], obj.get("token_based_billing")) - return CopilotUserResponse(access_type_sku, analytics_tracking_id, assigned_date, can_signup_for_limited, can_upgrade_plan, chat_enabled, cli_remote_control_enabled, cloud_session_storage_enabled, codex_agent_enabled, copilot_plan, copilotignore_enabled, endpoints, is_mcp_enabled, is_staff, limited_user_quotas, limited_user_reset_date, login, monthly_quotas, organization_list, organization_login_list, quota_reset_date, quota_reset_date_utc, quota_snapshots, restricted_telemetry, te, token_based_billing) + entries = from_list(DebugCollectLogsCollectedEntry.from_dict, obj.get("entries")) + kind = DebugCollectLogsResultKind(obj.get("kind")) + path = from_str(obj.get("path")) + skipped_entries = from_union([lambda x: from_list(DebugCollectLogsSkippedEntry.from_dict, x), from_none], obj.get("skippedEntries")) + return DebugCollectLogsResult(entries, kind, path, skipped_entries) def to_dict(self) -> dict: result: dict = {} - if self.access_type_sku is not None: - result["access_type_sku"] = from_union([from_str, from_none], self.access_type_sku) - if self.analytics_tracking_id is not None: - result["analytics_tracking_id"] = from_union([from_str, from_none], self.analytics_tracking_id) - if self.assigned_date is not None: - result["assigned_date"] = self.assigned_date - if self.can_signup_for_limited is not None: - result["can_signup_for_limited"] = from_union([from_bool, from_none], self.can_signup_for_limited) - if self.can_upgrade_plan is not None: - result["can_upgrade_plan"] = from_union([from_bool, from_none], self.can_upgrade_plan) - if self.chat_enabled is not None: - result["chat_enabled"] = from_union([from_bool, from_none], self.chat_enabled) - if self.cli_remote_control_enabled is not None: - result["cli_remote_control_enabled"] = from_union([from_bool, from_none], self.cli_remote_control_enabled) - if self.cloud_session_storage_enabled is not None: - result["cloud_session_storage_enabled"] = from_union([from_bool, from_none], self.cloud_session_storage_enabled) - if self.codex_agent_enabled is not None: - result["codex_agent_enabled"] = from_union([from_bool, from_none], self.codex_agent_enabled) - if self.copilot_plan is not None: - result["copilot_plan"] = from_union([from_str, from_none], self.copilot_plan) - if self.copilotignore_enabled is not None: - result["copilotignore_enabled"] = from_union([from_bool, from_none], self.copilotignore_enabled) - if self.endpoints is not None: - result["endpoints"] = from_union([lambda x: to_class(CopilotUserResponseEndpoints, x), from_none], self.endpoints) - if self.is_mcp_enabled is not None: - result["is_mcp_enabled"] = self.is_mcp_enabled - if self.is_staff is not None: - result["is_staff"] = from_union([from_bool, from_none], self.is_staff) - if self.limited_user_quotas is not None: - result["limited_user_quotas"] = from_union([lambda x: from_dict(to_float, x), from_none], self.limited_user_quotas) - if self.limited_user_reset_date is not None: - result["limited_user_reset_date"] = from_union([from_str, from_none], self.limited_user_reset_date) - if self.login is not None: - result["login"] = from_union([from_str, from_none], self.login) - if self.monthly_quotas is not None: - result["monthly_quotas"] = from_union([lambda x: from_dict(to_float, x), from_none], self.monthly_quotas) - if self.organization_list is not None: - result["organization_list"] = self.organization_list - if self.organization_login_list is not None: - result["organization_login_list"] = from_union([lambda x: from_list(from_str, x), from_none], self.organization_login_list) - if self.quota_reset_date is not None: - result["quota_reset_date"] = from_union([from_str, from_none], self.quota_reset_date) - if self.quota_reset_date_utc is not None: - result["quota_reset_date_utc"] = from_union([from_str, from_none], self.quota_reset_date_utc) - if self.quota_snapshots is not None: - result["quota_snapshots"] = from_union([lambda x: from_dict(lambda x: from_union([lambda x: to_class(CopilotUserResponseQuotaSnapshots, x), from_none], x), x), from_none], self.quota_snapshots) - if self.restricted_telemetry is not None: - result["restricted_telemetry"] = from_union([from_bool, from_none], self.restricted_telemetry) - if self.te is not None: - result["te"] = from_union([from_bool, from_none], self.te) - if self.token_based_billing is not None: - result["token_based_billing"] = from_union([from_bool, from_none], self.token_based_billing) + result["entries"] = from_list(lambda x: to_class(DebugCollectLogsCollectedEntry, x), self.entries) + result["kind"] = to_enum(DebugCollectLogsResultKind, self.kind) + result["path"] = from_str(self.path) + if self.skipped_entries is not None: + 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. @@ -17037,93 +17844,215 @@ def to_dict(self) -> dict: result["extensions"] = from_list(lambda x: to_class(Extension, x), self.extensions) return result -# Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class PermissionDecisionApproveForLocationApprovalExtensionPermissionAccess: - """Schema for the `PermissionDecisionApproveForLocationApprovalExtensionPermissionAccess` - type. - """ - extension_name: str - """Extension name.""" +class PermissionDecisionApproveForIonApproval: + """Session-scoped approval to remember (tool prompts only; omitted for path/url prompts) - kind: ClassVar[str] = "extension-permission-access" - """Approval covering an extension's request to access a permission-gated capability.""" + Session-scoped approval details for specific command identifiers. - @staticmethod - def from_dict(obj: Any) -> 'PermissionDecisionApproveForLocationApprovalExtensionPermissionAccess': - assert isinstance(obj, dict) - extension_name = from_str(obj.get("extensionName")) - return PermissionDecisionApproveForLocationApprovalExtensionPermissionAccess(extension_name) + Session-scoped approval details for read-only filesystem operations. - def to_dict(self) -> dict: - result: dict = {} - result["extensionName"] = from_str(self.extension_name) - result["kind"] = self.kind - return result + Session-scoped approval details for filesystem write operations. -# Experimental: this type is part of an experimental API and may change or be removed. -@dataclass -class PermissionDecisionApproveForSessionApprovalExtensionPermissionAccess: - """Schema for the `PermissionDecisionApproveForSessionApprovalExtensionPermissionAccess` - type. - """ - extension_name: str - """Extension name.""" + Session-scoped approval details for an MCP server tool, or all tools on the server when + `toolName` is null. - kind: ClassVar[str] = "extension-permission-access" - """Approval covering an extension's request to access a permission-gated capability.""" + Session-scoped approval details for MCP sampling requests from a server. - @staticmethod - def from_dict(obj: Any) -> 'PermissionDecisionApproveForSessionApprovalExtensionPermissionAccess': - assert isinstance(obj, dict) - extension_name = from_str(obj.get("extensionName")) - return PermissionDecisionApproveForSessionApprovalExtensionPermissionAccess(extension_name) + Session-scoped approval details for writes to long-term memory. - def to_dict(self) -> dict: - result: dict = {} - result["extensionName"] = from_str(self.extension_name) - result["kind"] = self.kind - return result + Session-scoped approval details for a custom tool, keyed by tool name. -# Experimental: this type is part of an experimental API and may change or be removed. -@dataclass -class PermissionsLocationsAddToolApprovalDetailsExtensionPermissionAccess: - """Schema for the `PermissionsLocationsAddToolApprovalDetailsExtensionPermissionAccess` type.""" + Session-scoped approval details for extension-management operations, optionally narrowed + by operation. - extension_name: str - """Extension name.""" + Session-scoped approval details for an extension's permission-gated capability access, + keyed by extension name. - kind: ClassVar[str] = "extension-permission-access" - """Approval covering an extension's request to access a permission-gated capability.""" + Approval to persist for this location - @staticmethod - def from_dict(obj: Any) -> 'PermissionsLocationsAddToolApprovalDetailsExtensionPermissionAccess': - assert isinstance(obj, dict) - extension_name = from_str(obj.get("extensionName")) - return PermissionsLocationsAddToolApprovalDetailsExtensionPermissionAccess(extension_name) + Location-scoped approval details for specific command identifiers. - def to_dict(self) -> dict: - result: dict = {} - result["extensionName"] = from_str(self.extension_name) - result["kind"] = self.kind - return result + Location-scoped approval details for read-only filesystem operations. -# Experimental: this type is part of an experimental API and may change or be removed. -@dataclass -class ExternalToolTextResultForLlm: - """Expanded external tool result payload""" + Location-scoped approval details for filesystem write operations. - text_result_for_llm: str - """Text result returned to the model""" + Location-scoped approval details for an MCP server tool, or all tools on the server when + `toolName` is null. - binary_results_for_llm: list[ExternalToolTextResultForLlmBinaryResultsForLlm] | None = None - """Base64-encoded binary results returned to the model""" + Location-scoped approval details for MCP sampling requests from a server. - contents: list[ExternalToolTextResultForLlmContent] | None = None - """Structured content blocks from the tool""" + Location-scoped approval details for writes to long-term memory. - error: str | None = None - """Optional error message for failed executions""" + Location-scoped approval details for a custom tool, keyed by tool name. + + Location-scoped approval details for extension-management operations, optionally narrowed + by operation. + + Location-scoped approval details for an extension's permission-gated capability access, + keyed by extension name. + + The approval to add as a session-scoped rule + + The approval to persist for this location + """ + command_identifiers: list[str] | None = None + """Command identifiers covered by this approval.""" + + kind: ApprovalKind | None = None + """Approval scoped to specific command identifiers. + + Approval covering read-only filesystem operations. + + Approval covering filesystem write operations. + + Approval covering an MCP tool. + + Approval covering MCP sampling requests for a server. + + Approval covering writes to long-term memory. + + Approval covering a custom tool. + + Approval covering extension lifecycle operations such as enable, disable, or reload. + + Approval covering an extension's request to access a permission-gated capability. + """ + server_name: str | None = None + """MCP server name.""" + + tool_name: str | None = None + """MCP tool name, or null to cover every tool on the server. + + Custom tool name. + """ + operation: str | None = None + """Optional operation identifier; when omitted, the approval covers all extension management + operations. + """ + extension_name: str | None = None + """Extension name.""" + + external_ref_marker_external_ref_user_tool_session_approval: str | None = None + + @staticmethod + def from_dict(obj: Any) -> 'PermissionDecisionApproveForIonApproval': + assert isinstance(obj, dict) + command_identifiers = from_union([lambda x: from_list(from_str, x), from_none], obj.get("commandIdentifiers")) + kind = from_union([ApprovalKind, from_none], obj.get("kind")) + server_name = from_union([from_str, from_none], obj.get("serverName")) + tool_name = from_union([from_none, from_str], obj.get("toolName")) + operation = from_union([from_str, from_none], obj.get("operation")) + extension_name = from_union([from_str, from_none], obj.get("extensionName")) + external_ref_marker_external_ref_user_tool_session_approval = from_union([from_str, from_none], obj.get("__externalRefMarker___ExternalRef_UserToolSessionApproval")) + return PermissionDecisionApproveForIonApproval(command_identifiers, kind, server_name, tool_name, operation, extension_name, external_ref_marker_external_ref_user_tool_session_approval) + + def to_dict(self) -> dict: + result: dict = {} + if self.command_identifiers is not None: + result["commandIdentifiers"] = from_union([lambda x: from_list(from_str, x), from_none], self.command_identifiers) + if self.kind is not None: + result["kind"] = from_union([lambda x: to_enum(ApprovalKind, x), from_none], self.kind) + if self.server_name is not None: + result["serverName"] = from_union([from_str, from_none], self.server_name) + if self.tool_name is not None: + result["toolName"] = from_union([from_none, from_str], self.tool_name) + if self.operation is not None: + result["operation"] = from_union([from_str, from_none], self.operation) + if self.extension_name is not None: + result["extensionName"] = from_union([from_str, from_none], self.extension_name) + if self.external_ref_marker_external_ref_user_tool_session_approval is not None: + result["__externalRefMarker___ExternalRef_UserToolSessionApproval"] = from_union([from_str, from_none], self.external_ref_marker_external_ref_user_tool_session_approval) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class PermissionDecisionApproveForLocationApprovalExtensionPermissionAccess: + """Location-scoped approval details for an extension's permission-gated capability access, + keyed by extension name. + """ + extension_name: str + """Extension name.""" + + kind: ClassVar[str] = "extension-permission-access" + """Approval covering an extension's request to access a permission-gated capability.""" + + @staticmethod + def from_dict(obj: Any) -> 'PermissionDecisionApproveForLocationApprovalExtensionPermissionAccess': + assert isinstance(obj, dict) + extension_name = from_str(obj.get("extensionName")) + return PermissionDecisionApproveForLocationApprovalExtensionPermissionAccess(extension_name) + + def to_dict(self) -> dict: + result: dict = {} + result["extensionName"] = from_str(self.extension_name) + result["kind"] = self.kind + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class PermissionDecisionApproveForSessionApprovalExtensionPermissionAccess: + """Session-scoped approval details for an extension's permission-gated capability access, + keyed by extension name. + """ + extension_name: str + """Extension name.""" + + kind: ClassVar[str] = "extension-permission-access" + """Approval covering an extension's request to access a permission-gated capability.""" + + @staticmethod + def from_dict(obj: Any) -> 'PermissionDecisionApproveForSessionApprovalExtensionPermissionAccess': + assert isinstance(obj, dict) + extension_name = from_str(obj.get("extensionName")) + return PermissionDecisionApproveForSessionApprovalExtensionPermissionAccess(extension_name) + + def to_dict(self) -> dict: + result: dict = {} + result["extensionName"] = from_str(self.extension_name) + result["kind"] = self.kind + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class PermissionsLocationsAddToolApprovalDetailsExtensionPermissionAccess: + """Location-persisted tool approval details for an extension's permission-gated capability + access, keyed by extension name. + """ + extension_name: str + """Extension name.""" + + kind: ClassVar[str] = "extension-permission-access" + """Approval covering an extension's request to access a permission-gated capability.""" + + @staticmethod + def from_dict(obj: Any) -> 'PermissionsLocationsAddToolApprovalDetailsExtensionPermissionAccess': + assert isinstance(obj, dict) + extension_name = from_str(obj.get("extensionName")) + return PermissionsLocationsAddToolApprovalDetailsExtensionPermissionAccess(extension_name) + + def to_dict(self) -> dict: + result: dict = {} + result["extensionName"] = from_str(self.extension_name) + result["kind"] = self.kind + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class ExternalToolTextResultForLlm: + """Expanded external tool result payload""" + + text_result_for_llm: str + """Text result returned to the model""" + + binary_results_for_llm: list[ExternalToolTextResultForLlmBinaryResultsForLlm] | None = None + """Base64-encoded binary results returned to the model""" + + contents: list[ExternalToolTextResultForLlmContent] | None = None + """Structured content blocks from the tool""" + + error: str | None = None + """Optional error message for failed executions""" result_type: str | None = None """Execution outcome classification. Optional for back-compat; normalized to 'success' (or @@ -17132,6 +18061,11 @@ class ExternalToolTextResultForLlm: session_log: str | None = None """Detailed log content for timeline display""" + tool_references: list[str] | None = None + """Tool references returned by a tool-search override: names of deferred tools to surface to + the model. When set, the tool result is materialized as `tool_reference` content blocks + (rather than plain text) so the model knows which deferred tools are now available. + """ tool_telemetry: dict[str, Any] | None = None """Optional tool-specific telemetry""" @@ -17144,8 +18078,9 @@ def from_dict(obj: Any) -> 'ExternalToolTextResultForLlm': error = from_union([from_str, from_none], obj.get("error")) result_type = from_union([from_str, from_none], obj.get("resultType")) session_log = from_union([from_str, from_none], obj.get("sessionLog")) + tool_references = from_union([lambda x: from_list(from_str, x), from_none], obj.get("toolReferences")) tool_telemetry = from_union([lambda x: from_dict(lambda x: x, x), from_none], obj.get("toolTelemetry")) - return ExternalToolTextResultForLlm(text_result_for_llm, binary_results_for_llm, contents, error, result_type, session_log, tool_telemetry) + return ExternalToolTextResultForLlm(text_result_for_llm, binary_results_for_llm, contents, error, result_type, session_log, tool_references, tool_telemetry) def to_dict(self) -> dict: result: dict = {} @@ -17160,6 +18095,8 @@ def to_dict(self) -> dict: result["resultType"] = from_union([from_str, from_none], self.result_type) if self.session_log is not None: result["sessionLog"] = from_union([from_str, from_none], self.session_log) + if self.tool_references is not None: + result["toolReferences"] = from_union([lambda x: from_list(from_str, x), from_none], self.tool_references) if self.tool_telemetry is not None: result["toolTelemetry"] = from_union([lambda x: from_dict(lambda x: x, x), from_none], self.tool_telemetry) return result @@ -17224,110 +18161,108 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class InstalledPluginSource: - """Schema for the `InstalledPluginSourceGitHub` type. +class InstalledPlugin: + """Installed plugin record from global state, with marketplace, version, install time, + enabled state, cache path, and source. + """ + enabled: bool + """Whether the plugin is currently enabled""" - Schema for the `InstalledPluginSourceUrl` type. + installed_at: str + """Installation timestamp""" - Schema for the `InstalledPluginSourceLocal` type. - """ - source: PurpleSource - """Constant value. Always "github". + marketplace: str + """Marketplace the plugin came from (empty string for direct repo installs)""" - Constant value. Always "url". + name: str + """Plugin name""" - Constant value. Always "local". - """ - path: str | None = None - ref: str | None = None - repo: str | None = None - url: str | None = None + cache_path: str | None = None + """Path where the plugin is cached locally""" + + source: InstalledPluginSource | str | None = None + """Source for direct repo installs (when marketplace is empty)""" + + version: str | None = None + """Version installed (if available)""" @staticmethod - def from_dict(obj: Any) -> 'InstalledPluginSource': + def from_dict(obj: Any) -> 'InstalledPlugin': assert isinstance(obj, dict) - source = PurpleSource(obj.get("source")) - path = from_union([from_str, from_none], obj.get("path")) - ref = from_union([from_str, from_none], obj.get("ref")) - repo = from_union([from_str, from_none], obj.get("repo")) - url = from_union([from_str, from_none], obj.get("url")) - return InstalledPluginSource(source, path, ref, repo, url) + enabled = from_bool(obj.get("enabled")) + installed_at = from_str(obj.get("installed_at")) + marketplace = from_str(obj.get("marketplace")) + name = from_str(obj.get("name")) + cache_path = from_union([from_str, from_none], obj.get("cache_path")) + source = from_union([InstalledPluginSource.from_dict, from_str, from_none], obj.get("source")) + version = from_union([from_str, from_none], obj.get("version")) + return InstalledPlugin(enabled, installed_at, marketplace, name, cache_path, source, version) def to_dict(self) -> dict: result: dict = {} - result["source"] = to_enum(PurpleSource, self.source) - if self.path is not None: - result["path"] = from_union([from_str, from_none], self.path) - if self.ref is not None: - result["ref"] = from_union([from_str, from_none], self.ref) - if self.repo is not None: - result["repo"] = from_union([from_str, from_none], self.repo) - if self.url is not None: - result["url"] = from_union([from_str, from_none], self.url) + result["enabled"] = from_bool(self.enabled) + result["installed_at"] = from_str(self.installed_at) + result["marketplace"] = from_str(self.marketplace) + 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.source is not None: + result["source"] = from_union([lambda x: to_class(InstalledPluginSource, x), from_str, from_none], self.source) + if self.version is not None: + result["version"] = from_union([from_str, from_none], self.version) return result # Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class SessionInstalledPluginSource: - """Schema for the `SessionInstalledPluginSourceGitHub` type. +class SessionInstalledPlugin: + """Installed plugin record for a session, with marketplace, version, install time, enabled + state, cache path, and source. + """ + enabled: bool + """Whether the plugin is currently enabled""" - Schema for the `SessionInstalledPluginSourceUrl` type. + installed_at: str + """Installation timestamp (ISO-8601)""" - Schema for the `SessionInstalledPluginSourceLocal` type. - """ - source: PurpleSource - """Constant value. Always "github". + marketplace: str + """Marketplace the plugin came from (empty string for direct repo installs)""" - Constant value. Always "url". + name: str + """Plugin name""" - Constant value. Always "local". - """ - path: str | None = None - ref: str | None = None - repo: str | None = None - url: str | None = None + cache_path: str | None = None + """Path where the plugin is cached locally""" + + source: SessionInstalledPluginSource | str | None = None + """Source descriptor for direct repo installs (when marketplace is empty)""" + + version: str | None = None + """Installed version, if known""" @staticmethod - def from_dict(obj: Any) -> 'SessionInstalledPluginSource': + def from_dict(obj: Any) -> 'SessionInstalledPlugin': assert isinstance(obj, dict) - source = PurpleSource(obj.get("source")) - path = from_union([from_str, from_none], obj.get("path")) - ref = from_union([from_str, from_none], obj.get("ref")) - repo = from_union([from_str, from_none], obj.get("repo")) - url = from_union([from_str, from_none], obj.get("url")) - return SessionInstalledPluginSource(source, path, ref, repo, url) - - def to_dict(self) -> dict: - result: dict = {} - result["source"] = to_enum(PurpleSource, self.source) - if self.path is not None: - result["path"] = from_union([from_str, from_none], self.path) - if self.ref is not None: - result["ref"] = from_union([from_str, from_none], self.ref) - if self.repo is not None: - result["repo"] = from_union([from_str, from_none], self.repo) - if self.url is not None: - result["url"] = from_union([from_str, from_none], self.url) - return result - -# Experimental: this type is part of an experimental API and may change or be removed. -@dataclass -class InstructionDiscoveryPathList: - """Canonical files and directories where custom instructions can be created so the runtime - will recognize them. - """ - paths: list[InstructionDiscoveryPath] - """Canonical instruction create/discovery files and directories, in priority order""" - - @staticmethod - def from_dict(obj: Any) -> 'InstructionDiscoveryPathList': - assert isinstance(obj, dict) - paths = from_list(InstructionDiscoveryPath.from_dict, obj.get("paths")) - return InstructionDiscoveryPathList(paths) + enabled = from_bool(obj.get("enabled")) + installed_at = from_str(obj.get("installed_at")) + marketplace = from_str(obj.get("marketplace")) + name = from_str(obj.get("name")) + cache_path = from_union([from_str, from_none], obj.get("cache_path")) + source = from_union([SessionInstalledPluginSource.from_dict, from_str, from_none], obj.get("source")) + version = from_union([from_str, from_none], obj.get("version")) + return SessionInstalledPlugin(enabled, installed_at, marketplace, name, cache_path, source, version) def to_dict(self) -> dict: result: dict = {} - result["paths"] = from_list(lambda x: to_class(InstructionDiscoveryPath, x), self.paths) + result["enabled"] = from_bool(self.enabled) + result["installed_at"] = from_str(self.installed_at) + result["marketplace"] = from_str(self.marketplace) + 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.source is not None: + result["source"] = from_union([lambda x: to_class(SessionInstalledPluginSource, x), from_str, from_none], self.source) + if self.version is not None: + result["version"] = from_union([from_str, from_none], self.version) return result # Experimental: this type is part of an experimental API and may change or be removed. @@ -17371,8 +18306,9 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class LocalSessionMetadataValue: - """Schema for the `LocalSessionMetadataValue` type.""" - + """Persisted local session metadata, including identifiers, timestamps, summary/name, + client, context, detached state, and task ID. + """ is_remote: bool """Always false for local sessions.""" @@ -17700,6 +18636,36 @@ def to_dict(self) -> dict: result["user_named"] = from_union([from_bool, from_none], self.user_named) return result +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class WorkspacesCheckpoints: + """Workspace checkpoint metadata with assigned number, human-readable title, and checkpoint + filename. + """ + filename: str + """Filename of the checkpoint within the workspace checkpoints directory""" + + number: int + """Checkpoint number assigned by the workspace manager""" + + title: str + """Human-readable checkpoint title""" + + @staticmethod + def from_dict(obj: Any) -> 'WorkspacesCheckpoints': + assert isinstance(obj, dict) + filename = from_str(obj.get("filename")) + number = from_int(obj.get("number")) + title = from_str(obj.get("title")) + return WorkspacesCheckpoints(filename, number, title) + + def to_dict(self) -> dict: + result: dict = {} + result["filename"] = from_str(self.filename) + result["number"] = from_int(self.number) + result["title"] = from_str(self.title) + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class WorkspacesGetWorkspaceResult: @@ -17727,25 +18693,6 @@ def to_dict(self) -> dict: result["workspace"] = from_union([lambda x: to_class(Workspace, x), from_none], self.workspace) return result -# Experimental: this type is part of an experimental API and may change or be removed. -@dataclass -class WorkspacesListCheckpointsResult: - """Workspace checkpoints in chronological order; empty when the workspace is not enabled.""" - - checkpoints: list[WorkspacesCheckpoints] - """Workspace checkpoints in chronological order. Empty when workspace is not enabled.""" - - @staticmethod - def from_dict(obj: Any) -> 'WorkspacesListCheckpointsResult': - assert isinstance(obj, dict) - checkpoints = from_list(WorkspacesCheckpoints.from_dict, obj.get("checkpoints")) - return WorkspacesListCheckpointsResult(checkpoints) - - def to_dict(self) -> dict: - result: dict = {} - result["checkpoints"] = from_list(lambda x: to_class(WorkspacesCheckpoints, x), self.checkpoints) - return result - # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class MCPAppsHostContext: @@ -17951,6 +18898,139 @@ def to_dict(self) -> dict: 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: + """A caller-provided server-local file or directory to include in the debug bundle.""" + + bundle_path: str + """Relative path to use inside the staged bundle/archive.""" + + kind: DebugCollectLogsEntryKind + """Kind of source path to include.""" + + path: str + """Server-local source path to read.""" + + redaction: DebugCollectLogsRedaction | None = None + """How text content from this entry should be redacted. Defaults to plain-text.""" + + required: bool | None = None + """When true, collection fails if this entry cannot be read. Defaults to false, which + records the entry in `skippedEntries`. + """ + + @staticmethod + def from_dict(obj: Any) -> 'DebugCollectLogsEntry': + assert isinstance(obj, dict) + bundle_path = from_str(obj.get("bundlePath")) + kind = DebugCollectLogsEntryKind(obj.get("kind")) + path = from_str(obj.get("path")) + redaction = from_union([DebugCollectLogsRedaction, from_none], obj.get("redaction")) + required = from_union([from_bool, from_none], obj.get("required")) + return DebugCollectLogsEntry(bundle_path, kind, path, redaction, required) + + def to_dict(self) -> dict: + result: dict = {} + result["bundlePath"] = from_str(self.bundle_path) + result["kind"] = to_enum(DebugCollectLogsEntryKind, self.kind) + result["path"] = from_str(self.path) + if self.redaction is not None: + result["redaction"] = from_union([lambda x: to_enum(DebugCollectLogsRedaction, x), from_none], self.redaction) + if self.required is not None: + result["required"] = from_union([from_bool, from_none], self.required) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class InstructionDiscoveryPath: + """Canonical file or directory where custom instructions can be discovered or created, with + location, kind, preference, and project path. + """ + kind: DebugCollectLogsEntryKind + """Whether the target is a single file or a directory of instruction files""" + + location: InstructionLocation + """Which tier this target belongs to""" + + path: str + """Absolute path of the file or directory (may not exist on disk yet)""" + + preferred_for_creation: bool + """Whether this is the canonical target to create new instructions in its tier. At most one + entry per tier is preferred. + """ + project_path: str | None = None + """The input project path this target was derived from (only for repository targets)""" + + @staticmethod + def from_dict(obj: Any) -> 'InstructionDiscoveryPath': + assert isinstance(obj, dict) + kind = DebugCollectLogsEntryKind(obj.get("kind")) + location = InstructionLocation(obj.get("location")) + path = from_str(obj.get("path")) + preferred_for_creation = from_bool(obj.get("preferredForCreation")) + project_path = from_union([from_str, from_none], obj.get("projectPath")) + return InstructionDiscoveryPath(kind, location, path, preferred_for_creation, project_path) + + def to_dict(self) -> dict: + result: dict = {} + result["kind"] = to_enum(DebugCollectLogsEntryKind, self.kind) + result["location"] = to_enum(InstructionLocation, self.location) + result["path"] = from_str(self.path) + result["preferredForCreation"] = from_bool(self.preferred_for_creation) + if self.project_path is not None: + result["projectPath"] = from_union([from_str, from_none], self.project_path) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class SessionFSReaddirWithTypesEntry: + """Directory entry returned by session filesystem `readdirWithTypes`, with name and entry + type. + """ + name: str + """Entry name""" + + type: DebugCollectLogsEntryKind + """Entry type""" + + @staticmethod + def from_dict(obj: Any) -> 'SessionFSReaddirWithTypesEntry': + assert isinstance(obj, dict) + name = from_str(obj.get("name")) + type = DebugCollectLogsEntryKind(obj.get("type")) + return SessionFSReaddirWithTypesEntry(name, type) + + def to_dict(self) -> dict: + result: dict = {} + result["name"] = from_str(self.name) + result["type"] = to_enum(DebugCollectLogsEntryKind, self.type) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class MetadataContextAttributionResult: + """Per-source attribution breakdown for the session's current context window, or null if + uninitialized. + """ + context_attribution: SessionContextAttribution | None = None + """Per-source context-window attribution, or null if the session has not yet been + initialized (no system prompt or tool metadata cached). + """ + + @staticmethod + def from_dict(obj: Any) -> 'MetadataContextAttributionResult': + assert isinstance(obj, dict) + context_attribution = from_union([SessionContextAttribution.from_dict, from_none], obj.get("contextAttribution")) + return MetadataContextAttributionResult(context_attribution) + + def to_dict(self) -> dict: + result: dict = {} + if self.context_attribution is not None: + result["contextAttribution"] = from_union([lambda x: to_class(SessionContextAttribution, x), from_none], self.context_attribution) + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class ModelBilling: @@ -18047,8 +19127,9 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class OptionsUpdateAdditionalContentExclusionPolicy: - """Schema for the `OptionsUpdateAdditionalContentExclusionPolicy` type.""" - + """Content-exclusion policy supplied to `session.options.update`, with rules, last-updated + data, and scope. + """ last_updated_at: Any rules: list[OptionsUpdateAdditionalContentExclusionPolicyRule] scope: AdditionalContentExclusionPolicyScope @@ -18072,8 +19153,9 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class PermissionsConfigureAdditionalContentExclusionPolicy: - """Schema for the `PermissionsConfigureAdditionalContentExclusionPolicy` type.""" - + """Content-exclusion policy supplied to `session.permissions.configure`, with rules, + last-updated data, and scope. + """ last_updated_at: Any rules: list[PermissionsConfigureAdditionalContentExclusionPolicyRule] scope: AdditionalContentExclusionPolicyScope @@ -18480,32 +19562,6 @@ def to_dict(self) -> dict: result["error"] = from_union([lambda x: to_class(SessionFSError, x), from_none], self.error) return result -# Experimental: this type is part of an experimental API and may change or be removed. -@dataclass -class SessionFSReaddirWithTypesResult: - """Entries in the requested directory paired with file/directory type information, or a - filesystem error if the read failed. - """ - entries: list[SessionFSReaddirWithTypesEntry] - """Directory entries with type information""" - - error: SessionFSError | None = None - """Describes a filesystem error.""" - - @staticmethod - def from_dict(obj: Any) -> 'SessionFSReaddirWithTypesResult': - assert isinstance(obj, dict) - entries = from_list(SessionFSReaddirWithTypesEntry.from_dict, obj.get("entries")) - error = from_union([SessionFSError.from_dict, from_none], obj.get("error")) - return SessionFSReaddirWithTypesResult(entries, error) - - def to_dict(self) -> dict: - result: dict = {} - result["entries"] = from_list(lambda x: to_class(SessionFSReaddirWithTypesEntry, x), self.entries) - if self.error is not None: - result["error"] = from_union([lambda x: to_class(SessionFSError, x), from_none], self.error) - return result - # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class SessionFSSqliteQueryResult: @@ -18596,8 +19652,9 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class SessionOpenOptionsAdditionalContentExclusionPolicy: - """Schema for the `SessionOpenOptionsAdditionalContentExclusionPolicy` type.""" - + """Content-exclusion policy supplied to `sessions.open` options, with rules, last-updated + data, and scope. + """ last_updated_at: Any rules: list[SessionOpenOptionsAdditionalContentExclusionPolicyRule] scope: AdditionalContentExclusionPolicyScope @@ -18620,6 +19677,53 @@ def to_dict(self) -> dict: result["scope"] = to_enum(AdditionalContentExclusionPolicyScope, self.scope) return result +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class SessionSettingsSnapshot: + """Redacted, serializable view of session runtime settings for SDK boundary consumers. + Secrets and raw feature flags are intentionally excluded. + """ + job: SessionSettingsJobSnapshot + model: SessionSettingsModelSnapshot + online_evaluation: SessionSettingsOnlineEvaluationSnapshot + repo: SessionSettingsRepoSnapshot + validation: SessionSettingsValidationSnapshot + client_name: str | None = None + start_time_ms: float | None = None + timeout_ms: float | None = None + version: str | None = None + + @staticmethod + def from_dict(obj: Any) -> 'SessionSettingsSnapshot': + assert isinstance(obj, dict) + job = SessionSettingsJobSnapshot.from_dict(obj.get("job")) + model = SessionSettingsModelSnapshot.from_dict(obj.get("model")) + online_evaluation = SessionSettingsOnlineEvaluationSnapshot.from_dict(obj.get("onlineEvaluation")) + repo = SessionSettingsRepoSnapshot.from_dict(obj.get("repo")) + validation = SessionSettingsValidationSnapshot.from_dict(obj.get("validation")) + client_name = from_union([from_str, from_none], obj.get("clientName")) + start_time_ms = from_union([from_float, from_none], obj.get("startTimeMs")) + timeout_ms = from_union([from_float, from_none], obj.get("timeoutMs")) + version = from_union([from_str, from_none], obj.get("version")) + return SessionSettingsSnapshot(job, model, online_evaluation, repo, validation, client_name, start_time_ms, timeout_ms, version) + + def to_dict(self) -> dict: + result: dict = {} + result["job"] = to_class(SessionSettingsJobSnapshot, self.job) + result["model"] = to_class(SessionSettingsModelSnapshot, self.model) + result["onlineEvaluation"] = to_class(SessionSettingsOnlineEvaluationSnapshot, self.online_evaluation) + result["repo"] = to_class(SessionSettingsRepoSnapshot, self.repo) + result["validation"] = to_class(SessionSettingsValidationSnapshot, self.validation) + if self.client_name is not None: + result["clientName"] = from_union([from_str, from_none], self.client_name) + if self.start_time_ms is not None: + result["startTimeMs"] = from_union([to_float, from_none], self.start_time_ms) + if self.timeout_ms is not None: + result["timeoutMs"] = from_union([to_float, from_none], self.timeout_ms) + if self.version is not None: + result["version"] = from_union([from_str, from_none], self.version) + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class AgentGetCurrentResult: @@ -18718,66 +19822,62 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class SkillDiscoveryPathList: - """Canonical locations where skills can be created so the runtime will recognize them.""" +class SkillsGetInvokedResult: + """Skills invoked during this session, ordered by invocation time (most recent last).""" - paths: list[SkillDiscoveryPath] - """Canonical skill create/discovery directories, in priority order""" + skills: list[SkillsInvokedSkill] + """Skills invoked during this session, ordered by invocation time (most recent last)""" @staticmethod - def from_dict(obj: Any) -> 'SkillDiscoveryPathList': + def from_dict(obj: Any) -> 'SkillsGetInvokedResult': assert isinstance(obj, dict) - paths = from_list(SkillDiscoveryPath.from_dict, obj.get("paths")) - return SkillDiscoveryPathList(paths) + skills = from_list(SkillsInvokedSkill.from_dict, obj.get("skills")) + return SkillsGetInvokedResult(skills) def to_dict(self) -> dict: result: dict = {} - result["paths"] = from_list(lambda x: to_class(SkillDiscoveryPath, x), self.paths) + result["skills"] = from_list(lambda x: to_class(SkillsInvokedSkill, x), self.skills) return result # Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class TaskProgress: - """Schema for the `TaskAgentProgress` type. +class SkillDiscoveryPathList: + """Canonical locations where skills can be created so the runtime will recognize them.""" - Schema for the `TaskShellProgress` type. - """ - type: TaskInfoType - """Progress kind""" + paths: list[SkillDiscoveryPath] + """Canonical skill create/discovery directories, in priority order""" - latest_intent: str | None = None - """The most recent intent reported by the agent""" + @staticmethod + def from_dict(obj: Any) -> 'SkillDiscoveryPathList': + assert isinstance(obj, dict) + paths = from_list(SkillDiscoveryPath.from_dict, obj.get("paths")) + return SkillDiscoveryPathList(paths) - recent_activity: list[TaskProgressLine] | None = None - """Recent tool execution events converted to display lines""" + def to_dict(self) -> dict: + result: dict = {} + result["paths"] = from_list(lambda x: to_class(SkillDiscoveryPath, x), self.paths) + return result - pid: int | None = None - """Process ID when available""" +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class TasksGetProgressResult: + """Progress information for the task, or null when no task with that ID is tracked.""" - recent_output: str | None = None - """Recent stdout/stderr lines from the running shell command""" + progress: TaskProgress | None = None + """Progress information for the task, discriminated by type. Returns null when no task with + this ID is currently tracked. + """ @staticmethod - def from_dict(obj: Any) -> 'TaskProgress': + def from_dict(obj: Any) -> 'TasksGetProgressResult': assert isinstance(obj, dict) - type = TaskInfoType(obj.get("type")) - latest_intent = from_union([from_str, from_none], obj.get("latestIntent")) - recent_activity = from_union([lambda x: from_list(TaskProgressLine.from_dict, x), from_none], obj.get("recentActivity")) - pid = from_union([from_int, from_none], obj.get("pid")) - recent_output = from_union([from_str, from_none], obj.get("recentOutput")) - return TaskProgress(type, latest_intent, recent_activity, pid, recent_output) + progress = from_union([TaskProgress.from_dict, from_none], obj.get("progress")) + return TasksGetProgressResult(progress) def to_dict(self) -> dict: result: dict = {} - result["type"] = to_enum(TaskInfoType, self.type) - if self.latest_intent is not None: - result["latestIntent"] = from_union([from_str, from_none], self.latest_intent) - if self.recent_activity is not None: - result["recentActivity"] = from_union([lambda x: from_list(lambda x: to_class(TaskProgressLine, x), x), from_none], self.recent_activity) - if self.pid is not None: - result["pid"] = from_union([from_int, from_none], self.pid) - if self.recent_output is not None: - result["recentOutput"] = from_union([from_str, from_none], self.recent_output) + if self.progress is not None: + 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. @@ -19068,7 +20168,9 @@ class UIHandlePendingExitPlanModeRequest: """The unique request ID from the exit_plan_mode.requested event""" response: UIExitPlanModeResponse - """Schema for the `UIExitPlanModeResponse` type.""" + """User response for a pending exit-plan-mode request, with approval state, selected action, + auto-approve flag, and feedback. + """ @staticmethod def from_dict(obj: Any) -> 'UIHandlePendingExitPlanModeRequest': @@ -19396,398 +20498,6 @@ def to_dict(self) -> dict: result["session"] = from_union([lambda x: to_class(CanvasSessionContext, x), from_none], self.session) return result -# Experimental: this type is part of an experimental API and may change or be removed. -@dataclass -class APIKeyAuthInfo: - """Schema for the `ApiKeyAuthInfo` type.""" - - api_key: str - """The API key. Treat as a secret.""" - - host: str - """Authentication host.""" - - type: ClassVar[str] = "api-key" - """API-key authentication for non-GitHub LLM providers (e.g. when running BYOM-style).""" - - 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) -> 'APIKeyAuthInfo': - assert isinstance(obj, dict) - api_key = from_str(obj.get("apiKey")) - host = from_str(obj.get("host")) - copilot_user = from_union([CopilotUserResponse.from_dict, from_none], obj.get("copilotUser")) - return APIKeyAuthInfo(api_key, host, copilot_user) - - def to_dict(self) -> dict: - result: dict = {} - result["apiKey"] = from_str(self.api_key) - result["host"] = from_str(self.host) - 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 CopilotAPITokenAuthInfo: - """Schema for the `CopilotApiTokenAuthInfo` type.""" - - host: Host - """Authentication host (always the public GitHub host).""" - - type: ClassVar[str] = "copilot-api-token" - """Direct Copilot API authentication via the `GITHUB_COPILOT_API_TOKEN` + `COPILOT_API_URL` - environment-variable pair. The token itself is read from the environment by the runtime, - not carried in this struct. - """ - 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) -> 'CopilotAPITokenAuthInfo': - assert isinstance(obj, dict) - host = Host(obj.get("host")) - copilot_user = from_union([CopilotUserResponse.from_dict, from_none], obj.get("copilotUser")) - return CopilotAPITokenAuthInfo(host, copilot_user) - - def to_dict(self) -> dict: - result: dict = {} - result["host"] = to_enum(Host, self.host) - 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 EnvAuthInfo: - """Schema for the `EnvAuthInfo` type.""" - - env_var: str - """Name of the environment variable the token was sourced from.""" - - host: str - """Authentication host (e.g. https://github.com or a GHES host).""" - - token: str - """The token value itself. Treat as a secret.""" - - type: ClassVar[str] = "env" - """Personal access token (PAT) or server-to-server token sourced from an environment - variable. - """ - 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. - """ - login: str | None = None - """User login associated with the token. Undefined for server-to-server tokens (those - starting with `ghs_`). - """ - - @staticmethod - def from_dict(obj: Any) -> 'EnvAuthInfo': - assert isinstance(obj, dict) - env_var = from_str(obj.get("envVar")) - host = from_str(obj.get("host")) - token = from_str(obj.get("token")) - copilot_user = from_union([CopilotUserResponse.from_dict, from_none], obj.get("copilotUser")) - login = from_union([from_str, from_none], obj.get("login")) - return EnvAuthInfo(env_var, host, token, copilot_user, login) - - def to_dict(self) -> dict: - result: dict = {} - result["envVar"] = from_str(self.env_var) - 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.login is not None: - result["login"] = from_union([from_str, from_none], self.login) - return result - -# Experimental: this type is part of an experimental API and may change or be removed. -@dataclass -class GhCLIAuthInfo: - """Schema for the `GhCliAuthInfo` type.""" - - host: str - """Authentication host.""" - - login: str - """User login as reported by `gh auth status`.""" - - token: str - """The token returned by `gh auth token`. Treat as a secret.""" - - type: ClassVar[str] = "gh-cli" - """Authentication via the `gh` CLI's saved credentials.""" - - 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) -> 'GhCLIAuthInfo': - assert isinstance(obj, dict) - host = from_str(obj.get("host")) - login = from_str(obj.get("login")) - token = from_str(obj.get("token")) - copilot_user = from_union([CopilotUserResponse.from_dict, from_none], obj.get("copilotUser")) - return GhCLIAuthInfo(host, login, token, copilot_user) - - def to_dict(self) -> dict: - result: dict = {} - result["host"] = from_str(self.host) - result["login"] = from_str(self.login) - result["token"] = from_str(self.token) - 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 HMACAuthInfo: - """Schema for the `HMACAuthInfo` type.""" - - hmac: str - """HMAC secret used to sign requests.""" - - host: Host - """Authentication host. HMAC auth always targets the public GitHub host.""" - - type: ClassVar[str] = "hmac" - """HMAC-based authentication used by GitHub-internal services.""" - - 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) -> 'HMACAuthInfo': - assert isinstance(obj, dict) - hmac = from_str(obj.get("hmac")) - host = Host(obj.get("host")) - copilot_user = from_union([CopilotUserResponse.from_dict, from_none], obj.get("copilotUser")) - return HMACAuthInfo(hmac, host, copilot_user) - - def to_dict(self) -> dict: - result: dict = {} - result["hmac"] = from_str(self.hmac) - result["host"] = to_enum(Host, self.host) - 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 TokenAuthInfo: - """Schema for the `TokenAuthInfo` type.""" - - 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) -> 'TokenAuthInfo': - 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 TokenAuthInfo(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 UserAuthInfo: - """Schema for the `UserAuthInfo` type.""" - - host: str - """Authentication host.""" - - login: str - """OAuth user login.""" - - type: ClassVar[str] = "user" - """OAuth user authentication. The token itself is held in the runtime's secret token store - (keyed by host+login) and is NOT carried in this struct. - """ - 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) -> 'UserAuthInfo': - assert isinstance(obj, dict) - host = from_str(obj.get("host")) - login = from_str(obj.get("login")) - copilot_user = from_union([CopilotUserResponse.from_dict, from_none], obj.get("copilotUser")) - return UserAuthInfo(host, login, copilot_user) - - def to_dict(self) -> dict: - result: dict = {} - result["host"] = from_str(self.host) - result["login"] = from_str(self.login) - 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 - -@dataclass -class PermissionDecisionApproveForIonApproval: - """Session-scoped approval to remember (tool prompts only; omitted for path/url prompts) - - Schema for the `PermissionDecisionApproveForSessionApprovalCommands` type. - - Schema for the `PermissionDecisionApproveForSessionApprovalRead` type. - - Schema for the `PermissionDecisionApproveForSessionApprovalWrite` type. - - Schema for the `PermissionDecisionApproveForSessionApprovalMcp` type. - - Schema for the `PermissionDecisionApproveForSessionApprovalMcpSampling` type. - - Schema for the `PermissionDecisionApproveForSessionApprovalMemory` type. - - Schema for the `PermissionDecisionApproveForSessionApprovalCustomTool` type. - - Schema for the `PermissionDecisionApproveForSessionApprovalExtensionManagement` type. - - Schema for the `PermissionDecisionApproveForSessionApprovalExtensionPermissionAccess` - type. - - Approval to persist for this location - - Schema for the `PermissionDecisionApproveForLocationApprovalCommands` type. - - Schema for the `PermissionDecisionApproveForLocationApprovalRead` type. - - Schema for the `PermissionDecisionApproveForLocationApprovalWrite` type. - - Schema for the `PermissionDecisionApproveForLocationApprovalMcp` type. - - Schema for the `PermissionDecisionApproveForLocationApprovalMcpSampling` type. - - Schema for the `PermissionDecisionApproveForLocationApprovalMemory` type. - - Schema for the `PermissionDecisionApproveForLocationApprovalCustomTool` type. - - Schema for the `PermissionDecisionApproveForLocationApprovalExtensionManagement` type. - - Schema for the `PermissionDecisionApproveForLocationApprovalExtensionPermissionAccess` - type. - - The approval to add as a session-scoped rule - - The approval to persist for this location - """ - command_identifiers: list[str] | None = None - """Command identifiers covered by this approval.""" - - kind: ApprovalKind | None = None - """Approval scoped to specific command identifiers. - - Approval covering read-only filesystem operations. - - Approval covering filesystem write operations. - - Approval covering an MCP tool. - - Approval covering MCP sampling requests for a server. - - Approval covering writes to long-term memory. - - Approval covering a custom tool. - - Approval covering extension lifecycle operations such as enable, disable, or reload. - - Approval covering an extension's request to access a permission-gated capability. - """ - server_name: str | None = None - """MCP server name.""" - - tool_name: str | None = None - """MCP tool name, or null to cover every tool on the server. - - Custom tool name. - """ - operation: str | None = None - """Optional operation identifier; when omitted, the approval covers all extension management - operations. - """ - extension_name: str | None = None - """Extension name.""" - - external_ref_marker_external_ref_user_tool_session_approval: str | None = None - - @staticmethod - def from_dict(obj: Any) -> 'PermissionDecisionApproveForIonApproval': - assert isinstance(obj, dict) - command_identifiers = from_union([lambda x: from_list(from_str, x), from_none], obj.get("commandIdentifiers")) - kind = from_union([ApprovalKind, from_none], obj.get("kind")) - server_name = from_union([from_str, from_none], obj.get("serverName")) - tool_name = from_union([from_none, from_str], obj.get("toolName")) - operation = from_union([from_str, from_none], obj.get("operation")) - extension_name = from_union([from_str, from_none], obj.get("extensionName")) - external_ref_marker_external_ref_user_tool_session_approval = from_union([from_str, from_none], obj.get("__externalRefMarker___ExternalRef_UserToolSessionApproval")) - return PermissionDecisionApproveForIonApproval(command_identifiers, kind, server_name, tool_name, operation, extension_name, external_ref_marker_external_ref_user_tool_session_approval) - - def to_dict(self) -> dict: - result: dict = {} - if self.command_identifiers is not None: - result["commandIdentifiers"] = from_union([lambda x: from_list(from_str, x), from_none], self.command_identifiers) - if self.kind is not None: - result["kind"] = from_union([lambda x: to_enum(ApprovalKind, x), from_none], self.kind) - if self.server_name is not None: - result["serverName"] = from_union([from_str, from_none], self.server_name) - if self.tool_name is not None: - result["toolName"] = from_union([from_none, from_str], self.tool_name) - if self.operation is not None: - result["operation"] = from_union([from_str, from_none], self.operation) - if self.extension_name is not None: - result["extensionName"] = from_union([from_str, from_none], self.extension_name) - if self.external_ref_marker_external_ref_user_tool_session_approval is not None: - result["__externalRefMarker___ExternalRef_UserToolSessionApproval"] = from_union([from_str, from_none], self.external_ref_marker_external_ref_user_tool_session_approval) - return result - # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class HandlePendingToolCallRequest: @@ -19813,115 +20523,32 @@ def from_dict(obj: Any) -> 'HandlePendingToolCallRequest': def to_dict(self) -> dict: result: dict = {} - result["requestId"] = from_str(self.request_id) - if self.error is not None: - result["error"] = from_union([from_str, from_none], self.error) - if self.result is not None: - result["result"] = from_union([lambda x: to_class(ExternalToolTextResultForLlm, x), from_str, from_none], self.result) - return result - -# Experimental: this type is part of an experimental API and may change or be removed. -@dataclass -class InstalledPlugin: - """Schema for the `InstalledPlugin` type.""" - - enabled: bool - """Whether the plugin is currently enabled""" - - installed_at: str - """Installation timestamp""" - - marketplace: str - """Marketplace the plugin came from (empty string for direct repo installs)""" - - name: str - """Plugin name""" - - cache_path: str | None = None - """Path where the plugin is cached locally""" - - source: InstalledPluginSource | str | None = None - """Source for direct repo installs (when marketplace is empty)""" - - version: str | None = None - """Version installed (if available)""" - - @staticmethod - def from_dict(obj: Any) -> 'InstalledPlugin': - assert isinstance(obj, dict) - enabled = from_bool(obj.get("enabled")) - installed_at = from_str(obj.get("installed_at")) - marketplace = from_str(obj.get("marketplace")) - name = from_str(obj.get("name")) - cache_path = from_union([from_str, from_none], obj.get("cache_path")) - source = from_union([InstalledPluginSource.from_dict, from_str, from_none], obj.get("source")) - version = from_union([from_str, from_none], obj.get("version")) - return InstalledPlugin(enabled, installed_at, marketplace, name, cache_path, source, version) - - def to_dict(self) -> dict: - result: dict = {} - result["enabled"] = from_bool(self.enabled) - result["installed_at"] = from_str(self.installed_at) - result["marketplace"] = from_str(self.marketplace) - 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.source is not None: - result["source"] = from_union([lambda x: to_class(InstalledPluginSource, x), from_str, from_none], self.source) - if self.version is not None: - result["version"] = from_union([from_str, from_none], self.version) + result["requestId"] = from_str(self.request_id) + if self.error is not None: + result["error"] = from_union([from_str, from_none], self.error) + if self.result is not None: + result["result"] = from_union([lambda x: to_class(ExternalToolTextResultForLlm, x), from_str, from_none], self.result) return result # Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class SessionInstalledPlugin: - """Schema for the `SessionInstalledPlugin` type.""" - - enabled: bool - """Whether the plugin is currently enabled""" - - installed_at: str - """Installation timestamp (ISO-8601)""" - - marketplace: str - """Marketplace the plugin came from (empty string for direct repo installs)""" - - name: str - """Plugin name""" - - cache_path: str | None = None - """Path where the plugin is cached locally""" - - source: SessionInstalledPluginSource | str | None = None - """Source descriptor for direct repo installs (when marketplace is empty)""" +class SessionsSetAdditionalPluginsRequest: + """Manager-wide additional plugins to register; replaces any previously-configured set.""" - version: str | None = None - """Installed version, if known""" + plugins: list[InstalledPlugin] + """Manager-wide additional plugins to register. Replaces any previously-configured set. Pass + an empty array to clear. + """ @staticmethod - def from_dict(obj: Any) -> 'SessionInstalledPlugin': + def from_dict(obj: Any) -> 'SessionsSetAdditionalPluginsRequest': assert isinstance(obj, dict) - enabled = from_bool(obj.get("enabled")) - installed_at = from_str(obj.get("installed_at")) - marketplace = from_str(obj.get("marketplace")) - name = from_str(obj.get("name")) - cache_path = from_union([from_str, from_none], obj.get("cache_path")) - source = from_union([SessionInstalledPluginSource.from_dict, from_str, from_none], obj.get("source")) - version = from_union([from_str, from_none], obj.get("version")) - return SessionInstalledPlugin(enabled, installed_at, marketplace, name, cache_path, source, version) + plugins = from_list(InstalledPlugin.from_dict, obj.get("plugins")) + return SessionsSetAdditionalPluginsRequest(plugins) def to_dict(self) -> dict: result: dict = {} - result["enabled"] = from_bool(self.enabled) - result["installed_at"] = from_str(self.installed_at) - result["marketplace"] = from_str(self.marketplace) - 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.source is not None: - result["source"] = from_union([lambda x: to_class(SessionInstalledPluginSource, x), from_str, from_none], self.source) - if self.version is not None: - result["version"] = from_union([from_str, from_none], self.version) + result["plugins"] = from_list(lambda x: to_class(InstalledPlugin, x), self.plugins) return result # Experimental: this type is part of an experimental API and may change or be removed. @@ -20134,6 +20761,105 @@ def to_dict(self) -> dict: result["workspacePath"] = from_union([from_none, from_str], self.workspace_path) return result +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class WorkspacesListCheckpointsResult: + """Workspace checkpoints in chronological order; empty when the workspace is not enabled.""" + + checkpoints: list[WorkspacesCheckpoints] + """Workspace checkpoints in chronological order. Empty when workspace is not enabled.""" + + @staticmethod + def from_dict(obj: Any) -> 'WorkspacesListCheckpointsResult': + assert isinstance(obj, dict) + checkpoints = from_list(WorkspacesCheckpoints.from_dict, obj.get("checkpoints")) + return WorkspacesListCheckpointsResult(checkpoints) + + def to_dict(self) -> dict: + result: dict = {} + result["checkpoints"] = from_list(lambda x: to_class(WorkspacesCheckpoints, x), self.checkpoints) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class DebugCollectLogsRequest: + """Options for collecting a redacted session debug bundle.""" + + destination: DebugCollectLogsDestination + """Where the redacted bundle should be written. Use `archive` to produce a .tgz, or + `directory` to stage redacted files for caller-managed upload/post-processing. + """ + additional_entries: list[DebugCollectLogsEntry] | None = None + """Caller-provided server-local files or directories to include in addition to the runtime's + built-in session diagnostics. This lets host applications add their own diagnostics + without changing the API shape. + """ + include: DebugCollectLogsInclude | None = None + """Which built-in session diagnostics to include. Omitted fields default to true.""" + + @staticmethod + def from_dict(obj: Any) -> 'DebugCollectLogsRequest': + assert isinstance(obj, dict) + destination = DebugCollectLogsDestination.from_dict(obj.get("destination")) + additional_entries = from_union([lambda x: from_list(DebugCollectLogsEntry.from_dict, x), from_none], obj.get("additionalEntries")) + include = from_union([DebugCollectLogsInclude.from_dict, from_none], obj.get("include")) + return DebugCollectLogsRequest(destination, additional_entries, include) + + def to_dict(self) -> dict: + result: dict = {} + result["destination"] = to_class(DebugCollectLogsDestination, self.destination) + if self.additional_entries is not None: + result["additionalEntries"] = from_union([lambda x: from_list(lambda x: to_class(DebugCollectLogsEntry, x), x), from_none], self.additional_entries) + if self.include is not None: + result["include"] = from_union([lambda x: to_class(DebugCollectLogsInclude, x), from_none], self.include) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class InstructionDiscoveryPathList: + """Canonical files and directories where custom instructions can be created so the runtime + will recognize them. + """ + paths: list[InstructionDiscoveryPath] + """Canonical instruction create/discovery files and directories, in priority order""" + + @staticmethod + def from_dict(obj: Any) -> 'InstructionDiscoveryPathList': + assert isinstance(obj, dict) + paths = from_list(InstructionDiscoveryPath.from_dict, obj.get("paths")) + return InstructionDiscoveryPathList(paths) + + def to_dict(self) -> dict: + result: dict = {} + result["paths"] = from_list(lambda x: to_class(InstructionDiscoveryPath, x), self.paths) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class SessionFSReaddirWithTypesResult: + """Entries in the requested directory paired with file/directory type information, or a + filesystem error if the read failed. + """ + entries: list[SessionFSReaddirWithTypesEntry] + """Directory entries with type information""" + + error: SessionFSError | None = None + """Describes a filesystem error.""" + + @staticmethod + def from_dict(obj: Any) -> 'SessionFSReaddirWithTypesResult': + assert isinstance(obj, dict) + entries = from_list(SessionFSReaddirWithTypesEntry.from_dict, obj.get("entries")) + error = from_union([SessionFSError.from_dict, from_none], obj.get("error")) + return SessionFSReaddirWithTypesResult(entries, error) + + def to_dict(self) -> dict: + result: dict = {} + result["entries"] = from_list(lambda x: to_class(SessionFSReaddirWithTypesEntry, x), self.entries) + if self.error is not None: + result["error"] = from_union([lambda x: to_class(SessionFSError, x), from_none], self.error) + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class ProviderModelConfig: @@ -20293,28 +21019,6 @@ def to_dict(self) -> dict: result["userPolicy"] = from_union([lambda x: to_class(SandboxConfigUserPolicy, x), from_none], self.user_policy) return result -# Experimental: this type is part of an experimental API and may change or be removed. -@dataclass -class TasksGetProgressResult: - """Progress information for the task, or null when no task with that ID is tracked.""" - - progress: TaskProgress | None = None - """Progress information for the task, discriminated by type. Returns null when no task with - this ID is currently tracked. - """ - - @staticmethod - def from_dict(obj: Any) -> 'TasksGetProgressResult': - assert isinstance(obj, dict) - progress = from_union([TaskProgress.from_dict, from_none], obj.get("progress")) - return TasksGetProgressResult(progress) - - def to_dict(self) -> dict: - result: dict = {} - if self.progress is not None: - 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. @dataclass class UIElicitationSchema: @@ -20345,27 +21049,6 @@ def to_dict(self) -> dict: result["required"] = from_union([lambda x: from_list(from_str, x), from_none], self.required) return result -# Experimental: this type is part of an experimental API and may change or be removed. -@dataclass -class SessionsSetAdditionalPluginsRequest: - """Manager-wide additional plugins to register; replaces any previously-configured set.""" - - plugins: list[InstalledPlugin] - """Manager-wide additional plugins to register. Replaces any previously-configured set. Pass - an empty array to clear. - """ - - @staticmethod - def from_dict(obj: Any) -> 'SessionsSetAdditionalPluginsRequest': - assert isinstance(obj, dict) - plugins = from_list(InstalledPlugin.from_dict, obj.get("plugins")) - return SessionsSetAdditionalPluginsRequest(plugins) - - def to_dict(self) -> dict: - result: dict = {} - result["plugins"] = from_list(lambda x: to_class(InstalledPlugin, x), self.plugins) - return result - # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class ProviderAddRequest: @@ -20470,6 +21153,9 @@ class SessionOpenOptions: `assistant.message` event. Off by default; may change or be removed while the citations surface is experimental. """ + enable_managed_settings: bool | None = None + """Opt-in: self-fetch and enforce enterprise managed settings at session bootstrap.""" + enable_on_demand_instruction_discovery: bool | None = None """Whether on-demand custom instruction discovery is enabled.""" @@ -20590,6 +21276,9 @@ class SessionOpenOptions: trajectory_file: str | None = None """Optional trajectory output file path.""" + verbosity: Verbosity | None = None + """Initial output verbosity level for supported models.""" + working_directory: str | None = None """Working directory to anchor the session.""" @@ -20618,6 +21307,7 @@ def from_dict(obj: Any) -> 'SessionOpenOptions': disabled_instruction_sources = from_union([lambda x: from_list(from_str, x), from_none], obj.get("disabledInstructionSources")) disabled_skills = from_union([lambda x: from_list(from_str, x), from_none], obj.get("disabledSkills")) enable_citations = from_union([from_bool, from_none], obj.get("enableCitations")) + enable_managed_settings = from_union([from_bool, from_none], obj.get("enableManagedSettings")) enable_on_demand_instruction_discovery = from_union([from_bool, from_none], obj.get("enableOnDemandInstructionDiscovery")) enable_script_safety = from_union([from_bool, from_none], obj.get("enableScriptSafety")) enable_streaming = from_union([from_bool, from_none], obj.get("enableStreaming")) @@ -20655,9 +21345,10 @@ def from_dict(obj: Any) -> 'SessionOpenOptions': skill_directories = from_union([lambda x: from_list(from_str, x), from_none], obj.get("skillDirectories")) skip_custom_instructions = from_union([from_bool, from_none], obj.get("skipCustomInstructions")) 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")) working_directory_context = from_union([SessionContext.from_dict, from_none], obj.get("workingDirectoryContext")) - return SessionOpenOptions(additional_content_exclusion_policies, agent_context, allow_all_mcp_server_instructions, ask_user_disabled, auth_info, available_tools, capi, client_kind, client_name, coauthor_enabled, config_dir, continue_on_auto_mode, copilot_url, custom_agents_local_only, detached_from_spawning_parent_engagement_id, detached_from_spawning_parent_session_id, disabled_instruction_sources, disabled_skills, enable_citations, enable_on_demand_instruction_discovery, enable_script_safety, enable_streaming, env_value_mode, events_log_directory, excluded_builtin_agents, excluded_tools, exp_assignments, feature_flags, installed_plugins, integration_id, is_experimental_mode, log_interactive_shells, lsp_client_name, max_inline_binary_bytes, memory, model, model_capabilities_overrides, models, name, provider, providers, reasoning_effort, reasoning_summary, remote_defaulted_on, remote_exporting, remote_steerable, running_in_interactive_mode, sandbox_config, session_capabilities, session_id, session_limits, shell_init_profile, shell_process_flags, skill_directories, skip_custom_instructions, trajectory_file, working_directory, working_directory_context) + return SessionOpenOptions(additional_content_exclusion_policies, agent_context, allow_all_mcp_server_instructions, ask_user_disabled, auth_info, available_tools, capi, client_kind, client_name, coauthor_enabled, config_dir, continue_on_auto_mode, copilot_url, custom_agents_local_only, detached_from_spawning_parent_engagement_id, detached_from_spawning_parent_session_id, disabled_instruction_sources, disabled_skills, enable_citations, enable_managed_settings, enable_on_demand_instruction_discovery, enable_script_safety, enable_streaming, env_value_mode, events_log_directory, excluded_builtin_agents, excluded_tools, exp_assignments, feature_flags, installed_plugins, integration_id, is_experimental_mode, log_interactive_shells, lsp_client_name, max_inline_binary_bytes, memory, model, model_capabilities_overrides, models, name, provider, providers, reasoning_effort, reasoning_summary, remote_defaulted_on, remote_exporting, remote_steerable, running_in_interactive_mode, sandbox_config, session_capabilities, session_id, session_limits, shell_init_profile, shell_process_flags, skill_directories, skip_custom_instructions, trajectory_file, verbosity, working_directory, working_directory_context) def to_dict(self) -> dict: result: dict = {} @@ -20699,6 +21390,8 @@ def to_dict(self) -> dict: result["disabledSkills"] = from_union([lambda x: from_list(from_str, x), from_none], self.disabled_skills) if self.enable_citations is not None: result["enableCitations"] = from_union([from_bool, from_none], self.enable_citations) + if self.enable_managed_settings is not None: + result["enableManagedSettings"] = from_union([from_bool, from_none], self.enable_managed_settings) if self.enable_on_demand_instruction_discovery is not None: result["enableOnDemandInstructionDiscovery"] = from_union([from_bool, from_none], self.enable_on_demand_instruction_discovery) if self.enable_script_safety is not None: @@ -20773,6 +21466,8 @@ def to_dict(self) -> dict: result["skipCustomInstructions"] = from_union([from_bool, from_none], self.skip_custom_instructions) if self.trajectory_file is not None: result["trajectoryFile"] = from_union([from_str, from_none], self.trajectory_file) + if self.verbosity is not None: + result["verbosity"] = from_union([lambda x: to_enum(Verbosity, x), from_none], self.verbosity) if self.working_directory is not None: result["workingDirectory"] = from_union([from_str, from_none], self.working_directory) if self.working_directory_context is not None: @@ -20963,6 +21658,9 @@ class SessionUpdateOptionsParams: trajectory_file: str | None = None """Optional path for trajectory output.""" + verbosity: Verbosity | None = None + """Output verbosity level for supported models.""" + working_directory: str | None = None """Absolute working-directory path for shell tools.""" @@ -21021,8 +21719,9 @@ def from_dict(obj: Any) -> 'SessionUpdateOptionsParams': suppress_custom_agent_prompt = from_union([from_bool, from_none], obj.get("suppressCustomAgentPrompt")) tool_filter_precedence = from_union([OptionsUpdateToolFilterPrecedence, from_none], obj.get("toolFilterPrecedence")) trajectory_file = from_union([from_str, from_none], obj.get("trajectoryFile")) + verbosity = from_union([Verbosity, from_none], obj.get("verbosity")) working_directory = from_union([from_str, from_none], obj.get("workingDirectory")) - return SessionUpdateOptionsParams(additional_content_exclusion_policies, agent_context, allow_all_mcp_server_instructions, ask_user_disabled, available_tools, capi, client_name, coauthor_enabled, context_tier, continue_on_auto_mode, copilot_url, custom_agents_local_only, disabled_instruction_sources, disabled_skills, enable_file_hooks, enable_host_git_operations, enable_on_demand_instruction_discovery, enable_reasoning_summaries, enable_script_safety, enable_session_store, enable_skills, enable_streaming, env_value_mode, events_log_directory, excluded_builtin_agents, excluded_tools, feature_flags, installed_plugins, integration_id, is_experimental_mode, log_interactive_shells, lsp_client_name, manage_schedule_enabled, max_inline_binary_bytes, model, model_capabilities_overrides, organization_custom_instructions, provider, reasoning_effort, reasoning_summary, running_in_interactive_mode, sandbox_config, session_capabilities, session_limits, shell_init_profile, shell_process_flags, skill_directories, skip_custom_instructions, skip_embedding_retrieval, suppress_custom_agent_prompt, tool_filter_precedence, trajectory_file, 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, excluded_builtin_agents, excluded_tools, feature_flags, installed_plugins, integration_id, is_experimental_mode, log_interactive_shells, lsp_client_name, manage_schedule_enabled, max_inline_binary_bytes, model, model_capabilities_overrides, organization_custom_instructions, provider, reasoning_effort, reasoning_summary, running_in_interactive_mode, sandbox_config, session_capabilities, session_limits, shell_init_profile, shell_process_flags, skill_directories, skip_custom_instructions, skip_embedding_retrieval, suppress_custom_agent_prompt, tool_filter_precedence, trajectory_file, verbosity, working_directory) def to_dict(self) -> dict: result: dict = {} @@ -21130,6 +21829,8 @@ def to_dict(self) -> dict: result["toolFilterPrecedence"] = from_union([lambda x: to_enum(OptionsUpdateToolFilterPrecedence, x), from_none], self.tool_filter_precedence) if self.trajectory_file is not None: result["trajectoryFile"] = from_union([from_str, from_none], self.trajectory_file) + if self.verbosity is not None: + result["verbosity"] = from_union([lambda x: to_enum(Verbosity, x), from_none], self.verbosity) if self.working_directory is not None: result["workingDirectory"] = from_union([from_str, from_none], self.working_directory) return result @@ -21291,13 +21992,191 @@ def from_dict(obj: Any) -> 'SessionsOpenResumeLast': def to_dict(self) -> dict: result: dict = {} - result["kind"] = self.kind - if self.context is not None: - result["context"] = from_union([lambda x: to_class(SessionContext, x), from_none], self.context) - if self.options is not None: - result["options"] = from_union([lambda x: to_class(SessionOpenOptions, x), from_none], self.options) - if self.suppress_resume_workspace_metadata_writeback is not None: - result["suppressResumeWorkspaceMetadataWriteback"] = from_union([from_bool, from_none], self.suppress_resume_workspace_metadata_writeback) + result["kind"] = self.kind + if self.context is not None: + result["context"] = from_union([lambda x: to_class(SessionContext, x), from_none], self.context) + if self.options is not None: + result["options"] = from_union([lambda x: to_class(SessionOpenOptions, x), from_none], self.options) + if self.suppress_resume_workspace_metadata_writeback is not None: + result["suppressResumeWorkspaceMetadataWriteback"] = from_union([from_bool, from_none], self.suppress_resume_workspace_metadata_writeback) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class CopilotUserResponse: + """Snapshot of the authenticated user's Copilot subscription info, if known. Mirrors the + GitHub API `/copilot_internal/v2/token` user response shape — the runtime trusts this + verbatim and does not re-fetch when set. + """ + access_type_sku: str | None = None + """Copilot access SKU identifier (e.g. `free_limited_copilot`, + `copilot_for_business_seat_quota`) used to gate model and feature access. + """ + analytics_tracking_id: str | None = None + """Opaque analytics tracking identifier for the user, forwarded from the Copilot API.""" + + assigned_date: Any = None + """Date the Copilot seat was assigned to the user, if applicable.""" + + can_signup_for_limited: bool | None = None + """Whether the user is eligible to sign up for the free/limited Copilot tier.""" + + can_upgrade_plan: bool | None = None + """Whether the user is able to upgrade their Copilot plan.""" + + chat_enabled: bool | None = None + """Whether Copilot chat is enabled for the user.""" + + cli_remote_control_enabled: bool | None = None + """Whether CLI remote control is enabled for the user.""" + + cloud_session_storage_enabled: bool | None = None + """Whether cloud session storage is enabled for the user.""" + + codex_agent_enabled: bool | None = None + """Whether the Codex agent is enabled for the user.""" + + copilot_plan: str | None = None + """Copilot plan name for the user (e.g. `individual`, `business`, `enterprise`).""" + + copilotignore_enabled: bool | None = None + """Whether `.copilotignore` content-exclusion support is enabled for the user.""" + + endpoints: CopilotUserResponseEndpoints | None = None + """Endpoint URLs from the raw Copilot `/copilot_internal/v2/token` user-response passthrough.""" + + is_mcp_enabled: Any = None + """Whether MCP (Model Context Protocol) support is enabled for the user.""" + + is_staff: bool | None = None + """Whether the user is a GitHub/Microsoft staff member.""" + + limited_user_quotas: dict[str, float] | None = None + """Per-category quota allotments for free/limited-tier users, keyed by quota category.""" + + limited_user_reset_date: str | None = None + """Date the free/limited-tier user's quotas next reset, as a raw string from the Copilot API.""" + + login: str | None = None + """GitHub login of the authenticated user.""" + + monthly_quotas: dict[str, float] | None = None + """Per-category monthly quota allotments, keyed by quota category.""" + + organization_list: Any = None + """Organizations the user belongs to, each with an optional login and display name.""" + + organization_login_list: list[str] | None = None + """Logins of the organizations the user belongs to.""" + + quota_reset_date: str | None = None + """Date the user's usage quota next resets, as a raw string from the Copilot API; see + `quota_reset_date_utc` for the UTC-normalized value. + """ + quota_reset_date_utc: str | None = None + """UTC-normalized form of `quota_reset_date` (the date the user's usage quota next resets).""" + + quota_snapshots: dict[str, CopilotUserResponseQuotaSnapshots | None] | None = None + """Quota snapshot map from the raw Copilot user-response passthrough, with chat, + completions, premium-interactions, and other entries. + """ + restricted_telemetry: bool | None = None + """Whether the user's telemetry is subject to restricted-data handling.""" + + te: bool | None = None + """Raw passthrough of the Copilot API `te` flag for the user (an opaque server-side + eligibility signal surfaced in telemetry); not otherwise interpreted by the runtime. + """ + token_based_billing: bool | None = None + """Whether the account is on usage-based (token/AI-credit) billing rather than a fixed + premium-request quota. + """ + + @staticmethod + def from_dict(obj: Any) -> 'CopilotUserResponse': + assert isinstance(obj, dict) + access_type_sku = from_union([from_str, from_none], obj.get("access_type_sku")) + analytics_tracking_id = from_union([from_str, from_none], obj.get("analytics_tracking_id")) + assigned_date = obj.get("assigned_date") + can_signup_for_limited = from_union([from_bool, from_none], obj.get("can_signup_for_limited")) + can_upgrade_plan = from_union([from_bool, from_none], obj.get("can_upgrade_plan")) + chat_enabled = from_union([from_bool, from_none], obj.get("chat_enabled")) + cli_remote_control_enabled = from_union([from_bool, from_none], obj.get("cli_remote_control_enabled")) + cloud_session_storage_enabled = from_union([from_bool, from_none], obj.get("cloud_session_storage_enabled")) + codex_agent_enabled = from_union([from_bool, from_none], obj.get("codex_agent_enabled")) + copilot_plan = from_union([from_str, from_none], obj.get("copilot_plan")) + copilotignore_enabled = from_union([from_bool, from_none], obj.get("copilotignore_enabled")) + endpoints = from_union([CopilotUserResponseEndpoints.from_dict, from_none], obj.get("endpoints")) + is_mcp_enabled = obj.get("is_mcp_enabled") + is_staff = from_union([from_bool, from_none], obj.get("is_staff")) + limited_user_quotas = from_union([lambda x: from_dict(from_float, x), from_none], obj.get("limited_user_quotas")) + limited_user_reset_date = from_union([from_str, from_none], obj.get("limited_user_reset_date")) + login = from_union([from_str, from_none], obj.get("login")) + monthly_quotas = from_union([lambda x: from_dict(from_float, x), from_none], obj.get("monthly_quotas")) + organization_list = obj.get("organization_list") + organization_login_list = from_union([lambda x: from_list(from_str, x), from_none], obj.get("organization_login_list")) + quota_reset_date = from_union([from_str, from_none], obj.get("quota_reset_date")) + quota_reset_date_utc = from_union([from_str, from_none], obj.get("quota_reset_date_utc")) + quota_snapshots = from_union([lambda x: from_dict(lambda x: from_union([CopilotUserResponseQuotaSnapshots.from_dict, from_none], x), x), from_none], obj.get("quota_snapshots")) + restricted_telemetry = from_union([from_bool, from_none], obj.get("restricted_telemetry")) + te = from_union([from_bool, from_none], obj.get("te")) + token_based_billing = from_union([from_bool, from_none], obj.get("token_based_billing")) + return CopilotUserResponse(access_type_sku, analytics_tracking_id, assigned_date, can_signup_for_limited, can_upgrade_plan, chat_enabled, cli_remote_control_enabled, cloud_session_storage_enabled, codex_agent_enabled, copilot_plan, copilotignore_enabled, endpoints, is_mcp_enabled, is_staff, limited_user_quotas, limited_user_reset_date, login, monthly_quotas, organization_list, organization_login_list, quota_reset_date, quota_reset_date_utc, quota_snapshots, restricted_telemetry, te, token_based_billing) + + def to_dict(self) -> dict: + result: dict = {} + if self.access_type_sku is not None: + result["access_type_sku"] = from_union([from_str, from_none], self.access_type_sku) + if self.analytics_tracking_id is not None: + result["analytics_tracking_id"] = from_union([from_str, from_none], self.analytics_tracking_id) + if self.assigned_date is not None: + result["assigned_date"] = self.assigned_date + if self.can_signup_for_limited is not None: + result["can_signup_for_limited"] = from_union([from_bool, from_none], self.can_signup_for_limited) + if self.can_upgrade_plan is not None: + result["can_upgrade_plan"] = from_union([from_bool, from_none], self.can_upgrade_plan) + if self.chat_enabled is not None: + result["chat_enabled"] = from_union([from_bool, from_none], self.chat_enabled) + if self.cli_remote_control_enabled is not None: + result["cli_remote_control_enabled"] = from_union([from_bool, from_none], self.cli_remote_control_enabled) + if self.cloud_session_storage_enabled is not None: + result["cloud_session_storage_enabled"] = from_union([from_bool, from_none], self.cloud_session_storage_enabled) + if self.codex_agent_enabled is not None: + result["codex_agent_enabled"] = from_union([from_bool, from_none], self.codex_agent_enabled) + if self.copilot_plan is not None: + result["copilot_plan"] = from_union([from_str, from_none], self.copilot_plan) + if self.copilotignore_enabled is not None: + result["copilotignore_enabled"] = from_union([from_bool, from_none], self.copilotignore_enabled) + if self.endpoints is not None: + result["endpoints"] = from_union([lambda x: to_class(CopilotUserResponseEndpoints, x), from_none], self.endpoints) + if self.is_mcp_enabled is not None: + result["is_mcp_enabled"] = self.is_mcp_enabled + if self.is_staff is not None: + result["is_staff"] = from_union([from_bool, from_none], self.is_staff) + if self.limited_user_quotas is not None: + result["limited_user_quotas"] = from_union([lambda x: from_dict(to_float, x), from_none], self.limited_user_quotas) + if self.limited_user_reset_date is not None: + result["limited_user_reset_date"] = from_union([from_str, from_none], self.limited_user_reset_date) + if self.login is not None: + result["login"] = from_union([from_str, from_none], self.login) + if self.monthly_quotas is not None: + result["monthly_quotas"] = from_union([lambda x: from_dict(to_float, x), from_none], self.monthly_quotas) + if self.organization_list is not None: + result["organization_list"] = self.organization_list + if self.organization_login_list is not None: + result["organization_login_list"] = from_union([lambda x: from_list(from_str, x), from_none], self.organization_login_list) + if self.quota_reset_date is not None: + result["quota_reset_date"] = from_union([from_str, from_none], self.quota_reset_date) + if self.quota_reset_date_utc is not None: + result["quota_reset_date_utc"] = from_union([from_str, from_none], self.quota_reset_date_utc) + if self.quota_snapshots is not None: + result["quota_snapshots"] = from_union([lambda x: from_dict(lambda x: from_union([lambda x: to_class(CopilotUserResponseQuotaSnapshots, x), from_none], x), x), from_none], self.quota_snapshots) + if self.restricted_telemetry is not None: + result["restricted_telemetry"] = from_union([from_bool, from_none], self.restricted_telemetry) + if self.te is not None: + result["te"] = from_union([from_bool, from_none], self.te) + if self.token_based_billing is not None: + result["token_based_billing"] = from_union([from_bool, from_none], self.token_based_billing) return result # Experimental: this type is part of an experimental API and may change or be removed. @@ -21522,56 +22401,223 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class CurrentToolMetadata: - """Lightweight metadata for a currently initialized session tool""" - - description: str - """Tool description""" - - name: str - """Model-facing tool name""" - - defer_loading: bool | None = None - """Whether the tool is loaded on demand via tool search""" +class APIKeyAuthInfo: + """Authentication-info variant for API-key authentication to a non-GitHub LLM provider, + carrying the secret `apiKey` and host. + """ + api_key: str + """The API key. Treat as a secret.""" + + host: str + """Authentication host.""" + + type: ClassVar[str] = "api-key" + """API-key authentication for non-GitHub LLM providers (e.g. when running BYOM-style).""" + + 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) -> 'APIKeyAuthInfo': + assert isinstance(obj, dict) + api_key = from_str(obj.get("apiKey")) + host = from_str(obj.get("host")) + copilot_user = from_union([CopilotUserResponse.from_dict, from_none], obj.get("copilotUser")) + return APIKeyAuthInfo(api_key, host, copilot_user) + + def to_dict(self) -> dict: + result: dict = {} + result["apiKey"] = from_str(self.api_key) + result["host"] = from_str(self.host) + 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 CopilotAPITokenAuthInfo: + """Authentication-info variant for direct Copilot API token auth sourced from environment + variables, with public GitHub host. + """ + host: Host + """Authentication host (always the public GitHub host).""" + + type: ClassVar[str] = "copilot-api-token" + """Direct Copilot API authentication via the `GITHUB_COPILOT_API_TOKEN` + `COPILOT_API_URL` + environment-variable pair. The token itself is read from the environment by the runtime, + not carried in this struct. + """ + 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) -> 'CopilotAPITokenAuthInfo': + assert isinstance(obj, dict) + host = Host(obj.get("host")) + copilot_user = from_union([CopilotUserResponse.from_dict, from_none], obj.get("copilotUser")) + return CopilotAPITokenAuthInfo(host, copilot_user) + + def to_dict(self) -> dict: + result: dict = {} + result["host"] = to_enum(Host, self.host) + 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 CurrentToolMetadata: + """Lightweight metadata for a currently initialized session tool""" + + description: str + """Tool description""" + + name: str + """Model-facing tool name""" + + defer_loading: bool | None = None + """Whether the tool is loaded on demand via tool search""" + + input_schema: dict[str, Any] | None = None + """JSON Schema for tool input""" + + mcp_server_name: str | None = None + """MCP server name for MCP-backed tools""" + + mcp_tool_name: str | None = None + """Raw MCP tool name for MCP-backed tools""" + + namespaced_name: str | None = None + """Optional MCP/config namespaced tool name""" + + @staticmethod + def from_dict(obj: Any) -> 'CurrentToolMetadata': + assert isinstance(obj, dict) + description = from_str(obj.get("description")) + name = from_str(obj.get("name")) + defer_loading = from_union([from_bool, from_none], obj.get("deferLoading")) + input_schema = from_union([lambda x: from_dict(lambda x: x, x), from_none], obj.get("input_schema")) + mcp_server_name = from_union([from_str, from_none], obj.get("mcpServerName")) + mcp_tool_name = from_union([from_str, from_none], obj.get("mcpToolName")) + namespaced_name = from_union([from_str, from_none], obj.get("namespacedName")) + return CurrentToolMetadata(description, name, defer_loading, input_schema, mcp_server_name, mcp_tool_name, namespaced_name) + + def to_dict(self) -> dict: + result: dict = {} + result["description"] = from_str(self.description) + result["name"] = from_str(self.name) + if self.defer_loading is not None: + result["deferLoading"] = from_union([from_bool, from_none], self.defer_loading) + if self.input_schema is not None: + result["input_schema"] = from_union([lambda x: from_dict(lambda x: x, x), from_none], self.input_schema) + if self.mcp_server_name is not None: + result["mcpServerName"] = from_union([from_str, from_none], self.mcp_server_name) + if self.mcp_tool_name is not None: + result["mcpToolName"] = from_union([from_str, from_none], self.mcp_tool_name) + if self.namespaced_name is not None: + result["namespacedName"] = from_union([from_str, from_none], self.namespaced_name) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class EnvAuthInfo: + """Authentication-info variant for a token sourced from an environment variable, with host, + optional login, token, and env var name. + """ + env_var: str + """Name of the environment variable the token was sourced from.""" + + host: str + """Authentication host (e.g. https://github.com or a GHES host).""" + + token: str + """The token value itself. Treat as a secret.""" + + type: ClassVar[str] = "env" + """Personal access token (PAT) or server-to-server token sourced from an environment + variable. + """ + 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. + """ + login: str | None = None + """User login associated with the token. Undefined for server-to-server tokens (those + starting with `ghs_`). + """ + + @staticmethod + def from_dict(obj: Any) -> 'EnvAuthInfo': + assert isinstance(obj, dict) + env_var = from_str(obj.get("envVar")) + host = from_str(obj.get("host")) + token = from_str(obj.get("token")) + copilot_user = from_union([CopilotUserResponse.from_dict, from_none], obj.get("copilotUser")) + login = from_union([from_str, from_none], obj.get("login")) + return EnvAuthInfo(env_var, host, token, copilot_user, login) + + def to_dict(self) -> dict: + result: dict = {} + result["envVar"] = from_str(self.env_var) + 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.login is not None: + result["login"] = from_union([from_str, from_none], self.login) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class GhCLIAuthInfo: + """Authentication-info variant for GitHub CLI credentials, carrying host, login, and the `gh + auth token` value. + """ + host: str + """Authentication host.""" - input_schema: dict[str, Any] | None = None - """JSON Schema for tool input""" + login: str + """User login as reported by `gh auth status`.""" - mcp_server_name: str | None = None - """MCP server name for MCP-backed tools""" + token: str + """The token returned by `gh auth token`. Treat as a secret.""" - mcp_tool_name: str | None = None - """Raw MCP tool name for MCP-backed tools""" + type: ClassVar[str] = "gh-cli" + """Authentication via the `gh` CLI's saved credentials.""" - namespaced_name: str | None = None - """Optional MCP/config namespaced tool name""" + 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) -> 'CurrentToolMetadata': + def from_dict(obj: Any) -> 'GhCLIAuthInfo': assert isinstance(obj, dict) - description = from_str(obj.get("description")) - name = from_str(obj.get("name")) - defer_loading = from_union([from_bool, from_none], obj.get("deferLoading")) - input_schema = from_union([lambda x: from_dict(lambda x: x, x), from_none], obj.get("input_schema")) - mcp_server_name = from_union([from_str, from_none], obj.get("mcpServerName")) - mcp_tool_name = from_union([from_str, from_none], obj.get("mcpToolName")) - namespaced_name = from_union([from_str, from_none], obj.get("namespacedName")) - return CurrentToolMetadata(description, name, defer_loading, input_schema, mcp_server_name, mcp_tool_name, namespaced_name) + host = from_str(obj.get("host")) + login = from_str(obj.get("login")) + token = from_str(obj.get("token")) + copilot_user = from_union([CopilotUserResponse.from_dict, from_none], obj.get("copilotUser")) + return GhCLIAuthInfo(host, login, token, copilot_user) def to_dict(self) -> dict: result: dict = {} - result["description"] = from_str(self.description) - result["name"] = from_str(self.name) - if self.defer_loading is not None: - result["deferLoading"] = from_union([from_bool, from_none], self.defer_loading) - if self.input_schema is not None: - result["input_schema"] = from_union([lambda x: from_dict(lambda x: x, x), from_none], self.input_schema) - if self.mcp_server_name is not None: - result["mcpServerName"] = from_union([from_str, from_none], self.mcp_server_name) - if self.mcp_tool_name is not None: - result["mcpToolName"] = from_union([from_str, from_none], self.mcp_tool_name) - if self.namespaced_name is not None: - result["namespacedName"] = from_union([from_str, from_none], self.namespaced_name) + result["host"] = from_str(self.host) + result["login"] = from_str(self.login) + 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. @@ -21654,8 +22700,8 @@ def to_dict(self) -> dict: @dataclass class GitHubTelemetryNotification: """Payload for a `gitHubTelemetry.event` notification: a single GitHub telemetry event the - runtime forwards to a host connection that opted into telemetry forwarding for the - session. + runtime forwards to a host connection that opted into telemetry forwarding during the + `server.connect` handshake. """ event: GitHubTelemetryEvent """The telemetry event, in the runtime's native GitHub-shaped telemetry format.""" @@ -21664,22 +22710,64 @@ class GitHubTelemetryNotification: """Whether this is a restricted telemetry event (cli.restricted_telemetry). Hosts must route restricted events to first-party Microsoft stores only. """ - session_id: str - """Session the telemetry event belongs to.""" + session_id: str | None = None + """Session the telemetry event belongs to, when it is session-scoped. Omitted for + sessionless events (for example, `server.sendTelemetry` calls with no session id), which + are still forwarded to opted-in connections. + """ @staticmethod def from_dict(obj: Any) -> 'GitHubTelemetryNotification': assert isinstance(obj, dict) event = GitHubTelemetryEvent.from_dict(obj.get("event")) restricted = from_bool(obj.get("restricted")) - session_id = from_str(obj.get("sessionId")) + session_id = from_union([from_str, from_none], obj.get("sessionId")) return GitHubTelemetryNotification(event, restricted, session_id) def to_dict(self) -> dict: result: dict = {} result["event"] = to_class(GitHubTelemetryEvent, self.event) result["restricted"] = from_bool(self.restricted) - result["sessionId"] = from_str(self.session_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 HMACAuthInfo: + """Authentication-info variant for GitHub-internal HMAC auth, carrying the public GitHub + host and HMAC secret. + """ + hmac: str + """HMAC secret used to sign requests.""" + + host: Host + """Authentication host. HMAC auth always targets the public GitHub host.""" + + type: ClassVar[str] = "hmac" + """HMAC-based authentication used by GitHub-internal services.""" + + 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) -> 'HMACAuthInfo': + assert isinstance(obj, dict) + hmac = from_str(obj.get("hmac")) + host = Host(obj.get("host")) + copilot_user = from_union([CopilotUserResponse.from_dict, from_none], obj.get("copilotUser")) + return HMACAuthInfo(hmac, host, copilot_user) + + def to_dict(self) -> dict: + result: dict = {} + result["hmac"] = from_str(self.hmac) + result["host"] = to_enum(Host, self.host) + 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. @@ -21722,34 +22810,6 @@ def to_dict(self) -> dict: result["serverName"] = from_str(self.server_name) return result -# Experimental: this type is part of an experimental API and may change or be removed. -# Internal: this type is an internal SDK API and is not part of the public surface. -@dataclass -class MCPOauthRespondRequest: - """MCP OAuth request id and optional provider response.""" - - request_id: str - """OAuth request identifier from mcp.oauth_required""" - - provider: Any = None - """In-process OAuthClientProvider instance, or omitted to deny. Marked internal: cannot be - serialized across the JSON-RPC boundary. - """ - - @staticmethod - def from_dict(obj: Any) -> 'MCPOauthRespondRequest': - assert isinstance(obj, dict) - request_id = from_str(obj.get("requestId")) - provider = obj.get("provider") - return MCPOauthRespondRequest(request_id, provider) - - def to_dict(self) -> dict: - result: dict = {} - result["requestId"] = from_str(self.request_id) - if self.provider is not None: - result["provider"] = self.provider - 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 @@ -21879,8 +22939,9 @@ class ModelPickerCategory(Enum): # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class Model: - """Schema for the `Model` type.""" - + """Copilot model metadata, including identifier, display name, capabilities, policy, + billing, reasoning efforts, and picker categories. + """ capabilities: ModelCapabilities """Model capabilities and limits""" @@ -21985,6 +23046,9 @@ class ModelSwitchToRequest: reasoning_summary: ReasoningSummary | None = None """Reasoning summary mode to request for supported model clients""" + verbosity: Verbosity | None = None + """Output verbosity level to request for supported models""" + @staticmethod def from_dict(obj: Any) -> 'ModelSwitchToRequest': assert isinstance(obj, dict) @@ -21993,7 +23057,8 @@ def from_dict(obj: Any) -> 'ModelSwitchToRequest': model_capabilities = from_union([ModelCapabilitiesOverride.from_dict, from_none], obj.get("modelCapabilities")) reasoning_effort = from_union([from_str, from_none], obj.get("reasoningEffort")) reasoning_summary = from_union([ReasoningSummary, from_none], obj.get("reasoningSummary")) - return ModelSwitchToRequest(model_id, context_tier, model_capabilities, reasoning_effort, reasoning_summary) + verbosity = from_union([Verbosity, from_none], obj.get("verbosity")) + return ModelSwitchToRequest(model_id, context_tier, model_capabilities, reasoning_effort, reasoning_summary, verbosity) def to_dict(self) -> dict: result: dict = {} @@ -22006,6 +23071,8 @@ def to_dict(self) -> dict: result["reasoningEffort"] = from_union([from_str, from_none], self.reasoning_effort) if self.reasoning_summary is not None: result["reasoningSummary"] = from_union([lambda x: to_enum(ReasoningSummary, x), from_none], self.reasoning_summary) + if self.verbosity is not None: + result["verbosity"] = from_union([lambda x: to_enum(Verbosity, x), from_none], self.verbosity) return result # Experimental: this type is part of an experimental API and may change or be removed. @@ -22020,24 +23087,40 @@ class PermissionsSetAAllSource(Enum): # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class PermissionsSetAllowAllRequest: - """Whether to enable full allow-all permissions for the session.""" - - enabled: bool - """Whether to enable full allow-all permissions""" + """Allow-all mode to apply for the session.""" + enabled: bool | None = None + """Legacy full allow-all toggle. Prefer `mode`; when `mode` is omitted, `enabled: true` is + treated as `mode: "on"` and any other value is treated as `mode: "off"`. + """ + mode: PermissionsAllowAllMode | None = None + """Allow-all mode to apply. `on` enables full allow-all; `auto` enables advisory LLM + auto-approval; `off` disables both. + """ + model: str | None = None + """Optional model id for the `auto` mode auto-approval LLM judging. Only meaningful when + `mode` is `auto`; ignored otherwise. When omitted, the session's active model is used. + """ source: PermissionsSetAAllSource | None = None """Optional source for allow-all telemetry. Defaults to `rpc` when omitted for SDK callers.""" @staticmethod def from_dict(obj: Any) -> 'PermissionsSetAllowAllRequest': assert isinstance(obj, dict) - enabled = from_bool(obj.get("enabled")) + enabled = from_union([from_bool, from_none], obj.get("enabled")) + mode = from_union([PermissionsAllowAllMode, from_none], obj.get("mode")) + model = from_union([from_str, from_none], obj.get("model")) source = from_union([PermissionsSetAAllSource, from_none], obj.get("source")) - return PermissionsSetAllowAllRequest(enabled, source) + return PermissionsSetAllowAllRequest(enabled, mode, model, source) def to_dict(self) -> dict: result: dict = {} - result["enabled"] = from_bool(self.enabled) + if self.enabled is not None: + result["enabled"] = from_union([from_bool, from_none], self.enabled) + if self.mode is not None: + result["mode"] = from_union([lambda x: to_enum(PermissionsAllowAllMode, x), from_none], self.mode) + if self.model is not None: + result["model"] = from_union([from_str, from_none], self.model) if self.source is not None: result["source"] = from_union([lambda x: to_enum(PermissionsSetAAllSource, x), from_none], self.source) return result @@ -22172,6 +23255,15 @@ class SessionsOpenHandoff: `sessions.list` with `source: "remote"`). """ # Internal: this field is an internal SDK API and is not part of the public surface. + on_confirm: Any = None + """In-process confirmation callback `(request) => boolean | Promise` invoked when + the handoff needs the caller to confirm a non-fatal blocker (e.g. a repository mismatch + between the current working directory and the remote session). Returning `true` proceeds + with the handoff; returning `false` (or omitting the callback) aborts it. Marked internal + because a function reference cannot cross the JSON-RPC boundary, for the same reasons as + `onProgress`. + """ + # Internal: this field is an internal SDK API and is not part of the public surface. on_progress: Any = None """In-process progress callback `(update) => void` invoked for each handoff step. Marked internal because a function reference cannot cross the JSON-RPC boundary. The host-side @@ -22192,15 +23284,18 @@ class SessionsOpenHandoff: def from_dict(obj: Any) -> 'SessionsOpenHandoff': assert isinstance(obj, dict) metadata = RemoteSessionMetadataValue.from_dict(obj.get("metadata")) + on_confirm = obj.get("onConfirm") on_progress = obj.get("onProgress") options = from_union([SessionOpenOptions.from_dict, from_none], obj.get("options")) task_type = from_union([TaskType, from_none], obj.get("taskType")) - return SessionsOpenHandoff(metadata, on_progress, options, task_type) + return SessionsOpenHandoff(metadata, on_confirm, on_progress, options, task_type) def to_dict(self) -> dict: result: dict = {} result["kind"] = self.kind result["metadata"] = to_class(RemoteSessionMetadataValue, self.metadata) + if self.on_confirm is not None: + result["onConfirm"] = self.on_confirm if self.on_progress is not None: result["onProgress"] = self.on_progress if self.options is not None: @@ -22280,6 +23375,44 @@ def to_dict(self) -> dict: result["maxDepth"] = from_union([from_int, from_none], self.max_depth) return result +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class TokenAuthInfo: + """Authentication-info variant for SDK-configured token authentication, carrying host and + the secret token value. + """ + 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) -> 'TokenAuthInfo': + 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 TokenAuthInfo(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 ToolsGetCurrentMetadataResult: @@ -22357,6 +23490,45 @@ def to_dict(self) -> dict: result["subagents"] = from_union([lambda x: to_class(SubagentSettings, x), from_none], self.subagents) return result +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class UserAuthInfo: + """Authentication-info variant for OAuth user auth, with host and login; the token remains + in the runtime secret store. + """ + host: str + """Authentication host.""" + + login: str + """OAuth user login.""" + + type: ClassVar[str] = "user" + """OAuth user authentication. The token itself is held in the runtime's secret token store + (keyed by host+login) and is NOT carried in this struct. + """ + 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) -> 'UserAuthInfo': + assert isinstance(obj, dict) + host = from_str(obj.get("host")) + login = from_str(obj.get("login")) + copilot_user = from_union([CopilotUserResponse.from_dict, from_none], obj.get("copilotUser")) + return UserAuthInfo(host, login, copilot_user) + + def to_dict(self) -> dict: + result: dict = {} + result["host"] = from_str(self.host) + result["login"] = from_str(self.login) + 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 + @dataclass class RPC: abort_request: AbortRequest @@ -22440,6 +23612,7 @@ class RPC: connect_request: _ConnectRequest connect_result: _ConnectResult content_filter_mode: ContentFilterMode + context_heaviest_message: ContextHeaviestMessage copilot_api_token_auth_info: CopilotAPITokenAuthInfo copilot_user_response: CopilotUserResponse copilot_user_response_endpoints: CopilotUserResponseEndpoints @@ -22449,6 +23622,17 @@ class RPC: copilot_user_response_quota_snapshots_premium_interactions: CopilotUserResponseQuotaSnapshotsPremiumInteractions current_model: CurrentModel current_tool_metadata: CurrentToolMetadata + debug_collect_logs_collected_entry: DebugCollectLogsCollectedEntry + debug_collect_logs_destination: DebugCollectLogsDestination + debug_collect_logs_entry: DebugCollectLogsEntry + debug_collect_logs_entry_kind: DebugCollectLogsEntryKind + debug_collect_logs_include: DebugCollectLogsInclude + debug_collect_logs_redaction: DebugCollectLogsRedaction + debug_collect_logs_request: DebugCollectLogsRequest + debug_collect_logs_result: DebugCollectLogsResult + debug_collect_logs_result_kind: DebugCollectLogsResultKind + debug_collect_logs_skipped_entry: DebugCollectLogsSkippedEntry + debug_collect_logs_source: DebugCollectLogsSource discovered_canvas: DiscoveredCanvas discovered_mcp_server: DiscoveredMCPServer discovered_mcp_server_type: DiscoveredMCPServerType @@ -22514,7 +23698,7 @@ class RPC: installed_plugin_source_local: InstalledPluginSourceLocal installed_plugin_source_url: InstalledPluginSourceURL instruction_discovery_path: InstructionDiscoveryPath - instruction_discovery_path_kind: InstructionDiscoveryPathKind + instruction_discovery_path_kind: DebugCollectLogsEntryKind instruction_discovery_path_list: InstructionDiscoveryPathList instruction_discovery_path_location: InstructionLocation instructions_discover_request: InstructionsDiscoverRequest @@ -22602,8 +23786,6 @@ class RPC: mcp_oauth_login_request: MCPOauthLoginRequest mcp_oauth_login_result: MCPOauthLoginResult mcp_oauth_pending_request_response: MCPOauthPendingRequestResponse - mcp_oauth_respond_request: MCPOauthRespondRequest - mcp_oauth_respond_result: MCPOauthRespondResult mcp_register_external_client_request: MCPRegisterExternalClientRequest mcp_reload_with_config_request: MCPReloadWithConfigRequest mcp_remove_git_hub_result: MCPRemoveGitHubResult @@ -22631,6 +23813,9 @@ class RPC: mcp_tools: MCPTools mcp_unregister_external_client_request: MCPUnregisterExternalClientRequest memory_configuration: MemoryConfiguration + metadata_context_attribution_result: MetadataContextAttributionResult + metadata_context_heaviest_messages_request: MetadataContextHeaviestMessagesRequest + metadata_context_heaviest_messages_result: MetadataContextHeaviestMessagesResult metadata_context_info_request: MetadataContextInfoRequest metadata_context_info_result: MetadataContextInfoResult metadata_is_processing_result: MetadataIsProcessingResult @@ -22738,6 +23923,7 @@ class RPC: permission_prompt_shown_notification: PermissionPromptShownNotification permission_request_result: PermissionRequestResult permission_rules_set: PermissionRulesSet + permissions_allow_all_mode: PermissionsAllowAllMode permissions_configure_additional_content_exclusion_policy: PermissionsConfigureAdditionalContentExclusionPolicy permissions_configure_additional_content_exclusion_policy_rule: PermissionsConfigureAdditionalContentExclusionPolicyRule permissions_configure_additional_content_exclusion_policy_rule_source: PermissionsConfigureAdditionalContentExclusionPolicyRuleSource @@ -22802,7 +23988,6 @@ class RPC: plugin_update_all_entry: PluginUpdateAllEntry plugin_update_all_result: PluginUpdateAllResult plugin_update_result: PluginUpdateResult - poll_spawned_sessions_result: PollSpawnedSessionsResult provider_add_request: ProviderAddRequest provider_add_result: ProviderAddResult provider_config: ProviderConfig @@ -22913,7 +24098,7 @@ class RPC: session_fs_readdir_request: SessionFSReaddirRequest session_fs_readdir_result: SessionFSReaddirResult session_fs_readdir_with_types_entry: SessionFSReaddirWithTypesEntry - session_fs_readdir_with_types_entry_type: InstructionDiscoveryPathKind + session_fs_readdir_with_types_entry_type: DebugCollectLogsEntryKind session_fs_readdir_with_types_request: SessionFSReaddirWithTypesRequest session_fs_readdir_with_types_result: SessionFSReaddirWithTypesResult session_fs_read_file_request: SessionFSReadFileRequest @@ -22964,6 +24149,16 @@ class RPC: sessions_enrich_metadata_request: SessionsEnrichMetadataRequest session_set_credentials_params: SessionSetCredentialsParams session_set_credentials_result: SessionSetCredentialsResult + session_settings_built_in_tool_availability_snapshot: SessionSettingsBuiltInToolAvailabilitySnapshot + session_settings_evaluate_predicate_request: SessionSettingsEvaluatePredicateRequest + session_settings_evaluate_predicate_result: SessionSettingsEvaluatePredicateResult + session_settings_job_snapshot: SessionSettingsJobSnapshot + session_settings_model_snapshot: SessionSettingsModelSnapshot + session_settings_online_evaluation_snapshot: SessionSettingsOnlineEvaluationSnapshot + session_settings_predicate_name: SessionSettingsPredicateName + session_settings_repo_snapshot: SessionSettingsRepoSnapshot + session_settings_snapshot: SessionSettingsSnapshot + session_settings_validation_snapshot: SessionSettingsValidationSnapshot sessions_find_by_prefix_request: SessionsFindByPrefixRequest sessions_find_by_prefix_result: SessionsFindByPrefixResult sessions_find_by_task_id_request: SessionsFindByTaskIDRequest @@ -22994,8 +24189,6 @@ class RPC: sessions_open_resume_last: SessionsOpenResumeLast sessions_open_status: SessionsOpenStatus session_source: SessionSource - sessions_poll_spawned_sessions_event: SessionsPollSpawnedSessionsEvent - sessions_poll_spawned_sessions_request: SessionsPollSpawnedSessionsRequest sessions_prune_old_request: SessionsPruneOldRequest sessions_register_extension_tools_on_session_options: SessionsRegisterExtensionToolsOnSessionOptions sessions_release_lock_request: SessionsReleaseLockRequest @@ -23041,6 +24234,7 @@ class RPC: slash_command_completed_result: SlashCommandCompletedResult slash_command_info: SlashCommandInfo slash_command_input: SlashCommandInput + slash_command_input_choice: SlashCommandInputChoice slash_command_input_completion: SlashCommandInputCompletion slash_command_invocation_result: SlashCommandInvocationResult slash_command_kind: SlashCommandKind @@ -23158,6 +24352,7 @@ class RPC: workspaces_save_large_paste_result: WorkspacesSaveLargePasteResult workspace_summary_host_type: HostType workspaces_workspace_details_host_type: HostType + session_context_attribution: SessionContextAttribution | None = None session_context_info: SessionContextInfo | None = None subagent_settings: SubagentSettings | None = None task_progress: TaskProgress | None = None @@ -23247,6 +24442,7 @@ def from_dict(obj: Any) -> 'RPC': connect_request = _ConnectRequest.from_dict(obj.get("ConnectRequest")) connect_result = _ConnectResult.from_dict(obj.get("ConnectResult")) content_filter_mode = ContentFilterMode(obj.get("ContentFilterMode")) + context_heaviest_message = ContextHeaviestMessage.from_dict(obj.get("ContextHeaviestMessage")) copilot_api_token_auth_info = CopilotAPITokenAuthInfo.from_dict(obj.get("CopilotApiTokenAuthInfo")) copilot_user_response = CopilotUserResponse.from_dict(obj.get("CopilotUserResponse")) copilot_user_response_endpoints = CopilotUserResponseEndpoints.from_dict(obj.get("CopilotUserResponseEndpoints")) @@ -23256,6 +24452,17 @@ def from_dict(obj: Any) -> 'RPC': copilot_user_response_quota_snapshots_premium_interactions = CopilotUserResponseQuotaSnapshotsPremiumInteractions.from_dict(obj.get("CopilotUserResponseQuotaSnapshotsPremiumInteractions")) current_model = CurrentModel.from_dict(obj.get("CurrentModel")) current_tool_metadata = CurrentToolMetadata.from_dict(obj.get("CurrentToolMetadata")) + debug_collect_logs_collected_entry = DebugCollectLogsCollectedEntry.from_dict(obj.get("DebugCollectLogsCollectedEntry")) + debug_collect_logs_destination = DebugCollectLogsDestination.from_dict(obj.get("DebugCollectLogsDestination")) + debug_collect_logs_entry = DebugCollectLogsEntry.from_dict(obj.get("DebugCollectLogsEntry")) + debug_collect_logs_entry_kind = DebugCollectLogsEntryKind(obj.get("DebugCollectLogsEntryKind")) + debug_collect_logs_include = DebugCollectLogsInclude.from_dict(obj.get("DebugCollectLogsInclude")) + debug_collect_logs_redaction = DebugCollectLogsRedaction(obj.get("DebugCollectLogsRedaction")) + debug_collect_logs_request = DebugCollectLogsRequest.from_dict(obj.get("DebugCollectLogsRequest")) + debug_collect_logs_result = DebugCollectLogsResult.from_dict(obj.get("DebugCollectLogsResult")) + 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")) discovered_canvas = DiscoveredCanvas.from_dict(obj.get("DiscoveredCanvas")) discovered_mcp_server = DiscoveredMCPServer.from_dict(obj.get("DiscoveredMcpServer")) discovered_mcp_server_type = DiscoveredMCPServerType(obj.get("DiscoveredMcpServerType")) @@ -23321,7 +24528,7 @@ def from_dict(obj: Any) -> 'RPC': installed_plugin_source_local = InstalledPluginSourceLocal.from_dict(obj.get("InstalledPluginSourceLocal")) installed_plugin_source_url = InstalledPluginSourceURL.from_dict(obj.get("InstalledPluginSourceUrl")) instruction_discovery_path = InstructionDiscoveryPath.from_dict(obj.get("InstructionDiscoveryPath")) - instruction_discovery_path_kind = InstructionDiscoveryPathKind(obj.get("InstructionDiscoveryPathKind")) + instruction_discovery_path_kind = DebugCollectLogsEntryKind(obj.get("InstructionDiscoveryPathKind")) instruction_discovery_path_list = InstructionDiscoveryPathList.from_dict(obj.get("InstructionDiscoveryPathList")) instruction_discovery_path_location = InstructionLocation(obj.get("InstructionDiscoveryPathLocation")) instructions_discover_request = InstructionsDiscoverRequest.from_dict(obj.get("InstructionsDiscoverRequest")) @@ -23409,8 +24616,6 @@ def from_dict(obj: Any) -> 'RPC': mcp_oauth_login_request = MCPOauthLoginRequest.from_dict(obj.get("McpOauthLoginRequest")) mcp_oauth_login_result = MCPOauthLoginResult.from_dict(obj.get("McpOauthLoginResult")) mcp_oauth_pending_request_response = MCPOauthPendingRequestResponse.from_dict(obj.get("McpOauthPendingRequestResponse")) - mcp_oauth_respond_request = MCPOauthRespondRequest.from_dict(obj.get("McpOauthRespondRequest")) - mcp_oauth_respond_result = MCPOauthRespondResult.from_dict(obj.get("McpOauthRespondResult")) mcp_register_external_client_request = MCPRegisterExternalClientRequest.from_dict(obj.get("McpRegisterExternalClientRequest")) mcp_reload_with_config_request = MCPReloadWithConfigRequest.from_dict(obj.get("McpReloadWithConfigRequest")) mcp_remove_git_hub_result = MCPRemoveGitHubResult.from_dict(obj.get("McpRemoveGitHubResult")) @@ -23438,6 +24643,9 @@ def from_dict(obj: Any) -> 'RPC': mcp_tools = MCPTools.from_dict(obj.get("McpTools")) mcp_unregister_external_client_request = MCPUnregisterExternalClientRequest.from_dict(obj.get("McpUnregisterExternalClientRequest")) memory_configuration = MemoryConfiguration.from_dict(obj.get("MemoryConfiguration")) + metadata_context_attribution_result = MetadataContextAttributionResult.from_dict(obj.get("MetadataContextAttributionResult")) + metadata_context_heaviest_messages_request = MetadataContextHeaviestMessagesRequest.from_dict(obj.get("MetadataContextHeaviestMessagesRequest")) + metadata_context_heaviest_messages_result = MetadataContextHeaviestMessagesResult.from_dict(obj.get("MetadataContextHeaviestMessagesResult")) metadata_context_info_request = MetadataContextInfoRequest.from_dict(obj.get("MetadataContextInfoRequest")) metadata_context_info_result = MetadataContextInfoResult.from_dict(obj.get("MetadataContextInfoResult")) metadata_is_processing_result = MetadataIsProcessingResult.from_dict(obj.get("MetadataIsProcessingResult")) @@ -23545,6 +24753,7 @@ def from_dict(obj: Any) -> 'RPC': permission_prompt_shown_notification = PermissionPromptShownNotification.from_dict(obj.get("PermissionPromptShownNotification")) permission_request_result = PermissionRequestResult.from_dict(obj.get("PermissionRequestResult")) permission_rules_set = PermissionRulesSet.from_dict(obj.get("PermissionRulesSet")) + permissions_allow_all_mode = PermissionsAllowAllMode(obj.get("PermissionsAllowAllMode")) permissions_configure_additional_content_exclusion_policy = PermissionsConfigureAdditionalContentExclusionPolicy.from_dict(obj.get("PermissionsConfigureAdditionalContentExclusionPolicy")) permissions_configure_additional_content_exclusion_policy_rule = PermissionsConfigureAdditionalContentExclusionPolicyRule.from_dict(obj.get("PermissionsConfigureAdditionalContentExclusionPolicyRule")) permissions_configure_additional_content_exclusion_policy_rule_source = PermissionsConfigureAdditionalContentExclusionPolicyRuleSource.from_dict(obj.get("PermissionsConfigureAdditionalContentExclusionPolicyRuleSource")) @@ -23609,7 +24818,6 @@ def from_dict(obj: Any) -> 'RPC': plugin_update_all_entry = PluginUpdateAllEntry.from_dict(obj.get("PluginUpdateAllEntry")) plugin_update_all_result = PluginUpdateAllResult.from_dict(obj.get("PluginUpdateAllResult")) plugin_update_result = PluginUpdateResult.from_dict(obj.get("PluginUpdateResult")) - poll_spawned_sessions_result = PollSpawnedSessionsResult.from_dict(obj.get("PollSpawnedSessionsResult")) provider_add_request = ProviderAddRequest.from_dict(obj.get("ProviderAddRequest")) provider_add_result = ProviderAddResult.from_dict(obj.get("ProviderAddResult")) provider_config = ProviderConfig.from_dict(obj.get("ProviderConfig")) @@ -23720,7 +24928,7 @@ def from_dict(obj: Any) -> 'RPC': session_fs_readdir_request = SessionFSReaddirRequest.from_dict(obj.get("SessionFsReaddirRequest")) session_fs_readdir_result = SessionFSReaddirResult.from_dict(obj.get("SessionFsReaddirResult")) session_fs_readdir_with_types_entry = SessionFSReaddirWithTypesEntry.from_dict(obj.get("SessionFsReaddirWithTypesEntry")) - session_fs_readdir_with_types_entry_type = InstructionDiscoveryPathKind(obj.get("SessionFsReaddirWithTypesEntryType")) + session_fs_readdir_with_types_entry_type = DebugCollectLogsEntryKind(obj.get("SessionFsReaddirWithTypesEntryType")) session_fs_readdir_with_types_request = SessionFSReaddirWithTypesRequest.from_dict(obj.get("SessionFsReaddirWithTypesRequest")) session_fs_readdir_with_types_result = SessionFSReaddirWithTypesResult.from_dict(obj.get("SessionFsReaddirWithTypesResult")) session_fs_read_file_request = SessionFSReadFileRequest.from_dict(obj.get("SessionFsReadFileRequest")) @@ -23771,6 +24979,16 @@ def from_dict(obj: Any) -> 'RPC': sessions_enrich_metadata_request = SessionsEnrichMetadataRequest.from_dict(obj.get("SessionsEnrichMetadataRequest")) session_set_credentials_params = SessionSetCredentialsParams.from_dict(obj.get("SessionSetCredentialsParams")) session_set_credentials_result = SessionSetCredentialsResult.from_dict(obj.get("SessionSetCredentialsResult")) + session_settings_built_in_tool_availability_snapshot = SessionSettingsBuiltInToolAvailabilitySnapshot.from_dict(obj.get("SessionSettingsBuiltInToolAvailabilitySnapshot")) + session_settings_evaluate_predicate_request = SessionSettingsEvaluatePredicateRequest.from_dict(obj.get("SessionSettingsEvaluatePredicateRequest")) + session_settings_evaluate_predicate_result = SessionSettingsEvaluatePredicateResult.from_dict(obj.get("SessionSettingsEvaluatePredicateResult")) + session_settings_job_snapshot = SessionSettingsJobSnapshot.from_dict(obj.get("SessionSettingsJobSnapshot")) + session_settings_model_snapshot = SessionSettingsModelSnapshot.from_dict(obj.get("SessionSettingsModelSnapshot")) + session_settings_online_evaluation_snapshot = SessionSettingsOnlineEvaluationSnapshot.from_dict(obj.get("SessionSettingsOnlineEvaluationSnapshot")) + session_settings_predicate_name = SessionSettingsPredicateName(obj.get("SessionSettingsPredicateName")) + session_settings_repo_snapshot = SessionSettingsRepoSnapshot.from_dict(obj.get("SessionSettingsRepoSnapshot")) + session_settings_snapshot = SessionSettingsSnapshot.from_dict(obj.get("SessionSettingsSnapshot")) + session_settings_validation_snapshot = SessionSettingsValidationSnapshot.from_dict(obj.get("SessionSettingsValidationSnapshot")) sessions_find_by_prefix_request = SessionsFindByPrefixRequest.from_dict(obj.get("SessionsFindByPrefixRequest")) sessions_find_by_prefix_result = SessionsFindByPrefixResult.from_dict(obj.get("SessionsFindByPrefixResult")) sessions_find_by_task_id_request = SessionsFindByTaskIDRequest.from_dict(obj.get("SessionsFindByTaskIDRequest")) @@ -23801,8 +25019,6 @@ def from_dict(obj: Any) -> 'RPC': sessions_open_resume_last = SessionsOpenResumeLast.from_dict(obj.get("SessionsOpenResumeLast")) sessions_open_status = SessionsOpenStatus(obj.get("SessionsOpenStatus")) session_source = SessionSource(obj.get("SessionSource")) - sessions_poll_spawned_sessions_event = SessionsPollSpawnedSessionsEvent.from_dict(obj.get("SessionsPollSpawnedSessionsEvent")) - sessions_poll_spawned_sessions_request = SessionsPollSpawnedSessionsRequest.from_dict(obj.get("SessionsPollSpawnedSessionsRequest")) sessions_prune_old_request = SessionsPruneOldRequest.from_dict(obj.get("SessionsPruneOldRequest")) sessions_register_extension_tools_on_session_options = SessionsRegisterExtensionToolsOnSessionOptions.from_dict(obj.get("SessionsRegisterExtensionToolsOnSessionOptions")) sessions_release_lock_request = SessionsReleaseLockRequest.from_dict(obj.get("SessionsReleaseLockRequest")) @@ -23848,6 +25064,7 @@ def from_dict(obj: Any) -> 'RPC': slash_command_completed_result = SlashCommandCompletedResult.from_dict(obj.get("SlashCommandCompletedResult")) slash_command_info = SlashCommandInfo.from_dict(obj.get("SlashCommandInfo")) slash_command_input = SlashCommandInput.from_dict(obj.get("SlashCommandInput")) + slash_command_input_choice = SlashCommandInputChoice.from_dict(obj.get("SlashCommandInputChoice")) slash_command_input_completion = SlashCommandInputCompletion(obj.get("SlashCommandInputCompletion")) slash_command_invocation_result = _load_SlashCommandInvocationResult(obj.get("SlashCommandInvocationResult")) slash_command_kind = SlashCommandKind(obj.get("SlashCommandKind")) @@ -23965,11 +25182,12 @@ def from_dict(obj: Any) -> 'RPC': workspaces_save_large_paste_result = WorkspacesSaveLargePasteResult.from_dict(obj.get("WorkspacesSaveLargePasteResult")) workspace_summary_host_type = HostType(obj.get("WorkspaceSummaryHostType")) workspaces_workspace_details_host_type = HostType(obj.get("WorkspacesWorkspaceDetailsHostType")) + session_context_attribution = from_union([SessionContextAttribution.from_dict, from_none], obj.get("SessionContextAttribution")) session_context_info = from_union([SessionContextInfo.from_dict, from_none], obj.get("SessionContextInfo")) subagent_settings = from_union([SubagentSettings.from_dict, from_none], obj.get("SubagentSettings")) task_progress = from_union([TaskProgress.from_dict, from_none], obj.get("TaskProgress")) workspace_summary = from_union([WorkspaceSummary.from_dict, from_none], obj.get("WorkspaceSummary")) - return RPC(abort_request, abort_result, account_all_users, account_get_all_users_result, account_get_current_auth_result, account_get_quota_request, account_get_quota_result, account_login_request, account_login_result, account_logout_request, account_logout_result, account_quota_snapshot, adaptive_thinking_support, agent_discovery_path, agent_discovery_path_list, agent_discovery_path_scope, agent_get_current_result, agent_info, agent_info_source, agent_list, agent_registry_live_target_entry, agent_registry_live_target_entry_attention_kind, agent_registry_live_target_entry_kind, agent_registry_live_target_entry_last_terminal_event, agent_registry_live_target_entry_status, agent_registry_log_capture, agent_registry_log_capture_open_error_reason, agent_registry_spawn_error, agent_registry_spawn_permission_mode, agent_registry_spawn_registry_timeout, agent_registry_spawn_request, agent_registry_spawn_result, agent_registry_spawn_spawned, agent_registry_spawn_validation_error, agent_registry_spawn_validation_error_field, agent_registry_spawn_validation_error_reason, agent_reload_result, agents_discover_request, agent_select_request, agent_select_result, agents_get_discovery_paths_request, allow_all_permission_set_result, allow_all_permission_state, api_key_auth_info, auth_info, auth_info_type, cancel_user_requested_shell_command_result, canvas_action, canvas_action_invoke_request, canvas_action_invoke_result, canvas_close_request, canvas_host_context, canvas_host_context_capabilities, canvas_json_schema, canvas_list, canvas_list_open_result, canvas_open_request, canvas_provider_close_request, canvas_provider_invoke_action_request, canvas_provider_open_request, canvas_provider_open_result, canvas_session_context, capi_session_options, command_list, commands_handle_pending_command_request, commands_handle_pending_command_result, commands_invoke_request, commands_list_request, commands_respond_to_queued_command_request, commands_respond_to_queued_command_result, completions_get_trigger_characters_result, completions_request_request, completions_request_result, configure_session_extensions_params, connected_remote_session_metadata, connected_remote_session_metadata_kind, connected_remote_session_metadata_repository, connect_remote_session_params, connect_request, connect_result, content_filter_mode, 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, discovered_canvas, discovered_mcp_server, discovered_mcp_server_type, enqueue_command_params, enqueue_command_result, env_auth_info, event_log_read_request, event_log_release_interest_result, event_log_tail_result, event_log_types, events_agent_scope, events_cursor_status, events_read_result, execute_command_params, execute_command_result, extension, extension_context_push_input, extension_list, extensions_disable_request, extensions_enable_request, extension_source, extension_status, external_tool_result, external_tool_text_result_for_llm, external_tool_text_result_for_llm_binary_results_for_llm, external_tool_text_result_for_llm_binary_results_for_llm_type, external_tool_text_result_for_llm_content, external_tool_text_result_for_llm_content_audio, external_tool_text_result_for_llm_content_image, external_tool_text_result_for_llm_content_resource, external_tool_text_result_for_llm_content_resource_details, external_tool_text_result_for_llm_content_resource_link, external_tool_text_result_for_llm_content_resource_link_icon, external_tool_text_result_for_llm_content_resource_link_icon_theme, external_tool_text_result_for_llm_content_shell_exit, external_tool_text_result_for_llm_content_terminal, external_tool_text_result_for_llm_content_text, filter_mapping, fleet_start_request, fleet_start_result, folder_trust_add_params, folder_trust_check_params, folder_trust_check_result, gh_cli_auth_info, git_hub_telemetry_client_info, git_hub_telemetry_event, git_hub_telemetry_notification, handle_pending_tool_call_request, handle_pending_tool_call_result, history_abort_manual_compaction_result, history_cancel_background_compaction_result, history_compact_context_window, history_compact_request, history_compact_result, history_summarize_for_handoff_result, history_truncate_request, history_truncate_result, hmac_auth_info, installed_plugin, installed_plugin_info, installed_plugin_source, installed_plugin_source_git_hub, installed_plugin_source_local, installed_plugin_source_url, instruction_discovery_path, instruction_discovery_path_kind, instruction_discovery_path_list, instruction_discovery_path_location, instructions_discover_request, instructions_get_discovery_paths_request, instructions_get_sources_result, instruction_source, instruction_source_location, instruction_source_type, llm_inference_headers, llm_inference_http_request_chunk_request, llm_inference_http_request_chunk_result, llm_inference_http_request_start_request, llm_inference_http_request_start_result, llm_inference_http_request_start_transport, llm_inference_http_response_chunk_error, llm_inference_http_response_chunk_request, llm_inference_http_response_chunk_result, llm_inference_http_response_start_request, llm_inference_http_response_start_result, llm_inference_set_provider_result, local_session_metadata_value, log_request, log_result, lsp_initialize_request, marketplace_add_result, marketplace_browse_result, marketplace_info, marketplace_list_result, marketplace_plugin_info, marketplace_refresh_entry, marketplace_refresh_result, marketplace_remove_result, mcp_allowed_server, mcp_apps_call_tool_request, mcp_apps_diagnose_capability, mcp_apps_diagnose_request, mcp_apps_diagnose_result, mcp_apps_diagnose_server, mcp_apps_host_context, mcp_apps_host_context_details, mcp_apps_host_context_details_available_display_mode, mcp_apps_host_context_details_display_mode, mcp_apps_host_context_details_platform, mcp_apps_host_context_details_theme, mcp_apps_list_tools_request, mcp_apps_list_tools_result, mcp_apps_read_resource_request, mcp_apps_read_resource_result, mcp_apps_resource_content, mcp_apps_set_host_context_details, mcp_apps_set_host_context_details_available_display_mode, mcp_apps_set_host_context_details_display_mode, mcp_apps_set_host_context_details_platform, mcp_apps_set_host_context_details_theme, mcp_apps_set_host_context_request, mcp_cancel_sampling_execution_params, mcp_cancel_sampling_execution_result, mcp_config_add_request, mcp_config_disable_request, mcp_config_enable_request, mcp_config_list, mcp_config_remove_request, mcp_config_update_request, mcp_configure_git_hub_request, mcp_configure_git_hub_result, mcp_disable_request, mcp_discover_request, mcp_discover_result, mcp_enable_request, mcp_execute_sampling_params, mcp_execute_sampling_request, mcp_execute_sampling_result, mcp_filtered_server, mcp_headers_handle_pending_headers_refresh_request, mcp_headers_handle_pending_headers_refresh_request_request, mcp_headers_handle_pending_headers_refresh_request_result, mcp_host_state, mcp_is_server_running_request, mcp_is_server_running_result, mcp_list_tools_request, mcp_list_tools_result, mcp_oauth_handle_pending_request, mcp_oauth_handle_pending_result, mcp_oauth_login_grant_type, mcp_oauth_login_request, mcp_oauth_login_result, mcp_oauth_pending_request_response, mcp_oauth_respond_request, mcp_oauth_respond_result, mcp_register_external_client_request, mcp_reload_with_config_request, mcp_remove_git_hub_result, mcp_restart_server_request, mcp_sampling_execution_action, mcp_sampling_execution_result, mcp_server, mcp_server_auth_config, mcp_server_auth_config_redirect_port, mcp_server_config, mcp_server_config_defer_tools, mcp_server_config_http, mcp_server_config_http_oauth_grant_type, mcp_server_config_http_type, mcp_server_config_stdio, mcp_server_failure_info, mcp_server_list, mcp_server_needs_auth_info, mcp_set_env_value_mode_details, mcp_set_env_value_mode_params, mcp_set_env_value_mode_result, mcp_start_server_request, mcp_start_servers_result, mcp_stop_server_request, mcp_tools, mcp_unregister_external_client_request, memory_configuration, metadata_context_info_request, metadata_context_info_result, metadata_is_processing_result, metadata_recompute_context_tokens_request, metadata_recompute_context_tokens_result, metadata_record_context_change_request, metadata_record_context_change_result, metadata_set_working_directory_request, metadata_set_working_directory_result, metadata_snapshot_current_mode, metadata_snapshot_remote_metadata, metadata_snapshot_remote_metadata_repository, metadata_snapshot_remote_metadata_task_type, model, model_billing, model_billing_token_prices, model_billing_token_prices_long_context, model_capabilities, model_capabilities_limits, model_capabilities_limits_vision, model_capabilities_override, model_capabilities_override_limits, model_capabilities_override_limits_vision, model_capabilities_override_supports, model_capabilities_supports, model_list, model_list_request, model_picker_category, model_picker_price_category, model_policy, model_policy_state, model_set_reasoning_effort_request, model_set_reasoning_effort_result, models_list_request, model_switch_to_request, model_switch_to_result, mode_set_request, named_provider_config, name_get_result, name_set_auto_request, name_set_auto_result, name_set_request, open_canvas_instance, options_update_additional_content_exclusion_policy, options_update_additional_content_exclusion_policy_rule, options_update_additional_content_exclusion_policy_rule_source, options_update_additional_content_exclusion_policy_scope, options_update_context_tier, options_update_env_value_mode, options_update_reasoning_summary, options_update_tool_filter_precedence, pending_permission_request, pending_permission_request_list, permission_decision, permission_decision_approved, permission_decision_approved_for_location, permission_decision_approved_for_session, permission_decision_approve_for_location, permission_decision_approve_for_location_approval, permission_decision_approve_for_location_approval_commands, permission_decision_approve_for_location_approval_custom_tool, permission_decision_approve_for_location_approval_extension_management, permission_decision_approve_for_location_approval_extension_permission_access, permission_decision_approve_for_location_approval_mcp, permission_decision_approve_for_location_approval_mcp_sampling, permission_decision_approve_for_location_approval_memory, permission_decision_approve_for_location_approval_read, permission_decision_approve_for_location_approval_write, permission_decision_approve_for_session, permission_decision_approve_for_session_approval, permission_decision_approve_for_session_approval_commands, permission_decision_approve_for_session_approval_custom_tool, permission_decision_approve_for_session_approval_extension_management, permission_decision_approve_for_session_approval_extension_permission_access, permission_decision_approve_for_session_approval_mcp, permission_decision_approve_for_session_approval_mcp_sampling, permission_decision_approve_for_session_approval_memory, permission_decision_approve_for_session_approval_read, permission_decision_approve_for_session_approval_write, permission_decision_approve_once, permission_decision_approve_permanently, permission_decision_cancelled, permission_decision_denied_by_content_exclusion_policy, permission_decision_denied_by_permission_request_hook, permission_decision_denied_by_rules, permission_decision_denied_interactively_by_user, permission_decision_denied_no_approval_rule_and_could_not_request_from_user, permission_decision_reject, permission_decision_request, permission_decision_user_not_available, permission_location_add_tool_approval_params, permission_location_apply_params, permission_location_apply_result, permission_location_resolve_params, permission_location_resolve_result, permission_location_type, permission_paths_add_params, permission_paths_allowed_check_params, permission_paths_allowed_check_result, permission_paths_config, permission_paths_list, permission_paths_update_primary_params, permission_paths_workspace_check_params, permission_paths_workspace_check_result, permission_prompt_shown_notification, permission_request_result, permission_rules_set, permissions_configure_additional_content_exclusion_policy, permissions_configure_additional_content_exclusion_policy_rule, permissions_configure_additional_content_exclusion_policy_rule_source, permissions_configure_additional_content_exclusion_policy_scope, permissions_configure_params, permissions_configure_result, permissions_folder_trust_add_trusted_result, permissions_get_allow_all_request, permissions_locations_add_tool_approval_details, permissions_locations_add_tool_approval_details_commands, permissions_locations_add_tool_approval_details_custom_tool, permissions_locations_add_tool_approval_details_extension_management, permissions_locations_add_tool_approval_details_extension_permission_access, permissions_locations_add_tool_approval_details_mcp, permissions_locations_add_tool_approval_details_mcp_sampling, permissions_locations_add_tool_approval_details_memory, permissions_locations_add_tool_approval_details_read, permissions_locations_add_tool_approval_details_write, permissions_locations_add_tool_approval_result, permissions_modify_rules_params, permissions_modify_rules_result, permissions_modify_rules_scope, permissions_notify_prompt_shown_result, permissions_paths_add_result, permissions_paths_list_request, permissions_paths_update_primary_result, permissions_pending_requests_request, permissions_reset_session_approvals_request, permissions_reset_session_approvals_result, permissions_set_allow_all_request, permissions_set_allow_all_source, permissions_set_approve_all_request, permissions_set_approve_all_result, permissions_set_approve_all_source, permissions_set_required_request, permissions_set_required_result, permissions_urls_set_unrestricted_mode_result, permission_urls_config, permission_urls_set_unrestricted_mode_params, ping_request, ping_result, plan_read_result, plan_read_sql_todos_result, plan_read_sql_todos_with_dependencies_result, plan_sql_todo_dependency, plan_sql_todos_row, plan_update_request, plugin, plugin_install_result, plugin_list, plugin_list_result, plugins_disable_request, plugins_enable_request, plugins_install_request, plugins_marketplaces_add_request, plugins_marketplaces_browse_request, plugins_marketplaces_refresh_request, plugins_marketplaces_remove_request, plugins_reload_request, plugins_uninstall_request, plugins_update_request, plugin_update_all_entry, plugin_update_all_result, plugin_update_result, poll_spawned_sessions_result, provider_add_request, provider_add_result, provider_config, provider_config_azure, provider_config_transport, provider_config_type, provider_config_wire_api, provider_endpoint, provider_endpoint_transport, provider_endpoint_type, provider_endpoint_wire_api, provider_get_endpoint_request, provider_model_config, provider_session_token, provider_token_acquire_request, provider_token_acquire_result, push_attachment, push_attachment_blob, push_attachment_directory, push_attachment_file, push_attachment_file_line_range, push_attachment_git_hub_actions_job, push_attachment_git_hub_commit, push_attachment_git_hub_file, push_attachment_git_hub_file_diff, push_attachment_git_hub_file_diff_side, push_attachment_git_hub_reference, push_attachment_git_hub_reference_type, push_attachment_git_hub_release, push_attachment_git_hub_repository, push_attachment_git_hub_snippet, push_attachment_git_hub_tree_comparison, push_attachment_git_hub_tree_comparison_side, push_attachment_git_hub_url, push_attachment_selection, push_attachment_selection_details, push_attachment_selection_details_end, push_attachment_selection_details_start, push_git_hub_repo_ref, queued_command_handled, queued_command_not_handled, queued_command_result, queue_pending_items, queue_pending_items_kind, queue_pending_items_result, queue_remove_most_recent_result, register_event_interest_params, register_event_interest_result, register_extension_tools_params, register_extension_tools_result, release_event_interest_params, remote_control_config, remote_control_config_existing_mc_session, remote_control_status, remote_control_status_active, remote_control_status_connecting, remote_control_status_error, remote_control_status_off, remote_control_status_result, remote_control_stop_result, remote_control_transfer_result, remote_enable_request, remote_enable_result, remote_notify_steerable_changed_request, remote_notify_steerable_changed_result, remote_session_connection_result, remote_session_metadata_repository, remote_session_metadata_task_type, remote_session_metadata_value, remote_session_mode, remote_session_repository, sandbox_config, sandbox_config_user_policy, sandbox_config_user_policy_experimental, sandbox_config_user_policy_experimental_seatbelt, sandbox_config_user_policy_filesystem, sandbox_config_user_policy_network, sandbox_config_user_policy_seatbelt, schedule_entry, schedule_list, schedule_stop_request, schedule_stop_result, secrets_add_filter_values_request, secrets_add_filter_values_result, send_agent_mode, send_attachments_to_message_params, send_mode, send_request, send_result, server_agent_list, server_instruction_source_list, server_skill, server_skill_list, session_activity, session_auth_status, session_bulk_delete_result, session_capability, session_completion_item, session_context, session_context_host_type, session_enrich_metadata_result, session_fs_append_file_request, session_fs_error, session_fs_error_code, session_fs_exists_request, session_fs_exists_result, session_fs_mkdir_request, session_fs_readdir_request, session_fs_readdir_result, session_fs_readdir_with_types_entry, session_fs_readdir_with_types_entry_type, session_fs_readdir_with_types_request, session_fs_readdir_with_types_result, session_fs_read_file_request, session_fs_read_file_result, session_fs_rename_request, session_fs_rm_request, session_fs_set_provider_capabilities, session_fs_set_provider_conventions, session_fs_set_provider_request, session_fs_set_provider_result, session_fs_sqlite_exists_request, session_fs_sqlite_exists_result, session_fs_sqlite_query_request, session_fs_sqlite_query_result, session_fs_sqlite_query_type, session_fs_stat_request, session_fs_stat_result, session_fs_write_file_request, session_installed_plugin, session_installed_plugin_source, session_installed_plugin_source_git_hub, session_installed_plugin_source_local, session_installed_plugin_source_url, session_list, session_list_entry, session_list_filter, session_load_deferred_repo_hooks_result, session_log_level, session_mcp_apps_call_tool_result, session_metadata_snapshot, session_mode, session_model_list, session_open_options, session_open_options_additional_content_exclusion_policy, session_open_options_additional_content_exclusion_policy_rule, session_open_options_additional_content_exclusion_policy_rule_source, session_open_options_additional_content_exclusion_policy_scope, session_open_options_env_value_mode, session_open_options_reasoning_summary, session_open_params, session_open_result, session_prune_result, sessions_bulk_delete_request, sessions_check_in_use_request, sessions_check_in_use_result, sessions_close_request, sessions_close_result, sessions_enrich_metadata_request, session_set_credentials_params, session_set_credentials_result, sessions_find_by_prefix_request, sessions_find_by_prefix_result, sessions_find_by_task_id_request, sessions_find_by_task_id_result, sessions_fork_request, sessions_fork_result, sessions_get_board_entry_count_request, sessions_get_board_entry_count_result, sessions_get_event_file_path_request, sessions_get_event_file_path_result, sessions_get_last_for_context_request, sessions_get_last_for_context_result, sessions_get_persisted_remote_steerable_request, sessions_get_persisted_remote_steerable_result, session_sizes, sessions_list_request, sessions_load_deferred_repo_hooks_request, sessions_open_attach, sessions_open_cloud, sessions_open_create, sessions_open_handoff, sessions_open_handoff_task_type, sessions_open_progress, sessions_open_progress_status, sessions_open_progress_step, sessions_open_remote, sessions_open_resume, sessions_open_resume_last, sessions_open_status, session_source, sessions_poll_spawned_sessions_event, sessions_poll_spawned_sessions_request, sessions_prune_old_request, sessions_register_extension_tools_on_session_options, sessions_release_lock_request, sessions_release_lock_result, sessions_reload_plugin_hooks_request, sessions_reload_plugin_hooks_result, sessions_save_request, sessions_save_result, sessions_set_additional_plugins_request, sessions_set_additional_plugins_result, sessions_set_remote_control_steering_request, sessions_start_remote_control_request, sessions_stop_remote_control_request, sessions_transfer_remote_control_request, session_telemetry_engagement, session_update_options_params, session_update_options_result, session_visibility_status, session_working_directory_context, session_working_directory_context_host_type, shell_cancel_user_requested_request, shell_exec_request, shell_exec_result, shell_execute_user_requested_request, shell_kill_request, shell_kill_result, shell_kill_signal, shutdown_request, skill, skill_discovery_path, skill_discovery_path_list, skill_discovery_scope, skill_list, skills_config_set_disabled_skills_request, skills_disable_request, skills_discover_request, skills_enable_request, skills_get_discovery_paths_request, skills_get_invoked_result, skills_invoked_skill, skills_load_diagnostics, slash_command_agent_prompt_result, slash_command_completed_result, slash_command_info, slash_command_input, slash_command_input_completion, slash_command_invocation_result, slash_command_kind, slash_command_select_subcommand_option, slash_command_select_subcommand_result, slash_command_text_result, subagent_settings_entry, subagent_settings_entry_context_tier, task_agent_info, task_agent_progress, task_execution_mode, task_info, task_list, task_progress_line, tasks_cancel_request, tasks_cancel_result, tasks_get_current_promotable_result, tasks_get_progress_request, tasks_get_progress_result, task_shell_info, task_shell_info_attachment_mode, task_shell_progress, tasks_promote_current_to_background_result, tasks_promote_to_background_request, tasks_promote_to_background_result, tasks_refresh_result, tasks_remove_request, tasks_remove_result, tasks_send_message_request, tasks_send_message_result, tasks_start_agent_request, tasks_start_agent_result, task_status, tasks_wait_for_pending_result, telemetry_set_feature_overrides_request, token_auth_info, tool, tool_list, tools_get_current_metadata_result, tools_initialize_and_validate_result, tools_list_request, tools_update_subagent_settings_result, ui_auto_mode_switch_response, ui_elicitation_array_any_of_field, ui_elicitation_array_any_of_field_items, ui_elicitation_array_any_of_field_items_any_of, ui_elicitation_array_enum_field, ui_elicitation_array_enum_field_items, ui_elicitation_field_value, ui_elicitation_request, ui_elicitation_response, ui_elicitation_response_action, ui_elicitation_response_content, ui_elicitation_result, ui_elicitation_schema, ui_elicitation_schema_property, ui_elicitation_schema_property_boolean, ui_elicitation_schema_property_number, ui_elicitation_schema_property_number_type, ui_elicitation_schema_property_string, ui_elicitation_schema_property_string_format, ui_elicitation_string_enum_field, ui_elicitation_string_one_of_field, ui_elicitation_string_one_of_field_one_of, ui_ephemeral_query_request, ui_ephemeral_query_result, ui_exit_plan_mode_action, ui_exit_plan_mode_response, ui_handle_pending_auto_mode_switch_request, ui_handle_pending_elicitation_request, ui_handle_pending_exit_plan_mode_request, ui_handle_pending_result, ui_handle_pending_sampling_request, ui_handle_pending_sampling_response, ui_handle_pending_session_limits_exhausted_request, ui_handle_pending_user_input_request, ui_register_direct_auto_mode_switch_handler_result, ui_session_limits_exhausted_response, ui_session_limits_exhausted_response_action, ui_unregister_direct_auto_mode_switch_handler_request, ui_unregister_direct_auto_mode_switch_handler_result, ui_user_input_response, update_subagent_settings_request, usage_get_metrics_result, usage_metrics_code_changes, usage_metrics_model_metric, usage_metrics_model_metric_requests, usage_metrics_model_metric_token_detail, usage_metrics_model_metric_usage, usage_metrics_token_detail, user_auth_info, user_requested_shell_command_result, user_setting_metadata, user_settings_get_result, user_settings_set_request, user_settings_set_result, visibility_get_result, visibility_set_request, visibility_set_result, workspace_diff_file_change, workspace_diff_file_change_type, workspace_diff_mode, workspace_diff_result, workspaces_checkpoints, workspaces_create_file_request, workspaces_diff_request, workspaces_get_workspace_result, workspaces_list_checkpoints_result, workspaces_list_files_result, workspaces_read_checkpoint_request, workspaces_read_checkpoint_result, workspaces_read_file_request, workspaces_read_file_result, workspaces_save_large_paste_request, workspaces_save_large_paste_result, workspace_summary_host_type, workspaces_workspace_details_host_type, session_context_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_registry_live_target_entry, agent_registry_live_target_entry_attention_kind, agent_registry_live_target_entry_kind, agent_registry_live_target_entry_last_terminal_event, agent_registry_live_target_entry_status, agent_registry_log_capture, agent_registry_log_capture_open_error_reason, agent_registry_spawn_error, agent_registry_spawn_permission_mode, agent_registry_spawn_registry_timeout, agent_registry_spawn_request, agent_registry_spawn_result, agent_registry_spawn_spawned, agent_registry_spawn_validation_error, agent_registry_spawn_validation_error_field, agent_registry_spawn_validation_error_reason, agent_reload_result, agents_discover_request, agent_select_request, agent_select_result, agents_get_discovery_paths_request, allow_all_permission_set_result, allow_all_permission_state, api_key_auth_info, auth_info, auth_info_type, cancel_user_requested_shell_command_result, canvas_action, canvas_action_invoke_request, canvas_action_invoke_result, canvas_close_request, canvas_host_context, canvas_host_context_capabilities, canvas_json_schema, canvas_list, canvas_list_open_result, canvas_open_request, canvas_provider_close_request, canvas_provider_invoke_action_request, canvas_provider_open_request, canvas_provider_open_result, canvas_session_context, capi_session_options, command_list, commands_handle_pending_command_request, commands_handle_pending_command_result, commands_invoke_request, commands_list_request, commands_respond_to_queued_command_request, commands_respond_to_queued_command_result, completions_get_trigger_characters_result, completions_request_request, completions_request_result, configure_session_extensions_params, connected_remote_session_metadata, connected_remote_session_metadata_kind, connected_remote_session_metadata_repository, connect_remote_session_params, connect_request, connect_result, content_filter_mode, context_heaviest_message, copilot_api_token_auth_info, copilot_user_response, copilot_user_response_endpoints, copilot_user_response_quota_snapshots, copilot_user_response_quota_snapshots_chat, copilot_user_response_quota_snapshots_completions, copilot_user_response_quota_snapshots_premium_interactions, current_model, current_tool_metadata, debug_collect_logs_collected_entry, debug_collect_logs_destination, debug_collect_logs_entry, debug_collect_logs_entry_kind, debug_collect_logs_include, debug_collect_logs_redaction, debug_collect_logs_request, debug_collect_logs_result, debug_collect_logs_result_kind, debug_collect_logs_skipped_entry, debug_collect_logs_source, discovered_canvas, discovered_mcp_server, discovered_mcp_server_type, enqueue_command_params, enqueue_command_result, env_auth_info, event_log_read_request, event_log_release_interest_result, event_log_tail_result, event_log_types, events_agent_scope, events_cursor_status, events_read_result, execute_command_params, execute_command_result, extension, extension_context_push_input, extension_list, extensions_disable_request, extensions_enable_request, extension_source, extension_status, external_tool_result, external_tool_text_result_for_llm, external_tool_text_result_for_llm_binary_results_for_llm, external_tool_text_result_for_llm_binary_results_for_llm_type, external_tool_text_result_for_llm_content, external_tool_text_result_for_llm_content_audio, external_tool_text_result_for_llm_content_image, external_tool_text_result_for_llm_content_resource, external_tool_text_result_for_llm_content_resource_details, external_tool_text_result_for_llm_content_resource_link, external_tool_text_result_for_llm_content_resource_link_icon, external_tool_text_result_for_llm_content_resource_link_icon_theme, external_tool_text_result_for_llm_content_shell_exit, external_tool_text_result_for_llm_content_terminal, external_tool_text_result_for_llm_content_text, filter_mapping, fleet_start_request, fleet_start_result, folder_trust_add_params, folder_trust_check_params, folder_trust_check_result, gh_cli_auth_info, git_hub_telemetry_client_info, git_hub_telemetry_event, git_hub_telemetry_notification, handle_pending_tool_call_request, handle_pending_tool_call_result, history_abort_manual_compaction_result, history_cancel_background_compaction_result, history_compact_context_window, history_compact_request, history_compact_result, history_summarize_for_handoff_result, history_truncate_request, history_truncate_result, hmac_auth_info, installed_plugin, installed_plugin_info, installed_plugin_source, installed_plugin_source_git_hub, installed_plugin_source_local, installed_plugin_source_url, instruction_discovery_path, instruction_discovery_path_kind, instruction_discovery_path_list, instruction_discovery_path_location, instructions_discover_request, instructions_get_discovery_paths_request, instructions_get_sources_result, instruction_source, instruction_source_location, instruction_source_type, llm_inference_headers, llm_inference_http_request_chunk_request, llm_inference_http_request_chunk_result, llm_inference_http_request_start_request, llm_inference_http_request_start_result, llm_inference_http_request_start_transport, llm_inference_http_response_chunk_error, llm_inference_http_response_chunk_request, llm_inference_http_response_chunk_result, llm_inference_http_response_start_request, llm_inference_http_response_start_result, llm_inference_set_provider_result, local_session_metadata_value, log_request, log_result, lsp_initialize_request, marketplace_add_result, marketplace_browse_result, marketplace_info, marketplace_list_result, marketplace_plugin_info, marketplace_refresh_entry, marketplace_refresh_result, marketplace_remove_result, mcp_allowed_server, mcp_apps_call_tool_request, mcp_apps_diagnose_capability, mcp_apps_diagnose_request, mcp_apps_diagnose_result, mcp_apps_diagnose_server, mcp_apps_host_context, mcp_apps_host_context_details, mcp_apps_host_context_details_available_display_mode, mcp_apps_host_context_details_display_mode, mcp_apps_host_context_details_platform, mcp_apps_host_context_details_theme, mcp_apps_list_tools_request, mcp_apps_list_tools_result, mcp_apps_read_resource_request, mcp_apps_read_resource_result, mcp_apps_resource_content, mcp_apps_set_host_context_details, mcp_apps_set_host_context_details_available_display_mode, mcp_apps_set_host_context_details_display_mode, mcp_apps_set_host_context_details_platform, mcp_apps_set_host_context_details_theme, mcp_apps_set_host_context_request, mcp_cancel_sampling_execution_params, mcp_cancel_sampling_execution_result, mcp_config_add_request, mcp_config_disable_request, mcp_config_enable_request, mcp_config_list, mcp_config_remove_request, mcp_config_update_request, mcp_configure_git_hub_request, mcp_configure_git_hub_result, mcp_disable_request, mcp_discover_request, mcp_discover_result, mcp_enable_request, mcp_execute_sampling_params, mcp_execute_sampling_request, mcp_execute_sampling_result, mcp_filtered_server, mcp_headers_handle_pending_headers_refresh_request, mcp_headers_handle_pending_headers_refresh_request_request, mcp_headers_handle_pending_headers_refresh_request_result, mcp_host_state, mcp_is_server_running_request, mcp_is_server_running_result, mcp_list_tools_request, mcp_list_tools_result, mcp_oauth_handle_pending_request, mcp_oauth_handle_pending_result, mcp_oauth_login_grant_type, mcp_oauth_login_request, mcp_oauth_login_result, mcp_oauth_pending_request_response, mcp_register_external_client_request, mcp_reload_with_config_request, mcp_remove_git_hub_result, mcp_restart_server_request, mcp_sampling_execution_action, mcp_sampling_execution_result, mcp_server, mcp_server_auth_config, mcp_server_auth_config_redirect_port, mcp_server_config, mcp_server_config_defer_tools, mcp_server_config_http, mcp_server_config_http_oauth_grant_type, mcp_server_config_http_type, mcp_server_config_stdio, mcp_server_failure_info, mcp_server_list, mcp_server_needs_auth_info, mcp_set_env_value_mode_details, mcp_set_env_value_mode_params, mcp_set_env_value_mode_result, mcp_start_server_request, mcp_start_servers_result, mcp_stop_server_request, mcp_tools, mcp_unregister_external_client_request, memory_configuration, metadata_context_attribution_result, metadata_context_heaviest_messages_request, metadata_context_heaviest_messages_result, metadata_context_info_request, metadata_context_info_result, metadata_is_processing_result, metadata_recompute_context_tokens_request, metadata_recompute_context_tokens_result, metadata_record_context_change_request, metadata_record_context_change_result, metadata_set_working_directory_request, metadata_set_working_directory_result, metadata_snapshot_current_mode, metadata_snapshot_remote_metadata, metadata_snapshot_remote_metadata_repository, metadata_snapshot_remote_metadata_task_type, model, model_billing, model_billing_token_prices, model_billing_token_prices_long_context, model_capabilities, model_capabilities_limits, model_capabilities_limits_vision, model_capabilities_override, model_capabilities_override_limits, model_capabilities_override_limits_vision, model_capabilities_override_supports, model_capabilities_supports, model_list, model_list_request, model_picker_category, model_picker_price_category, model_policy, model_policy_state, model_set_reasoning_effort_request, model_set_reasoning_effort_result, models_list_request, model_switch_to_request, model_switch_to_result, mode_set_request, named_provider_config, name_get_result, name_set_auto_request, name_set_auto_result, name_set_request, open_canvas_instance, options_update_additional_content_exclusion_policy, options_update_additional_content_exclusion_policy_rule, options_update_additional_content_exclusion_policy_rule_source, options_update_additional_content_exclusion_policy_scope, options_update_context_tier, options_update_env_value_mode, options_update_reasoning_summary, options_update_tool_filter_precedence, pending_permission_request, pending_permission_request_list, permission_decision, permission_decision_approved, permission_decision_approved_for_location, permission_decision_approved_for_session, permission_decision_approve_for_location, permission_decision_approve_for_location_approval, permission_decision_approve_for_location_approval_commands, permission_decision_approve_for_location_approval_custom_tool, permission_decision_approve_for_location_approval_extension_management, permission_decision_approve_for_location_approval_extension_permission_access, permission_decision_approve_for_location_approval_mcp, permission_decision_approve_for_location_approval_mcp_sampling, permission_decision_approve_for_location_approval_memory, permission_decision_approve_for_location_approval_read, permission_decision_approve_for_location_approval_write, permission_decision_approve_for_session, permission_decision_approve_for_session_approval, permission_decision_approve_for_session_approval_commands, permission_decision_approve_for_session_approval_custom_tool, permission_decision_approve_for_session_approval_extension_management, permission_decision_approve_for_session_approval_extension_permission_access, permission_decision_approve_for_session_approval_mcp, permission_decision_approve_for_session_approval_mcp_sampling, permission_decision_approve_for_session_approval_memory, permission_decision_approve_for_session_approval_read, permission_decision_approve_for_session_approval_write, permission_decision_approve_once, permission_decision_approve_permanently, permission_decision_cancelled, permission_decision_denied_by_content_exclusion_policy, permission_decision_denied_by_permission_request_hook, permission_decision_denied_by_rules, permission_decision_denied_interactively_by_user, permission_decision_denied_no_approval_rule_and_could_not_request_from_user, permission_decision_reject, permission_decision_request, permission_decision_user_not_available, permission_location_add_tool_approval_params, permission_location_apply_params, permission_location_apply_result, permission_location_resolve_params, permission_location_resolve_result, permission_location_type, permission_paths_add_params, permission_paths_allowed_check_params, permission_paths_allowed_check_result, permission_paths_config, permission_paths_list, permission_paths_update_primary_params, permission_paths_workspace_check_params, permission_paths_workspace_check_result, permission_prompt_shown_notification, permission_request_result, permission_rules_set, permissions_allow_all_mode, permissions_configure_additional_content_exclusion_policy, permissions_configure_additional_content_exclusion_policy_rule, permissions_configure_additional_content_exclusion_policy_rule_source, permissions_configure_additional_content_exclusion_policy_scope, permissions_configure_params, permissions_configure_result, permissions_folder_trust_add_trusted_result, permissions_get_allow_all_request, permissions_locations_add_tool_approval_details, permissions_locations_add_tool_approval_details_commands, permissions_locations_add_tool_approval_details_custom_tool, permissions_locations_add_tool_approval_details_extension_management, permissions_locations_add_tool_approval_details_extension_permission_access, permissions_locations_add_tool_approval_details_mcp, permissions_locations_add_tool_approval_details_mcp_sampling, permissions_locations_add_tool_approval_details_memory, permissions_locations_add_tool_approval_details_read, permissions_locations_add_tool_approval_details_write, permissions_locations_add_tool_approval_result, permissions_modify_rules_params, permissions_modify_rules_result, permissions_modify_rules_scope, permissions_notify_prompt_shown_result, permissions_paths_add_result, permissions_paths_list_request, permissions_paths_update_primary_result, permissions_pending_requests_request, permissions_reset_session_approvals_request, permissions_reset_session_approvals_result, permissions_set_allow_all_request, permissions_set_allow_all_source, permissions_set_approve_all_request, permissions_set_approve_all_result, permissions_set_approve_all_source, permissions_set_required_request, permissions_set_required_result, permissions_urls_set_unrestricted_mode_result, permission_urls_config, permission_urls_set_unrestricted_mode_params, ping_request, ping_result, plan_read_result, plan_read_sql_todos_result, plan_read_sql_todos_with_dependencies_result, plan_sql_todo_dependency, plan_sql_todos_row, plan_update_request, plugin, plugin_install_result, plugin_list, plugin_list_result, plugins_disable_request, plugins_enable_request, plugins_install_request, plugins_marketplaces_add_request, plugins_marketplaces_browse_request, plugins_marketplaces_refresh_request, plugins_marketplaces_remove_request, plugins_reload_request, plugins_uninstall_request, plugins_update_request, plugin_update_all_entry, plugin_update_all_result, plugin_update_result, provider_add_request, provider_add_result, provider_config, provider_config_azure, provider_config_transport, provider_config_type, provider_config_wire_api, provider_endpoint, provider_endpoint_transport, provider_endpoint_type, provider_endpoint_wire_api, provider_get_endpoint_request, provider_model_config, provider_session_token, provider_token_acquire_request, provider_token_acquire_result, push_attachment, push_attachment_blob, push_attachment_directory, push_attachment_file, push_attachment_file_line_range, push_attachment_git_hub_actions_job, push_attachment_git_hub_commit, push_attachment_git_hub_file, push_attachment_git_hub_file_diff, push_attachment_git_hub_file_diff_side, push_attachment_git_hub_reference, push_attachment_git_hub_reference_type, push_attachment_git_hub_release, push_attachment_git_hub_repository, push_attachment_git_hub_snippet, push_attachment_git_hub_tree_comparison, push_attachment_git_hub_tree_comparison_side, push_attachment_git_hub_url, push_attachment_selection, push_attachment_selection_details, push_attachment_selection_details_end, push_attachment_selection_details_start, push_git_hub_repo_ref, queued_command_handled, queued_command_not_handled, queued_command_result, queue_pending_items, queue_pending_items_kind, queue_pending_items_result, queue_remove_most_recent_result, register_event_interest_params, register_event_interest_result, register_extension_tools_params, register_extension_tools_result, release_event_interest_params, remote_control_config, remote_control_config_existing_mc_session, remote_control_status, remote_control_status_active, remote_control_status_connecting, remote_control_status_error, remote_control_status_off, remote_control_status_result, remote_control_stop_result, remote_control_transfer_result, remote_enable_request, remote_enable_result, remote_notify_steerable_changed_request, remote_notify_steerable_changed_result, remote_session_connection_result, remote_session_metadata_repository, remote_session_metadata_task_type, remote_session_metadata_value, remote_session_mode, remote_session_repository, sandbox_config, sandbox_config_user_policy, sandbox_config_user_policy_experimental, sandbox_config_user_policy_experimental_seatbelt, sandbox_config_user_policy_filesystem, sandbox_config_user_policy_network, sandbox_config_user_policy_seatbelt, schedule_entry, schedule_list, schedule_stop_request, schedule_stop_result, secrets_add_filter_values_request, secrets_add_filter_values_result, send_agent_mode, send_attachments_to_message_params, send_mode, send_request, send_result, server_agent_list, server_instruction_source_list, server_skill, server_skill_list, session_activity, session_auth_status, session_bulk_delete_result, session_capability, session_completion_item, session_context, session_context_host_type, session_enrich_metadata_result, session_fs_append_file_request, session_fs_error, session_fs_error_code, session_fs_exists_request, session_fs_exists_result, session_fs_mkdir_request, session_fs_readdir_request, session_fs_readdir_result, session_fs_readdir_with_types_entry, session_fs_readdir_with_types_entry_type, session_fs_readdir_with_types_request, session_fs_readdir_with_types_result, session_fs_read_file_request, session_fs_read_file_result, session_fs_rename_request, session_fs_rm_request, session_fs_set_provider_capabilities, session_fs_set_provider_conventions, session_fs_set_provider_request, session_fs_set_provider_result, session_fs_sqlite_exists_request, session_fs_sqlite_exists_result, session_fs_sqlite_query_request, session_fs_sqlite_query_result, session_fs_sqlite_query_type, session_fs_stat_request, session_fs_stat_result, session_fs_write_file_request, session_installed_plugin, session_installed_plugin_source, session_installed_plugin_source_git_hub, session_installed_plugin_source_local, session_installed_plugin_source_url, session_list, session_list_entry, session_list_filter, session_load_deferred_repo_hooks_result, session_log_level, session_mcp_apps_call_tool_result, session_metadata_snapshot, session_mode, session_model_list, session_open_options, session_open_options_additional_content_exclusion_policy, session_open_options_additional_content_exclusion_policy_rule, session_open_options_additional_content_exclusion_policy_rule_source, session_open_options_additional_content_exclusion_policy_scope, session_open_options_env_value_mode, session_open_options_reasoning_summary, session_open_params, session_open_result, session_prune_result, sessions_bulk_delete_request, sessions_check_in_use_request, sessions_check_in_use_result, sessions_close_request, sessions_close_result, sessions_enrich_metadata_request, session_set_credentials_params, session_set_credentials_result, session_settings_built_in_tool_availability_snapshot, session_settings_evaluate_predicate_request, session_settings_evaluate_predicate_result, session_settings_job_snapshot, session_settings_model_snapshot, session_settings_online_evaluation_snapshot, session_settings_predicate_name, session_settings_repo_snapshot, session_settings_snapshot, session_settings_validation_snapshot, sessions_find_by_prefix_request, sessions_find_by_prefix_result, sessions_find_by_task_id_request, sessions_find_by_task_id_result, sessions_fork_request, sessions_fork_result, sessions_get_board_entry_count_request, sessions_get_board_entry_count_result, sessions_get_event_file_path_request, sessions_get_event_file_path_result, sessions_get_last_for_context_request, sessions_get_last_for_context_result, sessions_get_persisted_remote_steerable_request, sessions_get_persisted_remote_steerable_result, session_sizes, sessions_list_request, sessions_load_deferred_repo_hooks_request, sessions_open_attach, sessions_open_cloud, sessions_open_create, sessions_open_handoff, sessions_open_handoff_task_type, sessions_open_progress, sessions_open_progress_status, sessions_open_progress_step, sessions_open_remote, sessions_open_resume, sessions_open_resume_last, sessions_open_status, session_source, sessions_prune_old_request, sessions_register_extension_tools_on_session_options, sessions_release_lock_request, sessions_release_lock_result, sessions_reload_plugin_hooks_request, sessions_reload_plugin_hooks_result, sessions_save_request, sessions_save_result, sessions_set_additional_plugins_request, sessions_set_additional_plugins_result, sessions_set_remote_control_steering_request, sessions_start_remote_control_request, sessions_stop_remote_control_request, sessions_transfer_remote_control_request, session_telemetry_engagement, session_update_options_params, session_update_options_result, session_visibility_status, session_working_directory_context, session_working_directory_context_host_type, shell_cancel_user_requested_request, shell_exec_request, shell_exec_result, shell_execute_user_requested_request, shell_kill_request, shell_kill_result, shell_kill_signal, shutdown_request, skill, skill_discovery_path, skill_discovery_path_list, skill_discovery_scope, skill_list, skills_config_set_disabled_skills_request, skills_disable_request, skills_discover_request, skills_enable_request, skills_get_discovery_paths_request, skills_get_invoked_result, skills_invoked_skill, skills_load_diagnostics, slash_command_agent_prompt_result, slash_command_completed_result, slash_command_info, slash_command_input, slash_command_input_choice, slash_command_input_completion, slash_command_invocation_result, slash_command_kind, slash_command_select_subcommand_option, slash_command_select_subcommand_result, slash_command_text_result, subagent_settings_entry, subagent_settings_entry_context_tier, task_agent_info, task_agent_progress, task_execution_mode, task_info, task_list, task_progress_line, tasks_cancel_request, tasks_cancel_result, tasks_get_current_promotable_result, tasks_get_progress_request, tasks_get_progress_result, task_shell_info, task_shell_info_attachment_mode, task_shell_progress, tasks_promote_current_to_background_result, tasks_promote_to_background_request, tasks_promote_to_background_result, tasks_refresh_result, tasks_remove_request, tasks_remove_result, tasks_send_message_request, tasks_send_message_result, tasks_start_agent_request, tasks_start_agent_result, task_status, tasks_wait_for_pending_result, telemetry_set_feature_overrides_request, token_auth_info, tool, tool_list, tools_get_current_metadata_result, tools_initialize_and_validate_result, tools_list_request, tools_update_subagent_settings_result, ui_auto_mode_switch_response, ui_elicitation_array_any_of_field, ui_elicitation_array_any_of_field_items, ui_elicitation_array_any_of_field_items_any_of, ui_elicitation_array_enum_field, ui_elicitation_array_enum_field_items, ui_elicitation_field_value, ui_elicitation_request, ui_elicitation_response, ui_elicitation_response_action, ui_elicitation_response_content, ui_elicitation_result, ui_elicitation_schema, ui_elicitation_schema_property, ui_elicitation_schema_property_boolean, ui_elicitation_schema_property_number, ui_elicitation_schema_property_number_type, ui_elicitation_schema_property_string, ui_elicitation_schema_property_string_format, ui_elicitation_string_enum_field, ui_elicitation_string_one_of_field, ui_elicitation_string_one_of_field_one_of, ui_ephemeral_query_request, ui_ephemeral_query_result, ui_exit_plan_mode_action, ui_exit_plan_mode_response, ui_handle_pending_auto_mode_switch_request, ui_handle_pending_elicitation_request, ui_handle_pending_exit_plan_mode_request, ui_handle_pending_result, ui_handle_pending_sampling_request, ui_handle_pending_sampling_response, ui_handle_pending_session_limits_exhausted_request, ui_handle_pending_user_input_request, ui_register_direct_auto_mode_switch_handler_result, ui_session_limits_exhausted_response, ui_session_limits_exhausted_response_action, ui_unregister_direct_auto_mode_switch_handler_request, ui_unregister_direct_auto_mode_switch_handler_result, ui_user_input_response, update_subagent_settings_request, usage_get_metrics_result, usage_metrics_code_changes, usage_metrics_model_metric, usage_metrics_model_metric_requests, usage_metrics_model_metric_token_detail, usage_metrics_model_metric_usage, usage_metrics_token_detail, user_auth_info, user_requested_shell_command_result, user_setting_metadata, user_settings_get_result, user_settings_set_request, user_settings_set_result, visibility_get_result, visibility_set_request, visibility_set_result, workspace_diff_file_change, workspace_diff_file_change_type, workspace_diff_mode, workspace_diff_result, workspaces_checkpoints, workspaces_create_file_request, workspaces_diff_request, workspaces_get_workspace_result, workspaces_list_checkpoints_result, workspaces_list_files_result, workspaces_read_checkpoint_request, workspaces_read_checkpoint_result, workspaces_read_file_request, workspaces_read_file_result, workspaces_save_large_paste_request, workspaces_save_large_paste_result, workspace_summary_host_type, workspaces_workspace_details_host_type, session_context_attribution, session_context_info, subagent_settings, task_progress, workspace_summary) def to_dict(self) -> dict: result: dict = {} @@ -24054,6 +25272,7 @@ def to_dict(self) -> dict: result["ConnectRequest"] = to_class(_ConnectRequest, self.connect_request) result["ConnectResult"] = to_class(_ConnectResult, self.connect_result) result["ContentFilterMode"] = to_enum(ContentFilterMode, self.content_filter_mode) + result["ContextHeaviestMessage"] = to_class(ContextHeaviestMessage, self.context_heaviest_message) result["CopilotApiTokenAuthInfo"] = to_class(CopilotAPITokenAuthInfo, self.copilot_api_token_auth_info) result["CopilotUserResponse"] = to_class(CopilotUserResponse, self.copilot_user_response) result["CopilotUserResponseEndpoints"] = to_class(CopilotUserResponseEndpoints, self.copilot_user_response_endpoints) @@ -24063,6 +25282,17 @@ def to_dict(self) -> dict: result["CopilotUserResponseQuotaSnapshotsPremiumInteractions"] = to_class(CopilotUserResponseQuotaSnapshotsPremiumInteractions, self.copilot_user_response_quota_snapshots_premium_interactions) result["CurrentModel"] = to_class(CurrentModel, self.current_model) result["CurrentToolMetadata"] = to_class(CurrentToolMetadata, self.current_tool_metadata) + result["DebugCollectLogsCollectedEntry"] = to_class(DebugCollectLogsCollectedEntry, self.debug_collect_logs_collected_entry) + result["DebugCollectLogsDestination"] = to_class(DebugCollectLogsDestination, self.debug_collect_logs_destination) + result["DebugCollectLogsEntry"] = to_class(DebugCollectLogsEntry, self.debug_collect_logs_entry) + result["DebugCollectLogsEntryKind"] = to_enum(DebugCollectLogsEntryKind, self.debug_collect_logs_entry_kind) + result["DebugCollectLogsInclude"] = to_class(DebugCollectLogsInclude, self.debug_collect_logs_include) + result["DebugCollectLogsRedaction"] = to_enum(DebugCollectLogsRedaction, self.debug_collect_logs_redaction) + result["DebugCollectLogsRequest"] = to_class(DebugCollectLogsRequest, self.debug_collect_logs_request) + result["DebugCollectLogsResult"] = to_class(DebugCollectLogsResult, self.debug_collect_logs_result) + 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["DiscoveredCanvas"] = to_class(DiscoveredCanvas, self.discovered_canvas) result["DiscoveredMcpServer"] = to_class(DiscoveredMCPServer, self.discovered_mcp_server) result["DiscoveredMcpServerType"] = to_enum(DiscoveredMCPServerType, self.discovered_mcp_server_type) @@ -24128,7 +25358,7 @@ def to_dict(self) -> dict: result["InstalledPluginSourceLocal"] = to_class(InstalledPluginSourceLocal, self.installed_plugin_source_local) result["InstalledPluginSourceUrl"] = to_class(InstalledPluginSourceURL, self.installed_plugin_source_url) result["InstructionDiscoveryPath"] = to_class(InstructionDiscoveryPath, self.instruction_discovery_path) - result["InstructionDiscoveryPathKind"] = to_enum(InstructionDiscoveryPathKind, self.instruction_discovery_path_kind) + result["InstructionDiscoveryPathKind"] = to_enum(DebugCollectLogsEntryKind, self.instruction_discovery_path_kind) result["InstructionDiscoveryPathList"] = to_class(InstructionDiscoveryPathList, self.instruction_discovery_path_list) result["InstructionDiscoveryPathLocation"] = to_enum(InstructionLocation, self.instruction_discovery_path_location) result["InstructionsDiscoverRequest"] = to_class(InstructionsDiscoverRequest, self.instructions_discover_request) @@ -24216,8 +25446,6 @@ def to_dict(self) -> dict: result["McpOauthLoginRequest"] = to_class(MCPOauthLoginRequest, self.mcp_oauth_login_request) result["McpOauthLoginResult"] = to_class(MCPOauthLoginResult, self.mcp_oauth_login_result) result["McpOauthPendingRequestResponse"] = to_class(MCPOauthPendingRequestResponse, self.mcp_oauth_pending_request_response) - result["McpOauthRespondRequest"] = to_class(MCPOauthRespondRequest, self.mcp_oauth_respond_request) - result["McpOauthRespondResult"] = to_class(MCPOauthRespondResult, self.mcp_oauth_respond_result) result["McpRegisterExternalClientRequest"] = to_class(MCPRegisterExternalClientRequest, self.mcp_register_external_client_request) result["McpReloadWithConfigRequest"] = to_class(MCPReloadWithConfigRequest, self.mcp_reload_with_config_request) result["McpRemoveGitHubResult"] = to_class(MCPRemoveGitHubResult, self.mcp_remove_git_hub_result) @@ -24245,6 +25473,9 @@ def to_dict(self) -> dict: result["McpTools"] = to_class(MCPTools, self.mcp_tools) result["McpUnregisterExternalClientRequest"] = to_class(MCPUnregisterExternalClientRequest, self.mcp_unregister_external_client_request) result["MemoryConfiguration"] = to_class(MemoryConfiguration, self.memory_configuration) + result["MetadataContextAttributionResult"] = to_class(MetadataContextAttributionResult, self.metadata_context_attribution_result) + result["MetadataContextHeaviestMessagesRequest"] = to_class(MetadataContextHeaviestMessagesRequest, self.metadata_context_heaviest_messages_request) + result["MetadataContextHeaviestMessagesResult"] = to_class(MetadataContextHeaviestMessagesResult, self.metadata_context_heaviest_messages_result) result["MetadataContextInfoRequest"] = to_class(MetadataContextInfoRequest, self.metadata_context_info_request) result["MetadataContextInfoResult"] = to_class(MetadataContextInfoResult, self.metadata_context_info_result) result["MetadataIsProcessingResult"] = to_class(MetadataIsProcessingResult, self.metadata_is_processing_result) @@ -24352,6 +25583,7 @@ def to_dict(self) -> dict: result["PermissionPromptShownNotification"] = to_class(PermissionPromptShownNotification, self.permission_prompt_shown_notification) result["PermissionRequestResult"] = to_class(PermissionRequestResult, self.permission_request_result) result["PermissionRulesSet"] = to_class(PermissionRulesSet, self.permission_rules_set) + result["PermissionsAllowAllMode"] = to_enum(PermissionsAllowAllMode, self.permissions_allow_all_mode) result["PermissionsConfigureAdditionalContentExclusionPolicy"] = to_class(PermissionsConfigureAdditionalContentExclusionPolicy, self.permissions_configure_additional_content_exclusion_policy) result["PermissionsConfigureAdditionalContentExclusionPolicyRule"] = to_class(PermissionsConfigureAdditionalContentExclusionPolicyRule, self.permissions_configure_additional_content_exclusion_policy_rule) result["PermissionsConfigureAdditionalContentExclusionPolicyRuleSource"] = to_class(PermissionsConfigureAdditionalContentExclusionPolicyRuleSource, self.permissions_configure_additional_content_exclusion_policy_rule_source) @@ -24416,7 +25648,6 @@ def to_dict(self) -> dict: result["PluginUpdateAllEntry"] = to_class(PluginUpdateAllEntry, self.plugin_update_all_entry) result["PluginUpdateAllResult"] = to_class(PluginUpdateAllResult, self.plugin_update_all_result) result["PluginUpdateResult"] = to_class(PluginUpdateResult, self.plugin_update_result) - result["PollSpawnedSessionsResult"] = to_class(PollSpawnedSessionsResult, self.poll_spawned_sessions_result) result["ProviderAddRequest"] = to_class(ProviderAddRequest, self.provider_add_request) result["ProviderAddResult"] = to_class(ProviderAddResult, self.provider_add_result) result["ProviderConfig"] = to_class(ProviderConfig, self.provider_config) @@ -24527,7 +25758,7 @@ def to_dict(self) -> dict: result["SessionFsReaddirRequest"] = to_class(SessionFSReaddirRequest, self.session_fs_readdir_request) result["SessionFsReaddirResult"] = to_class(SessionFSReaddirResult, self.session_fs_readdir_result) result["SessionFsReaddirWithTypesEntry"] = to_class(SessionFSReaddirWithTypesEntry, self.session_fs_readdir_with_types_entry) - result["SessionFsReaddirWithTypesEntryType"] = to_enum(InstructionDiscoveryPathKind, self.session_fs_readdir_with_types_entry_type) + result["SessionFsReaddirWithTypesEntryType"] = to_enum(DebugCollectLogsEntryKind, self.session_fs_readdir_with_types_entry_type) result["SessionFsReaddirWithTypesRequest"] = to_class(SessionFSReaddirWithTypesRequest, self.session_fs_readdir_with_types_request) result["SessionFsReaddirWithTypesResult"] = to_class(SessionFSReaddirWithTypesResult, self.session_fs_readdir_with_types_result) result["SessionFsReadFileRequest"] = to_class(SessionFSReadFileRequest, self.session_fs_read_file_request) @@ -24578,6 +25809,16 @@ def to_dict(self) -> dict: result["SessionsEnrichMetadataRequest"] = to_class(SessionsEnrichMetadataRequest, self.sessions_enrich_metadata_request) result["SessionSetCredentialsParams"] = to_class(SessionSetCredentialsParams, self.session_set_credentials_params) result["SessionSetCredentialsResult"] = to_class(SessionSetCredentialsResult, self.session_set_credentials_result) + result["SessionSettingsBuiltInToolAvailabilitySnapshot"] = to_class(SessionSettingsBuiltInToolAvailabilitySnapshot, self.session_settings_built_in_tool_availability_snapshot) + result["SessionSettingsEvaluatePredicateRequest"] = to_class(SessionSettingsEvaluatePredicateRequest, self.session_settings_evaluate_predicate_request) + result["SessionSettingsEvaluatePredicateResult"] = to_class(SessionSettingsEvaluatePredicateResult, self.session_settings_evaluate_predicate_result) + result["SessionSettingsJobSnapshot"] = to_class(SessionSettingsJobSnapshot, self.session_settings_job_snapshot) + result["SessionSettingsModelSnapshot"] = to_class(SessionSettingsModelSnapshot, self.session_settings_model_snapshot) + result["SessionSettingsOnlineEvaluationSnapshot"] = to_class(SessionSettingsOnlineEvaluationSnapshot, self.session_settings_online_evaluation_snapshot) + result["SessionSettingsPredicateName"] = to_enum(SessionSettingsPredicateName, self.session_settings_predicate_name) + result["SessionSettingsRepoSnapshot"] = to_class(SessionSettingsRepoSnapshot, self.session_settings_repo_snapshot) + result["SessionSettingsSnapshot"] = to_class(SessionSettingsSnapshot, self.session_settings_snapshot) + result["SessionSettingsValidationSnapshot"] = to_class(SessionSettingsValidationSnapshot, self.session_settings_validation_snapshot) result["SessionsFindByPrefixRequest"] = to_class(SessionsFindByPrefixRequest, self.sessions_find_by_prefix_request) result["SessionsFindByPrefixResult"] = to_class(SessionsFindByPrefixResult, self.sessions_find_by_prefix_result) result["SessionsFindByTaskIDRequest"] = to_class(SessionsFindByTaskIDRequest, self.sessions_find_by_task_id_request) @@ -24608,8 +25849,6 @@ def to_dict(self) -> dict: result["SessionsOpenResumeLast"] = to_class(SessionsOpenResumeLast, self.sessions_open_resume_last) result["SessionsOpenStatus"] = to_enum(SessionsOpenStatus, self.sessions_open_status) result["SessionSource"] = to_enum(SessionSource, self.session_source) - result["SessionsPollSpawnedSessionsEvent"] = to_class(SessionsPollSpawnedSessionsEvent, self.sessions_poll_spawned_sessions_event) - result["SessionsPollSpawnedSessionsRequest"] = to_class(SessionsPollSpawnedSessionsRequest, self.sessions_poll_spawned_sessions_request) result["SessionsPruneOldRequest"] = to_class(SessionsPruneOldRequest, self.sessions_prune_old_request) result["SessionsRegisterExtensionToolsOnSessionOptions"] = to_class(SessionsRegisterExtensionToolsOnSessionOptions, self.sessions_register_extension_tools_on_session_options) result["SessionsReleaseLockRequest"] = to_class(SessionsReleaseLockRequest, self.sessions_release_lock_request) @@ -24655,6 +25894,7 @@ def to_dict(self) -> dict: result["SlashCommandCompletedResult"] = to_class(SlashCommandCompletedResult, self.slash_command_completed_result) result["SlashCommandInfo"] = to_class(SlashCommandInfo, self.slash_command_info) result["SlashCommandInput"] = to_class(SlashCommandInput, self.slash_command_input) + result["SlashCommandInputChoice"] = to_class(SlashCommandInputChoice, self.slash_command_input_choice) result["SlashCommandInputCompletion"] = to_enum(SlashCommandInputCompletion, self.slash_command_input_completion) result["SlashCommandInvocationResult"] = (self.slash_command_invocation_result).to_dict() result["SlashCommandKind"] = to_enum(SlashCommandKind, self.slash_command_kind) @@ -24772,6 +26012,7 @@ def to_dict(self) -> dict: result["WorkspacesSaveLargePasteResult"] = to_class(WorkspacesSaveLargePasteResult, self.workspaces_save_large_paste_result) result["WorkspaceSummaryHostType"] = to_enum(HostType, self.workspace_summary_host_type) result["WorkspacesWorkspaceDetailsHostType"] = to_enum(HostType, self.workspaces_workspace_details_host_type) + result["SessionContextAttribution"] = from_union([lambda x: to_class(SessionContextAttribution, x), from_none], self.session_context_attribution) result["SessionContextInfo"] = from_union([lambda x: to_class(SessionContextInfo, x), from_none], self.session_context_info) result["SubagentSettings"] = from_union([lambda x: to_class(SubagentSettings, x), from_none], self.subagent_settings) result["TaskProgress"] = from_union([lambda x: to_class(TaskProgress, x), from_none], self.task_progress) @@ -24907,7 +26148,7 @@ def _load_PermissionsLocationsAddToolApprovalDetails(obj: Any) -> "PermissionsLo case "extension-permission-access": return PermissionsLocationsAddToolApprovalDetailsExtensionPermissionAccess.from_dict(obj) case _: raise ValueError(f"Unknown PermissionsLocationsAddToolApprovalDetails kind: {kind!r}") -# Schema for the `PushAttachment` type. +# Attachment union accepted by push input, covering files, directories, GitHub objects, blobs, snippets, and extension context. PushAttachment = PushAttachmentFile | PushAttachmentDirectory | PushAttachmentSelection | PushAttachmentGitHubReference | PushAttachmentGitHubCommit | PushAttachmentGitHubRelease | PushAttachmentGitHubActionsJob | PushAttachmentGitHubRepository | PushAttachmentGitHubFileDiff | PushAttachmentGitHubTreeComparison | PushAttachmentGitHubURL | PushAttachmentGitHubFile | PushAttachmentGitHubSnippet | PushAttachmentBlob | ExtensionContextPushInput def _load_PushAttachment(obj: Any) -> "PushAttachment": @@ -24995,7 +26236,7 @@ def _load_SlashCommandInvocationResult(obj: Any) -> "SlashCommandInvocationResul case "select-subcommand": return SlashCommandSelectSubcommandResult.from_dict(obj) case _: raise ValueError(f"Unknown SlashCommandInvocationResult kind: {kind!r}") -# Schema for the `TaskInfo` type. +# Tracked task union returned by task APIs, containing either an agent task or a shell task. TaskInfo = TaskAgentInfo | TaskShellInfo def _load_TaskInfo(obj: Any) -> "TaskInfo": @@ -25013,6 +26254,7 @@ def _load_TaskInfo(obj: Any) -> "TaskInfo": ExternalToolResult = ExternalToolTextResultForLlm ExternalToolTextResultForLlmContentResourceLinkIconTheme = Theme FilterMapping = dict +InstructionDiscoveryPathKind = DebugCollectLogsEntryKind InstructionDiscoveryPathLocation = InstructionLocation InstructionSourceLocation = InstructionLocation LlmInferenceHeaders = dict @@ -25043,7 +26285,7 @@ def _load_TaskInfo(obj: Any) -> "TaskInfo": ProviderEndpointWireApi = ProviderWireAPI RemoteSessionMetadataTaskType = TaskType SessionContextHostType = HostType -SessionFsReaddirWithTypesEntryType = InstructionDiscoveryPathKind +SessionFsReaddirWithTypesEntryType = DebugCollectLogsEntryKind SessionMcpAppsCallToolResult = dict SessionOpenOptionsAdditionalContentExclusionPolicyScope = AdditionalContentExclusionPolicyScope SessionOpenOptionsEnvValueMode = MCPSetEnvValueModeDetails @@ -25569,11 +26811,6 @@ async def _get_board_entry_count(self, params: SessionsGetBoardEntryCountRequest params_dict = {k: v for k, v in params.to_dict().items() if v is not None} return SessionsGetBoardEntryCountResult.from_dict(await self._client.request("sessions.getBoardEntryCount", params_dict, **_timeout_kwargs(timeout))) - async def _poll_spawned_sessions(self, params: SessionsPollSpawnedSessionsRequest, *, timeout: float | None = None) -> PollSpawnedSessionsResult: - "Cursor-based long-poll for sessions spawned by the runtime (e.g. in response to a Mission Control `start_session` command). The cursor is an opaque token; pass it back to receive only spawn events that occurred AFTER the cursor was issued. Omit the cursor on the first call to receive any events buffered since the runtime started. Internal: this is a CLI background-daemon plumbing primitive. SDK consumers that need to react to runtime-spawned sessions should subscribe to a higher-level event stream rather than driving a long-poll loop.\n\nArgs:\n params: Cursor and optional long-poll wait for polling runtime-spawned sessions.\n\nReturns:\n Batch of spawn events plus a cursor for follow-up polls.\n\n:meta private:\n\nInternal SDK API; not part of the public surface." - params_dict = {k: v for k, v in params.to_dict().items() if v is not None} - return PollSpawnedSessionsResult.from_dict(await self._client.request("sessions.pollSpawnedSessions", params_dict, **_timeout_kwargs(timeout))) - async def _register_extension_tools_on_session(self, params: _RegisterExtensionToolsParams, *, timeout: float | None = None) -> _RegisterExtensionToolsResult: "Registers extension-provided tools on the given session, gated by an optional `enabled` callback. Returns an opaque unsubscribe function the caller must invoke to deregister the tools when the extension is torn down. Marked internal because `loader`, `enabled`, and the returned `unsubscribe` are in-process handles that cannot cross the JSON-RPC boundary. Disappears once extension discovery / launch / tool registration are owned by the runtime: SDK consumers will pass pure config (search paths, disabled ids) via `SessionOptions` and the runtime will resolve, launch, register, and tear down extensions itself.\n\nArgs:\n params: Params to attach an extension loader's tools to a session.\n\nReturns:\n Handle for releasing the extension tool registration.\n\n:meta private:\n\nInternal SDK API; not part of the public surface." params_dict = {k: v for k, v in params.to_dict().items() if v is not None} @@ -25592,7 +26829,7 @@ def __init__(self, client: "JsonRpcClient"): self.sessions = _InternalServerSessionsApi(client) async def _connect(self, params: _ConnectRequest, *, timeout: float | None = None) -> _ConnectResult: - "Performs the SDK server connection handshake and validates the optional connection token. Marked internal because this is JSON-RPC transport plumbing invoked automatically by an SDK client's own `connect()` wrapper, not a user-facing method. Stays internal as long as the SDK client owns the handshake; would only become public if the SDK ever exposed the raw schema surface to consumers without a connection wrapper.\n\nArgs:\n params: Optional connection token presented by the SDK client during the handshake.\n\nReturns:\n Handshake result reporting the server's protocol version and package version on success.\n\n.. warning:: This API is experimental and may change or be removed in future versions.\n\n:meta private:\n\nInternal SDK API; not part of the public surface." + "Performs the SDK server connection handshake and validates the optional connection token. Marked internal because this is JSON-RPC transport plumbing invoked automatically by an SDK client's own `connect()` wrapper, not a user-facing method. Stays internal as long as the SDK client owns the handshake; would only become public if the SDK ever exposed the raw schema surface to consumers without a connection wrapper.\n\nArgs:\n params: Parameters for the `server.connect` handshake: an optional connection token and optional connection-level opt-ins (e.g. GitHub telemetry forwarding).\n\nReturns:\n Handshake result reporting the server's protocol version and package version on success.\n\n.. warning:: This API is experimental and may change or be removed in future versions.\n\n:meta private:\n\nInternal SDK API; not part of the public surface." params_dict = {k: v for k, v in params.to_dict().items() if v is not None} return _ConnectResult.from_dict(await self._client.request("connect", params_dict, **_timeout_kwargs(timeout))) @@ -25614,6 +26851,19 @@ async def set_credentials(self, params: SessionSetCredentialsParams, *, timeout: return SessionSetCredentialsResult.from_dict(await self._client.request("session.gitHubAuth.setCredentials", params_dict, **_timeout_kwargs(timeout))) +# Experimental: this API group is experimental and may change or be removed. +class DebugApi: + def __init__(self, client: "JsonRpcClient", session_id: str): + self._client = client + self._session_id = session_id + + async def collect_logs(self, params: DebugCollectLogsRequest, *, timeout: float | None = None) -> DebugCollectLogsResult: + "Collects a redacted session debug log bundle into a local archive or staging directory. The runtime includes session-owned logs by default and accepts caller-provided diagnostic entries so host applications can add their own files without changing this API shape.\n\nArgs:\n params: Options for collecting a redacted session debug bundle.\n\nReturns:\n Result of collecting a redacted debug bundle." + 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 DebugCollectLogsResult.from_dict(await self._client.request("session.debug.collectLogs", params_dict, **_timeout_kwargs(timeout))) + + # Experimental: this API group is experimental and may change or be removed. class CanvasActionApi: def __init__(self, client: "JsonRpcClient", session_id: str): @@ -26485,13 +27735,13 @@ async def set_approve_all(self, params: PermissionsSetApproveAllRequest, *, time return PermissionsSetApproveAllResult.from_dict(await self._client.request("session.permissions.setApproveAll", params_dict, **_timeout_kwargs(timeout))) async def set_allow_all(self, params: PermissionsSetAllowAllRequest, *, timeout: float | None = None) -> AllowAllPermissionSetResult: - "Enables or disables full allow-all permissions (tools, paths, and URLs) for the session. Used by attach-mode clients (e.g. LocalRpcSession's `/allow-all` forwarder) to flip the target session's permission state. Unlike `setApproveAll`, this swaps in the unrestricted path and URL managers and emits `session.permissions_changed` on transition. The result returns the authoritative post-mutation state so callers can update their local mirrors without racing the `session.permissions_changed` notification on the same wire.\n\nArgs:\n params: Whether to enable full allow-all permissions for the session.\n\nReturns:\n Indicates whether the operation succeeded and reports the post-mutation state." + "Sets the allow-all permission mode for the session. Used by attach-mode clients (e.g. LocalRpcSession's `/allow-all` forwarder) to flip the target session's permission state. The `on` mode swaps in unrestricted path and URL managers and emits `session.permissions_changed` on transition; the `auto` mode keeps normal prompt paths active while attaching LLM safety recommendations. The result returns the authoritative post-mutation state so callers can update their local mirrors without racing the `session.permissions_changed` notification on the same wire.\n\nArgs:\n params: Allow-all mode to apply for the session.\n\nReturns:\n Indicates whether the operation succeeded and reports the post-mutation state." params_dict: dict[str, Any] = {k: v for k, v in params.to_dict().items() if v is not None} params_dict["sessionId"] = self._session_id return AllowAllPermissionSetResult.from_dict(await self._client.request("session.permissions.setAllowAll", params_dict, **_timeout_kwargs(timeout))) async def get_allow_all(self, *, timeout: float | None = None) -> AllowAllPermissionState: - "Returns whether full allow-all permissions are currently active for the session.\n\nReturns:\n Current full allow-all permission state." + "Returns the current allow-all permission mode for the session.\n\nReturns:\n Current allow-all permission mode." return AllowAllPermissionState.from_dict(await self._client.request("session.permissions.getAllowAll", {"sessionId": self._session_id}, **_timeout_kwargs(timeout))) async def modify_rules(self, params: PermissionsModifyRulesParams, *, timeout: float | None = None) -> PermissionsModifyRulesResult: @@ -26541,6 +27791,16 @@ async def context_info(self, params: MetadataContextInfoRequest, *, timeout: flo params_dict["sessionId"] = self._session_id return MetadataContextInfoResult.from_dict(await self._client.request("session.metadata.contextInfo", params_dict, **_timeout_kwargs(timeout))) + async def get_context_attribution(self, *, timeout: float | None = None) -> MetadataContextAttributionResult: + "Returns the experimental per-source attribution breakdown of the session's current context window as a flat list of entries (skills, subagents, MCP servers, built-in tools, plugin rollups, system/tool-definition costs, with nesting via parentId), plus the successful compaction count. The heaviest individual messages are available separately via `metadata.getContextHeaviestMessages`. Returns null until the session has initialized its system prompt and tool metadata.\n\nReturns:\n Per-source attribution breakdown for the session's current context window, or null if uninitialized." + return MetadataContextAttributionResult.from_dict(await self._client.request("session.metadata.getContextAttribution", {"sessionId": self._session_id}, **_timeout_kwargs(timeout))) + + async def get_context_heaviest_messages(self, params: MetadataContextHeaviestMessagesRequest, *, timeout: float | None = None) -> MetadataContextHeaviestMessagesResult: + "Returns the largest individual messages currently in the session's context window, most-expensive first. Companion to `metadata.getContextAttribution`. Returns an empty list until the session has initialized.\n\nArgs:\n params: Parameters for the heaviest-messages query.\n\nReturns:\n The heaviest individual messages in the session's context window, most-expensive first." + 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 MetadataContextHeaviestMessagesResult.from_dict(await self._client.request("session.metadata.getContextHeaviestMessages", params_dict, **_timeout_kwargs(timeout))) + async def record_context_change(self, params: MetadataRecordContextChangeRequest, *, timeout: float | None = None) -> MetadataRecordContextChangeResult: "Records a working-directory/git context change and emits a `session.context_changed` event.\n\nArgs:\n params: Updated working-directory/git context to record on the session.\n\nReturns:\n Notify the session that its working directory context has changed. Emits a `session.context_changed` event so consumers (telemetry, OTel tracker, ACP, the timeline UI) can react. Use this when the host has detected a cwd/branch/repo change outside the session's normal lifecycle (e.g., after a shell command in interactive mode)." params_dict: dict[str, Any] = {k: v for k, v in params.to_dict().items() if v is not None} @@ -26744,6 +28004,7 @@ def __init__(self, client: "JsonRpcClient", session_id: str): self._client = client self._session_id = session_id self.git_hub_auth = GitHubAuthApi(client, session_id) + self.debug = DebugApi(client, session_id) self.canvas = CanvasApi(client, session_id) self.model = ModelApi(client, session_id) self.mode = ModeApi(client, session_id) @@ -26806,25 +28067,11 @@ async def log(self, params: LogRequest, *, timeout: float | None = None) -> LogR return LogResult.from_dict(await self._client.request("session.log", params_dict, **_timeout_kwargs(timeout))) -# Experimental: this API group is experimental and may change or be removed. -class _InternalMcpOauthApi: - def __init__(self, client: "JsonRpcClient", session_id: str): - self._client = client - self._session_id = session_id - - async def _respond(self, params: MCPOauthRespondRequest, *, timeout: float | None = None) -> MCPOauthRespondResult: - "Responds to a pending MCP OAuth request with an in-process provider. This internal CLI-only API accepts a live OAuthClientProvider instance and cannot be used over the SDK JSON-RPC boundary. Use session.mcp.oauth.handlePendingRequest instead for the public SDK-safe response path.\n\nArgs:\n params: MCP OAuth request id and optional provider response.\n\nReturns:\n Empty result after recording the MCP OAuth response.\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 MCPOauthRespondResult.from_dict(await self._client.request("session.mcp.oauth.respond", params_dict, **_timeout_kwargs(timeout))) - - # Experimental: this API group is experimental and may change or be removed. class _InternalMcpApi: def __init__(self, client: "JsonRpcClient", session_id: str): self._client = client self._session_id = session_id - self.oauth = _InternalMcpOauthApi(client, session_id) async def _reload_with_config(self, params: MCPReloadWithConfigRequest, *, timeout: float | None = None) -> MCPStartServersResult: "Reloads MCP server connections for the session with an explicit host-provided configuration.\n\nArgs:\n params: Opaque MCP reload configuration.\n\nReturns:\n MCP server startup filtering result.\n\n:meta private:\n\nInternal SDK API; not part of the public surface." @@ -26863,12 +28110,30 @@ async def _unregister_external_client(self, params: MCPUnregisterExternalClientR await self._client.request("session.mcp.unregisterExternalClient", params_dict, **_timeout_kwargs(timeout)) +# Experimental: this API group is experimental and may change or be removed. +class _InternalSettingsApi: + def __init__(self, client: "JsonRpcClient", session_id: str): + self._client = client + self._session_id = session_id + + async def _snapshot(self, *, timeout: float | None = None) -> SessionSettingsSnapshot: + "Returns a redacted snapshot of session runtime settings, with secrets and raw feature flags excluded. Internal: the runtime settings shape is a runtime-internal surface and is deliberately kept out of the public SDK, because consumers should not depend on the runtime's internal settings layout. It remains callable in-process and is expected to be reworked as the runtime internals are consolidated.\n\nReturns:\n Redacted, serializable view of session runtime settings for SDK boundary consumers. Secrets and raw feature flags are intentionally excluded.\n\n:meta private:\n\nInternal SDK API; not part of the public surface." + return SessionSettingsSnapshot.from_dict(await self._client.request("session.settings.snapshot", {"sessionId": self._session_id}, **_timeout_kwargs(timeout))) + + async def _evaluate_predicate(self, params: SessionSettingsEvaluatePredicateRequest, *, timeout: float | None = None) -> SessionSettingsEvaluatePredicateResult: + "Evaluates a named Rust-owned settings predicate without exposing raw feature flags. Internal: the raw feature-flag names and composition are runtime-internal, so this predicate-evaluation helper is kept out of the public SDK surface and is callable in-process only.\n\nArgs:\n params: Named Rust-owned settings predicate to evaluate for this session.\n\nReturns:\n Result of evaluating a Rust-owned settings predicate.\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 SessionSettingsEvaluatePredicateResult.from_dict(await self._client.request("session.settings.evaluatePredicate", params_dict, **_timeout_kwargs(timeout))) + + class _InternalSessionRpc: """Internal SDK session-scoped RPC methods. Not part of the public API.""" def __init__(self, client: "JsonRpcClient", session_id: str): self._client = client self._session_id = session_id self.mcp = _InternalMcpApi(client, session_id) + self.settings = _InternalSettingsApi(client, session_id) # Experimental: this API group is experimental and may change or be removed. @@ -27064,7 +28329,7 @@ async def http_request_chunk(self, params: LlmInferenceHTTPRequestChunkRequest) # Experimental: this API group is experimental and may change or be removed. class GitHubTelemetryHandler(Protocol): async def event(self, params: GitHubTelemetryNotification) -> None: - "Forwards a single GitHub telemetry event to a host connection that opted into telemetry forwarding for the session.\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 for the session." + "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 @dataclass @@ -27096,13 +28361,13 @@ async def handle_llm_inference_http_request_chunk(params: dict) -> dict | None: result = await handler.http_request_chunk(request) return result.to_dict() client.set_request_handler("llmInference.httpRequestChunk", handle_llm_inference_http_request_chunk) - async def handle_git_hub_telemetry_event(params: dict) -> dict | None: + async def handle_git_hub_telemetry_event(params: dict) -> None: request = GitHubTelemetryNotification.from_dict(params) handler = handlers.git_hub_telemetry - if handler is None: raise RuntimeError("No git_hub_telemetry client-global handler registered") + if handler is None: return None await handler.event(request) return None - client.set_request_handler("gitHubTelemetry.event", handle_git_hub_telemetry_event) + client.set_notification_method_handler("gitHubTelemetry.event", handle_git_hub_telemetry_event) __all__ = [ "APIKeyAuthInfo", @@ -27190,6 +28455,7 @@ async def handle_git_hub_telemetry_event(params: dict) -> dict | None: "CommandsListRequest", "CommandsRespondToQueuedCommandRequest", "CommandsRespondToQueuedCommandResult", + "Compactions", "CompletionsApi", "CompletionsGetTriggerCharactersResult", "CompletionsRequestRequest", @@ -27199,6 +28465,7 @@ async def handle_git_hub_telemetry_event(params: dict) -> dict | None: "ConnectedRemoteSessionMetadataKind", "ConnectedRemoteSessionMetadataRepository", "ContentFilterMode", + "ContextHeaviestMessage", "CopilotAPITokenAuthInfo", "CopilotAPITokenAuthInfoType", "CopilotUserResponse", @@ -27209,11 +28476,24 @@ async def handle_git_hub_telemetry_event(params: dict) -> dict | None: "CopilotUserResponseQuotaSnapshotsPremiumInteractions", "CurrentModel", "CurrentToolMetadata", + "DebugApi", + "DebugCollectLogsCollectedEntry", + "DebugCollectLogsDestination", + "DebugCollectLogsEntry", + "DebugCollectLogsEntryKind", + "DebugCollectLogsInclude", + "DebugCollectLogsRedaction", + "DebugCollectLogsRequest", + "DebugCollectLogsResult", + "DebugCollectLogsResultKind", + "DebugCollectLogsSkippedEntry", + "DebugCollectLogsSource", "DiscoveredCanvas", "DiscoveredMCPServer", "DiscoveredMCPServerType", "EnqueueCommandParams", "EnqueueCommandResult", + "Entry", "EnvAuthInfo", "EnvAuthInfoType", "EventLogApi", @@ -27373,8 +28653,6 @@ async def handle_git_hub_telemetry_event(params: dict) -> dict | None: "MCPOauthLoginResult", "MCPOauthPendingRequestResponse", "MCPOauthPendingRequestResponseKind", - "MCPOauthRespondRequest", - "MCPOauthRespondResult", "MCPRegisterExternalClientRequest", "MCPReloadWithConfigRequest", "MCPRemoveGitHubResult", @@ -27425,6 +28703,9 @@ async def handle_git_hub_telemetry_event(params: dict) -> dict | None: "McpServerConfigHttpOauthGrantType", "MemoryConfiguration", "MetadataApi", + "MetadataContextAttributionResult", + "MetadataContextHeaviestMessagesRequest", + "MetadataContextHeaviestMessagesResult", "MetadataContextInfoRequest", "MetadataContextInfoResult", "MetadataIsProcessingResult", @@ -27564,6 +28845,7 @@ async def handle_git_hub_telemetry_event(params: dict) -> dict | None: "PermissionRulesSet", "PermissionUrlsConfig", "PermissionUrlsSetUnrestrictedModeParams", + "PermissionsAllowAllMode", "PermissionsApi", "PermissionsConfigureAdditionalContentExclusionPolicy", "PermissionsConfigureAdditionalContentExclusionPolicyRule", @@ -27634,7 +28916,6 @@ async def handle_git_hub_telemetry_event(params: dict) -> dict | None: "PluginsReloadRequest", "PluginsUninstallRequest", "PluginsUpdateRequest", - "PollSpawnedSessionsResult", "ProviderAddRequest", "ProviderAddResult", "ProviderApi", @@ -27783,6 +29064,7 @@ async def handle_git_hub_telemetry_event(params: dict) -> dict | None: "SessionCapability", "SessionCompletionItem", "SessionContext", + "SessionContextAttribution", "SessionContextHostType", "SessionContextInfo", "SessionEnrichMetadataResult", @@ -27842,6 +29124,16 @@ async def handle_git_hub_telemetry_event(params: dict) -> dict | None: "SessionRpc", "SessionSetCredentialsParams", "SessionSetCredentialsResult", + "SessionSettingsBuiltInToolAvailabilitySnapshot", + "SessionSettingsEvaluatePredicateRequest", + "SessionSettingsEvaluatePredicateResult", + "SessionSettingsJobSnapshot", + "SessionSettingsModelSnapshot", + "SessionSettingsOnlineEvaluationSnapshot", + "SessionSettingsPredicateName", + "SessionSettingsRepoSnapshot", + "SessionSettingsSnapshot", + "SessionSettingsValidationSnapshot", "SessionSizes", "SessionSource", "SessionTelemetryEngagement", @@ -27891,8 +29183,6 @@ async def handle_git_hub_telemetry_event(params: dict) -> dict | None: "SessionsOpenResumeLast", "SessionsOpenResumeLastKind", "SessionsOpenStatus", - "SessionsPollSpawnedSessionsEvent", - "SessionsPollSpawnedSessionsRequest", "SessionsPruneOldRequest", "SessionsRegisterExtensionToolsOnSessionOptions", "SessionsReleaseLockRequest", @@ -27936,6 +29226,7 @@ async def handle_git_hub_telemetry_event(params: dict) -> dict | None: "SlashCommandCompletedResultKind", "SlashCommandInfo", "SlashCommandInput", + "SlashCommandInputChoice", "SlashCommandInputCompletion", "SlashCommandInvocationResult", "SlashCommandInvocationResultKind", diff --git a/python/copilot/generated/session_events.py b/python/copilot/generated/session_events.py index 8dca9a5118..ed414d474a 100644 --- a/python/copilot/generated/session_events.py +++ b/python/copilot/generated/session_events.py @@ -157,6 +157,7 @@ class SessionEventType(Enum): ASSISTANT_INTENT = "assistant.intent" ASSISTANT_REASONING = "assistant.reasoning" ASSISTANT_REASONING_DELTA = "assistant.reasoning_delta" + ASSISTANT_TOOL_CALL_DELTA = "assistant.tool_call_delta" ASSISTANT_STREAMING_DELTA = "assistant.streaming_delta" ASSISTANT_MESSAGE = "assistant.message" ASSISTANT_MESSAGE_START = "assistant.message_start" @@ -294,16 +295,31 @@ class Data: def __init__(self, **kwargs: Any): self._values = {key: _compat_from_json_value(value) for key, value in kwargs.items()} + self._json_keys: dict[str, str] = {} + self._json_values: dict[str, Any] | None = None for key, value in self._values.items(): setattr(self, key, value) @staticmethod def from_dict(obj: Any) -> "Data": assert isinstance(obj, dict) - return Data(**{_compat_to_python_key(key): _compat_from_json_value(value) for key, value in obj.items()}) + data = Data() + data._values = {} + data._json_keys = {} + data._json_values = {} + for key, value in obj.items(): + py_key = _compat_to_python_key(key) + json_value = _compat_from_json_value(value) + data._values[py_key] = json_value + data._json_keys[py_key] = key + data._json_values[key] = json_value + setattr(data, py_key, data._values[py_key]) + return data def to_dict(self) -> dict: - return {_compat_to_json_key(key): _compat_to_json_value(value) for key, value in self._values.items() if value is not None} + if self._json_values is not None: + return {key: _compat_to_json_value(value) for key, value in self._json_values.items() if value is not None} + return {(self._json_keys.get(key) or _compat_to_json_key(key)): _compat_to_json_value(value) for key, value in self._values.items() if value is not None} # Deprecated: this type is deprecated and will be removed in a future version. @@ -423,7 +439,7 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class CanvasRegistryChangedCanvas: - "Schema for the `CanvasRegistryChangedCanvas` type." + "A single canvas declaration in `session.canvas.registry_changed`, including provider IDs, display metadata, input schema, and actions." canvas_id: str description: str display_name: str @@ -470,7 +486,7 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class CanvasRegistryChangedCanvasAction: - "Schema for the `CanvasRegistryChangedCanvasAction` type." + "A single action within a canvas declaration, with its name, optional description, and optional input schema." name: str description: str | None = None input_schema: Any = None @@ -782,10 +798,35 @@ def to_dict(self) -> dict: return result +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class PermissionAutoApproval: + "Auto-approval judge information attached to a permission request. Present (non-null) only when the session's allow-all mode is \"auto\"; its absence means auto mode was off and the judge did not evaluate the request. The `recommendation` conveys the judge's disposition for this request." + recommendation: AutoApprovalRecommendation + reason: str | None = None + + @staticmethod + def from_dict(obj: Any) -> "PermissionAutoApproval": + assert isinstance(obj, dict) + recommendation = parse_enum(AutoApprovalRecommendation, obj.get("recommendation")) + reason = from_union([from_none, from_str], obj.get("reason")) + return PermissionAutoApproval( + recommendation=recommendation, + reason=reason, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["recommendation"] = to_enum(AutoApprovalRecommendation, self.recommendation) + if self.reason is not None: + result["reason"] = from_union([from_none, from_str], self.reason) + return result + + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class SessionCanvasClosedData: - "Schema for the `CanvasClosedData` type." + "Payload of `session.canvas.closed` with the closed canvas instance ID, provider ID, and canvas ID." canvas_id: str extension_id: str instance_id: str @@ -813,7 +854,7 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class SessionCanvasOpenedData: - "Schema for the `CanvasOpenedData` type." + "Payload of `session.canvas.opened` with canvas instance and provider IDs plus optional title, status, URL, and input." canvas_id: str extension_id: str instance_id: str @@ -904,7 +945,7 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class SessionCanvasRegistryChangedData: - "Schema for the `CanvasRegistryChangedData` type." + "Payload of `session.canvas.registry_changed` listing the canvas declarations currently available." canvases: list[CanvasRegistryChangedCanvas] @staticmethod @@ -1311,22 +1352,60 @@ def to_dict(self) -> dict: return result +@dataclass +class AssistantToolCallDeltaData: + "Streaming tool-call input delta for incremental tool-call updates" + input_delta: str + tool_call_id: str + tool_name: str | None = None + tool_type: AssistantMessageToolRequestType | None = None + + @staticmethod + def from_dict(obj: Any) -> "AssistantToolCallDeltaData": + assert isinstance(obj, dict) + input_delta = from_str(obj.get("inputDelta")) + tool_call_id = from_str(obj.get("toolCallId")) + tool_name = from_union([from_none, from_str], obj.get("toolName")) + tool_type = from_union([from_none, lambda x: parse_enum(AssistantMessageToolRequestType, x)], obj.get("toolType")) + return AssistantToolCallDeltaData( + input_delta=input_delta, + tool_call_id=tool_call_id, + tool_name=tool_name, + tool_type=tool_type, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["inputDelta"] = from_str(self.input_delta) + result["toolCallId"] = from_str(self.tool_call_id) + if self.tool_name is not None: + result["toolName"] = from_union([from_none, from_str], self.tool_name) + if self.tool_type is not None: + result["toolType"] = from_union([from_none, lambda x: to_enum(AssistantMessageToolRequestType, x)], self.tool_type) + return result + + @dataclass class AssistantTurnEndData: "Turn completion metadata including the turn identifier" turn_id: str + model: str | None = None @staticmethod def from_dict(obj: Any) -> "AssistantTurnEndData": assert isinstance(obj, dict) turn_id = from_str(obj.get("turnId")) + model = from_union([from_none, from_str], obj.get("model")) return AssistantTurnEndData( turn_id=turn_id, + model=model, ) def to_dict(self) -> dict: result: dict = {} result["turnId"] = from_str(self.turn_id) + if self.model is not None: + result["model"] = from_union([from_none, from_str], self.model) return result @@ -1335,15 +1414,18 @@ class AssistantTurnStartData: "Turn initialization metadata including identifier and interaction tracking" turn_id: str interaction_id: str | None = None + model: str | None = None @staticmethod def from_dict(obj: Any) -> "AssistantTurnStartData": assert isinstance(obj, dict) turn_id = from_str(obj.get("turnId")) interaction_id = from_union([from_none, from_str], obj.get("interactionId")) + model = from_union([from_none, from_str], obj.get("model")) return AssistantTurnStartData( turn_id=turn_id, interaction_id=interaction_id, + model=model, ) def to_dict(self) -> dict: @@ -1351,6 +1433,8 @@ def to_dict(self) -> dict: result["turnId"] = from_str(self.turn_id) if self.interaction_id is not None: result["interactionId"] = from_union([from_none, from_str], self.interaction_id) + if self.model is not None: + result["model"] = from_union([from_none, from_str], self.model) return result @@ -1528,13 +1612,13 @@ def to_dict(self) -> dict: if self.service_request_id is not None: result["serviceRequestId"] = from_union([from_none, from_str], self.service_request_id) if self.time_to_first_token is not None: - result["timeToFirstTokenMs"] = from_union([from_none, to_timedelta_int], self.time_to_first_token) + result["timeToFirstTokenMs"] = from_union([from_none, to_timedelta], self.time_to_first_token) return result @dataclass class _AssistantUsageQuotaSnapshot: - "Schema for the `_AssistantUsageQuotaSnapshot` type." + "Internal per-quota snapshot for assistant usage, including entitlement, consumed requests, overage, reset date, and remaining quota." # Internal: this field is an internal SDK API and is not part of the public surface. _entitlement_requests: int # Internal: this field is an internal SDK API and is not part of the public surface. @@ -2464,7 +2548,7 @@ def to_dict(self) -> dict: @dataclass class CommandsChangedCommand: - "Schema for the `CommandsChangedCommand` type." + "A single slash command available in the session, as listed by the `commands.changed` event." name: str description: str | None = None @@ -2614,7 +2698,7 @@ def to_dict(self) -> dict: @dataclass class CustomAgentsUpdatedAgent: - "Schema for the `CustomAgentsUpdatedAgent` type." + "A single loaded custom agent in `session.custom_agents_updated`, with identity, source, tools, invocability, and model override." description: str display_name: str id: str @@ -2767,7 +2851,7 @@ def to_dict(self) -> dict: @dataclass class EmbeddedBlobResourceContents: - "Schema for the `EmbeddedBlobResourceContents` type." + "Embedded binary resource contents identified by a URI, with an optional MIME type and a base64-encoded blob." blob: str uri: str mime_type: str | None = None @@ -2795,7 +2879,7 @@ def to_dict(self) -> dict: @dataclass class EmbeddedTextResourceContents: - "Schema for the `EmbeddedTextResourceContents` type." + "Embedded text resource contents identified by a URI, with an optional MIME type and a text payload." text: str uri: str mime_type: str | None = None @@ -2897,7 +2981,7 @@ def to_dict(self) -> dict: @dataclass class ExtensionsLoadedExtension: - "Schema for the `ExtensionsLoadedExtension` type." + "A single extension discovered by `session.extensions_loaded`, including qualified ID, source, and current status." id: str name: str source: ExtensionsLoadedExtensionSource @@ -3262,7 +3346,7 @@ def to_dict(self) -> dict: @dataclass class McpAppToolCallCompleteToolMetaUI: - "Schema for the `McpAppToolCallCompleteToolMetaUI` type." + "MCP App tool `_meta.ui` resource URI and SEP-1865 visibility captured with an `mcp_app.tool_call_complete` result." resource_uri: str | None = None visibility: list[str] | None = None @@ -3474,7 +3558,7 @@ def to_dict(self) -> dict: @dataclass class McpServersLoadedServer: - "Schema for the `McpServersLoadedServer` type." + "A single MCP server status summary in `session.mcp_servers_loaded`, including name, status, source, transport, and plugin metadata." name: str status: McpServerStatus error: str | None = None @@ -3663,7 +3747,7 @@ def to_dict(self) -> dict: @dataclass class PermissionApproved: - "Schema for the `PermissionApproved` type." + "Permission response variant indicating the request was approved without persisting an approval rule." kind: ClassVar[str] = "approved" @staticmethod @@ -3680,7 +3764,7 @@ def to_dict(self) -> dict: @dataclass class PermissionApprovedForLocation: - "Schema for the `PermissionApprovedForLocation` type." + "Permission response variant that approves a request and persists the provided approval to a project location key." approval: UserToolSessionApproval kind: ClassVar[str] = "approved-for-location" location_key: str @@ -3705,7 +3789,7 @@ def to_dict(self) -> dict: @dataclass class PermissionApprovedForSession: - "Schema for the `PermissionApprovedForSession` type." + "Permission response variant that approves a request and remembers the provided approval for the rest of the session." approval: UserToolSessionApproval kind: ClassVar[str] = "approved-for-session" @@ -3726,7 +3810,7 @@ def to_dict(self) -> dict: @dataclass class PermissionCancelled: - "Schema for the `PermissionCancelled` type." + "Permission response variant indicating the request was cancelled before use, with an optional reason." kind: ClassVar[str] = "cancelled" reason: str | None = None @@ -3776,7 +3860,7 @@ def to_dict(self) -> dict: @dataclass class PermissionDeniedByContentExclusionPolicy: - "Schema for the `PermissionDeniedByContentExclusionPolicy` type." + "Permission response variant denying a path under content exclusion policy, with the path and message." kind: ClassVar[str] = "denied-by-content-exclusion-policy" message: str path: str @@ -3801,7 +3885,7 @@ def to_dict(self) -> dict: @dataclass class PermissionDeniedByPermissionRequestHook: - "Schema for the `PermissionDeniedByPermissionRequestHook` type." + "Permission response variant denied by a permission-request hook, with optional message and interrupt flag." kind: ClassVar[str] = "denied-by-permission-request-hook" interrupt: bool | None = None message: str | None = None @@ -3828,7 +3912,7 @@ def to_dict(self) -> dict: @dataclass class PermissionDeniedByRules: - "Schema for the `PermissionDeniedByRules` type." + "Permission response variant denied because matching approval rules explicitly blocked the request." kind: ClassVar[str] = "denied-by-rules" rules: list[PermissionRule] @@ -3849,7 +3933,7 @@ def to_dict(self) -> dict: @dataclass class PermissionDeniedInteractivelyByUser: - "Schema for the `PermissionDeniedInteractivelyByUser` type." + "Permission response variant denied in an interactive user prompt, with optional feedback and force-reject flag." kind: ClassVar[str] = "denied-interactively-by-user" feedback: str | None = None force_reject: bool | None = None @@ -3876,7 +3960,7 @@ def to_dict(self) -> dict: @dataclass class PermissionDeniedNoApprovalRuleAndCouldNotRequestFromUser: - "Schema for the `PermissionDeniedNoApprovalRuleAndCouldNotRequestFromUser` type." + "Permission response variant denied because no approval rule matched and user confirmation was unavailable." kind: ClassVar[str] = "denied-no-approval-rule-and-could-not-request-from-user" @staticmethod @@ -3899,6 +3983,8 @@ class PermissionPromptRequestCommands: full_command_text: str intention: str kind: ClassVar[str] = "commands" + # Experimental: this field is part of an experimental API and may change or be removed. + auto_approval: PermissionAutoApproval | None = None tool_call_id: str | None = None warning: str | None = None @@ -3909,6 +3995,7 @@ def from_dict(obj: Any) -> "PermissionPromptRequestCommands": command_identifiers = from_list(from_str, obj.get("commandIdentifiers")) full_command_text = from_str(obj.get("fullCommandText")) intention = from_str(obj.get("intention")) + auto_approval = from_union([from_none, PermissionAutoApproval.from_dict], obj.get("autoApproval")) tool_call_id = from_union([from_none, from_str], obj.get("toolCallId")) warning = from_union([from_none, from_str], obj.get("warning")) return PermissionPromptRequestCommands( @@ -3916,6 +4003,7 @@ def from_dict(obj: Any) -> "PermissionPromptRequestCommands": command_identifiers=command_identifiers, full_command_text=full_command_text, intention=intention, + auto_approval=auto_approval, tool_call_id=tool_call_id, warning=warning, ) @@ -3927,6 +4015,8 @@ def to_dict(self) -> dict: result["fullCommandText"] = from_str(self.full_command_text) result["intention"] = from_str(self.intention) result["kind"] = self.kind + if self.auto_approval is not None: + result["autoApproval"] = from_union([from_none, lambda x: to_class(PermissionAutoApproval, x)], self.auto_approval) if self.tool_call_id is not None: result["toolCallId"] = from_union([from_none, from_str], self.tool_call_id) if self.warning is not None: @@ -3941,6 +4031,8 @@ class PermissionPromptRequestCustomTool: tool_description: str tool_name: str args: Any = None + # Experimental: this field is part of an experimental API and may change or be removed. + auto_approval: PermissionAutoApproval | None = None tool_call_id: str | None = None @staticmethod @@ -3949,11 +4041,13 @@ def from_dict(obj: Any) -> "PermissionPromptRequestCustomTool": tool_description = from_str(obj.get("toolDescription")) tool_name = from_str(obj.get("toolName")) args = obj.get("args") + auto_approval = from_union([from_none, PermissionAutoApproval.from_dict], obj.get("autoApproval")) tool_call_id = from_union([from_none, from_str], obj.get("toolCallId")) return PermissionPromptRequestCustomTool( tool_description=tool_description, tool_name=tool_name, args=args, + auto_approval=auto_approval, tool_call_id=tool_call_id, ) @@ -3964,6 +4058,8 @@ def to_dict(self) -> dict: result["toolName"] = from_str(self.tool_name) if self.args is not None: result["args"] = self.args + if self.auto_approval is not None: + result["autoApproval"] = from_union([from_none, lambda x: to_class(PermissionAutoApproval, x)], self.auto_approval) if self.tool_call_id is not None: result["toolCallId"] = from_union([from_none, from_str], self.tool_call_id) return result @@ -3974,6 +4070,8 @@ class PermissionPromptRequestExtensionManagement: "Extension management permission prompt" kind: ClassVar[str] = "extension-management" operation: str + # Experimental: this field is part of an experimental API and may change or be removed. + auto_approval: PermissionAutoApproval | None = None extension_name: str | None = None tool_call_id: str | None = None @@ -3981,10 +4079,12 @@ class PermissionPromptRequestExtensionManagement: def from_dict(obj: Any) -> "PermissionPromptRequestExtensionManagement": assert isinstance(obj, dict) operation = from_str(obj.get("operation")) + auto_approval = from_union([from_none, PermissionAutoApproval.from_dict], obj.get("autoApproval")) extension_name = from_union([from_none, from_str], obj.get("extensionName")) tool_call_id = from_union([from_none, from_str], obj.get("toolCallId")) return PermissionPromptRequestExtensionManagement( operation=operation, + auto_approval=auto_approval, extension_name=extension_name, tool_call_id=tool_call_id, ) @@ -3993,6 +4093,8 @@ def to_dict(self) -> dict: result: dict = {} result["kind"] = self.kind result["operation"] = from_str(self.operation) + if self.auto_approval is not None: + result["autoApproval"] = from_union([from_none, lambda x: to_class(PermissionAutoApproval, x)], self.auto_approval) if self.extension_name is not None: result["extensionName"] = from_union([from_none, from_str], self.extension_name) if self.tool_call_id is not None: @@ -4006,6 +4108,8 @@ class PermissionPromptRequestExtensionPermissionAccess: capabilities: list[str] extension_name: str kind: ClassVar[str] = "extension-permission-access" + # Experimental: this field is part of an experimental API and may change or be removed. + auto_approval: PermissionAutoApproval | None = None tool_call_id: str | None = None @staticmethod @@ -4013,10 +4117,12 @@ def from_dict(obj: Any) -> "PermissionPromptRequestExtensionPermissionAccess": assert isinstance(obj, dict) capabilities = from_list(from_str, obj.get("capabilities")) extension_name = from_str(obj.get("extensionName")) + auto_approval = from_union([from_none, PermissionAutoApproval.from_dict], obj.get("autoApproval")) tool_call_id = from_union([from_none, from_str], obj.get("toolCallId")) return PermissionPromptRequestExtensionPermissionAccess( capabilities=capabilities, extension_name=extension_name, + auto_approval=auto_approval, tool_call_id=tool_call_id, ) @@ -4025,6 +4131,8 @@ def to_dict(self) -> dict: result["capabilities"] = from_list(from_str, self.capabilities) result["extensionName"] = from_str(self.extension_name) result["kind"] = self.kind + if self.auto_approval is not None: + result["autoApproval"] = from_union([from_none, lambda x: to_class(PermissionAutoApproval, x)], self.auto_approval) if self.tool_call_id is not None: result["toolCallId"] = from_union([from_none, from_str], self.tool_call_id) return result @@ -4035,6 +4143,8 @@ class PermissionPromptRequestHook: "Hook confirmation permission prompt" kind: ClassVar[str] = "hook" tool_name: str + # Experimental: this field is part of an experimental API and may change or be removed. + auto_approval: PermissionAutoApproval | None = None hook_message: str | None = None tool_args: Any = None tool_call_id: str | None = None @@ -4043,11 +4153,13 @@ class PermissionPromptRequestHook: def from_dict(obj: Any) -> "PermissionPromptRequestHook": assert isinstance(obj, dict) tool_name = from_str(obj.get("toolName")) + auto_approval = from_union([from_none, PermissionAutoApproval.from_dict], obj.get("autoApproval")) hook_message = from_union([from_none, from_str], obj.get("hookMessage")) tool_args = obj.get("toolArgs") tool_call_id = from_union([from_none, from_str], obj.get("toolCallId")) return PermissionPromptRequestHook( tool_name=tool_name, + auto_approval=auto_approval, hook_message=hook_message, tool_args=tool_args, tool_call_id=tool_call_id, @@ -4057,6 +4169,8 @@ def to_dict(self) -> dict: result: dict = {} result["kind"] = self.kind result["toolName"] = from_str(self.tool_name) + if self.auto_approval is not None: + result["autoApproval"] = from_union([from_none, lambda x: to_class(PermissionAutoApproval, x)], self.auto_approval) if self.hook_message is not None: result["hookMessage"] = from_union([from_none, from_str], self.hook_message) if self.tool_args is not None: @@ -4074,6 +4188,8 @@ class PermissionPromptRequestMcp: tool_name: str tool_title: str args: Any = None + # Experimental: this field is part of an experimental API and may change or be removed. + auto_approval: PermissionAutoApproval | None = None tool_call_id: str | None = None @staticmethod @@ -4083,12 +4199,14 @@ def from_dict(obj: Any) -> "PermissionPromptRequestMcp": tool_name = from_str(obj.get("toolName")) tool_title = from_str(obj.get("toolTitle")) args = obj.get("args") + auto_approval = from_union([from_none, PermissionAutoApproval.from_dict], obj.get("autoApproval")) tool_call_id = from_union([from_none, from_str], obj.get("toolCallId")) return PermissionPromptRequestMcp( server_name=server_name, tool_name=tool_name, tool_title=tool_title, args=args, + auto_approval=auto_approval, tool_call_id=tool_call_id, ) @@ -4100,6 +4218,8 @@ def to_dict(self) -> dict: result["toolTitle"] = from_str(self.tool_title) if self.args is not None: result["args"] = self.args + if self.auto_approval is not None: + result["autoApproval"] = from_union([from_none, lambda x: to_class(PermissionAutoApproval, x)], self.auto_approval) if self.tool_call_id is not None: result["toolCallId"] = from_union([from_none, from_str], self.tool_call_id) return result @@ -4111,6 +4231,8 @@ class PermissionPromptRequestMemory: fact: str kind: ClassVar[str] = "memory" action: PermissionRequestMemoryAction | None = None + # Experimental: this field is part of an experimental API and may change or be removed. + auto_approval: PermissionAutoApproval | None = None citations: str | None = None direction: PermissionRequestMemoryDirection | None = None reason: str | None = None @@ -4122,6 +4244,7 @@ def from_dict(obj: Any) -> "PermissionPromptRequestMemory": assert isinstance(obj, dict) fact = from_str(obj.get("fact")) action = from_union([from_none, lambda x: parse_enum(PermissionRequestMemoryAction, x)], obj.get("action")) + auto_approval = from_union([from_none, PermissionAutoApproval.from_dict], obj.get("autoApproval")) citations = from_union([from_none, from_str], obj.get("citations")) direction = from_union([from_none, lambda x: parse_enum(PermissionRequestMemoryDirection, x)], obj.get("direction")) reason = from_union([from_none, from_str], obj.get("reason")) @@ -4130,6 +4253,7 @@ def from_dict(obj: Any) -> "PermissionPromptRequestMemory": return PermissionPromptRequestMemory( fact=fact, action=action, + auto_approval=auto_approval, citations=citations, direction=direction, reason=reason, @@ -4143,6 +4267,8 @@ def to_dict(self) -> dict: result["kind"] = self.kind if self.action is not None: result["action"] = from_union([from_none, lambda x: to_enum(PermissionRequestMemoryAction, x)], self.action) + if self.auto_approval is not None: + result["autoApproval"] = from_union([from_none, lambda x: to_class(PermissionAutoApproval, x)], self.auto_approval) if self.citations is not None: result["citations"] = from_union([from_none, from_str], self.citations) if self.direction is not None: @@ -4162,6 +4288,8 @@ class PermissionPromptRequestPath: access_kind: PermissionPromptRequestPathAccessKind kind: ClassVar[str] = "path" paths: list[str] + # Experimental: this field is part of an experimental API and may change or be removed. + auto_approval: PermissionAutoApproval | None = None tool_call_id: str | None = None @staticmethod @@ -4169,10 +4297,12 @@ def from_dict(obj: Any) -> "PermissionPromptRequestPath": assert isinstance(obj, dict) access_kind = parse_enum(PermissionPromptRequestPathAccessKind, obj.get("accessKind")) paths = from_list(from_str, obj.get("paths")) + auto_approval = from_union([from_none, PermissionAutoApproval.from_dict], obj.get("autoApproval")) tool_call_id = from_union([from_none, from_str], obj.get("toolCallId")) return PermissionPromptRequestPath( access_kind=access_kind, paths=paths, + auto_approval=auto_approval, tool_call_id=tool_call_id, ) @@ -4181,6 +4311,8 @@ def to_dict(self) -> dict: result["accessKind"] = to_enum(PermissionPromptRequestPathAccessKind, self.access_kind) result["kind"] = self.kind result["paths"] = from_list(from_str, self.paths) + if self.auto_approval is not None: + result["autoApproval"] = from_union([from_none, lambda x: to_class(PermissionAutoApproval, x)], self.auto_approval) if self.tool_call_id is not None: result["toolCallId"] = from_union([from_none, from_str], self.tool_call_id) return result @@ -4192,6 +4324,8 @@ class PermissionPromptRequestRead: intention: str kind: ClassVar[str] = "read" path: str + # Experimental: this field is part of an experimental API and may change or be removed. + auto_approval: PermissionAutoApproval | None = None tool_call_id: str | None = None @staticmethod @@ -4199,10 +4333,12 @@ def from_dict(obj: Any) -> "PermissionPromptRequestRead": assert isinstance(obj, dict) intention = from_str(obj.get("intention")) path = from_str(obj.get("path")) + auto_approval = from_union([from_none, PermissionAutoApproval.from_dict], obj.get("autoApproval")) tool_call_id = from_union([from_none, from_str], obj.get("toolCallId")) return PermissionPromptRequestRead( intention=intention, path=path, + auto_approval=auto_approval, tool_call_id=tool_call_id, ) @@ -4211,6 +4347,8 @@ def to_dict(self) -> dict: result["intention"] = from_str(self.intention) result["kind"] = self.kind result["path"] = from_str(self.path) + if self.auto_approval is not None: + result["autoApproval"] = from_union([from_none, lambda x: to_class(PermissionAutoApproval, x)], self.auto_approval) if self.tool_call_id is not None: result["toolCallId"] = from_union([from_none, from_str], self.tool_call_id) return result @@ -4222,6 +4360,10 @@ class PermissionPromptRequestUrl: intention: str kind: ClassVar[str] = "url" url: str + # Experimental: this field is part of an experimental API and may change or be removed. + auto_approval: PermissionAutoApproval | None = None + request_sandbox_bypass: bool | None = None + request_sandbox_bypass_reason: str | None = None tool_call_id: str | None = None @staticmethod @@ -4229,10 +4371,16 @@ def from_dict(obj: Any) -> "PermissionPromptRequestUrl": assert isinstance(obj, dict) intention = from_str(obj.get("intention")) url = from_str(obj.get("url")) + auto_approval = from_union([from_none, PermissionAutoApproval.from_dict], obj.get("autoApproval")) + request_sandbox_bypass = from_union([from_none, from_bool], obj.get("requestSandboxBypass")) + request_sandbox_bypass_reason = from_union([from_none, from_str], obj.get("requestSandboxBypassReason")) tool_call_id = from_union([from_none, from_str], obj.get("toolCallId")) return PermissionPromptRequestUrl( intention=intention, url=url, + auto_approval=auto_approval, + request_sandbox_bypass=request_sandbox_bypass, + request_sandbox_bypass_reason=request_sandbox_bypass_reason, tool_call_id=tool_call_id, ) @@ -4241,6 +4389,12 @@ def to_dict(self) -> dict: result["intention"] = from_str(self.intention) result["kind"] = self.kind result["url"] = from_str(self.url) + if self.auto_approval is not None: + result["autoApproval"] = from_union([from_none, lambda x: to_class(PermissionAutoApproval, x)], self.auto_approval) + if self.request_sandbox_bypass is not None: + result["requestSandboxBypass"] = from_union([from_none, from_bool], self.request_sandbox_bypass) + if self.request_sandbox_bypass_reason is not None: + result["requestSandboxBypassReason"] = from_union([from_none, from_str], self.request_sandbox_bypass_reason) if self.tool_call_id is not None: result["toolCallId"] = from_union([from_none, from_str], self.tool_call_id) return result @@ -4254,6 +4408,8 @@ class PermissionPromptRequestWrite: file_name: str intention: str kind: ClassVar[str] = "write" + # Experimental: this field is part of an experimental API and may change or be removed. + auto_approval: PermissionAutoApproval | None = None new_file_contents: str | None = None tool_call_id: str | None = None @@ -4264,6 +4420,7 @@ def from_dict(obj: Any) -> "PermissionPromptRequestWrite": diff = from_str(obj.get("diff")) file_name = from_str(obj.get("fileName")) intention = from_str(obj.get("intention")) + auto_approval = from_union([from_none, PermissionAutoApproval.from_dict], obj.get("autoApproval")) new_file_contents = from_union([from_none, from_str], obj.get("newFileContents")) tool_call_id = from_union([from_none, from_str], obj.get("toolCallId")) return PermissionPromptRequestWrite( @@ -4271,6 +4428,7 @@ def from_dict(obj: Any) -> "PermissionPromptRequestWrite": diff=diff, file_name=file_name, intention=intention, + auto_approval=auto_approval, new_file_contents=new_file_contents, tool_call_id=tool_call_id, ) @@ -4282,6 +4440,8 @@ def to_dict(self) -> dict: result["fileName"] = from_str(self.file_name) result["intention"] = from_str(self.intention) result["kind"] = self.kind + if self.auto_approval is not None: + result["autoApproval"] = from_union([from_none, lambda x: to_class(PermissionAutoApproval, x)], self.auto_approval) if self.new_file_contents is not None: result["newFileContents"] = from_union([from_none, from_str], self.new_file_contents) if self.tool_call_id is not None: @@ -4622,7 +4782,7 @@ def to_dict(self) -> dict: @dataclass class PermissionRequestShellCommand: - "Schema for the `PermissionRequestShellCommand` type." + "A parsed command identifier in a shell permission request, including whether it is read-only." identifier: str read_only: bool @@ -4645,7 +4805,7 @@ def to_dict(self) -> dict: @dataclass class PermissionRequestShellPossibleUrl: - "Schema for the `PermissionRequestShellPossibleUrl` type." + "A URL that may be accessed by a command in a shell permission request." url: str @staticmethod @@ -4668,6 +4828,8 @@ class PermissionRequestUrl: intention: str kind: ClassVar[str] = "url" url: str + request_sandbox_bypass: bool | None = None + request_sandbox_bypass_reason: str | None = None tool_call_id: str | None = None @staticmethod @@ -4675,10 +4837,14 @@ def from_dict(obj: Any) -> "PermissionRequestUrl": assert isinstance(obj, dict) intention = from_str(obj.get("intention")) url = from_str(obj.get("url")) + request_sandbox_bypass = from_union([from_none, from_bool], obj.get("requestSandboxBypass")) + request_sandbox_bypass_reason = from_union([from_none, from_str], obj.get("requestSandboxBypassReason")) tool_call_id = from_union([from_none, from_str], obj.get("toolCallId")) return PermissionRequestUrl( intention=intention, url=url, + request_sandbox_bypass=request_sandbox_bypass, + request_sandbox_bypass_reason=request_sandbox_bypass_reason, tool_call_id=tool_call_id, ) @@ -4687,6 +4853,10 @@ def to_dict(self) -> dict: result["intention"] = from_str(self.intention) result["kind"] = self.kind result["url"] = from_str(self.url) + if self.request_sandbox_bypass is not None: + result["requestSandboxBypass"] = from_union([from_none, from_bool], self.request_sandbox_bypass) + if self.request_sandbox_bypass_reason is not None: + result["requestSandboxBypassReason"] = from_union([from_none, from_str], self.request_sandbox_bypass_reason) if self.tool_call_id is not None: result["toolCallId"] = from_union([from_none, from_str], self.tool_call_id) return result @@ -4701,6 +4871,8 @@ class PermissionRequestWrite: intention: str kind: ClassVar[str] = "write" new_file_contents: str | None = None + request_sandbox_bypass: bool | None = None + request_sandbox_bypass_reason: str | None = None tool_call_id: str | None = None @staticmethod @@ -4711,6 +4883,8 @@ def from_dict(obj: Any) -> "PermissionRequestWrite": file_name = from_str(obj.get("fileName")) intention = from_str(obj.get("intention")) new_file_contents = from_union([from_none, from_str], obj.get("newFileContents")) + request_sandbox_bypass = from_union([from_none, from_bool], obj.get("requestSandboxBypass")) + request_sandbox_bypass_reason = from_union([from_none, from_str], obj.get("requestSandboxBypassReason")) tool_call_id = from_union([from_none, from_str], obj.get("toolCallId")) return PermissionRequestWrite( can_offer_session_approval=can_offer_session_approval, @@ -4718,6 +4892,8 @@ def from_dict(obj: Any) -> "PermissionRequestWrite": file_name=file_name, intention=intention, new_file_contents=new_file_contents, + request_sandbox_bypass=request_sandbox_bypass, + request_sandbox_bypass_reason=request_sandbox_bypass_reason, tool_call_id=tool_call_id, ) @@ -4730,6 +4906,10 @@ def to_dict(self) -> dict: result["kind"] = self.kind if self.new_file_contents is not None: result["newFileContents"] = from_union([from_none, from_str], self.new_file_contents) + if self.request_sandbox_bypass is not None: + result["requestSandboxBypass"] = from_union([from_none, from_bool], self.request_sandbox_bypass) + if self.request_sandbox_bypass_reason is not None: + result["requestSandboxBypassReason"] = from_union([from_none, from_str], self.request_sandbox_bypass_reason) if self.tool_call_id is not None: result["toolCallId"] = from_union([from_none, from_str], self.tool_call_id) return result @@ -4770,7 +4950,7 @@ def to_dict(self) -> dict: @dataclass class PermissionRule: - "Schema for the `PermissionRule` type." + "A permission approval or denial rule matched against a tool request, identified by a rule kind with an optional argument value." argument: str | None kind: str @@ -4905,7 +5085,7 @@ def to_dict(self) -> dict: @dataclass class SessionBackgroundTasksChangedData: - "Schema for the `BackgroundTasksChangedData` type." + "Empty payload for `session.background_tasks_changed`, indicating background task state changed." @staticmethod def from_dict(obj: Any) -> "SessionBackgroundTasksChangedData": assert isinstance(obj, dict) @@ -5068,6 +5248,7 @@ def to_dict(self) -> dict: class SessionCompactionStartData: "Context window breakdown at the start of LLM-powered conversation compaction" conversation_tokens: int | None = None + model: str | None = None system_tokens: int | None = None tool_definitions_tokens: int | None = None @@ -5075,10 +5256,12 @@ class SessionCompactionStartData: def from_dict(obj: Any) -> "SessionCompactionStartData": assert isinstance(obj, dict) conversation_tokens = from_union([from_none, from_int], obj.get("conversationTokens")) + model = from_union([from_none, from_str], obj.get("model")) system_tokens = from_union([from_none, from_int], obj.get("systemTokens")) tool_definitions_tokens = from_union([from_none, from_int], obj.get("toolDefinitionsTokens")) return SessionCompactionStartData( conversation_tokens=conversation_tokens, + model=model, system_tokens=system_tokens, tool_definitions_tokens=tool_definitions_tokens, ) @@ -5087,6 +5270,8 @@ def to_dict(self) -> dict: result: dict = {} if self.conversation_tokens is not None: result["conversationTokens"] = from_union([from_none, to_int], self.conversation_tokens) + if self.model is not None: + result["model"] = from_union([from_none, from_str], self.model) if self.system_tokens is not None: result["systemTokens"] = from_union([from_none, to_int], self.system_tokens) if self.tool_definitions_tokens is not None: @@ -5150,7 +5335,7 @@ def to_dict(self) -> dict: @dataclass class SessionCustomAgentsUpdatedData: - "Schema for the `CustomAgentsUpdatedData` type." + "Payload of `session.custom_agents_updated` with loaded custom agents plus non-fatal warnings and fatal errors." agents: list[CustomAgentsUpdatedAgent] errors: list[str] warnings: list[str] @@ -5272,7 +5457,7 @@ def to_dict(self) -> dict: @dataclass class SessionExtensionsAttachmentsPushedData: - "Schema for the `ExtensionsAttachmentsPushedData` type." + "Payload of `session.extensions.attachments_pushed` with extension-contributed attachments for the next send." attachments: list[Attachment] @staticmethod @@ -5291,7 +5476,7 @@ def to_dict(self) -> dict: @dataclass class SessionExtensionsLoadedData: - "Schema for the `ExtensionsLoadedData` type." + "Payload of `session.extensions_loaded` listing discovered extensions and their statuses." extensions: list[ExtensionsLoadedExtension] @staticmethod @@ -5510,7 +5695,7 @@ def to_dict(self) -> dict: @dataclass class SessionMcpServerStatusChangedData: - "Schema for the `McpServerStatusChangedData` type." + "Payload of `session.mcp_server_status_changed` for one MCP server's status and optional failure error." server_name: str status: McpServerStatus error: str | None = None @@ -5538,7 +5723,7 @@ def to_dict(self) -> dict: @dataclass class SessionMcpServersLoadedData: - "Schema for the `McpServersLoadedData` type." + "Payload of `session.mcp_servers_loaded` listing MCP server status summaries." servers: list[McpServersLoadedServer] @staticmethod @@ -5587,8 +5772,10 @@ class SessionModelChangeData: previous_model: str | None = None previous_reasoning_effort: str | None = None previous_reasoning_summary: ReasoningSummary | None = None + previous_verbosity: Verbosity | None = None reasoning_effort: str | None = None reasoning_summary: ReasoningSummary | None = None + verbosity: Verbosity | None = None @staticmethod def from_dict(obj: Any) -> "SessionModelChangeData": @@ -5599,8 +5786,10 @@ def from_dict(obj: Any) -> "SessionModelChangeData": previous_model = from_union([from_none, from_str], obj.get("previousModel")) previous_reasoning_effort = from_union([from_none, from_str], obj.get("previousReasoningEffort")) previous_reasoning_summary = from_union([from_none, lambda x: parse_enum(ReasoningSummary, x)], obj.get("previousReasoningSummary")) + previous_verbosity = from_union([from_none, lambda x: parse_enum(Verbosity, x)], obj.get("previousVerbosity")) reasoning_effort = from_union([from_none, from_str], obj.get("reasoningEffort")) reasoning_summary = from_union([from_none, lambda x: parse_enum(ReasoningSummary, x)], obj.get("reasoningSummary")) + verbosity = from_union([from_none, lambda x: parse_enum(Verbosity, x)], obj.get("verbosity")) return SessionModelChangeData( new_model=new_model, cause=cause, @@ -5608,8 +5797,10 @@ def from_dict(obj: Any) -> "SessionModelChangeData": previous_model=previous_model, previous_reasoning_effort=previous_reasoning_effort, previous_reasoning_summary=previous_reasoning_summary, + previous_verbosity=previous_verbosity, reasoning_effort=reasoning_effort, reasoning_summary=reasoning_summary, + verbosity=verbosity, ) def to_dict(self) -> dict: @@ -5625,33 +5816,49 @@ def to_dict(self) -> dict: result["previousReasoningEffort"] = from_union([from_none, from_str], self.previous_reasoning_effort) if self.previous_reasoning_summary is not None: result["previousReasoningSummary"] = from_union([from_none, lambda x: to_enum(ReasoningSummary, x)], self.previous_reasoning_summary) + if self.previous_verbosity is not None: + result["previousVerbosity"] = from_union([from_none, lambda x: to_enum(Verbosity, x)], self.previous_verbosity) if self.reasoning_effort is not None: result["reasoningEffort"] = from_union([from_none, from_str], self.reasoning_effort) if self.reasoning_summary is not None: result["reasoningSummary"] = from_union([from_none, lambda x: to_enum(ReasoningSummary, x)], self.reasoning_summary) + if self.verbosity is not None: + result["verbosity"] = from_union([from_none, lambda x: to_enum(Verbosity, x)], self.verbosity) return result @dataclass class SessionPermissionsChangedData: - "Permissions change details carrying the aggregate allow-all boolean transition." + "Permissions change details carrying the aggregate allow-all transition." allow_all_permissions: bool previous_allow_all_permissions: bool + # Experimental: this field is part of an experimental API and may change or be removed. + allow_all_permission_mode: PermissionAllowAllMode | None = None + # Experimental: this field is part of an experimental API and may change or be removed. + previous_allow_all_permission_mode: PermissionAllowAllMode | None = None @staticmethod def from_dict(obj: Any) -> "SessionPermissionsChangedData": assert isinstance(obj, dict) allow_all_permissions = from_bool(obj.get("allowAllPermissions")) previous_allow_all_permissions = from_bool(obj.get("previousAllowAllPermissions")) + allow_all_permission_mode = from_union([from_none, lambda x: parse_enum(PermissionAllowAllMode, x)], obj.get("allowAllPermissionMode")) + previous_allow_all_permission_mode = from_union([from_none, lambda x: parse_enum(PermissionAllowAllMode, x)], obj.get("previousAllowAllPermissionMode")) return SessionPermissionsChangedData( allow_all_permissions=allow_all_permissions, previous_allow_all_permissions=previous_allow_all_permissions, + allow_all_permission_mode=allow_all_permission_mode, + previous_allow_all_permission_mode=previous_allow_all_permission_mode, ) def to_dict(self) -> dict: result: dict = {} result["allowAllPermissions"] = from_bool(self.allow_all_permissions) result["previousAllowAllPermissions"] = from_bool(self.previous_allow_all_permissions) + if self.allow_all_permission_mode is not None: + result["allowAllPermissionMode"] = from_union([from_none, lambda x: to_enum(PermissionAllowAllMode, x)], self.allow_all_permission_mode) + if self.previous_allow_all_permission_mode is not None: + result["previousAllowAllPermissionMode"] = from_union([from_none, lambda x: to_enum(PermissionAllowAllMode, x)], self.previous_allow_all_permission_mode) return result @@ -5709,6 +5916,7 @@ class SessionResumeData: selected_model: str | None = None session_limits: SessionLimitsConfig | None = None session_was_active: bool | None = None + verbosity: Verbosity | None = None @staticmethod def from_dict(obj: Any) -> "SessionResumeData": @@ -5726,6 +5934,7 @@ def from_dict(obj: Any) -> "SessionResumeData": selected_model = from_union([from_none, from_str], obj.get("selectedModel")) session_limits = from_union([from_none, SessionLimitsConfig.from_dict], obj.get("sessionLimits")) session_was_active = from_union([from_none, from_bool], obj.get("sessionWasActive")) + verbosity = from_union([from_none, lambda x: parse_enum(Verbosity, x)], obj.get("verbosity")) return SessionResumeData( event_count=event_count, resume_time=resume_time, @@ -5740,6 +5949,7 @@ def from_dict(obj: Any) -> "SessionResumeData": selected_model=selected_model, session_limits=session_limits, session_was_active=session_was_active, + verbosity=verbosity, ) def to_dict(self) -> dict: @@ -5768,6 +5978,8 @@ def to_dict(self) -> dict: result["sessionLimits"] = from_union([from_none, lambda x: to_class(SessionLimitsConfig, x)], self.session_limits) if self.session_was_active is not None: result["sessionWasActive"] = from_union([from_none, from_bool], self.session_was_active) + if self.verbosity is not None: + result["verbosity"] = from_union([from_none, lambda x: to_enum(Verbosity, x)], self.verbosity) return result @@ -5979,7 +6191,7 @@ def to_dict(self) -> dict: @dataclass class SessionSkillsLoadedData: - "Schema for the `SkillsLoadedData` type." + "Payload of `session.skills_loaded` listing resolved skill metadata." skills: list[SkillsLoadedSkill] @staticmethod @@ -6036,6 +6248,7 @@ class SessionStartData: remote_steerable: bool | None = None selected_model: str | None = None session_limits: SessionLimitsConfig | None = None + verbosity: Verbosity | None = None @staticmethod def from_dict(obj: Any) -> "SessionStartData": @@ -6054,6 +6267,7 @@ def from_dict(obj: Any) -> "SessionStartData": remote_steerable = from_union([from_none, from_bool], obj.get("remoteSteerable")) selected_model = from_union([from_none, from_str], obj.get("selectedModel")) session_limits = from_union([from_none, SessionLimitsConfig.from_dict], obj.get("sessionLimits")) + verbosity = from_union([from_none, lambda x: parse_enum(Verbosity, x)], obj.get("verbosity")) return SessionStartData( copilot_version=copilot_version, producer=producer, @@ -6069,6 +6283,7 @@ def from_dict(obj: Any) -> "SessionStartData": remote_steerable=remote_steerable, selected_model=selected_model, session_limits=session_limits, + verbosity=verbosity, ) def to_dict(self) -> dict: @@ -6096,6 +6311,8 @@ def to_dict(self) -> dict: result["selectedModel"] = from_union([from_none, from_str], self.selected_model) if self.session_limits is not None: result["sessionLimits"] = from_union([from_none, lambda x: to_class(SessionLimitsConfig, x)], self.session_limits) + if self.verbosity is not None: + result["verbosity"] = from_union([from_none, lambda x: to_enum(Verbosity, x)], self.verbosity) return result @@ -6157,7 +6374,7 @@ def to_dict(self) -> dict: @dataclass class SessionToolsUpdatedData: - "Schema for the `ToolsUpdatedData` type." + "Payload of `session.tools_updated` identifying the model whose resolved tools were updated." model: str @staticmethod @@ -6373,7 +6590,7 @@ def to_dict(self) -> dict: @dataclass class ShutdownModelMetric: - "Schema for the `ShutdownModelMetric` type." + "Per-model shutdown metrics with request counts, token usage, nano-AI units, and token details." requests: ShutdownModelMetricRequests usage: ShutdownModelMetricUsage token_details: dict[str, ShutdownModelMetricTokenDetail] | None = None @@ -6434,7 +6651,7 @@ def to_dict(self) -> dict: @dataclass class ShutdownModelMetricTokenDetail: - "Schema for the `ShutdownModelMetricTokenDetail` type." + "A token-type entry in a shutdown model metric, storing the accumulated token count." token_count: int @staticmethod @@ -6489,7 +6706,7 @@ def to_dict(self) -> dict: @dataclass class ShutdownTokenDetail: - "Schema for the `ShutdownTokenDetail` type." + "A session-wide shutdown token-type entry storing the accumulated token count." token_count: int @staticmethod @@ -6514,6 +6731,7 @@ class SkillInvokedData: path: str allowed_tools: list[str] | None = None description: str | None = None + model: str | None = None plugin_name: str | None = None plugin_version: str | None = None source: str | None = None @@ -6527,6 +6745,7 @@ def from_dict(obj: Any) -> "SkillInvokedData": path = from_str(obj.get("path")) allowed_tools = from_union([from_none, lambda x: from_list(from_str, x)], obj.get("allowedTools")) description = from_union([from_none, from_str], obj.get("description")) + model = from_union([from_none, from_str], obj.get("model")) plugin_name = from_union([from_none, from_str], obj.get("pluginName")) plugin_version = from_union([from_none, from_str], obj.get("pluginVersion")) source = from_union([from_none, from_str], obj.get("source")) @@ -6537,6 +6756,7 @@ def from_dict(obj: Any) -> "SkillInvokedData": path=path, allowed_tools=allowed_tools, description=description, + model=model, plugin_name=plugin_name, plugin_version=plugin_version, source=source, @@ -6552,6 +6772,8 @@ def to_dict(self) -> dict: result["allowedTools"] = from_union([from_none, lambda x: from_list(from_str, x)], self.allowed_tools) if self.description is not None: result["description"] = from_union([from_none, from_str], self.description) + if self.model is not None: + result["model"] = from_union([from_none, from_str], self.model) if self.plugin_name is not None: result["pluginName"] = from_union([from_none, from_str], self.plugin_name) if self.plugin_version is not None: @@ -6565,7 +6787,7 @@ def to_dict(self) -> dict: @dataclass class SkillsLoadedSkill: - "Schema for the `SkillsLoadedSkill` type." + "A single resolved skill in `session.skills_loaded`, including source, invocability, enabled state, path, and argument hint." description: str enabled: bool name: str @@ -6841,7 +7063,7 @@ def to_dict(self) -> dict: @dataclass class SystemNotificationAgentCompleted: - "Schema for the `SystemNotificationAgentCompleted` type." + "System notification metadata for a background agent that completed or failed, including agent ID, type, status, description, and prompt." agent_id: str agent_type: str status: SystemNotificationAgentCompletedStatus @@ -6880,7 +7102,7 @@ def to_dict(self) -> dict: @dataclass class SystemNotificationAgentIdle: - "Schema for the `SystemNotificationAgentIdle` type." + "System notification metadata for a background agent that became idle, including agent ID, type, and description." agent_id: str agent_type: str type: ClassVar[str] = "agent_idle" @@ -6933,7 +7155,7 @@ def to_dict(self) -> dict: @dataclass class SystemNotificationInstructionDiscovered: - "Schema for the `SystemNotificationInstructionDiscovered` type." + "System notification metadata for an instruction file discovered during tool access, including source, trigger file, and tool." source_path: str trigger_file: str trigger_tool: str @@ -6967,7 +7189,7 @@ def to_dict(self) -> dict: @dataclass class SystemNotificationNewInboxMessage: - "Schema for the `SystemNotificationNewInboxMessage` type." + "System notification metadata for a new inbox message, including entry ID, sender details, and summary." entry_id: str sender_name: str sender_type: str @@ -7000,7 +7222,7 @@ def to_dict(self) -> dict: @dataclass class SystemNotificationShellCompleted: - "Schema for the `SystemNotificationShellCompleted` type." + "System notification metadata for a shell session that completed, including shell ID, optional exit code, and description." shell_id: str type: ClassVar[str] = "shell_completed" description: str | None = None @@ -7031,7 +7253,7 @@ def to_dict(self) -> dict: @dataclass class SystemNotificationShellDetachedCompleted: - "Schema for the `SystemNotificationShellDetachedCompleted` type." + "System notification metadata for a detached shell session that completed, including shell ID and description." shell_id: str type: ClassVar[str] = "shell_detached_completed" description: str | None = None @@ -7471,7 +7693,7 @@ def to_dict(self) -> dict: @dataclass class ToolExecutionCompleteToolDescriptionMetaUI: - "Schema for the `ToolExecutionCompleteToolDescriptionMetaUI` type." + "MCP Apps tool `_meta.ui` resource URI and visibility captured on `tool.execution_complete`." resource_uri: str | None = None visibility: list[ToolExecutionCompleteToolDescriptionMetaUIVisibility] | None = None @@ -7554,7 +7776,7 @@ def to_dict(self) -> dict: @dataclass class ToolExecutionCompleteUIResourceMetaUI: - "Schema for the `ToolExecutionCompleteUIResourceMetaUI` type." + "MCP Apps UI resource metadata for a completed tool result, including CSP, permissions, domain, and border preference." csp: ToolExecutionCompleteUIResourceMetaUICsp | None = None domain: str | None = None permissions: ToolExecutionCompleteUIResourceMetaUIPermissions | None = None @@ -7589,7 +7811,7 @@ def to_dict(self) -> dict: @dataclass class ToolExecutionCompleteUIResourceMetaUICsp: - "Schema for the `ToolExecutionCompleteUIResourceMetaUICsp` type." + "CSP domain allowlists for an MCP Apps UI resource, including connect, resource, frame, and base URI domains." base_uri_domains: list[str] | None = None connect_domains: list[str] | None = None frame_domains: list[str] | None = None @@ -7624,7 +7846,7 @@ def to_dict(self) -> dict: @dataclass class ToolExecutionCompleteUIResourceMetaUIPermissions: - "Schema for the `ToolExecutionCompleteUIResourceMetaUIPermissions` type." + "Browser permission metadata for an MCP Apps UI resource, including camera, microphone, geolocation, and clipboard-write." camera: ToolExecutionCompleteUIResourceMetaUIPermissionsCamera | None = None clipboard_write: ToolExecutionCompleteUIResourceMetaUIPermissionsClipboardWrite | None = None geolocation: ToolExecutionCompleteUIResourceMetaUIPermissionsGeolocation | None = None @@ -7659,7 +7881,7 @@ def to_dict(self) -> dict: @dataclass class ToolExecutionCompleteUIResourceMetaUIPermissionsCamera: - "Schema for the `ToolExecutionCompleteUIResourceMetaUIPermissionsCamera` type." + "Marker object for camera permission on an MCP Apps UI resource." @staticmethod def from_dict(obj: Any) -> "ToolExecutionCompleteUIResourceMetaUIPermissionsCamera": assert isinstance(obj, dict) @@ -7671,7 +7893,7 @@ def to_dict(self) -> dict: @dataclass class ToolExecutionCompleteUIResourceMetaUIPermissionsClipboardWrite: - "Schema for the `ToolExecutionCompleteUIResourceMetaUIPermissionsClipboardWrite` type." + "Marker object for clipboard-write permission on an MCP Apps UI resource." @staticmethod def from_dict(obj: Any) -> "ToolExecutionCompleteUIResourceMetaUIPermissionsClipboardWrite": assert isinstance(obj, dict) @@ -7683,7 +7905,7 @@ def to_dict(self) -> dict: @dataclass class ToolExecutionCompleteUIResourceMetaUIPermissionsGeolocation: - "Schema for the `ToolExecutionCompleteUIResourceMetaUIPermissionsGeolocation` type." + "Marker object for geolocation permission on an MCP Apps UI resource." @staticmethod def from_dict(obj: Any) -> "ToolExecutionCompleteUIResourceMetaUIPermissionsGeolocation": assert isinstance(obj, dict) @@ -7695,7 +7917,7 @@ def to_dict(self) -> dict: @dataclass class ToolExecutionCompleteUIResourceMetaUIPermissionsMicrophone: - "Schema for the `ToolExecutionCompleteUIResourceMetaUIPermissionsMicrophone` type." + "Marker object for microphone permission on an MCP Apps UI resource." @staticmethod def from_dict(obj: Any) -> "ToolExecutionCompleteUIResourceMetaUIPermissionsMicrophone": assert isinstance(obj, dict) @@ -7894,7 +8116,7 @@ def to_dict(self) -> dict: @dataclass class ToolExecutionStartToolDescriptionMetaUI: - "Schema for the `ToolExecutionStartToolDescriptionMetaUI` type." + "MCP Apps tool `_meta.ui` resource URI and visibility captured on `tool.execution_start`." resource_uri: str | None = None visibility: list[ToolExecutionStartToolDescriptionMetaUIVisibility] | None = None @@ -8014,7 +8236,7 @@ def to_dict(self) -> dict: @dataclass class UserMessageData: - "Schema for the `UserMessageData` type." + "Payload of `user.message` with displayed and model-transformed content, attachments, source/delivery metadata, mode, and telemetry IDs." content: str agent_mode: UserMessageAgentMode | None = None attachments: list[Attachment] | None = None @@ -8083,7 +8305,7 @@ def to_dict(self) -> dict: @dataclass class UserToolSessionApprovalCommands: - "Schema for the `UserToolSessionApprovalCommands` type." + "Session-scoped tool-approval rule for specific shell command identifiers." command_identifiers: list[str] kind: ClassVar[str] = "commands" @@ -8104,7 +8326,7 @@ def to_dict(self) -> dict: @dataclass class UserToolSessionApprovalCustomTool: - "Schema for the `UserToolSessionApprovalCustomTool` type." + "Session-scoped tool-approval rule for a custom tool, keyed by tool name." kind: ClassVar[str] = "custom-tool" tool_name: str @@ -8125,7 +8347,7 @@ def to_dict(self) -> dict: @dataclass class UserToolSessionApprovalExtensionManagement: - "Schema for the `UserToolSessionApprovalExtensionManagement` type." + "Session-scoped tool-approval rule for extension-management operations, optionally narrowed by operation." kind: ClassVar[str] = "extension-management" operation: str | None = None @@ -8147,7 +8369,7 @@ def to_dict(self) -> dict: @dataclass class UserToolSessionApprovalExtensionPermissionAccess: - "Schema for the `UserToolSessionApprovalExtensionPermissionAccess` type." + "Session-scoped tool-approval rule for an extension's permission-gated capability access, keyed by extension name." extension_name: str kind: ClassVar[str] = "extension-permission-access" @@ -8168,7 +8390,7 @@ def to_dict(self) -> dict: @dataclass class UserToolSessionApprovalMcp: - "Schema for the `UserToolSessionApprovalMcp` type." + "Session-scoped tool-approval rule for an MCP server tool, or all tools on the server when `toolName` is null." kind: ClassVar[str] = "mcp" server_name: str tool_name: str | None @@ -8193,7 +8415,7 @@ def to_dict(self) -> dict: @dataclass class UserToolSessionApprovalMemory: - "Schema for the `UserToolSessionApprovalMemory` type." + "Session-scoped tool-approval rule for writes to long-term memory." kind: ClassVar[str] = "memory" @staticmethod @@ -8210,7 +8432,7 @@ def to_dict(self) -> dict: @dataclass class UserToolSessionApprovalRead: - "Schema for the `UserToolSessionApprovalRead` type." + "Session-scoped tool-approval rule for read-only filesystem operations." kind: ClassVar[str] = "read" @staticmethod @@ -8227,7 +8449,7 @@ def to_dict(self) -> dict: @dataclass class UserToolSessionApprovalWrite: - "Schema for the `UserToolSessionApprovalWrite` type." + "Session-scoped tool-approval rule for filesystem write operations." kind: ClassVar[str] = "write" @staticmethod @@ -8461,6 +8683,19 @@ def _load_UserToolSessionApproval(obj: Any) -> "UserToolSessionApproval": PermissionResult = PermissionApproved | PermissionApprovedForSession | PermissionApprovedForLocation | PermissionCancelled | PermissionDeniedByRules | PermissionDeniedNoApprovalRuleAndCouldNotRequestFromUser | PermissionDeniedInteractivelyByUser | PermissionDeniedByContentExclusionPolicy | PermissionDeniedByPermissionRequestHook +# Experimental: this enum is part of an experimental API and may change or be removed. +class AutoApprovalRecommendation(Enum): + "Outcome of the auto-approval safety judge for a permission request. Present only when auto mode is enabled; its absence means the judge did not evaluate the request (auto mode was off)." + # The judge evaluated the request and recommends automatically approving it. + APPROVE = "approve" + # The judge evaluated the request and does not recommend auto-approving it; explicit approval is required. Whether that means prompting, denying, or something else is the consumer's decision. + REQUIRE_APPROVAL = "requireApproval" + # Auto mode is enabled, but this request category is never auto-approvable (for example, sandbox-bypass requests), so the judge was not consulted. + EXCLUDED = "excluded" + # The judge was consulted but did not return a usable recommendation, so the request requires explicit approval. + ERROR = "error" + + # Experimental: this enum is part of an experimental API and may change or be removed. class CitationProvider(Enum): "The system that produced a citation." @@ -8472,6 +8707,17 @@ class CitationProvider(Enum): CLIENT = "client" +# Experimental: this enum is part of an experimental API and may change or be removed. +class PermissionAllowAllMode(Enum): + "Allow-all mode for the session." + # Permission requests follow the normal approval flow. + OFF = "off" + # Tool, path, and URL permission requests are automatically approved. + ON = "on" + # Permission requests follow the normal approval flow with an LLM advisory recommendation attached; clients may choose to auto-approve requests the judge evaluated as acceptable. + AUTO = "auto" + + class AbortReason(Enum): "Finite reason code describing why the current turn was aborted" # The local user requested the abort, for example by pressing Ctrl+C in the CLI. @@ -8918,6 +9164,16 @@ class UserMessageDelivery(Enum): QUEUED = "queued" +class Verbosity(Enum): + "Output verbosity level used for supported model calls (e.g. \"low\", \"medium\", \"high\")" + # A terse response was requested. + LOW = "low" + # A medium amount of response detail was requested. + MEDIUM = "medium" + # A more detailed response was requested. + HIGH = "high" + + class WorkingDirectoryContextHostType(Enum): "Hosting platform type of the repository (github or ado)" # Repository is hosted on GitHub. @@ -8934,7 +9190,7 @@ class WorkspaceFileChangedOperation(Enum): UPDATE = "update" -SessionEventData = SessionStartData | SessionResumeData | SessionRemoteSteerableChangedData | SessionErrorData | SessionIdleData | SessionTitleChangedData | SessionScheduleCreatedData | SessionScheduleCancelledData | SessionScheduleRearmedData | SessionAutopilotObjectiveChangedData | SessionInfoData | SessionWarningData | SessionModelChangeData | SessionModeChangedData | SessionSessionLimitsChangedData | SessionPermissionsChangedData | SessionPlanChangedData | SessionTodosChangedData | SessionWorkspaceFileChangedData | SessionHandoffData | SessionTruncationData | SessionSnapshotRewindData | SessionShutdownData | SessionUsageCheckpointData | SessionContextChangedData | SessionUsageInfoData | SessionCompactionStartData | SessionCompactionCompleteData | SessionTaskCompleteData | UserMessageData | PendingMessagesModifiedData | AssistantTurnStartData | AssistantIntentData | AssistantReasoningData | AssistantReasoningDeltaData | AssistantStreamingDeltaData | AssistantMessageData | AssistantMessageStartData | AssistantMessageDeltaData | AssistantTurnEndData | AssistantIdleData | AssistantUsageData | ModelCallFailureData | AbortData | ToolUserRequestedData | ToolExecutionStartData | ToolExecutionPartialResultData | ToolExecutionProgressData | ToolExecutionCompleteData | SkillInvokedData | SubagentStartedData | SubagentCompletedData | SubagentFailedData | SubagentSelectedData | SubagentDeselectedData | HookStartData | HookEndData | HookProgressData | SessionBinaryAssetData | SystemMessageData | SystemNotificationData | PermissionRequestedData | PermissionCompletedData | UserInputRequestedData | UserInputCompletedData | ElicitationRequestedData | ElicitationCompletedData | SamplingRequestedData | SamplingCompletedData | McpOauthRequiredData | McpOauthCompletedData | McpHeadersRefreshRequiredData | McpHeadersRefreshCompletedData | SessionCustomNotificationData | ExternalToolRequestedData | ExternalToolCompletedData | CommandQueuedData | CommandExecuteData | CommandCompletedData | AutoModeSwitchRequestedData | AutoModeSwitchCompletedData | SessionLimitsExhaustedRequestedData | SessionLimitsExhaustedCompletedData | CommandsChangedData | CapabilitiesChangedData | ExitPlanModeRequestedData | ExitPlanModeCompletedData | SessionToolsUpdatedData | SessionBackgroundTasksChangedData | SessionSkillsLoadedData | SessionCustomAgentsUpdatedData | SessionMcpServersLoadedData | SessionMcpServerStatusChangedData | 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 | SessionCompactionStartData | SessionCompactionCompleteData | SessionTaskCompleteData | UserMessageData | PendingMessagesModifiedData | AssistantTurnStartData | AssistantIntentData | AssistantReasoningData | AssistantReasoningDeltaData | AssistantToolCallDeltaData | AssistantStreamingDeltaData | AssistantMessageData | AssistantMessageStartData | AssistantMessageDeltaData | AssistantTurnEndData | AssistantIdleData | AssistantUsageData | ModelCallFailureData | AbortData | ToolUserRequestedData | ToolExecutionStartData | ToolExecutionPartialResultData | ToolExecutionProgressData | ToolExecutionCompleteData | SkillInvokedData | SubagentStartedData | SubagentCompletedData | SubagentFailedData | SubagentSelectedData | SubagentDeselectedData | HookStartData | HookEndData | HookProgressData | SessionBinaryAssetData | SystemMessageData | SystemNotificationData | PermissionRequestedData | PermissionCompletedData | UserInputRequestedData | UserInputCompletedData | ElicitationRequestedData | ElicitationCompletedData | SamplingRequestedData | SamplingCompletedData | McpOauthRequiredData | McpOauthCompletedData | McpHeadersRefreshRequiredData | McpHeadersRefreshCompletedData | SessionCustomNotificationData | ExternalToolRequestedData | ExternalToolCompletedData | CommandQueuedData | CommandExecuteData | CommandCompletedData | AutoModeSwitchRequestedData | AutoModeSwitchCompletedData | SessionLimitsExhaustedRequestedData | SessionLimitsExhaustedCompletedData | CommandsChangedData | CapabilitiesChangedData | ExitPlanModeRequestedData | ExitPlanModeCompletedData | SessionToolsUpdatedData | SessionBackgroundTasksChangedData | SessionSkillsLoadedData | SessionCustomAgentsUpdatedData | SessionMcpServersLoadedData | SessionMcpServerStatusChangedData | SessionExtensionsLoadedData | SessionCanvasOpenedData | SessionCanvasRegistryChangedData | SessionCanvasClosedData | SessionCanvasUnavailableData | SessionCanvasRecordedData | SessionCanvasRemovedData | SessionExtensionsAttachmentsPushedData | McpAppToolCallCompleteData | RawSessionEventData | Data @dataclass @@ -8995,6 +9251,7 @@ def from_dict(obj: Any) -> "SessionEvent": case SessionEventType.ASSISTANT_INTENT: data = AssistantIntentData.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) + case SessionEventType.ASSISTANT_TOOL_CALL_DELTA: data = AssistantToolCallDeltaData.from_dict(data_obj) case SessionEventType.ASSISTANT_STREAMING_DELTA: data = AssistantStreamingDeltaData.from_dict(data_obj) case SessionEventType.ASSISTANT_MESSAGE: data = AssistantMessageData.from_dict(data_obj) case SessionEventType.ASSISTANT_MESSAGE_START: data = AssistantMessageStartData.from_dict(data_obj) @@ -9109,6 +9366,7 @@ def session_event_to_dict(x: SessionEvent) -> Any: "AssistantReasoningData", "AssistantReasoningDeltaData", "AssistantStreamingDeltaData", + "AssistantToolCallDeltaData", "AssistantTurnEndData", "AssistantTurnStartData", "AssistantUsageApiEndpoint", @@ -9138,6 +9396,7 @@ def session_event_to_dict(x: SessionEvent) -> Any: "AttachmentSelectionDetails", "AttachmentSelectionDetailsEnd", "AttachmentSelectionDetailsStart", + "AutoApprovalRecommendation", "AutoModeSwitchCompletedData", "AutoModeSwitchRequestedData", "AutoModeSwitchResponse", @@ -9218,9 +9477,11 @@ def session_event_to_dict(x: SessionEvent) -> Any: "OmittedBinaryResult", "OmittedBinaryType", "PendingMessagesModifiedData", + "PermissionAllowAllMode", "PermissionApproved", "PermissionApprovedForLocation", "PermissionApprovedForSession", + "PermissionAutoApproval", "PermissionCancelled", "PermissionCompletedData", "PermissionDeniedByContentExclusionPolicy", @@ -9399,6 +9660,7 @@ def session_event_to_dict(x: SessionEvent) -> Any: "UserToolSessionApprovalMemory", "UserToolSessionApprovalRead", "UserToolSessionApprovalWrite", + "Verbosity", "WorkingDirectoryContext", "WorkingDirectoryContextHostType", "WorkspaceFileChangedOperation", diff --git a/python/copilot/tools.py b/python/copilot/tools.py index a82a48b1e9..620a8cd58a 100644 --- a/python/copilot/tools.py +++ b/python/copilot/tools.py @@ -13,7 +13,7 @@ from dataclasses import dataclass, field from typing import Any, Literal, TypeVar, get_type_hints, overload -from pydantic import BaseModel +from pydantic import BaseModel, ValidationError ToolResultType = Literal["success", "failure", "rejected", "denied", "timeout"] @@ -211,7 +211,21 @@ async def wrapped_handler(invocation: ToolInvocation) -> ToolResult: if takes_params: args = invocation.arguments or {} if ptype is not None and _is_pydantic_model(ptype): - call_args.append(ptype.model_validate(args)) + try: + call_args.append(ptype.model_validate(args)) + except ValidationError as exc: + # Highlight input validation problems to the LLM. + parts = [] + for err in exc.errors(): + loc = ".".join(map(str, err["loc"])) + msg = err["msg"] + parts.append(f"{loc}: {msg}" if loc else msg) + return ToolResult( + text_result_for_llm="Invalid tool arguments:\n" + "\n".join(parts), + result_type="failure", + error=str(exc), + tool_telemetry={}, + ) else: call_args.append(args) if takes_invocation: diff --git a/python/e2e/_copilot_request_helpers.py b/python/e2e/_copilot_request_helpers.py index 6a3d3d1de2..2d91bc9bc2 100644 --- a/python/e2e/_copilot_request_helpers.py +++ b/python/e2e/_copilot_request_helpers.py @@ -224,6 +224,57 @@ def build_inference_response(request: httpx.Request, text: str = SYNTHETIC_TEXT) ) if url.endswith("/messages"): + if wants_stream: + events = [ + ( + "message_start", + { + "type": "message_start", + "message": { + "id": "msg_stub_1", + "type": "message", + "role": "assistant", + "model": "claude-sonnet-4.5", + "content": [], + "stop_reason": None, + "stop_sequence": None, + "usage": {"input_tokens": 5, "output_tokens": 1}, + }, + }, + ), + ( + "content_block_start", + { + "type": "content_block_start", + "index": 0, + "content_block": {"type": "text", "text": ""}, + }, + ), + ( + "content_block_delta", + { + "type": "content_block_delta", + "index": 0, + "delta": {"type": "text_delta", "text": text}, + }, + ), + ("content_block_stop", {"type": "content_block_stop", "index": 0}), + ( + "message_delta", + { + "type": "message_delta", + "delta": {"stop_reason": "end_turn", "stop_sequence": None}, + "usage": {"output_tokens": 7}, + }, + ), + ("message_stop", {"type": "message_stop"}), + ] + stream_body = "".join(sse(event, data) for event, data in events) + return httpx.Response( + 200, + headers={"content-type": "text/event-stream"}, + content=stream_body.encode(), + ) return httpx.Response( 200, headers={"content-type": "application/json"}, diff --git a/python/e2e/test_client_options_e2e.py b/python/e2e/test_client_options_e2e.py index 9a70c6cbfe..bca4dbb722 100644 --- a/python/e2e/test_client_options_e2e.py +++ b/python/e2e/test_client_options_e2e.py @@ -20,11 +20,20 @@ import pytest -from copilot import CopilotClient, RuntimeConnection +from copilot import ( + CanvasDeclaration, + CloudSessionOptions, + CloudSessionRepository, + CopilotClient, + ExtensionInfo, + OpenCanvasInstance, + RemoteSessionMode, + RuntimeConnection, +) from copilot.rpc import PingRequest from copilot.session import PermissionHandler -from .testharness import E2ETestContext +from .testharness import DEFAULT_GITHUB_TOKEN, E2ETestContext pytestmark = pytest.mark.asyncio(loop_scope="module") @@ -56,9 +65,7 @@ def _make_options( "connection": connection, "working_directory": ctx.work_dir, "env": ctx.get_env(), - "github_token": ( - "fake-token-for-e2e-tests" if os.environ.get("GITHUB_ACTIONS") == "true" else None - ), + "github_token": DEFAULT_GITHUB_TOKEN, } base.update(overrides) return base @@ -147,6 +154,16 @@ def _get_available_port() -> int: writeResponse(message.id, { sessionId, workspacePath: null, capabilities: null }); return; } + if (message.method === "session.resume") { + const sessionId = message.params?.sessionId ?? message.params?.session_id ?? "fake-session"; + writeResponse(message.id, { + sessionId, + workspacePath: null, + capabilities: null, + openCanvases: message.params?.openCanvases ?? [], + }); + return; + } writeResponse(message.id, {}); } @@ -164,6 +181,14 @@ def _assert_arg_value(args: list[str], name: str, expected_value: str) -> None: assert args[index + 1] == expected_value +def _get_captured_request(capture_path: str, method: str) -> dict: + with open(capture_path) as f: + capture = json.load(f) + request = next((r for r in capture["requests"] if r["method"] == method), None) + assert request is not None, f"Expected {method} request in capture" + return request["params"] + + class TestClientOptions: async def test_should_listen_on_configured_tcp_port(self, ctx: E2ETestContext): port = _get_available_port() @@ -277,3 +302,287 @@ async def test_should_propagate_process_options_to_spawned_cli(self, ctx: E2ETes await client.stop() except Exception: await client.force_stop() + + async def test_should_forward_advanced_session_options_in_create_wire_request( + self, ctx: E2ETestContext + ): + cli_path = os.path.join(ctx.work_dir, f"fake-cli-advanced-create-{os.getpid()}.js") + capture_path = os.path.join( + ctx.work_dir, f"fake-cli-advanced-create-capture-{os.getpid()}.json" + ) + output_directory = os.path.join(ctx.work_dir, "large-output-create") + with open(cli_path, "w") as f: + f.write(FAKE_STDIO_CLI_SCRIPT) + + client = CopilotClient( + **_make_options( + ctx, + cli_path=cli_path, + cli_args=["--capture-file", capture_path], + use_logged_in_user=False, + ) + ) + try: + await client.start() + session = await client.create_session( + client_name="advanced-create-client", + model="claude-sonnet-4.5", + reasoning_effort="medium", + reasoning_summary="detailed", + context_tier="long_context", + enable_citations=True, + capi={"enable_web_socket_responses": False}, + mcp_oauth_token_storage="persistent", + custom_agents=[ + { + "name": "agent-one", + "display_name": "Agent One", + "description": "Handles agent-one tasks.", + "prompt": "Be agent one.", + "tools": ["view"], + "infer": True, + "skills": ["create-skill"], + "model": "claude-haiku-4.5", + } + ], + default_agent={"excluded_tools": ["edit"]}, + agent="agent-one", + skill_directories=["skills-create"], + disabled_skills=["disabled-create-skill"], + plugin_directories=["plugins-create"], + infinite_sessions={ + "enabled": False, + "background_compaction_threshold": 0.5, + "buffer_exhaustion_threshold": 0.9, + }, + large_output={ + "enabled": True, + "max_size_bytes": 4096, + "output_directory": output_directory, + }, + memory={"enabled": True}, + github_token="session-create-token", + remote_session=RemoteSessionMode.EXPORT, + cloud=CloudSessionOptions( + repository=CloudSessionRepository( + owner="github", + name="copilot-sdk", + branch="main", + ) + ), + enable_mcp_apps=True, + request_canvas_renderer=True, + request_extensions=True, + extension_sdk_path="custom-extension-sdk", + extension_info=ExtensionInfo( + source="python-sdk-tests", + name="advanced-create-extension", + ), + canvases=[ + CanvasDeclaration( + id="advanced-create-canvas", + display_name="Advanced Create Canvas", + description="Covers create-time canvas options.", + ) + ], + providers=[ + { + "name": "create-provider", + "type": "openai", + "wire_api": "responses", + "base_url": "https://create-provider.example.test/v1", + "api_key": "create-provider-key", + "headers": {"X-Create-Provider": "yes"}, + } + ], + models=[ + { + "provider": "create-provider", + "id": "create-model", + "name": "Create Model", + "model_id": "claude-sonnet-4.5", + "wire_model": "create-wire-model", + "max_context_window_tokens": 12_000, + "max_prompt_tokens": 10_000, + "max_output_tokens": 2_000, + } + ], + on_permission_request=PermissionHandler.approve_all, + ) + try: + params = _get_captured_request(capture_path, "session.create") + assert params["clientName"] == "advanced-create-client" + assert params["model"] == "claude-sonnet-4.5" + assert params["reasoningEffort"] == "medium" + assert params["reasoningSummary"] == "detailed" + assert params["contextTier"] == "long_context" + assert params["enableCitations"] is True + assert params["capi"]["enableWebSocketResponses"] is False + assert params["mcpOAuthTokenStorage"] == "persistent" + assert params["agent"] == "agent-one" + assert params["defaultAgent"]["excludedTools"][0] == "edit" + assert params["customAgents"][0]["name"] == "agent-one" + assert params["pluginDirectories"][0] == "plugins-create" + assert params["disabledSkills"][0] == "disabled-create-skill" + assert params["infiniteSessions"]["enabled"] is False + assert params["largeOutput"]["enabled"] is True + assert params["largeOutput"]["maxSizeBytes"] == 4096 + assert params["largeOutput"]["outputDir"] == output_directory + assert params["memory"]["enabled"] is True + assert params["gitHubToken"] == "session-create-token" + assert params["remoteSession"] == "export" + assert params["cloud"]["repository"]["owner"] == "github" + assert params["requestMcpApps"] is True + assert params["requestCanvasRenderer"] is True + assert params["requestExtensions"] is True + assert params["extensionSdkPath"] == "custom-extension-sdk" + assert params["extensionInfo"]["name"] == "advanced-create-extension" + assert params["canvases"][0]["id"] == "advanced-create-canvas" + assert params["providers"][0]["name"] == "create-provider" + assert params["providers"][0]["wireApi"] == "responses" + assert params["models"][0]["id"] == "create-model" + assert params["models"][0]["maxContextWindowTokens"] == 12_000 + finally: + await session.disconnect() + finally: + await client.stop() + + async def test_should_forward_singular_provider_options_in_create_wire_request( + self, ctx: E2ETestContext + ): + cli_path = os.path.join(ctx.work_dir, f"fake-cli-provider-create-{os.getpid()}.js") + capture_path = os.path.join( + ctx.work_dir, f"fake-cli-provider-create-capture-{os.getpid()}.json" + ) + with open(cli_path, "w") as f: + f.write(FAKE_STDIO_CLI_SCRIPT) + + client = CopilotClient( + **_make_options( + ctx, + cli_path=cli_path, + cli_args=["--capture-file", capture_path], + use_logged_in_user=False, + ) + ) + try: + await client.start() + session = await client.create_session( + model="claude-sonnet-4.5", + provider={ + "type": "azure", + "wire_api": "responses", + "transport": "http", + "base_url": "https://azure-provider.example.test/openai", + "api_key": "provider-api-key", + "bearer_token": "provider-bearer-token", + "azure": {"api_version": "2024-02-15-preview"}, + "headers": {"X-Provider-Wire": "yes"}, + "model_id": "claude-sonnet-4.5", + "wire_model": "azure-deployment", + "max_prompt_tokens": 8192, + "max_output_tokens": 1024, + }, + on_permission_request=PermissionHandler.approve_all, + ) + try: + provider = _get_captured_request(capture_path, "session.create")["provider"] + assert provider["type"] == "azure" + assert provider["wireApi"] == "responses" + assert provider["transport"] == "http" + assert provider["baseUrl"] == "https://azure-provider.example.test/openai" + assert provider["apiKey"] == "provider-api-key" + assert provider["bearerToken"] == "provider-bearer-token" + assert provider["azure"]["apiVersion"] == "2024-02-15-preview" + assert provider["headers"]["X-Provider-Wire"] == "yes" + assert provider["modelId"] == "claude-sonnet-4.5" + assert provider["wireModel"] == "azure-deployment" + assert provider["maxPromptTokens"] == 8192 + assert provider["maxOutputTokens"] == 1024 + finally: + await session.disconnect() + finally: + await client.stop() + + async def test_should_forward_advanced_session_options_in_resume_wire_request( + self, ctx: E2ETestContext + ): + cli_path = os.path.join(ctx.work_dir, f"fake-cli-advanced-resume-{os.getpid()}.js") + capture_path = os.path.join( + ctx.work_dir, f"fake-cli-advanced-resume-capture-{os.getpid()}.json" + ) + output_directory = os.path.join(ctx.work_dir, "large-output-resume") + with open(cli_path, "w") as f: + f.write(FAKE_STDIO_CLI_SCRIPT) + + client = CopilotClient( + **_make_options( + ctx, + cli_path=cli_path, + cli_args=["--capture-file", capture_path], + use_logged_in_user=False, + ) + ) + try: + await client.start() + session = await client.resume_session( + "advanced-resume-session", + client_name="advanced-resume-client", + model="claude-haiku-4.5", + reasoning_effort="low", + reasoning_summary="none", + context_tier="default", + continue_pending_work=True, + mcp_oauth_token_storage="persistent", + plugin_directories=["plugins-resume"], + large_output={ + "enabled": False, + "max_size_bytes": 2048, + "output_directory": output_directory, + }, + memory={"enabled": False}, + remote_session=RemoteSessionMode.ON, + open_canvases=[ + OpenCanvasInstance( + canvas_id="resume-canvas", + extension_id="python-sdk-tests/resume-extension", + extension_name="Resume Extension", + instance_id="resume-canvas-1", + input={"start": 41}, + status="ready", + title="Resume Canvas", + url="https://example.com/resume-canvas", + ) + ], + on_permission_request=PermissionHandler.approve_all, + ) + try: + params = _get_captured_request(capture_path, "session.resume") + assert params["sessionId"] == "advanced-resume-session" + assert params["clientName"] == "advanced-resume-client" + assert params["model"] == "claude-haiku-4.5" + assert params["reasoningEffort"] == "low" + assert params["reasoningSummary"] == "none" + assert params["contextTier"] == "default" + assert params["continuePendingWork"] is True + assert params["mcpOAuthTokenStorage"] == "persistent" + assert params["pluginDirectories"][0] == "plugins-resume" + assert params["largeOutput"]["enabled"] is False + assert params["largeOutput"]["maxSizeBytes"] == 2048 + assert params["largeOutput"]["outputDir"] == output_directory + assert params["memory"]["enabled"] is False + assert params["remoteSession"] == "on" + + open_canvas = params["openCanvases"][0] + assert open_canvas["canvasId"] == "resume-canvas" + assert open_canvas["extensionId"] == "python-sdk-tests/resume-extension" + assert open_canvas["extensionName"] == "Resume Extension" + assert open_canvas["instanceId"] == "resume-canvas-1" + assert open_canvas["input"]["start"] == 41 + assert open_canvas["status"] == "ready" + assert open_canvas["title"] == "Resume Canvas" + assert open_canvas["url"] == "https://example.com/resume-canvas" + finally: + await session.disconnect() + finally: + await client.stop() diff --git a/python/e2e/test_github_telemetry_e2e.py b/python/e2e/test_github_telemetry_e2e.py new file mode 100644 index 0000000000..976b0b616e --- /dev/null +++ b/python/e2e/test_github_telemetry_e2e.py @@ -0,0 +1,57 @@ +"""Live CLI E2E coverage for forwarded GitHub telemetry notifications.""" + +from __future__ import annotations + +import asyncio + +import pytest + +from copilot import CopilotClient, GitHubTelemetryNotification, RuntimeConnection +from copilot.session import PermissionHandler + +from .testharness import DEFAULT_GITHUB_TOKEN, E2ETestContext +from .testharness.context import get_cli_path_for_tests + +pytestmark = pytest.mark.asyncio(loop_scope="module") + + +class TestGitHubTelemetryE2E: + async def test_should_receive_session_start_github_telemetry(self, ctx: E2ETestContext): + received: list[GitHubTelemetryNotification] = [] + + def on_github_telemetry(notification: GitHubTelemetryNotification) -> None: + received.append(notification) + + client = CopilotClient( + connection=RuntimeConnection.for_stdio(path=get_cli_path_for_tests(), args=()), + working_directory=ctx.work_dir, + env=ctx.get_env(), + github_token=DEFAULT_GITHUB_TOKEN, + on_github_telemetry=on_github_telemetry, + ) + + session = None + try: + await client.start() + session = await client.create_session( + on_permission_request=PermissionHandler.approve_all, + ) + + for _ in range(600): + if received: + break + await asyncio.sleep(0.05) + + assert received + notification = received[0] + assert isinstance(notification.session_id, str) + assert notification.session_id + assert isinstance(notification.restricted, bool) + assert notification.event is not None + assert isinstance(notification.event.kind, str) + finally: + try: + if session is not None: + await session.disconnect() + finally: + await client.stop() diff --git a/python/e2e/test_mcp_oauth_e2e.py b/python/e2e/test_mcp_oauth_e2e.py index 47897f69c0..8322a3aba1 100644 --- a/python/e2e/test_mcp_oauth_e2e.py +++ b/python/e2e/test_mcp_oauth_e2e.py @@ -7,7 +7,13 @@ import httpx import pytest -from copilot.generated.rpc import MCPAppsCallToolRequest, MCPListToolsRequest +from copilot.generated.rpc import ( + MCPAppsCallToolRequest, + MCPListToolsRequest, + MCPOauthHandlePendingRequest, + MCPOauthPendingRequestResponse, + MCPOauthPendingRequestResponseKind, +) from copilot.session import MCPServerConfig, PermissionHandler from copilot.session_events import McpServerStatus @@ -153,6 +159,75 @@ def on_mcp_auth_request(request, _invocation): finally: await _stop_process(process) + async def test_should_resolve_pending_mcp_oauth_request_with_direct_rpc( + self, ctx: E2ETestContext + ): + url, process = await _start_oauth_mcp_server() + server_name = "oauth-direct-rpc-mcp" + loop = asyncio.get_running_loop() + observed_request = loop.create_future() + release_handler = asyncio.Event() + + async def on_mcp_auth_request(request, _invocation): + if not observed_request.done(): + observed_request.set_result(request) + await release_handler.wait() + return {"kind": "token", "accessToken": EXPECTED_TOKEN} + + try: + mcp_servers: dict[str, MCPServerConfig] = { + server_name: { + "type": "http", + "url": f"{url}/mcp", + "tools": ["*"], + "oauthClientId": "sdk-e2e-client", + "oauthPublicClient": True, + } + } + async with await ctx.client.create_session( + on_permission_request=PermissionHandler.approve_all, + on_mcp_auth_request=on_mcp_auth_request, + mcp_servers=mcp_servers, + enable_mcp_apps=True, + ) as session: + connected = asyncio.create_task(_wait_for_mcp_server_status(session, server_name)) + try: + request = await asyncio.wait_for(observed_request, timeout=30.0) + assert request["serverName"] == server_name + assert request["serverUrl"] == f"{url}/mcp" + assert request["reason"] == "initial" + assert request["wwwAuthenticateParams"] == { + "resourceMetadataUrl": f"{url}/.well-known/oauth-protected-resource", + "scope": "mcp.read", + "error": "invalid_token", + } + + handled = await session.rpc.mcp.oauth.handle_pending_request( + MCPOauthHandlePendingRequest( + request_id=request["requestId"], + result=MCPOauthPendingRequestResponse( + kind=MCPOauthPendingRequestResponseKind.TOKEN, + access_token=EXPECTED_TOKEN, + token_type="Bearer", + expires_in=3600, + ), + ) + ) + assert handled.success is True + + connected_result = await asyncio.wait_for(connected, timeout=60.0) + assert connected_result is None + tools = await session.rpc.mcp.list_tools( + MCPListToolsRequest(server_name=server_name) + ) + assert [tool.name for tool in tools.tools] == ["whoami"] + finally: + release_handler.set() + if not connected.done(): + connected.cancel() + finally: + await _stop_process(process) + async def test_should_request_replacement_tokens_across_mcp_oauth_lifecycle( self, ctx: E2ETestContext ): @@ -249,7 +324,7 @@ def on_mcp_auth_request(request, _invocation): on_mcp_auth_request=on_mcp_auth_request, mcp_servers=mcp_servers, ) as session: - await _wait_for_mcp_server_status(session, server_name, McpServerStatus.FAILED) + await _wait_for_mcp_server_status(session, server_name, McpServerStatus.NEEDS_AUTH) assert observed_request is not None assert observed_request["serverName"] == server_name diff --git a/python/e2e/test_rpc_server_e2e.py b/python/e2e/test_rpc_server_e2e.py index 244eb2ec49..fb422d5b31 100644 --- a/python/e2e/test_rpc_server_e2e.py +++ b/python/e2e/test_rpc_server_e2e.py @@ -16,7 +16,14 @@ from copilot import CopilotClient, RuntimeConnection from copilot.rpc import ( AccountGetQuotaRequest, + AgentsDiscoverRequest, + AgentsGetDiscoveryPathsRequest, ConnectRemoteSessionParams, + InstructionsDiscoverRequest, + InstructionsGetDiscoveryPathsRequest, + LlmInferenceHTTPResponseChunkError, + LlmInferenceHTTPResponseChunkRequest, + LlmInferenceHTTPResponseStartRequest, LocalSessionMetadataValue, MCPDiscoverRequest, ModelsListRequest, @@ -43,6 +50,7 @@ SessionsSetAdditionalPluginsRequest, SkillsConfigSetDisabledSkillsRequest, SkillsDiscoverRequest, + SkillsGetDiscoveryPathsRequest, ToolsListRequest, ) from copilot.session import PermissionHandler @@ -68,6 +76,12 @@ def _create_skill_directory(work_dir: str, skill_name: str, description: str) -> return str(skills_dir) +def _paths_equal(left: str, right: str | None) -> bool: + if right is None: + return False + return os.path.normcase(os.path.abspath(left)) == os.path.normcase(os.path.abspath(right)) + + @pytest.fixture(scope="module") async def authed_ctx(ctx: E2ETestContext): """Configure proxy to redirect GitHub user lookups so per-token auth works.""" @@ -123,6 +137,44 @@ async def test_should_call_rpc_ping_with_typed_params_and_result(self, ctx: E2ET assert result.message == "pong: typed rpc test" assert result.timestamp is not None + async def test_should_reject_llm_inference_response_frames_for_missing_request( + self, ctx: E2ETestContext + ): + await ctx.client.start() + + start = await ctx.client.rpc.llm_inference.http_response_start( + LlmInferenceHTTPResponseStartRequest( + request_id="missing-llm-inference-request", + status=200, + status_text="OK", + headers={"content-type": ["text/event-stream"]}, + ) + ) + assert start.accepted is False + + chunk = await ctx.client.rpc.llm_inference.http_response_chunk( + LlmInferenceHTTPResponseChunkRequest( + request_id="missing-llm-inference-request", + data="data: {}\n\n", + binary=False, + end=False, + ) + ) + assert chunk.accepted is False + + error = await ctx.client.rpc.llm_inference.http_response_chunk( + LlmInferenceHTTPResponseChunkRequest( + request_id="missing-llm-inference-request", + data="", + end=True, + error=LlmInferenceHTTPResponseChunkError( + message="No pending LLM inference request.", + code="missing_request", + ), + ) + ) + assert error.accepted is False + async def test_should_call_rpc_models_list_with_typed_result(self, authed_ctx: E2ETestContext): token = "rpc-models-token" await _configure_user(authed_ctx, token) @@ -444,6 +496,68 @@ async def test_should_discover_server_mcp_and_skills(self, ctx: E2ETestContext): assert discovered.enabled is True assert discovered.path.endswith(os.path.join(skill_name, "SKILL.md")) + skill_paths = await ctx.client.rpc.skills.get_discovery_paths( + SkillsGetDiscoveryPathsRequest( + project_paths=[ctx.work_dir], + exclude_host_skills=True, + ) + ) + project_skill_path = next( + ( + path + for path in skill_paths.paths + if _paths_equal(ctx.work_dir, path.project_path) and path.preferred_for_creation + ), + None, + ) + assert project_skill_path is not None + assert project_skill_path.path.strip() + + agents = await ctx.client.rpc.agents.discover( + AgentsDiscoverRequest(project_paths=[ctx.work_dir], exclude_host_agents=True) + ) + assert all(agent.name.strip() for agent in agents.agents) + + agent_paths = await ctx.client.rpc.agents.get_discovery_paths( + AgentsGetDiscoveryPathsRequest( + project_paths=[ctx.work_dir], + exclude_host_agents=True, + ) + ) + project_agent_path = next( + ( + path + for path in agent_paths.paths + if _paths_equal(ctx.work_dir, path.project_path) and path.preferred_for_creation + ), + None, + ) + assert project_agent_path is not None + assert project_agent_path.path.strip() + + instructions = await ctx.client.rpc.instructions.discover( + InstructionsDiscoverRequest( + project_paths=[ctx.work_dir], + exclude_host_instructions=True, + ) + ) + assert all( + source.id.strip() and source.label.strip() and source.source_path.strip() + for source in instructions.sources + ) + + instruction_paths = await ctx.client.rpc.instructions.get_discovery_paths( + InstructionsGetDiscoveryPathsRequest( + project_paths=[ctx.work_dir], + exclude_host_instructions=True, + ) + ) + assert instruction_paths.paths + assert any( + _paths_equal(ctx.work_dir, path.project_path) for path in instruction_paths.paths + ) + assert all(path.path.strip() for path in instruction_paths.paths) + try: await ctx.client.rpc.skills.config.set_disabled_skills( SkillsConfigSetDisabledSkillsRequest(disabled_skills=[skill_name]) diff --git a/python/e2e/test_rpc_server_misc_e2e.py b/python/e2e/test_rpc_server_misc_e2e.py index 6f3870224a..d5ade1aec1 100644 --- a/python/e2e/test_rpc_server_misc_e2e.py +++ b/python/e2e/test_rpc_server_misc_e2e.py @@ -16,10 +16,13 @@ from copilot import CopilotClient, RuntimeConnection from copilot.rpc import ( + AccountLoginRequest, + AccountLogoutRequest, AgentRegistrySpawnRequest, SendAttachmentsToMessageParams, SessionsOpenResumeLast, SessionsOpenStatus, + UserSettingsSetRequest, ) from copilot.session import PermissionHandler @@ -37,17 +40,24 @@ def _create_dedicated_client(ctx: E2ETestContext) -> CopilotClient: ) -async def _create_isolated_client(ctx: E2ETestContext) -> tuple[CopilotClient, Path]: +async def _create_isolated_client( + ctx: E2ETestContext, github_token: str | None = DEFAULT_GITHUB_TOKEN +) -> tuple[CopilotClient, Path]: home = Path(ctx.work_dir) / f"copilot-e2e-misc-home-{uuid.uuid4().hex}" home.mkdir(parents=True) env = ctx.get_env() for key in ("COPILOT_HOME", "GH_CONFIG_DIR", "XDG_CONFIG_HOME", "XDG_STATE_HOME"): env[key] = str(home) + env["COPILOT_DEBUG_GITHUB_API_URL"] = ctx.proxy_url + if github_token is None: + env["GH_TOKEN"] = "" + env["GITHUB_TOKEN"] = "" client = CopilotClient( connection=RuntimeConnection.for_stdio(path=ctx.cli_path), working_directory=ctx.work_dir, env=env, - github_token=DEFAULT_GITHUB_TOKEN, + github_token=github_token, + use_logged_in_user=False if github_token is None else None, ) await client.start() return client, home @@ -70,6 +80,96 @@ async def test_should_reload_user_settings(self, ctx: E2ETestContext): await ctx.client.rpc.user.settings.reload() + async def test_should_get_set_and_clear_user_settings(self, ctx: E2ETestContext): + client, home = await _create_isolated_client(ctx) + try: + before = await client.rpc.user.settings.get() + assert len(before.settings) > 0 + for key, setting in before.settings.items(): + assert key.strip() + assert isinstance(setting.is_default, bool) + + setting_key, setting = next( + (key, value) + for key, value in before.settings.items() + if isinstance(value.value, bool) + ) + toggled_value = setting.value is not True + + set_result = await client.rpc.user.settings.set( + UserSettingsSetRequest(settings={setting_key: toggled_value}) + ) + assert setting_key not in set_result.shadowed_keys + + await client.rpc.user.settings.reload() + after_set = await client.rpc.user.settings.get() + assert after_set.settings[setting_key].is_default is False + assert after_set.settings[setting_key].value is toggled_value + + await client.rpc.user.settings.set(UserSettingsSetRequest(settings={setting_key: None})) + await client.rpc.user.settings.reload() + after_clear = await client.rpc.user.settings.get() + assert after_clear.settings[setting_key].is_default is True + finally: + await _dispose_isolated(client, home) + + async def test_should_login_list_get_current_auth_and_logout_account(self, ctx: E2ETestContext): + login = f"rpc-account-{uuid.uuid4().hex}" + token = f"rpc-account-token-{uuid.uuid4().hex}" + await ctx.set_copilot_user_by_token( + token, + { + "login": login, + "copilot_plan": "individual_pro", + "endpoints": { + "api": ctx.proxy_url, + "telemetry": "https://localhost:1/telemetry", + }, + "analytics_tracking_id": "rpc-account-tracking-id", + }, + ) + + client, home = await _create_isolated_client(ctx, github_token=None) + try: + initial = await client.rpc.account.get_current_auth() + assert initial.auth_info is None + + login_result = await client.rpc.account.login( + AccountLoginRequest(host="https://github.com", login=login, token=token) + ) + assert isinstance(login_result.stored_in_vault, bool) + + current = await client.rpc.account.get_current_auth() + assert current.auth_errors is None + assert current.auth_info is not None + assert current.auth_info.type == "user" + assert current.auth_info.host == "https://github.com" + assert current.auth_info.login == login + + users = await client.rpc.account.get_all_users() + assert isinstance(users, list) + account = next( + ( + user + for user in users + if user.auth_info.type == "user" + and getattr(user.auth_info, "login", None) == login + ), + None, + ) + if account is not None: + assert account.token == token + + logout = await client.rpc.account.logout( + AccountLogoutRequest(auth_info=current.auth_info) + ) + assert logout.has_more_users is False + + after_logout = await client.rpc.account.get_current_auth() + assert after_logout.auth_info is None + finally: + await _dispose_isolated(client, home) + async def test_should_report_agent_registry_spawn_gate_closed(self, ctx: E2ETestContext): client, home = await _create_isolated_client(ctx) try: diff --git a/python/e2e/test_rpc_session_state_extras_e2e.py b/python/e2e/test_rpc_session_state_extras_e2e.py index fb12157849..5d0d881a00 100644 --- a/python/e2e/test_rpc_session_state_extras_e2e.py +++ b/python/e2e/test_rpc_session_state_extras_e2e.py @@ -9,11 +9,28 @@ import contextlib import json +import time import pytest from copilot import CopilotClient, RuntimeConnection -from copilot.rpc import PermissionsSetAllowAllRequest +from copilot.rpc import ( + CompletionsRequestRequest, + MetadataContextHeaviestMessagesRequest, + ModelSwitchToRequest, + NamedProviderConfig, + PermissionsSetAllowAllRequest, + ProviderAddRequest, + ProviderModelConfig, + ProviderType, + ProviderWireAPI, + SessionVisibilityStatus, + SubagentSettings, + SubagentSettingsEntry, + SubagentSettingsEntryContextTier, + UpdateSubagentSettingsRequest, + VisibilitySetRequest, +) from copilot.session import PermissionHandler from .testharness import E2ETestContext @@ -83,6 +100,86 @@ async def test_should_report_session_activity_when_idle(self, ctx: E2ETestContex assert activity.has_active_work is False assert activity.abortable is False + async def test_should_add_byok_provider_and_model_at_runtime(self, ctx: E2ETestContext): + async with await ctx.client.create_session( + on_permission_request=PermissionHandler.approve_all, + ) as session: + provider_name = f"sdk-runtime-provider-{time.time_ns()}" + model_id = "sdk-runtime-model" + selection_id = f"{provider_name}/{model_id}" + + added = await session.rpc.provider.add( + ProviderAddRequest( + providers=[ + NamedProviderConfig( + name=provider_name, + type=ProviderType.OPENAI, + wire_api=ProviderWireAPI.COMPLETIONS, + base_url="https://api.example.test/v1", + api_key="runtime-provider-secret", + headers={"X-SDK-Provider": "runtime"}, + ) + ], + models=[ + ProviderModelConfig( + provider=provider_name, + id=model_id, + name="SDK Runtime Model", + model_id="claude-sonnet-4.5", + wire_model="wire-sdk-runtime-model", + max_context_window_tokens=4096, + max_prompt_tokens=3072, + max_output_tokens=1024, + ) + ], + ) + ) + + assert len(added.models) == 1 + assert selection_id in json.dumps(added.models[0], sort_keys=True) + assert "SDK Runtime Model" in json.dumps(added.models[0], sort_keys=True) + + listed = await session.rpc.model.list() + assert any(selection_id in json.dumps(model, sort_keys=True) for model in listed.list) + + switched = await session.rpc.model.switch_to( + ModelSwitchToRequest(model_id=selection_id) + ) + assert switched.model_id == selection_id + assert (await session.rpc.model.get_current()).model_id == selection_id + + async def test_should_return_empty_completions_when_host_does_not_provide_them( + self, ctx: E2ETestContext + ): + async with await ctx.client.create_session( + on_permission_request=PermissionHandler.approve_all, + ) as session: + triggers = await session.rpc.completions.get_trigger_characters() + assert triggers.trigger_characters == [] + + completions = await session.rpc.completions.request( + CompletionsRequestRequest(text="Use @", offset=5) + ) + assert completions.items == [] + + async def test_should_report_visibility_as_unsynced_for_local_session( + self, ctx: E2ETestContext + ): + async with await ctx.client.create_session( + on_permission_request=PermissionHandler.approve_all, + ) as session: + initial = await session.rpc.visibility.get() + assert initial.synced is False + assert initial.status is None + assert initial.share_url is None + + updated = await session.rpc.visibility.set( + VisibilitySetRequest(status=SessionVisibilityStatus.REPO) + ) + assert updated.synced is False + assert updated.status is None + assert updated.share_url is None + async def test_should_get_and_set_allowall_permissions(self, ctx: E2ETestContext): async with await ctx.client.create_session( on_permission_request=PermissionHandler.approve_all, @@ -110,6 +207,60 @@ async def test_should_get_and_set_allowall_permissions(self, ctx: E2ETestContext PermissionsSetAllowAllRequest(enabled=False) ) + async def test_should_get_context_attribution_and_heaviest_messages_after_turn( + self, ctx: E2ETestContext + ): + async with await ctx.client.create_session( + on_permission_request=PermissionHandler.approve_all, + ) as session: + answer = await session.send_and_wait("Say CONTEXT_METADATA_OK exactly.", timeout=60.0) + assert answer is not None + assert "CONTEXT_METADATA_OK" in (answer.data.content or "") + + attribution = await session.rpc.metadata.get_context_attribution() + assert attribution.context_attribution is not None + context_attribution = attribution.context_attribution + assert context_attribution.total_tokens > 0 + assert len(context_attribution.entries) > 0 + for entry in context_attribution.entries: + assert entry.id.strip() + assert entry.kind.strip() + assert entry.label.strip() + assert entry.tokens >= 0 + for key in entry.attributes or {}: + assert key.strip() + + heaviest = await session.rpc.metadata.get_context_heaviest_messages( + MetadataContextHeaviestMessagesRequest(limit=2) + ) + assert heaviest.total_tokens > 0 + assert len(heaviest.messages) <= 2 + for message in heaviest.messages: + assert message.id.strip() + assert message.tokens >= 0 + + async def test_should_update_and_clear_live_subagent_settings(self, ctx: E2ETestContext): + async with await ctx.client.create_session( + on_permission_request=PermissionHandler.approve_all, + ) as session: + await session.rpc.tools.update_subagent_settings( + UpdateSubagentSettingsRequest( + subagents=SubagentSettings( + { + "general-purpose": SubagentSettingsEntry( + model="claude-haiku-4.5", + effort_level="low", + context_tier=SubagentSettingsEntryContextTier.DEFAULT, + ) + } + ) + ) + ) + + await session.rpc.tools.update_subagent_settings( + UpdateSubagentSettingsRequest(subagents=None) + ) + async def test_should_read_empty_sql_todos_for_fresh_session(self, ctx: E2ETestContext): async with await ctx.client.create_session( on_permission_request=PermissionHandler.approve_all, diff --git a/python/e2e/test_rpc_tasks_and_handlers_e2e.py b/python/e2e/test_rpc_tasks_and_handlers_e2e.py index d712580a67..6a99cbb75d 100644 --- a/python/e2e/test_rpc_tasks_and_handlers_e2e.py +++ b/python/e2e/test_rpc_tasks_and_handlers_e2e.py @@ -14,6 +14,9 @@ from copilot.rpc import ( CommandsHandlePendingCommandRequest, HandlePendingToolCallRequest, + MCPHeadersHandlePendingHeadersRefreshRequest, + MCPHeadersHandlePendingHeadersRefreshRequestKind, + MCPHeadersHandlePendingHeadersRefreshRequestRequest, PermissionDecisionApproveForLocation, PermissionDecisionApproveForLocationApprovalCustomTool, PermissionDecisionApproveForSession, @@ -41,7 +44,10 @@ UIHandlePendingElicitationRequest, UIHandlePendingExitPlanModeRequest, UIHandlePendingSamplingRequest, + UIHandlePendingSessionLimitsExhaustedRequest, UIHandlePendingUserInputRequest, + UISessionLimitsExhaustedResponse, + UISessionLimitsExhaustedResponseAction, UIUnregisterDirectAutoModeSwitchHandlerRequest, UIUserInputResponse, ) @@ -253,6 +259,37 @@ async def test_should_return_expected_results_for_missing_pending_handler_reques ) ) assert location_approval.success is False + + session_limits = await session.rpc.ui.handle_pending_session_limits_exhausted( + UIHandlePendingSessionLimitsExhaustedRequest( + request_id="missing-session-limits-request", + response=UISessionLimitsExhaustedResponse( + action=UISessionLimitsExhaustedResponseAction.CANCEL + ), + ) + ) + assert session_limits.success is False + + headers = await session.rpc.mcp.headers.handle_pending_headers_refresh_request( + MCPHeadersHandlePendingHeadersRefreshRequestRequest( + request_id="missing-headers-refresh-request", + result=MCPHeadersHandlePendingHeadersRefreshRequest( + kind=MCPHeadersHandlePendingHeadersRefreshRequestKind.HEADERS, + headers={"X-SDK-Test": "missing"}, + ), + ) + ) + assert headers.success is False + + no_headers = await session.rpc.mcp.headers.handle_pending_headers_refresh_request( + MCPHeadersHandlePendingHeadersRefreshRequestRequest( + request_id="missing-headers-refresh-none-request", + result=MCPHeadersHandlePendingHeadersRefreshRequest( + kind=MCPHeadersHandlePendingHeadersRefreshRequestKind.NONE, + ), + ) + ) + assert no_headers.success is False finally: await session.disconnect() diff --git a/python/test_client.py b/python/test_client.py index 08076e87c1..5e1b8be634 100644 --- a/python/test_client.py +++ b/python/test_client.py @@ -2272,3 +2272,261 @@ def on_failure(input_data, invocation): }, ) assert result == {"additionalContext": "sync-ok"} + + +class TestGitHubTelemetry: + """Unit tests for the experimental gitHubTelemetry.event consumer surface.""" + + @pytest.mark.asyncio + async def test_create_session_enables_forwarding_when_handler_registered(self): + client = CopilotClient( + connection=RuntimeConnection.for_stdio(path=CLI_PATH), + on_github_telemetry=lambda _notification: None, + ) + await client.start() + + try: + captured = {} + original_request = client._client.request + + async def mock_request(method, params, **kwargs): + captured[method] = params + return await original_request(method, params, **kwargs) + + client._client.request = mock_request + await client.create_session( + on_permission_request=PermissionHandler.approve_all, + ) + assert captured["session.create"]["enableGitHubTelemetryForwarding"] is True + finally: + await client.force_stop() + + @pytest.mark.asyncio + async def test_create_session_omits_forwarding_without_handler(self): + client = CopilotClient(connection=RuntimeConnection.for_stdio(path=CLI_PATH)) + await client.start() + + try: + captured = {} + original_request = client._client.request + + async def mock_request(method, params, **kwargs): + captured[method] = params + return await original_request(method, params, **kwargs) + + client._client.request = mock_request + await client.create_session( + on_permission_request=PermissionHandler.approve_all, + ) + assert "enableGitHubTelemetryForwarding" not in captured["session.create"] + finally: + await client.force_stop() + + @pytest.mark.asyncio + async def test_resume_session_enables_forwarding_when_handler_registered(self): + client = CopilotClient( + connection=RuntimeConnection.for_stdio(path=CLI_PATH), + on_github_telemetry=lambda _notification: None, + ) + await client.start() + + try: + session = await client.create_session( + on_permission_request=PermissionHandler.approve_all + ) + + captured = {} + original_request = client._client.request + + async def mock_request(method, params, **kwargs): + captured[method] = params + if method == "session.resume": + return {"sessionId": session.session_id} + return await original_request(method, params, **kwargs) + + client._client.request = mock_request + await client.resume_session( + session.session_id, + on_permission_request=PermissionHandler.approve_all, + ) + assert captured["session.resume"]["enableGitHubTelemetryForwarding"] is True + finally: + await client.force_stop() + + @pytest.mark.asyncio + async def test_resume_session_omits_forwarding_without_handler(self): + client = CopilotClient(connection=RuntimeConnection.for_stdio(path=CLI_PATH)) + await client.start() + + try: + session = await client.create_session( + on_permission_request=PermissionHandler.approve_all + ) + + captured = {} + original_request = client._client.request + + async def mock_request(method, params, **kwargs): + captured[method] = params + if method == "session.resume": + return {"sessionId": session.session_id} + return await original_request(method, params, **kwargs) + + client._client.request = mock_request + await client.resume_session( + session.session_id, + on_permission_request=PermissionHandler.approve_all, + ) + assert "enableGitHubTelemetryForwarding" not in captured["session.resume"] + finally: + await client.force_stop() + + @pytest.mark.asyncio + async def test_connect_enables_forwarding_when_handler_registered(self): + client = CopilotClient( + connection=RuntimeConnection.for_stdio(path=CLI_PATH), + on_github_telemetry=lambda _notification: None, + ) + captured = {} + + class _FakeClient: + async def request(self, method, params, **kwargs): + captured[method] = params + return {"ok": True, "protocolVersion": 3, "version": "test"} + + client._client = _FakeClient() + await client._verify_protocol_version() + assert captured["connect"]["enableGitHubTelemetryForwarding"] is True + + @pytest.mark.asyncio + async def test_connect_omits_forwarding_without_handler(self): + client = CopilotClient(connection=RuntimeConnection.for_stdio(path=CLI_PATH)) + captured = {} + + class _FakeClient: + async def request(self, method, params, **kwargs): + captured[method] = params + return {"ok": True, "protocolVersion": 3, "version": "test"} + + client._client = _FakeClient() + await client._verify_protocol_version() + assert "enableGitHubTelemetryForwarding" not in captured["connect"] + + @pytest.mark.asyncio + async def test_event_routes_to_handler(self): + from copilot.generated.rpc import GitHubTelemetryNotification + + received: list = [] + + def on_telemetry(notification): + received.append(notification) + + client = CopilotClient( + connection=RuntimeConnection.for_stdio(path=CLI_PATH), + on_github_telemetry=on_telemetry, + ) + await client.start() + + try: + # gitHubTelemetry.event is a JSON-RPC *notification*: the generated + # client-global dispatcher wires it into the notification-handler + # table, never the request-handler table. Regressing to request-style + # dispatch would drop the runtime's id-less telemetry frames. + assert "gitHubTelemetry.event" in client._client.notification_method_handlers + assert "gitHubTelemetry.event" not in client._client.request_handlers + + # Drive a real id-less notification frame through the dispatcher to + # exercise the full from_dict decode + adapter + user-callback path. + client._client._handle_message( + { + "jsonrpc": "2.0", + "method": "gitHubTelemetry.event", + "params": { + "sessionId": "sess-telemetry", + "restricted": True, + "event": { + "kind": "tool_call_executed", + "metrics": {"duration_ms": 12.5}, + "properties": {"tool": "shell"}, + "session_id": "sess-telemetry", + }, + }, + } + ) + + # Notifications dispatch onto the event loop; yield until delivered. + for _ in range(100): + if received: + break + await asyncio.sleep(0.01) + + assert len(received) == 1 + notification = received[0] + assert isinstance(notification, GitHubTelemetryNotification) + assert notification.session_id == "sess-telemetry" + assert notification.restricted is True + assert notification.event.kind == "tool_call_executed" + assert notification.event.metrics["duration_ms"] == 12.5 + assert notification.event.properties["tool"] == "shell" + finally: + await client.force_stop() + + @pytest.mark.asyncio + async def test_event_routes_to_async_handler(self): + from copilot.generated.rpc import GitHubTelemetryNotification + + received: list = [] + delivered = asyncio.Event() + + async def on_telemetry(notification): + await asyncio.sleep(0) + received.append(notification) + delivered.set() + + client = CopilotClient( + connection=RuntimeConnection.for_stdio(path=CLI_PATH), + on_github_telemetry=on_telemetry, + ) + await client.start() + + try: + client._client._handle_message( + { + "jsonrpc": "2.0", + "method": "gitHubTelemetry.event", + "params": { + "sessionId": "sess-async-telemetry", + "restricted": False, + "event": { + "kind": "tool_call_executed", + "metrics": {"duration_ms": 3.5}, + "properties": {"tool": "python"}, + "session_id": "sess-async-telemetry", + }, + }, + } + ) + + await asyncio.wait_for(delivered.wait(), timeout=1) + + assert len(received) == 1 + notification = received[0] + assert isinstance(notification, GitHubTelemetryNotification) + assert notification.session_id == "sess-async-telemetry" + assert notification.restricted is False + assert notification.event.kind == "tool_call_executed" + assert notification.event.metrics["duration_ms"] == 3.5 + assert notification.event.properties["tool"] == "python" + finally: + await client.force_stop() + + @pytest.mark.asyncio + async def test_event_handler_not_registered_without_option(self): + client = CopilotClient(connection=RuntimeConnection.for_stdio(path=CLI_PATH)) + await client.start() + + try: + assert "gitHubTelemetry.event" not in client._client.notification_method_handlers + assert "gitHubTelemetry.event" not in client._client.request_handlers + finally: + await client.force_stop() diff --git a/python/test_event_forward_compatibility.py b/python/test_event_forward_compatibility.py index 13fa4f09e5..7f8c29b6e8 100644 --- a/python/test_event_forward_compatibility.py +++ b/python/test_event_forward_compatibility.py @@ -138,6 +138,22 @@ def test_data_shim_preserves_raw_mapping_values(self): constructed = Data(arguments={"tool_call_id": "call-1"}) assert constructed.to_dict() == {"arguments": {"tool_call_id": "call-1"}} + def test_data_shim_preserves_abbreviation_json_keys_on_round_trip(self): + """Data.from_dict(x).to_dict() should preserve JSON keys with abbreviations. + + Regression test for github/copilot-sdk#1138: keys like userURL, sessionID, + and OAuthToken were rewritten on round-trip because _compat_to_json_key could + not reconstruct the original camelCase abbreviation casing. + """ + for key in ["userURL", "sessionID", "XMLPayload", "serverIP", "OAuthToken"]: + incoming = {key: 42} + assert Data.from_dict(incoming).to_dict() == incoming + + def test_data_shim_preserves_colliding_json_keys_on_round_trip(self): + """Data.from_dict(x).to_dict() should preserve keys with the same Python name.""" + colliding_keys = {"userURL": 42, "userUrl": 43} + assert Data.from_dict(colliding_keys).to_dict() == colliding_keys + def test_missing_optional_fields_remain_none_after_parsing(self): """Generated event models should leave missing optional fields as None. diff --git a/python/test_tools.py b/python/test_tools.py index d583b59c01..c5230385f2 100644 --- a/python/test_tools.py +++ b/python/test_tools.py @@ -3,7 +3,7 @@ import json import pytest -from pydantic import BaseModel, Field +from pydantic import BaseModel, ConfigDict, Field, field_validator from copilot import define_tool from copilot.tools import ( @@ -197,6 +197,91 @@ def failing_tool(params: Params, invocation: ToolInvocation) -> str: # But the actual error is stored internally assert result.error == "secret error message" + async def test_validation_error_is_surfaced_to_llm(self): + class Params(BaseModel): + username: str + + @field_validator("username") + @classmethod + def check_username(cls, v: str) -> str: + if v == "admin": + raise ValueError("username 'admin' is reserved") + return v + + @define_tool("validate", description="A validating tool") + def validating_tool(params: Params) -> str: + return "ok" + + invocation = ToolInvocation( + session_id="s1", + tool_call_id="c1", + tool_name="validate", + arguments={"username": "admin"}, + ) + + result = await validating_tool.handler(invocation) + + assert result.result_type == "failure" + assert result.text_result_for_llm.startswith("Invalid tool arguments:") + assert "username 'admin' is reserved" in result.text_result_for_llm + # Full detail is retained in the debug field. + assert result.error is not None + + async def test_validation_error_extra_forbid_includes_field_name(self): + class Params(BaseModel): + model_config = ConfigDict(extra="forbid") + + request: str + + @define_tool("strict", description="A strict tool") + def strict_tool(params: Params) -> str: + return "ok" + + invocation = ToolInvocation( + session_id="s1", + tool_call_id="c1", + tool_name="strict", + arguments={"request": "ok", "extra_field": "unexpected"}, + ) + + result = await strict_tool.handler(invocation) + + assert result.result_type == "failure" + assert result.text_result_for_llm.startswith("Invalid tool arguments:") + # The offending key name is carried in `loc` even though the generic + # message is "Extra inputs are not permitted". + assert "extra_field" in result.text_result_for_llm + assert result.error is not None + + async def test_validation_error_from_handler_body_is_redacted(self): + class Params(BaseModel): + pass + + class Internal(BaseModel): + count: int + + @define_tool("body", description="A tool that validates internally") + def body_tool(params: Params) -> str: + Internal.model_validate({"count": "secret-not-an-int"}) + return "ok" + + invocation = ToolInvocation( + session_id="s1", + tool_call_id="c1", + tool_name="body", + arguments={}, + ) + + result = await body_tool.handler(invocation) + + assert result.result_type == "failure" + # A ValidationError from the handler body must not be surfaced as an + # argument-validation error; it stays redacted like any other exception. + assert not result.text_result_for_llm.startswith("Invalid tool arguments:") + assert "secret-not-an-int" not in result.text_result_for_llm + assert "error" in result.text_result_for_llm.lower() + assert result.error is not None + async def test_function_style_api(self): class Params(BaseModel): value: str diff --git a/rust/src/errors.rs b/rust/src/errors.rs index 5690f6412c..6e05bbfae1 100644 --- a/rust/src/errors.rs +++ b/rust/src/errors.rs @@ -63,6 +63,12 @@ pub enum ProtocolErrorKind { max: u32, }, + /// The CLI server reported a protocol version that can't be represented by the SDK. + InvalidProtocolVersion { + /// Version reported by the server. + server: i64, + }, + /// The CLI server's protocol version changed between calls. VersionChanged { /// Previously negotiated version. @@ -94,6 +100,9 @@ impl fmt::Display for ProtocolErrorKind { "version mismatch: server={server}, supported={min}\u{2013}{max}" ) } + ProtocolErrorKind::InvalidProtocolVersion { server } => { + write!(f, "invalid protocol version: server={server}") + } ProtocolErrorKind::VersionChanged { previous, current } => { write!(f, "version changed: was {previous}, now {current}") } diff --git a/rust/src/generated/api_types.rs b/rust/src/generated/api_types.rs index f59022b0d3..b364b4a4d8 100644 --- a/rust/src/generated/api_types.rs +++ b/rust/src/generated/api_types.rs @@ -12,7 +12,7 @@ use serde::{Deserialize, Serialize}; use super::session_events::{ AbortReason, ContextTier, McpServerSource, McpServerStatus, PermissionPromptRequest, PermissionRule, ReasoningSummary, SessionLimitsConfig, SessionMode, ShutdownType, SkillSource, - UserToolSessionApproval, + UserToolSessionApproval, Verbosity, }; use crate::types::{RequestId, SessionEvent, SessionId}; @@ -160,8 +160,6 @@ pub mod rpc_methods { pub const SESSIONS_STOPREMOTECONTROL: &str = "sessions.stopRemoteControl"; /// `sessions.getRemoteControlStatus` pub const SESSIONS_GETREMOTECONTROLSTATUS: &str = "sessions.getRemoteControlStatus"; - /// `sessions.pollSpawnedSessions` - pub const SESSIONS_POLLSPAWNEDSESSIONS: &str = "sessions.pollSpawnedSessions"; /// `sessions.registerExtensionToolsOnSession` pub const SESSIONS_REGISTEREXTENSIONTOOLSONSESSION: &str = "sessions.registerExtensionToolsOnSession"; @@ -181,6 +179,8 @@ pub mod rpc_methods { pub const SESSION_GITHUBAUTH_GETSTATUS: &str = "session.gitHubAuth.getStatus"; /// `session.gitHubAuth.setCredentials` pub const SESSION_GITHUBAUTH_SETCREDENTIALS: &str = "session.gitHubAuth.setCredentials"; + /// `session.debug.collectLogs` + pub const SESSION_DEBUG_COLLECTLOGS: &str = "session.debug.collectLogs"; /// `session.canvas.list` pub const SESSION_CANVAS_LIST: &str = "session.canvas.list"; /// `session.canvas.listOpen` @@ -324,8 +324,6 @@ pub mod rpc_methods { pub const SESSION_MCP_UNREGISTEREXTERNALCLIENT: &str = "session.mcp.unregisterExternalClient"; /// `session.mcp.isServerRunning` pub const SESSION_MCP_ISSERVERRUNNING: &str = "session.mcp.isServerRunning"; - /// `session.mcp.oauth.respond` - pub const SESSION_MCP_OAUTH_RESPOND: &str = "session.mcp.oauth.respond"; /// `session.mcp.oauth.handlePendingRequest` pub const SESSION_MCP_OAUTH_HANDLEPENDINGREQUEST: &str = "session.mcp.oauth.handlePendingRequest"; @@ -479,6 +477,12 @@ pub mod rpc_methods { pub const SESSION_METADATA_ACTIVITY: &str = "session.metadata.activity"; /// `session.metadata.contextInfo` pub const SESSION_METADATA_CONTEXTINFO: &str = "session.metadata.contextInfo"; + /// `session.metadata.getContextAttribution` + pub const SESSION_METADATA_GETCONTEXTATTRIBUTION: &str = + "session.metadata.getContextAttribution"; + /// `session.metadata.getContextHeaviestMessages` + pub const SESSION_METADATA_GETCONTEXTHEAVIESTMESSAGES: &str = + "session.metadata.getContextHeaviestMessages"; /// `session.metadata.recordContextChange` pub const SESSION_METADATA_RECORDCONTEXTCHANGE: &str = "session.metadata.recordContextChange"; /// `session.metadata.setWorkingDirectory` @@ -486,6 +490,10 @@ pub mod rpc_methods { /// `session.metadata.recomputeContextTokens` pub const SESSION_METADATA_RECOMPUTECONTEXTTOKENS: &str = "session.metadata.recomputeContextTokens"; + /// `session.settings.snapshot` + pub const SESSION_SETTINGS_SNAPSHOT: &str = "session.settings.snapshot"; + /// `session.settings.evaluatePredicate` + pub const SESSION_SETTINGS_EVALUATEPREDICATE: &str = "session.settings.evaluatePredicate"; /// `session.shell.exec` pub const SESSION_SHELL_EXEC: &str = "session.shell.exec"; /// `session.shell.kill` @@ -603,7 +611,7 @@ pub struct AbortResult { pub success: bool, } -/// Schema for the `AccountAllUsers` type. +/// Authenticated account entry returned by `account.getAllUsers`, with auth info and an optional associated token. /// ///
/// @@ -656,7 +664,7 @@ pub struct AccountGetQuotaRequest { pub git_hub_token: Option, } -/// Schema for the `AccountQuotaSnapshot` type. +/// Quota usage snapshot for a Copilot quota type, including entitlement, used requests, overage, reset date, and remaining percentage. /// ///
/// @@ -765,7 +773,7 @@ pub struct AccountLogoutResult { pub has_more_users: bool, } -/// Schema for the `AgentDiscoveryPath` type. +/// Canonical directory where custom agents can be discovered or created, with scope, preference, and optional project path. /// ///
/// @@ -802,7 +810,7 @@ pub struct AgentDiscoveryPathList { pub paths: Vec, } -/// Schema for the `AgentInfo` type. +/// Custom agent metadata, including identifiers, display details, source, tools, model, MCP servers, skills, and file path. /// ///
/// @@ -1177,13 +1185,16 @@ pub struct AgentsGetDiscoveryPathsRequest { #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct AllowAllPermissionSetResult { - /// Authoritative allow-all state after the mutation + /// Authoritative full allow-all state after the mutation pub enabled: bool, + /// Authoritative allow-all mode after the mutation + #[serde(skip_serializing_if = "Option::is_none")] + pub mode: Option, /// Whether the operation succeeded pub success: bool, } -/// Current full allow-all permission state. +/// Current allow-all permission mode. /// ///
/// @@ -1196,9 +1207,12 @@ pub struct AllowAllPermissionSetResult { pub struct AllowAllPermissionState { /// Whether full allow-all permissions are currently active pub enabled: bool, + /// Current allow-all mode + #[serde(skip_serializing_if = "Option::is_none")] + pub mode: Option, } -/// Schema for the `CopilotUserResponseEndpoints` type. +/// Endpoint URLs from the raw Copilot `/copilot_internal/v2/token` user-response passthrough. /// ///
/// @@ -1219,7 +1233,7 @@ pub struct CopilotUserResponseEndpoints { pub telemetry: Option, } -/// Schema for the `CopilotUserResponseQuotaSnapshotsChat` type. +/// Chat quota snapshot from the raw Copilot user-response passthrough, with entitlement, overage, remaining quota, reset, and billing fields. /// ///
/// @@ -1230,36 +1244,48 @@ pub struct CopilotUserResponseEndpoints { #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct CopilotUserResponseQuotaSnapshotsChat { + /// Number of requests/units included in the entitlement for this period; `-1` denotes an unlimited entitlement. #[serde(skip_serializing_if = "Option::is_none")] pub entitlement: Option, + /// Whether the user currently has quota available; when `false` and not unlimited, further requests are blocked until the quota resets. #[serde(rename = "has_quota", skip_serializing_if = "Option::is_none")] pub has_quota: Option, + /// Count of additional pay-per-request usage consumed this period beyond the entitlement. #[serde(rename = "overage_count", skip_serializing_if = "Option::is_none")] pub overage_count: Option, + /// Whether usage may continue at pay-per-request rates once the entitlement is exhausted. #[serde(rename = "overage_permitted", skip_serializing_if = "Option::is_none")] pub overage_permitted: Option, + /// Percentage of the entitlement remaining at the snapshot timestamp. #[serde(rename = "percent_remaining", skip_serializing_if = "Option::is_none")] pub percent_remaining: Option, + /// Identifier of the quota bucket this snapshot describes. #[serde(rename = "quota_id", skip_serializing_if = "Option::is_none")] pub quota_id: Option, + /// Amount of quota remaining at the snapshot timestamp. #[serde(rename = "quota_remaining", skip_serializing_if = "Option::is_none")] pub quota_remaining: Option, + /// Unix epoch time, in seconds, when this quota next resets. #[serde(rename = "quota_reset_at", skip_serializing_if = "Option::is_none")] pub quota_reset_at: Option, + /// Remaining entitlement/quota amount at the snapshot timestamp. #[serde(skip_serializing_if = "Option::is_none")] pub remaining: Option, + /// UTC timestamp when this snapshot was captured. #[serde(rename = "timestamp_utc", skip_serializing_if = "Option::is_none")] pub timestamp_utc: Option, + /// Whether this category uses usage-based (token/AI-credit) billing rather than a fixed premium-request count. #[serde( rename = "token_based_billing", skip_serializing_if = "Option::is_none" )] pub token_based_billing: Option, + /// Whether the entitlement for this category is unlimited. #[serde(skip_serializing_if = "Option::is_none")] pub unlimited: Option, } -/// Schema for the `CopilotUserResponseQuotaSnapshotsCompletions` type. +/// Completions quota snapshot from the raw Copilot user-response passthrough, with entitlement, overage, remaining quota, reset, and billing fields. /// ///
/// @@ -1270,36 +1296,48 @@ pub struct CopilotUserResponseQuotaSnapshotsChat { #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct CopilotUserResponseQuotaSnapshotsCompletions { + /// Number of requests/units included in the entitlement for this period; `-1` denotes an unlimited entitlement. #[serde(skip_serializing_if = "Option::is_none")] pub entitlement: Option, + /// Whether the user currently has quota available; when `false` and not unlimited, further requests are blocked until the quota resets. #[serde(rename = "has_quota", skip_serializing_if = "Option::is_none")] pub has_quota: Option, + /// Count of additional pay-per-request usage consumed this period beyond the entitlement. #[serde(rename = "overage_count", skip_serializing_if = "Option::is_none")] pub overage_count: Option, + /// Whether usage may continue at pay-per-request rates once the entitlement is exhausted. #[serde(rename = "overage_permitted", skip_serializing_if = "Option::is_none")] pub overage_permitted: Option, + /// Percentage of the entitlement remaining at the snapshot timestamp. #[serde(rename = "percent_remaining", skip_serializing_if = "Option::is_none")] pub percent_remaining: Option, + /// Identifier of the quota bucket this snapshot describes. #[serde(rename = "quota_id", skip_serializing_if = "Option::is_none")] pub quota_id: Option, + /// Amount of quota remaining at the snapshot timestamp. #[serde(rename = "quota_remaining", skip_serializing_if = "Option::is_none")] pub quota_remaining: Option, + /// Unix epoch time, in seconds, when this quota next resets. #[serde(rename = "quota_reset_at", skip_serializing_if = "Option::is_none")] pub quota_reset_at: Option, + /// Remaining entitlement/quota amount at the snapshot timestamp. #[serde(skip_serializing_if = "Option::is_none")] pub remaining: Option, + /// UTC timestamp when this snapshot was captured. #[serde(rename = "timestamp_utc", skip_serializing_if = "Option::is_none")] pub timestamp_utc: Option, + /// Whether this category uses usage-based (token/AI-credit) billing rather than a fixed premium-request count. #[serde( rename = "token_based_billing", skip_serializing_if = "Option::is_none" )] pub token_based_billing: Option, + /// Whether the entitlement for this category is unlimited. #[serde(skip_serializing_if = "Option::is_none")] pub unlimited: Option, } -/// Schema for the `CopilotUserResponseQuotaSnapshotsPremiumInteractions` type. +/// Premium-interactions quota snapshot from the raw Copilot user-response passthrough, with entitlement, overage, remaining quota, reset, and billing fields. /// ///
/// @@ -1310,36 +1348,48 @@ pub struct CopilotUserResponseQuotaSnapshotsCompletions { #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct CopilotUserResponseQuotaSnapshotsPremiumInteractions { + /// Number of requests/units included in the entitlement for this period; `-1` denotes an unlimited entitlement. #[serde(skip_serializing_if = "Option::is_none")] pub entitlement: Option, + /// Whether the user currently has quota available; when `false` and not unlimited, further requests are blocked until the quota resets. #[serde(rename = "has_quota", skip_serializing_if = "Option::is_none")] pub has_quota: Option, + /// Count of additional pay-per-request usage consumed this period beyond the entitlement. #[serde(rename = "overage_count", skip_serializing_if = "Option::is_none")] pub overage_count: Option, + /// Whether usage may continue at pay-per-request rates once the entitlement is exhausted. #[serde(rename = "overage_permitted", skip_serializing_if = "Option::is_none")] pub overage_permitted: Option, + /// Percentage of the entitlement remaining at the snapshot timestamp. #[serde(rename = "percent_remaining", skip_serializing_if = "Option::is_none")] pub percent_remaining: Option, + /// Identifier of the quota bucket this snapshot describes. #[serde(rename = "quota_id", skip_serializing_if = "Option::is_none")] pub quota_id: Option, + /// Amount of quota remaining at the snapshot timestamp. #[serde(rename = "quota_remaining", skip_serializing_if = "Option::is_none")] pub quota_remaining: Option, + /// Unix epoch time, in seconds, when this quota next resets. #[serde(rename = "quota_reset_at", skip_serializing_if = "Option::is_none")] pub quota_reset_at: Option, + /// Remaining entitlement/quota amount at the snapshot timestamp. #[serde(skip_serializing_if = "Option::is_none")] pub remaining: Option, + /// UTC timestamp when this snapshot was captured. #[serde(rename = "timestamp_utc", skip_serializing_if = "Option::is_none")] pub timestamp_utc: Option, + /// Whether this category uses usage-based (token/AI-credit) billing rather than a fixed premium-request count. #[serde( rename = "token_based_billing", skip_serializing_if = "Option::is_none" )] pub token_based_billing: Option, + /// Whether the entitlement for this category is unlimited. #[serde(skip_serializing_if = "Option::is_none")] pub unlimited: Option, } -/// Schema for the `CopilotUserResponseQuotaSnapshots` type. +/// Quota snapshot map from the raw Copilot user-response passthrough, with chat, completions, premium-interactions, and other entries. /// ///
/// @@ -1350,13 +1400,13 @@ pub struct CopilotUserResponseQuotaSnapshotsPremiumInteractions { #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct CopilotUserResponseQuotaSnapshots { - /// Schema for the `CopilotUserResponseQuotaSnapshotsChat` type. + /// Chat quota snapshot from the raw Copilot user-response passthrough, with entitlement, overage, remaining quota, reset, and billing fields. #[serde(skip_serializing_if = "Option::is_none")] pub chat: Option, - /// Schema for the `CopilotUserResponseQuotaSnapshotsCompletions` type. + /// Completions quota snapshot from the raw Copilot user-response passthrough, with entitlement, overage, remaining quota, reset, and billing fields. #[serde(skip_serializing_if = "Option::is_none")] pub completions: Option, - /// Schema for the `CopilotUserResponseQuotaSnapshotsPremiumInteractions` type. + /// Premium-interactions quota snapshot from the raw Copilot user-response passthrough, with entitlement, overage, remaining quota, reset, and billing fields. #[serde( rename = "premium_interactions", skip_serializing_if = "Option::is_none" @@ -1375,91 +1425,115 @@ pub struct CopilotUserResponseQuotaSnapshots { #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct CopilotUserResponse { + /// Copilot access SKU identifier (e.g. `free_limited_copilot`, `copilot_for_business_seat_quota`) used to gate model and feature access. #[serde(rename = "access_type_sku", skip_serializing_if = "Option::is_none")] pub access_type_sku: Option, + /// Opaque analytics tracking identifier for the user, forwarded from the Copilot API. #[serde( rename = "analytics_tracking_id", skip_serializing_if = "Option::is_none" )] pub analytics_tracking_id: Option, + /// Date the Copilot seat was assigned to the user, if applicable. #[serde(rename = "assigned_date", skip_serializing_if = "Option::is_none")] pub assigned_date: Option, + /// Whether the user is eligible to sign up for the free/limited Copilot tier. #[serde( rename = "can_signup_for_limited", skip_serializing_if = "Option::is_none" )] pub can_signup_for_limited: Option, + /// Whether the user is able to upgrade their Copilot plan. #[serde(rename = "can_upgrade_plan", skip_serializing_if = "Option::is_none")] pub can_upgrade_plan: Option, + /// Whether Copilot chat is enabled for the user. #[serde(rename = "chat_enabled", skip_serializing_if = "Option::is_none")] pub chat_enabled: Option, + /// Whether CLI remote control is enabled for the user. #[serde( rename = "cli_remote_control_enabled", skip_serializing_if = "Option::is_none" )] pub cli_remote_control_enabled: Option, + /// Whether cloud session storage is enabled for the user. #[serde( rename = "cloud_session_storage_enabled", skip_serializing_if = "Option::is_none" )] pub cloud_session_storage_enabled: Option, + /// Whether the Codex agent is enabled for the user. #[serde( rename = "codex_agent_enabled", skip_serializing_if = "Option::is_none" )] pub codex_agent_enabled: Option, + /// Copilot plan name for the user (e.g. `individual`, `business`, `enterprise`). #[serde(rename = "copilot_plan", skip_serializing_if = "Option::is_none")] pub copilot_plan: Option, + /// Whether `.copilotignore` content-exclusion support is enabled for the user. #[serde( rename = "copilotignore_enabled", skip_serializing_if = "Option::is_none" )] pub copilotignore_enabled: Option, - /// Schema for the `CopilotUserResponseEndpoints` type. + /// Endpoint URLs from the raw Copilot `/copilot_internal/v2/token` user-response passthrough. #[serde(skip_serializing_if = "Option::is_none")] pub endpoints: Option, + /// Whether MCP (Model Context Protocol) support is enabled for the user. #[serde(rename = "is_mcp_enabled", skip_serializing_if = "Option::is_none")] pub is_mcp_enabled: Option, + /// Whether the user is a GitHub/Microsoft staff member. #[serde(rename = "is_staff", skip_serializing_if = "Option::is_none")] pub is_staff: Option, + /// Per-category quota allotments for free/limited-tier users, keyed by quota category. #[serde( rename = "limited_user_quotas", skip_serializing_if = "Option::is_none" )] pub limited_user_quotas: Option>, + /// Date the free/limited-tier user's quotas next reset, as a raw string from the Copilot API. #[serde( rename = "limited_user_reset_date", skip_serializing_if = "Option::is_none" )] pub limited_user_reset_date: Option, + /// GitHub login of the authenticated user. #[serde(skip_serializing_if = "Option::is_none")] pub login: Option, + /// Per-category monthly quota allotments, keyed by quota category. #[serde(rename = "monthly_quotas", skip_serializing_if = "Option::is_none")] pub monthly_quotas: Option>, + /// Organizations the user belongs to, each with an optional login and display name. #[serde(rename = "organization_list", skip_serializing_if = "Option::is_none")] pub organization_list: Option, + /// Logins of the organizations the user belongs to. #[serde( rename = "organization_login_list", skip_serializing_if = "Option::is_none" )] pub organization_login_list: Option>, + /// Date the user's usage quota next resets, as a raw string from the Copilot API; see `quota_reset_date_utc` for the UTC-normalized value. #[serde(rename = "quota_reset_date", skip_serializing_if = "Option::is_none")] pub quota_reset_date: Option, + /// UTC-normalized form of `quota_reset_date` (the date the user's usage quota next resets). #[serde( rename = "quota_reset_date_utc", skip_serializing_if = "Option::is_none" )] pub quota_reset_date_utc: Option, - /// Schema for the `CopilotUserResponseQuotaSnapshots` type. + /// Quota snapshot map from the raw Copilot user-response passthrough, with chat, completions, premium-interactions, and other entries. #[serde(rename = "quota_snapshots", skip_serializing_if = "Option::is_none")] pub quota_snapshots: Option, + /// Whether the user's telemetry is subject to restricted-data handling. #[serde( rename = "restricted_telemetry", skip_serializing_if = "Option::is_none" )] pub restricted_telemetry: Option, + /// Raw passthrough of the Copilot API `te` flag for the user (an opaque server-side eligibility signal surfaced in telemetry); not otherwise interpreted by the runtime. #[serde(skip_serializing_if = "Option::is_none")] pub te: Option, + /// Whether the account is on usage-based (token/AI-credit) billing rather than a fixed premium-request quota. #[serde( rename = "token_based_billing", skip_serializing_if = "Option::is_none" @@ -1467,7 +1541,7 @@ pub struct CopilotUserResponse { pub token_based_billing: Option, } -/// Schema for the `ApiKeyAuthInfo` type. +/// Authentication-info variant for API-key authentication to a non-GitHub LLM provider, carrying the secret `apiKey` and host. /// ///
/// @@ -2369,6 +2443,23 @@ pub struct CapiSessionOptions { pub enable_web_socket_responses: Option, } +/// A literal choice the command input accepts, with a human-facing description +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SlashCommandInputChoice { + /// Human-readable description shown alongside the choice + pub description: String, + /// The literal choice value (e.g. 'on', 'off', 'show') + pub name: String, +} + /// Optional unstructured input hint /// ///
@@ -2380,6 +2471,9 @@ pub struct CapiSessionOptions { #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct SlashCommandInput { + /// Optional literal choices the input accepts, each with a human-facing description; clients may render these as selectable options + #[serde(skip_serializing_if = "Option::is_none")] + pub choices: Option>, /// Optional completion hint for the input (e.g. 'directory' for filesystem path completion) #[serde(skip_serializing_if = "Option::is_none")] pub completion: Option, @@ -2393,7 +2487,7 @@ pub struct SlashCommandInput { pub required: Option, } -/// Schema for the `SlashCommandInfo` type. +/// Slash-command metadata with name, aliases, description, kind, input hint, execution allowance, and schedulability. /// ///
/// @@ -2714,7 +2808,7 @@ pub struct ConnectRemoteSessionParams { pub session_id: SessionId, } -/// Optional connection token presented by the SDK client during the handshake. +/// Parameters for the `server.connect` handshake: an optional connection token and optional connection-level opt-ins (e.g. GitHub telemetry forwarding). /// ///
/// @@ -2725,6 +2819,9 @@ pub struct ConnectRemoteSessionParams { #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub(crate) struct ConnectRequest { + /// Opt this connection in to GitHub telemetry forwarding for its lifetime. When set, the runtime forwards every internal telemetry event it emits — across all sessions, plus sessionless events — to this connection over the `gitHubTelemetry.event` notification, in addition to the runtime's normal GitHub/CTS emission (dual-write). Intended for first-party hosts that re-emit the events into their own telemetry stores. Both unrestricted and restricted events are forwarded, each tagged with a `restricted` discriminator; a backstop drops restricted events when restricted telemetry is disabled. + #[serde(skip_serializing_if = "Option::is_none")] + pub enable_git_hub_telemetry_forwarding: Option, /// Connection token; required when the server was started with COPILOT_CONNECTION_TOKEN #[serde(skip_serializing_if = "Option::is_none")] pub token: Option, @@ -2749,7 +2846,28 @@ pub(crate) struct ConnectResult { pub version: String, } -/// Schema for the `CopilotApiTokenAuthInfo` type. +/// A single large message currently in context. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ContextHeaviestMessage { + /// Stable identifier for this message within the snapshot. + pub id: String, + /// Human-readable source label, e.g. `tool: bash` or `skill: tmux`. Presentation-only. + pub label: String, + /// Role of the chat message (`user`, `assistant`, or `tool`). + pub role: String, + /// Token count currently in context for this individual message. + pub tokens: i64, +} + +/// Authentication-info variant for direct Copilot API token auth sourced from environment variables, with public GitHub host. /// ///
/// @@ -2823,7 +2941,167 @@ pub struct CurrentToolMetadata { pub namespaced_name: Option, } -/// Schema for the `DiscoveredMcpServer` type. +/// A file included in the redacted debug bundle. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct DebugCollectLogsCollectedEntry { + /// Relative path of the file in the staged bundle/archive. + pub bundle_path: String, + /// Redacted output size in bytes. + pub size_bytes: i64, + /// Source category for this entry. + pub source: DebugCollectLogsSource, +} + +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct DebugCollectLogsDestinationArchive { + pub kind: DebugCollectLogsDestinationArchiveKind, + /// When true, create the archive atomically without overwriting an existing file by appending ` (N)` before the extension as needed. Defaults to false. + #[serde(skip_serializing_if = "Option::is_none")] + pub no_overwrite: Option, + /// Absolute or server-relative path for the .tgz archive to create. + pub output_path: String, +} + +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct DebugCollectLogsDestinationDirectory { + pub kind: DebugCollectLogsDestinationDirectoryKind, + /// Directory where redacted files should be staged. The directory is created if needed. + pub output_directory: String, +} + +/// A caller-provided server-local file or directory to include in the debug bundle. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct DebugCollectLogsEntry { + /// Relative path to use inside the staged bundle/archive. + pub bundle_path: String, + /// Kind of source path to include. + pub kind: DebugCollectLogsEntryKind, + /// Server-local source path to read. + pub path: String, + /// How text content from this entry should be redacted. Defaults to plain-text. + #[serde(skip_serializing_if = "Option::is_none")] + pub redaction: Option, + /// When true, collection fails if this entry cannot be read. Defaults to false, which records the entry in `skippedEntries`. + #[serde(skip_serializing_if = "Option::is_none")] + pub required: Option, +} + +/// Built-in session diagnostics to include in the bundle. Omitted fields default to true. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct DebugCollectLogsInclude { + /// Server-local path to the current process log. When set, it is included as `process.log` and its directory is searched for prior logs from the same session. + #[serde(skip_serializing_if = "Option::is_none")] + pub current_process_log_path: Option, + /// Include the session event log (`events.jsonl`). Defaults to true. + #[serde(skip_serializing_if = "Option::is_none")] + pub events: Option, + /// Server-local path to the session's events.jsonl file. Internal callers normally omit this and let the runtime derive it from the session. + #[serde(skip_serializing_if = "Option::is_none")] + pub events_path: Option, + /// Maximum number of previous process logs to include. Defaults to 5. + #[serde(skip_serializing_if = "Option::is_none")] + pub previous_process_log_limit: Option, + /// Server-local process log directory to search when `currentProcessLogPath` is unavailable, useful for collecting logs for inactive sessions. + #[serde(skip_serializing_if = "Option::is_none")] + pub process_log_directory: Option, + /// Include process logs for the session. Defaults to true. + #[serde(skip_serializing_if = "Option::is_none")] + pub process_logs: Option, + /// Include interactive shell logs written under the session's `shell-logs` directory. Defaults to true. + #[serde(skip_serializing_if = "Option::is_none")] + pub shell_logs: Option, +} + +/// Options for collecting a redacted session debug bundle. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct DebugCollectLogsRequest { + /// Caller-provided server-local files or directories to include in addition to the runtime's built-in session diagnostics. This lets host applications add their own diagnostics without changing the API shape. + #[serde(skip_serializing_if = "Option::is_none")] + pub additional_entries: Option>, + /// Where the redacted bundle should be written. Use `archive` to produce a .tgz, or `directory` to stage redacted files for caller-managed upload/post-processing. + pub destination: DebugCollectLogsDestination, + /// Which built-in session diagnostics to include. Omitted fields default to true. + #[serde(skip_serializing_if = "Option::is_none")] + pub include: Option, +} + +/// An optional debug bundle entry that could not be included. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct DebugCollectLogsSkippedEntry { + /// Relative path requested for this bundle entry. + pub bundle_path: String, + /// Server-local source path that could not be read. + #[serde(skip_serializing_if = "Option::is_none")] + pub path: Option, + /// Reason the entry was skipped. + pub reason: String, +} + +/// Result of collecting a redacted debug bundle. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct DebugCollectLogsResult { + /// Files included in the redacted bundle. + pub entries: Vec, + /// Destination kind that was written. + pub kind: DebugCollectLogsResultKind, + /// Actual archive path or staging directory path written. This may differ from the requested path when no-overwrite suffixing or fallback-to-temp-directory was needed. + pub path: String, + /// Optional files or directories that could not be included. + #[serde(skip_serializing_if = "Option::is_none")] + pub skipped_entries: Option>, +} + +/// MCP server discovered by `mcp.discover`, with config source, optional plugin source, transport type, and enabled state. /// ///
/// @@ -2881,7 +3159,7 @@ pub struct EnqueueCommandResult { pub queued: bool, } -/// Schema for the `EnvAuthInfo` type. +/// Authentication-info variant for a token sourced from an environment variable, with host, optional login, token, and env var name. /// ///
/// @@ -3020,7 +3298,7 @@ pub struct ExecuteCommandResult { pub error: Option, } -/// Schema for the `Extension` type. +/// Discovered extension metadata, including source-qualified ID, name, discovery source, status, and optional process ID. /// ///
/// @@ -3161,6 +3439,9 @@ pub struct ExternalToolTextResultForLlm { pub session_log: Option, /// Text result returned to the model pub text_result_for_llm: String, + /// Tool references returned by a tool-search override: names of deferred tools to surface to the model. When set, the tool result is materialized as `tool_reference` content blocks (rather than plain text) so the model knows which deferred tools are now available. + #[serde(skip_serializing_if = "Option::is_none")] + pub tool_references: Option>, /// Optional tool-specific telemetry #[serde(skip_serializing_if = "Option::is_none")] pub tool_telemetry: Option>, @@ -3423,7 +3704,7 @@ pub struct FolderTrustCheckResult { pub trusted: bool, } -/// Schema for the `GhCliAuthInfo` type. +/// Authentication-info variant for GitHub CLI credentials, carrying host, login, and the `gh auth token` value. /// ///
/// @@ -3536,7 +3817,7 @@ pub struct GitHubTelemetryEvent { pub session_id: Option, } -/// Payload for a `gitHubTelemetry.event` notification: a single GitHub telemetry event the runtime forwards to a host connection that opted into telemetry forwarding for the session. +/// 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. /// ///
/// @@ -3551,8 +3832,9 @@ pub struct GitHubTelemetryNotification { pub event: GitHubTelemetryEvent, /// Whether this is a restricted telemetry event (cli.restricted_telemetry). Hosts must route restricted events to first-party Microsoft stores only. pub restricted: bool, - /// Session the telemetry event belongs to. - pub session_id: SessionId, + /// Session the telemetry event belongs to, when it is session-scoped. Omitted for sessionless events (for example, `server.sendTelemetry` calls with no session id), which are still forwarded to opted-in connections. + #[serde(skip_serializing_if = "Option::is_none")] + pub session_id: Option, } /// Pending external tool call request ID, with the tool result or an error describing why it failed. @@ -3735,7 +4017,7 @@ pub struct HistoryTruncateResult { pub events_removed: i64, } -/// Schema for the `HMACAuthInfo` type. +/// Authentication-info variant for GitHub-internal HMAC auth, carrying the public GitHub host and HMAC secret. /// ///
/// @@ -3757,7 +4039,7 @@ pub struct HMACAuthInfo { pub r#type: HMACAuthInfoType, } -/// Schema for the `InstalledPlugin` type. +/// Installed plugin record from global state, with marketplace, version, install time, enabled state, cache path, and source. /// ///
/// @@ -3813,7 +4095,7 @@ pub struct InstalledPluginInfo { pub version: Option, } -/// Schema for the `InstalledPluginSourceGitHub` type. +/// Source descriptor for a direct GitHub plugin install, with `owner/repo`, optional ref, and optional subpath. /// ///
/// @@ -3833,7 +4115,7 @@ pub struct InstalledPluginSourceGitHub { pub source: InstalledPluginSourceGitHubSource, } -/// Schema for the `InstalledPluginSourceLocal` type. +/// Source descriptor for a direct local plugin install, with a local filesystem path. /// ///
/// @@ -3849,7 +4131,7 @@ pub struct InstalledPluginSourceLocal { pub source: InstalledPluginSourceLocalSource, } -/// Schema for the `InstalledPluginSourceUrl` type. +/// Source descriptor for a direct URL plugin install, with URL, optional ref, and optional subpath. /// ///
/// @@ -3869,7 +4151,7 @@ pub struct InstalledPluginSourceUrl { pub url: String, } -/// Schema for the `InstructionDiscoveryPath` type. +/// Canonical file or directory where custom instructions can be discovered or created, with location, kind, preference, and project path. /// ///
/// @@ -3946,7 +4228,7 @@ pub struct InstructionsGetDiscoveryPathsRequest { pub project_paths: Option>, } -/// Schema for the `InstructionSource` type. +/// Loaded instruction source for a session, including path, content, category, location, applicability, and optional description. /// ///
/// @@ -4029,9 +4311,18 @@ pub struct LlmInferenceHttpRequestChunkResult {} #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct LlmInferenceHttpRequestStartRequest { + /// Stable per-agent-instance id attributing this request to a specific agent trajectory. Present when the request originates from an agent turn; absent for requests issued outside any agent context (e.g. some SDK callers). A request with an `agentId` but no `parentAgentId` is a root-agent request; one carrying both is a subagent request. Sourced from the runtime's per-request agent context and surfaced on the envelope independently of transport, so it is available for both first-party (CAPI) and BYOK/custom-provider requests; on the CAPI transport the runtime derives the upstream `X-Agent-Task-Id` header from this same context. Consumers routing each provider call to a training trajectory should key on this rather than on lifecycle events, since it is available on the request path before sampling. + #[serde(skip_serializing_if = "Option::is_none")] + pub agent_id: Option, pub headers: HashMap>, + /// Coarse classification of the interaction that produced this request. Open string for forward-compatibility; known values include `conversation-agent`, `conversation-subagent`, `conversation-sampling`, `conversation-background`, `conversation-compaction`, and `conversation-user`. Absent when the runtime did not classify the request. Comes from the runtime's per-request agent context independently of transport; on the CAPI transport the runtime derives the upstream `X-Interaction-Type` header from this same context. + #[serde(skip_serializing_if = "Option::is_none")] + pub interaction_type: Option, /// HTTP method, e.g. GET, POST. pub method: String, + /// Id of the parent agent that spawned the agent issuing this request. Present only for subagent requests; absent for root-agent requests and non-agent requests. Combined with `agentId`, this lets consumers attribute a call to a child trajectory versus the root. Like `agentId`, it comes from the runtime's per-request agent context independently of transport; on the CAPI transport the runtime derives the upstream `X-Parent-Agent-Id` header from this same context. + #[serde(skip_serializing_if = "Option::is_none")] + pub parent_agent_id: Option, /// Opaque runtime-minted id, unique per in-flight request. The SDK uses this to correlate httpRequestChunk frames and to address its httpResponseStart / httpResponseChunk replies back to the runtime. pub request_id: RequestId, /// Id of the runtime session that triggered this request, when one is in scope. Absent for requests issued outside any session (e.g. startup model-catalog or capability resolution). This is a payload field — not a dispatch key — because the client-global API is registered process-wide rather than per session. @@ -4186,7 +4477,7 @@ pub struct SessionContext { pub repository: Option, } -/// Schema for the `LocalSessionMetadataValue` type. +/// Persisted local session metadata, including identifiers, timestamps, summary/name, client, context, detached state, and task ID. /// ///
/// @@ -4375,7 +4666,7 @@ pub struct MarketplaceListResult { pub marketplaces: Vec, } -/// Schema for the `MarketplaceRefreshEntry` type. +/// Per-marketplace refresh result, including marketplace name, success flag, and optional failure error. /// ///
/// @@ -4428,7 +4719,7 @@ pub struct MarketplaceRemoveResult { pub removed: bool, } -/// Schema for the `McpAllowedServer` type. +/// MCP server allowed by policy, with server name and optional PII-free explanatory note. /// ///
/// @@ -4638,7 +4929,7 @@ pub struct McpAppsReadResourceRequest { pub uri: String, } -/// Schema for the `McpAppsResourceContent` type. +/// MCP Apps resource content with URI, optional MIME type, text or base64 blob, and resource metadata. /// ///
/// @@ -4978,7 +5269,7 @@ pub struct McpExecuteSamplingParams { pub server_name: String, } -/// Schema for the `McpFilteredServer` type. +/// MCP server filtered by policy, with name, reason, optional redacted reason, and enterprise login. /// ///
/// @@ -5151,7 +5442,7 @@ pub struct McpListToolsRequest { pub server_name: String, } -/// Schema for the `McpTools` type. +/// MCP tool metadata with tool name and optional description. /// ///
/// @@ -5288,37 +5579,6 @@ pub struct McpOauthLoginResult { pub authorization_url: Option, } -/// MCP OAuth request id and optional provider response. -/// -///
-/// -/// **Experimental.** This type is part of an experimental wire-protocol surface -/// and may change or be removed in future SDK or CLI releases. -/// -///
-#[derive(Debug, Clone, Default, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub(crate) struct McpOauthRespondRequest { - /// In-process OAuthClientProvider instance, or omitted to deny. Marked internal: cannot be serialized across the JSON-RPC boundary. - #[doc(hidden)] - #[serde(skip_serializing_if = "Option::is_none")] - pub(crate) provider: Option, - /// OAuth request identifier from mcp.oauth_required - pub request_id: RequestId, -} - -/// Empty result after recording the MCP OAuth response. -/// -///
-/// -/// **Experimental.** This type is part of an experimental wire-protocol surface -/// and may change or be removed in future SDK or CLI releases. -/// -///
-#[derive(Debug, Clone, Default, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct McpOauthRespondResult {} - /// Registration parameters for an external MCP client. /// ///
@@ -5413,7 +5673,7 @@ pub struct McpSamplingExecutionResult { pub result: Option, } -/// Schema for the `McpServer` type. +/// MCP server status entry, including config source/plugin source and any connection error. /// ///
/// @@ -5683,6 +5943,93 @@ pub struct MemoryConfiguration { pub enabled: bool, } +/// Successful compaction history for the session. +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct MetadataContextAttributionResultContextAttributionCompactions { + /// Number of successful compactions in this session. + pub count: i64, +} + +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct MetadataContextAttributionResultContextAttributionEntriesItem { + /// Supplementary per-entry metadata (e.g. `messageCount`, `role`, `evictable`, `pluginSource`). Values are stringified; parse as needed and ignore unrecognized keys. + #[serde(skip_serializing_if = "Option::is_none")] + pub attributes: Option>, + /// Identifier for this entry, formed by joining its `kind` and source name (e.g. `tool:bash`, `skill:tmux`, `toolDefinition:bash`); unique within the snapshot. Use it to match the same entry across snapshots, to correlate with other APIs (skill/agent/MCP registries), and as the `parentId` target for nesting. Distinct from the human-facing `label`. + pub id: String, + /// Source category for this entry. Not a closed set — tolerate unknown values. Known values today: `skill`, `subagent`, `mcpServer`, `tool`, `system`, `toolDefinition`, `plugin`. + pub kind: String, + /// Human-readable display label, e.g. `bash` or `skill: tmux`. Presentation-only; may be localized/reformatted without notice — do not key off it. + pub label: String, + /// Optional `id` of the parent entry: e.g. a `plugin` entry parenting its `skill`/`mcpServer` entries, or the `system` entry parenting `toolDefinition` entries. Omitted for top-level entries. + #[serde(skip_serializing_if = "Option::is_none")] + pub parent_id: Option, + /// Token count currently in context attributable to this entry. + pub tokens: i64, +} + +/// Per-source token attribution snapshot for the current context window. The heaviest individual messages are available separately via `metadata.getContextHeaviestMessages`. +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct MetadataContextAttributionResultContextAttribution { + /// Successful compaction history for the session. + pub compactions: MetadataContextAttributionResultContextAttributionCompactions, + /// Flat list of per-source attribution entries. Group by `kind` and render unrecognized kinds generically. Nesting and rollups are expressed via `parentId`. + pub entries: Vec, + /// Total token count of the current context window the entries are measured against (system message + conversation messages + tool definitions — the same total reported by /context). Divide an entry's `tokens` by this to derive its share. + pub total_tokens: i64, +} + +/// Per-source attribution breakdown for the session's current context window, or null if uninitialized. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct MetadataContextAttributionResult { + /// Per-source context-window attribution, or null if the session has not yet been initialized (no system prompt or tool metadata cached). + pub context_attribution: Option, +} + +/// Parameters for the heaviest-messages query. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct MetadataContextHeaviestMessagesRequest { + /// Maximum number of messages to return, most-expensive first. Omit for the server default. + #[serde(skip_serializing_if = "Option::is_none")] + pub limit: Option, +} + +/// The heaviest individual messages in the session's context window, most-expensive first. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct MetadataContextHeaviestMessagesResult { + /// Heaviest messages, most-expensive first. + pub messages: Vec, + /// Total token count of the current context window, so callers can compute each message's share without a second call. + pub total_tokens: i64, +} + /// Model identifier and token limits used to compute the context-info breakdown. /// ///
@@ -6142,7 +6489,7 @@ pub struct ModelPolicy { pub terms: Option, } -/// Schema for the `Model` type. +/// Copilot model metadata, including identifier, display name, capabilities, policy, billing, reasoning efforts, and picker categories. /// ///
/// @@ -6378,6 +6725,9 @@ pub struct ModelSwitchToRequest { /// Reasoning summary mode to request for supported model clients #[serde(skip_serializing_if = "Option::is_none")] pub reasoning_summary: Option, + /// Output verbosity level to request for supported models + #[serde(skip_serializing_if = "Option::is_none")] + pub verbosity: Option, } /// The model identifier active on the session after the switch. @@ -6528,7 +6878,7 @@ pub struct NameSetRequest { pub name: String, } -/// Schema for the `OptionsUpdateAdditionalContentExclusionPolicyRuleSource` type. +/// Source descriptor for a `session.options.update` content-exclusion rule, with source name and type. /// ///
/// @@ -6543,7 +6893,7 @@ pub struct OptionsUpdateAdditionalContentExclusionPolicyRuleSource { pub r#type: String, } -/// Schema for the `OptionsUpdateAdditionalContentExclusionPolicyRule` type. +/// Single content-exclusion rule supplied to `session.options.update`, with paths, match conditions, and source. /// ///
/// @@ -6559,11 +6909,11 @@ pub struct OptionsUpdateAdditionalContentExclusionPolicyRule { #[serde(skip_serializing_if = "Option::is_none")] pub if_none_match: Option>, pub paths: Vec, - /// Schema for the `OptionsUpdateAdditionalContentExclusionPolicyRuleSource` type. + /// Source descriptor for a `session.options.update` content-exclusion rule, with source name and type. pub source: OptionsUpdateAdditionalContentExclusionPolicyRuleSource, } -/// Schema for the `OptionsUpdateAdditionalContentExclusionPolicy` type. +/// Content-exclusion policy supplied to `session.options.update`, with rules, last-updated data, and scope. /// ///
/// @@ -6581,7 +6931,7 @@ pub struct OptionsUpdateAdditionalContentExclusionPolicy { pub scope: OptionsUpdateAdditionalContentExclusionPolicyScope, } -/// Schema for the `PendingPermissionRequest` type. +/// Pending permission prompt reconstructed from event history, with request ID and user-facing prompt details. /// ///
/// @@ -6613,7 +6963,7 @@ pub struct PendingPermissionRequestList { pub items: Vec, } -/// Schema for the `PermissionDecisionApproveOnce` type. +/// Permission-decision request variant to approve only the current permission request. /// ///
/// @@ -6628,7 +6978,7 @@ pub struct PermissionDecisionApproveOnce { pub kind: PermissionDecisionApproveOnceKind, } -/// Schema for the `PermissionDecisionApproveForSessionApprovalCommands` type. +/// Session-scoped approval details for specific command identifiers. /// ///
/// @@ -6645,7 +6995,7 @@ pub struct PermissionDecisionApproveForSessionApprovalCommands { pub kind: PermissionDecisionApproveForSessionApprovalCommandsKind, } -/// Schema for the `PermissionDecisionApproveForSessionApprovalRead` type. +/// Session-scoped approval details for read-only filesystem operations. /// ///
/// @@ -6660,7 +7010,7 @@ pub struct PermissionDecisionApproveForSessionApprovalRead { pub kind: PermissionDecisionApproveForSessionApprovalReadKind, } -/// Schema for the `PermissionDecisionApproveForSessionApprovalWrite` type. +/// Session-scoped approval details for filesystem write operations. /// ///
/// @@ -6675,7 +7025,7 @@ pub struct PermissionDecisionApproveForSessionApprovalWrite { pub kind: PermissionDecisionApproveForSessionApprovalWriteKind, } -/// Schema for the `PermissionDecisionApproveForSessionApprovalMcp` type. +/// Session-scoped approval details for an MCP server tool, or all tools on the server when `toolName` is null. /// ///
/// @@ -6694,7 +7044,7 @@ pub struct PermissionDecisionApproveForSessionApprovalMcp { pub tool_name: Option, } -/// Schema for the `PermissionDecisionApproveForSessionApprovalMcpSampling` type. +/// Session-scoped approval details for MCP sampling requests from a server. /// ///
/// @@ -6711,7 +7061,7 @@ pub struct PermissionDecisionApproveForSessionApprovalMcpSampling { pub server_name: String, } -/// Schema for the `PermissionDecisionApproveForSessionApprovalMemory` type. +/// Session-scoped approval details for writes to long-term memory. /// ///
/// @@ -6726,7 +7076,7 @@ pub struct PermissionDecisionApproveForSessionApprovalMemory { pub kind: PermissionDecisionApproveForSessionApprovalMemoryKind, } -/// Schema for the `PermissionDecisionApproveForSessionApprovalCustomTool` type. +/// Session-scoped approval details for a custom tool, keyed by tool name. /// ///
/// @@ -6743,7 +7093,7 @@ pub struct PermissionDecisionApproveForSessionApprovalCustomTool { pub tool_name: String, } -/// Schema for the `PermissionDecisionApproveForSessionApprovalExtensionManagement` type. +/// Session-scoped approval details for extension-management operations, optionally narrowed by operation. /// ///
/// @@ -6761,7 +7111,7 @@ pub struct PermissionDecisionApproveForSessionApprovalExtensionManagement { pub operation: Option, } -/// Schema for the `PermissionDecisionApproveForSessionApprovalExtensionPermissionAccess` type. +/// Session-scoped approval details for an extension's permission-gated capability access, keyed by extension name. /// ///
/// @@ -6778,7 +7128,7 @@ pub struct PermissionDecisionApproveForSessionApprovalExtensionPermissionAccess pub kind: PermissionDecisionApproveForSessionApprovalExtensionPermissionAccessKind, } -/// Schema for the `PermissionDecisionApproveForSession` type. +/// Permission-decision request variant to approve for the rest of the session, with optional tool approval or URL domain. /// ///
/// @@ -6799,7 +7149,7 @@ pub struct PermissionDecisionApproveForSession { pub kind: PermissionDecisionApproveForSessionKind, } -/// Schema for the `PermissionDecisionApproveForLocationApprovalCommands` type. +/// Location-scoped approval details for specific command identifiers. /// ///
/// @@ -6816,7 +7166,7 @@ pub struct PermissionDecisionApproveForLocationApprovalCommands { pub kind: PermissionDecisionApproveForLocationApprovalCommandsKind, } -/// Schema for the `PermissionDecisionApproveForLocationApprovalRead` type. +/// Location-scoped approval details for read-only filesystem operations. /// ///
/// @@ -6831,7 +7181,7 @@ pub struct PermissionDecisionApproveForLocationApprovalRead { pub kind: PermissionDecisionApproveForLocationApprovalReadKind, } -/// Schema for the `PermissionDecisionApproveForLocationApprovalWrite` type. +/// Location-scoped approval details for filesystem write operations. /// ///
/// @@ -6846,7 +7196,7 @@ pub struct PermissionDecisionApproveForLocationApprovalWrite { pub kind: PermissionDecisionApproveForLocationApprovalWriteKind, } -/// Schema for the `PermissionDecisionApproveForLocationApprovalMcp` type. +/// Location-scoped approval details for an MCP server tool, or all tools on the server when `toolName` is null. /// ///
/// @@ -6865,7 +7215,7 @@ pub struct PermissionDecisionApproveForLocationApprovalMcp { pub tool_name: Option, } -/// Schema for the `PermissionDecisionApproveForLocationApprovalMcpSampling` type. +/// Location-scoped approval details for MCP sampling requests from a server. /// ///
/// @@ -6882,7 +7232,7 @@ pub struct PermissionDecisionApproveForLocationApprovalMcpSampling { pub server_name: String, } -/// Schema for the `PermissionDecisionApproveForLocationApprovalMemory` type. +/// Location-scoped approval details for writes to long-term memory. /// ///
/// @@ -6897,7 +7247,7 @@ pub struct PermissionDecisionApproveForLocationApprovalMemory { pub kind: PermissionDecisionApproveForLocationApprovalMemoryKind, } -/// Schema for the `PermissionDecisionApproveForLocationApprovalCustomTool` type. +/// Location-scoped approval details for a custom tool, keyed by tool name. /// ///
/// @@ -6914,7 +7264,7 @@ pub struct PermissionDecisionApproveForLocationApprovalCustomTool { pub tool_name: String, } -/// Schema for the `PermissionDecisionApproveForLocationApprovalExtensionManagement` type. +/// Location-scoped approval details for extension-management operations, optionally narrowed by operation. /// ///
/// @@ -6932,7 +7282,7 @@ pub struct PermissionDecisionApproveForLocationApprovalExtensionManagement { pub operation: Option, } -/// Schema for the `PermissionDecisionApproveForLocationApprovalExtensionPermissionAccess` type. +/// Location-scoped approval details for an extension's permission-gated capability access, keyed by extension name. /// ///
/// @@ -6949,7 +7299,7 @@ pub struct PermissionDecisionApproveForLocationApprovalExtensionPermissionAccess pub kind: PermissionDecisionApproveForLocationApprovalExtensionPermissionAccessKind, } -/// Schema for the `PermissionDecisionApproveForLocation` type. +/// Permission-decision request variant to approve and persist a permission for a project location, with approval details and location key. /// ///
/// @@ -6968,7 +7318,7 @@ pub struct PermissionDecisionApproveForLocation { pub location_key: String, } -/// Schema for the `PermissionDecisionApprovePermanently` type. +/// Permission-decision request variant to permanently approve a URL domain across sessions. /// ///
/// @@ -6985,7 +7335,7 @@ pub struct PermissionDecisionApprovePermanently { pub kind: PermissionDecisionApprovePermanentlyKind, } -/// Schema for the `PermissionDecisionReject` type. +/// Permission-decision request variant to reject a pending permission request, with optional feedback. /// ///
/// @@ -7003,7 +7353,7 @@ pub struct PermissionDecisionReject { pub kind: PermissionDecisionRejectKind, } -/// Schema for the `PermissionDecisionUserNotAvailable` type. +/// Permission-decision variant indicating no user was available to confirm the request. /// ///
/// @@ -7018,7 +7368,7 @@ pub struct PermissionDecisionUserNotAvailable { pub kind: PermissionDecisionUserNotAvailableKind, } -/// Schema for the `PermissionDecisionApproved` type. +/// Permission-decision variant indicating the request was approved. /// ///
/// @@ -7033,7 +7383,7 @@ pub struct PermissionDecisionApproved { pub kind: PermissionDecisionApprovedKind, } -/// Schema for the `PermissionDecisionApprovedForSession` type. +/// Permission-decision variant indicating approval was remembered for the session, with approval details. /// ///
/// @@ -7050,7 +7400,7 @@ pub struct PermissionDecisionApprovedForSession { pub kind: PermissionDecisionApprovedForSessionKind, } -/// Schema for the `PermissionDecisionApprovedForLocation` type. +/// Permission-decision variant indicating approval was persisted for a project location, with approval details and location key. /// ///
/// @@ -7069,7 +7419,7 @@ pub struct PermissionDecisionApprovedForLocation { pub location_key: String, } -/// Schema for the `PermissionDecisionCancelled` type. +/// Permission-decision variant indicating the request was cancelled before use, with an optional reason. /// ///
/// @@ -7087,7 +7437,7 @@ pub struct PermissionDecisionCancelled { pub reason: Option, } -/// Schema for the `PermissionDecisionDeniedByRules` type. +/// Permission-decision variant indicating explicit denial by permission rules, with the matching rules. /// ///
/// @@ -7104,7 +7454,7 @@ pub struct PermissionDecisionDeniedByRules { pub rules: Vec, } -/// Schema for the `PermissionDecisionDeniedNoApprovalRuleAndCouldNotRequestFromUser` type. +/// Permission-decision variant indicating no approval rule matched and user confirmation was unavailable. /// ///
/// @@ -7119,7 +7469,7 @@ pub struct PermissionDecisionDeniedNoApprovalRuleAndCouldNotRequestFromUser { pub kind: PermissionDecisionDeniedNoApprovalRuleAndCouldNotRequestFromUserKind, } -/// Schema for the `PermissionDecisionDeniedInteractivelyByUser` type. +/// Permission-decision variant indicating the user denied an interactive prompt, with optional feedback and force-reject flag. /// ///
/// @@ -7140,7 +7490,7 @@ pub struct PermissionDecisionDeniedInteractivelyByUser { pub kind: PermissionDecisionDeniedInteractivelyByUserKind, } -/// Schema for the `PermissionDecisionDeniedByContentExclusionPolicy` type. +/// Permission-decision variant indicating denial by content-exclusion policy, with path and message. /// ///
/// @@ -7159,7 +7509,7 @@ pub struct PermissionDecisionDeniedByContentExclusionPolicy { pub path: String, } -/// Schema for the `PermissionDecisionDeniedByPermissionRequestHook` type. +/// Permission-decision variant indicating denial by a permission request hook, with optional message and interrupt flag. /// ///
/// @@ -7197,7 +7547,7 @@ pub struct PermissionDecisionRequest { pub result: PermissionDecision, } -/// Schema for the `PermissionsLocationsAddToolApprovalDetailsCommands` type. +/// Location-persisted tool approval details for specific command identifiers. /// ///
/// @@ -7214,7 +7564,7 @@ pub struct PermissionsLocationsAddToolApprovalDetailsCommands { pub kind: PermissionsLocationsAddToolApprovalDetailsCommandsKind, } -/// Schema for the `PermissionsLocationsAddToolApprovalDetailsRead` type. +/// Location-persisted tool approval details for read-only filesystem operations. /// ///
/// @@ -7229,7 +7579,7 @@ pub struct PermissionsLocationsAddToolApprovalDetailsRead { pub kind: PermissionsLocationsAddToolApprovalDetailsReadKind, } -/// Schema for the `PermissionsLocationsAddToolApprovalDetailsWrite` type. +/// Location-persisted tool approval details for filesystem write operations. /// ///
/// @@ -7244,7 +7594,7 @@ pub struct PermissionsLocationsAddToolApprovalDetailsWrite { pub kind: PermissionsLocationsAddToolApprovalDetailsWriteKind, } -/// Schema for the `PermissionsLocationsAddToolApprovalDetailsMcp` type. +/// Location-persisted tool approval details for an MCP server tool, or all tools when `toolName` is null. /// ///
/// @@ -7263,7 +7613,7 @@ pub struct PermissionsLocationsAddToolApprovalDetailsMcp { pub tool_name: Option, } -/// Schema for the `PermissionsLocationsAddToolApprovalDetailsMcpSampling` type. +/// Location-persisted tool approval details for MCP sampling requests from a server. /// ///
/// @@ -7280,7 +7630,7 @@ pub struct PermissionsLocationsAddToolApprovalDetailsMcpSampling { pub server_name: String, } -/// Schema for the `PermissionsLocationsAddToolApprovalDetailsMemory` type. +/// Location-persisted tool approval details for writes to long-term memory. /// ///
/// @@ -7295,7 +7645,7 @@ pub struct PermissionsLocationsAddToolApprovalDetailsMemory { pub kind: PermissionsLocationsAddToolApprovalDetailsMemoryKind, } -/// Schema for the `PermissionsLocationsAddToolApprovalDetailsCustomTool` type. +/// Location-persisted tool approval details for a custom tool, keyed by tool name. /// ///
/// @@ -7312,7 +7662,7 @@ pub struct PermissionsLocationsAddToolApprovalDetailsCustomTool { pub tool_name: String, } -/// Schema for the `PermissionsLocationsAddToolApprovalDetailsExtensionManagement` type. +/// Location-persisted tool approval details for extension-management operations, optionally narrowed by operation. /// ///
/// @@ -7330,7 +7680,7 @@ pub struct PermissionsLocationsAddToolApprovalDetailsExtensionManagement { pub operation: Option, } -/// Schema for the `PermissionsLocationsAddToolApprovalDetailsExtensionPermissionAccess` type. +/// Location-persisted tool approval details for an extension's permission-gated capability access, keyed by extension name. /// ///
/// @@ -7615,7 +7965,7 @@ pub struct PermissionRulesSet { pub denied: Vec, } -/// Schema for the `PermissionsConfigureAdditionalContentExclusionPolicyRuleSource` type. +/// Source descriptor for a `session.permissions.configure` content-exclusion rule, with source name and type. /// ///
/// @@ -7630,7 +7980,7 @@ pub struct PermissionsConfigureAdditionalContentExclusionPolicyRuleSource { pub r#type: String, } -/// Schema for the `PermissionsConfigureAdditionalContentExclusionPolicyRule` type. +/// Single content-exclusion rule supplied to `session.permissions.configure`, with paths, match conditions, and source. /// ///
/// @@ -7646,11 +7996,11 @@ pub struct PermissionsConfigureAdditionalContentExclusionPolicyRule { #[serde(skip_serializing_if = "Option::is_none")] pub if_none_match: Option>, pub paths: Vec, - /// Schema for the `PermissionsConfigureAdditionalContentExclusionPolicyRuleSource` type. + /// Source descriptor for a `session.permissions.configure` content-exclusion rule, with source name and type. pub source: PermissionsConfigureAdditionalContentExclusionPolicyRuleSource, } -/// Schema for the `PermissionsConfigureAdditionalContentExclusionPolicy` type. +/// Content-exclusion policy supplied to `session.permissions.configure`, with rules, last-updated data, and scope. /// ///
/// @@ -7911,7 +8261,7 @@ pub struct PermissionsResetSessionApprovalsResult { pub success: bool, } -/// Whether to enable full allow-all permissions for the session. +/// Allow-all mode to apply for the session. /// ///
/// @@ -7922,8 +8272,15 @@ pub struct PermissionsResetSessionApprovalsResult { #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct PermissionsSetAllowAllRequest { - /// Whether to enable full allow-all permissions - pub enabled: bool, + /// Legacy full allow-all toggle. Prefer `mode`; when `mode` is omitted, `enabled: true` is treated as `mode: "on"` and any other value is treated as `mode: "off"`. + #[serde(skip_serializing_if = "Option::is_none")] + pub enabled: Option, + /// Allow-all mode to apply. `on` enables full allow-all; `auto` enables advisory LLM auto-approval; `off` disables both. + #[serde(skip_serializing_if = "Option::is_none")] + pub mode: Option, + /// Optional model id for the `auto` mode auto-approval LLM judging. Only meaningful when `mode` is `auto`; ignored otherwise. When omitted, the session's active model is used. + #[serde(skip_serializing_if = "Option::is_none")] + pub model: Option, /// Optional source for allow-all telemetry. Defaults to `rpc` when omitted for SDK callers. #[serde(skip_serializing_if = "Option::is_none")] pub source: Option, @@ -8165,7 +8522,7 @@ pub struct PlanUpdateRequest { pub content: String, } -/// Schema for the `Plugin` type. +/// Session plugin metadata, with name, marketplace, optional version, and enabled state. /// ///
/// @@ -8369,6 +8726,9 @@ pub struct PluginsReloadRequest { /// Re-run custom-agent discovery after refreshing plugins. Defaults to true. #[serde(skip_serializing_if = "Option::is_none")] pub reload_custom_agents: Option, + /// Re-discover and relaunch subprocess extensions (including plugin-shipped extensions) after refreshing plugins. Defaults to true. Has no effect when the session has no active extension controller (e.g. extensions were not requested for the session). + #[serde(skip_serializing_if = "Option::is_none")] + pub reload_extensions: Option, /// Re-load user, plugin, and (subject to `deferRepoHooks`) repo hooks. Defaults to true. Has no effect when the host has not registered a hook reloader (e.g. remote sessions). #[serde(skip_serializing_if = "Option::is_none")] pub reload_hooks: Option, @@ -8410,7 +8770,7 @@ pub struct PluginsUpdateRequest { pub name: String, } -/// Schema for the `PluginUpdateAllEntry` type. +/// Per-plugin result from updating all plugins, with versions, skills installed, success flag, and optional error. /// ///
/// @@ -8477,38 +8837,6 @@ pub struct PluginUpdateResult { pub skills_installed: i64, } -/// Schema for the `SessionsPollSpawnedSessionsEvent` type. -/// -///
-/// -/// **Experimental.** This type is part of an experimental wire-protocol surface -/// and may change or be removed in future SDK or CLI releases. -/// -///
-#[derive(Debug, Clone, Default, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct SessionsPollSpawnedSessionsEvent { - /// Session id of the newly-spawned session. - pub session_id: SessionId, -} - -/// Batch of spawn events plus a cursor for follow-up polls. -/// -///
-/// -/// **Experimental.** This type is part of an experimental wire-protocol surface -/// and may change or be removed in future SDK or CLI releases. -/// -///
-#[derive(Debug, Clone, Default, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct PollSpawnedSessionsResult { - /// Opaque cursor to pass back to receive only events after this batch. - pub cursor: String, - /// Spawn events emitted since the supplied cursor. - pub events: Vec, -} - /// A BYOK model definition referencing a named provider. /// ///
@@ -9180,7 +9508,7 @@ pub struct PushAttachmentSelection { pub r#type: PushAttachmentSelectionType, } -/// Schema for the `QueuedCommandHandled` type. +/// Queued-command response indicating the host executed the command, with an optional flag to stop queue processing. /// ///
/// @@ -9198,7 +9526,7 @@ pub struct QueuedCommandHandled { pub stop_processing_queue: Option, } -/// Schema for the `QueuedCommandNotHandled` type. +/// Queued-command response indicating the host did not execute the command and the queue may continue. /// ///
/// @@ -9213,7 +9541,7 @@ pub struct QueuedCommandNotHandled { pub handled: bool, } -/// Schema for the `QueuePendingItems` type. +/// User-facing pending queue entry, with kind and display text for a queued message, slash command, or model change. /// ///
/// @@ -9273,7 +9601,7 @@ pub struct QueueRemoveMostRecentResult { #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct RegisterEventInterestParams { - /// The event type the consumer wants the runtime to treat as 'observed' for behavior-switching gating. Some runtime code paths inspect whether any consumer is interested in a specific event type and choose a different implementation accordingly (e.g. `mcp.oauth_required`: when interest is registered the runtime delegates the full interactive OAuth flow to the consumer; when no interest is registered the runtime installs a browserless fallback that silently reuses cached tokens). SDK clients that long-poll events do NOT automatically appear as listeners to these gating checks — they must explicitly call `registerInterest` for each event type they want the runtime to count as having a consumer. Multiple registrations for the same event type from the same or different consumers are tracked independently and must each be released. See: `mcp.oauth_required`, `sampling.requested`, `auto_mode_switch.requested`, `session_limits_exhausted.requested`, `user_input.requested`, `elicitation.requested`, `command.queued`, `exit_plan_mode.requested`. + /// The event type the consumer wants the runtime to treat as 'observed' for behavior-switching gating. Some runtime code paths inspect whether any consumer is interested in a specific event type and choose a different implementation accordingly (e.g. `mcp.oauth_required`: when interest is registered the runtime delegates OAuth token acquisition to the consumer; when no interest is registered OAuth-required servers become needs-auth). SDK clients that long-poll events do NOT automatically appear as listeners to these gating checks — they must explicitly call `registerInterest` for each event type they want the runtime to count as having a consumer. Multiple registrations for the same event type from the same or different consumers are tracked independently and must each be released. See: `mcp.oauth_required`, `sampling.requested`, `auto_mode_switch.requested`, `session_limits_exhausted.requested`, `user_input.requested`, `elicitation.requested`, `command.queued`, `exit_plan_mode.requested`. pub event_type: String, } @@ -9418,6 +9746,10 @@ pub struct RemoteControlConfig { pub struct RemoteControlStatusActive { /// Session id remote control is pointed at. pub attached_session_id: String, + /// True while a read-only/session-sync export is deferred, awaiting the first `user.message` before its MC session exists. Marked internal: this field is excluded from the public SDK surface and is populated only on the CLI in-process path. + #[doc(hidden)] + #[serde(skip_serializing_if = "Option::is_none")] + pub(crate) awaiting_first_message: Option, /// MC frontend URL for this session, when known. #[serde(skip_serializing_if = "Option::is_none")] pub frontend_url: Option, @@ -9766,18 +10098,12 @@ pub struct SandboxConfigUserPolicyFilesystem { #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct SandboxConfigUserPolicyNetwork { - /// Hosts allowed in addition to the base policy. - #[serde(skip_serializing_if = "Option::is_none")] - pub allowed_hosts: Option>, /// Whether traffic to local/loopback addresses is allowed. #[serde(skip_serializing_if = "Option::is_none")] pub allow_local_network: Option, /// Whether outbound network traffic is allowed at all. #[serde(skip_serializing_if = "Option::is_none")] pub allow_outbound: Option, - /// Hosts explicitly blocked. - #[serde(skip_serializing_if = "Option::is_none")] - pub blocked_hosts: Option>, } /// macOS seatbelt-specific options. @@ -9842,7 +10168,7 @@ pub struct SandboxConfig { pub user_policy: Option, } -/// Schema for the `ScheduleEntry` type. +/// Scheduled prompt entry with ID, timing (`intervalMs`, `cron`, or `at`), prompt text, recurrence, and next run time. /// ///
/// @@ -10072,7 +10398,7 @@ pub struct ServerInstructionSourceList { pub sources: Vec, } -/// Schema for the `ServerSkill` type. +/// Server-side skill metadata, including name, description, source, enabled/invocable state, path, project path, and argument hint. /// ///
/// @@ -10181,6 +10507,52 @@ pub struct SessionBulkDeleteResult { pub freed_bytes: HashMap, } +/// Successful compaction history for the session. +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionContextAttributionCompactions { + /// Number of successful compactions in this session. + pub count: i64, +} + +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionContextAttributionEntriesItem { + /// Supplementary per-entry metadata (e.g. `messageCount`, `role`, `evictable`, `pluginSource`). Values are stringified; parse as needed and ignore unrecognized keys. + #[serde(skip_serializing_if = "Option::is_none")] + pub attributes: Option>, + /// Identifier for this entry, formed by joining its `kind` and source name (e.g. `tool:bash`, `skill:tmux`, `toolDefinition:bash`); unique within the snapshot. Use it to match the same entry across snapshots, to correlate with other APIs (skill/agent/MCP registries), and as the `parentId` target for nesting. Distinct from the human-facing `label`. + pub id: String, + /// Source category for this entry. Not a closed set — tolerate unknown values. Known values today: `skill`, `subagent`, `mcpServer`, `tool`, `system`, `toolDefinition`, `plugin`. + pub kind: String, + /// Human-readable display label, e.g. `bash` or `skill: tmux`. Presentation-only; may be localized/reformatted without notice — do not key off it. + pub label: String, + /// Optional `id` of the parent entry: e.g. a `plugin` entry parenting its `skill`/`mcpServer` entries, or the `system` entry parenting `toolDefinition` entries. Omitted for top-level entries. + #[serde(skip_serializing_if = "Option::is_none")] + pub parent_id: Option, + /// Token count currently in context attributable to this entry. + pub tokens: i64, +} + +/// Per-source context-window attribution, or null if the session has not yet been initialized (no system prompt or tool metadata cached). +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionContextAttribution { + /// Successful compaction history for the session. + pub compactions: SessionContextAttributionCompactions, + /// Flat list of per-source attribution entries. Group by `kind` and render unrecognized kinds generically. Nesting and rollups are expressed via `parentId`. + pub entries: Vec, + /// Total token count of the current context window the entries are measured against (system message + conversation messages + tool definitions — the same total reported by /context). Divide an entry's `tokens` by this to derive its share. + pub total_tokens: i64, +} + /// Token breakdown for the current context window, or null if the session has not yet been initialized (no system prompt or tool metadata cached). /// ///
@@ -10359,7 +10731,7 @@ pub struct SessionFsReaddirResult { pub error: Option, } -/// Schema for the `SessionFsReaddirWithTypesEntry` type. +/// Directory entry returned by session filesystem `readdirWithTypes`, with name and entry type. /// ///
/// @@ -10668,7 +11040,7 @@ pub struct SessionFsWriteFileRequest { pub mode: Option, } -/// Schema for the `SessionInstalledPlugin` type. +/// Installed plugin record for a session, with marketplace, version, install time, enabled state, cache path, and source. /// ///
/// @@ -10699,7 +11071,7 @@ pub struct SessionInstalledPlugin { pub version: Option, } -/// Schema for the `SessionInstalledPluginSourceGitHub` type. +/// Source descriptor for a direct GitHub plugin install, with `owner/repo`, optional ref, and optional subpath. /// ///
/// @@ -10719,7 +11091,7 @@ pub struct SessionInstalledPluginSourceGitHub { pub source: SessionInstalledPluginSourceGitHubSource, } -/// Schema for the `SessionInstalledPluginSourceLocal` type. +/// Source descriptor for a direct local plugin install, with a local filesystem path. /// ///
/// @@ -10735,7 +11107,7 @@ pub struct SessionInstalledPluginSourceLocal { pub source: SessionInstalledPluginSourceLocalSource, } -/// Schema for the `SessionInstalledPluginSourceUrl` type. +/// Source descriptor for a direct URL plugin install, with URL, optional ref, and optional subpath. /// ///
/// @@ -10913,7 +11285,7 @@ pub struct SessionModelList { pub quota_snapshots: Option>, } -/// Schema for the `SessionOpenOptionsAdditionalContentExclusionPolicyRuleSource` type. +/// Source descriptor for a `sessions.open` content-exclusion rule, with source name and type. /// ///
/// @@ -10928,7 +11300,7 @@ pub struct SessionOpenOptionsAdditionalContentExclusionPolicyRuleSource { pub r#type: String, } -/// Schema for the `SessionOpenOptionsAdditionalContentExclusionPolicyRule` type. +/// Single content-exclusion rule supplied to `sessions.open` options, with paths, match conditions, and source. /// ///
/// @@ -10944,11 +11316,11 @@ pub struct SessionOpenOptionsAdditionalContentExclusionPolicyRule { #[serde(skip_serializing_if = "Option::is_none")] pub if_none_match: Option>, pub paths: Vec, - /// Schema for the `SessionOpenOptionsAdditionalContentExclusionPolicyRuleSource` type. + /// Source descriptor for a `sessions.open` content-exclusion rule, with source name and type. pub source: SessionOpenOptionsAdditionalContentExclusionPolicyRuleSource, } -/// Schema for the `SessionOpenOptionsAdditionalContentExclusionPolicy` type. +/// Content-exclusion policy supplied to `sessions.open` options, with rules, last-updated data, and scope. /// ///
/// @@ -11049,6 +11421,9 @@ pub struct SessionOpenOptions { ///
#[serde(skip_serializing_if = "Option::is_none")] pub enable_citations: Option, + /// Opt-in: self-fetch and enforce enterprise managed settings at session bootstrap. + #[serde(skip_serializing_if = "Option::is_none")] + pub enable_managed_settings: Option, /// Whether on-demand custom instruction discovery is enabled. #[serde(skip_serializing_if = "Option::is_none")] pub enable_on_demand_instruction_discovery: Option, @@ -11175,6 +11550,9 @@ pub struct SessionOpenOptions { /// Optional trajectory output file path. #[serde(skip_serializing_if = "Option::is_none")] pub trajectory_file: Option, + /// Initial output verbosity level for supported models. + #[serde(skip_serializing_if = "Option::is_none")] + pub verbosity: Option, /// Working directory to anchor the session. #[serde(skip_serializing_if = "Option::is_none")] pub working_directory: Option, @@ -11337,6 +11715,10 @@ pub struct SessionsOpenHandoff { pub kind: SessionsOpenHandoffKind, /// Remote session metadata for the session to hand off (typically obtained from `sessions.list` with `source: "remote"`). pub metadata: RemoteSessionMetadataValue, + /// In-process confirmation callback `(request) => boolean | Promise` invoked when the handoff needs the caller to confirm a non-fatal blocker (e.g. a repository mismatch between the current working directory and the remote session). Returning `true` proceeds with the handoff; returning `false` (or omitting the callback) aborts it. Marked internal because a function reference cannot cross the JSON-RPC boundary, for the same reasons as `onProgress`. + #[doc(hidden)] + #[serde(skip_serializing_if = "Option::is_none")] + pub(crate) on_confirm: Option, /// In-process progress callback `(update) => void` invoked for each handoff step. Marked internal because a function reference cannot cross the JSON-RPC boundary. The host-side `handoffSession` is already declared as `AsyncGenerator`; the schema layer flattens it because it does not yet support streaming methods. The wire-clean replacement is to expose the AsyncGenerator directly (or use vscode-jsonrpc `$/progress` notifications) once the schema/transport layer supports it. #[doc(hidden)] #[serde(skip_serializing_if = "Option::is_none")] @@ -11349,7 +11731,7 @@ pub struct SessionsOpenHandoff { pub task_type: Option, } -/// Schema for the `SessionsOpenProgress` type. +/// `sessions.open` handoff progress update with step, status, and optional message. /// ///
/// @@ -11547,6 +11929,206 @@ pub struct SessionSetCredentialsResult { pub success: bool, } +/// Availability of built-in job tools surfaced to boundary consumers. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionSettingsBuiltInToolAvailabilitySnapshot { + #[serde(skip_serializing_if = "Option::is_none")] + pub create_pull_request: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub report_progress: Option, +} + +/// Named Rust-owned settings predicate to evaluate for this session. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionSettingsEvaluatePredicateRequest { + /// Predicate name. The runtime owns the raw feature-flag names and composition logic. + pub name: SessionSettingsPredicateName, + /// Tool name for tool-scoped predicates such as trivial-change handling. + #[serde(skip_serializing_if = "Option::is_none")] + pub tool_name: Option, +} + +/// Result of evaluating a Rust-owned settings predicate. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionSettingsEvaluatePredicateResult { + pub enabled: bool, +} + +/// Redacted job settings for a session. The job nonce is excluded. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionSettingsJobSnapshot { + #[serde(skip_serializing_if = "Option::is_none")] + pub built_in_tool_availability: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub event_type: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub is_trigger_job: Option, +} + +/// Redacted model routing settings for a session. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionSettingsModelSnapshot { + #[serde(skip_serializing_if = "Option::is_none")] + pub callback_url: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub default_reasoning_effort: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub instance_id: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub model: Option, +} + +/// Online-evaluation settings safe to expose across the SDK boundary. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionSettingsOnlineEvaluationSnapshot { + #[serde(skip_serializing_if = "Option::is_none")] + pub disable_online_evaluation: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub enable_online_evaluation_output_file: Option, +} + +/// Redacted repository and GitHub host settings for a session. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionSettingsRepoSnapshot { + #[serde(skip_serializing_if = "Option::is_none")] + pub branch: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub commit: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub host: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub host_protocol: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub id: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub name: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub owner_id: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub owner_name: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub pr_commit_count: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub read_write: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub secret_scanning_url: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub server_url: Option, +} + +/// Redacted validation and memory-tool settings for a session. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionSettingsValidationSnapshot { + #[serde(skip_serializing_if = "Option::is_none")] + pub advisory_enabled: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub codeql_enabled: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub code_review_enabled: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub code_review_model: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub dependabot_timeout: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub memory_store_enabled: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub memory_vote_enabled: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub secret_scanning_enabled: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub timeout: Option, +} + +/// Redacted, serializable view of session runtime settings for SDK boundary consumers. Secrets and raw feature flags are intentionally excluded. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionSettingsSnapshot { + #[serde(skip_serializing_if = "Option::is_none")] + pub client_name: Option, + pub job: SessionSettingsJobSnapshot, + pub model: SessionSettingsModelSnapshot, + pub online_evaluation: SessionSettingsOnlineEvaluationSnapshot, + pub repo: SessionSettingsRepoSnapshot, + #[serde(skip_serializing_if = "Option::is_none")] + pub start_time_ms: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub timeout_ms: Option, + pub validation: SessionSettingsValidationSnapshot, + #[serde(skip_serializing_if = "Option::is_none")] + pub version: Option, +} + /// UUID prefix to resolve to a unique session ID. /// ///
@@ -11807,30 +12389,15 @@ pub struct SessionsListRequest { /// When provided, only the first N local sessions (sorted by modification time, newest first) load full metadata; remaining sessions return basic info only. Use 0 to return only basic info for every local session. Has no effect on remote entries (which always carry their full shape). #[serde(skip_serializing_if = "Option::is_none")] pub metadata_limit: Option, - /// Which session sources to include. Defaults to `local` for backward compatibility. - #[serde(skip_serializing_if = "Option::is_none")] - pub source: Option, - /// Only meaningful when `source` includes remote. When true, propagates errors from the remote service instead of silently returning an empty remote list. Defaults to false. - #[serde(skip_serializing_if = "Option::is_none")] - pub throw_on_error: Option, -} - -/// Active session ID whose deferred repo-level hooks should be loaded. -/// -///
-/// -/// **Experimental.** This type is part of an experimental wire-protocol surface -/// and may change or be removed in future SDK or CLI releases. -/// -///
-#[derive(Debug, Clone, Default, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct SessionsLoadDeferredRepoHooksRequest { - /// Active session ID whose deferred repo-level hooks should be loaded - pub session_id: SessionId, + /// Which session sources to include. Defaults to `local` for backward compatibility. + #[serde(skip_serializing_if = "Option::is_none")] + pub source: Option, + /// Only meaningful when `source` includes remote. When true, propagates errors from the remote service instead of silently returning an empty remote list. Defaults to false. + #[serde(skip_serializing_if = "Option::is_none")] + pub throw_on_error: Option, } -/// Cursor and optional long-poll wait for polling runtime-spawned sessions. +/// Active session ID whose deferred repo-level hooks should be loaded. /// ///
/// @@ -11840,13 +12407,9 @@ pub struct SessionsLoadDeferredRepoHooksRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionsPollSpawnedSessionsRequest { - /// Opaque cursor returned by a previous poll. Omit on the first call to receive any spawn events buffered since the runtime started. - #[serde(skip_serializing_if = "Option::is_none")] - pub cursor: Option, - /// Milliseconds to wait for new spawn events when the cursor is at the tail. 0 (default) returns immediately even if no events are buffered. Capped at 60000ms. - #[serde(skip_serializing_if = "Option::is_none")] - pub wait_ms: Option, +pub struct SessionsLoadDeferredRepoHooksRequest { + /// Active session ID whose deferred repo-level hooks should be loaded + pub session_id: SessionId, } /// Age threshold and optional flags controlling which old sessions are pruned (or simulated when dryRun is true). @@ -12244,6 +12807,9 @@ pub struct SessionUpdateOptionsParams { /// Optional path for trajectory output. #[serde(skip_serializing_if = "Option::is_none")] pub trajectory_file: Option, + /// Output verbosity level for supported models. + #[serde(skip_serializing_if = "Option::is_none")] + pub verbosity: Option, /// Absolute working-directory path for shell tools. #[serde(skip_serializing_if = "Option::is_none")] pub working_directory: Option, @@ -12384,7 +12950,7 @@ pub struct ShutdownRequest { pub r#type: Option, } -/// Schema for the `Skill` type. +/// Skill metadata available to a session, with name, description, source, enabled/invocable state, path, plugin, and argument hint. /// ///
/// @@ -12416,7 +12982,7 @@ pub struct Skill { pub user_invocable: bool, } -/// Schema for the `SkillDiscoveryPath` type. +/// Canonical directory where skills can be discovered or created, with scope, preference, and optional project path. /// ///
/// @@ -12554,7 +13120,7 @@ pub struct SkillsGetDiscoveryPathsRequest { pub project_paths: Option>, } -/// Schema for the `SkillsInvokedSkill` type. +/// Skill invocation record with name, path, content, allowed tools, and turn number. /// ///
/// @@ -12610,7 +13176,7 @@ pub struct SkillsLoadDiagnostics { pub warnings: Vec, } -/// Schema for the `SlashCommandAgentPromptResult` type. +/// Slash-command invocation result that submits an agent prompt, with display prompt, optional mode, and settings-change flag. /// ///
/// @@ -12635,7 +13201,7 @@ pub struct SlashCommandAgentPromptResult { pub runtime_settings_changed: Option, } -/// Schema for the `SlashCommandCompletedResult` type. +/// Slash-command invocation result indicating completion, with optional message and settings-change flag. /// ///
/// @@ -12656,7 +13222,7 @@ pub struct SlashCommandCompletedResult { pub runtime_settings_changed: Option, } -/// Schema for the `SlashCommandTextResult` type. +/// Slash-command invocation result containing text output plus Markdown/ANSI rendering flags. /// ///
/// @@ -12682,7 +13248,7 @@ pub struct SlashCommandTextResult { pub text: String, } -/// Schema for the `SlashCommandSelectSubcommandOption` type. +/// Selectable slash-command subcommand option with name, description, and optional group label. /// ///
/// @@ -12702,7 +13268,7 @@ pub struct SlashCommandSelectSubcommandOption { pub name: String, } -/// Schema for the `SlashCommandSelectSubcommandResult` type. +/// Slash-command invocation result asking the client to present subcommand options for a parent command. /// ///
/// @@ -12773,7 +13339,7 @@ pub struct SubagentSettings { pub max_depth: Option, } -/// Schema for the `TaskAgentInfo` type. +/// Tracked background agent task metadata, including IDs, status, timing, agent type, prompt, model, result, and latest response. /// ///
/// @@ -12835,7 +13401,7 @@ pub struct TaskAgentInfo { pub r#type: TaskAgentInfoType, } -/// Schema for the `TaskProgressLine` type. +/// Timestamped display line for task progress output or recent agent activity. /// ///
/// @@ -12852,7 +13418,7 @@ pub struct TaskProgressLine { pub timestamp: String, } -/// Schema for the `TaskAgentProgress` type. +/// Progress snapshot for an agent task, with recent activity lines and optional latest intent. /// ///
/// @@ -12963,7 +13529,7 @@ pub struct TasksGetProgressResult { pub progress: Option, } -/// Schema for the `TaskShellInfo` type. +/// Tracked shell task metadata, including ID, command, status, timing, attachment/execution mode, log path, and PID. /// ///
/// @@ -13005,7 +13571,7 @@ pub struct TaskShellInfo { pub r#type: TaskShellInfoType, } -/// Schema for the `TaskShellProgress` type. +/// Progress snapshot for a shell task, with recent stdout/stderr output and optional process ID. /// ///
/// @@ -13218,7 +13784,7 @@ pub struct TelemetrySetFeatureOverridesRequest { pub features: HashMap, } -/// Schema for the `TokenAuthInfo` type. +/// Authentication-info variant for SDK-configured token authentication, carrying host and the secret token value. /// ///
/// @@ -13240,7 +13806,7 @@ pub struct TokenAuthInfo { pub r#type: TokenAuthInfoType, } -/// Schema for the `Tool` type. +/// Built-in tool metadata with identifier, optional namespaced name, description, input-parameter schema, and usage instructions. /// ///
/// @@ -13336,7 +13902,7 @@ pub struct ToolsListRequest { #[serde(rename_all = "camelCase")] pub struct ToolsUpdateSubagentSettingsResult {} -/// Schema for the `UIElicitationArrayAnyOfFieldItemsAnyOf` type. +/// Selectable option for a UI elicitation multi-select array item, with submitted value and display label. /// ///
/// @@ -13635,7 +14201,7 @@ pub struct UIElicitationStringEnumField { pub r#type: UIElicitationStringEnumFieldType, } -/// Schema for the `UIElicitationStringOneOfFieldOneOf` type. +/// Selectable option for a UI elicitation single-select string field, with submitted value and display label. /// ///
/// @@ -13716,7 +14282,7 @@ pub struct UIEphemeralQueryResult { pub answer: String, } -/// Schema for the `UIExitPlanModeResponse` type. +/// User response for a pending exit-plan-mode request, with approval state, selected action, auto-approve flag, and feedback. /// ///
/// @@ -13787,7 +14353,7 @@ pub struct UIHandlePendingElicitationRequest { pub struct UIHandlePendingExitPlanModeRequest { /// The unique request ID from the exit_plan_mode.requested event pub request_id: RequestId, - /// Schema for the `UIExitPlanModeResponse` type. + /// User response for a pending exit-plan-mode request, with approval state, selected action, auto-approve flag, and feedback. pub response: UIExitPlanModeResponse, } @@ -13874,7 +14440,7 @@ pub struct UIHandlePendingSessionLimitsExhaustedRequest { pub response: UISessionLimitsExhaustedResponse, } -/// Schema for the `UIUserInputResponse` type. +/// User response for a pending user-input request, with answer text and whether it was typed freeform. /// ///
/// @@ -13904,7 +14470,7 @@ pub struct UIUserInputResponse { pub struct UIHandlePendingUserInputRequest { /// The unique request ID from the user_input.requested event pub request_id: RequestId, - /// Schema for the `UIUserInputResponse` type. + /// User response for a pending user-input request, with answer text and whether it was typed freeform. pub response: UIUserInputResponse, } @@ -14024,7 +14590,7 @@ pub struct UsageMetricsModelMetricRequests { pub count: i64, } -/// Schema for the `UsageMetricsModelMetricTokenDetail` type. +/// Per-model token-detail entry containing the accumulated token count for one token type. /// ///
/// @@ -14063,7 +14629,7 @@ pub struct UsageMetricsModelMetricUsage { pub reasoning_tokens: Option, } -/// Schema for the `UsageMetricsModelMetric` type. +/// Per-model usage metrics, including request counts/costs, token usage, nano-AI units, and per-token-type details. /// ///
/// @@ -14086,7 +14652,7 @@ pub struct UsageMetricsModelMetric { pub usage: UsageMetricsModelMetricUsage, } -/// Schema for the `UsageMetricsTokenDetail` type. +/// Session-wide token-detail entry containing the accumulated token count for one token type. /// ///
/// @@ -14139,7 +14705,7 @@ pub struct UsageGetMetricsResult { pub total_user_requests: i64, } -/// Schema for the `UserAuthInfo` type. +/// Authentication-info variant for OAuth user auth, with host and login; the token remains in the runtime secret store. /// ///
/// @@ -14356,7 +14922,7 @@ pub struct WorkspaceDiffResult { pub requested_mode: WorkspaceDiffMode, } -/// Schema for the `WorkspacesCheckpoints` type. +/// Workspace checkpoint metadata with assigned number, human-readable title, and checkpoint filename. /// ///
/// @@ -15176,23 +15742,6 @@ pub struct SessionsGetRemoteControlStatusResult { pub status: serde_json::Value, } -/// Batch of spawn events plus a cursor for follow-up polls. -/// -///
-/// -/// **Experimental.** This type is part of an experimental wire-protocol surface -/// and may change or be removed in future SDK or CLI releases. -/// -///
-#[derive(Debug, Clone, Default, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct SessionsPollSpawnedSessionsResult { - /// Opaque cursor to pass back to receive only events after this batch. - pub cursor: String, - /// Spawn events emitted since the supplied cursor. - pub events: Vec, -} - /// Handle for releasing the extension tool registration. /// ///
@@ -15320,6 +15869,28 @@ pub struct SessionGitHubAuthSetCredentialsResult { pub success: bool, } +/// Result of collecting a redacted debug bundle. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionDebugCollectLogsResult { + /// Files included in the redacted bundle. + pub entries: Vec, + /// Destination kind that was written. + pub kind: DebugCollectLogsResultKind, + /// Actual archive path or staging directory path written. This may differ from the requested path when no-overwrite suffixing or fallback-to-temp-directory was needed. + pub path: String, + /// Optional files or directories that could not be included. + #[serde(skip_serializing_if = "Option::is_none")] + pub skipped_entries: Option>, +} + /// Identifies the target session. /// ///
@@ -16650,18 +17221,6 @@ pub struct SessionMcpIsServerRunningResult { pub running: bool, } -/// Empty result after recording the MCP OAuth response. -/// -///
-/// -/// **Experimental.** This type is part of an experimental wire-protocol surface -/// and may change or be removed in future SDK or CLI releases. -/// -///
-#[derive(Debug, Clone, Default, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct SessionMcpOauthRespondResult {} - /// Indicates whether the pending MCP OAuth response was accepted. /// ///
@@ -17351,13 +17910,16 @@ pub struct SessionPermissionsSetApproveAllResult { #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct SessionPermissionsSetAllowAllResult { - /// Authoritative allow-all state after the mutation + /// Authoritative full allow-all state after the mutation pub enabled: bool, + /// Authoritative allow-all mode after the mutation + #[serde(skip_serializing_if = "Option::is_none")] + pub mode: Option, /// Whether the operation succeeded pub success: bool, } -/// Current full allow-all permission state. +/// Current allow-all permission mode. /// ///
/// @@ -17370,6 +17932,9 @@ pub struct SessionPermissionsSetAllowAllResult { pub struct SessionPermissionsGetAllowAllResult { /// Whether full allow-all permissions are currently active pub enabled: bool, + /// Current allow-all mode + #[serde(skip_serializing_if = "Option::is_none")] + pub mode: Option, } /// Indicates whether the operation succeeded. @@ -17827,6 +18392,92 @@ pub struct SessionMetadataContextInfoResult { pub context_info: Option, } +/// 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 SessionMetadataGetContextAttributionParams { + /// Target session identifier + pub session_id: SessionId, +} + +/// Successful compaction history for the session. +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionMetadataGetContextAttributionResultContextAttributionCompactions { + /// Number of successful compactions in this session. + pub count: i64, +} + +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionMetadataGetContextAttributionResultContextAttributionEntriesItem { + /// Supplementary per-entry metadata (e.g. `messageCount`, `role`, `evictable`, `pluginSource`). Values are stringified; parse as needed and ignore unrecognized keys. + #[serde(skip_serializing_if = "Option::is_none")] + pub attributes: Option>, + /// Identifier for this entry, formed by joining its `kind` and source name (e.g. `tool:bash`, `skill:tmux`, `toolDefinition:bash`); unique within the snapshot. Use it to match the same entry across snapshots, to correlate with other APIs (skill/agent/MCP registries), and as the `parentId` target for nesting. Distinct from the human-facing `label`. + pub id: String, + /// Source category for this entry. Not a closed set — tolerate unknown values. Known values today: `skill`, `subagent`, `mcpServer`, `tool`, `system`, `toolDefinition`, `plugin`. + pub kind: String, + /// Human-readable display label, e.g. `bash` or `skill: tmux`. Presentation-only; may be localized/reformatted without notice — do not key off it. + pub label: String, + /// Optional `id` of the parent entry: e.g. a `plugin` entry parenting its `skill`/`mcpServer` entries, or the `system` entry parenting `toolDefinition` entries. Omitted for top-level entries. + #[serde(skip_serializing_if = "Option::is_none")] + pub parent_id: Option, + /// Token count currently in context attributable to this entry. + pub tokens: i64, +} + +/// Per-source token attribution snapshot for the current context window. The heaviest individual messages are available separately via `metadata.getContextHeaviestMessages`. +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionMetadataGetContextAttributionResultContextAttribution { + /// Successful compaction history for the session. + pub compactions: SessionMetadataGetContextAttributionResultContextAttributionCompactions, + /// Flat list of per-source attribution entries. Group by `kind` and render unrecognized kinds generically. Nesting and rollups are expressed via `parentId`. + pub entries: Vec, + /// Total token count of the current context window the entries are measured against (system message + conversation messages + tool definitions — the same total reported by /context). Divide an entry's `tokens` by this to derive its share. + pub total_tokens: i64, +} + +/// Per-source attribution breakdown for the session's current context window, or null if uninitialized. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionMetadataGetContextAttributionResult { + /// Per-source context-window attribution, or null if the session has not yet been initialized (no system prompt or tool metadata cached). + pub context_attribution: Option, +} + +/// The heaviest individual messages in the session's context window, most-expensive first. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionMetadataGetContextHeaviestMessagesResult { + /// Heaviest messages, most-expensive first. + pub messages: Vec, + /// Total token count of the current context window, so callers can compute each message's share without a second call. + pub total_tokens: i64, +} + /// Notify the session that its working directory context has changed. Emits a `session.context_changed` event so consumers (telemetry, OTel tracker, ACP, the timeline UI) can react. Use this when the host has detected a cwd/branch/repo change outside the session's normal lifecycle (e.g., after a shell command in interactive mode). /// ///
@@ -17873,6 +18524,47 @@ pub struct SessionMetadataRecomputeContextTokensResult { pub total_tokens: i64, } +/// Identifies the target session. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionSettingsSnapshotParams { + /// Target session identifier + pub session_id: SessionId, +} + +/// Redacted, serializable view of session runtime settings for SDK boundary consumers. Secrets and raw feature flags are intentionally excluded. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionSettingsSnapshotResult { + #[serde(skip_serializing_if = "Option::is_none")] + pub client_name: Option, + pub job: SessionSettingsJobSnapshot, + pub model: SessionSettingsModelSnapshot, + pub online_evaluation: SessionSettingsOnlineEvaluationSnapshot, + pub repo: SessionSettingsRepoSnapshot, + #[serde(skip_serializing_if = "Option::is_none")] + pub start_time_ms: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub timeout_ms: Option, + pub validation: SessionSettingsValidationSnapshot, + #[serde(skip_serializing_if = "Option::is_none")] + pub version: Option, +} + /// Identifier of the spawned process, used to correlate streamed output and exit notifications. /// ///
@@ -18879,6 +19571,31 @@ pub enum AgentRegistrySpawnResult { ValidationError(AgentRegistrySpawnValidationError), } +/// Current or requested allow-all mode. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum PermissionsAllowAllMode { + /// Permission requests follow the normal approval flow. + #[serde(rename = "off")] + Off, + /// Tool, path, and URL permission requests are automatically approved. + #[serde(rename = "on")] + On, + /// Permission requests follow the normal approval flow with an LLM advisory recommendation attached; clients may choose to auto-approve requests the judge evaluated as acceptable. + #[serde(rename = "auto")] + Auto, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, +} + /// API-key authentication for non-GitHub LLM providers (e.g. when running BYOM-style). #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] pub enum ApiKeyAuthInfoType { @@ -19190,6 +19907,129 @@ pub enum CopilotApiTokenAuthInfoType { CopilotApiToken, } +/// Source category for a collected debug bundle entry. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum DebugCollectLogsSource { + /// Session event log. + #[serde(rename = "events")] + Events, + /// Process log for the session. + #[serde(rename = "process-log")] + ProcessLog, + /// Interactive shell log for the session. + #[serde(rename = "shell-log")] + ShellLog, + /// Caller-provided diagnostic entry. + #[serde(rename = "additional")] + Additional, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, +} + +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum DebugCollectLogsDestinationArchiveKind { + #[serde(rename = "archive")] + #[default] + Archive, +} + +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum DebugCollectLogsDestinationDirectoryKind { + #[serde(rename = "directory")] + #[default] + Directory, +} + +/// Destination for the redacted debug bundle. +/// +///
+/// +/// **Experimental.** This 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 DebugCollectLogsDestination { + Archive(DebugCollectLogsDestinationArchive), + Directory(DebugCollectLogsDestinationDirectory), +} + +/// Kind of caller-provided debug log entry. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum DebugCollectLogsEntryKind { + /// Include a single server-local file. + #[serde(rename = "file")] + File, + /// Include files from a server-local directory recursively. + #[serde(rename = "directory")] + Directory, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, +} + +/// How a collected debug entry should be redacted before being staged. +/// +///
+/// +/// **Experimental.** This type 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 DebugCollectLogsRedaction { + /// Redact the file as plain UTF-8 log text. + #[serde(rename = "plain-text")] + PlainText, + /// Redact each non-empty line as a session event JSON object, falling back to plain-text redaction for malformed lines. + #[serde(rename = "events-jsonl")] + EventsJsonl, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, +} + +/// Destination kind that was written. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum DebugCollectLogsResultKind { + /// A .tgz archive was written. + #[serde(rename = "archive")] + Archive, + /// A directory containing redacted files was written. + #[serde(rename = "directory")] + Directory, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, +} + /// Server transport type: stdio, http, sse (deprecated), or memory /// ///
@@ -21718,6 +22558,79 @@ pub enum SessionsOpenStatus { Unknown, } +/// Rust-owned settings predicates exposed across the SDK boundary. Raw feature-flag names are intentionally not part of the contract. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum SessionSettingsPredicateName { + /// Whether the security-tools feature flag enables security tool wiring. + #[serde(rename = "securityToolsEnabled")] + SecurityToolsEnabled, + /// Whether third-party security tools should receive the security prompt. + #[serde(rename = "thirdPartySecurityPromptEnabled")] + ThirdPartySecurityPromptEnabled, + /// Whether validation may run in parallel. + #[serde(rename = "parallelValidationEnabled")] + ParallelValidationEnabled, + /// Whether runtime timing telemetry is enabled. + #[serde(rename = "runtimeTimingTelemetryEnabled")] + RuntimeTimingTelemetryEnabled, + /// Whether the co-author hook is enabled. + #[serde(rename = "coAuthorHookEnabled")] + CoAuthorHookEnabled, + /// Whether Chronicle integration is enabled. + #[serde(rename = "chronicleEnabled")] + ChronicleEnabled, + /// Whether content-exclusion policy may self-fetch data. + #[serde(rename = "contentExclusionSelfFetchEnabled")] + ContentExclusionSelfFetchEnabled, + /// Whether Claude Opus token-limit caps should be applied. + #[serde(rename = "capClaudeOpusTokenLimitsEnabled")] + CapClaudeOpusTokenLimitsEnabled, + /// Whether code-review behavior is enabled. + #[serde(rename = "codeReviewFeatureEnabled")] + CodeReviewFeatureEnabled, + /// Whether CCA should use the TypeScript autofind behavior. + #[serde(rename = "ccaUseTsAutofindEnabled")] + CcaUseTsAutofindEnabled, + /// Whether the dependency checker is enabled. + #[serde(rename = "dependencyCheckerEnabled")] + DependencyCheckerEnabled, + /// Whether the Dependabot checker is enabled. + #[serde(rename = "dependabotCheckerEnabled")] + DependabotCheckerEnabled, + /// Whether the CodeQL checker is enabled. + #[serde(rename = "codeqlCheckerEnabled")] + CodeqlCheckerEnabled, + /// Whether trivial-change handling is enabled. + #[serde(rename = "trivialChangeEnabled")] + TrivialChangeEnabled, + /// Whether trivial-change skip behavior is enabled. + #[serde(rename = "trivialChangeSkipEnabled")] + TrivialChangeSkipEnabled, + /// Whether trivial-change handling is enabled for code review. + #[serde(rename = "trivialChangeEnabledForCodeReview")] + TrivialChangeEnabledForCodeReview, + /// Whether trivial-change skip behavior is enabled for code review. + #[serde(rename = "trivialChangeSkipEnabledForCodeReview")] + TrivialChangeSkipEnabledForCodeReview, + /// Whether trivial-change handling is enabled for a specific tool. + #[serde(rename = "trivialChangeEnabledForTool")] + TrivialChangeEnabledForTool, + /// Whether trivial-change skip behavior is enabled for a specific tool. + #[serde(rename = "trivialChangeSkipEnabledForTool")] + TrivialChangeSkipEnabledForTool, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, +} + /// Which session sources to include. Defaults to `local` for backward compatibility. /// ///
diff --git a/rust/src/generated/rpc.rs b/rust/src/generated/rpc.rs index eb071cb1fb..b11a689fcf 100644 --- a/rust/src/generated/rpc.rs +++ b/rust/src/generated/rpc.rs @@ -161,7 +161,7 @@ impl<'a> ClientRpc<'a> { /// /// # Parameters /// - /// * `params` - Optional connection token presented by the SDK client during the handshake. + /// * `params` - Parameters for the `server.connect` handshake: an optional connection token and optional connection-level opt-ins (e.g. GitHub telemetry forwarding). /// /// # Returns /// @@ -2234,61 +2234,6 @@ impl<'a> ClientRpcSessions<'a> { Ok(serde_json::from_value(_value)?) } - /// Cursor-based long-poll for sessions spawned by the runtime (e.g. in response to a Mission Control `start_session` command). The cursor is an opaque token; pass it back to receive only spawn events that occurred AFTER the cursor was issued. Omit the cursor on the first call to receive any events buffered since the runtime started. Internal: this is a CLI background-daemon plumbing primitive. SDK consumers that need to react to runtime-spawned sessions should subscribe to a higher-level event stream rather than driving a long-poll loop. - /// - /// Wire method: `sessions.pollSpawnedSessions`. - /// - /// # Returns - /// - /// Batch of spawn events plus a cursor for follow-up polls. - /// - ///
- /// - /// **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 poll_spawned_sessions(&self) -> Result { - let wire_params = serde_json::json!({}); - let _value = self - .client - .call(rpc_methods::SESSIONS_POLLSPAWNEDSESSIONS, Some(wire_params)) - .await?; - Ok(serde_json::from_value(_value)?) - } - - /// Cursor-based long-poll for sessions spawned by the runtime (e.g. in response to a Mission Control `start_session` command). The cursor is an opaque token; pass it back to receive only spawn events that occurred AFTER the cursor was issued. Omit the cursor on the first call to receive any events buffered since the runtime started. Internal: this is a CLI background-daemon plumbing primitive. SDK consumers that need to react to runtime-spawned sessions should subscribe to a higher-level event stream rather than driving a long-poll loop. - /// - /// Wire method: `sessions.pollSpawnedSessions`. - /// - /// # Parameters - /// - /// * `params` - Cursor and optional long-poll wait for polling runtime-spawned sessions. - /// - /// # Returns - /// - /// Batch of spawn events plus a cursor for follow-up polls. - /// - ///
- /// - /// **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 poll_spawned_sessions_with_params( - &self, - params: SessionsPollSpawnedSessionsRequest, - ) -> Result { - let wire_params = serde_json::to_value(params)?; - let _value = self - .client - .call(rpc_methods::SESSIONS_POLLSPAWNEDSESSIONS, Some(wire_params)) - .await?; - Ok(serde_json::from_value(_value)?) - } - /// Registers extension-provided tools on the given session, gated by an optional `enabled` callback. Returns an opaque unsubscribe function the caller must invoke to deregister the tools when the extension is torn down. Marked internal because `loader`, `enabled`, and the returned `unsubscribe` are in-process handles that cannot cross the JSON-RPC boundary. Disappears once extension discovery / launch / tool registration are owned by the runtime: SDK consumers will pass pure config (search paths, disabled ids) via `SessionOptions` and the runtime will resolve, launch, register, and tear down extensions itself. /// /// Wire method: `sessions.registerExtensionToolsOnSession`. @@ -2635,6 +2580,13 @@ impl<'a> SessionRpc<'a> { } } + /// `session.debug.*` sub-namespace. + pub fn debug(&self) -> SessionRpcDebug<'a> { + SessionRpcDebug { + session: self.session, + } + } + /// `session.eventLog.*` sub-namespace. pub fn event_log(&self) -> SessionRpcEventLog<'a> { SessionRpcEventLog { @@ -2775,6 +2727,13 @@ impl<'a> SessionRpc<'a> { } } + /// `session.settings.*` sub-namespace. + pub fn settings(&self) -> SessionRpcSettings<'a> { + SessionRpcSettings { + session: self.session, + } + } + /// `session.shell.*` sub-namespace. pub fn shell(&self) -> SessionRpcShell<'a> { SessionRpcShell { @@ -3580,6 +3539,47 @@ impl<'a> SessionRpcCompletions<'a> { } } +/// `session.debug.*` RPCs. +#[derive(Clone, Copy)] +pub struct SessionRpcDebug<'a> { + pub(crate) session: &'a Session, +} + +impl<'a> SessionRpcDebug<'a> { + /// Collects a redacted session debug log bundle into a local archive or staging directory. The runtime includes session-owned logs by default and accepts caller-provided diagnostic entries so host applications can add their own files without changing this API shape. + /// + /// Wire method: `session.debug.collectLogs`. + /// + /// # Parameters + /// + /// * `params` - Options for collecting a redacted session debug bundle. + /// + /// # Returns + /// + /// Result of collecting a redacted debug bundle. + /// + ///
+ /// + /// **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 collect_logs( + &self, + params: DebugCollectLogsRequest, + ) -> 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_DEBUG_COLLECTLOGS, Some(wire_params)) + .await?; + Ok(serde_json::from_value(_value)?) + } +} + /// `session.eventLog.*` RPCs. #[derive(Clone, Copy)] pub struct SessionRpcEventLog<'a> { @@ -5002,39 +5002,6 @@ pub struct SessionRpcMcpOauth<'a> { } impl<'a> SessionRpcMcpOauth<'a> { - /// Responds to a pending MCP OAuth request with an in-process provider. This internal CLI-only API accepts a live OAuthClientProvider instance and cannot be used over the SDK JSON-RPC boundary. Use session.mcp.oauth.handlePendingRequest instead for the public SDK-safe response path. - /// - /// Wire method: `session.mcp.oauth.respond`. - /// - /// # Parameters - /// - /// * `params` - MCP OAuth request id and optional provider response. - /// - /// # Returns - /// - /// Empty result after recording the MCP OAuth response. - /// - ///
- /// - /// **Experimental.** This API is part of an experimental wire-protocol surface - /// and may change or be removed in future SDK or CLI releases. Pin both the - /// SDK and CLI versions if your code depends on it. - /// - ///
- pub(crate) async fn respond( - &self, - params: McpOauthRespondRequest, - ) -> Result { - let mut wire_params = serde_json::to_value(params)?; - wire_params["sessionId"] = serde_json::Value::String(self.session.id().to_string()); - let _value = self - .session - .client() - .call(rpc_methods::SESSION_MCP_OAUTH_RESPOND, Some(wire_params)) - .await?; - Ok(serde_json::from_value(_value)?) - } - /// Resolves a pending MCP OAuth request with a host-provided token or cancellation. The pending request is emitted as mcp.oauth_required with the data necessary to authorize the request. /// /// Wire method: `session.mcp.oauth.handlePendingRequest`. @@ -5220,6 +5187,70 @@ impl<'a> SessionRpcMetadata<'a> { Ok(serde_json::from_value(_value)?) } + /// Returns the experimental per-source attribution breakdown of the session's current context window as a flat list of entries (skills, subagents, MCP servers, built-in tools, plugin rollups, system/tool-definition costs, with nesting via parentId), plus the successful compaction count. The heaviest individual messages are available separately via `metadata.getContextHeaviestMessages`. Returns null until the session has initialized its system prompt and tool metadata. + /// + /// Wire method: `session.metadata.getContextAttribution`. + /// + /// # Returns + /// + /// Per-source attribution breakdown for the session's current context window, or null if uninitialized. + /// + ///
+ /// + /// **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_context_attribution(&self) -> Result { + let wire_params = serde_json::json!({ "sessionId": self.session.id() }); + let _value = self + .session + .client() + .call( + rpc_methods::SESSION_METADATA_GETCONTEXTATTRIBUTION, + Some(wire_params), + ) + .await?; + Ok(serde_json::from_value(_value)?) + } + + /// Returns the largest individual messages currently in the session's context window, most-expensive first. Companion to `metadata.getContextAttribution`. Returns an empty list until the session has initialized. + /// + /// Wire method: `session.metadata.getContextHeaviestMessages`. + /// + /// # Parameters + /// + /// * `params` - Parameters for the heaviest-messages query. + /// + /// # Returns + /// + /// The heaviest individual messages in the session's context window, most-expensive first. + /// + ///
+ /// + /// **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_context_heaviest_messages( + &self, + params: MetadataContextHeaviestMessagesRequest, + ) -> 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_METADATA_GETCONTEXTHEAVIESTMESSAGES, + Some(wire_params), + ) + .await?; + Ok(serde_json::from_value(_value)?) + } + /// Records a working-directory/git context change and emits a `session.context_changed` event. /// /// Wire method: `session.metadata.recordContextChange`. @@ -5849,13 +5880,13 @@ impl<'a> SessionRpcPermissions<'a> { Ok(serde_json::from_value(_value)?) } - /// Enables or disables full allow-all permissions (tools, paths, and URLs) for the session. Used by attach-mode clients (e.g. LocalRpcSession's `/allow-all` forwarder) to flip the target session's permission state. Unlike `setApproveAll`, this swaps in the unrestricted path and URL managers and emits `session.permissions_changed` on transition. The result returns the authoritative post-mutation state so callers can update their local mirrors without racing the `session.permissions_changed` notification on the same wire. + /// Sets the allow-all permission mode for the session. Used by attach-mode clients (e.g. LocalRpcSession's `/allow-all` forwarder) to flip the target session's permission state. The `on` mode swaps in unrestricted path and URL managers and emits `session.permissions_changed` on transition; the `auto` mode keeps normal prompt paths active while attaching LLM safety recommendations. The result returns the authoritative post-mutation state so callers can update their local mirrors without racing the `session.permissions_changed` notification on the same wire. /// /// Wire method: `session.permissions.setAllowAll`. /// /// # Parameters /// - /// * `params` - Whether to enable full allow-all permissions for the session. + /// * `params` - Allow-all mode to apply for the session. /// /// # Returns /// @@ -5885,13 +5916,13 @@ impl<'a> SessionRpcPermissions<'a> { Ok(serde_json::from_value(_value)?) } - /// Returns whether full allow-all permissions are currently active for the session. + /// Returns the current allow-all permission mode for the session. /// /// Wire method: `session.permissions.getAllowAll`. /// /// # Returns /// - /// Current full allow-all permission state. + /// Current allow-all permission mode. /// ///
/// @@ -7023,6 +7054,75 @@ impl<'a> SessionRpcSchedule<'a> { } } +/// `session.settings.*` RPCs. +#[derive(Clone, Copy)] +pub struct SessionRpcSettings<'a> { + pub(crate) session: &'a Session, +} + +impl<'a> SessionRpcSettings<'a> { + /// Returns a redacted snapshot of session runtime settings, with secrets and raw feature flags excluded. Internal: the runtime settings shape is a runtime-internal surface and is deliberately kept out of the public SDK, because consumers should not depend on the runtime's internal settings layout. It remains callable in-process and is expected to be reworked as the runtime internals are consolidated. + /// + /// Wire method: `session.settings.snapshot`. + /// + /// # Returns + /// + /// Redacted, serializable view of session runtime settings for SDK boundary consumers. Secrets and raw feature flags are intentionally excluded. + /// + ///
+ /// + /// **Experimental.** This API is part of an experimental wire-protocol surface + /// and may change or be removed in future SDK or CLI releases. Pin both the + /// SDK and CLI versions if your code depends on it. + /// + ///
+ pub(crate) async fn snapshot(&self) -> Result { + let wire_params = serde_json::json!({ "sessionId": self.session.id() }); + let _value = self + .session + .client() + .call(rpc_methods::SESSION_SETTINGS_SNAPSHOT, Some(wire_params)) + .await?; + Ok(serde_json::from_value(_value)?) + } + + /// Evaluates a named Rust-owned settings predicate without exposing raw feature flags. Internal: the raw feature-flag names and composition are runtime-internal, so this predicate-evaluation helper is kept out of the public SDK surface and is callable in-process only. + /// + /// Wire method: `session.settings.evaluatePredicate`. + /// + /// # Parameters + /// + /// * `params` - Named Rust-owned settings predicate to evaluate for this session. + /// + /// # Returns + /// + /// Result of evaluating a Rust-owned settings predicate. + /// + ///
+ /// + /// **Experimental.** This API is part of an experimental wire-protocol surface + /// and may change or be removed in future SDK or CLI releases. Pin both the + /// SDK and CLI versions if your code depends on it. + /// + ///
+ pub(crate) async fn evaluate_predicate( + &self, + params: SessionSettingsEvaluatePredicateRequest, + ) -> 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_SETTINGS_EVALUATEPREDICATE, + Some(wire_params), + ) + .await?; + Ok(serde_json::from_value(_value)?) + } +} + /// `session.shell.*` RPCs. #[derive(Clone, Copy)] pub struct SessionRpcShell<'a> { diff --git a/rust/src/generated/session_events.rs b/rust/src/generated/session_events.rs index af5fac2a8b..4e073d45bc 100644 --- a/rust/src/generated/session_events.rs +++ b/rust/src/generated/session_events.rs @@ -81,6 +81,8 @@ pub enum SessionEventType { AssistantReasoning, #[serde(rename = "assistant.reasoning_delta")] AssistantReasoningDelta, + #[serde(rename = "assistant.tool_call_delta")] + AssistantToolCallDelta, #[serde(rename = "assistant.streaming_delta")] AssistantStreamingDelta, #[serde(rename = "assistant.message")] @@ -346,6 +348,8 @@ pub enum SessionEventData { AssistantReasoning(AssistantReasoningData), #[serde(rename = "assistant.reasoning_delta")] AssistantReasoningDelta(AssistantReasoningDeltaData), + #[serde(rename = "assistant.tool_call_delta")] + AssistantToolCallDelta(AssistantToolCallDeltaData), #[serde(rename = "assistant.streaming_delta")] AssistantStreamingDelta(AssistantStreamingDeltaData), #[serde(rename = "assistant.message")] @@ -628,6 +632,9 @@ pub struct SessionStartData { pub session_limits: Option, /// ISO 8601 timestamp when the session was created pub start_time: String, + /// Output verbosity level used for model calls, if applicable (e.g. "low", "medium", "high") + #[serde(skip_serializing_if = "Option::is_none")] + pub verbosity: Option, /// Schema version number for the session event format pub version: i64, } @@ -673,6 +680,9 @@ pub struct SessionResumeData { /// True when this resume attached to a session that the runtime already had running in-memory (for example, an extension joining a session another client was actively driving). False (or omitted) for cold resumes — the runtime had to reconstitute the session from its persisted event log. #[serde(skip_serializing_if = "Option::is_none")] pub session_was_active: Option, + /// Output verbosity level used for model calls, if applicable (e.g. "low", "medium", "high") + #[serde(skip_serializing_if = "Option::is_none")] + pub verbosity: Option, } /// Session event "session.remote_steerable_changed". Notifies that the session's remote steering capability has changed @@ -844,12 +854,18 @@ pub struct SessionModelChangeData { /// Reasoning summary mode before the model change, if applicable #[serde(skip_serializing_if = "Option::is_none")] pub previous_reasoning_summary: Option, + /// Output verbosity level before the model change, if applicable + #[serde(skip_serializing_if = "Option::is_none")] + pub previous_verbosity: Option, /// Reasoning effort level after the model change, if applicable #[serde(skip_serializing_if = "Option::is_none")] pub reasoning_effort: Option, /// Reasoning summary mode after the model change, if applicable #[serde(skip_serializing_if = "Option::is_none")] pub reasoning_summary: Option, + /// Output verbosity level after the model change, if applicable + #[serde(skip_serializing_if = "Option::is_none")] + pub verbosity: Option, } /// Session event "session.mode_changed". Agent mode change details including previous and new modes @@ -870,12 +886,32 @@ pub struct SessionSessionLimitsChangedData { pub session_limits: Option, } -/// Session event "session.permissions_changed". Permissions change details carrying the aggregate allow-all boolean transition. +/// Session event "session.permissions_changed". Permissions change details carrying the aggregate allow-all transition. #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct SessionPermissionsChangedData { + /// Allow-all mode after the change + /// + ///
+ /// + /// **Experimental.** This type is part of an experimental wire-protocol surface + /// and may change or be removed in future SDK or CLI releases. + /// + ///
+ #[serde(skip_serializing_if = "Option::is_none")] + pub allow_all_permission_mode: Option, /// Aggregate allow-all flag after the change pub allow_all_permissions: bool, + /// Allow-all mode before the change + /// + ///
+ /// + /// **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 previous_allow_all_permission_mode: Option, /// Aggregate allow-all flag before the change pub previous_allow_all_permissions: bool, } @@ -1011,7 +1047,7 @@ pub struct ShutdownModelMetricRequests { pub count: Option, } -/// Schema for the `ShutdownModelMetricTokenDetail` type. +/// A token-type entry in a shutdown model metric, storing the accumulated token count. #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct ShutdownModelMetricTokenDetail { @@ -1036,7 +1072,7 @@ pub struct ShutdownModelMetricUsage { pub reasoning_tokens: Option, } -/// Schema for the `ShutdownModelMetric` type. +/// Per-model shutdown metrics with request counts, token usage, nano-AI units, and token details. #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct ShutdownModelMetric { @@ -1059,7 +1095,7 @@ pub struct ShutdownModelMetric { pub usage: ShutdownModelMetricUsage, } -/// Schema for the `ShutdownTokenDetail` type. +/// A session-wide shutdown token-type entry storing the accumulated token count. #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct ShutdownTokenDetail { @@ -1193,6 +1229,9 @@ pub struct SessionCompactionStartData { /// Token count from non-system messages (user, assistant, tool) at compaction start #[serde(skip_serializing_if = "Option::is_none")] pub conversation_tokens: Option, + /// Model identifier used for compaction, when known + #[serde(skip_serializing_if = "Option::is_none")] + pub model: Option, /// Token count from system message(s) at compaction start #[serde(skip_serializing_if = "Option::is_none")] pub system_tokens: Option, @@ -1327,7 +1366,7 @@ pub struct SessionTaskCompleteData { pub summary: Option, } -/// Session event "user.message". +/// Session event "user.message". Payload of `user.message` with displayed and model-transformed content, attachments, source/delivery metadata, mode, and telemetry IDs. #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct UserMessageData { @@ -1377,6 +1416,9 @@ pub struct AssistantTurnStartData { /// CAPI interaction ID for correlating this turn with upstream telemetry #[serde(skip_serializing_if = "Option::is_none")] pub interaction_id: Option, + /// Model identifier used for this turn, when known + #[serde(skip_serializing_if = "Option::is_none")] + pub model: Option, /// Identifier for this turn within the agentic loop, typically a stringified turn number pub turn_id: String, } @@ -1409,6 +1451,22 @@ pub struct AssistantReasoningDeltaData { pub reasoning_id: String, } +/// Session event "assistant.tool_call_delta". Streaming tool-call input delta for incremental tool-call updates +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct AssistantToolCallDeltaData { + /// Raw provider tool input fragment to append for this tool call. Function/tool-use providers stream serialized JSON argument text (so newlines inside JSON string values may appear as escaped `\n` until the accumulated JSON is parsed); custom tool calls stream raw custom input. + pub input_delta: String, + /// Tool call ID this delta belongs to, matching the corresponding assistant.message tool request + pub tool_call_id: String, + /// Name of the tool being invoked, when known from the stream + #[serde(skip_serializing_if = "Option::is_none")] + pub tool_name: Option, + /// Tool call type, when known from the stream + #[serde(skip_serializing_if = "Option::is_none")] + pub tool_type: Option, +} + /// Session event "assistant.streaming_delta". Streaming response progress with cumulative byte count #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] @@ -1650,6 +1708,9 @@ pub struct AssistantMessageDeltaData { #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct AssistantTurnEndData { + /// Model identifier used for this turn, when known + #[serde(skip_serializing_if = "Option::is_none")] + pub model: Option, /// Identifier of the turn that has ended, matching the corresponding assistant.turn_start event pub turn_id: String, } @@ -1689,7 +1750,7 @@ pub struct AssistantUsageCopilotUsage { pub total_nano_aiu: f64, } -/// Schema for the `AssistantUsageQuotaSnapshot` type. +/// Internal per-quota snapshot for assistant usage, including entitlement, consumed requests, overage, reset date, and remaining quota. #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub(crate) struct AssistantUsageQuotaSnapshot { @@ -1807,7 +1868,7 @@ pub struct AssistantUsageData { pub service_request_id: Option, /// Time to first token in milliseconds. Only available for streaming requests #[serde(skip_serializing_if = "Option::is_none")] - pub time_to_first_token_ms: Option, + pub time_to_first_token_ms: Option, } /// Content-free structural summary of the failing request for diagnosing malformed 4xx calls @@ -1910,7 +1971,7 @@ pub struct ToolExecutionStartShellToolInfo { pub possible_paths: Vec, } -/// Schema for the `ToolExecutionStartToolDescriptionMetaUI` type. +/// MCP Apps tool `_meta.ui` resource URI and visibility captured on `tool.execution_start`. #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct ToolExecutionStartToolDescriptionMetaUI { @@ -1926,7 +1987,7 @@ pub struct ToolExecutionStartToolDescriptionMetaUI { #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct ToolExecutionStartToolDescriptionMeta { - /// Schema for the `ToolExecutionStartToolDescriptionMetaUI` type. + /// MCP Apps tool `_meta.ui` resource URI and visibility captured on `tool.execution_start`. #[serde(skip_serializing_if = "Option::is_none")] pub ui: Option, } @@ -2158,7 +2219,7 @@ pub struct ToolExecutionCompleteContentResourceLink { pub uri: String, } -/// Schema for the `EmbeddedTextResourceContents` type. +/// Embedded text resource contents identified by a URI, with an optional MIME type and a text payload. #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct EmbeddedTextResourceContents { @@ -2171,7 +2232,7 @@ pub struct EmbeddedTextResourceContents { pub uri: String, } -/// Schema for the `EmbeddedBlobResourceContents` type. +/// Embedded binary resource contents identified by a URI, with an optional MIME type and a base64-encoded blob. #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct EmbeddedBlobResourceContents { @@ -2194,7 +2255,7 @@ pub struct ToolExecutionCompleteContentResource { pub r#type: ToolExecutionCompleteContentResourceType, } -/// Schema for the `ToolExecutionCompleteUIResourceMetaUICsp` type. +/// CSP domain allowlists for an MCP Apps UI resource, including connect, resource, frame, and base URI domains. #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct ToolExecutionCompleteUIResourceMetaUICsp { @@ -2208,54 +2269,54 @@ pub struct ToolExecutionCompleteUIResourceMetaUICsp { pub resource_domains: Option>, } -/// Schema for the `ToolExecutionCompleteUIResourceMetaUIPermissionsCamera` type. +/// Marker object for camera permission on an MCP Apps UI resource. #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct ToolExecutionCompleteUIResourceMetaUIPermissionsCamera {} -/// Schema for the `ToolExecutionCompleteUIResourceMetaUIPermissionsClipboardWrite` type. +/// Marker object for clipboard-write permission on an MCP Apps UI resource. #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct ToolExecutionCompleteUIResourceMetaUIPermissionsClipboardWrite {} -/// Schema for the `ToolExecutionCompleteUIResourceMetaUIPermissionsGeolocation` type. +/// Marker object for geolocation permission on an MCP Apps UI resource. #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct ToolExecutionCompleteUIResourceMetaUIPermissionsGeolocation {} -/// Schema for the `ToolExecutionCompleteUIResourceMetaUIPermissionsMicrophone` type. +/// Marker object for microphone permission on an MCP Apps UI resource. #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct ToolExecutionCompleteUIResourceMetaUIPermissionsMicrophone {} -/// Schema for the `ToolExecutionCompleteUIResourceMetaUIPermissions` type. +/// Browser permission metadata for an MCP Apps UI resource, including camera, microphone, geolocation, and clipboard-write. #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct ToolExecutionCompleteUIResourceMetaUIPermissions { - /// Schema for the `ToolExecutionCompleteUIResourceMetaUIPermissionsCamera` type. + /// Marker object for camera permission on an MCP Apps UI resource. #[serde(skip_serializing_if = "Option::is_none")] pub camera: Option, - /// Schema for the `ToolExecutionCompleteUIResourceMetaUIPermissionsClipboardWrite` type. + /// Marker object for clipboard-write permission on an MCP Apps UI resource. #[serde(skip_serializing_if = "Option::is_none")] pub clipboard_write: Option, - /// Schema for the `ToolExecutionCompleteUIResourceMetaUIPermissionsGeolocation` type. + /// Marker object for geolocation permission on an MCP Apps UI resource. #[serde(skip_serializing_if = "Option::is_none")] pub geolocation: Option, - /// Schema for the `ToolExecutionCompleteUIResourceMetaUIPermissionsMicrophone` type. + /// Marker object for microphone permission on an MCP Apps UI resource. #[serde(skip_serializing_if = "Option::is_none")] pub microphone: Option, } -/// Schema for the `ToolExecutionCompleteUIResourceMetaUI` type. +/// MCP Apps UI resource metadata for a completed tool result, including CSP, permissions, domain, and border preference. #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct ToolExecutionCompleteUIResourceMetaUI { - /// Schema for the `ToolExecutionCompleteUIResourceMetaUICsp` type. + /// CSP domain allowlists for an MCP Apps UI resource, including connect, resource, frame, and base URI domains. #[serde(skip_serializing_if = "Option::is_none")] pub csp: Option, #[serde(skip_serializing_if = "Option::is_none")] pub domain: Option, - /// Schema for the `ToolExecutionCompleteUIResourceMetaUIPermissions` type. + /// Browser permission metadata for an MCP Apps UI resource, including camera, microphone, geolocation, and clipboard-write. #[serde(skip_serializing_if = "Option::is_none")] pub permissions: Option, #[serde(skip_serializing_if = "Option::is_none")] @@ -2266,7 +2327,7 @@ pub struct ToolExecutionCompleteUIResourceMetaUI { #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct ToolExecutionCompleteUIResourceMeta { - /// Schema for the `ToolExecutionCompleteUIResourceMetaUI` type. + /// MCP Apps UI resource metadata for a completed tool result, including CSP, permissions, domain, and border preference. #[serde(skip_serializing_if = "Option::is_none")] pub ui: Option, } @@ -2330,7 +2391,7 @@ pub struct ToolExecutionCompleteResult { pub ui_resource: Option, } -/// Schema for the `ToolExecutionCompleteToolDescriptionMetaUI` type. +/// MCP Apps tool `_meta.ui` resource URI and visibility captured on `tool.execution_complete`. #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct ToolExecutionCompleteToolDescriptionMetaUI { @@ -2346,7 +2407,7 @@ pub struct ToolExecutionCompleteToolDescriptionMetaUI { #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct ToolExecutionCompleteToolDescriptionMeta { - /// Schema for the `ToolExecutionCompleteToolDescriptionMetaUI` type. + /// MCP Apps tool `_meta.ui` resource URI and visibility captured on `tool.execution_complete`. #[serde(skip_serializing_if = "Option::is_none")] pub ui: Option, } @@ -2419,6 +2480,9 @@ pub struct SkillInvokedData { /// Description of the skill from its SKILL.md frontmatter #[serde(skip_serializing_if = "Option::is_none")] pub description: Option, + /// Model identifier active when the skill was invoked, when known + #[serde(skip_serializing_if = "Option::is_none")] + pub model: Option, /// Name of the invoked skill pub name: String, /// File path to the SKILL.md definition @@ -2637,7 +2701,7 @@ pub struct SystemNotificationData { pub kind: serde_json::Value, } -/// Schema for the `PermissionRequestShellCommand` type. +/// A parsed command identifier in a shell permission request, including whether it is read-only. #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct PermissionRequestShellCommand { @@ -2647,7 +2711,7 @@ pub struct PermissionRequestShellCommand { pub read_only: bool, } -/// Schema for the `PermissionRequestShellPossibleUrl` type. +/// A URL that may be accessed by a command in a shell permission request. #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct PermissionRequestShellPossibleUrl { @@ -2706,6 +2770,12 @@ pub struct PermissionRequestWrite { /// Complete new file contents for newly created files #[serde(skip_serializing_if = "Option::is_none")] pub new_file_contents: Option, + /// True when a built-in file tool (apply_patch / str_replace_editor) asked to write a path the sandbox filesystem policy would block, and the host opted in via sandbox.allowBypass. This is a request, not a grant: the write happens unsandboxed only if the user approves this permission request. Hosts should highlight the elevated risk in the approval UI. + #[serde(skip_serializing_if = "Option::is_none")] + pub request_sandbox_bypass: Option, + /// Justification for the sandbox-bypass request. Only meaningful when requestSandboxBypass is true. + #[serde(skip_serializing_if = "Option::is_none")] + pub request_sandbox_bypass_reason: Option, /// Tool call ID that triggered this permission request #[serde(skip_serializing_if = "Option::is_none")] pub tool_call_id: Option, @@ -2762,6 +2832,12 @@ pub struct PermissionRequestUrl { pub intention: String, /// Permission kind discriminator pub kind: PermissionRequestUrlKind, + /// True when this URL fetch is requesting to bypass the sandbox network policy: either the model set requestSandboxBypass: true, or the tool re-issued the request as an interactive bypass after the network policy denied the approved URL (host opted in via sandbox.allowBypass). This is a request, not a grant: the fetch runs only if the user approves this permission request. Hosts should highlight the elevated risk in the approval UI. + #[serde(skip_serializing_if = "Option::is_none")] + pub request_sandbox_bypass: Option, + /// Model-provided justification for the sandbox-bypass request. Only meaningful when requestSandboxBypass is true. + #[serde(skip_serializing_if = "Option::is_none")] + pub request_sandbox_bypass_reason: Option, /// Tool call ID that triggered this permission request #[serde(skip_serializing_if = "Option::is_none")] pub tool_call_id: Option, @@ -2865,10 +2941,38 @@ pub struct PermissionRequestExtensionPermissionAccess { pub tool_call_id: Option, } +/// Auto-approval judge information attached to a permission request. Present (non-null) only when the session's allow-all mode is "auto"; its absence means auto mode was off and the judge did not evaluate the request. The `recommendation` conveys the judge's disposition for this request. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct PermissionAutoApproval { + /// Human-readable reason for the judge's recommendation, when available. + #[serde(skip_serializing_if = "Option::is_none")] + pub reason: Option, + /// The auto-approval safety judge's outcome for this request. + pub recommendation: AutoApprovalRecommendation, +} + /// Shell command permission prompt #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct PermissionPromptRequestCommands { + /// Auto-approval judge information for this request; present only when auto mode is enabled. + /// + ///
+ /// + /// **Experimental.** This type is part of an experimental wire-protocol surface + /// and may change or be removed in future SDK or CLI releases. + /// + ///
+ #[serde(skip_serializing_if = "Option::is_none")] + pub auto_approval: Option, /// Whether the UI can offer session-wide approval for this command pattern pub can_offer_session_approval: bool, /// Command identifiers covered by this approval prompt @@ -2891,6 +2995,16 @@ pub struct PermissionPromptRequestCommands { #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct PermissionPromptRequestWrite { + /// Auto-approval judge information for this request; present only when auto mode is enabled. + /// + ///
+ /// + /// **Experimental.** This type is part of an experimental wire-protocol surface + /// and may change or be removed in future SDK or CLI releases. + /// + ///
+ #[serde(skip_serializing_if = "Option::is_none")] + pub auto_approval: Option, /// Whether the UI can offer session-wide approval for file write operations pub can_offer_session_approval: bool, /// Unified diff showing the proposed changes @@ -2913,6 +3027,16 @@ pub struct PermissionPromptRequestWrite { #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct PermissionPromptRequestRead { + /// Auto-approval judge information for this request; present only when auto mode is enabled. + /// + ///
+ /// + /// **Experimental.** This type is part of an experimental wire-protocol surface + /// and may change or be removed in future SDK or CLI releases. + /// + ///
+ #[serde(skip_serializing_if = "Option::is_none")] + pub auto_approval: Option, /// Human-readable description of why the file is being read pub intention: String, /// Prompt kind discriminator @@ -2931,6 +3055,16 @@ pub struct PermissionPromptRequestMcp { /// Arguments to pass to the MCP tool #[serde(skip_serializing_if = "Option::is_none")] pub args: Option, + /// Auto-approval judge information for this request; present only when auto mode is enabled. + /// + ///
+ /// + /// **Experimental.** This type is part of an experimental wire-protocol surface + /// and may change or be removed in future SDK or CLI releases. + /// + ///
+ #[serde(skip_serializing_if = "Option::is_none")] + pub auto_approval: Option, /// Prompt kind discriminator pub kind: PermissionPromptRequestMcpKind, /// Name of the MCP server providing the tool @@ -2948,10 +3082,26 @@ pub struct PermissionPromptRequestMcp { #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct PermissionPromptRequestUrl { + /// Auto-approval judge information for this request; present only when auto mode is enabled. + /// + ///
+ /// + /// **Experimental.** This type is part of an experimental wire-protocol surface + /// and may change or be removed in future SDK or CLI releases. + /// + ///
+ #[serde(skip_serializing_if = "Option::is_none")] + pub auto_approval: Option, /// Human-readable description of why the URL is being accessed pub intention: String, /// Prompt kind discriminator pub kind: PermissionPromptRequestUrlKind, + /// True when this URL fetch is requesting to bypass the sandbox network policy: either the model set requestSandboxBypass: true, or the tool re-issued the request as an interactive bypass after the network policy denied the approved URL (host opted in via sandbox.allowBypass). This is a request, not a grant: the fetch runs only if the user approves this permission request. Hosts should highlight the elevated risk in the approval UI. + #[serde(skip_serializing_if = "Option::is_none")] + pub request_sandbox_bypass: Option, + /// Model-provided justification for the sandbox-bypass request. Only meaningful when requestSandboxBypass is true. + #[serde(skip_serializing_if = "Option::is_none")] + pub request_sandbox_bypass_reason: Option, /// Tool call ID that triggered this permission request #[serde(skip_serializing_if = "Option::is_none")] pub tool_call_id: Option, @@ -2966,6 +3116,16 @@ pub struct PermissionPromptRequestMemory { /// Whether this is a store or vote memory operation #[serde(skip_serializing_if = "Option::is_none")] pub action: Option, + /// Auto-approval judge information for this request; present only when auto mode is enabled. + /// + ///
+ /// + /// **Experimental.** This type is part of an experimental wire-protocol surface + /// and may change or be removed in future SDK or CLI releases. + /// + ///
+ #[serde(skip_serializing_if = "Option::is_none")] + pub auto_approval: Option, /// Source references for the stored fact (store only) #[serde(skip_serializing_if = "Option::is_none")] pub citations: Option, @@ -2994,6 +3154,16 @@ pub struct PermissionPromptRequestCustomTool { /// Arguments to pass to the custom tool #[serde(skip_serializing_if = "Option::is_none")] pub args: Option, + /// Auto-approval judge information for this request; present only when auto mode is enabled. + /// + ///
+ /// + /// **Experimental.** This type is part of an experimental wire-protocol surface + /// and may change or be removed in future SDK or CLI releases. + /// + ///
+ #[serde(skip_serializing_if = "Option::is_none")] + pub auto_approval: Option, /// Prompt kind discriminator pub kind: PermissionPromptRequestCustomToolKind, /// Tool call ID that triggered this permission request @@ -3011,6 +3181,16 @@ pub struct PermissionPromptRequestCustomTool { pub struct PermissionPromptRequestPath { /// Underlying permission kind that needs path approval pub access_kind: PermissionPromptRequestPathAccessKind, + /// Auto-approval judge information for this request; present only when auto mode is enabled. + /// + ///
+ /// + /// **Experimental.** This type is part of an experimental wire-protocol surface + /// and may change or be removed in future SDK or CLI releases. + /// + ///
+ #[serde(skip_serializing_if = "Option::is_none")] + pub auto_approval: Option, /// Prompt kind discriminator pub kind: PermissionPromptRequestPathKind, /// File paths that require explicit approval @@ -3024,6 +3204,16 @@ pub struct PermissionPromptRequestPath { #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct PermissionPromptRequestHook { + /// Auto-approval judge information for this request; present only when auto mode is enabled. + /// + ///
+ /// + /// **Experimental.** This type is part of an experimental wire-protocol surface + /// and may change or be removed in future SDK or CLI releases. + /// + ///
+ #[serde(skip_serializing_if = "Option::is_none")] + pub auto_approval: Option, /// Optional message from the hook explaining why confirmation is needed #[serde(skip_serializing_if = "Option::is_none")] pub hook_message: Option, @@ -3043,6 +3233,16 @@ pub struct PermissionPromptRequestHook { #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct PermissionPromptRequestExtensionManagement { + /// Auto-approval judge information for this request; present only when auto mode is enabled. + /// + ///
+ /// + /// **Experimental.** This type is part of an experimental wire-protocol surface + /// and may change or be removed in future SDK or CLI releases. + /// + ///
+ #[serde(skip_serializing_if = "Option::is_none")] + pub auto_approval: Option, /// Name of the extension being managed #[serde(skip_serializing_if = "Option::is_none")] pub extension_name: Option, @@ -3059,6 +3259,16 @@ pub struct PermissionPromptRequestExtensionManagement { #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct PermissionPromptRequestExtensionPermissionAccess { + /// Auto-approval judge information for this request; present only when auto mode is enabled. + /// + ///
+ /// + /// **Experimental.** This type is part of an experimental wire-protocol surface + /// and may change or be removed in future SDK or CLI releases. + /// + ///
+ #[serde(skip_serializing_if = "Option::is_none")] + pub auto_approval: Option, /// Capabilities the extension is requesting pub capabilities: Vec, /// Name of the extension requesting permission access @@ -3086,7 +3296,7 @@ pub struct PermissionRequestedData { pub resolved_by_hook: Option, } -/// Schema for the `PermissionApproved` type. +/// Permission response variant indicating the request was approved without persisting an approval rule. #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct PermissionApproved { @@ -3094,7 +3304,7 @@ pub struct PermissionApproved { pub kind: PermissionApprovedKind, } -/// Schema for the `UserToolSessionApprovalCommands` type. +/// Session-scoped tool-approval rule for specific shell command identifiers. #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct UserToolSessionApprovalCommands { @@ -3104,7 +3314,7 @@ pub struct UserToolSessionApprovalCommands { pub kind: UserToolSessionApprovalCommandsKind, } -/// Schema for the `UserToolSessionApprovalRead` type. +/// Session-scoped tool-approval rule for read-only filesystem operations. #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct UserToolSessionApprovalRead { @@ -3112,7 +3322,7 @@ pub struct UserToolSessionApprovalRead { pub kind: UserToolSessionApprovalReadKind, } -/// Schema for the `UserToolSessionApprovalWrite` type. +/// Session-scoped tool-approval rule for filesystem write operations. #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct UserToolSessionApprovalWrite { @@ -3120,7 +3330,7 @@ pub struct UserToolSessionApprovalWrite { pub kind: UserToolSessionApprovalWriteKind, } -/// Schema for the `UserToolSessionApprovalMcp` type. +/// Session-scoped tool-approval rule for an MCP server tool, or all tools on the server when `toolName` is null. #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct UserToolSessionApprovalMcp { @@ -3132,7 +3342,7 @@ pub struct UserToolSessionApprovalMcp { pub tool_name: Option, } -/// Schema for the `UserToolSessionApprovalMemory` type. +/// Session-scoped tool-approval rule for writes to long-term memory. #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct UserToolSessionApprovalMemory { @@ -3140,7 +3350,7 @@ pub struct UserToolSessionApprovalMemory { pub kind: UserToolSessionApprovalMemoryKind, } -/// Schema for the `UserToolSessionApprovalCustomTool` type. +/// Session-scoped tool-approval rule for a custom tool, keyed by tool name. #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct UserToolSessionApprovalCustomTool { @@ -3150,7 +3360,7 @@ pub struct UserToolSessionApprovalCustomTool { pub tool_name: String, } -/// Schema for the `UserToolSessionApprovalExtensionManagement` type. +/// Session-scoped tool-approval rule for extension-management operations, optionally narrowed by operation. #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct UserToolSessionApprovalExtensionManagement { @@ -3161,7 +3371,7 @@ pub struct UserToolSessionApprovalExtensionManagement { pub operation: Option, } -/// Schema for the `UserToolSessionApprovalExtensionPermissionAccess` type. +/// Session-scoped tool-approval rule for an extension's permission-gated capability access, keyed by extension name. #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct UserToolSessionApprovalExtensionPermissionAccess { @@ -3171,7 +3381,7 @@ pub struct UserToolSessionApprovalExtensionPermissionAccess { pub kind: UserToolSessionApprovalExtensionPermissionAccessKind, } -/// Schema for the `PermissionApprovedForSession` type. +/// Permission response variant that approves a request and remembers the provided approval for the rest of the session. #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct PermissionApprovedForSession { @@ -3181,7 +3391,7 @@ pub struct PermissionApprovedForSession { pub kind: PermissionApprovedForSessionKind, } -/// Schema for the `PermissionApprovedForLocation` type. +/// Permission response variant that approves a request and persists the provided approval to a project location key. #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct PermissionApprovedForLocation { @@ -3193,7 +3403,7 @@ pub struct PermissionApprovedForLocation { pub location_key: String, } -/// Schema for the `PermissionCancelled` type. +/// Permission response variant indicating the request was cancelled before use, with an optional reason. #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct PermissionCancelled { @@ -3204,7 +3414,7 @@ pub struct PermissionCancelled { pub reason: Option, } -/// Schema for the `PermissionRule` type. +/// A permission approval or denial rule matched against a tool request, identified by a rule kind with an optional argument value. #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct PermissionRule { @@ -3214,7 +3424,7 @@ pub struct PermissionRule { pub kind: String, } -/// Schema for the `PermissionDeniedByRules` type. +/// Permission response variant denied because matching approval rules explicitly blocked the request. #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct PermissionDeniedByRules { @@ -3224,7 +3434,7 @@ pub struct PermissionDeniedByRules { pub rules: Vec, } -/// Schema for the `PermissionDeniedNoApprovalRuleAndCouldNotRequestFromUser` type. +/// Permission response variant denied because no approval rule matched and user confirmation was unavailable. #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct PermissionDeniedNoApprovalRuleAndCouldNotRequestFromUser { @@ -3232,7 +3442,7 @@ pub struct PermissionDeniedNoApprovalRuleAndCouldNotRequestFromUser { pub kind: PermissionDeniedNoApprovalRuleAndCouldNotRequestFromUserKind, } -/// Schema for the `PermissionDeniedInteractivelyByUser` type. +/// Permission response variant denied in an interactive user prompt, with optional feedback and force-reject flag. #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct PermissionDeniedInteractivelyByUser { @@ -3246,7 +3456,7 @@ pub struct PermissionDeniedInteractivelyByUser { pub kind: PermissionDeniedInteractivelyByUserKind, } -/// Schema for the `PermissionDeniedByContentExclusionPolicy` type. +/// Permission response variant denying a path under content exclusion policy, with the path and message. #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct PermissionDeniedByContentExclusionPolicy { @@ -3258,7 +3468,7 @@ pub struct PermissionDeniedByContentExclusionPolicy { pub path: String, } -/// Schema for the `PermissionDeniedByPermissionRequestHook` type. +/// Permission response variant denied by a permission-request hook, with optional message and interrupt flag. #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct PermissionDeniedByPermissionRequestHook { @@ -3623,7 +3833,7 @@ pub struct SessionLimitsExhaustedCompletedData { pub response: SessionLimitsExhaustedResponse, } -/// Schema for the `CommandsChangedCommand` type. +/// A single slash command available in the session, as listed by the `commands.changed` event. #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct CommandsChangedCommand { @@ -3702,7 +3912,7 @@ pub struct ExitPlanModeCompletedData { pub selected_action: Option, } -/// Session event "session.tools_updated". +/// Session event "session.tools_updated". Payload of `session.tools_updated` identifying the model whose resolved tools were updated. #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct SessionToolsUpdatedData { @@ -3710,12 +3920,12 @@ pub struct SessionToolsUpdatedData { pub model: String, } -/// Session event "session.background_tasks_changed". +/// Session event "session.background_tasks_changed". Empty payload for `session.background_tasks_changed`, indicating background task state changed. #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct SessionBackgroundTasksChangedData {} -/// Schema for the `SkillsLoadedSkill` type. +/// A single resolved skill in `session.skills_loaded`, including source, invocability, enabled state, path, and argument hint. #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct SkillsLoadedSkill { @@ -3737,7 +3947,7 @@ pub struct SkillsLoadedSkill { pub user_invocable: bool, } -/// Session event "session.skills_loaded". +/// Session event "session.skills_loaded". Payload of `session.skills_loaded` listing resolved skill metadata. #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct SessionSkillsLoadedData { @@ -3745,7 +3955,7 @@ pub struct SessionSkillsLoadedData { pub skills: Vec, } -/// Schema for the `CustomAgentsUpdatedAgent` type. +/// A single loaded custom agent in `session.custom_agents_updated`, with identity, source, tools, invocability, and model override. #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct CustomAgentsUpdatedAgent { @@ -3768,7 +3978,7 @@ pub struct CustomAgentsUpdatedAgent { pub user_invocable: bool, } -/// Session event "session.custom_agents_updated". +/// Session event "session.custom_agents_updated". Payload of `session.custom_agents_updated` with loaded custom agents plus non-fatal warnings and fatal errors. #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct SessionCustomAgentsUpdatedData { @@ -3780,7 +3990,7 @@ pub struct SessionCustomAgentsUpdatedData { pub warnings: Vec, } -/// Schema for the `McpServersLoadedServer` type. +/// A single MCP server status summary in `session.mcp_servers_loaded`, including name, status, source, transport, and plugin metadata. #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct McpServersLoadedServer { @@ -3805,7 +4015,7 @@ pub struct McpServersLoadedServer { pub transport: Option, } -/// Session event "session.mcp_servers_loaded". +/// Session event "session.mcp_servers_loaded". Payload of `session.mcp_servers_loaded` listing MCP server status summaries. #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct SessionMcpServersLoadedData { @@ -3813,7 +4023,7 @@ pub struct SessionMcpServersLoadedData { pub servers: Vec, } -/// Session event "session.mcp_server_status_changed". +/// Session event "session.mcp_server_status_changed". Payload of `session.mcp_server_status_changed` for one MCP server's status and optional failure error. #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct SessionMcpServerStatusChangedData { @@ -3826,7 +4036,7 @@ pub struct SessionMcpServerStatusChangedData { pub status: McpServerStatus, } -/// Schema for the `ExtensionsLoadedExtension` type. +/// A single extension discovered by `session.extensions_loaded`, including qualified ID, source, and current status. #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct ExtensionsLoadedExtension { @@ -3840,7 +4050,7 @@ pub struct ExtensionsLoadedExtension { pub status: ExtensionsLoadedExtensionStatus, } -/// Session event "session.extensions_loaded". +/// Session event "session.extensions_loaded". Payload of `session.extensions_loaded` listing discovered extensions and their statuses. #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct SessionExtensionsLoadedData { @@ -3848,7 +4058,7 @@ pub struct SessionExtensionsLoadedData { pub extensions: Vec, } -/// Session event "session.canvas.opened". +/// Session event "session.canvas.opened". Payload of `session.canvas.opened` with canvas instance and provider IDs plus optional title, status, URL, and input. /// ///
/// @@ -3882,7 +4092,7 @@ pub struct SessionCanvasOpenedData { pub url: Option, } -/// Schema for the `CanvasRegistryChangedCanvasAction` type. +/// A single action within a canvas declaration, with its name, optional description, and optional input schema. /// ///
/// @@ -3903,7 +4113,7 @@ pub struct CanvasRegistryChangedCanvasAction { pub name: String, } -/// Schema for the `CanvasRegistryChangedCanvas` type. +/// A single canvas declaration in `session.canvas.registry_changed`, including provider IDs, display metadata, input schema, and actions. /// ///
/// @@ -3933,7 +4143,7 @@ pub struct CanvasRegistryChangedCanvas { pub input_schema: Option, } -/// Session event "session.canvas.registry_changed". +/// Session event "session.canvas.registry_changed". Payload of `session.canvas.registry_changed` listing the canvas declarations currently available. /// ///
/// @@ -3948,7 +4158,7 @@ pub struct SessionCanvasRegistryChangedData { pub canvases: Vec, } -/// Session event "session.canvas.closed". +/// Session event "session.canvas.closed". Payload of `session.canvas.closed` with the closed canvas instance ID, provider ID, and canvas ID. /// ///
/// @@ -4030,7 +4240,7 @@ pub struct SessionCanvasRemovedData { pub instance_id: String, } -/// Session event "session.extensions.attachments_pushed". +/// Session event "session.extensions.attachments_pushed". Payload of `session.extensions.attachments_pushed` with extension-contributed attachments for the next send. #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct SessionExtensionsAttachmentsPushedData { @@ -4046,7 +4256,7 @@ pub struct McpAppToolCallCompleteError { pub message: String, } -/// Schema for the `McpAppToolCallCompleteToolMetaUI` type. +/// MCP App tool `_meta.ui` resource URI and SEP-1865 visibility captured with an `mcp_app.tool_call_complete` result. #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct McpAppToolCallCompleteToolMetaUI { @@ -4062,7 +4272,7 @@ pub struct McpAppToolCallCompleteToolMetaUI { #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct McpAppToolCallCompleteToolMeta { - /// Schema for the `McpAppToolCallCompleteToolMetaUI` type. + /// MCP App tool `_meta.ui` resource URI and SEP-1865 visibility captured with an `mcp_app.tool_call_complete` result. #[serde(skip_serializing_if = "Option::is_none")] pub ui: Option, } @@ -4141,6 +4351,24 @@ pub enum ReasoningSummary { Unknown, } +/// Output verbosity level used for supported model calls (e.g. "low", "medium", "high") +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum Verbosity { + /// A terse response was requested. + #[serde(rename = "low")] + Low, + /// A medium amount of response detail was requested. + #[serde(rename = "medium")] + Medium, + /// A more detailed response was requested. + #[serde(rename = "high")] + High, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, +} + /// The type of operation performed on the autopilot objective state file #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] pub enum AutopilotObjectiveChangedOperation { @@ -4198,6 +4426,31 @@ pub enum SessionMode { Unknown, } +/// Allow-all mode for the session. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum PermissionAllowAllMode { + /// Permission requests follow the normal approval flow. + #[serde(rename = "off")] + Off, + /// Tool, path, and URL permission requests are automatically approved. + #[serde(rename = "on")] + On, + /// Permission requests follow the normal approval flow with an LLM advisory recommendation attached; clients may choose to auto-approve requests the judge evaluated as acceptable. + #[serde(rename = "auto")] + Auto, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, +} + /// The type of operation performed on the plan file #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] pub enum PlanChangedOperation { @@ -4300,6 +4553,21 @@ pub enum UserMessageDelivery { 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 { + /// Standard function-style tool call. + #[serde(rename = "function")] + Function, + /// Custom grammar-based tool call. + #[serde(rename = "custom")] + Custom, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, +} + /// The system that produced a citation. /// ///
@@ -4325,21 +4593,6 @@ pub enum CitationProvider { 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 { - /// Standard function-style tool call. - #[serde(rename = "function")] - Function, - /// Custom grammar-based tool call. - #[serde(rename = "custom")] - Custom, - /// 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 { @@ -4708,6 +4961,34 @@ pub enum PermissionRequest { ExtensionPermissionAccess(PermissionRequestExtensionPermissionAccess), } +/// Outcome of the auto-approval safety judge for a permission request. Present only when auto mode is enabled; its absence means the judge did not evaluate the request (auto mode was off). +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum AutoApprovalRecommendation { + /// The judge evaluated the request and recommends automatically approving it. + #[serde(rename = "approve")] + Approve, + /// The judge evaluated the request and does not recommend auto-approving it; explicit approval is required. Whether that means prompting, denying, or something else is the consumer's decision. + #[serde(rename = "requireApproval")] + RequireApproval, + /// Auto mode is enabled, but this request category is never auto-approvable (for example, sandbox-bypass requests), so the judge was not consulted. + #[serde(rename = "excluded")] + Excluded, + /// The judge was consulted but did not return a usable recommendation, so the request requires explicit approval. + #[serde(rename = "error")] + Error, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, +} + /// Prompt kind discriminator #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] pub enum PermissionPromptRequestCommandsKind { diff --git a/rust/src/github_telemetry.rs b/rust/src/github_telemetry.rs new file mode 100644 index 0000000000..9ef5c6e2e8 --- /dev/null +++ b/rust/src/github_telemetry.rs @@ -0,0 +1,28 @@ +//! GitHub telemetry forwarding callback surface. +//! +//! The runtime forwards per-session GitHub (hydro) telemetry to opted-in host +//! connections via the `gitHubTelemetry.event` JSON-RPC notification. The +//! payload types (`GitHubTelemetryNotification`, `GitHubTelemetryEvent`, +//! `GitHubTelemetryClientInfo`) are generated from the protocol schema and +//! re-exported here so consumers can register a callback against them via +//! [`ClientOptions::on_github_telemetry`](crate::ClientOptions::on_github_telemetry). +//! +//! Experimental: this surface is part of the GitHub telemetry forwarding +//! feature and may change or be removed without notice. + +use std::sync::Arc; + +#[doc(hidden)] +pub use crate::generated::api_types::{ + GitHubTelemetryClientInfo, GitHubTelemetryEvent, GitHubTelemetryNotification, +}; + +/// Callback invoked for each `gitHubTelemetry.event` notification forwarded by +/// the runtime to a connection that opted into telemetry forwarding. +/// +/// Set via +/// [`ClientOptions::on_github_telemetry`](crate::ClientOptions::on_github_telemetry). +/// Registering a callback auto-enables telemetry forwarding on every session +/// created or resumed by the client. +#[doc(hidden)] +pub type GitHubTelemetryCallback = Arc; diff --git a/rust/src/lib.rs b/rust/src/lib.rs index 22fdc53d78..0333281f59 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -15,6 +15,10 @@ pub use errors::*; /// model-layer HTTP and WebSocket traffic the runtime issues for both CAPI and /// BYOK sessions. pub mod copilot_request_handler; +/// GitHub telemetry forwarding callback surface (experimental). Public but +/// `#[doc(hidden)]` — re-exports the generated telemetry payload types. +#[doc(hidden)] +pub mod github_telemetry; /// Event handler traits for session lifecycle. pub mod handler; /// Lifecycle hook callbacks (pre/post tool use, prompt submission, session start/end). @@ -257,6 +261,15 @@ pub struct ClientOptions { /// [`CopilotRequestHandler`] /// instead of issuing the calls itself. pub request_handler: Option>, + /// Connection-level GitHub telemetry forwarding callback (experimental). + /// + /// When set, every session created or resumed on this client opts into + /// telemetry forwarding (`enableGitHubTelemetryForwarding`) and the + /// callback is invoked for each `gitHubTelemetry.event` notification the + /// runtime forwards. `#[doc(hidden)]`, consistent with the experimental + /// telemetry payload types. + #[doc(hidden)] + pub on_github_telemetry: Option, /// Optional [`TraceContextProvider`] used to inject W3C Trace Context /// headers (`traceparent` / `tracestate`) on outbound `session.create`, /// `session.resume`, and `session.send` requests. @@ -336,6 +349,10 @@ impl std::fmt::Debug for ClientOptions { "request_handler", &self.request_handler.as_ref().map(|_| ""), ) + .field( + "on_github_telemetry", + &self.on_github_telemetry.as_ref().map(|_| ""), + ) .field( "on_get_trace_context", &self.on_get_trace_context.as_ref().map(|_| ""), @@ -584,6 +601,7 @@ impl Default for ClientOptions { on_list_models: None, session_fs: None, request_handler: None, + on_github_telemetry: None, on_get_trace_context: None, telemetry: None, base_directory: None, @@ -728,6 +746,20 @@ impl ClientOptions { self } + /// Register a connection-level GitHub telemetry forwarding callback + /// (internal/experimental). Registering a callback auto-enables telemetry + /// forwarding on every session created or resumed on this client; the + /// callback fires for each forwarded `gitHubTelemetry.event` notification. + /// The callback is wrapped in `Arc` internally. + #[doc(hidden)] + pub fn with_on_github_telemetry(mut self, callback: F) -> Self + where + F: Fn(crate::github_telemetry::GitHubTelemetryNotification) + Send + Sync + 'static, + { + self.on_github_telemetry = Some(Arc::new(callback)); + self + } + /// Set the [`TraceContextProvider`] used to inject W3C Trace Context /// headers on outbound `session.create` / `session.resume` / /// `session.send` requests. The provider is wrapped in `Arc` internally. @@ -853,6 +885,11 @@ struct ClientInner { /// Inbound `llmInference.*` dispatcher, installed when /// [`ClientOptions::request_handler`] is set. llm_inference: OnceLock>, + /// Connection-level GitHub telemetry forwarding callback, set from + /// [`ClientOptions::on_github_telemetry`]. Drives the + /// `enableGitHubTelemetryForwarding` wire flag and the + /// `gitHubTelemetry.event` notification dispatch. + on_github_telemetry: Option, on_get_trace_context: Option>, /// Token sent in the `connect` handshake. Auto-generated when the /// SDK spawns its own CLI in TCP mode and no explicit token is set; @@ -1005,6 +1042,7 @@ impl Client { session_fs_config.is_some(), session_fs_sqlite_declared, options.on_get_trace_context, + options.on_github_telemetry, effective_connection_token.clone(), options.mode, )? @@ -1032,6 +1070,7 @@ impl Client { session_fs_config.is_some(), session_fs_sqlite_declared, options.on_get_trace_context, + options.on_github_telemetry, effective_connection_token.clone(), options.mode, )? @@ -1050,6 +1089,7 @@ impl Client { session_fs_config.is_some(), session_fs_sqlite_declared, options.on_get_trace_context, + options.on_github_telemetry, effective_connection_token.clone(), options.mode, )? @@ -1097,6 +1137,7 @@ impl Client { &client.inner.notification_tx, &client.inner.request_rx, Some(dispatcher.clone()), + client.inner.on_github_telemetry.clone(), ); client.rpc().llm_inference().set_provider().await?; debug!( @@ -1129,6 +1170,7 @@ impl Client { false, None, None, + None, ClientMode::default(), ) } @@ -1157,6 +1199,7 @@ impl Client { false, Some(provider), None, + None, ClientMode::default(), ) } @@ -1180,11 +1223,37 @@ impl Client { false, false, None, + None, token, ClientMode::default(), ) } + /// Construct a [`Client`] from raw streams with a preset GitHub telemetry + /// callback, for integration testing telemetry forwarding. + #[doc(hidden)] + #[cfg(any(test, feature = "test-support"))] + pub fn from_streams_with_github_telemetry( + reader: impl AsyncRead + Unpin + Send + 'static, + writer: impl AsyncWrite + Unpin + Send + 'static, + cwd: PathBuf, + on_github_telemetry: crate::github_telemetry::GitHubTelemetryCallback, + ) -> Result { + Self::from_transport( + reader, + writer, + None, + cwd, + None, + false, + false, + None, + Some(on_github_telemetry), + None, + ClientMode::default(), + ) + } + /// Public test-only wrapper around the random connection-token /// generator used by [`Client::start`] when the SDK spawns a TCP /// server without an explicit token. Lets integration tests @@ -1205,6 +1274,7 @@ impl Client { session_fs_configured: bool, session_fs_sqlite_declared: bool, on_get_trace_context: Option>, + on_github_telemetry: Option, effective_connection_token: Option, mode: ClientMode, ) -> Result { @@ -1237,6 +1307,7 @@ impl Client { session_fs_configured, session_fs_sqlite_declared, llm_inference: OnceLock::new(), + on_github_telemetry, on_get_trace_context, effective_connection_token, mode, @@ -1646,6 +1717,7 @@ impl Client { &self.inner.notification_tx, &self.inner.request_rx, self.inner.llm_inference.get().cloned(), + self.inner.on_github_telemetry.clone(), ); self.inner.router.register(session_id) } @@ -1746,13 +1818,26 @@ impl Client { /// param. Server-side, the token is required when the server was /// started with `COPILOT_CONNECTION_TOKEN`. async fn connect_handshake(&self) -> Result> { - let result = self - .rpc() - .connect(crate::generated::api_types::ConnectRequest { - token: self.inner.effective_connection_token.clone(), - }) + let params = crate::generated::api_types::ConnectRequest { + token: self.inner.effective_connection_token.clone(), + enable_git_hub_telemetry_forwarding: self + .inner + .on_github_telemetry + .is_some() + .then_some(true), + }; + let value = self + .call( + crate::generated::api_types::rpc_methods::CONNECT, + Some(serde_json::to_value(params)?), + ) .await?; - Ok(u32::try_from(result.protocol_version).ok()) + let result: crate::generated::api_types::ConnectResult = serde_json::from_value(value)?; + Ok(Some(u32::try_from(result.protocol_version).map_err( + |_| ProtocolErrorKind::InvalidProtocolVersion { + server: result.protocol_version, + }, + )?)) } /// Send a `ping` RPC and return the typed [`PingResponse`]. @@ -2732,6 +2817,7 @@ mod tests { session_fs_configured: false, session_fs_sqlite_declared: false, llm_inference: OnceLock::new(), + on_github_telemetry: None, on_get_trace_context: None, effective_connection_token: None, mode: ClientMode::default(), diff --git a/rust/src/router.rs b/rust/src/router.rs index cc621c287c..adc1923824 100644 --- a/rust/src/router.rs +++ b/rust/src/router.rs @@ -86,6 +86,7 @@ impl SessionRouter { notification_tx: &broadcast::Sender, request_rx: &Mutex>>, llm_inference: Option>, + github_telemetry: Option, ) { let mut started = self.started.lock(); if *started { @@ -100,6 +101,40 @@ impl SessionRouter { loop { match notif_rx.recv().await { Ok(notification) => { + // Client-global `gitHubTelemetry.event` notifications carry + // no routable session and are surfaced to the consumer + // callback (if any) registered at client construction. + if notification.method == "gitHubTelemetry.event" { + if let Some(ref callback) = github_telemetry { + let Some(ref params) = notification.params else { + continue; + }; + match serde_json::from_value::< + crate::github_telemetry::GitHubTelemetryNotification, + >(params.clone()) + { + Ok(telemetry) => { + if std::panic::catch_unwind(std::panic::AssertUnwindSafe( + || callback(telemetry), + )) + .is_err() + { + warn!( + "gitHubTelemetry.event callback panicked; \ + continuing notification routing" + ); + } + } + Err(e) => { + warn!( + error = %e, + "failed to deserialize gitHubTelemetry.event notification" + ); + } + } + } + continue; + } if notification.method != "session.event" { continue; } diff --git a/rust/src/session.rs b/rust/src/session.rs index e5a2dc4dd6..307139f20f 100644 --- a/rust/src/session.rs +++ b/rust/src/session.rs @@ -529,6 +529,7 @@ impl Session { model_id: model.to_string(), reasoning_effort: opts.reasoning_effort, reasoning_summary: opts.reasoning_summary, + verbosity: None, context_tier: opts.context_tier, model_capabilities: opts.model_capabilities, }; @@ -876,7 +877,9 @@ 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 (wire, mut runtime) = config.into_wire(local_session_id.clone())?; + 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); let permission_handler = crate::permission::resolve_handler( runtime.permission_handler.take(), @@ -1139,7 +1142,9 @@ 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 (wire, mut runtime) = config.into_wire()?; + let (mut wire, mut runtime) = config.into_wire()?; + wire.enable_github_telemetry_forwarding = + self.inner.on_github_telemetry.is_some().then_some(true); let permission_handler = crate::permission::resolve_handler( runtime.permission_handler.take(), diff --git a/rust/src/types.rs b/rust/src/types.rs index e42ecdd118..e5ba28a56e 100644 --- a/rust/src/types.rs +++ b/rust/src/types.rs @@ -2163,6 +2163,7 @@ impl SessionConfig { remote_session: self.remote_session, cloud: self.cloud, include_sub_agent_streaming_events: self.include_sub_agent_streaming_events, + enable_github_telemetry_forwarding: None, commands: wire_commands, exp_assignments: self.exp_assignments, }; @@ -3173,6 +3174,7 @@ impl ResumeSessionConfig { github_token: self.github_token, remote_session: self.remote_session, include_sub_agent_streaming_events: self.include_sub_agent_streaming_events, + enable_github_telemetry_forwarding: None, commands: wire_commands, exp_assignments: self.exp_assignments, suppress_resume_event: self.suppress_resume_event, diff --git a/rust/src/wire.rs b/rust/src/wire.rs index f888e032a9..f73870fa53 100644 --- a/rust/src/wire.rs +++ b/rust/src/wire.rs @@ -160,6 +160,11 @@ pub(crate) struct SessionCreateWire { pub cloud: Option, #[serde(skip_serializing_if = "Option::is_none")] pub include_sub_agent_streaming_events: Option, + #[serde( + rename = "enableGitHubTelemetryForwarding", + skip_serializing_if = "Option::is_none" + )] + pub enable_github_telemetry_forwarding: Option, #[serde(skip_serializing_if = "Option::is_none")] pub commands: Option>, #[serde(skip_serializing_if = "Option::is_none")] @@ -283,6 +288,11 @@ pub(crate) struct SessionResumeWire { pub remote_session: Option, #[serde(skip_serializing_if = "Option::is_none")] pub include_sub_agent_streaming_events: Option, + #[serde( + rename = "enableGitHubTelemetryForwarding", + skip_serializing_if = "Option::is_none" + )] + pub enable_github_telemetry_forwarding: Option, #[serde(skip_serializing_if = "Option::is_none")] pub commands: Option>, /// Maps to wire field `disableResume`. diff --git a/rust/tests/e2e.rs b/rust/tests/e2e.rs index 79059c7f28..62412963b8 100644 --- a/rust/tests/e2e.rs +++ b/rust/tests/e2e.rs @@ -31,6 +31,8 @@ mod elicitation; mod error_resilience; #[path = "e2e/event_fidelity.rs"] mod event_fidelity; +#[path = "e2e/github_telemetry.rs"] +mod github_telemetry; #[path = "e2e/hooks.rs"] mod hooks; #[path = "e2e/hooks_extended.rs"] diff --git a/rust/tests/e2e/client_options.rs b/rust/tests/e2e/client_options.rs index 8b13789179..dbf3c5c83d 100644 --- a/rust/tests/e2e/client_options.rs +++ b/rust/tests/e2e/client_options.rs @@ -1 +1,485 @@ +use std::collections::HashMap; +use std::path::PathBuf; +use github_copilot_sdk::canvas::CanvasDeclaration; +use github_copilot_sdk::rpc::{OpenCanvasInstance, RemoteSessionMode}; +use github_copilot_sdk::session_events::{ReasoningSummary, SessionLimitsConfig}; +use github_copilot_sdk::{ + CliProgram, Client, ClientOptions, ExtensionInfo, ProviderConfig, ResumeSessionConfig, + SessionConfig, SessionId, +}; +use serde::Deserialize; +use serde_json::{Value, json}; +use tempfile::TempDir; + +#[tokio::test] +async fn should_forward_advanced_session_creation_options_to_the_cli() { + let fake = FakeCli::new(); + let client = Client::start(fake.client_options("advanced-create-client-token")) + .await + .expect("start fake CLI client"); + + let config_dir = fake.path("config"); + let working_dir = fake.path("workspace"); + let extension_sdk_path = fake.path("extension-sdk"); + let session = client + .create_session( + SessionConfig::default() + .with_session_id("advanced-session-id") + .with_client_name("rust-sdk-e2e-client") + .with_model("claude-sonnet-4.5") + .with_reasoning_effort("low") + .with_reasoning_summary(ReasoningSummary::None) + .with_context_tier("long_context") + .with_config_directory(config_dir.clone()) + .with_enable_config_discovery(true) + .with_skip_embedding_retrieval(true) + .with_embedding_cache_storage("in-memory") + .with_organization_custom_instructions("organization guidance") + .with_enable_on_demand_instruction_discovery(true) + .with_enable_file_hooks(false) + .with_enable_host_git_operations(false) + .with_enable_session_store(false) + .with_enable_skills(false) + .with_working_directory(working_dir.clone()) + .with_streaming(true) + .with_include_sub_agent_streaming_events(false) + .with_available_tools(["read_file"]) + .with_excluded_tools(["bash"]) + .with_excluded_builtin_agents(["legacy-agent"]) + .with_enable_session_telemetry(false) + .with_enable_citations(true) + .with_session_limits(SessionLimitsConfig { + max_ai_credits: Some(42.0), + }) + .with_skip_custom_instructions(true) + .with_custom_agents_local_only(true) + .with_coauthor_enabled(false) + .with_manage_schedule_enabled(false) + .with_github_token("advanced-create-session-token") + .with_remote_session(RemoteSessionMode::Export) + .with_skill_directories([PathBuf::from("skills")]) + .with_plugin_directories([PathBuf::from("plugins")]) + .with_instruction_directories([PathBuf::from("instructions")]) + .with_disabled_skills(["disabled-skill"]) + .with_enable_mcp_apps(true) + .with_canvases([CanvasDeclaration::new( + "canvas", + "Canvas", + "Canvas description", + )]) + .with_request_canvas_renderer(true) + .with_request_extensions(true) + .with_extension_sdk_path(path_string(&extension_sdk_path)) + .with_extension_info(ExtensionInfo::new("github-app", "rust-e2e-extension")) + .with_exp_assignments(json!({ "feature": "enabled" })), + ) + .await + .expect("create session"); + session.disconnect().await.expect("disconnect session"); + client.stop().await.expect("stop client"); + + let create = fake.captured_request("session.create"); + let params = create.params.as_object().expect("session.create params"); + assert_json_values( + params, + [ + ("sessionId", json!("advanced-session-id")), + ("clientName", json!("rust-sdk-e2e-client")), + ("model", json!("claude-sonnet-4.5")), + ("reasoningEffort", json!("low")), + ("reasoningSummary", json!("none")), + ("contextTier", json!("long_context")), + ("configDir", json!(path_string(&config_dir))), + ("enableConfigDiscovery", json!(true)), + ("skipEmbeddingRetrieval", json!(true)), + ("embeddingCacheStorage", json!("in-memory")), + ( + "organizationCustomInstructions", + json!("organization guidance"), + ), + ("enableOnDemandInstructionDiscovery", json!(true)), + ("enableFileHooks", json!(false)), + ("enableHostGitOperations", json!(false)), + ("enableSessionStore", json!(false)), + ("enableSkills", json!(false)), + ("workingDirectory", json!(path_string(&working_dir))), + ("streaming", json!(true)), + ("includeSubAgentStreamingEvents", json!(false)), + ("enableSessionTelemetry", json!(false)), + ("enableCitations", json!(true)), + ("gitHubToken", json!("advanced-create-session-token")), + ("remoteSession", json!("export")), + ("requestMcpApps", json!(true)), + ("requestCanvasRenderer", json!(true)), + ("requestExtensions", json!(true)), + ("extensionSdkPath", json!(path_string(&extension_sdk_path))), + ("envValueMode", json!("direct")), + ], + ); + assert_eq!(params["availableTools"], json!(["read_file"])); + assert_eq!(params["excludedTools"], json!(["bash"])); + assert_eq!(params["excludedBuiltinAgents"], json!(["legacy-agent"])); + assert_eq!(params["skillDirectories"], json!(["skills"])); + assert_eq!(params["pluginDirectories"], json!(["plugins"])); + assert_eq!(params["instructionDirectories"], json!(["instructions"])); + assert_eq!(params["disabledSkills"], json!(["disabled-skill"])); + assert_eq!(params["sessionLimits"]["maxAiCredits"], json!(42)); + assert_eq!( + params["extensionInfo"], + json!({ "source": "github-app", "name": "rust-e2e-extension" }) + ); + assert_eq!(params["canvases"][0]["id"], json!("canvas")); + assert_eq!(params["canvases"][0]["displayName"], json!("Canvas")); + assert_eq!( + params["canvases"][0]["description"], + json!("Canvas description") + ); + assert_eq!(params["expAssignments"]["feature"], json!("enabled")); + + let update = fake.captured_request("session.options.update"); + let update_params = update.params.as_object().expect("options update params"); + assert_json_values( + update_params, + [ + ("sessionId", json!("advanced-session-id")), + ("skipCustomInstructions", json!(true)), + ("customAgentsLocalOnly", json!(true)), + ("coauthorEnabled", json!(false)), + ("manageScheduleEnabled", json!(false)), + ], + ); +} + +#[tokio::test] +async fn should_forward_singular_provider_configuration_on_session_creation() { + let fake = FakeCli::new(); + let client = Client::start(fake.client_options("provider-client-token")) + .await + .expect("start fake CLI client"); + + let session = client + .create_session( + SessionConfig::default().with_provider( + ProviderConfig::new("https://models.example.test/v1") + .with_provider_type("openai") + .with_wire_api("responses") + .with_transport("websockets") + .with_api_key("provider-key") + .with_model_id("base-model") + .with_wire_model("wire-model") + .with_max_prompt_tokens(1000) + .with_max_output_tokens(2000) + .with_headers(HashMap::from([( + "x-provider".to_string(), + "rust".to_string(), + )])), + ), + ) + .await + .expect("create session"); + session.disconnect().await.expect("disconnect session"); + client.stop().await.expect("stop client"); + + let create = fake.captured_request("session.create"); + let provider = create.params["provider"] + .as_object() + .expect("provider params"); + assert_json_values( + provider, + [ + ("type", json!("openai")), + ("wireApi", json!("responses")), + ("transport", json!("websockets")), + ("baseUrl", json!("https://models.example.test/v1")), + ("apiKey", json!("provider-key")), + ("modelId", json!("base-model")), + ("wireModel", json!("wire-model")), + ("maxPromptTokens", json!(1000)), + ("maxOutputTokens", json!(2000)), + ], + ); + assert_eq!(provider["headers"]["x-provider"], json!("rust")); +} + +#[tokio::test] +async fn should_forward_advanced_session_resume_options_to_the_cli() { + let fake = FakeCli::new(); + let client = Client::start(fake.client_options("advanced-resume-client-token")) + .await + .expect("start fake CLI client"); + + let config_dir = fake.path("resume-config"); + let working_dir = fake.path("resume-workspace"); + let extension_sdk_path = fake.path("resume-extension-sdk"); + let session = client + .resume_session( + ResumeSessionConfig::new(SessionId::from("resume-session-id")) + .with_model("gpt-5-mini") + .with_reasoning_effort("low") + .with_reasoning_summary(ReasoningSummary::None) + .with_context_tier("long_context") + .with_working_directory(working_dir.clone()) + .with_config_directory(config_dir.clone()) + .with_enable_config_discovery(false) + .with_suppress_resume_event(true) + .with_continue_pending_work(false) + .with_streaming(true) + .with_include_sub_agent_streaming_events(false) + .with_github_token("advanced-resume-session-token") + .with_canvases([CanvasDeclaration::new( + "resume-canvas", + "Resume Canvas", + "Resume canvas description", + )]) + .with_open_canvases([OpenCanvasInstance { + canvas_id: "resume-canvas".to_string(), + extension_id: "github-app/rust-e2e-extension".to_string(), + extension_name: None, + input: Some(json!({ "value": "from-resume" })), + instance_id: "resume-instance".to_string(), + status: None, + title: None, + url: None, + }]) + .with_request_canvas_renderer(true) + .with_request_extensions(true) + .with_extension_sdk_path(path_string(&extension_sdk_path)) + .with_extension_info(ExtensionInfo::new("github-app", "rust-e2e-extension")) + .with_skip_custom_instructions(true) + .with_custom_agents_local_only(true) + .with_coauthor_enabled(false) + .with_manage_schedule_enabled(false) + .with_exp_assignments(json!({ "resumeFeature": "enabled" })), + ) + .await + .expect("resume session"); + session.disconnect().await.expect("disconnect session"); + client.stop().await.expect("stop client"); + + let resume = fake.captured_request("session.resume"); + let params = resume.params.as_object().expect("session.resume params"); + assert_json_values( + params, + [ + ("sessionId", json!("resume-session-id")), + ("model", json!("gpt-5-mini")), + ("reasoningEffort", json!("low")), + ("reasoningSummary", json!("none")), + ("contextTier", json!("long_context")), + ("workingDirectory", json!(path_string(&working_dir))), + ("configDir", json!(path_string(&config_dir))), + ("enableConfigDiscovery", json!(false)), + ("disableResume", json!(true)), + ("continuePendingWork", json!(false)), + ("streaming", json!(true)), + ("includeSubAgentStreamingEvents", json!(false)), + ("gitHubToken", json!("advanced-resume-session-token")), + ("requestCanvasRenderer", json!(true)), + ("requestExtensions", json!(true)), + ("extensionSdkPath", json!(path_string(&extension_sdk_path))), + ("envValueMode", json!("direct")), + ], + ); + assert_eq!( + params["openCanvases"][0]["canvasId"], + json!("resume-canvas") + ); + assert_eq!( + params["openCanvases"][0]["extensionId"], + json!("github-app/rust-e2e-extension") + ); + assert_eq!( + params["openCanvases"][0]["instanceId"], + json!("resume-instance") + ); + assert_eq!( + params["extensionInfo"], + json!({ "source": "github-app", "name": "rust-e2e-extension" }) + ); + assert_eq!(params["expAssignments"]["resumeFeature"], json!("enabled")); + + let update = fake.captured_request("session.options.update"); + let update_params = update.params.as_object().expect("options update params"); + assert_json_values( + update_params, + [ + ("sessionId", json!("resume-session-id")), + ("skipCustomInstructions", json!(true)), + ("customAgentsLocalOnly", json!(true)), + ("coauthorEnabled", json!(false)), + ("manageScheduleEnabled", json!(false)), + ], + ); +} + +struct FakeCli { + _dir: TempDir, + script_path: PathBuf, + capture_path: PathBuf, + work_dir: PathBuf, +} + +impl FakeCli { + fn new() -> Self { + let dir = tempfile::tempdir().expect("create fake CLI temp dir"); + let script_path = dir.path().join("fake-cli.js"); + let capture_path = dir.path().join("fake-cli-capture.json"); + let work_dir = dir.path().join("cwd"); + std::fs::create_dir(&work_dir).expect("create fake CLI cwd"); + std::fs::write(&script_path, FAKE_STDIO_CLI_SCRIPT).expect("write fake CLI script"); + Self { + _dir: dir, + script_path, + capture_path, + work_dir, + } + } + + fn client_options(&self, token: &str) -> ClientOptions { + ClientOptions::new() + .with_program(CliProgram::Path(PathBuf::from("node"))) + .with_prefix_args([self.script_path.as_os_str().to_owned()]) + .with_cwd(&self.work_dir) + .with_extra_args([ + "--capture-file".to_string(), + self.capture_path.to_string_lossy().into_owned(), + ]) + .with_github_token(token) + .with_use_logged_in_user(false) + } + + fn path(&self, name: &str) -> PathBuf { + let path = self.work_dir.join(name); + std::fs::create_dir_all(&path).expect("create fake CLI test path"); + path + } + + fn captured_request(&self, method: &str) -> CapturedRequest { + let capture = self.capture(); + capture + .requests + .iter() + .find(|request| request.method == method) + .cloned() + .unwrap_or_else(|| panic!("expected {method} request in {capture:?}")) + } + + fn capture(&self) -> CapturedCli { + let text = std::fs::read_to_string(&self.capture_path).expect("read fake CLI capture file"); + serde_json::from_str(&text).expect("parse fake CLI capture file") + } +} + +#[derive(Debug, Deserialize)] +struct CapturedCli { + requests: Vec, +} + +#[derive(Debug, Clone, Deserialize)] +struct CapturedRequest { + method: String, + #[serde(default)] + params: Value, +} + +fn assert_json_values<'a>( + object: &serde_json::Map, + expected: impl IntoIterator, +) { + for (key, expected_value) in expected { + assert_eq!( + object.get(key), + Some(&expected_value), + "unexpected value for key {key} in {object:?}" + ); + } +} + +fn path_string(path: &std::path::Path) -> String { + path.to_string_lossy().into_owned() +} + +const FAKE_STDIO_CLI_SCRIPT: &str = r#" +const fs = require("fs"); + +const captureIndex = process.argv.indexOf("--capture-file"); +const captureFile = captureIndex >= 0 ? process.argv[captureIndex + 1] : undefined; +const requests = []; + +function saveCapture() { + if (!captureFile) { + return; + } + fs.writeFileSync(captureFile, JSON.stringify({ + requests, + args: process.argv.slice(2), + cwd: process.cwd(), + env: { + COPILOT_SDK_AUTH_TOKEN: process.env.COPILOT_SDK_AUTH_TOKEN, + }, + })); +} + +saveCapture(); + +let buffer = Buffer.alloc(0); +process.stdin.on("data", chunk => { + buffer = Buffer.concat([buffer, chunk]); + processBuffer(); +}); +process.stdin.resume(); + +function processBuffer() { + while (true) { + const headerEnd = buffer.indexOf("\r\n\r\n"); + if (headerEnd < 0) return; + const header = buffer.subarray(0, headerEnd).toString("utf8"); + const match = /Content-Length:\s*(\d+)/i.exec(header); + if (!match) throw new Error("Missing Content-Length header"); + const length = Number(match[1]); + const bodyStart = headerEnd + 4; + const bodyEnd = bodyStart + length; + if (buffer.length < bodyEnd) return; + const body = buffer.subarray(bodyStart, bodyEnd).toString("utf8"); + buffer = buffer.subarray(bodyEnd); + handleMessage(JSON.parse(body)); + } +} + +function handleMessage(message) { + if (!Object.prototype.hasOwnProperty.call(message, "id")) { + return; + } + requests.push({ method: message.method, params: message.params }); + saveCapture(); + if (message.method === "connect") { + writeResponse(message.id, { ok: true, protocolVersion: 3, version: "fake" }); + return; + } + if (message.method === "ping") { + writeResponse(message.id, { message: "pong", protocolVersion: 3, timestamp: Date.now() }); + return; + } + if (message.method === "session.create") { + const sessionId = (message.params && message.params.sessionId) || "fake-session"; + writeResponse(message.id, { sessionId, workspacePath: null, capabilities: null }); + return; + } + if (message.method === "session.resume") { + const sessionId = (message.params && message.params.sessionId) || "fake-session"; + writeResponse(message.id, { sessionId, workspacePath: null, capabilities: null, openCanvases: [] }); + return; + } + if (message.method === "session.options.update") { + writeResponse(message.id, { success: true }); + return; + } + writeResponse(message.id, {}); +} + +function writeResponse(id, result) { + const body = JSON.stringify({ jsonrpc: "2.0", id, result }); + process.stdout.write("Content-Length: " + Buffer.byteLength(body, "utf8") + "\r\n\r\n" + body); +} +"#; diff --git a/rust/tests/e2e/github_telemetry.rs b/rust/tests/e2e/github_telemetry.rs new file mode 100644 index 0000000000..2047ee34ff --- /dev/null +++ b/rust/tests/e2e/github_telemetry.rs @@ -0,0 +1,65 @@ +use std::sync::{Arc, Mutex}; +use std::time::Duration; + +use github_copilot_sdk::github_telemetry::GitHubTelemetryNotification; +use github_copilot_sdk::handler::ApproveAllHandler; +use github_copilot_sdk::{Client, SessionConfig}; + +use super::support::{DEFAULT_TEST_TOKEN, with_e2e_context_no_snapshot}; + +#[tokio::test] +async fn should_forward_github_telemetry_on_session_create() { + with_e2e_context_no_snapshot(|ctx| { + Box::pin(async move { + ctx.set_default_copilot_user(); + + let notifications = Arc::new(Mutex::new(Vec::::new())); + let collected = notifications.clone(); + let client = Client::start(ctx.client_options().with_on_github_telemetry(move |n| { + collected.lock().unwrap().push(n); + })) + .await + .expect("start client"); + let session = client + .create_session( + SessionConfig::default() + .with_github_token(DEFAULT_TEST_TOKEN) + .with_permission_handler(Arc::new(ApproveAllHandler)), + ) + .await + .expect("create session"); + + let deadline = tokio::time::Instant::now() + Duration::from_secs(30); + loop { + if !notifications.lock().unwrap().is_empty() { + break; + } + assert!( + tokio::time::Instant::now() < deadline, + "timed out waiting for github telemetry notification" + ); + tokio::time::sleep(Duration::from_millis(100)).await; + } + + { + let notifications = notifications.lock().unwrap(); + assert!(!notifications.is_empty()); + let first = notifications + .first() + .expect("github telemetry notification"); + assert!( + first + .session_id + .as_deref() + .is_some_and(|session_id| !session_id.is_empty()) + ); + let _: bool = first.restricted; + assert!(!first.event.kind.is_empty()); + } + + session.disconnect().await.expect("disconnect session"); + client.stop().await.expect("stop client"); + }) + }) + .await; +} diff --git a/rust/tests/e2e/mcp_oauth.rs b/rust/tests/e2e/mcp_oauth.rs index b1d932372c..f330df1155 100644 --- a/rust/tests/e2e/mcp_oauth.rs +++ b/rust/tests/e2e/mcp_oauth.rs @@ -14,6 +14,7 @@ use serde::Deserialize; use serde_json::Value; use tokio::io::{AsyncBufReadExt, BufReader}; use tokio::process::{Child, Command}; +use tokio::sync::Notify; use super::support::{wait_for_condition, with_e2e_context_no_snapshot}; @@ -98,11 +99,9 @@ async fn should_satisfy_mcp_oauth_using_host_provided_token() { .iter() .any(|request| request.authorization.is_none()) ); - assert!( - requests.iter().any( - |request| request.authorization.as_deref() == Some("Bearer sdk-host-token") - ) - ); + assert!(requests.iter().any(|request| { + request.authorization.as_deref() == Some(&format!("Bearer {EXPECTED_TOKEN}")) + })); session.disconnect().await.expect("disconnect session"); client.stop().await.expect("stop client"); @@ -160,24 +159,15 @@ async fn should_request_replacement_tokens_across_mcp_oauth_lifecycle() { ); let requests = oauth_server.requests().await; - assert!( - requests - .iter() - .any(|request| request.authorization.as_deref() - == Some("Bearer sdk-host-token-refresh")) - ); - assert!( - requests - .iter() - .any(|request| request.authorization.as_deref() - == Some("Bearer sdk-host-token-upscope")) - ); - assert!( - requests - .iter() - .any(|request| request.authorization.as_deref() - == Some("Bearer sdk-host-token-reauth")) - ); + assert!(requests.iter().any(|request| { + request.authorization.as_deref() == Some(&format!("Bearer {REFRESH_TOKEN}")) + })); + assert!(requests.iter().any(|request| { + request.authorization.as_deref() == Some(&format!("Bearer {UPSCOPE_TOKEN}")) + })); + assert!(requests.iter().any(|request| { + request.authorization.as_deref() == Some(&format!("Bearer {REAUTH_TOKEN}")) + })); session.disconnect().await.expect("disconnect session"); client.stop().await.expect("stop client"); @@ -217,7 +207,7 @@ async fn should_cancel_pending_mcp_oauth_request() { .await .expect("create session"); - wait_for_mcp_server_status(&session, server_name, McpServerStatus::Failed).await; + wait_for_mcp_server_status(&session, server_name, McpServerStatus::NeedsAuth).await; let request = handler .request @@ -235,6 +225,119 @@ async fn should_cancel_pending_mcp_oauth_request() { .await; } +#[tokio::test] +async fn should_resolve_pending_mcp_oauth_request_through_rpc() { + with_e2e_context_no_snapshot(|ctx| { + Box::pin(async move { + ctx.set_default_copilot_user(); + let mut oauth_server = OAuthMcpServer::start( + ctx.repo_root() + .join("test/harness/test-mcp-oauth-server.mjs"), + ) + .await; + let server_name = "oauth-direct-rpc-mcp"; + let observed_request = Arc::new(Mutex::new(None)); + let request_observed = Arc::new(Notify::new()); + let release_handler = Arc::new(Notify::new()); + let handler = Arc::new(BlockingAuthHandler { + request: observed_request.clone(), + request_observed: request_observed.clone(), + release: release_handler.clone(), + }); + let client = ctx.start_client().await; + let session = client + .create_session( + ctx.approve_all_session_config() + .with_enable_mcp_apps(true) + .with_mcp_auth_handler(handler) + .with_mcp_servers(HashMap::from([( + server_name.to_string(), + McpServerConfig::Http(McpHttpServerConfig { + tools: Some(vec!["*".to_string()]), + timeout: None, + url: format!("{}/mcp", oauth_server.url), + headers: HashMap::new(), + }), + )])), + ) + .await + .expect("create session"); + + let connected = + wait_for_mcp_server_status(&session, server_name, McpServerStatus::Connected); + tokio::pin!(connected); + tokio::select! { + () = request_observed.notified() => {} + () = &mut connected => panic!("MCP server connected before OAuth request was observed"), + } + let request = observed_request + .lock() + .clone() + .expect("MCP auth request"); + assert_eq!(request.server_name, server_name); + assert_eq!(request.server_url, format!("{}/mcp", oauth_server.url)); + assert_eq!(request.reason, McpOauthRequestReason::Initial); + let www_authenticate = request + .www_authenticate_params + .as_ref() + .expect("WWW-Authenticate params"); + assert_eq!( + www_authenticate.resource_metadata_url, + Some(format!( + "{}/.well-known/oauth-protected-resource", + oauth_server.url + )) + ); + assert_eq!(www_authenticate.scope.as_deref(), Some("mcp.read")); + assert_eq!(www_authenticate.error.as_deref(), Some("invalid_token")); + + let handled = session + .rpc() + .mcp() + .oauth() + .handle_pending_request(github_copilot_sdk::rpc::McpOauthHandlePendingRequest { + request_id: request.request_id, + result: github_copilot_sdk::rpc::McpOauthPendingRequestResponse::Token( + github_copilot_sdk::rpc::McpOauthPendingRequestResponseToken { + access_token: EXPECTED_TOKEN.to_string(), + expires_in: Some(3600), + kind: github_copilot_sdk::rpc::McpOauthPendingRequestResponseTokenKind::Token, + token_type: Some("Bearer".to_string()), + }, + ), + }) + .await + .expect("handle pending MCP OAuth request"); + assert!(handled.success); + + release_handler.notify_one(); + connected.await; + let tools = session + .rpc() + .mcp() + .list_tools(McpListToolsRequest { + server_name: server_name.to_string(), + }) + .await + .expect("list MCP tools"); + assert!(tools.tools.iter().any(|tool| tool.name == "whoami")); + let requests = oauth_server.requests().await; + assert!( + requests + .iter() + .any(|request| { + request.authorization.as_deref() == Some(&format!("Bearer {EXPECTED_TOKEN}")) + }) + ); + + session.disconnect().await.expect("disconnect session"); + client.stop().await.expect("stop client"); + oauth_server.stop().await; + }) + }) + .await; +} + #[derive(Default)] struct TokenAuthHandler { request: Mutex>, @@ -338,6 +441,32 @@ impl McpAuthHandler for CancelAuthHandler { } } +struct BlockingAuthHandler { + request: Arc>>, + request_observed: Arc, + release: Arc, +} + +#[async_trait] +impl McpAuthHandler for BlockingAuthHandler { + async fn handle( + &self, + _session_id: SessionId, + request_id: RequestId, + request: McpAuthRequest, + ) -> McpAuthResult { + assert_eq!(request.request_id, request_id); + *self.request.lock() = Some(request); + self.request_observed.notify_one(); + self.release.notified().await; + McpAuthResult::Token { + access_token: EXPECTED_TOKEN.to_string(), + token_type: Some("Bearer".to_string()), + expires_in: Some(3600), + } + } +} + #[derive(Deserialize)] struct OAuthMcpRequest { authorization: Option, diff --git a/rust/tests/e2e/rpc_mcp_lifecycle.rs b/rust/tests/e2e/rpc_mcp_lifecycle.rs index fc79828320..fa2b15d866 100644 --- a/rust/tests/e2e/rpc_mcp_lifecycle.rs +++ b/rust/tests/e2e/rpc_mcp_lifecycle.rs @@ -327,44 +327,6 @@ async fn should_configure_github_mcp_server() { .await; } -#[tokio::test] -async fn should_respond_to_mcp_oauth_request_without_pending_request() { - with_e2e_context( - "rpc_mcp_lifecycle", - "should_respond_to_mcp_oauth_request_without_pending_request", - |ctx| { - Box::pin(async move { - ctx.set_default_copilot_user(); - let host_server = "rpc-lifecycle-oauth-host"; - let client = ctx.start_client().await; - let session = - client - .create_session(ctx.approve_all_session_config().with_mcp_servers( - create_test_mcp_servers(ctx.repo_root(), host_server), - )) - .await - .expect("create session"); - wait_for_mcp_server_status(&session, host_server, McpServerStatus::Connected).await; - - let result = call_session_rpc( - &session, - "session.mcp.oauth.respond", - json!({ - "requestId": format!("missing-{}", uuid::Uuid::new_v4().simple()) - }), - ) - .await - .expect("respond to missing MCP OAuth request"); - assert!(result.is_object()); - - session.disconnect().await.expect("disconnect session"); - client.stop().await.expect("stop client"); - }) - }, - ) - .await; -} - fn create_test_mcp_servers( repo_root: &Path, server_name: &str, diff --git a/rust/tests/e2e/rpc_server.rs b/rust/tests/e2e/rpc_server.rs index 27cad2f69a..665041f49d 100644 --- a/rust/tests/e2e/rpc_server.rs +++ b/rust/tests/e2e/rpc_server.rs @@ -1,18 +1,23 @@ -use github_copilot_sdk::Client; +use std::collections::HashMap; + use github_copilot_sdk::rpc::{ - ConnectRemoteSessionParams, LocalSessionMetadataValue, McpDiscoverRequest, NameSetRequest, - PingRequest, SecretsAddFilterValuesRequest, SessionContext, SessionFsSetProviderConventions, + AgentsDiscoverRequest, AgentsGetDiscoveryPathsRequest, ConnectRemoteSessionParams, + InstructionsDiscoverRequest, InstructionsGetDiscoveryPathsRequest, + LlmInferenceHttpResponseChunkRequest, LlmInferenceHttpResponseStartRequest, + LocalSessionMetadataValue, McpDiscoverRequest, NameSetRequest, PingRequest, + SecretsAddFilterValuesRequest, SessionContext, SessionFsSetProviderConventions, SessionFsSetProviderRequest, SessionListFilter, SessionsBulkDeleteRequest, SessionsCheckInUseRequest, SessionsCloseRequest, SessionsEnrichMetadataRequest, SessionsFindByPrefixRequest, SessionsFindByTaskIDRequest, SessionsGetLastForContextRequest, SessionsListRequest, SessionsLoadDeferredRepoHooksRequest, SessionsPruneOldRequest, SessionsReleaseLockRequest, SessionsReloadPluginHooksRequest, SessionsSaveRequest, SessionsSetAdditionalPluginsRequest, SkillsConfigSetDisabledSkillsRequest, - SkillsDiscoverRequest, ToolsListRequest, + SkillsDiscoverRequest, SkillsGetDiscoveryPathsRequest, ToolsListRequest, }; +use github_copilot_sdk::{Client, RequestId}; use serde_json::json; -use super::support::with_e2e_context; +use super::support::{with_e2e_context, with_e2e_context_no_snapshot}; #[tokio::test] async fn should_call_rpc_ping_with_typed_params_and_result() { @@ -136,6 +141,49 @@ async fn should_call_rpc_tools_list_with_typed_result() { .await; } +#[tokio::test] +async fn should_reject_llm_response_frames_for_unknown_request() { + with_e2e_context_no_snapshot(|ctx| { + Box::pin(async move { + let client = ctx.start_client().await; + let request_id = RequestId::from("missing-llm-response-request"); + + let start = client + .rpc() + .llm_inference() + .http_response_start(LlmInferenceHttpResponseStartRequest { + headers: HashMap::from([( + "content-type".to_string(), + vec!["application/json".to_string()], + )]), + request_id: request_id.clone(), + status: 200, + status_text: Some("OK".to_string()), + }) + .await + .expect("send unknown LLM response start"); + assert!(!start.accepted); + + let chunk = client + .rpc() + .llm_inference() + .http_response_chunk(LlmInferenceHttpResponseChunkRequest { + binary: Some(false), + data: "{}".to_string(), + end: Some(true), + error: None, + request_id, + }) + .await + .expect("send unknown LLM response chunk"); + assert!(!chunk.accepted); + + client.stop().await.expect("stop client"); + }) + }) + .await; +} + #[tokio::test] async fn should_discover_server_mcp_and_skills() { with_e2e_context( @@ -150,12 +198,13 @@ async fn should_discover_server_mcp_and_skills() { "Skill discovered by server-scoped RPC tests.", ); let client = ctx.start_client().await; + let project_path = ctx.work_dir().to_string_lossy().to_string(); let mcp = client .rpc() .mcp() .discover(McpDiscoverRequest { - working_directory: Some(ctx.work_dir().to_string_lossy().to_string()), + working_directory: Some(project_path.clone()), }) .await .expect("mcp discover"); @@ -179,6 +228,101 @@ async fn should_discover_server_mcp_and_skills() { "Skill discovered by server-scoped RPC tests." ); + let skill_paths = client + .rpc() + .skills() + .get_discovery_paths(SkillsGetDiscoveryPathsRequest { + exclude_host_skills: Some(true), + project_paths: Some(vec![project_path.clone()]), + }) + .await + .expect("skills discovery paths"); + let project_skill_path = skill_paths + .paths + .iter() + .find(|path| { + path.project_path + .as_deref() + .is_some_and(|path| paths_equal(path, &project_path)) + && path.preferred_for_creation + }) + .expect("project skill discovery path"); + assert!(!project_skill_path.path.trim().is_empty()); + + let agents = client + .rpc() + .agents() + .discover(AgentsDiscoverRequest { + exclude_host_agents: Some(true), + project_paths: Some(vec![project_path.clone()]), + }) + .await + .expect("agents discover"); + assert!( + agents + .agents + .iter() + .all(|agent| !agent.name.trim().is_empty()) + ); + + let agent_paths = client + .rpc() + .agents() + .get_discovery_paths(AgentsGetDiscoveryPathsRequest { + exclude_host_agents: Some(true), + project_paths: Some(vec![project_path.clone()]), + }) + .await + .expect("agents discovery paths"); + let project_agent_path = agent_paths + .paths + .iter() + .find(|path| { + path.project_path + .as_deref() + .is_some_and(|path| paths_equal(path, &project_path)) + && path.preferred_for_creation + }) + .expect("project agent discovery path"); + assert!(!project_agent_path.path.trim().is_empty()); + + let instructions = client + .rpc() + .instructions() + .discover(InstructionsDiscoverRequest { + exclude_host_instructions: Some(true), + project_paths: Some(vec![project_path.clone()]), + }) + .await + .expect("instructions discover"); + assert!(instructions.sources.iter().all(|source| { + !source.id.trim().is_empty() + && !source.label.trim().is_empty() + && !source.source_path.trim().is_empty() + })); + + let instruction_paths = client + .rpc() + .instructions() + .get_discovery_paths(InstructionsGetDiscoveryPathsRequest { + exclude_host_instructions: Some(true), + project_paths: Some(vec![project_path.clone()]), + }) + .await + .expect("instructions discovery paths"); + assert!(!instruction_paths.paths.is_empty()); + assert!(instruction_paths.paths.iter().any(|path| { + path.project_path + .as_deref() + .is_some_and(|path| paths_equal(path, &project_path)) + })); + assert!( + instruction_paths + .paths + .iter() + .all(|path| !path.path.trim().is_empty()) + ); + client .rpc() .skills() @@ -701,3 +845,19 @@ fn assert_server_skill( ); skill } + +fn paths_equal(left: &str, right: &str) -> bool { + fn normalize(path: &str) -> String { + let mut normalized = path.replace('\\', "/"); + while normalized.ends_with('/') && normalized.len() > 1 { + normalized.pop(); + } + if cfg!(windows) { + normalized.to_ascii_lowercase() + } else { + normalized + } + } + + normalize(left) == normalize(right) +} diff --git a/rust/tests/e2e/rpc_server_misc.rs b/rust/tests/e2e/rpc_server_misc.rs index 2886aff280..b9e5cdf5c4 100644 --- a/rust/tests/e2e/rpc_server_misc.rs +++ b/rust/tests/e2e/rpc_server_misc.rs @@ -1,7 +1,9 @@ use github_copilot_sdk::Client; use github_copilot_sdk::rpc::{ - AgentRegistrySpawnRequest, SendAttachmentsToMessageParams, SessionsOpenStatus, + AccountLoginRequest, AccountLogoutRequest, AgentRegistrySpawnRequest, + SendAttachmentsToMessageParams, SessionsOpenStatus, UserSettingsSetRequest, }; +use serde_json::{Map, Value, json}; use super::support::{wait_for_condition, with_e2e_context}; @@ -25,6 +27,183 @@ async fn should_reload_user_settings() { .await; } +#[tokio::test] +async fn should_get_set_and_clear_user_settings() { + with_e2e_context( + "rpc_server_misc", + "should_get_set_and_clear_user_settings", + |ctx| { + Box::pin(async move { + let client = ctx.start_client().await; + + let initial = client + .rpc() + .user() + .settings() + .get() + .await + .expect("get initial user settings"); + let (key, value) = initial + .settings + .iter() + .find_map(|(key, setting)| { + setting.value.as_bool().map(|value| (key.clone(), value)) + }) + .expect("at least one boolean user setting"); + let toggled = !value; + + let set = client + .rpc() + .user() + .settings() + .set(UserSettingsSetRequest { + settings: setting_patch(&key, json!(toggled)), + }) + .await + .expect("set user setting"); + assert!(set.shadowed_keys.is_empty()); + client + .rpc() + .user() + .settings() + .reload() + .await + .expect("reload after set"); + let after_set = client + .rpc() + .user() + .settings() + .get() + .await + .expect("get after set"); + let metadata = after_set.settings.get(&key).expect("updated setting"); + assert_eq!(metadata.value, json!(toggled)); + assert!(!metadata.is_default); + + let clear = client + .rpc() + .user() + .settings() + .set(UserSettingsSetRequest { + settings: setting_patch(&key, Value::Null), + }) + .await + .expect("clear user setting"); + assert!(clear.shadowed_keys.is_empty()); + client + .rpc() + .user() + .settings() + .reload() + .await + .expect("reload after clear"); + let after_clear = client + .rpc() + .user() + .settings() + .get() + .await + .expect("get after clear"); + assert!( + after_clear + .settings + .get(&key) + .expect("cleared setting") + .is_default + ); + + client.stop().await.expect("stop client"); + }) + }, + ) + .await; +} + +#[tokio::test] +async fn should_login_list_getcurrentauth_and_logout_account() { + with_e2e_context( + "rpc_server_misc", + "should_login_list_getcurrentauth_and_logout_account", + |ctx| { + Box::pin(async move { + ctx.set_copilot_user_by_token_with_login("rust-account-token", "rust-account-user"); + let client = Client::start(ctx.client_options().with_use_logged_in_user(false)) + .await + .expect("start no-token client"); + + let initial = client + .rpc() + .account() + .get_current_auth() + .await + .expect("get initial auth"); + assert!(initial.auth_info.is_none()); + + let login = client + .rpc() + .account() + .login(AccountLoginRequest { + host: "https://github.com".to_string(), + login: "rust-account-user".to_string(), + token: "rust-account-token".to_string(), + }) + .await + .expect("account login"); + let _stored_in_vault = login.stored_in_vault; + + let current = client + .rpc() + .account() + .get_current_auth() + .await + .expect("get current auth after login"); + let auth_info = current.auth_info.expect("auth info after login"); + assert_eq!(auth_info["login"], json!("rust-account-user")); + assert_eq!(auth_info["host"], json!("https://github.com")); + + let users = client + .rpc() + .account() + .get_all_users() + .await + .expect("get all users"); + if let Some(user) = users + .iter() + .find(|user| user.auth_info["login"] == json!("rust-account-user")) + { + user.token + .as_deref() + .filter(|token| *token == "rust-account-token") + .unwrap_or_else(|| { + panic!("expected stored account token, got {:?}", user.token) + }); + } + + let logout = client + .rpc() + .account() + .logout(AccountLogoutRequest { auth_info }) + .await + .expect("account logout"); + assert!(!logout.has_more_users); + assert!( + client + .rpc() + .account() + .get_current_auth() + .await + .expect("get auth after logout") + .auth_info + .is_none() + ); + + client.stop().await.expect("stop client"); + }) + }, + ) + .await; +} + #[tokio::test] async fn should_report_agent_registry_spawn_gate_closed() { with_e2e_context( @@ -168,3 +347,9 @@ fn assert_not_unhandled(message: &str) { "{message}" ); } + +fn setting_patch(key: &str, value: Value) -> Value { + let mut settings = Map::new(); + settings.insert(key.to_string(), value); + Value::Object(settings) +} diff --git a/rust/tests/e2e/rpc_session_state_extras.rs b/rust/tests/e2e/rpc_session_state_extras.rs index 10954b4e27..148a4151c1 100644 --- a/rust/tests/e2e/rpc_session_state_extras.rs +++ b/rust/tests/e2e/rpc_session_state_extras.rs @@ -1,5 +1,13 @@ +use std::collections::HashMap; + use github_copilot_sdk::Client; -use github_copilot_sdk::rpc::PermissionsSetAllowAllRequest; +use github_copilot_sdk::rpc::{ + CompletionsRequestRequest, MetadataContextHeaviestMessagesRequest, ModelSwitchToRequest, + NamedProviderConfig, PermissionsSetAllowAllRequest, ProviderAddRequest, ProviderConfigType, + ProviderConfigWireApi, ProviderModelConfig, SessionVisibilityStatus, SubagentSettingsEntry, + SubagentSettingsEntryContextTier, UpdateSubagentSettingsRequest, + UpdateSubagentSettingsRequestSubagents, VisibilitySetRequest, +}; use super::support::{assistant_message_content, with_e2e_context}; @@ -102,7 +110,9 @@ async fn should_get_and_set_allowall_permissions() { .rpc() .permissions() .set_allow_all(PermissionsSetAllowAllRequest { - enabled: true, + enabled: Some(true), + mode: None, + model: None, source: None, }) .await @@ -123,7 +133,9 @@ async fn should_get_and_set_allowall_permissions() { .rpc() .permissions() .set_allow_all(PermissionsSetAllowAllRequest { - enabled: false, + enabled: Some(false), + mode: None, + model: None, source: None, }) .await @@ -248,6 +260,258 @@ async fn should_get_current_tool_metadata_after_initialization() { .await; } +#[tokio::test] +async fn should_add_byok_provider_and_model_at_runtime() { + with_e2e_context( + "rpc_session_state_extras", + "should_add_byok_provider_and_model_at_runtime", + |ctx| { + Box::pin(async move { + ctx.set_default_copilot_user(); + let client = ctx.start_client().await; + let session = client + .create_session(ctx.approve_all_session_config()) + .await + .expect("create session"); + + let result = session + .rpc() + .provider() + .add(ProviderAddRequest { + providers: Some(vec![NamedProviderConfig { + api_key: Some("provider-key".to_string()), + azure: None, + base_url: "https://models.example.test/v1".to_string(), + bearer_token: None, + has_bearer_token_provider: None, + headers: Some(HashMap::from([( + "x-provider".to_string(), + "rust".to_string(), + )])), + name: "rust-e2e-provider".to_string(), + transport: None, + r#type: Some(ProviderConfigType::Openai), + wire_api: Some(ProviderConfigWireApi::Completions), + }]), + models: Some(vec![ProviderModelConfig { + capabilities: None, + id: "small".to_string(), + max_context_window_tokens: None, + max_output_tokens: None, + max_prompt_tokens: Some(4096.0), + model_id: None, + name: Some("Rust Added Model".to_string()), + provider: "rust-e2e-provider".to_string(), + wire_model: None, + }]), + }) + .await + .expect("add provider model"); + assert_eq!(result.models.len(), 1); + + let selection_id = "rust-e2e-provider/small"; + session + .rpc() + .model() + .switch_to(ModelSwitchToRequest { + context_tier: None, + model_capabilities: None, + model_id: selection_id.to_string(), + reasoning_effort: None, + reasoning_summary: None, + verbosity: None, + }) + .await + .expect("switch to added model"); + let current = session + .rpc() + .model() + .get_current() + .await + .expect("get current model"); + assert_eq!(current.model_id.as_deref(), Some(selection_id)); + + session.disconnect().await.expect("disconnect session"); + client.stop().await.expect("stop client"); + }) + }, + ) + .await; +} + +#[tokio::test] +async fn should_return_empty_completions_when_host_does_not_provide_them() { + with_e2e_context( + "rpc_session_state_extras", + "should_return_empty_completions_when_host_does_not_provide_them", + |ctx| { + Box::pin(async move { + ctx.set_default_copilot_user(); + let client = ctx.start_client().await; + let session = client + .create_session(ctx.approve_all_session_config()) + .await + .expect("create session"); + + let result = session + .rpc() + .completions() + .request(CompletionsRequestRequest { + offset: 5, + text: "Use @ to mention context".to_string(), + }) + .await + .expect("request completions"); + assert!(result.items.is_empty()); + + session.disconnect().await.expect("disconnect session"); + client.stop().await.expect("stop client"); + }) + }, + ) + .await; +} + +#[tokio::test] +async fn should_report_visibility_as_unsynced_for_local_session() { + with_e2e_context( + "rpc_session_state_extras", + "should_report_visibility_as_unsynced_for_local_session", + |ctx| { + Box::pin(async move { + ctx.set_default_copilot_user(); + let client = ctx.start_client().await; + let session = client + .create_session(ctx.approve_all_session_config()) + .await + .expect("create session"); + + let set = session + .rpc() + .visibility() + .set(VisibilitySetRequest { + status: SessionVisibilityStatus::Unshared, + }) + .await + .expect("set visibility"); + assert!(!set.synced); + assert!(set.status.is_none()); + assert!(set.share_url.is_none()); + let get = session + .rpc() + .visibility() + .get() + .await + .expect("get visibility"); + assert!(!get.synced); + assert!(get.status.is_none()); + assert!(get.share_url.is_none()); + + session.disconnect().await.expect("disconnect session"); + client.stop().await.expect("stop client"); + }) + }, + ) + .await; +} + +#[tokio::test] +async fn should_get_context_attribution_and_heaviest_messages_after_turn() { + with_e2e_context( + "rpc_session_state_extras", + "should_get_context_attribution_and_heaviest_messages_after_turn", + |ctx| { + Box::pin(async move { + ctx.set_default_copilot_user(); + let client = ctx.start_client().await; + let session = client + .create_session(ctx.approve_all_session_config()) + .await + .expect("create session"); + + let answer = session + .send_and_wait("Say CONTEXT_METADATA_OK exactly.") + .await + .expect("send prompt") + .expect("assistant message"); + assert!(assistant_message_content(&answer).contains("CONTEXT_METADATA_OK")); + + let attribution = session + .rpc() + .metadata() + .get_context_attribution() + .await + .expect("get context attribution"); + assert!(attribution.context_attribution.is_some()); + let heaviest = session + .rpc() + .metadata() + .get_context_heaviest_messages(MetadataContextHeaviestMessagesRequest { + limit: Some(5), + }) + .await + .expect("get heaviest messages"); + assert!(heaviest.total_tokens >= 0); + + session.disconnect().await.expect("disconnect session"); + client.stop().await.expect("stop client"); + }) + }, + ) + .await; +} + +#[tokio::test] +async fn should_update_and_clear_live_subagent_settings() { + with_e2e_context( + "rpc_session_state_extras", + "should_update_and_clear_live_subagent_settings", + |ctx| { + Box::pin(async move { + ctx.set_default_copilot_user(); + let client = ctx.start_client().await; + let session = client + .create_session(ctx.approve_all_session_config()) + .await + .expect("create session"); + + session + .rpc() + .tools() + .update_subagent_settings(UpdateSubagentSettingsRequest { + subagents: Some(UpdateSubagentSettingsRequestSubagents { + agents: Some(HashMap::from([( + "general-purpose".to_string(), + SubagentSettingsEntry { + context_tier: Some( + SubagentSettingsEntryContextTier::LongContext, + ), + effort_level: Some("low".to_string()), + model: Some("gpt-5-mini".to_string()), + }, + )])), + disabled_subagents: Some(vec!["legacy-agent".to_string()]), + max_concurrency: None, + max_depth: None, + }), + }) + .await + .expect("update subagent settings"); + session + .rpc() + .tools() + .update_subagent_settings(UpdateSubagentSettingsRequest { subagents: None }) + .await + .expect("clear subagent settings"); + + session.disconnect().await.expect("disconnect session"); + client.stop().await.expect("stop client"); + }) + }, + ) + .await; +} + #[tokio::test] async fn should_reload_session_plugins() { with_e2e_context( diff --git a/rust/tests/e2e/rpc_tasks_and_handlers.rs b/rust/tests/e2e/rpc_tasks_and_handlers.rs index 9226addc0c..601cc70bf2 100644 --- a/rust/tests/e2e/rpc_tasks_and_handlers.rs +++ b/rust/tests/e2e/rpc_tasks_and_handlers.rs @@ -1,5 +1,11 @@ +use std::collections::HashMap; + use github_copilot_sdk::rpc::{ - CommandsHandlePendingCommandRequest, HandlePendingToolCallRequest, PermissionDecision, + CommandsHandlePendingCommandRequest, HandlePendingToolCallRequest, + McpHeadersHandlePendingHeadersRefreshRequest, + McpHeadersHandlePendingHeadersRefreshRequestHeaders, + McpHeadersHandlePendingHeadersRefreshRequestHeadersKind, + McpHeadersHandlePendingHeadersRefreshRequestRequest, PermissionDecision, PermissionDecisionApproveForLocation, PermissionDecisionApproveForLocationApproval, PermissionDecisionApproveForLocationApprovalCustomTool, PermissionDecisionApproveForLocationApprovalCustomToolKind, @@ -16,8 +22,9 @@ use github_copilot_sdk::rpc::{ UIElicitationResponse, UIElicitationResponseAction, UIExitPlanModeResponse, UIHandlePendingAutoModeSwitchRequest, UIHandlePendingElicitationRequest, UIHandlePendingExitPlanModeRequest, UIHandlePendingSamplingRequest, - UIHandlePendingUserInputRequest, UIUnregisterDirectAutoModeSwitchHandlerRequest, - UIUserInputResponse, + UIHandlePendingSessionLimitsExhaustedRequest, UIHandlePendingUserInputRequest, + UISessionLimitsExhaustedResponse, UISessionLimitsExhaustedResponseAction, + UIUnregisterDirectAutoModeSwitchHandlerRequest, UIUserInputResponse, }; use super::support::with_e2e_context; @@ -323,6 +330,23 @@ async fn should_return_expected_results_for_missing_pending_handler_requestids() .expect("handle missing exit plan"); assert!(!exit_plan.success); + let session_limits = session + .rpc() + .ui() + .handle_pending_session_limits_exhausted( + UIHandlePendingSessionLimitsExhaustedRequest { + request_id: "missing-session-limits-request".into(), + response: UISessionLimitsExhaustedResponse { + action: UISessionLimitsExhaustedResponseAction::Unset, + additional_ai_credits: None, + max_ai_credits: None, + }, + }, + ) + .await + .expect("handle missing session limits exhausted"); + assert!(!session_limits.success); + for (request_id, result) in [ ( "missing-permission-request", @@ -385,6 +409,28 @@ async fn should_return_expected_results_for_missing_pending_handler_requestids() assert!(!permission.success, "{request_id} should not be handled"); } + let headers_refresh = session + .rpc() + .mcp() + .headers() + .handle_pending_headers_refresh_request( + McpHeadersHandlePendingHeadersRefreshRequestRequest { + request_id: "missing-headers-refresh-request".into(), + result: McpHeadersHandlePendingHeadersRefreshRequest::Headers( + McpHeadersHandlePendingHeadersRefreshRequestHeaders { + headers: HashMap::from([( + "x-refresh".to_string(), + "missing".to_string(), + )]), + kind: McpHeadersHandlePendingHeadersRefreshRequestHeadersKind::Headers, + }, + ), + }, + ) + .await + .expect("handle missing headers refresh"); + assert!(!headers_refresh.success); + session.disconnect().await.expect("disconnect session"); client.stop().await.expect("stop client"); }) diff --git a/rust/tests/e2e/session_config.rs b/rust/tests/e2e/session_config.rs index d4948ba177..0a7a5eb11b 100644 --- a/rust/tests/e2e/session_config.rs +++ b/rust/tests/e2e/session_config.rs @@ -300,7 +300,7 @@ impl CopilotRequestHandler for RecordingHandler { body: request.body.clone(), }); if is_inference_url(&request.url) { - return Ok(synth_inference_response(&request.url)); + return Ok(synth_inference_response(&request.url, &request.body)); } Ok(synth_non_inference_response(&request.url)) } @@ -329,6 +329,78 @@ fn http_response(status: u16, headers: HeaderMap, body: Value) -> CopilotHttpRes CopilotHttpResponse::new(status, None, headers, Box::pin(stream)) } +fn sse_response(body: String) -> CopilotHttpResponse { + let mut headers = HeaderMap::new(); + headers.insert( + "content-type", + HeaderValue::from_static("text/event-stream"), + ); + let stream = futures_util::stream::once(async move { + Ok::(Bytes::from(body.into_bytes())) + }); + CopilotHttpResponse::new(200, None, headers, Box::pin(stream)) +} + +fn wants_stream(body: &[u8]) -> bool { + String::from_utf8_lossy(body) + .replace(char::is_whitespace, "") + .contains("\"stream\":true") +} + +fn anthropic_message_stream_body(text: &str) -> String { + let events = [ + ( + "message_start", + json!({ + "type": "message_start", + "message": { + "id": "msg_stub_1", + "type": "message", + "role": "assistant", + "model": "claude-sonnet-4.5", + "content": [], + "stop_reason": null, + "stop_sequence": null, + "usage": { "input_tokens": 5, "output_tokens": 1 }, + }, + }), + ), + ( + "content_block_start", + json!({ + "type": "content_block_start", + "index": 0, + "content_block": { "type": "text", "text": "" }, + }), + ), + ( + "content_block_delta", + json!({ + "type": "content_block_delta", + "index": 0, + "delta": { "type": "text_delta", "text": text }, + }), + ), + ( + "content_block_stop", + json!({ "type": "content_block_stop", "index": 0 }), + ), + ( + "message_delta", + json!({ + "type": "message_delta", + "delta": { "stop_reason": "end_turn", "stop_sequence": null }, + "usage": { "output_tokens": 7 }, + }), + ), + ("message_stop", json!({ "type": "message_stop" })), + ]; + events + .iter() + .map(|(name, data)| format!("event: {name}\ndata: {data}\n\n")) + .collect() +} + fn synth_non_inference_response(url: &str) -> CopilotHttpResponse { let lower = url.to_lowercase(); if lower.ends_with("/models") { @@ -369,9 +441,12 @@ fn synth_non_inference_response(url: &str) -> CopilotHttpResponse { http_response(200, json_headers(), json!({})) } -fn synth_inference_response(url: &str) -> CopilotHttpResponse { +fn synth_inference_response(url: &str, body: &[u8]) -> CopilotHttpResponse { let lower = url.to_lowercase(); if lower.ends_with("/messages") { + if wants_stream(body) { + return sse_response(anthropic_message_stream_body(SYNTHETIC_TEXT)); + } return http_response( 200, json_headers(), diff --git a/rust/tests/e2e/support.rs b/rust/tests/e2e/support.rs index 5052ef1be4..7e47d8fbae 100644 --- a/rust/tests/e2e/support.rs +++ b/rust/tests/e2e/support.rs @@ -157,12 +157,7 @@ impl E2eContext { } pub fn client_options(&self) -> ClientOptions { - ClientOptions::new() - .with_program(CliProgram::Path(PathBuf::from(node_program()))) - .with_prefix_args([self.cli_path.as_os_str().to_owned()]) - .with_cwd(self.work_dir.path()) - .with_env(self.environment()) - .with_use_logged_in_user(false) + client_options_for_cli(&self.cli_path, self.work_dir.path(), self.environment()) } pub fn client_options_with_transport(&self, transport: Transport) -> ClientOptions { @@ -188,12 +183,7 @@ impl E2eContext { .iter() .map(|(key, value)| (OsString::from(*key), OsString::from(*value))), ); - let options = ClientOptions::new() - .with_program(CliProgram::Path(PathBuf::from(node_program()))) - .with_prefix_args([self.cli_path.as_os_str().to_owned()]) - .with_cwd(self.work_dir.path()) - .with_env(env) - .with_use_logged_in_user(false) + let options = client_options_for_cli(&self.cli_path, self.work_dir.path(), env) .with_request_handler(handler); Client::start(options).await.expect("start E2E LLM client") } @@ -627,6 +617,28 @@ fn cli_path(repo_root: &Path) -> std::io::Result { )) } +fn client_options_for_cli( + cli_path: &Path, + cwd: &Path, + env: Vec<(OsString, OsString)>, +) -> ClientOptions { + let options = ClientOptions::new() + .with_cwd(cwd) + .with_env(env) + .with_use_logged_in_user(false); + if cli_path + .extension() + .and_then(|extension| extension.to_str()) + .is_some_and(|extension| extension.eq_ignore_ascii_case("js")) + { + options + .with_program(CliProgram::Path(PathBuf::from(node_program()))) + .with_prefix_args([cli_path.as_os_str().to_owned()]) + } else { + options.with_program(CliProgram::Path(cli_path.to_path_buf())) + } +} + fn canonical_temp_path(path: &Path) -> PathBuf { std::fs::canonicalize(path).unwrap_or_else(|_| path.to_path_buf()) } diff --git a/rust/tests/session_test.rs b/rust/tests/session_test.rs index 2a2ceabf80..2599ea6d3a 100644 --- a/rust/tests/session_test.rs +++ b/rust/tests/session_test.rs @@ -25,7 +25,7 @@ use github_copilot_sdk::types::{ MessageOptions, RequestId, SessionConfig, SessionId, SetModelOptions, Tool, ToolInvocation, ToolResult, }; -use github_copilot_sdk::{Client, ContextTier, tool}; +use github_copilot_sdk::{Client, ContextTier, ErrorKind, ProtocolErrorKind, tool}; use serde_json::Value; use tokio::io::{AsyncWrite, AsyncWriteExt, duplex}; use tokio::time::timeout; @@ -754,6 +754,322 @@ async fn create_session_sends_canvas_wire_fields() { timeout(TIMEOUT, create_handle).await.unwrap().unwrap(); } +fn make_client_with_telemetry( + callback: github_copilot_sdk::github_telemetry::GitHubTelemetryCallback, +) -> (Client, tokio::io::DuplexStream, tokio::io::DuplexStream) { + let (client_write, server_read) = duplex(8192); + let (server_write, client_read) = duplex(8192); + let client = Client::from_streams_with_github_telemetry( + client_read, + client_write, + std::env::temp_dir(), + callback, + ) + .unwrap(); + (client, server_read, server_write) +} + +#[tokio::test] +async fn create_and_resume_send_github_telemetry_forwarding_when_callback_registered() { + use github_copilot_sdk::types::ResumeSessionConfig; + + let callback: github_copilot_sdk::github_telemetry::GitHubTelemetryCallback = + Arc::new(|_notification| {}); + let (client, mut server_read, mut server_write) = make_client_with_telemetry(callback); + + let create_handle = tokio::spawn({ + let client = client.clone(); + async move { + client + .create_session(SessionConfig::default()) + .await + .unwrap() + } + }); + + let request = read_framed(&mut server_read).await; + assert_eq!(request["method"], "session.create"); + assert_eq!(request["params"]["enableGitHubTelemetryForwarding"], true); + + let id = request["id"].as_u64().unwrap(); + let session_id = requested_session_id(&request).to_string(); + let response = serde_json::json!({ + "jsonrpc": "2.0", + "id": id, + "result": { "sessionId": session_id.clone() }, + }); + write_framed(&mut server_write, &serde_json::to_vec(&response).unwrap()).await; + timeout(TIMEOUT, create_handle).await.unwrap().unwrap(); + + let resume_handle = tokio::spawn({ + let client = client.clone(); + let session_id = session_id.clone(); + async move { + client + .resume_session(ResumeSessionConfig::new(SessionId::from(session_id))) + .await + .unwrap() + } + }); + + let request = read_framed(&mut server_read).await; + assert_eq!(request["method"], "session.resume"); + assert_eq!(request["params"]["enableGitHubTelemetryForwarding"], true); + + let id = request["id"].as_u64().unwrap(); + let response = serde_json::json!({ + "jsonrpc": "2.0", + "id": id, + "result": { "sessionId": session_id }, + }); + write_framed(&mut server_write, &serde_json::to_vec(&response).unwrap()).await; + + let reload = read_framed(&mut server_read).await; + assert_eq!(reload["method"], "session.skills.reload"); + let id = reload["id"].as_u64().unwrap(); + let response = serde_json::json!({ "jsonrpc": "2.0", "id": id, "result": {} }); + write_framed(&mut server_write, &serde_json::to_vec(&response).unwrap()).await; + + timeout(TIMEOUT, resume_handle).await.unwrap().unwrap(); +} + +#[tokio::test] +async fn create_session_omits_github_telemetry_forwarding_without_callback() { + let (client, mut server_read, mut server_write) = make_client(); + + let create_handle = tokio::spawn({ + let client = client.clone(); + async move { + client + .create_session(SessionConfig::default()) + .await + .unwrap() + } + }); + + let request = read_framed(&mut server_read).await; + assert_eq!(request["method"], "session.create"); + assert!( + request["params"] + .get("enableGitHubTelemetryForwarding") + .is_none_or(Value::is_null), + "forwarding flag should be omitted when no callback is registered" + ); + + let id = request["id"].as_u64().unwrap(); + let session_id = requested_session_id(&request).to_string(); + let response = serde_json::json!({ + "jsonrpc": "2.0", + "id": id, + "result": { "sessionId": session_id }, + }); + write_framed(&mut server_write, &serde_json::to_vec(&response).unwrap()).await; + timeout(TIMEOUT, create_handle).await.unwrap().unwrap(); +} + +#[tokio::test] +async fn resume_session_omits_github_telemetry_forwarding_without_callback() { + use github_copilot_sdk::types::ResumeSessionConfig; + + let (client, mut server_read, mut server_write) = make_client(); + + let resume_handle = tokio::spawn({ + let client = client.clone(); + async move { + client + .resume_session(ResumeSessionConfig::new(SessionId::from( + "sess-1".to_string(), + ))) + .await + .unwrap() + } + }); + + let request = read_framed(&mut server_read).await; + assert_eq!(request["method"], "session.resume"); + assert!( + request["params"] + .get("enableGitHubTelemetryForwarding") + .is_none_or(Value::is_null), + "forwarding flag should be omitted when no callback is registered" + ); + + let id = request["id"].as_u64().unwrap(); + let response = serde_json::json!({ + "jsonrpc": "2.0", + "id": id, + "result": { "sessionId": "sess-1" }, + }); + write_framed(&mut server_write, &serde_json::to_vec(&response).unwrap()).await; + + let reload = read_framed(&mut server_read).await; + assert_eq!(reload["method"], "session.skills.reload"); + let id = reload["id"].as_u64().unwrap(); + let response = serde_json::json!({ "jsonrpc": "2.0", "id": id, "result": {} }); + write_framed(&mut server_write, &serde_json::to_vec(&response).unwrap()).await; + + timeout(TIMEOUT, resume_handle).await.unwrap().unwrap(); +} + +#[tokio::test] +async fn connect_sends_github_telemetry_forwarding_when_callback_registered() { + let callback: github_copilot_sdk::github_telemetry::GitHubTelemetryCallback = + Arc::new(|_notification| {}); + let (client, mut server_read, mut server_write) = make_client_with_telemetry(callback); + + let handle = tokio::spawn({ + let client = client.clone(); + async move { client.verify_protocol_version().await.unwrap() } + }); + + let request = read_framed(&mut server_read).await; + assert_eq!(request["method"], "connect"); + assert_eq!(request["params"]["enableGitHubTelemetryForwarding"], true); + + let id = request["id"].as_u64().unwrap(); + let response = serde_json::json!({ + "jsonrpc": "2.0", + "id": id, + "result": { "ok": true, "protocolVersion": 3, "version": "test" }, + }); + write_framed(&mut server_write, &serde_json::to_vec(&response).unwrap()).await; + timeout(TIMEOUT, handle).await.unwrap().unwrap(); +} + +#[tokio::test] +async fn connect_omits_github_telemetry_forwarding_without_callback() { + let (client, mut server_read, mut server_write) = make_client(); + + let handle = tokio::spawn({ + let client = client.clone(); + async move { client.verify_protocol_version().await.unwrap() } + }); + + let request = read_framed(&mut server_read).await; + assert_eq!(request["method"], "connect"); + assert!( + request["params"] + .get("enableGitHubTelemetryForwarding") + .is_none_or(Value::is_null), + "forwarding flag should be omitted when no callback is registered" + ); + + let id = request["id"].as_u64().unwrap(); + let response = serde_json::json!({ + "jsonrpc": "2.0", + "id": id, + "result": { "ok": true, "protocolVersion": 3, "version": "test" }, + }); + write_framed(&mut server_write, &serde_json::to_vec(&response).unwrap()).await; + timeout(TIMEOUT, handle).await.unwrap().unwrap(); +} + +#[tokio::test] +async fn connect_rejects_invalid_protocol_version_values() { + for protocol_version in [-1, i64::from(u32::MAX) + 1] { + let (client, mut server_read, mut server_write) = make_client(); + + let handle = tokio::spawn({ + let client = client.clone(); + async move { client.verify_protocol_version().await } + }); + + let request = read_framed(&mut server_read).await; + assert_eq!(request["method"], "connect"); + + let id = request["id"].as_u64().unwrap(); + let response = serde_json::json!({ + "jsonrpc": "2.0", + "id": id, + "result": { "ok": true, "protocolVersion": protocol_version, "version": "test" }, + }); + write_framed(&mut server_write, &serde_json::to_vec(&response).unwrap()).await; + + let err = timeout(TIMEOUT, handle) + .await + .unwrap() + .unwrap() + .unwrap_err(); + match err.kind() { + ErrorKind::Protocol(ProtocolErrorKind::InvalidProtocolVersion { server }) => { + assert_eq!(*server, protocol_version); + } + other => panic!("unexpected error kind: {other:?}"), + } + } +} + +#[tokio::test] +async fn github_telemetry_event_dispatches_to_callback() { + use github_copilot_sdk::github_telemetry::GitHubTelemetryNotification; + + let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel::(); + let callback: github_copilot_sdk::github_telemetry::GitHubTelemetryCallback = + Arc::new(move |notification| { + let _ = tx.send(notification); + }); + let (client, mut server_read, mut server_write) = make_client_with_telemetry(callback); + + let create_handle = tokio::spawn({ + let client = client.clone(); + async move { + client + .create_session(SessionConfig::default()) + .await + .unwrap() + } + }); + + let request = read_framed(&mut server_read).await; + let id = request["id"].as_u64().unwrap(); + let session_id = requested_session_id(&request).to_string(); + let response = serde_json::json!({ + "jsonrpc": "2.0", + "id": id, + "result": { "sessionId": session_id.clone() }, + }); + write_framed(&mut server_write, &serde_json::to_vec(&response).unwrap()).await; + timeout(TIMEOUT, create_handle).await.unwrap().unwrap(); + + let notification = serde_json::json!({ + "jsonrpc": "2.0", + "method": "gitHubTelemetry.event", + "params": { + "sessionId": session_id.clone(), + "restricted": false, + "event": { + "kind": "tool_call_executed", + "properties": { "tool": "bash" }, + "metrics": { "duration_ms": 12.0 }, + "session_id": session_id.clone(), + "created_at": "2025-01-01T00:00:00Z" + } + } + }); + write_framed( + &mut server_write, + &serde_json::to_vec(¬ification).unwrap(), + ) + .await; + + let received = timeout(TIMEOUT, rx.recv()).await.unwrap().unwrap(); + assert_eq!(received.session_id.as_deref(), Some(session_id.as_str())); + assert!(!received.restricted); + assert_eq!(received.event.kind, "tool_call_executed"); + assert_eq!( + received.event.properties.get("tool").map(String::as_str), + Some("bash") + ); + assert_eq!( + received.event.metrics.get("duration_ms").copied(), + Some(12.0) + ); + assert_eq!( + received.event.created_at.as_deref(), + Some("2025-01-01T00:00:00Z") + ); +} + #[tokio::test] async fn provider_canvas_dispatch_routes_direct_canvas_action_requests() { let (session, mut server) = create_session_pair_with_config(|cfg| { diff --git a/scripts/codegen/csharp.ts b/scripts/codegen/csharp.ts index b06809c183..c9a5f8237d 100644 --- a/scripts/codegen/csharp.ts +++ b/scripts/codegen/csharp.ts @@ -1431,6 +1431,7 @@ let nonExperimentalRpcTypes = new Set(); let rpcKnownTypes = new Map(); let rpcEnumOutput: string[] = []; let externalRpcValueTypes = new Set(); +let rpcRootJsonSerializableTypes = new Set(); /** Schema definitions available during RPC generation (for $ref resolution). */ let rpcDefinitions: DefinitionCollections = { definitions: {}, $defs: {} }; @@ -1703,7 +1704,11 @@ function emitRpcResultType(typeName: string, schema: JSONSchema7, visibility: "p return typeName; } - return resolveRpcType(schema, true, typeName, "", classes); + const resultType = resolveRpcType(schema, true, typeName, "", classes); + if (resultType.includes("<") || resultType.endsWith("[]")) { + rpcRootJsonSerializableTypes.add(resultType.replace(/\?$/, "")); + } + return resultType; } /** @@ -2446,6 +2451,7 @@ function generateRpcCode( nonExperimentalRpcTypes.clear(); rpcKnownTypes.clear(); rpcEnumOutput = []; + rpcRootJsonSerializableTypes.clear(); generatedEnums.clear(); // Clear shared enum deduplication map externalRpcValueTypes = new Set([...externalValueTypes].map(typeToClassName)); rpcDefinitions = collectDefinitionCollections(schema as Record); @@ -2513,7 +2519,9 @@ namespace GitHub.Copilot.Rpc; if (clientGlobalParts.length > 0) lines.push(...clientGlobalParts, ""); // Add JsonSerializerContext for AOT/trimming support - const typeNames = [...emittedRpcClassSchemas.keys(), ...emittedRpcEnumResultTypes].sort(); + const typeNames = [ + ...new Set([...emittedRpcClassSchemas.keys(), ...emittedRpcEnumResultTypes, ...rpcRootJsonSerializableTypes]), + ].sort(); if (typeNames.length > 0) { lines.push(`[JsonSourceGenerationOptions(`); lines.push(` JsonSerializerDefaults.Web,`); diff --git a/scripts/codegen/go.ts b/scripts/codegen/go.ts index 57957499e2..1e6cf0a42c 100644 --- a/scripts/codegen/go.ts +++ b/scripts/codegen/go.ts @@ -4385,6 +4385,11 @@ function emitClientGlobalApiRegistration(lines: string[], clientSchema: Record "Data":`); out.push(` assert isinstance(obj, dict)`); - out.push( - ` return Data(**{_compat_to_python_key(key): _compat_from_json_value(value) for key, value in obj.items()})` - ); + out.push(` data = Data()`); + out.push(` data._values = {}`); + out.push(` data._json_keys = {}`); + out.push(` data._json_values = {}`); + out.push(` for key, value in obj.items():`); + out.push(` py_key = _compat_to_python_key(key)`); + out.push(` json_value = _compat_from_json_value(value)`); + out.push(` data._values[py_key] = json_value`); + out.push(` data._json_keys[py_key] = key`); + out.push(` data._json_values[key] = json_value`); + out.push(` setattr(data, py_key, data._values[py_key])`); + out.push(` return data`); out.push(``); out.push(` def to_dict(self) -> dict:`); + out.push(` if self._json_values is not None:`); out.push( - ` return {_compat_to_json_key(key): _compat_to_json_value(value) for key, value in self._values.items() if value is not None}` + ` return {key: _compat_to_json_value(value) for key, value in self._json_values.items() if value is not None}` + ); + out.push( + ` return {(self._json_keys.get(key) or _compat_to_json_key(key)): _compat_to_json_value(value) for key, value in self._values.items() if value is not None}` ); out.push(``); out.push(``); @@ -3791,6 +3806,20 @@ function emitClientGlobalRegistrationMethod( const handlerField = toSnakeCase(groupName); const handlerMethod = clientSessionHandlerMethodName(method.rpcMethod); + if (method.notification) { + // Notification methods carry no response and are dispatched via the + // notification path (an `id`-less message never reaches a request + // handler), so register on the method-specific notification registry. + lines.push(` async def ${handlerVariableName}(params: dict) -> None:`); + lines.push(` request = ${paramsType}.from_dict(params)`); + lines.push(` handler = handlers.${handlerField}`); + lines.push(` if handler is None: return None`); + lines.push(` await handler.${handlerMethod}(request)`); + lines.push(` return None`); + lines.push(` client.set_notification_method_handler("${method.rpcMethod}", ${handlerVariableName})`); + return; + } + lines.push(` async def ${handlerVariableName}(params: dict) -> dict | None:`); lines.push(` request = ${paramsType}.from_dict(params)`); lines.push(` handler = handlers.${handlerField}`); diff --git a/scripts/codegen/typescript.ts b/scripts/codegen/typescript.ts index 1303a4979c..497c909ea5 100644 --- a/scripts/codegen/typescript.ts +++ b/scripts/codegen/typescript.ts @@ -1011,7 +1011,24 @@ function emitClientGlobalApiRegistration(clientSchema: Record): const pType = paramsTypeName(method); const hasParams = hasSchemaPayload(getMethodParamsSchema(method)); - if (hasParams) { + if (method.notification) { + // Notification methods carry no response; the server dispatches + // them via `sendNotification`, which only fires `onNotification` + // handlers (an `onRequest` handler would never be invoked). + if (hasParams) { + lines.push(` connection.onNotification("${method.rpcMethod}", async (params: ${pType}) => {`); + lines.push(` const handler = handlers.${groupName};`); + lines.push(` if (!handler) return;`); + lines.push(` await handler.${name}(params);`); + lines.push(` });`); + } else { + lines.push(` connection.onNotification("${method.rpcMethod}", async () => {`); + lines.push(` const handler = handlers.${groupName};`); + lines.push(` if (!handler) return;`); + lines.push(` await handler.${name}();`); + lines.push(` });`); + } + } else if (hasParams) { lines.push(` connection.onRequest("${method.rpcMethod}", async (params: ${pType}) => {`); lines.push(` const handler = handlers.${groupName};`); lines.push(` if (!handler) throw new Error("No ${groupName} client-global handler registered");`); diff --git a/scripts/codegen/utils.ts b/scripts/codegen/utils.ts index c63f9732c4..9ab335b05f 100644 --- a/scripts/codegen/utils.ts +++ b/scripts/codegen/utils.ts @@ -383,6 +383,7 @@ export interface RpcMethod { stability?: string; visibility?: string; deprecated?: boolean; + notification?: boolean; } export function getRpcSchemaTypeName(schema: JSONSchema7 | null | undefined, fallback: string): string { diff --git a/test/harness/package-lock.json b/test/harness/package-lock.json index 656982f7f2..4f5a2c3b9b 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.67", + "@github/copilot": "^1.0.69", "@modelcontextprotocol/sdk": "^1.26.0", "@types/node": "^25.3.3", "@types/node-forge": "^1.3.14", @@ -501,9 +501,9 @@ } }, "node_modules/@github/copilot": { - "version": "1.0.67", - "resolved": "https://registry.npmjs.org/@github/copilot/-/copilot-1.0.67.tgz", - "integrity": "sha512-5YEY9LNXBT9Q8uShjCdYcornJJJhGtdIzSYla2+pjfXYpHsDVibqYubzYjfgffOUKFChyzOpH7n/868+t56iIg==", + "version": "1.0.69", + "resolved": "https://registry.npmjs.org/@github/copilot/-/copilot-1.0.69.tgz", + "integrity": "sha512-sri6PKtRQu7mfok3eV505fmYo6M6PGZpB1vd+QbMEm2rEZaKE2+wJGCAtkpQNKfAWBvvoW/C94IaeFyyulUvqQ==", "dev": true, "license": "SEE LICENSE IN LICENSE.md", "dependencies": { @@ -513,20 +513,20 @@ "copilot": "npm-loader.js" }, "optionalDependencies": { - "@github/copilot-darwin-arm64": "1.0.67", - "@github/copilot-darwin-x64": "1.0.67", - "@github/copilot-linux-arm64": "1.0.67", - "@github/copilot-linux-x64": "1.0.67", - "@github/copilot-linuxmusl-arm64": "1.0.67", - "@github/copilot-linuxmusl-x64": "1.0.67", - "@github/copilot-win32-arm64": "1.0.67", - "@github/copilot-win32-x64": "1.0.67" + "@github/copilot-darwin-arm64": "1.0.69", + "@github/copilot-darwin-x64": "1.0.69", + "@github/copilot-linux-arm64": "1.0.69", + "@github/copilot-linux-x64": "1.0.69", + "@github/copilot-linuxmusl-arm64": "1.0.69", + "@github/copilot-linuxmusl-x64": "1.0.69", + "@github/copilot-win32-arm64": "1.0.69", + "@github/copilot-win32-x64": "1.0.69" } }, "node_modules/@github/copilot-darwin-arm64": { - "version": "1.0.67", - "resolved": "https://registry.npmjs.org/@github/copilot-darwin-arm64/-/copilot-darwin-arm64-1.0.67.tgz", - "integrity": "sha512-CO3mpgFXcN6e7ZsSmjMkt1AKxMfb1+mjdn3yrf2DRnnWIURSK9kGvw+E+E1+YE37D1MBiUn/VOBmhRad5+vl0A==", + "version": "1.0.69", + "resolved": "https://registry.npmjs.org/@github/copilot-darwin-arm64/-/copilot-darwin-arm64-1.0.69.tgz", + "integrity": "sha512-LpaVS2o21BbYTFUlkqdwUJXqE0l1Lp50Cb/EHru3z+5n5x7zNUU5IlPxdMg5gHMVw78clnMn2zSJbe5hYFyDDQ==", "cpu": [ "arm64" ], @@ -541,9 +541,9 @@ } }, "node_modules/@github/copilot-darwin-x64": { - "version": "1.0.67", - "resolved": "https://registry.npmjs.org/@github/copilot-darwin-x64/-/copilot-darwin-x64-1.0.67.tgz", - "integrity": "sha512-M20Hpn3bOJRkVwAIVRK4ZlX66AqtmGfXZRxZBRFQC045QIwcfmVUP45sTSgXDb4uHWeK0cZgdTdniHwKGtMplw==", + "version": "1.0.69", + "resolved": "https://registry.npmjs.org/@github/copilot-darwin-x64/-/copilot-darwin-x64-1.0.69.tgz", + "integrity": "sha512-QL5bsmnYqliBVIEE8RY91e6yAr39c2TNvJc/5IzoaKMlQ9MCrD9xakBJwd7LOS0SiOwRE4fqn8pUA4L1+UU5fg==", "cpu": [ "x64" ], @@ -558,9 +558,9 @@ } }, "node_modules/@github/copilot-linux-arm64": { - "version": "1.0.67", - "resolved": "https://registry.npmjs.org/@github/copilot-linux-arm64/-/copilot-linux-arm64-1.0.67.tgz", - "integrity": "sha512-b4ePtFBow+Ior+aVLKA1hHxhR5wF+ql5CD7TSg/NHGYgc1kwD+3a9uKSENy05J5Lit/G/DZ9C6JwowvvdMWSKg==", + "version": "1.0.69", + "resolved": "https://registry.npmjs.org/@github/copilot-linux-arm64/-/copilot-linux-arm64-1.0.69.tgz", + "integrity": "sha512-SOibp0I6APLoY2KkccbNOISpbhE3lp41s9fM3eANqfdoHQLsgBopfk4ruBP8k/DzjQA6t4MrbV8v6dLixAqv3w==", "cpu": [ "arm64" ], @@ -575,9 +575,9 @@ } }, "node_modules/@github/copilot-linux-x64": { - "version": "1.0.67", - "resolved": "https://registry.npmjs.org/@github/copilot-linux-x64/-/copilot-linux-x64-1.0.67.tgz", - "integrity": "sha512-4ynZyfKnWAdvEPAFDDBIz1wpFttcOTJu4Y8Mlz5oXCBA0NM/rwr8K4l7Adp8UzwbfmdrMJ9y+zivqRBMDbPInA==", + "version": "1.0.69", + "resolved": "https://registry.npmjs.org/@github/copilot-linux-x64/-/copilot-linux-x64-1.0.69.tgz", + "integrity": "sha512-URT7UaR3l1VAAtEpbKjybup2vxHTMSfDTVlu0jinu1O2nWxFOeQ9N4BQgRdcCB7wDu3N8Yz47o3fV1Gob6Z1Yw==", "cpu": [ "x64" ], @@ -592,9 +592,9 @@ } }, "node_modules/@github/copilot-linuxmusl-arm64": { - "version": "1.0.67", - "resolved": "https://registry.npmjs.org/@github/copilot-linuxmusl-arm64/-/copilot-linuxmusl-arm64-1.0.67.tgz", - "integrity": "sha512-IjezxBU8fYUr/b5hEiniXqzwoOrJ4egrQSBbG96M+roLTqd9txP0MgxZtcRtKV7phRIdIGE109wwrn4H6hSqmA==", + "version": "1.0.69", + "resolved": "https://registry.npmjs.org/@github/copilot-linuxmusl-arm64/-/copilot-linuxmusl-arm64-1.0.69.tgz", + "integrity": "sha512-Y6DY9RkyWV6l+S0cNz4d+AkUa38goNV5StQH3j8OoVrndK3+RgQeUjG1XpJ//PzXEXi4vQ0cZuc9XzFj/TY7tw==", "cpu": [ "arm64" ], @@ -609,9 +609,9 @@ } }, "node_modules/@github/copilot-linuxmusl-x64": { - "version": "1.0.67", - "resolved": "https://registry.npmjs.org/@github/copilot-linuxmusl-x64/-/copilot-linuxmusl-x64-1.0.67.tgz", - "integrity": "sha512-Zy/rbja1lnhzDoNfn051H0EybCseCvjvH7WmbcHCayjXUjzXeKF6OmAt4hvqFZH87ttT3KbKtQ8/6oDUhhM2YQ==", + "version": "1.0.69", + "resolved": "https://registry.npmjs.org/@github/copilot-linuxmusl-x64/-/copilot-linuxmusl-x64-1.0.69.tgz", + "integrity": "sha512-h0aNgUWRu70Tgp4/7Vdqa/4XKJi96wP32R1PoKeXkz1QNxb6i/IQxhoCjjS+xFon9e2kefKdF8pDrDtUJjsiBQ==", "cpu": [ "x64" ], @@ -626,9 +626,9 @@ } }, "node_modules/@github/copilot-win32-arm64": { - "version": "1.0.67", - "resolved": "https://registry.npmjs.org/@github/copilot-win32-arm64/-/copilot-win32-arm64-1.0.67.tgz", - "integrity": "sha512-O3VFRS5v9NXRP8o+N1SvcFbBqECDzZP7XQBeBj2Vcrma80gdJc5GQub/w2mwmr1w5UbwgzJkRasm0Ec/jxbcoA==", + "version": "1.0.69", + "resolved": "https://registry.npmjs.org/@github/copilot-win32-arm64/-/copilot-win32-arm64-1.0.69.tgz", + "integrity": "sha512-7DMoC1uwt01yZf48xq8MgKdnHacXabfsGsOizbgmrlZMfvQQofrF55x1Efu3BKsEKFGRl4fCLjJHJYCz/nWsHg==", "cpu": [ "arm64" ], @@ -643,9 +643,9 @@ } }, "node_modules/@github/copilot-win32-x64": { - "version": "1.0.67", - "resolved": "https://registry.npmjs.org/@github/copilot-win32-x64/-/copilot-win32-x64-1.0.67.tgz", - "integrity": "sha512-td5tQ/nve5dB7RPvNglBZwa/6DJqiOBgacXXa1GpYcohqpCzoI8gONNkeaeyr6oF4iu5wXJ9krUNr6QXL4yB5Q==", + "version": "1.0.69", + "resolved": "https://registry.npmjs.org/@github/copilot-win32-x64/-/copilot-win32-x64-1.0.69.tgz", + "integrity": "sha512-5uD1/k6oVzqRhiS9jLrySGa20HjItqUJsaMe+bV6eYT7zQPF/74xihEjxMm3/bJcATW2SesiYQxyNzTcKm3qRA==", "cpu": [ "x64" ], diff --git a/test/harness/package.json b/test/harness/package.json index 4e167fe7b0..51f925f246 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.67", + "@github/copilot": "^1.0.69", "@modelcontextprotocol/sdk": "^1.26.0", "@types/node": "^25.3.3", "@types/node-forge": "^1.3.14", diff --git a/test/snapshots/rpc_mcp_lifecycle/should_respond_to_mcp_oauth_request_without_pending_request.yaml b/test/snapshots/rpc_server_misc/should_get_set_and_clear_user_settings.yaml similarity index 100% rename from test/snapshots/rpc_mcp_lifecycle/should_respond_to_mcp_oauth_request_without_pending_request.yaml rename to test/snapshots/rpc_server_misc/should_get_set_and_clear_user_settings.yaml diff --git a/test/snapshots/rpc_server_misc/should_login_list_getcurrentauth_and_logout_account.yaml b/test/snapshots/rpc_server_misc/should_login_list_getcurrentauth_and_logout_account.yaml new file mode 100644 index 0000000000..056351ddb4 --- /dev/null +++ b/test/snapshots/rpc_server_misc/should_login_list_getcurrentauth_and_logout_account.yaml @@ -0,0 +1,3 @@ +models: + - claude-sonnet-4.5 +conversations: [] diff --git a/test/snapshots/rpc_session_state_extras/should_add_byok_provider_and_model_at_runtime.yaml b/test/snapshots/rpc_session_state_extras/should_add_byok_provider_and_model_at_runtime.yaml new file mode 100644 index 0000000000..056351ddb4 --- /dev/null +++ b/test/snapshots/rpc_session_state_extras/should_add_byok_provider_and_model_at_runtime.yaml @@ -0,0 +1,3 @@ +models: + - claude-sonnet-4.5 +conversations: [] diff --git a/test/snapshots/rpc_session_state_extras/should_get_context_attribution_and_heaviest_messages_after_turn.yaml b/test/snapshots/rpc_session_state_extras/should_get_context_attribution_and_heaviest_messages_after_turn.yaml new file mode 100644 index 0000000000..c4798dc83d --- /dev/null +++ b/test/snapshots/rpc_session_state_extras/should_get_context_attribution_and_heaviest_messages_after_turn.yaml @@ -0,0 +1,10 @@ +models: + - claude-sonnet-4.5 +conversations: + - messages: + - role: system + content: ${system} + - role: user + content: Say CONTEXT_METADATA_OK exactly. + - role: assistant + content: CONTEXT_METADATA_OK diff --git a/test/snapshots/rpc_session_state_extras/should_report_visibility_as_unsynced_for_local_session.yaml b/test/snapshots/rpc_session_state_extras/should_report_visibility_as_unsynced_for_local_session.yaml new file mode 100644 index 0000000000..056351ddb4 --- /dev/null +++ b/test/snapshots/rpc_session_state_extras/should_report_visibility_as_unsynced_for_local_session.yaml @@ -0,0 +1,3 @@ +models: + - claude-sonnet-4.5 +conversations: [] diff --git a/test/snapshots/rpc_session_state_extras/should_return_empty_completions_when_host_does_not_provide_them.yaml b/test/snapshots/rpc_session_state_extras/should_return_empty_completions_when_host_does_not_provide_them.yaml new file mode 100644 index 0000000000..056351ddb4 --- /dev/null +++ b/test/snapshots/rpc_session_state_extras/should_return_empty_completions_when_host_does_not_provide_them.yaml @@ -0,0 +1,3 @@ +models: + - claude-sonnet-4.5 +conversations: [] diff --git a/test/snapshots/rpc_session_state_extras/should_update_and_clear_live_subagent_settings.yaml b/test/snapshots/rpc_session_state_extras/should_update_and_clear_live_subagent_settings.yaml new file mode 100644 index 0000000000..056351ddb4 --- /dev/null +++ b/test/snapshots/rpc_session_state_extras/should_update_and_clear_live_subagent_settings.yaml @@ -0,0 +1,3 @@ +models: + - claude-sonnet-4.5 +conversations: [] diff --git a/test/snapshots/tools/ergonomic_tool_arity0.yaml b/test/snapshots/tools/ergonomic_tool_arity0.yaml new file mode 100644 index 0000000000..a55f486816 --- /dev/null +++ b/test/snapshots/tools/ergonomic_tool_arity0.yaml @@ -0,0 +1,21 @@ +models: + - claude-sonnet-4.5 +conversations: + - messages: + - role: system + content: ${system} + - role: user + content: Call get_status and tell me the result. + - role: assistant + content: I'll call get_status now. + tool_calls: + - id: toolcall_0 + type: function + function: + name: get_status + arguments: '{}' + - role: tool + tool_call_id: toolcall_0 + content: "Status: OK" + - role: assistant + content: "The status is: OK" diff --git a/test/snapshots/tools/ergonomic_tool_arity2.yaml b/test/snapshots/tools/ergonomic_tool_arity2.yaml new file mode 100644 index 0000000000..e34c695bd4 --- /dev/null +++ b/test/snapshots/tools/ergonomic_tool_arity2.yaml @@ -0,0 +1,21 @@ +models: + - claude-sonnet-4.5 +conversations: + - messages: + - role: system + content: ${system} + - role: user + content: Call combine_values with 'alpha' and 'beta', then report the combined result. + - role: assistant + content: I'll call combine_values with those arguments. + tool_calls: + - id: toolcall_0 + type: function + function: + name: combine_values + arguments: '{"value1":"alpha","value2":"beta"}' + - role: tool + tool_call_id: toolcall_0 + content: "combined: alpha + beta" + - role: assistant + content: "The combined result is: alpha + beta"