@github/copilot-sdk 0.1.19 → 0.1.21

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
@@ -24,14 +24,13 @@ const session = await client.createSession({
24
24
  model: "gpt-5",
25
25
  });
26
26
 
27
- // Wait for response using session.idle event
27
+ // Wait for response using typed event handlers
28
28
  const done = new Promise<void>((resolve) => {
29
- session.on((event) => {
30
- if (event.type === "assistant.message") {
31
- console.log(event.data.content);
32
- } else if (event.type === "session.idle") {
33
- resolve();
34
- }
29
+ session.on("assistant.message", (event) => {
30
+ console.log(event.data.content);
31
+ });
32
+ session.on("session.idle", () => {
33
+ resolve();
35
34
  });
36
35
  });
37
36
 
@@ -64,6 +63,8 @@ new CopilotClient(options?: CopilotClientOptions)
64
63
  - `logLevel?: string` - Log level (default: "info")
65
64
  - `autoStart?: boolean` - Auto-start server (default: true)
66
65
  - `autoRestart?: boolean` - Auto-restart on crash (default: true)
66
+ - `githubToken?: string` - GitHub token for authentication. When provided, takes priority over other auth methods.
67
+ - `useLoggedInUser?: boolean` - Whether to use logged-in user for authentication (default: true, but false when `githubToken` is provided). Cannot be used with `cliUrl`.
67
68
 
68
69
  #### Methods
69
70
 
@@ -85,11 +86,15 @@ Create a new conversation session.
85
86
 
86
87
  **Config:**
87
88
 
88
- - `sessionId?: string` - Custom session ID
89
- - `model?: string` - Model to use ("gpt-5", "claude-sonnet-4.5", etc.)
89
+ - `sessionId?: string` - Custom session ID.
90
+ - `model?: string` - Model to use ("gpt-5", "claude-sonnet-4.5", etc.). **Required when using custom provider.**
91
+ - `reasoningEffort?: "low" | "medium" | "high" | "xhigh"` - Reasoning effort level for models that support it. Use `listModels()` to check which models support this option.
90
92
  - `tools?: Tool[]` - Custom tools exposed to the CLI
91
93
  - `systemMessage?: SystemMessageConfig` - System message customization (see below)
92
94
  - `infiniteSessions?: InfiniteSessionConfig` - Configure automatic context compaction (see below)
95
+ - `provider?: ProviderConfig` - Custom API provider configuration (BYOK - Bring Your Own Key). See [Custom Providers](#custom-providers) section.
96
+ - `onUserInputRequest?: UserInputHandler` - Handler for user input requests from the agent. Enables the `ask_user` tool. See [User Input Requests](#user-input-requests) section.
97
+ - `hooks?: SessionHooks` - Hook handlers for session lifecycle events. See [Session Hooks](#session-hooks) section.
93
98
 
94
99
  ##### `resumeSession(sessionId: string, config?: ResumeSessionConfig): Promise<CopilotSession>`
95
100
 
@@ -111,6 +116,41 @@ List all available sessions.
111
116
 
112
117
  Delete a session and its data from disk.
113
118
 
119
+ ##### `getForegroundSessionId(): Promise<string | undefined>`
120
+
121
+ Get the ID of the session currently displayed in the TUI. Only available when connecting to a server running in TUI+server mode (`--ui-server`).
122
+
123
+ ##### `setForegroundSessionId(sessionId: string): Promise<void>`
124
+
125
+ Request the TUI to switch to displaying the specified session. Only available in TUI+server mode.
126
+
127
+ ##### `on(eventType: SessionLifecycleEventType, handler): () => void`
128
+
129
+ Subscribe to a specific session lifecycle event type. Returns an unsubscribe function.
130
+
131
+ ```typescript
132
+ const unsubscribe = client.on("session.foreground", (event) => {
133
+ console.log(`Session ${event.sessionId} is now in foreground`);
134
+ });
135
+ ```
136
+
137
+ ##### `on(handler: SessionLifecycleHandler): () => void`
138
+
139
+ Subscribe to all session lifecycle events. Returns an unsubscribe function.
140
+
141
+ ```typescript
142
+ const unsubscribe = client.on((event) => {
143
+ console.log(`${event.type}: ${event.sessionId}`);
144
+ });
145
+ ```
146
+
147
+ **Lifecycle Event Types:**
148
+ - `session.created` - A new session was created
149
+ - `session.deleted` - A session was deleted
150
+ - `session.updated` - A session was updated (e.g., new messages)
151
+ - `session.foreground` - A session became the foreground session in TUI
152
+ - `session.background` - A session is no longer the foreground session
153
+
114
154
  ---
115
155
 
116
156
  ### CopilotSession
@@ -154,13 +194,34 @@ Send a message and wait until the session becomes idle.
154
194
 
155
195
  Returns the final assistant message event, or undefined if none was received.
156
196
 
197
+ ##### `on(eventType: string, handler: TypedSessionEventHandler): () => void`
198
+
199
+ Subscribe to a specific event type. The handler receives properly typed events.
200
+
201
+ ```typescript
202
+ // Listen for specific event types with full type inference
203
+ session.on("assistant.message", (event) => {
204
+ console.log(event.data.content); // TypeScript knows about event.data.content
205
+ });
206
+
207
+ session.on("session.idle", () => {
208
+ console.log("Session is idle");
209
+ });
210
+
211
+ // Listen to streaming events
212
+ session.on("assistant.message_delta", (event) => {
213
+ process.stdout.write(event.data.deltaContent);
214
+ });
215
+ ```
216
+
157
217
  ##### `on(handler: SessionEventHandler): () => void`
158
218
 
159
- Subscribe to session events. Returns an unsubscribe function.
219
+ Subscribe to all session events. Returns an unsubscribe function.
160
220
 
161
221
  ```typescript
162
222
  const unsubscribe = session.on((event) => {
163
- console.log(event);
223
+ // Handle any event type
224
+ console.log(event.type, event);
164
225
  });
165
226
 
166
227
  // Later...
@@ -226,27 +287,33 @@ const session = await client.createSession({
226
287
  streaming: true,
227
288
  });
228
289
 
229
- // Wait for completion using session.idle event
290
+ // Wait for completion using typed event handlers
230
291
  const done = new Promise<void>((resolve) => {
231
- session.on((event) => {
232
- if (event.type === "assistant.message_delta") {
233
- // Streaming message chunk - print incrementally
234
- process.stdout.write(event.data.deltaContent);
235
- } else if (event.type === "assistant.reasoning_delta") {
236
- // Streaming reasoning chunk (if model supports reasoning)
237
- process.stdout.write(event.data.deltaContent);
238
- } else if (event.type === "assistant.message") {
239
- // Final message - complete content
240
- console.log("\n--- Final message ---");
241
- console.log(event.data.content);
242
- } else if (event.type === "assistant.reasoning") {
243
- // Final reasoning content (if model supports reasoning)
244
- console.log("--- Reasoning ---");
245
- console.log(event.data.content);
246
- } else if (event.type === "session.idle") {
247
- // Session finished processing
248
- resolve();
249
- }
292
+ session.on("assistant.message_delta", (event) => {
293
+ // Streaming message chunk - print incrementally
294
+ process.stdout.write(event.data.deltaContent);
295
+ });
296
+
297
+ session.on("assistant.reasoning_delta", (event) => {
298
+ // Streaming reasoning chunk (if model supports reasoning)
299
+ process.stdout.write(event.data.deltaContent);
300
+ });
301
+
302
+ session.on("assistant.message", (event) => {
303
+ // Final message - complete content
304
+ console.log("\n--- Final message ---");
305
+ console.log(event.data.content);
306
+ });
307
+
308
+ session.on("assistant.reasoning", (event) => {
309
+ // Final reasoning content (if model supports reasoning)
310
+ console.log("--- Reasoning ---");
311
+ console.log(event.data.content);
312
+ });
313
+
314
+ session.on("session.idle", () => {
315
+ // Session finished processing
316
+ resolve();
250
317
  });
251
318
  });
252
319
 
@@ -407,6 +474,163 @@ await session.send({
407
474
  });
408
475
  ```
409
476
 
477
+ ### Custom Providers
478
+
479
+ The SDK supports custom OpenAI-compatible API providers (BYOK - Bring Your Own Key), including local providers like Ollama. When using a custom provider, you must specify the `model` explicitly.
480
+
481
+ **ProviderConfig:**
482
+
483
+ - `type?: "openai" | "azure" | "anthropic"` - Provider type (default: "openai")
484
+ - `baseUrl: string` - API endpoint URL (required)
485
+ - `apiKey?: string` - API key (optional for local providers like Ollama)
486
+ - `bearerToken?: string` - Bearer token for authentication (takes precedence over apiKey)
487
+ - `wireApi?: "completions" | "responses"` - API format for OpenAI/Azure (default: "completions")
488
+ - `azure?.apiVersion?: string` - Azure API version (default: "2024-10-21")
489
+
490
+ **Example with Ollama:**
491
+
492
+ ```typescript
493
+ const session = await client.createSession({
494
+ model: "deepseek-coder-v2:16b", // Required when using custom provider
495
+ provider: {
496
+ type: "openai",
497
+ baseUrl: "http://localhost:11434/v1", // Ollama endpoint
498
+ // apiKey not required for Ollama
499
+ },
500
+ });
501
+
502
+ await session.sendAndWait({ prompt: "Hello!" });
503
+ ```
504
+
505
+ **Example with custom OpenAI-compatible API:**
506
+
507
+ ```typescript
508
+ const session = await client.createSession({
509
+ model: "gpt-4",
510
+ provider: {
511
+ type: "openai",
512
+ baseUrl: "https://my-api.example.com/v1",
513
+ apiKey: process.env.MY_API_KEY,
514
+ },
515
+ });
516
+ ```
517
+
518
+ **Example with Azure OpenAI:**
519
+
520
+ ```typescript
521
+ const session = await client.createSession({
522
+ model: "gpt-4",
523
+ provider: {
524
+ type: "azure", // Must be "azure" for Azure endpoints, NOT "openai"
525
+ baseUrl: "https://my-resource.openai.azure.com", // Just the host, no path
526
+ apiKey: process.env.AZURE_OPENAI_KEY,
527
+ azure: {
528
+ apiVersion: "2024-10-21",
529
+ },
530
+ },
531
+ });
532
+ ```
533
+
534
+ > **Important notes:**
535
+ > - When using a custom provider, the `model` parameter is **required**. The SDK will throw an error if no model is specified.
536
+ > - For Azure OpenAI endpoints (`*.openai.azure.com`), you **must** use `type: "azure"`, not `type: "openai"`.
537
+ > - 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.
538
+
539
+ ## User Input Requests
540
+
541
+ Enable the agent to ask questions to the user using the `ask_user` tool by providing an `onUserInputRequest` handler:
542
+
543
+ ```typescript
544
+ const session = await client.createSession({
545
+ model: "gpt-5",
546
+ onUserInputRequest: async (request, invocation) => {
547
+ // request.question - The question to ask
548
+ // request.choices - Optional array of choices for multiple choice
549
+ // request.allowFreeform - Whether freeform input is allowed (default: true)
550
+
551
+ console.log(`Agent asks: ${request.question}`);
552
+ if (request.choices) {
553
+ console.log(`Choices: ${request.choices.join(", ")}`);
554
+ }
555
+
556
+ // Return the user's response
557
+ return {
558
+ answer: "User's answer here",
559
+ wasFreeform: true, // Whether the answer was freeform (not from choices)
560
+ };
561
+ },
562
+ });
563
+ ```
564
+
565
+ ## Session Hooks
566
+
567
+ Hook into session lifecycle events by providing handlers in the `hooks` configuration:
568
+
569
+ ```typescript
570
+ const session = await client.createSession({
571
+ model: "gpt-5",
572
+ hooks: {
573
+ // Called before each tool execution
574
+ onPreToolUse: async (input, invocation) => {
575
+ console.log(`About to run tool: ${input.toolName}`);
576
+ // Return permission decision and optionally modify args
577
+ return {
578
+ permissionDecision: "allow", // "allow", "deny", or "ask"
579
+ modifiedArgs: input.toolArgs, // Optionally modify tool arguments
580
+ additionalContext: "Extra context for the model",
581
+ };
582
+ },
583
+
584
+ // Called after each tool execution
585
+ onPostToolUse: async (input, invocation) => {
586
+ console.log(`Tool ${input.toolName} completed`);
587
+ // Optionally modify the result or add context
588
+ return {
589
+ additionalContext: "Post-execution notes",
590
+ };
591
+ },
592
+
593
+ // Called when user submits a prompt
594
+ onUserPromptSubmitted: async (input, invocation) => {
595
+ console.log(`User prompt: ${input.prompt}`);
596
+ return {
597
+ modifiedPrompt: input.prompt, // Optionally modify the prompt
598
+ };
599
+ },
600
+
601
+ // Called when session starts
602
+ onSessionStart: async (input, invocation) => {
603
+ console.log(`Session started from: ${input.source}`); // "startup", "resume", "new"
604
+ return {
605
+ additionalContext: "Session initialization context",
606
+ };
607
+ },
608
+
609
+ // Called when session ends
610
+ onSessionEnd: async (input, invocation) => {
611
+ console.log(`Session ended: ${input.reason}`);
612
+ },
613
+
614
+ // Called when an error occurs
615
+ onErrorOccurred: async (input, invocation) => {
616
+ console.error(`Error in ${input.errorContext}: ${input.error}`);
617
+ return {
618
+ errorHandling: "retry", // "retry", "skip", or "abort"
619
+ };
620
+ },
621
+ },
622
+ });
623
+ ```
624
+
625
+ **Available hooks:**
626
+
627
+ - `onPreToolUse` - Intercept tool calls before execution. Can allow/deny or modify arguments.
628
+ - `onPostToolUse` - Process tool results after execution. Can modify results or add context.
629
+ - `onUserPromptSubmitted` - Intercept user prompts. Can modify the prompt before processing.
630
+ - `onSessionStart` - Run logic when a session starts or resumes.
631
+ - `onSessionEnd` - Cleanup or logging when session ends.
632
+ - `onErrorOccurred` - Handle errors with retry/skip/abort strategies.
633
+
410
634
  ## Error Handling
411
635
 
412
636
  ```typescript
package/dist/client.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { CopilotSession } from "./session.js";
2
- import type { ConnectionState, CopilotClientOptions, GetAuthStatusResponse, GetStatusResponse, ModelInfo, ResumeSessionConfig, SessionConfig, SessionMetadata } from "./types.js";
2
+ import type { ConnectionState, CopilotClientOptions, GetAuthStatusResponse, GetStatusResponse, ModelInfo, ResumeSessionConfig, SessionConfig, SessionLifecycleEventType, SessionLifecycleHandler, SessionMetadata, TypedSessionLifecycleHandler } from "./types.js";
3
3
  /**
4
4
  * Main client for interacting with the Copilot CLI.
5
5
  *
@@ -44,6 +44,10 @@ export declare class CopilotClient {
44
44
  private options;
45
45
  private isExternalServer;
46
46
  private forceStopping;
47
+ private modelsCache;
48
+ private modelsCacheLock;
49
+ private sessionLifecycleHandlers;
50
+ private typedLifecycleHandlers;
47
51
  /**
48
52
  * Creates a new CopilotClient instance.
49
53
  *
@@ -229,7 +233,11 @@ export declare class CopilotClient {
229
233
  */
230
234
  getAuthStatus(): Promise<GetAuthStatusResponse>;
231
235
  /**
232
- * List available models with their metadata
236
+ * List available models with their metadata.
237
+ *
238
+ * Results are cached after the first successful call to avoid rate limiting.
239
+ * The cache is cleared when the client disconnects.
240
+ *
233
241
  * @throws Error if not authenticated
234
242
  */
235
243
  listModels(): Promise<ModelInfo[]>;
@@ -288,6 +296,87 @@ export declare class CopilotClient {
288
296
  * ```
289
297
  */
290
298
  listSessions(): Promise<SessionMetadata[]>;
299
+ /**
300
+ * Gets the foreground session ID in TUI+server mode.
301
+ *
302
+ * This returns the ID of the session currently displayed in the TUI.
303
+ * Only available when connecting to a server running in TUI+server mode (--ui-server).
304
+ *
305
+ * @returns A promise that resolves with the foreground session ID, or undefined if none
306
+ * @throws Error if the client is not connected
307
+ *
308
+ * @example
309
+ * ```typescript
310
+ * const sessionId = await client.getForegroundSessionId();
311
+ * if (sessionId) {
312
+ * console.log(`TUI is displaying session: ${sessionId}`);
313
+ * }
314
+ * ```
315
+ */
316
+ getForegroundSessionId(): Promise<string | undefined>;
317
+ /**
318
+ * Sets the foreground session in TUI+server mode.
319
+ *
320
+ * This requests the TUI to switch to displaying the specified session.
321
+ * Only available when connecting to a server running in TUI+server mode (--ui-server).
322
+ *
323
+ * @param sessionId - The ID of the session to display in the TUI
324
+ * @returns A promise that resolves when the session is switched
325
+ * @throws Error if the client is not connected or if the operation fails
326
+ *
327
+ * @example
328
+ * ```typescript
329
+ * // Switch the TUI to display a specific session
330
+ * await client.setForegroundSessionId("session-123");
331
+ * ```
332
+ */
333
+ setForegroundSessionId(sessionId: string): Promise<void>;
334
+ /**
335
+ * Subscribes to a specific session lifecycle event type.
336
+ *
337
+ * Lifecycle events are emitted when sessions are created, deleted, updated,
338
+ * or change foreground/background state (in TUI+server mode).
339
+ *
340
+ * @param eventType - The specific event type to listen for
341
+ * @param handler - A callback function that receives events of the specified type
342
+ * @returns A function that, when called, unsubscribes the handler
343
+ *
344
+ * @example
345
+ * ```typescript
346
+ * // Listen for when a session becomes foreground in TUI
347
+ * const unsubscribe = client.on("session.foreground", (event) => {
348
+ * console.log(`Session ${event.sessionId} is now displayed in TUI`);
349
+ * });
350
+ *
351
+ * // Later, to stop receiving events:
352
+ * unsubscribe();
353
+ * ```
354
+ */
355
+ on<K extends SessionLifecycleEventType>(eventType: K, handler: TypedSessionLifecycleHandler<K>): () => void;
356
+ /**
357
+ * Subscribes to all session lifecycle events.
358
+ *
359
+ * @param handler - A callback function that receives all lifecycle events
360
+ * @returns A function that, when called, unsubscribes the handler
361
+ *
362
+ * @example
363
+ * ```typescript
364
+ * const unsubscribe = client.on((event) => {
365
+ * switch (event.type) {
366
+ * case "session.foreground":
367
+ * console.log(`Session ${event.sessionId} is now in foreground`);
368
+ * break;
369
+ * case "session.created":
370
+ * console.log(`New session created: ${event.sessionId}`);
371
+ * break;
372
+ * }
373
+ * });
374
+ *
375
+ * // Later, to stop receiving events:
376
+ * unsubscribe();
377
+ * ```
378
+ */
379
+ on(handler: SessionLifecycleHandler): () => void;
291
380
  /**
292
381
  * Start the CLI server process
293
382
  */
@@ -306,9 +395,12 @@ export declare class CopilotClient {
306
395
  private connectViaTcp;
307
396
  private attachConnectionHandlers;
308
397
  private handleSessionEventNotification;
398
+ private handleSessionLifecycleNotification;
309
399
  private handleToolCallRequest;
310
400
  private executeToolCall;
311
401
  private handlePermissionRequest;
402
+ private handleUserInputRequest;
403
+ private handleHooksInvoke;
312
404
  private normalizeToolResult;
313
405
  private isToolResultObject;
314
406
  private buildUnsupportedToolResult;