Skip to content

Commit 9193df9

Browse files
Move to per-session client APIs
1 parent b2118d0 commit 9193df9

9 files changed

Lines changed: 202 additions & 152 deletions

File tree

nodejs/src/client.ts

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import {
2424
StreamMessageReader,
2525
StreamMessageWriter,
2626
} from "vscode-jsonrpc/node.js";
27-
import { createServerRpc, registerClientApiHandlers } from "./generated/rpc.js";
27+
import { createServerRpc, registerClientSessionApiHandlers } from "./generated/rpc.js";
2828
import { getSdkProtocolVersion } from "./sdkProtocolVersion.js";
2929
import { CopilotSession, NO_RESULT_PERMISSION_V2_ERROR } from "./session.js";
3030
import { getTraceContext } from "./telemetry.js";
@@ -40,13 +40,13 @@ import type {
4040
SessionConfig,
4141
SessionContext,
4242
SessionEvent,
43+
SessionFsConfig,
4344
SessionLifecycleEvent,
4445
SessionLifecycleEventType,
4546
SessionLifecycleHandler,
4647
SessionListFilter,
4748
SessionMetadata,
4849
SystemMessageCustomizeConfig,
49-
SessionFsConfig,
5050
TelemetryConfig,
5151
Tool,
5252
ToolCallRequestPayload,
@@ -677,6 +677,9 @@ export class CopilotClient {
677677
session.on(config.onEvent);
678678
}
679679
this.sessions.set(sessionId, session);
680+
if (this.sessionFsConfig) {
681+
session.clientSessionApis.sessionFs = this.sessionFsConfig.createHandler(session);
682+
}
680683

681684
try {
682685
const response = await this.connection!.sendRequest("session.create", {
@@ -799,6 +802,9 @@ export class CopilotClient {
799802
session.on(config.onEvent);
800803
}
801804
this.sessions.set(sessionId, session);
805+
if (this.sessionFsConfig) {
806+
session.clientSessionApis.sessionFs = this.sessionFsConfig.createHandler(session);
807+
}
802808

803809
try {
804810
const response = await this.connection!.sendRequest("session.resume", {
@@ -1578,12 +1584,11 @@ export class CopilotClient {
15781584
await this.handleSystemMessageTransform(params)
15791585
);
15801586

1581-
// Register session filesystem RPC handlers if configured.
1582-
if (this.sessionFsConfig) {
1583-
registerClientApiHandlers(this.connection, {
1584-
sessionFs: this.sessionFsConfig,
1585-
});
1586-
}
1587+
// Register client session API handlers.
1588+
const sessions = this.sessions;
1589+
registerClientSessionApiHandlers(this.connection, (sessionId) =>
1590+
sessions.get(sessionId)?.clientSessionApis,
1591+
);
15871592

15881593
this.connection.onClose(() => {
15891594
this.state = "disconnected";

nodejs/src/generated/rpc.ts

Lines changed: 82 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1196,16 +1196,14 @@ export interface SessionFsReaddirParams {
11961196
path: string;
11971197
}
11981198

1199-
export interface SessionFsDirEntry {
1200-
name: string;
1201-
type: "file" | "directory";
1202-
}
1203-
12041199
export interface SessionFsReaddirWithTypesResult {
12051200
/**
12061201
* Directory entries with type information
12071202
*/
1208-
entries: SessionFsDirEntry[];
1203+
entries: {
1204+
name: string;
1205+
type: "file" | "directory";
1206+
}[];
12091207
}
12101208

12111209
export interface SessionFsReaddirWithTypesParams {
@@ -1378,10 +1376,7 @@ export function createSessionRpc(connection: MessageConnection, sessionId: strin
13781376
};
13791377
}
13801378

1381-
/**
1382-
* Handler interface for the `sessionFs` client API group.
1383-
* Implement this to provide a custom sessionFs backend.
1384-
*/
1379+
/** Handler for `sessionFs` client session API methods. */
13851380
export interface SessionFsHandler {
13861381
readFile(params: SessionFsReadFileParams): Promise<SessionFsReadFileResult>;
13871382
writeFile(params: SessionFsWriteFileParams): Promise<void>;
@@ -1395,32 +1390,89 @@ export interface SessionFsHandler {
13951390
rename(params: SessionFsRenameParams): Promise<void>;
13961391
}
13971392

1398-
/** All client API handler groups. Each group is optional. */
1399-
export interface ClientApiHandlers {
1393+
/** All client session API handler groups. */
1394+
export interface ClientSessionApiHandlers {
14001395
sessionFs?: SessionFsHandler;
14011396
}
14021397

14031398
/**
1404-
* Register client API handlers on a JSON-RPC connection.
1399+
* Register client session API handlers on a JSON-RPC connection.
14051400
* The server calls these methods to delegate work to the client.
1406-
* Methods for unregistered groups will respond with a standard JSON-RPC
1407-
* method-not-found error.
1401+
* Each incoming call includes a `sessionId` in the params; the registration
1402+
* function uses `getHandlers` to resolve the session's handlers.
14081403
*/
1409-
export function registerClientApiHandlers(
1404+
export function registerClientSessionApiHandlers(
14101405
connection: MessageConnection,
1411-
handlers: ClientApiHandlers,
1406+
getHandlers: (sessionId: string) => ClientSessionApiHandlers | undefined,
14121407
): void {
1413-
if (handlers.sessionFs) {
1414-
const h = handlers.sessionFs!;
1415-
connection.onRequest("sessionFs.readFile", (params: SessionFsReadFileParams) => h.readFile(params));
1416-
connection.onRequest("sessionFs.writeFile", (params: SessionFsWriteFileParams) => h.writeFile(params));
1417-
connection.onRequest("sessionFs.appendFile", (params: SessionFsAppendFileParams) => h.appendFile(params));
1418-
connection.onRequest("sessionFs.exists", (params: SessionFsExistsParams) => h.exists(params));
1419-
connection.onRequest("sessionFs.stat", (params: SessionFsStatParams) => h.stat(params));
1420-
connection.onRequest("sessionFs.mkdir", (params: SessionFsMkdirParams) => h.mkdir(params));
1421-
connection.onRequest("sessionFs.readdir", (params: SessionFsReaddirParams) => h.readdir(params));
1422-
connection.onRequest("sessionFs.readdirWithTypes", (params: SessionFsReaddirWithTypesParams) => h.readdirWithTypes(params));
1423-
connection.onRequest("sessionFs.rm", (params: SessionFsRmParams) => h.rm(params));
1424-
connection.onRequest("sessionFs.rename", (params: SessionFsRenameParams) => h.rename(params));
1425-
}
1408+
connection.onRequest("sessionFs.readFile", async (params: SessionFsReadFileParams) => {
1409+
const handlers = getHandlers(params.sessionId);
1410+
if (!handlers) throw new Error(`No session found for sessionId: ${params.sessionId}`);
1411+
const handler = handlers.sessionFs;
1412+
if (!handler) throw new Error(`No sessionFs handler registered for session: ${params.sessionId}`);
1413+
return handler.readFile(params);
1414+
});
1415+
connection.onRequest("sessionFs.writeFile", async (params: SessionFsWriteFileParams) => {
1416+
const handlers = getHandlers(params.sessionId);
1417+
if (!handlers) throw new Error(`No session found for sessionId: ${params.sessionId}`);
1418+
const handler = handlers.sessionFs;
1419+
if (!handler) throw new Error(`No sessionFs handler registered for session: ${params.sessionId}`);
1420+
return handler.writeFile(params);
1421+
});
1422+
connection.onRequest("sessionFs.appendFile", async (params: SessionFsAppendFileParams) => {
1423+
const handlers = getHandlers(params.sessionId);
1424+
if (!handlers) throw new Error(`No session found for sessionId: ${params.sessionId}`);
1425+
const handler = handlers.sessionFs;
1426+
if (!handler) throw new Error(`No sessionFs handler registered for session: ${params.sessionId}`);
1427+
return handler.appendFile(params);
1428+
});
1429+
connection.onRequest("sessionFs.exists", async (params: SessionFsExistsParams) => {
1430+
const handlers = getHandlers(params.sessionId);
1431+
if (!handlers) throw new Error(`No session found for sessionId: ${params.sessionId}`);
1432+
const handler = handlers.sessionFs;
1433+
if (!handler) throw new Error(`No sessionFs handler registered for session: ${params.sessionId}`);
1434+
return handler.exists(params);
1435+
});
1436+
connection.onRequest("sessionFs.stat", async (params: SessionFsStatParams) => {
1437+
const handlers = getHandlers(params.sessionId);
1438+
if (!handlers) throw new Error(`No session found for sessionId: ${params.sessionId}`);
1439+
const handler = handlers.sessionFs;
1440+
if (!handler) throw new Error(`No sessionFs handler registered for session: ${params.sessionId}`);
1441+
return handler.stat(params);
1442+
});
1443+
connection.onRequest("sessionFs.mkdir", async (params: SessionFsMkdirParams) => {
1444+
const handlers = getHandlers(params.sessionId);
1445+
if (!handlers) throw new Error(`No session found for sessionId: ${params.sessionId}`);
1446+
const handler = handlers.sessionFs;
1447+
if (!handler) throw new Error(`No sessionFs handler registered for session: ${params.sessionId}`);
1448+
return handler.mkdir(params);
1449+
});
1450+
connection.onRequest("sessionFs.readdir", async (params: SessionFsReaddirParams) => {
1451+
const handlers = getHandlers(params.sessionId);
1452+
if (!handlers) throw new Error(`No session found for sessionId: ${params.sessionId}`);
1453+
const handler = handlers.sessionFs;
1454+
if (!handler) throw new Error(`No sessionFs handler registered for session: ${params.sessionId}`);
1455+
return handler.readdir(params);
1456+
});
1457+
connection.onRequest("sessionFs.readdirWithTypes", async (params: SessionFsReaddirWithTypesParams) => {
1458+
const handlers = getHandlers(params.sessionId);
1459+
if (!handlers) throw new Error(`No session found for sessionId: ${params.sessionId}`);
1460+
const handler = handlers.sessionFs;
1461+
if (!handler) throw new Error(`No sessionFs handler registered for session: ${params.sessionId}`);
1462+
return handler.readdirWithTypes(params);
1463+
});
1464+
connection.onRequest("sessionFs.rm", async (params: SessionFsRmParams) => {
1465+
const handlers = getHandlers(params.sessionId);
1466+
if (!handlers) throw new Error(`No session found for sessionId: ${params.sessionId}`);
1467+
const handler = handlers.sessionFs;
1468+
if (!handler) throw new Error(`No sessionFs handler registered for session: ${params.sessionId}`);
1469+
return handler.rm(params);
1470+
});
1471+
connection.onRequest("sessionFs.rename", async (params: SessionFsRenameParams) => {
1472+
const handlers = getHandlers(params.sessionId);
1473+
if (!handlers) throw new Error(`No session found for sessionId: ${params.sessionId}`);
1474+
const handler = handlers.sessionFs;
1475+
if (!handler) throw new Error(`No sessionFs handler registered for session: ${params.sessionId}`);
1476+
return handler.rename(params);
1477+
});
14261478
}

nodejs/src/generated/session-events.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -91,10 +91,6 @@ export type SessionEvent =
9191
* Whether the session was already in use by another client at start time
9292
*/
9393
alreadyInUse?: boolean;
94-
/**
95-
* Whether this session supports remote steering via Mission Control
96-
*/
97-
steerable?: boolean;
9894
};
9995
}
10096
| {

nodejs/src/index.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,6 @@ export type {
5858
SessionUiApi,
5959
SessionFsConfig,
6060
SessionFsHandler,
61-
ClientApiHandlers,
6261
SystemMessageAppendConfig,
6362
SystemMessageConfig,
6463
SystemMessageCustomizeConfig,

nodejs/src/session.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import type { MessageConnection } from "vscode-jsonrpc/node.js";
1111
import { ConnectionError, ResponseError } from "vscode-jsonrpc/node.js";
1212
import { createSessionRpc } from "./generated/rpc.js";
13+
import type { ClientSessionApiHandlers } from "./generated/rpc.js";
1314
import { getTraceContext } from "./telemetry.js";
1415
import type {
1516
CommandHandler,
@@ -83,6 +84,9 @@ export class CopilotSession {
8384
private traceContextProvider?: TraceContextProvider;
8485
private _capabilities: SessionCapabilities = {};
8586

87+
/** @internal Client session API handlers, populated by CopilotClient during create/resume. */
88+
clientSessionApis: ClientSessionApiHandlers = {};
89+
8690
/**
8791
* Creates a new CopilotSession instance.
8892
*

nodejs/src/types.ts

Lines changed: 8 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -10,29 +10,8 @@
1010
import type { SessionEvent as GeneratedSessionEvent } from "./generated/session-events.js";
1111
export type SessionEvent = GeneratedSessionEvent;
1212

13-
// Re-export generated client API types
14-
export type {
15-
SessionFsHandler,
16-
SessionFsReadFileParams,
17-
SessionFsReadFileResult,
18-
SessionFsWriteFileParams,
19-
SessionFsAppendFileParams,
20-
SessionFsExistsParams,
21-
SessionFsExistsResult,
22-
SessionFsStatParams,
23-
SessionFsStatResult,
24-
SessionFsMkdirParams,
25-
SessionFsReaddirParams,
26-
SessionFsReaddirResult,
27-
SessionFsDirEntry,
28-
SessionFsReaddirWithTypesParams,
29-
SessionFsReaddirWithTypesResult,
30-
SessionFsRmParams,
31-
SessionFsRenameParams,
32-
ClientApiHandlers,
33-
} from "./generated/rpc.js";
34-
3513
import type { SessionFsHandler } from "./generated/rpc.js";
14+
export type { SessionFsHandler } from "./generated/rpc.js";
3615

3716
/**
3817
* Options for creating a CopilotClient
@@ -644,6 +623,7 @@ export interface PermissionRequest {
644623
}
645624

646625
import type { SessionPermissionsHandlePendingPermissionRequestParams } from "./generated/rpc.js";
626+
import { CopilotSession } from "./session.js";
647627

648628
export type PermissionRequestResult =
649629
| SessionPermissionsHandlePendingPermissionRequestParams["result"]
@@ -1352,11 +1332,8 @@ export interface SessionContext {
13521332

13531333
/**
13541334
* Configuration for a custom session filesystem provider.
1355-
*
1356-
* Extends the generated {@link SessionFsHandler} with registration
1357-
* parameters sent to the server's `sessionFs.setProvider` call.
13581335
*/
1359-
export interface SessionFsConfig extends SessionFsHandler {
1336+
export interface SessionFsConfig {
13601337
/**
13611338
* Initial working directory for sessions (user's project directory).
13621339
*/
@@ -1372,6 +1349,11 @@ export interface SessionFsConfig extends SessionFsHandler {
13721349
* Path conventions used by this filesystem provider.
13731350
*/
13741351
conventions: "windows" | "linux";
1352+
1353+
/**
1354+
* Supplies a handler for session filesystem operations.
1355+
*/
1356+
createHandler: (session: CopilotSession) => SessionFsHandler;
13751357
}
13761358

13771359
/**

0 commit comments

Comments
 (0)