@github/copilot-sdk 0.2.1-preview.1 → 0.2.1-unstable.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 +41 -2
- package/dist/cjs/client.js +85 -9
- package/dist/cjs/generated/rpc.js +69 -3
- package/dist/cjs/session.js +53 -0
- package/dist/client.d.ts +22 -0
- package/dist/client.js +86 -10
- package/dist/generated/rpc.d.ts +419 -2
- package/dist/generated/rpc.js +67 -2
- package/dist/generated/session-events.d.ts +135 -6
- package/dist/index.d.ts +1 -1
- package/dist/session.d.ts +18 -1
- package/dist/session.js +53 -0
- package/dist/types.d.ts +63 -2
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -120,6 +120,7 @@ Create a new conversation session.
|
|
|
120
120
|
- `provider?: ProviderConfig` - Custom API provider configuration (BYOK - Bring Your Own Key). See [Custom Providers](#custom-providers) section.
|
|
121
121
|
- `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.
|
|
122
122
|
- `onUserInputRequest?: UserInputHandler` - Handler for user input requests from the agent. Enables the `ask_user` tool. See [User Input Requests](#user-input-requests) section.
|
|
123
|
+
- `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.
|
|
123
124
|
- `hooks?: SessionHooks` - Hook handlers for session lifecycle events. See [Session Hooks](#session-hooks) section.
|
|
124
125
|
|
|
125
126
|
##### `resumeSession(sessionId: string, config?: ResumeSessionConfig): Promise<CopilotSession>`
|
|
@@ -293,6 +294,8 @@ if (session.capabilities.ui?.elicitation) {
|
|
|
293
294
|
}
|
|
294
295
|
```
|
|
295
296
|
|
|
297
|
+
Capabilities may update during the session. For example, when another client joins or disconnects with an elicitation handler. The SDK automatically applies `capabilities.changed` events, so this property always reflects the current state.
|
|
298
|
+
|
|
296
299
|
##### `ui: SessionUiApi`
|
|
297
300
|
|
|
298
301
|
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.
|
|
@@ -505,9 +508,9 @@ Commands are sent to the CLI on both `createSession` and `resumeSession`, so you
|
|
|
505
508
|
|
|
506
509
|
### UI Elicitation
|
|
507
510
|
|
|
508
|
-
When the
|
|
511
|
+
When the session has elicitation support — either from the CLI's TUI or from another client that registered an `onElicitationRequest` handler (see [Elicitation Requests](#elicitation-requests)) — the SDK can request interactive form dialogs from the user. The `session.ui` object provides convenience methods built on a single generic `elicitation` RPC.
|
|
509
512
|
|
|
510
|
-
> **Capability check:** Elicitation is only available when
|
|
513
|
+
> **Capability check:** Elicitation is only available when at least one connected participant advertises support. Always check `session.capabilities.ui?.elicitation` before calling UI methods — this property updates automatically as participants join and leave.
|
|
511
514
|
|
|
512
515
|
```ts
|
|
513
516
|
const session = await client.createSession({ onPermissionRequest: approveAll });
|
|
@@ -899,6 +902,42 @@ const session = await client.createSession({
|
|
|
899
902
|
});
|
|
900
903
|
```
|
|
901
904
|
|
|
905
|
+
## Elicitation Requests
|
|
906
|
+
|
|
907
|
+
Register an `onElicitationRequest` handler to let your client act as an elicitation provider — presenting form-based UI dialogs on behalf of the agent. When provided, the server notifies your client whenever a tool or MCP server needs structured user input.
|
|
908
|
+
|
|
909
|
+
```typescript
|
|
910
|
+
const session = await client.createSession({
|
|
911
|
+
model: "gpt-5",
|
|
912
|
+
onPermissionRequest: approveAll,
|
|
913
|
+
onElicitationRequest: async (request, invocation) => {
|
|
914
|
+
// request.message - Description of what information is needed
|
|
915
|
+
// request.requestedSchema - JSON Schema describing the form fields
|
|
916
|
+
// request.mode - "form" (structured input) or "url" (browser redirect)
|
|
917
|
+
// request.elicitationSource - Origin of the request (e.g. MCP server name)
|
|
918
|
+
|
|
919
|
+
console.log(`Elicitation from ${request.elicitationSource}: ${request.message}`);
|
|
920
|
+
|
|
921
|
+
// Present UI to the user and collect their response...
|
|
922
|
+
return {
|
|
923
|
+
action: "accept", // "accept", "decline", or "cancel"
|
|
924
|
+
content: { region: "us-east", dryRun: true },
|
|
925
|
+
};
|
|
926
|
+
},
|
|
927
|
+
});
|
|
928
|
+
|
|
929
|
+
// The session now reports elicitation capability
|
|
930
|
+
console.log(session.capabilities.ui?.elicitation); // true
|
|
931
|
+
```
|
|
932
|
+
|
|
933
|
+
When `onElicitationRequest` is provided, the SDK sends `requestElicitation: true` during session create/resume, which enables `session.capabilities.ui.elicitation` on the session.
|
|
934
|
+
|
|
935
|
+
In multi-client scenarios:
|
|
936
|
+
|
|
937
|
+
- If no connected client was previously providing an elicitation capability, but a new client joins that can, all clients will receive a `capabilities.changed` event to notify them that elicitation is now possible. The SDK automatically updates `session.capabilities` when these events arrive.
|
|
938
|
+
- Similarly, if the last elicitation provider disconnects, all clients receive a `capabilities.changed` event indicating elicitation is no longer available.
|
|
939
|
+
- The server fans out elicitation requests to **all** connected clients that registered a handler — the first response wins.
|
|
940
|
+
|
|
902
941
|
## Session Hooks
|
|
903
942
|
|
|
904
943
|
Hook into session lifecycle events by providing handlers in the `hooks` configuration:
|
package/dist/cjs/client.js
CHANGED
|
@@ -116,6 +116,8 @@ class CopilotClient {
|
|
|
116
116
|
processExitPromise = null;
|
|
117
117
|
// Rejects when CLI process exits
|
|
118
118
|
negotiatedProtocolVersion = null;
|
|
119
|
+
/** Connection-level session filesystem config, set via constructor option. */
|
|
120
|
+
sessionFsConfig = null;
|
|
119
121
|
/**
|
|
120
122
|
* Typed server-scoped RPC methods.
|
|
121
123
|
* @throws Error if the client is not connected
|
|
@@ -175,6 +177,7 @@ class CopilotClient {
|
|
|
175
177
|
}
|
|
176
178
|
this.onListModels = options.onListModels;
|
|
177
179
|
this.onGetTraceContext = options.onGetTraceContext;
|
|
180
|
+
this.sessionFsConfig = options.sessionFs ?? null;
|
|
178
181
|
const effectiveEnv = options.env ?? process.env;
|
|
179
182
|
this.options = {
|
|
180
183
|
cliPath: options.cliUrl ? void 0 : options.cliPath || effectiveEnv.COPILOT_CLI_PATH || getBundledCliPath(),
|
|
@@ -246,6 +249,13 @@ class CopilotClient {
|
|
|
246
249
|
}
|
|
247
250
|
await this.connectToServer();
|
|
248
251
|
await this.verifyProtocolVersion();
|
|
252
|
+
if (this.sessionFsConfig) {
|
|
253
|
+
await this.connection.sendRequest("sessionFs.setProvider", {
|
|
254
|
+
initialCwd: this.sessionFsConfig.initialCwd,
|
|
255
|
+
sessionStatePath: this.sessionFsConfig.sessionStatePath,
|
|
256
|
+
conventions: this.sessionFsConfig.conventions
|
|
257
|
+
});
|
|
258
|
+
}
|
|
249
259
|
this.state = "connected";
|
|
250
260
|
} catch (error) {
|
|
251
261
|
this.state = "error";
|
|
@@ -457,6 +467,9 @@ class CopilotClient {
|
|
|
457
467
|
if (config.onUserInputRequest) {
|
|
458
468
|
session.registerUserInputHandler(config.onUserInputRequest);
|
|
459
469
|
}
|
|
470
|
+
if (config.onElicitationRequest) {
|
|
471
|
+
session.registerElicitationHandler(config.onElicitationRequest);
|
|
472
|
+
}
|
|
460
473
|
if (config.hooks) {
|
|
461
474
|
session.registerHooks(config.hooks);
|
|
462
475
|
}
|
|
@@ -470,6 +483,15 @@ class CopilotClient {
|
|
|
470
483
|
session.on(config.onEvent);
|
|
471
484
|
}
|
|
472
485
|
this.sessions.set(sessionId, session);
|
|
486
|
+
if (this.sessionFsConfig) {
|
|
487
|
+
if (config.createSessionFsHandler) {
|
|
488
|
+
session.clientSessionApis.sessionFs = config.createSessionFsHandler(session);
|
|
489
|
+
} else {
|
|
490
|
+
throw new Error(
|
|
491
|
+
"createSessionFsHandler is required in session config when sessionFs is enabled in client options."
|
|
492
|
+
);
|
|
493
|
+
}
|
|
494
|
+
}
|
|
473
495
|
try {
|
|
474
496
|
const response = await this.connection.sendRequest("session.create", {
|
|
475
497
|
...await (0, import_telemetry.getTraceContext)(this.onGetTraceContext),
|
|
@@ -494,6 +516,7 @@ class CopilotClient {
|
|
|
494
516
|
provider: config.provider,
|
|
495
517
|
requestPermission: true,
|
|
496
518
|
requestUserInput: !!config.onUserInputRequest,
|
|
519
|
+
requestElicitation: !!config.onElicitationRequest,
|
|
497
520
|
hooks: !!(config.hooks && Object.values(config.hooks).some(Boolean)),
|
|
498
521
|
workingDirectory: config.workingDirectory,
|
|
499
522
|
streaming: config.streaming,
|
|
@@ -564,6 +587,9 @@ class CopilotClient {
|
|
|
564
587
|
if (config.onUserInputRequest) {
|
|
565
588
|
session.registerUserInputHandler(config.onUserInputRequest);
|
|
566
589
|
}
|
|
590
|
+
if (config.onElicitationRequest) {
|
|
591
|
+
session.registerElicitationHandler(config.onElicitationRequest);
|
|
592
|
+
}
|
|
567
593
|
if (config.hooks) {
|
|
568
594
|
session.registerHooks(config.hooks);
|
|
569
595
|
}
|
|
@@ -577,6 +603,15 @@ class CopilotClient {
|
|
|
577
603
|
session.on(config.onEvent);
|
|
578
604
|
}
|
|
579
605
|
this.sessions.set(sessionId, session);
|
|
606
|
+
if (this.sessionFsConfig) {
|
|
607
|
+
if (config.createSessionFsHandler) {
|
|
608
|
+
session.clientSessionApis.sessionFs = config.createSessionFsHandler(session);
|
|
609
|
+
} else {
|
|
610
|
+
throw new Error(
|
|
611
|
+
"createSessionFsHandler is required in session config when sessionFs is enabled in client options."
|
|
612
|
+
);
|
|
613
|
+
}
|
|
614
|
+
}
|
|
580
615
|
try {
|
|
581
616
|
const response = await this.connection.sendRequest("session.resume", {
|
|
582
617
|
...await (0, import_telemetry.getTraceContext)(this.onGetTraceContext),
|
|
@@ -601,6 +636,7 @@ class CopilotClient {
|
|
|
601
636
|
provider: config.provider,
|
|
602
637
|
requestPermission: true,
|
|
603
638
|
requestUserInput: !!config.onUserInputRequest,
|
|
639
|
+
requestElicitation: !!config.onElicitationRequest,
|
|
604
640
|
hooks: !!(config.hooks && Object.values(config.hooks).some(Boolean)),
|
|
605
641
|
workingDirectory: config.workingDirectory,
|
|
606
642
|
configDir: config.configDir,
|
|
@@ -812,16 +848,50 @@ class CopilotClient {
|
|
|
812
848
|
if (!this.connection) {
|
|
813
849
|
throw new Error("Client not connected");
|
|
814
850
|
}
|
|
815
|
-
const response = await this.connection.sendRequest("session.list", {
|
|
851
|
+
const response = await this.connection.sendRequest("session.list", {
|
|
852
|
+
filter
|
|
853
|
+
});
|
|
816
854
|
const { sessions } = response;
|
|
817
|
-
return sessions.map(
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
855
|
+
return sessions.map(CopilotClient.toSessionMetadata);
|
|
856
|
+
}
|
|
857
|
+
/**
|
|
858
|
+
* Gets metadata for a specific session by ID.
|
|
859
|
+
*
|
|
860
|
+
* This provides an efficient O(1) lookup of a single session's metadata
|
|
861
|
+
* instead of listing all sessions. Returns undefined if the session is not found.
|
|
862
|
+
*
|
|
863
|
+
* @param sessionId - The ID of the session to look up
|
|
864
|
+
* @returns A promise that resolves with the session metadata, or undefined if not found
|
|
865
|
+
* @throws Error if the client is not connected
|
|
866
|
+
*
|
|
867
|
+
* @example
|
|
868
|
+
* ```typescript
|
|
869
|
+
* const metadata = await client.getSessionMetadata("session-123");
|
|
870
|
+
* if (metadata) {
|
|
871
|
+
* console.log(`Session started at: ${metadata.startTime}`);
|
|
872
|
+
* }
|
|
873
|
+
* ```
|
|
874
|
+
*/
|
|
875
|
+
async getSessionMetadata(sessionId) {
|
|
876
|
+
if (!this.connection) {
|
|
877
|
+
throw new Error("Client not connected");
|
|
878
|
+
}
|
|
879
|
+
const response = await this.connection.sendRequest("session.getMetadata", { sessionId });
|
|
880
|
+
const { session } = response;
|
|
881
|
+
if (!session) {
|
|
882
|
+
return void 0;
|
|
883
|
+
}
|
|
884
|
+
return CopilotClient.toSessionMetadata(session);
|
|
885
|
+
}
|
|
886
|
+
static toSessionMetadata(raw) {
|
|
887
|
+
return {
|
|
888
|
+
sessionId: raw.sessionId,
|
|
889
|
+
startTime: new Date(raw.startTime),
|
|
890
|
+
modifiedTime: new Date(raw.modifiedTime),
|
|
891
|
+
summary: raw.summary,
|
|
892
|
+
isRemote: raw.isRemote,
|
|
893
|
+
context: raw.context
|
|
894
|
+
};
|
|
825
895
|
}
|
|
826
896
|
/**
|
|
827
897
|
* Gets the foreground session ID in TUI+server mode.
|
|
@@ -1151,6 +1221,12 @@ stderr: ${stderrOutput}`
|
|
|
1151
1221
|
"systemMessage.transform",
|
|
1152
1222
|
async (params) => await this.handleSystemMessageTransform(params)
|
|
1153
1223
|
);
|
|
1224
|
+
const sessions = this.sessions;
|
|
1225
|
+
(0, import_rpc.registerClientSessionApiHandlers)(this.connection, (sessionId) => {
|
|
1226
|
+
const session = sessions.get(sessionId);
|
|
1227
|
+
if (!session) throw new Error(`No session found for sessionId: ${sessionId}`);
|
|
1228
|
+
return session.clientSessionApis;
|
|
1229
|
+
});
|
|
1154
1230
|
this.connection.onClose(() => {
|
|
1155
1231
|
this.state = "disconnected";
|
|
1156
1232
|
});
|
|
@@ -19,7 +19,8 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
19
19
|
var rpc_exports = {};
|
|
20
20
|
__export(rpc_exports, {
|
|
21
21
|
createServerRpc: () => createServerRpc,
|
|
22
|
-
createSessionRpc: () => createSessionRpc
|
|
22
|
+
createSessionRpc: () => createSessionRpc,
|
|
23
|
+
registerClientSessionApiHandlers: () => registerClientSessionApiHandlers
|
|
23
24
|
});
|
|
24
25
|
module.exports = __toCommonJS(rpc_exports);
|
|
25
26
|
function createServerRpc(connection) {
|
|
@@ -33,6 +34,17 @@ function createServerRpc(connection) {
|
|
|
33
34
|
},
|
|
34
35
|
account: {
|
|
35
36
|
getQuota: async () => connection.sendRequest("account.getQuota", {})
|
|
37
|
+
},
|
|
38
|
+
mcp: {
|
|
39
|
+
config: {
|
|
40
|
+
list: async () => connection.sendRequest("mcp.config.list", {}),
|
|
41
|
+
add: async (params) => connection.sendRequest("mcp.config.add", params),
|
|
42
|
+
update: async (params) => connection.sendRequest("mcp.config.update", params),
|
|
43
|
+
remove: async (params) => connection.sendRequest("mcp.config.remove", params)
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
sessionFs: {
|
|
47
|
+
setProvider: async (params) => connection.sendRequest("sessionFs.setProvider", params)
|
|
36
48
|
}
|
|
37
49
|
};
|
|
38
50
|
}
|
|
@@ -104,7 +116,8 @@ function createSessionRpc(connection, sessionId) {
|
|
|
104
116
|
handlePendingCommand: async (params) => connection.sendRequest("session.commands.handlePendingCommand", { sessionId, ...params })
|
|
105
117
|
},
|
|
106
118
|
ui: {
|
|
107
|
-
elicitation: async (params) => connection.sendRequest("session.ui.elicitation", { sessionId, ...params })
|
|
119
|
+
elicitation: async (params) => connection.sendRequest("session.ui.elicitation", { sessionId, ...params }),
|
|
120
|
+
handlePendingElicitation: async (params) => connection.sendRequest("session.ui.handlePendingElicitation", { sessionId, ...params })
|
|
108
121
|
},
|
|
109
122
|
permissions: {
|
|
110
123
|
handlePendingPermissionRequest: async (params) => connection.sendRequest("session.permissions.handlePendingPermissionRequest", { sessionId, ...params })
|
|
@@ -116,8 +129,61 @@ function createSessionRpc(connection, sessionId) {
|
|
|
116
129
|
}
|
|
117
130
|
};
|
|
118
131
|
}
|
|
132
|
+
function registerClientSessionApiHandlers(connection, getHandlers) {
|
|
133
|
+
connection.onRequest("sessionFs.readFile", async (params) => {
|
|
134
|
+
const handler = getHandlers(params.sessionId).sessionFs;
|
|
135
|
+
if (!handler) throw new Error(`No sessionFs handler registered for session: ${params.sessionId}`);
|
|
136
|
+
return handler.readFile(params);
|
|
137
|
+
});
|
|
138
|
+
connection.onRequest("sessionFs.writeFile", async (params) => {
|
|
139
|
+
const handler = getHandlers(params.sessionId).sessionFs;
|
|
140
|
+
if (!handler) throw new Error(`No sessionFs handler registered for session: ${params.sessionId}`);
|
|
141
|
+
return handler.writeFile(params);
|
|
142
|
+
});
|
|
143
|
+
connection.onRequest("sessionFs.appendFile", async (params) => {
|
|
144
|
+
const handler = getHandlers(params.sessionId).sessionFs;
|
|
145
|
+
if (!handler) throw new Error(`No sessionFs handler registered for session: ${params.sessionId}`);
|
|
146
|
+
return handler.appendFile(params);
|
|
147
|
+
});
|
|
148
|
+
connection.onRequest("sessionFs.exists", async (params) => {
|
|
149
|
+
const handler = getHandlers(params.sessionId).sessionFs;
|
|
150
|
+
if (!handler) throw new Error(`No sessionFs handler registered for session: ${params.sessionId}`);
|
|
151
|
+
return handler.exists(params);
|
|
152
|
+
});
|
|
153
|
+
connection.onRequest("sessionFs.stat", async (params) => {
|
|
154
|
+
const handler = getHandlers(params.sessionId).sessionFs;
|
|
155
|
+
if (!handler) throw new Error(`No sessionFs handler registered for session: ${params.sessionId}`);
|
|
156
|
+
return handler.stat(params);
|
|
157
|
+
});
|
|
158
|
+
connection.onRequest("sessionFs.mkdir", async (params) => {
|
|
159
|
+
const handler = getHandlers(params.sessionId).sessionFs;
|
|
160
|
+
if (!handler) throw new Error(`No sessionFs handler registered for session: ${params.sessionId}`);
|
|
161
|
+
return handler.mkdir(params);
|
|
162
|
+
});
|
|
163
|
+
connection.onRequest("sessionFs.readdir", async (params) => {
|
|
164
|
+
const handler = getHandlers(params.sessionId).sessionFs;
|
|
165
|
+
if (!handler) throw new Error(`No sessionFs handler registered for session: ${params.sessionId}`);
|
|
166
|
+
return handler.readdir(params);
|
|
167
|
+
});
|
|
168
|
+
connection.onRequest("sessionFs.readdirWithTypes", async (params) => {
|
|
169
|
+
const handler = getHandlers(params.sessionId).sessionFs;
|
|
170
|
+
if (!handler) throw new Error(`No sessionFs handler registered for session: ${params.sessionId}`);
|
|
171
|
+
return handler.readdirWithTypes(params);
|
|
172
|
+
});
|
|
173
|
+
connection.onRequest("sessionFs.rm", async (params) => {
|
|
174
|
+
const handler = getHandlers(params.sessionId).sessionFs;
|
|
175
|
+
if (!handler) throw new Error(`No sessionFs handler registered for session: ${params.sessionId}`);
|
|
176
|
+
return handler.rm(params);
|
|
177
|
+
});
|
|
178
|
+
connection.onRequest("sessionFs.rename", async (params) => {
|
|
179
|
+
const handler = getHandlers(params.sessionId).sessionFs;
|
|
180
|
+
if (!handler) throw new Error(`No sessionFs handler registered for session: ${params.sessionId}`);
|
|
181
|
+
return handler.rename(params);
|
|
182
|
+
});
|
|
183
|
+
}
|
|
119
184
|
// Annotate the CommonJS export names for ESM import in node:
|
|
120
185
|
0 && (module.exports = {
|
|
121
186
|
createServerRpc,
|
|
122
|
-
createSessionRpc
|
|
187
|
+
createSessionRpc,
|
|
188
|
+
registerClientSessionApiHandlers
|
|
123
189
|
});
|
package/dist/cjs/session.js
CHANGED
|
@@ -48,11 +48,14 @@ class CopilotSession {
|
|
|
48
48
|
commandHandlers = /* @__PURE__ */ new Map();
|
|
49
49
|
permissionHandler;
|
|
50
50
|
userInputHandler;
|
|
51
|
+
elicitationHandler;
|
|
51
52
|
hooks;
|
|
52
53
|
transformCallbacks;
|
|
53
54
|
_rpc = null;
|
|
54
55
|
traceContextProvider;
|
|
55
56
|
_capabilities = {};
|
|
57
|
+
/** @internal Client session API handlers, populated by CopilotClient during create/resume. */
|
|
58
|
+
clientSessionApis = {};
|
|
56
59
|
/**
|
|
57
60
|
* Typed session-scoped RPC methods.
|
|
58
61
|
*/
|
|
@@ -269,6 +272,22 @@ class CopilotSession {
|
|
|
269
272
|
} else if (event.type === "command.execute") {
|
|
270
273
|
const { requestId, commandName, command, args } = event.data;
|
|
271
274
|
void this._executeCommandAndRespond(requestId, commandName, command, args);
|
|
275
|
+
} else if (event.type === "elicitation.requested") {
|
|
276
|
+
if (this.elicitationHandler) {
|
|
277
|
+
const { message, requestedSchema, mode, elicitationSource, url, requestId } = event.data;
|
|
278
|
+
void this._handleElicitationRequest(
|
|
279
|
+
{
|
|
280
|
+
message,
|
|
281
|
+
requestedSchema,
|
|
282
|
+
mode,
|
|
283
|
+
elicitationSource,
|
|
284
|
+
url
|
|
285
|
+
},
|
|
286
|
+
requestId
|
|
287
|
+
);
|
|
288
|
+
}
|
|
289
|
+
} else if (event.type === "capabilities.changed") {
|
|
290
|
+
this._capabilities = { ...this._capabilities, ...event.data };
|
|
272
291
|
}
|
|
273
292
|
}
|
|
274
293
|
/**
|
|
@@ -409,6 +428,40 @@ class CopilotSession {
|
|
|
409
428
|
this.commandHandlers.set(cmd.name, cmd.handler);
|
|
410
429
|
}
|
|
411
430
|
}
|
|
431
|
+
/**
|
|
432
|
+
* Registers the elicitation handler for this session.
|
|
433
|
+
*
|
|
434
|
+
* @param handler - The handler to invoke when the server dispatches an elicitation request
|
|
435
|
+
* @internal This method is typically called internally when creating/resuming a session.
|
|
436
|
+
*/
|
|
437
|
+
registerElicitationHandler(handler) {
|
|
438
|
+
this.elicitationHandler = handler;
|
|
439
|
+
}
|
|
440
|
+
/**
|
|
441
|
+
* Handles an elicitation.requested broadcast event.
|
|
442
|
+
* Invokes the registered handler and responds via handlePendingElicitation RPC.
|
|
443
|
+
* @internal
|
|
444
|
+
*/
|
|
445
|
+
async _handleElicitationRequest(request, requestId) {
|
|
446
|
+
if (!this.elicitationHandler) {
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
449
|
+
try {
|
|
450
|
+
const result = await this.elicitationHandler(request, { sessionId: this.sessionId });
|
|
451
|
+
await this.rpc.ui.handlePendingElicitation({ requestId, result });
|
|
452
|
+
} catch {
|
|
453
|
+
try {
|
|
454
|
+
await this.rpc.ui.handlePendingElicitation({
|
|
455
|
+
requestId,
|
|
456
|
+
result: { action: "cancel" }
|
|
457
|
+
});
|
|
458
|
+
} catch (rpcError) {
|
|
459
|
+
if (!(rpcError instanceof import_node.ConnectionError || rpcError instanceof import_node.ResponseError)) {
|
|
460
|
+
throw rpcError;
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
}
|
|
412
465
|
/**
|
|
413
466
|
* Sets the host capabilities for this session.
|
|
414
467
|
*
|
package/dist/client.d.ts
CHANGED
|
@@ -55,6 +55,8 @@ export declare class CopilotClient {
|
|
|
55
55
|
private _rpc;
|
|
56
56
|
private processExitPromise;
|
|
57
57
|
private negotiatedProtocolVersion;
|
|
58
|
+
/** Connection-level session filesystem config, set via constructor option. */
|
|
59
|
+
private sessionFsConfig;
|
|
58
60
|
/**
|
|
59
61
|
* Typed server-scoped RPC methods.
|
|
60
62
|
* @throws Error if the client is not connected
|
|
@@ -317,6 +319,26 @@ export declare class CopilotClient {
|
|
|
317
319
|
* const sessions = await client.listSessions({ repository: "owner/repo" });
|
|
318
320
|
*/
|
|
319
321
|
listSessions(filter?: SessionListFilter): Promise<SessionMetadata[]>;
|
|
322
|
+
/**
|
|
323
|
+
* Gets metadata for a specific session by ID.
|
|
324
|
+
*
|
|
325
|
+
* This provides an efficient O(1) lookup of a single session's metadata
|
|
326
|
+
* instead of listing all sessions. Returns undefined if the session is not found.
|
|
327
|
+
*
|
|
328
|
+
* @param sessionId - The ID of the session to look up
|
|
329
|
+
* @returns A promise that resolves with the session metadata, or undefined if not found
|
|
330
|
+
* @throws Error if the client is not connected
|
|
331
|
+
*
|
|
332
|
+
* @example
|
|
333
|
+
* ```typescript
|
|
334
|
+
* const metadata = await client.getSessionMetadata("session-123");
|
|
335
|
+
* if (metadata) {
|
|
336
|
+
* console.log(`Session started at: ${metadata.startTime}`);
|
|
337
|
+
* }
|
|
338
|
+
* ```
|
|
339
|
+
*/
|
|
340
|
+
getSessionMetadata(sessionId: string): Promise<SessionMetadata | undefined>;
|
|
341
|
+
private static toSessionMetadata;
|
|
320
342
|
/**
|
|
321
343
|
* Gets the foreground session ID in TUI+server mode.
|
|
322
344
|
*
|
package/dist/client.js
CHANGED
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
StreamMessageReader,
|
|
11
11
|
StreamMessageWriter
|
|
12
12
|
} from "vscode-jsonrpc/node.js";
|
|
13
|
-
import { createServerRpc } from "./generated/rpc.js";
|
|
13
|
+
import { createServerRpc, registerClientSessionApiHandlers } from "./generated/rpc.js";
|
|
14
14
|
import { getSdkProtocolVersion } from "./sdkProtocolVersion.js";
|
|
15
15
|
import { CopilotSession, NO_RESULT_PERMISSION_V2_ERROR } from "./session.js";
|
|
16
16
|
import { getTraceContext } from "./telemetry.js";
|
|
@@ -96,6 +96,8 @@ class CopilotClient {
|
|
|
96
96
|
processExitPromise = null;
|
|
97
97
|
// Rejects when CLI process exits
|
|
98
98
|
negotiatedProtocolVersion = null;
|
|
99
|
+
/** Connection-level session filesystem config, set via constructor option. */
|
|
100
|
+
sessionFsConfig = null;
|
|
99
101
|
/**
|
|
100
102
|
* Typed server-scoped RPC methods.
|
|
101
103
|
* @throws Error if the client is not connected
|
|
@@ -155,6 +157,7 @@ class CopilotClient {
|
|
|
155
157
|
}
|
|
156
158
|
this.onListModels = options.onListModels;
|
|
157
159
|
this.onGetTraceContext = options.onGetTraceContext;
|
|
160
|
+
this.sessionFsConfig = options.sessionFs ?? null;
|
|
158
161
|
const effectiveEnv = options.env ?? process.env;
|
|
159
162
|
this.options = {
|
|
160
163
|
cliPath: options.cliUrl ? void 0 : options.cliPath || effectiveEnv.COPILOT_CLI_PATH || getBundledCliPath(),
|
|
@@ -226,6 +229,13 @@ class CopilotClient {
|
|
|
226
229
|
}
|
|
227
230
|
await this.connectToServer();
|
|
228
231
|
await this.verifyProtocolVersion();
|
|
232
|
+
if (this.sessionFsConfig) {
|
|
233
|
+
await this.connection.sendRequest("sessionFs.setProvider", {
|
|
234
|
+
initialCwd: this.sessionFsConfig.initialCwd,
|
|
235
|
+
sessionStatePath: this.sessionFsConfig.sessionStatePath,
|
|
236
|
+
conventions: this.sessionFsConfig.conventions
|
|
237
|
+
});
|
|
238
|
+
}
|
|
229
239
|
this.state = "connected";
|
|
230
240
|
} catch (error) {
|
|
231
241
|
this.state = "error";
|
|
@@ -437,6 +447,9 @@ class CopilotClient {
|
|
|
437
447
|
if (config.onUserInputRequest) {
|
|
438
448
|
session.registerUserInputHandler(config.onUserInputRequest);
|
|
439
449
|
}
|
|
450
|
+
if (config.onElicitationRequest) {
|
|
451
|
+
session.registerElicitationHandler(config.onElicitationRequest);
|
|
452
|
+
}
|
|
440
453
|
if (config.hooks) {
|
|
441
454
|
session.registerHooks(config.hooks);
|
|
442
455
|
}
|
|
@@ -450,6 +463,15 @@ class CopilotClient {
|
|
|
450
463
|
session.on(config.onEvent);
|
|
451
464
|
}
|
|
452
465
|
this.sessions.set(sessionId, session);
|
|
466
|
+
if (this.sessionFsConfig) {
|
|
467
|
+
if (config.createSessionFsHandler) {
|
|
468
|
+
session.clientSessionApis.sessionFs = config.createSessionFsHandler(session);
|
|
469
|
+
} else {
|
|
470
|
+
throw new Error(
|
|
471
|
+
"createSessionFsHandler is required in session config when sessionFs is enabled in client options."
|
|
472
|
+
);
|
|
473
|
+
}
|
|
474
|
+
}
|
|
453
475
|
try {
|
|
454
476
|
const response = await this.connection.sendRequest("session.create", {
|
|
455
477
|
...await getTraceContext(this.onGetTraceContext),
|
|
@@ -474,6 +496,7 @@ class CopilotClient {
|
|
|
474
496
|
provider: config.provider,
|
|
475
497
|
requestPermission: true,
|
|
476
498
|
requestUserInput: !!config.onUserInputRequest,
|
|
499
|
+
requestElicitation: !!config.onElicitationRequest,
|
|
477
500
|
hooks: !!(config.hooks && Object.values(config.hooks).some(Boolean)),
|
|
478
501
|
workingDirectory: config.workingDirectory,
|
|
479
502
|
streaming: config.streaming,
|
|
@@ -544,6 +567,9 @@ class CopilotClient {
|
|
|
544
567
|
if (config.onUserInputRequest) {
|
|
545
568
|
session.registerUserInputHandler(config.onUserInputRequest);
|
|
546
569
|
}
|
|
570
|
+
if (config.onElicitationRequest) {
|
|
571
|
+
session.registerElicitationHandler(config.onElicitationRequest);
|
|
572
|
+
}
|
|
547
573
|
if (config.hooks) {
|
|
548
574
|
session.registerHooks(config.hooks);
|
|
549
575
|
}
|
|
@@ -557,6 +583,15 @@ class CopilotClient {
|
|
|
557
583
|
session.on(config.onEvent);
|
|
558
584
|
}
|
|
559
585
|
this.sessions.set(sessionId, session);
|
|
586
|
+
if (this.sessionFsConfig) {
|
|
587
|
+
if (config.createSessionFsHandler) {
|
|
588
|
+
session.clientSessionApis.sessionFs = config.createSessionFsHandler(session);
|
|
589
|
+
} else {
|
|
590
|
+
throw new Error(
|
|
591
|
+
"createSessionFsHandler is required in session config when sessionFs is enabled in client options."
|
|
592
|
+
);
|
|
593
|
+
}
|
|
594
|
+
}
|
|
560
595
|
try {
|
|
561
596
|
const response = await this.connection.sendRequest("session.resume", {
|
|
562
597
|
...await getTraceContext(this.onGetTraceContext),
|
|
@@ -581,6 +616,7 @@ class CopilotClient {
|
|
|
581
616
|
provider: config.provider,
|
|
582
617
|
requestPermission: true,
|
|
583
618
|
requestUserInput: !!config.onUserInputRequest,
|
|
619
|
+
requestElicitation: !!config.onElicitationRequest,
|
|
584
620
|
hooks: !!(config.hooks && Object.values(config.hooks).some(Boolean)),
|
|
585
621
|
workingDirectory: config.workingDirectory,
|
|
586
622
|
configDir: config.configDir,
|
|
@@ -792,16 +828,50 @@ class CopilotClient {
|
|
|
792
828
|
if (!this.connection) {
|
|
793
829
|
throw new Error("Client not connected");
|
|
794
830
|
}
|
|
795
|
-
const response = await this.connection.sendRequest("session.list", {
|
|
831
|
+
const response = await this.connection.sendRequest("session.list", {
|
|
832
|
+
filter
|
|
833
|
+
});
|
|
796
834
|
const { sessions } = response;
|
|
797
|
-
return sessions.map(
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
835
|
+
return sessions.map(CopilotClient.toSessionMetadata);
|
|
836
|
+
}
|
|
837
|
+
/**
|
|
838
|
+
* Gets metadata for a specific session by ID.
|
|
839
|
+
*
|
|
840
|
+
* This provides an efficient O(1) lookup of a single session's metadata
|
|
841
|
+
* instead of listing all sessions. Returns undefined if the session is not found.
|
|
842
|
+
*
|
|
843
|
+
* @param sessionId - The ID of the session to look up
|
|
844
|
+
* @returns A promise that resolves with the session metadata, or undefined if not found
|
|
845
|
+
* @throws Error if the client is not connected
|
|
846
|
+
*
|
|
847
|
+
* @example
|
|
848
|
+
* ```typescript
|
|
849
|
+
* const metadata = await client.getSessionMetadata("session-123");
|
|
850
|
+
* if (metadata) {
|
|
851
|
+
* console.log(`Session started at: ${metadata.startTime}`);
|
|
852
|
+
* }
|
|
853
|
+
* ```
|
|
854
|
+
*/
|
|
855
|
+
async getSessionMetadata(sessionId) {
|
|
856
|
+
if (!this.connection) {
|
|
857
|
+
throw new Error("Client not connected");
|
|
858
|
+
}
|
|
859
|
+
const response = await this.connection.sendRequest("session.getMetadata", { sessionId });
|
|
860
|
+
const { session } = response;
|
|
861
|
+
if (!session) {
|
|
862
|
+
return void 0;
|
|
863
|
+
}
|
|
864
|
+
return CopilotClient.toSessionMetadata(session);
|
|
865
|
+
}
|
|
866
|
+
static toSessionMetadata(raw) {
|
|
867
|
+
return {
|
|
868
|
+
sessionId: raw.sessionId,
|
|
869
|
+
startTime: new Date(raw.startTime),
|
|
870
|
+
modifiedTime: new Date(raw.modifiedTime),
|
|
871
|
+
summary: raw.summary,
|
|
872
|
+
isRemote: raw.isRemote,
|
|
873
|
+
context: raw.context
|
|
874
|
+
};
|
|
805
875
|
}
|
|
806
876
|
/**
|
|
807
877
|
* Gets the foreground session ID in TUI+server mode.
|
|
@@ -1131,6 +1201,12 @@ stderr: ${stderrOutput}`
|
|
|
1131
1201
|
"systemMessage.transform",
|
|
1132
1202
|
async (params) => await this.handleSystemMessageTransform(params)
|
|
1133
1203
|
);
|
|
1204
|
+
const sessions = this.sessions;
|
|
1205
|
+
registerClientSessionApiHandlers(this.connection, (sessionId) => {
|
|
1206
|
+
const session = sessions.get(sessionId);
|
|
1207
|
+
if (!session) throw new Error(`No session found for sessionId: ${sessionId}`);
|
|
1208
|
+
return session.clientSessionApis;
|
|
1209
|
+
});
|
|
1134
1210
|
this.connection.onClose(() => {
|
|
1135
1211
|
this.state = "disconnected";
|
|
1136
1212
|
});
|