From cedd806e240e2bf5930d983e3fcd3e314b8ef5f7 Mon Sep 17 00:00:00 2001 From: Karl Krukow Date: Fri, 12 Jun 2026 13:24:49 +0200 Subject: [PATCH 1/7] chore(codegen): bump schema 1.0.57 -> 1.0.61, regenerate event specs Sync to upstream copilot-sdk v1.0.1. Pinned @github/copilot schema bumped from 1.0.57 to 1.0.61 (upstream PRs #1597, #1612). Regenerated generated/event_specs.clj from the new session-events schema. Schema diffs picked up: - New CanvasClosedEvent + CanvasClosedData ({canvasId, extensionId, instanceId}, ephemeral) - ResumeData and ShutdownData gain optional eventsFileSizeBytes - ScheduleCreatedData gains optional at, cron, tz; intervalMs becomes optional - AssistantMessageData gains optional apiCallId - HookEndError gains optional source - HookProgressData gains optional temporary Project version bumped to 1.0.1.0 to match upstream parity. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .copilot-schema-version | 2 +- README.md | 2 +- build.clj | 2 +- doc/getting-started.md | 2 +- schemas/README.md | 2 +- schemas/api.schema.json | 7090 +++++++++++++---- schemas/session-events.schema.json | 145 +- .../copilot_sdk/generated/event_specs.clj | 30 +- test/github/copilot_sdk/codegen_test.clj | 6 +- 9 files changed, 5681 insertions(+), 1600 deletions(-) diff --git a/.copilot-schema-version b/.copilot-schema-version index 9f1a864..9972f12 100644 --- a/.copilot-schema-version +++ b/.copilot-schema-version @@ -1 +1 @@ -1.0.57 +1.0.61 diff --git a/README.md b/README.md index ea26411..5926844 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ Add to your `deps.edn`: ```clojure ;; From Maven Central -io.github.copilot-community-sdk/copilot-sdk-clojure {:mvn/version "1.0.0.0"} +io.github.copilot-community-sdk/copilot-sdk-clojure {:mvn/version "1.0.1.0"} ;; Or git dependency io.github.copilot-community-sdk/copilot-sdk-clojure {:git/url "https://github.com/copilot-community-sdk/copilot-sdk-clojure.git" diff --git a/build.clj b/build.clj index bd29da6..e5297c3 100644 --- a/build.clj +++ b/build.clj @@ -7,7 +7,7 @@ (:import [java.io File])) (def lib 'io.github.copilot-community-sdk/copilot-sdk-clojure) -(def version "1.0.0.0") +(def version "1.0.1.0") (def class-dir "target/classes") (defn- try-sh diff --git a/doc/getting-started.md b/doc/getting-started.md index f13fe9b..eb3ace2 100644 --- a/doc/getting-started.md +++ b/doc/getting-started.md @@ -34,7 +34,7 @@ copilot --version Add to your `deps.edn`: ```clojure -{:deps {io.github.copilot-community-sdk/copilot-sdk-clojure {:mvn/version "1.0.0.0"}}} +{:deps {io.github.copilot-community-sdk/copilot-sdk-clojure {:mvn/version "1.0.1.0"}}} ``` Or use as a Git dependency: diff --git a/schemas/README.md b/schemas/README.md index 80979c8..ac2c67e 100644 --- a/schemas/README.md +++ b/schemas/README.md @@ -4,4 +4,4 @@ These files are fetched verbatim from the `@github/copilot` npm package at the v **Do not edit by hand.** To update, run `bb schemas:fetch` after bumping `.copilot-schema-version`. -Currently pinned version: `1.0.57` +Currently pinned version: `1.0.61` diff --git a/schemas/api.schema.json b/schemas/api.schema.json index bbd2e75..9758ba2 100644 --- a/schemas/api.schema.json +++ b/schemas/api.schema.json @@ -17,14 +17,16 @@ }, "connect": { "rpcMethod": "connect", - "description": "Performs the SDK server connection handshake and validates the optional connection token.", + "description": "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.", "params": { "$ref": "#/definitions/ConnectRequest", - "description": "Optional connection token presented by the SDK client during the handshake." + "description": "Optional connection token presented by the SDK client during the handshake.", + "visibility": "internal" }, "result": { "$ref": "#/definitions/ConnectResult", - "description": "Handshake result reporting the server's protocol version and package version on success." + "description": "Handshake result reporting the server's protocol version and package version on success.", + "visibility": "internal" }, "visibility": "internal" }, @@ -172,6 +174,154 @@ } } }, + "plugins": { + "list": { + "rpcMethod": "plugins.list", + "description": "Lists plugins installed in user/global state.", + "params": null, + "result": { + "$ref": "#/definitions/PluginListResult", + "description": "Plugins installed in user/global state." + }, + "stability": "experimental" + }, + "install": { + "rpcMethod": "plugins.install", + "description": "Installs a plugin from a marketplace, GitHub repo, URL, or local path.", + "params": { + "$ref": "#/definitions/PluginsInstallRequest", + "description": "Plugin source and optional working directory for relative-path resolution." + }, + "result": { + "$ref": "#/definitions/PluginInstallResult", + "description": "Result of installing a plugin." + }, + "stability": "experimental" + }, + "uninstall": { + "rpcMethod": "plugins.uninstall", + "description": "Uninstalls an installed plugin.", + "params": { + "$ref": "#/definitions/PluginsUninstallRequest", + "description": "Name (or spec) of the plugin to uninstall." + }, + "result": { + "type": "null" + }, + "stability": "experimental" + }, + "update": { + "rpcMethod": "plugins.update", + "description": "Updates an installed plugin to its latest published version.", + "params": { + "$ref": "#/definitions/PluginsUpdateRequest", + "description": "Name (or spec) of the plugin to update." + }, + "result": { + "$ref": "#/definitions/PluginUpdateResult", + "description": "Result of updating a single plugin." + }, + "stability": "experimental" + }, + "updateAll": { + "rpcMethod": "plugins.updateAll", + "description": "Updates every installed plugin to its latest published version.", + "params": null, + "result": { + "$ref": "#/definitions/PluginUpdateAllResult", + "description": "Result of updating all installed plugins." + }, + "stability": "experimental" + }, + "enable": { + "rpcMethod": "plugins.enable", + "description": "Enables installed plugins for new sessions.", + "params": { + "$ref": "#/definitions/PluginsEnableRequest", + "description": "Plugin names (or specs) to enable." + }, + "result": { + "type": "null" + }, + "stability": "experimental" + }, + "disable": { + "rpcMethod": "plugins.disable", + "description": "Disables installed plugins for new sessions.", + "params": { + "$ref": "#/definitions/PluginsDisableRequest", + "description": "Plugin names (or specs) to disable." + }, + "result": { + "type": "null" + }, + "stability": "experimental" + }, + "marketplaces": { + "list": { + "rpcMethod": "plugins.marketplaces.list", + "description": "Lists all registered marketplaces (defaults + user-added).", + "params": null, + "result": { + "$ref": "#/definitions/MarketplaceListResult", + "description": "All registered marketplaces, including built-in defaults." + }, + "stability": "experimental" + }, + "add": { + "rpcMethod": "plugins.marketplaces.add", + "description": "Registers a new marketplace from a source (owner/repo, URL, or local path).", + "params": { + "$ref": "#/definitions/PluginsMarketplacesAddRequest", + "description": "Marketplace source to register." + }, + "result": { + "$ref": "#/definitions/MarketplaceAddResult", + "description": "Result of registering a new marketplace." + }, + "stability": "experimental" + }, + "remove": { + "rpcMethod": "plugins.marketplaces.remove", + "description": "Removes a previously-registered marketplace. When the marketplace has dependent plugins and `force` is not set, the marketplace is left intact and the result lists the dependents so the caller can decide whether to retry with `force=true`.", + "params": { + "$ref": "#/definitions/PluginsMarketplacesRemoveRequest", + "description": "Name of the marketplace to remove and an optional force flag." + }, + "result": { + "$ref": "#/definitions/MarketplaceRemoveResult", + "description": "Outcome of the remove attempt, including dependent-plugin info when applicable." + }, + "stability": "experimental" + }, + "browse": { + "rpcMethod": "plugins.marketplaces.browse", + "description": "Lists plugins advertised by a registered marketplace.", + "params": { + "$ref": "#/definitions/PluginsMarketplacesBrowseRequest", + "description": "Name of the marketplace whose plugin catalog to fetch." + }, + "result": { + "$ref": "#/definitions/MarketplaceBrowseResult", + "description": "Plugins advertised by the marketplace." + }, + "stability": "experimental" + }, + "refresh": { + "rpcMethod": "plugins.marketplaces.refresh", + "description": "Re-fetches one or all registered marketplace catalogs.", + "params": { + "$ref": "#/definitions/PluginsMarketplacesRefreshRequest", + "description": "Optional marketplace name; omit to refresh all." + }, + "result": { + "$ref": "#/definitions/MarketplaceRefreshResult", + "description": "Result of refreshing one or more marketplace catalogs." + }, + "stability": "experimental" + } + } + }, "skills": { "config": { "setDisabledSkills": { @@ -199,6 +349,36 @@ } } }, + "agents": { + "discover": { + "rpcMethod": "agents.discover", + "description": "Discovers custom agents across user, project, plugin, and remote sources.", + "params": { + "$ref": "#/definitions/AgentsDiscoverRequest", + "description": "Optional project paths to include in agent discovery." + }, + "result": { + "$ref": "#/definitions/ServerAgentList", + "description": "Agents discovered across user, project, plugin, and remote sources." + }, + "stability": "experimental" + } + }, + "instructions": { + "discover": { + "rpcMethod": "instructions.discover", + "description": "Discovers instruction sources across user, repository, and plugin sources.", + "params": { + "$ref": "#/definitions/InstructionsDiscoverRequest", + "description": "Optional project paths to include in instruction discovery." + }, + "result": { + "$ref": "#/definitions/ServerInstructionSourceList", + "description": "Instruction sources discovered across user, repository, and plugin sources." + }, + "stability": "experimental" + } + }, "user": { "settings": { "reload": { @@ -236,6 +416,19 @@ } }, "sessions": { + "open": { + "rpcMethod": "sessions.open", + "description": "Creates or resumes a local session and returns the opened session ID.", + "params": { + "$ref": "#/definitions/SessionOpenParams", + "description": "Open a session by creating, resuming, attaching, connecting to a remote, or handing off." + }, + "result": { + "$ref": "#/definitions/SessionOpenResult", + "description": "Result of opening a session." + }, + "stability": "experimental" + }, "fork": { "rpcMethod": "sessions.fork", "description": "Creates a new session by forking persisted history from an existing session.", @@ -264,14 +457,14 @@ }, "list": { "rpcMethod": "sessions.list", - "description": "Lists persisted sessions, optionally filtered by working-directory context.", + "description": "Lists sessions, optionally filtered by source and working-directory context. Returned entries are discriminated by `isRemote`: local entries carry only the lightweight `LocalSessionMetadataValue` shape; remote entries carry the full `RemoteSessionMetadataValue` shape (repository, PR number, taskType, etc.).", "params": { "$ref": "#/definitions/SessionsListRequest", - "description": "Optional metadata-load limit and filters applied to the returned sessions." + "description": "Optional source filter, metadata-load limit, and context filter applied to the returned sessions." }, "result": { "$ref": "#/definitions/SessionList", - "description": "Persisted sessions matching the filter, ordered most-recently-modified first." + "description": "Sessions matching the filter, ordered most-recently-modified first." }, "stability": "experimental" }, @@ -316,7 +509,7 @@ }, "getEventFilePath": { "rpcMethod": "sessions.getEventFilePath", - "description": "Computes the absolute path to a session's persisted events.jsonl file.", + "description": "Computes the absolute path to a session's persisted events.jsonl file. Internal: filesystem paths are only meaningful in-process (CLI and runtime share a filesystem). Currently used by the CLI's contribution-graph feature to read historical events directly. Remote SDK consumers must not depend on this; a proper event-query API would replace it if the contribution graph ever needed to work over the wire.", "params": { "$ref": "#/definitions/SessionsGetEventFilePathRequest", "description": "Session ID whose event-log file path to compute." @@ -325,7 +518,8 @@ "$ref": "#/definitions/SessionsGetEventFilePathResult", "description": "Absolute path to the session's events.jsonl file on disk." }, - "stability": "experimental" + "stability": "experimental", + "visibility": "internal" }, "getSizes": { "rpcMethod": "sessions.getSizes", @@ -352,7 +546,7 @@ }, "getPersistedRemoteSteerable": { "rpcMethod": "sessions.getPersistedRemoteSteerable", - "description": "Returns a session's persisted remote-steerable flag, if any has been recorded.", + "description": "Returns a session's persisted remote-steerable flag, if any has been recorded. Internal: this is CLI-specific book-keeping used by `--continue` / `--resume` to inherit the prior session's remote-steerable preference. SDK consumers that want similar behavior should manage their own persistence around start/stop calls rather than relying on this runtime-side flag.", "params": { "$ref": "#/definitions/SessionsGetPersistedRemoteSteerableRequest", "description": "Session ID to look up the persisted remote-steerable flag for." @@ -361,7 +555,8 @@ "$ref": "#/definitions/SessionsGetPersistedRemoteSteerableResult", "description": "The session's persisted remote-steerable flag, or omitted when no value has been persisted." }, - "stability": "experimental" + "stability": "experimental", + "visibility": "internal" }, "close": { "rpcMethod": "sessions.close", @@ -479,6 +674,126 @@ "description": "Replace the manager-wide additional plugins. New session creations and subsequent hook reloads see the new set; already-running sessions keep their existing hook installation until the next reload." }, "stability": "experimental" + }, + "getBoardEntryCount": { + "rpcMethod": "sessions.getBoardEntryCount", + "description": "Gets the dynamic-context board entry count associated with a session, when available. Internal: this exists solely so CLI telemetry events (`rem_spawn_gate`, `rem_consolidation_complete`) can pair START / END board counts around the detached rem-agent spawn. \"Dynamic context board\" is a runtime-internal concept that is not part of the public SDK contract; the long-term plan is to relocate the telemetry emission into the runtime so this method can be deleted entirely.", + "params": { + "$ref": "#/definitions/SessionsGetBoardEntryCountRequest", + "description": "Session ID whose board entry count should be returned." + }, + "result": { + "$ref": "#/definitions/SessionsGetBoardEntryCountResult", + "description": "Dynamic-context board entry count, when available." + }, + "stability": "experimental", + "visibility": "internal" + }, + "startRemoteControl": { + "rpcMethod": "sessions.startRemoteControl", + "description": "Attaches the runtime-managed remote-control singleton to a session, awaiting initial setup. If remote control is already attached to a different session, the singleton is transferred (preserving the underlying Mission Control connection). Returns the final status.", + "params": { + "$ref": "#/definitions/SessionsStartRemoteControlRequest", + "description": "Parameters for attaching the remote-control singleton to a session." + }, + "result": { + "$ref": "#/definitions/RemoteControlStatusResult", + "description": "Wrapper for the singleton's current status." + }, + "stability": "experimental" + }, + "transferRemoteControl": { + "rpcMethod": "sessions.transferRemoteControl", + "description": "Atomically rebinds the remote-control singleton to a different session, preserving the underlying Mission Control connection. When `expectedFromSessionId` is provided and does not match the singleton's current `attachedSessionId`, the transfer is rejected with `transferred: false` and the current status is returned unchanged.", + "params": { + "$ref": "#/definitions/SessionsTransferRemoteControlRequest", + "description": "Parameters for atomically rebinding the remote-control singleton." + }, + "result": { + "$ref": "#/definitions/RemoteControlTransferResult", + "description": "Outcome of a transferRemoteControl call." + }, + "stability": "experimental" + }, + "setRemoteControlSteering": { + "rpcMethod": "sessions.setRemoteControlSteering", + "description": "Patches the steering state of the active remote-control singleton. When remote control is off, this is a no-op and the off status is returned. Today only `enabled: true` is actionable on the underlying exporter; passing `false` is reserved for future use.", + "params": { + "$ref": "#/definitions/SessionsSetRemoteControlSteeringRequest", + "description": "Patch for the singleton's steering state." + }, + "result": { + "$ref": "#/definitions/RemoteControlStatusResult", + "description": "Wrapper for the singleton's current status." + }, + "stability": "experimental" + }, + "stopRemoteControl": { + "rpcMethod": "sessions.stopRemoteControl", + "description": "Stops the remote-control singleton. When `expectedSessionId` is provided and does not match the singleton's current `attachedSessionId`, the stop is rejected with `stopped: false` and the current status is returned unchanged (unless `force` is set, in which case the singleton is unconditionally torn down).", + "params": { + "$ref": "#/definitions/SessionsStopRemoteControlRequest", + "description": "Parameters for stopping the remote-control singleton." + }, + "result": { + "$ref": "#/definitions/RemoteControlStopResult", + "description": "Outcome of a stopRemoteControl call." + }, + "stability": "experimental" + }, + "getRemoteControlStatus": { + "rpcMethod": "sessions.getRemoteControlStatus", + "description": "Returns the current state of the remote-control singleton, including the attached session id and frontend URL when active.", + "params": null, + "result": { + "$ref": "#/definitions/RemoteControlStatusResult", + "description": "Wrapper for the singleton's current status." + }, + "stability": "experimental" + }, + "pollSpawnedSessions": { + "rpcMethod": "sessions.pollSpawnedSessions", + "description": "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.", + "params": { + "$ref": "#/definitions/SessionsPollSpawnedSessionsRequest", + "description": "Cursor and optional long-poll wait for polling runtime-spawned sessions." + }, + "result": { + "$ref": "#/definitions/PollSpawnedSessionsResult", + "description": "Batch of spawn events plus a cursor for follow-up polls." + }, + "stability": "experimental", + "visibility": "internal" + }, + "registerExtensionToolsOnSession": { + "rpcMethod": "sessions.registerExtensionToolsOnSession", + "description": "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.", + "params": { + "$ref": "#/definitions/RegisterExtensionToolsParams", + "description": "Params to attach an extension loader's tools to a session.", + "visibility": "internal" + }, + "result": { + "$ref": "#/definitions/RegisterExtensionToolsResult", + "description": "Handle for releasing the extension tool registration.", + "visibility": "internal" + }, + "stability": "experimental", + "visibility": "internal" + }, + "configureSessionExtensions": { + "rpcMethod": "sessions.configureSessionExtensions", + "description": "Attaches (or detaches) an in-process ExtensionController delegate for the given session, used by shared-API surfaces that need to query or modify the session's extension state. Pass `controller: undefined` to detach. Marked internal because the controller is an in-process object that cannot cross the JSON-RPC boundary. Disappears alongside `registerExtensionToolsOnSession`: once the runtime owns extension management, the public surface exposes list/enable/disable/reload as dedicated RPCs served by the runtime.", + "params": { + "$ref": "#/definitions/ConfigureSessionExtensionsParams", + "description": "Params to attach or detach an in-process ExtensionController delegate.", + "visibility": "internal" + }, + "result": { + "type": "null" + }, + "stability": "experimental", + "visibility": "internal" } }, "agentRegistry": { @@ -563,9 +878,10 @@ "description": "If set, the request will fail if the named tool is not available when this message is among the user messages at the start of the current exchange" }, "source": { - "description": "Optional provenance tag copied to the resulting user.message event. Supported values are `system`, `command-*`, and `schedule-*`.", - "visibility": "internal", - "x-opaque-json": true + "type": "string", + "pattern": "^(system|command-.*|schedule-\\d+)$", + "description": "Optional provenance tag copied to the resulting user.message event. Must match one of three forms: the literal `system`, `command-` for messages originating from a command (e.g. slash command, Mission Control command), or `schedule-` for messages originating from a scheduled job.", + "visibility": "internal" }, "agentMode": { "$ref": "#/definitions/SendAgentMode", @@ -1221,6 +1537,29 @@ "type": "null" }, "stability": "experimental" + }, + "readSqlTodos": { + "rpcMethod": "session.plan.readSqlTodos", + "description": "Reads todo rows from the session SQL database for plan rendering.", + "params": { + "type": "object", + "description": "Identifies the target session.", + "properties": { + "sessionId": { + "type": "string", + "description": "Target session identifier" + } + }, + "required": [ + "sessionId" + ], + "additionalProperties": false + }, + "result": { + "$ref": "#/definitions/PlanReadSqlTodosResult", + "description": "Todo rows read from the session SQL database. Empty when no session database is available." + }, + "stability": "experimental" } }, "workspaces": { @@ -2091,7 +2430,7 @@ "mcp": { "list": { "rpcMethod": "session.mcp.list", - "description": "Lists MCP servers configured for the session and their connection status.", + "description": "Lists MCP servers configured for the session, their connection status, and host-level state. The host-level state (disabled/filtered servers, failed/needs-auth/pending connections, mcp3p policy, full config) is empty/zero when no MCP host has been initialized for the session.", "params": { "type": "object", "description": "Identifies the target session.", @@ -2108,7 +2447,38 @@ }, "result": { "$ref": "#/definitions/McpServerList", - "description": "MCP servers configured for the session, with their connection status." + "description": "MCP servers configured for the session, with their connection status and host-level state." + }, + "stability": "experimental" + }, + "listTools": { + "rpcMethod": "session.mcp.listTools", + "description": "Lists the tools exposed by a connected MCP server on this session's host.", + "params": { + "type": "object", + "properties": { + "sessionId": { + "type": "string", + "description": "Target session identifier" + }, + "serverName": { + "type": "string", + "minLength": 1, + "pattern": "^[^\\x00-\\x1f/\\x7f-\\x9f}]+(?:\\/[^\\x00-\\x1f/\\x7f-\\x9f}]+)*$", + "description": "Name of the connected MCP server whose tools to list." + } + }, + "required": [ + "sessionId", + "serverName" + ], + "additionalProperties": false, + "description": "Server name whose tool list should be returned.", + "title": "McpListToolsRequest" + }, + "result": { + "$ref": "#/definitions/McpListToolsResult", + "description": "Tools exposed by the connected MCP server. Throws when the server is not connected." }, "stability": "experimental" }, @@ -2194,6 +2564,38 @@ }, "stability": "experimental" }, + "reloadWithConfig": { + "rpcMethod": "session.mcp.reloadWithConfig", + "description": "Reloads MCP server connections for the session with an explicit host-provided configuration.", + "params": { + "type": "object", + "properties": { + "sessionId": { + "type": "string", + "description": "Target session identifier" + }, + "config": { + "description": "Opaque runtime MCP reload configuration. Marked internal: an in-process runtime shape (reloadMcpServers throws over the wire).", + "visibility": "internal", + "x-opaque-json": true + } + }, + "required": [ + "sessionId", + "config" + ], + "additionalProperties": false, + "description": "Opaque MCP reload configuration.", + "title": "McpReloadWithConfigRequest", + "visibility": "internal" + }, + "result": { + "$ref": "#/definitions/McpStartServersResult", + "description": "MCP server startup filtering result." + }, + "stability": "experimental", + "visibility": "internal" + }, "executeSampling": { "rpcMethod": "session.mcp.executeSampling", "description": "Runs an MCP sampling inference on behalf of an MCP server.", @@ -2323,39 +2725,314 @@ }, "stability": "experimental" }, - "oauth": { - "login": { - "rpcMethod": "session.mcp.oauth.login", - "description": "Starts OAuth authentication for a remote MCP server.", - "params": { - "type": "object", - "properties": { - "sessionId": { - "type": "string", - "description": "Target session identifier" - }, - "serverName": { - "type": "string", - "minLength": 1, - "pattern": "^[^\\x00-\\x1f/\\x7f-\\x9f}]+(?:\\/[^\\x00-\\x1f/\\x7f-\\x9f}]+)*$", - "description": "Name of the remote MCP server to authenticate" - }, - "forceReauth": { - "type": "boolean", - "description": "When true, clears any cached OAuth token for the server and runs a full new authorization. Use when the user explicitly wants to switch accounts or believes their session is stuck." - }, - "clientName": { - "type": "string", - "description": "Optional override for the OAuth client display name shown on the consent screen. Applies to newly registered dynamic clients only — existing registrations keep the name they were created with. When omitted, the runtime applies a neutral fallback; callers driving interactive auth should pass their own surface-specific label so the consent screen matches the product the user sees." - }, - "callbackSuccessMessage": { - "type": "string", - "description": "Optional override for the body text shown on the OAuth loopback callback success page. When omitted, the runtime applies a neutral fallback; callers driving interactive auth should pass surface-specific copy telling the user where to return." - } - }, - "required": [ - "sessionId", - "serverName" + "configureGitHub": { + "rpcMethod": "session.mcp.configureGitHub", + "description": "Configures the built-in GitHub MCP server for the session's current auth context.", + "params": { + "type": "object", + "properties": { + "sessionId": { + "type": "string", + "description": "Target session identifier" + }, + "authInfo": { + "description": "Opaque runtime auth info for GitHub MCP configuration. Marked internal: an in-process runtime shape (configureGitHubMcp is a no-op over the wire).", + "visibility": "internal", + "x-opaque-json": true + } + }, + "required": [ + "sessionId", + "authInfo" + ], + "additionalProperties": false, + "description": "Opaque auth info used to configure GitHub MCP.", + "title": "McpConfigureGitHubRequest", + "visibility": "internal" + }, + "result": { + "$ref": "#/definitions/McpConfigureGitHubResult", + "description": "Result of configuring GitHub MCP." + }, + "stability": "experimental", + "visibility": "internal" + }, + "startServer": { + "rpcMethod": "session.mcp.startServer", + "description": "Starts an individual MCP server on the session's host.", + "params": { + "type": "object", + "properties": { + "sessionId": { + "type": "string", + "description": "Target session identifier" + }, + "serverName": { + "type": "string", + "description": "Name of the MCP server to start" + }, + "config": { + "description": "Opaque server configuration (MCPServerConfig). Marked internal: an in-process runtime shape supplied only by in-process CLI callers.", + "visibility": "internal", + "x-opaque-json": true + } + }, + "required": [ + "sessionId", + "serverName", + "config" + ], + "additionalProperties": false, + "description": "Server name and opaque configuration for an individual MCP server start.", + "title": "McpStartServerRequest", + "visibility": "internal" + }, + "result": { + "type": "null" + }, + "stability": "experimental", + "visibility": "internal" + }, + "restartServer": { + "rpcMethod": "session.mcp.restartServer", + "description": "Restarts an individual MCP server on the session's host (stops then starts).", + "params": { + "type": "object", + "properties": { + "sessionId": { + "type": "string", + "description": "Target session identifier" + }, + "serverName": { + "type": "string", + "description": "Name of the MCP server to restart" + }, + "config": { + "description": "Opaque server configuration (MCPServerConfig). Marked internal: an in-process runtime shape supplied only by in-process CLI callers.", + "visibility": "internal", + "x-opaque-json": true + } + }, + "required": [ + "sessionId", + "serverName", + "config" + ], + "additionalProperties": false, + "description": "Server name and opaque configuration for an individual MCP server restart.", + "title": "McpRestartServerRequest", + "visibility": "internal" + }, + "result": { + "type": "null" + }, + "stability": "experimental", + "visibility": "internal" + }, + "stopServer": { + "rpcMethod": "session.mcp.stopServer", + "description": "Stops an individual MCP server on the session's host.", + "params": { + "type": "object", + "properties": { + "sessionId": { + "type": "string", + "description": "Target session identifier" + }, + "serverName": { + "type": "string", + "description": "Name of the MCP server to stop" + } + }, + "required": [ + "sessionId", + "serverName" + ], + "additionalProperties": false, + "description": "Server name for an individual MCP server stop.", + "title": "McpStopServerRequest" + }, + "result": { + "type": "null" + }, + "stability": "experimental" + }, + "registerExternalClient": { + "rpcMethod": "session.mcp.registerExternalClient", + "description": "Registers a pre-connected external MCP client (e.g. IDE) on the session's host. The caller retains lifecycle ownership of the client and transport. Marked internal because the `client` and `transport` arguments are in-process MCP SDK instances that cannot be serialized across the JSON-RPC boundary; once the CLI moves on top of the SDK, external clients will be expressed as transport configs the runtime can construct itself.", + "params": { + "type": "object", + "properties": { + "sessionId": { + "type": "string", + "description": "Target session identifier" + }, + "serverName": { + "type": "string", + "description": "Logical server name for the external client" + }, + "client": { + "description": "In-process MCP Client instance. Marked internal: cannot be serialized across the JSON-RPC boundary.", + "visibility": "internal", + "x-opaque-json": true + }, + "transport": { + "description": "In-process MCP Transport instance. Marked internal: cannot be serialized across the JSON-RPC boundary.", + "visibility": "internal", + "x-opaque-json": true + }, + "config": { + "description": "In-process server config (MCPServerConfig) paired with the in-process client/transport. Marked internal alongside its companions.", + "visibility": "internal", + "x-opaque-json": true + } + }, + "required": [ + "sessionId", + "serverName", + "client", + "transport", + "config" + ], + "additionalProperties": false, + "description": "Registration parameters for an external MCP client.", + "title": "McpRegisterExternalClientRequest", + "visibility": "internal" + }, + "result": { + "type": "null" + }, + "stability": "experimental", + "visibility": "internal" + }, + "unregisterExternalClient": { + "rpcMethod": "session.mcp.unregisterExternalClient", + "description": "Unregisters a previously registered external MCP client by server name. Marked internal as the paired companion of `registerExternalClient`: only in-process callers that registered a client this way can meaningfully unregister it. Disappears alongside `registerExternalClient`: once external clients are described to the runtime as config rather than handed in as instances, lifecycle (including deregistration) is owned entirely by the runtime.", + "params": { + "type": "object", + "properties": { + "sessionId": { + "type": "string", + "description": "Target session identifier" + }, + "serverName": { + "type": "string", + "description": "Server name of the external client to unregister" + } + }, + "required": [ + "sessionId", + "serverName" + ], + "additionalProperties": false, + "description": "Server name identifying the external client to remove.", + "title": "McpUnregisterExternalClientRequest", + "visibility": "internal" + }, + "result": { + "type": "null" + }, + "stability": "experimental", + "visibility": "internal" + }, + "isServerRunning": { + "rpcMethod": "session.mcp.isServerRunning", + "description": "Checks whether a named MCP server is currently running on the session's host.", + "params": { + "type": "object", + "properties": { + "sessionId": { + "type": "string", + "description": "Target session identifier" + }, + "serverName": { + "type": "string", + "description": "Name of the MCP server to check" + } + }, + "required": [ + "sessionId", + "serverName" + ], + "additionalProperties": false, + "description": "Server name to check running status for.", + "title": "McpIsServerRunningRequest" + }, + "result": { + "$ref": "#/definitions/McpIsServerRunningResult", + "description": "Whether the named MCP server is running." + }, + "stability": "experimental" + }, + "oauth": { + "respond": { + "rpcMethod": "session.mcp.oauth.respond", + "description": "Responds to a pending MCP OAuth provider request. Marked internal because the `provider` argument is an in-process OAuthClientProvider instance that cannot be carried over the wire; the public OAuth surface will route the response through a wire-clean handshake once the CLI moves on top of the SDK.", + "params": { + "type": "object", + "properties": { + "sessionId": { + "type": "string", + "description": "Target session identifier" + }, + "requestId": { + "type": "string", + "description": "OAuth request identifier from mcp.oauth_required" + }, + "provider": { + "description": "In-process OAuthClientProvider instance, or omitted to deny. Marked internal: cannot be serialized across the JSON-RPC boundary.", + "visibility": "internal", + "x-opaque-json": true + } + }, + "required": [ + "sessionId", + "requestId" + ], + "additionalProperties": false, + "description": "MCP OAuth request id and optional provider response.", + "title": "McpOauthRespondRequest", + "visibility": "internal" + }, + "result": { + "$ref": "#/definitions/McpOauthRespondResult", + "description": "Empty result after recording the MCP OAuth response." + }, + "stability": "experimental", + "visibility": "internal" + }, + "login": { + "rpcMethod": "session.mcp.oauth.login", + "description": "Starts OAuth authentication for a remote MCP server.", + "params": { + "type": "object", + "properties": { + "sessionId": { + "type": "string", + "description": "Target session identifier" + }, + "serverName": { + "type": "string", + "minLength": 1, + "pattern": "^[^\\x00-\\x1f/\\x7f-\\x9f}]+(?:\\/[^\\x00-\\x1f/\\x7f-\\x9f}]+)*$", + "description": "Name of the remote MCP server to authenticate" + }, + "forceReauth": { + "type": "boolean", + "description": "When true, clears any cached OAuth token for the server and runs a full new authorization. Use when the user explicitly wants to switch accounts or believes their session is stuck." + }, + "clientName": { + "type": "string", + "description": "Optional override for the OAuth client display name shown on the consent screen. Applies to newly registered dynamic clients only — existing registrations keep the name they were created with. When omitted, the runtime applies a neutral fallback; callers driving interactive auth should pass their own surface-specific label so the consent screen matches the product the user sees." + }, + "callbackSuccessMessage": { + "type": "string", + "description": "Optional override for the body text shown on the OAuth loopback callback success page. When omitted, the runtime applies a neutral fallback; callers driving interactive auth should pass surface-specific copy telling the user where to return." + } + }, + "required": [ + "sessionId", + "serverName" ], "additionalProperties": false, "description": "Remote MCP server name and optional overrides controlling reauthentication, OAuth client display name, and the callback success-page copy.", @@ -2603,6 +3280,54 @@ "description": "Plugins installed for the session, with their enabled state and version metadata." }, "stability": "experimental" + }, + "reload": { + "rpcMethod": "session.plugins.reload", + "description": "Reloads the session's plugin set, refreshing MCP servers, custom agents, hooks, and skills cache so SDK-driven changes via `server.plugins.*` take effect immediately.", + "params": { + "anyOf": [ + { + "not": {} + }, + { + "type": "object", + "properties": { + "reloadMcp": { + "type": "boolean", + "description": "Reload MCP server connections after refreshing plugins. Defaults to true." + }, + "reloadCustomAgents": { + "type": "boolean", + "description": "Re-run custom-agent discovery after refreshing plugins. Defaults to true." + }, + "reloadHooks": { + "type": "boolean", + "description": "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)." + }, + "deferRepoHooks": { + "type": "boolean", + "description": "When true, skip repo-level hooks during the hook reload. Use before folder trust is confirmed; load them post-trust via `sessions.loadDeferredRepoHooks`." + } + }, + "additionalProperties": false + } + ], + "description": "Optional flags controlling which side effects the reload performs.", + "title": "PluginsReloadRequest", + "properties": { + "sessionId": { + "type": "string", + "description": "Target session identifier" + } + }, + "required": [ + "sessionId" + ] + }, + "result": { + "type": "null" + }, + "stability": "experimental" } }, "options": { @@ -2620,10 +3345,18 @@ "type": "string", "description": "The model ID to use for assistant turns." }, + "modelCapabilitiesOverrides": { + "$ref": "#/definitions/ModelCapabilitiesOverride", + "description": "Per-property model capability overrides for the selected model." + }, "reasoningEffort": { "type": "string", "description": "Reasoning effort for the selected model (model-defined enum)." }, + "reasoningSummary": { + "$ref": "#/definitions/OptionsUpdateReasoningSummary", + "description": "Reasoning summary mode for supported model clients." + }, "clientName": { "type": "string", "description": "Identifier of the client driving the session." @@ -2648,9 +3381,8 @@ "description": "Whether experimental capabilities are enabled." }, "provider": { - "description": "Custom model-provider configuration (BYOK). Opaque shape; see `ProviderConfig` in the runtime.", - "x-opaque-json": true, - "stability": "experimental" + "$ref": "#/definitions/ProviderConfig", + "description": "Custom model-provider configuration (BYOK)." }, "workingDirectory": { "type": "string", @@ -2690,9 +3422,8 @@ "description": "Per-shell process flags (e.g., `pwsh` arguments)." }, "sandboxConfig": { - "description": "Sandbox configuration shape; opaque to SDK consumers. See `SandboxConfig` in the runtime.", - "x-opaque-json": true, - "stability": "experimental" + "$ref": "#/definitions/SandboxConfig", + "description": "Resolved sandbox configuration." }, "logInteractiveShells": { "type": "boolean", @@ -2789,15 +3520,23 @@ "additionalContentExclusionPolicies": { "type": "array", "items": { - "x-opaque-json": true + "$ref": "#/definitions/OptionsUpdateAdditionalContentExclusionPolicy" }, - "description": "Additional content-exclusion policies to merge into the session's policy set. Opaque shape; see `ContentExclusionApiResponse` in the runtime.", + "description": "Additional content-exclusion policies to merge into the session's policy set.", "stability": "experimental" }, "manageScheduleEnabled": { "type": "boolean", "description": "Whether to expose the `manage_schedule` tool to the agent. The runtime always owns the per-session schedule registry; this flag only controls tool exposure (typically gated to staff users)." }, + "sessionCapabilities": { + "type": "array", + "items": { + "$ref": "#/definitions/SessionCapability", + "description": "Session capability id" + }, + "description": "Replaces the session's capability set with the given list. Use to enable or disable capabilities mid-session (e.g., remove `memory` for reproducible scripted runs). Omit the field to leave the existing capability set unchanged." + }, "skipEmbeddingRetrieval": { "type": "boolean", "description": "Whether to skip embedding retrieval pipeline initialization and execution." @@ -2821,6 +3560,10 @@ "enableSkills": { "type": "boolean", "description": "Whether to enable skill directory scanning and loading. Falls back to enableConfigDiscovery when unset." + }, + "contextTier": { + "$ref": "#/definitions/OptionsUpdateContextTier", + "description": "Context tier for models with tiered pricing. The session uses this to derive effective `modelCapabilitiesOverrides` so compaction, truncation, token display, and request limits honor the selected tier." } }, "additionalProperties": false, @@ -3308,6 +4051,29 @@ } }, "telemetry": { + "getEngagementId": { + "rpcMethod": "session.telemetry.getEngagementId", + "description": "Gets the telemetry engagement ID currently associated with the session, when available.", + "params": { + "type": "object", + "description": "Identifies the target session.", + "properties": { + "sessionId": { + "type": "string", + "description": "Target session identifier" + } + }, + "required": [ + "sessionId" + ], + "additionalProperties": false + }, + "result": { + "$ref": "#/definitions/SessionTelemetryEngagement", + "description": "Telemetry engagement ID for the session, when available." + }, + "stability": "experimental" + }, "setFeatureOverrides": { "rpcMethod": "session.telemetry.setFeatureOverrides", "description": "Sets feature override key/value pairs to attach to subsequent telemetry events for the session.", @@ -3341,6 +4107,45 @@ } }, "ui": { + "ephemeralQuery": { + "rpcMethod": "session.ui.ephemeralQuery", + "description": "Runs a transient no-tools model query against the current conversation context.", + "params": { + "type": "object", + "properties": { + "sessionId": { + "type": "string", + "description": "Target session identifier" + }, + "question": { + "type": "string", + "description": "Question to answer from the current conversation context." + }, + "onChunk": { + "description": "In-process streaming callback `(text) => void` invoked with each token as the model emits it. Marked internal: excluded from the public SDK surface. In a process-separated SDK this is replaced by a streaming RPC that yields chunks and a final answer.", + "visibility": "internal", + "x-opaque-json": true + }, + "abortSignal": { + "description": "In-process `AbortSignal` forwarded to the model client to cancel an in-flight request. Marked internal: excluded from the public SDK surface. Replaced by an explicit cancellation token + cancel RPC in the SDK migration.", + "visibility": "internal", + "x-opaque-json": true + } + }, + "required": [ + "sessionId", + "question" + ], + "additionalProperties": false, + "description": "Transient question to answer without adding it to conversation history.", + "title": "UIEphemeralQueryRequest" + }, + "result": { + "$ref": "#/definitions/UIEphemeralQueryResult", + "description": "Transient answer generated from current conversation context." + }, + "stability": "experimental" + }, "elicitation": { "rpcMethod": "session.ui.elicitation", "description": "Requests structured input from a UI-capable client.", @@ -4351,6 +5156,29 @@ }, "stability": "experimental" }, + "activity": { + "rpcMethod": "session.metadata.activity", + "description": "Returns a snapshot of activity flags for the session.", + "params": { + "type": "object", + "description": "Identifies the target session.", + "properties": { + "sessionId": { + "type": "string", + "description": "Target session identifier" + } + }, + "required": [ + "sessionId" + ], + "additionalProperties": false + }, + "result": { + "$ref": "#/definitions/SessionActivity", + "description": "Current activity flags for the session." + }, + "stability": "experimental" + }, "contextInfo": { "rpcMethod": "session.metadata.contextInfo", "description": "Returns the token breakdown for the session's current context window for a given model.", @@ -4551,6 +5379,69 @@ "description": "Indicates whether the signal was delivered; false if the process was unknown or already exited." }, "stability": "experimental" + }, + "executeUserRequested": { + "rpcMethod": "session.shell.executeUserRequested", + "description": "Executes a user-requested shell command through the session runtime.", + "params": { + "type": "object", + "properties": { + "sessionId": { + "type": "string", + "description": "Target session identifier" + }, + "requestId": { + "type": "string", + "description": "Caller-provided cancellation handle for this execution" + }, + "command": { + "type": "string", + "description": "Shell command to execute" + } + }, + "required": [ + "sessionId", + "requestId", + "command" + ], + "additionalProperties": false, + "description": "User-requested shell command and cancellation handle.", + "title": "ShellExecuteUserRequestedRequest" + }, + "result": { + "$ref": "#/definitions/UserRequestedShellCommandResult", + "description": "Result of a user-requested shell command." + }, + "stability": "experimental" + }, + "cancelUserRequested": { + "rpcMethod": "session.shell.cancelUserRequested", + "description": "Cancels a user-requested shell command by request ID.", + "params": { + "type": "object", + "properties": { + "sessionId": { + "type": "string", + "description": "Target session identifier" + }, + "requestId": { + "type": "string", + "description": "Request ID previously passed to executeUserRequested" + } + }, + "required": [ + "sessionId", + "requestId" + ], + "additionalProperties": false, + "description": "User-requested shell execution cancellation handle.", + "title": "ShellCancelUserRequestedRequest" + }, + "result": { + "$ref": "#/definitions/CancelUserRequestedShellCommandResult", + "description": "Cancellation result for a user-requested shell command." + }, + "stability": "experimental" } }, "history": { @@ -6356,6 +7247,25 @@ "description": "Custom agents available to the session after reloading definitions from disk.", "title": "AgentReloadResult" }, + "AgentsDiscoverRequest": { + "type": "object", + "properties": { + "projectPaths": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Optional list of project directory paths to scan for project-scoped agents. When omitted or empty, only user/plugin/remote-independent agents are returned (no project scan)." + }, + "excludeHostAgents": { + "type": "boolean", + "description": "When true, omit the host's agents (the `/agents` directory and all plugin agents), leaving only project and remote agents. For multitenant deployments." + } + }, + "additionalProperties": false, + "description": "Optional project paths to include in agent discovery.", + "title": "AgentsDiscoverRequest" + }, "AgentSelectRequest": { "type": "object", "properties": { @@ -6816,7 +7726,7 @@ "$ref": "#/definitions/ApiKeyAuthInfo" } ], - "description": "The new auth credentials to install on the session. When omitted or `undefined`, the call is a no-op and the session's existing credentials are preserved. The runtime stores the value verbatim and uses it for outbound model/API requests; it does NOT re-validate or re-fetch the associated Copilot user response. Several variants carry secret material; treat this method's params as containing secrets at rest and in transit.", + "description": "Initial authentication info for the session.", "title": "AuthInfo" }, "AuthInfoType": { @@ -6842,6 +7752,21 @@ "copilot-api-token": "Authentication from a Copilot API token." } }, + "CancelUserRequestedShellCommandResult": { + "type": "object", + "properties": { + "cancelled": { + "type": "boolean", + "description": "Whether an in-flight execution was found and signalled to cancel" + } + }, + "required": [ + "cancelled" + ], + "additionalProperties": false, + "description": "Cancellation result for a user-requested shell command.", + "title": "CancelUserRequestedShellCommandResult" + }, "CanvasAction": { "type": "object", "properties": { @@ -7301,6 +8226,27 @@ "description": "Indicates whether the queued-command response was matched to a pending request.", "title": "CommandsRespondToQueuedCommandResult" }, + "ConfigureSessionExtensionsParams": { + "type": "object", + "properties": { + "sessionId": { + "type": "string", + "description": "Session to attach the extension controller delegate to." + }, + "controller": { + "description": "In-process ExtensionController delegate (CLI-only optimization). Marked internal: this field is excluded from the public SDK surface. The post-SDK extension surface exposes list/enable/disable/reload via dedicated RPCs served by the runtime.", + "visibility": "internal", + "x-opaque-json": true + } + }, + "required": [ + "sessionId" + ], + "additionalProperties": false, + "description": "Params to attach or detach an in-process ExtensionController delegate.", + "title": "ConfigureSessionExtensionsParams", + "visibility": "internal" + }, "ConnectedRemoteSessionMetadata": { "type": "object", "properties": { @@ -9285,6 +10231,35 @@ "title": "InstalledPlugin", "description": "Schema for the `InstalledPlugin` type." }, + "InstalledPluginInfo": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Plugin name" + }, + "marketplace": { + "type": "string", + "description": "Marketplace the plugin came from. Empty string (\"\") for direct repo / URL / local installs." + }, + "version": { + "type": "string", + "description": "Installed version (when reported by the plugin manifest)" + }, + "enabled": { + "type": "boolean", + "description": "Whether the plugin is currently enabled for new sessions" + } + }, + "required": [ + "name", + "marketplace", + "enabled" + ], + "additionalProperties": false, + "description": "Information about an installed plugin tracked in global state.", + "title": "InstalledPluginInfo" + }, "InstalledPluginSource": { "anyOf": [ { @@ -9380,13 +10355,32 @@ "title": "InstalledPluginSourceUrl", "description": "Schema for the `InstalledPluginSourceUrl` type." }, + "InstructionsDiscoverRequest": { + "type": "object", + "properties": { + "projectPaths": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Optional list of project directory paths to scan for repository/working-directory instruction sources. When omitted or empty, only user-level and plugin instruction sources are returned (no project scan)." + }, + "excludeHostInstructions": { + "type": "boolean", + "description": "When true, omit the host's instruction sources (user/home-level files and plugin rules), leaving only repository and working-directory sources. For multitenant deployments." + } + }, + "additionalProperties": false, + "description": "Optional project paths to include in instruction discovery.", + "title": "InstructionsDiscoverRequest" + }, "InstructionsGetSourcesResult": { "type": "object", "properties": { "sources": { "type": "array", "items": { - "$ref": "#/definitions/InstructionsSources" + "$ref": "#/definitions/InstructionSource" }, "description": "Instruction sources for the session" } @@ -9398,7 +10392,7 @@ "description": "Instruction sources loaded for the session, in merge order.", "title": "InstructionsGetSourcesResult" }, - "InstructionsSources": { + "InstructionSource": { "type": "object", "properties": { "id": { @@ -9418,11 +10412,11 @@ "description": "Raw content of the instruction file" }, "type": { - "$ref": "#/definitions/InstructionsSourcesType", + "$ref": "#/definitions/InstructionSourceType", "description": "Category of instruction source — used for merge logic" }, "location": { - "$ref": "#/definitions/InstructionsSourcesLocation", + "$ref": "#/definitions/InstructionSourceLocation", "description": "Where this source lives — used for UI grouping" }, "applyTo": { @@ -9439,6 +10433,10 @@ "defaultDisabled": { "type": "boolean", "description": "When true, this source starts disabled and must be toggled on by the user" + }, + "projectPath": { + "type": "string", + "description": "The project path this source was discovered from. Only set by sessionless discovery for repository/working-directory sources, where it disambiguates same-named files (e.g. .github/copilot-instructions.md) across multiple workspace roots. The session-scoped getSources leaves it unset." } }, "required": [ @@ -9450,10 +10448,10 @@ "location" ], "additionalProperties": false, - "title": "InstructionsSources", - "description": "Schema for the `InstructionsSources` type." + "title": "InstructionSource", + "description": "Schema for the `InstructionSource` type." }, - "InstructionsSourcesLocation": { + "InstructionSourceLocation": { "type": "string", "enum": [ "user", @@ -9462,7 +10460,7 @@ "plugin" ], "description": "Where this source lives — used for UI grouping", - "title": "InstructionsSourcesLocation", + "title": "InstructionSourceLocation", "x-enumDescriptions": { "user": "Instructions live in user-level configuration.", "repository": "Instructions live in repository-level configuration.", @@ -9470,7 +10468,7 @@ "plugin": "Instructions live in plugin-provided configuration." } }, - "InstructionsSourcesType": { + "InstructionSourceType": { "type": "string", "enum": [ "home", @@ -9482,7 +10480,7 @@ "plugin" ], "description": "Category of instruction source — used for merge logic", - "title": "InstructionsSourcesType", + "title": "InstructionSourceType", "x-enumDescriptions": { "home": "Instructions loaded from the user's home configuration.", "repo": "Instructions loaded from repository-scoped files.", @@ -9493,6 +10491,61 @@ "plugin": "Instructions supplied by an installed plugin." } }, + "LocalSessionMetadataValue": { + "type": "object", + "properties": { + "sessionId": { + "type": "string", + "description": "Stable session identifier" + }, + "startTime": { + "type": "string", + "description": "Session creation time as an ISO 8601 timestamp" + }, + "modifiedTime": { + "type": "string", + "description": "Last-modified time of the session's persisted state, as ISO 8601" + }, + "summary": { + "type": "string", + "description": "Short summary of the session, when one has been derived" + }, + "name": { + "type": "string", + "description": "Optional human-friendly name set via /rename" + }, + "clientName": { + "type": "string", + "description": "Runtime client name that created/last resumed this session" + }, + "isRemote": { + "type": "boolean", + "const": false, + "description": "Always false for local sessions." + }, + "isDetached": { + "type": "boolean", + "description": "True for detached maintenance sessions that should be hidden from normal resume lists." + }, + "context": { + "$ref": "#/definitions/SessionContext", + "description": "Pre-resolved working-directory context for session startup." + }, + "mcTaskId": { + "type": "string", + "description": "GitHub task ID, when this local session is bound to one. Only present for local sessions exported to remote control." + } + }, + "required": [ + "sessionId", + "startTime", + "modifiedTime", + "isRemote" + ], + "additionalProperties": false, + "title": "LocalSessionMetadataValue", + "description": "Schema for the `LocalSessionMetadataValue` type." + }, "LogRequest": { "type": "object", "properties": { @@ -9565,23 +10618,202 @@ "description": "Parameters for (re)loading the merged LSP configuration set.", "title": "LspInitializeRequest" }, - "McpAppsCallToolRequest": { + "MarketplaceAddResult": { "type": "object", "properties": { - "serverName": { - "type": "string", - "minLength": 1, - "pattern": "^[^\\x00-\\x1f/\\x7f-\\x9f}]+(?:\\/[^\\x00-\\x1f/\\x7f-\\x9f}]+)*$", - "description": "MCP server hosting the tool" - }, - "toolName": { + "name": { "type": "string", - "description": "MCP tool name" - }, - "arguments": { - "type": "object", - "additionalProperties": { - "x-opaque-json": true + "description": "Final name of the marketplace as resolved from its manifest" + } + }, + "required": [ + "name" + ], + "additionalProperties": false, + "description": "Result of registering a new marketplace.", + "title": "MarketplaceAddResult" + }, + "MarketplaceBrowseResult": { + "type": "object", + "properties": { + "plugins": { + "type": "array", + "items": { + "$ref": "#/definitions/MarketplacePluginInfo", + "description": "Plugin entry advertised by a marketplace." + }, + "description": "Plugins advertised by the marketplace" + } + }, + "required": [ + "plugins" + ], + "additionalProperties": false, + "description": "Plugins advertised by the marketplace.", + "title": "MarketplaceBrowseResult" + }, + "MarketplaceInfo": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Marketplace name (matches the @marketplace suffix in plugin specs)" + }, + "source": { + "type": "string", + "description": "Human-readable description of where the marketplace data is fetched from (e.g. \"GitHub: owner/repo\")." + }, + "isDefault": { + "type": "boolean", + "description": "True when this is a default marketplace shipped with the runtime. Defaults are not removable." + } + }, + "required": [ + "name", + "source" + ], + "additionalProperties": false, + "description": "Registered marketplace summary.", + "title": "MarketplaceInfo" + }, + "MarketplaceListResult": { + "type": "object", + "properties": { + "marketplaces": { + "type": "array", + "items": { + "$ref": "#/definitions/MarketplaceInfo", + "description": "Registered marketplace summary." + }, + "description": "Registered marketplaces" + } + }, + "required": [ + "marketplaces" + ], + "additionalProperties": false, + "description": "All registered marketplaces, including built-in defaults.", + "title": "MarketplaceListResult" + }, + "MarketplacePluginInfo": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Plugin name as listed in the marketplace catalog" + }, + "description": { + "type": "string", + "description": "Short description from the marketplace catalog, when present" + } + }, + "required": [ + "name" + ], + "additionalProperties": false, + "description": "Plugin entry advertised by a marketplace.", + "title": "MarketplacePluginInfo" + }, + "MarketplaceRefreshEntry": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Marketplace name that was refreshed" + }, + "success": { + "type": "boolean", + "description": "Whether the refresh succeeded" + }, + "error": { + "type": "string", + "description": "Error message (failure only)" + } + }, + "required": [ + "name", + "success" + ], + "additionalProperties": false, + "title": "MarketplaceRefreshEntry", + "description": "Schema for the `MarketplaceRefreshEntry` type." + }, + "MarketplaceRefreshResult": { + "type": "object", + "properties": { + "results": { + "type": "array", + "items": { + "$ref": "#/definitions/MarketplaceRefreshEntry" + }, + "description": "Per-marketplace refresh results in deterministic order." + } + }, + "required": [ + "results" + ], + "additionalProperties": false, + "description": "Result of refreshing one or more marketplace catalogs.", + "title": "MarketplaceRefreshResult" + }, + "MarketplaceRemoveResult": { + "type": "object", + "properties": { + "removed": { + "type": "boolean", + "description": "True when the marketplace was actually removed. False when removal was skipped because the marketplace has dependent plugins and `force` was not set." + }, + "dependentPlugins": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Names of installed plugins that prevented removal. Populated only when `removed=false`." + } + }, + "required": [ + "removed" + ], + "additionalProperties": false, + "description": "Outcome of the remove attempt, including dependent-plugin info when applicable.", + "title": "MarketplaceRemoveResult" + }, + "McpAllowedServer": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Allowed server name" + }, + "redactedNote": { + "type": "string", + "description": "PII-free note explaining why the server was allowed" + } + }, + "required": [ + "name" + ], + "additionalProperties": false, + "title": "McpAllowedServer", + "description": "Schema for the `McpAllowedServer` type." + }, + "McpAppsCallToolRequest": { + "type": "object", + "properties": { + "serverName": { + "type": "string", + "minLength": 1, + "pattern": "^[^\\x00-\\x1f/\\x7f-\\x9f}]+(?:\\/[^\\x00-\\x1f/\\x7f-\\x9f}]+)*$", + "description": "MCP server hosting the tool" + }, + "toolName": { + "type": "string", + "description": "MCP tool name" + }, + "arguments": { + "type": "object", + "additionalProperties": { + "x-opaque-json": true }, "description": "Tool arguments" }, @@ -10195,6 +11427,38 @@ "description": "MCP server name and replacement configuration to write to user configuration.", "title": "McpConfigUpdateRequest" }, + "McpConfigureGitHubRequest": { + "type": "object", + "properties": { + "authInfo": { + "description": "Opaque runtime auth info for GitHub MCP configuration. Marked internal: an in-process runtime shape (configureGitHubMcp is a no-op over the wire).", + "visibility": "internal", + "x-opaque-json": true + } + }, + "required": [ + "authInfo" + ], + "additionalProperties": false, + "description": "Opaque auth info used to configure GitHub MCP.", + "title": "McpConfigureGitHubRequest", + "visibility": "internal" + }, + "McpConfigureGitHubResult": { + "type": "object", + "properties": { + "changed": { + "type": "boolean", + "description": "Whether GitHub MCP configuration changed." + } + }, + "required": [ + "changed" + ], + "additionalProperties": false, + "description": "Result of configuring GitHub MCP.", + "title": "McpConfigureGitHubResult" + }, "McpDisableRequest": { "type": "object", "properties": { @@ -10310,100 +11574,359 @@ "x-opaque-json": true, "stability": "experimental" }, - "McpOauthLoginRequest": { + "McpFilteredServer": { "type": "object", "properties": { - "serverName": { + "name": { "type": "string", - "minLength": 1, - "pattern": "^[^\\x00-\\x1f/\\x7f-\\x9f}]+(?:\\/[^\\x00-\\x1f/\\x7f-\\x9f}]+)*$", - "description": "Name of the remote MCP server to authenticate" + "description": "Filtered server name" }, - "forceReauth": { - "type": "boolean", - "description": "When true, clears any cached OAuth token for the server and runs a full new authorization. Use when the user explicitly wants to switch accounts or believes their session is stuck." + "reason": { + "type": "string", + "description": "Human-readable filter reason" }, - "clientName": { + "redactedReason": { "type": "string", - "description": "Optional override for the OAuth client display name shown on the consent screen. Applies to newly registered dynamic clients only — existing registrations keep the name they were created with. When omitted, the runtime applies a neutral fallback; callers driving interactive auth should pass their own surface-specific label so the consent screen matches the product the user sees." + "description": "PII-free filter reason" }, - "callbackSuccessMessage": { + "enterpriseName": { "type": "string", - "description": "Optional override for the body text shown on the OAuth loopback callback success page. When omitted, the runtime applies a neutral fallback; callers driving interactive auth should pass surface-specific copy telling the user where to return." + "description": "Enterprise login associated with an allowlist policy" } }, "required": [ - "serverName" + "name", + "reason" ], "additionalProperties": false, - "description": "Remote MCP server name and optional overrides controlling reauthentication, OAuth client display name, and the callback success-page copy.", - "title": "McpOauthLoginRequest" + "title": "McpFilteredServer", + "description": "Schema for the `McpFilteredServer` type." }, - "McpOauthLoginResult": { + "McpHostState": { "type": "object", "properties": { - "authorizationUrl": { - "type": "string", - "format": "uri", - "description": "URL the caller should open in a browser to complete OAuth. Omitted when cached tokens were still valid and no browser interaction was needed — the server is already reconnected in that case. When present, the runtime starts the callback listener before returning and continues the flow in the background; completion is signaled via session.mcp_server_status_changed." + "mcp3pEnabled": { + "type": "boolean", + "description": "Whether third-party MCP servers are policy-enabled for this session." + }, + "disabledServers": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Configured servers that are explicitly disabled." + }, + "filteredServers": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Configured servers filtered out by enterprise allowlist policy." + }, + "clients": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Names of currently-connected MCP clients." + }, + "pendingConnections": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Names of servers with in-flight connection attempts." + }, + "failedServers": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/McpServerFailureInfo", + "description": "Recorded MCP server connection failure." + }, + "description": "Map of server name to recorded connection failure." + }, + "needsAuthServers": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/McpServerNeedsAuthInfo", + "description": "Recorded MCP server pending-auth state." + }, + "description": "Map of server name to recorded pending-auth state." } }, + "required": [ + "mcp3pEnabled", + "disabledServers", + "filteredServers", + "clients", + "pendingConnections", + "failedServers", + "needsAuthServers" + ], "additionalProperties": false, - "description": "OAuth authorization URL the caller should open, or empty when cached tokens already authenticated the server.", - "title": "McpOauthLoginResult" + "description": "Host-level state, omitted when no MCP host is initialized.", + "title": "McpHostState" }, - "McpRemoveGitHubResult": { + "McpIsServerRunningRequest": { "type": "object", "properties": { - "removed": { - "type": "boolean", - "description": "True when the auto-managed `github` MCP server was removed; false when no removal happened (e.g. user has explicitly configured a `github` server, or the server was not registered)." + "serverName": { + "type": "string", + "description": "Name of the MCP server to check" } }, "required": [ - "removed" + "serverName" ], "additionalProperties": false, - "description": "Indicates whether the auto-managed `github` MCP server was removed (false when nothing to remove).", - "title": "McpRemoveGitHubResult" - }, - "McpSamplingExecutionAction": { - "type": "string", - "enum": [ - "success", - "failure", - "cancelled" - ], - "description": "Outcome of the sampling inference. 'success' produced a response; 'failure' encountered an error (including agent-side rejection by content filter or criteria); 'cancelled' the caller cancelled this execution via cancelSamplingExecution.", - "title": "McpSamplingExecutionAction", - "x-enumDescriptions": { - "success": "The sampling inference completed and produced a result.", - "failure": "The sampling inference failed or was rejected.", - "cancelled": "The sampling inference was cancelled before completion." - } + "description": "Server name to check running status for.", + "title": "McpIsServerRunningRequest" }, - "McpSamplingExecutionResult": { + "McpIsServerRunningResult": { "type": "object", "properties": { - "action": { - "$ref": "#/definitions/McpSamplingExecutionAction", - "description": "Outcome of the sampling inference. 'success' produced a response; 'failure' encountered an error (including agent-side rejection by content filter or criteria); 'cancelled' the caller cancelled this execution via cancelSamplingExecution." - }, - "result": { - "$ref": "#/definitions/McpExecuteSamplingResult", - "description": "MCP CreateMessageResult payload (with optional 'tools' extension), present when action='success'. Treated as opaque at the schema layer; consumers should construct/consume it per the MCP CreateMessageResult shape." - }, - "error": { - "type": "string", - "description": "Error description, present when action='failure'." + "running": { + "type": "boolean", + "description": "True if the server has an active client and transport." } }, "required": [ - "action" + "running" ], "additionalProperties": false, - "description": "Outcome of an MCP sampling execution: success result, failure error, or cancellation.", - "title": "McpSamplingExecutionResult" + "description": "Whether the named MCP server is running.", + "title": "McpIsServerRunningResult" + }, + "McpListToolsRequest": { + "type": "object", + "properties": { + "serverName": { + "type": "string", + "minLength": 1, + "pattern": "^[^\\x00-\\x1f/\\x7f-\\x9f}]+(?:\\/[^\\x00-\\x1f/\\x7f-\\x9f}]+)*$", + "description": "Name of the connected MCP server whose tools to list." + } + }, + "required": [ + "serverName" + ], + "additionalProperties": false, + "description": "Server name whose tool list should be returned.", + "title": "McpListToolsRequest" + }, + "McpListToolsResult": { + "type": "object", + "properties": { + "tools": { + "type": "array", + "items": { + "$ref": "#/definitions/McpTools" + }, + "description": "Tools exposed by the server." + } + }, + "required": [ + "tools" + ], + "additionalProperties": false, + "description": "Tools exposed by the connected MCP server. Throws when the server is not connected.", + "title": "McpListToolsResult" + }, + "McpOauthLoginRequest": { + "type": "object", + "properties": { + "serverName": { + "type": "string", + "minLength": 1, + "pattern": "^[^\\x00-\\x1f/\\x7f-\\x9f}]+(?:\\/[^\\x00-\\x1f/\\x7f-\\x9f}]+)*$", + "description": "Name of the remote MCP server to authenticate" + }, + "forceReauth": { + "type": "boolean", + "description": "When true, clears any cached OAuth token for the server and runs a full new authorization. Use when the user explicitly wants to switch accounts or believes their session is stuck." + }, + "clientName": { + "type": "string", + "description": "Optional override for the OAuth client display name shown on the consent screen. Applies to newly registered dynamic clients only — existing registrations keep the name they were created with. When omitted, the runtime applies a neutral fallback; callers driving interactive auth should pass their own surface-specific label so the consent screen matches the product the user sees." + }, + "callbackSuccessMessage": { + "type": "string", + "description": "Optional override for the body text shown on the OAuth loopback callback success page. When omitted, the runtime applies a neutral fallback; callers driving interactive auth should pass surface-specific copy telling the user where to return." + } + }, + "required": [ + "serverName" + ], + "additionalProperties": false, + "description": "Remote MCP server name and optional overrides controlling reauthentication, OAuth client display name, and the callback success-page copy.", + "title": "McpOauthLoginRequest" + }, + "McpOauthLoginResult": { + "type": "object", + "properties": { + "authorizationUrl": { + "type": "string", + "format": "uri", + "description": "URL the caller should open in a browser to complete OAuth. Omitted when cached tokens were still valid and no browser interaction was needed — the server is already reconnected in that case. When present, the runtime starts the callback listener before returning and continues the flow in the background; completion is signaled via session.mcp_server_status_changed." + } + }, + "additionalProperties": false, + "description": "OAuth authorization URL the caller should open, or empty when cached tokens already authenticated the server.", + "title": "McpOauthLoginResult" + }, + "McpOauthRespondRequest": { + "type": "object", + "properties": { + "requestId": { + "type": "string", + "description": "OAuth request identifier from mcp.oauth_required" + }, + "provider": { + "description": "In-process OAuthClientProvider instance, or omitted to deny. Marked internal: cannot be serialized across the JSON-RPC boundary.", + "visibility": "internal", + "x-opaque-json": true + } + }, + "required": [ + "requestId" + ], + "additionalProperties": false, + "description": "MCP OAuth request id and optional provider response.", + "title": "McpOauthRespondRequest", + "visibility": "internal" + }, + "McpOauthRespondResult": { + "type": "object", + "properties": {}, + "additionalProperties": false, + "description": "Empty result after recording the MCP OAuth response.", + "title": "McpOauthRespondResult" + }, + "McpRegisterExternalClientRequest": { + "type": "object", + "properties": { + "serverName": { + "type": "string", + "description": "Logical server name for the external client" + }, + "client": { + "description": "In-process MCP Client instance. Marked internal: cannot be serialized across the JSON-RPC boundary.", + "visibility": "internal", + "x-opaque-json": true + }, + "transport": { + "description": "In-process MCP Transport instance. Marked internal: cannot be serialized across the JSON-RPC boundary.", + "visibility": "internal", + "x-opaque-json": true + }, + "config": { + "description": "In-process server config (MCPServerConfig) paired with the in-process client/transport. Marked internal alongside its companions.", + "visibility": "internal", + "x-opaque-json": true + } + }, + "required": [ + "serverName", + "client", + "transport", + "config" + ], + "additionalProperties": false, + "description": "Registration parameters for an external MCP client.", + "title": "McpRegisterExternalClientRequest", + "visibility": "internal" + }, + "McpReloadWithConfigRequest": { + "type": "object", + "properties": { + "config": { + "description": "Opaque runtime MCP reload configuration. Marked internal: an in-process runtime shape (reloadMcpServers throws over the wire).", + "visibility": "internal", + "x-opaque-json": true + } + }, + "required": [ + "config" + ], + "additionalProperties": false, + "description": "Opaque MCP reload configuration.", + "title": "McpReloadWithConfigRequest", + "visibility": "internal" + }, + "McpRemoveGitHubResult": { + "type": "object", + "properties": { + "removed": { + "type": "boolean", + "description": "True when the auto-managed `github` MCP server was removed; false when no removal happened (e.g. user has explicitly configured a `github` server, or the server was not registered)." + } + }, + "required": [ + "removed" + ], + "additionalProperties": false, + "description": "Indicates whether the auto-managed `github` MCP server was removed (false when nothing to remove).", + "title": "McpRemoveGitHubResult" + }, + "McpRestartServerRequest": { + "type": "object", + "properties": { + "serverName": { + "type": "string", + "description": "Name of the MCP server to restart" + }, + "config": { + "description": "Opaque server configuration (MCPServerConfig). Marked internal: an in-process runtime shape supplied only by in-process CLI callers.", + "visibility": "internal", + "x-opaque-json": true + } + }, + "required": [ + "serverName", + "config" + ], + "additionalProperties": false, + "description": "Server name and opaque configuration for an individual MCP server restart.", + "title": "McpRestartServerRequest", + "visibility": "internal" + }, + "McpSamplingExecutionAction": { + "type": "string", + "enum": [ + "success", + "failure", + "cancelled" + ], + "description": "Outcome of the sampling inference. 'success' produced a response; 'failure' encountered an error (including agent-side rejection by content filter or criteria); 'cancelled' the caller cancelled this execution via cancelSamplingExecution.", + "title": "McpSamplingExecutionAction", + "x-enumDescriptions": { + "success": "The sampling inference completed and produced a result.", + "failure": "The sampling inference failed or was rejected.", + "cancelled": "The sampling inference was cancelled before completion." + } + }, + "McpSamplingExecutionResult": { + "type": "object", + "properties": { + "action": { + "$ref": "#/definitions/McpSamplingExecutionAction", + "description": "Outcome of the sampling inference. 'success' produced a response; 'failure' encountered an error (including agent-side rejection by content filter or criteria); 'cancelled' the caller cancelled this execution via cancelSamplingExecution." + }, + "result": { + "$ref": "#/definitions/McpExecuteSamplingResult", + "description": "MCP CreateMessageResult payload (with optional 'tools' extension), present when action='success'. Treated as opaque at the schema layer; consumers should construct/consume it per the MCP CreateMessageResult shape." + }, + "error": { + "type": "string", + "description": "Error description, present when action='failure'." + } + }, + "required": [ + "action" + ], + "additionalProperties": false, + "description": "Outcome of an MCP sampling execution: success result, failure error, or cancellation.", + "title": "McpSamplingExecutionResult" }, "McpServer": { "type": "object", @@ -10636,6 +12159,27 @@ "description": "Stdio MCP server configuration launched as a child process.", "title": "McpServerConfigStdio" }, + "McpServerFailureInfo": { + "type": "object", + "properties": { + "message": { + "type": "string", + "description": "Failure message produced when the MCP server connection failed." + }, + "timestamp": { + "type": "integer", + "minimum": 0, + "description": "epoch-ms timestamp at which the failure was recorded." + } + }, + "required": [ + "message", + "timestamp" + ], + "additionalProperties": false, + "description": "Recorded MCP server connection failure.", + "title": "McpServerFailureInfo" + }, "McpServerList": { "type": "object", "properties": { @@ -10645,15 +12189,35 @@ "$ref": "#/definitions/McpServer" }, "description": "Configured MCP servers" + }, + "host": { + "$ref": "#/definitions/McpHostState", + "description": "Host-level state, omitted when no MCP host is initialized." } }, "required": [ "servers" ], "additionalProperties": false, - "description": "MCP servers configured for the session, with their connection status.", + "description": "MCP servers configured for the session, with their connection status and host-level state.", "title": "McpServerList" }, + "McpServerNeedsAuthInfo": { + "type": "object", + "properties": { + "timestamp": { + "type": "integer", + "minimum": 0, + "description": "epoch-ms timestamp at which the server signalled it needs authentication." + } + }, + "required": [ + "timestamp" + ], + "additionalProperties": false, + "description": "Recorded MCP server pending-auth state.", + "title": "McpServerNeedsAuthInfo" + }, "McpServerSource": { "type": "string", "enum": [ @@ -10735,41 +12299,138 @@ "description": "Env-value mode recorded on the session after the update.", "title": "McpSetEnvValueModeResult" }, - "MetadataContextInfoRequest": { + "McpStartServerRequest": { "type": "object", "properties": { - "promptTokenLimit": { - "type": "integer", - "minimum": 0, - "description": "Maximum prompt tokens allowed by the target model. Pass 0 to use the runtime default." - }, - "outputTokenLimit": { - "type": "integer", - "minimum": 0, - "description": "Maximum output tokens allowed by the target model. Pass 0 if unknown." - }, - "selectedModel": { + "serverName": { "type": "string", - "description": "Model identifier used for tokenization. Omit to use the session default. Used both for token counting and to compute display values." + "description": "Name of the MCP server to start" + }, + "config": { + "description": "Opaque server configuration (MCPServerConfig). Marked internal: an in-process runtime shape supplied only by in-process CLI callers.", + "visibility": "internal", + "x-opaque-json": true } }, "required": [ - "promptTokenLimit", - "outputTokenLimit" + "serverName", + "config" ], "additionalProperties": false, - "description": "Model identifier and token limits used to compute the context-info breakdown.", - "title": "MetadataContextInfoRequest" + "description": "Server name and opaque configuration for an individual MCP server start.", + "title": "McpStartServerRequest", + "visibility": "internal" }, - "MetadataContextInfoResult": { + "McpStartServersResult": { "type": "object", "properties": { - "contextInfo": { - "$ref": "#/definitions/SessionContextInfo", - "description": "Token breakdown for the current context window, or null if the session has not yet been initialized (no system prompt or tool metadata cached)." - } - }, - "required": [ + "filteredServers": { + "type": "array", + "items": { + "$ref": "#/definitions/McpFilteredServer" + }, + "description": "Servers filtered out before startup" + }, + "allowedServers": { + "type": "array", + "items": { + "$ref": "#/definitions/McpAllowedServer" + }, + "description": "Non-default servers allowed by policy" + } + }, + "required": [ + "filteredServers" + ], + "additionalProperties": false, + "description": "MCP server startup filtering result.", + "title": "McpStartServersResult" + }, + "McpStopServerRequest": { + "type": "object", + "properties": { + "serverName": { + "type": "string", + "description": "Name of the MCP server to stop" + } + }, + "required": [ + "serverName" + ], + "additionalProperties": false, + "description": "Server name for an individual MCP server stop.", + "title": "McpStopServerRequest" + }, + "McpTools": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Tool name." + }, + "description": { + "type": "string", + "description": "Tool description, when provided." + } + }, + "required": [ + "name" + ], + "additionalProperties": false, + "title": "McpTools", + "description": "Schema for the `McpTools` type." + }, + "McpUnregisterExternalClientRequest": { + "type": "object", + "properties": { + "serverName": { + "type": "string", + "description": "Server name of the external client to unregister" + } + }, + "required": [ + "serverName" + ], + "additionalProperties": false, + "description": "Server name identifying the external client to remove.", + "title": "McpUnregisterExternalClientRequest", + "visibility": "internal" + }, + "MetadataContextInfoRequest": { + "type": "object", + "properties": { + "promptTokenLimit": { + "type": "integer", + "minimum": 0, + "description": "Maximum prompt tokens allowed by the target model. Pass 0 to use the runtime default." + }, + "outputTokenLimit": { + "type": "integer", + "minimum": 0, + "description": "Maximum output tokens allowed by the target model. Pass 0 if unknown." + }, + "selectedModel": { + "type": "string", + "description": "Model identifier used for tokenization. Omit to use the session default. Used both for token counting and to compute display values." + } + }, + "required": [ + "promptTokenLimit", + "outputTokenLimit" + ], + "additionalProperties": false, + "description": "Model identifier and token limits used to compute the context-info breakdown.", + "title": "MetadataContextInfoRequest" + }, + "MetadataContextInfoResult": { + "type": "object", + "properties": { + "contextInfo": { + "$ref": "#/definitions/SessionContextInfo", + "description": "Token breakdown for the current context window, or null if the session has not yet been initialized (no system prompt or tool metadata cached)." + } + }, + "required": [ "contextInfo" ], "additionalProperties": false, @@ -11184,7 +12845,7 @@ } }, "additionalProperties": false, - "description": "Override individual model capabilities resolved by the runtime", + "description": "Initial model capability overrides.", "title": "ModelCapabilitiesOverride" }, "ModelCapabilitiesOverrideLimits": { @@ -11601,6 +13262,114 @@ "description": "Open canvas instance snapshot.", "title": "OpenCanvasInstance" }, + "OptionsUpdateAdditionalContentExclusionPolicy": { + "type": "object", + "properties": { + "rules": { + "type": "array", + "items": { + "$ref": "#/definitions/OptionsUpdateAdditionalContentExclusionPolicyRule" + } + }, + "last_updated_at": { + "type": [ + "string", + "number" + ], + "x-opaque-json": true + }, + "scope": { + "$ref": "#/definitions/OptionsUpdateAdditionalContentExclusionPolicyScope", + "description": "Allowed values for the `OptionsUpdateAdditionalContentExclusionPolicyScope` enumeration." + } + }, + "required": [ + "rules", + "last_updated_at", + "scope" + ], + "additionalProperties": true, + "title": "OptionsUpdateAdditionalContentExclusionPolicy", + "description": "Schema for the `OptionsUpdateAdditionalContentExclusionPolicy` type." + }, + "OptionsUpdateAdditionalContentExclusionPolicyRule": { + "type": "object", + "properties": { + "paths": { + "type": "array", + "items": { + "type": "string" + } + }, + "ifAnyMatch": { + "type": "array", + "items": { + "type": "string" + } + }, + "ifNoneMatch": { + "type": "array", + "items": { + "type": "string" + } + }, + "source": { + "$ref": "#/definitions/OptionsUpdateAdditionalContentExclusionPolicyRuleSource", + "description": "Schema for the `OptionsUpdateAdditionalContentExclusionPolicyRuleSource` type." + } + }, + "required": [ + "paths", + "source" + ], + "additionalProperties": true, + "title": "OptionsUpdateAdditionalContentExclusionPolicyRule", + "description": "Schema for the `OptionsUpdateAdditionalContentExclusionPolicyRule` type." + }, + "OptionsUpdateAdditionalContentExclusionPolicyRuleSource": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "name", + "type" + ], + "additionalProperties": false, + "title": "OptionsUpdateAdditionalContentExclusionPolicyRuleSource", + "description": "Schema for the `OptionsUpdateAdditionalContentExclusionPolicyRuleSource` type." + }, + "OptionsUpdateAdditionalContentExclusionPolicyScope": { + "type": "string", + "enum": [ + "repo", + "all" + ], + "title": "OptionsUpdateAdditionalContentExclusionPolicyScope", + "x-enumDescriptions": { + "repo": "The content exclusion policy applies to the current repository.", + "all": "The content exclusion policy applies across all repositories." + }, + "description": "Allowed values for the `OptionsUpdateAdditionalContentExclusionPolicyScope` enumeration." + }, + "OptionsUpdateContextTier": { + "type": "string", + "enum": [ + "default", + "long_context" + ], + "description": "Context tier for models with tiered pricing. The session uses this to derive effective `modelCapabilitiesOverrides` so compaction, truncation, token display, and request limits honor the selected tier.", + "title": "OptionsUpdateContextTier", + "x-enumDescriptions": { + "default": "Use the model's default context tier and its standard token limits / pricing.", + "long_context": "Use the model's long-context tier (when available) so larger inputs are accepted and tier-specific pricing applies." + } + }, "OptionsUpdateEnvValueMode": { "type": "string", "enum": [ @@ -11614,6 +13383,21 @@ "indirect": "Resolve MCP server environment values from host-side references." } }, + "OptionsUpdateReasoningSummary": { + "type": "string", + "enum": [ + "none", + "concise", + "detailed" + ], + "description": "Reasoning summary mode for supported model clients.", + "title": "OptionsUpdateReasoningSummary", + "x-enumDescriptions": { + "none": "Do not request reasoning summaries from the model.", + "concise": "Request a concise summary of model reasoning.", + "detailed": "Request a detailed summary of model reasoning." + } + }, "OptionsUpdateToolFilterPrecedence": { "type": "string", "enum": [ @@ -13614,6 +15398,48 @@ "description": "Existence, contents, and resolved path of the session plan file.", "title": "PlanReadResult" }, + "PlanReadSqlTodosResult": { + "type": "object", + "properties": { + "rows": { + "type": "array", + "items": { + "$ref": "#/definitions/PlanSqlTodosRow" + }, + "description": "Rows from the session SQL todos table, ordered by creation time and id." + } + }, + "required": [ + "rows" + ], + "additionalProperties": false, + "description": "Todo rows read from the session SQL database. Empty when no session database is available.", + "title": "PlanReadSqlTodosResult" + }, + "PlanSqlTodosRow": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "Todo identifier." + }, + "title": { + "type": "string", + "description": "Todo title." + }, + "description": { + "type": "string", + "description": "Todo description." + }, + "status": { + "type": "string", + "description": "Todo status." + } + }, + "additionalProperties": false, + "title": "PlanSqlTodosRow", + "description": "Schema for the `PlanSqlTodosRow` type." + }, "PlanUpdateRequest": { "type": "object", "properties": { @@ -13658,6 +15484,34 @@ "title": "Plugin", "description": "Schema for the `Plugin` type." }, + "PluginInstallResult": { + "type": "object", + "properties": { + "plugin": { + "$ref": "#/definitions/InstalledPluginInfo", + "description": "The newly installed plugin's metadata" + }, + "skillsInstalled": { + "type": "integer", + "description": "Number of skills discovered and installed from the plugin" + }, + "postInstallMessage": { + "type": "string", + "description": "Optional post-install message provided by the plugin (e.g. setup instructions)" + }, + "deprecationWarning": { + "type": "string", + "description": "Set when the install path is deprecated (e.g. direct repo / URL / local installs). Callers should surface this to end users." + } + }, + "required": [ + "plugin", + "skillsInstalled" + ], + "additionalProperties": false, + "description": "Result of installing a plugin.", + "title": "PluginInstallResult" + }, "PluginList": { "type": "object", "properties": { @@ -13676,2031 +15530,3488 @@ "description": "Plugins installed for the session, with their enabled state and version metadata.", "title": "PluginList" }, - "PushAttachment": { - "anyOf": [ - { - "$ref": "#/definitions/PushAttachmentFile", - "description": "File attachment" - }, - { - "$ref": "#/definitions/PushAttachmentDirectory", - "description": "Directory attachment" - }, - { - "$ref": "#/definitions/PushAttachmentSelection", - "description": "Code selection attachment from an editor" - }, - { - "$ref": "#/definitions/PushAttachmentGitHubReference", - "description": "GitHub issue, pull request, or discussion reference" - }, - { - "$ref": "#/definitions/PushAttachmentBlob", - "description": "Blob attachment with inline base64-encoded data" - }, - { - "$ref": "#/definitions/ExtensionContextPushInput", - "description": "Slim input shape for extension_context attachments; identity fields are runtime-derived." - } - ], - "title": "PushAttachment", - "description": "Schema for the `PushAttachment` type." - }, - "PushAttachmentBlob": { + "PluginListResult": { "type": "object", "properties": { - "type": { - "type": "string", - "const": "blob", - "description": "Attachment type discriminator" - }, - "data": { - "type": "string", - "description": "Base64-encoded content", - "contentEncoding": "base64" - }, - "mimeType": { - "type": "string", - "description": "MIME type of the inline data" - }, - "displayName": { - "type": "string", - "description": "User-facing display name for the attachment" + "plugins": { + "type": "array", + "items": { + "$ref": "#/definitions/InstalledPluginInfo", + "description": "Information about an installed plugin tracked in global state." + }, + "description": "Installed plugins" } }, "required": [ - "type", - "data", - "mimeType" + "plugins" ], "additionalProperties": false, - "description": "Blob attachment with inline base64-encoded data", - "title": "PushAttachmentBlob" + "description": "Plugins installed in user/global state.", + "title": "PluginListResult" }, - "PushAttachmentDirectory": { + "PluginsDisableRequest": { "type": "object", "properties": { - "type": { - "type": "string", - "const": "directory", - "description": "Attachment type discriminator" - }, - "path": { - "type": "string", - "description": "Absolute directory path" - }, - "displayName": { - "type": "string", - "description": "User-facing display name for the attachment" + "names": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Plugin names or \"plugin@marketplace\" specs to disable. Unknown names are ignored. Non-marketplace direct installs cannot be disabled via this API; uninstall them instead. Plugin-owned MCP servers are stopped in active sessions immediately; other plugin contributions remain available until each session reloads plugins." } }, "required": [ - "type", - "path", - "displayName" + "names" ], "additionalProperties": false, - "description": "Directory attachment", - "title": "PushAttachmentDirectory" + "description": "Plugin names (or specs) to disable.", + "title": "PluginsDisableRequest" }, - "PushAttachmentFile": { + "PluginsEnableRequest": { "type": "object", "properties": { - "type": { - "type": "string", - "const": "file", - "description": "Attachment type discriminator" - }, - "path": { + "names": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Plugin names or \"plugin@marketplace\" specs to enable. Unknown names are ignored. Non-marketplace direct installs are always enabled and cannot be toggled via this API." + } + }, + "required": [ + "names" + ], + "additionalProperties": false, + "description": "Plugin names (or specs) to enable.", + "title": "PluginsEnableRequest" + }, + "PluginsInstallRequest": { + "type": "object", + "properties": { + "source": { "type": "string", - "description": "Absolute file path" + "description": "Plugin install spec. Accepts the same forms as the CLI: \"plugin@marketplace\" (marketplace install), \"owner/repo\" or \"owner/repo:subpath\" (GitHub direct), an http/https/ssh URL, or a local path. Direct (non-marketplace) installs are deprecated and will produce a deprecationWarning in the result." }, - "displayName": { + "workingDirectory": { "type": "string", - "description": "User-facing display name for the attachment" - }, - "lineRange": { - "$ref": "#/definitions/PushAttachmentFileLineRange", - "description": "Optional line range to scope the attachment to a specific section of the file" + "description": "Working directory used to resolve relative local paths in `source`. Defaults to the server's current working directory." } }, "required": [ - "type", - "path", - "displayName" + "source" ], "additionalProperties": false, - "description": "File attachment", - "title": "PushAttachmentFile" + "description": "Plugin source and optional working directory for relative-path resolution.", + "title": "PluginsInstallRequest" }, - "PushAttachmentFileLineRange": { + "PluginsMarketplacesAddRequest": { "type": "object", "properties": { - "start": { - "type": "integer", - "exclusiveMinimum": 0, - "description": "Start line number (1-based)" - }, - "end": { - "type": "integer", - "exclusiveMinimum": 0, - "description": "End line number (1-based, inclusive)" + "source": { + "type": "string", + "description": "Marketplace source. Accepts the same forms as the CLI: \"owner/repo\" or \"owner/repo#ref\" (GitHub), an http/https/ssh URL (optionally with #ref), a git scp-style URL (user@host:path), or a local path. The marketplace's own name (from its manifest) is used as the registration key." } }, "required": [ - "start", - "end" + "source" ], "additionalProperties": false, - "description": "Optional line range to scope the attachment to a specific section of the file", - "title": "PushAttachmentFileLineRange" + "description": "Marketplace source to register.", + "title": "PluginsMarketplacesAddRequest" }, - "PushAttachmentGitHubReference": { + "PluginsMarketplacesBrowseRequest": { "type": "object", "properties": { - "type": { - "type": "string", - "const": "github_reference", - "description": "Attachment type discriminator" - }, - "number": { - "type": "integer", - "exclusiveMinimum": 0, - "description": "Issue, pull request, or discussion number" - }, - "title": { - "type": "string", - "description": "Title of the referenced item" - }, - "referenceType": { - "$ref": "#/definitions/PushAttachmentGitHubReferenceType", - "description": "Type of GitHub reference" - }, - "state": { - "type": "string", - "description": "Current state of the referenced item (e.g., open, closed, merged)" - }, - "url": { + "name": { "type": "string", - "description": "URL to the referenced item on GitHub" + "description": "Marketplace name to browse" } }, "required": [ - "type", - "number", - "title", - "referenceType", - "state", - "url" + "name" ], "additionalProperties": false, - "description": "GitHub issue, pull request, or discussion reference", - "title": "PushAttachmentGitHubReference" + "description": "Name of the marketplace whose plugin catalog to fetch.", + "title": "PluginsMarketplacesBrowseRequest" }, - "PushAttachmentGitHubReferenceType": { - "type": "string", - "enum": [ - "issue", - "pr", - "discussion" + "PluginsMarketplacesRefreshRequest": { + "anyOf": [ + { + "not": {} + }, + { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Marketplace name to refresh. When omitted, every registered marketplace is refreshed." + } + }, + "additionalProperties": false + } ], - "description": "Type of GitHub reference", - "title": "PushAttachmentGitHubReferenceType", - "x-enumDescriptions": { - "issue": "GitHub issue reference.", - "pr": "GitHub pull request reference.", - "discussion": "GitHub discussion reference." - } + "description": "Optional marketplace name; omit to refresh all.", + "title": "PluginsMarketplacesRefreshRequest" }, - "PushAttachmentSelection": { + "PluginsMarketplacesRemoveRequest": { "type": "object", "properties": { - "type": { - "type": "string", - "const": "selection", - "description": "Attachment type discriminator" - }, - "filePath": { - "type": "string", - "description": "Absolute path to the file containing the selection" - }, - "displayName": { - "type": "string", - "description": "User-facing display name for the selection" - }, - "text": { + "name": { "type": "string", - "description": "The selected text content" + "description": "Marketplace name to remove" }, - "selection": { - "$ref": "#/definitions/PushAttachmentSelectionDetails", - "description": "Position range of the selection within the file" + "force": { + "type": "boolean", + "description": "When true, also uninstall every plugin sourced from this marketplace. When false (default), removal is a no-op if any plugin from this marketplace is installed and the dependent plugin names are returned in the result." } }, "required": [ - "type", - "filePath", - "displayName", - "text", - "selection" + "name" ], "additionalProperties": false, - "description": "Code selection attachment from an editor", - "title": "PushAttachmentSelection" + "description": "Name of the marketplace to remove and an optional force flag.", + "title": "PluginsMarketplacesRemoveRequest" }, - "PushAttachmentSelectionDetails": { + "PluginsReloadRequest": { + "anyOf": [ + { + "not": {} + }, + { + "type": "object", + "properties": { + "reloadMcp": { + "type": "boolean", + "description": "Reload MCP server connections after refreshing plugins. Defaults to true." + }, + "reloadCustomAgents": { + "type": "boolean", + "description": "Re-run custom-agent discovery after refreshing plugins. Defaults to true." + }, + "reloadHooks": { + "type": "boolean", + "description": "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)." + }, + "deferRepoHooks": { + "type": "boolean", + "description": "When true, skip repo-level hooks during the hook reload. Use before folder trust is confirmed; load them post-trust via `sessions.loadDeferredRepoHooks`." + } + }, + "additionalProperties": false + } + ], + "description": "Optional flags controlling which side effects the reload performs.", + "title": "PluginsReloadRequest" + }, + "PluginsUninstallRequest": { "type": "object", "properties": { - "start": { - "$ref": "#/definitions/PushAttachmentSelectionDetailsStart", - "description": "Start position of the selection" - }, - "end": { - "$ref": "#/definitions/PushAttachmentSelectionDetailsEnd", - "description": "End position of the selection" + "name": { + "type": "string", + "description": "Plugin name or \"plugin@marketplace\" spec to uninstall. When ambiguous, prefer the fully-qualified spec." } }, "required": [ - "start", - "end" + "name" ], "additionalProperties": false, - "description": "Position range of the selection within the file", - "title": "PushAttachmentSelectionDetails" + "description": "Name (or spec) of the plugin to uninstall.", + "title": "PluginsUninstallRequest" }, - "PushAttachmentSelectionDetailsEnd": { + "PluginsUpdateRequest": { "type": "object", "properties": { - "line": { - "type": "integer", - "minimum": 0, - "description": "End line number (0-based)" - }, - "character": { - "type": "integer", - "minimum": 0, - "description": "End character offset within the line (0-based)" + "name": { + "type": "string", + "description": "Plugin name or \"plugin@marketplace\" spec to update." } }, "required": [ - "line", - "character" + "name" ], "additionalProperties": false, - "description": "End position of the selection", - "title": "PushAttachmentSelectionDetailsEnd" + "description": "Name (or spec) of the plugin to update.", + "title": "PluginsUpdateRequest" }, - "PushAttachmentSelectionDetailsStart": { + "PluginUpdateAllEntry": { "type": "object", "properties": { - "line": { - "type": "integer", - "minimum": 0, - "description": "Start line number (0-based)" + "name": { + "type": "string", + "description": "Plugin name that was updated" }, - "character": { + "marketplace": { + "type": "string", + "description": "Marketplace the plugin came from. Empty string (\"\") for direct installs." + }, + "success": { + "type": "boolean", + "description": "Whether the update succeeded for this plugin" + }, + "previousVersion": { + "type": "string", + "description": "Previously installed version, when available" + }, + "newVersion": { + "type": "string", + "description": "Version after the update, when available" + }, + "skillsInstalled": { "type": "integer", - "minimum": 0, - "description": "Start character offset within the line (0-based)" + "description": "Number of skills installed after the update (success only)" + }, + "error": { + "type": "string", + "description": "Error message (failure only)" } }, "required": [ - "line", - "character" + "name", + "marketplace", + "success" ], "additionalProperties": false, - "description": "Start position of the selection", - "title": "PushAttachmentSelectionDetailsStart" + "title": "PluginUpdateAllEntry", + "description": "Schema for the `PluginUpdateAllEntry` type." }, - "QueuedCommandHandled": { + "PluginUpdateAllResult": { "type": "object", "properties": { - "handled": { - "type": "boolean", - "const": true, - "description": "The host actually executed the queued command." - }, - "stopProcessingQueue": { - "type": "boolean", - "description": "When true, the runtime will not process subsequent queued commands until a new request comes in." + "results": { + "type": "array", + "items": { + "$ref": "#/definitions/PluginUpdateAllEntry" + }, + "description": "Per-plugin update results in deterministic order." } }, "required": [ - "handled" + "results" ], "additionalProperties": false, - "title": "QueuedCommandHandled", - "description": "Schema for the `QueuedCommandHandled` type." + "description": "Result of updating all installed plugins.", + "title": "PluginUpdateAllResult" }, - "QueuedCommandNotHandled": { + "PluginUpdateResult": { "type": "object", "properties": { - "handled": { - "type": "boolean", - "const": false, - "description": "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)." + "previousVersion": { + "type": "string", + "description": "Version that was previously installed, when available" + }, + "newVersion": { + "type": "string", + "description": "Version after the update, when reported by the plugin manifest" + }, + "skillsInstalled": { + "type": "integer", + "description": "Number of skills discovered and installed after the update" } }, "required": [ - "handled" + "skillsInstalled" ], "additionalProperties": false, - "title": "QueuedCommandNotHandled", - "description": "Schema for the `QueuedCommandNotHandled` type." - }, - "QueuedCommandResult": { - "anyOf": [ - { - "$ref": "#/definitions/QueuedCommandHandled" - }, - { - "$ref": "#/definitions/QueuedCommandNotHandled" - } - ], - "description": "Result of the queued command execution.", - "title": "QueuedCommandResult" + "description": "Result of updating a single plugin.", + "title": "PluginUpdateResult" }, - "QueuePendingItems": { + "PollSpawnedSessionsResult": { "type": "object", "properties": { - "kind": { - "$ref": "#/definitions/QueuePendingItemsKind", - "description": "Whether this item is a queued user message or a queued slash command / model change" + "events": { + "type": "array", + "items": { + "$ref": "#/definitions/SessionsPollSpawnedSessionsEvent" + }, + "description": "Spawn events emitted since the supplied cursor." }, - "displayText": { + "cursor": { "type": "string", - "description": "Human-readable text to display for this queue entry in the UI" + "description": "Opaque cursor to pass back to receive only events after this batch." } }, "required": [ - "kind", - "displayText" + "events", + "cursor" ], "additionalProperties": false, - "title": "QueuePendingItems", - "description": "Schema for the `QueuePendingItems` type." - }, - "QueuePendingItemsKind": { - "type": "string", - "enum": [ - "message", - "command" - ], - "description": "Whether this item is a queued user message or a queued slash command / model change", - "title": "QueuePendingItemsKind", - "x-enumDescriptions": { - "message": "A queued user message.", - "command": "A queued slash command or model-change command." - } + "description": "Batch of spawn events plus a cursor for follow-up polls.", + "title": "PollSpawnedSessionsResult" }, - "QueuePendingItemsResult": { + "ProviderConfig": { "type": "object", "properties": { - "items": { - "type": "array", - "items": { - "$ref": "#/definitions/QueuePendingItems" - }, - "description": "Pending queued items in submission order. Includes user messages, queued slash commands, and queued model changes; omits internal system items." + "type": { + "$ref": "#/definitions/ProviderConfigType", + "description": "Provider type. Defaults to \"openai\" for generic OpenAI-compatible APIs." }, - "steeringMessages": { - "type": "array", - "items": { + "wireApi": { + "$ref": "#/definitions/ProviderConfigWireApi", + "description": "Wire API format (openai/azure only). Defaults to \"completions\"." + }, + "baseUrl": { + "type": "string", + "description": "API endpoint URL." + }, + "apiKey": { + "type": "string", + "description": "API key. Optional for local providers like Ollama." + }, + "bearerToken": { + "type": "string", + "description": "Bearer token for authentication. Sets the Authorization header directly. Takes precedence over apiKey when both are set." + }, + "azure": { + "$ref": "#/definitions/ProviderConfigAzure", + "description": "Azure-specific provider options." + }, + "modelId": { + "type": "string", + "description": "Well-known model ID used for capability lookup. When set, agent behavior config and token limits are inferred from this model." + }, + "wireModel": { + "type": "string", + "description": "The model identifier sent to the provider API for inference (the \"wire\" model), as opposed to modelId which is the well-known base." + }, + "maxPromptTokens": { + "type": "number", + "description": "Maximum prompt/input tokens for the model." + }, + "maxContextWindowTokens": { + "type": "number", + "description": "Maximum context window tokens for the model." + }, + "maxOutputTokens": { + "type": "number", + "description": "Maximum output tokens for the model." + }, + "headers": { + "type": "object", + "additionalProperties": { "type": "string" }, - "description": "Display text for messages currently in the immediate steering queue (interjections sent during a running turn)." + "description": "Custom HTTP headers to include in all outbound requests to the provider." } }, "required": [ - "items", - "steeringMessages" + "baseUrl" ], "additionalProperties": false, - "description": "Snapshot of the session's pending queued items and immediate-steering messages.", - "title": "QueuePendingItemsResult" + "description": "Custom model-provider configuration (BYOK).", + "title": "ProviderConfig", + "stability": "experimental" }, - "QueueRemoveMostRecentResult": { + "ProviderConfigAzure": { "type": "object", "properties": { - "removed": { - "type": "boolean", - "description": "True if a user-facing pending item was removed (LIFO across both queues); false when no removable items remained." + "apiVersion": { + "type": "string", + "description": "API version. When set, uses the versioned deployment route. When omitted, uses the GA versionless v1 route." } }, - "required": [ - "removed" - ], "additionalProperties": false, - "description": "Indicates whether a user-facing pending item was removed.", - "title": "QueueRemoveMostRecentResult" + "description": "Azure-specific provider options.", + "title": "ProviderConfigAzure" }, - "ReasoningSummary": { + "ProviderConfigType": { "type": "string", "enum": [ - "none", - "concise", - "detailed" + "openai", + "azure", + "anthropic" ], - "description": "Reasoning summary mode to request for supported model clients", - "title": "ReasoningSummary", + "description": "Provider type. Defaults to \"openai\" for generic OpenAI-compatible APIs.", + "title": "ProviderConfigType", "x-enumDescriptions": { - "none": "Do not request reasoning summaries from the model.", - "concise": "Request a concise summary of the model's reasoning.", - "detailed": "Request a detailed summary of the model's reasoning." + "openai": "Generic OpenAI-compatible API.", + "azure": "Azure OpenAI Service endpoint.", + "anthropic": "Anthropic API endpoint." } }, - "RegisterEventInterestParams": { - "type": "object", - "properties": { - "eventType": { - "type": "string", - "description": "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`, `user_input.requested`, `elicitation.requested`, `command.queued`, `exit_plan_mode.requested`." + "ProviderConfigWireApi": { + "type": "string", + "enum": [ + "completions", + "responses" + ], + "description": "Wire API format (openai/azure only). Defaults to \"completions\".", + "title": "ProviderConfigWireApi", + "x-enumDescriptions": { + "completions": "OpenAI Chat Completions wire format.", + "responses": "OpenAI Responses API wire format." + } + }, + "PushAttachment": { + "anyOf": [ + { + "$ref": "#/definitions/PushAttachmentFile", + "description": "File attachment" + }, + { + "$ref": "#/definitions/PushAttachmentDirectory", + "description": "Directory attachment" + }, + { + "$ref": "#/definitions/PushAttachmentSelection", + "description": "Code selection attachment from an editor" + }, + { + "$ref": "#/definitions/PushAttachmentGitHubReference", + "description": "GitHub issue, pull request, or discussion reference" + }, + { + "$ref": "#/definitions/PushAttachmentBlob", + "description": "Blob attachment with inline base64-encoded data" + }, + { + "$ref": "#/definitions/ExtensionContextPushInput", + "description": "Slim input shape for extension_context attachments; identity fields are runtime-derived." } - }, - "required": [ - "eventType" ], - "additionalProperties": false, - "description": "Event type to register consumer interest for, used by runtime gating logic.", - "title": "RegisterEventInterestParams" + "title": "PushAttachment", + "description": "Schema for the `PushAttachment` type." }, - "RegisterEventInterestResult": { + "PushAttachmentBlob": { "type": "object", "properties": { - "handle": { + "type": { "type": "string", - "description": "Opaque handle for this registration. Pass to releaseInterest to release. Each call to registerInterest produces a fresh handle, even when the same eventType is registered multiple times." + "const": "blob", + "description": "Attachment type discriminator" + }, + "data": { + "type": "string", + "description": "Base64-encoded content", + "contentEncoding": "base64" + }, + "mimeType": { + "type": "string", + "description": "MIME type of the inline data" + }, + "displayName": { + "type": "string", + "description": "User-facing display name for the attachment" } }, "required": [ - "handle" + "type", + "data", + "mimeType" ], "additionalProperties": false, - "description": "Opaque handle representing an event-type interest registration.", - "title": "RegisterEventInterestResult" + "description": "Blob attachment with inline base64-encoded data", + "title": "PushAttachmentBlob" }, - "ReleaseEventInterestParams": { + "PushAttachmentDirectory": { "type": "object", "properties": { - "handle": { + "type": { "type": "string", - "description": "Handle returned by a previous `registerInterest` call. Idempotent: releasing an unknown or already-released handle is a no-op (returns success). When the last outstanding handle for an event type is released, the runtime reverts to its 'no consumer' code path for that event type." + "const": "directory", + "description": "Attachment type discriminator" + }, + "path": { + "type": "string", + "description": "Absolute directory path" + }, + "displayName": { + "type": "string", + "description": "User-facing display name for the attachment" } }, "required": [ - "handle" + "type", + "path", + "displayName" ], "additionalProperties": false, - "description": "Opaque handle previously returned by `registerInterest` to release.", - "title": "ReleaseEventInterestParams" + "description": "Directory attachment", + "title": "PushAttachmentDirectory" }, - "RemoteEnableRequest": { + "PushAttachmentFile": { "type": "object", "properties": { - "mode": { - "$ref": "#/definitions/RemoteSessionMode", - "description": "Per-session remote mode. \"off\" disables remote, \"export\" exports session events to GitHub without enabling remote steering, \"on\" enables both export and remote steering." - } - }, - "additionalProperties": false, - "description": "Optional remote session mode (\"off\", \"export\", or \"on\"); defaults to enabling both export and remote steering.", - "title": "RemoteEnableRequest" - }, - "RemoteEnableResult": { - "type": "object", - "properties": { - "url": { + "type": { "type": "string", - "format": "uri", - "description": "GitHub frontend URL for this session" + "const": "file", + "description": "Attachment type discriminator" }, - "remoteSteerable": { - "type": "boolean", - "description": "Whether remote steering is enabled" + "path": { + "type": "string", + "description": "Absolute file path" + }, + "displayName": { + "type": "string", + "description": "User-facing display name for the attachment" + }, + "lineRange": { + "$ref": "#/definitions/PushAttachmentFileLineRange", + "description": "Optional line range to scope the attachment to a specific section of the file" } }, "required": [ - "remoteSteerable" + "type", + "path", + "displayName" ], "additionalProperties": false, - "description": "GitHub URL for the session and a flag indicating whether remote steering is enabled.", - "title": "RemoteEnableResult" + "description": "File attachment", + "title": "PushAttachmentFile" }, - "RemoteNotifySteerableChangedRequest": { + "PushAttachmentFileLineRange": { "type": "object", "properties": { - "remoteSteerable": { - "type": "boolean", - "description": "Whether the session now supports remote steering via GitHub. The runtime persists this as a `session.remote_steerable_changed` event so resume/replay sees the up-to-date capability." + "start": { + "type": "integer", + "exclusiveMinimum": 0, + "description": "Start line number (1-based)" + }, + "end": { + "type": "integer", + "exclusiveMinimum": 0, + "description": "End line number (1-based, inclusive)" } }, "required": [ - "remoteSteerable" + "start", + "end" ], "additionalProperties": false, - "description": "New remote-steerability state to persist as a `session.remote_steerable_changed` event.", - "title": "RemoteNotifySteerableChangedRequest" - }, - "RemoteNotifySteerableChangedResult": { - "type": "object", - "properties": {}, - "additionalProperties": false, - "description": "Persist a steerability change as a `session.remote_steerable_changed` event. Used by the host (CLI / SDK consumer) when it has just finished enabling or disabling steering on a remote exporter that the runtime does not directly own.", - "title": "RemoteNotifySteerableChangedResult" + "description": "Optional line range to scope the attachment to a specific section of the file", + "title": "PushAttachmentFileLineRange" }, - "RemoteSessionConnectionResult": { + "PushAttachmentGitHubReference": { "type": "object", "properties": { - "sessionId": { + "type": { "type": "string", - "description": "SDK session ID for the connected remote session." + "const": "github_reference", + "description": "Attachment type discriminator" }, - "metadata": { - "$ref": "#/definitions/ConnectedRemoteSessionMetadata", - "description": "Metadata for a connected remote session." + "number": { + "type": "integer", + "exclusiveMinimum": 0, + "description": "Issue, pull request, or discussion number" + }, + "title": { + "type": "string", + "description": "Title of the referenced item" + }, + "referenceType": { + "$ref": "#/definitions/PushAttachmentGitHubReferenceType", + "description": "Type of GitHub reference" + }, + "state": { + "type": "string", + "description": "Current state of the referenced item (e.g., open, closed, merged)" + }, + "url": { + "type": "string", + "description": "URL to the referenced item on GitHub" } }, "required": [ - "sessionId", - "metadata" + "type", + "number", + "title", + "referenceType", + "state", + "url" ], "additionalProperties": false, - "description": "Remote session connection result.", - "title": "RemoteSessionConnectionResult" + "description": "GitHub issue, pull request, or discussion reference", + "title": "PushAttachmentGitHubReference" }, - "RemoteSessionMode": { + "PushAttachmentGitHubReferenceType": { "type": "string", "enum": [ - "off", - "export", - "on" + "issue", + "pr", + "discussion" ], - "description": "Per-session remote mode. \"off\" disables remote, \"export\" exports session events to GitHub without enabling remote steering, \"on\" enables both export and remote steering.", - "title": "RemoteSessionMode", + "description": "Type of GitHub reference", + "title": "PushAttachmentGitHubReferenceType", "x-enumDescriptions": { - "off": "Disable remote session export and steering.", - "export": "Export session events to GitHub without enabling remote steering.", - "on": "Enable both remote session export and remote steering." + "issue": "GitHub issue reference.", + "pr": "GitHub pull request reference.", + "discussion": "GitHub discussion reference." } }, - "ScheduleEntry": { + "PushAttachmentSelection": { "type": "object", "properties": { - "id": { - "type": "integer", - "exclusiveMinimum": 0, - "description": "Sequential id assigned by the runtime within the session. Stable across resumes (rebuilt from the event log)." - }, - "intervalMs": { - "type": "integer", - "exclusiveMinimum": 0, - "description": "Interval between scheduled ticks, in milliseconds.", - "format": "duration" - }, - "prompt": { + "type": { "type": "string", - "description": "Prompt text that gets enqueued on every tick." + "const": "selection", + "description": "Attachment type discriminator" }, - "recurring": { - "type": "boolean", - "description": "Whether the schedule re-arms after each tick (`/every`) or fires once (`/after`)." + "filePath": { + "type": "string", + "description": "Absolute path to the file containing the selection" }, - "displayPrompt": { + "displayName": { "type": "string", - "description": "Display-only label for the prompt as shown in the UI (e.g. `/skill-name` for a skill-invocation schedule). The actual enqueued prompt is `prompt`." + "description": "User-facing display name for the selection" }, - "nextRunAt": { + "text": { "type": "string", - "format": "date-time", - "description": "ISO 8601 timestamp when the next tick is scheduled to fire." + "description": "The selected text content" + }, + "selection": { + "$ref": "#/definitions/PushAttachmentSelectionDetails", + "description": "Position range of the selection within the file" } }, "required": [ - "id", - "intervalMs", - "prompt", - "recurring", - "nextRunAt" + "type", + "filePath", + "displayName", + "text", + "selection" ], "additionalProperties": false, - "title": "ScheduleEntry", - "description": "Schema for the `ScheduleEntry` type." + "description": "Code selection attachment from an editor", + "title": "PushAttachmentSelection" }, - "ScheduleList": { + "PushAttachmentSelectionDetails": { "type": "object", "properties": { - "entries": { - "type": "array", - "items": { - "$ref": "#/definitions/ScheduleEntry" - }, - "description": "Active scheduled prompts, ordered by id." + "start": { + "$ref": "#/definitions/PushAttachmentSelectionDetailsStart", + "description": "Start position of the selection" + }, + "end": { + "$ref": "#/definitions/PushAttachmentSelectionDetailsEnd", + "description": "End position of the selection" } }, "required": [ - "entries" + "start", + "end" ], "additionalProperties": false, - "description": "Snapshot of the currently active recurring prompts for this session.", - "title": "ScheduleList" + "description": "Position range of the selection within the file", + "title": "PushAttachmentSelectionDetails" }, - "ScheduleStopRequest": { + "PushAttachmentSelectionDetailsEnd": { "type": "object", "properties": { - "id": { + "line": { "type": "integer", - "exclusiveMinimum": 0, - "description": "Id of the scheduled prompt to remove." + "minimum": 0, + "description": "End line number (0-based)" + }, + "character": { + "type": "integer", + "minimum": 0, + "description": "End character offset within the line (0-based)" } }, "required": [ - "id" + "line", + "character" ], "additionalProperties": false, - "description": "Identifier of the scheduled prompt to remove.", - "title": "ScheduleStopRequest" + "description": "End position of the selection", + "title": "PushAttachmentSelectionDetailsEnd" }, - "ScheduleStopResult": { + "PushAttachmentSelectionDetailsStart": { "type": "object", "properties": { - "entry": { - "$ref": "#/definitions/ScheduleEntry", - "description": "The removed entry, or omitted if no entry matched." + "line": { + "type": "integer", + "minimum": 0, + "description": "Start line number (0-based)" + }, + "character": { + "type": "integer", + "minimum": 0, + "description": "Start character offset within the line (0-based)" } }, + "required": [ + "line", + "character" + ], "additionalProperties": false, - "description": "Remove a scheduled prompt by id. The result entry is omitted if the id was unknown.", - "title": "ScheduleStopResult" + "description": "Start position of the selection", + "title": "PushAttachmentSelectionDetailsStart" }, - "SecretsAddFilterValuesRequest": { + "QueuedCommandHandled": { "type": "object", "properties": { - "values": { - "type": "array", - "items": { - "type": "string" - }, - "description": "Raw secret values to register for redaction" + "handled": { + "type": "boolean", + "const": true, + "description": "The host actually executed the queued command." + }, + "stopProcessingQueue": { + "type": "boolean", + "description": "When true, the runtime will not process subsequent queued commands until a new request comes in." } }, "required": [ - "values" + "handled" ], "additionalProperties": false, - "description": "Secret values to add to the redaction filter.", - "title": "SecretsAddFilterValuesRequest" + "title": "QueuedCommandHandled", + "description": "Schema for the `QueuedCommandHandled` type." }, - "SecretsAddFilterValuesResult": { + "QueuedCommandNotHandled": { "type": "object", "properties": { - "ok": { + "handled": { "type": "boolean", - "const": true, - "description": "Whether the values were successfully registered" + "const": false, + "description": "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)." } }, "required": [ - "ok" + "handled" ], "additionalProperties": false, - "description": "Confirmation that the secret values were registered.", - "title": "SecretsAddFilterValuesResult" + "title": "QueuedCommandNotHandled", + "description": "Schema for the `QueuedCommandNotHandled` type." }, - "SendAgentMode": { - "type": "string", - "enum": [ - "interactive", - "plan", - "autopilot", - "shell" + "QueuedCommandResult": { + "anyOf": [ + { + "$ref": "#/definitions/QueuedCommandHandled" + }, + { + "$ref": "#/definitions/QueuedCommandNotHandled" + } ], - "description": "The UI mode the agent was in when this message was sent. Defaults to the session's current mode.", - "title": "SendAgentMode", - "x-enumDescriptions": { - "interactive": "The agent is responding interactively to the user.", - "plan": "The agent is preparing a plan before making changes.", - "autopilot": "The agent is working autonomously toward task completion.", - "shell": "The agent is in shell-focused UI mode." - } + "description": "Result of the queued command execution.", + "title": "QueuedCommandResult" }, - "SendAttachmentsToMessageParams": { + "QueuePendingItems": { "type": "object", "properties": { - "instanceId": { - "type": "string", - "description": "Optional canvas instance binding the push for provenance. When supplied, the runtime resolves the canvas, verifies it is owned by the calling extension, and stamps canvasId/instanceId onto each extension_context entry. When omitted, no resolution runs and those fields stay unset on the attachment." + "kind": { + "$ref": "#/definitions/QueuePendingItemsKind", + "description": "Whether this item is a queued user message or a queued slash command / model change" }, - "attachments": { - "type": "array", - "items": { - "$ref": "#/definitions/PushAttachment" - }, - "description": "Attachments to push into the next user-message turn. extension_context entries take the slim shape; standard variants take their full AttachmentSchema shape." + "displayText": { + "type": "string", + "description": "Human-readable text to display for this queue entry in the UI" } }, "required": [ - "attachments" + "kind", + "displayText" ], "additionalProperties": false, - "description": "Parameters for session.extensions.sendAttachmentsToMessage.", - "title": "SendAttachmentsToMessageParams" + "title": "QueuePendingItems", + "description": "Schema for the `QueuePendingItems` type." }, - "SendMode": { + "QueuePendingItemsKind": { "type": "string", "enum": [ - "enqueue", - "immediate" + "message", + "command" ], - "description": "How to deliver the message. `enqueue` (default) appends to the message queue. `immediate` interjects during an in-progress turn.", - "title": "SendMode", + "description": "Whether this item is a queued user message or a queued slash command / model change", + "title": "QueuePendingItemsKind", "x-enumDescriptions": { - "enqueue": "Append the message to the normal session queue.", - "immediate": "Interject the message during the in-progress turn." + "message": "A queued user message.", + "command": "A queued slash command or model-change command." } }, - "SendRequest": { + "QueuePendingItemsResult": { "type": "object", "properties": { - "prompt": { - "type": "string", - "description": "The user message text" - }, - "displayPrompt": { - "type": "string", - "description": "If provided, this is shown in the timeline instead of `prompt`" - }, - "attachments": { + "items": { "type": "array", "items": { - "$ref": "#/definitions/Attachment", - "description": "A user message attachment — a file, directory, code selection, blob, GitHub reference, or extension-supplied context payload" + "$ref": "#/definitions/QueuePendingItems" }, - "description": "Optional attachments (files, directories, selections, blobs, GitHub references) to include with the message" - }, - "mode": { - "$ref": "#/definitions/SendMode", - "description": "How to deliver the message. `enqueue` (default) appends to the message queue. `immediate` interjects during an in-progress turn." - }, - "prepend": { - "type": "boolean", - "description": "If true, adds the message to the front of the queue instead of the end" - }, - "billable": { - "type": "boolean", - "description": "If false, this message will not trigger a Premium Request Unit charge. User messages default to billable." - }, - "requiredTool": { - "type": "string", - "description": "If set, the request will fail if the named tool is not available when this message is among the user messages at the start of the current exchange" - }, - "source": { - "description": "Optional provenance tag copied to the resulting user.message event. Supported values are `system`, `command-*`, and `schedule-*`.", - "visibility": "internal", - "x-opaque-json": true - }, - "agentMode": { - "$ref": "#/definitions/SendAgentMode", - "description": "The UI mode the agent was in when this message was sent. Defaults to the session's current mode." + "description": "Pending queued items in submission order. Includes user messages, queued slash commands, and queued model changes; omits internal system items." }, - "requestHeaders": { - "type": "object", - "additionalProperties": { + "steeringMessages": { + "type": "array", + "items": { "type": "string" }, - "description": "Custom HTTP headers to include in outbound model requests for this turn. Merged with session-level provider headers; per-turn headers augment and overwrite session-level headers with the same key." - }, - "traceparent": { - "type": "string", - "description": "W3C Trace Context traceparent header for distributed tracing of this agent turn" - }, - "tracestate": { - "type": "string", - "description": "W3C Trace Context tracestate header for distributed tracing" - }, - "wait": { + "description": "Display text for messages currently in the immediate steering queue (interjections sent during a running turn)." + } + }, + "required": [ + "items", + "steeringMessages" + ], + "additionalProperties": false, + "description": "Snapshot of the session's pending queued items and immediate-steering messages.", + "title": "QueuePendingItemsResult" + }, + "QueueRemoveMostRecentResult": { + "type": "object", + "properties": { + "removed": { "type": "boolean", - "description": "If true, await completion of the agentic loop for this message before returning. Defaults to false (fire-and-forget). When true, the result still contains the same `messageId`; the caller can rely on the agent having processed the message before the call resolves." + "description": "True if a user-facing pending item was removed (LIFO across both queues); false when no removable items remained." } }, "required": [ - "prompt" + "removed" ], "additionalProperties": false, - "description": "Parameters for sending a user message to the session", - "title": "SendRequest" + "description": "Indicates whether a user-facing pending item was removed.", + "title": "QueueRemoveMostRecentResult" }, - "SendResult": { + "ReasoningSummary": { + "type": "string", + "enum": [ + "none", + "concise", + "detailed" + ], + "description": "Reasoning summary mode to request for supported model clients", + "title": "ReasoningSummary", + "x-enumDescriptions": { + "none": "Do not request reasoning summaries from the model.", + "concise": "Request a concise summary of the model's reasoning.", + "detailed": "Request a detailed summary of the model's reasoning." + } + }, + "RegisterEventInterestParams": { "type": "object", "properties": { - "messageId": { + "eventType": { "type": "string", - "description": "Unique identifier assigned to the message" + "description": "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`, `user_input.requested`, `elicitation.requested`, `command.queued`, `exit_plan_mode.requested`." } }, "required": [ - "messageId" + "eventType" ], "additionalProperties": false, - "description": "Result of sending a user message", - "title": "SendResult" + "description": "Event type to register consumer interest for, used by runtime gating logic.", + "title": "RegisterEventInterestParams" }, - "ServerSkill": { + "RegisterEventInterestResult": { "type": "object", "properties": { - "name": { + "handle": { "type": "string", - "description": "Unique identifier for the skill" - }, - "description": { + "description": "Opaque handle for this registration. Pass to releaseInterest to release. Each call to registerInterest produces a fresh handle, even when the same eventType is registered multiple times." + } + }, + "required": [ + "handle" + ], + "additionalProperties": false, + "description": "Opaque handle representing an event-type interest registration.", + "title": "RegisterEventInterestResult" + }, + "RegisterExtensionToolsParams": { + "type": "object", + "properties": { + "sessionId": { "type": "string", - "description": "Description of what the skill does" - }, - "source": { - "$ref": "#/definitions/SkillSource", - "description": "Source location type (e.g., project, personal-copilot, plugin, builtin)" - }, - "userInvocable": { - "type": "boolean", - "description": "Whether the skill can be invoked by the user as a slash command" - }, - "enabled": { - "type": "boolean", - "description": "Whether the skill is currently enabled (based on global config)" + "description": "Session to register extension tools on." }, - "path": { - "type": "string", - "description": "Absolute path to the skill file" + "loader": { + "description": "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.", + "visibility": "internal", + "x-opaque-json": true }, - "projectPath": { - "type": "string", - "description": "The project path this skill belongs to (only for project/inherited skills)" + "options": { + "$ref": "#/definitions/SessionsRegisterExtensionToolsOnSessionOptions", + "description": "Optional registration options." } }, "required": [ - "name", - "description", - "source", - "userInvocable", - "enabled" + "sessionId", + "loader" ], "additionalProperties": false, - "title": "ServerSkill", - "description": "Schema for the `ServerSkill` type." + "description": "Params to attach an extension loader's tools to a session.", + "title": "RegisterExtensionToolsParams", + "visibility": "internal" }, - "ServerSkillList": { + "RegisterExtensionToolsResult": { "type": "object", "properties": { - "skills": { - "type": "array", - "items": { - "$ref": "#/definitions/ServerSkill" - }, - "description": "All discovered skills across all sources" + "unsubscribe": { + "description": "In-process unsubscribe function (CLI-only optimization). Marked internal: replaced by an explicit `extensions.unregister` RPC in the SDK migration.", + "visibility": "internal", + "x-opaque-json": true } }, "required": [ - "skills" + "unsubscribe" ], "additionalProperties": false, - "description": "Skills discovered across global and project sources.", - "title": "ServerSkillList" + "description": "Handle for releasing the extension tool registration.", + "title": "RegisterExtensionToolsResult", + "visibility": "internal" }, - "SessionAuthStatus": { + "ReleaseEventInterestParams": { "type": "object", "properties": { - "isAuthenticated": { - "type": "boolean", - "description": "Whether the session has resolved authentication" - }, - "authType": { - "$ref": "#/definitions/AuthInfoType", - "description": "Authentication type" - }, - "host": { - "type": "string", - "format": "uri", - "description": "Authentication host URL" - }, - "login": { - "type": "string", - "description": "Authenticated login/username, if available" - }, - "statusMessage": { - "type": "string", - "description": "Human-readable authentication status description" - }, - "copilotPlan": { + "handle": { "type": "string", - "description": "Copilot plan tier (e.g., individual_pro, business)" + "description": "Handle returned by a previous `registerInterest` call. Idempotent: releasing an unknown or already-released handle is a no-op (returns success). When the last outstanding handle for an event type is released, the runtime reverts to its 'no consumer' code path for that event type." } }, "required": [ - "isAuthenticated" + "handle" ], "additionalProperties": false, - "description": "Authentication status and account metadata for the session.", - "title": "SessionAuthStatus" + "description": "Opaque handle previously returned by `registerInterest` to release.", + "title": "ReleaseEventInterestParams" }, - "SessionBulkDeleteResult": { + "RemoteControlConfig": { "type": "object", "properties": { - "freedBytes": { - "type": "object", - "additionalProperties": { - "type": "integer", - "minimum": 0 - }, - "description": "Map of sessionId -> bytes freed by removing the session's workspace directory. Sessions whose deletion failed are omitted from this map (failures are logged on the server but not surfaced per-id; check the map for absent IDs to detect them)." + "remote": { + "type": "boolean", + "description": "Whether remote export should be enabled." + }, + "steerable": { + "type": "boolean", + "description": "Whether the MC session may steer the local session (write mode)." + }, + "explicit": { + "type": "boolean", + "description": "Whether the user explicitly requested remote (vs. implicit session-sync). Controls warning surfacing for missing-repo cases." + }, + "silent": { + "type": "boolean", + "description": "When true, suppresses timeline messages on successful setup." + }, + "taskId": { + "type": "string", + "description": "Existing Mission Control task ID to attach the exported session to." + }, + "existingMcSession": { + "$ref": "#/definitions/RemoteControlConfigExistingMcSession", + "description": "Reattach to an existing MC session without creating a new one." } }, "required": [ - "freedBytes" + "remote", + "steerable", + "explicit", + "silent" ], "additionalProperties": false, - "description": "Map of sessionId -> bytes freed by removing the session's workspace directory.", - "title": "SessionBulkDeleteResult" + "description": "Configuration for the runtime-managed remote-control singleton.", + "title": "RemoteControlConfig" }, - "SessionContext": { + "RemoteControlConfigExistingMcSession": { "type": "object", "properties": { - "cwd": { - "type": "string", - "description": "Most recent working directory for this session" - }, - "gitRoot": { - "type": "string", - "description": "Git repository root, if the cwd was inside a git repo" - }, - "repository": { + "mcSessionId": { "type": "string", - "description": "Repository slug in `owner/name` form, when known" - }, - "hostType": { - "$ref": "#/definitions/SessionContextHostType", - "description": "Repository host type" + "description": "Existing MC session ID to reattach to." }, - "branch": { + "mcTaskId": { "type": "string", - "description": "Active git branch" + "description": "Existing MC task ID for the reattached session." } }, "required": [ - "cwd" + "mcSessionId", + "mcTaskId" ], "additionalProperties": false, - "title": "SessionContext", - "description": "Schema for the `SessionContext` type." - }, - "SessionContextHostType": { - "type": "string", - "enum": [ - "github", - "ado" - ], - "description": "Repository host type", - "title": "SessionContextHostType", - "x-enumDescriptions": { - "github": "Session repository is hosted on GitHub.", - "ado": "Session repository is hosted on Azure DevOps." - } + "description": "Reattach to an existing MC session without creating a new one.", + "title": "RemoteControlConfigExistingMcSession" }, - "SessionContextInfo": { + "RemoteControlStatus": { "anyOf": [ { - "type": "object", - "properties": { - "modelName": { - "type": "string", - "description": "The model used for token counting" - }, - "systemTokens": { - "type": "integer", - "minimum": 0, - "description": "Tokens consumed by the system prompt" - }, - "conversationTokens": { - "type": "integer", - "minimum": 0, - "description": "Tokens consumed by user/assistant/tool messages" - }, - "toolDefinitionsTokens": { - "type": "integer", - "minimum": 0, - "description": "Tokens consumed by tool definitions sent to the model (excludes deferred tools)" - }, - "mcpToolsTokens": { - "type": "integer", - "minimum": 0, - "description": "Tokens consumed by MCP tool definitions (subset of toolDefinitionsTokens, excludes deferred tools)" - }, - "totalTokens": { - "type": "integer", - "minimum": 0, - "description": "Sum of system, conversation and tool-definition tokens" - }, - "promptTokenLimit": { - "type": "integer", - "minimum": 0, - "description": "Maximum prompt tokens allowed by the model (or DEFAULT_TOKEN_LIMIT if unspecified)" - }, - "compactionThreshold": { - "type": "integer", - "minimum": 0, - "description": "Token count at which background compaction starts (configurable percentage of promptTokenLimit)" - }, - "limit": { - "type": "integer", - "minimum": 0, - "description": "Total context limit for /context display: promptTokenLimit + outputTokenLimit (the model's full max_output_tokens reserved on top of the prompt budget)." - }, - "bufferTokens": { - "type": "integer", - "minimum": 0, - "description": "Output reserve plus tokens after the buffer-exhaustion blocking threshold (default 95%)" - } - }, - "required": [ - "modelName", - "systemTokens", - "conversationTokens", - "toolDefinitionsTokens", - "mcpToolsTokens", - "totalTokens", - "promptTokenLimit", - "compactionThreshold", - "limit", - "bufferTokens" - ], - "additionalProperties": false, - "description": "Token-usage breakdown for the session's current context window" + "$ref": "#/definitions/RemoteControlStatusOff", + "description": "Remote control is not connected." }, { - "type": "null" + "$ref": "#/definitions/RemoteControlStatusConnecting", + "description": "Remote control is in the middle of initial setup." + }, + { + "$ref": "#/definitions/RemoteControlStatusActive", + "description": "Remote control is connected to a local session." + }, + { + "$ref": "#/definitions/RemoteControlStatusError", + "description": "The last setup attempt failed. The singleton is otherwise off." } ], - "description": "Token breakdown for the current context window, or null if the session has not yet been initialized (no system prompt or tool metadata cached).", - "title": "SessionContextInfo" + "description": "State of the runtime-managed remote-control singleton.", + "title": "RemoteControlStatus" }, - "SessionEnrichMetadataResult": { + "RemoteControlStatusActive": { "type": "object", "properties": { - "sessions": { - "type": "array", - "items": { - "$ref": "#/definitions/SessionMetadata" - }, - "description": "Enriched records, with summary and context backfilled. Sessions confirmed empty and unnamed may be omitted." + "state": { + "type": "string", + "const": "active", + "description": "Remote control state tag: active." + }, + "attachedSessionId": { + "type": "string", + "description": "Session id remote control is pointed at." + }, + "frontendUrl": { + "type": "string", + "description": "MC frontend URL for this session, when known." + }, + "isSteerable": { + "type": "boolean", + "description": "Whether the MC session may steer this session." + }, + "promptManager": { + "description": "In-process prompt-manager handle (CLI-only optimization). Marked internal: this field is excluded from the public SDK surface. When the CLI migrates to a process-separated SDK, the same bidirectional prompt-routing handshake is expressed via dedicated remote-control RPCs (register/resolve) rather than a shared in-process object.", + "visibility": "internal", + "x-opaque-json": true } }, "required": [ - "sessions" + "state", + "attachedSessionId", + "isSteerable" ], "additionalProperties": false, - "description": "The enriched metadata records, with summary and context fields backfilled where available. Sessions confirmed empty and unnamed are omitted.", - "title": "SessionEnrichMetadataResult" + "description": "Remote control is connected to a local session.", + "title": "RemoteControlStatusActive" }, - "SessionFsAppendFileRequest": { + "RemoteControlStatusConnecting": { "type": "object", "properties": { - "path": { + "state": { "type": "string", - "description": "Path using SessionFs conventions" + "const": "connecting", + "description": "Remote control state tag: connecting." }, - "content": { + "attachedSessionId": { "type": "string", - "description": "Content to append" - }, - "mode": { - "type": "integer", - "minimum": 0, - "description": "Optional POSIX-style mode for newly created files" + "description": "Session id the connection is attaching to." } }, "required": [ - "path", - "content" + "state", + "attachedSessionId" ], "additionalProperties": false, - "description": "File path, content to append, and optional mode for the client-provided session filesystem.", - "title": "SessionFsAppendFileRequest" + "description": "Remote control is in the middle of initial setup.", + "title": "RemoteControlStatusConnecting" }, - "SessionFsError": { + "RemoteControlStatusError": { "type": "object", "properties": { - "code": { - "$ref": "#/definitions/SessionFsErrorCode", - "description": "Error classification" + "state": { + "type": "string", + "const": "error", + "description": "Remote control state tag: setup failed." }, - "message": { + "error": { "type": "string", - "description": "Free-form detail about the error, for logging/diagnostics" + "description": "Human-readable error message from the last setup attempt." + }, + "attachedSessionId": { + "type": "string", + "description": "Session id the failing setup attempt targeted, when known." } }, "required": [ - "code" + "state", + "error" ], "additionalProperties": false, - "description": "Describes a filesystem error.", - "title": "SessionFsError" - }, - "SessionFsErrorCode": { - "type": "string", - "enum": [ - "ENOENT", - "UNKNOWN" - ], - "description": "Error classification", - "title": "SessionFsErrorCode", - "x-enumDescriptions": { - "ENOENT": "The requested path does not exist.", - "UNKNOWN": "The filesystem operation failed for an unspecified reason." - } + "description": "The last setup attempt failed. The singleton is otherwise off.", + "title": "RemoteControlStatusError" }, - "SessionFsExistsRequest": { + "RemoteControlStatusOff": { "type": "object", "properties": { - "path": { + "state": { "type": "string", - "description": "Path using SessionFs conventions" + "const": "off", + "description": "Remote control state tag: not connected." } }, "required": [ - "path" + "state" ], "additionalProperties": false, - "description": "Path to test for existence in the client-provided session filesystem.", - "title": "SessionFsExistsRequest" + "description": "Remote control is not connected.", + "title": "RemoteControlStatusOff" }, - "SessionFsExistsResult": { + "RemoteControlStatusResult": { "type": "object", "properties": { - "exists": { - "type": "boolean", - "description": "Whether the path exists" + "status": { + "$ref": "#/definitions/RemoteControlStatus", + "description": "State of the runtime-managed remote-control singleton." } }, "required": [ - "exists" + "status" ], "additionalProperties": false, - "description": "Indicates whether the requested path exists in the client-provided session filesystem.", - "title": "SessionFsExistsResult" + "description": "Wrapper for the singleton's current status.", + "title": "RemoteControlStatusResult" }, - "SessionFsMkdirRequest": { + "RemoteControlStopResult": { "type": "object", "properties": { - "path": { - "type": "string", - "description": "Path using SessionFs conventions" + "status": { + "$ref": "#/definitions/RemoteControlStatus", + "description": "State of the runtime-managed remote-control singleton." }, - "recursive": { + "stopped": { "type": "boolean", - "description": "Create parent directories as needed" - }, - "mode": { - "type": "integer", - "minimum": 0, - "description": "Optional POSIX-style mode for newly created directories" + "description": "Whether the singleton was actually torn down by this call." } }, "required": [ - "path" + "status", + "stopped" ], "additionalProperties": false, - "description": "Directory path to create in the client-provided session filesystem, with options for recursive creation and POSIX mode.", - "title": "SessionFsMkdirRequest" + "description": "Outcome of a stopRemoteControl call.", + "title": "RemoteControlStopResult" }, - "SessionFsReaddirRequest": { + "RemoteControlTransferResult": { "type": "object", "properties": { - "path": { - "type": "string", - "description": "Path using SessionFs conventions" + "status": { + "$ref": "#/definitions/RemoteControlStatus", + "description": "State of the runtime-managed remote-control singleton." + }, + "transferred": { + "type": "boolean", + "description": "Whether the rebinding actually happened." } }, "required": [ - "path" + "status", + "transferred" ], "additionalProperties": false, - "description": "Directory path whose entries should be listed from the client-provided session filesystem.", - "title": "SessionFsReaddirRequest" + "description": "Outcome of a transferRemoteControl call.", + "title": "RemoteControlTransferResult" }, - "SessionFsReaddirResult": { + "RemoteEnableRequest": { "type": "object", "properties": { - "entries": { - "type": "array", - "items": { - "type": "string" - }, - "description": "Entry names in the directory" - }, - "error": { - "$ref": "#/definitions/SessionFsError", - "description": "Describes a filesystem error." + "mode": { + "$ref": "#/definitions/RemoteSessionMode", + "description": "Per-session remote mode. \"off\" disables remote, \"export\" exports session events to GitHub without enabling remote steering, \"on\" enables both export and remote steering." } }, - "required": [ - "entries" - ], "additionalProperties": false, - "description": "Names of entries in the requested directory, or a filesystem error if the read failed.", - "title": "SessionFsReaddirResult" + "description": "Optional remote session mode (\"off\", \"export\", or \"on\"); defaults to enabling both export and remote steering.", + "title": "RemoteEnableRequest" }, - "SessionFsReaddirWithTypesEntry": { + "RemoteEnableResult": { "type": "object", "properties": { - "name": { + "url": { "type": "string", - "description": "Entry name" + "format": "uri", + "description": "GitHub frontend URL for this session" }, - "type": { - "$ref": "#/definitions/SessionFsReaddirWithTypesEntryType", - "description": "Entry type" + "remoteSteerable": { + "type": "boolean", + "description": "Whether remote steering is enabled" } }, "required": [ - "name", - "type" + "remoteSteerable" ], "additionalProperties": false, - "title": "SessionFsReaddirWithTypesEntry", - "description": "Schema for the `SessionFsReaddirWithTypesEntry` type." - }, - "SessionFsReaddirWithTypesEntryType": { - "type": "string", - "enum": [ - "file", - "directory" - ], - "description": "Entry type", - "title": "SessionFsReaddirWithTypesEntryType", - "x-enumDescriptions": { - "file": "The entry is a file.", - "directory": "The entry is a directory." - } + "description": "GitHub URL for the session and a flag indicating whether remote steering is enabled.", + "title": "RemoteEnableResult" }, - "SessionFsReaddirWithTypesRequest": { + "RemoteNotifySteerableChangedRequest": { "type": "object", "properties": { - "path": { - "type": "string", - "description": "Path using SessionFs conventions" + "remoteSteerable": { + "type": "boolean", + "description": "Whether the session now supports remote steering via GitHub. The runtime persists this as a `session.remote_steerable_changed` event so resume/replay sees the up-to-date capability." } }, "required": [ - "path" + "remoteSteerable" ], "additionalProperties": false, - "description": "Directory path whose entries (with type information) should be listed from the client-provided session filesystem.", - "title": "SessionFsReaddirWithTypesRequest" + "description": "New remote-steerability state to persist as a `session.remote_steerable_changed` event.", + "title": "RemoteNotifySteerableChangedRequest" }, - "SessionFsReaddirWithTypesResult": { + "RemoteNotifySteerableChangedResult": { + "type": "object", + "properties": {}, + "additionalProperties": false, + "description": "Persist a steerability change as a `session.remote_steerable_changed` event. Used by the host (CLI / SDK consumer) when it has just finished enabling or disabling steering on a remote exporter that the runtime does not directly own.", + "title": "RemoteNotifySteerableChangedResult" + }, + "RemoteSessionConnectionResult": { "type": "object", "properties": { - "entries": { - "type": "array", - "items": { - "$ref": "#/definitions/SessionFsReaddirWithTypesEntry" - }, - "description": "Directory entries with type information" + "sessionId": { + "type": "string", + "description": "SDK session ID for the connected remote session." }, - "error": { - "$ref": "#/definitions/SessionFsError", - "description": "Describes a filesystem error." + "metadata": { + "$ref": "#/definitions/ConnectedRemoteSessionMetadata", + "description": "Metadata for a connected remote session." } }, "required": [ - "entries" + "sessionId", + "metadata" ], "additionalProperties": false, - "description": "Entries in the requested directory paired with file/directory type information, or a filesystem error if the read failed.", - "title": "SessionFsReaddirWithTypesResult" + "description": "Remote session connection result.", + "title": "RemoteSessionConnectionResult" }, - "SessionFsReadFileRequest": { + "RemoteSessionMetadataRepository": { "type": "object", "properties": { - "path": { + "owner": { "type": "string", - "description": "Path using SessionFs conventions" + "description": "Repository owner." + }, + "name": { + "type": "string", + "description": "Repository name." + }, + "branch": { + "type": "string", + "description": "Branch associated with the remote session." } }, "required": [ - "path" + "owner", + "name", + "branch" ], "additionalProperties": false, - "description": "Path of the file to read from the client-provided session filesystem.", - "title": "SessionFsReadFileRequest" + "description": "GitHub repository the remote session belongs to.", + "title": "RemoteSessionMetadataRepository" }, - "SessionFsReadFileResult": { + "RemoteSessionMetadataTaskType": { + "type": "string", + "enum": [ + "cca", + "cli" + ], + "description": "Whether the remote task originated from CCA or CLI `--remote`.", + "title": "RemoteSessionMetadataTaskType", + "x-enumDescriptions": { + "cca": "GitHub Copilot coding agent task.", + "cli": "CLI remote task." + } + }, + "RemoteSessionMetadataValue": { "type": "object", "properties": { - "content": { + "sessionId": { "type": "string", - "description": "File content as UTF-8 string" + "description": "Stable session identifier." }, - "error": { - "$ref": "#/definitions/SessionFsError", - "description": "Describes a filesystem error." + "startTime": { + "type": "string", + "description": "Session creation time as an ISO 8601 timestamp." + }, + "modifiedTime": { + "type": "string", + "description": "Last-modified time as an ISO 8601 timestamp." + }, + "summary": { + "type": "string", + "description": "Short summary of the session, when one has been derived." + }, + "name": { + "type": "string", + "description": "Optional human-friendly name set via /rename." + }, + "isRemote": { + "type": "boolean", + "const": true, + "description": "Always true for remote sessions." + }, + "context": { + "$ref": "#/definitions/SessionContext", + "description": "Most recent working directory context." + }, + "repository": { + "$ref": "#/definitions/RemoteSessionMetadataRepository", + "description": "GitHub repository the remote session belongs to." + }, + "remoteSessionIds": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Backing remote session IDs (most recent first)." + }, + "pullRequestNumber": { + "type": "integer", + "exclusiveMinimum": 0, + "description": "Pull request number associated with the session." + }, + "resourceId": { + "type": "string", + "description": "Original remote resource identifier (task ID or PR node ID)." + }, + "taskType": { + "$ref": "#/definitions/RemoteSessionMetadataTaskType", + "description": "Whether the remote task originated from CCA or CLI `--remote`." + }, + "staleAt": { + "type": "string", + "description": "Deadline (ISO 8601) at which a CLI remote session becomes stale without further heartbeats." + }, + "state": { + "type": "string", + "description": "Server-side task state returned by GitHub." } }, "required": [ - "content" + "sessionId", + "startTime", + "modifiedTime", + "isRemote", + "repository", + "remoteSessionIds" ], "additionalProperties": false, - "description": "File content as a UTF-8 string, or a filesystem error if the read failed.", - "title": "SessionFsReadFileResult" + "description": "Remote session metadata for the session to hand off (typically obtained from `sessions.list` with `source: \"remote\"`).", + "title": "RemoteSessionMetadataValue" }, - "SessionFsRenameRequest": { + "RemoteSessionMode": { + "type": "string", + "enum": [ + "off", + "export", + "on" + ], + "description": "Per-session remote mode. \"off\" disables remote, \"export\" exports session events to GitHub without enabling remote steering, \"on\" enables both export and remote steering.", + "title": "RemoteSessionMode", + "x-enumDescriptions": { + "off": "Disable remote session export and steering.", + "export": "Export session events to GitHub without enabling remote steering.", + "on": "Enable both remote session export and remote steering." + } + }, + "RemoteSessionRepository": { "type": "object", "properties": { - "src": { + "owner": { "type": "string", - "description": "Source path using SessionFs conventions" + "description": "Repository owner or organization login." }, - "dest": { + "name": { "type": "string", - "description": "Destination path using SessionFs conventions" + "description": "Repository name." + }, + "branch": { + "type": "string", + "description": "Optional branch associated with the remote session." } }, "required": [ - "src", - "dest" + "owner", + "name" ], "additionalProperties": false, - "description": "Source and destination paths for renaming or moving an entry in the client-provided session filesystem.", - "title": "SessionFsRenameRequest" + "description": "Repository context for the remote session.", + "title": "RemoteSessionRepository" }, - "SessionFsRmRequest": { + "SandboxConfig": { "type": "object", "properties": { - "path": { - "type": "string", - "description": "Path using SessionFs conventions" - }, - "recursive": { + "enabled": { "type": "boolean", - "description": "Remove directories and their contents recursively" + "description": "Whether sandboxing is enabled for the session." }, - "force": { + "userPolicy": { + "$ref": "#/definitions/SandboxConfigUserPolicy", + "description": "User-managed sandbox policy fragment merged into the auto-discovered base policy." + }, + "config": { + "type": "object", + "additionalProperties": {}, + "description": "Raw `ContainerConfig` (per `@microsoft/mxc-sdk`) passed directly to `spawnSandboxFromConfig`, bypassing policy merging.", + "x-opaque-json": true + }, + "addCurrentWorkingDirectory": { "type": "boolean", - "description": "Ignore errors if the path does not exist" + "description": "Whether to auto-add the current working directory to readwritePaths. Default: true." } }, "required": [ - "path" + "enabled" ], "additionalProperties": false, - "description": "Path to remove from the client-provided session filesystem, with options for recursive removal and force.", - "title": "SessionFsRmRequest" + "description": "Resolved sandbox configuration.", + "title": "SandboxConfig", + "stability": "experimental" }, - "SessionFsSetProviderCapabilities": { + "SandboxConfigUserPolicy": { "type": "object", "properties": { - "sqlite": { - "type": "boolean", - "description": "Whether the provider supports SQLite query/exists operations" + "filesystem": { + "$ref": "#/definitions/SandboxConfigUserPolicyFilesystem", + "description": "Filesystem rules to merge into the base policy." + }, + "network": { + "$ref": "#/definitions/SandboxConfigUserPolicyNetwork", + "description": "Network rules to merge into the base policy." + }, + "experimental": { + "$ref": "#/definitions/SandboxConfigUserPolicyExperimental", + "description": "Platform-specific experimental policy fields." } }, "additionalProperties": false, - "description": "Optional capabilities declared by the provider", - "title": "SessionFsSetProviderCapabilities" + "description": "User-managed sandbox policy fragment merged into the auto-discovered base policy.", + "title": "SandboxConfigUserPolicy" }, - "SessionFsSetProviderConventions": { - "type": "string", - "enum": [ - "windows", - "posix" - ], - "description": "Path conventions used by this filesystem", - "title": "SessionFsSetProviderConventions", - "x-enumDescriptions": { - "windows": "Paths use Windows path conventions.", - "posix": "Paths use POSIX path conventions." - } - }, - "SessionFsSetProviderRequest": { - "type": "object", - "properties": { - "initialCwd": { - "type": "string", - "description": "Initial working directory for sessions" - }, - "sessionStatePath": { - "type": "string", - "description": "Path within each session's SessionFs where the runtime stores files for that session" - }, - "conventions": { - "$ref": "#/definitions/SessionFsSetProviderConventions", - "description": "Path conventions used by this filesystem" - }, - "capabilities": { - "$ref": "#/definitions/SessionFsSetProviderCapabilities", - "description": "Optional capabilities declared by the provider" - } - }, - "required": [ - "initialCwd", - "sessionStatePath", - "conventions" - ], - "additionalProperties": false, - "description": "Initial working directory, session-state path layout, and path conventions used to register the calling SDK client as the session filesystem provider.", - "title": "SessionFsSetProviderRequest" - }, - "SessionFsSetProviderResult": { + "SandboxConfigUserPolicyExperimental": { "type": "object", "properties": { - "success": { - "type": "boolean", - "description": "Whether the provider was set successfully" + "seatbelt": { + "$ref": "#/definitions/SandboxConfigUserPolicyExperimentalSeatbelt", + "description": "macOS seatbelt experimental options." } }, - "required": [ - "success" - ], "additionalProperties": false, - "description": "Indicates whether the calling client was registered as the session filesystem provider.", - "title": "SessionFsSetProviderResult" + "description": "Platform-specific experimental policy fields.", + "title": "SandboxConfigUserPolicyExperimental" }, - "SessionFsSqliteExistsResult": { + "SandboxConfigUserPolicyExperimentalSeatbelt": { "type": "object", "properties": { - "exists": { + "keychainAccess": { "type": "boolean", - "description": "Whether the session database already exists" + "description": "Whether the macOS seatbelt profile may access the keychain." } }, - "required": [ - "exists" - ], "additionalProperties": false, - "description": "Indicates whether the per-session SQLite database already exists.", - "title": "SessionFsSqliteExistsResult" + "description": "macOS seatbelt experimental options.", + "title": "SandboxConfigUserPolicyExperimentalSeatbelt" }, - "SessionFsSqliteQueryRequest": { + "SandboxConfigUserPolicyFilesystem": { "type": "object", "properties": { - "query": { - "type": "string", - "description": "SQL query to execute" + "readwritePaths": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Paths granted read/write access." }, - "queryType": { - "$ref": "#/definitions/SessionFsSqliteQueryType", - "description": "How to execute the query: 'exec' for DDL/multi-statement (no results), 'query' for SELECT (returns rows), 'run' for INSERT/UPDATE/DELETE (returns rowsAffected)" + "readonlyPaths": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Paths granted read-only access." }, - "params": { - "type": "object", - "additionalProperties": { - "type": [ - "string", - "number", - "null" - ], - "x-opaque-json": true + "deniedPaths": { + "type": "array", + "items": { + "type": "string" }, - "description": "Optional named bind parameters" + "description": "Paths explicitly denied." + }, + "clearPolicyOnExit": { + "type": "boolean", + "description": "Whether to clear the policy when the session exits." } }, - "required": [ - "query", - "queryType" - ], "additionalProperties": false, - "description": "SQL query, query type, and optional bind parameters for executing a SQLite query against the per-session database.", - "title": "SessionFsSqliteQueryRequest" + "description": "Filesystem rules to merge into the base policy.", + "title": "SandboxConfigUserPolicyFilesystem" }, - "SessionFsSqliteQueryResult": { + "SandboxConfigUserPolicyNetwork": { "type": "object", "properties": { - "rows": { + "allowOutbound": { + "type": "boolean", + "description": "Whether outbound network traffic is allowed at all." + }, + "allowLocalNetwork": { + "type": "boolean", + "description": "Whether traffic to local/loopback addresses is allowed." + }, + "allowedHosts": { "type": "array", "items": { - "type": "object", - "additionalProperties": { - "x-opaque-json": true - } + "type": "string" }, - "description": "For SELECT: array of row objects. For others: empty array." + "description": "Hosts allowed in addition to the base policy." }, - "columns": { + "blockedHosts": { "type": "array", "items": { "type": "string" }, - "description": "Column names from the result set" + "description": "Hosts explicitly blocked." + } + }, + "additionalProperties": false, + "description": "Network rules to merge into the base policy.", + "title": "SandboxConfigUserPolicyNetwork" + }, + "ScheduleEntry": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "exclusiveMinimum": 0, + "description": "Sequential id assigned by the runtime within the session. Stable across resumes (rebuilt from the event log)." }, - "rowsAffected": { + "intervalMs": { "type": "integer", - "minimum": 0, - "description": "Number of rows affected (for INSERT/UPDATE/DELETE)" + "exclusiveMinimum": 0, + "description": "Interval between scheduled ticks, in milliseconds (relative-interval schedules).", + "format": "duration" }, - "lastInsertRowid": { + "cron": { + "type": "string", + "description": "5-field cron expression for a recurring calendar schedule, evaluated in `tz`." + }, + "tz": { + "type": "string", + "description": "IANA timezone the `cron` expression is evaluated in." + }, + "at": { "type": "integer", - "description": "SQLite last_insert_rowid() value for INSERT." + "exclusiveMinimum": 0, + "description": "Absolute fire time (epoch milliseconds) for a one-shot calendar schedule." }, - "error": { - "$ref": "#/definitions/SessionFsError", - "description": "Describes a filesystem error." + "prompt": { + "type": "string", + "description": "Prompt text that gets enqueued on every tick." + }, + "recurring": { + "type": "boolean", + "description": "Whether the schedule re-arms after each tick (`/every`) or fires once (`/after`)." + }, + "displayPrompt": { + "type": "string", + "description": "Display-only label for the prompt as shown in the UI (e.g. `/skill-name` for a skill-invocation schedule). The actual enqueued prompt is `prompt`." + }, + "nextRunAt": { + "type": "string", + "format": "date-time", + "description": "ISO 8601 timestamp when the next tick is scheduled to fire." } }, "required": [ - "rows", - "columns", - "rowsAffected" + "id", + "prompt", + "recurring", + "nextRunAt" ], "additionalProperties": false, - "description": "Query results including rows, columns, and rows affected, or a filesystem error if execution failed.", - "title": "SessionFsSqliteQueryResult" - }, - "SessionFsSqliteQueryType": { - "type": "string", - "enum": [ - "exec", - "query", - "run" - ], - "description": "How to execute the query: 'exec' for DDL/multi-statement (no results), 'query' for SELECT (returns rows), 'run' for INSERT/UPDATE/DELETE (returns rowsAffected)", - "title": "SessionFsSqliteQueryType", - "x-enumDescriptions": { - "exec": "Execute DDL or multi-statement SQL without returning rows.", - "query": "Execute a SELECT-style query and return rows.", - "run": "Execute INSERT, UPDATE, or DELETE SQL and return affected-row metadata." - } + "title": "ScheduleEntry", + "description": "Schema for the `ScheduleEntry` type." }, - "SessionFsStatRequest": { + "ScheduleList": { "type": "object", "properties": { - "path": { - "type": "string", - "description": "Path using SessionFs conventions" + "entries": { + "type": "array", + "items": { + "$ref": "#/definitions/ScheduleEntry" + }, + "description": "Active scheduled prompts, ordered by id." } }, "required": [ - "path" + "entries" ], "additionalProperties": false, - "description": "Path whose metadata should be returned from the client-provided session filesystem.", - "title": "SessionFsStatRequest" + "description": "Snapshot of the currently active recurring prompts for this session.", + "title": "ScheduleList" }, - "SessionFsStatResult": { + "ScheduleStopRequest": { "type": "object", "properties": { - "isFile": { - "type": "boolean", - "description": "Whether the path is a file" - }, - "isDirectory": { - "type": "boolean", - "description": "Whether the path is a directory" - }, - "size": { + "id": { "type": "integer", - "minimum": 0, - "description": "File size in bytes" - }, - "mtime": { - "type": "string", - "format": "date-time", - "description": "ISO 8601 timestamp of last modification" - }, - "birthtime": { - "type": "string", - "format": "date-time", - "description": "ISO 8601 timestamp of creation" - }, - "error": { - "$ref": "#/definitions/SessionFsError", - "description": "Describes a filesystem error." + "exclusiveMinimum": 0, + "description": "Id of the scheduled prompt to remove." } }, "required": [ - "isFile", - "isDirectory", - "size", - "mtime", - "birthtime" + "id" ], "additionalProperties": false, - "description": "Filesystem metadata for the requested path, or a filesystem error if the stat failed.", - "title": "SessionFsStatResult" + "description": "Identifier of the scheduled prompt to remove.", + "title": "ScheduleStopRequest" }, - "SessionFsWriteFileRequest": { + "ScheduleStopResult": { "type": "object", "properties": { - "path": { - "type": "string", - "description": "Path using SessionFs conventions" - }, - "content": { - "type": "string", - "description": "Content to write" - }, - "mode": { - "type": "integer", - "minimum": 0, - "description": "Optional POSIX-style mode for newly created files" + "entry": { + "$ref": "#/definitions/ScheduleEntry", + "description": "The removed entry, or omitted if no entry matched." } }, - "required": [ - "path", - "content" - ], "additionalProperties": false, - "description": "File path, content to write, and optional mode for the client-provided session filesystem.", - "title": "SessionFsWriteFileRequest" + "description": "Remove a scheduled prompt by id. The result entry is omitted if the id was unknown.", + "title": "ScheduleStopResult" }, - "SessionInstalledPlugin": { + "SecretsAddFilterValuesRequest": { "type": "object", "properties": { - "name": { - "type": "string", - "description": "Plugin name" - }, - "marketplace": { - "type": "string", - "description": "Marketplace the plugin came from (empty string for direct repo installs)" - }, - "version": { - "type": "string", - "description": "Installed version, if known" - }, - "installed_at": { - "type": "string", - "description": "Installation timestamp (ISO-8601)" - }, - "enabled": { + "values": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Raw secret values to register for redaction" + } + }, + "required": [ + "values" + ], + "additionalProperties": false, + "description": "Secret values to add to the redaction filter.", + "title": "SecretsAddFilterValuesRequest" + }, + "SecretsAddFilterValuesResult": { + "type": "object", + "properties": { + "ok": { "type": "boolean", - "description": "Whether the plugin is currently enabled" - }, - "cache_path": { - "type": "string", - "description": "Path where the plugin is cached locally" - }, - "source": { - "$ref": "#/definitions/SessionInstalledPluginSource", - "description": "Source descriptor for direct repo installs (when marketplace is empty)" + "const": true, + "description": "Whether the values were successfully registered" } }, "required": [ - "name", - "marketplace", - "installed_at", - "enabled" + "ok" ], "additionalProperties": false, - "title": "SessionInstalledPlugin", - "description": "Schema for the `SessionInstalledPlugin` type." + "description": "Confirmation that the secret values were registered.", + "title": "SecretsAddFilterValuesResult" }, - "SessionInstalledPluginSource": { - "anyOf": [ - { + "SendAgentMode": { + "type": "string", + "enum": [ + "interactive", + "plan", + "autopilot", + "shell" + ], + "description": "The UI mode the agent was in when this message was sent. Defaults to the session's current mode.", + "title": "SendAgentMode", + "x-enumDescriptions": { + "interactive": "The agent is responding interactively to the user.", + "plan": "The agent is preparing a plan before making changes.", + "autopilot": "The agent is working autonomously toward task completion.", + "shell": "The agent is in shell-focused UI mode." + } + }, + "SendAttachmentsToMessageParams": { + "type": "object", + "properties": { + "instanceId": { "type": "string", - "pattern": "^[^/]+\\/[^/]+$" - }, - { - "$ref": "#/definitions/SessionInstalledPluginSourceGitHub" - }, - { - "$ref": "#/definitions/SessionInstalledPluginSourceUrl" + "description": "Optional canvas instance binding the push for provenance. When supplied, the runtime resolves the canvas, verifies it is owned by the calling extension, and stamps canvasId/instanceId onto each extension_context entry. When omitted, no resolution runs and those fields stay unset on the attachment." }, - { - "$ref": "#/definitions/SessionInstalledPluginSourceLocal" + "attachments": { + "type": "array", + "items": { + "$ref": "#/definitions/PushAttachment" + }, + "description": "Attachments to push into the next user-message turn. extension_context entries take the slim shape; standard variants take their full AttachmentSchema shape." } + }, + "required": [ + "attachments" ], - "description": "Source descriptor for direct repo installs (when marketplace is empty)", - "title": "SessionInstalledPluginSource", - "x-opaque-json": true, - "stability": "experimental" + "additionalProperties": false, + "description": "Parameters for session.extensions.sendAttachmentsToMessage.", + "title": "SendAttachmentsToMessageParams" }, - "SessionInstalledPluginSourceGitHub": { + "SendMode": { + "type": "string", + "enum": [ + "enqueue", + "immediate" + ], + "description": "How to deliver the message. `enqueue` (default) appends to the message queue. `immediate` interjects during an in-progress turn.", + "title": "SendMode", + "x-enumDescriptions": { + "enqueue": "Append the message to the normal session queue.", + "immediate": "Interject the message during the in-progress turn." + } + }, + "SendRequest": { "type": "object", "properties": { + "prompt": { + "type": "string", + "description": "The user message text" + }, + "displayPrompt": { + "type": "string", + "description": "If provided, this is shown in the timeline instead of `prompt`" + }, + "attachments": { + "type": "array", + "items": { + "$ref": "#/definitions/Attachment", + "description": "A user message attachment — a file, directory, code selection, blob, GitHub reference, or extension-supplied context payload" + }, + "description": "Optional attachments (files, directories, selections, blobs, GitHub references) to include with the message" + }, + "mode": { + "$ref": "#/definitions/SendMode", + "description": "How to deliver the message. `enqueue` (default) appends to the message queue. `immediate` interjects during an in-progress turn." + }, + "prepend": { + "type": "boolean", + "description": "If true, adds the message to the front of the queue instead of the end" + }, + "billable": { + "type": "boolean", + "description": "If false, this message will not trigger a Premium Request Unit charge. User messages default to billable." + }, + "requiredTool": { + "type": "string", + "description": "If set, the request will fail if the named tool is not available when this message is among the user messages at the start of the current exchange" + }, "source": { "type": "string", - "const": "github", - "description": "Constant value. Always \"github\"." + "pattern": "^(system|command-.*|schedule-\\d+)$", + "description": "Optional provenance tag copied to the resulting user.message event. Must match one of three forms: the literal `system`, `command-` for messages originating from a command (e.g. slash command, Mission Control command), or `schedule-` for messages originating from a scheduled job.", + "visibility": "internal" }, - "repo": { + "agentMode": { + "$ref": "#/definitions/SendAgentMode", + "description": "The UI mode the agent was in when this message was sent. Defaults to the session's current mode." + }, + "requestHeaders": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "Custom HTTP headers to include in outbound model requests for this turn. Merged with session-level provider headers; per-turn headers augment and overwrite session-level headers with the same key." + }, + "traceparent": { "type": "string", - "pattern": "^[^/]+\\/[^/]+$" + "description": "W3C Trace Context traceparent header for distributed tracing of this agent turn" }, - "ref": { - "type": "string" + "tracestate": { + "type": "string", + "description": "W3C Trace Context tracestate header for distributed tracing" }, - "path": { - "type": "string" + "wait": { + "type": "boolean", + "description": "If true, await completion of the agentic loop for this message before returning. Defaults to false (fire-and-forget). When true, the result still contains the same `messageId`; the caller can rely on the agent having processed the message before the call resolves." } }, "required": [ - "source", - "repo" + "prompt" ], "additionalProperties": false, - "title": "SessionInstalledPluginSourceGitHub", - "description": "Schema for the `SessionInstalledPluginSourceGitHub` type." + "description": "Parameters for sending a user message to the session", + "title": "SendRequest" }, - "SessionInstalledPluginSourceLocal": { + "SendResult": { "type": "object", "properties": { - "source": { + "messageId": { "type": "string", - "const": "local", - "description": "Constant value. Always \"local\"." - }, - "path": { - "type": "string" + "description": "Unique identifier assigned to the message" } }, "required": [ - "source", - "path" + "messageId" ], "additionalProperties": false, - "title": "SessionInstalledPluginSourceLocal", - "description": "Schema for the `SessionInstalledPluginSourceLocal` type." + "description": "Result of sending a user message", + "title": "SendResult" }, - "SessionInstalledPluginSourceUrl": { + "ServerAgentList": { "type": "object", "properties": { - "source": { - "type": "string", - "const": "url", - "description": "Constant value. Always \"url\"." - }, - "url": { - "type": "string", - "format": "uri" - }, - "ref": { - "type": "string" - }, - "path": { - "type": "string" + "agents": { + "type": "array", + "items": { + "$ref": "#/definitions/AgentInfo" + }, + "description": "All discovered agents across all sources" } }, "required": [ - "source", - "url" + "agents" ], "additionalProperties": false, - "title": "SessionInstalledPluginSourceUrl", - "description": "Schema for the `SessionInstalledPluginSourceUrl` type." + "description": "Agents discovered across user, project, plugin, and remote sources.", + "title": "ServerAgentList" }, - "SessionList": { + "ServerInstructionSourceList": { "type": "object", "properties": { - "sessions": { + "sources": { "type": "array", "items": { - "$ref": "#/definitions/SessionMetadata" + "$ref": "#/definitions/InstructionSource" }, - "description": "Sessions ordered most-recently-modified first" + "description": "All discovered instruction sources" } }, "required": [ - "sessions" + "sources" ], "additionalProperties": false, - "description": "Persisted sessions matching the filter, ordered most-recently-modified first.", - "title": "SessionList" + "description": "Instruction sources discovered across user, repository, and plugin sources.", + "title": "ServerInstructionSourceList" }, - "SessionListFilter": { + "ServerSkill": { "type": "object", "properties": { - "cwd": { + "name": { "type": "string", - "description": "Match sessions whose context.cwd equals this value" + "description": "Unique identifier for the skill" }, - "gitRoot": { + "description": { "type": "string", - "description": "Match sessions whose context.gitRoot equals this value" + "description": "Description of what the skill does" }, - "repository": { - "type": "string", - "description": "Match sessions whose context.repository equals this value" + "source": { + "$ref": "#/definitions/SkillSource", + "description": "Source location type (e.g., project, personal-copilot, plugin, builtin)" }, - "branch": { - "type": "string", - "description": "Match sessions whose context.branch equals this value" + "userInvocable": { + "type": "boolean", + "description": "Whether the skill can be invoked by the user as a slash command" + }, + "enabled": { + "type": "boolean", + "description": "Whether the skill is currently enabled (based on global config)" + }, + "path": { + "type": "string", + "description": "Absolute path to the skill file" + }, + "projectPath": { + "type": "string", + "description": "The project path this skill belongs to (only for project/inherited skills)" } }, + "required": [ + "name", + "description", + "source", + "userInvocable", + "enabled" + ], "additionalProperties": false, - "description": "Optional filter applied to the returned sessions", - "title": "SessionListFilter" + "title": "ServerSkill", + "description": "Schema for the `ServerSkill` type." }, - "SessionLoadDeferredRepoHooksResult": { + "ServerSkillList": { "type": "object", "properties": { - "startupPrompts": { + "skills": { "type": "array", "items": { - "type": "string" + "$ref": "#/definitions/ServerSkill" }, - "description": "Repo-level startup prompts queued from repo hook configs. Empty on resume, when no repo configs were pending, or when disableAllHooks is set." - }, - "hookCount": { - "type": "integer", - "minimum": 0, - "description": "Total hook command count (user + plugin + repo) loaded for the session by this call. Captured atomically with startupPrompts so callers don't need to read a separate counter." + "description": "All discovered skills across all sources" } }, "required": [ - "startupPrompts", - "hookCount" + "skills" ], "additionalProperties": false, - "description": "Queued repo-level startup prompts and the total hook command count after loading.", - "title": "SessionLoadDeferredRepoHooksResult" + "description": "Skills discovered across global and project sources.", + "title": "ServerSkillList" }, - "SessionLogLevel": { - "type": "string", - "enum": [ - "info", - "warning", - "error" + "SessionActivity": { + "type": "object", + "properties": { + "abortable": { + "type": "boolean", + "description": "Whether an in-flight operation can currently be aborted." + }, + "hasActiveWork": { + "type": "boolean", + "description": "Whether the session currently has active work, including running turns or tasks." + } + }, + "required": [ + "abortable", + "hasActiveWork" ], - "description": "Log severity level. Determines how the message is displayed in the timeline. Defaults to \"info\".", - "title": "SessionLogLevel", - "x-enumDescriptions": { - "info": "Informational message.", - "warning": "Warning message that may require attention.", - "error": "Error message describing a failure." - } + "additionalProperties": false, + "description": "Current activity flags for the session.", + "title": "SessionActivity" }, - "SessionMetadata": { + "SessionAuthStatus": { "type": "object", "properties": { - "sessionId": { - "type": "string", - "description": "Stable session identifier" - }, - "startTime": { - "type": "string", - "description": "Session creation time as an ISO 8601 timestamp" + "isAuthenticated": { + "type": "boolean", + "description": "Whether the session has resolved authentication" }, - "modifiedTime": { - "type": "string", - "description": "Last-modified time of the session's persisted state, as ISO 8601" + "authType": { + "$ref": "#/definitions/AuthInfoType", + "description": "Authentication type" }, - "summary": { + "host": { "type": "string", - "description": "Short summary of the session, when one has been derived" + "format": "uri", + "description": "Authentication host URL" }, - "name": { + "login": { "type": "string", - "description": "Optional human-friendly name set via /rename" + "description": "Authenticated login/username, if available" }, - "clientName": { + "statusMessage": { "type": "string", - "description": "Runtime client name that created/last resumed this session" - }, - "isRemote": { - "type": "boolean", - "description": "True for remote (GitHub) sessions; false for local" - }, - "isDetached": { - "type": "boolean", - "description": "True for detached maintenance sessions that should be hidden from normal resume lists." - }, - "context": { - "$ref": "#/definitions/SessionContext", - "description": "Schema for the `SessionContext` type." + "description": "Human-readable authentication status description" }, - "mcTaskId": { + "copilotPlan": { "type": "string", - "description": "GitHub task ID, when this local session is bound to one. Only present for local sessions exported to remote control." + "description": "Copilot plan tier (e.g., individual_pro, business)" } }, "required": [ - "sessionId", - "startTime", - "modifiedTime", - "isRemote" + "isAuthenticated" ], "additionalProperties": false, - "title": "SessionMetadata", - "description": "Schema for the `SessionMetadata` type." + "description": "Authentication status and account metadata for the session.", + "title": "SessionAuthStatus" }, - "SessionMetadataSnapshot": { + "SessionBulkDeleteResult": { "type": "object", "properties": { - "sessionId": { - "type": "string", - "description": "The unique identifier of the session" - }, - "startTime": { - "type": "string", - "format": "date-time", - "description": "ISO 8601 timestamp of when the session started" - }, - "modifiedTime": { - "type": "string", - "format": "date-time", - "description": "ISO 8601 timestamp of when the session's persisted state was last modified on disk. For new sessions, equals startTime. For resumed sessions, reflects the previous modification time at construction." - }, - "isRemote": { - "type": "boolean", - "description": "Whether this is a remote session (i.e., one whose runtime executes elsewhere and is steered through this process)" - }, - "alreadyInUse": { - "type": "boolean", - "description": "True when the session was detected to be in use by another process at construction time. Local consumers may surface a confirmation prompt before fully attaching. Always false for new sessions." - }, - "workspacePath": { - "type": [ - "string", - "null" - ], - "description": "Absolute path to the session's workspace directory on disk, or null if the session has no associated workspace" - }, - "initialName": { - "type": "string", - "description": "User-provided name supplied at session construction (via `--name`), if any. Immutable after construction." - }, - "clientName": { + "freedBytes": { + "type": "object", + "additionalProperties": { + "type": "integer", + "minimum": 0 + }, + "description": "Map of sessionId -> bytes freed by removing the session's workspace directory. Sessions whose deletion failed are omitted from this map (failures are logged on the server but not surfaced per-id; check the map for absent IDs to detect them)." + } + }, + "required": [ + "freedBytes" + ], + "additionalProperties": false, + "description": "Map of sessionId -> bytes freed by removing the session's workspace directory.", + "title": "SessionBulkDeleteResult" + }, + "SessionCapability": { + "type": "string", + "enum": [ + "tui-hints", + "plan-mode", + "memory", + "cli-documentation", + "ask-user", + "interactive-mode", + "system-notifications", + "elicitation", + "session-store", + "mcp-apps", + "canvas-renderer" + ], + "description": "Session capability enabled for this session", + "title": "SessionCapability", + "x-enumDescriptions": { + "tui-hints": "TUI-specific prompt hints such as keyboard shortcuts.", + "plan-mode": "Plan-mode handling and instructions.", + "memory": "Memory tool and memories prompt section.", + "cli-documentation": "Copilot CLI documentation tool and prompt section.", + "ask-user": "Interactive ask_user tool support.", + "interactive-mode": "Interactive CLI identity and behavior.", + "system-notifications": "Automatic hidden system notifications.", + "elicitation": "SDK elicitation support.", + "session-store": "Cross-session history tools and session-store SQL prompt/tool metadata.", + "mcp-apps": "MCP Apps UI passthrough.", + "canvas-renderer": "Host-provided canvas rendering support." + } + }, + "SessionContext": { + "type": "object", + "properties": { + "cwd": { "type": "string", - "description": "Runtime client name associated with the session (telemetry identifier)." - }, - "remoteMetadata": { - "$ref": "#/definitions/MetadataSnapshotRemoteMetadata", - "description": "Remote-session-specific metadata. Populated only when `isRemote` is true. Fields are immutable for the lifetime of the session." + "description": "Most recent working directory for this session" }, - "summary": { + "gitRoot": { "type": "string", - "description": "Short human-readable summary of the session, if known. Omitted when no summary has been generated." + "description": "Git repository root, if the cwd was inside a git repo" }, - "workingDirectory": { + "repository": { "type": "string", - "description": "Absolute path to the session's current working directory" + "description": "Repository slug in `owner/name` form, when known" }, - "currentMode": { - "$ref": "#/definitions/MetadataSnapshotCurrentMode", - "description": "The current agent mode for this session (e.g., 'interactive', 'plan', 'autopilot')" + "hostType": { + "$ref": "#/definitions/SessionContextHostType", + "description": "Repository host type" }, - "selectedModel": { + "branch": { "type": "string", - "description": "Currently selected model identifier, if any" - }, - "workspace": { - "$ref": "#/definitions/WorkspaceSummary", - "description": "Public-facing workspace metadata for this session, or null if the session has no associated workspace. Excludes runtime-internal fields (GitHub IDs, summary count, internal flags)." + "description": "Active git branch" } }, "required": [ - "sessionId", - "startTime", - "modifiedTime", - "isRemote", - "alreadyInUse", - "workspacePath", - "workingDirectory", - "currentMode", - "workspace" + "cwd" ], "additionalProperties": false, - "description": "Point-in-time snapshot of slow-changing session identifier and state fields", - "title": "SessionMetadataSnapshot" + "description": "Pre-resolved working-directory context for session startup.", + "title": "SessionContext" }, - "SessionMode": { + "SessionContextHostType": { "type": "string", "enum": [ - "interactive", - "plan", - "autopilot" + "github", + "ado" ], - "description": "The session mode the agent is operating in", - "title": "SessionMode", + "description": "Repository host type", + "title": "SessionContextHostType", "x-enumDescriptions": { - "interactive": "The agent is responding interactively to the user.", - "plan": "The agent is preparing a plan before making changes.", - "autopilot": "The agent is working autonomously toward task completion." + "github": "Session repository is hosted on GitHub.", + "ado": "Session repository is hosted on Azure DevOps." } }, - "SessionModelList": { - "type": "object", - "properties": { - "list": { + "SessionContextInfo": { + "anyOf": [ + { + "type": "object", + "properties": { + "modelName": { + "type": "string", + "description": "The model used for token counting" + }, + "systemTokens": { + "type": "integer", + "minimum": 0, + "description": "Tokens consumed by the system prompt" + }, + "conversationTokens": { + "type": "integer", + "minimum": 0, + "description": "Tokens consumed by user/assistant/tool messages" + }, + "toolDefinitionsTokens": { + "type": "integer", + "minimum": 0, + "description": "Tokens consumed by tool definitions sent to the model (excludes deferred tools)" + }, + "mcpToolsTokens": { + "type": "integer", + "minimum": 0, + "description": "Tokens consumed by MCP tool definitions (subset of toolDefinitionsTokens, excludes deferred tools)" + }, + "totalTokens": { + "type": "integer", + "minimum": 0, + "description": "Sum of system, conversation and tool-definition tokens" + }, + "promptTokenLimit": { + "type": "integer", + "minimum": 0, + "description": "Maximum prompt tokens allowed by the model (or DEFAULT_TOKEN_LIMIT if unspecified)" + }, + "compactionThreshold": { + "type": "integer", + "minimum": 0, + "description": "Token count at which background compaction starts (configurable percentage of promptTokenLimit)" + }, + "limit": { + "type": "integer", + "minimum": 0, + "description": "Prompt token limit plus the model's full output token limit." + }, + "bufferTokens": { + "type": "integer", + "minimum": 0, + "description": "Output reserve plus tokens after the buffer-exhaustion blocking threshold (default 95%)" + } + }, + "required": [ + "modelName", + "systemTokens", + "conversationTokens", + "toolDefinitionsTokens", + "mcpToolsTokens", + "totalTokens", + "promptTokenLimit", + "compactionThreshold", + "limit", + "bufferTokens" + ], + "additionalProperties": false, + "description": "Token-usage breakdown for the session's current context window" + }, + { + "type": "null" + } + ], + "description": "Token breakdown for the current context window, or null if the session has not yet been initialized (no system prompt or tool metadata cached).", + "title": "SessionContextInfo" + }, + "SessionEnrichMetadataResult": { + "type": "object", + "properties": { + "sessions": { + "type": "array", + "items": { + "$ref": "#/definitions/LocalSessionMetadataValue" + }, + "description": "Enriched records, with summary and context backfilled. Sessions confirmed empty and unnamed may be omitted." + } + }, + "required": [ + "sessions" + ], + "additionalProperties": false, + "description": "The enriched metadata records, with summary and context fields backfilled where available. Sessions confirmed empty and unnamed are omitted.", + "title": "SessionEnrichMetadataResult" + }, + "SessionFsAppendFileRequest": { + "type": "object", + "properties": { + "path": { + "type": "string", + "description": "Path using SessionFs conventions" + }, + "content": { + "type": "string", + "description": "Content to append" + }, + "mode": { + "type": "integer", + "minimum": 0, + "description": "Optional POSIX-style mode for newly created files" + } + }, + "required": [ + "path", + "content" + ], + "additionalProperties": false, + "description": "File path, content to append, and optional mode for the client-provided session filesystem.", + "title": "SessionFsAppendFileRequest" + }, + "SessionFsError": { + "type": "object", + "properties": { + "code": { + "$ref": "#/definitions/SessionFsErrorCode", + "description": "Error classification" + }, + "message": { + "type": "string", + "description": "Free-form detail about the error, for logging/diagnostics" + } + }, + "required": [ + "code" + ], + "additionalProperties": false, + "description": "Describes a filesystem error.", + "title": "SessionFsError" + }, + "SessionFsErrorCode": { + "type": "string", + "enum": [ + "ENOENT", + "UNKNOWN" + ], + "description": "Error classification", + "title": "SessionFsErrorCode", + "x-enumDescriptions": { + "ENOENT": "The requested path does not exist.", + "UNKNOWN": "The filesystem operation failed for an unspecified reason." + } + }, + "SessionFsExistsRequest": { + "type": "object", + "properties": { + "path": { + "type": "string", + "description": "Path using SessionFs conventions" + } + }, + "required": [ + "path" + ], + "additionalProperties": false, + "description": "Path to test for existence in the client-provided session filesystem.", + "title": "SessionFsExistsRequest" + }, + "SessionFsExistsResult": { + "type": "object", + "properties": { + "exists": { + "type": "boolean", + "description": "Whether the path exists" + } + }, + "required": [ + "exists" + ], + "additionalProperties": false, + "description": "Indicates whether the requested path exists in the client-provided session filesystem.", + "title": "SessionFsExistsResult" + }, + "SessionFsMkdirRequest": { + "type": "object", + "properties": { + "path": { + "type": "string", + "description": "Path using SessionFs conventions" + }, + "recursive": { + "type": "boolean", + "description": "Create parent directories as needed" + }, + "mode": { + "type": "integer", + "minimum": 0, + "description": "Optional POSIX-style mode for newly created directories" + } + }, + "required": [ + "path" + ], + "additionalProperties": false, + "description": "Directory path to create in the client-provided session filesystem, with options for recursive creation and POSIX mode.", + "title": "SessionFsMkdirRequest" + }, + "SessionFsReaddirRequest": { + "type": "object", + "properties": { + "path": { + "type": "string", + "description": "Path using SessionFs conventions" + } + }, + "required": [ + "path" + ], + "additionalProperties": false, + "description": "Directory path whose entries should be listed from the client-provided session filesystem.", + "title": "SessionFsReaddirRequest" + }, + "SessionFsReaddirResult": { + "type": "object", + "properties": { + "entries": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Entry names in the directory" + }, + "error": { + "$ref": "#/definitions/SessionFsError", + "description": "Describes a filesystem error." + } + }, + "required": [ + "entries" + ], + "additionalProperties": false, + "description": "Names of entries in the requested directory, or a filesystem error if the read failed.", + "title": "SessionFsReaddirResult" + }, + "SessionFsReaddirWithTypesEntry": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Entry name" + }, + "type": { + "$ref": "#/definitions/SessionFsReaddirWithTypesEntryType", + "description": "Entry type" + } + }, + "required": [ + "name", + "type" + ], + "additionalProperties": false, + "title": "SessionFsReaddirWithTypesEntry", + "description": "Schema for the `SessionFsReaddirWithTypesEntry` type." + }, + "SessionFsReaddirWithTypesEntryType": { + "type": "string", + "enum": [ + "file", + "directory" + ], + "description": "Entry type", + "title": "SessionFsReaddirWithTypesEntryType", + "x-enumDescriptions": { + "file": "The entry is a file.", + "directory": "The entry is a directory." + } + }, + "SessionFsReaddirWithTypesRequest": { + "type": "object", + "properties": { + "path": { + "type": "string", + "description": "Path using SessionFs conventions" + } + }, + "required": [ + "path" + ], + "additionalProperties": false, + "description": "Directory path whose entries (with type information) should be listed from the client-provided session filesystem.", + "title": "SessionFsReaddirWithTypesRequest" + }, + "SessionFsReaddirWithTypesResult": { + "type": "object", + "properties": { + "entries": { + "type": "array", + "items": { + "$ref": "#/definitions/SessionFsReaddirWithTypesEntry" + }, + "description": "Directory entries with type information" + }, + "error": { + "$ref": "#/definitions/SessionFsError", + "description": "Describes a filesystem error." + } + }, + "required": [ + "entries" + ], + "additionalProperties": false, + "description": "Entries in the requested directory paired with file/directory type information, or a filesystem error if the read failed.", + "title": "SessionFsReaddirWithTypesResult" + }, + "SessionFsReadFileRequest": { + "type": "object", + "properties": { + "path": { + "type": "string", + "description": "Path using SessionFs conventions" + } + }, + "required": [ + "path" + ], + "additionalProperties": false, + "description": "Path of the file to read from the client-provided session filesystem.", + "title": "SessionFsReadFileRequest" + }, + "SessionFsReadFileResult": { + "type": "object", + "properties": { + "content": { + "type": "string", + "description": "File content as UTF-8 string" + }, + "error": { + "$ref": "#/definitions/SessionFsError", + "description": "Describes a filesystem error." + } + }, + "required": [ + "content" + ], + "additionalProperties": false, + "description": "File content as a UTF-8 string, or a filesystem error if the read failed.", + "title": "SessionFsReadFileResult" + }, + "SessionFsRenameRequest": { + "type": "object", + "properties": { + "src": { + "type": "string", + "description": "Source path using SessionFs conventions" + }, + "dest": { + "type": "string", + "description": "Destination path using SessionFs conventions" + } + }, + "required": [ + "src", + "dest" + ], + "additionalProperties": false, + "description": "Source and destination paths for renaming or moving an entry in the client-provided session filesystem.", + "title": "SessionFsRenameRequest" + }, + "SessionFsRmRequest": { + "type": "object", + "properties": { + "path": { + "type": "string", + "description": "Path using SessionFs conventions" + }, + "recursive": { + "type": "boolean", + "description": "Remove directories and their contents recursively" + }, + "force": { + "type": "boolean", + "description": "Ignore errors if the path does not exist" + } + }, + "required": [ + "path" + ], + "additionalProperties": false, + "description": "Path to remove from the client-provided session filesystem, with options for recursive removal and force.", + "title": "SessionFsRmRequest" + }, + "SessionFsSetProviderCapabilities": { + "type": "object", + "properties": { + "sqlite": { + "type": "boolean", + "description": "Whether the provider supports SQLite query/exists operations" + } + }, + "additionalProperties": false, + "description": "Optional capabilities declared by the provider", + "title": "SessionFsSetProviderCapabilities" + }, + "SessionFsSetProviderConventions": { + "type": "string", + "enum": [ + "windows", + "posix" + ], + "description": "Path conventions used by this filesystem", + "title": "SessionFsSetProviderConventions", + "x-enumDescriptions": { + "windows": "Paths use Windows path conventions.", + "posix": "Paths use POSIX path conventions." + } + }, + "SessionFsSetProviderRequest": { + "type": "object", + "properties": { + "initialCwd": { + "type": "string", + "description": "Initial working directory for sessions" + }, + "sessionStatePath": { + "type": "string", + "description": "Path within each session's SessionFs where the runtime stores files for that session" + }, + "conventions": { + "$ref": "#/definitions/SessionFsSetProviderConventions", + "description": "Path conventions used by this filesystem" + }, + "capabilities": { + "$ref": "#/definitions/SessionFsSetProviderCapabilities", + "description": "Optional capabilities declared by the provider" + } + }, + "required": [ + "initialCwd", + "sessionStatePath", + "conventions" + ], + "additionalProperties": false, + "description": "Initial working directory, session-state path layout, and path conventions used to register the calling SDK client as the session filesystem provider.", + "title": "SessionFsSetProviderRequest" + }, + "SessionFsSetProviderResult": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "description": "Whether the provider was set successfully" + } + }, + "required": [ + "success" + ], + "additionalProperties": false, + "description": "Indicates whether the calling client was registered as the session filesystem provider.", + "title": "SessionFsSetProviderResult" + }, + "SessionFsSqliteExistsResult": { + "type": "object", + "properties": { + "exists": { + "type": "boolean", + "description": "Whether the session database already exists" + } + }, + "required": [ + "exists" + ], + "additionalProperties": false, + "description": "Indicates whether the per-session SQLite database already exists.", + "title": "SessionFsSqliteExistsResult" + }, + "SessionFsSqliteQueryRequest": { + "type": "object", + "properties": { + "query": { + "type": "string", + "description": "SQL query to execute" + }, + "queryType": { + "$ref": "#/definitions/SessionFsSqliteQueryType", + "description": "How to execute the query: 'exec' for DDL/multi-statement (no results), 'query' for SELECT (returns rows), 'run' for INSERT/UPDATE/DELETE (returns rowsAffected)" + }, + "params": { + "type": "object", + "additionalProperties": { + "type": [ + "string", + "number", + "null" + ], + "x-opaque-json": true + }, + "description": "Optional named bind parameters" + } + }, + "required": [ + "query", + "queryType" + ], + "additionalProperties": false, + "description": "SQL query, query type, and optional bind parameters for executing a SQLite query against the per-session database.", + "title": "SessionFsSqliteQueryRequest" + }, + "SessionFsSqliteQueryResult": { + "type": "object", + "properties": { + "rows": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": { + "x-opaque-json": true + } + }, + "description": "For SELECT: array of row objects. For others: empty array." + }, + "columns": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Column names from the result set" + }, + "rowsAffected": { + "type": "integer", + "minimum": 0, + "description": "Number of rows affected (for INSERT/UPDATE/DELETE)" + }, + "lastInsertRowid": { + "type": "integer", + "description": "SQLite last_insert_rowid() value for INSERT." + }, + "error": { + "$ref": "#/definitions/SessionFsError", + "description": "Describes a filesystem error." + } + }, + "required": [ + "rows", + "columns", + "rowsAffected" + ], + "additionalProperties": false, + "description": "Query results including rows, columns, and rows affected, or a filesystem error if execution failed.", + "title": "SessionFsSqliteQueryResult" + }, + "SessionFsSqliteQueryType": { + "type": "string", + "enum": [ + "exec", + "query", + "run" + ], + "description": "How to execute the query: 'exec' for DDL/multi-statement (no results), 'query' for SELECT (returns rows), 'run' for INSERT/UPDATE/DELETE (returns rowsAffected)", + "title": "SessionFsSqliteQueryType", + "x-enumDescriptions": { + "exec": "Execute DDL or multi-statement SQL without returning rows.", + "query": "Execute a SELECT-style query and return rows.", + "run": "Execute INSERT, UPDATE, or DELETE SQL and return affected-row metadata." + } + }, + "SessionFsStatRequest": { + "type": "object", + "properties": { + "path": { + "type": "string", + "description": "Path using SessionFs conventions" + } + }, + "required": [ + "path" + ], + "additionalProperties": false, + "description": "Path whose metadata should be returned from the client-provided session filesystem.", + "title": "SessionFsStatRequest" + }, + "SessionFsStatResult": { + "type": "object", + "properties": { + "isFile": { + "type": "boolean", + "description": "Whether the path is a file" + }, + "isDirectory": { + "type": "boolean", + "description": "Whether the path is a directory" + }, + "size": { + "type": "integer", + "minimum": 0, + "description": "File size in bytes" + }, + "mtime": { + "type": "string", + "format": "date-time", + "description": "ISO 8601 timestamp of last modification" + }, + "birthtime": { + "type": "string", + "format": "date-time", + "description": "ISO 8601 timestamp of creation" + }, + "error": { + "$ref": "#/definitions/SessionFsError", + "description": "Describes a filesystem error." + } + }, + "required": [ + "isFile", + "isDirectory", + "size", + "mtime", + "birthtime" + ], + "additionalProperties": false, + "description": "Filesystem metadata for the requested path, or a filesystem error if the stat failed.", + "title": "SessionFsStatResult" + }, + "SessionFsWriteFileRequest": { + "type": "object", + "properties": { + "path": { + "type": "string", + "description": "Path using SessionFs conventions" + }, + "content": { + "type": "string", + "description": "Content to write" + }, + "mode": { + "type": "integer", + "minimum": 0, + "description": "Optional POSIX-style mode for newly created files" + } + }, + "required": [ + "path", + "content" + ], + "additionalProperties": false, + "description": "File path, content to write, and optional mode for the client-provided session filesystem.", + "title": "SessionFsWriteFileRequest" + }, + "SessionInstalledPlugin": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Plugin name" + }, + "marketplace": { + "type": "string", + "description": "Marketplace the plugin came from (empty string for direct repo installs)" + }, + "version": { + "type": "string", + "description": "Installed version, if known" + }, + "installed_at": { + "type": "string", + "description": "Installation timestamp (ISO-8601)" + }, + "enabled": { + "type": "boolean", + "description": "Whether the plugin is currently enabled" + }, + "cache_path": { + "type": "string", + "description": "Path where the plugin is cached locally" + }, + "source": { + "$ref": "#/definitions/SessionInstalledPluginSource", + "description": "Source descriptor for direct repo installs (when marketplace is empty)" + } + }, + "required": [ + "name", + "marketplace", + "installed_at", + "enabled" + ], + "additionalProperties": false, + "title": "SessionInstalledPlugin", + "description": "Schema for the `SessionInstalledPlugin` type." + }, + "SessionInstalledPluginSource": { + "anyOf": [ + { + "type": "string", + "pattern": "^[^/]+\\/[^/]+$" + }, + { + "$ref": "#/definitions/SessionInstalledPluginSourceGitHub" + }, + { + "$ref": "#/definitions/SessionInstalledPluginSourceUrl" + }, + { + "$ref": "#/definitions/SessionInstalledPluginSourceLocal" + } + ], + "description": "Source descriptor for direct repo installs (when marketplace is empty)", + "title": "SessionInstalledPluginSource", + "x-opaque-json": true, + "stability": "experimental" + }, + "SessionInstalledPluginSourceGitHub": { + "type": "object", + "properties": { + "source": { + "type": "string", + "const": "github", + "description": "Constant value. Always \"github\"." + }, + "repo": { + "type": "string", + "pattern": "^[^/]+\\/[^/]+$" + }, + "ref": { + "type": "string" + }, + "path": { + "type": "string" + } + }, + "required": [ + "source", + "repo" + ], + "additionalProperties": false, + "title": "SessionInstalledPluginSourceGitHub", + "description": "Schema for the `SessionInstalledPluginSourceGitHub` type." + }, + "SessionInstalledPluginSourceLocal": { + "type": "object", + "properties": { + "source": { + "type": "string", + "const": "local", + "description": "Constant value. Always \"local\"." + }, + "path": { + "type": "string" + } + }, + "required": [ + "source", + "path" + ], + "additionalProperties": false, + "title": "SessionInstalledPluginSourceLocal", + "description": "Schema for the `SessionInstalledPluginSourceLocal` type." + }, + "SessionInstalledPluginSourceUrl": { + "type": "object", + "properties": { + "source": { + "type": "string", + "const": "url", + "description": "Constant value. Always \"url\"." + }, + "url": { + "type": "string", + "format": "uri" + }, + "ref": { + "type": "string" + }, + "path": { + "type": "string" + } + }, + "required": [ + "source", + "url" + ], + "additionalProperties": false, + "title": "SessionInstalledPluginSourceUrl", + "description": "Schema for the `SessionInstalledPluginSourceUrl` type." + }, + "SessionList": { + "type": "object", + "properties": { + "sessions": { + "type": "array", + "items": { + "$ref": "#/definitions/SessionListEntry", + "description": "Local or remote session metadata entry. Narrow on `isRemote` to access source-specific fields." + }, + "description": "Sessions ordered most-recently-modified first. Discriminated by `isRemote`." + } + }, + "required": [ + "sessions" + ], + "additionalProperties": false, + "description": "Sessions matching the filter, ordered most-recently-modified first.", + "title": "SessionList" + }, + "SessionListEntry": { + "anyOf": [ + { + "$ref": "#/definitions/LocalSessionMetadataValue" + }, + { + "$ref": "#/definitions/RemoteSessionMetadataValue", + "description": "Full remote-session metadata in wire-portable form." + } + ], + "description": "Local or remote session metadata entry. Narrow on `isRemote` to access source-specific fields.", + "title": "SessionListEntry" + }, + "SessionListFilter": { + "type": "object", + "properties": { + "cwd": { + "type": "string", + "description": "Match sessions whose context.cwd equals this value" + }, + "gitRoot": { + "type": "string", + "description": "Match sessions whose context.gitRoot equals this value" + }, + "repository": { + "type": "string", + "description": "Match sessions whose context.repository equals this value" + }, + "branch": { + "type": "string", + "description": "Match sessions whose context.branch equals this value" + } + }, + "additionalProperties": false, + "description": "Optional filter applied to the returned sessions", + "title": "SessionListFilter" + }, + "SessionLoadDeferredRepoHooksResult": { + "type": "object", + "properties": { + "startupPrompts": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Repo-level startup prompts queued from repo hook configs. Empty on resume, when no repo configs were pending, or when disableAllHooks is set." + }, + "hookCount": { + "type": "integer", + "minimum": 0, + "description": "Total hook command count (user + plugin + repo) loaded for the session by this call. Captured atomically with startupPrompts so callers don't need to read a separate counter." + } + }, + "required": [ + "startupPrompts", + "hookCount" + ], + "additionalProperties": false, + "description": "Queued repo-level startup prompts and the total hook command count after loading.", + "title": "SessionLoadDeferredRepoHooksResult" + }, + "SessionLogLevel": { + "type": "string", + "enum": [ + "info", + "warning", + "error" + ], + "description": "Log severity level. Determines how the message is displayed in the timeline. Defaults to \"info\".", + "title": "SessionLogLevel", + "x-enumDescriptions": { + "info": "Informational message.", + "warning": "Warning message that may require attention.", + "error": "Error message describing a failure." + } + }, + "SessionMetadataSnapshot": { + "type": "object", + "properties": { + "sessionId": { + "type": "string", + "description": "The unique identifier of the session" + }, + "startTime": { + "type": "string", + "format": "date-time", + "description": "ISO 8601 timestamp of when the session started" + }, + "modifiedTime": { + "type": "string", + "format": "date-time", + "description": "ISO 8601 timestamp of when the session's persisted state was last modified on disk. For new sessions, equals startTime. For resumed sessions, reflects the previous modification time at construction." + }, + "isRemote": { + "type": "boolean", + "description": "Whether this is a remote session (i.e., one whose runtime executes elsewhere and is steered through this process)" + }, + "alreadyInUse": { + "type": "boolean", + "description": "True when the session was detected to be in use by another process at construction time. Local consumers may surface a confirmation prompt before fully attaching. Always false for new sessions." + }, + "workspacePath": { + "type": [ + "string", + "null" + ], + "description": "Absolute path to the session's workspace directory on disk, or null if the session has no associated workspace" + }, + "initialName": { + "type": "string", + "description": "User-provided name supplied at session construction (via `--name`), if any. Immutable after construction." + }, + "clientName": { + "type": "string", + "description": "Runtime client name associated with the session (telemetry identifier)." + }, + "remoteMetadata": { + "$ref": "#/definitions/MetadataSnapshotRemoteMetadata", + "description": "Remote-session-specific metadata. Populated only when `isRemote` is true. Fields are immutable for the lifetime of the session." + }, + "summary": { + "type": "string", + "description": "Short human-readable summary of the session, if known. Omitted when no summary has been generated." + }, + "workingDirectory": { + "type": "string", + "description": "Absolute path to the session's current working directory" + }, + "currentMode": { + "$ref": "#/definitions/MetadataSnapshotCurrentMode", + "description": "The current agent mode for this session (e.g., 'interactive', 'plan', 'autopilot')" + }, + "selectedModel": { + "type": "string", + "description": "Currently selected model identifier, if any" + }, + "workspace": { + "$ref": "#/definitions/WorkspaceSummary", + "description": "Public-facing workspace metadata for this session, or null if the session has no associated workspace. Excludes runtime-internal fields (GitHub IDs, summary count, internal flags)." + } + }, + "required": [ + "sessionId", + "startTime", + "modifiedTime", + "isRemote", + "alreadyInUse", + "workspacePath", + "workingDirectory", + "currentMode", + "workspace" + ], + "additionalProperties": false, + "description": "Point-in-time snapshot of slow-changing session identifier and state fields", + "title": "SessionMetadataSnapshot" + }, + "SessionMode": { + "type": "string", + "enum": [ + "interactive", + "plan", + "autopilot" + ], + "description": "The session mode the agent is operating in", + "title": "SessionMode", + "x-enumDescriptions": { + "interactive": "The agent is responding interactively to the user.", + "plan": "The agent is preparing a plan before making changes.", + "autopilot": "The agent is working autonomously toward task completion." + } + }, + "SessionModelList": { + "type": "object", + "properties": { + "list": { + "type": "array", + "items": { + "x-opaque-json": true + }, + "description": "Available models, ordered with the most preferred default first." + }, + "quotaSnapshots": { + "type": "object", + "additionalProperties": { + "x-opaque-json": true + }, + "description": "Per-quota snapshots returned alongside the model list, keyed by quota type." + } + }, + "required": [ + "list" + ], + "additionalProperties": false, + "description": "The list of models available to this session.", + "title": "SessionModelList" + }, + "SessionOpenOptions": { + "type": "object", + "properties": { + "sessionId": { + "type": "string", + "description": "Optional stable session identifier to use for a new session." + }, + "name": { + "type": "string", + "description": "Optional human-friendly session name." + }, + "model": { + "type": "string", + "description": "Initial model identifier." + }, + "reasoningEffort": { + "type": "string", + "description": "Initial reasoning effort level." + }, + "reasoningSummary": { + "$ref": "#/definitions/SessionOpenOptionsReasoningSummary", + "description": "Initial reasoning summary mode for supported model clients." + }, + "clientName": { + "type": "string", + "description": "Identifier of the client driving the session." + }, + "clientKind": { + "type": "string", + "description": "Structured client kind used for runtime behavior gates." + }, + "lspClientName": { + "type": "string", + "description": "Identifier sent to LSP-style integrations." + }, + "integrationId": { + "type": "string", + "description": "Stable integration identifier for analytics." + }, + "featureFlags": { + "type": "object", + "additionalProperties": { + "type": "boolean" + }, + "description": "Feature-flag values resolved by the host." + }, + "isExperimentalMode": { + "type": "boolean", + "description": "Whether experimental behavior is enabled." + }, + "authInfo": { + "$ref": "#/definitions/AuthInfo", + "description": "Initial authentication info for the session." + }, + "provider": { + "$ref": "#/definitions/ProviderConfig", + "description": "Custom model-provider configuration (BYOK)." + }, + "workingDirectory": { + "type": "string", + "description": "Working directory to anchor the session." + }, + "workingDirectoryContext": { + "$ref": "#/definitions/SessionContext", + "description": "Pre-resolved working-directory context for session startup." + }, + "remoteSteerable": { + "type": "boolean", + "description": "Whether this session supports remote steering." + }, + "remoteExporting": { + "type": "boolean", + "description": "Telemetry-only remote exporting flag." + }, + "remoteDefaultedOn": { + "type": "boolean", + "description": "Telemetry-only remote-defaulted flag." + }, + "detachedFromSpawningParentSessionId": { + "type": "string", + "description": "Parent session ID for detached child telemetry rollup." + }, + "detachedFromSpawningParentEngagementId": { + "type": "string", + "description": "Parent engagement ID for detached child telemetry rollup." + }, + "availableTools": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Allowlist of available tool names." + }, + "excludedTools": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Denylist of tool names." + }, + "enableScriptSafety": { + "type": "boolean", + "description": "Whether shell-script safety heuristics are enabled." + }, + "shellInitProfile": { + "type": "string", + "description": "Shell init profile." + }, + "shellProcessFlags": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Per-shell process flags." + }, + "sandboxConfig": { + "$ref": "#/definitions/SandboxConfig", + "description": "Resolved sandbox configuration." + }, + "logInteractiveShells": { + "type": "boolean", + "description": "Whether interactive shell sessions are logged." + }, + "envValueMode": { + "$ref": "#/definitions/SessionOpenOptionsEnvValueMode", + "description": "How MCP server environment values are interpreted." + }, + "skillDirectories": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Additional directories to search for skills." + }, + "disabledSkills": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Skill IDs disabled for this session." + }, + "installedPlugins": { + "type": "array", + "items": { + "$ref": "#/definitions/InstalledPlugin" + }, + "description": "Installed plugins visible to the session." + }, + "customAgentsLocalOnly": { + "type": "boolean", + "description": "Whether custom agents default to local-only execution." + }, + "skipCustomInstructions": { + "type": "boolean", + "description": "Whether to skip custom instruction sources." + }, + "disabledInstructionSources": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Instruction source IDs disabled for this session." + }, + "coauthorEnabled": { + "type": "boolean", + "description": "Whether commit-message coauthor trailers are enabled." + }, + "trajectoryFile": { + "type": "string", + "description": "Optional trajectory output file path." + }, + "enableStreaming": { + "type": "boolean", + "description": "Whether model responses stream as delta events." + }, + "copilotUrl": { + "type": "string", + "description": "Override URL for the Copilot API endpoint." + }, + "askUserDisabled": { + "type": "boolean", + "description": "Whether ask_user is explicitly disabled." + }, + "continueOnAutoMode": { + "type": "boolean", + "description": "Whether auto-mode continuation is enabled." + }, + "runningInInteractiveMode": { + "type": "boolean", + "description": "Whether the host is an interactive UI." + }, + "enableOnDemandInstructionDiscovery": { + "type": "boolean", + "description": "Whether on-demand custom instruction discovery is enabled." + }, + "modelCapabilitiesOverrides": { + "$ref": "#/definitions/ModelCapabilitiesOverride", + "description": "Initial model capability overrides." + }, + "agentContext": { + "type": "string", + "description": "Runtime context discriminator for agent filtering." + }, + "eventsLogDirectory": { + "type": "string", + "description": "Override directory for session event logs." + }, + "configDir": { + "type": "string", + "description": "Override Copilot configuration directory." + }, + "additionalContentExclusionPolicies": { + "type": "array", + "items": { + "$ref": "#/definitions/SessionOpenOptionsAdditionalContentExclusionPolicy" + }, + "description": "Additional content-exclusion policies to merge into the session policy set.", + "stability": "experimental" + }, + "sessionCapabilities": { + "type": "array", + "items": { + "$ref": "#/definitions/SessionCapability", + "description": "Session capability enabled for this session" + }, + "description": "Capabilities enabled for this session." + } + }, + "additionalProperties": false, + "description": "Session construction options.", + "title": "SessionOpenOptions" + }, + "SessionOpenOptionsAdditionalContentExclusionPolicy": { + "type": "object", + "properties": { + "rules": { + "type": "array", + "items": { + "$ref": "#/definitions/SessionOpenOptionsAdditionalContentExclusionPolicyRule" + } + }, + "last_updated_at": { + "type": [ + "string", + "number" + ], + "x-opaque-json": true + }, + "scope": { + "$ref": "#/definitions/SessionOpenOptionsAdditionalContentExclusionPolicyScope", + "description": "Allowed values for the `SessionOpenOptionsAdditionalContentExclusionPolicyScope` enumeration." + } + }, + "required": [ + "rules", + "last_updated_at", + "scope" + ], + "additionalProperties": true, + "title": "SessionOpenOptionsAdditionalContentExclusionPolicy", + "description": "Schema for the `SessionOpenOptionsAdditionalContentExclusionPolicy` type." + }, + "SessionOpenOptionsAdditionalContentExclusionPolicyRule": { + "type": "object", + "properties": { + "paths": { + "type": "array", + "items": { + "type": "string" + } + }, + "ifAnyMatch": { + "type": "array", + "items": { + "type": "string" + } + }, + "ifNoneMatch": { + "type": "array", + "items": { + "type": "string" + } + }, + "source": { + "$ref": "#/definitions/SessionOpenOptionsAdditionalContentExclusionPolicyRuleSource", + "description": "Schema for the `SessionOpenOptionsAdditionalContentExclusionPolicyRuleSource` type." + } + }, + "required": [ + "paths", + "source" + ], + "additionalProperties": true, + "title": "SessionOpenOptionsAdditionalContentExclusionPolicyRule", + "description": "Schema for the `SessionOpenOptionsAdditionalContentExclusionPolicyRule` type." + }, + "SessionOpenOptionsAdditionalContentExclusionPolicyRuleSource": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "name", + "type" + ], + "additionalProperties": false, + "title": "SessionOpenOptionsAdditionalContentExclusionPolicyRuleSource", + "description": "Schema for the `SessionOpenOptionsAdditionalContentExclusionPolicyRuleSource` type." + }, + "SessionOpenOptionsAdditionalContentExclusionPolicyScope": { + "type": "string", + "enum": [ + "repo", + "all" + ], + "title": "SessionOpenOptionsAdditionalContentExclusionPolicyScope", + "x-enumDescriptions": { + "repo": "The content exclusion policy applies to the current repository.", + "all": "The content exclusion policy applies across all repositories." + }, + "description": "Allowed values for the `SessionOpenOptionsAdditionalContentExclusionPolicyScope` enumeration." + }, + "SessionOpenOptionsEnvValueMode": { + "type": "string", + "enum": [ + "direct", + "indirect" + ], + "description": "How MCP server environment values are interpreted.", + "title": "SessionOpenOptionsEnvValueMode", + "x-enumDescriptions": { + "direct": "Pass MCP server environment values as literal strings.", + "indirect": "Resolve MCP server environment values from host-side references." + } + }, + "SessionOpenOptionsReasoningSummary": { + "type": "string", + "enum": [ + "none", + "concise", + "detailed" + ], + "description": "Initial reasoning summary mode for supported model clients.", + "title": "SessionOpenOptionsReasoningSummary", + "x-enumDescriptions": { + "none": "Do not request reasoning summaries from the model.", + "concise": "Request a concise summary of model reasoning.", + "detailed": "Request a detailed summary of model reasoning." + } + }, + "SessionOpenParams": { + "anyOf": [ + { + "$ref": "#/definitions/SessionsOpenCreate", + "description": "Parameters for creating a new local session." + }, + { + "$ref": "#/definitions/SessionsOpenResume", + "description": "Parameters for resuming a specific local session." + }, + { + "$ref": "#/definitions/SessionsOpenResumeLast", + "description": "Parameters for resuming the most relevant local session." + }, + { + "$ref": "#/definitions/SessionsOpenAttach", + "description": "Parameters for attaching to an already-active session by ID." + }, + { + "$ref": "#/definitions/SessionsOpenRemote", + "description": "Parameters for connecting to a live remote session." + }, + { + "$ref": "#/definitions/SessionsOpenCloud", + "description": "Parameters for creating a new cloud session." + }, + { + "$ref": "#/definitions/SessionsOpenHandoff", + "description": "Parameters for fetching a remote session and handing it off to a new local session." + } + ], + "description": "Open a session by creating, resuming, attaching, connecting to a remote, or handing off.", + "title": "SessionOpenParams" + }, + "SessionOpenResult": { + "type": "object", + "properties": { + "status": { + "$ref": "#/definitions/SessionsOpenStatus", + "description": "Outcome of the open request." + }, + "sessionId": { + "type": "string", + "description": "Opened session ID. Omitted when status is `not_found`." + }, + "sessionApi": { + "description": "In-process SessionClientApi handle for the opened session, returned to CLI callers as a transitional shortcut. Marked internal so the public SDK surface does not expose it; SDK consumers should construct per-session clients from `sessionId` instead.", + "visibility": "internal", + "x-opaque-json": true + }, + "startupPrompts": { "type": "array", "items": { - "x-opaque-json": true + "type": "string" }, - "description": "Available models, ordered with the most preferred default first." + "description": "Startup prompts queued by user-level hook configs at session creation. Only populated when status is `created`; resumed sessions return an empty array." }, - "quotaSnapshots": { - "type": "object", - "additionalProperties": { - "x-opaque-json": true + "remoteSessionId": { + "type": "string", + "description": "Remote session ID, present when status is `connected`." + }, + "metadata": { + "$ref": "#/definitions/RemoteSessionMetadataValue", + "description": "Remote session metadata, present when status is `connected`." + }, + "progress": { + "type": "array", + "items": { + "$ref": "#/definitions/SessionsOpenProgress" }, - "description": "Per-quota snapshots returned alongside the model list, keyed by quota type." + "description": "Handoff progress steps, present when status is `handed_off`." } }, "required": [ - "list" + "status" ], "additionalProperties": false, - "description": "The list of models available to this session.", - "title": "SessionModelList" + "description": "Result of opening a session.", + "title": "SessionOpenResult" }, "SessionPruneResult": { "type": "object", @@ -15829,7 +19140,7 @@ "sessions": { "type": "array", "items": { - "$ref": "#/definitions/SessionMetadata" + "$ref": "#/definitions/LocalSessionMetadataValue" }, "description": "Session metadata records to enrich. Records that already have summary and context are returned unchanged." } @@ -15964,6 +19275,34 @@ "description": "Identifier and optional friendly name assigned to the newly forked session.", "title": "SessionsForkResult" }, + "SessionsGetBoardEntryCountRequest": { + "type": "object", + "properties": { + "sessionId": { + "type": "string", + "description": "Session ID whose board entry count should be returned." + } + }, + "required": [ + "sessionId" + ], + "additionalProperties": false, + "description": "Session ID whose board entry count should be returned.", + "title": "SessionsGetBoardEntryCountRequest" + }, + "SessionsGetBoardEntryCountResult": { + "type": "object", + "properties": { + "count": { + "type": "integer", + "minimum": 0, + "description": "Board entry count, when available." + } + }, + "additionalProperties": false, + "description": "Dynamic-context board entry count, when available.", + "title": "SessionsGetBoardEntryCountResult" + }, "SessionsGetEventFilePathRequest": { "type": "object", "properties": { @@ -16023,48 +19362,422 @@ "properties": { "sessionId": { "type": "string", - "description": "Session ID to look up the persisted remote-steerable flag for" + "description": "Session ID to look up the persisted remote-steerable flag for" + } + }, + "required": [ + "sessionId" + ], + "additionalProperties": false, + "description": "Session ID to look up the persisted remote-steerable flag for.", + "title": "SessionsGetPersistedRemoteSteerableRequest" + }, + "SessionsGetPersistedRemoteSteerableResult": { + "type": "object", + "properties": { + "remoteSteerable": { + "type": "boolean", + "description": "The session's persisted remote-steerable flag if recorded; omitted when no value has been persisted" + } + }, + "additionalProperties": false, + "description": "The session's persisted remote-steerable flag, or omitted when no value has been persisted.", + "title": "SessionsGetPersistedRemoteSteerableResult" + }, + "SessionSizes": { + "type": "object", + "properties": { + "sizes": { + "type": "object", + "additionalProperties": { + "type": "integer", + "minimum": 0 + }, + "description": "Map of sessionId -> on-disk size in bytes for the session's workspace directory" + } + }, + "required": [ + "sizes" + ], + "additionalProperties": false, + "description": "Map of sessionId -> on-disk size in bytes for each session's workspace directory.", + "title": "SessionSizes" + }, + "SessionsListRequest": { + "anyOf": [ + { + "not": {} + }, + { + "type": "object", + "properties": { + "source": { + "$ref": "#/definitions/SessionSource", + "description": "Which session sources to include. Defaults to `local` for backward compatibility." + }, + "metadataLimit": { + "type": "integer", + "minimum": 0, + "description": "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)." + }, + "filter": { + "$ref": "#/definitions/SessionListFilter", + "description": "Optional filter applied to the returned sessions" + }, + "includeDetached": { + "type": "boolean", + "description": "When true, include detached maintenance sessions. Defaults to false for user-facing session lists." + }, + "throwOnError": { + "type": "boolean", + "description": "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." + } + }, + "additionalProperties": false, + "description": "Optional source filter, metadata-load limit, and context filter applied to the returned sessions." + } + ], + "description": "Optional source filter, metadata-load limit, and context filter applied to the returned sessions.", + "title": "SessionsListRequest" + }, + "SessionsLoadDeferredRepoHooksRequest": { + "type": "object", + "properties": { + "sessionId": { + "type": "string", + "description": "Active session ID whose deferred repo-level hooks should be loaded" + } + }, + "required": [ + "sessionId" + ], + "additionalProperties": false, + "description": "Active session ID whose deferred repo-level hooks should be loaded.", + "title": "SessionsLoadDeferredRepoHooksRequest" + }, + "SessionsOpenAttach": { + "type": "object", + "properties": { + "kind": { + "type": "string", + "const": "attach", + "description": "Attach to an already-active in-process session by ID. Unlike `resume`, this does NOT re-load from disk; the session must already be loaded by an earlier `create`/`resume` call. Returns `status: 'not_found'` when no active session matches the id. Useful for in-process consumers that need a fresh API handle to a session opened elsewhere (e.g., a peer foreground-session switch)." + }, + "sessionId": { + "type": "string", + "description": "Session ID to attach to." + } + }, + "required": [ + "kind", + "sessionId" + ], + "additionalProperties": false, + "description": "Parameters for attaching to an already-active session by ID.", + "title": "SessionsOpenAttach" + }, + "SessionsOpenCloud": { + "type": "object", + "properties": { + "kind": { + "type": "string", + "const": "cloud", + "description": "Create a new cloud (coding-agent) session." + }, + "repository": { + "$ref": "#/definitions/RemoteSessionRepository", + "description": "Repository for the cloud session." + }, + "owner": { + "type": "string", + "description": "Optional owner (user or organization login) to associate with the cloud session when no repository is provided. Ignored when `repository` is set (the repo's owner takes precedence)." + }, + "options": { + "$ref": "#/definitions/SessionOpenOptions", + "description": "Session options for cloud session creation." + }, + "onTaskCreated": { + "description": "In-process callback invoked when the cloud task is created (before connection). Marked internal because a function reference cannot cross the JSON-RPC boundary. Disappears in the SDK migration: the field is purely cosmetic (it flips a single CLI phase label from 'creating' to 'connecting') and the wire-clean version just drops the intermediate phase.", + "visibility": "internal", + "x-opaque-json": true + } + }, + "required": [ + "kind" + ], + "additionalProperties": false, + "description": "Parameters for creating a new cloud session.", + "title": "SessionsOpenCloud" + }, + "SessionsOpenCreate": { + "type": "object", + "properties": { + "kind": { + "type": "string", + "const": "create", + "description": "Create a new local session." + }, + "options": { + "$ref": "#/definitions/SessionOpenOptions", + "description": "Session construction options." + }, + "emitStart": { + "type": "boolean", + "description": "Whether to emit session.start during creation. Defaults to true." + } + }, + "required": [ + "kind" + ], + "additionalProperties": false, + "description": "Parameters for creating a new local session.", + "title": "SessionsOpenCreate" + }, + "SessionsOpenHandoff": { + "type": "object", + "properties": { + "kind": { + "type": "string", + "const": "handoff", + "description": "Fetch a remote session and hand it off to a new local session." + }, + "metadata": { + "$ref": "#/definitions/RemoteSessionMetadataValue", + "description": "Remote session metadata for the session to hand off (typically obtained from `sessions.list` with `source: \"remote\"`)." + }, + "options": { + "$ref": "#/definitions/SessionOpenOptions", + "description": "Session construction options for the new local session." + }, + "taskType": { + "$ref": "#/definitions/SessionsOpenHandoffTaskType", + "description": "Task type determines the handoff strategy (CCA fetches events; CLI prepares a transient session)." + }, + "onProgress": { + "description": "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.", + "visibility": "internal", + "x-opaque-json": true + } + }, + "required": [ + "kind", + "metadata" + ], + "additionalProperties": false, + "description": "Parameters for fetching a remote session and handing it off to a new local session.", + "title": "SessionsOpenHandoff" + }, + "SessionsOpenHandoffTaskType": { + "type": "string", + "enum": [ + "cca", + "cli" + ], + "description": "Task type determines the handoff strategy (CCA fetches events; CLI prepares a transient session).", + "title": "SessionsOpenHandoffTaskType", + "x-enumDescriptions": { + "cca": "GitHub Copilot coding agent task.", + "cli": "CLI remote task." + } + }, + "SessionsOpenProgress": { + "type": "object", + "properties": { + "step": { + "$ref": "#/definitions/SessionsOpenProgressStep", + "description": "Handoff step." + }, + "status": { + "$ref": "#/definitions/SessionsOpenProgressStatus", + "description": "Step status." + }, + "message": { + "type": "string", + "description": "Optional step message." + } + }, + "required": [ + "step", + "status" + ], + "additionalProperties": false, + "title": "SessionsOpenProgress", + "description": "Schema for the `SessionsOpenProgress` type." + }, + "SessionsOpenProgressStatus": { + "type": "string", + "enum": [ + "in-progress", + "complete" + ], + "description": "Step status.", + "title": "SessionsOpenProgressStatus", + "x-enumDescriptions": { + "in-progress": "The step has started and has not yet finished.", + "complete": "The step has completed successfully." + } + }, + "SessionsOpenProgressStep": { + "type": "string", + "enum": [ + "load-session", + "validate-repo", + "check-changes", + "checkout-branch", + "create-session", + "save-session" + ], + "description": "Handoff step.", + "title": "SessionsOpenProgressStep", + "x-enumDescriptions": { + "load-session": "Loading the source session's events from the remote service.", + "validate-repo": "Validating that the local repository matches the remote session's repository.", + "check-changes": "Checking the local working tree for uncommitted changes that would block the handoff.", + "checkout-branch": "Checking out the branch associated with the remote session in the local working tree.", + "create-session": "Creating the new local session and seeding it with the source session's events.", + "save-session": "Persisting the newly-created local session to disk." + } + }, + "SessionsOpenRemote": { + "type": "object", + "properties": { + "kind": { + "type": "string", + "const": "remote", + "description": "Connect to a live remote session." + }, + "remoteSessionId": { + "type": "string", + "description": "Remote session identifier to connect to." + }, + "repository": { + "$ref": "#/definitions/RemoteSessionRepository", + "description": "Repository context for the remote session." + }, + "options": { + "$ref": "#/definitions/SessionOpenOptions", + "description": "Session options for the connection." + } + }, + "required": [ + "kind", + "remoteSessionId" + ], + "additionalProperties": false, + "description": "Parameters for connecting to a live remote session.", + "title": "SessionsOpenRemote" + }, + "SessionsOpenResume": { + "type": "object", + "properties": { + "kind": { + "type": "string", + "const": "resume", + "description": "Resume a specific local session by ID or prefix." + }, + "sessionId": { + "type": "string", + "description": "Session ID or unique prefix to resume." + }, + "options": { + "$ref": "#/definitions/SessionOpenOptions", + "description": "Session resume options." + }, + "resume": { + "type": "boolean", + "description": "Whether to emit session.resume after loading. Defaults to true." + }, + "suppressResumeWorkspaceMetadataWriteback": { + "type": "boolean", + "description": "Suppress workspace.yaml metadata writeback when resuming from an incidental cwd." } }, "required": [ + "kind", "sessionId" ], "additionalProperties": false, - "description": "Session ID to look up the persisted remote-steerable flag for.", - "title": "SessionsGetPersistedRemoteSteerableRequest" + "description": "Parameters for resuming a specific local session.", + "title": "SessionsOpenResume" }, - "SessionsGetPersistedRemoteSteerableResult": { + "SessionsOpenResumeLast": { "type": "object", "properties": { - "remoteSteerable": { + "kind": { + "type": "string", + "const": "resumeLast", + "description": "Resume the most relevant existing local session." + }, + "context": { + "$ref": "#/definitions/SessionContext", + "description": "Working-directory context used to choose the most relevant session." + }, + "options": { + "$ref": "#/definitions/SessionOpenOptions", + "description": "Session resume options." + }, + "suppressResumeWorkspaceMetadataWriteback": { "type": "boolean", - "description": "The session's persisted remote-steerable flag if recorded; omitted when no value has been persisted" + "description": "Suppress workspace.yaml metadata writeback when resuming from an incidental cwd." } }, + "required": [ + "kind" + ], "additionalProperties": false, - "description": "The session's persisted remote-steerable flag, or omitted when no value has been persisted.", - "title": "SessionsGetPersistedRemoteSteerableResult" + "description": "Parameters for resuming the most relevant local session.", + "title": "SessionsOpenResumeLast" }, - "SessionSizes": { + "SessionsOpenStatus": { + "type": "string", + "enum": [ + "created", + "resumed", + "not_found", + "connected", + "handed_off" + ], + "description": "Outcome of the open request.", + "title": "SessionsOpenStatus", + "x-enumDescriptions": { + "created": "A new session was created.", + "resumed": "An existing session was loaded or reattached.", + "not_found": "No matching persisted session was found.", + "connected": "Connected to an existing remote session.", + "handed_off": "Remote session was handed off to a new local session." + } + }, + "SessionSource": { + "type": "string", + "enum": [ + "local", + "remote", + "all" + ], + "description": "Which session sources to include. Defaults to `local` for backward compatibility.", + "title": "SessionSource", + "x-enumDescriptions": { + "local": "Return only local sessions.", + "remote": "Return only remote sessions.", + "all": "Return both local and remote sessions." + } + }, + "SessionsPollSpawnedSessionsEvent": { "type": "object", "properties": { - "sizes": { - "type": "object", - "additionalProperties": { - "type": "integer", - "minimum": 0 - }, - "description": "Map of sessionId -> on-disk size in bytes for the session's workspace directory" + "sessionId": { + "type": "string", + "description": "Session id of the newly-spawned session." } }, "required": [ - "sizes" + "sessionId" ], "additionalProperties": false, - "description": "Map of sessionId -> on-disk size in bytes for each session's workspace directory.", - "title": "SessionSizes" + "title": "SessionsPollSpawnedSessionsEvent", + "description": "Schema for the `SessionsPollSpawnedSessionsEvent` type." }, - "SessionsListRequest": { + "SessionsPollSpawnedSessionsRequest": { "anyOf": [ { "not": {} @@ -16072,41 +19785,23 @@ { "type": "object", "properties": { - "metadataLimit": { + "cursor": { + "type": "string", + "description": "Opaque cursor returned by a previous poll. Omit on the first call to receive any spawn events buffered since the runtime started." + }, + "waitMs": { "type": "integer", "minimum": 0, - "description": "When provided, only the first N 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 session." - }, - "filter": { - "$ref": "#/definitions/SessionListFilter", - "description": "Optional filter applied to the returned sessions" - }, - "includeDetached": { - "type": "boolean", - "description": "When true, include detached maintenance sessions. Defaults to false for user-facing session lists." + "maximum": 60000, + "description": "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.", + "format": "duration" } }, - "additionalProperties": false, - "description": "Optional metadata-load limit and filters applied to the returned sessions." - } - ], - "description": "Optional metadata-load limit and filters applied to the returned sessions.", - "title": "SessionsListRequest" - }, - "SessionsLoadDeferredRepoHooksRequest": { - "type": "object", - "properties": { - "sessionId": { - "type": "string", - "description": "Active session ID whose deferred repo-level hooks should be loaded" + "additionalProperties": false } - }, - "required": [ - "sessionId" ], - "additionalProperties": false, - "description": "Active session ID whose deferred repo-level hooks should be loaded.", - "title": "SessionsLoadDeferredRepoHooksRequest" + "description": "Cursor and optional long-poll wait for polling runtime-spawned sessions.", + "title": "SessionsPollSpawnedSessionsRequest" }, "SessionsPruneOldRequest": { "type": "object", @@ -16139,6 +19834,19 @@ "description": "Age threshold and optional flags controlling which old sessions are pruned (or simulated when dryRun is true).", "title": "SessionsPruneOldRequest" }, + "SessionsRegisterExtensionToolsOnSessionOptions": { + "type": "object", + "properties": { + "enabled": { + "description": "In-process `() => boolean` gating callback (CLI-only optimization). Marked internal: replaced by runtime-side enable/disable RPCs in the SDK migration.", + "visibility": "internal", + "x-opaque-json": true + } + }, + "additionalProperties": false, + "description": "Optional registration options.", + "title": "SessionsRegisterExtensionToolsOnSessionOptions" + }, "SessionsReleaseLockRequest": { "type": "object", "properties": { @@ -16234,6 +19942,95 @@ "description": "Replace the manager-wide additional plugins. New session creations and subsequent hook reloads see the new set; already-running sessions keep their existing hook installation until the next reload.", "title": "SessionsSetAdditionalPluginsResult" }, + "SessionsSetRemoteControlSteeringRequest": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Target steering state. Today only `true` is actionable on the underlying exporter; `false` is reserved for future use." + } + }, + "required": [ + "enabled" + ], + "additionalProperties": false, + "description": "Patch for the singleton's steering state.", + "title": "SessionsSetRemoteControlSteeringRequest" + }, + "SessionsStartRemoteControlRequest": { + "type": "object", + "properties": { + "sessionId": { + "type": "string", + "description": "Local session id to attach remote control to." + }, + "config": { + "$ref": "#/definitions/RemoteControlConfig", + "description": "Configuration for the runtime-managed remote-control singleton." + } + }, + "required": [ + "sessionId", + "config" + ], + "additionalProperties": false, + "description": "Parameters for attaching the remote-control singleton to a session.", + "title": "SessionsStartRemoteControlRequest" + }, + "SessionsStopRemoteControlRequest": { + "anyOf": [ + { + "not": {} + }, + { + "type": "object", + "properties": { + "expectedSessionId": { + "type": "string", + "description": "When provided, the stop is rejected unless the singleton currently points at this session id (compare-and-swap semantics)." + }, + "force": { + "type": "boolean", + "description": "When true, the singleton is unconditionally torn down regardless of `expectedSessionId`. Use during shutdown or explicit `/remote off`." + } + }, + "additionalProperties": false + } + ], + "description": "Parameters for stopping the remote-control singleton.", + "title": "SessionsStopRemoteControlRequest" + }, + "SessionsTransferRemoteControlRequest": { + "type": "object", + "properties": { + "toSessionId": { + "type": "string", + "description": "Local session id to point remote control at." + }, + "expectedFromSessionId": { + "type": "string", + "description": "When provided, the transfer is rejected unless the singleton currently points at this session id (compare-and-swap semantics to avoid clobbering newer state)." + } + }, + "required": [ + "toSessionId" + ], + "additionalProperties": false, + "description": "Parameters for atomically rebinding the remote-control singleton.", + "title": "SessionsTransferRemoteControlRequest" + }, + "SessionTelemetryEngagement": { + "type": "object", + "properties": { + "engagementId": { + "type": "string", + "description": "Current telemetry engagement ID, when available." + } + }, + "additionalProperties": false, + "description": "Telemetry engagement ID for the session, when available.", + "title": "SessionTelemetryEngagement" + }, "SessionUpdateOptionsParams": { "type": "object", "properties": { @@ -16241,10 +20038,18 @@ "type": "string", "description": "The model ID to use for assistant turns." }, + "modelCapabilitiesOverrides": { + "$ref": "#/definitions/ModelCapabilitiesOverride", + "description": "Per-property model capability overrides for the selected model." + }, "reasoningEffort": { "type": "string", "description": "Reasoning effort for the selected model (model-defined enum)." }, + "reasoningSummary": { + "$ref": "#/definitions/OptionsUpdateReasoningSummary", + "description": "Reasoning summary mode for supported model clients." + }, "clientName": { "type": "string", "description": "Identifier of the client driving the session." @@ -16269,9 +20074,8 @@ "description": "Whether experimental capabilities are enabled." }, "provider": { - "description": "Custom model-provider configuration (BYOK). Opaque shape; see `ProviderConfig` in the runtime.", - "x-opaque-json": true, - "stability": "experimental" + "$ref": "#/definitions/ProviderConfig", + "description": "Custom model-provider configuration (BYOK)." }, "workingDirectory": { "type": "string", @@ -16311,9 +20115,8 @@ "description": "Per-shell process flags (e.g., `pwsh` arguments)." }, "sandboxConfig": { - "description": "Sandbox configuration shape; opaque to SDK consumers. See `SandboxConfig` in the runtime.", - "x-opaque-json": true, - "stability": "experimental" + "$ref": "#/definitions/SandboxConfig", + "description": "Resolved sandbox configuration." }, "logInteractiveShells": { "type": "boolean", @@ -16410,15 +20213,23 @@ "additionalContentExclusionPolicies": { "type": "array", "items": { - "x-opaque-json": true + "$ref": "#/definitions/OptionsUpdateAdditionalContentExclusionPolicy" }, - "description": "Additional content-exclusion policies to merge into the session's policy set. Opaque shape; see `ContentExclusionApiResponse` in the runtime.", + "description": "Additional content-exclusion policies to merge into the session's policy set.", "stability": "experimental" }, "manageScheduleEnabled": { "type": "boolean", "description": "Whether to expose the `manage_schedule` tool to the agent. The runtime always owns the per-session schedule registry; this flag only controls tool exposure (typically gated to staff users)." }, + "sessionCapabilities": { + "type": "array", + "items": { + "$ref": "#/definitions/SessionCapability", + "description": "Session capability id" + }, + "description": "Replaces the session's capability set with the given list. Use to enable or disable capabilities mid-session (e.g., remove `memory` for reproducible scripted runs). Omit the field to leave the existing capability set unchanged." + }, "skipEmbeddingRetrieval": { "type": "boolean", "description": "Whether to skip embedding retrieval pipeline initialization and execution." @@ -16442,6 +20253,10 @@ "enableSkills": { "type": "boolean", "description": "Whether to enable skill directory scanning and loading. Falls back to enableConfigDiscovery when unset." + }, + "contextTier": { + "$ref": "#/definitions/OptionsUpdateContextTier", + "description": "Context tier for models with tiered pricing. The session uses this to derive effective `modelCapabilitiesOverrides` so compaction, truncation, token display, and request limits honor the selected tier." } }, "additionalProperties": false, @@ -16519,6 +20334,21 @@ "ado": "The working directory repository is hosted on Azure DevOps." } }, + "ShellCancelUserRequestedRequest": { + "type": "object", + "properties": { + "requestId": { + "type": "string", + "description": "Request ID previously passed to executeUserRequested" + } + }, + "required": [ + "requestId" + ], + "additionalProperties": false, + "description": "User-requested shell execution cancellation handle.", + "title": "ShellCancelUserRequestedRequest" + }, "ShellExecRequest": { "type": "object", "properties": { @@ -16559,6 +20389,26 @@ "description": "Identifier of the spawned process, used to correlate streamed output and exit notifications.", "title": "ShellExecResult" }, + "ShellExecuteUserRequestedRequest": { + "type": "object", + "properties": { + "requestId": { + "type": "string", + "description": "Caller-provided cancellation handle for this execution" + }, + "command": { + "type": "string", + "description": "Shell command to execute" + } + }, + "required": [ + "requestId", + "command" + ], + "additionalProperties": false, + "description": "User-requested shell command and cancellation handle.", + "title": "ShellExecuteUserRequestedRequest" + }, "ShellKillRequest": { "type": "object", "properties": { @@ -17197,7 +21047,7 @@ }, "prompt": { "type": "string", - "description": "Prompt passed to the agent" + "description": "Most recent prompt delivered to the agent. Updated whenever the agent receives a follow-up message." }, "result": { "type": "string", @@ -17205,7 +21055,11 @@ }, "model": { "type": "string", - "description": "Model used for the task when specified" + "description": "Requested model override for the task when specified" + }, + "resolvedModel": { + "type": "string", + "description": "Runtime model resolved for the task when available" }, "executionMode": { "$ref": "#/definitions/TaskExecutionMode", @@ -18443,6 +22297,46 @@ "title": "UIElicitationStringOneOfFieldOneOf", "description": "Schema for the `UIElicitationStringOneOfFieldOneOf` type." }, + "UIEphemeralQueryRequest": { + "type": "object", + "properties": { + "question": { + "type": "string", + "description": "Question to answer from the current conversation context." + }, + "onChunk": { + "description": "In-process streaming callback `(text) => void` invoked with each token as the model emits it. Marked internal: excluded from the public SDK surface. In a process-separated SDK this is replaced by a streaming RPC that yields chunks and a final answer.", + "visibility": "internal", + "x-opaque-json": true + }, + "abortSignal": { + "description": "In-process `AbortSignal` forwarded to the model client to cancel an in-flight request. Marked internal: excluded from the public SDK surface. Replaced by an explicit cancellation token + cancel RPC in the SDK migration.", + "visibility": "internal", + "x-opaque-json": true + } + }, + "required": [ + "question" + ], + "additionalProperties": false, + "description": "Transient question to answer without adding it to conversation history.", + "title": "UIEphemeralQueryRequest" + }, + "UIEphemeralQueryResult": { + "type": "object", + "properties": { + "answer": { + "type": "string", + "description": "Full assistant response text." + } + }, + "required": [ + "answer" + ], + "additionalProperties": false, + "description": "Transient answer generated from current conversation context.", + "title": "UIEphemeralQueryResult" + }, "UIExitPlanModeAction": { "type": "string", "enum": [ @@ -18939,6 +22833,46 @@ "title": "UserAuthInfo", "description": "Schema for the `UserAuthInfo` type." }, + "UserRequestedShellCommandResult": { + "type": "object", + "properties": { + "toolCallId": { + "type": "string", + "description": "Tool call id emitted for the shell execution" + }, + "success": { + "type": "boolean", + "description": "Whether the command completed successfully" + }, + "output": { + "type": "string", + "description": "Captured command output" + }, + "exitCode": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "description": "Process exit code, when available" + }, + "error": { + "type": "string", + "description": "Error output when the execution failed" + } + }, + "required": [ + "toolCallId", + "success", + "output" + ], + "additionalProperties": false, + "description": "Result of a user-requested shell command.", + "title": "UserRequestedShellCommandResult" + }, "UserToolSessionApproval": { "anyOf": [ { @@ -19575,6 +23509,10 @@ "type": "string", "description": "Display name for the session, if set" }, + "user_named": { + "type": "boolean", + "description": "Whether the display name was explicitly set by the user" + }, "created_at": { "type": "string", "format": "date-time", diff --git a/schemas/session-events.schema.json b/schemas/session-events.schema.json index 5202b7a..b08ee4e 100644 --- a/schemas/session-events.schema.json +++ b/schemas/session-events.schema.json @@ -212,6 +212,10 @@ "type": "string", "description": "Copilot service request ID (x-copilot-service-request-id header) for CAPI log correlation" }, + "apiCallId": { + "type": "string", + "description": "Provider's completion / response identifier; shared across all chunks of a single API call. Used to group multi-chunk assistant utterances." + }, "anthropicAdvisorBlocks": { "type": "array", "items": { @@ -1036,14 +1040,16 @@ "quotaSnapshots": { "type": "object", "additionalProperties": { - "$ref": "#/definitions/AssistantUsageQuotaSnapshot" + "$ref": "#/definitions/AssistantUsageQuotaSnapshot", + "visibility": "internal" }, "description": "Per-quota resource usage snapshots, keyed by quota identifier", "visibility": "internal" }, "copilotUsage": { "$ref": "#/definitions/AssistantUsageCopilotUsage", - "description": "Per-request cost and usage data from the CAPI copilot_usage response field" + "description": "Per-request cost and usage data from the CAPI copilot_usage response field", + "visibility": "internal" }, "reasoningEffort": { "type": "string", @@ -1863,6 +1869,88 @@ "description": "Session event \"session.background_tasks_changed\".", "title": "BackgroundTasksChangedEvent" }, + "CanvasClosedData": { + "type": "object", + "properties": { + "instanceId": { + "type": "string", + "minLength": 1, + "description": "Stable caller-supplied identifier of the canvas instance that was closed" + }, + "extensionId": { + "type": "string", + "description": "Owning provider identifier" + }, + "canvasId": { + "type": "string", + "description": "Provider-local canvas identifier" + } + }, + "required": [ + "instanceId", + "extensionId", + "canvasId" + ], + "additionalProperties": false, + "title": "CanvasClosedData", + "description": "Schema for the `CanvasClosedData` type." + }, + "CanvasClosedEvent": { + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uuid", + "description": "Unique event identifier (UUID v4), generated when the event is emitted" + }, + "timestamp": { + "type": "string", + "format": "date-time", + "description": "ISO 8601 timestamp when the event was created" + }, + "parentId": { + "anyOf": [ + { + "type": "string", + "format": "uuid" + }, + { + "type": "null" + } + ], + "description": "ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event." + }, + "ephemeral": { + "type": "boolean", + "const": true, + "description": "Always true for events that are transient and not persisted to the session event log on disk." + }, + "agentId": { + "type": "string", + "description": "Sub-agent instance identifier. Absent for events from the root/main agent and session-level events." + }, + "type": { + "type": "string", + "const": "session.canvas.closed", + "description": "Type discriminator. Always \"session.canvas.closed\"." + }, + "data": { + "$ref": "#/definitions/CanvasClosedData", + "description": "Schema for the `CanvasClosedData` type." + } + }, + "required": [ + "id", + "timestamp", + "parentId", + "ephemeral", + "type", + "data" + ], + "additionalProperties": false, + "description": "Session event \"session.canvas.closed\".", + "title": "CanvasClosedEvent" + }, "CanvasOpenedAvailability": { "type": "string", "enum": [ @@ -2573,7 +2661,8 @@ }, "copilotUsage": { "$ref": "#/definitions/CompactionCompleteCompactionTokensUsedCopilotUsage", - "description": "Per-request cost and usage data from the CAPI copilot_usage response field" + "description": "Per-request cost and usage data from the CAPI copilot_usage response field", + "visibility": "internal" }, "duration": { "type": "integer", @@ -4376,6 +4465,10 @@ "stack": { "type": "string", "description": "Error stack trace, when available" + }, + "source": { + "type": "string", + "description": "Source label of the hook that errored (e.g. the plugin it was loaded from), when known" } }, "required": [ @@ -4445,6 +4538,10 @@ "message": { "type": "string", "description": "Human-readable progress message from the hook process" + }, + "temporary": { + "type": "boolean", + "description": "When true, this status message replaces the previous temporary one instead of accumulating" } }, "required": [ @@ -4597,7 +4694,7 @@ } }, "additionalProperties": false, - "description": "Payload indicating the session is idle with no background agents in flight", + "description": "Payload indicating the session is idle with no background agents or attached shell commands in flight", "title": "IdleData" }, "IdleEvent": { @@ -4641,7 +4738,7 @@ }, "data": { "$ref": "#/definitions/IdleData", - "description": "Payload indicating the session is idle with no background agents in flight" + "description": "Payload indicating the session is idle with no background agents or attached shell commands in flight" } }, "required": [ @@ -4653,7 +4750,7 @@ "data" ], "additionalProperties": false, - "description": "Session event \"session.idle\". Payload indicating the session is idle with no background agents in flight", + "description": "Session event \"session.idle\". Payload indicating the session is idle with no background agents or attached shell commands in flight", "title": "IdleEvent" }, "InfoData": { @@ -7321,6 +7418,11 @@ "minimum": 0, "description": "Total number of persisted events in the session at the time of resume" }, + "eventsFileSizeBytes": { + "type": "integer", + "minimum": 0, + "description": "On-disk byte size of the session's persisted events.jsonl file at resume time; omitted when the file does not exist or cannot be stat'd" + }, "selectedModel": { "type": "string", "description": "Model currently selected at resume time" @@ -7664,9 +7766,22 @@ "intervalMs": { "type": "integer", "exclusiveMinimum": 0, - "description": "Interval between ticks in milliseconds", + "description": "Interval between ticks in milliseconds (relative-interval schedules)", "format": "duration" }, + "cron": { + "type": "string", + "description": "5-field cron expression for a recurring calendar schedule, evaluated in `tz`" + }, + "tz": { + "type": "string", + "description": "IANA timezone the `cron` expression is evaluated in" + }, + "at": { + "type": "integer", + "exclusiveMinimum": 0, + "description": "Absolute fire time (epoch milliseconds) for a one-shot calendar schedule" + }, "prompt": { "type": "string", "description": "Prompt text that gets enqueued on every tick" @@ -7682,7 +7797,6 @@ }, "required": [ "id", - "intervalMs", "prompt" ], "additionalProperties": false, @@ -7763,7 +7877,7 @@ }, { "$ref": "#/definitions/IdleEvent", - "description": "Session event \"session.idle\". Payload indicating the session is idle with no background agents in flight" + "description": "Session event \"session.idle\". Payload indicating the session is idle with no background agents or attached shell commands in flight" }, { "$ref": "#/definitions/TitleChangedEvent", @@ -8089,6 +8203,10 @@ "$ref": "#/definitions/CanvasRegistryChangedEvent", "description": "Session event \"session.canvas.registry_changed\"." }, + { + "$ref": "#/definitions/CanvasClosedEvent", + "description": "Session event \"session.canvas.closed\"." + }, { "$ref": "#/definitions/ExtensionsAttachmentsPushedEvent", "description": "Session event \"session.extensions.attachments_pushed\"." @@ -8186,6 +8304,11 @@ "minimum": 0, "description": "Unix timestamp (milliseconds) when the session started" }, + "eventsFileSizeBytes": { + "type": "integer", + "minimum": 0, + "description": "On-disk byte size of the session's persisted events.jsonl file at shutdown time; omitted when the file does not exist or cannot be stat'd" + }, "codeChanges": { "$ref": "#/definitions/ShutdownCodeChanges", "description": "Aggregate code change metrics for the session" @@ -9068,7 +9191,7 @@ }, "model": { "type": "string", - "description": "Model used by the sub-agent (if any model calls succeeded before failure)" + "description": "Model selected for the sub-agent, when known" }, "totalToolCalls": { "type": "integer", @@ -9261,7 +9384,7 @@ }, "model": { "type": "string", - "description": "Model the sub-agent will run with, when known at start. Surfaced in the timeline for auto-selected sub-agents (e.g. rubber-duck)." + "description": "Model the sub-agent will run with, when known at start." } }, "required": [ diff --git a/src/github/copilot_sdk/generated/event_specs.clj b/src/github/copilot_sdk/generated/event_specs.clj index 631154d..3e25ff3 100644 --- a/src/github/copilot_sdk/generated/event_specs.clj +++ b/src/github/copilot_sdk/generated/event_specs.clj @@ -45,6 +45,8 @@ (s/def :github.copilot-sdk.generated.event-specs/arguments (s/spec (fn [v950] (or (s/valid? clojure.core/any? v950) (s/valid? clojure.core/map? v950))))) +(s/def :github.copilot-sdk.generated.event-specs/at clojure.core/integer?) + (s/def :github.copilot-sdk.generated.event-specs/attachments (s/coll-of (s/or :branch-0 clojure.core/map? :branch-1 clojure.core/map? :branch-2 clojure.core/map? :branch-3 clojure.core/map? :branch-4 clojure.core/map? :branch-5 clojure.core/map?))) (s/def :github.copilot-sdk.generated.event-specs/auto-approve-edits clojure.core/boolean?) @@ -97,6 +99,8 @@ (s/def :github.copilot-sdk.generated.event-specs/cost clojure.core/number?) +(s/def :github.copilot-sdk.generated.event-specs/cron clojure.core/string?) + (s/def :github.copilot-sdk.generated.event-specs/current-model clojure.core/string?) (s/def :github.copilot-sdk.generated.event-specs/current-tokens clojure.core/integer?) @@ -143,6 +147,8 @@ (s/def :github.copilot-sdk.generated.event-specs/event-count clojure.core/integer?) +(s/def :github.copilot-sdk.generated.event-specs/events-file-size-bytes clojure.core/integer?) + (s/def :github.copilot-sdk.generated.event-specs/events-removed clojure.core/integer?) (s/def :github.copilot-sdk.generated.event-specs/extension-id clojure.core/string?) @@ -389,6 +395,8 @@ (s/def :github.copilot-sdk.generated.event-specs/system-tokens clojure.core/integer?) +(s/def :github.copilot-sdk.generated.event-specs/temporary clojure.core/boolean?) + (s/def :github.copilot-sdk.generated.event-specs/time-to-first-token-ms clojure.core/integer?) (s/def :github.copilot-sdk.generated.event-specs/timestamp clojure.core/string?) @@ -443,7 +451,9 @@ (s/def :github.copilot-sdk.generated.event-specs/turn-id clojure.core/string?) -(s/def :github.copilot-sdk.generated.event-specs/type (s/spec (fn [v949] (or (s/valid? #{"session.start"} v949) (s/valid? #{"session.resume"} v949) (s/valid? #{"session.remote_steerable_changed"} v949) (s/valid? #{"session.error"} v949) (s/valid? #{"session.idle"} v949) (s/valid? #{"session.title_changed"} v949) (s/valid? #{"session.schedule_created"} v949) (s/valid? #{"session.schedule_cancelled"} v949) (s/valid? #{"session.autopilot_objective_changed"} v949) (s/valid? #{"session.info"} v949) (s/valid? #{"session.warning"} v949) (s/valid? #{"session.model_change"} v949) (s/valid? #{"session.mode_changed"} v949) (s/valid? #{"session.permissions_changed"} v949) (s/valid? #{"session.plan_changed"} v949) (s/valid? #{"session.workspace_file_changed"} v949) (s/valid? #{"session.handoff"} v949) (s/valid? #{"session.truncation"} v949) (s/valid? #{"session.snapshot_rewind"} v949) (s/valid? #{"session.shutdown"} v949) (s/valid? #{"session.context_changed"} v949) (s/valid? #{"session.usage_info"} v949) (s/valid? #{"session.compaction_start"} v949) (s/valid? #{"session.compaction_complete"} v949) (s/valid? #{"session.task_complete"} v949) (s/valid? #{"user.message"} v949) (s/valid? #{"pending_messages.modified"} v949) (s/valid? #{"assistant.turn_start"} v949) (s/valid? #{"assistant.intent"} v949) (s/valid? #{"assistant.reasoning"} v949) (s/valid? #{"assistant.reasoning_delta"} v949) (s/valid? #{"assistant.streaming_delta"} v949) (s/valid? #{"assistant.message"} v949) (s/valid? #{"assistant.message_start"} v949) (s/valid? #{"assistant.message_delta"} v949) (s/valid? #{"assistant.turn_end"} v949) (s/valid? #{"assistant.usage"} v949) (s/valid? #{"model.call_failure"} v949) (s/valid? #{"abort"} v949) (s/valid? #{"tool.user_requested"} v949) (s/valid? #{"tool.execution_start"} v949) (s/valid? #{"tool.execution_partial_result"} v949) (s/valid? #{"tool.execution_progress"} v949) (s/valid? #{"tool.execution_complete"} v949) (s/valid? #{"skill.invoked"} v949) (s/valid? #{"subagent.started"} v949) (s/valid? #{"subagent.completed"} v949) (s/valid? #{"subagent.failed"} v949) (s/valid? #{"subagent.selected"} v949) (s/valid? #{"subagent.deselected"} v949) (s/valid? #{"hook.start"} v949) (s/valid? #{"hook.end"} v949) (s/valid? #{"hook.progress"} v949) (s/valid? #{"system.message"} v949) (s/valid? #{"system.notification"} v949) (s/valid? #{"permission.requested"} v949) (s/valid? #{"permission.completed"} v949) (s/valid? #{"user_input.requested"} v949) (s/valid? #{"user_input.completed"} v949) (s/valid? #{"elicitation.requested"} v949) (s/valid? #{"elicitation.completed"} v949) (s/valid? #{"sampling.requested"} v949) (s/valid? #{"sampling.completed"} v949) (s/valid? #{"mcp.oauth_required"} v949) (s/valid? #{"mcp.oauth_completed"} v949) (s/valid? #{"session.custom_notification"} v949) (s/valid? #{"external_tool.requested"} v949) (s/valid? #{"external_tool.completed"} v949) (s/valid? #{"command.queued"} v949) (s/valid? #{"command.execute"} v949) (s/valid? #{"command.completed"} v949) (s/valid? #{"auto_mode_switch.requested"} v949) (s/valid? #{"auto_mode_switch.completed"} v949) (s/valid? #{"commands.changed"} v949) (s/valid? #{"capabilities.changed"} v949) (s/valid? #{"exit_plan_mode.requested"} v949) (s/valid? #{"exit_plan_mode.completed"} v949) (s/valid? #{"session.tools_updated"} v949) (s/valid? #{"session.background_tasks_changed"} v949) (s/valid? #{"session.skills_loaded"} v949) (s/valid? #{"session.custom_agents_updated"} v949) (s/valid? #{"session.mcp_servers_loaded"} v949) (s/valid? #{"session.mcp_server_status_changed"} v949) (s/valid? #{"session.extensions_loaded"} v949) (s/valid? #{"session.canvas.opened"} v949) (s/valid? #{"session.canvas.registry_changed"} v949) (s/valid? #{"session.extensions.attachments_pushed"} v949) (s/valid? #{"mcp_app.tool_call_complete"} v949))))) +(s/def :github.copilot-sdk.generated.event-specs/type (s/spec (fn [v949] (or (s/valid? #{"session.start"} v949) (s/valid? #{"session.resume"} v949) (s/valid? #{"session.remote_steerable_changed"} v949) (s/valid? #{"session.error"} v949) (s/valid? #{"session.idle"} v949) (s/valid? #{"session.title_changed"} v949) (s/valid? #{"session.schedule_created"} v949) (s/valid? #{"session.schedule_cancelled"} v949) (s/valid? #{"session.autopilot_objective_changed"} v949) (s/valid? #{"session.info"} v949) (s/valid? #{"session.warning"} v949) (s/valid? #{"session.model_change"} v949) (s/valid? #{"session.mode_changed"} v949) (s/valid? #{"session.permissions_changed"} v949) (s/valid? #{"session.plan_changed"} v949) (s/valid? #{"session.workspace_file_changed"} v949) (s/valid? #{"session.handoff"} v949) (s/valid? #{"session.truncation"} v949) (s/valid? #{"session.snapshot_rewind"} v949) (s/valid? #{"session.shutdown"} v949) (s/valid? #{"session.context_changed"} v949) (s/valid? #{"session.usage_info"} v949) (s/valid? #{"session.compaction_start"} v949) (s/valid? #{"session.compaction_complete"} v949) (s/valid? #{"session.task_complete"} v949) (s/valid? #{"user.message"} v949) (s/valid? #{"pending_messages.modified"} v949) (s/valid? #{"assistant.turn_start"} v949) (s/valid? #{"assistant.intent"} v949) (s/valid? #{"assistant.reasoning"} v949) (s/valid? #{"assistant.reasoning_delta"} v949) (s/valid? #{"assistant.streaming_delta"} v949) (s/valid? #{"assistant.message"} v949) (s/valid? #{"assistant.message_start"} v949) (s/valid? #{"assistant.message_delta"} v949) (s/valid? #{"assistant.turn_end"} v949) (s/valid? #{"assistant.usage"} v949) (s/valid? #{"model.call_failure"} v949) (s/valid? #{"abort"} v949) (s/valid? #{"tool.user_requested"} v949) (s/valid? #{"tool.execution_start"} v949) (s/valid? #{"tool.execution_partial_result"} v949) (s/valid? #{"tool.execution_progress"} v949) (s/valid? #{"tool.execution_complete"} v949) (s/valid? #{"skill.invoked"} v949) (s/valid? #{"subagent.started"} v949) (s/valid? #{"subagent.completed"} v949) (s/valid? #{"subagent.failed"} v949) (s/valid? #{"subagent.selected"} v949) (s/valid? #{"subagent.deselected"} v949) (s/valid? #{"hook.start"} v949) (s/valid? #{"hook.end"} v949) (s/valid? #{"hook.progress"} v949) (s/valid? #{"system.message"} v949) (s/valid? #{"system.notification"} v949) (s/valid? #{"permission.requested"} v949) (s/valid? #{"permission.completed"} v949) (s/valid? #{"user_input.requested"} v949) (s/valid? #{"user_input.completed"} v949) (s/valid? #{"elicitation.requested"} v949) (s/valid? #{"elicitation.completed"} v949) (s/valid? #{"sampling.requested"} v949) (s/valid? #{"sampling.completed"} v949) (s/valid? #{"mcp.oauth_required"} v949) (s/valid? #{"mcp.oauth_completed"} v949) (s/valid? #{"session.custom_notification"} v949) (s/valid? #{"external_tool.requested"} v949) (s/valid? #{"external_tool.completed"} v949) (s/valid? #{"command.queued"} v949) (s/valid? #{"command.execute"} v949) (s/valid? #{"command.completed"} v949) (s/valid? #{"auto_mode_switch.requested"} v949) (s/valid? #{"auto_mode_switch.completed"} v949) (s/valid? #{"commands.changed"} v949) (s/valid? #{"capabilities.changed"} v949) (s/valid? #{"exit_plan_mode.requested"} v949) (s/valid? #{"exit_plan_mode.completed"} v949) (s/valid? #{"session.tools_updated"} v949) (s/valid? #{"session.background_tasks_changed"} v949) (s/valid? #{"session.skills_loaded"} v949) (s/valid? #{"session.custom_agents_updated"} v949) (s/valid? #{"session.mcp_servers_loaded"} v949) (s/valid? #{"session.mcp_server_status_changed"} v949) (s/valid? #{"session.extensions_loaded"} v949) (s/valid? #{"session.canvas.opened"} v949) (s/valid? #{"session.canvas.registry_changed"} v949) (s/valid? #{"session.canvas.closed"} v949) (s/valid? #{"session.extensions.attachments_pushed"} v949) (s/valid? #{"mcp_app.tool_call_complete"} v949))))) + +(s/def :github.copilot-sdk.generated.event-specs/tz clojure.core/string?) (s/def :github.copilot-sdk.generated.event-specs/ui clojure.core/map?) @@ -465,7 +475,7 @@ (s/def :github.copilot-sdk.generated.event-specs/assistant.intent-data (s/keys :req-un [:github.copilot-sdk.generated.event-specs/intent])) -(s/def :github.copilot-sdk.generated.event-specs/assistant.message-data (s/keys :req-un [:github.copilot-sdk.generated.event-specs/content :github.copilot-sdk.generated.event-specs/message-id] :opt-un [:github.copilot-sdk.generated.event-specs/anthropic-advisor-blocks :github.copilot-sdk.generated.event-specs/anthropic-advisor-model :github.copilot-sdk.generated.event-specs/encrypted-content :github.copilot-sdk.generated.event-specs/interaction-id :github.copilot-sdk.generated.event-specs/model :github.copilot-sdk.generated.event-specs/output-tokens :github.copilot-sdk.generated.event-specs/parent-tool-call-id :github.copilot-sdk.generated.event-specs/phase :github.copilot-sdk.generated.event-specs/reasoning-opaque :github.copilot-sdk.generated.event-specs/reasoning-text :github.copilot-sdk.generated.event-specs/request-id :github.copilot-sdk.generated.event-specs/service-request-id :github.copilot-sdk.generated.event-specs/tool-requests :github.copilot-sdk.generated.event-specs/turn-id])) +(s/def :github.copilot-sdk.generated.event-specs/assistant.message-data (s/keys :req-un [:github.copilot-sdk.generated.event-specs/content :github.copilot-sdk.generated.event-specs/message-id] :opt-un [:github.copilot-sdk.generated.event-specs/anthropic-advisor-blocks :github.copilot-sdk.generated.event-specs/anthropic-advisor-model :github.copilot-sdk.generated.event-specs/api-call-id :github.copilot-sdk.generated.event-specs/encrypted-content :github.copilot-sdk.generated.event-specs/interaction-id :github.copilot-sdk.generated.event-specs/model :github.copilot-sdk.generated.event-specs/output-tokens :github.copilot-sdk.generated.event-specs/parent-tool-call-id :github.copilot-sdk.generated.event-specs/phase :github.copilot-sdk.generated.event-specs/reasoning-opaque :github.copilot-sdk.generated.event-specs/reasoning-text :github.copilot-sdk.generated.event-specs/request-id :github.copilot-sdk.generated.event-specs/service-request-id :github.copilot-sdk.generated.event-specs/tool-requests :github.copilot-sdk.generated.event-specs/turn-id])) (s/def :github.copilot-sdk.generated.event-specs/assistant.message_delta-data (s/keys :req-un [:github.copilot-sdk.generated.event-specs/delta-content :github.copilot-sdk.generated.event-specs/message-id] :opt-un [:github.copilot-sdk.generated.event-specs/parent-tool-call-id])) @@ -511,7 +521,7 @@ (s/def :github.copilot-sdk.generated.event-specs/hook.end-data (s/keys :req-un [:github.copilot-sdk.generated.event-specs/hook-invocation-id :github.copilot-sdk.generated.event-specs/hook-type :github.copilot-sdk.generated.event-specs/success] :opt-un [:github.copilot-sdk.generated.event-specs/error :github.copilot-sdk.generated.event-specs/output])) -(s/def :github.copilot-sdk.generated.event-specs/hook.progress-data (s/keys :req-un [:github.copilot-sdk.generated.event-specs/message])) +(s/def :github.copilot-sdk.generated.event-specs/hook.progress-data (s/keys :req-un [:github.copilot-sdk.generated.event-specs/message] :opt-un [:github.copilot-sdk.generated.event-specs/temporary])) (s/def :github.copilot-sdk.generated.event-specs/hook.start-data (s/keys :req-un [:github.copilot-sdk.generated.event-specs/hook-invocation-id :github.copilot-sdk.generated.event-specs/hook-type] :opt-un [:github.copilot-sdk.generated.event-specs/input])) @@ -537,6 +547,8 @@ (s/def :github.copilot-sdk.generated.event-specs/session.background_tasks_changed-data (s/keys)) +(s/def :github.copilot-sdk.generated.event-specs/session.canvas.closed-data (s/keys :req-un [:github.copilot-sdk.generated.event-specs/canvas-id :github.copilot-sdk.generated.event-specs/extension-id :github.copilot-sdk.generated.event-specs/instance-id])) + (s/def :github.copilot-sdk.generated.event-specs/session.canvas.opened-data (s/keys :req-un [:github.copilot-sdk.generated.event-specs/availability :github.copilot-sdk.generated.event-specs/canvas-id :github.copilot-sdk.generated.event-specs/extension-id :github.copilot-sdk.generated.event-specs/instance-id :github.copilot-sdk.generated.event-specs/reopen] :opt-un [:github.copilot-sdk.generated.event-specs/extension-name :github.copilot-sdk.generated.event-specs/input :github.copilot-sdk.generated.event-specs/status :github.copilot-sdk.generated.event-specs/title :github.copilot-sdk.generated.event-specs/url])) (s/def :github.copilot-sdk.generated.event-specs/session.canvas.registry_changed-data (s/keys :req-un [:github.copilot-sdk.generated.event-specs/canvases])) @@ -577,13 +589,13 @@ (s/def :github.copilot-sdk.generated.event-specs/session.remote_steerable_changed-data (s/keys :req-un [:github.copilot-sdk.generated.event-specs/remote-steerable])) -(s/def :github.copilot-sdk.generated.event-specs/session.resume-data (s/keys :req-un [:github.copilot-sdk.generated.event-specs/event-count :github.copilot-sdk.generated.event-specs/resume-time] :opt-un [:github.copilot-sdk.generated.event-specs/already-in-use :github.copilot-sdk.generated.event-specs/context :github.copilot-sdk.generated.event-specs/context-tier :github.copilot-sdk.generated.event-specs/continue-pending-work :github.copilot-sdk.generated.event-specs/reasoning-effort :github.copilot-sdk.generated.event-specs/reasoning-summary :github.copilot-sdk.generated.event-specs/remote-steerable :github.copilot-sdk.generated.event-specs/selected-model :github.copilot-sdk.generated.event-specs/session-was-active])) +(s/def :github.copilot-sdk.generated.event-specs/session.resume-data (s/keys :req-un [:github.copilot-sdk.generated.event-specs/event-count :github.copilot-sdk.generated.event-specs/resume-time] :opt-un [:github.copilot-sdk.generated.event-specs/already-in-use :github.copilot-sdk.generated.event-specs/context :github.copilot-sdk.generated.event-specs/context-tier :github.copilot-sdk.generated.event-specs/continue-pending-work :github.copilot-sdk.generated.event-specs/events-file-size-bytes :github.copilot-sdk.generated.event-specs/reasoning-effort :github.copilot-sdk.generated.event-specs/reasoning-summary :github.copilot-sdk.generated.event-specs/remote-steerable :github.copilot-sdk.generated.event-specs/selected-model :github.copilot-sdk.generated.event-specs/session-was-active])) (s/def :github.copilot-sdk.generated.event-specs/session.schedule_cancelled-data (s/and (s/keys :req-un [:github.copilot-sdk.generated.event-specs/id]) (fn [data] (s/valid? clojure.core/integer? (:id data))))) -(s/def :github.copilot-sdk.generated.event-specs/session.schedule_created-data (s/and (s/keys :req-un [:github.copilot-sdk.generated.event-specs/id :github.copilot-sdk.generated.event-specs/interval-ms :github.copilot-sdk.generated.event-specs/prompt] :opt-un [:github.copilot-sdk.generated.event-specs/display-prompt :github.copilot-sdk.generated.event-specs/recurring]) (fn [data] (s/valid? clojure.core/integer? (:id data))))) +(s/def :github.copilot-sdk.generated.event-specs/session.schedule_created-data (s/and (s/keys :req-un [:github.copilot-sdk.generated.event-specs/id :github.copilot-sdk.generated.event-specs/prompt] :opt-un [:github.copilot-sdk.generated.event-specs/at :github.copilot-sdk.generated.event-specs/cron :github.copilot-sdk.generated.event-specs/display-prompt :github.copilot-sdk.generated.event-specs/interval-ms :github.copilot-sdk.generated.event-specs/recurring :github.copilot-sdk.generated.event-specs/tz]) (fn [data] (s/valid? clojure.core/integer? (:id data))))) -(s/def :github.copilot-sdk.generated.event-specs/session.shutdown-data (s/keys :req-un [:github.copilot-sdk.generated.event-specs/code-changes :github.copilot-sdk.generated.event-specs/model-metrics :github.copilot-sdk.generated.event-specs/session-start-time :github.copilot-sdk.generated.event-specs/shutdown-type :github.copilot-sdk.generated.event-specs/total-api-duration-ms] :opt-un [:github.copilot-sdk.generated.event-specs/conversation-tokens :github.copilot-sdk.generated.event-specs/current-model :github.copilot-sdk.generated.event-specs/current-tokens :github.copilot-sdk.generated.event-specs/error-reason :github.copilot-sdk.generated.event-specs/system-tokens :github.copilot-sdk.generated.event-specs/token-details :github.copilot-sdk.generated.event-specs/tool-definitions-tokens :github.copilot-sdk.generated.event-specs/total-nano-aiu :github.copilot-sdk.generated.event-specs/total-premium-requests])) +(s/def :github.copilot-sdk.generated.event-specs/session.shutdown-data (s/keys :req-un [:github.copilot-sdk.generated.event-specs/code-changes :github.copilot-sdk.generated.event-specs/model-metrics :github.copilot-sdk.generated.event-specs/session-start-time :github.copilot-sdk.generated.event-specs/shutdown-type :github.copilot-sdk.generated.event-specs/total-api-duration-ms] :opt-un [:github.copilot-sdk.generated.event-specs/conversation-tokens :github.copilot-sdk.generated.event-specs/current-model :github.copilot-sdk.generated.event-specs/current-tokens :github.copilot-sdk.generated.event-specs/error-reason :github.copilot-sdk.generated.event-specs/events-file-size-bytes :github.copilot-sdk.generated.event-specs/system-tokens :github.copilot-sdk.generated.event-specs/token-details :github.copilot-sdk.generated.event-specs/tool-definitions-tokens :github.copilot-sdk.generated.event-specs/total-nano-aiu :github.copilot-sdk.generated.event-specs/total-premium-requests])) (s/def :github.copilot-sdk.generated.event-specs/session.skills_loaded-data (s/keys :req-un [:github.copilot-sdk.generated.event-specs/skills])) @@ -713,6 +725,8 @@ (s/def :github.copilot-sdk.generated.event-specs/session.background_tasks_changed (s/and (s/keys :req-un [:github.copilot-sdk.generated.event-specs/data :github.copilot-sdk.generated.event-specs/ephemeral :github.copilot-sdk.generated.event-specs/id :github.copilot-sdk.generated.event-specs/parent-id :github.copilot-sdk.generated.event-specs/timestamp :github.copilot-sdk.generated.event-specs/type] :opt-un [:github.copilot-sdk.generated.event-specs/agent-id]) (fn [event] (clojure.core/= true (:ephemeral event))) (fn [event] (clojure.core/= "session.background_tasks_changed" (:type event))) (fn [event] (s/valid? clojure.core/string? (:id event))) (fn [event] (s/valid? :github.copilot-sdk.generated.event-specs/session.background_tasks_changed-data (:data event))))) +(s/def :github.copilot-sdk.generated.event-specs/session.canvas.closed (s/and (s/keys :req-un [:github.copilot-sdk.generated.event-specs/data :github.copilot-sdk.generated.event-specs/ephemeral :github.copilot-sdk.generated.event-specs/id :github.copilot-sdk.generated.event-specs/parent-id :github.copilot-sdk.generated.event-specs/timestamp :github.copilot-sdk.generated.event-specs/type] :opt-un [:github.copilot-sdk.generated.event-specs/agent-id]) (fn [event] (clojure.core/= true (:ephemeral event))) (fn [event] (clojure.core/= "session.canvas.closed" (:type event))) (fn [event] (s/valid? clojure.core/string? (:id event))) (fn [event] (s/valid? :github.copilot-sdk.generated.event-specs/session.canvas.closed-data (:data event))))) + (s/def :github.copilot-sdk.generated.event-specs/session.canvas.opened (s/and (s/keys :req-un [:github.copilot-sdk.generated.event-specs/data :github.copilot-sdk.generated.event-specs/ephemeral :github.copilot-sdk.generated.event-specs/id :github.copilot-sdk.generated.event-specs/parent-id :github.copilot-sdk.generated.event-specs/timestamp :github.copilot-sdk.generated.event-specs/type] :opt-un [:github.copilot-sdk.generated.event-specs/agent-id]) (fn [event] (clojure.core/= true (:ephemeral event))) (fn [event] (clojure.core/= "session.canvas.opened" (:type event))) (fn [event] (s/valid? clojure.core/string? (:id event))) (fn [event] (s/valid? :github.copilot-sdk.generated.event-specs/session.canvas.opened-data (:data event))))) (s/def :github.copilot-sdk.generated.event-specs/session.canvas.registry_changed (s/and (s/keys :req-un [:github.copilot-sdk.generated.event-specs/data :github.copilot-sdk.generated.event-specs/ephemeral :github.copilot-sdk.generated.event-specs/id :github.copilot-sdk.generated.event-specs/parent-id :github.copilot-sdk.generated.event-specs/timestamp :github.copilot-sdk.generated.event-specs/type] :opt-un [:github.copilot-sdk.generated.event-specs/agent-id]) (fn [event] (clojure.core/= true (:ephemeral event))) (fn [event] (clojure.core/= "session.canvas.registry_changed" (:type event))) (fn [event] (s/valid? clojure.core/string? (:id event))) (fn [event] (s/valid? :github.copilot-sdk.generated.event-specs/session.canvas.registry_changed-data (:data event))))) @@ -813,7 +827,7 @@ (s/def :github.copilot-sdk.generated.event-specs/user_input.requested (s/and (s/keys :req-un [:github.copilot-sdk.generated.event-specs/data :github.copilot-sdk.generated.event-specs/ephemeral :github.copilot-sdk.generated.event-specs/id :github.copilot-sdk.generated.event-specs/parent-id :github.copilot-sdk.generated.event-specs/timestamp :github.copilot-sdk.generated.event-specs/type] :opt-un [:github.copilot-sdk.generated.event-specs/agent-id]) (fn [event] (clojure.core/= true (:ephemeral event))) (fn [event] (clojure.core/= "user_input.requested" (:type event))) (fn [event] (s/valid? clojure.core/string? (:id event))) (fn [event] (s/valid? :github.copilot-sdk.generated.event-specs/user_input.requested-data (:data event))))) -(def event-types "Set of all event-type strings known to the schema." #{"abort" "assistant.intent" "assistant.message" "assistant.message_delta" "assistant.message_start" "assistant.reasoning" "assistant.reasoning_delta" "assistant.streaming_delta" "assistant.turn_end" "assistant.turn_start" "assistant.usage" "auto_mode_switch.completed" "auto_mode_switch.requested" "capabilities.changed" "command.completed" "command.execute" "command.queued" "commands.changed" "elicitation.completed" "elicitation.requested" "exit_plan_mode.completed" "exit_plan_mode.requested" "external_tool.completed" "external_tool.requested" "hook.end" "hook.progress" "hook.start" "mcp.oauth_completed" "mcp.oauth_required" "mcp_app.tool_call_complete" "model.call_failure" "pending_messages.modified" "permission.completed" "permission.requested" "sampling.completed" "sampling.requested" "session.autopilot_objective_changed" "session.background_tasks_changed" "session.canvas.opened" "session.canvas.registry_changed" "session.compaction_complete" "session.compaction_start" "session.context_changed" "session.custom_agents_updated" "session.custom_notification" "session.error" "session.extensions.attachments_pushed" "session.extensions_loaded" "session.handoff" "session.idle" "session.info" "session.mcp_server_status_changed" "session.mcp_servers_loaded" "session.mode_changed" "session.model_change" "session.permissions_changed" "session.plan_changed" "session.remote_steerable_changed" "session.resume" "session.schedule_cancelled" "session.schedule_created" "session.shutdown" "session.skills_loaded" "session.snapshot_rewind" "session.start" "session.task_complete" "session.title_changed" "session.tools_updated" "session.truncation" "session.usage_info" "session.warning" "session.workspace_file_changed" "skill.invoked" "subagent.completed" "subagent.deselected" "subagent.failed" "subagent.selected" "subagent.started" "system.message" "system.notification" "tool.execution_complete" "tool.execution_partial_result" "tool.execution_progress" "tool.execution_start" "tool.user_requested" "user.message" "user_input.completed" "user_input.requested"}) +(def event-types "Set of all event-type strings known to the schema." #{"abort" "assistant.intent" "assistant.message" "assistant.message_delta" "assistant.message_start" "assistant.reasoning" "assistant.reasoning_delta" "assistant.streaming_delta" "assistant.turn_end" "assistant.turn_start" "assistant.usage" "auto_mode_switch.completed" "auto_mode_switch.requested" "capabilities.changed" "command.completed" "command.execute" "command.queued" "commands.changed" "elicitation.completed" "elicitation.requested" "exit_plan_mode.completed" "exit_plan_mode.requested" "external_tool.completed" "external_tool.requested" "hook.end" "hook.progress" "hook.start" "mcp.oauth_completed" "mcp.oauth_required" "mcp_app.tool_call_complete" "model.call_failure" "pending_messages.modified" "permission.completed" "permission.requested" "sampling.completed" "sampling.requested" "session.autopilot_objective_changed" "session.background_tasks_changed" "session.canvas.closed" "session.canvas.opened" "session.canvas.registry_changed" "session.compaction_complete" "session.compaction_start" "session.context_changed" "session.custom_agents_updated" "session.custom_notification" "session.error" "session.extensions.attachments_pushed" "session.extensions_loaded" "session.handoff" "session.idle" "session.info" "session.mcp_server_status_changed" "session.mcp_servers_loaded" "session.mode_changed" "session.model_change" "session.permissions_changed" "session.plan_changed" "session.remote_steerable_changed" "session.resume" "session.schedule_cancelled" "session.schedule_created" "session.shutdown" "session.skills_loaded" "session.snapshot_rewind" "session.start" "session.task_complete" "session.title_changed" "session.tools_updated" "session.truncation" "session.usage_info" "session.warning" "session.workspace_file_changed" "skill.invoked" "subagent.completed" "subagent.deselected" "subagent.failed" "subagent.selected" "subagent.started" "system.message" "system.notification" "tool.execution_complete" "tool.execution_partial_result" "tool.execution_progress" "tool.execution_start" "tool.user_requested" "user.message" "user_input.completed" "user_input.requested"}) (defmulti event-mm :type) @@ -893,6 +907,8 @@ (defmethod event-mm "session.background_tasks_changed" [_] (s/get-spec :github.copilot-sdk.generated.event-specs/session.background_tasks_changed)) +(defmethod event-mm "session.canvas.closed" [_] (s/get-spec :github.copilot-sdk.generated.event-specs/session.canvas.closed)) + (defmethod event-mm "session.canvas.opened" [_] (s/get-spec :github.copilot-sdk.generated.event-specs/session.canvas.opened)) (defmethod event-mm "session.canvas.registry_changed" [_] (s/get-spec :github.copilot-sdk.generated.event-specs/session.canvas.registry_changed)) diff --git a/test/github/copilot_sdk/codegen_test.clj b/test/github/copilot_sdk/codegen_test.clj index 526b506..0a30c44 100644 --- a/test/github/copilot_sdk/codegen_test.clj +++ b/test/github/copilot_sdk/codegen_test.clj @@ -254,7 +254,11 @@ :status "active"} "hook.progress" - {:message "extracting..."}}) + {:message "extracting..."} + + ;; v1.0.1 sync: session.canvas.closed (upstream PR #1604). + "session.canvas.closed" + {:instance-id "i1" :extension-id "ext.x" :canvas-id "diff"}}) (defn- envelope "Wrap a data payload in a minimal valid envelope of the given type. Honours From d31a27b6097781430e7e1dfc50f6b7c850c792d6 Mon Sep 17 00:00:00 2001 From: Karl Krukow Date: Fri, 12 Jun 2026 13:25:39 +0200 Subject: [PATCH 2/7] feat(session): port open-canvases snapshot from upstream PR #1604 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a per-session in-memory snapshot of currently-open canvases, mirroring the upstream Node.js client's openCanvasInstances behavior. State machine - Initialize :open-canvases [] alongside :capabilities at session creation. - Internal mutators set-open-canvases! / upsert-open-canvas! / and validates strictly (mirrors upstream isOpenCanvasInstance: required :instance-id, :extension-id, :canvas-id, :reopen boolean, :availability "ready"|"stale"); bad payloads log a warn and no-op. Remove only requires :instance-id. - Public getter open-canvases returns the immutable snapshot vector. Wiring (client.clj) - session.canvas.opened -> upsert-open-canvas! before publishing event, so observers see consistent state (matches the capabilities.changed pattern at lines 612-614). - session.canvas.closed -> remove-open-canvas! before publishing. - session.resume responses populate the snapshot via set-open-canvases! on both sync and async resume paths. session.create does NOT populate it (matches upstream client.ts). - :open-canvases accepted in resume-session / join-session config to seed the snapshot when reconnecting (upstream ResumeSessionConfig). canvas-instance->wire produces an explicit camelCase keyword wire shape and stringifies caller-supplied :input keys deeply so they bypass csk on outbound — caller-defined opaque keys (e.g. :user_id) round-trip verbatim. Opaque :input preservation (protocol.clj) - session.canvas.opened event :input map is preserved verbatim through preserve-event-opaque-fields. - session.resume response openCanvases[] :input maps are restored positionally in normalize-incoming after wire->clj recursion. Specs - :copilot/session.canvas.closed added to the public ::event-type set. - ::open-canvas-instance / ::open-canvases (open-keys for forward-compat). - :open-canvases added to resume-session-config-keys, ::resume-session-config, ::join-session-config :opt-un. - ::at tightened to pos-int? (epoch-ms). - ::session.schedule_created-data: :interval-ms moved to :opt-un, plus new :at, :cron, :tz fields. - :events-file-size-bytes nat-int? on resume/shutdown :opt-un. - :api-call-id on assistant.message :opt-un. - :temporary boolean? on hook.progress :opt-un. Public surface (copilot_sdk.clj) - :copilot/session.canvas.closed added to event-types (not session-events, matching the existing canvas treatment from 1.0.0). - session/open-canvases re-exported at the top-level namespace. Tests (integration_test.clj) - 14 new canvas/v1.0.1 tests: - canvas.closed registration + idiom data spec - open-canvases initialized from resume - opened event upserts in place (same instanceId replaces) - closed event removes from snapshot - malformed payload no-op (idempotent) - session.create does NOT populate snapshot - schedule data with cron-only / :at variants pass spec - eventsFileSizeBytes / apiCallId / temporary accepted - opened event :input preserved verbatim (incl. snake_case) - resume response openCanvases[] :input preserved verbatim - upsert strict validation (4 rejection cases + 1 happy path) - outbound :open-canvases resume config wire shape preserves :input - ::at spec rejects non-positive / non-integer values - mock_server.clj: set-resume-response-extras! helper added for shaping resume responses with custom fields. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/github/copilot_sdk.clj | 20 + src/github/copilot_sdk/client.clj | 52 +++ src/github/copilot_sdk/instrument.clj | 4 + src/github/copilot_sdk/protocol.clj | 24 ++ src/github/copilot_sdk/session.clj | 80 ++++ src/github/copilot_sdk/specs.clj | 63 +++- test/github/copilot_sdk/integration_test.clj | 367 +++++++++++++++++++ test/github/copilot_sdk/mock_server.clj | 17 +- 8 files changed, 609 insertions(+), 18 deletions(-) diff --git a/src/github/copilot_sdk.clj b/src/github/copilot_sdk.clj index 6a0dff8..7615fcb 100644 --- a/src/github/copilot_sdk.clj +++ b/src/github/copilot_sdk.clj @@ -139,6 +139,7 @@ :copilot/model.call_failure :copilot/session.extensions.attachments_pushed :copilot/session.canvas.opened + :copilot/session.canvas.closed :copilot/session.canvas.registry_changed}) (def session-events @@ -984,6 +985,25 @@ [session] (session/capabilities session)) +(defn open-canvases + "Get the current open-canvases snapshot for `session`. Returns a vector of + canvas-instance maps. The snapshot is initialized from `session.resume` and + updated by `:copilot/session.canvas.opened` / `:copilot/session.canvas.closed` + events. `session.create` does NOT populate it (matches upstream Node.js). + + Each entry has required keys `:instance-id`, `:extension-id`, `:canvas-id`, + `:reopen`, `:availability` and optional `:extension-name`, `:title`, + `:status`, `:url`, `:input`. + + Example: + ```clojure + (copilot/open-canvases session) + ;; => [{:instance-id \"i1\" :canvas-id \"diff\" :extension-id \"ext.x\" + ;; :reopen false :availability \"ready\"}] + ```" + [session] + (session/open-canvases session)) + (defn elicitation-supported? "Check if the CLI host supports interactive elicitation dialogs. diff --git a/src/github/copilot_sdk/client.clj b/src/github/copilot_sdk/client.clj index 4342cfc..7095967 100644 --- a/src/github/copilot_sdk/client.clj +++ b/src/github/copilot_sdk/client.clj @@ -607,6 +607,15 @@ (log/warn "Model mismatch for session " session-id ": requested " requested-model ", server selected " selected-model)))) (when-not (:destroyed? (get-in @(:state client) [:sessions session-id])) + ;; Canvas state (upstream PR #1604) — apply at all protocol + ;; versions, before publishing, so observers see a consistent + ;; snapshot. These are session events, not v3 broadcasts. + (case event-type + :copilot/session.canvas.opened + (session/upsert-open-canvas! client session-id (:data normalized-event)) + :copilot/session.canvas.closed + (session/remove-open-canvas! client session-id (:data normalized-event)) + nil) ;; Protocol v3: apply state-mutating broadcast handlers before publishing, ;; so event observers see consistent state (e.g. capabilities.changed) (when (>= (negotiated-protocol-version client) 3) @@ -1893,6 +1902,42 @@ true)) true (assoc :env-value-mode "direct")))) +(defn- stringify-keys-deep + "Recursively convert keyword keys to strings in a value (used for opaque + payloads). Strings, sequences, and primitives pass through. csk's recursive + transform-keys leaves non-keyword keys untouched, so stringified maps + round-trip through `util/clj->wire` unchanged." + [v] + (cond + (map? v) (into {} (map (fn [[k vv]] + [(if (keyword? k) (name k) k) + (stringify-keys-deep vv)])) + v) + (sequential? v) (mapv stringify-keys-deep v) + :else v)) + +(defn- canvas-instance->wire + "Convert an open-canvas instance to its wire shape. Required fields go through + explicit per-key conversion to camelCase; the optional `:input` map's keys + are stringified so they round-trip through `util/clj->wire` verbatim — caller + keys (e.g. `:user_id`) are NOT camelCased. + + Used to seed `openCanvases` on `session.resume` / `session.join` (upstream + PR #1604, ResumeSessionConfig.openCanvases)." + [canvas] + (cond-> {} + (some? (:instance-id canvas)) (assoc :instanceId (:instance-id canvas)) + (some? (:extension-id canvas)) (assoc :extensionId (:extension-id canvas)) + (some? (:canvas-id canvas)) (assoc :canvasId (:canvas-id canvas)) + (some? (:reopen canvas)) (assoc :reopen (:reopen canvas)) + (some? (:availability canvas)) (assoc :availability (:availability canvas)) + (some? (:extension-name canvas)) (assoc :extensionName (:extension-name canvas)) + (some? (:title canvas)) (assoc :title (:title canvas)) + (some? (:status canvas)) (assoc :status (:status canvas)) + (some? (:url canvas)) (assoc :url (:url canvas)) + (contains? canvas :input) (assoc :input (stringify-keys-deep + (:input canvas))))) + (defn- build-resume-session-params "Build wire params for session.resume from session-id and config." [session-id config] @@ -1999,6 +2044,11 @@ (assoc :enable-session-store (:enable-session-store config)) (some? (:enable-skills config)) (assoc :enable-skills (:enable-skills config)) + ;; Upstream PR #1604: seed the open-canvases snapshot on resume/join. + ;; Per-canvas wire conversion is explicit so the opaque `:input` map is + ;; passed through verbatim (see `canvas-instance->wire`). + (seq (:open-canvases config)) + (assoc :open-canvases (mapv canvas-instance->wire (:open-canvases config))) true (assoc :include-sub-agent-streaming-events (if (some? (:include-sub-agent-streaming-events? config)) (:include-sub-agent-streaming-events? config) @@ -2325,6 +2375,7 @@ (let [result (proto/send-request! connection-io "session.resume" params)] (session/set-workspace-path! client session-id (:workspace-path result)) (session/set-capabilities! client session-id (:capabilities result)) + (session/set-open-canvases! client session-id (:open-canvases result)) ;; Mode-specific post-resume options patch (upstream PR #1428). (apply-session-options-update! client session config) session) @@ -2519,6 +2570,7 @@ (let [result (:result response)] (session/set-workspace-path! client session-id (:workspace-path result)) (session/set-capabilities! client session-id (:capabilities result)) + (session/set-open-canvases! client session-id (:open-canvases result)) (let [r ( converted-event + (contains? (:data raw-event) :input) + (assoc-in [:data :input] (get-in raw-event [:data :input]))) + converted-event)) (defn- normalize-incoming @@ -363,6 +372,21 @@ raw-events (get-in converted [:result :events]))) + ;; Upstream PR #1604: `session.resume` responses include `openCanvases[]` + ;; — each canvas may carry an opaque caller-supplied `:input` map. + ;; Preserve raw input keys so they aren't kebab-cased when surfaced via + ;; `(open-canvases session)`. Raw and converted vectors are positionally + ;; aligned (conversion preserves order). + (and (:id msg) (not method) + (sequential? (get-in msg [:result :openCanvases]))) + (assoc-in converted [:result :open-canvases] + (mapv (fn [raw conv] + (if (contains? raw :input) + (assoc conv :input (:input raw)) + conv)) + (get-in msg [:result :openCanvases]) + (get-in converted [:result :open-canvases]))) + :else converted))) diff --git a/src/github/copilot_sdk/session.clj b/src/github/copilot_sdk/session.clj index 566ee99..0ce5b67 100644 --- a/src/github/copilot_sdk/session.clj +++ b/src/github/copilot_sdk/session.clj @@ -84,6 +84,7 @@ :destroyed? false :workspace-path workspace-path :capabilities {} + :open-canvases [] :config config}) (assoc-in [:session-io session-id] {:event-chan event-chan @@ -125,6 +126,73 @@ [client session-id capabilities] (swap! (:state client) assoc-in [:sessions session-id :capabilities] (or capabilities {}))) +;; --- Open canvas snapshot (upstream PR #1604) ------------------------------- +;; The CLI host can open auxiliary UI canvases inside a session. The SDK keeps +;; an in-memory snapshot of currently-open canvases per session. The snapshot +;; is initialized from `session.resume` (NOT `session.create` — matches upstream +;; client.ts behavior) and mutated by `session.canvas.opened` / +;; `session.canvas.closed` events. Each entry's keys go through `wire->clj` +;; kebab-case conversion (`:instance-id`, `:extension-id`, `:canvas-id`, +;; `:reopen`, `:availability`, optional `:extension-name`, `:title`, `:status`, +;; `:url`, `:input`). The opaque `:input` map (caller-defined keys) is preserved +;; verbatim by the protocol layer so source-supplied keys are not kebab-cased. + +(defn- valid-open-canvas-instance? + "Mirrors upstream `isOpenCanvasInstance` (nodejs/src/session.ts). All five + required fields must be present and well-typed before an entry is admitted + to the snapshot." + [data] + (and (map? data) + (string? (:instance-id data)) (not (str/blank? (:instance-id data))) + (string? (:extension-id data)) (not (str/blank? (:extension-id data))) + (string? (:canvas-id data)) (not (str/blank? (:canvas-id data))) + (boolean? (:reopen data)) + (#{"ready" "stale"} (:availability data)))) + +(defn set-open-canvases! + "Replace the open-canvases snapshot for `session-id`. Called once after + `session.resume` succeeds. Stores `[]` when called with `nil` so the + resume/create paths can pass through `(or (:open-canvases result) [])` + uniformly." + [client session-id canvases] + (swap! (:state client) assoc-in [:sessions session-id :open-canvases] + (vec (or canvases [])))) + +(defn upsert-open-canvas! + "Apply a `session.canvas.opened` event payload to the snapshot. If an entry + with the same `:instance-id` exists, replace it in place (preserves order); + otherwise append. Logs a warning and no-ops on payloads missing any of the + required fields (`:instance-id`, `:extension-id`, `:canvas-id`, `:reopen`, + `:availability`), matching upstream `isOpenCanvasInstance` validation." + [client session-id data] + (if-not (valid-open-canvas-instance? data) + (log/warn "failed to deserialize session.canvas.opened payload" + {:session-id session-id}) + (let [iid (:instance-id data)] + (swap! (:state client) update-in [:sessions session-id :open-canvases] + (fn [canvases] + (let [canvases (vec (or canvases [])) + idx (first (keep-indexed + (fn [i c] (when (= (:instance-id c) iid) i)) + canvases))] + (if idx + (assoc canvases idx data) + (conj canvases data)))))))) + +(defn remove-open-canvas! + "Apply a `session.canvas.closed` event payload to the snapshot — removes the + entry with matching `:instance-id`. Logs a warning and no-ops on missing/ + blank `:instance-id`. Closing an absent instance is a silent no-op + (idempotent), matching upstream `removeOpenCanvas`." + [client session-id data] + (let [iid (:instance-id data)] + (if (or (nil? iid) (and (string? iid) (str/blank? iid))) + (log/warn "failed to deserialize session.canvas.closed payload" + {:session-id session-id}) + (swap! (:state client) update-in [:sessions session-id :open-canvases] + (fn [canvases] + (filterv #(not= (:instance-id %) iid) (or canvases []))))))) + (defn register-transform-callbacks! "Store system message transform callbacks on a session. Callbacks is a map of wire section ID strings to 1-arity functions @@ -2119,6 +2187,18 @@ (let [{:keys [session-id client]} session] (:capabilities (session-state client session-id)))) +(defn open-canvases + "Get the current open-canvases snapshot for `session`. Returns a vector of + canvas-instance maps (each with `:instance-id`, `:extension-id`, `:canvas-id`, + `:reopen`, `:availability`, plus optional `:extension-name`, `:title`, + `:status`, `:url`, `:input`). The snapshot is initialized from + `session.resume` and updated by `session.canvas.opened` / + `session.canvas.closed` events. `session.create` does NOT populate it + (matches upstream Node.js behavior)." + [session] + (let [{:keys [session-id client]} session] + (vec (:open-canvases (session-state client session-id))))) + (defn elicitation-supported? "Check if the CLI host supports interactive elicitation dialogs." [session] diff --git a/src/github/copilot_sdk/specs.clj b/src/github/copilot_sdk/specs.clj index ee0417a..ce98bd2 100644 --- a/src/github/copilot_sdk/specs.clj +++ b/src/github/copilot_sdk/specs.clj @@ -751,7 +751,9 @@ :custom-agents-local-only :coauthor-enabled :manage-schedule-enabled - :include-sub-agent-streaming-events?}) + :include-sub-agent-streaming-events? + ;; Upstream PR #1604: resume/join may seed the open-canvases snapshot. + :open-canvases}) (s/def ::resume-session-config (closed-keys @@ -786,7 +788,8 @@ ::custom-agents-local-only ::coauthor-enabled ::manage-schedule-enabled - ::include-sub-agent-streaming-events?]) + ::include-sub-agent-streaming-events? + ::open-canvases]) resume-session-config-keys)) ;; join-session config: same as resume-session-config but :on-permission-request is optional. @@ -823,7 +826,8 @@ ::custom-agents-local-only ::coauthor-enabled ::manage-schedule-enabled - ::include-sub-agent-streaming-events?]) + ::include-sub-agent-streaming-events? + ::open-canvases]) resume-session-config-keys)) ;; ----------------------------------------------------------------------------- @@ -1093,6 +1097,7 @@ :copilot/model.call_failure :copilot/session.extensions.attachments_pushed :copilot/session.canvas.opened + :copilot/session.canvas.closed :copilot/session.canvas.registry_changed}) ;; Session events @@ -1115,10 +1120,11 @@ ::detached-from-spawning-parent-session-id])) (s/def ::event-count nat-int?) +(s/def ::events-file-size-bytes nat-int?) (s/def ::session.resume-data (s/keys :req-un [::event-count] :opt-un [::selected-model ::reasoning-effort ::already-in-use? ::remote-steerable? - ::host-type ::head-commit ::base-commit])) + ::host-type ::head-commit ::base-commit ::events-file-size-bytes])) (s/def ::status-code integer?) (s/def ::provider-call-id string?) @@ -1217,7 +1223,7 @@ (s/keys :req-un [::message-id ::content] :opt-un [::tool-requests ::parent-tool-call-id ::encrypted-content ::interaction-id ::output-tokens ::phase ::reasoning-opaque - ::reasoning-text ::request-id + ::reasoning-text ::request-id ::api-call-id ::anthropic-advisor-blocks ::anthropic-advisor-model ::model])) (s/def ::total-response-size-bytes nat-int?) @@ -1310,7 +1316,8 @@ (s/def ::session.shutdown-data (s/keys :req-un [::shutdown-type ::total-api-duration-ms ::session-start-time ::code-changes ::model-metrics] - :opt-un [::error-reason ::current-model ::total-premium-requests])) + :opt-un [::error-reason ::current-model ::total-premium-requests + ::events-file-size-bytes])) ;; Session title changed event (s/def ::title string?) @@ -1352,9 +1359,12 @@ ;; Hook progress event (upstream schema 1.0.56-1, round 6 sync). Ephemeral ;; event emitted by hooks during long-running work. Reuses the existing -;; ::message non-blank-string spec. +;; ::message non-blank-string spec. `:temporary` (added v1.0.1, PR #1612) +;; signals a transient progress message that may be replaced. +(s/def ::temporary boolean?) (s/def ::hook.progress-data - (s/keys :req-un [::message])) + (s/keys :req-un [::message] + :opt-un [::temporary])) ;; Session plan changed event (s/def ::operation #{"create" "update" "delete"}) @@ -1372,12 +1382,13 @@ (s/def ::session.task_complete-data (s/keys :opt-un [::summary ::aborted?])) -;; Schedule events (upstream schema 1.0.42) -;; Note: schedule data uses a positive integer `:id` (numeric scheduled-prompt -;; id, `exclusiveMinimum: 0` in the schema), distinct from the string ::id -;; (UUID) used elsewhere — validated via predicate to avoid colliding with the -;; global ::id spec. ::interval-ms is also strictly positive per schema. +;; Schedule events (upstream schema 1.0.42; v1.0.1 added cron/at variants — +;; upstream PR #1597). `:interval-ms` is now optional; cron-only and one-shot +;; (`:at`) schedules are also valid. (s/def ::interval-ms pos-int?) +(s/def ::at pos-int?) +(s/def ::cron string?) +(s/def ::tz string?) ;; ::prompt is already defined above (::non-blank-string), reused here ;; :recurring — boolean, added upstream CLI 1.0.48-1 (PR #1288). Whether the ;; schedule re-arms after each tick (`/every`) or fires once (`/after`). @@ -1385,8 +1396,8 @@ ;; `:recurring` (csk does not append `?` for booleans). (s/def ::recurring boolean?) (s/def ::session.schedule_created-data - (s/and (s/keys :req-un [::interval-ms ::prompt] - :opt-un [::recurring]) + (s/and (s/keys :req-un [::prompt] + :opt-un [::recurring ::interval-ms ::at ::cron ::tz]) #(contains? % :id) #(pos-int? (:id %)))) (s/def ::session.schedule_cancelled-data @@ -1394,6 +1405,28 @@ #(contains? % :id) #(pos-int? (:id %)))) +;; Canvas event data and snapshot entries (upstream PR #1604). +;; Wire fields: instanceId, extensionId, canvasId, extensionName, reopen, +;; availability, status, title, url, input. csk converts to kebab-case; +;; booleans (`reopen`) keep no trailing `?`. `:input` is opaque caller-supplied +;; data — open map per upstream `{ [k: string]: unknown }`. +(s/def ::instance-id ::non-blank-string) +(s/def ::extension-id ::non-blank-string) +(s/def ::canvas-id ::non-blank-string) +(s/def ::extension-name string?) +(s/def ::reopen boolean?) +(s/def ::availability #{"ready" "stale"}) +(s/def ::input map?) + +(s/def ::open-canvas-instance + (s/keys :req-un [::instance-id ::extension-id ::canvas-id ::reopen ::availability] + :opt-un [::extension-name ::title ::status ::url ::input])) + +(s/def ::open-canvases (s/coll-of ::open-canvas-instance :kind sequential? :into [])) + +(s/def ::session.canvas.closed-data + (s/keys :req-un [::instance-id ::extension-id ::canvas-id])) + ;; Skill invoked event (s/def ::allowed-tools (s/coll-of string?)) (s/def ::plugin-name string?) diff --git a/test/github/copilot_sdk/integration_test.clj b/test/github/copilot_sdk/integration_test.clj index 5c60a15..9e2bd00 100644 --- a/test/github/copilot_sdk/integration_test.clj +++ b/test/github/copilot_sdk/integration_test.clj @@ -6146,3 +6146,370 @@ (is (= 1 @calls) "exactly one concurrent disconnect! should send session.destroy") (is (true? (get-in @(:state client) [:sessions "s1" :destroyed?])))))) + +;; ----------------------------------------------------------------------------- +;; v1.0.1 sync — open-canvases snapshot, schedule cron/at, optional event fields +;; (upstream PRs #1597, #1604, #1612) +;; ----------------------------------------------------------------------------- + +(deftest test-canvas-closed-event-in-public-event-types + (testing "session.canvas.closed registered (upstream PR #1604)" + (is (contains? sdk/event-types :copilot/session.canvas.closed)) + (is (s/valid? :github.copilot-sdk.specs/event-type :copilot/session.canvas.closed))) + (testing "::session.canvas.closed-data idiom spec accepts well-formed payload" + (is (s/valid? :github.copilot-sdk.specs/session.canvas.closed-data + {:instance-id "i1" :extension-id "ext-a" :canvas-id "c1"})) + (is (not (s/valid? :github.copilot-sdk.specs/session.canvas.closed-data + {:extension-id "ext-a" :canvas-id "c1"}))))) + +(deftest test-open-canvases-initialized-from-resume + (testing "session.resume populates open-canvases snapshot (upstream PR #1604)" + (mock/set-resume-response-extras! + *mock-server* + {:openCanvases [{:instanceId "i1" + :extensionId "ext-a" + :canvasId "c1" + :reopen false + :availability "ready" + :title "Hello"}]}) + ;; create-session does NOT populate open-canvases + (let [created (sdk/create-session *test-client* {:on-permission-request sdk/approve-all})] + (is (= [] (sdk/open-canvases created))) + (let [session-id (sdk/session-id created) + resumed (sdk/resume-session *test-client* session-id + {:on-permission-request sdk/approve-all}) + canvases (sdk/open-canvases resumed)] + (is (= 1 (count canvases))) + (is (= "i1" (:instance-id (first canvases)))) + (is (= "ext-a" (:extension-id (first canvases)))) + (is (= "c1" (:canvas-id (first canvases)))) + (is (= "ready" (:availability (first canvases)))) + (is (= "Hello" (:title (first canvases)))))))) + +(deftest test-create-session-no-open-canvases-snapshot + (testing "session.create does NOT populate open-canvases (resume-only)" + (mock/set-resume-response-extras! + *mock-server* + {:openCanvases [{:instanceId "i1" + :extensionId "ext-a" + :canvasId "c1" + :reopen false + :availability "ready"}]}) + (let [session (sdk/create-session *test-client* {:on-permission-request sdk/approve-all})] + (is (= [] (sdk/open-canvases session)) + "create response is ignored even if mock injects openCanvases")))) + +(deftest test-canvas-opened-upserts-snapshot + (testing "session.canvas.opened upserts by instanceId" + (let [session (sdk/create-session *test-client* {:on-permission-request sdk/approve-all}) + session-id (sdk/session-id session)] + (is (= [] (sdk/open-canvases session))) + (mock/send-session-event! *mock-server* session-id + "session.canvas.opened" + {:instanceId "i1" + :extensionId "ext-a" + :canvasId "c1" + :reopen false + :availability "ready" + :status "loading"}) + (Thread/sleep 200) + (is (= 1 (count (sdk/open-canvases session)))) + (is (= "loading" (:status (first (sdk/open-canvases session))))) + ;; Re-emit with same instanceId, different status — replace in place + (mock/send-session-event! *mock-server* session-id + "session.canvas.opened" + {:instanceId "i1" + :extensionId "ext-a" + :canvasId "c1" + :reopen false + :availability "ready" + :status "ready"}) + (Thread/sleep 200) + (is (= 1 (count (sdk/open-canvases session))) "length unchanged on upsert") + (is (= "ready" (:status (first (sdk/open-canvases session)))) "entry updated in place") + ;; Append a new instance + (mock/send-session-event! *mock-server* session-id + "session.canvas.opened" + {:instanceId "i2" + :extensionId "ext-a" + :canvasId "c2" + :reopen false + :availability "ready"}) + (Thread/sleep 200) + (is (= 2 (count (sdk/open-canvases session)))) + (is (= ["i1" "i2"] (mapv :instance-id (sdk/open-canvases session))))))) + +(deftest test-canvas-closed-removes-from-snapshot + (testing "session.canvas.closed removes by instanceId (upstream PR #1604)" + (let [session (sdk/create-session *test-client* {:on-permission-request sdk/approve-all}) + session-id (sdk/session-id session)] + (mock/send-session-event! *mock-server* session-id + "session.canvas.opened" + {:instanceId "i1" + :extensionId "ext-a" + :canvasId "c1" + :reopen false + :availability "ready"}) + (Thread/sleep 200) + (is (= 1 (count (sdk/open-canvases session)))) + (mock/send-session-event! *mock-server* session-id + "session.canvas.closed" + {:instanceId "i1" + :extensionId "ext-a" + :canvasId "c1"}) + (Thread/sleep 200) + (is (= [] (sdk/open-canvases session))) + ;; Idempotent — closing absent instanceId is a no-op + (mock/send-session-event! *mock-server* session-id + "session.canvas.closed" + {:instanceId "i-missing" + :extensionId "ext-a" + :canvasId "c1"}) + (Thread/sleep 200) + (is (= [] (sdk/open-canvases session)))))) + +(deftest test-canvas-events-malformed-payload-no-op + (testing "missing/blank instanceId on opened/closed warns and no-ops" + (let [session (sdk/create-session *test-client* {:on-permission-request sdk/approve-all}) + session-id (sdk/session-id session)] + ;; Seed one entry so we can verify no mutation + (mock/send-session-event! *mock-server* session-id + "session.canvas.opened" + {:instanceId "i1" + :extensionId "ext-a" + :canvasId "c1" + :reopen false + :availability "ready"}) + (Thread/sleep 200) + (is (= 1 (count (sdk/open-canvases session)))) + ;; Closed with empty instanceId — no-op + (mock/send-session-event! *mock-server* session-id + "session.canvas.closed" + {:instanceId "" + :extensionId "ext-a" + :canvasId "c1"}) + (Thread/sleep 200) + (is (= 1 (count (sdk/open-canvases session)))) + ;; Opened with blank instanceId — no-op + (mock/send-session-event! *mock-server* session-id + "session.canvas.opened" + {:instanceId "" + :extensionId "ext-a" + :canvasId "c1" + :reopen false + :availability "ready"}) + (Thread/sleep 200) + (is (= 1 (count (sdk/open-canvases session))))))) + +(deftest test-schedule-data-cron-and-at + (testing "schedule_created accepts cron-only payload (no :interval-ms)" + (is (s/valid? :github.copilot-sdk.specs/session.schedule_created-data + {:id 1 :prompt "ping" :cron "0 * * * *" :tz "UTC"}))) + (testing "schedule_created accepts :at one-shot payload" + (is (s/valid? :github.copilot-sdk.specs/session.schedule_created-data + {:id 1 :prompt "ping" :at 1700000000000}))) + (testing "schedule_created still requires :id" + (is (not (s/valid? :github.copilot-sdk.specs/session.schedule_created-data + {:prompt "ping" :cron "0 * * * *"}))))) + +(deftest test-events-file-size-bytes-on-resume-and-shutdown + (testing ":events-file-size-bytes accepted on resume idiom data" + (is (s/valid? :github.copilot-sdk.specs/session.resume-data + {:event-count 0 :events-file-size-bytes 1024}))) + (testing ":events-file-size-bytes accepted on shutdown idiom data" + (is (s/valid? :github.copilot-sdk.specs/session.shutdown-data + {:shutdown-type "routine" + :total-api-duration-ms 10 + :session-start-time 0 + :code-changes {} + :model-metrics {} + :events-file-size-bytes 1024})))) + +(deftest test-api-call-id-on-assistant-message + (testing ":api-call-id accepted on assistant.message idiom data" + (is (s/valid? :github.copilot-sdk.specs/assistant.message-data + {:message-id "m1" :content "hi" :api-call-id "call_1"})))) + +(deftest test-temporary-on-hook-progress + (testing ":temporary accepted on hook.progress idiom data" + (is (s/valid? :github.copilot-sdk.specs/hook.progress-data + {:message "in progress" :temporary true})) + (is (s/valid? :github.copilot-sdk.specs/hook.progress-data + {:message "in progress" :temporary false})))) + +;; ----------------------------------------------------------------------------- +;; v1.0.1 sync — review-driven fixes +;; ----------------------------------------------------------------------------- + +(deftest test-canvas-opened-event-input-preserved-verbatim + (testing "session.canvas.opened :input keys round-trip without kebab-casing" + (let [session (sdk/create-session *test-client* {:on-permission-request sdk/approve-all}) + session-id (sdk/session-id session)] + ;; Caller-defined opaque input map with snake_case AND camelCase AND + ;; nested keys — none should be re-cased by wire->clj. + (mock/send-session-event! *mock-server* session-id + "session.canvas.opened" + {:instanceId "i1" + :extensionId "ext-a" + :canvasId "c1" + :reopen false + :availability "ready" + :input {:user_id 42 + :myKey "v" + :nested {:secondKey "v2" + :third_key 3}}}) + (Thread/sleep 200) + (let [snap (first (sdk/open-canvases session)) + input (:input snap)] + (is (some? snap) "snapshot was upserted") + (is (= 42 (:user_id input)) "snake_case caller key preserved") + (is (= "v" (:myKey input)) "camelCase caller key preserved") + (let [nested (:nested input)] + (is (= "v2" (:secondKey nested)) "nested camelCase preserved") + (is (= 3 (:third_key nested)) "nested snake_case preserved")))))) + +(deftest test-resume-response-open-canvases-input-preserved + (testing "session.resume openCanvases[].input keys round-trip verbatim" + (mock/set-resume-response-extras! + *mock-server* + {:openCanvases [{:instanceId "i1" + :extensionId "ext-a" + :canvasId "c1" + :reopen false + :availability "ready" + :input {:user_id 7 + :nested {:keepThis "v" + :and_this 1}}}]}) + (let [created (sdk/create-session *test-client* {:on-permission-request sdk/approve-all}) + session-id (sdk/session-id created) + resumed (sdk/resume-session *test-client* session-id + {:on-permission-request sdk/approve-all}) + canvases (sdk/open-canvases resumed)] + (is (= 1 (count canvases))) + (let [input (-> canvases first :input)] + (is (= 7 (:user_id input)) "top-level snake_case preserved") + (is (= "v" (get-in input [:nested :keepThis])) "nested camelCase preserved") + (is (= 1 (get-in input [:nested :and_this])) "nested snake_case preserved"))) + ;; Cleanup: clear the stub so subsequent tests see a clean resume response. + (mock/set-resume-response-extras! *mock-server* {}))) + +(deftest test-canvas-upsert-strict-validation + (testing "upsert rejects payloads missing required fields (matches isOpenCanvasInstance)" + (let [session (sdk/create-session *test-client* {:on-permission-request sdk/approve-all}) + session-id (sdk/session-id session)] + ;; Seed a valid entry first + (mock/send-session-event! *mock-server* session-id + "session.canvas.opened" + {:instanceId "i1" + :extensionId "ext-a" + :canvasId "c1" + :reopen false + :availability "ready"}) + (Thread/sleep 200) + (is (= 1 (count (sdk/open-canvases session)))) + ;; Missing :reopen — strict guard rejects, no upsert + (mock/send-session-event! *mock-server* session-id + "session.canvas.opened" + {:instanceId "i2" + :extensionId "ext-a" + :canvasId "c1" + :availability "ready"}) + (Thread/sleep 200) + (is (= 1 (count (sdk/open-canvases session))) + "missing :reopen rejected") + ;; Invalid :availability — strict guard rejects + (mock/send-session-event! *mock-server* session-id + "session.canvas.opened" + {:instanceId "i3" + :extensionId "ext-a" + :canvasId "c1" + :reopen false + :availability "broken"}) + (Thread/sleep 200) + (is (= 1 (count (sdk/open-canvases session))) + "invalid :availability rejected") + ;; Wrong type for :reopen — strict guard rejects + (mock/send-session-event! *mock-server* session-id + "session.canvas.opened" + {:instanceId "i4" + :extensionId "ext-a" + :canvasId "c1" + :reopen "yes" + :availability "ready"}) + (Thread/sleep 200) + (is (= 1 (count (sdk/open-canvases session))) + ":reopen must be boolean") + ;; Sanity: a fully valid second entry is still admitted + (mock/send-session-event! *mock-server* session-id + "session.canvas.opened" + {:instanceId "i2" + :extensionId "ext-a" + :canvasId "c1" + :reopen true + :availability "stale"}) + (Thread/sleep 200) + (is (= 2 (count (sdk/open-canvases session))) "valid entry admitted")))) + +(deftest test-resume-config-open-canvases-outbound-wire + (testing "resume-session :open-canvases sends camelCase wire shape with verbatim :input" + (let [captured (atom nil) + hook (fn [method params] + (when (= "session.resume" method) + (reset! captured params)) + nil)] + (mock/set-request-hook! *mock-server* hook) + (try + (let [created (sdk/create-session *test-client* {:on-permission-request sdk/approve-all}) + session-id (sdk/session-id created)] + (sdk/resume-session + *test-client* session-id + {:on-permission-request sdk/approve-all + :open-canvases [{:instance-id "i1" + :extension-id "ext-a" + :canvas-id "c1" + :reopen false + :availability "ready" + :extension-name "Ext A" + :title "Hello" + :status "loading" + :url "https://example.test/c1" + :input {:user_id 99 + :nested {:keepCase "v" + :snake_field 1}}}]}) + (let [params @captured] + (is (some? params) "session.resume request was captured") + (let [oc (first (:openCanvases params))] + (is (some? oc) "openCanvases array was sent") + (is (= "i1" (:instanceId oc))) + (is (= "ext-a" (:extensionId oc))) + (is (= "c1" (:canvasId oc))) + (is (false? (:reopen oc))) + (is (= "ready" (:availability oc))) + (is (= "Ext A" (:extensionName oc))) + (is (= "Hello" (:title oc))) + (is (= "loading" (:status oc))) + (is (= "https://example.test/c1" (:url oc))) + ;; Input keys must NOT have been camelCased. + (let [input (:input oc)] + ;; Keys may be strings (preserved through clj->wire) or + ;; symbols of the original name; verify by string lookup. + (is (= 99 (or (get input "user_id") (get input :user_id)))) + (let [nested (or (get input "nested") (get input :nested))] + (is (= "v" (or (get nested "keepCase") (get nested :keepCase)))) + (is (= 1 (or (get nested "snake_field") (get nested :snake_field))))))))) + (finally + (mock/set-request-hook! *mock-server* nil)))))) + +(deftest test-schedule-at-spec-rejects-non-positive + (testing "::at requires a positive integer (epoch ms)" + (is (s/valid? :github.copilot-sdk.specs/session.schedule_created-data + {:id 1 :prompt "p" :at 1700000000000})) + (is (not (s/valid? :github.copilot-sdk.specs/session.schedule_created-data + {:id 1 :prompt "p" :at 0})) + "zero is not a valid epoch-ms timestamp") + (is (not (s/valid? :github.copilot-sdk.specs/session.schedule_created-data + {:id 1 :prompt "p" :at -1})) + "negative is rejected") + (is (not (s/valid? :github.copilot-sdk.specs/session.schedule_created-data + {:id 1 :prompt "p" :at 1.5})) + "non-integer (double) is rejected"))) diff --git a/test/github/copilot_sdk/mock_server.clj b/test/github/copilot_sdk/mock_server.clj index 3eeba46..1514c56 100644 --- a/test/github/copilot_sdk/mock_server.clj +++ b/test/github/copilot_sdk/mock_server.clj @@ -70,7 +70,8 @@ supports-connect? ; atom boolean - when false, `connect` returns -32601 (legacy fallback) pending-events ; atom - events to send on next opportunity pending-responses ; atom {id -> chan} - responses to server→client RPCs - protocol-version]) ; atom Long - protocol version reported by ping/connect/status + protocol-version ; atom Long - protocol version reported by ping/connect/status + resume-response-extras]) ; atom map - extra fields to merge into session.resume responses (defn- generate-id [^AtomicLong counter] (str "evt-" (.incrementAndGet counter))) @@ -170,7 +171,8 @@ (make-event server "session.resume" {:resumeTime (.toString (java.time.Instant/now)) :eventCount 0})) - {:sessionId session-id}) + (merge {:sessionId session-id} + @(:resume-response-extras server))) (throw (ex-info "Session not found" {:code -32001 :session-id session-id}))))) (defn- handle-session-send [server params] @@ -463,7 +465,8 @@ :supports-connect? (atom true) :pending-events (atom []) :pending-responses (atom {}) - :protocol-version (atom DEFAULT_PROTOCOL_VERSION)}))) + :protocol-version (atom DEFAULT_PROTOCOL_VERSION) + :resume-response-extras (atom {})}))) (defn start-mock-server! "Start the mock server in a background thread." @@ -497,6 +500,14 @@ [server hook-fn] (reset! (:on-request server) hook-fn)) +(defn set-resume-response-extras! + "Set extra wire-shape (camelCase) fields the mock server merges into + `session.resume` RPC responses. Lets tests inject fields like + `:openCanvases` without changing the default success path. Pass `{}` + to clear." + [server extras] + (reset! (:resume-response-extras server) (or extras {}))) + (defn inject-tool-call! "Inject a tool call request from the mock server. This simulates the CLI requesting a tool invocation." From 7e728c51cccffa58bec0c27acb3db6d3368b9d65 Mon Sep 17 00:00:00 2001 From: Karl Krukow Date: Fri, 12 Jun 2026 13:25:49 +0200 Subject: [PATCH 3/7] docs(v1.0.1): document open-canvases snapshot and config seeding - API.md: open-canvases section explains the strict upsert contract (required fields per upstream isOpenCanvasInstance), :input verbatim preservation, and how to seed the snapshot via :open-canvases on resume-session / join-session. Adds :open-canvases row to the config table. - CHANGELOG.md: split v1.0.1 sync entries into the schema-driven baseline and the review-driven follow-up (strict upsert validation, :input preservation, ::at tightening, :open-canvases config). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- CHANGELOG.md | 45 +++++++++++++++++++++++++++++++++++++++++++ doc/reference/API.md | 46 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 91 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 32b7bd8..66fe2b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,51 @@ All notable changes to this project will be documented in this file. This change ## [Unreleased] +### Added (v1.0.1 sync) +- **`open-canvases` snapshot** — port of upstream PR #1604. A new + `github.copilot-sdk/open-canvases` (also `github.copilot-sdk.session/open-canvases`) + returns the per-session vector of currently-open canvases. The snapshot is + initialized from the `session.resume` response (`session.create` does NOT + populate it, matching upstream Node.js client) and updated by the + `:copilot/session.canvas.opened` and `:copilot/session.canvas.closed` + events. Missing/blank `:instance-id` payloads log a warning and no-op. +- **`:copilot/session.canvas.closed` event type** — newly added in upstream + PR #1604. Fires when a canvas is closed; the SDK removes the matching + entry from the open-canvases snapshot before publishing the event so + observers see consistent state. +- **New optional event-data fields** (upstream schema 1.0.57 → 1.0.61): + - `:copilot/session.resume` and `:copilot/session.shutdown`: + `:events-file-size-bytes` (`nat-int?`). + - `:copilot/assistant.message`: `:api-call-id`. + - `:copilot/hook.progress`: `:temporary` (`boolean?`). + - `:copilot/session.schedule_created`: `:at`, `:cron`, `:tz` (with + `:interval-ms` relaxed to optional — schedules can now use + cron / fixed-time variants instead of intervals). + +### Changed (v1.0.1 sync) +- Bumped pinned `@github/copilot` schema 1.0.57 → 1.0.61, regenerating + `generated/event_specs.clj` and `generated/coerce.clj`. + +### Added (v1.0.1 sync follow-up) +- **`:open-canvases` accepted in `resume-session` / `join-session` config** + (upstream `ResumeSessionConfig.openCanvases`). Lets callers seed the + open-canvases snapshot when reconnecting to a session. + +### Changed (v1.0.1 sync follow-up) +- **Strict validation on `session.canvas.opened` upserts** — payloads missing + any of `:instance-id`, `:extension-id`, `:canvas-id`, `:reopen` (boolean), + or `:availability` (`"ready"` / `"stale"`) are now no-ops with a warn log, + matching upstream `isOpenCanvasInstance`. +- **`:input` map keys preserved verbatim** on `session.canvas.opened` events, + on `openCanvases` returned by `session.resume`, and when sent outbound via + the `:open-canvases` resume config. Caller-defined opaque keys (e.g. + `:user_id`, nested or non-camelCase) are NOT re-cased by wire conversion. + +### Fixed (v1.0.1 sync follow-up) +- `:github.copilot-sdk.specs/at` now requires `pos-int?` (was lax `number?`). + `at` represents an epoch-ms timestamp, so non-integer or non-positive + values are invalid by construction. + ## [1.0.0.0] - 2026-06-04 ### Highlights First generally available (GA) release, at full API/wire/schema parity with diff --git a/doc/reference/API.md b/doc/reference/API.md index f56cedc..6074cae 100644 --- a/doc/reference/API.md +++ b/doc/reference/API.md @@ -290,6 +290,7 @@ Create a client and session together, ensuring both are cleaned up on exit. | `:custom-agents-local-only` | boolean | Restrict custom-agent loading to caller-supplied configs only (no on-disk discovery). Forwarded via `session.options.update`. Defaulted to `true` in `:empty` mode. (upstream PR #1428) | | `:coauthor-enabled` | boolean | Add a Copilot Co-authored-by trailer to commits made by the CLI. Forwarded via `session.options.update`. Defaulted to `false` in `:empty` mode. (upstream PR #1428) | | `:manage-schedule-enabled` | boolean | Enable the built-in schedule-management tools. Forwarded via `session.options.update`. Defaulted to `false` in `:empty` mode. (upstream PR #1428) | +| `:open-canvases` | vector | (resume-session / join-session only) Seed the open-canvases snapshot when reconnecting. Each entry: `{:instance-id ... :extension-id ... :canvas-id ... :reopen bool :availability "ready"\|"stale" :extension-name? ... :title? ... :status? ... :url? ... :input? {...}}`. Caller-defined `:input` keys are preserved verbatim through wire conversion (no kebab→camel re-casing). See [`open-canvases`](#open-canvases). (upstream PR #1604) | #### `resume-session` @@ -1276,6 +1277,48 @@ Request structured user input via interactive dialogs. Check host support before Get the host capabilities map reported when the session was created or resumed. +### `open-canvases` + +```clojure +(copilot/open-canvases session) +;; => [{:instance-id "i1" :canvas-id "diff" :extension-id "ext.x" +;; :reopen false :availability "ready"}] +``` + +Get the current open-canvases snapshot for `session`. Returns a vector of +canvas-instance maps. The snapshot is initialized from `session.resume` and +updated by `:copilot/session.canvas.opened` / `:copilot/session.canvas.closed` +events. `session.create` does NOT populate it (matches upstream Node.js). + +Each entry has required keys `:instance-id`, `:extension-id`, `:canvas-id`, +`:reopen`, `:availability` and optional `:extension-name`, `:title`, +`:status`, `:url`, `:input`. Closing an instance that's not in the snapshot is a +silent no-op (idempotent); malformed payloads (missing required field, wrong +type, or invalid `:availability`) log a warning and leave the snapshot +unchanged — matches upstream `isOpenCanvasInstance` strictness. + +The `:input` map (caller-defined opaque data on each canvas) is preserved +verbatim through wire conversion. Keys you receive (e.g. via the canvas +opened event or after a resume) round-trip back to the CLI without +camelCasing — including `snake_case` and nested keys. + +#### Seeding `open-canvases` on resume + +To restore canvases after reconnecting, pass `:open-canvases` to +[`resume-session`](#resume-session) or [`join-session`](#join-session). The +shape mirrors what `(open-canvases session)` returned previously: + +```clojure +(let [snap (copilot/open-canvases old-session)] + (copilot/resume-session client session-id + {:on-permission-request copilot/approve-all + :open-canvases snap})) +``` + +The SDK preserves caller-defined `:input` keys verbatim on the wire (they are +sent as JSON object fields with the original key names, unchanged by Clojure's +kebab-case conversion). + ### `elicitation-supported?` ```clojure @@ -1488,6 +1531,9 @@ Convert an unqualified event keyword to a namespace-qualified `:copilot/` keywor | `:copilot/session.remote_steerable_changed` | Session remote steering capability changed; data: `{:remote-steerable true/false}` | | `:copilot/capabilities.changed` | Session capabilities dynamically changed (e.g., elicitation support); ephemeral. Data: `{:ui {:elicitation true/false}}` | | `:copilot/mcp_app.tool_call_complete` | An MCP App tool call completed (upstream schema 1.0.52-4, SEP-1865); ephemeral. Data: `{:server-name ... :tool-name ... :duration-ms ... :success bool :arguments {...} :result {...}}` — `:arguments` and `:result` are opaque source-defined maps whose keys are preserved verbatim (not kebab-cased). | +| `:copilot/session.canvas.opened` | A canvas (auxiliary UI surface) was opened in the session; ephemeral. Data: `{:instance-id ... :canvas-id ... :extension-id ... :reopen bool :availability "ready"|"stale" :extension-name? ... :title? ... :status? ... :url? ... :input? {...}}`. The SDK upserts the entry into the [`open-canvases`](#open-canvases) snapshot before publishing. | +| `:copilot/session.canvas.closed` | A canvas was closed; ephemeral. Data: `{:instance-id ... :canvas-id ... :extension-id ...}`. The SDK removes the matching entry from the [`open-canvases`](#open-canvases) snapshot before publishing. (upstream PR #1604) | +| `:copilot/session.canvas.registry_changed` | The set of canvases the host can offer changed; ephemeral. | ### Example: Handling Events From a20a7d05286312a599457341e261305b4013b2b9 Mon Sep 17 00:00:00 2001 From: Karl Krukow Date: Fri, 12 Jun 2026 13:36:40 +0200 Subject: [PATCH 4/7] fix(canvas): tighten remove-open-canvas! to require non-blank string Address GitHub Copilot Code Review feedback on PR #136: 1. canvas-instance->wire (client.clj): keep wrapping fields in idiomatic kebab-case keywords so util/clj->wire camelCases them exactly once, instead of relying on csk's idempotence on already-camelCase keys. Behavior unchanged; opaque :input keys still preserved verbatim via stringify-keys-deep. 2. remove-open-canvas! (session.clj): tighten validation to require a non-blank string :instance-id, matching the strict check in upsert-open-canvas! and the ::instance-id non-blank-string spec. A non-string (e.g. numeric) :instance-id now logs a warn and no-ops instead of being silently treated as a valid removal key. 3. Regression test for the non-string :instance-id rejection. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/github/copilot_sdk/client.clj | 30 ++++++++++---------- src/github/copilot_sdk/session.clj | 8 +++--- test/github/copilot_sdk/integration_test.clj | 9 ++++++ 3 files changed, 28 insertions(+), 19 deletions(-) diff --git a/src/github/copilot_sdk/client.clj b/src/github/copilot_sdk/client.clj index 7095967..9fbf6f8 100644 --- a/src/github/copilot_sdk/client.clj +++ b/src/github/copilot_sdk/client.clj @@ -1917,26 +1917,26 @@ :else v)) (defn- canvas-instance->wire - "Convert an open-canvas instance to its wire shape. Required fields go through - explicit per-key conversion to camelCase; the optional `:input` map's keys - are stringified so they round-trip through `util/clj->wire` verbatim — caller - keys (e.g. `:user_id`) are NOT camelCased. + "Convert an open-canvas instance to its wire shape. Required fields stay in + idiomatic kebab-case so `util/clj->wire` does the camelCase conversion once; + the optional `:input` map's keys are stringified so they round-trip through + `util/clj->wire` verbatim — caller keys (e.g. `:user_id`) are NOT camelCased. Used to seed `openCanvases` on `session.resume` / `session.join` (upstream PR #1604, ResumeSessionConfig.openCanvases)." [canvas] (cond-> {} - (some? (:instance-id canvas)) (assoc :instanceId (:instance-id canvas)) - (some? (:extension-id canvas)) (assoc :extensionId (:extension-id canvas)) - (some? (:canvas-id canvas)) (assoc :canvasId (:canvas-id canvas)) - (some? (:reopen canvas)) (assoc :reopen (:reopen canvas)) - (some? (:availability canvas)) (assoc :availability (:availability canvas)) - (some? (:extension-name canvas)) (assoc :extensionName (:extension-name canvas)) - (some? (:title canvas)) (assoc :title (:title canvas)) - (some? (:status canvas)) (assoc :status (:status canvas)) - (some? (:url canvas)) (assoc :url (:url canvas)) - (contains? canvas :input) (assoc :input (stringify-keys-deep - (:input canvas))))) + (some? (:instance-id canvas)) (assoc :instance-id (:instance-id canvas)) + (some? (:extension-id canvas)) (assoc :extension-id (:extension-id canvas)) + (some? (:canvas-id canvas)) (assoc :canvas-id (:canvas-id canvas)) + (some? (:reopen canvas)) (assoc :reopen (:reopen canvas)) + (some? (:availability canvas)) (assoc :availability (:availability canvas)) + (some? (:extension-name canvas)) (assoc :extension-name (:extension-name canvas)) + (some? (:title canvas)) (assoc :title (:title canvas)) + (some? (:status canvas)) (assoc :status (:status canvas)) + (some? (:url canvas)) (assoc :url (:url canvas)) + (contains? canvas :input) (assoc :input (stringify-keys-deep + (:input canvas))))) (defn- build-resume-session-params "Build wire params for session.resume from session-id and config." diff --git a/src/github/copilot_sdk/session.clj b/src/github/copilot_sdk/session.clj index 0ce5b67..1e7f990 100644 --- a/src/github/copilot_sdk/session.clj +++ b/src/github/copilot_sdk/session.clj @@ -181,12 +181,12 @@ (defn remove-open-canvas! "Apply a `session.canvas.closed` event payload to the snapshot — removes the - entry with matching `:instance-id`. Logs a warning and no-ops on missing/ - blank `:instance-id`. Closing an absent instance is a silent no-op - (idempotent), matching upstream `removeOpenCanvas`." + entry with matching `:instance-id`. Logs a warning and no-ops when + `:instance-id` is not a non-blank string. Closing an absent instance is a + silent no-op (idempotent), matching upstream `removeOpenCanvas`." [client session-id data] (let [iid (:instance-id data)] - (if (or (nil? iid) (and (string? iid) (str/blank? iid))) + (if-not (and (string? iid) (not (str/blank? iid))) (log/warn "failed to deserialize session.canvas.closed payload" {:session-id session-id}) (swap! (:state client) update-in [:sessions session-id :open-canvases] diff --git a/test/github/copilot_sdk/integration_test.clj b/test/github/copilot_sdk/integration_test.clj index 9e2bd00..468738f 100644 --- a/test/github/copilot_sdk/integration_test.clj +++ b/test/github/copilot_sdk/integration_test.clj @@ -6299,6 +6299,15 @@ :reopen false :availability "ready"}) (Thread/sleep 200) + (is (= 1 (count (sdk/open-canvases session)))) + ;; Closed with non-string instanceId (numeric) — no-op (matches strict + ;; ::instance-id non-blank-string spec used elsewhere) + (mock/send-session-event! *mock-server* session-id + "session.canvas.closed" + {:instanceId 42 + :extensionId "ext-a" + :canvasId "c1"}) + (Thread/sleep 200) (is (= 1 (count (sdk/open-canvases session))))))) (deftest test-schedule-data-cron-and-at From aaf2120ad46580b42372af6ad6f9a08bc03dee92 Mon Sep 17 00:00:00 2001 From: Karl Krukow Date: Fri, 12 Jun 2026 13:45:08 +0200 Subject: [PATCH 5/7] fix(canvas): forward explicit empty :open-canvases on resume MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Address GitHub Copilot Code Review feedback round 2 on PR #136: build-resume-session-params now gates on (contains? config :open-canvases) instead of (seq …), so an explicit empty vector is sent verbatim — matching upstream client.ts which forwards config.openCanvases directly. Allows callers to deliberately seed/clear the snapshot on resume. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/github/copilot_sdk/client.clj | 8 +++-- test/github/copilot_sdk/integration_test.clj | 33 ++++++++++++++++++++ 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/src/github/copilot_sdk/client.clj b/src/github/copilot_sdk/client.clj index 9fbf6f8..e537a72 100644 --- a/src/github/copilot_sdk/client.clj +++ b/src/github/copilot_sdk/client.clj @@ -2046,8 +2046,12 @@ (assoc :enable-skills (:enable-skills config)) ;; Upstream PR #1604: seed the open-canvases snapshot on resume/join. ;; Per-canvas wire conversion is explicit so the opaque `:input` map is - ;; passed through verbatim (see `canvas-instance->wire`). - (seq (:open-canvases config)) + ;; passed through verbatim (see `canvas-instance->wire`). Mirrors upstream + ;; client.ts which sends `openCanvases: config.openCanvases` directly: an + ;; explicit empty vector is forwarded so callers can deliberately clear/ + ;; seed an empty snapshot, while a missing `:open-canvases` key omits the + ;; param entirely. + (contains? config :open-canvases) (assoc :open-canvases (mapv canvas-instance->wire (:open-canvases config))) true (assoc :include-sub-agent-streaming-events (if (some? (:include-sub-agent-streaming-events? config)) diff --git a/test/github/copilot_sdk/integration_test.clj b/test/github/copilot_sdk/integration_test.clj index 468738f..4924132 100644 --- a/test/github/copilot_sdk/integration_test.clj +++ b/test/github/copilot_sdk/integration_test.clj @@ -6509,6 +6509,39 @@ (finally (mock/set-request-hook! *mock-server* nil)))))) +(deftest test-resume-config-open-canvases-explicit-empty-and-omitted + (testing "resume-session forwards explicit empty :open-canvases (parity with upstream config.openCanvases) and omits the param when the key is absent" + (let [captured (atom nil) + hook (fn [method params] + (when (= "session.resume" method) + (reset! captured params)) + nil)] + (mock/set-request-hook! *mock-server* hook) + (try + ;; Explicit empty vector — must be sent as [] + (let [created (sdk/create-session *test-client* {:on-permission-request sdk/approve-all}) + session-id (sdk/session-id created)] + (sdk/resume-session + *test-client* session-id + {:on-permission-request sdk/approve-all + :open-canvases []}) + (let [params @captured] + (is (some? params)) + (is (contains? params :openCanvases) "explicit empty vector forwarded as openCanvases param") + (is (= [] (:openCanvases params))))) + (reset! captured nil) + ;; Key absent — param must be omitted entirely + (let [created (sdk/create-session *test-client* {:on-permission-request sdk/approve-all}) + session-id (sdk/session-id created)] + (sdk/resume-session + *test-client* session-id + {:on-permission-request sdk/approve-all}) + (let [params @captured] + (is (some? params)) + (is (not (contains? params :openCanvases)) "missing :open-canvases key omits openCanvases param"))) + (finally + (mock/set-request-hook! *mock-server* nil)))))) + (deftest test-schedule-at-spec-rejects-non-positive (testing "::at requires a positive integer (epoch ms)" (is (s/valid? :github.copilot-sdk.specs/session.schedule_created-data From ee9f00b9c4127cf33378bb18c6b62e01b155f0b4 Mon Sep 17 00:00:00 2001 From: Karl Krukow Date: Fri, 12 Jun 2026 13:52:42 +0200 Subject: [PATCH 6/7] fix(canvas): preserve namespaced keys, harden mock + tighten fdef MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Address GitHub Copilot Code Review feedback round 3 on PR #136: * stringify-keys-deep now uses (subs (str k) 1) instead of (name k) so namespaced keyword keys in opaque :input payloads (e.g. :my.app/user_id) are preserved as 'my.app/user_id' on the wire — not silently truncated to 'user_id'. This honors the verbatim-keys contract for caller-supplied data. Added regression test test-resume-config-open-canvases-namespaced-input-keys. * test/mock_server.clj handle-session-resume now merges :resume-response-extras before {:sessionId session-id} so the canonical response key wins — prevents tests from accidentally producing invalid responses by including :sessionId in extras. * instrument.clj: tightened session/open-canvases fdef :ret from (s/coll-of map?) to ::specs/open-canvases so instrumentation enforces the canvas-instance shape on the snapshot getter. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/github/copilot_sdk/client.clj | 9 ++++- src/github/copilot_sdk/instrument.clj | 2 +- test/github/copilot_sdk/integration_test.clj | 40 ++++++++++++++++++++ test/github/copilot_sdk/mock_server.clj | 4 +- 4 files changed, 50 insertions(+), 5 deletions(-) diff --git a/src/github/copilot_sdk/client.clj b/src/github/copilot_sdk/client.clj index e537a72..801945f 100644 --- a/src/github/copilot_sdk/client.clj +++ b/src/github/copilot_sdk/client.clj @@ -1906,11 +1906,16 @@ "Recursively convert keyword keys to strings in a value (used for opaque payloads). Strings, sequences, and primitives pass through. csk's recursive transform-keys leaves non-keyword keys untouched, so stringified maps - round-trip through `util/clj->wire` unchanged." + round-trip through `util/clj->wire` unchanged. + + Namespaced keywords are preserved as `\"ns/name\"` so caller-supplied keys + (e.g. `:my.app/user_id`) are not silently truncated to their local name." [v] (cond (map? v) (into {} (map (fn [[k vv]] - [(if (keyword? k) (name k) k) + [(if (keyword? k) + (subs (str k) 1) + k) (stringify-keys-deep vv)])) v) (sequential? v) (mapv stringify-keys-deep v) diff --git a/src/github/copilot_sdk/instrument.clj b/src/github/copilot_sdk/instrument.clj index ee31b29..1dc740a 100644 --- a/src/github/copilot_sdk/instrument.clj +++ b/src/github/copilot_sdk/instrument.clj @@ -426,7 +426,7 @@ (register-fdef! github.copilot-sdk.session/open-canvases :args (s/cat :session ::specs/session) - :ret (s/coll-of map? :kind sequential?)) + :ret ::specs/open-canvases) (register-fdef! github.copilot-sdk.session/elicitation-supported? :args (s/cat :session ::specs/session) diff --git a/test/github/copilot_sdk/integration_test.clj b/test/github/copilot_sdk/integration_test.clj index 4924132..e4b83cd 100644 --- a/test/github/copilot_sdk/integration_test.clj +++ b/test/github/copilot_sdk/integration_test.clj @@ -6509,6 +6509,46 @@ (finally (mock/set-request-hook! *mock-server* nil)))))) +(deftest test-resume-config-open-canvases-namespaced-input-keys + (testing "Namespaced keyword keys in :input preserve their full ns/name when stringified" + (let [captured (atom nil) + hook (fn [method params] + (when (= "session.resume" method) + (reset! captured params)) + nil)] + (mock/set-request-hook! *mock-server* hook) + (try + (let [created (sdk/create-session *test-client* {:on-permission-request sdk/approve-all}) + session-id (sdk/session-id created)] + (sdk/resume-session + *test-client* session-id + {:on-permission-request sdk/approve-all + :open-canvases [{:instance-id "i1" + :extension-id "ext-a" + :canvas-id "c1" + :reopen false + :availability "ready" + :input {:my.app/user_id 42 + :nested {:other.ns/key "v"}}}]}) + (let [params @captured + oc (first (:openCanvases params)) + input (:input oc)] + (is (some? input)) + ;; Namespaced keys must be preserved as "ns/name", NOT silently + ;; truncated to just the local name. Keys may surface as strings + ;; or as preserved keywords through clj->wire — check both. + (is (= 42 (or (get input "my.app/user_id") + (get input :my.app/user_id))) + "namespaced keyword key preserved as full ns/name") + (is (nil? (get input "user_id")) "local-name-only must not appear") + (is (nil? (get input :user_id))) + (let [nested (or (get input "nested") (get input :nested))] + (is (= "v" (or (get nested "other.ns/key") + (get nested :other.ns/key))) + "nested namespaced key also preserved")))) + (finally + (mock/set-request-hook! *mock-server* nil)))))) + (deftest test-resume-config-open-canvases-explicit-empty-and-omitted (testing "resume-session forwards explicit empty :open-canvases (parity with upstream config.openCanvases) and omits the param when the key is absent" (let [captured (atom nil) diff --git a/test/github/copilot_sdk/mock_server.clj b/test/github/copilot_sdk/mock_server.clj index 1514c56..6753a7c 100644 --- a/test/github/copilot_sdk/mock_server.clj +++ b/test/github/copilot_sdk/mock_server.clj @@ -171,8 +171,8 @@ (make-event server "session.resume" {:resumeTime (.toString (java.time.Instant/now)) :eventCount 0})) - (merge {:sessionId session-id} - @(:resume-response-extras server))) + (merge @(:resume-response-extras server) + {:sessionId session-id})) (throw (ex-info "Session not found" {:code -32001 :session-id session-id}))))) (defn- handle-session-send [server params] From b1cc80042fe42da75eda5551c4724b2b154ea563 Mon Sep 17 00:00:00 2001 From: Karl Krukow Date: Fri, 12 Jun 2026 14:01:53 +0200 Subject: [PATCH 7/7] fix(canvas): sanitize resume-provided open-canvases snapshot Address GitHub Copilot Code Review feedback round 4 on PR #136: set-open-canvases! (called after session.resume) was storing the CLI's openCanvases payload verbatim, bypassing the same valid-open-canvas-instance? check that gates live session.canvas.opened upserts. A malformed CLI response could leak invalid entries into the snapshot and trip instrumentation on (open-canvases session). Now: non-sequential values become [], and entries failing valid-open-canvas-instance? are dropped with a single warning carrying the dropped count. Snapshot invariant holds for all callers. Added regression test test-open-canvases-resume-sanitizes-invalid-entries mixing missing :reopen, missing :availability, and bad availability values. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/github/copilot_sdk/session.clj | 20 +++++++++--- test/github/copilot_sdk/integration_test.clj | 32 ++++++++++++++++++++ 2 files changed, 47 insertions(+), 5 deletions(-) diff --git a/src/github/copilot_sdk/session.clj b/src/github/copilot_sdk/session.clj index 1e7f990..6f842ff 100644 --- a/src/github/copilot_sdk/session.clj +++ b/src/github/copilot_sdk/session.clj @@ -151,12 +151,22 @@ (defn set-open-canvases! "Replace the open-canvases snapshot for `session-id`. Called once after - `session.resume` succeeds. Stores `[]` when called with `nil` so the - resume/create paths can pass through `(or (:open-canvases result) [])` - uniformly." + `session.resume` succeeds. Sanitizes the input the same way live + `session.canvas.opened` upserts do — non-sequential values are treated as + empty, and entries failing `valid-open-canvas-instance?` are dropped with a + warning so the snapshot invariant holds for callers (and instrumentation)." [client session-id canvases] - (swap! (:state client) assoc-in [:sessions session-id :open-canvases] - (vec (or canvases [])))) + (let [coll (when (sequential? canvases) canvases) + {:keys [valid invalid]} (group-by (fn [c] + (if (valid-open-canvas-instance? c) + :valid :invalid)) + coll)] + (when (seq invalid) + (log/warn "dropping invalid entries from session.resume openCanvases" + {:session-id session-id + :dropped-count (count invalid)})) + (swap! (:state client) assoc-in [:sessions session-id :open-canvases] + (vec valid)))) (defn upsert-open-canvas! "Apply a `session.canvas.opened` event payload to the snapshot. If an entry diff --git a/test/github/copilot_sdk/integration_test.clj b/test/github/copilot_sdk/integration_test.clj index e4b83cd..50a9bc4 100644 --- a/test/github/copilot_sdk/integration_test.clj +++ b/test/github/copilot_sdk/integration_test.clj @@ -6186,6 +6186,38 @@ (is (= "ready" (:availability (first canvases)))) (is (= "Hello" (:title (first canvases)))))))) +(deftest test-open-canvases-resume-sanitizes-invalid-entries + (testing "session.resume openCanvases drops invalid entries (parity with live upserts)" + (mock/set-resume-response-extras! + *mock-server* + ;; Mix of one valid, one missing :reopen, one missing :availability, + ;; one with bad availability value, one not even a map. + {:openCanvases [{:instanceId "i1" + :extensionId "ext-a" + :canvasId "c1" + :reopen false + :availability "ready"} + {:instanceId "i2" + :extensionId "ext-a" + :canvasId "c2" + :availability "ready"} + {:instanceId "i3" + :extensionId "ext-a" + :canvasId "c3" + :reopen true} + {:instanceId "i4" + :extensionId "ext-a" + :canvasId "c4" + :reopen true + :availability "BOGUS"}]}) + (let [created (sdk/create-session *test-client* {:on-permission-request sdk/approve-all}) + session-id (sdk/session-id created) + resumed (sdk/resume-session *test-client* session-id + {:on-permission-request sdk/approve-all}) + canvases (sdk/open-canvases resumed)] + (is (= 1 (count canvases)) "only the fully-valid entry is kept") + (is (= "i1" (:instance-id (first canvases))))))) + (deftest test-create-session-no-open-canvases-snapshot (testing "session.create does NOT populate open-canvases (resume-only)" (mock/set-resume-response-extras!