@@ -52,7 +52,6 @@ import {
5252} from "./app-server-client" ;
5353import { handleServerRequest } from "./approvals" ;
5454import {
55- type AccumulatedUsage ,
5655 buildSdkSessionParams ,
5756 buildTurnCompleteParams ,
5857 buildUsageBreakdownParams ,
@@ -162,6 +161,31 @@ function parseGoalCommand(prompt: PromptRequest["prompt"]): GoalCommand | null {
162161 }
163162}
164163
164+ function mergePromptUsage (
165+ left : PromptResponse [ "usage" ] ,
166+ right : PromptResponse [ "usage" ] ,
167+ ) : PromptResponse [ "usage" ] {
168+ if ( ! left ) return right ;
169+ if ( ! right ) return left ;
170+ return {
171+ inputTokens : left . inputTokens + right . inputTokens ,
172+ outputTokens : left . outputTokens + right . outputTokens ,
173+ cachedReadTokens :
174+ ( left . cachedReadTokens ?? 0 ) + ( right . cachedReadTokens ?? 0 ) ,
175+ cachedWriteTokens :
176+ ( left . cachedWriteTokens ?? 0 ) + ( right . cachedWriteTokens ?? 0 ) ,
177+ thoughtTokens : ( left . thoughtTokens ?? 0 ) + ( right . thoughtTokens ?? 0 ) ,
178+ totalTokens : left . totalTokens + right . totalTokens ,
179+ } ;
180+ }
181+
182+ function mergePromptResponses (
183+ left : PromptResponse ,
184+ right : PromptResponse ,
185+ ) : PromptResponse {
186+ return { ...right , usage : mergePromptUsage ( left . usage , right . usage ) } ;
187+ }
188+
165189// The native app-server owns its config; BaseAcpAgent only calls dispose() on this.
166190class NoopSettingsManager implements BaseSettingsManager {
167191 constructor ( private cwd : string ) { }
@@ -219,7 +243,7 @@ export class CodexAppServerAgent extends BaseAcpAgent {
219243 /** The in-flight turn's <proposed_plan>, streamed or completed (drives the implement handoff). */
220244 private planProposal ?: { itemId : string ; text : string } ;
221245 /** Idle signal deferred while the plan handoff keeps this prompt busy. */
222- private deferredTurnComplete ?: { usage : AccumulatedUsage } ;
246+ private deferredTurnComplete ?: { usage : PromptResponse [ "usage" ] } ;
223247 /** Settles the pending plan-approval race on cancel/close/preempting prompt. */
224248 private planHandoffCancel ?: ( ) => void ;
225249 private readonly mcp = new McpManager ( ) ;
@@ -727,15 +751,16 @@ export class CodexAppServerAgent extends BaseAcpAgent {
727751 return undefined ;
728752 } ) ;
729753 this . turns . onSteered ( steerRes ?. turnId ) ;
730- return { stopReason : await this . turns . awaitCompletion ( ) } ;
754+ const response = await this . turns . awaitCompletion ( ) ;
755+ return { stopReason : response . stopReason } ;
731756 }
732757 if ( this . turns . isPending ) {
733758 // A turn is pending but has no turnId yet, so we can't steer; fail fast.
734759 throw new Error ( "prompt() called while a turn is already in progress" ) ;
735760 }
736761
737- const stopReason = await this . runTurn ( input ) ;
738- return { stopReason : await this . maybeOfferPlanImplementation ( stopReason ) } ;
762+ const response = await this . runTurn ( input ) ;
763+ return this . maybeOfferPlanImplementation ( response ) ;
739764 }
740765
741766 private async handleGoalCommand ( command : GoalCommand ) : Promise < void > {
@@ -850,7 +875,7 @@ export class CodexAppServerAgent extends BaseAcpAgent {
850875 }
851876
852877 /** Start one codex turn and await its completion. */
853- private async runTurn ( input : CodexUserInput [ ] ) : Promise < StopReason > {
878+ private async runTurn ( input : CodexUserInput [ ] ) : Promise < PromptResponse > {
854879 this . lastAgentMessage = "" ;
855880 this . resetUsage ( ) ;
856881 this . planProposal = undefined ;
@@ -895,12 +920,12 @@ export class CodexAppServerAgent extends BaseAcpAgent {
895920 * back into another plan turn, whose revised plan prompts again.
896921 */
897922 private async maybeOfferPlanImplementation (
898- stopReason : StopReason ,
899- ) : Promise < StopReason > {
900- let reason = stopReason ;
923+ response : PromptResponse ,
924+ ) : Promise < PromptResponse > {
925+ let result = response ;
901926 try {
902927 while (
903- reason === "end_turn" &&
928+ result . stopReason === "end_turn" &&
904929 this . config . mode === "plan" &&
905930 this . planProposal &&
906931 ! this . session . cancelled
@@ -911,7 +936,7 @@ export class CodexAppServerAgent extends BaseAcpAgent {
911936 // Re-check after the await: a cancel that raced the response wins, so a
912937 // late accept can never start implementation on a cancelled prompt.
913938 if ( this . session . cancelled ) {
914- reason = "cancelled" ;
939+ result = { ... result , stopReason : "cancelled" } ;
915940 break ;
916941 }
917942 // A picker change while approval was open owns the mode. Never let a
@@ -921,19 +946,25 @@ export class CodexAppServerAgent extends BaseAcpAgent {
921946 this . config . setOption ( "mode" , outcome . mode ) ;
922947 this . emitCurrentMode ( outcome . mode ) ;
923948 this . emitConfigOptions ( ) ;
924- reason = await this . runFollowUpTurn ( IMPLEMENT_PLAN_MESSAGE ) ;
949+ result = mergePromptResponses (
950+ result ,
951+ await this . runFollowUpTurn ( IMPLEMENT_PLAN_MESSAGE ) ,
952+ ) ;
925953 break ;
926954 }
927955 if ( outcome . kind === "feedback" ) {
928- reason = await this . runFollowUpTurn ( outcome . feedback ) ;
956+ result = mergePromptResponses (
957+ result ,
958+ await this . runFollowUpTurn ( outcome . feedback ) ,
959+ ) ;
929960 continue ;
930961 }
931962 break ;
932963 }
933964 } finally {
934- await this . flushDeferredTurnComplete ( reason ) ;
965+ await this . flushDeferredTurnComplete ( result . stopReason ) ;
935966 }
936- return reason ;
967+ return result ;
937968 }
938969
939970 /**
@@ -949,7 +980,7 @@ export class CodexAppServerAgent extends BaseAcpAgent {
949980 }
950981
951982 /** Run an adapter-initiated turn, echoed as a user message like a host prompt. */
952- private async runFollowUpTurn ( text : string ) : Promise < StopReason > {
983+ private async runFollowUpTurn ( text : string ) : Promise < PromptResponse > {
953984 this . broadcastUserInput ( [ { type : "text" , text } ] ) ;
954985 return this . runTurn ( toCodexInput ( [ { type : "text" , text } ] ) ) ;
955986 }
@@ -1448,7 +1479,10 @@ export class CodexAppServerAgent extends BaseAcpAgent {
14481479 await this . emitTurnCompleteSignal ( reason , usage ) ;
14491480 await this . emitUsageBreakdown ( contextUsed ) ;
14501481 }
1451- pending . resolve ( reason ) ;
1482+ pending . resolve ( {
1483+ stopReason : reason ,
1484+ ...( usage ? { usage } : { } ) ,
1485+ } ) ;
14521486 }
14531487
14541488 /** Whether maybeOfferPlanImplementation will run for a turn that ended this way. */
@@ -1464,7 +1498,7 @@ export class CodexAppServerAgent extends BaseAcpAgent {
14641498 /** Emit the cloud idle signal `_posthog/turn_complete` (only with a taskRunId). */
14651499 private async emitTurnCompleteSignal (
14661500 reason : StopReason ,
1467- usage : AccumulatedUsage ,
1501+ usage : PromptResponse [ "usage" ] ,
14681502 ) : Promise < void > {
14691503 if ( ! this . sessionId || ! this . taskRunId ) return ;
14701504 await this . client
0 commit comments