22 * Copyright (c) Microsoft Corporation. All rights reserved.
33 *--------------------------------------------------------------------------------------------*/
44
5- import { MemoryProvider } from "@platformatic/vfs" ;
5+ import { MemoryProvider , VirtualProvider } from "@platformatic/vfs" ;
66import { describe , expect , it , onTestFinished } from "vitest" ;
77import { CopilotClient } from "../../src/client.js" ;
8- import { approveAll , defineTool , SessionEvent , type SessionFsConfig } from "../../src/index.js" ;
8+ import { SessionFsHandler } from "../../src/generated/rpc.js" ;
9+ import {
10+ approveAll ,
11+ CopilotSession ,
12+ defineTool ,
13+ SessionEvent ,
14+ type SessionFsConfig ,
15+ } from "../../src/index.js" ;
916import { createSdkTestContext } from "./harness/sdkTestContext.js" ;
1017
18+ process . env . COPILOT_CLI_PATH =
19+ "c:\\Users\\stevesa\\.copilot\\worktrees\\copilot-agent-runtime\\amber-aura\\dist-cli\\index.js" ;
20+
1121describe ( "Session Fs" , async ( ) => {
1222 // Single provider for the describe block — session IDs are unique per test,
1323 // so no cross-contamination between tests.
1424 const provider = new MemoryProvider ( ) ;
15- const { config } = createMemorySessionFs ( "/projects/test" , "/session-state" , provider ) ;
25+ const createSessionFsHandler = ( session : CopilotSession ) =>
26+ createTestSessionFsHandler ( session , provider ) ;
1627
1728 // Helpers to build session-namespaced paths for direct provider assertions
1829 const p = ( sessionId : string , path : string ) =>
1930 `/${ sessionId } ${ path . startsWith ( "/" ) ? path : "/" + path } ` ;
2031
2132 const { copilotClient : client , env } = await createSdkTestContext ( {
22- copilotClientOptions : {
23- sessionFs : config ,
24- } ,
33+ copilotClientOptions : { sessionFs : sessionFsConfig } ,
2534 } ) ;
2635
2736 it ( "should route file operations through the session fs provider" , async ( ) => {
28- const session = await client . createSession ( { onPermissionRequest : approveAll } ) ;
37+ const session = await client . createSession ( {
38+ onPermissionRequest : approveAll ,
39+ createSessionFsHandler,
40+ } ) ;
2941
3042 const msg = await session . sendAndWait ( { prompt : "What is 100 + 200?" } ) ;
3143 expect ( msg ?. data . content ) . toContain ( "300" ) ;
@@ -37,7 +49,10 @@ describe("Session Fs", async () => {
3749 } ) ;
3850
3951 it ( "should load session data from fs provider on resume" , async ( ) => {
40- const session1 = await client . createSession ( { onPermissionRequest : approveAll } ) ;
52+ const session1 = await client . createSession ( {
53+ onPermissionRequest : approveAll ,
54+ createSessionFsHandler,
55+ } ) ;
4156 const sessionId = session1 . sessionId ;
4257
4358 const msg = await session1 . sendAndWait ( { prompt : "What is 50 + 50?" } ) ;
@@ -49,6 +64,7 @@ describe("Session Fs", async () => {
4964
5065 const session2 = await client . resumeSession ( sessionId , {
5166 onPermissionRequest : approveAll ,
67+ createSessionFsHandler,
5268 } ) ;
5369
5470 // Send another message to verify the session is functional after resume
@@ -62,23 +78,18 @@ describe("Session Fs", async () => {
6278 useStdio : false , // Use TCP so we can connect from a second client
6379 env,
6480 } ) ;
65- await client . createSession ( { onPermissionRequest : approveAll } ) ;
81+ await client . createSession ( { onPermissionRequest : approveAll , createSessionFsHandler } ) ;
6682
6783 // Get the port the first client's runtime is listening on
6884 const port = ( client as unknown as { actualPort : number } ) . actualPort ;
6985
7086 // Second client tries to connect with a session fs — should fail
7187 // because sessions already exist on the runtime.
72- const { config : config2 } = createMemorySessionFs (
73- "/projects/test" ,
74- "/session-state" ,
75- new MemoryProvider ( )
76- ) ;
7788 const client2 = new CopilotClient ( {
7889 env,
7990 logLevel : "error" ,
8091 cliUrl : `localhost:${ port } ` ,
81- sessionFs : config2 ,
92+ sessionFs : sessionFsConfig ,
8293 } ) ;
8394 onTestFinished ( ( ) => client2 . forceStop ( ) ) ;
8495
@@ -89,6 +100,7 @@ describe("Session Fs", async () => {
89100 const suppliedFileContent = "x" . repeat ( 100_000 ) ;
90101 const session = await client . createSession ( {
91102 onPermissionRequest : approveAll ,
103+ createSessionFsHandler,
92104 tools : [
93105 defineTool ( "get_big_string" , {
94106 description : "Returns a large string" ,
@@ -132,78 +144,71 @@ function findToolName(messages: SessionEvent[], toolCallId: string): string | un
132144 }
133145}
134146
135- /**
136- * Builds a SessionFsConfig backed by a @platformatic/vfs MemoryProvider.
137- * Each sessionId is namespaced under `/<sessionId>/` in the provider's tree.
138- * Tests can assert directly against the returned MemoryProvider instance.
139- */
140- function createMemorySessionFs (
141- initialCwd : string ,
142- sessionStatePath : string ,
143- provider : MemoryProvider
144- ) : { config : SessionFsConfig } {
147+ const sessionFsConfig : SessionFsConfig = {
148+ initialCwd : "/" ,
149+ sessionStatePath : "/session-state" ,
150+ conventions : "linux" ,
151+ } ;
152+
153+ function createTestSessionFsHandler (
154+ session : CopilotSession ,
155+ provider : VirtualProvider
156+ ) : SessionFsHandler {
145157 const sp = ( sessionId : string , path : string ) =>
146158 `/${ sessionId } ${ path . startsWith ( "/" ) ? path : "/" + path } ` ;
147159
148- const config : SessionFsConfig = {
149- initialCwd,
150- sessionStatePath,
151- conventions : "linux" ,
152- createHandler : ( session ) => ( {
153- readFile : async ( { path } ) => {
154- const content = await provider . readFile ( sp ( session . sessionId , path ) , "utf8" ) ;
155- return { content : content as string } ;
156- } ,
157- writeFile : async ( { path, content } ) => {
158- await provider . writeFile ( sp ( session . sessionId , path ) , content ) ;
159- } ,
160- appendFile : async ( { path, content } ) => {
161- await provider . appendFile ( sp ( session . sessionId , path ) , content ) ;
162- } ,
163- exists : async ( { path } ) => {
164- return { exists : await provider . exists ( sp ( session . sessionId , path ) ) } ;
165- } ,
166- stat : async ( { path } ) => {
167- const st = await provider . stat ( sp ( session . sessionId , path ) ) ;
168- return {
169- isFile : st . isFile ( ) ,
170- isDirectory : st . isDirectory ( ) ,
171- size : st . size ,
172- mtime : new Date ( st . mtimeMs ) . toISOString ( ) ,
173- birthtime : new Date ( st . birthtimeMs ) . toISOString ( ) ,
174- } ;
175- } ,
176- mkdir : async ( { path, recursive, mode } ) => {
177- await provider . mkdir ( sp ( session . sessionId , path ) , {
178- recursive : recursive ?? false ,
179- mode,
180- } ) ;
181- } ,
182- readdir : async ( { path } ) => {
183- const entries = await provider . readdir ( sp ( session . sessionId , path ) ) ;
184- return { entries : entries as string [ ] } ;
185- } ,
186- readdirWithTypes : async ( { path } ) => {
187- const names = ( await provider . readdir ( sp ( session . sessionId , path ) ) ) as string [ ] ;
188- const entries = await Promise . all (
189- names . map ( async ( name ) => {
190- const st = await provider . stat ( sp ( session . sessionId , `${ path } /${ name } ` ) ) ;
191- return {
192- name,
193- type : st . isDirectory ( ) ? ( "directory" as const ) : ( "file" as const ) ,
194- } ;
195- } )
196- ) ;
197- return { entries } ;
198- } ,
199- rm : async ( { path } ) => {
200- await provider . unlink ( sp ( session . sessionId , path ) ) ;
201- } ,
202- rename : async ( { src, dest } ) => {
203- await provider . rename ( sp ( session . sessionId , src ) , sp ( session . sessionId , dest ) ) ;
204- } ,
205- } ) ,
160+ return {
161+ readFile : async ( { path } ) => {
162+ const content = await provider . readFile ( sp ( session . sessionId , path ) , "utf8" ) ;
163+ return { content : content as string } ;
164+ } ,
165+ writeFile : async ( { path, content } ) => {
166+ await provider . writeFile ( sp ( session . sessionId , path ) , content ) ;
167+ } ,
168+ appendFile : async ( { path, content } ) => {
169+ await provider . appendFile ( sp ( session . sessionId , path ) , content ) ;
170+ } ,
171+ exists : async ( { path } ) => {
172+ return { exists : await provider . exists ( sp ( session . sessionId , path ) ) } ;
173+ } ,
174+ stat : async ( { path } ) => {
175+ const st = await provider . stat ( sp ( session . sessionId , path ) ) ;
176+ return {
177+ isFile : st . isFile ( ) ,
178+ isDirectory : st . isDirectory ( ) ,
179+ size : st . size ,
180+ mtime : new Date ( st . mtimeMs ) . toISOString ( ) ,
181+ birthtime : new Date ( st . birthtimeMs ) . toISOString ( ) ,
182+ } ;
183+ } ,
184+ mkdir : async ( { path, recursive, mode } ) => {
185+ await provider . mkdir ( sp ( session . sessionId , path ) , {
186+ recursive : recursive ?? false ,
187+ mode,
188+ } ) ;
189+ } ,
190+ readdir : async ( { path } ) => {
191+ const entries = await provider . readdir ( sp ( session . sessionId , path ) ) ;
192+ return { entries : entries as string [ ] } ;
193+ } ,
194+ readdirWithTypes : async ( { path } ) => {
195+ const names = ( await provider . readdir ( sp ( session . sessionId , path ) ) ) as string [ ] ;
196+ const entries = await Promise . all (
197+ names . map ( async ( name ) => {
198+ const st = await provider . stat ( sp ( session . sessionId , `${ path } /${ name } ` ) ) ;
199+ return {
200+ name,
201+ type : st . isDirectory ( ) ? ( "directory" as const ) : ( "file" as const ) ,
202+ } ;
203+ } )
204+ ) ;
205+ return { entries } ;
206+ } ,
207+ rm : async ( { path } ) => {
208+ await provider . unlink ( sp ( session . sessionId , path ) ) ;
209+ } ,
210+ rename : async ( { src, dest } ) => {
211+ await provider . rename ( sp ( session . sessionId , src ) , sp ( session . sessionId , dest ) ) ;
212+ } ,
206213 } ;
207-
208- return { config } ;
209214}
0 commit comments