@github/copilot-sdk 0.1.32 → 0.1.33-unstable.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/dist/client.d.ts +38 -1
- package/dist/client.js +20 -8
- package/dist/extension.d.ts +20 -2
- package/dist/extension.js +14 -2
- package/dist/generated/rpc.d.ts +26 -0
- package/dist/generated/rpc.js +2 -1
- package/dist/generated/session-events.d.ts +76 -0
- package/dist/session.d.ts +21 -1
- package/dist/session.js +20 -1
- package/dist/types.d.ts +14 -1
- package/docs/agent-author.md +265 -0
- package/docs/examples.md +681 -0
- package/docs/extensions.md +61 -0
- package/package.json +2 -1
|
@@ -0,0 +1,265 @@
|
|
|
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 { approveAll } from "@github/copilot-sdk";
|
|
63
|
+
import { joinSession } from "@github/copilot-sdk/extension";
|
|
64
|
+
|
|
65
|
+
await joinSession({
|
|
66
|
+
onPermissionRequest: approveAll, // Required — handle permission requests
|
|
67
|
+
tools: [], // Optional — custom tools
|
|
68
|
+
hooks: {}, // Optional — lifecycle hooks
|
|
69
|
+
});
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## Registering Tools
|
|
75
|
+
|
|
76
|
+
```js
|
|
77
|
+
tools: [
|
|
78
|
+
{
|
|
79
|
+
name: "tool_name", // Required. Must be globally unique across all extensions.
|
|
80
|
+
description: "What it does", // Required. Shown to the agent in tool descriptions.
|
|
81
|
+
parameters: { // Optional. JSON Schema for the arguments.
|
|
82
|
+
type: "object",
|
|
83
|
+
properties: {
|
|
84
|
+
arg1: { type: "string", description: "..." },
|
|
85
|
+
},
|
|
86
|
+
required: ["arg1"],
|
|
87
|
+
},
|
|
88
|
+
handler: async (args, invocation) => {
|
|
89
|
+
// args: parsed arguments matching the schema
|
|
90
|
+
// invocation.sessionId: current session ID
|
|
91
|
+
// invocation.toolCallId: unique call ID
|
|
92
|
+
// invocation.toolName: this tool's name
|
|
93
|
+
//
|
|
94
|
+
// Return value: string or ToolResultObject
|
|
95
|
+
// string → treated as success
|
|
96
|
+
// { textResultForLlm, resultType } → structured result
|
|
97
|
+
// resultType: "success" | "failure" | "rejected" | "denied"
|
|
98
|
+
return `Result: ${args.arg1}`;
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
]
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
**Constraints:**
|
|
105
|
+
- Tool names must be unique across ALL loaded extensions. Collisions cause the second extension to fail to load.
|
|
106
|
+
- Handler must return a string or `{ textResultForLlm: string, resultType?: string }`.
|
|
107
|
+
- Handler receives `(args, invocation)` — the second argument has `sessionId`, `toolCallId`, `toolName`.
|
|
108
|
+
- Use `session.log()` to surface messages to the user. Don't use `console.log()` (stdout is reserved for JSON-RPC).
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
## Registering Hooks
|
|
113
|
+
|
|
114
|
+
```js
|
|
115
|
+
hooks: {
|
|
116
|
+
onUserPromptSubmitted: async (input, invocation) => { ... },
|
|
117
|
+
onPreToolUse: async (input, invocation) => { ... },
|
|
118
|
+
onPostToolUse: async (input, invocation) => { ... },
|
|
119
|
+
onSessionStart: async (input, invocation) => { ... },
|
|
120
|
+
onSessionEnd: async (input, invocation) => { ... },
|
|
121
|
+
onErrorOccurred: async (input, invocation) => { ... },
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
All hook inputs include `timestamp` (unix ms) and `cwd` (working directory).
|
|
126
|
+
All handlers receive `invocation: { sessionId: string }` as the second argument.
|
|
127
|
+
All handlers may return `void`/`undefined` (no-op) or an output object.
|
|
128
|
+
|
|
129
|
+
### onUserPromptSubmitted
|
|
130
|
+
|
|
131
|
+
**Input:** `{ prompt: string, timestamp, cwd }`
|
|
132
|
+
|
|
133
|
+
**Output (all fields optional):**
|
|
134
|
+
| Field | Type | Effect |
|
|
135
|
+
|-------|------|--------|
|
|
136
|
+
| `modifiedPrompt` | `string` | Replaces the user's prompt |
|
|
137
|
+
| `additionalContext` | `string` | Appended as hidden context the agent sees |
|
|
138
|
+
|
|
139
|
+
### onPreToolUse
|
|
140
|
+
|
|
141
|
+
**Input:** `{ toolName: string, toolArgs: unknown, timestamp, cwd }`
|
|
142
|
+
|
|
143
|
+
**Output (all fields optional):**
|
|
144
|
+
| Field | Type | Effect |
|
|
145
|
+
|-------|------|--------|
|
|
146
|
+
| `permissionDecision` | `"allow" \| "deny" \| "ask"` | Override the permission check |
|
|
147
|
+
| `permissionDecisionReason` | `string` | Shown to user if denied |
|
|
148
|
+
| `modifiedArgs` | `unknown` | Replaces the tool arguments |
|
|
149
|
+
| `additionalContext` | `string` | Injected into the conversation |
|
|
150
|
+
|
|
151
|
+
### onPostToolUse
|
|
152
|
+
|
|
153
|
+
**Input:** `{ toolName: string, toolArgs: unknown, toolResult: ToolResultObject, timestamp, cwd }`
|
|
154
|
+
|
|
155
|
+
**Output (all fields optional):**
|
|
156
|
+
| Field | Type | Effect |
|
|
157
|
+
|-------|------|--------|
|
|
158
|
+
| `modifiedResult` | `ToolResultObject` | Replaces the tool result |
|
|
159
|
+
| `additionalContext` | `string` | Injected into the conversation |
|
|
160
|
+
|
|
161
|
+
### onSessionStart
|
|
162
|
+
|
|
163
|
+
**Input:** `{ source: "startup" \| "resume" \| "new", initialPrompt?: string, timestamp, cwd }`
|
|
164
|
+
|
|
165
|
+
**Output (all fields optional):**
|
|
166
|
+
| Field | Type | Effect |
|
|
167
|
+
|-------|------|--------|
|
|
168
|
+
| `additionalContext` | `string` | Injected as initial context |
|
|
169
|
+
|
|
170
|
+
### onSessionEnd
|
|
171
|
+
|
|
172
|
+
**Input:** `{ reason: "complete" \| "error" \| "abort" \| "timeout" \| "user_exit", finalMessage?: string, error?: string, timestamp, cwd }`
|
|
173
|
+
|
|
174
|
+
**Output (all fields optional):**
|
|
175
|
+
| Field | Type | Effect |
|
|
176
|
+
|-------|------|--------|
|
|
177
|
+
| `sessionSummary` | `string` | Summary for session persistence |
|
|
178
|
+
| `cleanupActions` | `string[]` | Cleanup descriptions |
|
|
179
|
+
|
|
180
|
+
### onErrorOccurred
|
|
181
|
+
|
|
182
|
+
**Input:** `{ error: string, errorContext: "model_call" \| "tool_execution" \| "system" \| "user_input", recoverable: boolean, timestamp, cwd }`
|
|
183
|
+
|
|
184
|
+
**Output (all fields optional):**
|
|
185
|
+
| Field | Type | Effect |
|
|
186
|
+
|-------|------|--------|
|
|
187
|
+
| `errorHandling` | `"retry" \| "skip" \| "abort"` | How to handle the error |
|
|
188
|
+
| `retryCount` | `number` | Max retries (when errorHandling is "retry") |
|
|
189
|
+
| `userNotification` | `string` | Message shown to the user |
|
|
190
|
+
|
|
191
|
+
---
|
|
192
|
+
|
|
193
|
+
## Session Object
|
|
194
|
+
|
|
195
|
+
After `joinSession()`, the returned `session` provides:
|
|
196
|
+
|
|
197
|
+
### session.send(options)
|
|
198
|
+
|
|
199
|
+
Send a message programmatically:
|
|
200
|
+
```js
|
|
201
|
+
await session.send({ prompt: "Analyze the test results." });
|
|
202
|
+
await session.send({
|
|
203
|
+
prompt: "Review this file",
|
|
204
|
+
attachments: [{ type: "file", path: "./src/index.ts" }],
|
|
205
|
+
});
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
### session.sendAndWait(options, timeout?)
|
|
209
|
+
|
|
210
|
+
Send and block until the agent finishes (resolves on `session.idle`):
|
|
211
|
+
```js
|
|
212
|
+
const response = await session.sendAndWait({ prompt: "What is 2+2?" });
|
|
213
|
+
// response?.data.content contains the agent's reply
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
### session.log(message, options?)
|
|
217
|
+
|
|
218
|
+
Log to the CLI timeline:
|
|
219
|
+
```js
|
|
220
|
+
await session.log("Extension ready");
|
|
221
|
+
await session.log("Rate limit approaching", { level: "warning" });
|
|
222
|
+
await session.log("Connection failed", { level: "error" });
|
|
223
|
+
await session.log("Processing...", { ephemeral: true }); // transient, not persisted
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
### session.on(eventType, handler)
|
|
227
|
+
|
|
228
|
+
Subscribe to session events. Returns an unsubscribe function.
|
|
229
|
+
```js
|
|
230
|
+
const unsub = session.on("tool.execution_complete", (event) => {
|
|
231
|
+
// event.data.toolName, event.data.success, event.data.result
|
|
232
|
+
});
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
### Key Event Types
|
|
236
|
+
|
|
237
|
+
| Event | Key Data Fields |
|
|
238
|
+
|-------|----------------|
|
|
239
|
+
| `assistant.message` | `content`, `messageId` |
|
|
240
|
+
| `tool.execution_start` | `toolCallId`, `toolName`, `arguments` |
|
|
241
|
+
| `tool.execution_complete` | `toolCallId`, `toolName`, `success`, `result`, `error` |
|
|
242
|
+
| `user.message` | `content`, `attachments`, `source` |
|
|
243
|
+
| `session.idle` | `backgroundTasks` |
|
|
244
|
+
| `session.error` | `errorType`, `message`, `stack` |
|
|
245
|
+
| `permission.requested` | `requestId`, `permissionRequest.kind` |
|
|
246
|
+
| `session.shutdown` | `shutdownType`, `totalPremiumRequests` |
|
|
247
|
+
|
|
248
|
+
### session.workspacePath
|
|
249
|
+
|
|
250
|
+
Path to the session workspace directory (checkpoints, plan.md, files/). `undefined` if infinite sessions disabled.
|
|
251
|
+
|
|
252
|
+
### session.rpc
|
|
253
|
+
|
|
254
|
+
Low-level typed RPC access to all session APIs (model, mode, plan, workspace, etc.).
|
|
255
|
+
|
|
256
|
+
---
|
|
257
|
+
|
|
258
|
+
## Gotchas
|
|
259
|
+
|
|
260
|
+
- **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.
|
|
261
|
+
- **Tool name collisions are fatal.** If two extensions register the same tool name, the second extension fails to initialize.
|
|
262
|
+
- **Don't call `session.send()` synchronously from `onUserPromptSubmitted`.** Use `setTimeout(() => session.send(...), 0)` to avoid infinite loops.
|
|
263
|
+
- **Extensions are reloaded on `/clear`.** Any in-memory state is lost between sessions.
|
|
264
|
+
- **Only `.mjs` is supported.** TypeScript (`.ts`) is not yet supported.
|
|
265
|
+
- **The handler's return value is the tool result.** Returning `undefined` sends an empty success. Throwing sends a failure with the error message.
|