@@ -21,11 +21,15 @@ import type {
2121 McpServerStatusUpdatedNotification ,
2222 ModelListParams ,
2323 ModelListResponse ,
24+ ReviewStartParams ,
25+ ReviewStartResponse ,
2426 SkillsExtraRootsSetParams ,
2527 SkillsListParams ,
2628 SkillsListResponse ,
2729 ThreadArchiveParams ,
2830 ThreadArchiveResponse ,
31+ ThreadCompactStartParams ,
32+ ThreadCompactStartResponse ,
2933 ThreadLoadedListParams ,
3034 ThreadLoadedListResponse ,
3135 ThreadListParams ,
@@ -47,6 +51,7 @@ import type {
4751 CommandExecutionRequestApprovalResponse ,
4852 FileChangeRequestApprovalParams ,
4953 FileChangeRequestApprovalResponse ,
54+ ItemCompletedNotification ,
5055} from "./app-server/v2" ;
5156
5257export interface ApprovalHandler {
@@ -99,6 +104,7 @@ export class CodexAppServerClient {
99104 private readonly mcpServerStartupStates = new Map < string , McpServerStartupSnapshot > ( ) ;
100105 private readonly mcpServerStartupResolvers : Array < McpServerStartupResolver > = [ ] ;
101106 private readonly pendingTurnCompletionResolvers = new Map < string , Map < string , ( event : TurnCompletedNotification ) => void > > ( ) ;
107+ private readonly pendingCompactionCompletionResolvers = new Map < string , Set < ( event : CompactionCompletedNotification ) => void > > ( ) ;
102108 private readonly turnCompletionCaptures = new Map < string , Set < ( event : TurnCompletedNotification ) => void > > ( ) ;
103109 private readonly staleTurnIds = new Map < string , Set < string > > ( ) ;
104110
@@ -118,6 +124,9 @@ export class CodexAppServerClient {
118124 if ( isTurnCompletedNotification ( serverNotification ) ) {
119125 this . recordTurnCompleted ( serverNotification . params ) ;
120126 }
127+ if ( isCompactionCompletedNotification ( serverNotification ) ) {
128+ this . recordCompactionCompleted ( serverNotification ) ;
129+ }
121130 const routing = extractTurnRouting ( serverNotification ) ;
122131 const staleTurnNotification = this . isStaleTurn ( routing . threadId , routing . turnId ) ;
123132 if ( staleTurnNotification ) {
@@ -213,10 +222,40 @@ export class CodexAppServerClient {
213222 }
214223 }
215224
225+ async runReview ( params : ReviewStartParams , onTurnStarted ?: ( turnId : string ) => void ) : Promise < TurnCompletedNotification > {
226+ const capturedCompletions : Array < TurnCompletedNotification > = [ ] ;
227+ const releaseCapture = this . captureTurnCompletions ( params . threadId , ( event ) => {
228+ capturedCompletions . push ( event ) ;
229+ } ) ;
230+
231+ try {
232+ const reviewStarted = await this . reviewStart ( params ) ;
233+ onTurnStarted ?.( reviewStarted . turn . id ) ;
234+ const earlyCompletion = capturedCompletions . find ( event => event . turn . id === reviewStarted . turn . id ) ;
235+ releaseCapture ( ) ;
236+ if ( earlyCompletion ) {
237+ return earlyCompletion ;
238+ }
239+ return await this . awaitTurnCompleted ( reviewStarted . reviewThreadId , reviewStarted . turn . id ) ;
240+ } finally {
241+ releaseCapture ( ) ;
242+ }
243+ }
244+
245+ async runCompact ( params : ThreadCompactStartParams ) : Promise < CompactionCompletedNotification > {
246+ const compactionCompleted = this . awaitCompactionCompleted ( params . threadId ) ;
247+ await this . threadCompactStart ( params ) ;
248+ return await compactionCompleted ;
249+ }
250+
216251 async turnInterrupt ( params : TurnInterruptParams ) : Promise < TurnInterruptResponse > {
217252 return await this . sendRequest ( { method : "turn/interrupt" , params : params } ) ;
218253 }
219254
255+ async reviewStart ( params : ReviewStartParams ) : Promise < ReviewStartResponse > {
256+ return await this . sendRequest ( { method : "review/start" , params : params } ) ;
257+ }
258+
220259 markTurnStale ( threadId : string , turnId : string ) : void {
221260 const threadStaleTurns = this . staleTurnIds . get ( threadId ) ?? new Set < string > ( ) ;
222261 threadStaleTurns . add ( turnId ) ;
@@ -251,6 +290,10 @@ export class CodexAppServerClient {
251290 return await this . sendRequest ( { method : "thread/unsubscribe" , params : params } ) ;
252291 }
253292
293+ async threadCompactStart ( params : ThreadCompactStartParams ) : Promise < ThreadCompactStartResponse > {
294+ return await this . sendRequest ( { method : "thread/compact/start" , params : params } ) ;
295+ }
296+
254297 async listMcpServerStatus ( params : ListMcpServerStatusParams ) : Promise < ListMcpServerStatusResponse > {
255298 return await this . sendRequest ( { method : "mcpServerStatus/list" , params } ) ;
256299 }
@@ -303,6 +346,14 @@ export class CodexAppServerClient {
303346 } ) ;
304347 }
305348
349+ async awaitCompactionCompleted ( threadId : string ) : Promise < CompactionCompletedNotification > {
350+ return await new Promise ( ( resolve ) => {
351+ const resolvers = this . pendingCompactionCompletionResolvers . get ( threadId ) ?? new Set ( ) ;
352+ resolvers . add ( resolve ) ;
353+ this . pendingCompactionCompletionResolvers . set ( threadId , resolvers ) ;
354+ } ) ;
355+ }
356+
306357 resolveTurnInterrupted ( threadId : string , turnId : string ) : void {
307358 this . recordTurnCompleted ( {
308359 threadId,
@@ -380,6 +431,21 @@ export class CodexAppServerClient {
380431 }
381432 }
382433
434+ private recordCompactionCompleted ( event : CompactionCompletedNotification ) : void {
435+ const threadId = extractThreadId ( event ) ;
436+ if ( threadId === null ) {
437+ return ;
438+ }
439+ const resolvers = this . pendingCompactionCompletionResolvers . get ( threadId ) ;
440+ if ( ! resolvers ) {
441+ return ;
442+ }
443+ this . pendingCompactionCompletionResolvers . delete ( threadId ) ;
444+ for ( const resolve of resolvers ) {
445+ resolve ( event ) ;
446+ }
447+ }
448+
383449 private isStaleTurn ( threadId : string | null , turnId : string | null ) : boolean {
384450 if ( threadId === null || turnId === null ) {
385451 return false ;
@@ -493,6 +559,10 @@ export type CodexConnectionEvent =
493559 | ( { eventType : "response" } & unknown )
494560 | ( { eventType : "notification" } & ServerNotification ) ;
495561
562+ export type CompactionCompletedNotification =
563+ | { method : "thread/compacted" , params : Extract < ServerNotification , { method : "thread/compacted" } > [ "params" ] }
564+ | { method : "item/completed" , params : ItemCompletedNotification & { item : Extract < ItemCompletedNotification [ "item" ] , { type : "contextCompaction" } > } } ;
565+
496566type CodexRequest = DistributiveOmit < ClientRequest , "id" >
497567
498568type DistributiveOmit < T , K extends keyof any > = T extends any
@@ -525,6 +595,13 @@ function isTurnCompletedNotification(notification: ServerNotification): notifica
525595 return notification . method === "turn/completed" ;
526596}
527597
598+ function isCompactionCompletedNotification ( notification : ServerNotification ) : notification is CompactionCompletedNotification {
599+ if ( notification . method === "thread/compacted" ) {
600+ return true ;
601+ }
602+ return notification . method === "item/completed" && notification . params . item . type === "contextCompaction" ;
603+ }
604+
528605function extractThreadId ( notification : ServerNotification ) : string | null {
529606 const params = notification . params as { threadId ?: unknown } | undefined ;
530607 if ( params && typeof params . threadId === "string" ) {
0 commit comments