From 56a2a9e6a597568a64196e0de50c4f20720d77c3 Mon Sep 17 00:00:00 2001 From: Karl Krukow Date: Fri, 15 May 2026 09:42:44 +0200 Subject: [PATCH 1/6] =?UTF-8?q?chore:=20bump=20Copilot=20CLI=20schemas=201?= =?UTF-8?q?.0.46=20=E2=86=92=201.0.48?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pinned upstream @github/copilot npm version advanced from 1.0.46 to the 1.0.48 GA release. Regenerated session-events.schema.json and api.schema.json from the upstream package, then regenerated wire specs in generated/event_specs.clj. This brings the schema in line with upstream PRs: - PR #1292 (CLI 1.0.48) — session.custom_notification event - PR #1295 (CLI 1.0.48) — remoteSession session config option - and the previously-pinned 1.0.48-1 prerelease changes. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .copilot-schema-version | 2 +- schemas/README.md | 2 +- schemas/api.schema.json | 490 ++++++++++++++++-- schemas/session-events.schema.json | 155 +++++- .../copilot_sdk/generated/event_specs.clj | 44 +- 5 files changed, 638 insertions(+), 55 deletions(-) diff --git a/.copilot-schema-version b/.copilot-schema-version index 3c92cd0..56d0dad 100644 --- a/.copilot-schema-version +++ b/.copilot-schema-version @@ -1 +1 @@ -1.0.46 +1.0.48 diff --git a/schemas/README.md b/schemas/README.md index 252e87b..b239b19 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.46` +Currently pinned version: `1.0.48` diff --git a/schemas/api.schema.json b/schemas/api.schema.json index 26d5c09..30250b9 100644 --- a/schemas/api.schema.json +++ b/schemas/api.schema.json @@ -26,15 +26,7 @@ "list": { "rpcMethod": "models.list", "params": { - "anyOf": [ - { - "not": {} - }, - { - "$ref": "#/definitions/ModelsListRequest" - } - ], - "title": "ModelsListRequest" + "$ref": "#/definitions/ModelsListRequest" }, "result": { "$ref": "#/definitions/ModelList" @@ -56,15 +48,7 @@ "getQuota": { "rpcMethod": "account.getQuota", "params": { - "anyOf": [ - { - "not": {} - }, - { - "$ref": "#/definitions/AccountGetQuotaRequest" - } - ], - "title": "AccountGetQuotaRequest" + "$ref": "#/definitions/AccountGetQuotaRequest" }, "result": { "$ref": "#/definitions/AccountGetQuotaResult" @@ -971,8 +955,7 @@ "additionalProperties": false }, "result": { - "type": "null", - "title": "SkillsReloadResult" + "$ref": "#/definitions/SkillsLoadDiagnostics" }, "stability": "experimental" } @@ -1275,6 +1258,76 @@ } }, "commands": { + "list": { + "rpcMethod": "session.commands.list", + "params": { + "anyOf": [ + { + "not": {} + }, + { + "type": "object", + "properties": { + "includeBuiltins": { + "type": "boolean", + "description": "Include runtime built-in commands" + }, + "includeSkills": { + "type": "boolean", + "description": "Include enabled user-invocable skills and commands" + }, + "includeClientCommands": { + "type": "boolean", + "description": "Include commands registered by protocol clients, including SDK clients and extensions" + } + }, + "additionalProperties": false + } + ], + "title": "CommandsListRequest", + "properties": { + "sessionId": { + "type": "string", + "description": "Target session identifier" + } + }, + "required": [ + "sessionId" + ] + }, + "result": { + "$ref": "#/definitions/CommandList" + } + }, + "invoke": { + "rpcMethod": "session.commands.invoke", + "params": { + "type": "object", + "properties": { + "sessionId": { + "type": "string", + "description": "Target session identifier" + }, + "name": { + "type": "string", + "description": "Command name. Leading slashes are stripped and the name is matched case-insensitively." + }, + "input": { + "type": "string", + "description": "Raw input after the command name" + } + }, + "required": [ + "sessionId", + "name" + ], + "additionalProperties": false, + "title": "CommandsInvokeRequest" + }, + "result": { + "$ref": "#/definitions/SlashCommandInvocationResult" + } + }, "handlePendingCommand": { "rpcMethod": "session.commands.handlePendingCommand", "params": { @@ -1657,12 +1710,17 @@ "sessionId": { "type": "string", "description": "Target session identifier" + }, + "mode": { + "$ref": "#/definitions/RemoteSessionMode", + "description": "Per-session remote mode. \"off\" disables remote, \"export\" exports session events to Mission Control without enabling remote steering, \"on\" enables both export and remote steering." } }, + "additionalProperties": false, + "title": "RemoteEnableRequest", "required": [ "sessionId" - ], - "additionalProperties": false + ] }, "result": { "$ref": "#/definitions/RemoteEnableResult" @@ -2035,14 +2093,22 @@ }, "definitions": { "AccountGetQuotaRequest": { - "type": "object", - "properties": { - "gitHubToken": { - "type": "string", - "description": "GitHub token for per-user quota lookup. When provided, resolves this token to determine the user's quota instead of using the global auth." + "anyOf": [ + { + "not": {} + }, + { + "type": "object", + "properties": { + "gitHubToken": { + "type": "string", + "description": "GitHub token for per-user quota lookup. When provided, resolves this token to determine the user's quota instead of using the global auth." + } + }, + "additionalProperties": false } - }, - "additionalProperties": false + ], + "title": "AccountGetQuotaRequest" }, "AccountGetQuotaResult": { "type": "object", @@ -2229,6 +2295,23 @@ "description": "Authentication type", "title": "AuthInfoType" }, + "CommandList": { + "type": "object", + "properties": { + "commands": { + "type": "array", + "items": { + "$ref": "#/definitions/SlashCommandInfo" + }, + "description": "Commands available in this session" + } + }, + "required": [ + "commands" + ], + "additionalProperties": false, + "title": "CommandList" + }, "CommandsHandlePendingCommandRequest": { "type": "object", "properties": { @@ -2261,6 +2344,50 @@ "additionalProperties": false, "title": "CommandsHandlePendingCommandResult" }, + "CommandsInvokeRequest": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Command name. Leading slashes are stripped and the name is matched case-insensitively." + }, + "input": { + "type": "string", + "description": "Raw input after the command name" + } + }, + "required": [ + "name" + ], + "additionalProperties": false, + "title": "CommandsInvokeRequest" + }, + "CommandsListRequest": { + "anyOf": [ + { + "not": {} + }, + { + "type": "object", + "properties": { + "includeBuiltins": { + "type": "boolean", + "description": "Include runtime built-in commands" + }, + "includeSkills": { + "type": "boolean", + "description": "Include enabled user-invocable skills and commands" + }, + "includeClientCommands": { + "type": "boolean", + "description": "Include commands registered by protocol clients, including SDK clients and extensions" + } + }, + "additionalProperties": false + } + ], + "title": "CommandsListRequest" + }, "CommandsRespondToQueuedCommandRequest": { "type": "object", "properties": { @@ -3661,12 +3788,40 @@ "multiplier": { "type": "number", "description": "Billing cost multiplier relative to the base rate" + }, + "tokenPrices": { + "$ref": "#/definitions/ModelBillingTokenPrices", + "description": "Token-level pricing information for this model" } }, "additionalProperties": false, "description": "Billing information", "title": "ModelBilling" }, + "ModelBillingTokenPrices": { + "type": "object", + "properties": { + "inputPrice": { + "type": "integer", + "description": "Price per billing batch of input tokens in nano-AIUs (1 nano-AIU = 0.000000001 AIU, 1 AIU = $0.01 USD)" + }, + "outputPrice": { + "type": "integer", + "description": "Price per billing batch of output tokens in nano-AIUs (1 nano-AIU = 0.000000001 AIU, 1 AIU = $0.01 USD)" + }, + "cachePrice": { + "type": "integer", + "description": "Price per billing batch of cached tokens in nano-AIUs (1 nano-AIU = 0.000000001 AIU, 1 AIU = $0.01 USD)" + }, + "batchSize": { + "type": "integer", + "description": "Number of tokens per standard billing batch" + } + }, + "additionalProperties": false, + "description": "Token-level pricing information for this model", + "title": "ModelBillingTokenPrices" + }, "ModelCapabilities": { "type": "object", "properties": { @@ -3892,14 +4047,22 @@ "title": "ModelPolicy" }, "ModelsListRequest": { - "type": "object", - "properties": { - "gitHubToken": { - "type": "string", - "description": "GitHub token for per-user model listing. When provided, resolves this token to determine the user's Copilot plan and available models instead of using the global auth." + "anyOf": [ + { + "not": {} + }, + { + "type": "object", + "properties": { + "gitHubToken": { + "type": "string", + "description": "GitHub token for per-user model listing. When provided, resolves this token to determine the user's Copilot plan and available models instead of using the global auth." + } + }, + "additionalProperties": false } - }, - "additionalProperties": false + ], + "title": "ModelsListRequest" }, "ModelSwitchToRequest": { "type": "object", @@ -4755,6 +4918,17 @@ "description": "Result of the queued command execution", "title": "QueuedCommandResult" }, + "RemoteEnableRequest": { + "type": "object", + "properties": { + "mode": { + "$ref": "#/definitions/RemoteSessionMode", + "description": "Per-session remote mode. \"off\" disables remote, \"export\" exports session events to Mission Control without enabling remote steering, \"on\" enables both export and remote steering." + } + }, + "additionalProperties": false, + "title": "RemoteEnableRequest" + }, "RemoteEnableResult": { "type": "object", "properties": { @@ -4773,6 +4947,16 @@ "additionalProperties": false, "title": "RemoteEnableResult" }, + "RemoteSessionMode": { + "type": "string", + "enum": [ + "off", + "export", + "on" + ], + "description": "Per-session remote mode. \"off\" disables remote, \"export\" exports session events to Mission Control without enabling remote steering, \"on\" enables both export and remote steering.", + "title": "RemoteSessionMode" + }, "ServerSkill": { "type": "object", "properties": { @@ -5296,6 +5480,10 @@ "toEventId": { "type": "string", "description": "Optional event ID boundary. When provided, the fork includes only events before this ID (exclusive). When omitted, all events are included." + }, + "name": { + "type": "string", + "description": "Optional friendly name to assign to the forked session." } }, "required": [ @@ -5310,6 +5498,10 @@ "sessionId": { "type": "string", "description": "The new forked session's ID" + }, + "name": { + "type": "string", + "description": "Friendly name assigned to the forked session, if any." } }, "required": [ @@ -5519,6 +5711,232 @@ "additionalProperties": false, "title": "SkillsEnableRequest" }, + "SkillsLoadDiagnostics": { + "type": "object", + "properties": { + "warnings": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Warnings emitted while loading skills (e.g. skills that loaded but had issues)" + }, + "errors": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Errors emitted while loading skills (e.g. skills that failed to load entirely)" + } + }, + "required": [ + "warnings", + "errors" + ], + "additionalProperties": false, + "title": "SkillsLoadDiagnostics" + }, + "SlashCommandAgentPromptMode": { + "type": "string", + "enum": [ + "interactive", + "plan", + "autopilot" + ], + "description": "Optional target session mode", + "title": "SlashCommandAgentPromptMode" + }, + "SlashCommandAgentPromptResult": { + "type": "object", + "properties": { + "kind": { + "type": "string", + "const": "agent-prompt", + "description": "Agent prompt result discriminator" + }, + "prompt": { + "type": "string", + "description": "Prompt to submit to the agent" + }, + "displayPrompt": { + "type": "string", + "description": "Prompt text to display to the user" + }, + "mode": { + "$ref": "#/definitions/SlashCommandAgentPromptMode", + "description": "Optional target session mode" + }, + "runtimeSettingsChanged": { + "type": "boolean", + "description": "True when the invocation mutated user runtime settings; consumers caching settings should refresh" + } + }, + "required": [ + "kind", + "prompt", + "displayPrompt" + ], + "additionalProperties": false, + "title": "SlashCommandAgentPromptResult" + }, + "SlashCommandCompletedResult": { + "type": "object", + "properties": { + "kind": { + "type": "string", + "const": "completed", + "description": "Completed result discriminator" + }, + "message": { + "type": "string", + "description": "Optional user-facing message describing the completed command" + }, + "runtimeSettingsChanged": { + "type": "boolean", + "description": "True when the invocation mutated user runtime settings; consumers caching settings should refresh" + } + }, + "required": [ + "kind" + ], + "additionalProperties": false, + "title": "SlashCommandCompletedResult" + }, + "SlashCommandInfo": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Canonical command name without a leading slash" + }, + "aliases": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Canonical aliases without leading slashes" + }, + "description": { + "type": "string", + "description": "Human-readable command description" + }, + "kind": { + "$ref": "#/definitions/SlashCommandKind", + "description": "Coarse command category for grouping and behavior: runtime built-in, skill-backed command, or SDK/client-owned command" + }, + "input": { + "$ref": "#/definitions/SlashCommandInput", + "description": "Optional unstructured input hint" + }, + "allowDuringAgentExecution": { + "type": "boolean", + "description": "Whether the command may run while an agent turn is active" + }, + "experimental": { + "type": "boolean", + "description": "Whether the command is experimental" + } + }, + "required": [ + "name", + "description", + "kind", + "allowDuringAgentExecution" + ], + "additionalProperties": false, + "title": "SlashCommandInfo" + }, + "SlashCommandInput": { + "type": "object", + "properties": { + "hint": { + "type": "string", + "description": "Hint to display when command input has not been provided" + }, + "required": { + "type": "boolean", + "description": "When true, the command requires non-empty input; clients should render the input hint as required" + }, + "completion": { + "$ref": "#/definitions/SlashCommandInputCompletion", + "description": "Optional completion hint for the input (e.g. 'directory' for filesystem path completion)" + }, + "preserveMultilineInput": { + "type": "boolean", + "description": "When true, clients should pass the full text after the command name as a single argument rather than splitting on whitespace" + } + }, + "required": [ + "hint" + ], + "additionalProperties": false, + "description": "Optional unstructured input hint", + "title": "SlashCommandInput" + }, + "SlashCommandInputCompletion": { + "type": "string", + "enum": [ + "directory" + ], + "description": "Optional completion hint for the input (e.g. 'directory' for filesystem path completion)", + "title": "SlashCommandInputCompletion" + }, + "SlashCommandInvocationResult": { + "anyOf": [ + { + "$ref": "#/definitions/SlashCommandTextResult" + }, + { + "$ref": "#/definitions/SlashCommandAgentPromptResult" + }, + { + "$ref": "#/definitions/SlashCommandCompletedResult" + } + ], + "title": "SlashCommandInvocationResult" + }, + "SlashCommandKind": { + "type": "string", + "enum": [ + "builtin", + "skill", + "client" + ], + "description": "Coarse command category for grouping and behavior: runtime built-in, skill-backed command, or SDK/client-owned command", + "title": "SlashCommandKind" + }, + "SlashCommandTextResult": { + "type": "object", + "properties": { + "kind": { + "type": "string", + "const": "text", + "description": "Text result discriminator" + }, + "text": { + "type": "string", + "description": "Text output for the client to render" + }, + "markdown": { + "type": "boolean", + "description": "Whether text contains Markdown" + }, + "preserveAnsi": { + "type": "boolean", + "description": "Whether ANSI sequences should be preserved" + }, + "runtimeSettingsChanged": { + "type": "boolean", + "description": "True when the invocation mutated user runtime settings; consumers caching settings should refresh" + } + }, + "required": [ + "kind", + "text" + ], + "additionalProperties": false, + "title": "SlashCommandTextResult" + }, "TaskAgentInfo": { "type": "object", "properties": { diff --git a/schemas/session-events.schema.json b/schemas/session-events.schema.json index 76c2d41..ce89f29 100644 --- a/schemas/session-events.schema.json +++ b/schemas/session-events.schema.json @@ -832,6 +832,17 @@ "additionalProperties": false, "title": "AssistantTurnStartEvent" }, + "AssistantUsageApiEndpoint": { + "type": "string", + "enum": [ + "/chat/completions", + "/v1/messages", + "/responses", + "ws:/responses" + ], + "description": "API endpoint used for this model call, matching CAPI supported_endpoints vocabulary", + "title": "AssistantUsageApiEndpoint" + }, "AssistantUsageCopilotUsage": { "type": "object", "properties": { @@ -941,6 +952,10 @@ "type": "string", "description": "GitHub request tracing ID (x-github-request-id header) for server-side log correlation" }, + "apiEndpoint": { + "$ref": "#/definitions/AssistantUsageApiEndpoint", + "description": "API endpoint used for this model call, matching CAPI supported_endpoints vocabulary" + }, "parentToolCallId": { "type": "string", "description": "Parent tool call ID when this usage originates from a sub-agent", @@ -2146,6 +2161,129 @@ "additionalProperties": false, "title": "CustomAgentsUpdatedEvent" }, + "CustomNotificationData": { + "type": "object", + "properties": { + "source": { + "type": "string", + "minLength": 1, + "description": "Namespace for the custom notification producer" + }, + "name": { + "type": "string", + "minLength": 1, + "description": "Source-defined custom notification name" + }, + "version": { + "type": "integer", + "exclusiveMinimum": 0, + "description": "Optional source-defined payload schema version" + }, + "subject": { + "$ref": "#/definitions/CustomNotificationSubject", + "description": "Optional source-defined string identifiers describing the payload subject" + }, + "payload": { + "$ref": "#/definitions/CustomNotificationPayload", + "description": "Source-defined JSON payload for the custom notification" + } + }, + "required": [ + "source", + "name", + "payload" + ], + "additionalProperties": false, + "description": "Opaque custom notification data. Consumers may branch on source and name, but payload semantics are source-defined.", + "title": "CustomNotificationData" + }, + "CustomNotificationEvent": { + "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 + }, + "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.custom_notification" + }, + "data": { + "$ref": "#/definitions/CustomNotificationData", + "description": "Opaque custom notification data. Consumers may branch on source and name, but payload semantics are source-defined." + } + }, + "required": [ + "id", + "timestamp", + "parentId", + "ephemeral", + "type", + "data" + ], + "additionalProperties": false, + "title": "CustomNotificationEvent" + }, + "CustomNotificationPayload": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "number" + }, + { + "type": "boolean" + }, + { + "type": "null" + }, + { + "type": "array", + "items": {} + }, + { + "type": "object", + "additionalProperties": {} + } + ], + "description": "Source-defined JSON payload for the custom notification", + "title": "CustomNotificationPayload" + }, + "CustomNotificationSubject": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "Optional source-defined string identifiers describing the payload subject", + "title": "CustomNotificationSubject" + }, "ElicitationCompletedAction": { "type": "string", "enum": [ @@ -2442,7 +2580,7 @@ }, "errorCode": { "type": "string", - "description": "Fine-grained error code from the upstream provider, when available. For `errorType: \"rate_limit\"`, this is one of the `RateLimitErrorCode` values (e.g., `\"user_weekly_rate_limited\"`, `\"user_global_rate_limited\"`, `\"rate_limited\"`, `\"user_model_rate_limited\"`, `\"integration_rate_limited\"`)." + "description": "Fine-grained error code from the upstream provider, when available. For `errorType: \"rate_limit\"`, this is one of the `RateLimitErrorCode` values (e.g., `\"user_weekly_rate_limited\"`, `\"user_global_rate_limited\"`, `\"rate_limited\"`, `\"user_model_rate_limited\"`, `\"integration_rate_limited\"`). For `errorType: \"quota\"`, this is the CAPI quota error code (e.g., `\"quota_exceeded\"`, `\"session_quota_exceeded\"`, `\"billing_not_configured\"`)." }, "eligibleForAutoSwitch": { "type": "boolean", @@ -5962,6 +6100,10 @@ "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`)" } }, "required": [ @@ -5970,7 +6112,7 @@ "prompt" ], "additionalProperties": false, - "description": "Scheduled prompt registered via /every", + "description": "Scheduled prompt registered via /every or /after", "title": "ScheduleCreatedData" }, "ScheduleCreatedEvent": { @@ -6012,7 +6154,7 @@ }, "data": { "$ref": "#/definitions/ScheduleCreatedData", - "description": "Scheduled prompt registered via /every" + "description": "Scheduled prompt registered via /every or /after" } }, "required": [ @@ -6213,6 +6355,9 @@ { "$ref": "#/definitions/McpOauthCompletedEvent" }, + { + "$ref": "#/definitions/CustomNotificationEvent" + }, { "$ref": "#/definitions/ExternalToolRequestedEvent" }, @@ -9367,6 +9512,10 @@ "$ref": "#/definitions/UserMessageAgentMode", "description": "The agent mode that was active when this message was sent" }, + "isAutopilotContinuation": { + "type": "boolean", + "description": "True when this user message was auto-injected by autopilot's continuation loop rather than typed by the user; used to distinguish autopilot-driven turns in telemetry." + }, "interactionId": { "type": "string", "description": "CAPI interaction ID for correlating this user message with its turn" diff --git a/src/github/copilot_sdk/generated/event_specs.clj b/src/github/copilot_sdk/generated/event_specs.clj index c9f0b59..61a3d2c 100644 --- a/src/github/copilot_sdk/generated/event_specs.clj +++ b/src/github/copilot_sdk/generated/event_specs.clj @@ -35,6 +35,8 @@ (s/def :github.copilot-sdk.generated.event-specs/api-call-id clojure.core/string?) +(s/def :github.copilot-sdk.generated.event-specs/api-endpoint #{"/v1/messages" "/chat/completions" "/responses" "ws:/responses"}) + (s/def :github.copilot-sdk.generated.event-specs/approved clojure.core/boolean?) (s/def :github.copilot-sdk.generated.event-specs/args clojure.core/string?) @@ -71,9 +73,9 @@ (s/def :github.copilot-sdk.generated.event-specs/compaction-tokens-used clojure.core/map?) -(s/def :github.copilot-sdk.generated.event-specs/content (s/spec (fn [v940] (or (s/valid? clojure.core/string? v940) (s/valid? clojure.core/map? v940))))) +(s/def :github.copilot-sdk.generated.event-specs/content (s/spec (fn [v941] (or (s/valid? clojure.core/string? v941) (s/valid? clojure.core/map? v941))))) -(s/def :github.copilot-sdk.generated.event-specs/context (s/spec (fn [v938] (or (s/valid? clojure.core/map? v938) (s/valid? clojure.core/string? v938))))) +(s/def :github.copilot-sdk.generated.event-specs/context (s/spec (fn [v939] (or (s/valid? clojure.core/map? v939) (s/valid? clojure.core/string? v939))))) (s/def :github.copilot-sdk.generated.event-specs/continue-pending-work clojure.core/boolean?) @@ -111,7 +113,7 @@ (s/def :github.copilot-sdk.generated.event-specs/ephemeral clojure.core/boolean?) -(s/def :github.copilot-sdk.generated.event-specs/error (s/spec (fn [v936] (or (s/valid? clojure.core/string? v936) (s/valid? clojure.core/map? v936))))) +(s/def :github.copilot-sdk.generated.event-specs/error (s/spec (fn [v937] (or (s/valid? clojure.core/string? v937) (s/valid? clojure.core/map? v937))))) (s/def :github.copilot-sdk.generated.event-specs/error-code clojure.core/string?) @@ -145,7 +147,7 @@ (s/def :github.copilot-sdk.generated.event-specs/host-type #{"github" "ado"}) -(s/def :github.copilot-sdk.generated.event-specs/id (s/spec (fn [v937] (or (s/valid? clojure.core/string? v937) (s/valid? clojure.core/integer? v937))))) +(s/def :github.copilot-sdk.generated.event-specs/id (s/spec (fn [v938] (or (s/valid? clojure.core/string? v938) (s/valid? clojure.core/integer? v938))))) (s/def :github.copilot-sdk.generated.event-specs/info-type clojure.core/string?) @@ -163,6 +165,8 @@ (s/def :github.copilot-sdk.generated.event-specs/interval-ms clojure.core/integer?) +(s/def :github.copilot-sdk.generated.event-specs/is-autopilot-continuation clojure.core/boolean?) + (s/def :github.copilot-sdk.generated.event-specs/is-initial clojure.core/boolean?) (s/def :github.copilot-sdk.generated.event-specs/is-user-requested clojure.core/boolean?) @@ -201,7 +205,7 @@ (s/def :github.copilot-sdk.generated.event-specs/new-model clojure.core/string?) -(s/def :github.copilot-sdk.generated.event-specs/operation (s/spec (fn [v939] (or (s/valid? #{"delete" "update" "create"} v939) (s/valid? #{"update" "create"} v939))))) +(s/def :github.copilot-sdk.generated.event-specs/operation (s/spec (fn [v940] (or (s/valid? #{"delete" "update" "create"} v940) (s/valid? #{"update" "create"} v940))))) (s/def :github.copilot-sdk.generated.event-specs/output clojure.core/any?) @@ -217,6 +221,8 @@ (s/def :github.copilot-sdk.generated.event-specs/path clojure.core/string?) +(s/def :github.copilot-sdk.generated.event-specs/payload (s/nilable (s/or :branch-0 clojure.core/string? :branch-1 clojure.core/number? :branch-2 clojure.core/boolean? :branch-3 (s/coll-of clojure.core/any?) :branch-4 clojure.core/map?))) + (s/def :github.copilot-sdk.generated.event-specs/performed-by clojure.core/string?) (s/def :github.copilot-sdk.generated.event-specs/permission-request (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? :branch-6 clojure.core/map? :branch-7 clojure.core/map? :branch-8 clojure.core/map? :branch-9 clojure.core/map?)) @@ -277,11 +283,13 @@ (s/def :github.copilot-sdk.generated.event-specs/recommended-action clojure.core/string?) +(s/def :github.copilot-sdk.generated.event-specs/recurring clojure.core/boolean?) + (s/def :github.copilot-sdk.generated.event-specs/remote-session-id clojure.core/string?) (s/def :github.copilot-sdk.generated.event-specs/remote-steerable clojure.core/boolean?) -(s/def :github.copilot-sdk.generated.event-specs/repository (s/spec (fn [v935] (or (s/valid? clojure.core/map? v935) (s/valid? clojure.core/string? v935))))) +(s/def :github.copilot-sdk.generated.event-specs/repository (s/spec (fn [v936] (or (s/valid? clojure.core/map? v936) (s/valid? clojure.core/string? v936))))) (s/def :github.copilot-sdk.generated.event-specs/repository-host clojure.core/string?) @@ -293,7 +301,7 @@ (s/def :github.copilot-sdk.generated.event-specs/response clojure.core/string?) -(s/def :github.copilot-sdk.generated.event-specs/result (s/spec (fn [v943] (or (s/valid? clojure.core/map? v943) (s/valid? (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? :branch-6 clojure.core/map? :branch-7 clojure.core/map? :branch-8 clojure.core/map?) v943))))) +(s/def :github.copilot-sdk.generated.event-specs/result (s/spec (fn [v945] (or (s/valid? clojure.core/map? v945) (s/valid? (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? :branch-6 clojure.core/map? :branch-7 clojure.core/map? :branch-8 clojure.core/map?) v945))))) (s/def :github.copilot-sdk.generated.event-specs/resume-time clojure.core/string?) @@ -321,7 +329,7 @@ (s/def :github.copilot-sdk.generated.event-specs/skills (s/coll-of clojure.core/map?)) -(s/def :github.copilot-sdk.generated.event-specs/source (s/spec (fn [v941] (or (s/valid? clojure.core/string? v941) (s/valid? #{"top_level" "subagent" "mcp_sampling"} v941))))) +(s/def :github.copilot-sdk.generated.event-specs/source (s/spec (fn [v942] (or (s/valid? clojure.core/string? v942) (s/valid? #{"top_level" "subagent" "mcp_sampling"} v942))))) (s/def :github.copilot-sdk.generated.event-specs/source-type #{"remote" "local"}) @@ -335,6 +343,8 @@ (s/def :github.copilot-sdk.generated.event-specs/status-code clojure.core/integer?) +(s/def :github.copilot-sdk.generated.event-specs/subject clojure.core/map?) + (s/def :github.copilot-sdk.generated.event-specs/success clojure.core/boolean?) (s/def :github.copilot-sdk.generated.event-specs/summary clojure.core/string?) @@ -393,7 +403,7 @@ (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 [v942] (or (s/valid? #{"session.start"} v942) (s/valid? #{"session.resume"} v942) (s/valid? #{"session.remote_steerable_changed"} v942) (s/valid? #{"session.error"} v942) (s/valid? #{"session.idle"} v942) (s/valid? #{"session.title_changed"} v942) (s/valid? #{"session.schedule_created"} v942) (s/valid? #{"session.schedule_cancelled"} v942) (s/valid? #{"session.info"} v942) (s/valid? #{"session.warning"} v942) (s/valid? #{"session.model_change"} v942) (s/valid? #{"session.mode_changed"} v942) (s/valid? #{"session.plan_changed"} v942) (s/valid? #{"session.workspace_file_changed"} v942) (s/valid? #{"session.handoff"} v942) (s/valid? #{"session.truncation"} v942) (s/valid? #{"session.snapshot_rewind"} v942) (s/valid? #{"session.shutdown"} v942) (s/valid? #{"session.context_changed"} v942) (s/valid? #{"session.usage_info"} v942) (s/valid? #{"session.compaction_start"} v942) (s/valid? #{"session.compaction_complete"} v942) (s/valid? #{"session.task_complete"} v942) (s/valid? #{"user.message"} v942) (s/valid? #{"pending_messages.modified"} v942) (s/valid? #{"assistant.turn_start"} v942) (s/valid? #{"assistant.intent"} v942) (s/valid? #{"assistant.reasoning"} v942) (s/valid? #{"assistant.reasoning_delta"} v942) (s/valid? #{"assistant.streaming_delta"} v942) (s/valid? #{"assistant.message"} v942) (s/valid? #{"assistant.message_start"} v942) (s/valid? #{"assistant.message_delta"} v942) (s/valid? #{"assistant.turn_end"} v942) (s/valid? #{"assistant.usage"} v942) (s/valid? #{"model.call_failure"} v942) (s/valid? #{"abort"} v942) (s/valid? #{"tool.user_requested"} v942) (s/valid? #{"tool.execution_start"} v942) (s/valid? #{"tool.execution_partial_result"} v942) (s/valid? #{"tool.execution_progress"} v942) (s/valid? #{"tool.execution_complete"} v942) (s/valid? #{"skill.invoked"} v942) (s/valid? #{"subagent.started"} v942) (s/valid? #{"subagent.completed"} v942) (s/valid? #{"subagent.failed"} v942) (s/valid? #{"subagent.selected"} v942) (s/valid? #{"subagent.deselected"} v942) (s/valid? #{"hook.start"} v942) (s/valid? #{"hook.end"} v942) (s/valid? #{"system.message"} v942) (s/valid? #{"system.notification"} v942) (s/valid? #{"permission.requested"} v942) (s/valid? #{"permission.completed"} v942) (s/valid? #{"user_input.requested"} v942) (s/valid? #{"user_input.completed"} v942) (s/valid? #{"elicitation.requested"} v942) (s/valid? #{"elicitation.completed"} v942) (s/valid? #{"sampling.requested"} v942) (s/valid? #{"sampling.completed"} v942) (s/valid? #{"mcp.oauth_required"} v942) (s/valid? #{"mcp.oauth_completed"} v942) (s/valid? #{"external_tool.requested"} v942) (s/valid? #{"external_tool.completed"} v942) (s/valid? #{"command.queued"} v942) (s/valid? #{"command.execute"} v942) (s/valid? #{"command.completed"} v942) (s/valid? #{"auto_mode_switch.requested"} v942) (s/valid? #{"auto_mode_switch.completed"} v942) (s/valid? #{"commands.changed"} v942) (s/valid? #{"capabilities.changed"} v942) (s/valid? #{"exit_plan_mode.requested"} v942) (s/valid? #{"exit_plan_mode.completed"} v942) (s/valid? #{"session.tools_updated"} v942) (s/valid? #{"session.background_tasks_changed"} v942) (s/valid? #{"session.skills_loaded"} v942) (s/valid? #{"session.custom_agents_updated"} v942) (s/valid? #{"session.mcp_servers_loaded"} v942) (s/valid? #{"session.mcp_server_status_changed"} v942) (s/valid? #{"session.extensions_loaded"} v942))))) +(s/def :github.copilot-sdk.generated.event-specs/type (s/spec (fn [v943] (or (s/valid? #{"session.start"} v943) (s/valid? #{"session.resume"} v943) (s/valid? #{"session.remote_steerable_changed"} v943) (s/valid? #{"session.error"} v943) (s/valid? #{"session.idle"} v943) (s/valid? #{"session.title_changed"} v943) (s/valid? #{"session.schedule_created"} v943) (s/valid? #{"session.schedule_cancelled"} v943) (s/valid? #{"session.info"} v943) (s/valid? #{"session.warning"} v943) (s/valid? #{"session.model_change"} v943) (s/valid? #{"session.mode_changed"} v943) (s/valid? #{"session.plan_changed"} v943) (s/valid? #{"session.workspace_file_changed"} v943) (s/valid? #{"session.handoff"} v943) (s/valid? #{"session.truncation"} v943) (s/valid? #{"session.snapshot_rewind"} v943) (s/valid? #{"session.shutdown"} v943) (s/valid? #{"session.context_changed"} v943) (s/valid? #{"session.usage_info"} v943) (s/valid? #{"session.compaction_start"} v943) (s/valid? #{"session.compaction_complete"} v943) (s/valid? #{"session.task_complete"} v943) (s/valid? #{"user.message"} v943) (s/valid? #{"pending_messages.modified"} v943) (s/valid? #{"assistant.turn_start"} v943) (s/valid? #{"assistant.intent"} v943) (s/valid? #{"assistant.reasoning"} v943) (s/valid? #{"assistant.reasoning_delta"} v943) (s/valid? #{"assistant.streaming_delta"} v943) (s/valid? #{"assistant.message"} v943) (s/valid? #{"assistant.message_start"} v943) (s/valid? #{"assistant.message_delta"} v943) (s/valid? #{"assistant.turn_end"} v943) (s/valid? #{"assistant.usage"} v943) (s/valid? #{"model.call_failure"} v943) (s/valid? #{"abort"} v943) (s/valid? #{"tool.user_requested"} v943) (s/valid? #{"tool.execution_start"} v943) (s/valid? #{"tool.execution_partial_result"} v943) (s/valid? #{"tool.execution_progress"} v943) (s/valid? #{"tool.execution_complete"} v943) (s/valid? #{"skill.invoked"} v943) (s/valid? #{"subagent.started"} v943) (s/valid? #{"subagent.completed"} v943) (s/valid? #{"subagent.failed"} v943) (s/valid? #{"subagent.selected"} v943) (s/valid? #{"subagent.deselected"} v943) (s/valid? #{"hook.start"} v943) (s/valid? #{"hook.end"} v943) (s/valid? #{"system.message"} v943) (s/valid? #{"system.notification"} v943) (s/valid? #{"permission.requested"} v943) (s/valid? #{"permission.completed"} v943) (s/valid? #{"user_input.requested"} v943) (s/valid? #{"user_input.completed"} v943) (s/valid? #{"elicitation.requested"} v943) (s/valid? #{"elicitation.completed"} v943) (s/valid? #{"sampling.requested"} v943) (s/valid? #{"sampling.completed"} v943) (s/valid? #{"mcp.oauth_required"} v943) (s/valid? #{"mcp.oauth_completed"} v943) (s/valid? #{"session.custom_notification"} v943) (s/valid? #{"external_tool.requested"} v943) (s/valid? #{"external_tool.completed"} v943) (s/valid? #{"command.queued"} v943) (s/valid? #{"command.execute"} v943) (s/valid? #{"command.completed"} v943) (s/valid? #{"auto_mode_switch.requested"} v943) (s/valid? #{"auto_mode_switch.completed"} v943) (s/valid? #{"commands.changed"} v943) (s/valid? #{"capabilities.changed"} v943) (s/valid? #{"exit_plan_mode.requested"} v943) (s/valid? #{"exit_plan_mode.completed"} v943) (s/valid? #{"session.tools_updated"} v943) (s/valid? #{"session.background_tasks_changed"} v943) (s/valid? #{"session.skills_loaded"} v943) (s/valid? #{"session.custom_agents_updated"} v943) (s/valid? #{"session.mcp_servers_loaded"} v943) (s/valid? #{"session.mcp_server_status_changed"} v943) (s/valid? #{"session.extensions_loaded"} v943))))) (s/def :github.copilot-sdk.generated.event-specs/ui clojure.core/map?) @@ -401,7 +411,7 @@ (s/def :github.copilot-sdk.generated.event-specs/url clojure.core/string?) -(s/def :github.copilot-sdk.generated.event-specs/version clojure.core/number?) +(s/def :github.copilot-sdk.generated.event-specs/version (s/spec (fn [v944] (or (s/valid? clojure.core/number? v944) (s/valid? clojure.core/integer? v944))))) (s/def :github.copilot-sdk.generated.event-specs/warning-type clojure.core/string?) @@ -429,7 +439,7 @@ (s/def :github.copilot-sdk.generated.event-specs/assistant.turn_start-data (s/keys :req-un [:github.copilot-sdk.generated.event-specs/turn-id] :opt-un [:github.copilot-sdk.generated.event-specs/interaction-id])) -(s/def :github.copilot-sdk.generated.event-specs/assistant.usage-data (s/keys :req-un [:github.copilot-sdk.generated.event-specs/model] :opt-un [:github.copilot-sdk.generated.event-specs/api-call-id :github.copilot-sdk.generated.event-specs/cache-read-tokens :github.copilot-sdk.generated.event-specs/cache-write-tokens :github.copilot-sdk.generated.event-specs/copilot-usage :github.copilot-sdk.generated.event-specs/cost :github.copilot-sdk.generated.event-specs/duration :github.copilot-sdk.generated.event-specs/initiator :github.copilot-sdk.generated.event-specs/input-tokens :github.copilot-sdk.generated.event-specs/inter-token-latency-ms :github.copilot-sdk.generated.event-specs/output-tokens :github.copilot-sdk.generated.event-specs/parent-tool-call-id :github.copilot-sdk.generated.event-specs/provider-call-id :github.copilot-sdk.generated.event-specs/quota-snapshots :github.copilot-sdk.generated.event-specs/reasoning-effort :github.copilot-sdk.generated.event-specs/reasoning-tokens :github.copilot-sdk.generated.event-specs/ttft-ms])) +(s/def :github.copilot-sdk.generated.event-specs/assistant.usage-data (s/keys :req-un [:github.copilot-sdk.generated.event-specs/model] :opt-un [:github.copilot-sdk.generated.event-specs/api-call-id :github.copilot-sdk.generated.event-specs/api-endpoint :github.copilot-sdk.generated.event-specs/cache-read-tokens :github.copilot-sdk.generated.event-specs/cache-write-tokens :github.copilot-sdk.generated.event-specs/copilot-usage :github.copilot-sdk.generated.event-specs/cost :github.copilot-sdk.generated.event-specs/duration :github.copilot-sdk.generated.event-specs/initiator :github.copilot-sdk.generated.event-specs/input-tokens :github.copilot-sdk.generated.event-specs/inter-token-latency-ms :github.copilot-sdk.generated.event-specs/output-tokens :github.copilot-sdk.generated.event-specs/parent-tool-call-id :github.copilot-sdk.generated.event-specs/provider-call-id :github.copilot-sdk.generated.event-specs/quota-snapshots :github.copilot-sdk.generated.event-specs/reasoning-effort :github.copilot-sdk.generated.event-specs/reasoning-tokens :github.copilot-sdk.generated.event-specs/ttft-ms])) (s/def :github.copilot-sdk.generated.event-specs/auto_mode_switch.completed-data (s/keys :req-un [:github.copilot-sdk.generated.event-specs/request-id :github.copilot-sdk.generated.event-specs/response])) @@ -487,6 +497,8 @@ (s/def :github.copilot-sdk.generated.event-specs/session.custom_agents_updated-data (s/keys :req-un [:github.copilot-sdk.generated.event-specs/agents :github.copilot-sdk.generated.event-specs/errors :github.copilot-sdk.generated.event-specs/warnings])) +(s/def :github.copilot-sdk.generated.event-specs/session.custom_notification-data (s/keys :req-un [:github.copilot-sdk.generated.event-specs/name :github.copilot-sdk.generated.event-specs/payload :github.copilot-sdk.generated.event-specs/source] :opt-un [:github.copilot-sdk.generated.event-specs/subject :github.copilot-sdk.generated.event-specs/version])) + (s/def :github.copilot-sdk.generated.event-specs/session.error-data (s/keys :req-un [:github.copilot-sdk.generated.event-specs/error-type :github.copilot-sdk.generated.event-specs/message] :opt-un [:github.copilot-sdk.generated.event-specs/eligible-for-auto-switch :github.copilot-sdk.generated.event-specs/error-code :github.copilot-sdk.generated.event-specs/provider-call-id :github.copilot-sdk.generated.event-specs/stack :github.copilot-sdk.generated.event-specs/status-code :github.copilot-sdk.generated.event-specs/url])) (s/def :github.copilot-sdk.generated.event-specs/session.extensions_loaded-data (s/keys :req-un [:github.copilot-sdk.generated.event-specs/extensions])) @@ -513,7 +525,7 @@ (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]) (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/recurring]) (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 :github.copilot-sdk.generated.event-specs/total-premium-requests] :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])) @@ -563,7 +575,7 @@ (s/def :github.copilot-sdk.generated.event-specs/tool.user_requested-data (s/keys :req-un [:github.copilot-sdk.generated.event-specs/tool-call-id :github.copilot-sdk.generated.event-specs/tool-name] :opt-un [:github.copilot-sdk.generated.event-specs/arguments])) -(s/def :github.copilot-sdk.generated.event-specs/user.message-data (s/keys :req-un [:github.copilot-sdk.generated.event-specs/content] :opt-un [:github.copilot-sdk.generated.event-specs/agent-mode :github.copilot-sdk.generated.event-specs/attachments :github.copilot-sdk.generated.event-specs/interaction-id :github.copilot-sdk.generated.event-specs/native-document-path-fallback-paths :github.copilot-sdk.generated.event-specs/parent-agent-task-id :github.copilot-sdk.generated.event-specs/source :github.copilot-sdk.generated.event-specs/supported-native-document-mime-types :github.copilot-sdk.generated.event-specs/transformed-content])) +(s/def :github.copilot-sdk.generated.event-specs/user.message-data (s/keys :req-un [:github.copilot-sdk.generated.event-specs/content] :opt-un [:github.copilot-sdk.generated.event-specs/agent-mode :github.copilot-sdk.generated.event-specs/attachments :github.copilot-sdk.generated.event-specs/interaction-id :github.copilot-sdk.generated.event-specs/is-autopilot-continuation :github.copilot-sdk.generated.event-specs/native-document-path-fallback-paths :github.copilot-sdk.generated.event-specs/parent-agent-task-id :github.copilot-sdk.generated.event-specs/source :github.copilot-sdk.generated.event-specs/supported-native-document-mime-types :github.copilot-sdk.generated.event-specs/transformed-content])) (s/def :github.copilot-sdk.generated.event-specs/user_input.completed-data (s/keys :req-un [:github.copilot-sdk.generated.event-specs/request-id] :opt-un [:github.copilot-sdk.generated.event-specs/answer :github.copilot-sdk.generated.event-specs/was-freeform])) @@ -647,6 +659,8 @@ (s/def :github.copilot-sdk.generated.event-specs/session.custom_agents_updated (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.custom_agents_updated" (:type event))) (fn [event] (s/valid? clojure.core/string? (:id event))) (fn [event] (s/valid? :github.copilot-sdk.generated.event-specs/session.custom_agents_updated-data (:data event))))) +(s/def :github.copilot-sdk.generated.event-specs/session.custom_notification (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.custom_notification" (:type event))) (fn [event] (s/valid? clojure.core/string? (:id event))) (fn [event] (s/valid? :github.copilot-sdk.generated.event-specs/session.custom_notification-data (:data event))))) + (s/def :github.copilot-sdk.generated.event-specs/session.error (s/and (s/keys :req-un [:github.copilot-sdk.generated.event-specs/data :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 :github.copilot-sdk.generated.event-specs/ephemeral]) (fn [event] (clojure.core/= "session.error" (:type event))) (fn [event] (s/valid? clojure.core/string? (:id event))) (fn [event] (s/valid? :github.copilot-sdk.generated.event-specs/session.error-data (:data event))))) (s/def :github.copilot-sdk.generated.event-specs/session.extensions_loaded (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.extensions_loaded" (:type event))) (fn [event] (s/valid? clojure.core/string? (:id event))) (fn [event] (s/valid? :github.copilot-sdk.generated.event-specs/session.extensions_loaded-data (:data event))))) @@ -729,7 +743,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.start" "mcp.oauth_completed" "mcp.oauth_required" "model.call_failure" "pending_messages.modified" "permission.completed" "permission.requested" "sampling.completed" "sampling.requested" "session.background_tasks_changed" "session.compaction_complete" "session.compaction_start" "session.context_changed" "session.custom_agents_updated" "session.error" "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.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.start" "mcp.oauth_completed" "mcp.oauth_required" "model.call_failure" "pending_messages.modified" "permission.completed" "permission.requested" "sampling.completed" "sampling.requested" "session.background_tasks_changed" "session.compaction_complete" "session.compaction_start" "session.context_changed" "session.custom_agents_updated" "session.custom_notification" "session.error" "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.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) @@ -811,6 +825,8 @@ (defmethod event-mm "session.custom_agents_updated" [_] (s/get-spec :github.copilot-sdk.generated.event-specs/session.custom_agents_updated)) +(defmethod event-mm "session.custom_notification" [_] (s/get-spec :github.copilot-sdk.generated.event-specs/session.custom_notification)) + (defmethod event-mm "session.error" [_] (s/get-spec :github.copilot-sdk.generated.event-specs/session.error)) (defmethod event-mm "session.extensions_loaded" [_] (s/get-spec :github.copilot-sdk.generated.event-specs/session.extensions_loaded)) From 7e2a118ab1a9939697a2b19ffeb1a3d36e9efa68 Mon Sep 17 00:00:00 2001 From: Karl Krukow Date: Fri, 15 May 2026 09:43:00 +0200 Subject: [PATCH 2/6] feat: sync session features from upstream copilot-sdk 1.0.47/1.0.48 Three upstream features ported with idiomatic Clojure surface: 1. Per-session remote mode for remote-enable (PR #1288, CLI 1.0.48-1) - session/remote-enable! now accepts a 2-arity overload with :mode opts (:off / :export / :on). Existing 1-arity form preserved. - New ::remote-session-mode and ::remote-enable-opts specs. 2. :remote-session session config option (PR #1295, CLI 1.0.48) - create-session and resume-session accept :remote-session set to :off, :export, or :on; forwarded on the wire as remoteSession. Validated by ::remote-session (alias of ::remote-session-mode); bogus values are rejected at session-create time. 3. :copilot/session.custom_notification event (PR #1292, CLI 1.0.48) - Skills can emit application-level events via the Notify block. Exposed in sdk/event-types and sdk/session-events; idiom spec ::session.custom_notification-data covers :source, :name, :payload, :subject, :version. - Protocol normalizer preserves :subject and :payload verbatim (no kebab-casing) because both carry source-defined identifiers and opaque JSON. Mirrors the existing escape hatch for external_tool.requested arguments. Also surfaces post-1.0.46 wire fields previously left implicit: :recurring on schedule events, :is-autopilot-continuation on user messages, and :api-endpoint on extension auth events. Extension permission kinds (:extension-management, :extension-permission-access) added to ::permission-kind. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/github/copilot_sdk.clj | 2 + src/github/copilot_sdk/client.clj | 7 +++ src/github/copilot_sdk/instrument.clj | 3 +- src/github/copilot_sdk/protocol.clj | 20 ++++++- src/github/copilot_sdk/session.clj | 23 +++++--- src/github/copilot_sdk/specs.clj | 77 ++++++++++++++++++++++++--- 6 files changed, 116 insertions(+), 16 deletions(-) diff --git a/src/github/copilot_sdk.clj b/src/github/copilot_sdk.clj index 94b5315..283c21e 100644 --- a/src/github/copilot_sdk.clj +++ b/src/github/copilot_sdk.clj @@ -116,6 +116,7 @@ :copilot/session.mcp_server_status_changed :copilot/session.extensions_loaded :copilot/session.custom_agents_updated + :copilot/session.custom_notification :copilot/sampling.requested :copilot/sampling.completed :copilot/session.remote_steerable_changed @@ -152,6 +153,7 @@ :copilot/session.mcp_server_status_changed :copilot/session.extensions_loaded :copilot/session.custom_agents_updated + :copilot/session.custom_notification :copilot/session.remote_steerable_changed :copilot/capabilities.changed}) diff --git a/src/github/copilot_sdk/client.clj b/src/github/copilot_sdk/client.clj index 1f85235..7abcb35 100644 --- a/src/github/copilot_sdk/client.clj +++ b/src/github/copilot_sdk/client.clj @@ -1499,6 +1499,8 @@ (assoc :enable-config-discovery (:enable-config-discovery config)) (some? (:enable-session-telemetry? config)) (assoc :enable-session-telemetry (:enable-session-telemetry? config)) + (:remote-session config) + (assoc :remote-session (name (:remote-session config))) (:model-capabilities config) (assoc :model-capabilities (util/clj->wire (:model-capabilities config))) true (assoc :include-sub-agent-streaming-events @@ -1577,6 +1579,8 @@ (assoc :enable-config-discovery (:enable-config-discovery config)) (some? (:enable-session-telemetry? config)) (assoc :enable-session-telemetry (:enable-session-telemetry? config)) + (:remote-session config) + (assoc :remote-session (name (:remote-session config))) (:model-capabilities config) (assoc :model-capabilities (util/clj->wire (:model-capabilities config))) true (assoc :include-sub-agent-streaming-events @@ -1651,6 +1655,9 @@ - :include-sub-agent-streaming-events? - Boolean. When true (default), streaming events from sub-agents are forwarded to this session's event stream. (upstream PR #1108) + - :remote-session - Keyword. Per-session Mission Control remote mode: :off, :export, or :on. + Forwarded as `remoteSession` on session.create. When omitted, the CLI + applies its default. (upstream PR #1295, CLI 1.0.48) Returns a CopilotSession." [client config] diff --git a/src/github/copilot_sdk/instrument.clj b/src/github/copilot_sdk/instrument.clj index c4eb34e..5155c1e 100644 --- a/src/github/copilot_sdk/instrument.clj +++ b/src/github/copilot_sdk/instrument.clj @@ -511,7 +511,8 @@ ;; Remote sessions RPC function specs (upstream PR #1192) (register-fdef! github.copilot-sdk.session/remote-enable - :args (s/cat :session ::specs/session) + :args (s/cat :session ::specs/session + :opts (s/? (s/nilable ::specs/remote-enable-opts))) :ret ::specs/remote-enable-result) (register-fdef! github.copilot-sdk.session/remote-disable diff --git a/src/github/copilot_sdk/protocol.clj b/src/github/copilot_sdk/protocol.clj index cf5ef90..623acd1 100644 --- a/src/github/copilot_sdk/protocol.clj +++ b/src/github/copilot_sdk/protocol.clj @@ -172,10 +172,12 @@ :message (str "Internal error: " (ex-message e))}})))))) (defn- normalize-incoming - "Convert wire-format keys to Clojure keys, preserving tool.call arguments. + "Convert wire-format keys to Clojure keys, preserving opaque user data. For v2 tool.call RPC and v3 external_tool.requested broadcast events, tool arguments are kept in their original wire format so user-defined - tool handlers receive the keys the server sent." + tool handlers receive the keys the server sent. For v3 + session.custom_notification events, the source-defined `:subject` and + opaque `:payload` are also preserved verbatim." [msg] (let [method (:method msg) params (:params msg) @@ -192,6 +194,20 @@ (assoc-in converted [:params :event :data :arguments] (get-in params [:event :data :arguments])) + ;; v3: preserve raw subject/payload keys in session.custom_notification + ;; events. Both fields contain source-defined identifiers/opaque JSON + ;; (PR #1292); kebab-casing would mangle keys like "GitHub-Login" and + ;; collapse "actor" / "Actor" into the same keyword. + (and (= "session.event" method) + (= "session.custom_notification" (get-in params [:event :type]))) + (cond-> converted + (contains? (get-in params [:event :data]) :subject) + (assoc-in [:params :event :data :subject] + (get-in params [:event :data :subject])) + (contains? (get-in params [:event :data]) :payload) + (assoc-in [:params :event :data :payload] + (get-in params [:event :data :payload]))) + :else converted))) diff --git a/src/github/copilot_sdk/session.clj b/src/github/copilot_sdk/session.clj index cf8ad76..3f66fcd 100644 --- a/src/github/copilot_sdk/session.clj +++ b/src/github/copilot_sdk/session.clj @@ -1778,6 +1778,13 @@ "Enable remote steering for this session, exposing it to GitHub Mission Control web/mobile clients. + Optional `opts`: + - `:mode` — keyword, one of `:off`, `:export`, or `:on`. Per-session remote + mode. `:off` disables remote, `:export` exports session events to Mission + Control without enabling remote steering, `:on` enables both export and + remote steering. When omitted, the CLI applies its default. (Upstream CLI + 1.0.48-1, PR #1288.) + Returns a map: - `:url` — Mission Control frontend URL (may be absent). - `:remote-steerable` — boolean; whether remote steering is enabled. @@ -1785,12 +1792,16 @@ **Experimental** — corresponds to the `session.remote.enable` JSON-RPC method introduced upstream in PR #1192. The shape of the result and guarantees may change." - [session] - (let [{:keys [session-id client]} session - conn (connection-io client)] - (util/wire->clj - (proto/send-request! conn "session.remote.enable" - {:session-id session-id})))) + ([session] (remote-enable session nil)) + ([session opts] + (let [{:keys [session-id client]} session + conn (connection-io client) + base {:session-id session-id} + params (if-let [m (:mode opts)] + (assoc base :mode (name m)) + base)] + (util/wire->clj + (proto/send-request! conn "session.remote.enable" params))))) (defn ^:experimental remote-disable "Disable remote steering for this session. Returns nil. diff --git a/src/github/copilot_sdk/specs.clj b/src/github/copilot_sdk/specs.clj index f0f8552..bf74734 100644 --- a/src/github/copilot_sdk/specs.clj +++ b/src/github/copilot_sdk/specs.clj @@ -518,6 +518,7 @@ :working-directory :agent :on-event :create-session-fs-handler :enable-config-discovery :model-capabilities :github-token :enable-session-telemetry? + :remote-session :include-sub-agent-streaming-events?}) (s/def ::session-config @@ -534,6 +535,7 @@ ::working-directory ::agent ::on-event ::create-session-fs-handler ::enable-config-discovery ::model-capabilities ::github-token ::enable-session-telemetry? + ::remote-session ::include-sub-agent-streaming-events?]) session-config-keys)) @@ -548,6 +550,7 @@ :continue-pending-work? :create-session-fs-handler :enable-config-discovery :model-capabilities :github-token :enable-session-telemetry? + :remote-session :include-sub-agent-streaming-events?}) (s/def ::resume-session-config @@ -564,6 +567,7 @@ ::enable-config-discovery ::model-capabilities ::github-token ::continue-pending-work? ::enable-session-telemetry? + ::remote-session ::include-sub-agent-streaming-events?]) resume-session-config-keys)) @@ -583,6 +587,7 @@ ::enable-config-discovery ::model-capabilities ::github-token ::continue-pending-work? ::enable-session-telemetry? + ::remote-session ::include-sub-agent-streaming-events?]) resume-session-config-keys)) @@ -813,6 +818,8 @@ :copilot/session.skills_loaded :copilot/session.mcp_servers_loaded :copilot/session.mcp_server_status_changed :copilot/session.extensions_loaded :copilot/session.custom_agents_updated + ;; Custom notification (upstream PR #1292, CLI 1.0.48) + :copilot/session.custom_notification ;; Sampling events (upstream PR #908) :copilot/sampling.requested :copilot/sampling.completed ;; Session remote steerable (upstream PR #908) @@ -873,14 +880,38 @@ (s/keys :req-un [::remote-steerable] :opt-un [::url])) +;; Per-session remote mode (upstream CLI 1.0.48-1, PR #1288). `:off` disables +;; remote, `:export` exports session events to Mission Control without +;; enabling remote steering, `:on` enables both export and remote steering. +(s/def ::remote-session-mode #{:off :export :on}) +;; Per-session remote mode set via session config (upstream PR #1295). +;; Same allowed values as the `remote-enable` opts `:mode`. +(s/def ::remote-session ::remote-session-mode) +;; Note: opts uses an unqualified `:mode` key (distinct from ::mode which is +;; reused elsewhere for the send queue mode). A custom predicate validates the +;; value rather than s/keys so we don't collide with the existing ::mode spec. +(s/def ::remote-enable-opts + (s/and map? + (fn [m] + (or (not (contains? m :mode)) + (s/valid? ::remote-session-mode (:mode m)))))) + (s/def ::agent-mode #{:interactive :plan :autopilot :shell}) (s/def ::interaction-id string?) (s/def ::source string?) ;; user.message event data — attachments can include blobs (inbound-only types) +;; :is-autopilot-continuation — boolean, added upstream CLI 1.0.47 (PR #1286). +;; True when the message was auto-injected by autopilot's continuation loop +;; rather than typed by the user. Note: NO trailing `?` — camel-snake-kebab +;; converts wire `isAutopilotContinuation` to `:is-autopilot-continuation` +;; (csk does not append `?` for booleans). See `::remote-steerable` for the +;; same precedent. +(s/def ::is-autopilot-continuation boolean?) (s/def ::user.message-data (s/and (s/keys :req-un [::content] - :opt-un [::transformed-content ::source ::agent-mode ::interaction-id]) + :opt-un [::transformed-content ::source ::agent-mode + ::interaction-id ::is-autopilot-continuation]) #(or (not (contains? % :attachments)) (s/valid? ::inbound-attachments (:attachments %))))) @@ -931,13 +962,22 @@ (s/def ::ttft-ms nat-int?) (s/def ::copilot-usage map?) +;; :api-endpoint — open string enum, added upstream CLI 1.0.47 (PR #1286). +;; API endpoint used for this model call, matching CAPI supported_endpoints +;; vocabulary. Known values: "/chat/completions", "/v1/messages", "/responses", +;; "ws:/responses". Modeled as an open string for forward-compatibility — the +;; upstream wire spec restricts the enum, but the idiom spec deliberately +;; doesn't so unknown future values pass through. +(s/def ::api-endpoint string?) + (s/def ::assistant.usage-data (s/keys :req-un [::model] - :opt-un [::api-call-id ::cache-read-tokens ::cache-write-tokens - ::copilot-usage ::cost ::duration ::initiator ::input-tokens - ::inter-token-latency-ms ::output-tokens ::parent-tool-call-id - ::provider-call-id ::quota-snapshots ::reasoning-effort - ::reasoning-tokens ::ttft-ms])) + :opt-un [::api-call-id ::api-endpoint ::cache-read-tokens + ::cache-write-tokens ::copilot-usage ::cost ::duration + ::initiator ::input-tokens ::inter-token-latency-ms + ::output-tokens ::parent-tool-call-id ::provider-call-id + ::quota-snapshots ::reasoning-effort ::reasoning-tokens + ::ttft-ms])) (s/def ::mcp-server-name string?) (s/def ::mcp-tool-name string?) @@ -1028,8 +1068,14 @@ ;; global ::id spec. ::interval-ms is also strictly positive per schema. (s/def ::interval-ms pos-int?) ;; ::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`). +;; Note: NO trailing `?` — camel-snake-kebab converts wire `recurring` to +;; `: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]) + (s/and (s/keys :req-un [::interval-ms ::prompt] + :opt-un [::recurring]) #(contains? % :id) #(pos-int? (:id %)))) (s/def ::session.schedule_cancelled-data @@ -1088,6 +1134,23 @@ (s/def ::session.custom_agents_updated-data (s/keys :req-un [::agents ::warnings ::errors])) +;; session.custom_notification (upstream PR #1292, CLI 1.0.48). Emitted from +;; Skills via Notify; opaque envelope with extension-supplied payload. +;; Note: the wire schema's :version is a positive integer, distinct from the +;; SDK's global ::version spec (string?) used by ::model-info. We validate +;; :version inline via s/and to avoid collision. +;; `:subject` keys are preserved verbatim (not kebab-cased) by +;; protocol/normalize-incoming — subject identifiers and payload contents are +;; source-defined and opaque. Keys are still keywords (JSON parser uses +;; `:key-fn keyword`) but retain original casing/dots. +(s/def ::payload any?) +(s/def ::subject (s/map-of keyword? string?)) +(s/def ::session.custom_notification-data + (s/and (s/keys :req-un [::source ::name ::payload] + :opt-un [::subject]) + #(or (not (contains? % :version)) + (pos-int? (:version %))))) + ;; Session status/listing events from generated session-events schema. (s/def ::mcp-server-status #{"connected" "failed" "needs-auth" "pending" "disabled" "not_configured"}) From bbc70e6ae91198b7ba8c95e9fa6ec259f47c4b21 Mon Sep 17 00:00:00 2001 From: Karl Krukow Date: Fri, 15 May 2026 09:43:10 +0200 Subject: [PATCH 3/6] test: integration tests for upstream 1.0.47/1.0.48 sync MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - test-custom-notification-event-type: verifies public event-types/ session-events registration, idiom spec acceptance, and that protocol/normalize-incoming preserves source-defined :subject keys (e.g., :GitHub-Login) and opaque :payload contents verbatim. - test-remote-session-config-forwarded-on-wire: covers :on, :export, :off, omission, and spec rejection of bogus values for both session.create and session.resume params. - Fix two pre-existing tests (test-schedule-created-recurring-field, test-user-message-is-autopilot-continuation-field) to use keyword-keyed wire data — util/wire->clj only transforms keyword keys, so string-keyed fixtures bypassed conversion silently. - Codegen fixture for session.custom_notification added so wire/idiom spec drift would be caught. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- test/github/copilot_sdk/codegen_test.clj | 9 +- test/github/copilot_sdk/integration_test.clj | 206 +++++++++++++++++++ 2 files changed, 214 insertions(+), 1 deletion(-) diff --git a/test/github/copilot_sdk/codegen_test.clj b/test/github/copilot_sdk/codegen_test.clj index 59c9d05..3c1b57e 100644 --- a/test/github/copilot_sdk/codegen_test.clj +++ b/test/github/copilot_sdk/codegen_test.clj @@ -233,7 +233,14 @@ {:id 42 :interval-ms 1000 :prompt "ping me"} "session.schedule_cancelled" - {:id 42}}) + {:id 42} + + "session.custom_notification" + {:source "my-extension" + :name "doc.opened" + :payload {:path "/tmp/x"} + :subject {:doc "foo"} + :version 1}}) (defn- envelope "Wrap a data payload in a minimal valid envelope of the given type. Honours diff --git a/test/github/copilot_sdk/integration_test.clj b/test/github/copilot_sdk/integration_test.clj index a65ccd5..e6c4c3f 100644 --- a/test/github/copilot_sdk/integration_test.clj +++ b/test/github/copilot_sdk/integration_test.clj @@ -9,6 +9,7 @@ [github.copilot-sdk.protocol :as protocol] [github.copilot-sdk.session :as session] [github.copilot-sdk.tools :as tools] + [github.copilot-sdk.util :as util] [github.copilot-sdk.generated.event-specs] [github.copilot-sdk.mock-server :as mock])) @@ -869,6 +870,57 @@ "schedule data must reject non-integer :id (vs the UUID-string ::id used elsewhere)") (is (s/valid? :github.copilot-sdk.specs/session.schedule_cancelled-data {:id 42})))) +(deftest test-custom-notification-event-type + (testing "session.custom_notification is part of the public ::sdk/event-types set (upstream PR #1292)" + (is (contains? sdk/event-types :copilot/session.custom_notification))) + (testing "session.custom_notification is categorized under session-events" + (is (contains? sdk/session-events :copilot/session.custom_notification))) + (testing "session.custom_notification is accepted by the idiom ::specs/event-type spec" + (is (s/valid? :github.copilot-sdk.specs/event-type :copilot/session.custom_notification))) + (testing "::session.custom_notification-data idiom spec accepts a well-formed payload" + (is (s/valid? :github.copilot-sdk.specs/session.custom_notification-data + {:source "my-extension" + :name "doc.opened" + :payload {:path "/tmp/x"} + :subject {:doc "foo"} + :version 1})) + (is (s/valid? :github.copilot-sdk.specs/session.custom_notification-data + {:source "my-extension" + :name "ping" + :payload "scalar-ok"}) + "payload may be a scalar") + (is (not (s/valid? :github.copilot-sdk.specs/session.custom_notification-data + {:name "doc.opened" :payload {}})) + "missing :source must reject")) + (testing "subject and payload keys are preserved verbatim (not kebab-cased) by normalize-incoming" + ;; Source-defined identifiers (subject) and opaque JSON (payload) must + ;; survive normalize-incoming without key transformation, matching the + ;; existing escape hatch for external_tool.requested arguments. + (let [normalize @#'github.copilot-sdk.protocol/normalize-incoming + raw-msg {:jsonrpc "2.0" + :method "session.event" + :params {:sessionId "abc" + :event {:type "session.custom_notification" + :data {:source "my-ext" + :name "doc.opened" + :subject {:GitHub-Login "octocat" + :actor.id "123"} + :payload {:firstName "Foo" + :nested {:userId 42}}}}}} + normalized (normalize raw-msg) + data (get-in normalized [:params :event :data])] + (is (= "session.custom_notification" (get-in normalized [:params :event :type]))) + (is (s/valid? :github.copilot-sdk.specs/session.custom_notification-data data)) + (is (contains? (:subject data) :GitHub-Login) + "subject must preserve original casing, not collapse to :git-hub-login") + (is (= "octocat" (get-in data [:subject :GitHub-Login]))) + (is (= "123" (get-in data [:subject :actor.id])) + "subject must preserve dotted keys") + (is (= 42 (get-in data [:payload :nested :userId])) + "payload nested keys must not be kebab-cased") + (is (= "Foo" (get-in data [:payload :firstName])) + "payload top-level keys must not be kebab-cased")))) + (deftest test-custom-agent-info-tools-nilable (testing "::custom-agent-info accepts :tools nil (upstream schema 1.0.41-1: tools: string[] | null)" (let [agent-with-nil-tools {:id "a" @@ -1056,6 +1108,57 @@ (is (not (contains? create-params :gitHubToken))) (is (not (contains? create-params :githubToken)))))) +(deftest test-remote-session-config-forwarded-on-wire + (testing "remote-session :on is forwarded as remoteSession in session.create (upstream PR #1295)" + (let [seen (atom {}) + _ (mock/set-request-hook! *mock-server* (fn [method params] + (when (= "session.create" method) + (swap! seen assoc method params)))) + _ (sdk/create-session *test-client* + {:on-permission-request sdk/approve-all + :remote-session :on}) + create-params (get @seen "session.create")] + (is (= "on" (:remoteSession create-params))) + (is (not (contains? create-params :remote-session))))) + + (testing "remote-session :export is forwarded in session.resume (upstream PR #1295)" + (let [seen (atom {}) + session-id (sdk/session-id (sdk/create-session *test-client* {:on-permission-request sdk/approve-all})) + _ (mock/set-request-hook! *mock-server* (fn [method params] + (when (= "session.resume" method) + (swap! seen assoc method params)))) + _ (sdk/resume-session *test-client* session-id + {:on-permission-request sdk/approve-all + :remote-session :export}) + resume-params (get @seen "session.resume")] + (is (= "export" (:remoteSession resume-params))))) + + (testing "remote-session :off is forwarded literally (not stripped)" + (let [seen (atom {}) + _ (mock/set-request-hook! *mock-server* (fn [method params] + (when (= "session.create" method) + (swap! seen assoc method params)))) + _ (sdk/create-session *test-client* + {:on-permission-request sdk/approve-all + :remote-session :off}) + create-params (get @seen "session.create")] + (is (= "off" (:remoteSession create-params))))) + + (testing "remote-session is omitted from wire when not set" + (let [seen (atom {}) + _ (mock/set-request-hook! *mock-server* (fn [method params] + (when (= "session.create" method) + (swap! seen assoc method params)))) + _ (sdk/create-session *test-client* {:on-permission-request sdk/approve-all}) + create-params (get @seen "session.create")] + (is (not (contains? create-params :remoteSession))))) + + (testing "remote-session config rejects unknown values via spec validation" + (is (thrown? Exception + (sdk/create-session *test-client* + {:on-permission-request sdk/approve-all + :remote-session :bogus}))))) + (deftest test-agent-forwarded-on-wire (testing "agent is forwarded in session.create when set (upstream PR #722)" (let [seen (atom {}) @@ -3387,6 +3490,109 @@ (is (some #(= "session.remote.disable" (:method %)) @requests)) (is (nil? result))))) +;; --- Remote enable mode parameter (upstream CLI 1.0.48-1, PR #1288) -------- + +(deftest test-remote-enable-no-mode + (testing "remote-enable with no args sends no :mode on the wire (back-compat)" + (let [requests (atom []) + _ (mock/set-request-hook! *mock-server* + (fn [method params] + (swap! requests conj {:method method :params params}))) + session (sdk/create-session *test-client* + {:on-permission-request sdk/approve-all}) + _ (session/remote-enable session) + req (first (filter #(= "session.remote.enable" (:method %)) @requests))] + (is (some? req)) + (is (not (contains? (:params req) :mode)) + "no-arg call must not include :mode in wire params")))) + +(deftest test-remote-enable-with-mode + (testing "remote-enable accepts opts {:mode :export} and forwards as wire string" + (doseq [m [:off :export :on]] + (let [requests (atom []) + _ (mock/set-request-hook! *mock-server* + (fn [method params] + (swap! requests conj {:method method :params params}))) + session (sdk/create-session *test-client* + {:on-permission-request sdk/approve-all}) + _ (session/remote-enable session {:mode m}) + req (first (filter #(= "session.remote.enable" (:method %)) @requests))] + (is (some? req)) + (is (= (name m) (:mode (:params req))) + (str ":mode " m " must arrive on the wire as the raw enum string")))))) + +(deftest test-remote-enable-mode-spec + (testing "::remote-session-mode accepts upstream values" + (is (s/valid? :github.copilot-sdk.specs/remote-session-mode :off)) + (is (s/valid? :github.copilot-sdk.specs/remote-session-mode :export)) + (is (s/valid? :github.copilot-sdk.specs/remote-session-mode :on)) + (is (not (s/valid? :github.copilot-sdk.specs/remote-session-mode :bogus))))) + +;; --- session.schedule_created :recurring? (upstream CLI 1.0.48-1) ---------- + +(deftest test-schedule-created-recurring-field + (testing "session.schedule_created-data accepts optional :recurring boolean" + (is (s/valid? :github.copilot-sdk.specs/session.schedule_created-data + {:id 1 :interval-ms 1000 :prompt "ping" :recurring true})) + (is (s/valid? :github.copilot-sdk.specs/session.schedule_created-data + {:id 1 :interval-ms 1000 :prompt "ping" :recurring false})) + (is (s/valid? :github.copilot-sdk.specs/session.schedule_created-data + {:id 1 :interval-ms 1000 :prompt "ping"}) + "still valid without :recurring for back-compat") + (is (not (s/valid? :github.copilot-sdk.specs/session.schedule_created-data + {:id 1 :interval-ms 1000 :prompt "ping" :recurring "yes"})) + ":recurring must be a boolean if present")) + (testing "wire-shaped event from wire->clj round-trip validates against idiom spec" + ;; Real upstream wire data uses `recurring` (csk does NOT append `?`) + (let [wire-data {:id 1 :intervalMs 1000 :prompt "ping" :recurring true} + clj-data (util/wire->clj wire-data)] + (is (= true (:recurring clj-data)) + "wire->clj must produce :recurring (no `?` suffix)") + (is (s/valid? :github.copilot-sdk.specs/session.schedule_created-data clj-data) + "post-wire->clj event data must validate against the idiom spec")))) + +;; --- user.message :is-autopilot-continuation (upstream CLI 1.0.47) --------- + +(deftest test-user-message-is-autopilot-continuation-field + (testing "user.message-data accepts optional :is-autopilot-continuation boolean" + (is (s/valid? :github.copilot-sdk.specs/user.message-data + {:content "hello" :is-autopilot-continuation true})) + (is (s/valid? :github.copilot-sdk.specs/user.message-data + {:content "hello" :is-autopilot-continuation false})) + (is (s/valid? :github.copilot-sdk.specs/user.message-data {:content "hello"}) + "still valid without :is-autopilot-continuation for back-compat") + (is (not (s/valid? :github.copilot-sdk.specs/user.message-data + {:content "hello" :is-autopilot-continuation "no"})) + ":is-autopilot-continuation must be boolean if present")) + (testing "wire-shaped event from wire->clj round-trip validates against idiom spec" + (let [wire-data {:content "hi" :isAutopilotContinuation true} + clj-data (util/wire->clj wire-data)] + (is (= true (:is-autopilot-continuation clj-data)) + "wire->clj must produce :is-autopilot-continuation (no `?` suffix)") + (is (s/valid? :github.copilot-sdk.specs/user.message-data clj-data) + "post-wire->clj event data must validate against the idiom spec")))) + +;; --- assistant.usage :api-endpoint (upstream CLI 1.0.47) ------------------- + +(deftest test-assistant-usage-api-endpoint-field + (testing "assistant.usage-data accepts optional :api-endpoint string (open enum)" + (is (s/valid? :github.copilot-sdk.specs/assistant.usage-data + {:model "gpt-5" :api-endpoint "/chat/completions"})) + (is (s/valid? :github.copilot-sdk.specs/assistant.usage-data + {:model "gpt-5" :api-endpoint "/v1/messages"})) + (is (s/valid? :github.copilot-sdk.specs/assistant.usage-data + {:model "gpt-5" :api-endpoint "/responses"})) + (is (s/valid? :github.copilot-sdk.specs/assistant.usage-data + {:model "gpt-5" :api-endpoint "ws:/responses"})) + (is (s/valid? :github.copilot-sdk.specs/assistant.usage-data + {:model "gpt-5" :api-endpoint "/future-unknown"}) + "open enum: unknown future strings should validate (forward-compat)") + (is (s/valid? :github.copilot-sdk.specs/assistant.usage-data {:model "gpt-5"}) + "still valid without :api-endpoint") + (is (not (s/valid? :github.copilot-sdk.specs/assistant.usage-data + {:model "gpt-5" :api-endpoint 42})) + ":api-endpoint must be a string if present"))) + ;; --- Memory permission event data specs (CLI 1.0.22) ----------------------- (deftest test-memory-permission-event-specs From 14f73a821a81cc08e17fc6424a729152e8b76d22 Mon Sep 17 00:00:00 2001 From: Karl Krukow Date: Fri, 15 May 2026 09:43:18 +0200 Subject: [PATCH 4/6] docs: changelog and API reference for 1.0.47/1.0.48 sync - CHANGELOG: schema bump to 1.0.48 GA, new :remote-session config (PR #1295), new :copilot/session.custom_notification event with key-preservation note (PR #1292), plus the previously-pinned 1.0.48-1 entries. - doc/reference/API.md: :remote-session row in create-session config table; :copilot/session.custom_notification row in events table. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- CHANGELOG.md | 59 ++++++++++++++++++++++++++++++++++++++++++-- doc/reference/API.md | 11 ++++++++- 2 files changed, 67 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 61b3350..19d2733 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,8 +5,24 @@ All notable changes to this project will be documented in this file. This change ### Added (post-v1.0.0-beta.3 CLI sync) - **Schema bump** — `.copilot-schema-version` advanced from `1.0.44-2` to - `1.0.46` (current `latest` on npm). Generated wire specs and coercions - regenerated. + `1.0.48` (the latest GA on npm; previously pinned at `1.0.48-1`). + Generated wire specs and coercions regenerated. +- **`:remote-session` session config option** — `create-session` and + `resume-session` accept an optional `:remote-session` key set to `:off`, + `:export`, or `:on`, enabling per-session Mission Control remote mode at + session-creation time without a separate `remote-enable` call. Forwarded to + the wire as `remoteSession`. Reuses the `::remote-session-mode` spec. + (upstream PR #1295, CLI 1.0.48) +- **`:copilot/session.custom_notification` event** — Skills (via the `Notify` + block) can emit arbitrary application-level events to the SDK. The event + exposes `:source`, `:name`, `:payload` (any JSON value), optional + `:subject` (map of keyword→string), and optional `:version` (positive + integer). Added to `sdk/event-types` and `sdk/session-events`; new idiom + spec `::session.custom_notification-data`. The `:subject` and `:payload` + fields contain source-defined identifiers and opaque JSON, so their keys + are preserved verbatim by the protocol normalizer (no kebab-casing) — + matching the existing escape hatch for `external_tool.requested` + arguments. (upstream PR #1292, CLI 1.0.48) - **Extension permission kinds** — `::permission-kind` accepts the new upstream values `:extension-management` and `:extension-permission-access`, emitted by the CLI for extension lifecycle and capability-access prompts. @@ -38,6 +54,34 @@ All notable changes to this project will be documented in this file. This change `{:requestId ..., :result {:handled bool, :stopProcessingQueue bool?}}`. Marked experimental, mirroring upstream's exposure of this only via the generated low-level RPC. (upstream PR #1263, CLI 1.0.45) +- **`:is-autopilot-continuation` on `user.message` events** — + `::user.message-data` now accepts the optional boolean flag emitted by + autopilot's continuation loop. `true` indicates the message was + auto-injected rather than typed by the user; used to distinguish + autopilot-driven turns in telemetry. Wire key: `isAutopilotContinuation` + → kebab-case key `:is-autopilot-continuation` (no `?` suffix — + camel-snake-kebab does not append `?` for booleans). (upstream PR #1286, + CLI 1.0.47) +- **`:api-endpoint` on `assistant.usage` events** — + `::assistant.usage-data` now accepts the optional API endpoint string + identifying which CAPI endpoint produced the model call. Known values: + `"/chat/completions"`, `"/v1/messages"`, `"/responses"`, `"ws:/responses"`. + Modeled as an open string spec for forward-compatibility; the regenerated + wire spec enforces the closed enum. (upstream PR #1286, CLI 1.0.47) +- **`:mode` on `session/remote-enable` (experimental)** — the + `session.remote.enable` RPC now accepts an optional `RemoteSessionMode` + parameter. `remote-enable` gained a 2-arity overload `(remote-enable session + opts)` where `opts` may contain `:mode` set to `:off`, `:export`, or `:on`. + `:off` disables remote, `:export` exports session events to Mission Control + without enabling remote steering, `:on` enables both. Zero-arg call is + unchanged. New idiom specs: `::remote-session-mode` and + `::remote-enable-opts`. (upstream PR #1288, CLI 1.0.48-1) +- **`:recurring` on `session.schedule_created` events** — + `::session.schedule_created-data` now accepts the optional boolean flag + indicating whether the schedule re-arms after each tick (`/every`) or fires + once (`/after`). Wire key: `recurring` → kebab-case key `:recurring` (no + `?` suffix — csk does not append `?` for booleans). (upstream PR #1288, + CLI 1.0.48-1) ### Tracked-but-not-ported (post-v1.0.0-beta.3 CLI sync) - **`session.tasks.sendMessage` (experimental)** — the upstream Tasks API @@ -53,6 +97,17 @@ All notable changes to this project will be documented in this file. This change - **`WorkspacesGetWorkspaceResult.session_sync_level` removal** — upstream dropped this field. The Clojure SDK never surfaced it; no change needed. (upstream PR #1239, CLI 1.0.44-3) +- **`session.commands.list` / `session.commands.invoke` RPCs** — upstream + added these slash-command discovery and invocation methods to the generated + RPC layer in CLI 1.0.47. The Node.js SDK's public `CopilotSession` does + NOT expose them as high-level methods (only via the low-level generated + RPC), so per the API-parity rule the Clojure SDK does not surface them + either. (upstream PR #1286, CLI 1.0.47) +- **`ModelBilling.tokenPrices`** — upstream added per-token pricing fields + (`inputPrice`, `outputPrice`, `cachePrice`, `batchSize`) to `Model.billing`. + The Clojure idiom spec for `::billing` is intentionally broad (open map), + so these payloads pass through `list-models` unchanged. No dedicated idiom + spec added yet. (upstream PR #1270, CLI 1.0.46) ## [1.0.0-beta.3.0] - 2026-05-12 ### Changed (release tooling) diff --git a/doc/reference/API.md b/doc/reference/API.md index ff02fb6..4ab475d 100644 --- a/doc/reference/API.md +++ b/doc/reference/API.md @@ -265,6 +265,7 @@ Create a client and session together, ensuring both are cleaned up on exit. | `:enable-config-discovery` | boolean | Auto-discover `.mcp.json`, `.vscode/mcp.json`, skills, etc. Instruction files always load regardless. (upstream PR #1044) | | `:model-capabilities` | map | Model capabilities override. DeepPartial of model capabilities, e.g. `{:model-supports {:supports-vision true}}`. (upstream PR #1029) | | `:include-sub-agent-streaming-events?` | boolean | Forward streaming events from sub-agents to the parent session's event stream. Defaults to `true` on the wire. (upstream PR #1108) | +| `:remote-session` | keyword | Per-session Mission Control mode: `:off`, `:export`, or `:on`. When omitted, the CLI applies its default. `:off` disables remote, `:export` exports session events to Mission Control without enabling remote steering, `:on` enables both. Forwarded as `remoteSession`. (upstream PR #1295, CLI 1.0.48) | #### `resume-session` @@ -1159,13 +1160,20 @@ and `:command`. Clients respond via `respond-to-queued-command!`: | Function | Description | |----------|-------------| -| `session/remote-enable` | Enable remote steerability for the session. Returns `{:url :remote-steerable }`. | +| `session/remote-enable` | Enable remote steerability for the session. Returns `{:url :remote-steerable }`. Optional 2-arity `opts` map accepts `:mode` set to `:off`, `:export`, or `:on` (upstream CLI 1.0.48-1). | | `session/remote-disable` | Disable remote steerability for the session. Returns `nil`. | ```clojure (session/remote-enable my-session) ;; => {:url "https://copilot-remote.test/abc" :remote-steerable true} +;; Optional per-session mode (upstream CLI 1.0.48-1): +;; - :off — disable remote +;; - :export — export session events to Mission Control without remote steering +;; - :on — export + enable remote steering +(session/remote-enable my-session {:mode :export}) +;; => {:remote-steerable false} + (session/remote-disable my-session) ;; => nil ``` @@ -1392,6 +1400,7 @@ Convert an unqualified event keyword to a namespace-qualified `:copilot/` keywor | `:copilot/session.mcp_server_status_changed` | MCP server status changed | | `:copilot/session.extensions_loaded` | Extensions loaded for the session | | `:copilot/session.custom_agents_updated` | Custom agents list updated | +| `:copilot/session.custom_notification` | Custom Skill notification (Notify block); ephemeral. Data: `{:source "" :name "" :payload :subject? { } :version? }` (upstream PR #1292, CLI 1.0.48) | | `:copilot/sampling.requested` | MCP sampling request initiated; ephemeral | | `:copilot/sampling.completed` | MCP sampling request completed; ephemeral | | `:copilot/session.remote_steerable_changed` | Session remote steering capability changed; data: `{:remote-steerable true/false}` | From 6ff9421f43b60d6b38283c9a1e3956cbebfc4d7c Mon Sep 17 00:00:00 2001 From: Karl Krukow Date: Fri, 15 May 2026 10:00:08 +0200 Subject: [PATCH 5/6] docs: correct schema bump prior version in CHANGELOG MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Copilot Code Review round 1: the CHANGELOG entry said the schema was 'previously pinned at 1.0.48-1' but main had 1.0.46 at the point this PR branched. Fix to reflect 1.0.46 → 1.0.48. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 19d2733..0cedde3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,8 +4,8 @@ All notable changes to this project will be documented in this file. This change ## [Unreleased] ### Added (post-v1.0.0-beta.3 CLI sync) -- **Schema bump** — `.copilot-schema-version` advanced from `1.0.44-2` to - `1.0.48` (the latest GA on npm; previously pinned at `1.0.48-1`). +- **Schema bump** — `.copilot-schema-version` advanced from `1.0.46` to + `1.0.48` (the latest GA on npm). Generated wire specs and coercions regenerated. - **`:remote-session` session config option** — `create-session` and `resume-session` accept an optional `:remote-session` key set to `:off`, From c3ed421392f2ff51052da3ece7b1a2ed54756c1f Mon Sep 17 00:00:00 2001 From: Karl Krukow Date: Fri, 15 May 2026 10:14:03 +0200 Subject: [PATCH 6/6] fix: address Copilot Code Review round 2 feedback MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three findings, all valid: 1. CHANGELOG already-fixed in 6ff9421 (round 1). 2. doc/reference/API.md said custom_notification fields :subject and :version were `:subject?`/`:version?`. The actual keys do not carry a `?` suffix (subject is a map, version is a positive int — neither is a boolean, and camel-snake-kebab doesn't append `?`). Fix the row to use the actual keys and call out that they are optional in prose. 3. protocol/normalize-incoming only preserved opaque event fields (subject/payload for custom_notification; arguments for external_tool.requested) on live session.event notifications. Historical events returned by session.getMessages flow through the same function as response messages (no :method, id present) and fell into the :else branch, so their opaque keys were kebab-cased. Refactor the preservation logic into preserve-event-opaque-fields and apply it to both live notifications and response :result :events collections, so live and historical events have the same shape. New test covers both new (custom_notification) and existing (external_tool.requested) event preservation through the response path. 4. session/remote-enable 2-arity did not validate the :mode opt before sending — outside spec instrumentation, {:mode :bogus} would forward as wire data. Validate opts against ::remote-enable-opts at the API boundary and throw on bad shape, matching the synchronous rejection create-session does for :remote-session. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- doc/reference/API.md | 2 +- src/github/copilot_sdk/protocol.clj | 65 +++++++++++++------- src/github/copilot_sdk/session.clj | 4 ++ test/github/copilot_sdk/integration_test.clj | 35 ++++++++++- 4 files changed, 83 insertions(+), 23 deletions(-) diff --git a/doc/reference/API.md b/doc/reference/API.md index 4ab475d..fafa972 100644 --- a/doc/reference/API.md +++ b/doc/reference/API.md @@ -1400,7 +1400,7 @@ Convert an unqualified event keyword to a namespace-qualified `:copilot/` keywor | `:copilot/session.mcp_server_status_changed` | MCP server status changed | | `:copilot/session.extensions_loaded` | Extensions loaded for the session | | `:copilot/session.custom_agents_updated` | Custom agents list updated | -| `:copilot/session.custom_notification` | Custom Skill notification (Notify block); ephemeral. Data: `{:source "" :name "" :payload :subject? { } :version? }` (upstream PR #1292, CLI 1.0.48) | +| `:copilot/session.custom_notification` | Custom Skill notification (Notify block); ephemeral. Data: `{:source "" :name "" :payload :subject { } :version }` (`:subject` and `:version` are optional; `:subject` keys are preserved verbatim — see PR #1292, CLI 1.0.48) | | `:copilot/sampling.requested` | MCP sampling request initiated; ephemeral | | `:copilot/sampling.completed` | MCP sampling request completed; ephemeral | | `:copilot/session.remote_steerable_changed` | Session remote steering capability changed; data: `{:remote-steerable true/false}` | diff --git a/src/github/copilot_sdk/protocol.clj b/src/github/copilot_sdk/protocol.clj index 623acd1..4795154 100644 --- a/src/github/copilot_sdk/protocol.clj +++ b/src/github/copilot_sdk/protocol.clj @@ -171,42 +171,65 @@ :error {:code -32603 :message (str "Internal error: " (ex-message e))}})))))) +(defn- preserve-event-opaque-fields + "Given a raw wire event (pre-`wire->clj`) and a converted event, restore + source-defined / opaque fields verbatim onto the converted shape so + kebab-casing doesn't mangle user-supplied keys. Applies the per-event-type + rules used by `normalize-incoming` for live notifications, so live and + historical events share the same shape." + [raw-event converted-event] + (case (:type raw-event) + "external_tool.requested" + (cond-> converted-event + (contains? (:data raw-event) :arguments) + (assoc-in [:data :arguments] (get-in raw-event [:data :arguments]))) + + "session.custom_notification" + (cond-> converted-event + (contains? (:data raw-event) :subject) + (assoc-in [:data :subject] (get-in raw-event [:data :subject])) + (contains? (:data raw-event) :payload) + (assoc-in [:data :payload] (get-in raw-event [:data :payload]))) + + converted-event)) + (defn- normalize-incoming "Convert wire-format keys to Clojure keys, preserving opaque user data. For v2 tool.call RPC and v3 external_tool.requested broadcast events, tool arguments are kept in their original wire format so user-defined tool handlers receive the keys the server sent. For v3 session.custom_notification events, the source-defined `:subject` and - opaque `:payload` are also preserved verbatim." + opaque `:payload` are also preserved verbatim. The same preservation + applies to historical events returned in `session.getMessages` responses + so live and historical event shapes agree." [msg] (let [method (:method msg) params (:params msg) - converted (util/wire->clj msg)] + converted (util/wire->clj msg) + raw-events (get-in msg [:result :events])] (cond ;; v2: preserve raw arguments for tool.call RPC (and (= "tool.call" method) (map? params) (contains? params :arguments)) (assoc-in converted [:params :arguments] (:arguments params)) - ;; v3: preserve raw arguments in external_tool.requested broadcast events - (and (= "session.event" method) - (= "external_tool.requested" (get-in params [:event :type])) - (contains? (get-in params [:event :data]) :arguments)) - (assoc-in converted [:params :event :data :arguments] - (get-in params [:event :data :arguments])) - - ;; v3: preserve raw subject/payload keys in session.custom_notification - ;; events. Both fields contain source-defined identifiers/opaque JSON - ;; (PR #1292); kebab-casing would mangle keys like "GitHub-Login" and - ;; collapse "actor" / "Actor" into the same keyword. + ;; v3: preserve raw arguments / subject / payload in broadcast events (and (= "session.event" method) - (= "session.custom_notification" (get-in params [:event :type]))) - (cond-> converted - (contains? (get-in params [:event :data]) :subject) - (assoc-in [:params :event :data :subject] - (get-in params [:event :data :subject])) - (contains? (get-in params [:event :data]) :payload) - (assoc-in [:params :event :data :payload] - (get-in params [:event :data :payload]))) + (map? (:event params))) + (assoc-in converted [:params :event] + (preserve-event-opaque-fields (:event params) + (get-in converted [:params :event]))) + + ;; Response carrying an event collection (e.g. session.getMessages). + ;; Preserve opaque fields per-event so historical custom_notification + ;; events keep their subject/payload keys, and historical + ;; external_tool.requested events keep their arguments. Without this, + ;; live and historical events would have divergent key shapes. + (and (:id msg) (not method) (sequential? raw-events)) + (assoc-in converted [:result :events] + (mapv (fn [raw conv] + (preserve-event-opaque-fields raw conv)) + raw-events + (get-in converted [:result :events]))) :else converted))) diff --git a/src/github/copilot_sdk/session.clj b/src/github/copilot_sdk/session.clj index 3f66fcd..0887e74 100644 --- a/src/github/copilot_sdk/session.clj +++ b/src/github/copilot_sdk/session.clj @@ -1794,6 +1794,10 @@ guarantees may change." ([session] (remote-enable session nil)) ([session opts] + (when (and opts (not (s/valid? ::specs/remote-enable-opts opts))) + (throw (ex-info "Invalid remote-enable opts" + {:opts opts + :explain (s/explain-data ::specs/remote-enable-opts opts)}))) (let [{:keys [session-id client]} session conn (connection-io client) base {:session-id session-id} diff --git a/test/github/copilot_sdk/integration_test.clj b/test/github/copilot_sdk/integration_test.clj index e6c4c3f..3461004 100644 --- a/test/github/copilot_sdk/integration_test.clj +++ b/test/github/copilot_sdk/integration_test.clj @@ -919,7 +919,40 @@ (is (= 42 (get-in data [:payload :nested :userId])) "payload nested keys must not be kebab-cased") (is (= "Foo" (get-in data [:payload :firstName])) - "payload top-level keys must not be kebab-cased")))) + "payload top-level keys must not be kebab-cased"))) + (testing "subject and payload keys are preserved in historical events from session.getMessages responses" + ;; Response messages (id, no :method) carrying :result :events collections + ;; must apply the same preservation rules per-event, so live and + ;; historical custom_notification events have the same shape. + (let [normalize @#'github.copilot-sdk.protocol/normalize-incoming + raw-response {:jsonrpc "2.0" + :id 42 + :result {:events [{:type "session.start" + :data {:sessionId "s1"}} + {:type "session.custom_notification" + :data {:source "ext" + :name "x" + :subject {:GitHub-Login "octocat"} + :payload {:nestedKey {:userId 7}}}} + {:type "external_tool.requested" + :data {:id "t1" + :name "do" + :arguments {:OriginalKey "v"}}}]}} + normalized (normalize raw-response) + events (get-in normalized [:result :events]) + custom (nth events 1) + ext-tool (nth events 2)] + (is (= "octocat" (get-in custom [:data :subject :GitHub-Login])) + "historical custom_notification subject keys must be preserved") + (is (= 7 (get-in custom [:data :payload :nestedKey :userId])) + "historical custom_notification payload keys must be preserved") + (is (= {:OriginalKey "v"} (get-in ext-tool [:data :arguments])) + "historical external_tool.requested arguments must be preserved"))) + (testing "remote-enable opts are validated synchronously when provided" + (let [session {:session-id "s" :client {}}] + (is (thrown? Exception (github.copilot-sdk.session/remote-enable session {:mode :bogus}))) + (is (thrown? Exception (github.copilot-sdk.session/remote-enable session {:mode "on"})) + "string :mode value is rejected — the spec requires a keyword from #{:off :export :on}")))) (deftest test-custom-agent-info-tools-nilable (testing "::custom-agent-info accepts :tools nil (upstream schema 1.0.41-1: tools: string[] | null)"