@@ -21,8 +21,12 @@ import type {
2121 McpServerStatusUpdatedNotification ,
2222 ModelListParams ,
2323 ModelListResponse ,
24+ ReviewStartParams ,
25+ ReviewStartResponse ,
2426 SkillsListParams ,
2527 SkillsListResponse ,
28+ ThreadCompactStartParams ,
29+ ThreadCompactStartResponse ,
2630 ThreadLoadedListParams ,
2731 ThreadLoadedListResponse ,
2832 ThreadListParams ,
@@ -44,6 +48,7 @@ import type {
4448 CommandExecutionRequestApprovalResponse ,
4549 FileChangeRequestApprovalParams ,
4650 FileChangeRequestApprovalResponse ,
51+ ItemCompletedNotification ,
4752} from "./app-server/v2" ;
4853
4954export interface ApprovalHandler {
@@ -96,6 +101,7 @@ export class CodexAppServerClient {
96101 private readonly mcpServerStartupStates = new Map < string , McpServerStartupSnapshot > ( ) ;
97102 private readonly mcpServerStartupResolvers : Array < McpServerStartupResolver > = [ ] ;
98103 private readonly pendingTurnCompletionResolvers = new Map < string , Map < string , ( event : TurnCompletedNotification ) => void > > ( ) ;
104+ private readonly pendingCompactionCompletionResolvers = new Map < string , Set < ( event : CompactionCompletedNotification ) => void > > ( ) ;
99105 private readonly turnCompletionCaptures = new Map < string , Set < ( event : TurnCompletedNotification ) => void > > ( ) ;
100106 private readonly staleTurnIds = new Map < string , Set < string > > ( ) ;
101107
@@ -115,6 +121,9 @@ export class CodexAppServerClient {
115121 if ( isTurnCompletedNotification ( serverNotification ) ) {
116122 this . recordTurnCompleted ( serverNotification . params ) ;
117123 }
124+ if ( isCompactionCompletedNotification ( serverNotification ) ) {
125+ this . recordCompactionCompleted ( serverNotification ) ;
126+ }
118127 const routing = extractTurnRouting ( serverNotification ) ;
119128 const staleTurnNotification = this . isStaleTurn ( routing . threadId , routing . turnId ) ;
120129 if ( staleTurnNotification ) {
@@ -210,10 +219,40 @@ export class CodexAppServerClient {
210219 }
211220 }
212221
222+ async runReview ( params : ReviewStartParams , onTurnStarted ?: ( turnId : string ) => void ) : Promise < TurnCompletedNotification > {
223+ const capturedCompletions : Array < TurnCompletedNotification > = [ ] ;
224+ const releaseCapture = this . captureTurnCompletions ( params . threadId , ( event ) => {
225+ capturedCompletions . push ( event ) ;
226+ } ) ;
227+
228+ try {
229+ const reviewStarted = await this . reviewStart ( params ) ;
230+ onTurnStarted ?.( reviewStarted . turn . id ) ;
231+ const earlyCompletion = capturedCompletions . find ( event => event . turn . id === reviewStarted . turn . id ) ;
232+ releaseCapture ( ) ;
233+ if ( earlyCompletion ) {
234+ return earlyCompletion ;
235+ }
236+ return await this . awaitTurnCompleted ( reviewStarted . reviewThreadId , reviewStarted . turn . id ) ;
237+ } finally {
238+ releaseCapture ( ) ;
239+ }
240+ }
241+
242+ async runCompact ( params : ThreadCompactStartParams ) : Promise < CompactionCompletedNotification > {
243+ const compactionCompleted = this . awaitCompactionCompleted ( params . threadId ) ;
244+ await this . threadCompactStart ( params ) ;
245+ return await compactionCompleted ;
246+ }
247+
213248 async turnInterrupt ( params : TurnInterruptParams ) : Promise < TurnInterruptResponse > {
214249 return await this . sendRequest ( { method : "turn/interrupt" , params : params } ) ;
215250 }
216251
252+ async reviewStart ( params : ReviewStartParams ) : Promise < ReviewStartResponse > {
253+ return await this . sendRequest ( { method : "review/start" , params : params } ) ;
254+ }
255+
217256 markTurnStale ( threadId : string , turnId : string ) : void {
218257 const threadStaleTurns = this . staleTurnIds . get ( threadId ) ?? new Set < string > ( ) ;
219258 threadStaleTurns . add ( turnId ) ;
@@ -244,6 +283,10 @@ export class CodexAppServerClient {
244283 return await this . sendRequest ( { method : "thread/unsubscribe" , params : params } ) ;
245284 }
246285
286+ async threadCompactStart ( params : ThreadCompactStartParams ) : Promise < ThreadCompactStartResponse > {
287+ return await this . sendRequest ( { method : "thread/compact/start" , params : params } ) ;
288+ }
289+
247290 async listMcpServerStatus ( params : ListMcpServerStatusParams ) : Promise < ListMcpServerStatusResponse > {
248291 return await this . sendRequest ( { method : "mcpServerStatus/list" , params } ) ;
249292 }
@@ -296,6 +339,14 @@ export class CodexAppServerClient {
296339 } ) ;
297340 }
298341
342+ async awaitCompactionCompleted ( threadId : string ) : Promise < CompactionCompletedNotification > {
343+ return await new Promise ( ( resolve ) => {
344+ const resolvers = this . pendingCompactionCompletionResolvers . get ( threadId ) ?? new Set ( ) ;
345+ resolvers . add ( resolve ) ;
346+ this . pendingCompactionCompletionResolvers . set ( threadId , resolvers ) ;
347+ } ) ;
348+ }
349+
299350 resolveTurnInterrupted ( threadId : string , turnId : string ) : void {
300351 this . recordTurnCompleted ( {
301352 threadId,
@@ -368,6 +419,21 @@ export class CodexAppServerClient {
368419 }
369420 }
370421
422+ private recordCompactionCompleted ( event : CompactionCompletedNotification ) : void {
423+ const threadId = extractThreadId ( event ) ;
424+ if ( threadId === null ) {
425+ return ;
426+ }
427+ const resolvers = this . pendingCompactionCompletionResolvers . get ( threadId ) ;
428+ if ( ! resolvers ) {
429+ return ;
430+ }
431+ this . pendingCompactionCompletionResolvers . delete ( threadId ) ;
432+ for ( const resolve of resolvers ) {
433+ resolve ( event ) ;
434+ }
435+ }
436+
371437 private isStaleTurn ( threadId : string | null , turnId : string | null ) : boolean {
372438 if ( threadId === null || turnId === null ) {
373439 return false ;
@@ -481,6 +547,10 @@ export type CodexConnectionEvent =
481547 | ( { eventType : "response" } & unknown )
482548 | ( { eventType : "notification" } & ServerNotification ) ;
483549
550+ export type CompactionCompletedNotification =
551+ | { method : "thread/compacted" , params : Extract < ServerNotification , { method : "thread/compacted" } > [ "params" ] }
552+ | { method : "item/completed" , params : ItemCompletedNotification & { item : Extract < ItemCompletedNotification [ "item" ] , { type : "contextCompaction" } > } } ;
553+
484554type CodexRequest = DistributiveOmit < ClientRequest , "id" >
485555
486556type DistributiveOmit < T , K extends keyof any > = T extends any
@@ -513,6 +583,13 @@ function isTurnCompletedNotification(notification: ServerNotification): notifica
513583 return notification . method === "turn/completed" ;
514584}
515585
586+ function isCompactionCompletedNotification ( notification : ServerNotification ) : notification is CompactionCompletedNotification {
587+ if ( notification . method === "thread/compacted" ) {
588+ return true ;
589+ }
590+ return notification . method === "item/completed" && notification . params . item . type === "contextCompaction" ;
591+ }
592+
516593function extractThreadId ( notification : ServerNotification ) : string | null {
517594 const params = notification . params as { threadId ?: unknown } | undefined ;
518595 if ( params && typeof params . threadId === "string" ) {
0 commit comments