@github/copilot-sdk 0.1.32 → 0.1.33-preview.1
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 +13 -1
- package/dist/client.d.ts +38 -5
- package/dist/client.js +118 -90
- package/dist/extension.d.ts +19 -2
- package/dist/extension.js +18 -2
- package/dist/generated/rpc.d.ts +119 -0
- package/dist/generated/rpc.js +5 -0
- package/dist/generated/session-events.d.ts +419 -20
- package/dist/session.d.ts +23 -2
- package/dist/session.js +33 -3
- package/dist/types.d.ts +33 -4
- package/docs/agent-author.md +263 -0
- package/docs/examples.md +668 -0
- package/docs/extensions.md +59 -0
- package/package.json +3 -2
package/dist/session.d.ts
CHANGED
|
@@ -2,9 +2,10 @@
|
|
|
2
2
|
* Copilot Session - represents a single conversation session with the Copilot CLI.
|
|
3
3
|
* @module session
|
|
4
4
|
*/
|
|
5
|
-
import type { MessageConnection } from "vscode-jsonrpc/node";
|
|
5
|
+
import type { MessageConnection } from "vscode-jsonrpc/node.js";
|
|
6
6
|
import { createSessionRpc } from "./generated/rpc.js";
|
|
7
7
|
import type { MessageOptions, PermissionHandler, PermissionRequestResult, SessionEvent, SessionEventHandler, SessionEventType, SessionHooks, Tool, ToolHandler, TypedSessionEventHandler, UserInputHandler, UserInputResponse } from "./types.js";
|
|
8
|
+
export declare const NO_RESULT_PERMISSION_V2_ERROR = "Permission handlers cannot return 'no-result' when connected to a protocol v2 server.";
|
|
8
9
|
/** Assistant message event - the final response from the assistant. */
|
|
9
10
|
export type AssistantMessageEvent = Extract<SessionEvent, {
|
|
10
11
|
type: "assistant.message";
|
|
@@ -37,7 +38,7 @@ export type AssistantMessageEvent = Extract<SessionEvent, {
|
|
|
37
38
|
export declare class CopilotSession {
|
|
38
39
|
readonly sessionId: string;
|
|
39
40
|
private connection;
|
|
40
|
-
private
|
|
41
|
+
private _workspacePath?;
|
|
41
42
|
private eventHandlers;
|
|
42
43
|
private typedEventHandlers;
|
|
43
44
|
private toolHandlers;
|
|
@@ -339,4 +340,24 @@ export declare class CopilotSession {
|
|
|
339
340
|
* ```
|
|
340
341
|
*/
|
|
341
342
|
setModel(model: string): Promise<void>;
|
|
343
|
+
/**
|
|
344
|
+
* Log a message to the session timeline.
|
|
345
|
+
* The message appears in the session event stream and is visible to SDK consumers
|
|
346
|
+
* and (for non-ephemeral messages) persisted to the session event log on disk.
|
|
347
|
+
*
|
|
348
|
+
* @param message - Human-readable message text
|
|
349
|
+
* @param options - Optional log level and ephemeral flag
|
|
350
|
+
*
|
|
351
|
+
* @example
|
|
352
|
+
* ```typescript
|
|
353
|
+
* await session.log("Processing started");
|
|
354
|
+
* await session.log("Disk usage high", { level: "warning" });
|
|
355
|
+
* await session.log("Connection failed", { level: "error" });
|
|
356
|
+
* await session.log("Debug info", { ephemeral: true });
|
|
357
|
+
* ```
|
|
358
|
+
*/
|
|
359
|
+
log(message: string, options?: {
|
|
360
|
+
level?: "info" | "warning" | "error";
|
|
361
|
+
ephemeral?: boolean;
|
|
362
|
+
}): Promise<void>;
|
|
342
363
|
}
|
package/dist/session.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { ConnectionError, ResponseError } from "vscode-jsonrpc/node";
|
|
1
|
+
import { ConnectionError, ResponseError } from "vscode-jsonrpc/node.js";
|
|
2
2
|
import { createSessionRpc } from "./generated/rpc.js";
|
|
3
|
+
const NO_RESULT_PERMISSION_V2_ERROR = "Permission handlers cannot return 'no-result' when connected to a protocol v2 server.";
|
|
3
4
|
class CopilotSession {
|
|
4
5
|
/**
|
|
5
6
|
* Creates a new CopilotSession instance.
|
|
@@ -239,6 +240,9 @@ class CopilotSession {
|
|
|
239
240
|
const result = await this.permissionHandler(permissionRequest, {
|
|
240
241
|
sessionId: this.sessionId
|
|
241
242
|
});
|
|
243
|
+
if (result.kind === "no-result") {
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
242
246
|
await this.rpc.permissions.handlePendingPermissionRequest({ requestId, result });
|
|
243
247
|
} catch (_error) {
|
|
244
248
|
try {
|
|
@@ -335,8 +339,14 @@ class CopilotSession {
|
|
|
335
339
|
const result = await this.permissionHandler(request, {
|
|
336
340
|
sessionId: this.sessionId
|
|
337
341
|
});
|
|
342
|
+
if (result.kind === "no-result") {
|
|
343
|
+
throw new Error(NO_RESULT_PERMISSION_V2_ERROR);
|
|
344
|
+
}
|
|
338
345
|
return result;
|
|
339
|
-
} catch (
|
|
346
|
+
} catch (error) {
|
|
347
|
+
if (error instanceof Error && error.message === NO_RESULT_PERMISSION_V2_ERROR) {
|
|
348
|
+
throw error;
|
|
349
|
+
}
|
|
340
350
|
return { kind: "denied-no-approval-rule-and-could-not-request-from-user" };
|
|
341
351
|
}
|
|
342
352
|
}
|
|
@@ -501,7 +511,27 @@ class CopilotSession {
|
|
|
501
511
|
async setModel(model) {
|
|
502
512
|
await this.rpc.model.switchTo({ modelId: model });
|
|
503
513
|
}
|
|
514
|
+
/**
|
|
515
|
+
* Log a message to the session timeline.
|
|
516
|
+
* The message appears in the session event stream and is visible to SDK consumers
|
|
517
|
+
* and (for non-ephemeral messages) persisted to the session event log on disk.
|
|
518
|
+
*
|
|
519
|
+
* @param message - Human-readable message text
|
|
520
|
+
* @param options - Optional log level and ephemeral flag
|
|
521
|
+
*
|
|
522
|
+
* @example
|
|
523
|
+
* ```typescript
|
|
524
|
+
* await session.log("Processing started");
|
|
525
|
+
* await session.log("Disk usage high", { level: "warning" });
|
|
526
|
+
* await session.log("Connection failed", { level: "error" });
|
|
527
|
+
* await session.log("Debug info", { ephemeral: true });
|
|
528
|
+
* ```
|
|
529
|
+
*/
|
|
530
|
+
async log(message, options) {
|
|
531
|
+
await this.rpc.log({ message, ...options });
|
|
532
|
+
}
|
|
504
533
|
}
|
|
505
534
|
export {
|
|
506
|
-
CopilotSession
|
|
535
|
+
CopilotSession,
|
|
536
|
+
NO_RESULT_PERMISSION_V2_ERROR
|
|
507
537
|
};
|
package/dist/types.d.ts
CHANGED
|
@@ -56,8 +56,7 @@ export interface CopilotClientOptions {
|
|
|
56
56
|
*/
|
|
57
57
|
autoStart?: boolean;
|
|
58
58
|
/**
|
|
59
|
-
*
|
|
60
|
-
* @default true
|
|
59
|
+
* @deprecated This option has no effect and will be removed in a future release.
|
|
61
60
|
*/
|
|
62
61
|
autoRestart?: boolean;
|
|
63
62
|
/**
|
|
@@ -77,6 +76,13 @@ export interface CopilotClientOptions {
|
|
|
77
76
|
* @default true (but defaults to false when githubToken is provided)
|
|
78
77
|
*/
|
|
79
78
|
useLoggedInUser?: boolean;
|
|
79
|
+
/**
|
|
80
|
+
* Custom handler for listing available models.
|
|
81
|
+
* When provided, client.listModels() calls this handler instead of
|
|
82
|
+
* querying the CLI server. Useful in BYOK mode to return models
|
|
83
|
+
* available from your custom provider.
|
|
84
|
+
*/
|
|
85
|
+
onListModels?: () => Promise<ModelInfo[]> | ModelInfo[];
|
|
80
86
|
}
|
|
81
87
|
/**
|
|
82
88
|
* Configuration for creating a session
|
|
@@ -129,6 +135,10 @@ export interface Tool<TArgs = unknown> {
|
|
|
129
135
|
* will return an error.
|
|
130
136
|
*/
|
|
131
137
|
overridesBuiltInTool?: boolean;
|
|
138
|
+
/**
|
|
139
|
+
* When true, the tool can execute without a permission prompt.
|
|
140
|
+
*/
|
|
141
|
+
skipPermission?: boolean;
|
|
132
142
|
}
|
|
133
143
|
/**
|
|
134
144
|
* Helper to define a tool with Zod schema and get type inference for the handler.
|
|
@@ -139,6 +149,7 @@ export declare function defineTool<T = unknown>(name: string, config: {
|
|
|
139
149
|
parameters?: ZodSchema<T> | Record<string, unknown>;
|
|
140
150
|
handler: ToolHandler<T>;
|
|
141
151
|
overridesBuiltInTool?: boolean;
|
|
152
|
+
skipPermission?: boolean;
|
|
142
153
|
}): Tool<T>;
|
|
143
154
|
export interface ToolCallRequestPayload {
|
|
144
155
|
sessionId: string;
|
|
@@ -186,7 +197,9 @@ export interface PermissionRequest {
|
|
|
186
197
|
[key: string]: unknown;
|
|
187
198
|
}
|
|
188
199
|
import type { SessionPermissionsHandlePendingPermissionRequestParams } from "./generated/rpc.js";
|
|
189
|
-
export type PermissionRequestResult = SessionPermissionsHandlePendingPermissionRequestParams["result"]
|
|
200
|
+
export type PermissionRequestResult = SessionPermissionsHandlePendingPermissionRequestParams["result"] | {
|
|
201
|
+
kind: "no-result";
|
|
202
|
+
};
|
|
190
203
|
export type PermissionHandler = (request: PermissionRequest, invocation: {
|
|
191
204
|
sessionId: string;
|
|
192
205
|
}) => Promise<PermissionRequestResult> | PermissionRequestResult;
|
|
@@ -586,6 +599,12 @@ export interface SessionConfig {
|
|
|
586
599
|
* Custom agent configurations for the session.
|
|
587
600
|
*/
|
|
588
601
|
customAgents?: CustomAgentConfig[];
|
|
602
|
+
/**
|
|
603
|
+
* Name of the custom agent to activate when the session starts.
|
|
604
|
+
* Must match the `name` of one of the agents in `customAgents`.
|
|
605
|
+
* Equivalent to calling `session.rpc.agent.select({ name })` after creation.
|
|
606
|
+
*/
|
|
607
|
+
agent?: string;
|
|
589
608
|
/**
|
|
590
609
|
* Directories to load skills from.
|
|
591
610
|
*/
|
|
@@ -600,11 +619,21 @@ export interface SessionConfig {
|
|
|
600
619
|
* Set to `{ enabled: false }` to disable.
|
|
601
620
|
*/
|
|
602
621
|
infiniteSessions?: InfiniteSessionConfig;
|
|
622
|
+
/**
|
|
623
|
+
* Optional event handler that is registered on the session before the
|
|
624
|
+
* session.create RPC is issued. This guarantees that early events emitted
|
|
625
|
+
* by the CLI during session creation (e.g. session.start) are delivered to
|
|
626
|
+
* the handler.
|
|
627
|
+
*
|
|
628
|
+
* Equivalent to calling `session.on(handler)` immediately after creation,
|
|
629
|
+
* but executes earlier in the lifecycle so no events are missed.
|
|
630
|
+
*/
|
|
631
|
+
onEvent?: SessionEventHandler;
|
|
603
632
|
}
|
|
604
633
|
/**
|
|
605
634
|
* Configuration for resuming a session
|
|
606
635
|
*/
|
|
607
|
-
export type ResumeSessionConfig = Pick<SessionConfig, "clientName" | "model" | "tools" | "systemMessage" | "availableTools" | "excludedTools" | "provider" | "streaming" | "reasoningEffort" | "onPermissionRequest" | "onUserInputRequest" | "hooks" | "workingDirectory" | "configDir" | "mcpServers" | "customAgents" | "skillDirectories" | "disabledSkills" | "infiniteSessions"> & {
|
|
636
|
+
export type ResumeSessionConfig = Pick<SessionConfig, "clientName" | "model" | "tools" | "systemMessage" | "availableTools" | "excludedTools" | "provider" | "streaming" | "reasoningEffort" | "onPermissionRequest" | "onUserInputRequest" | "hooks" | "workingDirectory" | "configDir" | "mcpServers" | "customAgents" | "agent" | "skillDirectories" | "disabledSkills" | "infiniteSessions" | "onEvent"> & {
|
|
608
637
|
/**
|
|
609
638
|
* When true, skips emitting the session.resume event.
|
|
610
639
|
* Useful for reconnecting to a session without triggering resume-related side effects.
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
# Agent Extension Authoring Guide
|
|
2
|
+
|
|
3
|
+
A precise, step-by-step reference for agents writing Copilot CLI extensions programmatically.
|
|
4
|
+
|
|
5
|
+
## Workflow
|
|
6
|
+
|
|
7
|
+
### Step 1: Scaffold the extension
|
|
8
|
+
|
|
9
|
+
Use the `extensions_manage` tool with `operation: "scaffold"`:
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
extensions_manage({ operation: "scaffold", name: "my-extension" })
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
This creates `.github/extensions/my-extension/extension.mjs` with a working skeleton.
|
|
16
|
+
For user-scoped extensions (persist across all repos), add `location: "user"`.
|
|
17
|
+
|
|
18
|
+
### Step 2: Edit the extension file
|
|
19
|
+
|
|
20
|
+
Modify the generated `extension.mjs` using `edit` or `create` tools. The file must:
|
|
21
|
+
- Be named `extension.mjs` (only `.mjs` is supported)
|
|
22
|
+
- Use ES module syntax (`import`/`export`)
|
|
23
|
+
- Call `joinSession({ ... })`
|
|
24
|
+
|
|
25
|
+
### Step 3: Reload extensions
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
extensions_reload({})
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
This stops all running extensions and re-discovers/re-launches them. New tools are available immediately in the same turn (mid-turn refresh).
|
|
32
|
+
|
|
33
|
+
### Step 4: Verify
|
|
34
|
+
|
|
35
|
+
```
|
|
36
|
+
extensions_manage({ operation: "list" })
|
|
37
|
+
extensions_manage({ operation: "inspect", name: "my-extension" })
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Check that the extension loaded successfully and isn't marked as "failed".
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## File Structure
|
|
45
|
+
|
|
46
|
+
```
|
|
47
|
+
.github/extensions/<name>/extension.mjs
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Discovery rules:
|
|
51
|
+
- The CLI scans `.github/extensions/` relative to the git root
|
|
52
|
+
- It also scans the user's copilot config extensions directory
|
|
53
|
+
- Only immediate subdirectories are checked (not recursive)
|
|
54
|
+
- Each subdirectory must contain a file named `extension.mjs`
|
|
55
|
+
- Project extensions shadow user extensions on name collision
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## Minimal Skeleton
|
|
60
|
+
|
|
61
|
+
```js
|
|
62
|
+
import { joinSession } from "@github/copilot-sdk/extension";
|
|
63
|
+
|
|
64
|
+
await joinSession({
|
|
65
|
+
tools: [], // Optional — custom tools
|
|
66
|
+
hooks: {}, // Optional — lifecycle hooks
|
|
67
|
+
});
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## Registering Tools
|
|
73
|
+
|
|
74
|
+
```js
|
|
75
|
+
tools: [
|
|
76
|
+
{
|
|
77
|
+
name: "tool_name", // Required. Must be globally unique across all extensions.
|
|
78
|
+
description: "What it does", // Required. Shown to the agent in tool descriptions.
|
|
79
|
+
parameters: { // Optional. JSON Schema for the arguments.
|
|
80
|
+
type: "object",
|
|
81
|
+
properties: {
|
|
82
|
+
arg1: { type: "string", description: "..." },
|
|
83
|
+
},
|
|
84
|
+
required: ["arg1"],
|
|
85
|
+
},
|
|
86
|
+
handler: async (args, invocation) => {
|
|
87
|
+
// args: parsed arguments matching the schema
|
|
88
|
+
// invocation.sessionId: current session ID
|
|
89
|
+
// invocation.toolCallId: unique call ID
|
|
90
|
+
// invocation.toolName: this tool's name
|
|
91
|
+
//
|
|
92
|
+
// Return value: string or ToolResultObject
|
|
93
|
+
// string → treated as success
|
|
94
|
+
// { textResultForLlm, resultType } → structured result
|
|
95
|
+
// resultType: "success" | "failure" | "rejected" | "denied"
|
|
96
|
+
return `Result: ${args.arg1}`;
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
]
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
**Constraints:**
|
|
103
|
+
- Tool names must be unique across ALL loaded extensions. Collisions cause the second extension to fail to load.
|
|
104
|
+
- Handler must return a string or `{ textResultForLlm: string, resultType?: string }`.
|
|
105
|
+
- Handler receives `(args, invocation)` — the second argument has `sessionId`, `toolCallId`, `toolName`.
|
|
106
|
+
- Use `session.log()` to surface messages to the user. Don't use `console.log()` (stdout is reserved for JSON-RPC).
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
## Registering Hooks
|
|
111
|
+
|
|
112
|
+
```js
|
|
113
|
+
hooks: {
|
|
114
|
+
onUserPromptSubmitted: async (input, invocation) => { ... },
|
|
115
|
+
onPreToolUse: async (input, invocation) => { ... },
|
|
116
|
+
onPostToolUse: async (input, invocation) => { ... },
|
|
117
|
+
onSessionStart: async (input, invocation) => { ... },
|
|
118
|
+
onSessionEnd: async (input, invocation) => { ... },
|
|
119
|
+
onErrorOccurred: async (input, invocation) => { ... },
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
All hook inputs include `timestamp` (unix ms) and `cwd` (working directory).
|
|
124
|
+
All handlers receive `invocation: { sessionId: string }` as the second argument.
|
|
125
|
+
All handlers may return `void`/`undefined` (no-op) or an output object.
|
|
126
|
+
|
|
127
|
+
### onUserPromptSubmitted
|
|
128
|
+
|
|
129
|
+
**Input:** `{ prompt: string, timestamp, cwd }`
|
|
130
|
+
|
|
131
|
+
**Output (all fields optional):**
|
|
132
|
+
| Field | Type | Effect |
|
|
133
|
+
|-------|------|--------|
|
|
134
|
+
| `modifiedPrompt` | `string` | Replaces the user's prompt |
|
|
135
|
+
| `additionalContext` | `string` | Appended as hidden context the agent sees |
|
|
136
|
+
|
|
137
|
+
### onPreToolUse
|
|
138
|
+
|
|
139
|
+
**Input:** `{ toolName: string, toolArgs: unknown, timestamp, cwd }`
|
|
140
|
+
|
|
141
|
+
**Output (all fields optional):**
|
|
142
|
+
| Field | Type | Effect |
|
|
143
|
+
|-------|------|--------|
|
|
144
|
+
| `permissionDecision` | `"allow" \| "deny" \| "ask"` | Override the permission check |
|
|
145
|
+
| `permissionDecisionReason` | `string` | Shown to user if denied |
|
|
146
|
+
| `modifiedArgs` | `unknown` | Replaces the tool arguments |
|
|
147
|
+
| `additionalContext` | `string` | Injected into the conversation |
|
|
148
|
+
|
|
149
|
+
### onPostToolUse
|
|
150
|
+
|
|
151
|
+
**Input:** `{ toolName: string, toolArgs: unknown, toolResult: ToolResultObject, timestamp, cwd }`
|
|
152
|
+
|
|
153
|
+
**Output (all fields optional):**
|
|
154
|
+
| Field | Type | Effect |
|
|
155
|
+
|-------|------|--------|
|
|
156
|
+
| `modifiedResult` | `ToolResultObject` | Replaces the tool result |
|
|
157
|
+
| `additionalContext` | `string` | Injected into the conversation |
|
|
158
|
+
|
|
159
|
+
### onSessionStart
|
|
160
|
+
|
|
161
|
+
**Input:** `{ source: "startup" \| "resume" \| "new", initialPrompt?: string, timestamp, cwd }`
|
|
162
|
+
|
|
163
|
+
**Output (all fields optional):**
|
|
164
|
+
| Field | Type | Effect |
|
|
165
|
+
|-------|------|--------|
|
|
166
|
+
| `additionalContext` | `string` | Injected as initial context |
|
|
167
|
+
|
|
168
|
+
### onSessionEnd
|
|
169
|
+
|
|
170
|
+
**Input:** `{ reason: "complete" \| "error" \| "abort" \| "timeout" \| "user_exit", finalMessage?: string, error?: string, timestamp, cwd }`
|
|
171
|
+
|
|
172
|
+
**Output (all fields optional):**
|
|
173
|
+
| Field | Type | Effect |
|
|
174
|
+
|-------|------|--------|
|
|
175
|
+
| `sessionSummary` | `string` | Summary for session persistence |
|
|
176
|
+
| `cleanupActions` | `string[]` | Cleanup descriptions |
|
|
177
|
+
|
|
178
|
+
### onErrorOccurred
|
|
179
|
+
|
|
180
|
+
**Input:** `{ error: string, errorContext: "model_call" \| "tool_execution" \| "system" \| "user_input", recoverable: boolean, timestamp, cwd }`
|
|
181
|
+
|
|
182
|
+
**Output (all fields optional):**
|
|
183
|
+
| Field | Type | Effect |
|
|
184
|
+
|-------|------|--------|
|
|
185
|
+
| `errorHandling` | `"retry" \| "skip" \| "abort"` | How to handle the error |
|
|
186
|
+
| `retryCount` | `number` | Max retries (when errorHandling is "retry") |
|
|
187
|
+
| `userNotification` | `string` | Message shown to the user |
|
|
188
|
+
|
|
189
|
+
---
|
|
190
|
+
|
|
191
|
+
## Session Object
|
|
192
|
+
|
|
193
|
+
After `joinSession()`, the returned `session` provides:
|
|
194
|
+
|
|
195
|
+
### session.send(options)
|
|
196
|
+
|
|
197
|
+
Send a message programmatically:
|
|
198
|
+
```js
|
|
199
|
+
await session.send({ prompt: "Analyze the test results." });
|
|
200
|
+
await session.send({
|
|
201
|
+
prompt: "Review this file",
|
|
202
|
+
attachments: [{ type: "file", path: "./src/index.ts" }],
|
|
203
|
+
});
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### session.sendAndWait(options, timeout?)
|
|
207
|
+
|
|
208
|
+
Send and block until the agent finishes (resolves on `session.idle`):
|
|
209
|
+
```js
|
|
210
|
+
const response = await session.sendAndWait({ prompt: "What is 2+2?" });
|
|
211
|
+
// response?.data.content contains the agent's reply
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
### session.log(message, options?)
|
|
215
|
+
|
|
216
|
+
Log to the CLI timeline:
|
|
217
|
+
```js
|
|
218
|
+
await session.log("Extension ready");
|
|
219
|
+
await session.log("Rate limit approaching", { level: "warning" });
|
|
220
|
+
await session.log("Connection failed", { level: "error" });
|
|
221
|
+
await session.log("Processing...", { ephemeral: true }); // transient, not persisted
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
### session.on(eventType, handler)
|
|
225
|
+
|
|
226
|
+
Subscribe to session events. Returns an unsubscribe function.
|
|
227
|
+
```js
|
|
228
|
+
const unsub = session.on("tool.execution_complete", (event) => {
|
|
229
|
+
// event.data.toolName, event.data.success, event.data.result
|
|
230
|
+
});
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
### Key Event Types
|
|
234
|
+
|
|
235
|
+
| Event | Key Data Fields |
|
|
236
|
+
|-------|----------------|
|
|
237
|
+
| `assistant.message` | `content`, `messageId` |
|
|
238
|
+
| `tool.execution_start` | `toolCallId`, `toolName`, `arguments` |
|
|
239
|
+
| `tool.execution_complete` | `toolCallId`, `toolName`, `success`, `result`, `error` |
|
|
240
|
+
| `user.message` | `content`, `attachments`, `source` |
|
|
241
|
+
| `session.idle` | `backgroundTasks` |
|
|
242
|
+
| `session.error` | `errorType`, `message`, `stack` |
|
|
243
|
+
| `permission.requested` | `requestId`, `permissionRequest.kind` |
|
|
244
|
+
| `session.shutdown` | `shutdownType`, `totalPremiumRequests` |
|
|
245
|
+
|
|
246
|
+
### session.workspacePath
|
|
247
|
+
|
|
248
|
+
Path to the session workspace directory (checkpoints, plan.md, files/). `undefined` if infinite sessions disabled.
|
|
249
|
+
|
|
250
|
+
### session.rpc
|
|
251
|
+
|
|
252
|
+
Low-level typed RPC access to all session APIs (model, mode, plan, workspace, etc.).
|
|
253
|
+
|
|
254
|
+
---
|
|
255
|
+
|
|
256
|
+
## Gotchas
|
|
257
|
+
|
|
258
|
+
- **stdout is reserved for JSON-RPC.** Don't use `console.log()` — it will corrupt the protocol. Use `session.log()` to surface messages to the user.
|
|
259
|
+
- **Tool name collisions are fatal.** If two extensions register the same tool name, the second extension fails to initialize.
|
|
260
|
+
- **Don't call `session.send()` synchronously from `onUserPromptSubmitted`.** Use `setTimeout(() => session.send(...), 0)` to avoid infinite loops.
|
|
261
|
+
- **Extensions are reloaded on `/clear`.** Any in-memory state is lost between sessions.
|
|
262
|
+
- **Only `.mjs` is supported.** TypeScript (`.ts`) is not yet supported.
|
|
263
|
+
- **The handler's return value is the tool result.** Returning `undefined` sends an empty success. Throwing sends a failure with the error message.
|