@github/copilot-sdk 0.1.33-unstable.1 → 0.2.1-preview.0

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
@@ -26,15 +26,16 @@ npm start
26
26
  ## Quick Start
27
27
 
28
28
  ```typescript
29
- import { CopilotClient } from "@github/copilot-sdk";
29
+ import { CopilotClient, approveAll } from "@github/copilot-sdk";
30
30
 
31
31
  // Create and start client
32
32
  const client = new CopilotClient();
33
33
  await client.start();
34
34
 
35
- // Create a session
35
+ // Create a session (onPermissionRequest is required)
36
36
  const session = await client.createSession({
37
37
  model: "gpt-5",
38
+ onPermissionRequest: approveAll,
38
39
  });
39
40
 
40
41
  // Wait for response using typed event handlers
@@ -59,7 +60,7 @@ await client.stop();
59
60
  Sessions also support `Symbol.asyncDispose` for use with [`await using`](https://github.com/tc39/proposal-explicit-resource-management) (TypeScript 5.2+/Node.js 18.0+):
60
61
 
61
62
  ```typescript
62
- await using session = await client.createSession({ model: "gpt-5" });
63
+ await using session = await client.createSession({ model: "gpt-5", onPermissionRequest: approveAll });
63
64
  // session is automatically disconnected when leaving scope
64
65
  ```
65
66
 
@@ -82,9 +83,10 @@ new CopilotClient(options?: CopilotClientOptions)
82
83
  - `useStdio?: boolean` - Use stdio transport instead of TCP (default: true)
83
84
  - `logLevel?: string` - Log level (default: "info")
84
85
  - `autoStart?: boolean` - Auto-start server (default: true)
85
- - `autoRestart?: boolean` - Auto-restart on crash (default: true)
86
86
  - `githubToken?: string` - GitHub token for authentication. When provided, takes priority over other auth methods.
87
87
  - `useLoggedInUser?: boolean` - Whether to use logged-in user for authentication (default: true, but false when `githubToken` is provided). Cannot be used with `cliUrl`.
88
+ - `telemetry?: TelemetryConfig` - OpenTelemetry configuration for the CLI process. Providing this object enables telemetry — no separate flag needed. See [Telemetry](#telemetry) below.
89
+ - `onGetTraceContext?: TraceContextProvider` - Advanced: callback for linking your application's own OpenTelemetry spans into the same distributed trace as the CLI's spans. Not needed for normal telemetry collection. See [Telemetry](#telemetry) below.
88
90
 
89
91
  #### Methods
90
92
 
@@ -113,6 +115,7 @@ Create a new conversation session.
113
115
  - `systemMessage?: SystemMessageConfig` - System message customization (see below)
114
116
  - `infiniteSessions?: InfiniteSessionConfig` - Configure automatic context compaction (see below)
115
117
  - `provider?: ProviderConfig` - Custom API provider configuration (BYOK - Bring Your Own Key). See [Custom Providers](#custom-providers) section.
118
+ - `onPermissionRequest: PermissionHandler` - **Required.** Handler called before each tool execution to approve or deny it. Use `approveAll` to allow everything, or provide a custom function for fine-grained control. See [Permission Handling](#permission-handling) section.
116
119
  - `onUserInputRequest?: UserInputHandler` - Handler for user input requests from the agent. Enables the `ask_user` tool. See [User Input Requests](#user-input-requests) section.
117
120
  - `hooks?: SessionHooks` - Hook handlers for session lifecycle events. See [Session Hooks](#session-hooks) section.
118
121
 
@@ -276,6 +279,20 @@ Get all events/messages from this session.
276
279
 
277
280
  Disconnect the session and free resources. Session data on disk is preserved for later resumption.
278
281
 
282
+ ##### `capabilities: SessionCapabilities`
283
+
284
+ Host capabilities reported when the session was created or resumed. Use this to check feature support before calling capability-gated APIs.
285
+
286
+ ```typescript
287
+ if (session.capabilities.ui?.elicitation) {
288
+ const ok = await session.ui.confirm("Deploy?");
289
+ }
290
+ ```
291
+
292
+ ##### `ui: SessionUiApi`
293
+
294
+ Interactive UI methods for showing dialogs to the user. Only available when the CLI host supports elicitation (`session.capabilities.ui?.elicitation === true`). See [UI Elicitation](#ui-elicitation) for full details.
295
+
279
296
  ##### `destroy(): Promise<void>` *(deprecated)*
280
297
 
281
298
  Deprecated — use `disconnect()` instead.
@@ -291,15 +308,18 @@ Sessions emit various events during processing:
291
308
  - `assistant.message_delta` - Streaming response chunk
292
309
  - `tool.execution_start` - Tool execution started
293
310
  - `tool.execution_complete` - Tool execution completed
311
+ - `command.execute` - Command dispatch request (handled internally by the SDK)
312
+ - `commands.changed` - Command registration changed
294
313
  - And more...
295
314
 
296
315
  See `SessionEvent` type in the source for full details.
297
316
 
298
317
  ## Image Support
299
318
 
300
- The SDK supports image attachments via the `attachments` parameter. You can attach images by providing their file path:
319
+ The SDK supports image attachments via the `attachments` parameter. You can attach images by providing their file path, or by passing base64-encoded data directly using a blob attachment:
301
320
 
302
321
  ```typescript
322
+ // File attachment — runtime reads from disk
303
323
  await session.send({
304
324
  prompt: "What's in this image?",
305
325
  attachments: [
@@ -309,6 +329,18 @@ await session.send({
309
329
  },
310
330
  ],
311
331
  });
332
+
333
+ // Blob attachment — provide base64 data directly
334
+ await session.send({
335
+ prompt: "What's in this image?",
336
+ attachments: [
337
+ {
338
+ type: "blob",
339
+ data: base64ImageData,
340
+ mimeType: "image/png",
341
+ },
342
+ ],
343
+ });
312
344
  ```
313
345
 
314
346
  Supported image formats include JPG, PNG, GIF, and other common image types. The agent's `view` tool can also read images directly from the filesystem, so you can also ask questions like:
@@ -426,6 +458,85 @@ defineTool("edit_file", {
426
458
  })
427
459
  ```
428
460
 
461
+ #### Skipping Permission Prompts
462
+
463
+ Set `skipPermission: true` on a tool definition to allow it to execute without triggering a permission prompt:
464
+
465
+ ```ts
466
+ defineTool("safe_lookup", {
467
+ description: "A read-only lookup that needs no confirmation",
468
+ parameters: z.object({ id: z.string() }),
469
+ skipPermission: true,
470
+ handler: async ({ id }) => { /* your logic */ },
471
+ })
472
+ ```
473
+
474
+ ### Commands
475
+
476
+ Register slash commands so that users of the CLI's TUI can invoke custom actions via `/commandName`. Each command has a `name`, optional `description`, and a `handler` called when the user executes it.
477
+
478
+ ```ts
479
+ const session = await client.createSession({
480
+ onPermissionRequest: approveAll,
481
+ commands: [
482
+ {
483
+ name: "deploy",
484
+ description: "Deploy the app to production",
485
+ handler: async ({ commandName, args }) => {
486
+ console.log(`Deploying with args: ${args}`);
487
+ // Do work here — any thrown error is reported back to the CLI
488
+ },
489
+ },
490
+ ],
491
+ });
492
+ ```
493
+
494
+ When the user types `/deploy staging` in the CLI, the SDK receives a `command.execute` event, routes it to your handler, and automatically responds to the CLI. If the handler throws, the error message is forwarded.
495
+
496
+ Commands are sent to the CLI on both `createSession` and `resumeSession`, so you can update the command set when resuming.
497
+
498
+ ### UI Elicitation
499
+
500
+ When the CLI is running with a TUI (not in headless mode), the SDK can request interactive form dialogs from the user. The `session.ui` object provides convenience methods built on a single generic `elicitation` RPC.
501
+
502
+ > **Capability check:** Elicitation is only available when the host advertises support. Always check `session.capabilities.ui?.elicitation` before calling UI methods.
503
+
504
+ ```ts
505
+ const session = await client.createSession({ onPermissionRequest: approveAll });
506
+
507
+ if (session.capabilities.ui?.elicitation) {
508
+ // Confirm dialog — returns boolean
509
+ const ok = await session.ui.confirm("Deploy to production?");
510
+
511
+ // Selection dialog — returns selected value or null
512
+ const env = await session.ui.select("Pick environment", ["production", "staging", "dev"]);
513
+
514
+ // Text input — returns string or null
515
+ const name = await session.ui.input("Project name:", {
516
+ title: "Name",
517
+ minLength: 1,
518
+ maxLength: 50,
519
+ });
520
+
521
+ // Generic elicitation with full schema control
522
+ const result = await session.ui.elicitation({
523
+ message: "Configure deployment",
524
+ requestedSchema: {
525
+ type: "object",
526
+ properties: {
527
+ region: { type: "string", enum: ["us-east", "eu-west"] },
528
+ dryRun: { type: "boolean", default: true },
529
+ },
530
+ required: ["region"],
531
+ },
532
+ });
533
+ // result.action: "accept" | "decline" | "cancel"
534
+ // result.content: { region: "us-east", dryRun: true } (when accepted)
535
+ }
536
+ ```
537
+
538
+ All UI methods throw if elicitation is not supported by the host.
539
+
429
540
  ### System Message Customization
430
541
 
431
542
  Control the system prompt using `systemMessage` in session config:
@@ -444,7 +555,45 @@ const session = await client.createSession({
444
555
  });
445
556
  ```
446
557
 
447
- The SDK auto-injects environment context, tool instructions, and security guardrails. The default CLI persona is preserved, and your `content` is appended after SDK-managed sections. To change the persona or fully redefine the prompt, use `mode: "replace"`.
558
+ The SDK auto-injects environment context, tool instructions, and security guardrails. The default CLI persona is preserved, and your `content` is appended after SDK-managed sections. To change the persona or fully redefine the prompt, use `mode: "replace"` or `mode: "customize"`.
559
+
560
+ #### Customize Mode
561
+
562
+ Use `mode: "customize"` to selectively override individual sections of the prompt while preserving the rest:
563
+
564
+ ```typescript
565
+ import { SYSTEM_PROMPT_SECTIONS } from "@github/copilot-sdk";
566
+ import type { SectionOverride, SystemPromptSection } from "@github/copilot-sdk";
567
+
568
+ const session = await client.createSession({
569
+ model: "gpt-5",
570
+ systemMessage: {
571
+ mode: "customize",
572
+ sections: {
573
+ // Replace the tone/style section
574
+ tone: { action: "replace", content: "Respond in a warm, professional tone. Be thorough in explanations." },
575
+ // Remove coding-specific rules
576
+ code_change_rules: { action: "remove" },
577
+ // Append to existing guidelines
578
+ guidelines: { action: "append", content: "\n* Always cite data sources" },
579
+ },
580
+ // Additional instructions appended after all sections
581
+ content: "Focus on financial analysis and reporting.",
582
+ },
583
+ });
584
+ ```
585
+
586
+ Available section IDs: `identity`, `tone`, `tool_efficiency`, `environment_context`, `code_change_rules`, `guidelines`, `safety`, `tool_instructions`, `custom_instructions`, `last_instructions`. Use the `SYSTEM_PROMPT_SECTIONS` constant for descriptions of each section.
587
+
588
+ Each section override supports four actions:
589
+ - **`replace`** — Replace the section content entirely
590
+ - **`remove`** — Remove the section from the prompt
591
+ - **`append`** — Add content after the existing section
592
+ - **`prepend`** — Add content before the existing section
593
+
594
+ Unknown section IDs are handled gracefully: content from `replace`/`append`/`prepend` overrides is appended to additional instructions, and `remove` overrides are silently ignored.
595
+
596
+ #### Replace Mode
448
597
 
449
598
  For full control (removes all guardrails), use `mode: "replace"`:
450
599
 
@@ -589,6 +738,127 @@ const session = await client.createSession({
589
738
  > - For Azure OpenAI endpoints (`*.openai.azure.com`), you **must** use `type: "azure"`, not `type: "openai"`.
590
739
  > - The `baseUrl` should be just the host (e.g., `https://my-resource.openai.azure.com`). Do **not** include `/openai/v1` in the URL - the SDK handles path construction automatically.
591
740
 
741
+ ## Telemetry
742
+
743
+ The SDK supports OpenTelemetry for distributed tracing. Provide a `telemetry` config to enable trace export from the CLI process — this is all most users need:
744
+
745
+ ```typescript
746
+ const client = new CopilotClient({
747
+ telemetry: {
748
+ otlpEndpoint: "http://localhost:4318",
749
+ },
750
+ });
751
+ ```
752
+
753
+ With just this configuration, the CLI emits spans for every session, message, and tool call to your collector. No additional dependencies or setup required.
754
+
755
+ **TelemetryConfig options:**
756
+
757
+ - `otlpEndpoint?: string` - OTLP HTTP endpoint URL
758
+ - `filePath?: string` - File path for JSON-lines trace output
759
+ - `exporterType?: string` - `"otlp-http"` or `"file"`
760
+ - `sourceName?: string` - Instrumentation scope name
761
+ - `captureContent?: boolean` - Whether to capture message content
762
+
763
+ ### Advanced: Trace Context Propagation
764
+
765
+ > **You don't need this for normal telemetry collection.** The `telemetry` config above is sufficient to get full traces from the CLI.
766
+
767
+ `onGetTraceContext` is only needed if your application creates its own OpenTelemetry spans and you want them to appear in the **same distributed trace** as the CLI's spans — for example, to nest a "handle tool call" span inside the CLI's "execute tool" span, or to show the SDK call as a child of your application's request-handling span.
768
+
769
+ If you're already using `@opentelemetry/api` in your app and want this linkage, provide a callback:
770
+
771
+ ```typescript
772
+ import { propagation, context } from "@opentelemetry/api";
773
+
774
+ const client = new CopilotClient({
775
+ telemetry: { otlpEndpoint: "http://localhost:4318" },
776
+ onGetTraceContext: () => {
777
+ const carrier: Record<string, string> = {};
778
+ propagation.inject(context.active(), carrier);
779
+ return carrier;
780
+ },
781
+ });
782
+ ```
783
+
784
+ Inbound trace context from the CLI is available on the `ToolInvocation` object passed to tool handlers as `traceparent` and `tracestate` fields. See the [OpenTelemetry guide](../docs/observability/opentelemetry.md) for a full wire-up example.
785
+
786
+ ## Permission Handling
787
+
788
+ An `onPermissionRequest` handler is **required** whenever you create or resume a session. The handler is called before the agent executes each tool (file writes, shell commands, custom tools, etc.) and must return a decision.
789
+
790
+ ### Approve All (simplest)
791
+
792
+ Use the built-in `approveAll` helper to allow every tool call without any checks:
793
+
794
+ ```typescript
795
+ import { CopilotClient, approveAll } from "@github/copilot-sdk";
796
+
797
+ const session = await client.createSession({
798
+ model: "gpt-5",
799
+ onPermissionRequest: approveAll,
800
+ });
801
+ ```
802
+
803
+ ### Custom Permission Handler
804
+
805
+ Provide your own function to inspect each request and apply custom logic:
806
+
807
+ ```typescript
808
+ import type { PermissionRequest, PermissionRequestResult } from "@github/copilot-sdk";
809
+
810
+ const session = await client.createSession({
811
+ model: "gpt-5",
812
+ onPermissionRequest: (request: PermissionRequest, invocation): PermissionRequestResult => {
813
+ // request.kind — what type of operation is being requested:
814
+ // "shell" — executing a shell command
815
+ // "write" — writing or editing a file
816
+ // "read" — reading a file
817
+ // "mcp" — calling an MCP tool
818
+ // "custom-tool" — calling one of your registered tools
819
+ // "url" — fetching a URL
820
+ // "memory" — storing or retrieving persistent session memory
821
+ // "hook" — invoking a server-side hook or integration
822
+ // (additional kinds may be added; include a default case in handlers)
823
+ // request.toolCallId — the tool call that triggered this request
824
+ // request.toolName — name of the tool (for custom-tool / mcp)
825
+ // request.fileName — file being written (for write)
826
+ // request.fullCommandText — full shell command (for shell)
827
+
828
+ if (request.kind === "shell") {
829
+ // Deny shell commands
830
+ return { kind: "denied-interactively-by-user" };
831
+ }
832
+
833
+ return { kind: "approved" };
834
+ },
835
+ });
836
+ ```
837
+
838
+ ### Permission Result Kinds
839
+
840
+ | Kind | Meaning |
841
+ |------|---------|
842
+ | `"approved"` | Allow the tool to run |
843
+ | `"denied-interactively-by-user"` | User explicitly denied the request |
844
+ | `"denied-no-approval-rule-and-could-not-request-from-user"` | No approval rule matched and user could not be asked |
845
+ | `"denied-by-rules"` | Denied by a policy rule |
846
+ | `"denied-by-content-exclusion-policy"` | Denied due to a content exclusion policy |
847
+ | `"no-result"` | Leave the request unanswered (only valid with protocol v1; rejected by protocol v2 servers) |
848
+ ### Resuming Sessions
849
+
850
+ Pass `onPermissionRequest` when resuming a session too — it is required:
851
+
852
+ ```typescript
853
+ const session = await client.resumeSession("session-id", {
854
+ onPermissionRequest: approveAll,
855
+ });
856
+ ```
857
+
858
+ ### Per-Tool Skip Permission
859
+
860
+ To let a specific custom tool bypass the permission prompt entirely, set `skipPermission: true` on the tool definition. See [Skipping Permission Prompts](#skipping-permission-prompts) under Tools.
861
+
592
862
  ## User Input Requests
593
863
 
594
864
  Enable the agent to ask questions to the user using the `ask_user` tool by providing an `onUserInputRequest` handler: