Skip to content

Commit 503dc80

Browse files
feat: replace sessionDataStore with SessionFs virtual filesystem
Migrate the TypeScript SDK from the event-level sessionDataStore abstraction to the general-purpose SessionFs virtual filesystem, matching the runtime's new design (copilot-agent-runtime#5432). Key changes: - Regenerate RPC types from runtime schema with sessionFs.* methods - Replace SessionDataStoreConfig with SessionFsConfig (initialCwd, sessionStatePath, conventions + 9 filesystem handler callbacks) - Client calls sessionFs.setProvider on connect (was setDataStore) - Client registers sessionFs.* RPC handlers (readFile, writeFile, appendFile, exists, stat, mkdir, readdir, rm, rename) - New E2E tests with InMemorySessionFs (filesystem-level, not events) - Remove old session_store tests and snapshots
1 parent 3d11cff commit 503dc80

16 files changed

Lines changed: 513 additions & 445 deletions

nodejs/src/client.ts

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ import type {
4646
SessionListFilter,
4747
SessionMetadata,
4848
SystemMessageCustomizeConfig,
49-
SessionDataStoreConfig,
49+
SessionFsConfig,
5050
TelemetryConfig,
5151
Tool,
5252
ToolCallRequestPayload,
@@ -217,7 +217,7 @@ export class CopilotClient {
217217
| "onListModels"
218218
| "telemetry"
219219
| "onGetTraceContext"
220-
| "sessionDataStore"
220+
| "sessionFs"
221221
>
222222
> & {
223223
cliPath?: string;
@@ -240,8 +240,8 @@ export class CopilotClient {
240240
private _rpc: ReturnType<typeof createServerRpc> | null = null;
241241
private processExitPromise: Promise<never> | null = null; // Rejects when CLI process exits
242242
private negotiatedProtocolVersion: number | null = null;
243-
/** Connection-level session data store config, set via constructor option. */
244-
private sessionDataStoreConfig: SessionDataStoreConfig | null = null;
243+
/** Connection-level session filesystem config, set via constructor option. */
244+
private sessionFsConfig: SessionFsConfig | null = null;
245245

246246
/**
247247
* Typed server-scoped RPC methods.
@@ -311,7 +311,7 @@ export class CopilotClient {
311311

312312
this.onListModels = options.onListModels;
313313
this.onGetTraceContext = options.onGetTraceContext;
314-
this.sessionDataStoreConfig = options.sessionDataStore ?? null;
314+
this.sessionFsConfig = options.sessionFs ?? null;
315315

316316
const effectiveEnv = options.env ?? process.env;
317317
this.options = {
@@ -404,10 +404,12 @@ export class CopilotClient {
404404
// Verify protocol version compatibility
405405
await this.verifyProtocolVersion();
406406

407-
// If a session data store was configured, register as the storage provider
408-
if (this.sessionDataStoreConfig) {
409-
await this.connection!.sendRequest("sessionDataStore.setDataStore", {
410-
descriptor: this.sessionDataStoreConfig.descriptor,
407+
// If a session filesystem provider was configured, register it
408+
if (this.sessionFsConfig) {
409+
await this.connection!.sendRequest("sessionFs.setProvider", {
410+
initialCwd: this.sessionFsConfig.initialCwd,
411+
sessionStatePath: this.sessionFsConfig.sessionStatePath,
412+
conventions: this.sessionFsConfig.conventions,
411413
});
412414
}
413415

@@ -1576,10 +1578,10 @@ export class CopilotClient {
15761578
await this.handleSystemMessageTransform(params)
15771579
);
15781580

1579-
// Register session data store RPC handlers if configured.
1580-
if (this.sessionDataStoreConfig) {
1581+
// Register session filesystem RPC handlers if configured.
1582+
if (this.sessionFsConfig) {
15811583
registerClientApiHandlers(this.connection, {
1582-
sessionDataStore: this.sessionDataStoreConfig,
1584+
sessionFs: this.sessionFsConfig,
15831585
});
15841586
}
15851587

nodejs/src/generated/rpc.ts

Lines changed: 143 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -179,18 +179,26 @@ export interface AccountGetQuotaResult {
179179
};
180180
}
181181

182-
export interface SessionDataStoreSetDataStoreResult {
182+
export interface SessionFsSetProviderResult {
183183
/**
184-
* Whether the data store was set successfully
184+
* Whether the provider was set successfully
185185
*/
186186
success: boolean;
187187
}
188188

189-
export interface SessionDataStoreSetDataStoreParams {
189+
export interface SessionFsSetProviderParams {
190190
/**
191-
* Opaque descriptor identifying the storage backend (e.g., 'redis://localhost/sessions')
191+
* Initial working directory for sessions
192192
*/
193-
descriptor: string;
193+
initialCwd: string;
194+
/**
195+
* Path within each session's SessionFs where the runtime stores files for that session
196+
*/
197+
sessionStatePath: string;
198+
/**
199+
* Path conventions used by this filesystem
200+
*/
201+
conventions: "windows" | "linux";
194202
}
195203

196204
export interface SessionModelGetCurrentResult {
@@ -1064,76 +1072,143 @@ export interface SessionShellKillParams {
10641072
signal?: "SIGTERM" | "SIGKILL" | "SIGINT";
10651073
}
10661074

1067-
export interface SessionDataStoreLoadResult {
1075+
export interface SessionFsReadFileResult {
10681076
/**
1069-
* All persisted events for the session, in order
1077+
* File content as UTF-8 string
10701078
*/
1071-
events: {
1072-
[k: string]: unknown;
1073-
}[];
1079+
content: string;
10741080
}
10751081

1076-
export interface SessionDataStoreLoadParams {
1082+
export interface SessionFsReadFileParams {
10771083
/**
1078-
* The session to load events for
1084+
* Target session identifier
10791085
*/
10801086
sessionId: string;
1087+
/**
1088+
* Path using SessionFs conventions
1089+
*/
1090+
path: string;
10811091
}
10821092

1083-
export interface SessionDataStoreAppendParams {
1093+
export interface SessionFsWriteFileParams {
10841094
/**
1085-
* The session to append events to
1095+
* Target session identifier
10861096
*/
10871097
sessionId: string;
10881098
/**
1089-
* Events to append, in order
1099+
* Path using SessionFs conventions
10901100
*/
1091-
events: {
1092-
[k: string]: unknown;
1093-
}[];
1101+
path: string;
1102+
/**
1103+
* Content to write
1104+
*/
1105+
content: string;
1106+
/**
1107+
* Optional POSIX-style mode for newly created files
1108+
*/
1109+
mode?: number;
10941110
}
10951111

1096-
export interface SessionDataStoreTruncateResult {
1112+
export interface SessionFsAppendFileParams {
1113+
/**
1114+
* Target session identifier
1115+
*/
1116+
sessionId: string;
1117+
/**
1118+
* Path using SessionFs conventions
1119+
*/
1120+
path: string;
10971121
/**
1098-
* Number of events removed
1122+
* Content to append
10991123
*/
1100-
eventsRemoved: number;
1124+
content: string;
11011125
/**
1102-
* Number of events kept
1126+
* Optional POSIX-style mode for newly created files
11031127
*/
1104-
eventsKept: number;
1128+
mode?: number;
1129+
}
1130+
1131+
export interface SessionFsExistsResult {
1132+
exists: boolean;
11051133
}
11061134

1107-
export interface SessionDataStoreTruncateParams {
1135+
export interface SessionFsExistsParams {
11081136
/**
1109-
* The session to truncate
1137+
* Target session identifier
11101138
*/
11111139
sessionId: string;
11121140
/**
1113-
* Event ID marking the truncation boundary (excluded)
1141+
* Path using SessionFs conventions
11141142
*/
1115-
upToEventId: string;
1143+
path: string;
11161144
}
11171145

1118-
export interface SessionDataStoreListResult {
1119-
sessions: {
1120-
sessionId: string;
1121-
/**
1122-
* ISO 8601 timestamp of last modification
1123-
*/
1124-
mtime: string;
1125-
/**
1126-
* ISO 8601 timestamp of creation
1127-
*/
1128-
birthtime: string;
1129-
}[];
1146+
export interface SessionFsStatResult {
1147+
isFile: boolean;
1148+
isDirectory: boolean;
1149+
size: number;
1150+
/**
1151+
* ISO 8601 timestamp of last modification
1152+
*/
1153+
mtime: string;
1154+
/**
1155+
* ISO 8601 timestamp of creation
1156+
*/
1157+
birthtime: string;
11301158
}
11311159

1132-
export interface SessionDataStoreDeleteParams {
1160+
export interface SessionFsStatParams {
1161+
/**
1162+
* Target session identifier
1163+
*/
1164+
sessionId: string;
11331165
/**
1134-
* The session to delete
1166+
* Path using SessionFs conventions
1167+
*/
1168+
path: string;
1169+
}
1170+
1171+
export interface SessionFsMkdirParams {
1172+
/**
1173+
* Target session identifier
1174+
*/
1175+
sessionId: string;
1176+
path: string;
1177+
recursive?: boolean;
1178+
}
1179+
1180+
export interface SessionFsReaddirResult {
1181+
/**
1182+
* Entry names in the directory
1183+
*/
1184+
entries: string[];
1185+
}
1186+
1187+
export interface SessionFsReaddirParams {
1188+
/**
1189+
* Target session identifier
1190+
*/
1191+
sessionId: string;
1192+
path: string;
1193+
}
1194+
1195+
export interface SessionFsRmParams {
1196+
/**
1197+
* Target session identifier
1198+
*/
1199+
sessionId: string;
1200+
path: string;
1201+
recursive?: boolean;
1202+
force?: boolean;
1203+
}
1204+
1205+
export interface SessionFsRenameParams {
1206+
/**
1207+
* Target session identifier
11351208
*/
11361209
sessionId: string;
1210+
src: string;
1211+
dest: string;
11371212
}
11381213

11391214
/** Create typed server-scoped RPC methods (no session required). */
@@ -1153,9 +1228,9 @@ export function createServerRpc(connection: MessageConnection) {
11531228
getQuota: async (): Promise<AccountGetQuotaResult> =>
11541229
connection.sendRequest("account.getQuota", {}),
11551230
},
1156-
sessionDataStore: {
1157-
setDataStore: async (params: SessionDataStoreSetDataStoreParams): Promise<SessionDataStoreSetDataStoreResult> =>
1158-
connection.sendRequest("sessionDataStore.setDataStore", params),
1231+
sessionFs: {
1232+
setProvider: async (params: SessionFsSetProviderParams): Promise<SessionFsSetProviderResult> =>
1233+
connection.sendRequest("sessionFs.setProvider", params),
11591234
},
11601235
};
11611236
}
@@ -1280,20 +1355,24 @@ export function createSessionRpc(connection: MessageConnection, sessionId: strin
12801355
}
12811356

12821357
/**
1283-
* Handler interface for the `sessionDataStore` client API group.
1284-
* Implement this to provide a custom sessionDataStore backend.
1358+
* Handler interface for the `sessionFs` client API group.
1359+
* Implement this to provide a custom sessionFs backend.
12851360
*/
1286-
export interface SessionDataStoreHandler {
1287-
load(params: SessionDataStoreLoadParams): Promise<SessionDataStoreLoadResult>;
1288-
append(params: SessionDataStoreAppendParams): Promise<void>;
1289-
truncate(params: SessionDataStoreTruncateParams): Promise<SessionDataStoreTruncateResult>;
1290-
list(): Promise<SessionDataStoreListResult>;
1291-
delete(params: SessionDataStoreDeleteParams): Promise<void>;
1361+
export interface SessionFsHandler {
1362+
readFile(params: SessionFsReadFileParams): Promise<SessionFsReadFileResult>;
1363+
writeFile(params: SessionFsWriteFileParams): Promise<void>;
1364+
appendFile(params: SessionFsAppendFileParams): Promise<void>;
1365+
exists(params: SessionFsExistsParams): Promise<SessionFsExistsResult>;
1366+
stat(params: SessionFsStatParams): Promise<SessionFsStatResult>;
1367+
mkdir(params: SessionFsMkdirParams): Promise<void>;
1368+
readdir(params: SessionFsReaddirParams): Promise<SessionFsReaddirResult>;
1369+
rm(params: SessionFsRmParams): Promise<void>;
1370+
rename(params: SessionFsRenameParams): Promise<void>;
12921371
}
12931372

12941373
/** All client API handler groups. Each group is optional. */
12951374
export interface ClientApiHandlers {
1296-
sessionDataStore?: SessionDataStoreHandler;
1375+
sessionFs?: SessionFsHandler;
12971376
}
12981377

12991378
/**
@@ -1306,12 +1385,16 @@ export function registerClientApiHandlers(
13061385
connection: MessageConnection,
13071386
handlers: ClientApiHandlers,
13081387
): void {
1309-
if (handlers.sessionDataStore) {
1310-
const h = handlers.sessionDataStore!;
1311-
connection.onRequest("sessionDataStore.load", (params: SessionDataStoreLoadParams) => h.load(params));
1312-
connection.onRequest("sessionDataStore.append", (params: SessionDataStoreAppendParams) => h.append(params));
1313-
connection.onRequest("sessionDataStore.truncate", (params: SessionDataStoreTruncateParams) => h.truncate(params));
1314-
connection.onRequest("sessionDataStore.list", () => h.list());
1315-
connection.onRequest("sessionDataStore.delete", (params: SessionDataStoreDeleteParams) => h.delete(params));
1388+
if (handlers.sessionFs) {
1389+
const h = handlers.sessionFs!;
1390+
connection.onRequest("sessionFs.readFile", (params: SessionFsReadFileParams) => h.readFile(params));
1391+
connection.onRequest("sessionFs.writeFile", (params: SessionFsWriteFileParams) => h.writeFile(params));
1392+
connection.onRequest("sessionFs.appendFile", (params: SessionFsAppendFileParams) => h.appendFile(params));
1393+
connection.onRequest("sessionFs.exists", (params: SessionFsExistsParams) => h.exists(params));
1394+
connection.onRequest("sessionFs.stat", (params: SessionFsStatParams) => h.stat(params));
1395+
connection.onRequest("sessionFs.mkdir", (params: SessionFsMkdirParams) => h.mkdir(params));
1396+
connection.onRequest("sessionFs.readdir", (params: SessionFsReaddirParams) => h.readdir(params));
1397+
connection.onRequest("sessionFs.rm", (params: SessionFsRmParams) => h.rm(params));
1398+
connection.onRequest("sessionFs.rename", (params: SessionFsRenameParams) => h.rename(params));
13161399
}
13171400
}

nodejs/src/generated/session-events.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,10 @@ 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;
9498
};
9599
}
96100
| {

nodejs/src/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,8 @@ export type {
5656
SessionListFilter,
5757
SessionMetadata,
5858
SessionUiApi,
59-
SessionDataStoreConfig,
60-
SessionDataStoreHandler,
59+
SessionFsConfig,
60+
SessionFsHandler,
6161
ClientApiHandlers,
6262
SystemMessageAppendConfig,
6363
SystemMessageConfig,

0 commit comments

Comments
 (0)