@@ -14,13 +14,17 @@ import type {
1414import type { JsonValue } from "./app-server/serde_json/JsonValue" ;
1515import { ModelId } from "./ModelId" ;
1616import { AgentMode } from "./AgentMode" ;
17+ import path from "node:path" ;
18+ import { logger } from "./Logger" ;
1719import type {
1820 GetAccountResponse ,
1921 ListMcpServerStatusParams ,
2022 ListMcpServerStatusResponse ,
2123 Model ,
2224 SkillsListParams ,
2325 SkillsListResponse ,
26+ Thread ,
27+ ThreadSourceKind ,
2428 TurnCompletedNotification ,
2529 UserInput ,
2630} from "./app-server/v2" ;
@@ -198,6 +202,31 @@ export class CodexAcpClient {
198202 }
199203 }
200204
205+ async loadSession ( request : acp . LoadSessionRequest ) : Promise < SessionMetadataWithThread > {
206+ const response = await this . codexClient . threadResume ( {
207+ approvalPolicy : null ,
208+ sandbox : null ,
209+ baseInstructions : null ,
210+ config : this . createSessionConfig ( request . cwd , request . mcpServers ?? [ ] ) ,
211+ cwd : request . cwd ,
212+ developerInstructions : null ,
213+ history : null ,
214+ model : null ,
215+ modelProvider : this . getModelProvider ( ) ,
216+ path : null ,
217+ personality : null ,
218+ threadId : request . sessionId ,
219+ } ) ;
220+ const codexModels = await this . fetchAvailableModels ( ) ;
221+ const currentModelId = this . createModelId ( codexModels , response . model , response . reasoningEffort ) . toString ( ) ;
222+ return {
223+ sessionId : request . sessionId ,
224+ currentModelId : currentModelId ,
225+ models : codexModels ,
226+ thread : response . thread ,
227+ } ;
228+ }
229+
201230 async newSession ( request : acp . NewSessionRequest ) : Promise < SessionMetadata > {
202231 const response = await this . codexClient . threadStart ( {
203232 config : this . createSessionConfig ( request . cwd , request . mcpServers ) ,
@@ -336,6 +365,73 @@ export class CodexAcpClient {
336365 return this . codexClient . listMcpServerStatus ( params ) ;
337366 }
338367
368+ async listSessions ( request : acp . ListSessionsRequest ) : Promise < acp . ListSessionsResponse > {
369+ const sourceKinds : ThreadSourceKind [ ] = [
370+ "cli" ,
371+ "vscode" ,
372+ "exec" ,
373+ "appServer" ,
374+ "subAgent" ,
375+ "subAgentReview" ,
376+ "subAgentCompact" ,
377+ "subAgentThreadSpawn" ,
378+ "subAgentOther" ,
379+ "unknown" ,
380+ ] ;
381+ const requestedCwd = request . cwd ?. trim ( ) ?? null ;
382+ const filterByCwd = ( thread : Thread ) : boolean => {
383+ if ( ! requestedCwd ) return true ;
384+ if ( path . isAbsolute ( requestedCwd ) ) {
385+ return thread . cwd === requestedCwd ;
386+ }
387+ const requestedBase = path . basename ( requestedCwd ) ;
388+ return path . basename ( thread . cwd ) === requestedBase ;
389+ } ;
390+
391+ const preferredProvider = this . getModelProvider ( ) ;
392+ const modelProviders = preferredProvider ? [ preferredProvider ] : [ ] ;
393+ const listResponse = await this . codexClient . threadList ( {
394+ cursor : request . cursor ?? null ,
395+ limit : null ,
396+ sortKey : null ,
397+ modelProviders : modelProviders ,
398+ sourceKinds : sourceKinds ,
399+ archived : null ,
400+ } ) ;
401+
402+ if ( listResponse . data . length === 0 ) {
403+ const diagnostics = await this . runSessionListDiagnostics ( ) ;
404+ logger . log ( "Session list diagnostics" , diagnostics ) ;
405+ }
406+
407+ let sessions = listResponse . data . map ( ( thread ) => ( {
408+ sessionId : thread . id ,
409+ cwd : thread . cwd ,
410+ title : thread . preview || null ,
411+ updatedAt : new Date ( thread . updatedAt * 1000 ) . toISOString ( ) ,
412+ } ) ) ;
413+ if ( requestedCwd ) {
414+ const filtered = listResponse . data
415+ . filter ( filterByCwd )
416+ . map ( ( thread ) => ( {
417+ sessionId : thread . id ,
418+ cwd : thread . cwd ,
419+ title : thread . preview || null ,
420+ updatedAt : new Date ( thread . updatedAt * 1000 ) . toISOString ( ) ,
421+ } ) ) ;
422+ if ( filtered . length > 0 || path . isAbsolute ( requestedCwd ) ) {
423+ sessions = filtered ;
424+ } else {
425+ logger . log ( "Ignoring non-absolute cwd filter for session/list" , { cwd : requestedCwd } ) ;
426+ }
427+ }
428+
429+ return {
430+ sessions,
431+ nextCursor : listResponse . nextCursor ?? null ,
432+ } ;
433+ }
434+
339435 async turnInterrupt ( params : { threadId : string , turnId : string } ) : Promise < void > {
340436 await this . codexClient . turnInterrupt ( {
341437 threadId : params . threadId ,
@@ -355,6 +451,51 @@ export class CodexAcpClient {
355451
356452 return models ;
357453 }
454+
455+ private async runSessionListDiagnostics ( ) : Promise < Record < string , unknown > > {
456+ const [ allProviders , archivedAllProviders , customGateway ] = await Promise . all ( [
457+ this . codexClient . threadList ( {
458+ cursor : null ,
459+ limit : null ,
460+ sortKey : null ,
461+ modelProviders : [ ] ,
462+ sourceKinds : null ,
463+ archived : null ,
464+ } ) ,
465+ this . codexClient . threadList ( {
466+ cursor : null ,
467+ limit : null ,
468+ sortKey : null ,
469+ modelProviders : [ ] ,
470+ sourceKinds : null ,
471+ archived : true ,
472+ } ) ,
473+ this . codexClient . threadList ( {
474+ cursor : null ,
475+ limit : null ,
476+ sortKey : null ,
477+ modelProviders : [ "custom-gateway" ] ,
478+ sourceKinds : null ,
479+ archived : null ,
480+ } ) ,
481+ ] ) ;
482+
483+ return {
484+ allProviders : {
485+ count : allProviders . data . length ,
486+ nextCursor : allProviders . nextCursor ?? null ,
487+ } ,
488+ archivedAllProviders : {
489+ count : archivedAllProviders . data . length ,
490+ nextCursor : archivedAllProviders . nextCursor ?? null ,
491+ } ,
492+ customGateway : {
493+ count : customGateway . data . length ,
494+ nextCursor : customGateway . nextCursor ?? null ,
495+ } ,
496+ } ;
497+ }
498+
358499}
359500
360501export type JsonObject = { [ key in string ] ?: JsonValue }
@@ -365,6 +506,10 @@ export type SessionMetadata = {
365506 models : Model [ ] ,
366507}
367508
509+ export type SessionMetadataWithThread = SessionMetadata & {
510+ thread : Thread ,
511+ }
512+
368513function buildPromptItems ( prompt : acp . ContentBlock [ ] ) : UserInput [ ] {
369514 return prompt . map ( ( block ) : UserInput | null => {
370515 switch ( block . type ) {
0 commit comments