@github/copilot-sdk 1.0.17-preview.1 → 1.0.17-preview.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -270,6 +270,19 @@ new CopilotClient(options?: CopilotClientOptions)
270
270
  - `telemetry?: TelemetryConfig` - OpenTelemetry configuration for the runtime process. Providing this object enables telemetry — no separate flag needed. See [Telemetry](#telemetry) below.
271
271
  - `onGetTraceContext?: TraceContextProvider` - Advanced: callback for linking your application's own OpenTelemetry spans into the same distributed trace as the runtime's spans. Not needed for normal telemetry collection. See [Telemetry](#telemetry) below.
272
272
  - `sessionFs?: SessionFsConfig` - Custom session filesystem provider.
273
+
274
+ For a custom provider to serve images to the `view` tool, declare
275
+ `sessionFs.capabilities.binary: true` and implement
276
+ `readFileBytes(path): Promise<Uint8Array>` and
277
+ `writeFileBytes(path, content: Uint8Array, mode?: number): Promise<void>` on each session's provider. The
278
+ adapter transmits exact bytes as standard base64; text-only providers still
279
+ support text operations, but image reads fail rather than falling back to the
280
+ runtime machine's filesystem. A missing binary implementation is rejected
281
+ when a session is created.
282
+
283
+ Binary reads and writes are limited to 50,330,880 raw bytes (approximately 48 MiB);
284
+ larger results return a filesystem error before encoding or decoding.
285
+
273
286
  - `sessionIdleTimeoutSeconds?: number` - Server-wide idle timeout for sessions in seconds. Ignored when connecting via `RuntimeConnection.forUri`.
274
287
  - `enableRemoteSessions?: boolean` - Enable Mission Control remote session support. Ignored when connecting via `RuntimeConnection.forUri`.
275
288
 
@@ -356,6 +369,7 @@ Create a new conversation session.
356
369
  - `askUserVariant?: "legacy" | "elicitation"` - Selects the model-facing `ask_user` tool shape when creating or cold-resuming a session. Defaults to `"legacy"`; use `"elicitation"` with `onElicitationRequest`.
357
370
  - `onElicitationRequest?: ElicitationHandler` - Handler for elicitation requests dispatched by the server. Enables this client to present form-based UI dialogs on behalf of the agent or other session participants. See [Elicitation Requests](#elicitation-requests) section.
358
371
  - `hooks?: SessionHooks` - Hook handlers for session lifecycle events. See [Session Hooks](#session-hooks) section.
372
+ - `skillProvider?: SkillProvider` - **Experimental.** Serves skills from host storage instead of skill directories. Not persisted; pass it again on resume. See [Skill providers](#skill-providers-experimental).
359
373
 
360
374
  ```typescript
361
375
  const session = await client.createSession({
@@ -644,6 +658,24 @@ if (result.status === "pending") {
644
658
 
645
659
  See [Auto tier persistence](../docs/features/session-persistence.md#auto-tier-persistence) for the full lifecycle rules.
646
660
 
661
+ ##### `setTools(tools: Tool[]): Promise<void>`
662
+
663
+ Replace the tools this client supplies to the session, together with the handlers that serve them (experimental). `tools` takes the same definitions as `createSession` and becomes this client's complete tool set; pass `[]` to remove all of this client's tools. Built-in, MCP, and plugin tools, and tools other connected clients supply, are unaffected.
664
+
665
+ Once the runtime accepts the replacement, every tool call this session dispatches uses the new handlers; calls already running finish on their original handlers. If the runtime rejects it, the promise rejects and the previous tools and handlers stay in place. Concurrent calls are applied one at a time, in call order.
666
+
667
+ ```typescript
668
+ await session.setTools([
669
+ defineTool("search_issues", {
670
+ description: "Search the issues shown on the current page",
671
+ parameters: z.object({ query: z.string() }),
672
+ handler: async ({ query }) => searchIssues(query),
673
+ }),
674
+ ]);
675
+ ```
676
+
677
+ The agent sees the new tools from its next model request, which can fall within a turn in progress, so a model request already in flight can still call a tool you removed. See [Changing tools during a session](../docs/features/changing-tools.md) for the details.
678
+
647
679
  ##### `abort(): Promise<void>`
648
680
 
649
681
  Abort the currently processing message in this session.
@@ -839,6 +871,22 @@ defineTool("edit_file", {
839
871
  });
840
872
  ```
841
873
 
874
+ An `apply_patch` override can declare a string schema. The model sees a required
875
+ `input` property, but the runtime restores the declared scalar shape before
876
+ dispatching to any SDK. Both a Zod-inferred Node handler and
877
+ `invocation.arguments` receive the patch text as a string:
878
+
879
+ ```ts
880
+ defineTool("apply_patch", {
881
+ parameters: z.string(),
882
+ overridesBuiltInTool: true,
883
+ handler: (patch) => patch.trim(),
884
+ });
885
+ ```
886
+
887
+ String-schema `apply_patch` overrides cannot contain JSON Schema references;
888
+ use an object schema if references are needed.
889
+
842
890
  #### Skipping Permission Prompts
843
891
 
844
892
  Set `skipPermission: true` on a tool definition to allow it to execute without triggering a permission prompt:
@@ -988,6 +1036,8 @@ Available section IDs: `preamble`, `identity`, `tone`, `tool_efficiency`, `envir
988
1036
 
989
1037
  `identity` and `tool_instructions` are section _groups_ that target a collection of related sub-sections as a unit. Use `preamble` to target just the identity preamble without affecting its sibling sub-sections.
990
1038
 
1039
+ `last_instructions` includes configured subagent-model guidance when the `task` tool is available. Removing or replacing this section also removes that guidance; a transform callback receives the complete section, including the guidance, and its returned content is authoritative. Append, prepend, and preserve retain their usual section semantics. These overrides change prompt prose only, not configured subagent models, tool availability, or runtime dispatch policy. `runtime_instructions` is a separate section: removing it does not remove `last_instructions`.
1040
+
991
1041
  Each section override supports five actions:
992
1042
 
993
1043
  - **`replace`** — Replace the section content entirely
@@ -996,6 +1046,8 @@ Each section override supports five actions:
996
1046
  - **`prepend`** — Add content before the existing section
997
1047
  - **`preserve`** — No-op that opts an individually-addressable section out of a group-level `remove`
998
1048
 
1049
+ An `action` can also be a callback that receives the current section content and returns the replacement content, synchronously or asynchronously.
1050
+
999
1051
  Unknown section IDs are handled gracefully: content from `replace`/`append`/`prepend` overrides is appended to additional instructions, and `remove` overrides are silently ignored.
1000
1052
 
1001
1053
  #### Replace Mode
@@ -1069,6 +1121,46 @@ const session = await client.createSession({
1069
1121
  });
1070
1122
  ```
1071
1123
 
1124
+ ### Skill providers (experimental)
1125
+
1126
+ A skill provider serves skills from your own storage, such as a database, instead of `SKILL.md`
1127
+ files on disk. Provider skills join the session's skill catalog and load on demand through the
1128
+ `skill` tool, just like file-based skills.
1129
+
1130
+ ```typescript
1131
+ import type { SkillProvider } from "@github/copilot-sdk";
1132
+
1133
+ const skillProvider: SkillProvider = {
1134
+ listSkills: async () => [
1135
+ { name: "release-notes", description: "Writes release notes in the team's format." },
1136
+ ],
1137
+ readSkill: async (name, { signal }) => (await db.findSkill(name, { signal }))?.markdown ?? null,
1138
+ };
1139
+
1140
+ const session = await client.createSession({
1141
+ onPermissionRequest: approveAll,
1142
+ skillProvider,
1143
+ });
1144
+ ```
1145
+
1146
+ - `listSkills()` returns the catalog metadata. `readSkill(name)` returns the skill's markdown, or
1147
+ `null`/`undefined` if the skill no longer exists. The markdown may omit YAML frontmatter; when
1148
+ frontmatter is present, its fields must agree with the listed metadata, and `allowed-tools` is
1149
+ read only from frontmatter.
1150
+ - Each call receives `{ signal }`, an `AbortSignal` that fires when the runtime cancels the call,
1151
+ for example after its 30-second limit or when the session disconnects. It doesn't fire when the
1152
+ connection closes or the client is force-stopped; a running call then continues until it returns.
1153
+ - The provider is never persisted. Pass it again to `resumeSession`; resuming without it unbinds
1154
+ the provider.
1155
+ - A provider enables skills unless you set `enableSkills: false`, which keeps it bound but unused.
1156
+ In `mode: "empty"`, skills stay disabled until you set `enableSkills: true`.
1157
+ - Errors thrown by the provider are reported to the model as a generic load failure; their
1158
+ messages are not forwarded.
1159
+ - The runtime may call the provider concurrently, so both methods must be safe for concurrent use.
1160
+ - Skill providers are not supported for cloud sessions.
1161
+
1162
+ See [Custom skills](../docs/features/skills.md#skill-providers-experimental) for limits and details.
1163
+
1072
1164
  ### Multiple Sessions
1073
1165
 
1074
1166
  ```typescript
@@ -1443,6 +1535,20 @@ const session = await client.createSession({
1443
1535
  };
1444
1536
  }
1445
1537
  },
1538
+
1539
+ // Called before a sub-agent's first turn (the input identifies the parent session)
1540
+ onSubagentStart: (input, invocation) => {
1541
+ console.log(`Starting ${input.agentDisplayName ?? input.agentName}`);
1542
+ return { additionalContext: "Check the requested file before reporting back." };
1543
+ },
1544
+
1545
+ // Called when a sub-agent completes a turn
1546
+ onSubagentStop: (input, invocation) => {
1547
+ console.log(`${input.agentName} replied: ${input.response}`);
1548
+ // Return { decision: "block", reason: "Continue checking the file." }
1549
+ // to request another child turn instead.
1550
+ return { modifiedResponse: `Reviewed: ${input.response}` };
1551
+ },
1446
1552
  },
1447
1553
  });
1448
1554
  ```
@@ -1457,6 +1563,8 @@ const session = await client.createSession({
1457
1563
  - `onSessionEnd` - Cleanup or logging when session ends.
1458
1564
  - `onErrorOccurred` - Handle errors with retry/skip/abort strategies.
1459
1565
  - `onAgentStop` - Observe natural top-level agent completion. Return `{ decision: "block", reason }` to request another turn; use `stopHookActive` to avoid repeated blocks.
1566
+ - `onSubagentStart` - Observe a sub-agent before its first turn and prepend `additionalContext` to the child's prompt.
1567
+ - `onSubagentStop` - Observe a sub-agent's final response. Return `{ decision: "block", reason }` to request another child turn, or `{ modifiedResponse }` to replace the response reported to the parent.
1460
1568
 
1461
1569
  ## Error Handling
1462
1570
 
package/dist/canvas.d.ts CHANGED
@@ -32,7 +32,12 @@ export interface CanvasAction {
32
32
  description?: string;
33
33
  /** Optional JSON Schema for the action's `input` payload. */
34
34
  inputSchema?: CanvasJsonSchema;
35
- /** Required per-action dispatch handler. */
35
+ /**
36
+ * Required per-action dispatch handler. The returned value becomes the
37
+ * `invoke_canvas_action` tool result. Return a `ToolResultObject`
38
+ * (with `binaryResultsForLlm`) to send text and images to the model, as a
39
+ * tool handler would; any other value is rendered to the model as JSON text.
40
+ */
36
41
  handler: (ctx: CanvasProviderInvokeActionRequest) => Promise<unknown> | unknown;
37
42
  }
38
43
  /**
@@ -22,7 +22,7 @@ __export(cliVersion_exports, {
22
22
  COPILOT_CLI_VERSION: () => COPILOT_CLI_VERSION
23
23
  });
24
24
  module.exports = __toCommonJS(cliVersion_exports);
25
- const COPILOT_CLI_VERSION = "1.0.92-1";
25
+ const COPILOT_CLI_VERSION = "1.0.93-4";
26
26
  const COPILOT_CLI_USE_NPM_PACKAGE = false;
27
27
  // Annotate the CommonJS export names for ESM import in node:
28
28
  0 && (module.exports = {
@@ -52,6 +52,7 @@ var import_toolSet = require("./toolSet.js");
52
52
  var import_types = require("./types.js");
53
53
  const MIN_PROTOCOL_VERSION = 3;
54
54
  const RUNTIME_SHUTDOWN_TIMEOUT_MS = 1e4;
55
+ const CLOUD_SESSION_CLEANUP_TIMEOUT_MS = 1e4;
55
56
  function createMessageConnection(reader, writer) {
56
57
  let dispatch;
57
58
  let finishDrain;
@@ -580,6 +581,11 @@ class CopilotClient {
580
581
  "SessionFsConfig declares capabilities.sqlite but the provider does not implement sqlite."
581
582
  );
582
583
  }
584
+ if (this.sessionFsConfig.capabilities?.binary && (!provider.readFileBytes || !provider.writeFileBytes)) {
585
+ throw new Error(
586
+ "SessionFsConfig declares capabilities.binary but the provider does not implement readFileBytes and writeFileBytes."
587
+ );
588
+ }
583
589
  session.clientSessionApis.sessionFs = (0, import_sessionFsProvider.createSessionFsAdapter)(provider);
584
590
  }
585
591
  setupClientGlobalHandlers() {
@@ -1198,6 +1204,9 @@ class CopilotClient {
1198
1204
  if (config.gitHubToken !== void 0 && config.gitHubTokenProvider !== void 0) {
1199
1205
  throw new Error("gitHubToken and gitHubTokenProvider are mutually exclusive");
1200
1206
  }
1207
+ if (config.cloud != null && config.skillProvider) {
1208
+ throw new Error("Skill providers are not supported for cloud sessions.");
1209
+ }
1201
1210
  if (!this.connection) {
1202
1211
  await this.start();
1203
1212
  }
@@ -1257,18 +1266,22 @@ class CopilotClient {
1257
1266
  if (config.hooks) {
1258
1267
  s.registerHooks(config.hooks);
1259
1268
  }
1269
+ if (config.skillProvider) {
1270
+ s.registerSkillProvider(config.skillProvider);
1271
+ }
1260
1272
  if (transformCallbacks) {
1261
1273
  s.registerTransformCallbacks(transformCallbacks);
1262
1274
  }
1263
1275
  if (config.onEvent) {
1264
1276
  s.on(config.onEvent);
1265
1277
  }
1266
- this.sessions.set(sessionId, s);
1267
1278
  this.setupSessionFs(s, config);
1279
+ this.sessions.set(sessionId, s);
1268
1280
  return s;
1269
1281
  };
1270
1282
  let session;
1271
1283
  let registeredId;
1284
+ let uninitializedCloudSessionId;
1272
1285
  if (localSessionId !== void 0) {
1273
1286
  try {
1274
1287
  session = initializeSession(localSessionId);
@@ -1362,6 +1375,7 @@ class CopilotClient {
1362
1375
  enableSessionStore: config.enableSessionStore,
1363
1376
  enableSkills: config.enableSkills,
1364
1377
  skillDirectories: config.skillDirectories,
1378
+ ...config.skillProvider ? { hasSkillProvider: true } : {},
1365
1379
  pluginDirectories: config.pluginDirectories,
1366
1380
  instructionDirectories: config.instructionDirectories,
1367
1381
  disabledSkills: config.disabledSkills,
@@ -1391,7 +1405,9 @@ class CopilotClient {
1391
1405
  );
1392
1406
  }
1393
1407
  if (session === void 0) {
1408
+ uninitializedCloudSessionId = returnedSessionId;
1394
1409
  session = initializeSession(returnedSessionId);
1410
+ uninitializedCloudSessionId = void 0;
1395
1411
  registeredId = returnedSessionId;
1396
1412
  }
1397
1413
  this.assignGitHubTokenProvider(gitHubTokenProviderRegistrationId, returnedSessionId);
@@ -1413,6 +1429,20 @@ class CopilotClient {
1413
1429
  if (gitHubTokenProviderRegistrationId !== void 0) {
1414
1430
  this.githubTokenProviders.delete(gitHubTokenProviderRegistrationId);
1415
1431
  }
1432
+ if (uninitializedCloudSessionId !== void 0) {
1433
+ try {
1434
+ await withTimeout(
1435
+ this.deleteSession(uninitializedCloudSessionId),
1436
+ CLOUD_SESSION_CLEANUP_TIMEOUT_MS,
1437
+ `session.delete timed out after ${CLOUD_SESSION_CLEANUP_TIMEOUT_MS}ms`
1438
+ );
1439
+ } catch (cleanupError) {
1440
+ throw new AggregateError(
1441
+ [e, cleanupError],
1442
+ "Failed to initialize and delete cloud session"
1443
+ );
1444
+ }
1445
+ }
1416
1446
  throw e;
1417
1447
  }
1418
1448
  for (const entry of this.hostHandoffs.values()) {
@@ -1499,6 +1529,9 @@ class CopilotClient {
1499
1529
  if (config.hooks) {
1500
1530
  session.registerHooks(config.hooks);
1501
1531
  }
1532
+ if (config.skillProvider) {
1533
+ session.registerSkillProvider(config.skillProvider);
1534
+ }
1502
1535
  const modeDefaults = this.configDefaultsForMode();
1503
1536
  config = { ...modeDefaults, ...config };
1504
1537
  config.customAgentsLocalOnly ??= modeDefaults.customAgentsLocalOnly;
@@ -1512,9 +1545,10 @@ class CopilotClient {
1512
1545
  if (config.onEvent) {
1513
1546
  session.on(config.onEvent);
1514
1547
  }
1515
- this.sessions.set(sessionId, session);
1516
- this.setupSessionFs(session, config);
1517
1548
  const toolFilterOptions = this.resolveToolFilterOptions(config);
1549
+ this.setupSessionFs(session, config);
1550
+ const replacedSession = this.sessions.get(sessionId);
1551
+ this.sessions.set(sessionId, session);
1518
1552
  const gitHubTokenProviderRegistrationId = this.registerGitHubTokenProvider(
1519
1553
  config.gitHubTokenProvider,
1520
1554
  sessionId
@@ -1607,6 +1641,7 @@ class CopilotClient {
1607
1641
  defaultAgent: config.defaultAgent,
1608
1642
  agent: config.agent,
1609
1643
  skillDirectories: config.skillDirectories,
1644
+ ...config.skillProvider ? { hasSkillProvider: true } : {},
1610
1645
  pluginDirectories: config.pluginDirectories,
1611
1646
  instructionDirectories: config.instructionDirectories,
1612
1647
  disabledSkills: config.disabledSkills,
@@ -1651,7 +1686,13 @@ class CopilotClient {
1651
1686
  this.commitGitHubTokenProvider(sessionId, gitHubTokenProviderRegistrationId);
1652
1687
  } catch (e) {
1653
1688
  session._markDisconnected();
1654
- this.sessions.delete(sessionId);
1689
+ if (this.sessions.get(sessionId) === session) {
1690
+ if (replacedSession) {
1691
+ this.sessions.set(sessionId, replacedSession);
1692
+ } else {
1693
+ this.sessions.delete(sessionId);
1694
+ }
1695
+ }
1655
1696
  if (gitHubTokenProviderRegistrationId !== void 0) {
1656
1697
  this.githubTokenProviders.delete(gitHubTokenProviderRegistrationId);
1657
1698
  }
@@ -2627,6 +2668,20 @@ stderr: ${stderrOutput}` : ""}`
2627
2668
  return await this.handleHooksInvoke(params);
2628
2669
  }
2629
2670
  );
2671
+ this.connection.onRequest(
2672
+ "skillProvider.list",
2673
+ async (params, token) => await this.resolveSkillProviderSession(params)._handleSkillProviderList(token)
2674
+ );
2675
+ this.connection.onRequest(
2676
+ "skillProvider.read",
2677
+ async (params, token) => {
2678
+ const session = this.resolveSkillProviderSession(params);
2679
+ if (typeof params.name !== "string") {
2680
+ throw new Error("Invalid skillProvider.read payload");
2681
+ }
2682
+ return await session._handleSkillProviderRead(params.name, token);
2683
+ }
2684
+ );
2630
2685
  const connection = this.connection;
2631
2686
  const messageWriter = this.messageWriter;
2632
2687
  const cliProcess = this.isExternalServer ? null : this.cliProcess;
@@ -2783,6 +2838,16 @@ stderr: ${stderrOutput}` : ""}`
2783
2838
  });
2784
2839
  return { response };
2785
2840
  }
2841
+ resolveSkillProviderSession(params) {
2842
+ if (!params || typeof params.sessionId !== "string") {
2843
+ throw new Error("Invalid skillProvider payload");
2844
+ }
2845
+ const session = this.sessions.get(params.sessionId);
2846
+ if (!session) {
2847
+ throw new Error(`Session not found: ${params.sessionId}`);
2848
+ }
2849
+ return session;
2850
+ }
2786
2851
  async handleHooksInvoke(params) {
2787
2852
  if (!params || typeof params.sessionId !== "string" || typeof params.hookType !== "string") {
2788
2853
  throw new Error("Invalid hooks invoke payload");
@@ -41,11 +41,13 @@ async function joinSession(config = {}) {
41
41
  const client = new import_client.CopilotClient({ _internalConnection: { kind: "parent-process" } });
42
42
  const {
43
43
  extensionSdkPath: _stripped,
44
+ skillProvider: _strippedSkillProvider,
44
45
  workflows,
45
46
  requestedEnvironmentVariables,
46
47
  ...rest
47
48
  } = config;
48
49
  void _stripped;
50
+ void _strippedSkillProvider;
49
51
  return client.resumeSessionForExtension(
50
52
  sessionId,
51
53
  {